prometheus-client 0.5.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 525f1dcd2ca63486f95a2d2fd44b7f047625edc3
4
- data.tar.gz: 0480ce9fd716c715553e8c1a0877345f2124b563
3
+ metadata.gz: 1ca1c148a74709f3c2fbbba112f651babdf43fbe
4
+ data.tar.gz: e55c7b9be73218b6a252a5a624fc8a865d00d851
5
5
  SHA512:
6
- metadata.gz: 362f0157652258f5270dc815434d7ef4768e4842a68a5a344e9dcff41ee494fadcab29d0c15c5764310bfa8d9b1655e27a829a7c62782505ea3c6f611402796e
7
- data.tar.gz: 548905e7ec71056de3e6a622073bf0fc342f6dca7a6d9c0c783411c35ab7f306aa87f71aecab7fc128a4dfd232f62dcb72dca0682a3421451d162844dab66960
6
+ metadata.gz: 337ad7b5c4315463492d5120b4652bd0e4d58c1b2a53fa8be05c5f1d02c7b567133196e31e5b43899001f25fd896c7f60d4cccda717d1d8fede675aaa21276e0
7
+ data.tar.gz: fcbb6c9963f8f0e5c5c532209fecf8643fad16a8943b74fd601644972ff25bb30f8c0e0be02b307c0247fb7a83549d21a54fe1f75850df795000d7756c9092ab
data/README.md CHANGED
@@ -73,7 +73,7 @@ require 'prometheus/client'
73
73
  require 'prometheus/client/push'
74
74
 
75
75
  prometheus = Prometheus::Client.registry
76
- # ... register some metrics, set/add/increment/etc. their values
76
+ # ... register some metrics, set/increment/observe/etc. their values
77
77
 
78
78
  # push the registry state to the default gateway
79
79
  Prometheus::Client::Push.new('my-batch-job').add(prometheus)
@@ -96,19 +96,16 @@ The following metric types are currently supported.
96
96
  Counter is a metric that exposes merely a sum or tally of things.
97
97
 
98
98
  ```ruby
99
- counter = Prometheus::Client::Counter.new(:foo, '...')
99
+ counter = Prometheus::Client::Counter.new(:service_requests_total, '...')
100
100
 
101
101
  # increment the counter for a given label set
102
- counter.increment(service: 'foo')
102
+ counter.increment({ service: 'foo' })
103
103
 
104
104
  # increment by a given value
105
105
  counter.increment({ service: 'bar' }, 5)
106
106
 
107
- # decrement the counter
108
- counter.decrement(service: 'exceptional')
109
-
110
107
  # get current value for a given label set
111
- counter.get(service: 'bar')
108
+ counter.get({ service: 'bar' })
112
109
  # => 5
113
110
  ```
114
111
 
@@ -118,30 +115,47 @@ Gauge is a metric that exposes merely an instantaneous value or some snapshot
118
115
  thereof.
119
116
 
120
117
  ```ruby
121
- gauge = Prometheus::Client::Gauge.new(:bar, '...')
118
+ gauge = Prometheus::Client::Gauge.new(:room_temperature_celsius, '...')
122
119
 
123
120
  # set a value
124
- gauge.set({ role: 'base' }, 'up')
121
+ gauge.set({ room: 'kitchen' }, 21.534)
125
122
 
126
123
  # retrieve the current value for a given label set
127
- gauge.get({ role: 'problematic' })
128
- # => 'down'
124
+ gauge.get({ room: 'kitchen' })
125
+ # => 21.534
126
+ ```
127
+
128
+ ### Histogram
129
+
130
+ A histogram samples observations (usually things like request durations or
131
+ response sizes) and counts them in configurable buckets. It also provides a sum
132
+ of all observed values.
133
+
134
+ ```ruby
135
+ histogram = Prometheus::Client::Histogram.new(:service_latency_seconds, '...')
136
+
137
+ # record a value
138
+ histogram.observe({ service: 'users' }, Benchmark.realtime { service.call(arg) })
139
+
140
+ # retrieve the current quantile values
141
+ histogram.get({ service: 'users' })
142
+ # => { 0.005 => 3, 0.01 => 15, 0.025 => 18, ..., 2.5 => 42, 5 => 42, 10 = >42 }
129
143
  ```
130
144
 
131
145
  ### Summary
132
146
 
