DDMetrics
DDMetrics is a Ruby library for recording and analysing measurements in short-running Ruby processes.
If you are looking for a full-featured timeseries monitoring system, look no further than Prometheus.
Example
Take the following (naïve) cache implementation as a starting point:
class Cache
def initialize
@map = {}
end
def []=(key, value)
@map[key] = value
end
def [](key)
@map[key]
end
end
To start instrumenting this code, require ddmetrics
, create a counter, and record some metrics:
require 'ddmetrics'
class Cache
attr_reader :counter
def initialize
@map = {}
@counter = DDMetrics::Counter.new
end
def []=(key, value)
@counter.increment(type: :set)
@map[key] = value
end
def [](key)
if @map.key?(key)
@counter.increment(type: :get_hit)
else
@counter.increment(type: :get_miss)
end
@map[key]
end
end
Let’s construct a cache and exercise it:
cache = Cache.new
cache['greeting']
cache['greeting']
cache['greeting'] = 'Hi there!'
cache['greeting']
cache['greeting']
cache['greeting']
Finally, get the recorded values:
cache.counter.get(type: :set) # => 1
cache.counter.get(type: :get_hit) # => 3
cache.counter.get(type: :get_miss) # => 2
Or even print all stats:
puts cache.counter
type │ count
─────────┼──────
get_hit │ 3
get_miss │ 2
set │ 1
Scope
-
No timeseries: Metrics are not recorded over time. If you want to record timeseries data, consider using Prometheus.
-
Not intended for long-running processes: Metrics data (particularly summary metrics) can accumulate in memory and cause memory pressure. This project is not suited for long-running processes, such as servers. For monitoring long-running processes, consider using Prometheus.
-
Not thread-safe: The implementation is not thread-safe. If you require thread safety, consider wrapping the functionality provided.
Installation
Add this line to your application's Gemfile:
gem 'ddmetrics'
And then execute:
$ bundle
Or install it yourself as:
$ gem install ddmetrics
Usage
DDMetrics provides two metric types:
-
A counter is an integer metric that only ever increases. Examples: cache hits, number of files written, …
-
A summary records observations, and provides functionality for describing the distribution of the observations through quantiles. Examples: outgoing request durations, size of written files, …
Each metric is recorded with a label, which is a hash that is useful to further refine the kind of data that is being recorded. For example:
cache_hits_counter.increment(target: :file_cache)
request_durations_summary.observe(1.07, api: :weather)
Counters
To create a counter, instantiate DDMetrics::Counter
:
counter = DDMetrics::Counter.new
To increment a counter, call #increment
with a label:
counter.increment(target: :file_cache)
To get the value for a certain label, use #get
:
counter.get(target: :file_cache)
# => 1
Summaries
To create a summary, instantiate DDMetrics::Summary
:
summary = DDMetrics::Summary.new
To observe a value, call #observe
with the value to observe, along with a label:
summary.observe(0.88, api: :weather)
summary.observe(1.07, api: :weather)
summary.observe(0.91, api: :weather)
To get the list of observations for a certain label, use #get
, which will return a DDMetrics::Stats
instance:
summary.get(api: :weather)
# => <DDMetrics::Stats>
The following methods are available on DDMetrics::Stats
:
-
#count
: returns the number of values -
#sum
: returns the sum of all values -
#avg
: returns the average of all values -
#min
: returns the minimum value -
#max
: returns the maximum value -
#quantile(fraction)
: returns the quantile at the given fraction (0.0 – 1.0)
Printing metrics
To print a metric, use #to_s
. For example:
summary = DDMetrics::Summary.new
summary.observe(2.1, filter: :erb)
summary.observe(4.1, filter: :erb)
summary.observe(5.3, filter: :haml)
puts summary
Output:
filter │ count min .50 .90 .95 max tot
───────┼────────────────────────────────────────────────
erb │ 2 2.10 3.10 3.90 4.00 4.10 6.20
haml │ 1 5.30 5.30 5.30 5.30 5.30 5.30
Stopwatch
The DDMetrics::Stopwatch
class can be used to measure durations. Use #start
and #stop
to start and stop the stopwatch, respectively, and #duration
to read the value of the stopwatch:
stopwatch = DDMetrics::Stopwatch.new
stopwatch.start
sleep 1
stopwatch.stop
puts "That took #{stopwatch.duration}s."
# Output: That took 1.006831s.
A stopwatch, once created, will never reset its duration. Running the stopwatch again will add to the existing duration:
stopwatch.start
sleep 1
stopwatch.stop
puts "That took #{stopwatch.duration}s."
# Output: That took 2.012879s.
You can use #run
with a block for convenience:
stopwatch.run do
sleep 1
end
#run
will return the value of the block:
result = stopwatch.run do
sleep 1
'Donkey'
end
puts result
# Output: Donkey
You can query whether or not a stopwatch is running using #running?
; #stopped?
is the opposite of #running?
.
Development
Install dependencies:
$ bundle
Run tests:
$ bundle exec rake
Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/ddfreyne/ddmetrics. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the Contributor Covenant code of conduct.
License
The gem is available as open source under the terms of the MIT License.
Code of Conduct
Everyone interacting in the DDMetrics project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.