prometheus-client 0.9.0 → 0.10.0.pre.alpha.1

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.
@@ -12,32 +12,26 @@ module Prometheus
12
12
  end
13
13
 
14
14
  # Sets the value for the given label set
15
- def set(labels, value)
15
+ def set(value, labels: {})
16
16
  unless value.is_a?(Numeric)
17
17
  raise ArgumentError, 'value must be a number'
18
18
  end
19
19
 
20
- @values[label_set_for(labels)] = value.to_f
20
+ @store.set(labels: label_set_for(labels), val: value)
21
21
  end
22
22
 
23
23
  # Increments Gauge value by 1 or adds the given value to the Gauge.
24
24
  # (The value can be negative, resulting in a decrease of the Gauge.)
25
- def increment(labels = {}, by = 1)
25
+ def increment(by: 1, labels: {})
26
26
  label_set = label_set_for(labels)
27
- synchronize do
28
- @values[label_set] ||= 0
29
- @values[label_set] += by
30
- end
27
+ @store.increment(labels: label_set, by: by)
31
28
  end
32
29
 
33
30
  # Decrements Gauge value by 1 or subtracts the given value from the Gauge.
34
31
  # (The value can be negative, resulting in a increase of the Gauge.)
35
- def decrement(labels = {}, by = 1)
32
+ def decrement(by: 1, labels: {})
36
33
  label_set = label_set_for(labels)
37
- synchronize do
38
- @values[label_set] ||= 0
39
- @values[label_set] -= by
40
- end
34
+ @store.increment(labels: label_set, by: -by)
41
35
  end
42
36
  end
43
37
  end
@@ -8,61 +8,109 @@ module Prometheus
8
8
  # or response sizes) and counts them in configurable buckets. It also
9
9
  # provides a sum of all observed values.
10
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.0
18
-
19
- buckets.each do |bucket|
20
- self[bucket] = 0.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
11
  # DEFAULT_BUCKETS are the default Histogram buckets. The default buckets
35
12
  # are tailored to broadly measure the response time (in seconds) of a
36
13
  # network service. (From DefBuckets client_golang)
37
14
  DEFAULT_BUCKETS = [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1,
38
15
  2.5, 5, 10].freeze
39
16
 
17
+ attr_reader :buckets
18
+
40
19
  # 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
20
+ def initialize(name,
21
+ docstring:,
22
+ labels: [],
23
+ preset_labels: {},
24
+ buckets: DEFAULT_BUCKETS,
25
+ store_settings: {})
26
+ raise ArgumentError, 'Unsorted buckets, typo?' unless sorted?(buckets)
44
27
 
45
28
  @buckets = buckets
46
- super(name, docstring, base_labels)
29
+ super(name,
30
+ docstring: docstring,
31
+ labels: labels,
32
+ preset_labels: preset_labels,
33
+ store_settings: store_settings)
34
+ end
35
+
36
+ def with_labels(labels)
37
+ self.class.new(name,
38
+ docstring: docstring,
39
+ labels: @labels,
40
+ preset_labels: preset_labels.merge(labels),
41
+ buckets: @buckets,
42
+ store_settings: @store_settings)
47
43
  end
48
44
 
49
45
  def type
50
46
  :histogram
51
47
  end
52
48
 
53
- def observe(labels, value)
54
- if labels[:le]
55
- raise ArgumentError, 'Label with name "le" is not permitted'
49
+ def observe(value, labels: {})
50
+ bucket = buckets.find {|upper_limit| upper_limit > value }
51
+ bucket = "+Inf" if bucket.nil?
52
+
53
+ base_label_set = label_set_for(labels)
54
+
55
+ # This is basically faster than doing `.merge`
56
+ bucket_label_set = base_label_set.dup
57
+ bucket_label_set[:le] = bucket.to_s
58
+ sum_label_set = base_label_set.dup
59
+ sum_label_set[:le] = "sum"
60
+
61
+ @store.synchronize do
62
+ @store.increment(labels: bucket_label_set, by: 1)
63
+ @store.increment(labels: sum_label_set, by: value)
56
64
  end
65
+ end
66
+
67
+ # Returns a hash with all the buckets plus +Inf (count) plus Sum for the given label set
68
+ def get(labels: {})
69
+ base_label_set = label_set_for(labels)
57
70
 
