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.
- 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
|
+

|
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
|