ezmetrics 2.0.3 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/FUNDING.yml +1 -0
- data/.gitignore +3 -0
- data/.rspec +2 -0
- data/CHANGELOG.md +13 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +41 -0
- data/LICENSE +1 -1
- data/README.md +36 -72
- data/_config.yml +1 -0
- data/ezmetrics.gemspec +21 -0
- data/lib/ezmetrics.rb +7 -289
- 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 +39 -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 +79 -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/scripts/compile_assets +20 -0
- metadata +49 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1c984dfd8093ab6d30506e366dd849c7885cc2085134df96770412fba9f11680
|
4
|
+
data.tar.gz: bc6d30ebcf66184deb53c0deb68c41bf74e2d3e88d9ac3a405b0bff8e421735a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1763e0aaf730c99b89e070d2f36ab99bb38d247cc8e8d0575f5d9c0baa03c93d7fd7aef7d4846fc4c4d346186d2519d9185d1b99e9b5b2fa5ab3a337a9d6e2e7
|
7
|
+
data.tar.gz: 8835762f7d3c666424b9d811182ff009d42d6a86b33ee2e37bf5f91b366e3d39d474028685a5466e4526ce6e55df4a6c4e14bf4b9502a90516cd7b47614d5023
|
data/.github/FUNDING.yml
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
patreon: nyku
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -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
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
360
|
+
Ezmetrics::Benchmark.new.measure_aggregation
|
399
361
|
|
400
362
|
# 2. Partitioned
|
401
|
-
|
363
|
+
Ezmetrics::Benchmark.new.measure_aggregation(:minute)
|
402
364
|
|
403
365
|
# 3. Percentile
|
404
|
-
|
366
|
+
Ezmetrics::Benchmark.new(true).measure_aggregation
|
405
367
|
|
406
|
-
# 4.
|
407
|
-
|
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
|
-
|
382
|
+
## [Changelog](CHANGELOG.md)
|
383
|
+
|
384
|
+
## License
|
421
385
|
|
422
386
|
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,294 +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
|
-
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
|