58
- label_set = label_set_for(labels)
59
- synchronize { @values[label_set].observe(value) }
71
+ all_buckets = buckets + ["+Inf", "sum"]
72
+
73
+ @store.synchronize do
74
+ all_buckets.each_with_object({}) do |upper_limit, acc|
75
+ acc[upper_limit.to_s] = @store.get(labels: base_label_set.merge(le: upper_limit.to_s))
76
+ end.tap do |acc|
77
+ accumulate_buckets(acc)
78
+ end
79
+ end
80
+ end
81
+
82
+ # Returns all label sets with their values expressed as hashes with their buckets
83
+ def values
84
+ v = @store.all_values
85
+
86
+ result = v.each_with_object({}) do |(label_set, v), acc|
87
+ actual_label_set = label_set.reject{|l| l == :le }
88
+ acc[actual_label_set] ||= @buckets.map{|b| [b.to_s, 0.0]}.to_h
89
+ acc[actual_label_set][label_set[:le].to_s] = v
90
+ end
91
+
92
+ result.each do |(label_set, v)|
93
+ accumulate_buckets(v)
94
+ end
60
95
  end
61
96
 
62
97
  private
63
98
 
64
- def default
65
- Value.new(@buckets)
99
+ # Modifies the passed in parameter
100
+ def accumulate_buckets(h)
101
+ bucket_acc = 0
102
+ buckets.each do |upper_limit|
103
+ bucket_value = h[upper_limit.to_s]
104
+ h[upper_limit.to_s] += bucket_acc
105
+ bucket_acc += bucket_value
106
+ end
107
+
108
+ inf_value = h["+Inf"] || 0.0
109
+ h["+Inf"] = inf_value + bucket_acc
110
+ end
111
+
112
+ def reserved_labels
113
+ [:le]
66
114
  end
67
115
 
68
116
  def sorted?(bucket)
@@ -6,18 +6,22 @@ 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].freeze
9
+ BASE_RESERVED_LABELS = [:job, :instance].freeze
10
10
 
11
11
  class LabelSetError < StandardError; end
12
12
  class InvalidLabelSetError < LabelSetError; end
13
13
  class InvalidLabelError < LabelSetError; end
14
14
  class ReservedLabelError < LabelSetError; end
15
15
 
16
- def initialize
16
+ attr_reader :expected_labels, :reserved_labels
17
+
18
+ def initialize(expected_labels:, reserved_labels: [])
19
+ @expected_labels = expected_labels.sort
20
+ @reserved_labels = BASE_RESERVED_LABELS + reserved_labels
17
21
  @validated = {}
18
22
  end
19
23
 
20
- def valid?(labels)
24
+ def validate_symbols!(labels)
21
25
  unless labels.respond_to?(:all?)
22
26
  raise InvalidLabelSetError, "#{labels} is not a valid label set"
23
27
  end
@@ -29,24 +33,24 @@ module Prometheus
29
33
  end
30
34
  end
31
35
 
32
- def validate(labels)
33
- return labels if @validated.key?(labels.hash)
36
+ def validate_labelset!(labelset)
37
+ return labelset if @validated.key?(labelset.hash)
34
38
 
35
- valid?(labels)
39
+ validate_symbols!(labelset)
36
40
 
37
- unless @validated.empty? || match?(labels, @validated.first.last)
41
+ unless keys_match?(labelset)
38
42
  raise InvalidLabelSetError, "labels must have the same signature " \
39
- "(keys given: #{labels.keys.sort} vs." \
40
- " keys expected: #{@validated.first.last.keys.sort}"
43
+ "(keys given: #{labelset.keys.sort} vs." \
44
+ " keys expected: #{expected_labels}"
41
45
  end
42
46
 
43
- @validated[labels.hash] = labels
47
+ @validated[labelset.hash] = labelset
44
48
  end
45
49
 
46
50
  private
47
51
 
48
- def match?(a, b)
49
- a.keys.sort == b.keys.sort
52
+ def keys_match?(labelset)
53
+ labelset.keys.sort == expected_labels
50
54
  end
51
55
 
52
56
  def validate_symbol(key)
@@ -62,7 +66,7 @@ module Prometheus
62
66
  end
