ezmetrics 1.1.1 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +88 -7
- data/lib/ezmetrics.rb +42 -9
- data/lib/ezmetrics/benchmark.rb +16 -9
- metadata +5 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0affbdfcda841ffe4eec9105ded5526b13d209ccf0aa564e1b84c91651079d0e
|
4
|
+
data.tar.gz: 8c0f3879ee0f2cedeba7f6b0526f679ef9962e2270c7639837821f226a0f3580
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 28d6883722e140005b24071ab637104d6d0e16e22a75fffac734b242fafbb88c1ecdbfd4d4d44a11439a8cbac5d0ad5dcfba705ed26a706f6abb83f55f3f5513
|
7
|
+
data.tar.gz: b02ddcabf2669f9458a21dd37e0495218eedff6f15047c923b81d6fbcd311ce34ed2d27c7ea664a97aebaf806eac653dd4ec09fb3ee526607da796ec4c3a54f2
|
data/README.md
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
[![Gem Version](https://badge.fury.io/rb/ezmetrics.svg)](https://badge.fury.io/rb/ezmetrics)
|
4
4
|
|
5
|
-
|
5
|
+
Simple, lightweight and fast metrics aggregation for Rails.
|
6
6
|
|
7
7
|
## Installation
|
8
8
|
|
@@ -28,12 +28,24 @@ You can change the timeframe according to your needs and save the metrics by cal
|
|
28
28
|
|
29
29
|
```ruby
|
30
30
|
# Store the metrics for 60 seconds (default behaviour)
|
31
|
-
EZmetrics.new.log(
|
31
|
+
EZmetrics.new.log(
|
32
|
+
duration: 100.5,
|
33
|
+
views: 40.7,
|
34
|
+
db: 59.8,
|
35
|
+
queries: 4,
|
36
|
+
status: 200
|
37
|
+
)
|
32
38
|
```
|
33
39
|
|
34
40
|
```ruby
|
35
41
|
# Store the metrics for 10 minutes
|
36
|
-
EZmetrics.new(10.minutes).log(
|
42
|
+
EZmetrics.new(10.minutes).log(
|
43
|
+
duration: 100.5,
|
44
|
+
views: 40.7,
|
45
|
+
db: 59.8,
|
46
|
+
queries: 4,
|
47
|
+
status: 200
|
48
|
+
)
|
37
49
|
```
|
38
50
|
|
39
51
|
---
|
@@ -50,7 +62,7 @@ For displaying metrics you need to call `show` method:
|
|
50
62
|
EZmetrics.new(10.minutes).show
|
51
63
|
```
|
52
64
|
|
53
|
-
|
65
|
+
You can combine these timeframes, for example - store for 10 minutes, display for 5 minutes.
|
54
66
|
|
55
67
|
### Capture metrics
|
56
68
|
|
@@ -119,7 +131,7 @@ This will return a hash with the following structure:
|
|
119
131
|
}
|
120
132
|
```
|
121
133
|
|
122
|
-
### Aggregation
|
134
|
+
### Aggregation
|
123
135
|
|
124
136
|
The aggregation can be easily configured by specifying aggregation options as in the following examples:
|
125
137
|
|
@@ -205,6 +217,59 @@ EZmetrics.new.show(views: :avg, :db: [:avg, :max], requests: true)
|
|
205
217
|
}
|
206
218
|
```
|
207
219
|
|
220
|
+
### Partitioning
|
221
|
+
|
222
|
+
To aggregate metrics, partitioned by a unit of time you need to call `partition_by({time_unit})` before calling `show`
|
223
|
+
|
224
|
+
```ruby
|
225
|
+
# Aggregate metrics for last hour, partition by minute
|
226
|
+
EZmetrics.new(1.hour).partition_by(:minute).show(duration: [:avg, :max], db: :avg)
|
227
|
+
```
|
228
|
+
|
229
|
+
This will return an array of objects with the following structure:
|
230
|
+
|
231
|
+
```ruby
|
232
|
+
[
|
233
|
+
{
|
234
|
+
timestamp: # UNIX timestamp
|
235
|
+
data: # a hash with aggregated metrics
|
236
|
+
}
|
237
|
+
]
|
238
|
+
```
|
239
|
+
|
240
|
+
like in the example below:
|
241
|
+
|
242
|
+
```ruby
|
243
|
+
[
|
244
|
+
{
|
245
|
+
timestamp: 1575242880,
|
246
|
+
data: {
|
247
|
+
duration: {
|
248
|
+
avg: 477,
|
249
|
+
max: 8566
|
250
|
+
},
|
251
|
+
db: {
|
252
|
+
avg: 387
|
253
|
+
}
|
254
|
+
}
|
255
|
+
},
|
256
|
+
{
|
257
|
+
timestamp: 1575242940,
|
258
|
+
data: {
|
259
|
+
duration: {
|
260
|
+
avg: 234,
|
261
|
+
max: 3675
|
262
|
+
},
|
263
|
+
db: {
|
264
|
+
avg: 123
|
265
|
+
}
|
266
|
+
}
|
267
|
+
}
|
268
|
+
]
|
269
|
+
```
|
270
|
+
|
271
|
+
Available time units for partitioning: `second`, `minute`, `hour`, `day`. Default: `minute`.
|
272
|
+
|
208
273
|
### Performance
|
209
274
|
|
210
275
|
The aggregation speed relies on the performance of **Redis** (data storage) and **Oj** (json serialization/parsing).
|
@@ -215,8 +280,6 @@ You can check the **aggregation** time by running:
|
|
215
280
|
EZmetrics::Benchmark.new.measure_aggregation
|
216
281
|
```
|
217
282
|
|
218
|
-
The result of running this benchmark on a _2017 Macbook Pro 2.9 GHz Intel Core i7 with 16 GB of RAM_:
|
219
|
-
|
220
283
|
| Interval | Duration (seconds) |
|
221
284
|
| :------: | :----------------: |
|
222
285
|
| 1 minute | 0.0 |
|
@@ -224,3 +287,21 @@ The result of running this benchmark on a _2017 Macbook Pro 2.9 GHz Intel Core i
|
|
224
287
|
| 12 hours | 0.49 |
|
225
288
|
| 24 hours | 1.51 |
|
226
289
|
| 48 hours | 3.48 |
|
290
|
+
|
291
|
+
---
|
292
|
+
|
293
|
+
To check the **partitioned aggregation** time you need to run:
|
294
|
+
|
295
|
+
```ruby
|
296
|
+
EZmetrics::Benchmark.new.measure_aggregation(:minute)
|
297
|
+
```
|
298
|
+
|
299
|
+
| Interval | Duration (seconds) |
|
300
|
+
| :------: | :----------------: |
|
301
|
+
| 1 minute | 0.0 |
|
302
|
+
| 1 hour | 0.05 |
|
303
|
+
| 12 hours | 0.74 |
|
304
|
+
| 24 hours | 2.12 |
|
305
|
+
| 48 hours | 4.85 |
|
306
|
+
|
307
|
+
The benchmarks above were run on a _2017 Macbook Pro 2.9 GHz Intel Core i7 with 16 GB of RAM_
|
data/lib/ezmetrics.rb
CHANGED
@@ -5,6 +5,7 @@ require "oj"
|
|
5
5
|
class EZmetrics
|
6
6
|
METRICS = [:duration, :views, :db, :queries].freeze
|
7
7
|
AGGREGATION_FUNCTIONS = [:max, :avg].freeze
|
8
|
+
PARTITION_UNITS = [:second, :minute, :hour, :day].freeze
|
8
9
|
|
9
10
|
def initialize(interval_seconds=60)
|
10
11
|
@interval_seconds = interval_seconds.to_i
|
@@ -36,6 +37,7 @@ class EZmetrics
|
|
36
37
|
this_second_metrics["statuses"][status_group] += 1
|
37
38
|
else
|
38
39
|
@this_second_metrics = {
|
40
|
+
"second" => this_second,
|
39
41
|
"duration_sum" => safe_payload[:duration],
|
40
42
|
"duration_max" => safe_payload[:duration],
|
41
43
|
"views_sum" => safe_payload[:views],
|
@@ -57,23 +59,38 @@ class EZmetrics
|
|
57
59
|
end
|
58
60
|
|
59
61
|
def show(options=nil)
|
60
|
-
@options
|
61
|
-
|
62
|
-
|
63
|
-
@interval_metrics = redis.mget(interval_keys).compact.map { |hash| Oj.load(hash) }
|
62
|
+
@options = options || default_options
|
63
|
+
partitioned_metrics ? aggregate_partitioned_data : aggregate_data
|
64
|
+
end
|
64
65
|
|
65
|
-
|
66
|
+
def partition_by(time_unit=:minute)
|
67
|
+
time_unit = PARTITION_UNITS.include?(time_unit) ? time_unit : :minute
|
68
|
+
@partitioned_metrics = interval_metrics.group_by { |h| second_to_partition_unit(time_unit, h["second"]) }
|
69
|
+
self
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
66
73
|
|
74
|
+
attr_reader :redis, :interval_seconds, :interval_metrics, :requests,
|
75
|
+
:storage_key, :safe_payload, :this_second_metrics, :partitioned_metrics, :options
|
76
|
+
|
77
|
+
def aggregate_data
|
78
|
+
return {} unless interval_metrics.any?
|
67
79
|
@requests = interval_metrics.sum { |hash| hash["statuses"]["all"] }
|
68
80
|
build_result
|
69
81
|
rescue
|
70
82
|
{}
|
71
83
|
end
|
72
84
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
85
|
+
def aggregate_partitioned_data
|
86
|
+
partitioned_metrics.map do |partition, metrics|
|
87
|
+
@interval_metrics = metrics
|
88
|
+
@requests = interval_metrics.sum { |hash| hash["statuses"]["all"] }
|
89
|
+
{ timestamp: partition, data: build_result }
|
90
|
+
end
|
91
|
+
rescue
|
92
|
+
new(options)
|
93
|
+
end
|
77
94
|
|
78
95
|
def build_result
|
79
96
|
result = {}
|
@@ -95,6 +112,22 @@ class EZmetrics
|
|
95
112
|
result
|
96
113
|
end
|
97
114
|
|
115
|
+
def second_to_partition_unit(time_unit, second)
|
116
|
+
return second if time_unit == :second
|
117
|
+
time_unit_depth = { minute: 4, hour: 3, day: 2 }
|
118
|
+
reset_depth = time_unit_depth[time_unit]
|
119
|
+
time_to_array = Time.at(second).to_a[0..5].reverse
|
120
|
+
Time.new(*time_to_array[0..reset_depth]).to_i
|
121
|
+
end
|
122
|
+
|
123
|
+
def interval_metrics
|
124
|
+
@interval_metrics ||= begin
|
125
|
+
interval_start = Time.now.to_i - interval_seconds
|
126
|
+
interval_keys = (interval_start..Time.now.to_i).to_a
|
127
|
+
redis.mget(interval_keys).compact.map { |hash| Oj.load(hash) }
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
98
131
|
def aggregate(metrics, aggregation_function)
|
99
132
|
return unless AGGREGATION_FUNCTIONS.include?(aggregation_function)
|
100
133
|
return avg("#{metrics}_sum") if aggregation_function == :avg
|
data/lib/ezmetrics/benchmark.rb
CHANGED
@@ -16,11 +16,11 @@ class EZmetrics::Benchmark
|
|
16
16
|
}
|
17
17
|
end
|
18
18
|
|
19
|
-
def measure_aggregation
|
19
|
+
def measure_aggregation(partition_by=nil)
|
20
20
|
write_metrics
|
21
21
|
print_header
|
22
22
|
intervals.each do |interval, seconds|
|
23
|
-
result = measure_aggregation_time(interval, seconds)
|
23
|
+
result = measure_aggregation_time(interval, seconds, partition_by)
|
24
24
|
print_row(result)
|
25
25
|
end
|
26
26
|
cleanup_metrics
|
@@ -36,6 +36,7 @@ class EZmetrics::Benchmark
|
|
36
36
|
seconds.times do |i|
|
37
37
|
second = start - i
|
38
38
|
payload = {
|
39
|
+
"second" => second,
|
39
40
|
"duration_sum" => rand(10000),
|
40
41
|
"duration_max" => rand(10000),
|
41
42
|
"views_sum" => rand(1000),
|
@@ -45,11 +46,11 @@ class EZmetrics::Benchmark
|
|
45
46
|
"queries_sum" => rand(100),
|
46
47
|
"queries_max" => rand(100),
|
47
48
|
"statuses" => {
|
48
|
-
"2xx" => rand(10),
|
49
|
-
"3xx" => rand(10),
|
50
|
-
"4xx" => rand(10),
|
51
|
-
"5xx" => rand(10),
|
52
|
-
"all" => rand(40)
|
49
|
+
"2xx" => rand(1..10),
|
50
|
+
"3xx" => rand(1..10),
|
51
|
+
"4xx" => rand(1..10),
|
52
|
+
"5xx" => rand(1..10),
|
53
|
+
"all" => rand(1..40)
|
53
54
|
}
|
54
55
|
}
|
55
56
|
redis.setex(second, seconds, Oj.dump(payload))
|
@@ -63,9 +64,15 @@ class EZmetrics::Benchmark
|
|
63
64
|
redis.del(interval_keys)
|
64
65
|
end
|
65
66
|
|
66
|
-
def measure_aggregation_time(interval, seconds)
|
67
|
+
def measure_aggregation_time(interval, seconds, partition_by)
|
67
68
|
iterations.times do
|
68
|
-
durations << ::Benchmark.measure
|
69
|
+
durations << ::Benchmark.measure do
|
70
|
+
if partition_by
|
71
|
+
EZmetrics.new(seconds).partition_by(partition_by).show
|
72
|
+
else
|
73
|
+
EZmetrics.new(seconds).show
|
74
|
+
end
|
75
|
+
end.real
|
69
76
|
end
|
70
77
|
|
71
78
|
return {
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ezmetrics
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nicolae Rotaru
|
@@ -66,7 +66,7 @@ dependencies:
|
|
66
66
|
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '3.5'
|
69
|
-
description:
|
69
|
+
description: Simple, lightweight and fast metrics aggregation for Rails.
|
70
70
|
email: nyku.rn@gmail.com
|
71
71
|
executables: []
|
72
72
|
extensions: []
|
@@ -76,10 +76,11 @@ files:
|
|
76
76
|
- README.md
|
77
77
|
- lib/ezmetrics.rb
|
78
78
|
- lib/ezmetrics/benchmark.rb
|
79
|
-
homepage: https://github.
|
79
|
+
homepage: https://nyku.github.io/ezmetrics
|
80
80
|
licenses:
|
81
81
|
- GPL-3.0
|
82
|
-
metadata:
|
82
|
+
metadata:
|
83
|
+
source_code_uri: https://github.com/nyku/ezmetrics
|
83
84
|
post_install_message:
|
84
85
|
rdoc_options: []
|
85
86
|
require_paths:
|