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.
- checksums.yaml +4 -4
- data/README.md +230 -19
- data/lib/prometheus/client.rb +5 -0
- data/lib/prometheus/client/config.rb +15 -0
- data/lib/prometheus/client/counter.rb +2 -8
- data/lib/prometheus/client/data_stores/README.md +306 -0
- data/lib/prometheus/client/data_stores/direct_file_store.rb +313 -0
- data/lib/prometheus/client/data_stores/single_threaded.rb +58 -0
- data/lib/prometheus/client/data_stores/synchronized.rb +64 -0
- data/lib/prometheus/client/formats/text.rb +8 -14
- data/lib/prometheus/client/gauge.rb +6 -12
- data/lib/prometheus/client/histogram.rb +82 -34
- data/lib/prometheus/client/label_set_validator.rb +17 -13
- data/lib/prometheus/client/metric.rb +41 -22
- data/lib/prometheus/client/registry.rb +27 -9
- data/lib/prometheus/client/summary.rb +26 -35
- data/lib/prometheus/client/version.rb +1 -1
- data/lib/prometheus/middleware/collector.rb +32 -29
- metadata +36 -12
@@ -12,32 +12,26 @@ module Prometheus
|
|
12
12
|
end
|
13
13
|
|
14
14
|
# Sets the value for the given label set
|
15
|
-
def set(
|
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
|
-
@
|
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
|
25
|
+
def increment(by: 1, labels: {})
|
26
26
|
label_set = label_set_for(labels)
|
27
|
-
|
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
|
32
|
+
def decrement(by: 1, labels: {})
|
36
33
|
label_set = label_set_for(labels)
|
37
|
-
|
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,
|
42
|
-
|
43
|
-
|
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,
|
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(
|
54
|
-
|
55
|
-
|
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
|
-
|
59
|
-
|
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
|
-
|
65
|
-
|
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
|
-
|
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
|
-
|
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
|
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
|
33
|
-
return
|
36
|
+
def validate_labelset!(labelset)
|
37
|
+
return labelset if @validated.key?(labelset.hash)
|
34
38
|
|
35
|
-
|
39
|
+
validate_symbols!(labelset)
|
36
40
|
|
37
|
-
unless
|
41
|
+
unless keys_match?(labelset)
|
38
42
|
raise InvalidLabelSetError, "labels must have the same signature " \
|
39
|
-
"(keys given: #{
|
40
|
-
" keys expected: #{
|
43
|
+
"(keys given: #{labelset.keys.sort} vs." \
|
44
|
+
" keys expected: #{expected_labels}"
|
41
45
|
end
|
42
46
|
|
43
|
-
@validated[
|
47
|
+
@validated[labelset.hash] = labelset
|
44
48
|
end
|
45
49
|
|
46
50
|
private
|
47
51
|
|
48
|
-
def
|
49
|
-
|
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
|
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, :
|
10
|
+
attr_reader :name, :docstring, :preset_labels
|
11
11
|
|
12
|
-
def initialize(name,
|
13
|
-
|
14
|
-
|
15
|
-
|
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.
|
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
|
-
@
|
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
|
-
|
45
|
+
def get(labels: {})
|
46
|
+
label_set = label_set_for(labels)
|
47
|
+
@store.get(labels: label_set)
|
48
|
+
end
|
29
49
|
|
30
|
-
|
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
|
-
|
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
|
45
|
-
|
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
|
-
|
66
|
-
|
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,
|
41
|
-
register(Counter.new(name,
|
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,
|
45
|
-
register(Summary.new(name,
|
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,
|
49
|
-
register(Gauge.new(name,
|
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,
|
53
|
-
buckets
|
54
|
-
|
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
|
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(
|
33
|
-
|
34
|
-
|
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
|
40
|
-
def get(labels
|
41
|
-
|
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
|
-
|
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
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
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
|
60
|
-
|
50
|
+
def reserved_labels
|
51
|
+
[:quantile]
|
61
52
|
end
|
62
53
|
end
|
63
54
|
end
|