133
- Summary is an accumulator for samples. It captures Numeric data and provides
134
- an efficient percentile calculation mechanism.
147
+ Summary, similar to histograms, is an accumulator for samples. It captures
148
+ Numeric data and provides an efficient percentile calculation mechanism.
135
149
 
136
150
  ```ruby
137
- summary = Prometheus::Client::Summary.new(:baz, '...')
151
+ summary = Prometheus::Client::Summary.new(:service_latency_seconds, '...')
138
152
 
139
153
  # record a value
140
- summary.add({ service: 'slow' }, Benchmark.realtime { service.call(arg) })
154
+ summary.observe({ service: 'database' }, Benchmark.realtime { service.call() })
141
155
 
142
156
  # retrieve the current quantile values
143
157
  summary.get({ service: 'database' })
144
- # => { 0.5: 1.233122, 0.9: 83.4323, 0.99: 341.3428231 }
158
+ # => { 0.5 => 0.1233122, 0.9 => 3.4323, 0.99 => 5.3428231 }
145
159
  ```
146
160
 
147
161
  ## Todo
@@ -11,7 +11,7 @@ module Prometheus
11
11
  end
12
12
 
13
13
  def increment(labels = {}, by = 1)
14
- fail ArgumentError, 'increment must be a non-negative number' if by < 0
14
+ raise ArgumentError, 'increment must be a non-negative number' if by < 0
15
15
 
16
16
  label_set = label_set_for(labels)
17
17
  synchronize { @values[label_set] += by }
@@ -5,20 +5,20 @@ module Prometheus
5
5
  module Formats
6
6
  # Text format is human readable mainly used for manual inspection.
7
7
  module Text
8
- MEDIA_TYPE = 'text/plain'
9
- VERSION = '0.0.4'
10
- CONTENT_TYPE = "#{MEDIA_TYPE}; version=#{VERSION}"
8
+ MEDIA_TYPE = 'text/plain'.freeze
9
+ VERSION = '0.0.4'.freeze
10
+ CONTENT_TYPE = "#{MEDIA_TYPE}; version=#{VERSION}".freeze
11
11
 
12
- METRIC_LINE = '%s%s %s'
13
- TYPE_LINE = '# TYPE %s %s'
14
- HELP_LINE = '# HELP %s %s'
12
+ METRIC_LINE = '%s%s %s'.freeze
13
+ TYPE_LINE = '# TYPE %s %s'.freeze
14
+ HELP_LINE = '# HELP %s %s'.freeze
15
15
 
16
- LABEL = '%s="%s"'
17
- SEPARATOR = ','
18
- DELIMITER = "\n"
16
+ LABEL = '%s="%s"'.freeze
17
+ SEPARATOR = ','.freeze
18
+ DELIMITER = "\n".freeze
19
19
 
