fluent-plugin-prometheus 1.4.0 → 1.7.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -5,6 +5,7 @@ require 'fluent/plugin/prometheus'
5
5
  module Fluent::Plugin
6
6
  class PrometheusTailMonitorInput < Fluent::Plugin::Input
7
7
  Fluent::Plugin.register_input('prometheus_tail_monitor', self)
8
+ include Fluent::Plugin::PrometheusLabelParser
8
9
 
9
10
  helpers :timer
10
11
 
@@ -27,14 +28,14 @@ module Fluent::Plugin
27
28
  def configure(conf)
28
29
  super
29
30
  hostname = Socket.gethostname
30
- expander = Fluent::Plugin::Prometheus.placeholder_expander(log)
31
- placeholders = expander.prepare_placeholders({'hostname' => hostname, 'worker_id' => fluentd_worker_id})
32
- @base_labels = Fluent::Plugin::Prometheus.parse_labels_elements(conf)
31
+ expander_builder = Fluent::Plugin::Prometheus.placeholder_expander(log)
32
+ expander = expander_builder.prepare_placeholders({ 'hostname' => hostname, 'worker_id' => fluentd_worker_id })
33
+ @base_labels = parse_labels_elements(conf)
33
34
  @base_labels.each do |key, value|
34
35
  unless value.is_a?(String)
35
36
  raise Fluent::ConfigError, "record accessor syntax is not available in prometheus_tail_monitor"
36
37
  end
37
- @base_labels[key] = expander.expand(value, placeholders)
38
+ @base_labels[key] = expander.expand(value)
38
39
  end
39
40
 
40
41
  if defined?(Fluent::Plugin) && defined?(Fluent::Plugin::MonitorAgentInput)
@@ -4,6 +4,7 @@ require 'fluent/plugin/prometheus'
4
4
  module Fluent::Plugin
5
5
  class PrometheusOutput < Fluent::Plugin::Output
6
6
  Fluent::Plugin.register_output('prometheus', self)
7
+ include Fluent::Plugin::PrometheusLabelParser
7
8
  include Fluent::Plugin::Prometheus
8
9
 
9
10
  def initialize
@@ -17,7 +18,7 @@ module Fluent::Plugin
17
18
 
18
19
  def configure(conf)
19
20
  super
20
- labels = Fluent::Plugin::Prometheus.parse_labels_elements(conf)
21
+ labels = parse_labels_elements(conf)
21
22
  @metrics = Fluent::Plugin::Prometheus.parse_metrics_elements(conf, @registry, labels)
22
23
  end
23
24
 
@@ -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