ezmetrics 2.0.1 → 3.0.2

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.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/.github/FUNDING.yml +1 -0
  3. data/.gitignore +3 -0
  4. data/.rspec +2 -0
  5. data/CHANGELOG.md +21 -0
  6. data/Gemfile +6 -0
  7. data/Gemfile.lock +41 -0
  8. data/LICENSE +21 -674
  9. data/README.md +68 -143
  10. data/_config.yml +1 -0
  11. data/ezmetrics.gemspec +21 -0
  12. data/lib/ezmetrics.rb +7 -281
  13. data/lib/ezmetrics/benchmark.rb +2 -2
  14. data/lib/ezmetrics/dashboard/app/assets/config/dashboard_manifest.js +2 -0
  15. data/lib/ezmetrics/dashboard/app/assets/images/dashboard/.keep +0 -0
  16. data/lib/ezmetrics/dashboard/app/assets/javascripts/dashboard/application.js +15 -0
  17. data/lib/ezmetrics/dashboard/app/assets/javascripts/dashboard/main.js +1 -0
  18. data/lib/ezmetrics/dashboard/app/assets/stylesheets/dashboard/application.css +16 -0
  19. data/lib/ezmetrics/dashboard/app/assets/stylesheets/dashboard/main.css +6 -0
  20. data/lib/ezmetrics/dashboard/app/controllers/dashboard/application_controller.rb +5 -0
  21. data/lib/ezmetrics/dashboard/app/controllers/dashboard/metrics_controller.rb +41 -0
  22. data/lib/ezmetrics/dashboard/app/views/dashboard/metrics/index.html.erb +3 -0
  23. data/lib/ezmetrics/dashboard/app/views/layouts/dashboard/application.html.erb +12 -0
  24. data/lib/ezmetrics/dashboard/bin/rails +14 -0
  25. data/lib/ezmetrics/dashboard/config/routes.rb +5 -0
  26. data/lib/ezmetrics/dashboard/lib/dashboard.rb +4 -0
  27. data/lib/ezmetrics/dashboard/lib/dashboard/ezmetrics.rb +6 -0
  28. data/lib/ezmetrics/dashboard/lib/dashboard/version.rb +3 -0
  29. data/lib/ezmetrics/dashboard/react-dashboard/.gitignore +23 -0
  30. data/lib/ezmetrics/dashboard/react-dashboard/.rescriptsrc.js +9 -0
  31. data/lib/ezmetrics/dashboard/react-dashboard/README.md +0 -0
  32. data/lib/ezmetrics/dashboard/react-dashboard/package-lock.json +15472 -0
  33. data/lib/ezmetrics/dashboard/react-dashboard/package.json +49 -0
  34. data/lib/ezmetrics/dashboard/react-dashboard/public/index.html +18 -0
  35. data/lib/ezmetrics/dashboard/react-dashboard/public/manifest.json +10 -0
  36. data/lib/ezmetrics/dashboard/react-dashboard/public/robots.txt +2 -0
  37. data/lib/ezmetrics/dashboard/react-dashboard/src/App.tsx +216 -0
  38. data/lib/ezmetrics/dashboard/react-dashboard/src/Constants.tsx +74 -0
  39. data/lib/ezmetrics/dashboard/react-dashboard/src/Graph.tsx +71 -0
  40. data/lib/ezmetrics/dashboard/react-dashboard/src/Metric.tsx +21 -0
  41. data/lib/ezmetrics/dashboard/react-dashboard/src/MetricsBlock.tsx +22 -0
  42. data/lib/ezmetrics/dashboard/react-dashboard/src/Nav.tsx +105 -0
  43. data/lib/ezmetrics/dashboard/react-dashboard/src/RequestsBlock.tsx +78 -0
  44. data/lib/ezmetrics/dashboard/react-dashboard/src/index.css +82 -0
  45. data/lib/ezmetrics/dashboard/react-dashboard/src/index.tsx +9 -0
  46. data/lib/ezmetrics/dashboard/react-dashboard/src/react-app-env.d.ts +1 -0
  47. data/lib/ezmetrics/dashboard/react-dashboard/src/setupTests.ts +5 -0
  48. data/lib/ezmetrics/dashboard/react-dashboard/tsconfig.json +25 -0
  49. data/lib/ezmetrics/dashboard/react-dashboard/yarn.lock +11651 -0
  50. data/lib/ezmetrics/storage.rb +289 -0
  51. data/lib/ezmetrics/version.rb +3 -0
  52. data/lib/generators/ezmetrics/initializer_generator.rb +39 -0
  53. data/scripts/compile_assets +20 -0
  54. metadata +51 -3
