fluent-plugin-prometheus-smarter 1.8.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,95 @@
1
+ require 'fluent/plugin/input'
2
+ require 'fluent/plugin/in_monitor_agent'
3
+ require 'fluent/plugin/prometheus'
4
+
5
+ module Fluent::Plugin
6
+ class PrometheusTailMonitorInput < Fluent::Plugin::Input
7
+ Fluent::Plugin.register_input('prometheus_tail_monitor', self)
8
+ include Fluent::Plugin::PrometheusLabelParser
9
+
10
+ helpers :timer
11
+
12
+ config_param :interval, :time, default: 5
13
+ attr_reader :registry
14
+
15
+ MONITOR_IVARS = [
16
+ :tails,
17
+ ]
18
+
19
+ def initialize
20
+ super
21
+ @registry = ::Prometheus::Client.registry
22
+ end
23
+
24
+ def multi_workers_ready?
25
+ true
26
+ end
27
+
28
+ def configure(conf)
29
+ super
30
+ hostname = Socket.gethostname
31
+ expander_builder = Fluent::Plugin::Prometheus.placeholder_expander(log)
32
+ expander = expander_builder.build({ 'hostname' => hostname, 'worker_id' => fluentd_worker_id })
33
+ @base_labels = parse_labels_elements(conf)
34
+ @base_labels.each do |key, value|
35
+ unless value.is_a?(String)
36
+ raise Fluent::ConfigError, "record accessor syntax is not available in prometheus_tail_monitor"
37
+ end
38
+ @base_labels[key] = expander.expand(value)
39
+ end
40
+
41
+ if defined?(Fluent::Plugin) && defined?(Fluent::Plugin::MonitorAgentInput)
42
+ # from v0.14.6
43
+ @monitor_agent = Fluent::Plugin::MonitorAgentInput.new
44
+ else
45
+ @monitor_agent = Fluent::MonitorAgentInput.new
46
+ end
47
+ end
48
+
49
+ def start
50
+ super
51
+
52
+ @metrics = {
53
+ position: @registry.gauge(
54
+ :fluentd_tail_file_position,
55
+ 'Current position of file.'),
56
+ inode: @registry.gauge(
57
+ :fluentd_tail_file_inode,
58
+ 'Current inode of file.'),
59
+ }
60
+ timer_execute(:in_prometheus_tail_monitor, @interval, &method(:update_monitor_info))
61
+ end
62
+
63
+ def update_monitor_info
64
+ opts = {
65
+ ivars: MONITOR_IVARS,
66
+ }
67
+
68
+ agent_info = @monitor_agent.plugins_info_all(opts).select {|info|
69
+ info['type'] == 'tail'.freeze
70
+ }
71
+
72
+ agent_info.each do |info|
73
+ tails = info['instance_variables'][:tails]
74
+ next if tails.nil?
75
+
76
+ tails.clone.each do |_, watcher|
77
+ # Access to internal variable of internal class...
78
+ # Very fragile implementation
79
+ pe = watcher.instance_variable_get(:@pe)
80
+ label = labels(info, watcher.path)
81
+ @metrics[:inode].set(label, pe.read_inode)
82
+ @metrics[:position].set(label, pe.read_pos)
83
+ end
84
+ end
85
+ end
86
+
87
+ def labels(plugin_info, path)
88
+ @base_labels.merge(
89
+ plugin_id: plugin_info["plugin_id"],
90
+ type: plugin_info["type"],
91
+ path: path,
92
+ )
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,71 @@
1
+ require 'fluent/plugin/output'
2
+ require 'fluent/plugin/prometheus'
3
+
4
+ module Fluent::Plugin
5
+ class PrometheusOutput < Fluent::Plugin::Output
6
+ Fluent::Plugin.register_output('prometheus_metric', self)
7
+ include Fluent::Plugin::PrometheusLabelParser
8
+ include Fluent::Plugin::Prometheus
9
+
10
+ def initialize
11
+ super
12
+ @registry = ::Prometheus::Client.registry
13
+ end
14
+
15
+ def multi_workers_ready?
16
+ true
17
+ end
18
+
19
+ config_param :key, :string
20
+
21
+ def labels(record, expander)
22
+ label = {}
23
+ labels.each do |k, v|
24
+ if v.is_a?(String)
25
+ label[k] = expander.expand(v)
26
+ else
27
+ label[k] = v.call(record)
28
+ end
29
+ end
30
+ label
31
+ end
32
+
33
+ def configure(conf)
34
+ super
35
+ @labels = parse_labels_elements(conf)
36
+ @placeholder_values = {}
37
+ @placeholder_expander_builder = Fluent::Plugin::Prometheus.placeholder_expander(log)
38
+ @hostname = Socket.gethostname
39
+ end
40
+
41
+ def process(tag, es)
42
+ placeholder_values = {
43
+ 'tag' => tag,
44
+ 'hostname' => @hostname,
45
+ 'worker_id' => fluentd_worker_id,
46
+ }
47
+
48
+ # Create metric if not exists
49
+ begin
50
+ gauge = registry.gauge(tag.to_sym)
51
+ rescue ::Prometheus::Client::Registry::AlreadyRegisteredError
52
+ gauge = registry.get(tag.to_sym)
53
+ end
54
+
55
+ # Write out values in event stream to Registry
56
+ es.each do |time, record|
57
+ placeholders = record.merge(placeholder_values)
58
+ expander = @placeholder_expander_builder.build(placeholders)
59
+ if @key.is_a?(String)
60
+ value = record[@key]
61
+ else
62
+ value = @key.call(record)
63
+ end
64
+ if value
65
+ gauge.set(labels(record, expander), value)
66
+ end
67
+ end
68
+ end
69
+
70
+ end
71
+ end
@@ -0,0 +1,29 @@
1
+ require 'fluent/plugin/output'
2
+ require 'fluent/plugin/prometheus'
3
+
4
+ module Fluent::Plugin
5
+ class PrometheusOutput < Fluent::Plugin::Output
6
+ Fluent::Plugin.register_output('prometheus', self)
7
+ include Fluent::Plugin::PrometheusLabelParser
8
+ include Fluent::Plugin::Prometheus
9
+
10
+ def initialize
11
+ super
12
+ @registry = ::Prometheus::Client.registry
13
+ end
14
+
15
+ def multi_workers_ready?
16
+ true
17
+ end
18
+
19
+ def configure(conf)
20
+ super
21
+ labels = parse_labels_elements(conf)
22
+ @metrics = Fluent::Plugin::Prometheus.parse_metrics_elements(conf, @registry, labels)
23
+ end
24
+
25
+ def process(tag, es)
26
+ instrument(tag, es, @metrics)
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,296 @@
1
+ require 'prometheus/client'
2
+ require 'prometheus/client/formats/text'
3
+ require 'fluent/plugin/prometheus/placeholder_expander'
4
+
5
+ module Fluent
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
+
32
+ module Prometheus
33
+ class AlreadyRegisteredError < StandardError; end
34
+
35
+ def self.parse_labels_elements(conf)
36
+ labels = conf.elements.select { |e| e.name == 'labels' }
37
+ if labels.size > 1
38
+ raise ConfigError, "labels section must have at most 1"
39
+ end
40
+
41
+ base_labels = {}
42
+ unless labels.empty?
43
+ labels.first.each do |key, value|
44
+ labels.first.has_key?(key)
45
+
46
+ # use RecordAccessor only for $. and $[ syntax
47
+ # otherwise use the value as is or expand the value by RecordTransformer for ${} syntax
48
+ if value.start_with?('$.') || value.start_with?('$[')
49
+ base_labels[key.to_sym] = PluginHelper::RecordAccessor::Accessor.new(value)
50
+ else
51
+ base_labels[key.to_sym] = value
52
+ end
53
+ end
54
+ end
55
+
56
+ base_labels
57
+ end
58
+
59
+ def self.parse_metrics_elements(conf, registry, labels = {})
60
+ metrics = []
61
+ conf.elements.select { |element|
62
+ element.name == 'metric'
63
+ }.each { |element|
64
+ if element.has_key?('key') && (element['key'].start_with?('$.') || element['key'].start_with?('$['))
65
+ value = element['key']
66
+ element['key'] = PluginHelper::RecordAccessor::Accessor.new(value)
67
+ end
68
+ case element['type']
69
+ when 'summary'
70
+ metrics << Fluent::Plugin::Prometheus::Summary.new(element, registry, labels)
71
+ when 'gauge'
72
+ metrics << Fluent::Plugin::Prometheus::Gauge.new(element, registry, labels)
73
+ when 'counter'
74
+ metrics << Fluent::Plugin::Prometheus::Counter.new(element, registry, labels)
75
+ when 'histogram'
76
+ metrics << Fluent::Plugin::Prometheus::Histogram.new(element, registry, labels)
77
+ else
78
+ raise ConfigError, "type option must be 'counter', 'gauge', 'summary' or 'histogram'"
79
+ end
80
+ }
81
+ metrics
82
+ end
83
+
84
+ def self.placeholder_expander(log)
85
+ Fluent::Plugin::Prometheus::ExpandBuilder.new(log: log)
86
+ end
87
+
88
+ def configure(conf)
89
+ super
90
+ @placeholder_values = {}
91
+ @placeholder_expander_builder = Fluent::Plugin::Prometheus.placeholder_expander(log)
92
+ @hostname = Socket.gethostname
93
+ end
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
+
114
+ def instrument(tag, es, metrics)
115
+ placeholder_values = {
116
+ 'tag' => tag,
117
+ 'hostname' => @hostname,
118
+ 'worker_id' => fluentd_worker_id,
119
+ }
120
+
121
+ es.each do |time, record|
122
+ placeholders = record.merge(placeholder_values)
123
+ expander = @placeholder_expander_builder.build(placeholders)
124
+ metrics.each do |metric|
125
+ begin
126
+ metric.instrument(record, expander)
127
+ rescue => e
128
+ log.warn "prometheus: failed to instrument a metric.", error_class: e.class, error: e, tag: tag, name: metric.name
129
+ router.emit_error_event(tag, time, record, e)
130
+ end
131
+ end
132
+ end
133
+ end
134
+
135
+ class Metric
136
+ attr_reader :type
137
+ attr_reader :name
138
+ attr_reader :key
139
+ attr_reader :desc
140
+
141
+ def initialize(element, registry, labels)
142
+ ['name', 'desc'].each do |key|
143
+ if element[key].nil?
144
+ raise ConfigError, "metric requires '#{key}' option"
145
+ end
146
+ end
147
+ @type = element['type']
148
+ @name = element['name']
149
+ @key = element['key']
150
+ @desc = element['desc']
151
+
152
+ @base_labels = Fluent::Plugin::Prometheus.parse_labels_elements(element)
153
+ @base_labels = labels.merge(@base_labels)
154
+ end
155
+
156
+ def labels(record, expander)
157
+ label = {}
158
+ @base_labels.each do |k, v|
159
+ if v.is_a?(String)
160
+ label[k] = expander.expand(v)
161
+ else
162
+ label[k] = v.call(record)
163
+ end
164
+ end
165
+ label
166
+ end
167
+
168
+ def self.get(registry, name, type, docstring)
169
+ metric = registry.get(name)
170
+
171
+ # should have same type, docstring
172
+ if metric.type != type
173
+ raise AlreadyRegisteredError, "#{name} has already been registered as #{type} type"
174
+ end
175
+ if metric.docstring != docstring
176
+ raise AlreadyRegisteredError, "#{name} has already been registered with different docstring"
177
+ end
178
+
179
+ metric
180
+ end
181
+ end
182
+
183
+ class Gauge < Metric
184
+ def initialize(element, registry, labels)
185
+ super
186
+ if @key.nil?
187
+ raise ConfigError, "gauge metric requires 'key' option"
188
+ end
189
+
190
+ begin
191
+ @gauge = registry.gauge(element['name'].to_sym, element['desc'])
192
+ rescue ::Prometheus::Client::Registry::AlreadyRegisteredError
193
+ @gauge = Fluent::Plugin::Prometheus::Metric.get(registry, element['name'].to_sym, :gauge, element['desc'])
194
+ end
195
+ end
196
+
197
+ def instrument(record, expander)
198
+ if @key.is_a?(String)
199
+ value = record[@key]
200
+ else
201
+ value = @key.call(record)
202
+ end
203
+ if value
204
+ @gauge.set(labels(record, expander), value)
205
+ end
206
+ end
207
+ end
208
+
209
+ class Counter < Metric
210
+ def initialize(element, registry, labels)
211
+ super
212
+ begin
213
+ @counter = registry.counter(element['name'].to_sym, element['desc'])
214
+ rescue ::Prometheus::Client::Registry::AlreadyRegisteredError
215
+ @counter = Fluent::Plugin::Prometheus::Metric.get(registry, element['name'].to_sym, :counter, element['desc'])
216
+ end
217
+ end
218
+
219
+ def instrument(record, expander)
220
+ # use record value of the key if key is specified, otherwise just increment
221
+ if @key.nil?
222
+ value = 1
223
+ elsif @key.is_a?(String)
224
+ value = record[@key]
225
+ else
226
+ value = @key.call(record)
227
+ end
228
+
229
+ # ignore if record value is nil
230
+ return if value.nil?
231
+
232
+ @counter.increment(labels(record, expander), value)
233
+ end
234
+ end
235
+
236
+ class Summary < Metric
237
+ def initialize(element, registry, labels)
238
+ super
239
+ if @key.nil?
240
+ raise ConfigError, "summary metric requires 'key' option"
241
+ end
242
+
243
+ begin
244
+ @summary = registry.summary(element['name'].to_sym, element['desc'])
245
+ rescue ::Prometheus::Client::Registry::AlreadyRegisteredError
246
+ @summary = Fluent::Plugin::Prometheus::Metric.get(registry, element['name'].to_sym, :summary, element['desc'])
247
+ end
248
+ end
249
+
250
+ def instrument(record, expander)
251
+ if @key.is_a?(String)
252
+ value = record[@key]
253
+ else
254
+ value = @key.call(record)
255
+ end
256
+ if value
257
+ @summary.observe(labels(record, expander), value)
258
+ end
259
+ end
260
+ end
261
+
262
+ class Histogram < Metric
263
+ def initialize(element, registry, labels)
264
+ super
265
+ if @key.nil?
266
+ raise ConfigError, "histogram metric requires 'key' option"
267
+ end
268
+
269
+ begin
270
+ if element['buckets']
271
+ buckets = element['buckets'].split(/,/).map(&:strip).map do |e|
272
+ e[/\A\d+.\d+\Z/] ? e.to_f : e.to_i
273
+ end
274
+ @histogram = registry.histogram(element['name'].to_sym, element['desc'], {}, buckets)
275
+ else
276
+ @histogram = registry.histogram(element['name'].to_sym, element['desc'])
277
+ end
278
+ rescue ::Prometheus::Client::Registry::AlreadyRegisteredError
279
+ @histogram = Fluent::Plugin::Prometheus::Metric.get(registry, element['name'].to_sym, :histogram, element['desc'])
280
+ end
281
+ end
282
+
283
+ def instrument(record, expander)
284
+ if @key.is_a?(String)
285
+ value = record[@key]
286
+ else
287
+ value = @key.call(record)
288
+ end
289
+ if value
290
+ @histogram.observe(labels(record, expander), value)
291
+ end
292
+ end
293
+ end
294
+ end
295
+ end
296
+ end