ezmetrics 2.0.3 → 3.0.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.
Files changed (53) 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 +13 -0
  6. data/Gemfile +6 -0
  7. data/Gemfile.lock +41 -0
  8. data/LICENSE +1 -1
  9. data/README.md +36 -72
  10. data/_config.yml +1 -0
  11. data/ezmetrics.gemspec +21 -0
  12. data/lib/ezmetrics.rb +7 -289
  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 +39 -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 +79 -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/scripts/compile_assets +20 -0
  53. metadata +49 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8285f47dd4bba3f689deb9547be625a3f1b1bd6c1a5834b3d7a51604999e2d9d
4
- data.tar.gz: d344781510df94d4394edd207723cf82037a34850077d341f57c7b9b54b351f3
3
+ metadata.gz: 1c984dfd8093ab6d30506e366dd849c7885cc2085134df96770412fba9f11680
4
+ data.tar.gz: bc6d30ebcf66184deb53c0deb68c41bf74e2d3e88d9ac3a405b0bff8e421735a
5
5
  SHA512:
6
- metadata.gz: 9e7d50a7d5a39290aa6dcd488d804caec87f8259e3bbde8a6e8bd9f817c16cf6919769cc95ca64366b076b53dac6abb48e33f9c9550d0123e7d681ba652b25f3
7
- data.tar.gz: 39e1b548e6f89fda6a92f0c02425b7b5ef81d1cbf525494cfecef98854f23ecb0922ef16b309b0ac718cca8937302005bd1d04cd6a91d87cef7f45891a65619b
6
+ metadata.gz: 1763e0aaf730c99b89e070d2f36ab99bb38d247cc8e8d0575f5d9c0baa03c93d7fd7aef7d4846fc4c4d346186d2519d9185d1b99e9b5b2fa5ab3a337a9d6e2e7
7
+ data.tar.gz: 8835762f7d3c666424b9d811182ff009d42d6a86b33ee2e37bf5f91b366e3d39d474028685a5466e4526ce6e55df4a6c4e14bf4b9502a90516cd7b47614d5023
@@ -0,0 +1 @@
1
+ patreon: nyku
@@ -0,0 +1,3 @@
1
+ dump.rdb
2
+ *.gem
3
+ .DS_Store
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format documentation
@@ -0,0 +1,13 @@
1
+ # Changelog
2
+
3
+
4
+ ## [3.0.0] - 2020-02-01
5
+
6
+ ### Added
7
+
8
+ - Dashboard (mountable rails engine)
9
+
10
+ ### Changed
11
+
12
+ - Internal structure.
13
+ In order to migrate from older version - replace `EZmetrics.new` with `Ezmetrics::Storage.new`
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+ gemspec
3
+
4
+ gem "redis", "~> 4.0"
5
+ gem "hiredis"
6
+ gem "oj"
@@ -0,0 +1,41 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ ezmetrics (3.0.0)
5
+ hiredis (~> 0.6.3)
6
+ oj (~> 3.10)
7
+ redis (~> 4.0)
8
+
9
+ GEM
10
+ remote: https://rubygems.org/
11
+ specs:
12
+ diff-lcs (1.3)
13
+ hiredis (0.6.3)
14
+ oj (3.10.0)
15
+ redis (4.1.3)
16
+ rspec (3.9.0)
17
+ rspec-core (~> 3.9.0)
18
+ rspec-expectations (~> 3.9.0)
19
+ rspec-mocks (~> 3.9.0)
20
+ rspec-core (3.9.0)
21
+ rspec-support (~> 3.9.0)
22
+ rspec-expectations (3.9.0)
23
+ diff-lcs (>= 1.2.0, < 2.0)
24
+ rspec-support (~> 3.9.0)
25
+ rspec-mocks (3.9.0)
26
+ diff-lcs (>= 1.2.0, < 2.0)
27
+ rspec-support (~> 3.9.0)
28
+ rspec-support (3.9.0)
29
+
30
+ PLATFORMS
31
+ ruby
32
+
33
+ DEPENDENCIES
34
+ ezmetrics!
35
+ hiredis
36
+ oj
37
+ redis (~> 4.0)
38
+ rspec (~> 3.5)
39
+
40
+ BUNDLED WITH
41
+ 1.17.3
data/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2019 Nicolae Rotaru
3
+ Copyright (c) 2019-2020 Nicolae Rotaru
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
data/README.md CHANGED
@@ -22,68 +22,17 @@ gem 'ezmetrics'
22
22
 
23
23
  ## Usage
24
24
 
