fluent-plugin-prometheus-thread 0.1.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.
@@ -0,0 +1,24 @@
1
+ require 'fluent/plugin/prometheus'
2
+
3
+ module Fluent
4
+ class PrometheusFilter < Filter
5
+ Plugin.register_filter('prometheus', self)
6
+ include Fluent::Prometheus
7
+
8
+ def initialize
9
+ super
10
+ @registry = ::Prometheus::Client.registry
11
+ end
12
+
13
+ def configure(conf)
14
+ super
15
+ labels = Fluent::Prometheus.parse_labels_elements(conf)
16
+ @metrics = Fluent::Prometheus.parse_metrics_elements(conf, @registry, labels)
17
+ end
18
+
19
+ def filter_stream(tag, es)
20
+ instrument(tag, es, @metrics)
21
+ es
22
+ end
23
+ end if defined?(Filter)
24
+ end
@@ -0,0 +1,62 @@
1
+ require 'fluent/input'
2
+ require 'fluent/plugin/prometheus'
3
+ require 'webrick'
4
+
5
+ module Fluent
6
+ class PrometheusInput < Input
7
+ Plugin.register_input('prometheus', self)
8
+
9
+ config_param :bind, :string, :default => '0.0.0.0'
10
+ config_param :port, :integer, :default => 24231
11
+ config_param :metrics_path, :string, :default => '/metrics'
12
+
13
+ attr_reader :registry
14
+
15
+ def initialize
16
+ super
17
+ @registry = ::Prometheus::Client.registry
18
+ end
19
+
20
+ def configure(conf)
21
+ super
22
+ end
23
+
24
+ def start
25
+ @server = WEBrick::HTTPServer.new(
26
+ BindAddress: @bind,
27
+ Port: @port,
28
+ Logger: WEBrick::Log.new(STDERR, WEBrick::Log::FATAL),
29
+ AccessLog: [],
30
+ )
31
+ @server.mount(@metrics_path, MonitorServlet, self)
32
+ @thread = Thread.new { @server.start }
33
+ end
34
+
35
+ def shutdown
36
+ if @server
37
+ @server.shutdown
38
+ @server = nil
39
+ end
40
+ if @thread
41
+ @thread.join
42
+ @thread = nil
43
+ end
44
+ end
45
+
46
+ class MonitorServlet < WEBrick::HTTPServlet::AbstractServlet
47
+ def initialize(server, prometheus)
48
+ @prometheus = prometheus
49
+ end
50
+
51
+ def do_GET(req, res)
52
+ res.status = 200
53
+ res['Content-Type'] = ::Prometheus::Client::Formats::Text::CONTENT_TYPE
54
+ res.body = ::Prometheus::Client::Formats::Text.marshal(@prometheus.registry)
55
+ rescue
56
+ res.status = 500
57
+ res['Content-Type'] = 'text/plain'
58
+ res.body = $!.to_s
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,101 @@
1
+ require 'fluent/input'
2
+ require 'fluent/plugin/in_monitor_agent'
3
+ require 'fluent/plugin/prometheus'
4
+ require 'webrick'
5
+
6
+ module Fluent
7
+ class PrometheusMonitorInput < Input
8
+ Plugin.register_input('prometheus_monitor', self)
9
+
10
+ config_param :interval, :time, :default => 5
11
+ attr_reader :registry
12
+
13
+ def initialize
14
+ super
15
+ @registry = ::Prometheus::Client.registry
16
+ end
17
+
18
+ def configure(conf)
19
+ super
20
+ hostname = Socket.gethostname
21
+ expander = Fluent::Prometheus.placeholder_expander(log)
22
+ placeholders = expander.prepare_placeholders(
23
+ Time.now, {'hostname' => hostname}, [])
24
+ @base_labels = Fluent::Prometheus.parse_labels_elements(conf)
25
+ @base_labels.each do |key, value|
26
+ @base_labels[key] = expander.expand(value, placeholders)
27
+ end
28
+
29
+ @monitor_agent = Fluent::MonitorAgentInput.new
30
+
31
+ buffer_queue_length = @registry.gauge(
32
+ :fluentd_status_buffer_queue_length,
33
+ 'Current buffer queue length.')
34
+ buffer_total_queued_size = @registry.gauge(
35
+ :fluentd_status_buffer_total_bytes,
36
+ 'Current total size of queued buffers.')
37
+ retry_counts = @registry.gauge(
38
+ :fluentd_status_retry_count,
39
+ 'Current retry counts.')
40
+
41
+ @monitor_info = {
42
+ 'buffer_queue_length' => buffer_queue_length,
43
+ 'buffer_total_queued_size' => buffer_total_queued_size,
44
+ 'retry_count' => retry_counts,
45
+ }
46
+ end
47
+
48
+ class TimerWatcher < Coolio::TimerWatcher
49
+ def initialize(interval, repeat, log, &callback)
50
+ @callback = callback
51
+ @log = log
52
+ super(interval, repeat)
53
+ end
54
+
55
+ def on_timer
56
+ @callback.call
57
+ rescue
58
+ @log.error $!.to_s
59
+ @log.error_backtrace
60
+ end
61
+ end
62
+
63
+ def start
64
+ @loop = Coolio::Loop.new
65
+ @timer = TimerWatcher.new(@interval, true, log, &method(:update_monitor_info))
66
+ @loop.attach(@timer)
67
+ @thread = Thread.new(&method(:run))
68
+ end
69
+
70
+ def shutdown
71
+ @loop.watchers.each {|w| w.detach }
72
+ @loop.stop
73
+ @thread.join
74
+ end
75
+
76
+ def run
77
+ @loop.run
78
+ rescue
79
+ log.error "unexpected error", :error=>$!.to_s
80
+ log.error_backtrace
81
+ end
82
+
83
+ def update_monitor_info
84
+ @monitor_agent.plugins_info_all.each do |info|
85
+ @monitor_info.each do |name, metric|
86
+ if info[name]
87
+ metric.set(labels(info), info[name])
88
+ end
89
+ end
90
+ end
91
+ end
92
+
93
+ def labels(plugin_info)
94
+ @base_labels.merge(
95
+ plugin_id: plugin_info["plugin_id"],
96
+ plugin_category: plugin_info["plugin_category"],
97
+ type: plugin_info["type"],
98
+ )
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,25 @@
1
+ require 'fluent/output'
2
+ require 'fluent/plugin/prometheus'
3
+
4
+ module Fluent
5
+ class PrometheusOutput < Output
6
+ Plugin.register_output('prometheus', self)
7
+ include Fluent::Prometheus
8
+
9
+ def initialize
10
+ super
11
+ @registry = ::Prometheus::Client.registry
12
+ end
13
+
14
+ def configure(conf)
15
+ super
16
+ labels = Fluent::Prometheus.parse_labels_elements(conf)
17
+ @metrics = Fluent::Prometheus.parse_metrics_elements(conf, @registry, labels)
18
+ end
19
+
20
+ def emit(tag, es, chain)
21
+ instrument(tag, es, @metrics)
22
+ chain.next
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,196 @@
1
+ require 'prometheus/client'
2
+ require 'prometheus/client/formats/text'
3
+
4
+ module Fluent
5
+ module Prometheus
6
+ class AlreadyRegisteredError < StandardError; end
7
+
8
+ def self.parse_labels_elements(conf)
9
+ labels = conf.elements.select { |e| e.name == 'labels' }
10
+ if labels.size > 1
11
+ raise ConfigError, "labels section must have at most 1"
12
+ end
13
+
14
+ base_labels = {}
15
+ unless labels.empty?
16
+ labels.first.each do |key, value|
17
+ labels.first.has_key?(key)
18
+ base_labels[key.to_sym] = value
19
+ end
20
+ end
21
+
22
+ base_labels
23
+ end
24
+
25
+ def self.parse_metrics_elements(conf, registry, labels = {})
26
+ metrics = []
27
+ conf.elements.select { |element|
28
+ element.name == 'metric'
29
+ }.each { |element|
30
+ case element['type']
31
+ when 'summary'
32
+ metrics << Fluent::Prometheus::Summary.new(element, registry, labels)
33
+ when 'gauge'
34
+ metrics << Fluent::Prometheus::Gauge.new(element, registry, labels)
35
+ when 'counter'
36
+ metrics << Fluent::Prometheus::Counter.new(element, registry, labels)
37
+ else
38
+ raise ConfigError, "type option must be 'counter', 'gauge' or 'summary'"
39
+ end
40
+ }
41
+ metrics
42
+ end
43
+
44
+ def self.placeholder_expander(log)
45
+ # Use internal class in order to expand placeholder
46
+ if defined?(Fluent::Filter) # for v0.12, built-in PlaceholderExpander
47
+ begin
48
+ require 'fluent/plugin/filter_record_transformer'
49
+ return Fluent::RecordTransformerFilter::PlaceholderExpander.new(log: log)
50
+ rescue LoadError => e
51
+ raise ConfigError, "cannot find filter_record_transformer plugin: #{e.message}"
52
+ end
53
+ else # for v0.10, use PlaceholderExapander in fluent-plugin-record-reformer plugin
54
+ begin
55
+ require 'fluent/plugin/out_record_reformer.rb'
56
+ return Fluent::RecordReformerOutput::PlaceholderExpander.new(log: log)
57
+ rescue LoadError => e
58
+ raise ConfigError, "cannot find fluent-plugin-record-reformer: #{e.message}"
59
+ end
60
+ end
61
+ end
62
+
63
+ def configure(conf)
64
+ super
65
+ @placeholder_expander = Fluent::Prometheus.placeholder_expander(log)
66
+ @hostname = Socket.gethostname
67
+ end
68
+
69
+ def instrument(tag, es, metrics)
70
+ placeholder_values = {
71
+ 'tag' => tag,
72
+ 'hostname' => @hostname,
73
+ }
74
+
75
+ es.each do |time, record|
76
+ placeholders = record.merge(placeholder_values)
77
+ placeholders = @placeholder_expander.prepare_placeholders(placeholders)
78
+ metrics.each do |metric|
79
+ begin
80
+ metric.instrument(record, @placeholder_expander, placeholders)
81
+ rescue => e
82
+ log.warn "prometheus: failed to instrument a metric.", error_class: e.class, error: e, tag: tag, name: metric.name
83
+ router.emit_error_event(tag, time, record, e)
84
+ end
85
+ end
86
+ end
87
+ end
88
+
89
+ class Metric
90
+ attr_reader :type
91
+ attr_reader :name
92
+ attr_reader :key
93
+ attr_reader :desc
94
+
95
+ def initialize(element, registry, labels)
96
+ ['name', 'desc'].each do |key|
97
+ if element[key].nil?
98
+ raise ConfigError, "metric requires '#{key}' option"
99
+ end
100
+ end
101
+ @type = element['type']
102
+ @name = element['name']
103
+ @key = element['key']
104
+ @desc = element['desc']
105
+
106
+ @base_labels = Fluent::Prometheus.parse_labels_elements(element)
107
+ @base_labels = labels.merge(@base_labels)
108
+ end
109
+
110
+ def labels(record, expander, placeholders)
111
+ label = {}
112
+ @base_labels.each do |k, v|
113
+ label[k] = expander.expand(v, placeholders)
114
+ end
115
+ label
116
+ end
117
+
118
+ def self.get(registry, name, type, docstring)
119
+ metric = registry.get(name)
120
+
121
+ # should have same type, docstring
122
+ if metric.type != type
123
+ raise AlreadyRegisteredError, "#{name} has already been registered as #{type} type"
124
+ end
125
+ if metric.docstring != docstring
126
+ raise AlreadyRegisteredError, "#{name} has already been registered with different docstring"
127
+ end
128
+
129
+ metric
130
+ end
131
+ end
132
+
133
+ class Gauge < Metric
134
+ def initialize(element, registry, labels)
135
+ super
136
+ if @key.nil?
137
+ raise ConfigError, "gauge metric requires 'key' option"
138
+ end
139
+
140
+ begin
141
+ @gauge = registry.gauge(element['name'].to_sym, element['desc'])
142
+ rescue ::Prometheus::Client::Registry::AlreadyRegisteredError
143
+ @gauge = Fluent::Prometheus::Metric.get(registry, element['name'].to_sym, :gauge, element['desc'])
144
+ end
145
+ end
146
+
147
+ def instrument(record, expander, placeholders)
148
+ if record[@key]
149
+ @gauge.set(labels(record, expander, placeholders), record[@key])
150
+ end
151
+ end
152
+ end
153
+
154
+ class Counter < Metric
155
+ def initialize(element, registry, labels)
156
+ super
157
+ begin
158
+ @counter = registry.counter(element['name'].to_sym, element['desc'])
159
+ rescue ::Prometheus::Client::Registry::AlreadyRegisteredError
160
+ @counter = Fluent::Prometheus::Metric.get(registry, element['name'].to_sym, :counter, element['desc'])
161
+ end
162
+ end
163
+
164
+ def instrument(record, expander, placeholders)
165
+ # use record value of the key if key is specified, otherwise just increment
166
+ value = @key ? record[@key] : 1
167
+
168
+ # ignore if record value is nil
169
+ return if value.nil?
170
+
171
+ @counter.increment(labels(record, expander, placeholders), value)
172
+ end
173
+ end
174
+
175
+ class Summary < Metric
176
+ def initialize(element, registry, labels)
177
+ super
178
+ if @key.nil?
179
+ raise ConfigError, "summary metric requires 'key' option"
180
+ end
181
+
182
+ begin
183
+ @summary = registry.summary(element['name'].to_sym, element['desc'])
184
+ rescue ::Prometheus::Client::Registry::AlreadyRegisteredError
185
+ @summary = Fluent::Prometheus::Metric.get(registry, element['name'].to_sym, :summary, element['desc'])
186
+ end
187
+ end
188
+
189
+ def instrument(record, expander, placeholders)
190
+ if record[@key]
191
+ @summary.add(labels(record, expander, placeholders), record[@key])
192
+ end
193
+ end
194
+ end
195
+ end
196
+ end
@@ -0,0 +1,135 @@
1
+ ## Prometheus Input Plugin Configuration
2
+
3
+ # input plugin that exports metrics
4
+ <source>
5
+ @type prometheus
6
+ </source>
7
+
8
+ # input plugin that collects metrics from MonitorAgent
9
+ <source>
10
+ @type prometheus_monitor
11
+ <labels>
12
+ host ${hostname}
13
+ </labels>
14
+ </source>
15
+
16
+ ## Nginx Access Log Configuration
17
+
18
+ <source>
19
+ @type tail
20
+ format nginx
21
+ tag nginx
22
+ path /var/log/nginx/access.log
23
+ pos_file /tmp/fluent_nginx.pos
24
+ types size:integer
25
+ </source>
26
+
27
+ <filter nginx>
28
+ @type prometheus
29
+
30
+ # You can use counter type with specifying a key,
31
+ # and increments counter by the value
32
+ <metric>
33
+ name nginx_size_counter_bytes
34
+ type counter
35
+ desc nginx bytes sent
36
+ key size
37
+ <labels>
38
+ host ${hostname}
39
+ </labels>
40
+ </metric>
41
+
42
+ # You can use counter type without specifying a key
43
+ # This just increments counter by 1
44
+ <metric>
45
+ name nginx_record_counts
46
+ type counter
47
+ desc the number of emited records
48
+ <labels>
49
+ host ${hostname}
50
+ </labels>
51
+ </metric>
52
+ </filter>
53
+
54
+ <match nginx>
55
+ @type copy
56
+ # for MonitorAgent sample
57
+ # <store>
58
+ # @id test_forward
59
+ # @type forward
60
+ # buffer_type memory
61
+ # <server>
62
+ # host 127.0.0.1
63
+ # port 20000
64
+ # </server>
65
+ # </store>
66
+ <store>
67
+ @type stdout
68
+ </store>
69
+ </match>
70
+
71
+ ## Nginx Proxy Log Configuration
72
+
73
+ <source>
74
+ @type tail
75
+ format ltsv
76
+ tag nginx_proxy
77
+ path /var/log/nginx/access_proxy.log
78
+ pos_file /tmp/fluent_nginx_proxy.pos
79
+ types size:integer,request_length:integer,bytes_sent:integer,body_bytes_sent:integer,request_time:float,upstream_response_time:float
80
+ </source>
81
+
82
+ <filter nginx_proxy>
83
+ @type prometheus
84
+
85
+ # common labels for all metrics
86
+ <labels>
87
+ host ${hostname}
88
+ method ${request_method}
89
+ status ${status}
90
+ </labels>
91
+
92
+ <metric>
93
+ name nginx_proxy_request_length_total_bytes
94
+ type counter
95
+ desc nginx proxy request length bytes
96
+ key request_length
97
+ </metric>
98
+ <metric>
99
+ name nginx_proxy_bytes_sent_total_bytes
100
+ type counter
101
+ desc nginx proxy bytes sent
102
+ key bytes_sent
103
+ </metric>
104
+ <metric>
105
+ name nginx_proxy_request_duration_total_milliseconds
106
+ type counter
107
+ desc nginx proxy request time
108
+ key request_time
109
+ </metric>
110
+ <metric>
111
+ name nginx_proxy_upstream_response_duration_total_milliseconds
112
+ type counter
113
+ desc nginx proxy upstream response time
114
+ key upstream_response_time
115
+ </metric>
116
+ <metric>
117
+ name nginx_proxy_request_duration_milliseconds
118
+ type summary
119
+ desc nginx proxy request duration summary
120
+ key request_time
121
+ </metric>
122
+ <metric>
123
+ name nginx_proxy_upstream_duration_milliseconds
124
+ type summary
125
+ desc nginx proxy upstream response duration summary
126
+ key upstream_response_time
127
+ </metric>
128
+ </filter>
129
+
130
+ <match nginx_proxy>
131
+ @type copy
132
+ <store>
133
+ @type stdout
134
+ </store>
135
+ </match>