20
- REGEX = { doc: /[\n\\]/, label: /[\n\\"]/ }
21
- REPLACE = { "\n" => '\n', '\\' => '\\\\', '"' => '\"' }
20
+ REGEX = { doc: /[\n\\]/, label: /[\n\\"]/ }.freeze
21
+ REPLACE = { "\n" => '\n', '\\' => '\\\\', '"' => '\"' }.freeze
22
22
 
23
23
  def self.marshal(registry)
24
24
  lines = []
@@ -36,44 +36,59 @@ module Prometheus
36
36
  (lines << nil).join(DELIMITER)
37
37
  end
38
38
 
39
- private
39
+ class << self
40
+ private
40
41
 
41
- def self.representation(metric, label_set, value, &block)
42
- set = metric.base_labels.merge(label_set)
42
+ def representation(metric, label_set, value, &block)
43
+ set = metric.base_labels.merge(label_set)
43
44
 
44
- if metric.type == :summary
45
- summary(metric.name, set, value, &block)
46
- else
47
- yield metric(metric.name, labels(set), value)
45
+ if metric.type == :summary
46
+ summary(metric.name, set, value, &block)
47
+ elsif metric.type == :histogram
48
+ histogram(metric.name, set, value, &block)
49
+ else
50
+ yield metric(metric.name, labels(set), value)
51
+ end
48
52
  end
49
- end
50
53
 
51
- def self.summary(name, set, value)
52
- value.each do |q, v|
53
- yield metric(name, labels(set.merge(quantile: q)), v)
54
- end
54
+ def summary(name, set, value)
55
+ value.each do |q, v|
56
+ yield metric(name, labels(set.merge(quantile: q)), v)
57
+ end
55
58
 
56
- l = labels(set)
57
- yield metric("#{name}_sum", l, value.sum)
58
- yield metric("#{name}_count", l, value.total)
59
- end
59
+ l = labels(set)
60
+ yield metric("#{name}_sum", l, value.sum)
61
+ yield metric("#{name}_count", l, value.total)
62
+ end
60
63
 
61
- def self.metric(name, labels, value)
62
- format(METRIC_LINE, name, labels, value)
63
- end
64
+ def histogram(name, set, value)
65
+ value.each do |q, v|
66
+ yield metric(name, labels(set.merge(le: q)), v)
67
+ end
68
+ yield metric(name, labels(set.merge(le: '+Inf')), value.total)
64
69
 
65
- def self.labels(set)
66
- return if set.empty?
70
+ l = labels(set)
71
+ yield metric("#{name}_sum", l, value.sum)
72
+ yield metric("#{name}_count", l, value.total)
73
+ end
67
74
 
68
- strings = set.each_with_object([]) do |(key, value), memo|
69
- memo << format(LABEL, key, escape(value, :label))
75
+ def metric(name, labels, value)
76
+ format(METRIC_LINE, name, labels, value)
70
77
  end
71
78
 
72
- "{#{strings.join(SEPARATOR)}}"
73
- end
79
+ def labels(set)
80
+ return if set.empty?
81
+
82
+ strings = set.each_with_object([]) do |(key, value), memo|
83
+ memo << format(LABEL, key, escape(value, :label))
84
+ end
74
85
 
75
- def self.escape(string, format = :doc)
76
- string.to_s.gsub(REGEX[format], REPLACE)
86
+ "{#{strings.join(SEPARATOR)}}"
87
+ end
88
+
89
+ def escape(string, format = :doc)
90
+ string.to_s.gsub(REGEX[format], REPLACE)
91
+ end
77
92
  end
78
93
  end
79
94
  end
@@ -0,0 +1,73 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'prometheus/client/metric'
4
+
5
+ module Prometheus
6
+ module Client
7
+ # A histogram samples observations (usually things like request durations
8
+ # or response sizes) and counts them in configurable buckets. It also
9
+ # provides a sum of all observed values.
10
+ class Histogram < Metric
11
+ # Value represents the state of a Histogram at a given point.
12
+ class Value < Hash
13
+ attr_accessor :sum, :total
14
+
15
+ def initialize(buckets)
16
+ @sum = 0.0
17
+ @total = 0
18
+
19
+ buckets.each do |bucket|
20
+ self[bucket] = 0
21
+ end
22
+ end
23
+
24
+ def observe(value)
25
+ @sum += value
26
+ @total += 1
27
+
28
+ each_key do |bucket|
29
+ self[bucket] += 1 if value <= bucket
30
+ end
31
+ end
32
+ end
33
+
34
+ # DEFAULT_BUCKETS are the default Histogram buckets. The default buckets
35
+ # are tailored to broadly measure the response time (in seconds) of a
36
+ # network service. (From DefBuckets client_golang)
37
+ DEFAULT_BUCKETS = [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1,
38
+ 2.5, 5, 10].freeze
39
+
40
+ # Offer a way to manually specify buckets
41
+ def initialize(name, docstring, base_labels = {},
42
+ buckets = DEFAULT_BUCKETS)
43
+ raise ArgumentError, 'Unsorted buckets, typo?' unless sorted? buckets
44
+
45
+ @buckets = buckets
46
+ super(name, docstring, base_labels)
47
+ end
48
+
49
+ def type
50
+ :histogram
51
+ end
52
+
53
+ def observe(labels, value)
54
+ if labels[:le]
55
+ raise ArgumentError, 'Label with name "le" is not permitted'
56
+ end
57
+
58
+ label_set = label_set_for(labels)
59
+ synchronize { @values[label_set].observe(value) }
60
+ end
61
+
62
+ private
63
+
64
+ def default
65
+ Value.new(@buckets)
66
+ end
67
+
68
+ def sorted?(bucket)
69
+ bucket.each_cons(2).all? { |i, j| i <= j }
70
+ end
71
+ end
72
+ end
73
+ end
@@ -6,7 +6,7 @@ module Prometheus
6
6
  # Prometheus specification.
7
7
  class LabelSetValidator
8
8
  # TODO: we might allow setting :instance in the future
9
- RESERVED_LABELS = [:job, :instance]
9
+ RESERVED_LABELS = [:job, :instance].freeze
10
10
 
11
11
  class LabelSetError < StandardError; end
12
12
  class InvalidLabelSetError < LabelSetError; end
@@ -19,7 +19,7 @@ module Prometheus
19
19
 
20
20
  def valid?(labels)
21
21
  unless labels.respond_to?(:all?)
22
- fail InvalidLabelSetError, "#{labels} is not a valid label set"
22
+ raise InvalidLabelSetError, "#{labels} is not a valid label set"
23
23
  end
24
24
 
25
25
  labels.all? do |key, _|
@@ -35,7 +35,7 @@ module Prometheus
35
35
  valid?(labels)
36
36
 
37
37
  unless @validated.empty? || match?(labels, @validated.first.last)
38
- fail InvalidLabelSetError, 'labels must have the same signature'
38
+ raise InvalidLabelSetError, 'labels must have the same signature'
39
39
  end
40
40
 
41
41
  @validated[labels.hash] = labels
@@ -50,19 +50,19 @@ module Prometheus
50
50
  def validate_symbol(key)
51
51
  return true if key.is_a?(Symbol)
52
52
 
53
- fail InvalidLabelError, "label #{key} is not a symbol"
53
+ raise InvalidLabelError, "label #{key} is not a symbol"
54
54
  end
55
55
 
56
56
  def validate_name(key)
57
57
  return true unless key.to_s.start_with?('__')
58
58
 
59
- fail ReservedLabelError, "label #{key} must not start with __"
59
+ raise ReservedLabelError, "label #{key} must not start with __"
60
60
  end
61
61
 
62
62
  def validate_reserved_key(key)
63
63
  return true unless RESERVED_LABELS.include?(key)
64
64
 
65
- fail ReservedLabelError, "#{key} is reserved"
65
+ raise ReservedLabelError, "#{key} is reserved"
66
66
  end
67
67
  end
68
68
  end
@@ -25,7 +25,7 @@ module Prometheus
25
25
 
26
26
  # Returns the metric type
27
27
  def type
28
- fail NotImplementedError
28
+ raise NotImplementedError
29
29
  end
30
30
 
31
31
  # Returns the value for the given label set
@@ -53,13 +53,13 @@ module Prometheus
53
53
  def validate_name(name)
54
54
  return true if name.is_a?(Symbol)
55
55
 
56
- fail ArgumentError, 'given name must be a symbol'
56
+ raise ArgumentError, 'given name must be a symbol'
57
57
  end
58
58
 
59
59
  def validate_docstring(docstring)
60
60
  return true if docstring.respond_to?(:empty?) && !docstring.empty?
61
61
 
62
- fail ArgumentError, 'docstring must be given'
62
+ raise ArgumentError, 'docstring must be given'
63
63
  end
64
64
 
65
65
  def label_set_for(labels)
@@ -12,10 +12,10 @@ module Prometheus
12
12
  # Push implements a simple way to transmit a given registry to a given
13
13
  # Pushgateway.
14
14
  class Push
15
- DEFAULT_GATEWAY = 'http://localhost:9091'
16
- PATH = '/metrics/jobs/%s'
17
- INSTANCE_PATH = '/metrics/jobs/%s/instances/%s'
18
- HEADER = { 'Content-Type' => Formats::Text::CONTENT_TYPE }
15
+ DEFAULT_GATEWAY = 'http://localhost:9091'.freeze
16
+ PATH = '/metrics/jobs/%s'.freeze
17
+ INSTANCE_PATH = '/metrics/jobs/%s/instances/%s'.freeze
18
+ HEADER = { 'Content-Type' => Formats::Text::CONTENT_TYPE }.freeze
19
19
 
20
20
  attr_reader :job, :instance, :gateway, :path
21
21
 
@@ -44,7 +44,7 @@ module Prometheus
44
44
  if uri.scheme == 'http'
45
45
  uri
46
46
  else
47
- fail ArgumentError, 'only HTTP gateway URLs are supported currently.'
47
+ raise ArgumentError, 'only HTTP gateway URLs are supported currently.'
48
48
  end
49
49
  rescue URI::InvalidURIError => e
50
50
  raise ArgumentError, "#{url} is not a valid URL: #{e}"
@@ -37,19 +37,19 @@ module Prometheus
37
37
  def init_request_metrics
38
38
  @requests = @registry.counter(
39
39
  :http_requests_total,
40
- 'A counter of the total number of HTTP requests made.')
41
- @requests_duration = @registry.counter(
42
- :http_request_duration_total_seconds,
43
- 'The total amount of time spent answering HTTP requests.')
40
+ 'A counter of the total number of HTTP requests made.',
41
+ )
44
42
  @durations = @registry.summary(
45
43
  :http_request_duration_seconds,
46
- 'A histogram of the response latency.')
44
+ 'A histogram of the response latency.',
45
+ )
47
46
  end
48
47
 
49
48
  def init_exception_metrics
50
49
  @exceptions = @registry.counter(
51
50
  :http_exceptions_total,
52
- 'A counter of the total number of exceptions raised.')
51
+ 'A counter of the total number of exceptions raised.',
52
+ )
53
53
  end
54
54
 
55
55
  def trace(env)
@@ -71,8 +71,7 @@ module Prometheus
71
71
 
72
72
  def record(labels, duration)
73
73
  @requests.increment(labels)
74
- @requests_duration.increment(labels, duration)
75
- @durations.add(labels, duration)
74
+ @durations.observe(labels, duration)
76
75
  rescue
77
76
  # TODO: log unexpected exception during request recording
78
77
  nil
@@ -11,7 +11,7 @@ module Prometheus
11
11
  class Exporter
12
12
  attr_reader :app, :registry, :path
13
13
 
14
- FORMATS = [Formats::Text]
14
+ FORMATS = [Formats::Text].freeze
15
15
  FALLBACK = Formats::Text
16
16
 
17
17
  def initialize(app, options = {})
@@ -5,6 +5,7 @@ require 'thread'
5
5
  require 'prometheus/client/counter'
6
6
  require 'prometheus/client/summary'
7
7
  require 'prometheus/client/gauge'
8
+ require 'prometheus/client/histogram'
8
9
 
9
10
  module Prometheus
10
11
  module Client
@@ -22,7 +23,7 @@ module Prometheus
22
23
 
23
24
  @mutex.synchronize do
24
25
  if exist?(name.to_sym)
25
- fail AlreadyRegisteredError, "#{name} has already been registered"
26
+ raise AlreadyRegisteredError, "#{name} has already been registered"
26
27
  else
27
28
  @metrics[name.to_sym] = metric
28
29
  end
@@ -43,6 +44,11 @@ module Prometheus
43
44
  register(Gauge.new(name, docstring, base_labels))
44
45
  end
45
46
 
47
+ def histogram(name, docstring, base_labels = {},
48
+ buckets = Histogram::DEFAULT_BUCKETS)
49
+ register(Histogram.new(name, docstring, base_labels, buckets))
50
+ end
51
+
46
52
  def exist?(name)
47
53
  @metrics.key?(name)
48
54
  end
@@ -8,6 +8,8 @@ module Prometheus
8
8
  # Summary is an accumulator for samples. It captures Numeric data and
9
9
  # provides an efficient quantile calculation mechanism.
10
10
  class Summary < Metric
11
+ extend Gem::Deprecate
12
+
11
13
  # Value represents the state of a Summary at a given point.
12
14
  class Value < Hash
13
15
  attr_accessor :sum, :total
@@ -27,10 +29,12 @@ module Prometheus
27
29
  end
28
30
 
29
31
  # Records a given value.
30
- def add(labels, value)
32
+ def observe(labels, value)
31
33
  label_set = label_set_for(labels)
32
34
  synchronize { @values[label_set].observe(value) }
33
35
  end
36
+ alias add observe
37
+ deprecate :add, :observe, 2016, 10
34
38
 
35
39
  # Returns the value for the given label set
36
40
  def get(labels = {})
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Prometheus
4
4
  module Client
5
- VERSION = '0.5.0'
5
+ VERSION = '0.6.0'
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: prometheus-client
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tobias Schmidt
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-01-12 00:00:00.000000000 Z
11
+ date: 2016-08-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: quantile
@@ -37,6 +37,7 @@ files:
37
37
  - lib/prometheus/client/counter.rb
38
38
  - lib/prometheus/client/formats/text.rb
39
39
  - lib/prometheus/client/gauge.rb
40
+ - lib/prometheus/client/histogram.rb
40
41
  - lib/prometheus/client/label_set_validator.rb
41
42
  - lib/prometheus/client/metric.rb
42
43
  - lib/prometheus/client/push.rb