fluent-plugin-numeric-monitor 0.0.1

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