prometheus-client-mmap 0.7.0.beta1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/README.md +183 -0
- data/lib/prometheus.rb +5 -0
- data/lib/prometheus/client.rb +13 -0
- data/lib/prometheus/client/counter.rb +28 -0
- data/lib/prometheus/client/formats/text.rb +200 -0
- data/lib/prometheus/client/gauge.rb +32 -0
- data/lib/prometheus/client/histogram.rb +84 -0
- data/lib/prometheus/client/label_set_validator.rb +69 -0
- data/lib/prometheus/client/metric.rb +70 -0
- data/lib/prometheus/client/push.rb +72 -0
- data/lib/prometheus/client/rack/collector.rb +82 -0
- data/lib/prometheus/client/rack/exporter.rb +91 -0
- data/lib/prometheus/client/registry.rb +65 -0
- data/lib/prometheus/client/summary.rb +71 -0
- data/lib/prometheus/client/valuetype.rb +204 -0
- data/lib/prometheus/client/version.rb +7 -0
- metadata +75 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 10577a7fe85f4340b412160d874b2249fb9af895
|
4
|
+
data.tar.gz: c04685b407830ad5d30ed1b2f74980078beca53c
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 114932d1e37c16c689b1dbdf9e9c22c4c0b17e7d3fe554418b37ca2795fc4553e4f821457791d75896fc07832a2a8057d103b93cc23dab23740d1ec1bd20a946
|
7
|
+
data.tar.gz: ba68e2b1367ee698ed3a4895537be822557d12c90402ad3f46bfad847f334f5b511d25eba8808b89ecd994c3bcfce95a246eaf97b46c80dcd1e7cbd8563d9f0b
|
data/README.md
ADDED
@@ -0,0 +1,183 @@
|
|
1
|
+
# Prometheus Ruby Client
|
2
|
+
|
3
|
+
A suite of instrumentation metric primitives for Ruby that can be exposed
|
4
|
+
through a HTTP interface. Intended to be used together with a
|
5
|
+
[Prometheus server][1].
|
6
|
+
|
7
|
+
[![Gem Version][4]](http://badge.fury.io/rb/prometheus-client)
|
8
|
+
[![Build Status][3]](http://travis-ci.org/prometheus/client_ruby)
|
9
|
+
[![Dependency Status][5]](https://gemnasium.com/prometheus/client_ruby)
|
10
|
+
[![Code Climate][6]](https://codeclimate.com/github/prometheus/client_ruby)
|
11
|
+
[![Coverage Status][7]](https://coveralls.io/r/prometheus/client_ruby)
|
12
|
+
|
13
|
+
## Usage
|
14
|
+
|
15
|
+
### Overview
|
16
|
+
|
17
|
+
```ruby
|
18
|
+
require 'prometheus/client'
|
19
|
+
|
20
|
+
# returns a default registry
|
21
|
+
prometheus = Prometheus::Client.registry
|
22
|
+
|
23
|
+
# create a new counter metric
|
24
|
+
http_requests = Prometheus::Client::Counter.new(:http_requests, 'A counter of HTTP requests made')
|
25
|
+
# register the metric
|
26
|
+
prometheus.register(http_requests)
|
27
|
+
|
28
|
+
# equivalent helper function
|
29
|
+
http_requests = prometheus.counter(:http_requests, 'A counter of HTTP requests made')
|
30
|
+
|
31
|
+
# start using the counter
|
32
|
+
http_requests.increment
|
33
|
+
```
|
34
|
+
|
35
|
+
### Rack middleware
|
36
|
+
|
37
|
+
There are two [Rack][2] middlewares available, one to expose a metrics HTTP
|
38
|
+
endpoint to be scraped by a prometheus server ([Exporter][9]) and one to trace all HTTP
|
39
|
+
requests ([Collector][10]).
|
40
|
+
|
41
|
+
It's highly recommended to enable gzip compression for the metrics endpoint,
|
42
|
+
for example by including the `Rack::Deflater` middleware.
|
43
|
+
|
44
|
+
```ruby
|
45
|
+
# config.ru
|
46
|
+
|
47
|
+
require 'rack'
|
48
|
+
require 'prometheus/client/rack/collector'
|
49
|
+
require 'prometheus/client/rack/exporter'
|
50
|
+
|
51
|
+
use Rack::Deflater, if: ->(env, status, headers, body) { body.any? && body[0].length > 512 }
|
52
|
+
use Prometheus::Client::Rack::Collector
|
53
|
+
use Prometheus::Client::Rack::Exporter
|
54
|
+
|
55
|
+
run ->(env) { [200, {'Content-Type' => 'text/html'}, ['OK']] }
|
56
|
+
```
|
57
|
+
|
58
|
+
Start the server and have a look at the metrics endpoint:
|
59
|
+
[http://localhost:5000/metrics](http://localhost:5000/metrics).
|
60
|
+
|
61
|
+
For further instructions and other scripts to get started, have a look at the
|
62
|
+
integrated [example application](examples/rack/README.md).
|
63
|
+
|
64
|
+
### Pushgateway
|
65
|
+
|
66
|
+
The Ruby client can also be used to push its collected metrics to a
|
67
|
+
[Pushgateway][8]. This comes in handy with batch jobs or in other scenarios
|
68
|
+
where it's not possible or feasible to let a Prometheus server scrape a Ruby
|
69
|
+
process.
|
70
|
+
|
71
|
+
```ruby
|
72
|
+
require 'prometheus/client'
|
73
|
+
require 'prometheus/client/push'
|
74
|
+
|
75
|
+
prometheus = Prometheus::Client.registry
|
76
|
+
# ... register some metrics, set/increment/observe/etc. their values
|
77
|
+
|
78
|
+
# push the registry state to the default gateway
|
79
|
+
Prometheus::Client::Push.new('my-batch-job').add(prometheus)
|
80
|
+
|
81
|
+
# optional: specify the instance name (instead of IP) and gateway
|
82
|
+
Prometheus::Client::Push.new(
|
83
|
+
'my-job', 'instance-name', 'http://example.domain:1234').add(prometheus)
|
84
|
+
|
85
|
+
# If you want to replace any previously pushed metrics for a given instance,
|
86
|
+
# use the #replace method.
|
87
|
+
Prometheus::Client::Push.new('my-batch-job', 'instance').replace(prometheus)
|
88
|
+
|
89
|
+
# If you want to delete all previously pushed metrics for a given instance,
|
90
|
+
# use the #delete method.
|
91
|
+
Prometheus::Client::Push.new('my-batch-job', 'instance').delete
|
92
|
+
```
|
93
|
+
|
94
|
+
## Metrics
|
95
|
+
|
96
|
+
The following metric types are currently supported.
|
97
|
+
|
98
|
+
### Counter
|
99
|
+
|
100
|
+
Counter is a metric that exposes merely a sum or tally of things.
|
101
|
+
|
102
|
+
```ruby
|
103
|
+
counter = Prometheus::Client::Counter.new(:service_requests_total, '...')
|
104
|
+
|
105
|
+
# increment the counter for a given label set
|
106
|
+
counter.increment({ service: 'foo' })
|
107
|
+
|
108
|
+
# increment by a given value
|
109
|
+
counter.increment({ service: 'bar' }, 5)
|
110
|
+
|
111
|
+
# get current value for a given label set
|
112
|
+
counter.get({ service: 'bar' })
|
113
|
+
# => 5
|
114
|
+
```
|
115
|
+
|
116
|
+
### Gauge
|
117
|
+
|
118
|
+
Gauge is a metric that exposes merely an instantaneous value or some snapshot
|
119
|
+
thereof.
|
120
|
+
|
121
|
+
```ruby
|
122
|
+
gauge = Prometheus::Client::Gauge.new(:room_temperature_celsius, '...')
|
123
|
+
|
124
|
+
# set a value
|
125
|
+
gauge.set({ room: 'kitchen' }, 21.534)
|
126
|
+
|
127
|
+
# retrieve the current value for a given label set
|
128
|
+
gauge.get({ room: 'kitchen' })
|
129
|
+
# => 21.534
|
130
|
+
```
|
131
|
+
|
132
|
+
### Histogram
|
133
|
+
|
134
|
+
A histogram samples observations (usually things like request durations or
|
135
|
+
response sizes) and counts them in configurable buckets. It also provides a sum
|
136
|
+
of all observed values.
|
137
|
+
|
138
|
+
```ruby
|
139
|
+
histogram = Prometheus::Client::Histogram.new(:service_latency_seconds, '...')
|
140
|
+
|
141
|
+
# record a value
|
142
|
+
histogram.observe({ service: 'users' }, Benchmark.realtime { service.call(arg) })
|
143
|
+
|
144
|
+
# retrieve the current bucket values
|
145
|
+
histogram.get({ service: 'users' })
|
146
|
+
# => { 0.005 => 3, 0.01 => 15, 0.025 => 18, ..., 2.5 => 42, 5 => 42, 10 = >42 }
|
147
|
+
```
|
148
|
+
|
149
|
+
### Summary
|
150
|
+
|
151
|
+
Summary, similar to histograms, is an accumulator for samples. It captures
|
152
|
+
Numeric data and provides an efficient percentile calculation mechanism.
|
153
|
+
|
154
|
+
```ruby
|
155
|
+
summary = Prometheus::Client::Summary.new(:service_latency_seconds, '...')
|
156
|
+
|
157
|
+
# record a value
|
158
|
+
summary.observe({ service: 'database' }, Benchmark.realtime { service.call() })
|
159
|
+
|
160
|
+
# retrieve the current quantile values
|
161
|
+
summary.get({ service: 'database' })
|
162
|
+
# => { 0.5 => 0.1233122, 0.9 => 3.4323, 0.99 => 5.3428231 }
|
163
|
+
```
|
164
|
+
|
165
|
+
## Tests
|
166
|
+
|
167
|
+
Install necessary development gems with `bundle install` and run tests with
|
168
|
+
rspec:
|
169
|
+
|
170
|
+
```bash
|
171
|
+
rake
|
172
|
+
```
|
173
|
+
|
174
|
+
[1]: https://github.com/prometheus/prometheus
|
175
|
+
[2]: http://rack.github.io/
|
176
|
+
[3]: https://secure.travis-ci.org/prometheus/client_ruby.png?branch=master
|
177
|
+
[4]: https://badge.fury.io/rb/prometheus-client.svg
|
178
|
+
[5]: https://gemnasium.com/prometheus/client_ruby.svg
|
179
|
+
[6]: https://codeclimate.com/github/prometheus/client_ruby.png
|
180
|
+
[7]: https://coveralls.io/repos/prometheus/client_ruby/badge.png?branch=master
|
181
|
+
[8]: https://github.com/prometheus/pushgateway
|
182
|
+
[9]: lib/prometheus/client/rack/exporter.rb
|
183
|
+
[10]: lib/prometheus/client/rack/collector.rb
|
data/lib/prometheus.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require 'prometheus/client/registry'
|
4
|
+
|
5
|
+
module Prometheus
|
6
|
+
# Client is a ruby implementation for a Prometheus compatible client.
|
7
|
+
module Client
|
8
|
+
# Returns a default registry object
|
9
|
+
def self.registry
|
10
|
+
@registry ||= Registry.new
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require 'prometheus/client/metric'
|
4
|
+
require 'prometheus/client/valuetype'
|
5
|
+
|
6
|
+
module Prometheus
|
7
|
+
module Client
|
8
|
+
# Counter is a metric that exposes merely a sum or tally of things.
|
9
|
+
class Counter < Metric
|
10
|
+
def type
|
11
|
+
:counter
|
12
|
+
end
|
13
|
+
|
14
|
+
def increment(labels = {}, by = 1)
|
15
|
+
raise ArgumentError, 'increment must be a non-negative number' if by < 0
|
16
|
+
|
17
|
+
label_set = label_set_for(labels)
|
18
|
+
synchronize { @values[label_set].increment(by) }
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def default(labels)
|
24
|
+
ValueClass.new(type, @name, @name, labels)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,200 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require 'prometheus/client/valuetype'
|
4
|
+
|
5
|
+
module Prometheus
|
6
|
+
module Client
|
7
|
+
module Formats
|
8
|
+
# Text format is human readable mainly used for manual inspection.
|
9
|
+
module Text
|
10
|
+
MEDIA_TYPE = 'text/plain'.freeze
|
11
|
+
VERSION = '0.0.4'.freeze
|
12
|
+
CONTENT_TYPE = "#{MEDIA_TYPE}; version=#{VERSION}".freeze
|
13
|
+
|
14
|
+
METRIC_LINE = '%s%s %s'.freeze
|
15
|
+
TYPE_LINE = '# TYPE %s %s'.freeze
|
16
|
+
HELP_LINE = '# HELP %s %s'.freeze
|
17
|
+
|
18
|
+
LABEL = '%s="%s"'.freeze
|
19
|
+
SEPARATOR = ','.freeze
|
20
|
+
DELIMITER = "\n".freeze
|
21
|
+
|
22
|
+
REGEX = { doc: /[\n\\]/, label: /[\n\\"]/ }.freeze
|
23
|
+
REPLACE = { "\n" => '\n', '\\' => '\\\\', '"' => '\"' }.freeze
|
24
|
+
|
25
|
+
def self.marshal(registry)
|
26
|
+
lines = []
|
27
|
+
|
28
|
+
registry.metrics.each do |metric|
|
29
|
+
lines << format(TYPE_LINE, metric.name, metric.type)
|
30
|
+
lines << format(HELP_LINE, metric.name, escape(metric.docstring))
|
31
|
+
|
32
|
+
metric.values.each do |label_set, value|
|
33
|
+
representation(metric, label_set, value) { |l| lines << l }
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# there must be a trailing delimiter
|
38
|
+
(lines << nil).join(DELIMITER)
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.marshal_multiprocess(path=ENV['prometheus_multiproc_dir'])
|
42
|
+
metrics = {}
|
43
|
+
Dir.glob(File.join(path, "*.db")).each do |f|
|
44
|
+
parts = File.basename(f).split("_")
|
45
|
+
type = parts[0].to_sym
|
46
|
+
d = MmapedDict.new(f)
|
47
|
+
d.all_values.each do |key, value|
|
48
|
+
metric_name, name, labelnames, labelvalues = JSON.parse(key)
|
49
|
+
metric = metrics.fetch(metric_name, {
|
50
|
+
metric_name: metric_name,
|
51
|
+
help: 'Multiprocess metric',
|
52
|
+
type: type,
|
53
|
+
samples: [],
|
54
|
+
})
|
55
|
+
if type == :gauge
|
56
|
+
pid = parts[2][0..-3]
|
57
|
+
metric[:multiprocess_mode] = parts[1]
|
58
|
+
metric[:samples] += [[name, labelnames.zip(labelvalues) + [['pid', pid]], value]]
|
59
|
+
else
|
60
|
+
# The duplicates and labels are fixed in the next for.
|
61
|
+
metric[:samples] += [[name, labelnames.zip(labelvalues), value]]
|
62
|
+
end
|
63
|
+
metrics[metric_name] = metric
|
64
|
+
end
|
65
|
+
d.close
|
66
|
+
end
|
67
|
+
|
68
|
+
metrics.each_value do |metric|
|
69
|
+
samples = {}
|
70
|
+
buckets = {}
|
71
|
+
metric[:samples].each do |name, labels, value|
|
72
|
+
case metric[:type]
|
73
|
+
when :gauge
|
74
|
+
without_pid = labels.select{ |l| l[0] != 'pid' }
|
75
|
+
case metric[:multiprocess_mode]
|
76
|
+
when 'min'
|
77
|
+
s = samples.fetch([name, without_pid], value)
|
78
|
+
samples[[name, without_pid]] = [s, value].min
|
79
|
+
when 'max'
|
80
|
+
s = samples.fetch([name, without_pid], value)
|
81
|
+
samples[[name, without_pid]] = [s, value].max
|
82
|
+
when 'livesum'
|
83
|
+
s = samples.fetch([name, without_pid], 0.0)
|
84
|
+
samples[[name, without_pid]] = s + value
|
85
|
+
else # all/liveall
|
86
|
+
samples[[name, labels]] = value
|
87
|
+
end
|
88
|
+
when :histogram
|
89
|
+
bucket = labels.select{|l| l[0] == 'le' }.map {|k, v| v.to_f}.first
|
90
|
+
if bucket
|
91
|
+
without_le = labels.select{ |l| l[0] != 'le' }
|
92
|
+
b = buckets.fetch(without_le, {})
|
93
|
+
v = b.fetch(bucket, 0.0) + value
|
94
|
+
if !buckets.has_key?(without_le)
|
95
|
+
buckets[without_le] = {}
|
96
|
+
end
|
97
|
+
buckets[without_le][bucket] = v
|
98
|
+
else
|
99
|
+
s = samples.fetch([name, labels], 0.0)
|
100
|
+
samples[[name, labels]] = s + value
|
101
|
+
end
|
102
|
+
else
|
103
|
+
# Counter and Summary.
|
104
|
+
s = samples.fetch([name, without_pid], 0.0)
|
105
|
+
samples[[name, without_pid]] = s + value
|
106
|
+
end
|
107
|
+
|
108
|
+
if metric[:type] == :histogram
|
109
|
+
buckets.each do |labels, values|
|
110
|
+
acc = 0.0
|
111
|
+
values.sort.each do |bucket, value|
|
112
|
+
acc += value
|
113
|
+
# TODO: handle Infinity
|
114
|
+
samples[[metric[:metric_name] + '_bucket', labels + [['le', bucket.to_s]]]] = acc
|
115
|
+
end
|
116
|
+
samples[[metric[:metric_name] + '_count', labels]] = acc
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
metric[:samples] = samples.map do |name_labels, value|
|
121
|
+
name, labels = name_labels
|
122
|
+
[name, labels.to_h, value]
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
output = ''
|
128
|
+
metrics.each do |name, metric|
|
129
|
+
output += "# HELP #{name} #{metric[:help]}\n"
|
130
|
+
output += "# TYPE #{name} #{metric[:type].to_s}\n"
|
131
|
+
metric[:samples].each do |metric_name, labels, value|
|
132
|
+
if !labels.empty?
|
133
|
+
labelstr = '{' + labels.sort.map { |k, v| "#{k}='#{v}'" }.join(',') + '}'
|
134
|
+
else
|
135
|
+
labelstr = ''
|
136
|
+
end
|
137
|
+
output += "#{metric_name}#{labelstr} #{value}\n"
|
138
|
+
end
|
139
|
+
end
|
140
|
+
output
|
141
|
+
end
|
142
|
+
|
143
|
+
class << self
|
144
|
+
private
|
145
|
+
|
146
|
+
def representation(metric, label_set, value, &block)
|
147
|
+
set = metric.base_labels.merge(label_set)
|
148
|
+
|
149
|
+
if metric.type == :summary
|
150
|
+
summary(metric.name, set, value, &block)
|
151
|
+
elsif metric.type == :histogram
|
152
|
+
histogram(metric.name, set, value, &block)
|
153
|
+
else
|
154
|
+
yield metric(metric.name, labels(set), value.get)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def summary(name, set, value)
|
159
|
+
value.each do |q, v|
|
160
|
+
yield metric(name, labels(set.merge(quantile: q)), v.get)
|
161
|
+
end
|
162
|
+
|
163
|
+
l = labels(set)
|
164
|
+
yield metric("#{name}_sum", l, value.sum.get)
|
165
|
+
yield metric("#{name}_count", l, value.total.get)
|
166
|
+
end
|
167
|
+
|
168
|
+
def histogram(name, set, value)
|
169
|
+
value.each do |q, v|
|
170
|
+
yield metric(name, labels(set.merge(le: q)), v.get)
|
171
|
+
end
|
172
|
+
yield metric(name, labels(set.merge(le: '+Inf')), value.total.get)
|
173
|
+
|
174
|
+
l = labels(set)
|
175
|
+
yield metric("#{name}_sum", l, value.sum.get)
|
176
|
+
yield metric("#{name}_count", l, value.total.get)
|
177
|
+
end
|
178
|
+
|
179
|
+
def metric(name, labels, value)
|
180
|
+
format(METRIC_LINE, name, labels, value)
|
181
|
+
end
|
182
|
+
|
183
|
+
def labels(set)
|
184
|
+
return if set.empty?
|
185
|
+
|
186
|
+
strings = set.each_with_object([]) do |(key, value), memo|
|
187
|
+
memo << format(LABEL, key, escape(value, :label))
|
188
|
+
end
|
189
|
+
|
190
|
+
"{#{strings.join(SEPARATOR)}}"
|
191
|
+
end
|
192
|
+
|
193
|
+
def escape(string, format = :doc)
|
194
|
+
string.to_s.gsub(REGEX[format], REPLACE)
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require 'prometheus/client/metric'
|
4
|
+
|
5
|
+
module Prometheus
|
6
|
+
module Client
|
7
|
+
# A Gauge is a metric that exposes merely an instantaneous value or some
|
8
|
+
# snapshot thereof.
|
9
|
+
class Gauge < Metric
|
10
|
+
def initialize(name, docstring, base_labels = {}, multiprocess_mode=:all)
|
11
|
+
super(name, docstring, base_labels)
|
12
|
+
if ValueClass.multiprocess and ![:min, :max, :livesum, :liveall, :all].include?(multiprocess_mode)
|
13
|
+
raise ArgumentError, 'Invalid multiprocess mode: ' + multiprocess_mode
|
14
|
+
end
|
15
|
+
@multiprocess_mode = multiprocess_mode
|
16
|
+
end
|
17
|
+
|
18
|
+
def type
|
19
|
+
:gauge
|
20
|
+
end
|
21
|
+
|
22
|
+
def default(labels)
|
23
|
+
ValueClass.new(type, @name, @name, labels, @multiprocess_mode)
|
24
|
+
end
|
25
|
+
|
26
|
+
# Sets the value for the given label set
|
27
|
+
def set(labels, value)
|
28
|
+
@values[label_set_for(labels)].set(value)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|