data/README.md CHANGED
@@ -12,101 +12,41 @@ gem 'ezmetrics'
12
12
 
13
13
  ## Available metrics
14
14
 
15
- | Type | Aggregate functions |
16
- |:----------:|:---------------------------------:|
17
- | `duration` | `avg`, `max`, `percentile` |
18
- | `views` | `avg`, `max`, `percentile` |
19
- | `db` | `avg`, `max`, `percentile` |
20
- | `queries` | `avg`, `max`, `percentile` |
15
+ | Type | Aggregate functions |
16
+ | :--------: | :-------------------------------: |
17
+ | `duration` | `avg`, `max`, `percentile` |
18
+ | `views` | `avg`, `max`, `percentile` |
19
+ | `db` | `avg`, `max`, `percentile` |
20
+ | `queries` | `avg`, `max`, `percentile` |
21
21
  | `requests` | `all`, `2xx`, `3xx`, `4xx`, `5xx` |
22
22
 
23
23
  ## Usage
24
24
 
25
- ### Getting started
26
25
 
27
- This tool captures and aggregates Rails application metrics such as
28
-
29
- - `duration`
30
- - `views`
31
- - `db`
32
- - `queries`
33
- - `status`
34
-
35
- and stores them for the timeframe you specified, 60 seconds by default.
36
-
37
- You can change the timeframe according to your needs and save the metrics by calling `log` method:
26
+ ### Capture metrics
38
27
 
39
- ```ruby
40
- # Store the metrics for 60 seconds (default behaviour)
41
- EZmetrics.new.log(
42
- duration: 100.5,
43
- views: 40.7,
44
- db: 59.8,
45
- queries: 4,
46
- status: 200
47
- )
48
28
  ```
49
-
50
- ```ruby
51
- # Store the metrics for 10 minutes
52
- EZmetrics.new(10.minutes).log(
53
- duration: 100.5,
54
- views: 40.7,
55
- db: 59.8,
56
- queries: 4,
57
- status: 200
58
- )
29
+ rails generate ezmetrics:initializer
59
30
  ```
60
31
 
61
- ---
32
+ ### Display metrics
62
33
 
63
- For displaying metrics you need to call `show` method:
34
+ #### 1. Dashboard
64
35
 
65
36
  ```ruby
66
- # Aggregate and show metrics for last 60 seconds (default behaviour)
67
- EZmetrics.new.show
68
- ```
37
+ # add the following line to 'config/routes.rb'
69
38
 
70
- ```ruby
71
- # Aggregate and show metrics for last 10 minutes
72
- EZmetrics.new(10.minutes).show
39
+ mount Dashboard::Ezmetrics, at: "/dashboard", as: "dashboard"
73
40
  ```
74
41
 
