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.
- checksums.yaml +4 -4
- data/.github/FUNDING.yml +1 -0
- data/.gitignore +3 -0
- data/.rspec +2 -0
- data/CHANGELOG.md +21 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +41 -0
- data/LICENSE +21 -674
- data/README.md +68 -143
- data/_config.yml +1 -0
- data/ezmetrics.gemspec +21 -0
- data/lib/ezmetrics.rb +7 -281
- data/lib/ezmetrics/benchmark.rb +2 -2
- data/lib/ezmetrics/dashboard/app/assets/config/dashboard_manifest.js +2 -0
- data/lib/ezmetrics/dashboard/app/assets/images/dashboard/.keep +0 -0
- data/lib/ezmetrics/dashboard/app/assets/javascripts/dashboard/application.js +15 -0
- data/lib/ezmetrics/dashboard/app/assets/javascripts/dashboard/main.js +1 -0
- data/lib/ezmetrics/dashboard/app/assets/stylesheets/dashboard/application.css +16 -0
- data/lib/ezmetrics/dashboard/app/assets/stylesheets/dashboard/main.css +6 -0
- data/lib/ezmetrics/dashboard/app/controllers/dashboard/application_controller.rb +5 -0
- data/lib/ezmetrics/dashboard/app/controllers/dashboard/metrics_controller.rb +41 -0
- data/lib/ezmetrics/dashboard/app/views/dashboard/metrics/index.html.erb +3 -0
- data/lib/ezmetrics/dashboard/app/views/layouts/dashboard/application.html.erb +12 -0
- data/lib/ezmetrics/dashboard/bin/rails +14 -0
- data/lib/ezmetrics/dashboard/config/routes.rb +5 -0
- data/lib/ezmetrics/dashboard/lib/dashboard.rb +4 -0
- data/lib/ezmetrics/dashboard/lib/dashboard/ezmetrics.rb +6 -0
- data/lib/ezmetrics/dashboard/lib/dashboard/version.rb +3 -0
- data/lib/ezmetrics/dashboard/react-dashboard/.gitignore +23 -0
- data/lib/ezmetrics/dashboard/react-dashboard/.rescriptsrc.js +9 -0
- data/lib/ezmetrics/dashboard/react-dashboard/README.md +0 -0
- data/lib/ezmetrics/dashboard/react-dashboard/package-lock.json +15472 -0
- data/lib/ezmetrics/dashboard/react-dashboard/package.json +49 -0
- data/lib/ezmetrics/dashboard/react-dashboard/public/index.html +18 -0
- data/lib/ezmetrics/dashboard/react-dashboard/public/manifest.json +10 -0
- data/lib/ezmetrics/dashboard/react-dashboard/public/robots.txt +2 -0
- data/lib/ezmetrics/dashboard/react-dashboard/src/App.tsx +216 -0
- data/lib/ezmetrics/dashboard/react-dashboard/src/Constants.tsx +74 -0
- data/lib/ezmetrics/dashboard/react-dashboard/src/Graph.tsx +71 -0
- data/lib/ezmetrics/dashboard/react-dashboard/src/Metric.tsx +21 -0
- data/lib/ezmetrics/dashboard/react-dashboard/src/MetricsBlock.tsx +22 -0
- data/lib/ezmetrics/dashboard/react-dashboard/src/Nav.tsx +105 -0
- data/lib/ezmetrics/dashboard/react-dashboard/src/RequestsBlock.tsx +78 -0
- data/lib/ezmetrics/dashboard/react-dashboard/src/index.css +82 -0
- data/lib/ezmetrics/dashboard/react-dashboard/src/index.tsx +9 -0
- data/lib/ezmetrics/dashboard/react-dashboard/src/react-app-env.d.ts +1 -0
- data/lib/ezmetrics/dashboard/react-dashboard/src/setupTests.ts +5 -0
- data/lib/ezmetrics/dashboard/react-dashboard/tsconfig.json +25 -0
- data/lib/ezmetrics/dashboard/react-dashboard/yarn.lock +11651 -0
- data/lib/ezmetrics/storage.rb +289 -0
- data/lib/ezmetrics/version.rb +3 -0
- data/lib/generators/ezmetrics/initializer_generator.rb +39 -0
- data/scripts/compile_assets +20 -0
- metadata +51 -3
data/README.md
CHANGED
@@ -12,101 +12,41 @@ gem 'ezmetrics'
|
|
12
12
|
|
13
13
|
## Available metrics
|
14
14
|
|
15
|
-
| Type |
|
16
|
-
|
17
|
-
| `duration` |
|
18
|
-
|
|
19
|
-
| `db` |
|
20
|
-
|
|
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
|
-
|
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
|
-
|
34
|
+
#### 1. Dashboard
|
64
35
|
|
65
36
|
```ruby
|
66
|
-
#
|
67
|
-
EZmetrics.new.show
|
68
|
-
```
|
37
|
+
# add the following line to 'config/routes.rb'
|
69
38
|
|
70
|
-
|
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
|
-
|
42
|
+

|
76
43
|
|
77
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
394
|
-
|
337
|
+
# 1. Simple
|
338
|
+
Ezmetrics::Benchmark.new.measure_aggregation
|
395
339
|
|
396
|
-
|
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
|
-
|
343
|
+
# 3. Percentile
|
344
|
+
Ezmetrics::Benchmark.new(true).measure_aggregation
|
407
345
|
|
408
|
-
|
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 |
|
415
|
-
| :------: | :----------------: |
|
416
|
-
| 1 minute | 0.0 |
|
417
|
-
| 1 hour | 0.
|
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
|
-
|
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
|
-
|
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
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
5
|
+
module Ezmetrics
|
6
|
+
require_relative "ezmetrics/version"
|
7
|
+
require_relative "ezmetrics/storage"
|
8
|
+
require_relative "ezmetrics/benchmark"
|
9
9
|
|
10
|
-
|
11
|
-
|
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
|