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

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