fluent-plugin-datacalculator 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,15 +1,29 @@
1
1
  <source>
2
2
  type forward
3
3
  </source>
4
- <match payment.**>
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
- count_interval 5s
20
+ tag result.shop
21
+ count_interval 5s
8
22
  aggregate all
9
23
  formulas sum = amount * price, cnt = 1, total = amount
10
- finalizer ave = cnt > 0 ? 1.00 * sum / cnt : 0
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>
@@ -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.1"
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
- raise Fluent::ConfigError, "flowcounter aggregate allows tag/all"
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
- rights = right.scan(/[a-zA-Z][\w\d_\.\$]*/).uniq
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 = [[0, 'unmatched', nil, nil]]
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 + 1, str) )
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
- # index 0 is unmatched
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,@counts = @counts,count_initialized(@counts.keys.dup)
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
- Fluent::Engine.emit(@tag, Fluent::Engine.now, flush(step))
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
- c = [0] * @_formulas.length
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
- matched = true
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,0], d.instance.counts['all']
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 'unmatched', d.instance._formulas[0][1]
73
- assert_equal nil, d.instance._formulas[0][2]
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 'sum', d.instance._formulas[1][1]
76
- assert_equal ['amount', 'price'], d.instance._formulas[1][2]
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, 0])
86
- assert_equal [0,0,0,0], d.instance.counts['test.input']
87
- d.instance.countups('test.input', [1, 1, 1, 0])
88
- assert_equal [1,1,1,0], d.instance.counts['test.input']
89
- d.instance.countups('test.input', [0, 5, 1, 0])
90
- assert_equal [1,6,2,0], d.instance.counts['test.input']
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' => [60,240,120,180], 'test.input2' => [0,600,0,0]}, 60)
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' => [60,240,120,180]}, 60)
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.1
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-17 00:00:00.000000000 Z
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