ezmetrics 1.1.1 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 518d9350ed026801311bdc695857fdd057856f6b7fe40715bf8072c990e8e5b8
4
- data.tar.gz: 7d36fbd3e63994789ce8ac04212fb44c30041fd33f7173fbfdf1a6257beea99a
3
+ metadata.gz: 0affbdfcda841ffe4eec9105ded5526b13d209ccf0aa564e1b84c91651079d0e
4
+ data.tar.gz: 8c0f3879ee0f2cedeba7f6b0526f679ef9962e2270c7639837821f226a0f3580
5
5
  SHA512:
6
- metadata.gz: 42e990916d474ed52f08330bd7210b4c6f464a0199354cfc1fe3710817b9d57790d445d0d1f88c8fccd6daeb627638a80dd8b578c685544f6a4f9c7765522a5f
7
- data.tar.gz: 5b49eec021acaef39af2c9401681fa85f02d368f36231a60a64faf595e5b0c1432ce07a67fb86ce9c8b3041835fd4480da86ea5d91f7ca0939ef5b40592e6a49
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
- A simple tool for capturing and displaying Rails metrics.
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(duration: 100.5, views: 40.7, db: 59.8, queries: 4, status: 200)
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(duration: 100.5, views: 40.7, db: 59.8, queries: 4, status: 200)
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
- > Please note that you can combine these timeframes, for example - store for 10 minutes, display for 5 minutes.
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 options
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_
@@ -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 = options || default_options
61
- interval_start = Time.now.to_i - interval_seconds
62
- interval_keys = (interval_start..Time.now.to_i).to_a
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
- return {} unless interval_metrics.any?
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
- private
74
-
75
- attr_reader :redis, :interval_seconds, :interval_metrics, :requests,
76
- :storage_key, :safe_payload, :this_second_metrics, :options
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
@@ -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 { EZmetrics.new(seconds).show }.real
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.1.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: A simple tool for capturing and displaying Rails metrics.
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.com/nyku/ezmetrics
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: