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 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,5 @@
1
+ # encoding: UTF-8
2
+
3
+ # Prometheus is a generic time-series collection and computation server.
4
+ module Prometheus
5
+ end
@@ -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