Skip to content

Commit

Permalink
Merge pull request #24 from bukalapak/master
Browse files Browse the repository at this point in the history
histogram support for ruby client
  • Loading branch information
grobie committed Aug 8, 2016
2 parents 58bd969 + f4a4ea9 commit 0444e5e
Show file tree
Hide file tree
Showing 6 changed files with 195 additions and 0 deletions.
13 changes: 13 additions & 0 deletions lib/prometheus/client/formats/text.rb
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ def representation(metric, label_set, value, &block)

if metric.type == :summary
summary(metric.name, set, value, &block)
elsif metric.type == :histogram
histogram(metric.name, set, value, &block)
else
yield metric(metric.name, labels(set), value)
end
Expand All @@ -59,6 +61,17 @@ def summary(name, set, value)
yield metric("#{name}_count", l, value.total)
end

def histogram(name, set, value)
value.each do |q, v|
yield metric(name, labels(set.merge(le: q)), v)
end
yield metric(name, labels(set.merge(le: '+Inf')), value.total)

l = labels(set)
yield metric("#{name}_sum", l, value.sum)
yield metric("#{name}_count", l, value.total)
end

def metric(name, labels, value)
format(METRIC_LINE, name, labels, value)
end
Expand Down
73 changes: 73 additions & 0 deletions lib/prometheus/client/histogram.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# encoding: UTF-8

require 'prometheus/client/metric'

module Prometheus
module Client
# A histogram samples observations (usually things like request durations
# or response sizes) and counts them in configurable buckets. It also
# provides a sum of all observed values.
class Histogram < Metric
# Value represents the state of a Histogram at a given point.
class Value < Hash
attr_accessor :sum, :total

def initialize(buckets)
@sum = 0.0
@total = 0

buckets.each do |bucket|
self[bucket] = 0
end
end

def observe(value)
@sum += value
@total += 1

each_key do |bucket|
self[bucket] += 1 if value <= bucket
end
end
end

# DEFAULT_BUCKETS are the default Histogram buckets. The default buckets
# are tailored to broadly measure the response time (in seconds) of a
# network service. (From DefBuckets client_golang)
DEFAULT_BUCKETS = [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1,
2.5, 5, 10].freeze

# Offer a way to manually specify buckets
def initialize(name, docstring, base_labels = {},
buckets = DEFAULT_BUCKETS)
raise ArgumentError, 'Unsorted buckets, typo?' unless sorted? buckets

@buckets = buckets
super(name, docstring, base_labels)
end

def type
:histogram
end

def observe(labels, value)
if labels[:le]
raise ArgumentError, 'Label with name "le" is not permitted'
end

label_set = label_set_for(labels)
synchronize { @values[label_set].observe(value) }
end

private

def default
Value.new(@buckets)
end

def sorted?(bucket)
bucket.each_cons(2).all? { |i, j| i <= j }
end
end
end
end
6 changes: 6 additions & 0 deletions lib/prometheus/client/registry.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
require 'prometheus/client/counter'
require 'prometheus/client/summary'
require 'prometheus/client/gauge'
require 'prometheus/client/histogram'

module Prometheus
module Client
Expand Down Expand Up @@ -43,6 +44,11 @@ def gauge(name, docstring, base_labels = {})
register(Gauge.new(name, docstring, base_labels))
end

def histogram(name, docstring, base_labels = {},
buckets = Histogram::DEFAULT_BUCKETS)
register(Histogram.new(name, docstring, base_labels, buckets))
end

def exist?(name)
@metrics.key?(name)
end
Expand Down
23 changes: 23 additions & 0 deletions spec/prometheus/client/formats/text_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@
end
end

let(:histogram_value) do
{ 10 => 1, 20 => 2, 30 => 2 }.tap do |value|
allow(value).to receive_messages(sum: 15.2, total: 2)
end
end

let(:registry) do
metrics = [
double(
Expand Down Expand Up @@ -49,6 +55,15 @@
{ code: '1' } => summary_value,
},
),
double(
name: :xuq,
docstring: 'xuq description',
base_labels: {},
type: :histogram,
values: {
{ code: 'ah' } => histogram_value,
},
),
]
double(metrics: metrics)
end
Expand All @@ -74,6 +89,14 @@
qux{for="sake",code="1",quantile="0.99"} 15.3
qux_sum{for="sake",code="1"} 1243.21
qux_count{for="sake",code="1"} 93
# TYPE xuq histogram
# HELP xuq xuq description
xuq{code="ah",le="10"} 1
xuq{code="ah",le="20"} 2
xuq{code="ah",le="30"} 2
xuq{code="ah",le="+Inf"} 2
xuq_sum{code="ah"} 15.2
xuq_count{code="ah"} 2
TEXT
end
end
Expand Down
72 changes: 72 additions & 0 deletions spec/prometheus/client/histogram_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# encoding: UTF-8

require 'prometheus/client/histogram'
require 'examples/metric_example'

describe Prometheus::Client::Histogram do
let(:histogram) do
described_class.new(:bar, 'bar description', {}, [2.5, 5, 10])
end

it_behaves_like Prometheus::Client::Metric do
let(:type) { Hash }
end

describe '#initialization' do
it 'raise error for unsorted buckets' do
expect do
described_class.new(:bar, 'bar description', {}, [5, 2.5, 10])
end.to raise_error ArgumentError
end
end

describe '#observe' do
it 'records the given value' do
expect do
histogram.observe({}, 5)
end.to change { histogram.get }
end

it 'raise error for le labels' do
expect do
histogram.observe({ le: 1 }, 5)
end.to raise_error ArgumentError
end
end

describe '#get' do
before do
histogram.observe({ foo: 'bar' }, 3)
histogram.observe({ foo: 'bar' }, 5.2)
histogram.observe({ foo: 'bar' }, 13)
histogram.observe({ foo: 'bar' }, 4)
end

it 'returns a set of buckets values' do
expect(histogram.get(foo: 'bar')).to eql(2.5 => 0, 5 => 2, 10 => 3)
end

it 'returns a value which responds to #sum and #total' do
value = histogram.get(foo: 'bar')

expect(value.sum).to eql(25.2)
expect(value.total).to eql(4)
end

it 'uses zero as default value' do
expect(histogram.get({})).to eql(2.5 => 0, 5 => 0, 10 => 0)
end
end

describe '#values' do
it 'returns a hash of all recorded summaries' do
histogram.observe({ status: 'bar' }, 3)
histogram.observe({ status: 'foo' }, 6)

expect(histogram.values).to eql(
{ status: 'bar' } => { 2.5 => 0, 5 => 1, 10 => 1 },
{ status: 'foo' } => { 2.5 => 0, 5 => 0, 10 => 1 },
)
end
end
end
8 changes: 8 additions & 0 deletions spec/prometheus/client/registry_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,14 @@ def registry.exist?(*args)
end
end

describe '#histogram' do
it 'registers a new histogram metric container and returns the histogram' do
metric = registry.histogram(:test, 'test docstring')

expect(metric).to be_a(Prometheus::Client::Histogram)
end
end

describe '#exist?' do
it 'returns true if a metric name has been registered' do
registry.register(double(name: :test))
Expand Down

0 comments on commit 0444e5e

Please sign in to comment.