checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
---
2
- SHA1:
3
- metadata.gz: dc9e49c32e99f5d45f45ca6d3d467e53c09b8502
4
- data.tar.gz: ce9f340d9f70ddfd7aebbac487e2d1b4b094527f
2
+ SHA256:
3
+ metadata.gz: 5ba2e24bca76b2786c1264b779e72764e123094150855cae241d673505df95ad
4
+ data.tar.gz: 3c297b92a53c51b8b881fbbafb15929b9bac36eb53164c3faab37c2caa72db02
5
5
SHA512:
6
- metadata.gz: 295ae4b987d80ec5feb6cbb21e0f5dee0ea6523d140e50840b5637b57c3263f712b5c798eed3c69794fb8618a5236e7f6005a1fee34f4e2f6e8812f97ab636e7
7
- data.tar.gz: 8c9523d1385036e83a1eaea18e275c91f2865c391ad60f50a2af76b865843338e1d4408e8f08f037c2af14ba552181f7a82e85be7ea5b7242755f2c4070fb418
6
+ metadata.gz: 4d1e8118d48cac15dca166c6c4f0d8e38344b28bfcc85343fa87ff4877d34bc7f12b9841241de563723261df5a48210cf1838c09eece3bc15d5f2dde6a58c8c2
7
+ data.tar.gz: 8e62b7b53f73a6343c8fd6f11e0e8a9f856314dd262c10611cd9bf061636c8801255de58818a1736069679525bed73c949e6f61778910de8a306da30c352e2a2
data/README.md CHANGED
@@ -4,7 +4,6 @@
4
4
5
5
A simple tool for capturing and displaying Rails metrics.
6
6
7
-
8
7
## Installation
9
8
10
9
```
@@ -16,6 +15,7 @@ gem 'ezmetrics'
16
15
### Getting started
17
16
18
17
This tool captures and aggregates Rails application metrics such as
18
+
19
19
- `duration`
20
20
- `views`
21
21
- `db`
@@ -30,6 +30,7 @@ You can change the timeframe according to your needs and save the metrics by cal
30
30
# Store the metrics for 60 seconds (default behaviour)
31
31
EZmetrics.new.log(duration: 100.5, views: 40.7, db: 59.8, queries: 4, status: 200)
32
32
```
33
+
33
34
or
34
35
35
36
```ruby
@@ -53,7 +54,6 @@ or
53
54
54
55
> Please note that you can combine these timeframes, for example - store for 10 minutes, display for 5 minutes.
55
56
56
-
57
57
### Capture metrics
58
58
59
59
Just add an initializer to your application:
@@ -121,6 +121,86 @@ This will return a hash with the following structure:
121
121
}
122
122
```
123
123
124
+ ### Output configuration
125
+
126
+ The output can be easily configured by specifying aggregation options as in the following examples:
127
+
128
+ 1. Single
129
+
130
+ ```ruby
131
+ EZmetrics.new.show(duration: :max)
132
+ ```
133
+
134
+ ```ruby
135
+ {
136
+ duration: {
137
+ max: 9675
138
+ }
139
+ }
140
+ ```
141
+
142
+ 2. Multiple
143
+
144
+ ```ruby
145
+ EZmetrics.new.show(queries: [:max, :avg])
146
+ ```
147
+
148
+ ```ruby
149
+ {
150
+ queries: {
151
+ max: 76,
152
+ avg: 26
153
+ }
154
+ }
155
+ ```
156
+
157
+ 3. Requests
158
+
159
+ ```ruby
160
+ EZmetrics.new.show(requests: true)
161
+ ```
162
+
163
+ ```ruby
164
+ {
165
+ requests: {
166
+ all: 2000,
167
+ grouped: {
168
+ "2xx" => 1900,
169
+ "3xx" => 15,
170
+ "4xx" => 80,
171
+ "5xx" => 5
172
+ }
173
+ }
174
+ }
175
+ ```
176
+
177
+ 4. Combined
178
+
179
+ ```ruby
180
+ EZmetrics.new.show(views: :avg, :db: [:avg, :max], requests: true)
181
+ ```
182
+
183
+ ```ruby
184
+ {
185
+ views: {
186
+ avg: 12
187
+ },
188
+ db: {
189
+ avg: 155,
190
+ max: 4382
191
+ },
192
+ requests: {
193
+ all: 2000,
194
+ grouped: {
195
+ "2xx" => 1900,
196
+ "3xx" => 15,
197
+ "4xx" => 80,
198
+ "5xx" => 5
199
+ }
200
+ }
201
+ }
202
+ ```
203
+
124
204
### Performance
125
205
126
206
The implementation is based on **Redis** commands such as:
@@ -137,11 +217,12 @@ You can check the **aggregation** time by running:
137
217
EZmetrics::Benchmark.new.measure_aggregation
138
218
```
139
219
140
- The result of running this benchmark on a *2017 Macbook Pro 2.9 GHz Intel Core i7 with 16 GB of RAM*:
220
+ The result of running this benchmark on a _2017 Macbook Pro 2.9 GHz Intel Core i7 with 16 GB of RAM_:
141
221
142
222
| Interval | Duration (seconds) |
143
- |:--------:|:------------------:|
223
+ | :------: | :----------------: |
144
224
| 1 minute | 0.0 |
145
- | 1 hour | 0.11 |
146
- | 12 hours | 1.6 |
147
- | 24 hours | 3.5 |
225
+ | 1 hour | 0.05 |
226
+ | 12 hours | 0.66 |
227
+ | 24 hours | 1.83 |
228
+ | 48 hours | 4.06 |
data/lib/ezmetrics.rb CHANGED
@@ -1,7 +1,11 @@
1
- require "redis" unless defined?(Redis)
2
- require "json" unless defined?(JSON)
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
+
5
9
def initialize(interval_seconds=60)
6
10
@interval_seconds = interval_seconds.to_i
7
11
@redis = Redis.new
@@ -22,9 +26,9 @@ class EZmetrics
22
26
@this_second_metrics = redis.get("#{storage_key}:#{this_second}")
23
27
24
28
if this_second_metrics
25
- @this_second_metrics = JSON.parse(this_second_metrics)
29
+ @this_second_metrics = Oj.load(this_second_metrics)
26
30
27
- [:duration, :views, :db, :queries].each do |metrics_type|
31
+ METRICS.each do |metrics_type|
28
32
update_sum(metrics_type)
29
33
update_max(metrics_type)
30
34
end
@@ -47,31 +51,66 @@ class EZmetrics
47
51
this_second_metrics["statuses"][status_group] = 1
48
52
end
49
53
50
- redis.setex("#{storage_key}:#{this_second}", interval_seconds, JSON.generate(this_second_metrics))
54
+ redis.setex("#{storage_key}:#{this_second}", interval_seconds, Oj.dump(this_second_metrics))
51
-
52
55
true
53
56
rescue => error
54
57
formatted_error(error)
55
58
end
56
59
57
- def show
60
+ def show(options=nil)
61
+ @options = options || default_options
58
62
interval_start = Time.now.to_i - interval_seconds
59
63
interval_keys = (interval_start..Time.now.to_i).to_a.map { |second| "#{storage_key}:#{second}" }
60
- @interval_metrics = redis.mget(interval_keys).compact.map { |hash| JSON.parse(hash) }
64
+ @interval_metrics = redis.mget(interval_keys).compact.map { |hash| Oj.load(hash) }
61
65
62
- return empty_metrics_object unless interval_metrics.any?
66
+ return {} unless interval_metrics.any?
63
67
64
68
@requests = interval_metrics.map { |hash| hash["statuses"]["all"] }.compact.sum
65
-
69
+ build_result
66
- metrics_object
67
70
rescue
68
- empty_metrics_object
71
+ {}
69
72
end
70
73
71
74
private
72
75
73
- attr_reader :redis, :interval_seconds, :interval_metrics, :requests, :storage_key,
74
- :safe_payload, :this_second_metrics
76
+ attr_reader :redis, :interval_seconds, :interval_metrics, :requests,
77
+ :storage_key, :safe_payload, :this_second_metrics, :options
78
+
79
+ def build_result
80
+ result = {}
81
+
82
+ if options[:requests]
83
+ result[:requests] = {
84
+ all: requests,
85
+ grouped: {
86
+ "2xx" => count("2xx"),
87
+ "3xx" => count("3xx"),
88
+ "4xx" => count("4xx"),
89
+ "5xx" => count("5xx")
90
+ }
91
+ }
92
+ end
93
+
94
+ options.each do |metrics, aggregation_functions|
95
+ next unless METRICS.include?(metrics)
96
+ aggregation_functions = [aggregation_functions] unless aggregation_functions.is_a?(Array)
97
+ next unless aggregation_functions.any?
98
+
99
+ aggregation_functions.each do |aggregation_function|
100
+ result[metrics] ||= {}
101
+ result[metrics][aggregation_function] = aggregate(metrics, aggregation_function)
102
+ end
103
+ end
104
+ result
105
+ ensure
106
+ result
107
+ end
108
+
109
+ def aggregate(metrics, aggregation_function)
110
+ return unless AGGREGATION_FUNCTIONS.include?(aggregation_function)
111
+ return avg("#{metrics}_sum".to_sym) if aggregation_function == :avg
112
+ return max("#{metrics}_max".to_sym) if aggregation_function == :max
113
+ end
75
114
76
115
def update_sum(metrics)
77
116
this_second_metrics["#{metrics}_sum"] += safe_payload[metrics.to_sym]
@@ -94,65 +133,23 @@ class EZmetrics
94
133
interval_metrics.map { |h| h["statuses"][group.to_s] }.sum
95
134
end
96
135
97
- def formatted_error(error)
136
+ def default_options
98
137
{
99
- error: error.class.name,
100
- message: error.message,
101
- backtrace: error.backtrace.reject { |line| line.match(/ruby|gems/) }
138
+ duration: AGGREGATION_FUNCTIONS,
139
+ views: AGGREGATION_FUNCTIONS,
140
+ db: AGGREGATION_FUNCTIONS,
141
+ queries: AGGREGATION_FUNCTIONS,
142
+ requests: true
102
143
}
103
144
end
104
145
105
- def metrics_object
146
+ def formatted_error(error)
106
- {
107
- duration: {
108
- avg: avg(:duration_sum),
109
- max: max(:duration_max)
110
- },
111
- views: {
112
- avg: avg(:views_sum),
113
- max: max(:views_max)
114
- },
115
- db: {
116
- avg: avg(:db_sum),
117
- max: max(:db_max)
118
- },
119
- queries: {
120
- avg: avg(:queries_sum),
121
- max: max(:queries_max)
122
- },
123
- requests: {
124
- all: requests,
125
- grouped: {
126
- "2xx" => count("2xx"),
127
- "3xx" => count("3xx"),
128
- "4xx" => count("4xx"),
129
- "5xx" => count("5xx")
130
- }
131
- }
132
- }
133
- end
134
-
135
- def empty_metrics_object
136
147
{
137
- duration: {
138
- avg: 0,
139
- max: 0
148
+ error: error.class.name,
149
+ message: error.message,
150
+ backtrace: error.backtrace.reject { |line| line.match(/ruby|gems/) }
140
- },
141
- views: {
142
- avg: 0,
143
- max: 0
144
- },
145
- db: {
146
- avg: 0,
147
- max: 0
148
- },
149
- queries: {
150
- avg: 0,
151
- max: 0
152
- },
153
- requests: {}
154
151
}
155
152
end
156
153
end
157
154
158
- require "ezmetrics/benchmark"
155
+ require "ezmetrics/benchmark"
data/lib/ezmetrics/benchmark.rb CHANGED
@@ -11,7 +11,8 @@ class EZmetrics::Benchmark
11
11
"1.minute" => 60,
12
12
"1.hour " => 3600,
13
13
"12.hours" => 43200,
14
- "24.hours" => 86400
14
+ "24.hours" => 86400,
15
+ "48.hours" => 172800
15
16
}
16
17
end
17
18
@@ -35,15 +36,15 @@ class EZmetrics::Benchmark
35
36
seconds.times do |i|
36
37
second = start - i
37
38
payload = {
38
- "duration_sum" => rand(10000),
39
- "duration_max" => rand(10000),
40
- "views_sum" => rand(1000),
41
- "views_max" => rand(1000),
42
- "db_sum" => rand(8000),
43
- "db_max" => rand(8000),
44
- "queries_sum" => rand(100),
45
- "queries_max" => rand(100),
46
- "statuses" => {
39
+ "duration_sum" => rand(10000),
40
+ "duration_max" => rand(10000),
41
+ "views_sum" => rand(1000),
42
+ "views_max" => rand(1000),
43
+ "db_sum" => rand(8000),
44
+ "db_max" => rand(8000),
45
+ "queries_sum" => rand(100),
46
+ "queries_max" => rand(100),
47
+ "statuses" => {
47
48
"2xx" => rand(10),
48
49
"3xx" => rand(10),
49
50
"4xx" => rand(10),
@@ -51,7 +52,7 @@ class EZmetrics::Benchmark
51
52
"all" => rand(40)
52
53
}
53
54
}
54
- redis.setex("ez-metrics:#{second}", seconds, JSON.generate(payload))
55
+ redis.setex("ez-metrics:#{second}", seconds, Oj.dump(payload))
55
56
end
56
57
nil
57
58
end
@@ -84,4 +85,4 @@ class EZmetrics::Benchmark
84
85
def print_footer
85
86
print "#{'─'*31}\n"
86
87
end
87
- end
88
+ 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.6
4
+ version: 1.1.0
5
5
platform: ruby
6
6
authors:
7
7
- Nicolae Rotaru
@@ -25,6 +25,34 @@ dependencies:
25
25
- !ruby/object:Gem::Version
26
26
version: '4.0'
27
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'
55
+ - !ruby/object:Gem::Dependency
28
56
name: rspec
29
57
requirement: !ruby/object:Gem::Requirement
30
58
requirements:
@@ -67,8 +95,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
67
95
- !ruby/object:Gem::Version
68
96
version: '0'
69
97
requirements: []
70
- rubyforge_project:
98
+ rubygems_version: 3.0.6
71
- rubygems_version: 2.6.13
72
99
signing_key:
73
100
specification_version: 4
74
101
summary: Rails metrics aggregation tool.