25
- ### Getting started
26
-
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:
38
-
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
- ```
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
- )
59
- ```
60
-
61
- ---
62
-
63
- For displaying metrics you need to call `show` method:
64
-
65
- ```ruby
66
- # Aggregate and show metrics for last 60 seconds (default behaviour)
67
- EZmetrics.new.show
68
- ```
69
-
70
- ```ruby
71
- # Aggregate and show metrics for last 10 minutes
72
- EZmetrics.new(10.minutes).show
73
- ```
74
-
75
- You can combine these timeframes, for example - store for 10 minutes, display for 5 minutes.
76
25
 
77
26
  ### Capture metrics
78
27
 
79
- Just add an initializer to your application:
28
+ Add an initializer to your application:
80
29
 
81
30
  ```ruby
82
31
  # config/initializers/ezmetrics.rb
83
32
 
84
33
  ActiveSupport::Notifications.subscribe("sql.active_record") do |*args|
85
34
  event = ActiveSupport::Notifications::Event.new(*args)
86
- unless event.payload[:name] == "SCHEMA"
35
+ unless event.payload[:name] == "SCHEMA" || event.payload[:sql] =~ /\ABEGIN|COMMIT|ROLLBACK\z/
87
36
  Thread.current[:queries] ||= 0
88
37
  Thread.current[:queries] += 1
89
38
  end
@@ -91,22 +40,35 @@ end
91
40
 
92
41
  ActiveSupport::Notifications.subscribe("process_action.action_controller") do |*args|
93
42
  event = ActiveSupport::Notifications::Event.new(*args)
94
- EZmetrics.new.log(
43
+ Ezmetrics::Storage.new(24.hours).log(
95
44
  duration: event.duration.to_f,
96
45
  views: event.payload[:view_runtime].to_f,
97
46
  db: event.payload[:db_runtime].to_f,
98
47
  status: event.payload[:status].to_i || 500,
99
48
  queries: Thread.current[:queries].to_i,
100
49
  )
50
+ Thread.current[:queries] = 0
101
51
  end
102
52
  ```
103
53
 
104
54
  ### Display metrics
105
55
 
56
+ #### 1. Dashboard
57
+
58
+ ```ruby
59
+ # add the following line to 'config/routes.rb'
60
+
61
+ mount Dashboard::Ezmetrics, at: "/dashboard", as: "dashboard"
62
+ ```
63
+
64
+ ![Dashboard](https://user-images.githubusercontent.com/1847948/73551868-e26b2800-444f-11ea-83b9-fc81c8d05c07.png)
65
+
66
+ #### 2. Directly
67
+
106
68
  As simple as:
107
69
 
108
70
  ```ruby
109
- EZmetrics.new.show
71
+ Ezmetrics::Storage.new.show
110
72
  ```
111
73
 
112
74
  This will return a hash with the following structure:
@@ -146,7 +108,7 @@ This will return a hash with the following structure:
146
108
  If you prefer a single level object - you can change the default output structure by calling `.flatten` before `.show`
147
109
 
148
110
  ```ruby
149
- EZmetrics.new(1.hour).flatten.show(db: :avg, duration: [:avg, :max])
111
+ Ezmetrics::Storage.new(1.hour).flatten.show(db: :avg, duration: [:avg, :max])
150
112
  ```
151
113
 
152
114
  ```ruby
@@ -162,7 +124,7 @@ EZmetrics.new(1.hour).flatten.show(db: :avg, duration: [:avg, :max])
162
124
  Same for [partitioned aggregation](#partitioning)
163
125
 
164
126
  ```ruby
165
- EZmetrics.new(1.hour).partition_by(:minute).flatten.show(db: :avg, duration: [:avg, :max])
127
+ Ezmetrics::Storage.new(1.hour).partition_by(:minute).flatten.show(db: :avg, duration: [:avg, :max])
166
128
  ```
167
129
 
168
130
  ```ruby
@@ -189,7 +151,7 @@ The aggregation can be easily configured by specifying aggregation options as in
189
151
  **1. Single**
190
152
 
191
153
  ```ruby
192
- EZmetrics.new.show(duration: :max)
154
+ Ezmetrics::Storage.new.show(duration: :max)
193
155
  ```
194
156
 
195
157
  ```ruby
@@ -205,7 +167,7 @@ EZmetrics.new.show(duration: :max)
205
167
  **2. Multiple**
206
168
 
207
169
  ```ruby
208
- EZmetrics.new.show(queries: [:max, :avg])
170
+ Ezmetrics::Storage.new.show(queries: [:max, :avg])
209
171
  ```
210
172
 
211
173
  ```ruby
@@ -222,7 +184,7 @@ EZmetrics.new.show(queries: [:max, :avg])
222
184
  **3. Requests**
223
185
 
224
186
  ```ruby
225
- EZmetrics.new.show(requests: true)
187
+ Ezmetrics::Storage.new.show(requests: true)
226
188
  ```
227
189
 
228
190
  ```ruby
@@ -244,7 +206,7 @@ EZmetrics.new.show(requests: true)
244
206
  **4. Combined**
245
207
 
246
208
  ```ruby
247
- EZmetrics.new.show(views: :avg, :db: [:avg, :max], requests: true)
209
+ Ezmetrics::Storage.new.show(views: :avg, :db: [:avg, :max], requests: true)
248
210
  ```
249
211
 
250
212
  ```ruby
@@ -279,7 +241,7 @@ By default percentile aggregation is turned off because it requires to store eac
279
241
  To enable this feature - you need to set `store_each_value: true` when saving the metrics:
280
242
 
281
243
  ```ruby
282
- EZmetrics.new.log(
244
+ Ezmetrics::Storage.new.log(
283
245
  duration: 100.5,
284
246
  views: 40.7,
285
247
  db: 59.8,
@@ -292,7 +254,7 @@ EZmetrics.new.log(
292
254
  The aggregation syntax has the following format `metrics_type: :percentile_{number}` where `number` is any integer in the 1..99 range.
293
255
 
294
256
  ```ruby
295
- EZmetrics.new.show(db: [:avg, :percentile_90, :percentile_95], duration: :percentile_99)
257
+ Ezmetrics::Storage.new.show(db: [:avg, :percentile_90, :percentile_95], duration: :percentile_99)
296
258
  ```
297
259
 
298
260
  ```ruby
@@ -313,7 +275,7 @@ EZmetrics.new.show(db: [:avg, :percentile_90, :percentile_95], duration: :percen
313
275
  If you want to visualize percentile distribution (from 1% to 99%):
314
276
 
315
277
  ```ruby
316
- EZmetrics.new.show(duration: :percentile_distribution)
278
+ Ezmetrics::Storage.new.show(duration: :percentile_distribution)
317
279
  ```
318
280
 
319
281
  ```ruby
@@ -340,7 +302,7 @@ To aggregate metrics, partitioned by a unit of time you need to call `.partition
340
302
 
341
303
  ```ruby
342
304
  # Aggregate metrics for last hour, partition by minute
343
- EZmetrics.new(1.hour).partition_by(:minute).show(duration: [:avg, :max], db: :avg)
305
+ Ezmetrics::Storage.new(1.hour).partition_by(:minute).show(duration: [:avg, :max], db: :avg)
344
306
  ```
345
307
 
346
308
  This will return an array of objects with the following structure:
@@ -387,7 +349,7 @@ like in the example below:
387
349
 
388
350
  Available time units for partitioning: `second`, `minute`, `hour`, `day`. Default: `minute`.
389
351
 
390
- ### Performance
352
+ ## Performance
391
353
 
392
354
  The aggregation speed relies on the performance of **Redis** (data storage) and **Oj** (json serialization/parsing).
393
355
 
@@ -395,16 +357,16 @@ You can check the **aggregation** time (in seconds) by running:
395
357
 
396
358
  ```ruby
397
359
  # 1. Simple
398
- EZmetrics::Benchmark.new.measure_aggregation
360
+ Ezmetrics::Benchmark.new.measure_aggregation
399
361
 
400
362
  # 2. Partitioned
401
- EZmetrics::Benchmark.new.measure_aggregation(:minute)
363
+ Ezmetrics::Benchmark.new.measure_aggregation(:minute)
402
364
 
403
365
  # 3. Percentile
404
- EZmetrics::Benchmark.new(true).measure_aggregation
366
+ Ezmetrics::Benchmark.new(true).measure_aggregation
405
367
 
406
- # 4. Percentile (partitioned)
407
- EZmetrics::Benchmark.new(true).measure_aggregation(:minute)
368
+ # 4. Percentile (partitioned)
369
+ Ezmetrics::Benchmark.new(true).measure_aggregation(:minute)
408
370
  ```
409
371
 
410
372
  | Interval | Simple aggregation | Partitioned | Percentile | Percentile (partitioned) |
@@ -417,6 +379,8 @@ EZmetrics::Benchmark.new(true).measure_aggregation(:minute)
417
379
 
418
380
  The benchmarks above were run on a _2017 Macbook Pro 2.9 GHz Intel Core i7 with 16 GB of RAM_
419
381
 
420
- ### License
382
+ ## [Changelog](CHANGELOG.md)
383
+
384
+ ## License
421
385
 
422
386
  ezmetrics is released under the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1 @@
1
+ theme: jekyll-theme-tactile
@@ -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
@@ -2,294 +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
- if aggregation_function == :percentile_distribution
178
- sorted_values = send("sorted_#{metrics}_values")
179
- return (1..99).to_a.inject({}) do |result, number|
180
- result[number] = percentile(sorted_values, number)
181
- result
182
- end
183
- end
184
-
185
- percentile = aggregation_function.match(/percentile_(?<value>\d+)/)
186
-
187
- if percentile && percentile["value"].to_i.between?(1, 99)
188
- sorted_values = send("sorted_#{metrics}_values")
189
- percentile(sorted_values, percentile["value"].to_i)
190
- end
191
- end
192
-
193
- METRICS.each do |metrics|
194
- define_method "sorted_#{metrics}_values" do
195
- instance_variable_get("@sorted_#{metrics}_values") || instance_variable_set(
196
- "@sorted_#{metrics}_values", interval_metrics.map { |array| array[schema["#{metrics}_values"]] }.flatten.compact.sort
197
- )
198
- end
199
- end
200
-
201
- def redis_schema
202
- [
203
- "second",
204
- "duration_sum",
205
- "duration_max",
206
- "views_sum",
207
- "views_max",
208
- "db_sum",
209
- "db_max",
210
- "queries_sum",
211
- "queries_max",
212
- "2xx",
213
- "3xx",
214
- "4xx",
215
- "5xx",
216
- "all",
217
- "duration_values",
218
- "views_values",
219
- "db_values",
220
- "queries_values"
221
- ].each_with_index.inject({}){ |result, pair| result[pair[0]] = pair[1] ; result }
222
- end
223
-
224
- def update_sum(metrics)
225
- this_second_metrics[schema["#{metrics}_sum"]] += safe_payload[metrics]
226
- end
227
-
228
- def store_value(metrics)
229
- this_second_metrics[schema["#{metrics}_values"]] << safe_payload[metrics]
230
- end
231
-
232
- def update_max(metrics)
233
- max_value = [safe_payload[metrics], this_second_metrics[schema["#{metrics}_max"]]].max
234
- this_second_metrics[schema["#{metrics}_max"]] = max_value
235
- end
236
-
237
- def avg(metrics)
238
- (interval_metrics.sum { |array| array[schema[metrics]] }.to_f / requests).round
239
- end
240
-
241
- def max(metrics)
242
- interval_metrics.max { |array| array[schema[metrics]] }[schema[metrics]].round
243
- end
244
-
245
- def percentile(sorted_array, pcnt)
246
- array_length = sorted_array.length
247
-
248
- return "not enough data (requests: #{array_length}, required: #{pcnt})" if array_length < pcnt
249
-
250
- rank = (pcnt.to_f / 100) * (array_length + 1)
251
- whole = rank.truncate
252
-
253
- # if has fractional part
254
- if whole != rank
255
- s0 = sorted_array[whole - 1]
256
- s1 = sorted_array[whole]
257
-
258
- f = (rank - rank.truncate).abs
259
-
260
- return ((f * (s1 - s0)) + s0)&.round
261
- else
262
- return (sorted_array[whole - 1])&.round
263
- end
264
- end
265
-
266
- def count_all_status_groups
267
- interval_metrics.inject({ "2xx" => 0, "3xx" => 0, "4xx" => 0, "5xx" => 0 }) do |result, array|
268
- result["2xx"] += array[schema["2xx"]]
269
- result["3xx"] += array[schema["3xx"]]
270
- result["4xx"] += array[schema["4xx"]]
271
- result["5xx"] += array[schema["5xx"]]
272
- result
273
- end
274
- end
275
-
276
- def default_options
277
- {
278
- duration: AGGREGATION_FUNCTIONS,
279
- views: AGGREGATION_FUNCTIONS,
280
- db: AGGREGATION_FUNCTIONS,
281
- queries: AGGREGATION_FUNCTIONS,
282
- requests: true
283
- }
284
- end
285
-
286
- def formatted_error(error)
287
- {
288
- error: error.class.name,
289
- message: error.message,
290
- backtrace: error.backtrace.reject { |line| line.match(/ruby|gems/) }
291
- }
292
- end
293
- end
294
-
295
- require "ezmetrics/benchmark"
13
+ end