fluent-plugin-prometheus 0.1.0

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,61 @@
1
+ require 'fluent/plugin/prometheus'
2
+ require 'webrick'
3
+
4
+ module Fluent
5
+ class PrometheusInput < Input
6
+ Plugin.register_input('prometheus', self)
7
+
8
+ config_param :bind, :string, :default => '0.0.0.0'
9
+ config_param :port, :integer, :default => 24231
10
+ config_param :metrics_path, :string, :default => '/metrics'
11
+
12
+ attr_reader :registry
13
+
14
+ def initialize
15
+ super
16
+ @registry = ::Prometheus::Client.registry
17
+ end
18
+
19
+ def configure(conf)
20
+ super
21
+ end
22
+
23
+ def start
24
+ @server = WEBrick::HTTPServer.new(
25
+ BindAddress: @bind,
26
+ Port: @port,
27
+ Logger: WEBrick::Log.new(STDERR, WEBrick::Log::FATAL),
28
+ AccessLog: [],
29
+ )
30
+ @server.mount(@metrics_path, MonitorServlet, self)
31
+ @thread = Thread.new { @server.start }
32
+ end
33
+
34
+ def shutdown
35
+ if @server
36
+ @server.shutdown
37
+ @server = nil
38
+ end
39
+ if @thread
40
+ @thread.join
41
+ @thread = nil
42
+ end
43
+ end
44
+
45
+ class MonitorServlet < WEBrick::HTTPServlet::AbstractServlet
46
+ def initialize(server, prometheus)
47
+ @prometheus = prometheus
48
+ end
49
+
50
+ def do_GET(req, res)
51
+ res.status = 200
52
+ res['Content-Type'] = ::Prometheus::Client::Formats::Text::CONTENT_TYPE
53
+ res.body = ::Prometheus::Client::Formats::Text.marshal(@prometheus.registry)
54
+ rescue
55
+ res.status = 500
56
+ res['Content-Type'] = 'text/plain'
57
+ res.body = $!.to_s
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,98 @@
1
+ require 'fluent/plugin/prometheus'
2
+ require 'webrick'
3
+
4
+ module Fluent
5
+ class PrometheusMonitorInput < Input
6
+ Plugin.register_input('prometheus_monitor', self)
7
+
8
+ config_param :interval, :time, :default => 5
9
+ attr_reader :registry
10
+
11
+ def initialize
12
+ super
13
+ @registry = ::Prometheus::Client.registry
14
+ end
15
+
16
+ def configure(conf)
17
+ super
18
+ hostname = Socket.gethostname
19
+ expander = Fluent::Prometheus.placeholder_expnader(log)
20
+ expander.prepare_placeholders(Time.now.to_i, {}, {'hostname' => hostname})
21
+ @base_labels = Fluent::Prometheus.parse_labels_elements(conf)
22
+ @base_labels.each do |key, value|
23
+ @base_labels[key] = expander.expand(value)
24
+ end
25
+
26
+ @monitor_agent = MonitorAgentInput.new
27
+
28
+ buffer_queue_length = @registry.gauge(
29
+ :fluentd_status_buffer_queue_length,
30
+ 'Current buffer queue length.')
31
+ buffer_total_queued_size = @registry.gauge(
32
+ :fluentd_status_buffer_total_bytes,
33
+ 'Current total size of ququed buffers.')
34
+ retry_counts = @registry.gauge(
35
+ :fluentd_status_retry_count,
36
+ 'Current retry counts.')
37
+
38
+ @monitor_info = {
39
+ 'buffer_queue_length' => buffer_queue_length,
40
+ 'buffer_total_queued_size' => buffer_total_queued_size,
41
+ 'retry_count' => retry_counts,
42
+ }
43
+ end
44
+
45
+ class TimerWatcher < Coolio::TimerWatcher
46
+ def initialize(interval, repeat, log, &callback)
47
+ @callback = callback
48
+ @log = log
49
+ super(interval, repeat)
50
+ end
51
+
52
+ def on_timer
53
+ @callback.call
54
+ rescue
55
+ @log.error $!.to_s
56
+ @log.error_backtrace
57
+ end
58
+ end
59
+
60
+ def start
61
+ @loop = Coolio::Loop.new
62
+ @timer = TimerWatcher.new(@interval, true, log, &method(:update_monitor_info))
63
+ @loop.attach(@timer)
64
+ @thread = Thread.new(&method(:run))
65
+ end
66
+
67
+ def shutdown
68
+ @loop.watchers.each {|w| w.detach }
69
+ @loop.stop
70
+ @thread.join
71
+ end
72
+
73
+ def run
74
+ @loop.run
75
+ rescue
76
+ log.error "unexpected error", :error=>$!.to_s
77
+ log.error_backtrace
78
+ end
79
+
80
+ def update_monitor_info
81
+ @monitor_agent.plugins_info_all.each do |info|
82
+ @monitor_info.each do |name, metric|
83
+ if info[name]
84
+ metric.set(labels(info), info[name])
85
+ end
86
+ end
87
+ end
88
+ end
89
+
90
+ def labels(plugin_info)
91
+ @base_labels.merge(
92
+ plugin_id: plugin_info["plugin_id"],
93
+ plugin_category: plugin_info["plugin_category"],
94
+ type: plugin_info["type"],
95
+ )
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,24 @@
1
+ require 'fluent/plugin/prometheus'
2
+
3
+ module Fluent
4
+ class PrometheusOutput < Output
5
+ Plugin.register_output('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 emit(tag, es, chain)
20
+ instrument(tag, es, @metrics)
21
+ chain.next
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,195 @@
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_expnader(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)
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)
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_expnader(log)
66
+ @hostname = Socket.gethostname
67
+ end
68
+
69
+ def instrument(tag, es, metrics)
70
+ placeholder_options = {
71
+ 'tag' => tag,
72
+ 'hostname' => @hostname,
73
+ }
74
+
75
+ es.each do |time, record|
76
+ @placeholder_expander.prepare_placeholders(time, record, placeholder_options)
77
+ metrics.each do |metric|
78
+ begin
79
+ metric.instrument(record, @placeholder_expander)
80
+ rescue => e
81
+ log.warn "prometheus: failed to instrument a metric.", error_class: e.class, error: e, tag: tag, name: metric.name
82
+ router.emit_error_event(tag, time, record, e)
83
+ end
84
+ end
85
+ end
86
+ end
87
+
88
+ class Metric
89
+ attr_reader :type
90
+ attr_reader :name
91
+ attr_reader :key
92
+ attr_reader :desc
93
+
94
+ def initialize(element, registry, labels)
95
+ ['name', 'desc'].each do |key|
96
+ if element[key].nil?
97
+ raise ConfigError, "metric requires '#{key}' option"
98
+ end
99
+ end
100
+ @type = element['type']
101
+ @name = element['name']
102
+ @key = element['key']
103
+ @desc = element['desc']
104
+
105
+ @base_labels = Fluent::Prometheus.parse_labels_elements(element)
106
+ @base_labels = labels.merge(@base_labels)
107
+ end
108
+
109
+ def labels(record, expander)
110
+ label = {}
111
+ @base_labels.each do |k, v|
112
+ label[k] = expander.expand(v)
113
+ end
114
+ label
115
+ end
116
+
117
+ def self.get(registry, name, type, docstring)
118
+ metric = registry.get(name)
119
+
120
+ # should have same type, docstring
121
+ if metric.type != type
122
+ raise AlreadyRegisteredError, "#{name} has already been registered as #{type} type"
123
+ end
124
+ if metric.docstring != docstring
125
+ raise AlreadyRegisteredError, "#{name} has already been registered with different docstring"
126
+ end
127
+
128
+ metric
129
+ end
130
+ end
131
+
132
+ class Gauge < Metric
133
+ def initialize(element, registry, labels)
134
+ super
135
+ if @key.nil?
136
+ raise ConfigError, "gauge metric requires 'key' option"
137
+ end
138
+
139
+ begin
140
+ @gauge = registry.gauge(element['name'].to_sym, element['desc'])
141
+ rescue ::Prometheus::Client::Registry::AlreadyRegisteredError
142
+ @gauge = Fluent::Prometheus::Metric.get(registry, element['name'].to_sym, :gauge, element['desc'])
143
+ end
144
+ end
145
+
146
+ def instrument(record, expander)
147
+ if record[@key]
148
+ @gauge.set(labels(record, expander), record[@key])
149
+ end
150
+ end
151
+ end
152
+
153
+ class Counter < Metric
154
+ def initialize(element, registry, labels)
155
+ super
156
+ begin
157
+ @counter = registry.counter(element['name'].to_sym, element['desc'])
158
+ rescue ::Prometheus::Client::Registry::AlreadyRegisteredError
159
+ @counter = Fluent::Prometheus::Metric.get(registry, element['name'].to_sym, :counter, element['desc'])
160
+ end
161
+ end
162
+
163
+ def instrument(record, expander)
164
+ # use record value of the key if key is specified, otherwise just increment
165
+ value = @key ? record[@key] : 1
166
+
167
+ # ignore if record value is nil
168
+ return if value.nil?
169
+
170
+ @counter.increment(labels(record, expander), value)
171
+ end
172
+ end
173
+
174
+ class Summary < Metric
175
+ def initialize(element, registry, labels)
176
+ super
177
+ if @key.nil?
178
+ raise ConfigError, "summary metric requires 'key' option"
179
+ end
180
+
181
+ begin
182
+ @summary = registry.summary(element['name'].to_sym, element['desc'])
183
+ rescue ::Prometheus::Client::Registry::AlreadyRegisteredError
184
+ @summary = Fluent::Prometheus::Metric.get(registry, element['name'].to_sym, :summary, element['desc'])
185
+ end
186
+ end
187
+
188
+ def instrument(record, expander)
189
+ if record[@key]
190
+ @summary.add(labels(record, expander), record[@key])
191
+ end
192
+ end
193
+ end
194
+ end
195
+ 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>