fluent-plugin-numeric-monitor 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in fluent-plugin-numeric-aggregator.gemspec
4
+ gemspec
@@ -0,0 +1,13 @@
1
+ Copyright (c) 2012- TAGOMORI Satoshi
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ Unless required by applicable law or agreed to in writing, software
10
+ distributed under the License is distributed on an "AS IS" BASIS,
11
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ See the License for the specific language governing permissions and
13
+ limitations under the License.
@@ -0,0 +1,39 @@
1
+ # fluent-plugin-numeric-monitor
2
+
3
+ ## Component
4
+
5
+ ### NumericMonitorOutput
6
+
7
+ Plugin to calculate min/max/avg and specified percentile values, which used in notifications (such as fluent-plugin-notifier)
8
+
9
+ ## Configuration
10
+
11
+ ### NumericMonitorOutput
12
+
13
+ To calculate about HTTP requests duration (microseconds) in 'duraion', with 90 and 95 percentile values:
14
+
15
+ <match apache.log.**>
16
+ type numeric_monitor
17
+ unit minute
18
+ tag monitor.duration
19
+ aggregate all
20
+ input_tag_remove_prefix apache.log
21
+ monitor_key duration
22
+ percentiles 90,95
23
+ </match>
24
+
25
+ Output messages like:
26
+
27
+ {"min":3012,"max":913243,"avg":100123.51,"percentile_90":154390,"percentile_95":223110}
28
+
29
+ ## TODO
30
+
31
+ * more tests
32
+ * more documents
33
+
34
+ ## Copyright
35
+
36
+ * Copyright
37
+ * Copyright (c) 2012- TAGOMORI Satoshi (tagomoris)
38
+ * License
39
+ * Apache License, Version 2.0
@@ -0,0 +1,11 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ require 'rake/testtask'
4
+ Rake::TestTask.new(:test) do |test|
5
+ test.libs << 'lib' << 'test'
6
+ test.pattern = 'test/**/test_*.rb'
7
+ test.verbose = true
8
+ end
9
+
10
+ task :default => :test
11
+
@@ -0,0 +1,18 @@
1
+ # -*- encoding: utf-8 -*-
2
+ Gem::Specification.new do |gem|
3
+ gem.name = "fluent-plugin-numeric-monitor"
4
+ gem.version = "0.0.1"
5
+ gem.authors = ["TAGOMORI Satoshi"]
6
+ gem.email = ["tagomoris@gmail.com"]
7
+ gem.description = %q{Fluentd plugin to calculate min/max/avg/Xpercentile values, and emit these data as message}
8
+ gem.summary = %q{Fluentd plugin to calculate min/max/avg/Xpercentile values}
9
+ gem.homepage = "https://github.com/tagomoris/fluent-plugin-numeric-monitor"
10
+
11
+ gem.files = `git ls-files`.split($\)
12
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
13
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
14
+ gem.require_paths = ["lib"]
15
+
16
+ gem.add_development_dependency "fluentd"
17
+ gem.add_runtime_dependency "fluentd"
18
+ end
@@ -0,0 +1,218 @@
1
+ class Fluent::NumericMonitorOutput < Fluent::Output
2
+ Fluent::Plugin.register_output('numeric_monitor', self)
3
+
4
+ EMIT_STREAM_RECORDS = 100
5
+
6
+ config_param :count_interval, :time, :default => 60
7
+ config_param :unit, :string, :default => nil
8
+ config_param :tag, :string, :default => 'monitor'
9
+
10
+ config_param :aggregate, :default => 'tag' do |val|
11
+ case val
12
+ when 'tag' then :tag
13
+ when 'all' then :all
14
+ else
15
+ raise Fluent::ConfigError, "aggregate MUST be one of 'tag' or 'all'"
16
+ end
17
+ end
18
+ config_param :input_tag_remove_prefix, :string, :default => nil
19
+ config_param :monitor_key, :string
20
+ config_param :percentiles, :default => nil do |val|
21
+ values = val.split(",").map(&:to_i)
22
+ if values.select{|i| i < 1 or i > 99 }.size > 0
23
+ raise Fluent::ConfigError, "percentiles MUST be specified between 1 and 99 by integer"
24
+ end
25
+ values
26
+ end
27
+
28
+ config_param :samples_limit, :integer, :default => 1000000
29
+
30
+ attr_accessor :count, :last_checked
31
+
32
+ def configure(conf)
33
+ super
34
+
35
+ if @unit
36
+ @count_interval = case @unit
37
+ when 'minute' then 60
38
+ when 'hour' then 3600
39
+ when 'day' then 86400
40
+ else
41
+ raise Fluent::ConfigError, "unit must be one of minute/hour/day"
42
+ end
43
+ end
44
+
45
+ if @input_tag_remove_prefix
46
+ @removed_prefix_string = @input_tag_remove_prefix + '.'
47
+ @removed_length = @removed_prefix_string.length
48
+ end
49
+
50
+ @count = count_initialized
51
+ @mutex = Mutex.new
52
+ end
53
+
54
+ def start
55
+ super
56
+ start_watch
57
+ end
58
+
59
+ def shutdown
60
+ super
61
+ @watcher.terminate
62
+ @watcher.join
63
+ end
64
+
65
+ def start_watch
66
+ # for internal, or tests
67
+ @watcher = Thread.new(&method(:watch))
68
+ end
69
+
70
+ def watch
71
+ @last_checked = Fluent::Engine.now
72
+ while true
73
+ sleep 0.5
74
+ if Fluent::Engine.now - @last_checked
75
+ now = Fluent::Engine.now
76
+ flush_emit
77
+ @last_checked = now
78
+ end
79
+ end
80
+ end
81
+
82
+ def count_initialized(keys=nil)
83
+ # counts['tag'] = {:min => num, :max => num, :sum => num, :num => num [, :sample => [....]]}
84
+ if @aggregate == :all
85
+ if @percentiles
86
+ {'all' => {:min => nil, :max => nil, :sum => nil, :num => 0, :sample => []}}
87
+ else
88
+ {'all' => {:min => nil, :max => nil, :sum => nil, :num => 0}}
89
+ end
90
+ elsif keys
91
+ values = if @percentiles
92
+ Array.new(keys.length) {|i| {:min => nil, :max => nil, :sum => nil, :num => 0, :sample => []}}
93
+ else
94
+ Array.new(keys.length) {|i| {:min => nil, :max => nil, :sum => nil, :num => 0}}
95
+ end
96
+ Hash[[keys, values].transpose]
97
+ else
98
+ {}
99
+ end
100
+ end
101
+
102
+ def stripped_tag(tag)
103
+ return tag unless @input_tag_remove_prefix
104
+ return tag[@removed_length..-1] if tag.start_with?(@removed_prefix_string) and tag.length > @removed_length
105
+ return tag[@removed_length..-1] if tag == @input_tag_remove_prefix
106
+ tag
107
+ end
108
+
109
+ def generate_output(count)
110
+ output = {}
111
+ if @aggregate == :all
112
+ c = count['all']
113
+ if c[:min] then output['min'] = c[:min] end
114
+ if c[:max] then output['max'] = c[:max] end
115
+ if c[:num] > 0 then output['avg'] = (c[:sum] * 100.0 / (c[:num] * 1.0)).round / 100 end
116
+ if @percentiles
117
+ sorted = c[:sample].sort
118
+ @percentiles.each do |p|
119
+ i = (c[:num] * p / 100).floor
120
+ output["percentile_#{p}"] = c[:sample][i]
121
+ end
122
+ end
123
+ return output
124
+ end
125
+
126
+ count.keys.each do |tag|
127
+ t = stripped_tag(tag)
128
+ c = count[tag]
129
+ if c[:min] then output[t + '_min'] = c[:min] end
130
+ if c[:max] then output[t + '_max'] = c[:max] end
131
+ if c[:num] > 0 then output[t + '_avg'] = (c[:sum] * 100.0 / (c[:num] * 1.0)).round / 100.0 end
132
+ if @percentiles
133
+ sorted = c[:sample].sort
134
+ @percentiles.each do |p|
135
+ i = (c[:num] * p / 100).floor
136
+ if i > 0
137
+ i -= 1
138
+ end
139
+ output[t + "_percentile_#{p}"] = sorted[i]
140
+ end
141
+ end
142
+ end
143
+ output
144
+ end
145
+
146
+ def flush
147
+ flushed,@count = @count,count_initialized(@count.keys.dup)
148
+ generate_output(flushed)
149
+ end
150
+
151
+ def flush_emit
152
+ Fluent::Engine.emit(@tag, Fluent::Engine.now, flush)
153
+ end
154
+
155
+ def countups(tag, min, max, sum, num, sample)
156
+ if @aggregate == :all
157
+ tag = 'all'
158
+ end
159
+
160
+ @mutex.synchronize do
161
+ c = (@count[tag] ||= {:min => nil, :max => nil, :sum => nil, :num => 0})
162
+
163
+ if c[:min].nil? or c[:min] > min
164
+ c[:min] = min
165
+ end
166
+ if c[:max].nil? or c[:max] < max
167
+ c[:max] = max
168
+ end
169
+ c[:sum] = (c[:sum] || 0) + sum
170
+ c[:num] += num
171
+
172
+ if @percentiles
173
+ c[:sample] ||= []
174
+ if c[:sample].size + sample.size > @samples_limit
175
+ (c[:sample].size + sample.size - @samples_limit).times do
176
+ c[:sample].delete_at(rand(c[:sample].size))
177
+ end
178
+ end
179
+ c[:sample] += sample
180
+ end
181
+ end
182
+ end
183
+
184
+ def emit(tag, es, chain)
185
+ min = nil
186
+ max = nil
187
+ sum = 0
188
+ num = 0
189
+ sample = if @percentiles then [] else nil end
190
+
191
+ es.each do |time,record|
192
+ value = record[@monitor_key]
193
+ next if value.nil?
194
+
195
+ value = value.to_f
196
+ if min.nil? or min > value
197
+ min = value
198
+ end
199
+ if max.nil? or max < value
200
+ max = value
201
+ end
202
+ sum += value
203
+ num += 1
204
+
205
+ if @percentiles
206
+ sample.push(value)
207
+ end
208
+ end
209
+ if sample.size > @samples_limit
210
+ (sample.size - @samples_limit / 2).to_i.times do
211
+ sample.delete_at(rand(sample.size))
212
+ end
213
+ end
214
+ countups(tag, min, max, sum, num, sample)
215
+
216
+ chain.next
217
+ end
218
+ end
@@ -0,0 +1,28 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ begin
4
+ Bundler.setup(:default, :development)
5
+ rescue Bundler::BundlerError => e
6
+ $stderr.puts e.message
7
+ $stderr.puts "Run `bundle install` to install missing gems"
8
+ exit e.status_code
9
+ end
10
+ require 'test/unit'
11
+
12
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
13
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
14
+ require 'fluent/test'
15
+ unless ENV.has_key?('VERBOSE')
16
+ nulllogger = Object.new
17
+ nulllogger.instance_eval {|obj|
18
+ def method_missing(method, *args)
19
+ # pass
20
+ end
21
+ }
22
+ $log = nulllogger
23
+ end
24
+
25
+ require 'fluent/plugin/out_numeric_monitor'
26
+
27
+ class Test::Unit::TestCase
28
+ end
@@ -0,0 +1,66 @@
1
+ require 'helper'
2
+
3
+ class NumericMonitorOutputTest < Test::Unit::TestCase
4
+ def setup
5
+ Fluent::Test.setup
6
+ end
7
+
8
+ CONFIG = %[
9
+ unit minute
10
+ tag monitor.test
11
+ input_tag_remove_prefix test
12
+ monitor_key field1
13
+ percentiles 80,90
14
+ ]
15
+
16
+ def create_driver(conf = CONFIG, tag='test.input')
17
+ Fluent::Test::OutputTestDriver.new(Fluent::NumericMonitorOutput, tag).configure(conf)
18
+ end
19
+
20
+ def test_configure
21
+ #TODO
22
+ end
23
+
24
+ def test_count_initialized
25
+ #TODO
26
+ end
27
+
28
+ def test_countups
29
+ #TODO
30
+ end
31
+
32
+ def test_stripped_tag
33
+ d = create_driver
34
+ assert_equal 'input', d.instance.stripped_tag('test.input')
35
+ assert_equal 'test.input', d.instance.stripped_tag('test.test.input')
36
+ assert_equal 'input', d.instance.stripped_tag('input')
37
+ end
38
+
39
+ def test_generate_output
40
+ #TODO
41
+ end
42
+
43
+ def test_emit
44
+ d1 = create_driver(CONFIG, 'test.tag1')
45
+ d1.run do
46
+ 10.times do
47
+ d1.emit({'field1' => 0})
48
+ d1.emit({'field1' => '1'})
49
+ d1.emit({'field1' => 2})
50
+ d1.emit({'field1' => '3'})
51
+ d1.emit({'field1' => 4})
52
+ d1.emit({'field1' => 5})
53
+ d1.emit({'field1' => 6})
54
+ d1.emit({'field1' => 7})
55
+ d1.emit({'field1' => 8})
56
+ d1.emit({'field1' => 9})
57
+ end
58
+ end
59
+ r1 = d1.instance.flush
60
+ assert_equal 0, r1['tag1_min']
61
+ assert_equal 9, r1['tag1_max']
62
+ assert_equal 4.5, r1['tag1_avg']
63
+ assert_equal 7, r1['tag1_percentile_80']
64
+ assert_equal 8, r1['tag1_percentile_90']
65
+ end
66
+ end
metadata ADDED
@@ -0,0 +1,89 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fluent-plugin-numeric-monitor
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - TAGOMORI Satoshi
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-05-18 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: fluentd
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: fluentd
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ description: Fluentd plugin to calculate min/max/avg/Xpercentile values, and emit
47
+ these data as message
48
+ email:
49
+ - tagomoris@gmail.com
50
+ executables: []
51
+ extensions: []
52
+ extra_rdoc_files: []
53
+ files:
54
+ - .gitignore
55
+ - Gemfile
56
+ - LICENSE.txt
57
+ - README.md
58
+ - Rakefile
59
+ - fluent-plugin-numeric-monitor.gemspec
60
+ - lib/fluent/plugin/out_numeric_monitor.rb
61
+ - test/helper.rb
62
+ - test/plugin/test_out_numeric_monitor.rb
63
+ homepage: https://github.com/tagomoris/fluent-plugin-numeric-monitor
64
+ licenses: []
65
+ post_install_message:
66
+ rdoc_options: []
67
+ require_paths:
68
+ - lib
69
+ required_ruby_version: !ruby/object:Gem::Requirement
70
+ none: false
71
+ requirements:
72
+ - - ! '>='
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ required_rubygems_version: !ruby/object:Gem::Requirement
76
+ none: false
77
+ requirements:
78
+ - - ! '>='
79
+ - !ruby/object:Gem::Version
80
+ version: '0'
81
+ requirements: []
82
+ rubyforge_project:
83
+ rubygems_version: 1.8.21
84
+ signing_key:
85
+ specification_version: 3
86
+ summary: Fluentd plugin to calculate min/max/avg/Xpercentile values
87
+ test_files:
88
+ - test/helper.rb
89
+ - test/plugin/test_out_numeric_monitor.rb