ezmetrics 2.0.3 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
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