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.
@@ -1,9 +1,34 @@
1
1
  require 'prometheus/client'
2
2
  require 'prometheus/client/formats/text'
3
- require 'fluent/plugin/filter_record_transformer'
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
- # Use internal class in order to expand placeholder
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
- @placeholder_expander = Fluent::Plugin::Prometheus.placeholder_expander(log)
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
- placeholders = @placeholder_expander.prepare_placeholders(placeholders)
123
+ expander = @placeholder_expander_builder.build(placeholders)
80
124
  metrics.each do |metric|
81
125
  begin
82
- metric.instrument(record, @placeholder_expander, placeholders)
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, placeholders)
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, placeholders)
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, placeholders)
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, placeholders), value)
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, placeholders)
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, placeholders), value)
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, placeholders)
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, placeholders), value)
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, placeholders)
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, placeholders), value)
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
@@ -47,7 +47,7 @@ ALERT FluentdQueueLength
47
47
  }
48
48
 
49
49
  ALERT FluentdRecordsCountsHigh
50
- IF sum(rate(fluentd_record_counts{job="fluentd"}[5m])) BY (instance) > (3 * sum(rate(fluentd_record_counts{job="fluentd"}[15m])) BY (instance))
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.registry }
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
- include_context 'simple_config'
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 include(name)
27
- es
28
- expect(registry.metrics.map(&:name)).to include(name)
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
- expect(es.first).to eq(message)
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
 
@@ -23,7 +23,6 @@ describe Fluent::Plugin::PrometheusMonitorInput do
23
23
  ]
24
24
 
25
25
  let(:config) { MONITOR_CONFIG }
26
- let(:port) { 24231 }
27
26
  let(:driver) { Fluent::Test::Driver::Input.new(Fluent::Plugin::PrometheusMonitorInput).configure(config) }
28
27
 
29
28
  describe '#configure' do