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