63
67
 
64
68
  def validate_reserved_key(key)
65
- return true unless RESERVED_LABELS.include?(key)
69
+ return true unless reserved_labels.include?(key)
66
70
 
67
71
  raise ReservedLabelError, "#{key} is reserved"
68
72
  end
@@ -7,42 +7,63 @@ module Prometheus
7
7
  module Client
8
8
  # Metric
9
9
  class Metric
10
- attr_reader :name, :docstring, :base_labels
10
+ attr_reader :name, :docstring, :preset_labels
11
11
 
12
- def initialize(name, docstring, base_labels = {})
13
- @mutex = Mutex.new
14
- @validator = LabelSetValidator.new
15
- @values = Hash.new { |hash, key| hash[key] = default }
12
+ def initialize(name,
13
+ docstring:,
14
+ labels: [],
15
+ preset_labels: {},
16
+ store_settings: {})
16
17
 
17
18
  validate_name(name)
18
19
  validate_docstring(docstring)
19
- @validator.valid?(base_labels)
20
+ @validator = LabelSetValidator.new(expected_labels: labels,
21
+ reserved_labels: reserved_labels)
22
+ @validator.validate_symbols!(labels)
23
+ @validator.validate_symbols!(preset_labels)
24
+
25
+ @labels = labels
26
+ @store_settings = store_settings
20
27
 
21
28
  @name = name
22
29
  @docstring = docstring
23
- @base_labels = base_labels
30
+ @preset_labels = preset_labels
31
+
32
+ @store = Prometheus::Client.config.data_store.for_metric(
33
+ name,
34
+ metric_type: type,
35
+ metric_settings: store_settings
36
+ )
37
+
38
+ if preset_labels.keys.length == labels.length
39
+ @validator.validate_labelset!(preset_labels)
40
+ @all_labels_preset = true
41
+ end
24
42
  end
25
43
 
26
44
  # Returns the value for the given label set
27
- def get(labels = {})
28
- @validator.valid?(labels)
45
+ def get(labels: {})
46
+ label_set = label_set_for(labels)
47
+ @store.get(labels: label_set)
48
+ end
29
49
 
30
- @values[labels]
50
+ def with_labels(labels)
51
+ self.class.new(name,
52
+ docstring: docstring,
53
+ labels: @labels,
54
+ preset_labels: preset_labels.merge(labels),
55
+ store_settings: @store_settings)
31
56
  end
32
57
 
33
58
  # Returns all label sets with their values
34
59
  def values
35
- synchronize do
36
- @values.each_with_object({}) do |(labels, value), memo|
37
- memo[labels] = value
38
- end
39
- end
60
+ @store.all_values
40
61
  end
41
62
 
42
63
  private
43
64
 
44
- def default
45
- nil
65
+ def reserved_labels
66
+ []
46
67
  end
47
68
 
48
69
  def validate_name(name)
@@ -62,11 +83,9 @@ module Prometheus
62
83
  end
63
84
 
64
85
  def label_set_for(labels)
65
- @validator.validate(labels)
66
- end
67
-
68
- def synchronize
69
- @mutex.synchronize { yield }
86
+ # We've already validated, and there's nothing to merge. Save some cycles
87
+ return preset_labels if @all_labels_preset && labels.empty?
88
+ @validator.validate_labelset!(preset_labels.merge(labels))
70
89
  end
71
90
  end
72
91
  end
@@ -37,21 +37,39 @@ module Prometheus
37
37
  end
38
38
  end
39
39
 
40
- def counter(name, docstring, base_labels = {})
41
- register(Counter.new(name, docstring, base_labels))
40
+ def counter(name, docstring:, labels: [], preset_labels: {}, store_settings: {})
41
+ register(Counter.new(name,
42
+ docstring: docstring,
43
+ labels: labels,
44
+ preset_labels: preset_labels,
45
+ store_settings: {}))
42
46
  end
43
47
 
44
- def summary(name, docstring, base_labels = {})
45
- register(Summary.new(name, docstring, base_labels))
48
+ def summary(name, docstring:, labels: [], preset_labels: {}, store_settings: {})
49
+ register(Summary.new(name,
50
+ docstring: docstring,
51
+ labels: labels,
52
+ preset_labels: preset_labels,
53
+ store_settings: {}))
46
54
  end
