fluent-plugin-librato-metrics 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/ChangeLog ADDED
@@ -0,0 +1,5 @@
1
+
2
+ Release 0.2.2 - 2012/03/29
3
+
4
+ * First release
5
+
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in fluent-plugin-librato-metrics.gemspec
4
+ gemspec
data/README.rdoc ADDED
@@ -0,0 +1,51 @@
1
+ = Librato Metrics output plugin for Fluent event collector
2
+
3
+ == Overview
4
+
5
+ *librato_metrics* output plugin buffers aggregate logs and upload them to Librato Metrics periodically.
6
+
7
+ == Installation
8
+
9
+ Simply use RubyGems:
10
+
11
+ gem install fluent-plugin-librato-metrics
12
+
13
+ == Configuration
14
+
15
+ <match pattern>
16
+ type librato_metrics
17
+
18
+ librato_user YOUR_LIBRATO_USER_NAME
19
+ librato_token YOUR_LIBRATO_TOKEN
20
+
21
+ # simple key-value metrics
22
+ <metrics metrics.status.**>
23
+ name status
24
+ each_key key
25
+ value_key value
26
+ </metrics>
27
+
28
+ # single-stream event count
29
+ <metrics metrics.event.mytask>
30
+ name mytask.count
31
+ </metrics>
32
+
33
+ # single-stream event sum
34
+ <metrics metrics.event.mytask>
35
+ name mytask.size
36
+ value_key size
37
+ </metrics>
38
+
39
+ # single-stream event sum; value type is float
40
+ <metrics metrics.event.mytask>
41
+ name mytask.elapsed
42
+ value_key elapsed
43
+ type float
44
+ </metrics>
45
+
46
+ </match>
47
+
48
+ [librato_user (required)] User name of librato metrics
49
+
50
+ [librato_token (required)] Token of librato metrics
51
+
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ require 'rake/testtask'
4
+
5
+ Rake::TestTask.new(:test) do |test|
6
+ test.libs << 'lib' << 'test'
7
+ test.test_files = FileList['test/*.rb']
8
+ test.verbose = true
9
+ end
10
+
11
+ task :default => [:build]
12
+
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.2.2
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = "fluent-plugin-librato-metrics"
6
+ s.version = File.read("VERSION").strip
7
+ s.authors = ["Sadayuki Furuhashi"]
8
+ s.email = ["frsyuki@gmail.com"]
9
+ #s.homepage = "https://github.com/fluent/fluent-plugin-librato-metrics" # TODO
10
+ s.summary = %q{Librato metrics output plugin for Fluent event collector}
11
+ s.description = %q{Librato metrics output plugin for Fluent event collector}
12
+
13
+ s.files = `git ls-files`.split("\n")
14
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
15
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
16
+ s.require_paths = ["lib"]
17
+
18
+ # specify any dependencies here; for example:
19
+ # s.add_development_dependency "rspec"
20
+ # s.add_runtime_dependency "rest-client"
21
+ s.add_dependency "fluentd", "~> 0.10.0"
22
+ s.add_development_dependency "rake", ">= 0.9.2"
23
+ end
@@ -0,0 +1,148 @@
1
+ #
2
+ # fluent-plugin-librato-metrics
3
+ #
4
+ # Copyright (C) 2011-2012 Sadayuki Furuhashi
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+ module Fluent
19
+
20
+
21
+ class LibratoMetricsOutput < Fluent::BufferedOutput
22
+ Fluent::Plugin.register_output('librato_metrics', self)
23
+
24
+ require 'net/http'
25
+ require "#{File.dirname(__FILE__)}/out_librato_metrics_mixin"
26
+ include MetricsMixin
27
+
28
+ config_param :librato_user, :string
29
+ config_param :librato_token, :string
30
+ config_param :source, :string, :default => nil
31
+
32
+ def initialize
33
+ super
34
+ end
35
+
36
+ def configure(conf)
37
+ super
38
+ end
39
+
40
+ def format(tag, time, record)
41
+ out = ''.force_encoding('ASCII-8BIT')
42
+ each_metrics(tag, time, record) {|d|
43
+ name = d.name
44
+ if d.each_keys.empty?
45
+ key = name
46
+ else
47
+ key = "#{name}.#{d.keys.join('.')}"
48
+ end
49
+ partitioned_time = time / 60 * 60
50
+ [key, partitioned_time, d.value*d.count].to_msgpack(out)
51
+ }
52
+ out
53
+ end
54
+
55
+ def write(chunk)
56
+ counters = {} #=> {partitioned_time => {name => CounterAggregator}}
57
+ gauges = {} #=> {partitioned_time => {name => GaugeAggregator}}
58
+
59
+ chunk.msgpack_each {|key,partitioned_time,value|
60
+ if m = /^counter\.(.*)$/.match(key)
61
+ name = m[1]
62
+ ((counters[partitioned_time] ||= {})[name] ||= CounterAggregator.new).add(value)
63
+ elsif m = /^gauge\.(.*)$/.match(key)
64
+ name = m[1]
65
+ ((gauges[partitioned_time] ||= {})[name] ||= GaugeAggregator.new).add(value)
66
+ else
67
+ name = key
68
+ ((gauges[partitioned_time] ||= {})[name] ||= GaugeAggregator.new).add(value)
69
+ end
70
+ }
71
+
72
+ #http = Net::HTTP.new('metrics-api.librato.com', 80)
73
+ http = Net::HTTP.new('metrics-api.librato.com', 443)
74
+ http.use_ssl = true
75
+ #http.verify_mode = OpenSSL::SSL::VERIFY_PEER
76
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE # TODO verify
77
+ http.cert_store = OpenSSL::X509::Store.new
78
+ header = {}
79
+
80
+ begin
81
+ {'counters'=>counters, 'gauges'=>gauges}.each_pair {|type,partitions|
82
+ partitions.each_pair {|partitioned_time,name_aggrs|
83
+ req = Net::HTTP::Post.new('/v1/metrics', header)
84
+ req.basic_auth @librato_user, @librato_token
85
+
86
+ params = {
87
+ 'measure_time' => partitioned_time.to_s,
88
+ }
89
+ params['source'] = @source if @source
90
+
91
+ name_aggrs.each_with_index {|(name,aggr),i|
92
+ params["#{type}[#{i}][name]"] = name
93
+ params["#{type}[#{i}][value]"] = aggr.get.to_s
94
+ }
95
+
96
+ $log.trace { "librato metrics: #{params.inspect}" }
97
+ req.set_form_data(params)
98
+ res = http.request(req)
99
+
100
+ # TODO error handling
101
+ if res.code != "200"
102
+ $log.warn "librato_metrics: #{res.code}: #{res.body}"
103
+ end
104
+ }
105
+ }
106
+ ensure
107
+ http.finish if http.started?
108
+ end
109
+ end
110
+
111
+
112
+ # max(value) aggregator
113
+ class CounterAggregator
114
+ def initialize
115
+ @value = 0
116
+ end
117
+
118
+ def add(value)
119
+ @value = value if @value < value
120
+ end
121
+
122
+ def get
123
+ # librato metrics's counter only supports integer:
124
+ # 'invalid value for Integer(): "0.06268015"'
125
+ @value.to_i
126
+ end
127
+ end
128
+
129
+ # avg(value) aggregator
130
+ class GaugeAggregator
131
+ def initialize
132
+ @value = 0
133
+ @num = 0
134
+ end
135
+
136
+ def add(value)
137
+ @value += value
138
+ @num += 1
139
+ end
140
+
141
+ def get
142
+ @value / @num
143
+ end
144
+ end
145
+ end
146
+
147
+
148
+ end
@@ -0,0 +1,317 @@
1
+ #
2
+ # fluent-plugin-librato-metrics
3
+ #
4
+ # Copyright (C) 2011 FURUHASHI Sadayuki
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+ module Fluent
19
+
20
+
21
+ module LibratoMetricsOutput::MetricsMixin
22
+ include Configurable
23
+
24
+ def initialize
25
+ super
26
+ @metrics = []
27
+ end
28
+
29
+ attr_reader :metrics
30
+
31
+ config_param :default_value_key, :string, :default => nil
32
+ config_param :default_count_key, :string, :default => nil
33
+ config_param :default_type, :string, :default => nil
34
+ config_param :match_key, :string, :default => nil
35
+
36
+ def configure(conf)
37
+ super
38
+
39
+ defaults = {
40
+ 'value_key' => @default_value_key,
41
+ 'count_key' => @default_count_key,
42
+ 'type' => @default_type,
43
+ }
44
+
45
+ conf.elements.select {|e|
46
+ e.name == 'metrics'
47
+ }.each {|e|
48
+ defaults.each_pair {|k,v| e[k] = v if v != nil }
49
+ add_metrics(e, e.arg)
50
+ }
51
+
52
+ if mk = @match_key
53
+ @match_key_proc = Proc.new {|tag,record| record[mk] }
54
+ else
55
+ @match_key_proc = Proc.new {|tag,record| tag }
56
+ end
57
+ end
58
+
59
+ def add_metrics(conf, pattern)
60
+ m = Metrics.new(pattern)
61
+ m.configure(conf)
62
+ @metrics << m
63
+ end
64
+
65
+ class Data
66
+ def initialize(metrics)
67
+ @metrics = metrics
68
+ @name = metrics.name
69
+ @each_keys = metrics.each_keys
70
+ @value_key = metrics.value_key
71
+ @count_key = metrics.count_key
72
+ end
73
+
74
+ attr_reader :metrics
75
+ attr_reader :name, :each_keys, :value_key, :count_key
76
+ attr_accessor :keys, :value, :count
77
+ end
78
+
79
+ class Metrics
80
+ include Configurable
81
+
82
+ def initialize(pattern)
83
+ super()
84
+ if pattern && !pattern.empty?
85
+ @pattern = MatchPattern.create(pattern)
86
+ else
87
+ @pattern = nil
88
+ end
89
+ end
90
+
91
+ attr_reader :pattern
92
+
93
+ config_param :name, :string
94
+
95
+ config_param :each_key, :string, :default => nil
96
+ attr_reader :each_keys # parsed 'each_key'
97
+
98
+ config_param :value_key, :string, :default => nil
99
+ config_param :default_value, :string, :default => nil
100
+
101
+ config_param :count_key, :string, :default => nil
102
+
103
+ config_param :type, :default => :int do |val|
104
+ case val
105
+ when 'int'
106
+ :int
107
+ when 'float'
108
+ :float
109
+ else
110
+ raise ConfigError, "unexpected 'type' parameter on metrics: expected int or float"
111
+ end
112
+ end
113
+
114
+ config_param :match_key, :string, :default => nil
115
+
116
+ attr_accessor :key_proc
117
+ attr_accessor :value_proc
118
+ attr_accessor :count_proc
119
+ attr_accessor :match_proc
120
+
121
+ def configure(conf)
122
+ super
123
+
124
+ if @each_key
125
+ @each_keys = @each_key.split(',').map {|e| e.strip }
126
+ @key_proc = create_combined_key_proc(@each_keys)
127
+ else
128
+ @each_keys = []
129
+ use_keys = [@name]
130
+ @key_proc = Proc.new {|record| use_keys }
131
+ end
132
+
133
+ case @type
134
+ when :int
135
+ if vk = @value_key
136
+ dv = @default_value ? @default_value.to_i : nil
137
+ @value_proc = Proc.new {|record| val = record[vk]; val ? val.to_i : dv }
138
+ else
139
+ dv = @default_value ? @default_value.to_i : 1
140
+ @value_proc = Proc.new {|record| dv }
141
+ end
142
+ when :float
143
+ if vk = @value_key
144
+ dv = @default_value ? @default_value.to_f : nil
145
+ @value_proc = Proc.new {|record| val = record[vk]; val ? val.to_f : dv }
146
+ else
147
+ dv = @default_value ? @default_value.to_f : 1.0
148
+ @value_proc = Proc.new {|record| dv }
149
+ end
150
+ end
151
+
152
+ if ck = @count_key
153
+ @count_proc = Proc.new {|record| val = record[ck].to_i; val == 0 ? 1 : val }
154
+ else
155
+ @count_proc = Proc.new {|record| 1 }
156
+ end
157
+
158
+ if pt = @pattern
159
+ @match_proc = Proc.new {|tag| pt.match(tag) }
160
+ else
161
+ @match_proc = Proc.new {|tag| true }
162
+ end
163
+
164
+ @data = Data.new(self)
165
+ end
166
+
167
+ def create_combined_key_proc(keys)
168
+ if keys.length == 1
169
+ key = keys[0]
170
+ Proc.new {|record|
171
+ val = record[key]
172
+ val ? [val] : nil
173
+ }
174
+ else
175
+ Proc.new {|record|
176
+ keys.map {|key|
177
+ val = record[key]
178
+ break nil unless val
179
+ val
180
+ }
181
+ }
182
+ end
183
+ end
184
+
185
+ def evaluate(tag, time, record)
186
+ return nil unless @match_proc.call(tag)
187
+
188
+ data = @data
189
+
190
+ keys = data.keys = @key_proc.call(record)
191
+ return nil unless keys
192
+
193
+ value = data.value = @value_proc.call(record)
194
+ return nil unless value
195
+
196
+ data.count = @count_proc.call(record)
197
+
198
+ return data
199
+ end
200
+ end
201
+
202
+ def each_metrics(tag, time, record, &block)
203
+ mk = @match_key_proc.call(tag, record)
204
+ return [] unless mk
205
+ @metrics.map {|m|
206
+ data = m.evaluate(mk, time, record)
207
+ next unless data
208
+ block.call(data)
209
+ }
210
+ end
211
+ end
212
+
213
+
214
+ ## Example
215
+ #
216
+ #class MetricsOutput < Output
217
+ # Plugin.register_output('metrics', self)
218
+ #
219
+ # include MetricsMixin
220
+ #
221
+ # def initialize
222
+ # super
223
+ # end
224
+ #
225
+ # config_param :tag, :string, :default => nil
226
+ # config_param :tag_prefix, :string, :default => nil
227
+ #
228
+ # def configure(conf)
229
+ # super
230
+ #
231
+ # if constant_tag = @tag
232
+ # @tag_proc = Proc.new {|name,tag| constant_tag }
233
+ # elsif tag_prefix = @tag_prefix
234
+ # @tag_proc = Proc.new {|name,tag| "#{tag_prefix}.#{name}.#{tag}" }
235
+ # else
236
+ # @tag_proc = Proc.new {|name,tag| "#{name}.#{tag}" }
237
+ # end
238
+ # end
239
+ #
240
+ # def emit(tag, es, chain)
241
+ # es.each {|time,record|
242
+ # each_metrics(tag, time, record) {|m|
243
+ # otag = @tag_proc.call(m.name, tag)
244
+ # orecord = {
245
+ # 'name' => m.name,
246
+ # 'key' => m.keys.join(','),
247
+ # 'keys' => m.keys,
248
+ # 'key_hash' => Hash[ m.each_keys.zip(m.keys) ],
249
+ # 'value' => m.value,
250
+ # 'value_hash' => {m.value_key => m.value},
251
+ # 'count' => m.count,
252
+ # }
253
+ # Engine.emit(otag, time, orecord)
254
+ # }
255
+ # }
256
+ # chain.next
257
+ # end
258
+ #end
259
+
260
+
261
+ ## Example
262
+ #
263
+ #require 'metrics'
264
+ #
265
+ #class ExampleAggregationOutput < BufferedOutput
266
+ # Plugin.register_output('aggregation_example', self)
267
+ #
268
+ # include MetricsMixin
269
+ #
270
+ # def initialize
271
+ # super
272
+ # end
273
+ #
274
+ # def configure(conf)
275
+ # super
276
+ #
277
+ # # do something ...
278
+ # end
279
+ #
280
+ # def format(tag, time, record)
281
+ # # call each_metrics(tag, time,record):
282
+ # each_metrics(tag, time, record) {|m|
283
+ # # 'm.name' is always avaiable
284
+ # name = m.name
285
+ #
286
+ # # 'keys' is an array of record value if each_key option is specified.
287
+ # # otherwise, 'keys' is same as [m.name]
288
+ # key = m.keys.join(',')
289
+ #
290
+ # if !m.each_keys.empty?
291
+ # # you can create Hash of the record using 'm.each_keys'
292
+ # key_hash = Hash[ m.each_keys.zip(m.keys) ]
293
+ # else
294
+ # # m.keys is [name]
295
+ # end
296
+ #
297
+ # if m.value_key
298
+ # # value is parsed record value
299
+ # value_hash = {m.value_key => m.value}
300
+ # else
301
+ # # m.value is 1 or 1.0
302
+ # end
303
+ #
304
+ # if m.count_key
305
+ # # value is parsed record value
306
+ # count_hash = {m.count_key => m.count}
307
+ # else
308
+ # # count is 1
309
+ # end
310
+ #
311
+ # # ...
312
+ # }
313
+ # end
314
+ #end
315
+
316
+
317
+ end
@@ -0,0 +1,81 @@
1
+ $:.push File.expand_path("../lib", __FILE__)
2
+ require 'fluent/test'
3
+ require 'fluent/plugin/out_librato_metrics'
4
+
5
+ class LibratoMetricsOutputTest < Test::Unit::TestCase
6
+ def setup
7
+ Fluent::Test.setup
8
+ end
9
+
10
+ CONFIG = %[
11
+ librato_user test
12
+ librato_token TEST
13
+
14
+ # simple key-value metrics
15
+ <metrics test1.**>
16
+ name test1
17
+ each_key key
18
+ value_key value
19
+ count_key count
20
+ </metrics>
21
+
22
+ # single-stream event count
23
+ <metrics test2.**>
24
+ name test2.count
25
+ </metrics>
26
+
27
+ # single-stream event sum
28
+ <metrics test3.**>
29
+ name test3.size
30
+ value_key size
31
+ </metrics>
32
+
33
+ # single-stream event sum; value type is float
34
+ <metrics test3.test4>
35
+ name test4.elapsed
36
+ value_key elapsed
37
+ type float
38
+ </metrics>
39
+ ]
40
+
41
+ TIME = Time.parse("2011-01-02 13:14:15 UTC").to_i
42
+
43
+ def create_driver(tag="test", conf=CONFIG)
44
+ Fluent::Test::BufferedOutputTestDriver.new(Fluent::LibratoMetricsOutput, tag) do
45
+ def write(chunk)
46
+ chunk.read
47
+ end
48
+ end.configure(conf)
49
+ end
50
+
51
+ def test_configure
52
+ d = create_driver
53
+ assert_equal 'test', d.instance.librato_user
54
+ assert_equal 'TEST', d.instance.librato_token
55
+ end
56
+
57
+ def test_format
58
+ d = create_driver("test1.1")
59
+ d.emit({"key"=>"a", "value"=>10, "count"=>2}, TIME)
60
+ d.expect_format ["test1.a", TIME/60*60, 20].to_msgpack
61
+ d.run
62
+
63
+ d = create_driver("test2.2")
64
+ d.emit({}, TIME)
65
+ d.expect_format ["test2.count", TIME/60*60, 1].to_msgpack
66
+ d.run
67
+
68
+ d = create_driver("test3.3")
69
+ d.emit({"size"=>2100}, TIME)
70
+ d.expect_format ["test3.size", TIME/60*60, 2100].to_msgpack
71
+ d.run
72
+
73
+ d = create_driver("test3.test4")
74
+ d.emit({"size"=>2400, "elapsed"=>10.0}, TIME)
75
+ d.expect_format ["test3.size", TIME/60*60, 2400].to_msgpack
76
+ d.expect_format ["test4.elapsed", TIME/60*60, 10.0].to_msgpack
77
+ d.run
78
+ end
79
+
80
+ end
81
+
metadata ADDED
@@ -0,0 +1,84 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fluent-plugin-librato-metrics
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.2
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Sadayuki Furuhashi
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-03-30 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: fluentd
16
+ requirement: &70131020756540 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 0.10.0
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *70131020756540
25
+ - !ruby/object:Gem::Dependency
26
+ name: rake
27
+ requirement: &70131020756060 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: 0.9.2
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: *70131020756060
36
+ description: Librato metrics output plugin for Fluent event collector
37
+ email:
38
+ - frsyuki@gmail.com
39
+ executables: []
40
+ extensions: []
41
+ extra_rdoc_files: []
42
+ files:
43
+ - .gitignore
44
+ - ChangeLog
45
+ - Gemfile
46
+ - README.rdoc
47
+ - Rakefile
48
+ - VERSION
49
+ - fluent-plugin-librato-metrics.gemspec
50
+ - lib/fluent/plugin/out_librato_metrics.rb
51
+ - lib/fluent/plugin/out_librato_metrics_mixin.rb
52
+ - test/out_librato_metrics.rb
53
+ homepage:
54
+ licenses: []
55
+ post_install_message:
56
+ rdoc_options: []
57
+ require_paths:
58
+ - lib
59
+ required_ruby_version: !ruby/object:Gem::Requirement
60
+ none: false
61
+ requirements:
62
+ - - ! '>='
63
+ - !ruby/object:Gem::Version
64
+ version: '0'
65
+ segments:
66
+ - 0
67
+ hash: -197996435032986197
68
+ required_rubygems_version: !ruby/object:Gem::Requirement
69
+ none: false
70
+ requirements:
71
+ - - ! '>='
72
+ - !ruby/object:Gem::Version
73
+ version: '0'
74
+ segments:
75
+ - 0
76
+ hash: -197996435032986197
77
+ requirements: []
78
+ rubyforge_project:
79
+ rubygems_version: 1.8.12
80
+ signing_key:
81
+ specification_version: 3
82
+ summary: Librato metrics output plugin for Fluent event collector
83
+ test_files:
84
+ - test/out_librato_metrics.rb