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.
- 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
|