prometheus-client-mmap 0.7.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
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