fluent-plugin-datacalculator 0.0.1 → 0.0.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/example.conf +19 -5
- data/example.json +8 -0
- data/fluent-plugin-datacalculator.gemspec +1 -1
- data/lib/fluent/plugin/out_datacalculator.rb +111 -27
- data/test/plugin/test_out_datacalculator.rb +63 -25
- metadata +3 -2
data/example.conf
CHANGED
@@ -1,15 +1,29 @@
|
|
1
1
|
<source>
|
2
2
|
type forward
|
3
3
|
</source>
|
4
|
-
|
4
|
+
|
5
|
+
<match payment.quest>
|
6
|
+
type datacalculator
|
7
|
+
tag result.quest
|
8
|
+
count_interval 5s
|
9
|
+
aggregate keys area_id, mission_id
|
10
|
+
formulas sum = amount * price, cnt = 1, total = amount
|
11
|
+
finalizer ave = cnt > 0 ? 1.00 * sum / cnt : 0
|
12
|
+
<unmatched>
|
13
|
+
type file
|
14
|
+
path unmatched
|
15
|
+
</unmatched>
|
16
|
+
</match>
|
17
|
+
|
18
|
+
<match payment.shop>
|
5
19
|
type datacalculator
|
6
|
-
tag result
|
7
|
-
|
20
|
+
tag result.shop
|
21
|
+
count_interval 5s
|
8
22
|
aggregate all
|
9
23
|
formulas sum = amount * price, cnt = 1, total = amount
|
10
|
-
|
24
|
+
finalizer ave = cnt > 0 ? 1.00 * sum / cnt : 0
|
11
25
|
</match>
|
12
26
|
|
13
|
-
<match result
|
27
|
+
<match result.**>
|
14
28
|
type stdout
|
15
29
|
</match>
|
data/example.json
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
{"area_id": 1, "mission_id":1, "amount": 3, "price": 100}
|
2
|
+
{"area_id": 2, "mission_id":2, "amount": 2, "price": 200}
|
3
|
+
{"area_id": 3, "mission_id":1, "amount": 3, "price": 100}
|
4
|
+
{"area_id": 4, "mission_id":1, "amount": 4, "price": 300}
|
5
|
+
{"area_id": 5, "mission_id":2, "amount": 5, "price": 200}
|
6
|
+
{"area_id": 1, "mission_id":1, "amount": 1, "price": 400}
|
7
|
+
{"area_id": 4, "mission_id":1, "amount": 2, "price": 200}
|
8
|
+
{"area_id": 3, "mission_id":2, "amount": 1, "price": 300}
|
@@ -3,7 +3,7 @@ $:.push File.expand_path("../lib", __FILE__)
|
|
3
3
|
|
4
4
|
Gem::Specification.new do |s|
|
5
5
|
s.name = "fluent-plugin-datacalculator"
|
6
|
-
s.version = "0.0.
|
6
|
+
s.version = "0.0.2"
|
7
7
|
s.authors = ["Muddy Dixon"]
|
8
8
|
s.email = ["muddydixon@gmail.com"]
|
9
9
|
s.homepage = "https://github.com/muddydixon/fluent-plugin-datacalculator"
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
1
2
|
class Fluent::DataCalculatorOutput < Fluent::Output
|
2
3
|
Fluent::Plugin.register_output('datacalculator', self)
|
3
4
|
|
@@ -8,11 +9,11 @@ class Fluent::DataCalculatorOutput < Fluent::Output
|
|
8
9
|
config_param :input_tag_remove_prefix, :string, :default => nil
|
9
10
|
config_param :formulas, :string
|
10
11
|
config_param :finalizer, :string, :default => nil
|
11
|
-
config_param :outcast_unmatched, :bool, :default => false
|
12
12
|
|
13
13
|
attr_accessor :tick
|
14
14
|
attr_accessor :counts
|
15
15
|
attr_accessor :last_checked
|
16
|
+
attr_accessor :aggregate_keys
|
16
17
|
attr_accessor :_formulas
|
17
18
|
attr_accessor :_finalizer
|
18
19
|
|
@@ -30,19 +31,47 @@ class Fluent::DataCalculatorOutput < Fluent::Output
|
|
30
31
|
raise RuntimeError, "@unit must be one of minute/hour/day"
|
31
32
|
end
|
32
33
|
end
|
34
|
+
|
35
|
+
|
36
|
+
conf.elements.each do |element|
|
37
|
+
element.keys.each do |k|
|
38
|
+
element[k]
|
39
|
+
end
|
40
|
+
|
41
|
+
case element.name
|
42
|
+
when 'unmatched'
|
43
|
+
@unmatched = element
|
44
|
+
end
|
45
|
+
end
|
46
|
+
# TODO: unmatchedの時に別のタグを付けて、ふってあげないと行けない気がする
|
47
|
+
# unmatchedの定義
|
48
|
+
# 1. aggregate_keys を持たないレコードが入ってきた時
|
49
|
+
# 2. fomulaで必要な要素がなかったレコードが入ってきた時
|
50
|
+
# 3. fomulaで集計可能な数値でない場合(文字列や真偽値、正規表現、ハッシュ、配列など)
|
33
51
|
|
52
|
+
@aggregate_keys = []
|
34
53
|
@aggregate = case @aggregate
|
35
54
|
when 'tag' then :tag
|
36
55
|
when 'all' then :all
|
37
56
|
else
|
38
|
-
|
57
|
+
if @aggregate.index('keys') == 0
|
58
|
+
@aggregate_keys = @aggregate.split(/\s/, 2)[1]
|
59
|
+
unless @aggregate_keys
|
60
|
+
raise Fluent::ConfigError, "aggregate_keys require in keys"
|
61
|
+
end
|
62
|
+
@aggregate_keys = @aggregate_keys.split(/\s*,\s*/)
|
63
|
+
@aggregate = 'keys'
|
64
|
+
else
|
65
|
+
raise Fluent::ConfigError, "flowcounter aggregate allows tag/all"
|
66
|
+
end
|
39
67
|
end
|
40
68
|
|
41
69
|
def createFunc (cnt, str)
|
42
70
|
str.strip!
|
43
71
|
left, right = str.split(/\s*=\s*/, 2)
|
44
|
-
|
45
|
-
|
72
|
+
# Fluent moduleだけはOK
|
73
|
+
rights = right.scan(/[a-zA-Z][\w\d_\.\$\:\@]*/).uniq.select{|x| x.index('Fluent') != 0}
|
74
|
+
|
46
75
|
begin
|
47
76
|
f = eval('lambda {|'+rights.join(',')+'| '+right + '}')
|
48
77
|
rescue SyntaxError
|
@@ -57,7 +86,7 @@ class Fluent::DataCalculatorOutput < Fluent::Output
|
|
57
86
|
tag = stripped_tag (tag)
|
58
87
|
end
|
59
88
|
_argv = []
|
60
|
-
|
89
|
+
|
61
90
|
argv.each {|arg|
|
62
91
|
if tag != nil and tag != 'all'
|
63
92
|
arg = tag + '_' + arg
|
@@ -67,11 +96,11 @@ class Fluent::DataCalculatorOutput < Fluent::Output
|
|
67
96
|
formula.call(*_argv)
|
68
97
|
end
|
69
98
|
|
70
|
-
@_formulas = [
|
99
|
+
@_formulas = []
|
71
100
|
if conf.has_key?('formulas')
|
72
101
|
fs = conf['formulas'].split(/\s*,\s*/)
|
73
102
|
fs.each_with_index { |str,i |
|
74
|
-
@_formulas.push( createFunc(i
|
103
|
+
@_formulas.push( createFunc(i, str) )
|
75
104
|
}
|
76
105
|
end
|
77
106
|
|
@@ -129,7 +158,7 @@ class Fluent::DataCalculatorOutput < Fluent::Output
|
|
129
158
|
if @aggregate == :all
|
130
159
|
tag = 'all'
|
131
160
|
end
|
132
|
-
|
161
|
+
|
133
162
|
@mutex.synchronize {
|
134
163
|
@counts[tag] ||= [0] * @_formulas.length
|
135
164
|
counts.each_with_index do |count, i|
|
@@ -146,14 +175,8 @@ class Fluent::DataCalculatorOutput < Fluent::Output
|
|
146
175
|
end
|
147
176
|
|
148
177
|
def generate_output(counts, step)
|
149
|
-
output = {}
|
150
178
|
if @aggregate == :all
|
151
|
-
|
152
|
-
sum = if @outcast_unmatched
|
153
|
-
counts['all'][1..-1].inject(:+)
|
154
|
-
else
|
155
|
-
counts['all'].inject(:+)
|
156
|
-
end
|
179
|
+
output = {}
|
157
180
|
counts['all'].each_with_index do |count,i|
|
158
181
|
name = @_formulas[i][1]
|
159
182
|
output[name] = count
|
@@ -163,16 +186,37 @@ class Fluent::DataCalculatorOutput < Fluent::Output
|
|
163
186
|
output[@_finalizer[1]] = execFunc('all', output, @_finalizer[2], @_finalizer[3])
|
164
187
|
end
|
165
188
|
|
166
|
-
return output
|
189
|
+
return [output]
|
167
190
|
end
|
168
191
|
|
192
|
+
if @aggregate == 'keys'
|
193
|
+
outputs = []
|
194
|
+
|
195
|
+
counts.keys.each do |pat|
|
196
|
+
output = {}
|
197
|
+
pat_val = pat.split('_').map{|x| x.to_i }
|
198
|
+
counts[pat].each_with_index do |count, i|
|
199
|
+
name = @_formulas[i][1]
|
200
|
+
output[name] = count
|
201
|
+
end
|
202
|
+
|
203
|
+
@aggregate_keys.each_with_index do |key, i|
|
204
|
+
output[@aggregate_keys[i]] = pat_val[i]
|
205
|
+
end
|
206
|
+
|
207
|
+
if @_finalizer
|
208
|
+
output[@_finalizer[1]] = execFunc('all', output, @_finalizer[2], @_finalizer[3])
|
209
|
+
end
|
210
|
+
|
211
|
+
outputs.push(output)
|
212
|
+
end
|
213
|
+
|
214
|
+
return outputs
|
215
|
+
end
|
216
|
+
|
217
|
+
output = {}
|
169
218
|
counts.keys.each do |tag|
|
170
219
|
t = stripped_tag(tag)
|
171
|
-
sum = if @outcast_unmatched
|
172
|
-
counts[tag][1..-1].inject(:+)
|
173
|
-
else
|
174
|
-
counts[tag].inject(:+)
|
175
|
-
end
|
176
220
|
counts[tag].each_with_index do |count,i|
|
177
221
|
name = @_formulas[i][1]
|
178
222
|
output[t + '_' + name] = count
|
@@ -181,16 +225,19 @@ class Fluent::DataCalculatorOutput < Fluent::Output
|
|
181
225
|
output[t + '_' + @_finalizer[1]] = execFunc(tag, output, @_finalizer[2], @_finalizer[3])
|
182
226
|
end
|
183
227
|
end
|
184
|
-
output
|
228
|
+
[output]
|
185
229
|
end
|
186
230
|
|
187
231
|
def flush(step)
|
188
|
-
flushed
|
232
|
+
flushed, @counts = @counts,count_initialized(@counts.keys.dup)
|
189
233
|
generate_output(flushed, step)
|
190
234
|
end
|
191
235
|
|
192
236
|
def flush_emit(step)
|
193
|
-
|
237
|
+
data = flush(step)
|
238
|
+
data.each do |dat|
|
239
|
+
Fluent::Engine.emit(@tag, Fluent::Engine.now, dat)
|
240
|
+
end
|
194
241
|
end
|
195
242
|
|
196
243
|
def start_watch
|
@@ -220,9 +267,45 @@ class Fluent::DataCalculatorOutput < Fluent::Output
|
|
220
267
|
return true
|
221
268
|
end
|
222
269
|
|
223
|
-
def emit(tag, es, chain)
|
224
|
-
|
270
|
+
def emit (tag, es, chain)
|
271
|
+
|
272
|
+
if @aggregate == 'keys'
|
273
|
+
emit_aggregate_keys(tag, es, chain)
|
274
|
+
else
|
275
|
+
emit_single_tag(tag, es, chain)
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
def emit_aggregate_keys (tag, es, chain)
|
280
|
+
cs = {}
|
281
|
+
es.each do |time, record|
|
282
|
+
matched = false
|
283
|
+
pat = @aggregate_keys.map{ |key| record[key] }.join('_')
|
284
|
+
cs[pat] = [0] * @_formulas.length unless cs.has_key?(pat)
|
225
285
|
|
286
|
+
if @_formulas.length > 0
|
287
|
+
@_formulas.each do | index, outkey, inkeys, formula|
|
288
|
+
next unless formula and checkArgs(record, inkeys)
|
289
|
+
|
290
|
+
cs[pat][index] += execFunc('all', record, inkeys, formula)
|
291
|
+
matched = true
|
292
|
+
end
|
293
|
+
else
|
294
|
+
$log.warn index
|
295
|
+
end
|
296
|
+
cs[pat][0] += 1 unless matched
|
297
|
+
end
|
298
|
+
|
299
|
+
cs.keys.each do |pat|
|
300
|
+
countups(pat, cs[pat])
|
301
|
+
end
|
302
|
+
|
303
|
+
chain.next
|
304
|
+
end
|
305
|
+
|
306
|
+
def emit_single_tag (tag, es, chain)
|
307
|
+
c = [0] * @_formulas.length
|
308
|
+
|
226
309
|
es.each do |time,record|
|
227
310
|
matched = false
|
228
311
|
if @_formulas.length > 0
|
@@ -230,13 +313,14 @@ class Fluent::DataCalculatorOutput < Fluent::Output
|
|
230
313
|
next unless formula and checkArgs(record, inkeys)
|
231
314
|
|
232
315
|
c[index] += execFunc(nil, record, inkeys, formula)
|
233
|
-
|
316
|
+
matched = true
|
234
317
|
end
|
235
318
|
else
|
236
319
|
$log.warn index
|
237
320
|
end
|
238
321
|
c[0] += 1 unless matched
|
239
322
|
end
|
323
|
+
|
240
324
|
countups(tag, c)
|
241
325
|
|
242
326
|
chain.next
|
@@ -28,6 +28,12 @@ class DataCalculatorOutputTest < Test::Unit::TestCase
|
|
28
28
|
formulas sum = 10 ab
|
29
29
|
]
|
30
30
|
}
|
31
|
+
# aggregateに必要な要素がない
|
32
|
+
assert_raise(Fluent::ConfigError) {
|
33
|
+
d = create_driver %[
|
34
|
+
aggregate keys
|
35
|
+
]
|
36
|
+
}
|
31
37
|
# finalizerに必要な要素がない
|
32
38
|
assert_raise(Fluent::ConfigError) {
|
33
39
|
d = create_driver %[
|
@@ -51,7 +57,6 @@ class DataCalculatorOutputTest < Test::Unit::TestCase
|
|
51
57
|
assert_nil d.instance.input_tag_remove_prefix
|
52
58
|
assert_equal 'sum = amount * price, cnt = amount', d.instance.formulas
|
53
59
|
assert_equal 'ave = cnt > 0 ? sum / cnt : 0', d.instance.finalizer
|
54
|
-
assert_equal false, d.instance.outcast_unmatched
|
55
60
|
|
56
61
|
end
|
57
62
|
|
@@ -60,7 +65,7 @@ class DataCalculatorOutputTest < Test::Unit::TestCase
|
|
60
65
|
aggregate all
|
61
66
|
formulas sum = amount * price, cnt = amount
|
62
67
|
]
|
63
|
-
assert_equal [0,0
|
68
|
+
assert_equal [0,0], d.instance.counts['all']
|
64
69
|
end
|
65
70
|
|
66
71
|
def test_create_formula
|
@@ -69,25 +74,22 @@ class DataCalculatorOutputTest < Test::Unit::TestCase
|
|
69
74
|
formulas sum = amount * price, cnt = amount
|
70
75
|
]
|
71
76
|
assert_equal 0, d.instance._formulas[0][0]
|
72
|
-
assert_equal '
|
73
|
-
assert_equal
|
77
|
+
assert_equal 'sum', d.instance._formulas[0][1]
|
78
|
+
assert_equal ['amount', 'price'], d.instance._formulas[0][2]
|
74
79
|
assert_equal 1, d.instance._formulas[1][0]
|
75
|
-
assert_equal '
|
76
|
-
assert_equal ['amount'
|
77
|
-
assert_equal 2, d.instance._formulas[2][0]
|
78
|
-
assert_equal 'cnt', d.instance._formulas[2][1]
|
79
|
-
assert_equal ['amount'], d.instance._formulas[2][2]
|
80
|
+
assert_equal 'cnt', d.instance._formulas[1][1]
|
81
|
+
assert_equal ['amount'], d.instance._formulas[1][2]
|
80
82
|
end
|
81
83
|
|
82
84
|
def test_countups
|
83
85
|
d = create_driver
|
84
86
|
assert_nil d.instance.counts['test.input']
|
85
|
-
d.instance.countups('test.input', [0, 0, 0
|
86
|
-
assert_equal [0,0,0
|
87
|
-
d.instance.countups('test.input', [1, 1,
|
88
|
-
assert_equal [1,1,
|
89
|
-
d.instance.countups('test.input', [
|
90
|
-
assert_equal [
|
87
|
+
d.instance.countups('test.input', [0, 0, 0])
|
88
|
+
assert_equal [0,0,0], d.instance.counts['test.input']
|
89
|
+
d.instance.countups('test.input', [1, 1, 0])
|
90
|
+
assert_equal [1,1,0], d.instance.counts['test.input']
|
91
|
+
d.instance.countups('test.input', [5, 1, 0])
|
92
|
+
assert_equal [6,2,0], d.instance.counts['test.input']
|
91
93
|
end
|
92
94
|
|
93
95
|
def test_stripped_tag
|
@@ -97,23 +99,29 @@ class DataCalculatorOutputTest < Test::Unit::TestCase
|
|
97
99
|
assert_equal 'input', d.instance.stripped_tag('input')
|
98
100
|
end
|
99
101
|
|
102
|
+
def test_aggregate_keys
|
103
|
+
d = create_driver %[
|
104
|
+
aggregate keys area_id, mission_id
|
105
|
+
formulas sum = amount * price, cnt = amount
|
106
|
+
]
|
107
|
+
assert_equal 'keys', d.instance.aggregate
|
108
|
+
assert_equal ['area_id', 'mission_id'], d.instance.aggregate_keys
|
109
|
+
end
|
110
|
+
|
100
111
|
def test_generate_output
|
101
112
|
d = create_driver
|
102
|
-
r1 = d.instance.generate_output({'test.input' => [
|
113
|
+
r1 = d.instance.generate_output({'test.input' => [240,120,180], 'test.input2' => [600,0,0]}, 60)[0]
|
103
114
|
|
104
|
-
assert_equal 60, r1['input_unmatched']
|
105
115
|
assert_equal 240, r1['input_sum']
|
106
116
|
assert_equal 120, r1['input_amounts']
|
107
117
|
assert_equal 180, r1['input_record']
|
108
118
|
assert_equal 2, r1['input_ave']
|
109
119
|
|
110
|
-
assert_equal 0, r1['input2_unmatched']
|
111
120
|
assert_equal 600, r1['input2_sum']
|
112
121
|
assert_equal 0, r1['input2_amounts']
|
113
122
|
assert_equal 0, r1['input2_record']
|
114
123
|
assert_equal 0, r1['input2_ave']
|
115
124
|
|
116
|
-
|
117
125
|
d = create_driver %[
|
118
126
|
aggregate all
|
119
127
|
input_tag_remove_prefix test
|
@@ -121,12 +129,12 @@ class DataCalculatorOutputTest < Test::Unit::TestCase
|
|
121
129
|
finalizer ave = amounts > 0 ? sum / amounts : 0
|
122
130
|
]
|
123
131
|
|
124
|
-
r2 = d.instance.generate_output({'all' => [
|
125
|
-
assert_equal 60, r2['unmatched']
|
132
|
+
r2 = d.instance.generate_output({'all' => [240,120,180]}, 60)[0]
|
126
133
|
assert_equal 240, r2['sum']
|
127
134
|
assert_equal 120, r2['amounts']
|
128
135
|
assert_equal 180, r2['record']
|
129
136
|
assert_equal 2, r2['ave']
|
137
|
+
|
130
138
|
end
|
131
139
|
|
132
140
|
def test_emit
|
@@ -139,8 +147,7 @@ class DataCalculatorOutputTest < Test::Unit::TestCase
|
|
139
147
|
d1.emit({'amount' => 10, 'price' => 100})
|
140
148
|
end
|
141
149
|
end
|
142
|
-
r1 = d1.instance.flush(60)
|
143
|
-
assert_equal 0, r1['input_unmatched']
|
150
|
+
r1 = d1.instance.flush(60)[0]
|
144
151
|
assert_equal 132000, r1['input_sum']
|
145
152
|
assert_equal 1320, r1['input_amounts']
|
146
153
|
assert_equal 240, r1['input_record']
|
@@ -162,11 +169,42 @@ class DataCalculatorOutputTest < Test::Unit::TestCase
|
|
162
169
|
d2.emit({'amount' => 10, 'price' => 100})
|
163
170
|
end
|
164
171
|
end
|
165
|
-
r2 = d2.instance.flush(60)
|
166
|
-
assert_equal 0, r2['unmatched']
|
172
|
+
r2 = d2.instance.flush(60)[0]
|
167
173
|
assert_equal 132000, r2['sum']
|
168
174
|
assert_equal 1320, r2['amounts']
|
169
175
|
assert_equal 240, r2['record']
|
170
176
|
assert_equal 100.0, r2['ave']
|
177
|
+
|
178
|
+
d3 = create_driver(%[
|
179
|
+
unit minute
|
180
|
+
aggregate keys area_id, mission_id
|
181
|
+
formulas sum = amount * price, count = 1
|
182
|
+
<unmatched>
|
183
|
+
type stdout
|
184
|
+
</unmatched>
|
185
|
+
], 'test.input3')
|
186
|
+
|
187
|
+
sums = {}
|
188
|
+
counts = {}
|
189
|
+
d3.run do
|
190
|
+
240.times do
|
191
|
+
area_id = rand(5)
|
192
|
+
mission_id = rand(5)
|
193
|
+
amount = rand(10)
|
194
|
+
price = rand(5) * 100
|
195
|
+
pat = [area_id, mission_id].join(',')
|
196
|
+
d3.emit({'amount' => amount, 'price' => price, 'area_id' => area_id, 'mission_id' => mission_id})
|
197
|
+
sums[pat] = 0 unless sums.has_key?(pat)
|
198
|
+
counts[pat] = 0 unless counts.has_key?(pat)
|
199
|
+
sums[pat] += amount * price
|
200
|
+
counts[pat] += 1
|
201
|
+
end
|
202
|
+
end
|
203
|
+
r3 = d3.instance.flush(60)
|
204
|
+
r3.each do |r|
|
205
|
+
pat = [r['area_id'], r['mission_id']].join(',')
|
206
|
+
assert_equal sums[pat], r['sum']
|
207
|
+
assert_equal counts[pat], r['count']
|
208
|
+
end
|
171
209
|
end
|
172
210
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fluent-plugin-datacalculator
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-05-
|
12
|
+
date: 2012-05-22 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: fluentd
|
@@ -56,6 +56,7 @@ files:
|
|
56
56
|
- README.rdoc
|
57
57
|
- Rakefile
|
58
58
|
- example.conf
|
59
|
+
- example.json
|
59
60
|
- fluent-plugin-datacalculator.gemspec
|
60
61
|
- lib/fluent/plugin/out_datacalculator.rb
|
61
62
|
- test/helper.rb
|