[![Gem version](https://img.shields.io/gem/v/ddmetrics.svg)](http://rubygems.org/gems/ddmetrics)
[![Gem downloads](https://img.shields.io/gem/dt/ddmetrics.svg)](http://rubygems.org/gems/ddmetrics)
[![Build status](https://img.shields.io/travis/ddfreyne/ddmetrics.svg)](https://travis-ci.org/ddfreyne/ddmetrics)
[![Code Climate](https://img.shields.io/codeclimate/github/ddfreyne/ddmetrics.svg)](https://codeclimate.com/github/ddfreyne/ddmetrics)
[![Code Coverage](https://img.shields.io/codecov/c/github/ddfreyne/ddmetrics.svg)](https://codecov.io/gh/ddfreyne/ddmetrics)

# 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](https://prometheus.io/).

## Example

Take the following (naïve) cache implementation as a starting point:

```ruby
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:

```ruby
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:

```ruby
cache = Cache.new

cache['greeting']
cache['greeting']
cache['greeting'] = 'Hi there!'
cache['greeting']
cache['greeting']
cache['greeting']
```

Finally, get the recorded values:

```ruby
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:

```ruby
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](https://prometheus.io/).

* 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](https://prometheus.io/).

* 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:

```ruby
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:

```ruby
cache_hits_counter.increment(target: :file_cache)
request_durations_summary.observe(1.07, api: :weather)
```

### Counters

To create a counter, instantiate `DDMetrics::Counter`:

```ruby
counter = DDMetrics::Counter.new
```

To increment a counter, call `#increment` with a label:

```ruby
counter.increment(target: :file_cache)
```

To get the value for a certain label, use `#get`:

```ruby
counter.get(target: :file_cache)
# => 1
```

### Summaries

To create a summary, instantiate `DDMetrics::Summary`:

```ruby
summary = DDMetrics::Summary.new
```

To observe a value, call `#observe` with the value to observe, along with a label:

```ruby
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:

```ruby
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:

```ruby
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:

```ruby
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:

```ruby
stopwatch.start
sleep 1
stopwatch.stop
puts "That took #{stopwatch.duration}s."
# Output: That took 2.012879s.
```

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](http://contributor-covenant.org) code of conduct.

## License

The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).

## 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](https://github.com/ddfreyne/ddmetrics/blob/master/CODE_OF_CONDUCT.md).
