ezmetrics
Simple, lightweight and fast metrics aggregation for Rails.
Installation
gem 'ezmetrics'
Available metrics
Type | Aggregate functions |
---|---|
duration |
avg , max , percentile |
views |
avg , max , percentile |
db |
avg , max , percentile |
queries |
avg , max , percentile |
requests |
all , 2xx , 3xx , 4xx , 5xx |
Usage
Getting started
This tool captures and aggregates Rails application metrics such as
duration
views
db
queries
status
and stores them for the timeframe you specified, 60 seconds by default.
You can change the timeframe according to your needs and save the metrics by calling log
method:
# Store the metrics for 60 seconds (default behaviour)
EZmetrics.new.log(
duration: 100.5,
views: 40.7,
db: 59.8,
queries: 4,
status: 200
)
# Store the metrics for 10 minutes
EZmetrics.new(10.minutes).log(
duration: 100.5,
views: 40.7,
db: 59.8,
queries: 4,
status: 200
)
For displaying metrics you need to call show
method:
# Aggregate and show metrics for last 60 seconds (default behaviour)
EZmetrics.new.show
# Aggregate and show metrics for last 10 minutes
EZmetrics.new(10.minutes).show
You can combine these timeframes, for example - store for 10 minutes, display for 5 minutes.
Capture metrics
Just add an initializer to your application:
# config/initializers/ezmetrics.rb
ActiveSupport::Notifications.subscribe("sql.active_record") do |*args|
event = ActiveSupport::Notifications::Event.new(*args)
unless event.payload[:name] == "SCHEMA"
Thread.current[:queries] ||= 0
Thread.current[:queries] += 1
end
end
ActiveSupport::Notifications.subscribe("process_action.action_controller") do |*args|
event = ActiveSupport::Notifications::Event.new(*args)
EZmetrics.new.log(
duration: event.duration.to_f,
views: event.payload[:view_runtime].to_f,
db: event.payload[:db_runtime].to_f,
status: event.payload[:status].to_i || 500,
queries: Thread.current[:queries].to_i,
)
end
Display metrics
As simple as:
EZmetrics.new.show
This will return a hash with the following structure:
{
duration: {
avg: 5569,
max: 9675
},
views: {
avg: 12,
max: 240
},
db: {
avg: 155,
max: 4382
},
queries: {
avg: 26,
max: 76
},
requests: {
all: 2000,
grouped: {
"2xx" => 1900,
"3xx" => 15,
"4xx" => 80,
"5xx" => 5
}
}
}
If you prefer a single level object - you can change the default output structure by calling .flatten
before .show
EZmetrics.new(1.hour).flatten.show(db: :avg, duration: [:avg, :max])
{
db_avg: 182,
duration_avg: 205,
duration_max: 5171
}
Same for partitioned aggregation
EZmetrics.new(1.hour).partition_by(:minute).flatten.show(db: :avg, duration: [:avg, :max])
[
{
timestamp: 1575242880,
db_avg: 387,
duration_avg: 477,
duration_max: 8566
},
{
timestamp: 1575242940,
db_avg: 123,
duration_avg: 234,
duration_max: 3675
}
]
Aggregation
The aggregation can be easily configured by specifying aggregation options as in the following examples:
1. Single
EZmetrics.new.show(duration: :max)
{
duration: {
max: 9675
}
}
2. Multiple
EZmetrics.new.show(queries: [:max, :avg])
{
queries: {
max: 76,
avg: 26
}
}
3. Requests
EZmetrics.new.show(requests: true)
{
requests: {
all: 2000,
grouped: {
"2xx" => 1900,
"3xx" => 15,
"4xx" => 80,
"5xx" => 5
}
}
}
4. Combined
EZmetrics.new.show(views: :avg, :db: [:avg, :max], requests: true)
{
views: {
avg: 12
},
db: {
avg: 155,
max: 4382
},
requests: {
all: 2000,
grouped: {
"2xx" => 1900,
"3xx" => 15,
"4xx" => 80,
"5xx" => 5
}
}
}
5. Percentile
This feature is available since version 2.0.0
.
By default percentile aggregation is turned off because it requires to store each value of all metrics.
To enable this feature - you need to set store_each_value: true
when saving the metrics:
EZmetrics.new.log(
duration: 100.5,
views: 40.7,
db: 59.8,
queries: 4,
status: 200,
store_each_value: true
)
The aggregation syntax has the following format metrics_type: :percentile_{number}
where number
is any integer in the 1..99 range.
EZmetrics.new.show(db: [:avg, :percentile_90, :percentile_95], duration: :percentile_99)
{
db: {
avg: 155,
percentile_90: 205,
percentile_95: 215
},
duration: {
percentile_99: 236
}
}
6. Percentile distribution
If you want to visualize percentile distribution (from 1% to 99%):
EZmetrics.new.show(duration: :percentile_distribution)
{
duration: {
percentile_distribution: {
1: 12,
2: 15,
3: 19,
#...
97: 6540,
98: 6682,
99: 6730
}
}
}
Partitioning
If you want to visualize your metrics by using a line chart, you will need to use partitioning.
To aggregate metrics, partitioned by a unit of time you need to call .partition_by({time_unit})
before calling .show
# Aggregate metrics for last hour, partition by minute
EZmetrics.new(1.hour).partition_by(:minute).show(duration: [:avg, :max], db: :avg)
This will return an array of objects with the following structure:
[
{
timestamp: # UNIX timestamp
data: # a hash with aggregated metrics
}
]
like in the example below:
[
{
timestamp: 1575242880,
data: {
duration: {
avg: 477,
max: 8566
},
db: {
avg: 387
}
}
},
{
timestamp: 1575242940,
data: {
duration: {
avg: 234,
max: 3675
},
db: {
avg: 123
}
}
}
]
Available time units for partitioning: second
, minute
, hour
, day
. Default: minute
.
Performance
The aggregation speed relies on the performance of Redis (data storage) and Oj (json serialization/parsing).
You can check the aggregation time (in seconds) by running:
# 1. Simple
EZmetrics::Benchmark.new.measure_aggregation
# 2. Partitioned
EZmetrics::Benchmark.new.measure_aggregation(:minute)
# 3. Percentile
EZmetrics::Benchmark.new(true).measure_aggregation
# 4. Percentile (partitioned)
EZmetrics::Benchmark.new(true).measure_aggregation(:minute)
Interval | Simple aggregation | Partitioned | Percentile | Percentile (partitioned) |
---|---|---|---|---|
1 minute | 0.0 | 0.0 | 0.0 | 0.0 |
1 hour | 0.02 | 0.02 | 0.14 | 0.16 |
12 hours | 0.22 | 0.25 | 2.11 | 1.97 |
24 hours | 0.61 | 0.78 | 5.85 | 5.85 |
48 hours | 1.42 | 1.75 | 14.1 | 13.9 |
The benchmarks above were run on a 2017 Macbook Pro 2.9 GHz Intel Core i7 with 16 GB of RAM
License
ezmetrics is released under the MIT License.