fluent-plugin-derive 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/.travis.yml +6 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +104 -0
- data/Rakefile +1 -0
- data/fluent-plugin-derive.gemspec +23 -0
- data/lib/fluent/plugin/out_derive.rb +175 -0
- data/spec/out_derive_bench.rb +49 -0
- data/spec/out_derive_spec.rb +289 -0
- data/spec/spec_helper.rb +13 -0
- metadata +101 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 51798346dc27a15ff0a23c51c4926e146c3af789
|
4
|
+
data.tar.gz: 16dc81bbc0e36994303602b7becc18ccf8091ff0
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: d2a1c43275e27ea1e9a270c6dc35aba23fa89ffd200b6d23099920188ba8deb5e4528e74e767be24ff9cc3de82b911f4c492f4d813c578c2a5a69d16685ac411
|
7
|
+
data.tar.gz: c13f6fede3c12711518daeb96131062032876e5a2528bf0acc6fc976a5ccf445866c509067ea771b45d8a6da67c45b40174498913ff753a114a6d544d81b3a95
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Nobuhiro Nikushi
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
# fluent-plugin-derive
|
2
|
+
|
3
|
+
Calculate per second value for the increasing/decreasing value between the last and the current, like derive in RRDTool.
|
4
|
+
|
5
|
+
For example, imagine interface counter values that are inputted by SNMP polling, What we want to know is essentially the bps not the raw value. By this plug-in, the last inputted values of the specific keys and time are cached, and the per second rate for each key are culculated and re-emitted when the next record is inputted.
|
6
|
+
|
7
|
+
Note that Fluentd does not guarantee the order of arrival of messages, it may not be able to calculate accurately if the messages which are tagged with same name are inputted at short intervals. So DO NOT USE this plugin in that case.
|
8
|
+
|
9
|
+
I am using the derive plugin in combination with [fluent-plugin-snmp](https://github.com/iij/fluent-plugin-snmp).
|
10
|
+
|
11
|
+
## Configuration
|
12
|
+
|
13
|
+
### Example 1
|
14
|
+
|
15
|
+
<match foo.bar.**>
|
16
|
+
type derive
|
17
|
+
add_tag_prefix derive
|
18
|
+
key1 foo_count
|
19
|
+
key2 bar_count
|
20
|
+
</match>
|
21
|
+
|
22
|
+
Assuming following inputs are coming:
|
23
|
+
|
24
|
+
2013-12-19 20:01:00 +0900 foo.bar: {"foo_count": 100, "bar_count": 200}
|
25
|
+
2013-12-19 20:02:00 +0900 foo.bar: {"foo_count": 700, "bar_count": 1400}
|
26
|
+
2013-12-19 20:03:10 +0900 foo.bar: {"foo_count": 700, "bar_count": 1470}
|
27
|
+
2013-12-19 20:04:10 +0900 foo.bar: {"foo_count": 1300, "bar_count": 870}
|
28
|
+
|
29
|
+
then output becomes as below:
|
30
|
+
|
31
|
+
2013-12-19 20:01:00 +0900 derive.foo.bar: {"foo_count": nil, "bar_count": nil}
|
32
|
+
2013-12-19 20:02:00 +0900 derive.foo.bar: {"foo_count": 10, "bar_count": 20}
|
33
|
+
2013-12-19 20:03:10 +0900 derive.foo.bar: {"foo_count": 0, "bar_count": 1}
|
34
|
+
2013-12-19 20:04:10 +0900 derive.foo.bar: {"foo_count": 10, "bar_count": -10}
|
35
|
+
|
36
|
+
Cacled as a per sec rate. See below how calced.
|
37
|
+
|
38
|
+
(700/100)/(20:02:00 - 20:01:00) => 10
|
39
|
+
|
40
|
+
### Example 2
|
41
|
+
|
42
|
+
<match foo.bar.**>
|
43
|
+
type derive
|
44
|
+
add_tag_prefix derive
|
45
|
+
key1 foo_count *1000
|
46
|
+
key2 bar_count *1000
|
47
|
+
</match>
|
48
|
+
|
49
|
+
Assuming following inputs are coming:
|
50
|
+
|
51
|
+
2013-12-19 20:01:00 +0900 foo.bar: {"foo_count": 100, "bar_count": 200}
|
52
|
+
2013-12-19 20:02:00 +0900 foo.bar: {"foo_count": 700, "bar_count": 1400}
|
53
|
+
2013-12-19 20:03:10 +0900 foo.bar: {"foo_count": 700, "bar_count": 1470}
|
54
|
+
2013-12-19 20:04:10 +0900 foo.bar: {"foo_count": 1300, "bar_count": 870}
|
55
|
+
|
56
|
+
then output becomes as below:
|
57
|
+
|
58
|
+
2013-12-19 20:01:00 +0900 derive.foo.bar: {"foo_count": nil, "bar_count": nil}
|
59
|
+
2013-12-19 20:02:00 +0900 derive.foo.bar: {"foo_count": 10000, "bar_count": 20000}
|
60
|
+
2013-12-19 20:03:10 +0900 derive.foo.bar: {"foo_count": 0, "bar_count": 1000}
|
61
|
+
2013-12-19 20:04:10 +0900 derive.foo.bar: {"foo_count": 10000, "bar_count": -10000}
|
62
|
+
|
63
|
+
## Paramteres
|
64
|
+
* key[1-20] [Adjustment]
|
65
|
+
|
66
|
+
A pair of a field name of the input record, and to be calculated. key1 or key_pattern is required. `Adjustment` is optional.
|
67
|
+
|
68
|
+
Use `Adjustment` like follow:
|
69
|
+
|
70
|
+
key1 foo_count *3600000 => output the rate as K/h
|
71
|
+
key1 foo_count *8 => shift unit (e.g. Byteps to bps)
|
72
|
+
key1 foo_count /1000 => shift unit (e.g. M to K)
|
73
|
+
|
74
|
+
* key_pattern [Adjustment]
|
75
|
+
|
76
|
+
A pair of a regular expression to specify field names of the input record, and to be calculated. key1 or key_pattern is required. `Adjustment` is optional.
|
77
|
+
|
78
|
+
* tag
|
79
|
+
|
80
|
+
The output tag name
|
81
|
+
|
82
|
+
* add_tag_prefix
|
83
|
+
|
84
|
+
Add tag prefix for output message
|
85
|
+
|
86
|
+
* remove_tag_prefix
|
87
|
+
|
88
|
+
Remove tag prefix for output message
|
89
|
+
|
90
|
+
* min
|
91
|
+
|
92
|
+
Define the expected range value. If min and/or max are specified any value outside the defined range will be truncated.
|
93
|
+
|
94
|
+
* max
|
95
|
+
|
96
|
+
Define the expected range value. If min and/or max are specified any value outside the defined range will be truncated.
|
97
|
+
|
98
|
+
## Contributing
|
99
|
+
|
100
|
+
1. Fork it
|
101
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
102
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
103
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
104
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "fluent-plugin-derive"
|
7
|
+
spec.version = "0.0.1"
|
8
|
+
spec.authors = ["Nobuhiro Nikushi"]
|
9
|
+
spec.email = ["deneb.ge@gmail.com"]
|
10
|
+
spec.description = spec.summary
|
11
|
+
spec.summary = "fluentd plugin to derive rate"
|
12
|
+
spec.homepage = "https://github.com/niku4i/fluent-plugin-derive"
|
13
|
+
spec.license = "MIT"
|
14
|
+
|
15
|
+
spec.files = `git ls-files`.split($/)
|
16
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
17
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
18
|
+
spec.require_paths = ["lib"]
|
19
|
+
|
20
|
+
spec.add_runtime_dependency "fluentd"
|
21
|
+
spec.add_development_dependency "rake"
|
22
|
+
spec.add_development_dependency "rspec"
|
23
|
+
end
|
@@ -0,0 +1,175 @@
|
|
1
|
+
class Fluent::DeriveOutput < Fluent::Output
|
2
|
+
Fluent::Plugin.register_output('derive', self)
|
3
|
+
|
4
|
+
KEY_MAX_NUM = 20
|
5
|
+
(1..KEY_MAX_NUM).each {|i| config_param "key#{i}".to_sym, :string, :default => nil }
|
6
|
+
config_param :key_pattern, :string, :default => nil
|
7
|
+
config_param :tag, :string, :default => nil
|
8
|
+
config_param :add_tag_prefix, :string, :default => nil
|
9
|
+
config_param :remove_tag_prefix, :string, :default => nil
|
10
|
+
config_param :min, :integer, :default => nil
|
11
|
+
config_param :max, :integer, :default => nil
|
12
|
+
|
13
|
+
# for test
|
14
|
+
attr_reader :key_pattern
|
15
|
+
attr_reader :key_pattern_adjustment
|
16
|
+
attr_reader :keys
|
17
|
+
attr_reader :prev
|
18
|
+
|
19
|
+
def configure(conf)
|
20
|
+
super
|
21
|
+
|
22
|
+
if @key_pattern
|
23
|
+
key_pattern, @key_pattern_adjustment = @key_pattern.split(/ +/, 2)
|
24
|
+
@key_pattern_adjustment = parse_adjustment(@key_pattern_adjustment)
|
25
|
+
@key_pattern = Regexp.compile(key_pattern)
|
26
|
+
else
|
27
|
+
@keys = {}
|
28
|
+
(1..KEY_MAX_NUM).each do |i|
|
29
|
+
next unless conf["key#{i}"]
|
30
|
+
key, adjustment = conf["key#{i}"].split(/ +/, 2)
|
31
|
+
adjustment = parse_adjustment(adjustment)
|
32
|
+
@keys[key] = adjustment
|
33
|
+
end
|
34
|
+
end
|
35
|
+
raise Fluent::ConfigError, "Either of `key_pattern` or `key1` must be specified" if (@key_pattern.nil? and @keys.empty?)
|
36
|
+
|
37
|
+
raise Fluent::ConfigError, "Either of `tag`, `add_tag_prefix`, or `remove_tag_prefix` must be specified" if (@tag.nil? and @add_tag_prefix.nil? and @remove_tag_prefix.nil?)
|
38
|
+
@tag_prefix = "#{@add_tag_prefix}." if @add_tag_prefix
|
39
|
+
@tag_prefix_match = "#{@remove_tag_prefix}." if @remove_tag_prefix
|
40
|
+
@tag_proc =
|
41
|
+
if @tag
|
42
|
+
Proc.new {|tag| @tag }
|
43
|
+
elsif @tag_prefix and @tag_prefix_match
|
44
|
+
Proc.new {|tag| "#{@tag_prefix}#{lstrip(tag, @tag_prefix_match)}" }
|
45
|
+
elsif @tag_prefix_match
|
46
|
+
Proc.new {|tag| lstrip(tag, @tag_prefix_match) }
|
47
|
+
elsif @tag_prefix
|
48
|
+
Proc.new {|tag| "#{@tag_prefix}#{tag}" }
|
49
|
+
else
|
50
|
+
Proc.new {|tag| tag }
|
51
|
+
end
|
52
|
+
|
53
|
+
raise Fluent::ConfigError, "`max` must be greater than `min`" if (@min && @max && @min >= @max)
|
54
|
+
|
55
|
+
@prev = {}
|
56
|
+
@mutex = Mutex.new
|
57
|
+
rescue => e
|
58
|
+
raise Fluent::ConfigError, "#{e.class} #{e.message} #{e.backtrace.first}"
|
59
|
+
end
|
60
|
+
|
61
|
+
def start
|
62
|
+
super
|
63
|
+
end
|
64
|
+
|
65
|
+
def shutdown
|
66
|
+
super
|
67
|
+
end
|
68
|
+
|
69
|
+
def emit(tag, es, chain)
|
70
|
+
emit_tag = @tag_proc.call(tag)
|
71
|
+
|
72
|
+
if @key_pattern
|
73
|
+
es.each do |time, record|
|
74
|
+
record.each do |key, value|
|
75
|
+
next unless key =~ @key_pattern
|
76
|
+
value = value.to_i
|
77
|
+
prev_time, prev_value = get_prev_value(tag, key)
|
78
|
+
unless prev_time && prev_value
|
79
|
+
save_to_prev(time, tag, key, value)
|
80
|
+
record[key] = nil
|
81
|
+
next
|
82
|
+
end
|
83
|
+
# adjustment
|
84
|
+
rate = calc_rate(tag, key, value, prev_value, time, prev_time, @key_pattern_adjustment)
|
85
|
+
rate = truncate_min(rate, @min) if @min
|
86
|
+
rate = truncate_max(rate, @max) if @max
|
87
|
+
# Set new value
|
88
|
+
record[key] = rate
|
89
|
+
save_to_prev(time, tag, key, value)
|
90
|
+
end
|
91
|
+
Fluent::Engine.emit(emit_tag, time, record)
|
92
|
+
end
|
93
|
+
else #keys
|
94
|
+
es.each do |time, record|
|
95
|
+
@keys.each do |key, adjustment|
|
96
|
+
next unless value = record[key]
|
97
|
+
value = value.to_i
|
98
|
+
prev_time, prev_value = get_prev_value(tag, key)
|
99
|
+
unless prev_time && prev_value
|
100
|
+
save_to_prev(time, tag, key, value)
|
101
|
+
record[key] = nil
|
102
|
+
next
|
103
|
+
end
|
104
|
+
# adjustment
|
105
|
+
rate = calc_rate(tag, key, value, prev_value, time, prev_time, adjustment)
|
106
|
+
rate = truncate_min(rate, @min) if @min
|
107
|
+
rate = truncate_max(rate, @max) if @max
|
108
|
+
# Set new value
|
109
|
+
record[key] = rate
|
110
|
+
save_to_prev(time, tag, key, value)
|
111
|
+
end
|
112
|
+
Fluent::Engine.emit(emit_tag, time, record)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
chain.next
|
117
|
+
rescue => e
|
118
|
+
$log.warn e.message
|
119
|
+
$log.warn e.backtrace.join(', ')
|
120
|
+
end
|
121
|
+
|
122
|
+
# @return [Array] time, value
|
123
|
+
def get_prev_value(tag, key)
|
124
|
+
@prev["#{tag}:#{key}"] || []
|
125
|
+
end
|
126
|
+
|
127
|
+
def save_to_prev(time, tag, key, value)
|
128
|
+
@mutex.synchronize { @prev["#{tag}:#{key}"] = [time, value] }
|
129
|
+
end
|
130
|
+
|
131
|
+
private
|
132
|
+
|
133
|
+
def lstrip(string, substring)
|
134
|
+
string.index(substring) == 0 ? string[substring.size..-1] : string
|
135
|
+
end
|
136
|
+
|
137
|
+
def parse_adjustment(str)
|
138
|
+
case str
|
139
|
+
when /^\*(\d+)$/
|
140
|
+
['*', $1.to_i]
|
141
|
+
when /^\/(\d+)$/
|
142
|
+
['/', $1.to_i]
|
143
|
+
else
|
144
|
+
nil
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def calc_rate(tag, key, cur_value, prev_value, cur_time, prev_time, adjustment = nil)
|
149
|
+
if cur_time - prev_time <= 0
|
150
|
+
$log.warn "Could not calculate the rate. multiple input less than one second or minus delta of seconds on tag=#{tag}, key=#{key}"
|
151
|
+
return nil
|
152
|
+
end
|
153
|
+
rate = (cur_value - prev_value)/(cur_time - prev_time)
|
154
|
+
if adjustment && adjustment[0] == '*'
|
155
|
+
rate * adjustment[1]
|
156
|
+
elsif adjustment && adjustment[0] == '/'
|
157
|
+
rate / adjustment[1]
|
158
|
+
else
|
159
|
+
rate
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
def truncate_min(value, min)
|
164
|
+
return nil unless value
|
165
|
+
(value < min) ? min : value
|
166
|
+
end
|
167
|
+
|
168
|
+
def truncate_max(value, max)
|
169
|
+
return nil unless value
|
170
|
+
(value > max) ? max : value
|
171
|
+
end
|
172
|
+
|
173
|
+
|
174
|
+
end
|
175
|
+
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require_relative 'spec_helper'
|
3
|
+
|
4
|
+
# setup
|
5
|
+
Fluent::Test.setup
|
6
|
+
config = %[
|
7
|
+
remove_tag_prefix foo
|
8
|
+
add_tag_prefix hoge
|
9
|
+
key1 foooooo_baaaaaa_count *1000
|
10
|
+
key2 hogeeee_fugaaaa_count *1500
|
11
|
+
min 0
|
12
|
+
max 1000000000
|
13
|
+
]
|
14
|
+
|
15
|
+
time = Time.now.to_i
|
16
|
+
tag = 'foo.bar'
|
17
|
+
driver = Fluent::Test::OutputTestDriver.new(Fluent::DeriveOutput, tag).configure(config)
|
18
|
+
|
19
|
+
# bench
|
20
|
+
require 'benchmark'
|
21
|
+
message = "2013/01/13T07:02:11.124202 INFO GET /ping"
|
22
|
+
n = 100000
|
23
|
+
Benchmark.bm(7) do |x|
|
24
|
+
x.report { driver.run { n.times do |i|
|
25
|
+
time = time + i*60
|
26
|
+
driver.emit({'foooooo_baaaaaa_count' => 1234, 'hogeeee_fugaaaa_count' => 5000, 'unmached_keeeeee' => "abvc" }, time)
|
27
|
+
end
|
28
|
+
} }
|
29
|
+
end
|
30
|
+
|
31
|
+
# key_pattern without adjustment
|
32
|
+
# user system total real
|
33
|
+
# 2.920000 0.030000 2.950000 ( 3.466375)
|
34
|
+
#
|
35
|
+
# key_pattern with adjustment
|
36
|
+
# user system total real
|
37
|
+
# 3.000000 0.040000 3.040000 ( 3.550168)
|
38
|
+
#
|
39
|
+
# key1 without adjustment and key2 without adjustment
|
40
|
+
# user system total real
|
41
|
+
# 2.520000 0.030000 2.550000 ( 3.058561)
|
42
|
+
#
|
43
|
+
# key1 with adjustment and key2 with adjustment
|
44
|
+
# user system total real
|
45
|
+
# 2.630000 0.030000 2.660000 ( 3.180860)
|
46
|
+
#
|
47
|
+
# key1 with adjustment, key2 with adjustment, min and max
|
48
|
+
# user system total real
|
49
|
+
# 2.650000 0.040000 2.690000 ( 3.188757)
|
@@ -0,0 +1,289 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require_relative 'spec_helper'
|
3
|
+
|
4
|
+
describe Fluent::DeriveOutput do
|
5
|
+
before { Fluent::Test.setup }
|
6
|
+
let(:tag) { 'counter.host1' }
|
7
|
+
let(:driver) { Fluent::Test::OutputTestDriver.new(Fluent::DeriveOutput, tag).configure(config) }
|
8
|
+
|
9
|
+
describe 'test configure' do
|
10
|
+
describe 'bad configuration' do
|
11
|
+
context 'none of key_pattern or key1 are included' do
|
12
|
+
let(:config) { "" }
|
13
|
+
it { expect { driver }.to raise_error(Fluent::ConfigError) }
|
14
|
+
end
|
15
|
+
context 'none of tag, add_tag_prefix, or remove_tag_prefix are included' do
|
16
|
+
let(:config) { "key1 foo" }
|
17
|
+
end
|
18
|
+
context 'min greater than max' do
|
19
|
+
let(:config) {%[
|
20
|
+
tag rate
|
21
|
+
key1 foo_count
|
22
|
+
min 1
|
23
|
+
max 0
|
24
|
+
]}
|
25
|
+
it { expect { driver }.to raise_error(Fluent::ConfigError) }
|
26
|
+
end
|
27
|
+
context 'min == max' do
|
28
|
+
let(:config) {%[
|
29
|
+
tag rate
|
30
|
+
key1 foo_count
|
31
|
+
min 1
|
32
|
+
max 1
|
33
|
+
]}
|
34
|
+
it { expect { driver }.to raise_error(Fluent::ConfigError) }
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe 'good configuration' do
|
39
|
+
subject { driver.instance }
|
40
|
+
|
41
|
+
context "check default" do
|
42
|
+
let(:config) { %[
|
43
|
+
tag rate
|
44
|
+
key1 foo
|
45
|
+
] }
|
46
|
+
its(:tag) { should eq "rate" }
|
47
|
+
its(:add_tag_prefix) { should be_nil }
|
48
|
+
its(:remove_tag_prefix) { should be_nil }
|
49
|
+
its(:min) { should be_nil }
|
50
|
+
its(:max) { should be_nil }
|
51
|
+
end
|
52
|
+
|
53
|
+
context "key_pattern" do
|
54
|
+
let(:config) {%[
|
55
|
+
tag rate
|
56
|
+
key_pattern _count$
|
57
|
+
]}
|
58
|
+
it { subject.key_pattern.should == Regexp.compile("_count$") }
|
59
|
+
end
|
60
|
+
|
61
|
+
context "key_pattern_adjustment" do
|
62
|
+
let(:config) {%[
|
63
|
+
tag rate
|
64
|
+
key_pattern _count$ *1000
|
65
|
+
]}
|
66
|
+
it { subject.key_pattern_adjustment.should eq ['*', 1000] }
|
67
|
+
end
|
68
|
+
|
69
|
+
context "keys" do
|
70
|
+
let(:config) {%[
|
71
|
+
tag rate
|
72
|
+
key1 foo_count
|
73
|
+
key2 bar_count
|
74
|
+
]}
|
75
|
+
it { subject.keys["foo_count"].should be_nil }
|
76
|
+
it { subject.keys["bar_count"].should be_nil }
|
77
|
+
end
|
78
|
+
|
79
|
+
context "keys adjustment" do
|
80
|
+
let(:config) {%[
|
81
|
+
tag rate
|
82
|
+
key1 foo_count *1000
|
83
|
+
key2 bar_count /1000
|
84
|
+
]}
|
85
|
+
it { subject.keys["foo_count"].should eq ['*', 1000] }
|
86
|
+
it { subject.keys["bar_count"].should eq ['/', 1000] }
|
87
|
+
end
|
88
|
+
|
89
|
+
context 'min < max' do
|
90
|
+
let(:config) {%[
|
91
|
+
tag rate
|
92
|
+
key1 foo_count
|
93
|
+
min 0
|
94
|
+
max 1
|
95
|
+
]}
|
96
|
+
it { expect { driver }.not_to raise_error(Fluent::ConfigError) }
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
describe 'test emit' do
|
103
|
+
let(:time) { Time.now.to_i }
|
104
|
+
|
105
|
+
context 'keys' do
|
106
|
+
|
107
|
+
context 'normal' do
|
108
|
+
let(:config) { %[
|
109
|
+
tag rate
|
110
|
+
key1 foo_count
|
111
|
+
key2 bar_count
|
112
|
+
]}
|
113
|
+
before do
|
114
|
+
driver.run {
|
115
|
+
driver.emit({'foo_count'=> 100, 'bar_count' =>200, 'other_key' => 'abc'}, time)
|
116
|
+
driver.emit({'foo_count'=> 700, 'bar_count' =>1400}, time + 60)
|
117
|
+
driver.emit({'foo_count'=> 700, 'bar_count' =>800}, time + 120)
|
118
|
+
}
|
119
|
+
end
|
120
|
+
it {
|
121
|
+
driver.emits[0].should == ['rate', time, {'foo_count' => nil, 'bar_count' => nil, 'other_key' => 'abc'}]
|
122
|
+
driver.emits[1].should == ['rate', time + 60, {'foo_count' => 10, 'bar_count' => 20}]
|
123
|
+
driver.emits[2].should == ['rate', time + 120, {'foo_count' => 0, 'bar_count' => -10}]
|
124
|
+
driver.instance.prev.should == {"#{tag}:foo_count"=>[time+120, 700], "#{tag}:bar_count"=>[time+120, 800]}
|
125
|
+
}
|
126
|
+
end
|
127
|
+
|
128
|
+
context 'multiple records in same time' do
|
129
|
+
let(:config) { %[
|
130
|
+
tag rate
|
131
|
+
key1 foo_count
|
132
|
+
key2 bar_count
|
133
|
+
]}
|
134
|
+
before do
|
135
|
+
driver.run {
|
136
|
+
driver.emit({'foo_count'=> 100, 'bar_count' =>200}, time)
|
137
|
+
driver.emit({'foo_count'=> 100, 'bar_count' =>200}, time)
|
138
|
+
}
|
139
|
+
end
|
140
|
+
it {
|
141
|
+
driver.emits[1].should == ['rate', time, {'foo_count' => nil, 'bar_count' => nil}]
|
142
|
+
}
|
143
|
+
end
|
144
|
+
|
145
|
+
context 'adjustment' do
|
146
|
+
let(:config) { %[
|
147
|
+
tag rate
|
148
|
+
key1 foo_count
|
149
|
+
key2 bar_count
|
150
|
+
key3 baz_count *1000
|
151
|
+
]}
|
152
|
+
before do
|
153
|
+
driver.run {
|
154
|
+
driver.emit({'foo_count'=> 100, 'bar_count' =>200, 'baz_count' => 300}, time)
|
155
|
+
driver.emit({'foo_count'=> 700, 'bar_count' =>1400, 'baz_count' => 900}, time + 60)
|
156
|
+
}
|
157
|
+
end
|
158
|
+
it {
|
159
|
+
driver.emits[0].should == ['rate', time, {'foo_count' => nil, 'bar_count' => nil, 'baz_count' => nil}]
|
160
|
+
driver.emits[1].should == ['rate', time + 60, {'foo_count' => 10, 'bar_count' => 20, 'baz_count' => 10000}]
|
161
|
+
}
|
162
|
+
end
|
163
|
+
|
164
|
+
context 'min/max' do
|
165
|
+
let(:config) { %[
|
166
|
+
tag rate
|
167
|
+
key1 foo_count
|
168
|
+
key2 bar_count *1000000
|
169
|
+
min 0
|
170
|
+
max 1000
|
171
|
+
]}
|
172
|
+
before do
|
173
|
+
driver.run {
|
174
|
+
driver.emit({'foo_count'=> 100, 'bar_count'=>0}, time)
|
175
|
+
driver.emit({'foo_count'=> 0, 'bar_count'=>6000}, time + 60)
|
176
|
+
}
|
177
|
+
end
|
178
|
+
it {
|
179
|
+
driver.emits[1].should == ['rate', time + 60, {'foo_count' => 0, 'bar_count' => 1000}]
|
180
|
+
}
|
181
|
+
end
|
182
|
+
|
183
|
+
context 'add_tag_prefix' do
|
184
|
+
let(:config) {%[
|
185
|
+
add_tag_prefix rate
|
186
|
+
key1 foo_count
|
187
|
+
key2 bar_count
|
188
|
+
]}
|
189
|
+
before do
|
190
|
+
driver.run {
|
191
|
+
driver.emit({'foo_count'=> 100, 'bar_count' =>200, 'other_key' => 'abc'}, time)
|
192
|
+
}
|
193
|
+
end
|
194
|
+
it { driver.emits[0][0].should == "rate.#{tag}" }
|
195
|
+
end
|
196
|
+
|
197
|
+
context 'remove_tag_prefix' do
|
198
|
+
let(:config) {%[
|
199
|
+
remove_tag_prefix counter
|
200
|
+
key1 foo_count
|
201
|
+
key2 bar_count
|
202
|
+
]}
|
203
|
+
before do
|
204
|
+
driver.run {
|
205
|
+
driver.emit({'foo_count'=> 100, 'bar_count' =>200, 'other_key' => 'abc'}, time)
|
206
|
+
}
|
207
|
+
end
|
208
|
+
it { driver.emits[0][0].should == 'host1' }
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
context 'key_pattern' do
|
213
|
+
|
214
|
+
context 'normal' do
|
215
|
+
let(:config) { %[
|
216
|
+
tag rate
|
217
|
+
key_pattern .*_count$
|
218
|
+
]}
|
219
|
+
before do
|
220
|
+
driver.run {
|
221
|
+
driver.emit({'foo_count'=> 100, 'bar_count' =>200, 'other_key' => 'abc'}, time)
|
222
|
+
driver.emit({'foo_count'=> 700, 'bar_count' =>1400}, time + 60)
|
223
|
+
driver.emit({'foo_count'=> 700, 'bar_count' =>800}, time + 120)
|
224
|
+
}
|
225
|
+
end
|
226
|
+
it {
|
227
|
+
driver.emits[0].should == ['rate', time, {'foo_count' => nil, 'bar_count' => nil, 'other_key' => 'abc'}]
|
228
|
+
driver.emits[1].should == ['rate', time + 60, {'foo_count' => 10, 'bar_count' => 20}]
|
229
|
+
driver.emits[2].should == ['rate', time + 120, {'foo_count' => 0, 'bar_count' => -10}]
|
230
|
+
driver.instance.prev.should == {"#{tag}:foo_count"=>[time+120, 700], "#{tag}:bar_count"=>[time+120, 800]}
|
231
|
+
}
|
232
|
+
end
|
233
|
+
|
234
|
+
context 'multiple records in same time' do
|
235
|
+
let(:config) { %[
|
236
|
+
tag rate
|
237
|
+
key_pattern .*_count$
|
238
|
+
]}
|
239
|
+
before do
|
240
|
+
driver.run {
|
241
|
+
driver.emit({'foo_count'=> 100, 'bar_count' =>200}, time)
|
242
|
+
driver.emit({'foo_count'=> 100, 'bar_count' =>200}, time)
|
243
|
+
}
|
244
|
+
end
|
245
|
+
it {
|
246
|
+
driver.emits[1].should == ['rate', time, {'foo_count' => nil, 'bar_count' => nil}]
|
247
|
+
}
|
248
|
+
end
|
249
|
+
|
250
|
+
context 'adjustment' do
|
251
|
+
let(:config) { %[
|
252
|
+
tag rate
|
253
|
+
key_pattern .*_count$ /10
|
254
|
+
]}
|
255
|
+
before do
|
256
|
+
driver.run {
|
257
|
+
driver.emit({'foo_count'=> 100, 'bar_count' =>200, 'baz_count' => 300}, time)
|
258
|
+
driver.emit({'foo_count'=> 700, 'bar_count' =>1400, 'baz_count' => 900}, time + 60)
|
259
|
+
}
|
260
|
+
end
|
261
|
+
it {
|
262
|
+
driver.emits[0].should == ['rate', time, {'foo_count' => nil, 'bar_count' => nil, 'baz_count' => nil}]
|
263
|
+
driver.emits[1].should == ['rate', time + 60, {'foo_count' => 1, 'bar_count' => 2, 'baz_count' => 1}]
|
264
|
+
}
|
265
|
+
end
|
266
|
+
|
267
|
+
context 'min/max' do
|
268
|
+
let(:config) { %[
|
269
|
+
tag rate
|
270
|
+
key_pattern .*_count$ *10000
|
271
|
+
min 0
|
272
|
+
max 1000
|
273
|
+
]}
|
274
|
+
before do
|
275
|
+
driver.run {
|
276
|
+
driver.emit({'foo_count'=> 100, 'bar_count'=>0}, time)
|
277
|
+
driver.emit({'foo_count'=> 0, 'bar_count'=>6000}, time + 60)
|
278
|
+
}
|
279
|
+
end
|
280
|
+
it {
|
281
|
+
driver.emits[1].should == ['rate', time + 60, {'foo_count' => 0, 'bar_count' => 1000}]
|
282
|
+
}
|
283
|
+
end
|
284
|
+
|
285
|
+
|
286
|
+
end
|
287
|
+
|
288
|
+
end
|
289
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require 'rubygems'
|
3
|
+
require 'bundler'
|
4
|
+
Bundler.setup(:default, :test)
|
5
|
+
Bundler.require(:default, :test)
|
6
|
+
|
7
|
+
require 'fluent/test'
|
8
|
+
require 'rspec'
|
9
|
+
|
10
|
+
$TESTING=true
|
11
|
+
$:.unshift File.join(File.dirname(__FILE__), '..', 'lib')
|
12
|
+
require 'fluent/plugin/out_derive'
|
13
|
+
|
metadata
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: fluent-plugin-derive
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Nobuhiro Nikushi
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-12-23 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: fluentd
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
description: ''
|
56
|
+
email:
|
57
|
+
- deneb.ge@gmail.com
|
58
|
+
executables: []
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- .gitignore
|
63
|
+
- .rspec
|
64
|
+
- .travis.yml
|
65
|
+
- Gemfile
|
66
|
+
- LICENSE.txt
|
67
|
+
- README.md
|
68
|
+
- Rakefile
|
69
|
+
- fluent-plugin-derive.gemspec
|
70
|
+
- lib/fluent/plugin/out_derive.rb
|
71
|
+
- spec/out_derive_bench.rb
|
72
|
+
- spec/out_derive_spec.rb
|
73
|
+
- spec/spec_helper.rb
|
74
|
+
homepage: https://github.com/niku4i/fluent-plugin-derive
|
75
|
+
licenses:
|
76
|
+
- MIT
|
77
|
+
metadata: {}
|
78
|
+
post_install_message:
|
79
|
+
rdoc_options: []
|
80
|
+
require_paths:
|
81
|
+
- lib
|
82
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
83
|
+
requirements:
|
84
|
+
- - '>='
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
version: '0'
|
87
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
88
|
+
requirements:
|
89
|
+
- - '>='
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
version: '0'
|
92
|
+
requirements: []
|
93
|
+
rubyforge_project:
|
94
|
+
rubygems_version: 2.1.10
|
95
|
+
signing_key:
|
96
|
+
specification_version: 4
|
97
|
+
summary: fluentd plugin to derive rate
|
98
|
+
test_files:
|
99
|
+
- spec/out_derive_bench.rb
|
100
|
+
- spec/out_derive_spec.rb
|
101
|
+
- spec/spec_helper.rb
|