apisonator 2.100.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +317 -0
- data/Gemfile +11 -0
- data/Gemfile.base +65 -0
- data/Gemfile.lock +319 -0
- data/Gemfile.on_prem +1 -0
- data/Gemfile.on_prem.lock +297 -0
- data/LICENSE +202 -0
- data/NOTICE +15 -0
- data/README.md +230 -0
- data/Rakefile +287 -0
- data/apisonator.gemspec +47 -0
- data/app/api/api.rb +13 -0
- data/app/api/internal/alert_limits.rb +32 -0
- data/app/api/internal/application_keys.rb +49 -0
- data/app/api/internal/application_referrer_filters.rb +43 -0
- data/app/api/internal/applications.rb +77 -0
- data/app/api/internal/errors.rb +54 -0
- data/app/api/internal/events.rb +42 -0
- data/app/api/internal/internal.rb +104 -0
- data/app/api/internal/metrics.rb +40 -0
- data/app/api/internal/service_tokens.rb +46 -0
- data/app/api/internal/services.rb +58 -0
- data/app/api/internal/stats.rb +42 -0
- data/app/api/internal/usagelimits.rb +62 -0
- data/app/api/internal/utilization.rb +23 -0
- data/bin/3scale_backend +223 -0
- data/bin/3scale_backend_worker +26 -0
- data/config.ru +4 -0
- data/config/puma.rb +192 -0
- data/config/schedule.rb +9 -0
- data/ext/mkrf_conf.rb +64 -0
- data/lib/3scale/backend.rb +67 -0
- data/lib/3scale/backend/alert_limit.rb +56 -0
- data/lib/3scale/backend/alerts.rb +137 -0
- data/lib/3scale/backend/analytics/kinesis.rb +3 -0
- data/lib/3scale/backend/analytics/kinesis/adapter.rb +180 -0
- data/lib/3scale/backend/analytics/kinesis/exporter.rb +86 -0
- data/lib/3scale/backend/analytics/kinesis/job.rb +135 -0
- data/lib/3scale/backend/analytics/redshift.rb +3 -0
- data/lib/3scale/backend/analytics/redshift/adapter.rb +367 -0
- data/lib/3scale/backend/analytics/redshift/importer.rb +83 -0
- data/lib/3scale/backend/analytics/redshift/job.rb +33 -0
- data/lib/3scale/backend/application.rb +330 -0
- data/lib/3scale/backend/application_events.rb +76 -0
- data/lib/3scale/backend/background_job.rb +65 -0
- data/lib/3scale/backend/configurable.rb +20 -0
- data/lib/3scale/backend/configuration.rb +151 -0
- data/lib/3scale/backend/configuration/loader.rb +42 -0
- data/lib/3scale/backend/constants.rb +19 -0
- data/lib/3scale/backend/cors.rb +84 -0
- data/lib/3scale/backend/distributed_lock.rb +67 -0
- data/lib/3scale/backend/environment.rb +21 -0
- data/lib/3scale/backend/error_storage.rb +52 -0
- data/lib/3scale/backend/errors.rb +343 -0
- data/lib/3scale/backend/event_storage.rb +120 -0
- data/lib/3scale/backend/experiment.rb +84 -0
- data/lib/3scale/backend/extensions.rb +5 -0
- data/lib/3scale/backend/extensions/array.rb +19 -0
- data/lib/3scale/backend/extensions/hash.rb +26 -0
- data/lib/3scale/backend/extensions/nil_class.rb +13 -0
- data/lib/3scale/backend/extensions/redis.rb +44 -0
- data/lib/3scale/backend/extensions/string.rb +13 -0
- data/lib/3scale/backend/extensions/time.rb +110 -0
- data/lib/3scale/backend/failed_jobs_scheduler.rb +141 -0
- data/lib/3scale/backend/job_fetcher.rb +122 -0
- data/lib/3scale/backend/listener.rb +728 -0
- data/lib/3scale/backend/listener_metrics.rb +99 -0
- data/lib/3scale/backend/logging.rb +48 -0
- data/lib/3scale/backend/logging/external.rb +44 -0
- data/lib/3scale/backend/logging/external/impl.rb +93 -0
- data/lib/3scale/backend/logging/external/impl/airbrake.rb +66 -0
- data/lib/3scale/backend/logging/external/impl/bugsnag.rb +69 -0
- data/lib/3scale/backend/logging/external/impl/default.rb +18 -0
- data/lib/3scale/backend/logging/external/resque.rb +57 -0
- data/lib/3scale/backend/logging/logger.rb +18 -0
- data/lib/3scale/backend/logging/middleware.rb +62 -0
- data/lib/3scale/backend/logging/middleware/json_writer.rb +21 -0
- data/lib/3scale/backend/logging/middleware/text_writer.rb +60 -0
- data/lib/3scale/backend/logging/middleware/writer.rb +143 -0
- data/lib/3scale/backend/logging/worker.rb +107 -0
- data/lib/3scale/backend/manifest.rb +80 -0
- data/lib/3scale/backend/memoizer.rb +277 -0
- data/lib/3scale/backend/metric.rb +275 -0
- data/lib/3scale/backend/metric/collection.rb +91 -0
- data/lib/3scale/backend/oauth.rb +4 -0
- data/lib/3scale/backend/oauth/token.rb +26 -0
- data/lib/3scale/backend/oauth/token_key.rb +30 -0
- data/lib/3scale/backend/oauth/token_storage.rb +313 -0
- data/lib/3scale/backend/oauth/token_value.rb +25 -0
- data/lib/3scale/backend/period.rb +3 -0
- data/lib/3scale/backend/period/boundary.rb +107 -0
- data/lib/3scale/backend/period/cache.rb +28 -0
- data/lib/3scale/backend/period/period.rb +402 -0
- data/lib/3scale/backend/queue_storage.rb +16 -0
- data/lib/3scale/backend/rack.rb +49 -0
- data/lib/3scale/backend/rack/exception_catcher.rb +136 -0
- data/lib/3scale/backend/rack/internal_error_catcher.rb +23 -0
- data/lib/3scale/backend/rack/prometheus.rb +19 -0
- data/lib/3scale/backend/saas.rb +6 -0
- data/lib/3scale/backend/saas_analytics.rb +4 -0
- data/lib/3scale/backend/server.rb +30 -0
- data/lib/3scale/backend/server/falcon.rb +52 -0
- data/lib/3scale/backend/server/puma.rb +71 -0
- data/lib/3scale/backend/service.rb +317 -0
- data/lib/3scale/backend/service_token.rb +97 -0
- data/lib/3scale/backend/stats.rb +8 -0
- data/lib/3scale/backend/stats/aggregator.rb +170 -0
- data/lib/3scale/backend/stats/aggregators/base.rb +72 -0
- data/lib/3scale/backend/stats/aggregators/response_code.rb +58 -0
- data/lib/3scale/backend/stats/aggregators/usage.rb +34 -0
- data/lib/3scale/backend/stats/bucket_reader.rb +135 -0
- data/lib/3scale/backend/stats/bucket_storage.rb +108 -0
- data/lib/3scale/backend/stats/cleaner.rb +195 -0
- data/lib/3scale/backend/stats/codes_commons.rb +14 -0
- data/lib/3scale/backend/stats/delete_job_def.rb +60 -0
- data/lib/3scale/backend/stats/key_generator.rb +73 -0
- data/lib/3scale/backend/stats/keys.rb +104 -0
- data/lib/3scale/backend/stats/partition_eraser_job.rb +58 -0
- data/lib/3scale/backend/stats/partition_generator_job.rb +46 -0
- data/lib/3scale/backend/stats/period_commons.rb +34 -0
- data/lib/3scale/backend/stats/stats_parser.rb +141 -0
- data/lib/3scale/backend/stats/storage.rb +113 -0
- data/lib/3scale/backend/statsd.rb +14 -0
- data/lib/3scale/backend/storable.rb +35 -0
- data/lib/3scale/backend/storage.rb +40 -0
- data/lib/3scale/backend/storage_async.rb +4 -0
- data/lib/3scale/backend/storage_async/async_redis.rb +21 -0
- data/lib/3scale/backend/storage_async/client.rb +205 -0
- data/lib/3scale/backend/storage_async/pipeline.rb +79 -0
- data/lib/3scale/backend/storage_async/resque_extensions.rb +30 -0
- data/lib/3scale/backend/storage_helpers.rb +278 -0
- data/lib/3scale/backend/storage_key_helpers.rb +9 -0
- data/lib/3scale/backend/storage_sync.rb +43 -0
- data/lib/3scale/backend/transaction.rb +62 -0
- data/lib/3scale/backend/transactor.rb +177 -0
- data/lib/3scale/backend/transactor/limit_headers.rb +54 -0
- data/lib/3scale/backend/transactor/notify_batcher.rb +139 -0
- data/lib/3scale/backend/transactor/notify_job.rb +47 -0
- data/lib/3scale/backend/transactor/process_job.rb +33 -0
- data/lib/3scale/backend/transactor/report_job.rb +84 -0
- data/lib/3scale/backend/transactor/status.rb +236 -0
- data/lib/3scale/backend/transactor/usage_report.rb +182 -0
- data/lib/3scale/backend/usage.rb +63 -0
- data/lib/3scale/backend/usage_limit.rb +115 -0
- data/lib/3scale/backend/use_cases/provider_key_change_use_case.rb +60 -0
- data/lib/3scale/backend/util.rb +17 -0
- data/lib/3scale/backend/validators.rb +26 -0
- data/lib/3scale/backend/validators/base.rb +36 -0
- data/lib/3scale/backend/validators/key.rb +17 -0
- data/lib/3scale/backend/validators/limits.rb +57 -0
- data/lib/3scale/backend/validators/oauth_key.rb +15 -0
- data/lib/3scale/backend/validators/oauth_setting.rb +15 -0
- data/lib/3scale/backend/validators/redirect_uri.rb +33 -0
- data/lib/3scale/backend/validators/referrer.rb +60 -0
- data/lib/3scale/backend/validators/service_state.rb +15 -0
- data/lib/3scale/backend/validators/state.rb +15 -0
- data/lib/3scale/backend/version.rb +5 -0
- data/lib/3scale/backend/views/oauth_access_tokens.builder +14 -0
- data/lib/3scale/backend/views/oauth_app_id_by_token.builder +4 -0
- data/lib/3scale/backend/worker.rb +87 -0
- data/lib/3scale/backend/worker_async.rb +88 -0
- data/lib/3scale/backend/worker_metrics.rb +44 -0
- data/lib/3scale/backend/worker_sync.rb +32 -0
- data/lib/3scale/bundler_shim.rb +17 -0
- data/lib/3scale/prometheus_server.rb +10 -0
- data/lib/3scale/tasks/connectivity.rake +41 -0
- data/lib/3scale/tasks/helpers.rb +3 -0
- data/lib/3scale/tasks/helpers/environment.rb +23 -0
- data/lib/3scale/tasks/stats.rake +131 -0
- data/lib/3scale/tasks/swagger.rake +46 -0
- data/licenses.xml +1215 -0
- metadata +227 -0
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
raise "Memoizer is not thread safe" if ThreeScale::Backend::Manifest.thread_safe?
|
|
2
|
+
|
|
3
|
+
module ThreeScale
|
|
4
|
+
module Backend
|
|
5
|
+
class Memoizer
|
|
6
|
+
EXPIRE = 60
|
|
7
|
+
PURGE = 60
|
|
8
|
+
MAX_ENTRIES = 10000
|
|
9
|
+
ACTIVE = true
|
|
10
|
+
private_constant :EXPIRE, :PURGE, :MAX_ENTRIES, :ACTIVE
|
|
11
|
+
|
|
12
|
+
def self.reset!
|
|
13
|
+
# Initialize class instance variables
|
|
14
|
+
# Note: we would be better off pre-allocating the Hash size
|
|
15
|
+
# ie. rb_hash_new_with_size(MAX_ENTRIES)
|
|
16
|
+
@memoizer_cache = Hash.new
|
|
17
|
+
@memoizer_purge_time = Time.now.getutc.to_i + PURGE
|
|
18
|
+
@memoizer_stats_count = 0
|
|
19
|
+
@memoizer_stats_hits = 0
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
reset!
|
|
23
|
+
|
|
24
|
+
# Key management:
|
|
25
|
+
#
|
|
26
|
+
# When automatically memoizing using the decorator, you should
|
|
27
|
+
# NEVER assume a specific key format. Here are some helpers to
|
|
28
|
+
# let you build keys to clear or getting them.
|
|
29
|
+
#
|
|
30
|
+
# You should only use build_keys_for_class and build_key
|
|
31
|
+
#
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def self.build_class_key(klass)
|
|
36
|
+
classkey = klass.to_s
|
|
37
|
+
# This can receive both objects and classes, so check if we can
|
|
38
|
+
# call singleton_class? before actually doing so.
|
|
39
|
+
if klass.respond_to? :singleton_class? and klass.singleton_class?
|
|
40
|
+
# obtain class from Ruby's metaclass notation
|
|
41
|
+
classkey.split(':'.freeze).delete_if do |k|
|
|
42
|
+
k[0] == '#'.freeze
|
|
43
|
+
end.join(':'.freeze).split('>'.freeze).first
|
|
44
|
+
else
|
|
45
|
+
classkey
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def self.build_method_key(classkey, methodname)
|
|
50
|
+
classkey + '.'.freeze + methodname
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def self.build_args_key(methodkey, *args)
|
|
54
|
+
if args.empty?
|
|
55
|
+
methodkey
|
|
56
|
+
else
|
|
57
|
+
methodkey + '-'.freeze + args.join('-'.freeze)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# A method to inspect in debugging mode the contents of the cache
|
|
62
|
+
def self.cache
|
|
63
|
+
raise 'Memoizer.cache only in development!' unless ThreeScale::Backend.development?
|
|
64
|
+
@memoizer_cache
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
public
|
|
68
|
+
|
|
69
|
+
# Generate a key for the given class, method and args
|
|
70
|
+
def self.build_key(klass, method, *args)
|
|
71
|
+
key = build_class_key klass
|
|
72
|
+
key = build_method_key key, method.to_s
|
|
73
|
+
build_args_key key, *args
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Pass in the class or object that receives the memoized
|
|
77
|
+
# methods, and a hash containing the methods as keys and
|
|
78
|
+
# an array of their arguments in order.
|
|
79
|
+
def self.build_keys_for_class(klass, methods_n_args)
|
|
80
|
+
classkey = build_class_key klass
|
|
81
|
+
methods_n_args.map do |method, args|
|
|
82
|
+
key = build_method_key(classkey, method.to_s)
|
|
83
|
+
build_args_key key, *args
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
if ACTIVE
|
|
88
|
+
Entry = Struct.new(:obj, :expire)
|
|
89
|
+
private_constant :Entry
|
|
90
|
+
|
|
91
|
+
def self.fetch(key)
|
|
92
|
+
@memoizer_stats_count = @memoizer_stats_count + 1
|
|
93
|
+
|
|
94
|
+
cached = @memoizer_cache[key]
|
|
95
|
+
|
|
96
|
+
now = Time.now.getutc.to_i
|
|
97
|
+
purge(now) if now > @memoizer_purge_time
|
|
98
|
+
|
|
99
|
+
if cached && now <= cached.expire
|
|
100
|
+
@memoizer_stats_hits = @memoizer_stats_hits + 1
|
|
101
|
+
cached
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def self.memoize(key, obj)
|
|
106
|
+
@memoizer_cache[key] = Entry.new(obj, Time.now.getutc.to_i + EXPIRE)
|
|
107
|
+
obj
|
|
108
|
+
end
|
|
109
|
+
else
|
|
110
|
+
def self.fetch(_key)
|
|
111
|
+
nil
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def self.memoize(_key, obj)
|
|
115
|
+
obj
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def self.get(key)
|
|
120
|
+
entry = @memoizer_cache[key]
|
|
121
|
+
entry.obj if entry
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def self.memoized?(key)
|
|
125
|
+
!!(fetch key)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def self.clear(keys)
|
|
129
|
+
Array(keys).each do |key|
|
|
130
|
+
@memoizer_cache.delete key
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def self.purge(time)
|
|
135
|
+
@memoizer_purge_time = time + PURGE
|
|
136
|
+
|
|
137
|
+
@memoizer_cache.delete_if do |_, entry|
|
|
138
|
+
time > entry.expire
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
##safety, should never reach this unless massive concurrency
|
|
142
|
+
reset! if @memoizer_cache.size > MAX_ENTRIES
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def self.stats
|
|
146
|
+
{
|
|
147
|
+
size: @memoizer_cache.size,
|
|
148
|
+
count: @memoizer_stats_count,
|
|
149
|
+
hits: @memoizer_stats_hits,
|
|
150
|
+
}
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def self.memoize_block(key, &block)
|
|
154
|
+
entry = fetch key
|
|
155
|
+
if entry.nil?
|
|
156
|
+
Memoizer.memoize(key, yield)
|
|
157
|
+
else
|
|
158
|
+
entry.obj
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Decorator allows a class or module to include it and get
|
|
163
|
+
# memoize :method1, :method2, ...
|
|
164
|
+
# using keys "#{classname}.#{methodname}-#{arg1}-#{arg2}-..."
|
|
165
|
+
module Decorator
|
|
166
|
+
def self.included(base)
|
|
167
|
+
base.extend(ClassMethods)
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
module ClassMethods
|
|
171
|
+
module Helpers
|
|
172
|
+
module_function
|
|
173
|
+
|
|
174
|
+
def memoize_instance_method(m, klass)
|
|
175
|
+
method_name = m.name
|
|
176
|
+
method_s = method_name.to_s
|
|
177
|
+
klass.send :define_method, method_name do |*args|
|
|
178
|
+
key = Memoizer.build_method_key self.to_s, method_s
|
|
179
|
+
key = Memoizer.build_args_key key, *args
|
|
180
|
+
Memoizer.memoize_block(key) do
|
|
181
|
+
m.bind(self).call(*args)
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
private :memoize_instance_method
|
|
186
|
+
|
|
187
|
+
def memoize_class_method(m, partialkey, klass)
|
|
188
|
+
klass.define_singleton_method(m.name) do |*args|
|
|
189
|
+
key = Memoizer.build_args_key partialkey, *args
|
|
190
|
+
Memoizer.memoize_block(key) do
|
|
191
|
+
m.call(*args)
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
private :memoize_class_method
|
|
196
|
+
|
|
197
|
+
# helper to go down one level from the current class context
|
|
198
|
+
# ie. the reverse of singleton_class: from metaclass to class
|
|
199
|
+
def get_instance_class(klass)
|
|
200
|
+
return klass unless klass.singleton_class?
|
|
201
|
+
# workaround Ruby's lack of the inverse of singleton_class
|
|
202
|
+
base_s = klass.to_s.split(':').delete_if { |k| k.start_with? '#' }.
|
|
203
|
+
join(':').split('>').first
|
|
204
|
+
iklass = Kernel.const_get(base_s)
|
|
205
|
+
# got the root class, now go up a level shy of self
|
|
206
|
+
iklass = iklass.singleton_class while iklass.singleton_class != klass
|
|
207
|
+
iklass
|
|
208
|
+
end
|
|
209
|
+
private :get_instance_class
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
# memoize :method, :other_method, ...
|
|
213
|
+
#
|
|
214
|
+
# Decorate the methods passed in memoizing their results based
|
|
215
|
+
# on the parameters they receive using a key with the form:
|
|
216
|
+
# (ClassName|Instance).methodname[-param1[-param2[-...]]]
|
|
217
|
+
#
|
|
218
|
+
# You can call this from a class on instance or class methods, and
|
|
219
|
+
# from a metaclass on instance methods, which actually are class
|
|
220
|
+
# methods.
|
|
221
|
+
#
|
|
222
|
+
# CAVEAT: if you have an instance method named exactly as an existing
|
|
223
|
+
# class method you either memoize the instance method BEFORE defining
|
|
224
|
+
# the class method or use memoize_i on the instance method.
|
|
225
|
+
#
|
|
226
|
+
# WARNING: do NOT use this memoize method on frequently called instance
|
|
227
|
+
# methods since you'll have a noticeable overhead from the necessity
|
|
228
|
+
# to bind the method to the object.
|
|
229
|
+
#
|
|
230
|
+
def memoize(*methods)
|
|
231
|
+
classkey = Memoizer.build_class_key self
|
|
232
|
+
# get the base class of self so that we get rid of metaclasses
|
|
233
|
+
klass = Helpers.get_instance_class self
|
|
234
|
+
# make sure klass points to the klass that self is a metaclass of
|
|
235
|
+
# in case we're being invoked from a metaclass
|
|
236
|
+
methods.each do |m|
|
|
237
|
+
# For each method, first search for a class method, which is the
|
|
238
|
+
# common case. If not found, then look for an instance method.
|
|
239
|
+
#
|
|
240
|
+
begin
|
|
241
|
+
key = Memoizer.build_method_key(classkey, m.to_s)
|
|
242
|
+
original_method = klass.method m
|
|
243
|
+
raise NameError unless original_method.owner == klass.singleton_class
|
|
244
|
+
Helpers.memoize_class_method original_method, key, klass
|
|
245
|
+
rescue NameError
|
|
246
|
+
# If we cannot find a class method, try an instance method
|
|
247
|
+
# before bailing out.
|
|
248
|
+
memoize_i m
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
# memoize_i :method, :other_method
|
|
254
|
+
#
|
|
255
|
+
# Forces memoization of methods which are instance methods in
|
|
256
|
+
# the current context level. This can be used to, for example,
|
|
257
|
+
# override the default look up when we have two methods, class
|
|
258
|
+
# and instance, which are named the same and we want to memoize
|
|
259
|
+
# both or only the instance level one.
|
|
260
|
+
def memoize_i(*methods)
|
|
261
|
+
methods.each do |m|
|
|
262
|
+
# We don't support calling this from a metaclass, because
|
|
263
|
+
# we have not built a correct key. The user should use memoize
|
|
264
|
+
# instead of this, which will already work with the instance
|
|
265
|
+
# methods within a metaclass, that is, the class' class methods.
|
|
266
|
+
raise NameError if singleton_class?
|
|
267
|
+
original_method = instance_method m
|
|
268
|
+
raise NameError unless original_method.owner == self
|
|
269
|
+
Helpers.memoize_instance_method original_method, self
|
|
270
|
+
end
|
|
271
|
+
end
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
end
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
require 'set'
|
|
2
|
+
|
|
3
|
+
module ThreeScale
|
|
4
|
+
module Backend
|
|
5
|
+
class Metric
|
|
6
|
+
module KeyHelpers
|
|
7
|
+
def key(service_id, id, attribute)
|
|
8
|
+
encode_key("metric/service_id:#{service_id}/id:#{id}/#{attribute}")
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def id_key(service_id, name)
|
|
12
|
+
encode_key("metric/service_id:#{service_id}/name:#{name}/id")
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def id_set_key(service_id)
|
|
16
|
+
encode_key("metrics/service_id:#{service_id}/ids")
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
include KeyHelpers
|
|
21
|
+
extend KeyHelpers
|
|
22
|
+
|
|
23
|
+
include Storable
|
|
24
|
+
|
|
25
|
+
attr_accessor :service_id, :id, :parent_id, :name
|
|
26
|
+
attr_writer :children
|
|
27
|
+
|
|
28
|
+
def save
|
|
29
|
+
old_name = self.class.load_name(service_id, id)
|
|
30
|
+
storage.pipelined do
|
|
31
|
+
save_attributes
|
|
32
|
+
save_to_list
|
|
33
|
+
remove_reverse_mapping(service_id, old_name) if old_name != name
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# can't include this in the pipeline since it is a potentially
|
|
37
|
+
# large number of commands.
|
|
38
|
+
save_children
|
|
39
|
+
|
|
40
|
+
self.class.clear_cache(service_id, id, name)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def children
|
|
44
|
+
@children ||= []
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def to_hash
|
|
48
|
+
{
|
|
49
|
+
service_id: service_id,
|
|
50
|
+
id: id,
|
|
51
|
+
parent_id: parent_id,
|
|
52
|
+
name: name
|
|
53
|
+
}
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def update(attributes)
|
|
57
|
+
attributes.each do |attr, val|
|
|
58
|
+
public_send("#{attr}=", val)
|
|
59
|
+
end
|
|
60
|
+
self
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
class << self
|
|
64
|
+
include Memoizer::Decorator
|
|
65
|
+
|
|
66
|
+
def attribute_names
|
|
67
|
+
%i[service_id id parent_id name children].freeze
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def load(service_id, id)
|
|
71
|
+
name, parent_id = storage.mget(key(service_id, id, :name),
|
|
72
|
+
key(service_id, id, :parent_id))
|
|
73
|
+
|
|
74
|
+
name && new(id: id.to_s,
|
|
75
|
+
service_id: service_id.to_s,
|
|
76
|
+
name: name,
|
|
77
|
+
parent_id: parent_id)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def load_all(service_id)
|
|
81
|
+
Collection.new(service_id)
|
|
82
|
+
end
|
|
83
|
+
memoize :load_all
|
|
84
|
+
|
|
85
|
+
def load_id(service_id, name)
|
|
86
|
+
storage.get(id_key(service_id, name))
|
|
87
|
+
end
|
|
88
|
+
memoize :load_id
|
|
89
|
+
|
|
90
|
+
def load_all_ids(service_id)
|
|
91
|
+
# smembers is guaranteed to return an array of strings, even if empty
|
|
92
|
+
storage.smembers(id_set_key(service_id))
|
|
93
|
+
end
|
|
94
|
+
memoize :load_all_ids
|
|
95
|
+
|
|
96
|
+
def load_name(service_id, id)
|
|
97
|
+
storage.get(key(service_id, id, :name))
|
|
98
|
+
end
|
|
99
|
+
memoize :load_name
|
|
100
|
+
|
|
101
|
+
def load_all_names(service_id, ids)
|
|
102
|
+
if ids.nil? || ids.empty?
|
|
103
|
+
{}
|
|
104
|
+
else
|
|
105
|
+
name_keys = ids.map { |id| key(service_id, id, :name) }
|
|
106
|
+
Hash[ids.zip(storage.mget(name_keys))]
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
memoize :load_all_names
|
|
110
|
+
|
|
111
|
+
def load_parent_id(service_id, id)
|
|
112
|
+
storage.get(key(service_id, id, :parent_id))
|
|
113
|
+
end
|
|
114
|
+
memoize :load_parent_id
|
|
115
|
+
|
|
116
|
+
def save(attributes)
|
|
117
|
+
metrics = new(attributes)
|
|
118
|
+
metrics.save
|
|
119
|
+
metrics
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Returns a hash where the keys can only be parent metric ids (as
|
|
123
|
+
# Strings) and their values are arrays of children.
|
|
124
|
+
#
|
|
125
|
+
# The as_names optional parameter (default: true) returns metric names
|
|
126
|
+
# instead of metric ids when true.
|
|
127
|
+
def hierarchy(service_id, as_names = true)
|
|
128
|
+
h_ids = hierarchy_ids(service_id)
|
|
129
|
+
return h_ids unless as_names
|
|
130
|
+
|
|
131
|
+
metric_ids = Set.new(h_ids.keys + h_ids.values.flatten)
|
|
132
|
+
return {} if metric_ids.empty?
|
|
133
|
+
|
|
134
|
+
res = {}
|
|
135
|
+
metric_names = load_all_names(service_id, metric_ids)
|
|
136
|
+
|
|
137
|
+
h_ids.each do |m_id, c_ids|
|
|
138
|
+
m_name = metric_names[m_id]
|
|
139
|
+
res[m_name] = c_ids.map do |c_id|
|
|
140
|
+
metric_names[c_id]
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
res
|
|
145
|
+
end
|
|
146
|
+
memoize :hierarchy
|
|
147
|
+
|
|
148
|
+
def children(service_id, id)
|
|
149
|
+
hierarchy(service_id, false)[id.to_s]
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Returns the "descendants" of a metric, that is, its children,
|
|
153
|
+
# grandchildren, etc. in the metric hierarchy of the given service.
|
|
154
|
+
# In other words, the "descendants" of a metric are its children plus
|
|
155
|
+
# the descendants of each of them.
|
|
156
|
+
def descendants(service_id, metric_name)
|
|
157
|
+
metrics_hierarchy = hierarchy(service_id)
|
|
158
|
+
children = metrics_hierarchy[metric_name] || []
|
|
159
|
+
|
|
160
|
+
children.reduce(children) do |acc, child|
|
|
161
|
+
acc + descendants(service_id, child)
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
memoize :descendants
|
|
165
|
+
|
|
166
|
+
# Returns the "ascendants" of a metric, that is, its parent,
|
|
167
|
+
# grandparent, etc. The "ascendants" of a metric are its parent plus
|
|
168
|
+
# the ascendants of the parent.
|
|
169
|
+
def ascendants(service_id, metric_name)
|
|
170
|
+
parents_of_metric = parents(service_id, [metric_name])
|
|
171
|
+
|
|
172
|
+
parents_of_metric.reduce(parents_of_metric) do |acc, parent|
|
|
173
|
+
acc + ascendants(service_id, parent)
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
memoize :ascendants
|
|
177
|
+
|
|
178
|
+
# Given an array of metrics, returns an array without duplicates that
|
|
179
|
+
# includes the names of the metrics that are parent of at least one of
|
|
180
|
+
# the given metrics.
|
|
181
|
+
def parents(service_id, metric_names)
|
|
182
|
+
parents = []
|
|
183
|
+
|
|
184
|
+
metric_names.each do |name|
|
|
185
|
+
parent_id = load_parent_id service_id, load_id(service_id, name)
|
|
186
|
+
if parent_id
|
|
187
|
+
parents << load_name(service_id, parent_id)
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
parents.uniq
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def delete(service_id, id)
|
|
195
|
+
name = load_name(service_id, id)
|
|
196
|
+
return false unless name and not name.empty?
|
|
197
|
+
clear_cache(service_id, id, name)
|
|
198
|
+
|
|
199
|
+
storage.pipelined do
|
|
200
|
+
storage.srem(id_set_key(service_id), id)
|
|
201
|
+
|
|
202
|
+
storage.del(key(service_id, id, :name),
|
|
203
|
+
key(service_id, id, :parent_id),
|
|
204
|
+
id_key(service_id, name))
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
true
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
def clear_cache(service_id, id, name)
|
|
211
|
+
metric_ids = load_all_ids(service_id)
|
|
212
|
+
Memoizer.clear(Memoizer.build_keys_for_class(self,
|
|
213
|
+
load_all: [service_id],
|
|
214
|
+
load_all_names: [service_id, metric_ids],
|
|
215
|
+
load_name: [service_id, id],
|
|
216
|
+
load_id: [service_id, name],
|
|
217
|
+
load_all_ids: [service_id]))
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
private
|
|
221
|
+
|
|
222
|
+
def hierarchy_ids(service_id)
|
|
223
|
+
ids = load_all_ids(service_id)
|
|
224
|
+
parent_ids_keys = ids.map { |id| key(service_id, id, :parent_id) }
|
|
225
|
+
|
|
226
|
+
parent_ids = storage.pipelined do
|
|
227
|
+
parent_ids_keys.each_slice(PIPELINED_SLICE_SIZE).map do |slice|
|
|
228
|
+
storage.mget(slice)
|
|
229
|
+
end
|
|
230
|
+
end.flatten
|
|
231
|
+
|
|
232
|
+
parent_child_rels = parent_ids.zip(ids)
|
|
233
|
+
|
|
234
|
+
parent_child_rels.inject({}) do |acc, (parent_id, child_id)|
|
|
235
|
+
if parent_id # nil if child_id has no parent
|
|
236
|
+
acc[parent_id] ||= []
|
|
237
|
+
acc[parent_id] << child_id
|
|
238
|
+
end
|
|
239
|
+
acc
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
private
|
|
245
|
+
|
|
246
|
+
def remove_reverse_mapping(service_id, name)
|
|
247
|
+
storage.del id_key(service_id, name)
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
def save_attributes
|
|
251
|
+
storage.set(id_key(service_id, name), id)
|
|
252
|
+
storage.set(key(service_id, id, :name), name)
|
|
253
|
+
storage.set(key(service_id, id, :parent_id), parent_id) if parent_id
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
def save_to_list
|
|
257
|
+
storage.sadd(id_set_key(service_id), id)
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
def save_children
|
|
261
|
+
children.each do |child|
|
|
262
|
+
child.service_id = service_id
|
|
263
|
+
child.parent_id = id
|
|
264
|
+
child.save
|
|
265
|
+
end
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
end
|
|
269
|
+
end
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
# this is required by our code, but it is nested inside class Metric
|
|
273
|
+
# require'ing it here ensures we always reopen the class instead of
|
|
274
|
+
# defining it.
|
|
275
|
+
require '3scale/backend/metric/collection'
|