phihos-fluent-plugin-prometheus 2.0.3.pre.1
Sign up to get free protection for your applications and to get access to all the features.
- 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 +50 -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 +49 -0
- data/lib/fluent/plugin/prometheus/data_store.rb +103 -0
- data/lib/fluent/plugin/prometheus/placeholder_expander.rb +132 -0
- data/lib/fluent/plugin/prometheus.rb +445 -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 +145 -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 +166 -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,49 @@
|
|
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
|
+
Fluent::Plugin::Prometheus.start_reset_threads(
|
37
|
+
@metrics,
|
38
|
+
@registry,
|
39
|
+
method(:thread_create),
|
40
|
+
method(:thread_current_running?),
|
41
|
+
@log
|
42
|
+
)
|
43
|
+
end
|
44
|
+
|
45
|
+
def process(tag, es)
|
46
|
+
instrument(tag, es, @metrics)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,103 @@
|
|
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
|
+
|
12
|
+
def reset_values
|
13
|
+
@store.reset_values
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
module Fluent
|
20
|
+
module Plugin
|
21
|
+
module Prometheus
|
22
|
+
# Stores all the data in simple hashes, one per metric. Each of these metrics
|
23
|
+
# synchronizes access to their hash, but multiple metrics can run observations
|
24
|
+
# concurrently.
|
25
|
+
class DataStore
|
26
|
+
class InvalidStoreSettingsError < StandardError; end
|
27
|
+
DEFAULT_METRIC_SETTINGS = { topk: 0 }
|
28
|
+
|
29
|
+
def for_metric(metric_name, metric_type:, metric_settings: {})
|
30
|
+
settings = DEFAULT_METRIC_SETTINGS.merge(metric_settings)
|
31
|
+
validate_metric_settings(metric_settings: settings)
|
32
|
+
MetricStore.new(metric_settings: settings)
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def validate_metric_settings(metric_settings:)
|
38
|
+
unless metric_settings.has_key?(:topk) &&
|
39
|
+
(metric_settings[:topk].is_a? Integer) &&
|
40
|
+
metric_settings[:topk] >= 0
|
41
|
+
raise InvalidStoreSettingsError,
|
42
|
+
"Metrics need a valid :topk key"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
class MetricStore
|
47
|
+
def initialize(metric_settings:)
|
48
|
+
@internal_store = Hash.new { |hash, key| hash[key] = 0.0 }
|
49
|
+
@topk = metric_settings[:topk]
|
50
|
+
@lock = Monitor.new
|
51
|
+
end
|
52
|
+
|
53
|
+
def synchronize
|
54
|
+
@lock.synchronize { yield }
|
55
|
+
end
|
56
|
+
|
57
|
+
def set(labels:, val:)
|
58
|
+
synchronize do
|
59
|
+
@internal_store[labels] = val.to_f
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def increment(labels:, by: 1)
|
64
|
+
synchronize do
|
65
|
+
@internal_store[labels] += by
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def get(labels:)
|
70
|
+
synchronize do
|
71
|
+
@internal_store[labels]
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def remove(labels:)
|
76
|
+
synchronize do
|
77
|
+
@internal_store.delete(labels)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def all_values
|
82
|
+
synchronize do
|
83
|
+
store = @internal_store.dup
|
84
|
+
if @topk > 0
|
85
|
+
store.sort_by { |_, value| -value }.first(@topk).to_h
|
86
|
+
else
|
87
|
+
store
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def reset_values
|
93
|
+
synchronize do
|
94
|
+
@internal_store.transform_values! { |_| 0.0 }
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
private_constant :MetricStore
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
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
|