fluent-plugin-prometheus-thread 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -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>