ezmetrics 1.0.4 → 1.2.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 +5 -5
- data/README.md +213 -19
- data/lib/ezmetrics.rb +131 -83
- data/lib/ezmetrics/benchmark.rb +95 -0
- metadata +35 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
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,8 +2,7 @@
|
|
2
2
|
|
3
3
|
[](https://badge.fury.io/rb/ezmetrics)
|
4
4
|
|
5
|
-
|
6
|
-
|
5
|
+
Simple, lightweight and fast metrics aggregation for Rails.
|
7
6
|
|
8
7
|
## Installation
|
9
8
|
|
@@ -15,45 +14,59 @@ gem 'ezmetrics'
|
|
15
14
|
|
16
15
|
### Getting started
|
17
16
|
|
18
|
-
This tool captures and aggregates metrics such as
|
19
|
-
|
17
|
+
This tool captures and aggregates Rails application metrics such as
|
18
|
+
|
20
19
|
- `duration`
|
21
|
-
- `
|
20
|
+
- `views`
|
22
21
|
- `db`
|
22
|
+
- `queries`
|
23
|
+
- `status`
|
23
24
|
|
24
|
-
for
|
25
|
+
and stores them for the timeframe you specified, 60 seconds by default.
|
25
26
|
|
26
|
-
You can change the timeframe according to your needs and save the metrics by calling `log` method:
|
27
|
+
You can change the timeframe according to your needs and save the metrics by calling `log` method:
|
27
28
|
|
28
29
|
```ruby
|
29
30
|
# Store the metrics for 60 seconds (default behaviour)
|
30
|
-
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
|
+
)
|
31
38
|
```
|
32
|
-
or
|
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
|
+
---
|
52
|
+
|
53
|
+
For displaying metrics you need to call `show` method:
|
40
54
|
|
41
55
|
```ruby
|
42
56
|
# Aggregate and show metrics for last 60 seconds (default behaviour)
|
43
|
-
EZmetrics.new.show
|
57
|
+
EZmetrics.new.show
|
44
58
|
```
|
45
59
|
|
46
|
-
or
|
47
|
-
|
48
60
|
```ruby
|
49
61
|
# Aggregate and show metrics for last 10 minutes
|
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
|
|
67
|
+
### Capture metrics
|
55
68
|
|
56
|
-
|
69
|
+
Just add an initializer to your application:
|
57
70
|
|
58
71
|
```ruby
|
59
72
|
# config/initializers/ezmetrics.rb
|
@@ -69,16 +82,19 @@ end
|
|
69
82
|
ActiveSupport::Notifications.subscribe("process_action.action_controller") do |*args|
|
70
83
|
event = ActiveSupport::Notifications::Event.new(*args)
|
71
84
|
EZmetrics.new.log(
|
72
|
-
queries: Thread.current[:queries].to_i,
|
73
|
-
db: event.payload[:db_runtime].to_f,
|
74
85
|
duration: event.duration.to_f,
|
75
|
-
|
86
|
+
views: event.payload[:view_runtime].to_f,
|
87
|
+
db: event.payload[:db_runtime].to_f,
|
88
|
+
status: event.payload[:status].to_i || 500,
|
89
|
+
queries: Thread.current[:queries].to_i,
|
76
90
|
)
|
77
91
|
end
|
78
92
|
```
|
79
93
|
|
80
94
|
### Display metrics
|
81
95
|
|
96
|
+
As simple as:
|
97
|
+
|
82
98
|
```ruby
|
83
99
|
EZmetrics.new.show
|
84
100
|
```
|
@@ -91,6 +107,10 @@ This will return a hash with the following structure:
|
|
91
107
|
avg: 5569,
|
92
108
|
max: 9675
|
93
109
|
},
|
110
|
+
views: {
|
111
|
+
avg: 12,
|
112
|
+
max: 240
|
113
|
+
},
|
94
114
|
db: {
|
95
115
|
avg: 155,
|
96
116
|
max: 4382
|
@@ -111,3 +131,177 @@ This will return a hash with the following structure:
|
|
111
131
|
}
|
112
132
|
```
|
113
133
|
|
134
|
+
### Aggregation
|
135
|
+
|
136
|
+
The aggregation can be easily configured by specifying aggregation options as in the following examples:
|
137
|
+
|
138
|
+
**1. Single**
|
139
|
+
|
140
|
+
```ruby
|
141
|
+
EZmetrics.new.show(duration: :max)
|
142
|
+
```
|
143
|
+
|
144
|
+
```ruby
|
145
|
+
{
|
146
|
+
duration: {
|
147
|
+
max: 9675
|
148
|
+
}
|
149
|
+
}
|
150
|
+
```
|
151
|
+
|
152
|
+
---
|
153
|
+
|
154
|
+
**2. Multiple**
|
155
|
+
|
156
|
+
```ruby
|
157
|
+
EZmetrics.new.show(queries: [:max, :avg])
|
158
|
+
```
|
159
|
+
|
160
|
+
```ruby
|
161
|
+
{
|
162
|
+
queries: {
|
163
|
+
max: 76,
|
164
|
+
avg: 26
|
165
|
+
}
|
166
|
+
}
|
167
|
+
```
|
168
|
+
|
169
|
+
---
|
170
|
+
|
171
|
+
**3. Requests**
|
172
|
+
|
173
|
+
```ruby
|
174
|
+
EZmetrics.new.show(requests: true)
|
175
|
+
```
|
176
|
+
|
177
|
+
```ruby
|
178
|
+
{
|
179
|
+
requests: {
|
180
|
+
all: 2000,
|
181
|
+
grouped: {
|
182
|
+
"2xx" => 1900,
|
183
|
+
"3xx" => 15,
|
184
|
+
"4xx" => 80,
|
185
|
+
"5xx" => 5
|
186
|
+
}
|
187
|
+
}
|
188
|
+
}
|
189
|
+
```
|
190
|
+
|
191
|
+
---
|
192
|
+
|
193
|
+
**4. Combined**
|
194
|
+
|
195
|
+
```ruby
|
196
|
+
EZmetrics.new.show(views: :avg, :db: [:avg, :max], requests: true)
|
197
|
+
```
|
198
|
+
|
199
|
+
```ruby
|
200
|
+
{
|
201
|
+
views: {
|
202
|
+
avg: 12
|
203
|
+
},
|
204
|
+
db: {
|
205
|
+
avg: 155,
|
206
|
+
max: 4382
|
207
|
+
},
|
208
|
+
requests: {
|
209
|
+
all: 2000,
|
210
|
+
grouped: {
|
211
|
+
"2xx" => 1900,
|
212
|
+
"3xx" => 15,
|
213
|
+
"4xx" => 80,
|
214
|
+
"5xx" => 5
|
215
|
+
}
|
216
|
+
}
|
217
|
+
}
|
218
|
+
```
|
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
|
+
|
273
|
+
### Performance
|
274
|
+
|
275
|
+
The aggregation speed relies on the performance of **Redis** (data storage) and **Oj** (json serialization/parsing).
|
276
|
+
|
277
|
+
You can check the **aggregation** time by running:
|
278
|
+
|
279
|
+
```ruby
|
280
|
+
EZmetrics::Benchmark.new.measure_aggregation
|
281
|
+
```
|
282
|
+
|
283
|
+
| Interval | Duration (seconds) |
|
284
|
+
| :------: | :----------------: |
|
285
|
+
| 1 minute | 0.0 |
|
286
|
+
| 1 hour | 0.04 |
|
287
|
+
| 12 hours | 0.49 |
|
288
|
+
| 24 hours | 1.51 |
|
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
@@ -1,135 +1,183 @@
|
|
1
|
-
require "redis"
|
2
|
-
require "
|
1
|
+
require "redis"
|
2
|
+
require "redis/connection/hiredis"
|
3
|
+
require "oj"
|
3
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
|
9
|
+
|
5
10
|
def initialize(interval_seconds=60)
|
6
11
|
@interval_seconds = interval_seconds.to_i
|
7
12
|
@redis = Redis.new
|
8
|
-
@storage_key = "ez-metrics"
|
9
13
|
end
|
10
14
|
|
11
|
-
def log(payload={
|
12
|
-
|
15
|
+
def log(payload={duration: 0.0, views: 0.0, db: 0.0, queries: 0, status: 200})
|
16
|
+
@safe_payload = {
|
17
|
+
duration: payload[:duration].to_f,
|
18
|
+
views: payload[:views].to_f,
|
13
19
|
db: payload[:db].to_f,
|
14
20
|
queries: payload[:queries].to_i,
|
15
|
-
duration: payload[:duration].to_f,
|
16
21
|
status: payload[:status].to_i
|
17
22
|
}
|
18
23
|
|
19
|
-
this_second
|
20
|
-
status_group
|
21
|
-
this_second_metrics = redis.get(
|
24
|
+
this_second = Time.now.to_i
|
25
|
+
status_group = "#{payload[:status].to_s[0]}xx"
|
26
|
+
@this_second_metrics = redis.get(this_second)
|
22
27
|
|
23
28
|
if this_second_metrics
|
24
|
-
this_second_metrics =
|
25
|
-
|
26
|
-
|
27
|
-
|
29
|
+
@this_second_metrics = Oj.load(this_second_metrics)
|
30
|
+
|
31
|
+
METRICS.each do |metrics_type|
|
32
|
+
update_sum(metrics_type)
|
33
|
+
update_max(metrics_type)
|
34
|
+
end
|
35
|
+
|
28
36
|
this_second_metrics["statuses"]["all"] += 1
|
29
37
|
this_second_metrics["statuses"][status_group] += 1
|
30
|
-
this_second_metrics["db_max"] = [payload[:db], this_second_metrics["db_max"]].max
|
31
|
-
this_second_metrics["queries_max"] = [payload[:queries], this_second_metrics["queries_max"]].max
|
32
|
-
this_second_metrics["duration_max"] = [payload[:duration], this_second_metrics["duration_max"]].max
|
33
38
|
else
|
34
|
-
this_second_metrics = {
|
35
|
-
"
|
36
|
-
"
|
37
|
-
"
|
38
|
-
"
|
39
|
-
"
|
40
|
-
"
|
39
|
+
@this_second_metrics = {
|
40
|
+
"second" => this_second,
|
41
|
+
"duration_sum" => safe_payload[:duration],
|
42
|
+
"duration_max" => safe_payload[:duration],
|
43
|
+
"views_sum" => safe_payload[:views],
|
44
|
+
"views_max" => safe_payload[:views],
|
45
|
+
"db_sum" => safe_payload[:db],
|
46
|
+
"db_max" => safe_payload[:db],
|
47
|
+
"queries_sum" => safe_payload[:queries],
|
48
|
+
"queries_max" => safe_payload[:queries],
|
41
49
|
"statuses" => { "2xx" => 0, "3xx" => 0, "4xx" => 0, "5xx" => 0, "all" => 1 }
|
42
50
|
}
|
43
51
|
|
44
52
|
this_second_metrics["statuses"][status_group] = 1
|
45
53
|
end
|
46
54
|
|
47
|
-
redis.setex(
|
48
|
-
|
55
|
+
redis.setex(this_second, interval_seconds, Oj.dump(this_second_metrics))
|
49
56
|
true
|
50
57
|
rescue => error
|
51
58
|
formatted_error(error)
|
52
59
|
end
|
53
60
|
|
54
|
-
def show
|
55
|
-
|
56
|
-
|
57
|
-
|
61
|
+
def show(options=nil)
|
62
|
+
@options = options || default_options
|
63
|
+
partitioned_metrics ? aggregate_partitioned_data : aggregate_data
|
64
|
+
end
|
58
65
|
|
59
|
-
|
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
|
60
71
|
|
61
|
-
|
72
|
+
private
|
62
73
|
|
63
|
-
|
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?
|
79
|
+
@requests = interval_metrics.sum { |hash| hash["statuses"]["all"] }
|
80
|
+
build_result
|
64
81
|
rescue
|
65
|
-
|
82
|
+
{}
|
66
83
|
end
|
67
84
|
|
68
|
-
|
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
|
69
94
|
|
70
|
-
|
95
|
+
def build_result
|
96
|
+
result = {}
|
97
|
+
|
98
|
+
result[:requests] = { all: requests, grouped: count_all_status_groups } if options[:requests]
|
99
|
+
|
100
|
+
options.each do |metrics, aggregation_functions|
|
101
|
+
next unless METRICS.include?(metrics)
|
102
|
+
aggregation_functions = [aggregation_functions] unless aggregation_functions.is_a?(Array)
|
103
|
+
next unless aggregation_functions.any?
|
104
|
+
|
105
|
+
aggregation_functions.each do |aggregation_function|
|
106
|
+
result[metrics] ||= {}
|
107
|
+
result[metrics][aggregation_function] = aggregate(metrics, aggregation_function)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
result
|
111
|
+
ensure
|
112
|
+
result
|
113
|
+
end
|
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
|
+
|
131
|
+
def aggregate(metrics, aggregation_function)
|
132
|
+
return unless AGGREGATION_FUNCTIONS.include?(aggregation_function)
|
133
|
+
return avg("#{metrics}_sum") if aggregation_function == :avg
|
134
|
+
return max("#{metrics}_max") if aggregation_function == :max
|
135
|
+
end
|
136
|
+
|
137
|
+
def update_sum(metrics)
|
138
|
+
this_second_metrics["#{metrics}_sum"] += safe_payload[metrics]
|
139
|
+
end
|
140
|
+
|
141
|
+
def update_max(metrics)
|
142
|
+
max_value = [safe_payload[metrics], this_second_metrics["#{metrics}_max"]].max
|
143
|
+
this_second_metrics["#{metrics}_max"] = max_value
|
144
|
+
end
|
71
145
|
|
72
146
|
def avg(metrics)
|
73
|
-
(interval_metrics.
|
147
|
+
(interval_metrics.sum { |h| h[metrics] }.to_f / requests).round
|
74
148
|
end
|
75
149
|
|
76
150
|
def max(metrics)
|
77
|
-
interval_metrics.
|
151
|
+
interval_metrics.max { |h| h[metrics] }[metrics].round
|
78
152
|
end
|
79
153
|
|
80
|
-
def
|
81
|
-
interval_metrics.
|
154
|
+
def count_all_status_groups
|
155
|
+
interval_metrics.inject({ "2xx" => 0, "3xx" => 0, "4xx" => 0, "5xx" => 0 }) do |result, h|
|
156
|
+
result["2xx"] += h["statuses"]["2xx"]
|
157
|
+
result["3xx"] += h["statuses"]["3xx"]
|
158
|
+
result["4xx"] += h["statuses"]["4xx"]
|
159
|
+
result["5xx"] += h["statuses"]["5xx"]
|
160
|
+
result
|
161
|
+
end
|
82
162
|
end
|
83
163
|
|
84
|
-
def
|
164
|
+
def default_options
|
85
165
|
{
|
86
|
-
|
87
|
-
|
88
|
-
|
166
|
+
duration: AGGREGATION_FUNCTIONS,
|
167
|
+
views: AGGREGATION_FUNCTIONS,
|
168
|
+
db: AGGREGATION_FUNCTIONS,
|
169
|
+
queries: AGGREGATION_FUNCTIONS,
|
170
|
+
requests: true
|
89
171
|
}
|
90
172
|
end
|
91
173
|
|
92
|
-
def
|
174
|
+
def formatted_error(error)
|
93
175
|
{
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
},
|
98
|
-
db: {
|
99
|
-
avg: avg(:db_sum),
|
100
|
-
max: max(:db_max)
|
101
|
-
},
|
102
|
-
queries: {
|
103
|
-
avg: avg(:queries_sum),
|
104
|
-
max: max(:queries_max)
|
105
|
-
},
|
106
|
-
requests: {
|
107
|
-
all: requests,
|
108
|
-
grouped: {
|
109
|
-
"2xx" => count("2xx"),
|
110
|
-
"3xx" => count("3xx"),
|
111
|
-
"4xx" => count("4xx"),
|
112
|
-
"5xx" => count("5xx")
|
113
|
-
}
|
114
|
-
}
|
176
|
+
error: error.class.name,
|
177
|
+
message: error.message,
|
178
|
+
backtrace: error.backtrace.reject { |line| line.match(/ruby|gems/) }
|
115
179
|
}
|
116
180
|
end
|
181
|
+
end
|
117
182
|
|
118
|
-
|
119
|
-
{
|
120
|
-
duration: {
|
121
|
-
avg: 0,
|
122
|
-
max: 0
|
123
|
-
},
|
124
|
-
db: {
|
125
|
-
avg: 0,
|
126
|
-
max: 0
|
127
|
-
},
|
128
|
-
queries: {
|
129
|
-
avg: 0,
|
130
|
-
max: 0
|
131
|
-
},
|
132
|
-
requests: {}
|
133
|
-
}
|
134
|
-
end
|
135
|
-
end
|
183
|
+
require "ezmetrics/benchmark"
|
@@ -0,0 +1,95 @@
|
|
1
|
+
require "benchmark"
|
2
|
+
|
3
|
+
class EZmetrics::Benchmark
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@start = Time.now.to_i
|
7
|
+
@redis = Redis.new
|
8
|
+
@durations = []
|
9
|
+
@iterations = 3
|
10
|
+
@intervals = {
|
11
|
+
"1.minute" => 60,
|
12
|
+
"1.hour " => 3600,
|
13
|
+
"12.hours" => 43200,
|
14
|
+
"24.hours" => 86400,
|
15
|
+
"48.hours" => 172800
|
16
|
+
}
|
17
|
+
end
|
18
|
+
|
19
|
+
def measure_aggregation(partition_by=nil)
|
20
|
+
write_metrics
|
21
|
+
print_header
|
22
|
+
intervals.each do |interval, seconds|
|
23
|
+
result = measure_aggregation_time(interval, seconds, partition_by)
|
24
|
+
print_row(result)
|
25
|
+
end
|
26
|
+
cleanup_metrics
|
27
|
+
print_footer
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
attr_reader :start, :redis, :durations, :intervals, :iterations
|
33
|
+
|
34
|
+
def write_metrics
|
35
|
+
seconds = intervals.values.max
|
36
|
+
seconds.times do |i|
|
37
|
+
second = start - i
|
38
|
+
payload = {
|
39
|
+
"second" => second,
|
40
|
+
"duration_sum" => rand(10000),
|
41
|
+
"duration_max" => rand(10000),
|
42
|
+
"views_sum" => rand(1000),
|
43
|
+
"views_max" => rand(1000),
|
44
|
+
"db_sum" => rand(8000),
|
45
|
+
"db_max" => rand(8000),
|
46
|
+
"queries_sum" => rand(100),
|
47
|
+
"queries_max" => rand(100),
|
48
|
+
"statuses" => {
|
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)
|
54
|
+
}
|
55
|
+
}
|
56
|
+
redis.setex(second, seconds, Oj.dump(payload))
|
57
|
+
end
|
58
|
+
nil
|
59
|
+
end
|
60
|
+
|
61
|
+
def cleanup_metrics
|
62
|
+
interval_start = Time.now.to_i - intervals.values.max - 100
|
63
|
+
interval_keys = (interval_start..Time.now.to_i).to_a
|
64
|
+
redis.del(interval_keys)
|
65
|
+
end
|
66
|
+
|
67
|
+
def measure_aggregation_time(interval, seconds, partition_by)
|
68
|
+
iterations.times do
|
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
|
76
|
+
end
|
77
|
+
|
78
|
+
return {
|
79
|
+
interval: interval.gsub(".", " "),
|
80
|
+
duration: (durations.sum.to_f / iterations).round(2)
|
81
|
+
}
|
82
|
+
end
|
83
|
+
|
84
|
+
def print_header
|
85
|
+
print "\n#{'─'*31}\n| Interval | Duration (seconds)\n#{'─'*31}\n"
|
86
|
+
end
|
87
|
+
|
88
|
+
def print_row(result)
|
89
|
+
print "| #{result[:interval]} | #{result[:duration]}\n"
|
90
|
+
end
|
91
|
+
|
92
|
+
def print_footer
|
93
|
+
print "#{'─'*31}\n"
|
94
|
+
end
|
95
|
+
end
|
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.0
|
4
|
+
version: 1.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nicolae Rotaru
|
@@ -24,6 +24,34 @@ dependencies:
|
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '4.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: hiredis
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.6.3
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 0.6.3
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: oj
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.10'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.10'
|
27
55
|
- !ruby/object:Gem::Dependency
|
28
56
|
name: rspec
|
29
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -38,7 +66,7 @@ dependencies:
|
|
38
66
|
- - "~>"
|
39
67
|
- !ruby/object:Gem::Version
|
40
68
|
version: '3.5'
|
41
|
-
description:
|
69
|
+
description: Simple, lightweight and fast metrics aggregation for Rails.
|
42
70
|
email: nyku.rn@gmail.com
|
43
71
|
executables: []
|
44
72
|
extensions: []
|
@@ -47,10 +75,12 @@ files:
|
|
47
75
|
- LICENSE
|
48
76
|
- README.md
|
49
77
|
- lib/ezmetrics.rb
|
50
|
-
|
78
|
+
- lib/ezmetrics/benchmark.rb
|
79
|
+
homepage: https://nyku.github.io/ezmetrics
|
51
80
|
licenses:
|
52
81
|
- GPL-3.0
|
53
|
-
metadata:
|
82
|
+
metadata:
|
83
|
+
source_code_uri: https://github.com/nyku/ezmetrics
|
54
84
|
post_install_message:
|
55
85
|
rdoc_options: []
|
56
86
|
require_paths:
|
@@ -66,8 +96,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
66
96
|
- !ruby/object:Gem::Version
|
67
97
|
version: '0'
|
68
98
|
requirements: []
|
69
|
-
|
70
|
-
rubygems_version: 2.6.13
|
99
|
+
rubygems_version: 3.0.6
|
71
100
|
signing_key:
|
72
101
|
specification_version: 4
|
73
102
|
summary: Rails metrics aggregation tool.
|