fluent-plugin-prometheus 2.2.1 → 2.3.0
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/.github/ISSUE_TEMPLATE/bug_report.yaml +72 -0
- data/.github/ISSUE_TEMPLATE/config.yml +5 -0
- data/.github/ISSUE_TEMPLATE/feature_request.yaml +38 -0
- data/.github/dependabot.yml +18 -1
- data/.github/workflows/add-to-project.yml +24 -0
- data/.github/workflows/linux.yml +13 -3
- data/.gitignore +1 -0
- data/ChangeLog +17 -0
- data/README.md +129 -2
- data/fluent-plugin-prometheus.gemspec +1 -1
- data/lib/fluent/plugin/filter_prometheus.rb +1 -1
- data/lib/fluent/plugin/in_prometheus/async_wrapper.rb +9 -2
- data/lib/fluent/plugin/in_prometheus.rb +73 -24
- data/lib/fluent/plugin/out_prometheus.rb +3 -1
- data/lib/fluent/plugin/prometheus/log_throttle.rb +45 -0
- data/lib/fluent/plugin/prometheus/placeholder_expander.rb +15 -1
- data/lib/fluent/plugin/prometheus.rb +299 -15
- data/spec/fluent/plugin/filter_prometheus_spec.rb +141 -0
- data/spec/fluent/plugin/in_prometheus_spec.rb +257 -1
- data/spec/fluent/plugin/out_prometheus_spec.rb +29 -1
- data/spec/fluent/plugin/prometheus/log_throttle_spec.rb +127 -0
- data/spec/fluent/plugin/prometheus/placeholder_expander_spec.rb +96 -0
- data/spec/fluent/plugin/prometheus/series_limit_spec.rb +469 -0
- data/spec/fluent/plugin/shared.rb +113 -1
- data/spec/spec_helper.rb +18 -0
- metadata +12 -6
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
require 'fluent/clock'
|
|
2
|
+
|
|
3
|
+
module Fluent
|
|
4
|
+
module Plugin
|
|
5
|
+
module Prometheus
|
|
6
|
+
# Suppresses the repeated log for the same key within the interval.
|
|
7
|
+
# in_prometheus and filter/out_prometheus use it, each with its own
|
|
8
|
+
# instance. The key decides what is throttled: an error scope or a
|
|
9
|
+
# metric. When a fingerprint is given, a log whose fingerprint differs
|
|
10
|
+
# from the last one is not suppressed.
|
|
11
|
+
class LogThrottle
|
|
12
|
+
Entry = Struct.new(:time, :fingerprint, :suppressed)
|
|
13
|
+
|
|
14
|
+
def initialize(interval)
|
|
15
|
+
@interval = interval
|
|
16
|
+
@mutex = Mutex.new
|
|
17
|
+
# one entry per key, so this does not grow without a limit
|
|
18
|
+
@entries = {}
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Returns [emit, suppressed_count]. emit is true for the first log of a
|
|
22
|
+
# key, for a new fingerprint, and after the interval has passed.
|
|
23
|
+
# suppressed_count is how many logs were suppressed since the last one
|
|
24
|
+
# was emitted. Without a fingerprint, a key is throttled by the
|
|
25
|
+
# interval alone.
|
|
26
|
+
def check(key, fingerprint = nil)
|
|
27
|
+
return [true, 0] if @interval <= 0
|
|
28
|
+
|
|
29
|
+
@mutex.synchronize do
|
|
30
|
+
now = Fluent::Clock.now
|
|
31
|
+
last = @entries[key]
|
|
32
|
+
if last.nil? || last.fingerprint != fingerprint || (now - last.time) >= @interval
|
|
33
|
+
suppressed = (last && last.fingerprint == fingerprint) ? last.suppressed : 0
|
|
34
|
+
@entries[key] = Entry.new(now, fingerprint, 0)
|
|
35
|
+
[true, suppressed]
|
|
36
|
+
else
|
|
37
|
+
last.suppressed += 1
|
|
38
|
+
[false, 0]
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -2,6 +2,10 @@ module Fluent
|
|
|
2
2
|
module Plugin
|
|
3
3
|
module Prometheus
|
|
4
4
|
class ExpandBuilder
|
|
5
|
+
# ${tag}, ${tag_parts[...]}, ${tag_prefix[...]} and ${tag_suffix[...]}
|
|
6
|
+
# must be built from the tag only.
|
|
7
|
+
TAG_DERIVED_PLACEHOLDER = /\A\$\{tag(_parts|_prefix|_suffix)?(\[[^\]]*\])?\}\z/.freeze
|
|
8
|
+
|
|
5
9
|
def self.build(placeholder, log:)
|
|
6
10
|
new(log: log).build(placeholder)
|
|
7
11
|
end
|
|
@@ -12,6 +16,7 @@ module Fluent
|
|
|
12
16
|
|
|
13
17
|
def build(placeholder_values)
|
|
14
18
|
placeholders = {}
|
|
19
|
+
tag_placeholders = {}
|
|
15
20
|
placeholder_values.each do |key, value|
|
|
16
21
|
case value
|
|
17
22
|
when Array
|
|
@@ -26,13 +31,22 @@ module Fluent
|
|
|
26
31
|
end
|
|
27
32
|
else
|
|
28
33
|
if key == 'tag'
|
|
29
|
-
|
|
34
|
+
tag_placeholders = build_tag(value)
|
|
30
35
|
else
|
|
31
36
|
placeholders["${#{key}}"] = value
|
|
32
37
|
end
|
|
33
38
|
end
|
|
34
39
|
end
|
|
35
40
|
|
|
41
|
+
# A record may have an array named "tag_parts", or a key named
|
|
42
|
+
# "tag_parts[0]". Both make the same placeholder as the tag does.
|
|
43
|
+
# merge! is not enough here, because a record can also use an index
|
|
44
|
+
# which build_tag does not make, such as ${tag_parts[3]} for a tag
|
|
45
|
+
# of 3 parts, or ${tag_prefix[-1]}. So remove them first, then such
|
|
46
|
+
# a placeholder stays unknown.
|
|
47
|
+
placeholders.delete_if { |k, _| TAG_DERIVED_PLACEHOLDER.match?(k) }
|
|
48
|
+
placeholders.merge!(tag_placeholders)
|
|
49
|
+
|
|
36
50
|
Fluent::Plugin::Prometheus::ExpandBuilder::PlaceholderExpander.new(@log, placeholders)
|
|
37
51
|
end
|
|
38
52
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
require 'prometheus/client'
|
|
2
2
|
require 'prometheus/client/formats/text'
|
|
3
|
+
require 'fluent/plugin/prometheus/log_throttle'
|
|
3
4
|
require 'fluent/plugin/prometheus/placeholder_expander'
|
|
4
5
|
|
|
5
6
|
module Fluent
|
|
@@ -31,6 +32,101 @@ module Fluent
|
|
|
31
32
|
|
|
32
33
|
module Prometheus
|
|
33
34
|
class AlreadyRegisteredError < StandardError; end
|
|
35
|
+
class LabelSetLimitError < StandardError; end
|
|
36
|
+
|
|
37
|
+
# 0 or less means unlimited. The limit is unlimited by default, because
|
|
38
|
+
# enabling it changes the existing metrics silently: dropping a label set
|
|
39
|
+
# loses the record without any way to recover it. An operator who needs
|
|
40
|
+
# to bound the cardinality has to opt in explicitly.
|
|
41
|
+
DEFAULT_MAX_SERIES_PER_METRIC = 0
|
|
42
|
+
DEFAULT_IGNORE_ERROR_LOG_INTERVAL = 3600
|
|
43
|
+
|
|
44
|
+
# the drops are visible in Prometheus, not only in the Fluentd log
|
|
45
|
+
DROPPED_LABEL_SETS_METRIC_NAME = :fluentd_prometheus_dropped_label_sets_total
|
|
46
|
+
DROPPED_LABEL_SETS_METRIC_DESC = 'The total number of records dropped because the metric reached max_series_per_metric.'
|
|
47
|
+
|
|
48
|
+
def self.included(klass)
|
|
49
|
+
klass.class_eval do
|
|
50
|
+
desc 'The maximum number of label sets a metric can hold. Exceeding label sets are dropped. 0 (default) means unlimited.'
|
|
51
|
+
config_param :max_series_per_metric, :integer, default: DEFAULT_MAX_SERIES_PER_METRIC
|
|
52
|
+
desc 'The interval to suppress the repeated warning about the drops.'
|
|
53
|
+
config_param :ignore_error_log_interval, :time, default: DEFAULT_IGNORE_ERROR_LOG_INTERVAL
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# The label sets a client metric holds. The client registry keys its
|
|
58
|
+
# metrics by name alone, so every <metric> section with the same name
|
|
59
|
+
# instruments the same client metric and shares this set, instead of
|
|
60
|
+
# holding max_series_per_metric label sets of its own.
|
|
61
|
+
class SeriesSet
|
|
62
|
+
# The set is kept on the client metric, so that it is found again by
|
|
63
|
+
# every section. The registry does not drop its metrics, so the set is
|
|
64
|
+
# still there after a reload.
|
|
65
|
+
IVAR = :@fluent_plugin_prometheus_series_set
|
|
66
|
+
|
|
67
|
+
# Sections are built at configuration time, which is single threaded,
|
|
68
|
+
# so this needs no lock.
|
|
69
|
+
def self.of(client_metric)
|
|
70
|
+
client_metric.instance_variable_get(IVAR) ||
|
|
71
|
+
client_metric.instance_variable_set(IVAR, new)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def initialize
|
|
75
|
+
@series = {}
|
|
76
|
+
@mutex = Mutex.new
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Only <initlabels> come from the configuration. Counting a label set
|
|
80
|
+
# a record brought would refuse a good configuration after a reload.
|
|
81
|
+
def initial_size
|
|
82
|
+
@mutex.synchronize { @series.count { |_, state| state == :initial } }
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Checking the limit and taking the slot happen under the same lock, so
|
|
86
|
+
# that concurrent calls cannot both take the last one. The slot stays
|
|
87
|
+
# :reserved until the instrumentation confirms it, so that a failing
|
|
88
|
+
# call can tell an in-flight reservation from a series the client holds.
|
|
89
|
+
def reserve(label, limit, name)
|
|
90
|
+
@mutex.synchronize do
|
|
91
|
+
next false if @series.key?(label)
|
|
92
|
+
|
|
93
|
+
if @series.size >= limit
|
|
94
|
+
raise LabelSetLimitError, "#{name} reached max_series_per_metric (#{limit})"
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
@series[label] = :reserved
|
|
98
|
+
next true
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Marks a label set as established, once the client actually holds it.
|
|
103
|
+
# The limit is not checked on purpose: the series exists on the client
|
|
104
|
+
# side already, so it has to be accounted for even when a concurrent
|
|
105
|
+
# failure gave the reservation back in the meantime.
|
|
106
|
+
def confirm(label)
|
|
107
|
+
@mutex.synchronize do
|
|
108
|
+
# #initial_size has to keep counting it, so a record on it does not
|
|
109
|
+
# change where it came from
|
|
110
|
+
next if @series[label] == :initial
|
|
111
|
+
|
|
112
|
+
@series[label] = :confirmed
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# The limit is not checked here either: a section which cannot hold
|
|
117
|
+
# these label sets is refused when the configuration is read.
|
|
118
|
+
def confirm_initial(label)
|
|
119
|
+
@mutex.synchronize { @series[label] = :initial }
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Gives a reserved slot back when the instrumentation failed, so that a
|
|
123
|
+
# label set the client does not hold does not consume the limit. One
|
|
124
|
+
# which a concurrent call confirmed in the meantime is kept: the client
|
|
125
|
+
# holds that series.
|
|
126
|
+
def release(label)
|
|
127
|
+
@mutex.synchronize { @series.delete(label) if @series[label] == :reserved }
|
|
128
|
+
end
|
|
129
|
+
end
|
|
34
130
|
|
|
35
131
|
def self.parse_labels_elements(conf)
|
|
36
132
|
labels = conf.elements.select { |e| e.name == 'labels' }
|
|
@@ -119,7 +215,7 @@ module Fluent
|
|
|
119
215
|
base_initlabels
|
|
120
216
|
end
|
|
121
217
|
|
|
122
|
-
def self.parse_metrics_elements(conf, registry, labels = {})
|
|
218
|
+
def self.parse_metrics_elements(conf, registry, labels = {}, opts = {})
|
|
123
219
|
metrics = []
|
|
124
220
|
conf.elements.select { |element|
|
|
125
221
|
element.name == 'metric'
|
|
@@ -130,17 +226,25 @@ module Fluent
|
|
|
130
226
|
end
|
|
131
227
|
case element['type']
|
|
132
228
|
when 'summary'
|
|
133
|
-
metrics << Fluent::Plugin::Prometheus::Summary.new(element, registry, labels)
|
|
229
|
+
metrics << Fluent::Plugin::Prometheus::Summary.new(element, registry, labels, opts)
|
|
134
230
|
when 'gauge'
|
|
135
|
-
metrics << Fluent::Plugin::Prometheus::Gauge.new(element, registry, labels)
|
|
231
|
+
metrics << Fluent::Plugin::Prometheus::Gauge.new(element, registry, labels, opts)
|
|
136
232
|
when 'counter'
|
|
137
|
-
metrics << Fluent::Plugin::Prometheus::Counter.new(element, registry, labels)
|
|
233
|
+
metrics << Fluent::Plugin::Prometheus::Counter.new(element, registry, labels, opts)
|
|
138
234
|
when 'histogram'
|
|
139
|
-
metrics << Fluent::Plugin::Prometheus::Histogram.new(element, registry, labels)
|
|
235
|
+
metrics << Fluent::Plugin::Prometheus::Histogram.new(element, registry, labels, opts)
|
|
140
236
|
else
|
|
141
237
|
raise ConfigError, "type option must be 'counter', 'gauge', 'summary' or 'histogram'"
|
|
142
238
|
end
|
|
143
239
|
}
|
|
240
|
+
|
|
241
|
+
# <metric> sections with the same name share one client
|
|
242
|
+
# metric. All of their <initlabels> label sets are known only
|
|
243
|
+
# after every section is built, so the check runs here and not
|
|
244
|
+
# in the constructor not to depend on the order of the
|
|
245
|
+
# sections.
|
|
246
|
+
metrics.each(&:check_series_limit!)
|
|
247
|
+
|
|
144
248
|
metrics
|
|
145
249
|
end
|
|
146
250
|
|
|
@@ -165,6 +269,49 @@ module Fluent
|
|
|
165
269
|
@placeholder_values = {}
|
|
166
270
|
@placeholder_expander_builder = Fluent::Plugin::Prometheus.placeholder_expander(log)
|
|
167
271
|
@hostname = Socket.gethostname
|
|
272
|
+
@label_set_limit_log_throttle = Fluent::Plugin::Prometheus::LogThrottle.new(@ignore_error_log_interval)
|
|
273
|
+
@dropped_label_sets_counter = nil
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
def metric_options
|
|
277
|
+
{
|
|
278
|
+
max_series_per_metric: @max_series_per_metric,
|
|
279
|
+
}
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
# Registered on the first occurrence only, so that a plugin which never
|
|
283
|
+
# drops anything does not expose a counter which stays 0 forever. Its
|
|
284
|
+
# labels come from the configuration, so they cannot blow up on their own.
|
|
285
|
+
def limit_counter(name, docstring, labels)
|
|
286
|
+
@registry.counter(name, docstring: docstring, labels: labels)
|
|
287
|
+
rescue ::Prometheus::Client::Registry::AlreadyRegisteredError
|
|
288
|
+
# another plugin instance shares the registry and registered it first
|
|
289
|
+
Fluent::Plugin::Prometheus::Metric.get(@registry, name, :counter, docstring)
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
def dropped_label_sets_counter
|
|
293
|
+
@dropped_label_sets_counter ||=
|
|
294
|
+
limit_counter(DROPPED_LABEL_SETS_METRIC_NAME, DROPPED_LABEL_SETS_METRIC_DESC, [:name])
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
def warn_label_set_limit(metric)
|
|
298
|
+
# the drop is always counted, while the log below is throttled
|
|
299
|
+
dropped_label_sets_counter.increment(labels: { name: metric.name.to_s })
|
|
300
|
+
|
|
301
|
+
warn_throttled(@label_set_limit_log_throttle, metric.name,
|
|
302
|
+
"prometheus: dropped a label set because the metric reached max_series_per_metric.",
|
|
303
|
+
name: metric.name, max_series_per_metric: metric.max_series_per_metric)
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
# The counter above is never throttled, only the log which comes with it:
|
|
307
|
+
# one line per record would flood the Fluentd log, and the count is in
|
|
308
|
+
# Prometheus already.
|
|
309
|
+
def warn_throttled(throttle, key, message, **details)
|
|
310
|
+
emit, suppressed = throttle.check(key)
|
|
311
|
+
return unless emit
|
|
312
|
+
|
|
313
|
+
details = details.merge(suppressed_log_count: suppressed) if suppressed > 0
|
|
314
|
+
log.warn(message, details)
|
|
168
315
|
end
|
|
169
316
|
|
|
170
317
|
def instrument_single(tag, time, record, metrics)
|
|
@@ -180,6 +327,9 @@ module Fluent
|
|
|
180
327
|
metrics.each do |metric|
|
|
181
328
|
begin
|
|
182
329
|
metric.instrument(record, expander)
|
|
330
|
+
rescue Fluent::Plugin::Prometheus::LabelSetLimitError
|
|
331
|
+
# dropping the label set is intended, so it is not an error event
|
|
332
|
+
warn_label_set_limit(metric)
|
|
183
333
|
rescue => e
|
|
184
334
|
log.warn "prometheus: failed to instrument a metric.", error_class: e.class, error: e, tag: tag, name: metric.name
|
|
185
335
|
router.emit_error_event(tag, time, record, e)
|
|
@@ -201,6 +351,9 @@ module Fluent
|
|
|
201
351
|
metrics.each do |metric|
|
|
202
352
|
begin
|
|
203
353
|
metric.instrument(record, expander)
|
|
354
|
+
rescue Fluent::Plugin::Prometheus::LabelSetLimitError
|
|
355
|
+
# dropping the label set is intended, so it is not an error event
|
|
356
|
+
warn_label_set_limit(metric)
|
|
204
357
|
rescue => e
|
|
205
358
|
log.warn "prometheus: failed to instrument a metric.", error_class: e.class, error: e, tag: tag, name: metric.name
|
|
206
359
|
router.emit_error_event(tag, time, record, e)
|
|
@@ -214,8 +367,9 @@ module Fluent
|
|
|
214
367
|
attr_reader :name
|
|
215
368
|
attr_reader :key
|
|
216
369
|
attr_reader :desc
|
|
370
|
+
attr_reader :max_series_per_metric
|
|
217
371
|
|
|
218
|
-
def initialize(element, registry, labels)
|
|
372
|
+
def initialize(element, registry, labels, opts = {})
|
|
219
373
|
['name', 'desc'].each do |key|
|
|
220
374
|
if element[key].nil?
|
|
221
375
|
raise ConfigError, "metric requires '#{key}' option"
|
|
@@ -230,6 +384,10 @@ module Fluent
|
|
|
230
384
|
@base_labels = Fluent::Plugin::Prometheus.parse_labels_elements(element)
|
|
231
385
|
@base_labels = labels.merge(@base_labels)
|
|
232
386
|
|
|
387
|
+
# <metric> overrides the limit given by the plugin
|
|
388
|
+
@max_series_per_metric = metric_limit(element, 'max_series_per_metric',
|
|
389
|
+
opts.fetch(:max_series_per_metric, DEFAULT_MAX_SERIES_PER_METRIC))
|
|
390
|
+
|
|
233
391
|
if @initialized
|
|
234
392
|
@base_initlabels = Fluent::Plugin::Prometheus.parse_initlabels_elements(element, @base_labels)
|
|
235
393
|
end
|
|
@@ -254,12 +412,31 @@ module Fluent
|
|
|
254
412
|
if v.is_a?(String)
|
|
255
413
|
label[k] = expander.expand(v)
|
|
256
414
|
else
|
|
257
|
-
label[k] = v.call(record)
|
|
415
|
+
label[k] = normalize_label_value(v.call(record))
|
|
258
416
|
end
|
|
259
417
|
end
|
|
260
418
|
label
|
|
261
419
|
end
|
|
262
420
|
|
|
421
|
+
# Instruments a record through the given block and keeps its label set
|
|
422
|
+
# as a series once the client holds it. The slot is taken before
|
|
423
|
+
# instrumenting and given back when the client refused the record, so
|
|
424
|
+
# that a label set the client does not hold does not exhaust
|
|
425
|
+
# max_series_per_metric.
|
|
426
|
+
def with_label_set(record, expander)
|
|
427
|
+
label = labels(record, expander)
|
|
428
|
+
reserved = reserve_series!(label)
|
|
429
|
+
begin
|
|
430
|
+
result = yield label
|
|
431
|
+
rescue
|
|
432
|
+
# a call which joined another's reservation has nothing to release
|
|
433
|
+
release_series(label) if reserved
|
|
434
|
+
raise
|
|
435
|
+
end
|
|
436
|
+
confirm_series(label)
|
|
437
|
+
result
|
|
438
|
+
end
|
|
439
|
+
|
|
263
440
|
def self.get(registry, name, type, docstring)
|
|
264
441
|
metric = registry.get(name)
|
|
265
442
|
|
|
@@ -273,10 +450,101 @@ module Fluent
|
|
|
273
450
|
|
|
274
451
|
metric
|
|
275
452
|
end
|
|
453
|
+
|
|
454
|
+
# <initlabels> label sets go to the client as soon as the
|
|
455
|
+
# configuration is read. Every <metric> section with the same name
|
|
456
|
+
# shares them, so reject a section which has no room for the label
|
|
457
|
+
# sets.
|
|
458
|
+
def check_series_limit!
|
|
459
|
+
return if @max_series_per_metric <= 0
|
|
460
|
+
# two <initlabels> blocks with the same values make one label set
|
|
461
|
+
held = @series_set.initial_size
|
|
462
|
+
return if held <= @max_series_per_metric
|
|
463
|
+
|
|
464
|
+
raise ConfigError, "metric #{@name} already holds #{held} label sets from <initlabels>, " \
|
|
465
|
+
"shared by every <metric> section with this name, " \
|
|
466
|
+
"but max_series_per_metric is #{@max_series_per_metric} in this section: " \
|
|
467
|
+
"the limit is already exceeded before any record arrives"
|
|
468
|
+
end
|
|
469
|
+
|
|
470
|
+
private
|
|
471
|
+
|
|
472
|
+
# The client refuses a value which is not a number, but it is only
|
|
473
|
+
# called once the label set has been reserved, and Summary refuses it
|
|
474
|
+
# only once it has already incremented its count: releasing the slot
|
|
475
|
+
# does not take that half instrumented series back from the client.
|
|
476
|
+
# Refuse the value before it reaches either.
|
|
477
|
+
def validate_value!(value)
|
|
478
|
+
return if value.is_a?(Numeric)
|
|
479
|
+
|
|
480
|
+
raise ArgumentError, 'value must be a number'
|
|
481
|
+
end
|
|
482
|
+
|
|
483
|
+
def metric_limit(element, name, default)
|
|
484
|
+
return default unless element.has_key?(name)
|
|
485
|
+
|
|
486
|
+
begin
|
|
487
|
+
# base 10 explicitly, so that a value like 08 is not an octal
|
|
488
|
+
Integer(element[name], 10)
|
|
489
|
+
rescue ArgumentError, TypeError
|
|
490
|
+
raise ConfigError, "#{name} in <metric> must be an integer: #{element[name]}"
|
|
491
|
+
end
|
|
492
|
+
end
|
|
493
|
+
|
|
494
|
+
# Called by a subclass, once it has its client metric.
|
|
495
|
+
def bind_series_set(client_metric)
|
|
496
|
+
@series_set = SeriesSet.of(client_metric)
|
|
497
|
+
|
|
498
|
+
return unless @initialized
|
|
499
|
+
|
|
500
|
+
# The client gets them even when this section has no limit, so they
|
|
501
|
+
# take their slots in both cases. A section with the same name shares
|
|
502
|
+
# this set and has to see them. Their number is fixed by the
|
|
503
|
+
# configuration. Counting them cannot leak like the label sets that
|
|
504
|
+
# records bring.
|
|
505
|
+
@base_initlabels.each do |initlabels|
|
|
506
|
+
@series_set.confirm_initial(normalize_label_set(initlabels))
|
|
507
|
+
end
|
|
508
|
+
end
|
|
509
|
+
|
|
510
|
+
# The SeriesSet keys a label set by its values, so the same value has to
|
|
511
|
+
# look the same whether a RecordAccessor or <initlabels> produced it.
|
|
512
|
+
def normalize_label_value(value)
|
|
513
|
+
value.is_a?(String) ? value : value.to_s
|
|
514
|
+
end
|
|
515
|
+
|
|
516
|
+
def normalize_label_set(label)
|
|
517
|
+
label.each_with_object({}) do |(k, v), normalized|
|
|
518
|
+
normalized[k] = normalize_label_value(v)
|
|
519
|
+
end
|
|
520
|
+
end
|
|
521
|
+
|
|
522
|
+
# Returns true when this call took the slot, which tells #with_label_set
|
|
523
|
+
# whether it has something to give back on failure.
|
|
524
|
+
def reserve_series!(label)
|
|
525
|
+
# If the limit is off, a label set from a record is not counted.
|
|
526
|
+
# If it is counted, the set grows with every new label set and
|
|
527
|
+
# uses too much memory.
|
|
528
|
+
return false if @max_series_per_metric <= 0
|
|
529
|
+
|
|
530
|
+
@series_set.reserve(label, @max_series_per_metric, @name)
|
|
531
|
+
end
|
|
532
|
+
|
|
533
|
+
def confirm_series(label)
|
|
534
|
+
return if @max_series_per_metric <= 0
|
|
535
|
+
|
|
536
|
+
@series_set.confirm(label)
|
|
537
|
+
end
|
|
538
|
+
|
|
539
|
+
def release_series(label)
|
|
540
|
+
return if @max_series_per_metric <= 0
|
|
541
|
+
|
|
542
|
+
@series_set.release(label)
|
|
543
|
+
end
|
|
276
544
|
end
|
|
277
545
|
|
|
278
546
|
class Gauge < Metric
|
|
279
|
-
def initialize(element, registry, labels)
|
|
547
|
+
def initialize(element, registry, labels, opts = {})
|
|
280
548
|
super
|
|
281
549
|
if @key.nil?
|
|
282
550
|
raise ConfigError, "gauge metric requires 'key' option"
|
|
@@ -287,6 +555,7 @@ module Fluent
|
|
|
287
555
|
rescue ::Prometheus::Client::Registry::AlreadyRegisteredError
|
|
288
556
|
@gauge = Fluent::Plugin::Prometheus::Metric.get(registry, element['name'].to_sym, :gauge, element['desc'])
|
|
289
557
|
end
|
|
558
|
+
bind_series_set(@gauge)
|
|
290
559
|
|
|
291
560
|
if @initialized
|
|
292
561
|
Fluent::Plugin::Prometheus::Metric.init_label_set(@gauge, @base_initlabels, @base_labels)
|
|
@@ -300,19 +569,23 @@ module Fluent
|
|
|
300
569
|
value = @key.call(record)
|
|
301
570
|
end
|
|
302
571
|
if value
|
|
303
|
-
|
|
572
|
+
validate_value!(value)
|
|
573
|
+
with_label_set(record, expander) do |label|
|
|
574
|
+
@gauge.set(value, labels: label)
|
|
575
|
+
end
|
|
304
576
|
end
|
|
305
577
|
end
|
|
306
578
|
end
|
|
307
579
|
|
|
308
580
|
class Counter < Metric
|
|
309
|
-
def initialize(element, registry, labels)
|
|
581
|
+
def initialize(element, registry, labels, opts = {})
|
|
310
582
|
super
|
|
311
583
|
begin
|
|
312
584
|
@counter = registry.counter(element['name'].to_sym, docstring: element['desc'], labels: @base_labels.keys)
|
|
313
585
|
rescue ::Prometheus::Client::Registry::AlreadyRegisteredError
|
|
314
586
|
@counter = Fluent::Plugin::Prometheus::Metric.get(registry, element['name'].to_sym, :counter, element['desc'])
|
|
315
587
|
end
|
|
588
|
+
bind_series_set(@counter)
|
|
316
589
|
|
|
317
590
|
if @initialized
|
|
318
591
|
Fluent::Plugin::Prometheus::Metric.init_label_set(@counter, @base_initlabels, @base_labels)
|
|
@@ -332,12 +605,15 @@ module Fluent
|
|
|
332
605
|
# ignore if record value is nil
|
|
333
606
|
return if value.nil?
|
|
334
607
|
|
|
335
|
-
|
|
608
|
+
validate_value!(value)
|
|
609
|
+
with_label_set(record, expander) do |label|
|
|
610
|
+
@counter.increment(by: value, labels: label)
|
|
611
|
+
end
|
|
336
612
|
end
|
|
337
613
|
end
|
|
338
614
|
|
|
339
615
|
class Summary < Metric
|
|
340
|
-
def initialize(element, registry, labels)
|
|
616
|
+
def initialize(element, registry, labels, opts = {})
|
|
341
617
|
super
|
|
342
618
|
if @key.nil?
|
|
343
619
|
raise ConfigError, "summary metric requires 'key' option"
|
|
@@ -348,6 +624,7 @@ module Fluent
|
|
|
348
624
|
rescue ::Prometheus::Client::Registry::AlreadyRegisteredError
|
|
349
625
|
@summary = Fluent::Plugin::Prometheus::Metric.get(registry, element['name'].to_sym, :summary, element['desc'])
|
|
350
626
|
end
|
|
627
|
+
bind_series_set(@summary)
|
|
351
628
|
|
|
352
629
|
if @initialized
|
|
353
630
|
Fluent::Plugin::Prometheus::Metric.init_label_set(@summary, @base_initlabels, @base_labels)
|
|
@@ -361,13 +638,16 @@ module Fluent
|
|
|
361
638
|
value = @key.call(record)
|
|
362
639
|
end
|
|
363
640
|
if value
|
|
364
|
-
|
|
641
|
+
validate_value!(value)
|
|
642
|
+
with_label_set(record, expander) do |label|
|
|
643
|
+
@summary.observe(value, labels: label)
|
|
644
|
+
end
|
|
365
645
|
end
|
|
366
646
|
end
|
|
367
647
|
end
|
|
368
648
|
|
|
369
649
|
class Histogram < Metric
|
|
370
|
-
def initialize(element, registry, labels)
|
|
650
|
+
def initialize(element, registry, labels, opts = {})
|
|
371
651
|
super
|
|
372
652
|
if @key.nil?
|
|
373
653
|
raise ConfigError, "histogram metric requires 'key' option"
|
|
@@ -385,6 +665,7 @@ module Fluent
|
|
|
385
665
|
rescue ::Prometheus::Client::Registry::AlreadyRegisteredError
|
|
386
666
|
@histogram = Fluent::Plugin::Prometheus::Metric.get(registry, element['name'].to_sym, :histogram, element['desc'])
|
|
387
667
|
end
|
|
668
|
+
bind_series_set(@histogram)
|
|
388
669
|
|
|
389
670
|
if @initialized
|
|
390
671
|
Fluent::Plugin::Prometheus::Metric.init_label_set(@histogram, @base_initlabels, @base_labels)
|
|
@@ -398,7 +679,10 @@ module Fluent
|
|
|
398
679
|
value = @key.call(record)
|
|
399
680
|
end
|
|
400
681
|
if value
|
|
401
|
-
|
|
682
|
+
validate_value!(value)
|
|
683
|
+
with_label_set(record, expander) do |label|
|
|
684
|
+
@histogram.observe(value, labels: label)
|
|
685
|
+
end
|
|
402
686
|
end
|
|
403
687
|
end
|
|
404
688
|
end
|