phihos-fluent-plugin-prometheus 2.0.3
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 +7 -0
- data/.github/workflows/linux.yml +34 -0
- data/.gitignore +16 -0
- data/.rspec +2 -0
- data/.travis.yml +14 -0
- data/ChangeLog +43 -0
- data/Gemfile +4 -0
- data/LICENSE +202 -0
- data/README.md +537 -0
- data/Rakefile +7 -0
- data/fluent-plugin-prometheus.gemspec +22 -0
- data/lib/fluent/plugin/filter_prometheus.rb +43 -0
- data/lib/fluent/plugin/in_prometheus/async_wrapper.rb +47 -0
- data/lib/fluent/plugin/in_prometheus.rb +230 -0
- data/lib/fluent/plugin/in_prometheus_monitor.rb +107 -0
- data/lib/fluent/plugin/in_prometheus_output_monitor.rb +234 -0
- data/lib/fluent/plugin/in_prometheus_tail_monitor.rb +98 -0
- data/lib/fluent/plugin/out_prometheus.rb +42 -0
- data/lib/fluent/plugin/prometheus/data_store.rb +93 -0
- data/lib/fluent/plugin/prometheus/placeholder_expander.rb +132 -0
- data/lib/fluent/plugin/prometheus.rb +418 -0
- data/lib/fluent/plugin/prometheus_metrics.rb +77 -0
- data/misc/fluentd_sample.conf +170 -0
- data/misc/nginx_proxy.conf +22 -0
- data/misc/prometheus.yaml +13 -0
- data/misc/prometheus_alerts.yaml +59 -0
- data/spec/fluent/plugin/filter_prometheus_spec.rb +118 -0
- data/spec/fluent/plugin/in_prometheus_monitor_spec.rb +42 -0
- data/spec/fluent/plugin/in_prometheus_spec.rb +225 -0
- data/spec/fluent/plugin/in_prometheus_tail_monitor_spec.rb +42 -0
- data/spec/fluent/plugin/out_prometheus_spec.rb +139 -0
- 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 +248 -0
- data/spec/spec_helper.rb +10 -0
- metadata +176 -0
@@ -0,0 +1,42 @@
|
|
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
|
+
helpers :thread
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
super
|
14
|
+
@registry = ::Prometheus::Client.registry
|
15
|
+
end
|
16
|
+
|
17
|
+
def multi_workers_ready?
|
18
|
+
true
|
19
|
+
end
|
20
|
+
|
21
|
+
def configure(conf)
|
22
|
+
super
|
23
|
+
labels = parse_labels_elements(conf)
|
24
|
+
@metrics = Fluent::Plugin::Prometheus.parse_metrics_elements(conf, @registry, labels)
|
25
|
+
end
|
26
|
+
|
27
|
+
def start
|
28
|
+
super
|
29
|
+
Fluent::Plugin::Prometheus.start_retention_threads(
|
30
|
+
@metrics,
|
31
|
+
@registry,
|
32
|
+
method(:thread_create),
|
33
|
+
method(:thread_current_running?),
|
34
|
+
@log
|
35
|
+
)
|
36
|
+
end
|
37
|
+
|
38
|
+
def process(tag, es)
|
39
|
+
instrument(tag, es, @metrics)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
# The default Prometheus client data store has no means of removing values.
|
2
|
+
# For the "retention" feature we need to be able to remove metrics with specific labels after some time of inactivity.
|
3
|
+
# By patching the Metric class and using our own DataStore we implement that missing feature.
|
4
|
+
module Prometheus
|
5
|
+
module Client
|
6
|
+
class Metric
|
7
|
+
def remove(labels)
|
8
|
+
label_set = label_set_for(labels)
|
9
|
+
@store.remove(labels: label_set)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
module Fluent
|
16
|
+
module Plugin
|
17
|
+
module Prometheus
|
18
|
+
# Stores all the data in simple hashes, one per metric. Each of these metrics
|
19
|
+
# synchronizes access to their hash, but multiple metrics can run observations
|
20
|
+
# concurrently.
|
21
|
+
class DataStore
|
22
|
+
class InvalidStoreSettingsError < StandardError; end
|
23
|
+
DEFAULT_METRIC_SETTINGS = { topk: 0 }
|
24
|
+
|
25
|
+
def for_metric(metric_name, metric_type:, metric_settings: {})
|
26
|
+
settings = DEFAULT_METRIC_SETTINGS.merge(metric_settings)
|
27
|
+
validate_metric_settings(metric_settings: settings)
|
28
|
+
MetricStore.new(metric_settings: settings)
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def validate_metric_settings(metric_settings:)
|
34
|
+
unless metric_settings.has_key?(:topk) &&
|
35
|
+
(metric_settings[:topk].is_a? Integer) &&
|
36
|
+
metric_settings[:topk] >= 0
|
37
|
+
raise InvalidStoreSettingsError,
|
38
|
+
"Metrics need a valid :topk key"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
class MetricStore
|
43
|
+
def initialize(metric_settings:)
|
44
|
+
@internal_store = Hash.new { |hash, key| hash[key] = 0.0 }
|
45
|
+
@topk = metric_settings[:topk]
|
46
|
+
@lock = Monitor.new
|
47
|
+
end
|
48
|
+
|
49
|
+
def synchronize
|
50
|
+
@lock.synchronize { yield }
|
51
|
+
end
|
52
|
+
|
53
|
+
def set(labels:, val:)
|
54
|
+
synchronize do
|
55
|
+
@internal_store[labels] = val.to_f
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def increment(labels:, by: 1)
|
60
|
+
synchronize do
|
61
|
+
@internal_store[labels] += by
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def get(labels:)
|
66
|
+
synchronize do
|
67
|
+
@internal_store[labels]
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def remove(labels:)
|
72
|
+
synchronize do
|
73
|
+
@internal_store.delete(labels)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def all_values
|
78
|
+
synchronize do
|
79
|
+
store = @internal_store.dup
|
80
|
+
if @topk > 0
|
81
|
+
store.sort_by { |_, value| -value }.first(@topk).to_h
|
82
|
+
else
|
83
|
+
store
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
private_constant :MetricStore
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
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,418 @@
|
|
1
|
+
require 'prometheus/client'
|
2
|
+
require 'prometheus/client/formats/text'
|
3
|
+
require 'fluent/plugin/prometheus/placeholder_expander'
|
4
|
+
require 'fluent/plugin/prometheus/data_store'
|
5
|
+
|
6
|
+
module Fluent
|
7
|
+
module Plugin
|
8
|
+
module PrometheusLabelParser
|
9
|
+
def configure(conf)
|
10
|
+
super
|
11
|
+
# Check if running with multiple workers
|
12
|
+
sysconf = if self.respond_to?(:owner) && owner.respond_to?(:system_config)
|
13
|
+
owner.system_config
|
14
|
+
elsif self.respond_to?(:system_config)
|
15
|
+
self.system_config
|
16
|
+
else
|
17
|
+
nil
|
18
|
+
end
|
19
|
+
@multi_worker = sysconf && sysconf.workers ? (sysconf.workers > 1) : false
|
20
|
+
end
|
21
|
+
|
22
|
+
def parse_labels_elements(conf)
|
23
|
+
base_labels = Fluent::Plugin::Prometheus.parse_labels_elements(conf)
|
24
|
+
|
25
|
+
if @multi_worker
|
26
|
+
base_labels[:worker_id] = fluentd_worker_id.to_s
|
27
|
+
end
|
28
|
+
|
29
|
+
base_labels
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
module Prometheus
|
34
|
+
class AlreadyRegisteredError < StandardError; end
|
35
|
+
|
36
|
+
def self.parse_labels_elements(conf)
|
37
|
+
labels = conf.elements.select { |e| e.name == 'labels' }
|
38
|
+
if labels.size > 1
|
39
|
+
raise ConfigError, "labels section must have at most 1"
|
40
|
+
end
|
41
|
+
|
42
|
+
base_labels = {}
|
43
|
+
unless labels.empty?
|
44
|
+
labels.first.each do |key, value|
|
45
|
+
labels.first.has_key?(key)
|
46
|
+
|
47
|
+
# use RecordAccessor only for $. and $[ syntax
|
48
|
+
# otherwise use the value as is or expand the value by RecordTransformer for ${} syntax
|
49
|
+
if value.start_with?('$.') || value.start_with?('$[')
|
50
|
+
base_labels[key.to_sym] = PluginHelper::RecordAccessor::Accessor.new(value)
|
51
|
+
else
|
52
|
+
base_labels[key.to_sym] = value
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
base_labels
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.parse_metrics_elements(conf, registry, labels = {})
|
61
|
+
metrics = []
|
62
|
+
conf.elements.select { |element|
|
63
|
+
element.name == 'metric'
|
64
|
+
}.each { |element|
|
65
|
+
if element.has_key?('key') && (element['key'].start_with?('$.') || element['key'].start_with?('$['))
|
66
|
+
value = element['key']
|
67
|
+
element['key'] = PluginHelper::RecordAccessor::Accessor.new(value)
|
68
|
+
end
|
69
|
+
case element['type']
|
70
|
+
when 'summary'
|
71
|
+
metrics << Fluent::Plugin::Prometheus::Summary.new(element, registry, labels)
|
72
|
+
when 'gauge'
|
73
|
+
metrics << Fluent::Plugin::Prometheus::Gauge.new(element, registry, labels)
|
74
|
+
when 'counter'
|
75
|
+
metrics << Fluent::Plugin::Prometheus::Counter.new(element, registry, labels)
|
76
|
+
when 'histogram'
|
77
|
+
metrics << Fluent::Plugin::Prometheus::Histogram.new(element, registry, labels)
|
78
|
+
else
|
79
|
+
raise ConfigError, "type option must be 'counter', 'gauge', 'summary' or 'histogram'"
|
80
|
+
end
|
81
|
+
}
|
82
|
+
metrics
|
83
|
+
end
|
84
|
+
|
85
|
+
def self.start_retention_threads(metrics, registry, thread_create, thread_running, log)
|
86
|
+
metrics.select { |metric| metric.has_retention? }.each do |metric|
|
87
|
+
thread_create.call("prometheus_retention_#{metric.name}".to_sym) do
|
88
|
+
while thread_running.call()
|
89
|
+
metric.remove_expired_metrics(registry, log)
|
90
|
+
sleep(metric.retention_check_interval)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def self.placeholder_expander(log)
|
97
|
+
Fluent::Plugin::Prometheus::ExpandBuilder.new(log: log)
|
98
|
+
end
|
99
|
+
|
100
|
+
def stringify_keys(hash_to_stringify)
|
101
|
+
# Adapted from: https://www.jvt.me/posts/2019/09/07/ruby-hash-keys-string-symbol/
|
102
|
+
hash_to_stringify.map do |k,v|
|
103
|
+
value_or_hash = if v.instance_of? Hash
|
104
|
+
stringify_keys(v)
|
105
|
+
else
|
106
|
+
v
|
107
|
+
end
|
108
|
+
[k.to_s, value_or_hash]
|
109
|
+
end.to_h
|
110
|
+
end
|
111
|
+
|
112
|
+
def initialize
|
113
|
+
super
|
114
|
+
::Prometheus::Client.config.data_store = Fluent::Plugin::Prometheus::DataStore.new
|
115
|
+
end
|
116
|
+
|
117
|
+
def configure(conf)
|
118
|
+
super
|
119
|
+
@placeholder_values = {}
|
120
|
+
@placeholder_expander_builder = Fluent::Plugin::Prometheus.placeholder_expander(log)
|
121
|
+
@hostname = Socket.gethostname
|
122
|
+
end
|
123
|
+
|
124
|
+
def instrument_single(tag, time, record, metrics)
|
125
|
+
@placeholder_values[tag] ||= {
|
126
|
+
'tag' => tag,
|
127
|
+
'hostname' => @hostname,
|
128
|
+
'worker_id' => fluentd_worker_id,
|
129
|
+
}
|
130
|
+
|
131
|
+
record = stringify_keys(record)
|
132
|
+
placeholders = record.merge(@placeholder_values[tag])
|
133
|
+
expander = @placeholder_expander_builder.build(placeholders)
|
134
|
+
metrics.each do |metric|
|
135
|
+
begin
|
136
|
+
metric.instrument(record, expander)
|
137
|
+
rescue => e
|
138
|
+
log.warn "prometheus: failed to instrument a metric.", error_class: e.class, error: e, tag: tag, name: metric.name
|
139
|
+
router.emit_error_event(tag, time, record, e)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def instrument(tag, es, metrics)
|
145
|
+
placeholder_values = {
|
146
|
+
'tag' => tag,
|
147
|
+
'hostname' => @hostname,
|
148
|
+
'worker_id' => fluentd_worker_id,
|
149
|
+
}
|
150
|
+
|
151
|
+
es.each do |time, record|
|
152
|
+
record = stringify_keys(record)
|
153
|
+
placeholders = record.merge(placeholder_values)
|
154
|
+
expander = @placeholder_expander_builder.build(placeholders)
|
155
|
+
metrics.each do |metric|
|
156
|
+
begin
|
157
|
+
metric.instrument(record, expander)
|
158
|
+
rescue => e
|
159
|
+
log.warn "prometheus: failed to instrument a metric.", error_class: e.class, error: e, tag: tag, name: metric.name
|
160
|
+
router.emit_error_event(tag, time, record, e)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
class Metric
|
167
|
+
attr_reader :type
|
168
|
+
attr_reader :name
|
169
|
+
attr_reader :key
|
170
|
+
attr_reader :desc
|
171
|
+
attr_reader :retention
|
172
|
+
attr_reader :retention_check_interval
|
173
|
+
|
174
|
+
def initialize(element, registry, labels)
|
175
|
+
['name', 'desc'].each do |key|
|
176
|
+
if element[key].nil?
|
177
|
+
raise ConfigError, "metric requires '#{key}' option"
|
178
|
+
end
|
179
|
+
end
|
180
|
+
@type = element['type']
|
181
|
+
@name = element['name']
|
182
|
+
@key = element['key']
|
183
|
+
@desc = element['desc']
|
184
|
+
@retention = element['retention'].to_i
|
185
|
+
@retention_check_interval = element.fetch('retention_check_interval', 60).to_i
|
186
|
+
if has_retention?
|
187
|
+
@last_modified_store = LastModifiedStore.new
|
188
|
+
end
|
189
|
+
@topk = element['topk'].to_i
|
190
|
+
|
191
|
+
@base_labels = Fluent::Plugin::Prometheus.parse_labels_elements(element)
|
192
|
+
@base_labels = labels.merge(@base_labels)
|
193
|
+
end
|
194
|
+
|
195
|
+
def labels(record, expander)
|
196
|
+
label = {}
|
197
|
+
@base_labels.each do |k, v|
|
198
|
+
if v.is_a?(String)
|
199
|
+
label[k] = expander.expand(v)
|
200
|
+
else
|
201
|
+
label[k] = v.call(record)
|
202
|
+
end
|
203
|
+
end
|
204
|
+
label
|
205
|
+
end
|
206
|
+
|
207
|
+
def self.get(registry, name, type, docstring)
|
208
|
+
metric = registry.get(name)
|
209
|
+
|
210
|
+
# should have same type, docstring
|
211
|
+
if metric.type != type
|
212
|
+
raise AlreadyRegisteredError, "#{name} has already been registered as #{type} type"
|
213
|
+
end
|
214
|
+
if metric.docstring != docstring
|
215
|
+
raise AlreadyRegisteredError, "#{name} has already been registered with different docstring"
|
216
|
+
end
|
217
|
+
|
218
|
+
metric
|
219
|
+
end
|
220
|
+
|
221
|
+
def set_value?(value)
|
222
|
+
if value
|
223
|
+
return true
|
224
|
+
end
|
225
|
+
false
|
226
|
+
end
|
227
|
+
|
228
|
+
def instrument(record, expander)
|
229
|
+
value = self.value(record)
|
230
|
+
if self.set_value?(value)
|
231
|
+
labels = labels(record, expander)
|
232
|
+
set_value(value, labels)
|
233
|
+
if has_retention?
|
234
|
+
@last_modified_store.set_last_updated(labels)
|
235
|
+
end
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
def has_retention?
|
240
|
+
@retention > 0
|
241
|
+
end
|
242
|
+
|
243
|
+
def remove_expired_metrics(registry, log)
|
244
|
+
if has_retention?
|
245
|
+
metric = registry.get(@name)
|
246
|
+
|
247
|
+
expiration_time = Time.now - @retention
|
248
|
+
expired_label_sets = @last_modified_store.get_labels_not_modified_since(expiration_time)
|
249
|
+
|
250
|
+
expired_label_sets.each { |expired_label_set|
|
251
|
+
log.debug "Metric #{@name} with labels #{expired_label_set} expired. Removing..."
|
252
|
+
metric.remove(expired_label_set) # this method is supplied by the require at the top of this method
|
253
|
+
@last_modified_store.remove(expired_label_set)
|
254
|
+
}
|
255
|
+
else
|
256
|
+
log.warn('remove_expired_metrics should not be called when retention is not set for this metric!')
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
class LastModifiedStore
|
261
|
+
def initialize
|
262
|
+
@internal_store = Hash.new
|
263
|
+
@lock = Monitor.new
|
264
|
+
end
|
265
|
+
|
266
|
+
def synchronize
|
267
|
+
@lock.synchronize { yield }
|
268
|
+
end
|
269
|
+
|
270
|
+
def set_last_updated(labels)
|
271
|
+
synchronize do
|
272
|
+
@internal_store[labels] = Time.now
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
def remove(labels)
|
277
|
+
synchronize do
|
278
|
+
@internal_store.delete(labels)
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
def get_labels_not_modified_since(time)
|
283
|
+
synchronize do
|
284
|
+
@internal_store.select { |k, v| v < time }.keys
|
285
|
+
end
|
286
|
+
end
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
class Gauge < Metric
|
291
|
+
def initialize(element, registry, labels)
|
292
|
+
super
|
293
|
+
if @key.nil?
|
294
|
+
raise ConfigError, "gauge metric requires 'key' option"
|
295
|
+
end
|
296
|
+
|
297
|
+
begin
|
298
|
+
@gauge = registry.gauge(
|
299
|
+
element['name'].to_sym,
|
300
|
+
docstring: element['desc'],
|
301
|
+
labels: @base_labels.keys,
|
302
|
+
store_settings: { topk: @topk }
|
303
|
+
)
|
304
|
+
rescue ::Prometheus::Client::Registry::AlreadyRegisteredError
|
305
|
+
@gauge = Fluent::Plugin::Prometheus::Metric.get(registry, element['name'].to_sym, :gauge, element['desc'])
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
def value(record)
|
310
|
+
if @key.is_a?(String)
|
311
|
+
record[@key]
|
312
|
+
else
|
313
|
+
@key.call(record)
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
317
|
+
def set_value(value, labels)
|
318
|
+
@gauge.set(value, labels: labels)
|
319
|
+
end
|
320
|
+
end
|
321
|
+
|
322
|
+
class Counter < Metric
|
323
|
+
def initialize(element, registry, labels)
|
324
|
+
super
|
325
|
+
begin
|
326
|
+
@counter = registry.counter(
|
327
|
+
element['name'].to_sym,
|
328
|
+
docstring: element['desc'],
|
329
|
+
labels: @base_labels.keys,
|
330
|
+
store_settings: { topk: @topk }
|
331
|
+
)
|
332
|
+
rescue ::Prometheus::Client::Registry::AlreadyRegisteredError
|
333
|
+
@counter = Fluent::Plugin::Prometheus::Metric.get(registry, element['name'].to_sym, :counter, element['desc'])
|
334
|
+
end
|
335
|
+
end
|
336
|
+
|
337
|
+
def value(record)
|
338
|
+
if @key.nil?
|
339
|
+
1
|
340
|
+
elsif @key.is_a?(String)
|
341
|
+
record[@key]
|
342
|
+
else
|
343
|
+
@key.call(record)
|
344
|
+
end
|
345
|
+
end
|
346
|
+
|
347
|
+
def set_value?(value)
|
348
|
+
!value.nil?
|
349
|
+
end
|
350
|
+
|
351
|
+
def set_value(value, labels)
|
352
|
+
@counter.increment(by: value, labels: labels)
|
353
|
+
end
|
354
|
+
end
|
355
|
+
|
356
|
+
class Summary < Metric
|
357
|
+
def initialize(element, registry, labels)
|
358
|
+
super
|
359
|
+
if @key.nil?
|
360
|
+
raise ConfigError, "summary metric requires 'key' option"
|
361
|
+
end
|
362
|
+
|
363
|
+
begin
|
364
|
+
@summary = registry.summary(element['name'].to_sym, docstring: element['desc'], labels: @base_labels.keys)
|
365
|
+
rescue ::Prometheus::Client::Registry::AlreadyRegisteredError
|
366
|
+
@summary = Fluent::Plugin::Prometheus::Metric.get(registry, element['name'].to_sym, :summary, element['desc'])
|
367
|
+
end
|
368
|
+
end
|
369
|
+
|
370
|
+
def value(record)
|
371
|
+
if @key.is_a?(String)
|
372
|
+
record[@key]
|
373
|
+
else
|
374
|
+
@key.call(record)
|
375
|
+
end
|
376
|
+
end
|
377
|
+
|
378
|
+
def set_value(value, labels)
|
379
|
+
@summary.observe(value, labels: labels)
|
380
|
+
end
|
381
|
+
end
|
382
|
+
|
383
|
+
class Histogram < Metric
|
384
|
+
def initialize(element, registry, labels)
|
385
|
+
super
|
386
|
+
if @key.nil?
|
387
|
+
raise ConfigError, "histogram metric requires 'key' option"
|
388
|
+
end
|
389
|
+
|
390
|
+
begin
|
391
|
+
if element['buckets']
|
392
|
+
buckets = element['buckets'].split(/,/).map(&:strip).map do |e|
|
393
|
+
e[/\A\d+.\d+\Z/] ? e.to_f : e.to_i
|
394
|
+
end
|
395
|
+
@histogram = registry.histogram(element['name'].to_sym, docstring: element['desc'], labels: @base_labels.keys, buckets: buckets)
|
396
|
+
else
|
397
|
+
@histogram = registry.histogram(element['name'].to_sym, docstring: element['desc'], labels: @base_labels.keys)
|
398
|
+
end
|
399
|
+
rescue ::Prometheus::Client::Registry::AlreadyRegisteredError
|
400
|
+
@histogram = Fluent::Plugin::Prometheus::Metric.get(registry, element['name'].to_sym, :histogram, element['desc'])
|
401
|
+
end
|
402
|
+
end
|
403
|
+
|
404
|
+
def value(record)
|
405
|
+
if @key.is_a?(String)
|
406
|
+
record[@key]
|
407
|
+
else
|
408
|
+
@key.call(record)
|
409
|
+
end
|
410
|
+
end
|
411
|
+
|
412
|
+
def set_value(value, labels)
|
413
|
+
@histogram.observe(value, labels: labels)
|
414
|
+
end
|
415
|
+
end
|
416
|
+
end
|
417
|
+
end
|
418
|
+
end
|