fluent-plugin-prometheus 1.5.0 → 1.7.2
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/.travis.yml +3 -3
- data/README.md +38 -18
- data/fluent-plugin-prometheus.gemspec +2 -2
- data/lib/fluent/plugin/filter_prometheus.rb +5 -4
- data/lib/fluent/plugin/in_prometheus.rb +50 -0
- data/lib/fluent/plugin/in_prometheus_monitor.rb +20 -5
- data/lib/fluent/plugin/in_prometheus_output_monitor.rb +40 -9
- data/lib/fluent/plugin/in_prometheus_tail_monitor.rb +5 -4
- data/lib/fluent/plugin/out_prometheus.rb +2 -1
- data/lib/fluent/plugin/prometheus.rb +60 -16
- data/lib/fluent/plugin/prometheus/placeholder_expander.rb +132 -0
- data/lib/fluent/plugin/prometheus_metrics.rb +77 -0
- data/misc/prometheus_alerts.yaml +1 -1
- data/spec/fluent/plugin/filter_prometheus_spec.rb +20 -10
- data/spec/fluent/plugin/in_prometheus_monitor_spec.rb +0 -1
- data/spec/fluent/plugin/{prometheus_spec.rb → in_prometheus_spec.rb} +0 -0
- data/spec/fluent/plugin/out_prometheus_spec.rb +18 -9
- data/spec/fluent/plugin/prometheus/placeholder_expander_spec.rb +110 -0
- data/spec/fluent/plugin/prometheus_metrics_spec.rb +138 -0
- data/spec/fluent/plugin/shared.rb +50 -101
- metadata +14 -8
@@ -1,9 +1,34 @@
|
|
1
1
|
require 'prometheus/client'
|
2
2
|
require 'prometheus/client/formats/text'
|
3
|
-
require 'fluent/plugin/
|
3
|
+
require 'fluent/plugin/prometheus/placeholder_expander'
|
4
4
|
|
5
5
|
module Fluent
|
6
6
|
module Plugin
|
7
|
+
module PrometheusLabelParser
|
8
|
+
def configure(conf)
|
9
|
+
super
|
10
|
+
# Check if running with multiple workers
|
11
|
+
sysconf = if self.respond_to?(:owner) && owner.respond_to?(:system_config)
|
12
|
+
owner.system_config
|
13
|
+
elsif self.respond_to?(:system_config)
|
14
|
+
self.system_config
|
15
|
+
else
|
16
|
+
nil
|
17
|
+
end
|
18
|
+
@multi_worker = sysconf && sysconf.workers ? (sysconf.workers > 1) : false
|
19
|
+
end
|
20
|
+
|
21
|
+
def parse_labels_elements(conf)
|
22
|
+
base_labels = Fluent::Plugin::Prometheus.parse_labels_elements(conf)
|
23
|
+
|
24
|
+
if @multi_worker
|
25
|
+
base_labels[:worker_id] = fluentd_worker_id.to_s
|
26
|
+
end
|
27
|
+
|
28
|
+
base_labels
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
7
32
|
module Prometheus
|
8
33
|
class AlreadyRegisteredError < StandardError; end
|
9
34
|
|
@@ -57,16 +82,35 @@ module Fluent
|
|
57
82
|
end
|
58
83
|
|
59
84
|
def self.placeholder_expander(log)
|
60
|
-
|
61
|
-
Fluent::Plugin::RecordTransformerFilter::PlaceholderExpander.new(log: log)
|
85
|
+
Fluent::Plugin::Prometheus::ExpandBuilder.new(log: log)
|
62
86
|
end
|
63
87
|
|
64
88
|
def configure(conf)
|
65
89
|
super
|
66
|
-
@
|
90
|
+
@placeholder_values = {}
|
91
|
+
@placeholder_expander_builder = Fluent::Plugin::Prometheus.placeholder_expander(log)
|
67
92
|
@hostname = Socket.gethostname
|
68
93
|
end
|
69
94
|
|
95
|
+
def instrument_single(tag, time, record, metrics)
|
96
|
+
@placeholder_values[tag] ||= {
|
97
|
+
'tag' => tag,
|
98
|
+
'hostname' => @hostname,
|
99
|
+
'worker_id' => fluentd_worker_id,
|
100
|
+
}
|
101
|
+
|
102
|
+
placeholders = record.merge(@placeholder_values[tag])
|
103
|
+
expander = @placeholder_expander_builder.build(placeholders)
|
104
|
+
metrics.each do |metric|
|
105
|
+
begin
|
106
|
+
metric.instrument(record, expander)
|
107
|
+
rescue => e
|
108
|
+
log.warn "prometheus: failed to instrument a metric.", error_class: e.class, error: e, tag: tag, name: metric.name
|
109
|
+
router.emit_error_event(tag, time, record, e)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
70
114
|
def instrument(tag, es, metrics)
|
71
115
|
placeholder_values = {
|
72
116
|
'tag' => tag,
|
@@ -76,10 +120,10 @@ module Fluent
|
|
76
120
|
|
77
121
|
es.each do |time, record|
|
78
122
|
placeholders = record.merge(placeholder_values)
|
79
|
-
|
123
|
+
expander = @placeholder_expander_builder.build(placeholders)
|
80
124
|
metrics.each do |metric|
|
81
125
|
begin
|
82
|
-
metric.instrument(record,
|
126
|
+
metric.instrument(record, expander)
|
83
127
|
rescue => e
|
84
128
|
log.warn "prometheus: failed to instrument a metric.", error_class: e.class, error: e, tag: tag, name: metric.name
|
85
129
|
router.emit_error_event(tag, time, record, e)
|
@@ -109,11 +153,11 @@ module Fluent
|
|
109
153
|
@base_labels = labels.merge(@base_labels)
|
110
154
|
end
|
111
155
|
|
112
|
-
def labels(record, expander
|
156
|
+
def labels(record, expander)
|
113
157
|
label = {}
|
114
158
|
@base_labels.each do |k, v|
|
115
159
|
if v.is_a?(String)
|
116
|
-
label[k] = expander.expand(v
|
160
|
+
label[k] = expander.expand(v)
|
117
161
|
else
|
118
162
|
label[k] = v.call(record)
|
119
163
|
end
|
@@ -150,14 +194,14 @@ module Fluent
|
|
150
194
|
end
|
151
195
|
end
|
152
196
|
|
153
|
-
def instrument(record, expander
|
197
|
+
def instrument(record, expander)
|
154
198
|
if @key.is_a?(String)
|
155
199
|
value = record[@key]
|
156
200
|
else
|
157
201
|
value = @key.call(record)
|
158
202
|
end
|
159
203
|
if value
|
160
|
-
@gauge.set(labels(record, expander
|
204
|
+
@gauge.set(labels(record, expander), value)
|
161
205
|
end
|
162
206
|
end
|
163
207
|
end
|
@@ -172,7 +216,7 @@ module Fluent
|
|
172
216
|
end
|
173
217
|
end
|
174
218
|
|
175
|
-
def instrument(record, expander
|
219
|
+
def instrument(record, expander)
|
176
220
|
# use record value of the key if key is specified, otherwise just increment
|
177
221
|
if @key.nil?
|
178
222
|
value = 1
|
@@ -185,7 +229,7 @@ module Fluent
|
|
185
229
|
# ignore if record value is nil
|
186
230
|
return if value.nil?
|
187
231
|
|
188
|
-
@counter.increment(labels(record, expander
|
232
|
+
@counter.increment(labels(record, expander), value)
|
189
233
|
end
|
190
234
|
end
|
191
235
|
|
@@ -203,14 +247,14 @@ module Fluent
|
|
203
247
|
end
|
204
248
|
end
|
205
249
|
|
206
|
-
def instrument(record, expander
|
250
|
+
def instrument(record, expander)
|
207
251
|
if @key.is_a?(String)
|
208
252
|
value = record[@key]
|
209
253
|
else
|
210
254
|
value = @key.call(record)
|
211
255
|
end
|
212
256
|
if value
|
213
|
-
@summary.observe(labels(record, expander
|
257
|
+
@summary.observe(labels(record, expander), value)
|
214
258
|
end
|
215
259
|
end
|
216
260
|
end
|
@@ -236,14 +280,14 @@ module Fluent
|
|
236
280
|
end
|
237
281
|
end
|
238
282
|
|
239
|
-
def instrument(record, expander
|
283
|
+
def instrument(record, expander)
|
240
284
|
if @key.is_a?(String)
|
241
285
|
value = record[@key]
|
242
286
|
else
|
243
287
|
value = @key.call(record)
|
244
288
|
end
|
245
289
|
if value
|
246
|
-
@histogram.observe(labels(record, expander
|
290
|
+
@histogram.observe(labels(record, expander), value)
|
247
291
|
end
|
248
292
|
end
|
249
293
|
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
module Fluent
|
2
|
+
module Plugin
|
3
|
+
module Prometheus
|
4
|
+
class ExpandBuilder
|
5
|
+
def self.build(placeholder, log:)
|
6
|
+
new(log: log).build(placeholder)
|
7
|
+
end
|
8
|
+
|
9
|
+
def initialize(log:)
|
10
|
+
@log = log
|
11
|
+
end
|
12
|
+
|
13
|
+
def build(placeholder_values)
|
14
|
+
placeholders = {}
|
15
|
+
placeholder_values.each do |key, value|
|
16
|
+
case value
|
17
|
+
when Array
|
18
|
+
size = value.size
|
19
|
+
value.each_with_index do |v, i|
|
20
|
+
placeholders["${#{key}[#{i}]}"] = v
|
21
|
+
placeholders["${#{key}[#{i - size}]}"] = v
|
22
|
+
end
|
23
|
+
when Hash
|
24
|
+
value.each do |k, v|
|
25
|
+
placeholders[%(${#{key}["#{k}"]})] = v
|
26
|
+
end
|
27
|
+
else
|
28
|
+
if key == 'tag'
|
29
|
+
placeholders.merge!(build_tag(value))
|
30
|
+
else
|
31
|
+
placeholders["${#{key}}"] = value
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
Fluent::Plugin::Prometheus::ExpandBuilder::PlaceholderExpander.new(@log, placeholders)
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def build_tag(tag)
|
42
|
+
tags = tag.split('.')
|
43
|
+
|
44
|
+
placeholders = { '${tag}' => tag }
|
45
|
+
|
46
|
+
size = tags.size
|
47
|
+
|
48
|
+
tags.each_with_index do |v, i|
|
49
|
+
placeholders["${tag_parts[#{i}]}"] = v
|
50
|
+
placeholders["${tag_parts[#{i - size}]}"] = v
|
51
|
+
end
|
52
|
+
|
53
|
+
tag_prefix(tags).each_with_index do |v, i|
|
54
|
+
placeholders["${tag_prefix[#{i}]}"] = v
|
55
|
+
end
|
56
|
+
|
57
|
+
tag_suffix(tags).each_with_index do |v, i|
|
58
|
+
placeholders["${tag_suffix[#{i}]}"] = v
|
59
|
+
end
|
60
|
+
|
61
|
+
placeholders
|
62
|
+
end
|
63
|
+
|
64
|
+
def tag_prefix(tags)
|
65
|
+
tags = tags.dup
|
66
|
+
return [] if tags.empty?
|
67
|
+
|
68
|
+
ret = [tags.shift]
|
69
|
+
tags.each.with_index(1) do |tag, i|
|
70
|
+
ret[i] = "#{ret[i-1]}.#{tag}"
|
71
|
+
end
|
72
|
+
ret
|
73
|
+
end
|
74
|
+
|
75
|
+
def tag_suffix(tags)
|
76
|
+
return [] if tags.empty?
|
77
|
+
|
78
|
+
tags = tags.dup.reverse
|
79
|
+
ret = [tags.shift]
|
80
|
+
tags.each.with_index(1) do |tag, i|
|
81
|
+
ret[i] = "#{tag}.#{ret[i-1]}"
|
82
|
+
end
|
83
|
+
ret
|
84
|
+
end
|
85
|
+
|
86
|
+
class PlaceholderExpander
|
87
|
+
PLACEHOLDER_REGEX = /(\${[^\[}]+(\[[^\]]+\])?})/.freeze
|
88
|
+
|
89
|
+
attr_reader :placeholder
|
90
|
+
|
91
|
+
def initialize(log, placeholder)
|
92
|
+
@placeholder = placeholder
|
93
|
+
@log = log
|
94
|
+
@expander_cache = {}
|
95
|
+
end
|
96
|
+
|
97
|
+
def merge_placeholder(placeholder)
|
98
|
+
@placeholder.merge!(placeholder)
|
99
|
+
end
|
100
|
+
|
101
|
+
def expand(str, dynamic_placeholders: nil)
|
102
|
+
expander = if dynamic_placeholders
|
103
|
+
if @expander_cache[dynamic_placeholders]
|
104
|
+
@expander_cache[dynamic_placeholders]
|
105
|
+
else
|
106
|
+
e = ExpandBuilder.build(dynamic_placeholders, log: @log)
|
107
|
+
e.merge_placeholder(@placeholder)
|
108
|
+
@expander_cache[dynamic_placeholders] = e
|
109
|
+
e
|
110
|
+
end
|
111
|
+
else
|
112
|
+
self
|
113
|
+
end
|
114
|
+
|
115
|
+
expander.expand!(str)
|
116
|
+
end
|
117
|
+
|
118
|
+
protected
|
119
|
+
|
120
|
+
def expand!(str)
|
121
|
+
str.gsub(PLACEHOLDER_REGEX) { |value|
|
122
|
+
@placeholder.fetch(value) do
|
123
|
+
@log.warn("unknown placeholder `#{value}` found")
|
124
|
+
value # return as it is
|
125
|
+
end
|
126
|
+
}
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
module Fluent::Plugin
|
2
|
+
|
3
|
+
##
|
4
|
+
# PromMetricsAggregator aggregates multiples metrics exposed using Prometheus text-based format
|
5
|
+
# see https://github.com/prometheus/docs/blob/master/content/docs/instrumenting/exposition_formats.md
|
6
|
+
|
7
|
+
|
8
|
+
class PrometheusMetrics
|
9
|
+
def initialize
|
10
|
+
@comments = []
|
11
|
+
@metrics = []
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_string
|
15
|
+
(@comments + @metrics).join("\n")
|
16
|
+
end
|
17
|
+
|
18
|
+
def add_comment(comment)
|
19
|
+
@comments << comment
|
20
|
+
end
|
21
|
+
|
22
|
+
def add_metric_value(value)
|
23
|
+
@metrics << value
|
24
|
+
end
|
25
|
+
|
26
|
+
attr_writer :comments, :metrics
|
27
|
+
end
|
28
|
+
|
29
|
+
class PromMetricsAggregator
|
30
|
+
def initialize
|
31
|
+
@metrics = {}
|
32
|
+
end
|
33
|
+
|
34
|
+
def get_metric_name_from_comment(line)
|
35
|
+
tokens = line.split(' ')
|
36
|
+
if ['HELP', 'TYPE'].include?(tokens[1])
|
37
|
+
tokens[2]
|
38
|
+
else
|
39
|
+
''
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def add_metrics(metrics)
|
44
|
+
current_metric = ''
|
45
|
+
new_metric = false
|
46
|
+
lines = metrics.split("\n")
|
47
|
+
for line in lines
|
48
|
+
if line[0] == '#'
|
49
|
+
# Metric comment (# TYPE, # HELP)
|
50
|
+
parsed_metric = get_metric_name_from_comment(line)
|
51
|
+
if parsed_metric != ''
|
52
|
+
if parsed_metric != current_metric
|
53
|
+
# Starting a new metric comment block
|
54
|
+
new_metric = !@metrics.key?(parsed_metric)
|
55
|
+
if new_metric
|
56
|
+
@metrics[parsed_metric] = PrometheusMetrics.new()
|
57
|
+
end
|
58
|
+
current_metric = parsed_metric
|
59
|
+
end
|
60
|
+
|
61
|
+
if new_metric && parsed_metric == current_metric
|
62
|
+
# New metric, inject comments (# TYPE, # HELP)
|
63
|
+
@metrics[parsed_metric].add_comment(line)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
else
|
67
|
+
# Metric value, simply append line
|
68
|
+
@metrics[current_metric].add_metric_value(line)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def get_metrics
|
74
|
+
@metrics.map{|k,v| v.to_string()}.join("\n") + (@metrics.length ? "\n" : "")
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
data/misc/prometheus_alerts.yaml
CHANGED
@@ -47,7 +47,7 @@ ALERT FluentdQueueLength
|
|
47
47
|
}
|
48
48
|
|
49
49
|
ALERT FluentdRecordsCountsHigh
|
50
|
-
IF sum(rate(
|
50
|
+
IF sum(rate(fluentd_output_status_emit_records{job="fluentd"}[5m])) BY (instance) > (3 * sum(rate(fluentd_output_status_emit_records{job="fluentd"}[15m])) BY (instance))
|
51
51
|
FOR 1m
|
52
52
|
LABELS {
|
53
53
|
service = "fluentd",
|
@@ -6,7 +6,11 @@ require_relative 'shared'
|
|
6
6
|
describe Fluent::Plugin::PrometheusFilter do
|
7
7
|
let(:tag) { 'prometheus.test' }
|
8
8
|
let(:driver) { Fluent::Test::Driver::Filter.new(Fluent::Plugin::PrometheusFilter).configure(config) }
|
9
|
-
let(:registry) { ::Prometheus::Client.
|
9
|
+
let(:registry) { ::Prometheus::Client::Registry.new }
|
10
|
+
|
11
|
+
before do
|
12
|
+
allow(Prometheus::Client).to receive(:registry).and_return(registry)
|
13
|
+
end
|
10
14
|
|
11
15
|
describe '#configure' do
|
12
16
|
it_behaves_like 'output configuration'
|
@@ -14,22 +18,28 @@ describe Fluent::Plugin::PrometheusFilter do
|
|
14
18
|
|
15
19
|
describe '#run' do
|
16
20
|
let(:message) { {"foo" => 100, "bar" => 100, "baz" => 100, "qux" => 10} }
|
17
|
-
let(:es) {
|
18
|
-
driver.run(default_tag: tag) { driver.feed(event_time, message) }
|
19
|
-
driver.filtered_records
|
20
|
-
}
|
21
21
|
|
22
22
|
context 'simple config' do
|
23
|
-
|
23
|
+
let(:config) {
|
24
|
+
BASE_CONFIG + %(
|
25
|
+
<metric>
|
26
|
+
name simple
|
27
|
+
type counter
|
28
|
+
desc Something foo.
|
29
|
+
key foo
|
30
|
+
</metric>
|
31
|
+
)
|
32
|
+
}
|
24
33
|
|
25
34
|
it 'adds a new counter metric' do
|
26
|
-
expect(registry.metrics.map(&:name)).not_to
|
27
|
-
|
28
|
-
expect(registry.metrics.map(&:name)).to
|
35
|
+
expect(registry.metrics.map(&:name)).not_to eq([:simple])
|
36
|
+
driver.run(default_tag: tag) { driver.feed(event_time, message) }
|
37
|
+
expect(registry.metrics.map(&:name)).to eq([:simple])
|
29
38
|
end
|
30
39
|
|
31
40
|
it 'should keep original message' do
|
32
|
-
|
41
|
+
driver.run(default_tag: tag) { driver.feed(event_time, message) }
|
42
|
+
expect(driver.filtered_records.first).to eq(message)
|
33
43
|
end
|
34
44
|
end
|
35
45
|
|
File without changes
|