fluent-plugin-librato-metrics 0.2.2

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.
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