phihos-fluent-plugin-prometheus 2.0.3.pre.1
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 +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
|