47
55
 
48
- def gauge(name, docstring, base_labels = {})
49
- register(Gauge.new(name, docstring, base_labels))
56
+ def gauge(name, docstring:, labels: [], preset_labels: {}, store_settings: {})
57
+ register(Gauge.new(name,
58
+ docstring: docstring,
59
+ labels: labels,
60
+ preset_labels: preset_labels,
61
+ store_settings: {}))
50
62
  end
51
63
 
52
- def histogram(name, docstring, base_labels = {},
53
- buckets = Histogram::DEFAULT_BUCKETS)
54
- register(Histogram.new(name, docstring, base_labels, buckets))
64
+ def histogram(name, docstring:, labels: [], preset_labels: {},
65
+ buckets: Histogram::DEFAULT_BUCKETS,
66
+ store_settings: {})
67
+ register(Histogram.new(name,
68
+ docstring: docstring,
69
+ labels: labels,
70
+ preset_labels: preset_labels,
71
+ buckets: buckets,
72
+ store_settings: {}))
55
73
  end
56
74
 
57
75
  def exist?(name)
@@ -1,63 +1,54 @@
1
1
  # encoding: UTF-8
2
2
 
3
- require 'quantile'
4
3
  require 'prometheus/client/metric'
5
4
 
6
5
  module Prometheus
7
6
  module Client
8
7
  # Summary is an accumulator for samples. It captures Numeric data and
9
- # provides an efficient quantile calculation mechanism.
8
+ # provides the total count and sum of observations.
10
9
  class Summary < Metric
11
- extend Gem::Deprecate
12
-
13
- # Value represents the state of a Summary at a given point.
14
- class Value < Hash
15
- attr_accessor :sum, :total
16
-
17
- def initialize(estimator)
18
- @sum = estimator.sum
19
- @total = estimator.observations
20
-
21
- estimator.invariants.each do |invariant|
22
- self[invariant.quantile] = estimator.query(invariant.quantile)
23
- end
24
- end
25
- end
26
-
27
10
  def type
28
11
  :summary
29
12
  end
30
13
 
31
14
  # Records a given value.
32
- def observe(labels, value)
33
- label_set = label_set_for(labels)
34
- synchronize { @values[label_set].observe(value) }
15
+ def observe(value, labels: {})
16
+ base_label_set = label_set_for(labels)
17
+
18
+ @store.synchronize do
19
+ @store.increment(labels: base_label_set.merge(quantile: "count"), by: 1)
20
+ @store.increment(labels: base_label_set.merge(quantile: "sum"), by: value)
21
+ end
35
22
  end
36
- alias add observe
37
- deprecate :add, :observe, 2016, 10
38
23
 
39
- # Returns the value for the given label set
40
- def get(labels = {})
41
- @validator.valid?(labels)
24
+ # Returns a hash with "sum" and "count" as keys
25
+ def get(labels: {})
26
+ base_label_set = label_set_for(labels)
27
+
28
+ internal_counters = ["count", "sum"]
42
29
 
43
- synchronize do
44
- Value.new(@values[labels])
30
+ @store.synchronize do
31
+ internal_counters.each_with_object({}) do |counter, acc|
32
+ acc[counter] = @store.get(labels: base_label_set.merge(quantile: counter))
33
+ end
45
34
  end
46
35
  end
47
36
 
48
- # Returns all label sets with their values
37
+ # Returns all label sets with their values expressed as hashes with their sum/count
49
38
  def values
50
- synchronize do
51
- @values.each_with_object({}) do |(labels, value), memo|
52
- memo[labels] = Value.new(value)
53
- end
39
+ v = @store.all_values
40
+
41
+ v.each_with_object({}) do |(label_set, v), acc|
42
+ actual_label_set = label_set.reject{|l| l == :quantile }
43
+ acc[actual_label_set] ||= { "count" => 0.0, "sum" => 0.0 }
44
+ acc[actual_label_set][label_set[:quantile]] = v
54
45
  end
55
46
  end
56
47
 
57
48
  private
58
49
 
59
- def default
60
- Quantile::Estimator.new
50
+ def reserved_labels
51
+ [:quantile]
61
52
  end
62
53
  end
63
54
  end