75
- You can combine these timeframes, for example - store for 10 minutes, display for 5 minutes.
42
+ ![Dashboard](https://user-images.githubusercontent.com/1847948/73551868-e26b2800-444f-11ea-83b9-fc81c8d05c07.png)
76
43
 
77
- ### Capture metrics
78
-
79
- Just add an initializer to your application:
80
-
81
- ```ruby
82
- # config/initializers/ezmetrics.rb
83
-
84
- ActiveSupport::Notifications.subscribe("sql.active_record") do |*args|
85
- event = ActiveSupport::Notifications::Event.new(*args)
86
- unless event.payload[:name] == "SCHEMA"
87
- Thread.current[:queries] ||= 0
88
- Thread.current[:queries] += 1
89
- end
90
- end
91
-
92
- ActiveSupport::Notifications.subscribe("process_action.action_controller") do |*args|
93
- event = ActiveSupport::Notifications::Event.new(*args)
94
- EZmetrics.new.log(
95
- duration: event.duration.to_f,
96
- views: event.payload[:view_runtime].to_f,
97
- db: event.payload[:db_runtime].to_f,
98
- status: event.payload[:status].to_i || 500,
99
- queries: Thread.current[:queries].to_i,
100
- )
101
- end
102
- ```
103
-
104
- ### Display metrics
44
+ #### 2. Directly
105
45
 
106
46
  As simple as:
107
47
 
108
48
  ```ruby
109
- EZmetrics.new.show
49
+ Ezmetrics::Storage.new.show
110
50
  ```
111
51
 
112
52
  This will return a hash with the following structure:
@@ -146,7 +86,7 @@ This will return a hash with the following structure:
146
86
  If you prefer a single level object - you can change the default output structure by calling `.flatten` before `.show`
147
87
 
148
88
  ```ruby
149
- EZmetrics.new(1.hour).flatten.show(db: :avg, duration: [:avg, :max])
89
+ Ezmetrics::Storage.new(1.hour).flatten.show(db: :avg, duration: [:avg, :max])
150
90
  ```
151
91
 
152
92
  ```ruby
@@ -162,7 +102,7 @@ EZmetrics.new(1.hour).flatten.show(db: :avg, duration: [:avg, :max])
162
102
  Same for [partitioned aggregation](#partitioning)
163
103
 
164
104
  ```ruby
165
- EZmetrics.new(1.hour).partition_by(:minute).flatten.show(db: :avg, duration: [:avg, :max])
105
+ Ezmetrics::Storage.new(1.hour).partition_by(:minute).flatten.show(db: :avg, duration: [:avg, :max])
166
106
  ```
167
107
 
168
108
  ```ruby
@@ -189,7 +129,7 @@ The aggregation can be easily configured by specifying aggregation options as in
189
129
  **1. Single**
190
130
 
191
131
  ```ruby
192
- EZmetrics.new.show(duration: :max)
132
+ Ezmetrics::Storage.new.show(duration: :max)
193
133
  ```
194
134
 
195
135
  ```ruby
@@ -205,7 +145,7 @@ EZmetrics.new.show(duration: :max)
205
145
  **2. Multiple**
206
146
 
207
147
  ```ruby
208
- EZmetrics.new.show(queries: [:max, :avg])
148
+ Ezmetrics::Storage.new.show(queries: [:max, :avg])
209
149
  ```
210
150
 
211
151
  ```ruby
@@ -222,7 +162,7 @@ EZmetrics.new.show(queries: [:max, :avg])
222
162
  **3. Requests**
223
163
 
224
164
  ```ruby
225
- EZmetrics.new.show(requests: true)
165
+ Ezmetrics::Storage.new.show(requests: true)
226
166
  ```
227
167
 
228
168
  ```ruby
@@ -244,7 +184,7 @@ EZmetrics.new.show(requests: true)
244
184
  **4. Combined**
245
185
 
246
186
  ```ruby
247
- EZmetrics.new.show(views: :avg, :db: [:avg, :max], requests: true)
187
+ Ezmetrics::Storage.new.show(views: :avg, :db: [:avg, :max], requests: true)
248
188
  ```
249
189
 
250
190
  ```ruby
@@ -279,7 +219,7 @@ By default percentile aggregation is turned off because it requires to store eac
279
219
  To enable this feature - you need to set `store_each_value: true` when saving the metrics:
280
220
 
281
221
  ```ruby
282
- EZmetrics.new.log(
222
+ Ezmetrics::Storage.new.log(
283
223
  duration: 100.5,
284
224
  views: 40.7,
285
225
  db: 59.8,
@@ -291,9 +231,8 @@ EZmetrics.new.log(
291
231
 
292
232
  The aggregation syntax has the following format `metrics_type: :percentile_{number}` where `number` is any integer in the 1..99 range.
293
233
 
294
-
295
234
  ```ruby
296
- EZmetrics.new.show(db: [:avg, :percentile_90, :percentile_95], duration: :percentile_99)
235
+ Ezmetrics::Storage.new.show(db: [:avg, :percentile_90, :percentile_95], duration: :percentile_99)
297
236
  ```
298
237
 
299
238
  ```ruby
@@ -309,6 +248,29 @@ EZmetrics.new.show(db: [:avg, :percentile_90, :percentile_95], duration: :percen
309
248
  }
310
249
  ```
311
250
 
251
+ **6. Percentile distribution**
252
+
253
+ If you want to visualize percentile distribution (from 1% to 99%):
254
+
255
+ ```ruby
256
+ Ezmetrics::Storage.new.show(duration: :percentile_distribution)
257
+ ```
258
+
259
+ ```ruby
260
+ {
261
+ duration: {
262
+ percentile_distribution: {
263
+ 1: 12,
264
+ 2: 15,
265
+ 3: 19,
266
+ #...
267
+ 97: 6540,
268
+ 98: 6682,
269
+ 99: 6730
270
+ }
271
+ }
272
+ }
273
+ ```
312
274
 
313
275
  ### Partitioning
314
276
 
@@ -318,7 +280,7 @@ To aggregate metrics, partitioned by a unit of time you need to call `.partition
318
280
 
319
281
  ```ruby
320
282
  # Aggregate metrics for last hour, partition by minute
321
- EZmetrics.new(1.hour).partition_by(:minute).show(duration: [:avg, :max], db: :avg)
283
+ Ezmetrics::Storage.new(1.hour).partition_by(:minute).show(duration: [:avg, :max], db: :avg)
322
284
  ```
323
285
 
324
286
  This will return an array of objects with the following structure:
@@ -365,75 +327,38 @@ like in the example below:
365
327
 
366
328
  Available time units for partitioning: `second`, `minute`, `hour`, `day`. Default: `minute`.
367
329
 
368
- ### Performance
330
+ ## Performance
369
331
 
370
332
  The aggregation speed relies on the performance of **Redis** (data storage) and **Oj** (json serialization/parsing).
371
333
 
372
- #### Simple aggregation
373
-
374
- You can check the **aggregation** time by running:
375
-
376
- ```ruby
377
- EZmetrics::Benchmark.new.measure_aggregation
378
- ```
379
-
380
- | Interval | Duration (seconds) |
381
- | :------: | :----------------: |
382
- | 1 minute | 0.0 |
383
- | 1 hour | 0.02 |
384
- | 12 hours | 0.22 |
385
- | 24 hours | 0.61 |
386
- | 48 hours | 1.42 |
387
-
388
- ---
389
-
390
- To check the **partitioned aggregation** time you need to run:
334
+ You can check the **aggregation** time (in seconds) by running:
391
335
 
392
336
  ```ruby
393
- EZmetrics::Benchmark.new.measure_aggregation(:minute)
394
- ```
337
+ # 1. Simple
338
+ Ezmetrics::Benchmark.new.measure_aggregation
395
339
 
396
- | Interval | Duration (seconds) |
397
- | :------: | :----------------: |
398
- | 1 minute | 0.0 |
399
- | 1 hour | 0.02 |
400
- | 12 hours | 0.25 |
401
- | 24 hours | 0.78 |
402
- | 48 hours | 1.75 |
403
-
404
- ---
340
+ # 2. Partitioned
341
+ Ezmetrics::Benchmark.new.measure_aggregation(:minute)
405
342
 
406
- #### Percentile aggregation
343
+ # 3. Percentile
344
+ Ezmetrics::Benchmark.new(true).measure_aggregation
407
345
 
408
- You can check the **percentile aggregation** time by running:
409
-
410
- ```ruby
411
- EZmetrics::Benchmark.new(true).measure_aggregation
346
+ # 4. Percentile (partitioned)
347
+ Ezmetrics::Benchmark.new(true).measure_aggregation(:minute)
412
348
  ```
413
349
 
414
- | Interval | Duration (seconds) |
415
- | :------: | :----------------: |
416
- | 1 minute | 0.0 |
417
- | 1 hour | 0.14 |
418
- | 12 hours | 2.11 |
419
- | 24 hours | 5.85 |
420
- | 48 hours | 14.1 |
350
+ | Interval | Simple aggregation | Partitioned | Percentile | Percentile (partitioned) |
351
+ | :------: | :----------------: | :---------: | :--------: | :----------------------: |
352
+ | 1 minute | 0.0 | 0.0 | 0.0 | 0.0 |
353
+ | 1 hour | 0.02 | 0.02 | 0.14 | 0.16 |
354
+ | 12 hours | 0.22 | 0.25 | 2.11 | 1.97 |
355
+ | 24 hours | 0.61 | 0.78 | 5.85 | 5.85 |
356
+ | 48 hours | 1.42 | 1.75 | 14.1 | 13.9 |
421
357
 
422
- ---
423
-
424
- To check the **partitioned aggregation** time for percentile you need to run:
425
-
426
- ```ruby
427
- EZmetrics::Benchmark.new(true).measure_aggregation(:minute)
428
- ```
358
+ The benchmarks above were run on a _2017 Macbook Pro 2.9 GHz Intel Core i7 with 16 GB of RAM_
429
359
 
430
- | Interval | Duration (seconds) |
431
- | :------: | :----------------: |
432
- | 1 minute | 0.0 |
433
- | 1 hour | 0.16 |
434
- | 12 hours | 1.97 |
435
- | 24 hours | 5.85 |
436
- | 48 hours | 13.9 |
360
+ ## [Changelog](CHANGELOG.md)
437
361
 
362
+ ## License
438
363
 
439
- The benchmarks above were run on a _2017 Macbook Pro 2.9 GHz Intel Core i7 with 16 GB of RAM_
364
+ ezmetrics is released under the [MIT License](https://opensource.org/licenses/MIT).
data/_config.yml ADDED
@@ -0,0 +1 @@
1
+ theme: jekyll-theme-tactile
data/ezmetrics.gemspec ADDED
@@ -0,0 +1,21 @@
1
+ require_relative "lib/ezmetrics/version"
2
+
3
+ Gem::Specification.new do |gem|
4
+ gem.name = "ezmetrics"
5
+ gem.version = Ezmetrics::VERSION
6
+ gem.date = "2020-02-01"
7
+ gem.summary = "Rails metrics aggregation tool."
8
+ gem.description = "Simple, lightweight and fast metrics aggregation for Rails."
9
+ gem.authors = ["Nicolae Rotaru"]
10
+ gem.email = "nyku.rn@gmail.com"
11
+ gem.homepage = "https://github.com/nyku/ezmetrics"
12
+ gem.license = "MIT"
13
+ gem.files = `git ls-files | grep -Ev '^(spec)'`.split("\n")
14
+ gem.require_paths = ["lib"]
15
+ gem.required_ruby_version = ">= 2.4.0"
16
+ gem.add_dependency "redis", ["~> 4.0"]
17
+ gem.add_dependency "hiredis", ["~> 0.6.3"]
18
+ gem.add_dependency "oj", ["~> 3.10"]
19
+
20
+ gem.add_development_dependency "rspec", "~> 3.5"
21
+ end
data/lib/ezmetrics.rb CHANGED
@@ -2,286 +2,12 @@ require "redis"
2
2
  require "redis/connection/hiredis"
3
3
  require "oj"
4
4
 
5
- class EZmetrics
6
- METRICS = [:duration, :views, :db, :queries].freeze
7
- AGGREGATION_FUNCTIONS = [:max, :avg].freeze
8
- PARTITION_UNITS = [:second, :minute, :hour, :day].freeze
5
+ module Ezmetrics
6
+ require_relative "ezmetrics/version"
7
+ require_relative "ezmetrics/storage"
8
+ require_relative "ezmetrics/benchmark"
9
9
 
10
- def initialize(interval_seconds=60)
11
- @interval_seconds = interval_seconds.to_i
12
- @redis = Redis.new(driver: :hiredis)
13
- @schema = redis_schema
10
+ if defined?(Rails)
11
+ require_relative "ezmetrics/dashboard/lib/dashboard"
14
12
  end
15
-
16
- def log(payload={duration: 0.0, views: 0.0, db: 0.0, queries: 0, status: 200, store_each_value: false})
17
- @safe_payload = {
18
- duration: payload[:duration].to_f,
19
- views: payload[:views].to_f,
20
- db: payload[:db].to_f,
21
- queries: payload[:queries].to_i,
22
- status: payload[:status].to_i,
23
- store_each_value: payload[:store_each_value].to_s == "true"
24
- }
25
-
26
- this_second = Time.now.to_i
27
- status_group = "#{payload[:status].to_s[0]}xx"
28
- @this_second_metrics = redis.get(this_second)
29
-
30
- if this_second_metrics
31
- @this_second_metrics = Oj.load(this_second_metrics)
32
-
33
- METRICS.each do |metrics_type|
34
- update_sum(metrics_type)
35
- update_max(metrics_type)
36
- store_value(metrics_type) if safe_payload[:store_each_value]
37
- end
38
-
39
- this_second_metrics[schema["all"]] += 1
40
- this_second_metrics[schema[status_group]] += 1
41
- else
42
- @this_second_metrics = {
43
- "second" => this_second,
44
- "duration_sum" => safe_payload[:duration],
45
- "duration_max" => safe_payload[:duration],
46
- "views_sum" => safe_payload[:views],
47
- "views_max" => safe_payload[:views],
48
- "db_sum" => safe_payload[:db],
49
- "db_max" => safe_payload[:db],
50
- "queries_sum" => safe_payload[:queries],
51
- "queries_max" => safe_payload[:queries],
52
- "2xx" => 0,
53
- "3xx" => 0,
54
- "4xx" => 0,
55
- "5xx" => 0,
56
- "all" => 1
57
- }
58
-
59
- if safe_payload[:store_each_value]
60
- this_second_metrics.merge!(
61
- "duration_values" => [safe_payload[:duration]],
62
- "views_values" => [safe_payload[:views]],
63
- "db_values" => [safe_payload[:db]],
64
- "queries_values" => [safe_payload[:queries]]
65
- )
66
- end
67
-
68
- this_second_metrics[status_group] = 1
69
-
70
- @this_second_metrics = this_second_metrics.values
71
- end
72
-
73
- redis.setex(this_second, interval_seconds, Oj.dump(this_second_metrics))
74
- true
75
- rescue => error
76
- formatted_error(error)
77
- end
78
-
79
- def show(options=nil)
80
- @options = options || default_options
81
- partitioned_metrics ? aggregate_partitioned_data : aggregate_data
82
- end
83
-
84
- def flatten
85
- @flat = true
86
- self
87
- end
88
-
89
- def partition_by(time_unit=:minute)
90
- time_unit = PARTITION_UNITS.include?(time_unit) ? time_unit : :minute
91
- @partitioned_metrics = interval_metrics.group_by { |array| second_to_partition_unit(time_unit, array[schema["second"]]) }
92
- self
93
- end
94
-
95
- private
96
-
97
- attr_reader :redis, :interval_seconds, :interval_metrics, :requests, :flat, :schema,
98
- :storage_key, :safe_payload, :this_second_metrics, :partitioned_metrics, :options
99
-
100
- def aggregate_data
101
- return {} unless interval_metrics.any?
102
- @requests = interval_metrics.sum { |array| array[schema["all"]] }
103
- build_result
104
- rescue
105
- {}
106
- end
107
-
108
- def aggregate_partitioned_data
109
- partitioned_metrics.map do |partition, metrics|
110
- @interval_metrics = metrics
111
- @requests = interval_metrics.sum { |array| array[schema["all"]] }
112
- METRICS.each { |metrics_type| instance_variable_set("@sorted_#{metrics_type}_values", nil) }
113
- flat ? { timestamp: partition, **build_result } : { timestamp: partition, data: build_result }
114
- end
115
- rescue
116
- self
117
- end
118
-
119
- def build_result
120
- result = {}
121
-
122
- if options[:requests]
123
- append_requests_to_result(result, { all: requests, grouped: count_all_status_groups })
124
- end
125
-
126
- options.each do |metrics, aggregation_functions|
127
- next unless METRICS.include?(metrics)
128
- aggregation_functions = [aggregation_functions] unless aggregation_functions.is_a?(Array)
129
- next unless aggregation_functions.any?
130
-
131
- aggregation_functions.each do |aggregation_function|
132
- aggregated_metrics = aggregate(metrics, aggregation_function)
133
- append_metrics_to_result(result, metrics, aggregation_function, aggregated_metrics)
134
- end
135
- end
136
- result
137
- ensure
138
- result
139
- end
140
-
141
- def append_requests_to_result(result, aggregated_requests)
142
- return result[:requests] = aggregated_requests unless flat
143
-
144
- result[:requests_all] = aggregated_requests[:all]
145
- aggregated_requests[:grouped].each do |group, counter|
146
- result[:"requests_#{group}"] = counter
147
- end
148
- end
149
-
150
- def append_metrics_to_result(result, metrics, aggregation_function, aggregated_metrics)
151
- return result[:"#{metrics}_#{aggregation_function}"] = aggregated_metrics if flat
152
-
153
- result[metrics] ||= {}
154
- result[metrics][aggregation_function] = aggregated_metrics
155
- end
156
-
157
- def second_to_partition_unit(time_unit, second)
158
- return second if time_unit == :second
159
- time = Time.at(second)
160
- return (time - time.sec - time.min * 60 - time.hour * 3600).to_i if time_unit == :day
161
- return (time - time.sec - time.min * 60).to_i if time_unit == :hour
162
- (time - time.sec).to_i
163
- end
164
-
165
- def interval_metrics
166
- @interval_metrics ||= begin
167
- interval_start = Time.now.to_i - interval_seconds
168
- interval_keys = (interval_start..Time.now.to_i).to_a
169
- redis.mget(interval_keys).compact.map { |array| Oj.load(array) }
170
- end
171
- end
172
-
173
- def aggregate(metrics, aggregation_function)
174
- return avg("#{metrics}_sum") if aggregation_function == :avg
175
- return max("#{metrics}_max") if aggregation_function == :max
176
-
177
- percentile = aggregation_function.match(/percentile_(?<value>\d+)/)
178
-
179
- if percentile && percentile["value"]
180
- sorted_values = send("sorted_#{metrics}_values")
181
- percentile(sorted_values, percentile["value"].to_i)&.round
182
- end
183
- end
184
-
185
- METRICS.each do |metrics|
186
- define_method "sorted_#{metrics}_values" do
187
- instance_variable_get("@sorted_#{metrics}_values") || instance_variable_set(
188
- "@sorted_#{metrics}_values", interval_metrics.map { |array| array[schema["#{metrics}_values"]] }.flatten.compact
189
- )
190
- end
191
- end
192
-
193
- def redis_schema
194
- [
195
- "second",
196
- "duration_sum",
197
- "duration_max",
198
- "views_sum",
199
- "views_max",
200
- "db_sum",
201
- "db_max",
202
- "queries_sum",
203
- "queries_max",
204
- "2xx",
205
- "3xx",
206
- "4xx",
207
- "5xx",
208
- "all",
209
- "duration_values",
210
- "views_values",
211
- "db_values",
212
- "queries_values"
213
- ].each_with_index.inject({}){ |result, pair| result[pair[0]] = pair[1] ; result }
214
- end
215
-
216
- def update_sum(metrics)
217
- this_second_metrics[schema["#{metrics}_sum"]] += safe_payload[metrics]
218
- end
219
-
220
- def store_value(metrics)
221
- this_second_metrics[schema["#{metrics}_values"]] << safe_payload[metrics]
222
- end
223
-
224
- def update_max(metrics)
225
- max_value = [safe_payload[metrics], this_second_metrics[schema["#{metrics}_max"]]].max
226
- this_second_metrics[schema["#{metrics}_max"]] = max_value
227
- end
228
-
229
- def avg(metrics)
230
- (interval_metrics.sum { |array| array[schema[metrics]] }.to_f / requests).round
231
- end
232
-
233
- def max(metrics)
234
- interval_metrics.max { |array| array[schema[metrics]] }[schema[metrics]].round
235
- end
236
-
237
- def percentile(array, pcnt)
238
- sorted_array = array.sort
239
-
240
- return nil if array.length == 0
241
-
242
- rank = (pcnt.to_f / 100) * (array.length + 1)
243
- whole = rank.truncate
244
-
245
- # if has fractional part
246
- if whole != rank
247
- s0 = sorted_array[whole - 1]
248
- s1 = sorted_array[whole]
249
-
250
- f = (rank - rank.truncate).abs
251
-
252
- return (f * (s1 - s0)) + s0
253
- else
254
- return sorted_array[whole - 1]
255
- end
256
- end
257
-
258
- def count_all_status_groups
259
- interval_metrics.inject({ "2xx" => 0, "3xx" => 0, "4xx" => 0, "5xx" => 0 }) do |result, array|
260
- result["2xx"] += array[schema["2xx"]]
261
- result["3xx"] += array[schema["3xx"]]
262
- result["4xx"] += array[schema["4xx"]]
263
- result["5xx"] += array[schema["5xx"]]
264
- result
265
- end
266
- end
267
-
268
- def default_options
269
- {
270
- duration: AGGREGATION_FUNCTIONS,
271
- views: AGGREGATION_FUNCTIONS,
272
- db: AGGREGATION_FUNCTIONS,
273
- queries: AGGREGATION_FUNCTIONS,
274
- requests: true
275
- }
276
- end
277
-
278
- def formatted_error(error)
279
- {
280
- error: error.class.name,
281
- message: error.message,
282
- backtrace: error.backtrace.reject { |line| line.match(/ruby|gems/) }
283
- }
284
- end
285
- end
286
-
287
- require "ezmetrics/benchmark"
13
+ end