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.
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +13 -0
- data/README.md +39 -0
- data/Rakefile +11 -0
- data/fluent-plugin-numeric-monitor.gemspec +18 -0
- data/lib/fluent/plugin/out_numeric_monitor.rb +218 -0
- data/test/helper.rb +28 -0
- data/test/plugin/test_out_numeric_monitor.rb +66 -0
- metadata +89 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -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.
|
data/README.md
ADDED
@@ -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
|
data/Rakefile
ADDED
@@ -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
|
data/test/helper.rb
ADDED
@@ -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
|