fluent-plugin-numeric-counter 0.1.0 → 0.1.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/README.md CHANGED
@@ -61,6 +61,58 @@ Size specifier (like 10k, 5M, 103g) available as 1024\*\*1, 1024\*\*2, 1024\*\*3
61
61
 
62
62
  You can try to use negative numbers, and floating point numbers.... (not tested enough).
63
63
 
64
+ With 'output\_per\_tag' option and 'tag\_prefix', we get one result message for one tag:
65
+
66
+ <match accesslog.{foo,bar}>
67
+ type numeric_counter
68
+ unit hour
69
+ aggregate tag
70
+ count_key bytes
71
+ output_per_tag yes
72
+ tag_prefix byteslog
73
+ input_tag_remove_prefix accesslog
74
+
75
+ pattern1 SMALL 0 1k
76
+ pattern2 MIDDLE 1k 1m
77
+ pattern3 LARGE 1m 10m
78
+ pattern4 HUGE 10m 1g
79
+ pattern5 XXXX 1g
80
+ </match>
81
+ # => tag: 'byteslog.foo' and 'byteslog.bar'
82
+ # message: {'SMALL_count' => 100, ... }
83
+
84
+ And you can get tested messages count with 'output\_messages' option:
85
+
86
+ <match accesslog.{foo,bar}>
87
+ type numeric_counter
88
+ unit hour
89
+ aggregate tag
90
+ count_key bytes
91
+ input_tag_remove_prefix accesslog
92
+ output_messages yes
93
+
94
+ pattern1 SMALL 0 1k
95
+ pattern2 LARGE 1k
96
+ </match>
97
+ # => tag: 'numcount'
98
+ # message: {'foo_messages' => xxx, 'bar_messages' => yyy, 'foo_SMALL_count' => 100, ... }
99
+
100
+ <match accesslog.{foo,bar}>
101
+ type numeric_counter
102
+ unit hour
103
+ aggregate tag
104
+ count_key bytes
105
+ output_per_tag yes
106
+ tag_prefix num
107
+ input_tag_remove_prefix accesslog
108
+ output_messages yes
109
+
110
+ pattern1 SMALL 0 1k
111
+ pattern2 LARGE 1k
112
+ </match>
113
+ # => tag: 'num.foo' or 'num.bar'
114
+ # message: {'messages' => xxx, 'SMALL_count' => 100, ... }
115
+
64
116
  ## TODO
65
117
 
66
118
  * more tests
@@ -2,7 +2,7 @@
2
2
 
3
3
  Gem::Specification.new do |gem|
4
4
  gem.name = "fluent-plugin-numeric-counter"
5
- gem.version = "0.1.0"
5
+ gem.version = "0.1.1"
6
6
  gem.authors = ["TAGOMORI Satoshi"]
7
7
  gem.email = ["tagomoris@gmail.com"]
8
8
  gem.description = %q{Counts messages, with specified key and numeric value in specified range}
@@ -14,6 +14,7 @@ Gem::Specification.new do |gem|
14
14
  gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
15
15
  gem.require_paths = ["lib"]
16
16
 
17
+ gem.add_development_dependency "rake"
17
18
  gem.add_development_dependency "fluentd"
18
19
  gem.add_runtime_dependency "fluentd"
19
20
  end
@@ -5,11 +5,14 @@ class Fluent::NumericCounterOutput < Fluent::Output
5
5
 
6
6
  config_param :count_interval, :time, :default => 60
7
7
  config_param :unit, :string, :default => nil
8
+ config_param :output_per_tag, :bool, :default => false
8
9
  config_param :aggregate, :string, :default => 'tag'
9
10
  config_param :tag, :string, :default => 'numcount'
11
+ config_param :tag_prefix, :string, :default => nil
10
12
  config_param :input_tag_remove_prefix, :string, :default => nil
11
13
  config_param :count_key, :string
12
14
  config_param :outcast_unmatched, :bool, :default => false
15
+ config_param :output_messages, :bool, :default => false
13
16
 
14
17
  # pattern0 reserved as unmatched counts
15
18
  config_param :pattern1, :string # string: NAME LOW HIGH
@@ -20,8 +23,6 @@ class Fluent::NumericCounterOutput < Fluent::Output
20
23
 
21
24
  attr_accessor :counts, :last_checked
22
25
 
23
- # for test
24
- # attr_accessor :count_interval, :unit, :aggregate, :tag, :input_tag_remove_prefix, :count_key, :outcast_unmatched
25
26
  attr_accessor :patterns
26
27
 
27
28
  def parse_num(str)
@@ -77,6 +78,11 @@ class Fluent::NumericCounterOutput < Fluent::Output
77
78
  raise Fluent::ConfigError, "unspecified high threshold allowed only in last pattern" if high.nil? and index != @patterns.length - 1
78
79
  end
79
80
 
81
+ if @output_per_tag
82
+ raise Fluent::ConfigError, "tag_prefix must be specified with output_per_tag" unless @tag_prefix
83
+ @tag_prefix_string = @tag_prefix + '.'
84
+ end
85
+
80
86
  if @input_tag_remove_prefix
81
87
  @removed_prefix_string = @input_tag_remove_prefix + '.'
82
88
  @removed_length = @removed_prefix_string.length
@@ -131,53 +137,72 @@ class Fluent::NumericCounterOutput < Fluent::Output
131
137
  tag
132
138
  end
133
139
 
140
+ def generate_fields(step, target_counts, attr_prefix, output)
141
+ sum = if @outcast_unmatched
142
+ target_counts[1..-1].inject(:+)
143
+ else
144
+ target_counts.inject(:+)
145
+ end
146
+ messages = sum + (@outcast_unmatched ? target_counts[0] : 0)
147
+
148
+ target_counts.each_with_index do |count,i|
149
+ name = @patterns[i][1]
150
+ output[attr_prefix + name + '_count'] = count
151
+ output[attr_prefix + name + '_rate'] = ((count * 100.0) / (1.00 * step)).floor / 100.0
152
+ unless i == 0 and @outcast_unmatched
153
+ output[attr_prefix + name + '_percentage'] = count * 100.0 / (1.00 * sum) if sum > 0
154
+ end
155
+ if @output_messages
156
+ output[attr_prefix + 'messages'] = messages
157
+ end
158
+ end
159
+
160
+ output
161
+ end
162
+
134
163
  def generate_output(counts, step)
164
+ if @aggregate == :all
165
+ return generate_fields(step, counts['all'], '', {})
166
+ end
167
+
135
168
  output = {}
169
+ counts.keys.each do |tag|
170
+ generate_fields(step, counts[tag], stripped_tag(tag) + '_', output)
171
+ end
172
+ output
173
+ end
136
174
 
175
+ def generate_output_per_tags(counts, step)
137
176
  if @aggregate == :all
138
- # index 0 is unmatched
139
- sum = if @outcast_unmatched
140
- counts['all'][1..-1].inject(:+)
141
- else
142
- counts['all'].inject(:+)
143
- end
144
- counts['all'].each_with_index do |count,i|
145
- name = @patterns[i][1]
146
- output[name + '_count'] = count
147
- output[name + '_rate'] = ((count * 100.0) / (1.00 * step)).floor / 100.0
148
- unless i == 0 and @outcast_unmatched
149
- output[name + '_percentage'] = count * 100.0 / (1.00 * sum) if sum > 0
150
- end
151
- end
152
- return output
177
+ return {'all' => generate_fields(step, counts['all'], '', {})}
153
178
  end
154
179
 
180
+ output_pairs = {}
155
181
  counts.keys.each do |tag|
156
- t = stripped_tag(tag)
157
- sum = if @outcast_unmatched
158
- counts[tag][1..-1].inject(:+)
159
- else
160
- counts[tag].inject(:+)
161
- end
162
- counts[tag].each_with_index do |count,i|
163
- name = @patterns[i][1]
164
- output[t + '_' + name + '_count'] = count
165
- output[t + '_' + name + '_rate'] = ((count * 100.0) / (1.00 * step)).floor / 100.0
166
- unless i == 0 and @outcast_unmatched
167
- output[t + '_' + name + '_percentage'] = count * 100.0 / (1.00 * sum) if sum > 0
168
- end
169
- end
182
+ output_pairs[stripped_tag(tag)] = generate_fields(step, counts[tag], '', {})
170
183
  end
171
- output
184
+ output_pairs
172
185
  end
173
186
 
174
- def flush(step)
187
+ def flush(step) # returns one message
175
188
  flushed,@counts = @counts,count_initialized(@counts.keys.dup)
176
189
  generate_output(flushed, step)
177
190
  end
178
191
 
192
+ def flush_per_tags(step) # returns map of tag - message
193
+ flushed,@counts = @counts,count_initialized(@counts.keys.dup)
194
+ generate_output_per_tags(flushed, step)
195
+ end
196
+
179
197
  def flush_emit(step)
180
- Fluent::Engine.emit(@tag, Fluent::Engine.now, flush(step))
198
+ if @output_per_tag
199
+ time = Fluent::Engine.now
200
+ flush_per_tags(step).each do |tag,message|
201
+ Fluent::Engine.emit(@tag_prefix_string + tag, time, message)
202
+ end
203
+ else
204
+ Fluent::Engine.emit(@tag, Fluent::Engine.now, flush(step))
205
+ end
181
206
  end
182
207
 
183
208
  def start_watch
@@ -15,6 +15,19 @@ class NumericCounterOutputTest < Test::Unit::TestCase
15
15
  pattern3 u3s 1000000 3000000
16
16
  ]
17
17
 
18
+ CONFIG_OUTPUT_PER_TAG = %[
19
+ count_interval 60
20
+ aggregate tag
21
+ output_per_tag true
22
+ tag_prefix n
23
+ input_tag_remove_prefix test
24
+ count_key target
25
+ pattern1 u100ms 0 100000
26
+ pattern2 u1s 100000 1000000
27
+ pattern3 u3s 1000000 3000000
28
+ output_messages true
29
+ ]
30
+
18
31
  def create_driver(conf=CONFIG, tag='test')
19
32
  Fluent::Test::OutputTestDriver.new(Fluent::NumericCounterOutput, tag).configure(conf)
20
33
  end
@@ -42,14 +55,63 @@ class NumericCounterOutputTest < Test::Unit::TestCase
42
55
  assert_nil d.instance.input_tag_remove_prefix
43
56
  assert_equal false, d.instance.outcast_unmatched
44
57
  assert_equal [[0, 'unmatched', nil, nil], [1, 'smallnum', 0.1, 200], [2, 'subnum', -500, -1]], d.instance.patterns
58
+ assert_equal false, d.instance.output_per_tag
59
+ assert_equal false, d.instance.output_messages
45
60
 
46
61
  d = create_driver %[
47
62
  count_key key1
48
63
  pattern1 x 0.1 10
49
64
  pattern2 y 10 11
50
65
  pattern3 z 11
66
+ output_messages yes
51
67
  ]
52
68
  assert_equal [[0, 'unmatched', nil, nil], [1, 'x', 0.1, 10], [2, 'y', 10, 11], [3, 'z', 11, nil]], d.instance.patterns
69
+ assert_equal true, d.instance.output_messages
70
+ end
71
+
72
+ def test_configure_output_per_tag
73
+ d = create_driver %[
74
+ count_key field1
75
+ pattern1 smallnum 0.1 200
76
+ pattern2 subnum -500 -1
77
+ output_per_tag yes
78
+ tag_prefix numcount
79
+ ]
80
+
81
+ assert_equal 60, d.instance.count_interval
82
+ assert_equal :tag, d.instance.aggregate
83
+ assert_equal 'numcount', d.instance.tag
84
+ assert_nil d.instance.input_tag_remove_prefix
85
+ assert_equal false, d.instance.outcast_unmatched
86
+ assert_equal [[0, 'unmatched', nil, nil], [1, 'smallnum', 0.1, 200], [2, 'subnum', -500, -1]], d.instance.patterns
87
+ assert_equal true, d.instance.output_per_tag
88
+ assert_equal 'numcount', d.instance.tag_prefix
89
+ assert_equal false, d.instance.output_messages
90
+
91
+ d = create_driver %[
92
+ count_key key1
93
+ pattern1 x 0.1 10
94
+ pattern2 y 10 11
95
+ pattern3 z 11
96
+ output_per_tag yes
97
+ tag_prefix n
98
+ output_messages yes
99
+ ]
100
+ assert_equal [[0, 'unmatched', nil, nil], [1, 'x', 0.1, 10], [2, 'y', 10, 11], [3, 'z', 11, nil]], d.instance.patterns
101
+ assert_equal true, d.instance.output_per_tag
102
+ assert_equal 'n', d.instance.tag_prefix
103
+ assert_equal true, d.instance.output_messages
104
+
105
+ x_config = %[
106
+ count_key key1
107
+ pattern1 x 0.1 10
108
+ pattern2 y 10 11
109
+ pattern3 z 11
110
+ output_per_tag yes
111
+ ]
112
+ assert_raise(Fluent::ConfigError) {
113
+ d = create_driver(x_config)
114
+ }
53
115
  end
54
116
 
55
117
  def test_countups
@@ -83,6 +145,7 @@ class NumericCounterOutputTest < Test::Unit::TestCase
83
145
  assert_equal 120, r1['input_u3s_count']
84
146
  assert_equal 2.0, r1['input_u3s_rate']
85
147
  assert_equal 20.0, r1['input_u3s_percentage']
148
+ assert_nil r1['input_messages']
86
149
 
87
150
  assert_equal 0, r1['input2_unmatched_count']
88
151
  assert_equal 0.0, r1['input2_unmatched_rate']
@@ -96,12 +159,20 @@ class NumericCounterOutputTest < Test::Unit::TestCase
96
159
  assert_equal 0, r1['input2_u3s_count']
97
160
  assert_equal 0.0, r1['input2_u3s_rate']
98
161
  assert_equal 0.0, r1['input2_u3s_percentage']
162
+ assert_nil r1['input2_messages']
163
+
164
+ d = create_driver(CONFIG + "\n output_messages yes \n")
165
+
166
+ r1 = d.instance.generate_output({'test.input' => [60,240,180,120], 'test.input2' => [0,600,0,0]}, 60)
167
+ assert_equal 600, r1['input_messages']
168
+ assert_equal 600, r1['input2_messages']
99
169
 
100
170
  d = create_driver %[
101
171
  aggregate all
102
172
  count_key f1
103
173
  pattern1 good 1 2
104
174
  outcast_unmatched yes
175
+ output_messages true
105
176
  ]
106
177
  r2 = d.instance.generate_output({'all' => [60,240]}, 60)
107
178
  assert_equal 60, r2['unmatched_count']
@@ -110,6 +181,69 @@ class NumericCounterOutputTest < Test::Unit::TestCase
110
181
  assert_equal 240, r2['good_count']
111
182
  assert_equal 4.0, r2['good_rate']
112
183
  assert_equal 100.0, r2['good_percentage']
184
+ assert_equal 300, r2['messages']
185
+ end
186
+
187
+ def test_generate_output_per_tag
188
+ d = create_driver(CONFIG_OUTPUT_PER_TAG + "\n output_messages false \n")
189
+ # pattern1 u100ms 0 100000
190
+ # pattern2 u1s 100000 1000000
191
+ # pattern3 u3s 1000000 3000000
192
+
193
+ r1 = d.instance.generate_output_per_tags({'test.input' => [60,240,180,120], 'test.input2' => [0,600,0,0]}, 60)
194
+ assert_equal 2, r1.keys.size
195
+
196
+ r = r1['input']
197
+ assert_equal 60, r['unmatched_count']
198
+ assert_equal 1.0, r['unmatched_rate']
199
+ assert_equal 10.0, r['unmatched_percentage']
200
+ assert_equal 240, r['u100ms_count']
201
+ assert_equal 4.0, r['u100ms_rate']
202
+ assert_equal 40.0, r['u100ms_percentage']
203
+ assert_equal 180, r['u1s_count']
204
+ assert_equal 3.0, r['u1s_rate']
205
+ assert_equal 30.0, r['u1s_percentage']
206
+ assert_equal 120, r['u3s_count']
207
+ assert_equal 2.0, r['u3s_rate']
208
+ assert_equal 20.0, r['u3s_percentage']
209
+ assert_nil r['messages']
210
+
211
+ r = r1['input2']
212
+ assert_equal 0, r['unmatched_count']
213
+ assert_equal 0.0, r['unmatched_rate']
214
+ assert_equal 0.0, r['unmatched_percentage']
215
+ assert_equal 600, r['u100ms_count']
216
+ assert_equal 10.0, r['u100ms_rate']
217
+ assert_equal 100.0, r['u100ms_percentage']
218
+ assert_equal 0, r['u1s_count']
219
+ assert_equal 0.0, r['u1s_rate']
220
+ assert_equal 0.0, r['u1s_percentage']
221
+ assert_equal 0, r['u3s_count']
222
+ assert_equal 0.0, r['u3s_rate']
223
+ assert_equal 0.0, r['u3s_percentage']
224
+ assert_nil r['messages']
225
+
226
+ d = create_driver(CONFIG_OUTPUT_PER_TAG)
227
+
228
+ r1 = d.instance.generate_output_per_tags({'test.input' => [60,240,180,120], 'test.input2' => [0,600,0,0]}, 60)
229
+ assert_equal 600, r1['input']['messages']
230
+ assert_equal 600, r1['input2']['messages']
231
+
232
+ d = create_driver %[
233
+ aggregate all
234
+ count_key f1
235
+ pattern1 good 1 2
236
+ outcast_unmatched yes
237
+ output_messages true
238
+ ]
239
+ r2 = d.instance.generate_output_per_tags({'all' => [60,240]}, 60)
240
+ assert_equal 60, r2['all']['unmatched_count']
241
+ assert_equal 1.0, r2['all']['unmatched_rate']
242
+ assert_nil r2['all']['unmatched_percentage']
243
+ assert_equal 240, r2['all']['good_count']
244
+ assert_equal 4.0, r2['all']['good_rate']
245
+ assert_equal 100.0, r2['all']['good_percentage']
246
+ assert_equal 300, r2['all']['messages']
113
247
  end
114
248
 
115
249
  def test_pattern_num
@@ -186,5 +320,93 @@ class NumericCounterOutputTest < Test::Unit::TestCase
186
320
  assert_equal 60, r['tag1_unmatched_count']
187
321
  assert_equal 1.0, r['tag1_unmatched_rate']
188
322
  assert_equal 20, r['tag1_unmatched_percentage']
323
+
324
+ d = create_driver(CONFIG, 'test.tag1')
325
+ d.run do
326
+ 60.times do
327
+ d.emit({'target' => '50000'})
328
+ d.emit({'target' => '100000'})
329
+ d.emit({'target' => '100001'})
330
+ d.emit({'target' => '0.0'})
331
+ d.emit({'target' => '-1'})
332
+ end
333
+ end
334
+ d.instance.flush_emit(60)
335
+ emits = d.emits
336
+ assert_equal 1, emits.length
337
+ data = emits[0]
338
+ assert_equal 'numcount', data[0] # tag
339
+ r = data[2] # message
340
+ assert_equal 120, r['tag1_u100ms_count']
341
+ assert_equal 2.0, r['tag1_u100ms_rate']
342
+ assert_equal 40.0, r['tag1_u100ms_percentage']
343
+ assert_equal 120, r['tag1_u1s_count']
344
+ assert_equal 2.0, r['tag1_u1s_rate']
345
+ assert_equal 40, r['tag1_u1s_percentage']
346
+ assert_equal 0, r['tag1_u3s_count']
347
+ assert_equal 0, r['tag1_u3s_rate']
348
+ assert_equal 0, r['tag1_u3s_percentage']
349
+ assert_equal 60, r['tag1_unmatched_count']
350
+ assert_equal 1.0, r['tag1_unmatched_rate']
351
+ assert_equal 20, r['tag1_unmatched_percentage']
352
+ end
353
+
354
+ def test_emit_output_per_tag
355
+ d = create_driver(CONFIG_OUTPUT_PER_TAG, 'test.tag1')
356
+ d.run do
357
+ 60.times do
358
+ d.emit({'target' => '50000'})
359
+ d.emit({'target' => '100000'})
360
+ d.emit({'target' => '100001'})
361
+ d.emit({'target' => '0.0'})
362
+ d.emit({'target' => '-1'})
363
+ end
364
+ end
365
+ r = d.instance.flush_per_tags(60)
366
+ assert_equal 1, r.keys.size
367
+ r1 = r['tag1']
368
+ assert_equal 120, r1['u100ms_count']
369
+ assert_equal 2.0, r1['u100ms_rate']
370
+ assert_equal 40.0, r1['u100ms_percentage']
371
+ assert_equal 120, r1['u1s_count']
372
+ assert_equal 2.0, r1['u1s_rate']
373
+ assert_equal 40, r1['u1s_percentage']
374
+ assert_equal 0, r1['u3s_count']
375
+ assert_equal 0, r1['u3s_rate']
376
+ assert_equal 0, r1['u3s_percentage']
377
+ assert_equal 60, r1['unmatched_count']
378
+ assert_equal 1.0, r1['unmatched_rate']
379
+ assert_equal 20, r1['unmatched_percentage']
380
+ assert_equal 300, r1['messages']
381
+
382
+ d = create_driver(CONFIG_OUTPUT_PER_TAG, 'test.tag1')
383
+ d.run do
384
+ 60.times do
385
+ d.emit({'target' => '50000'})
386
+ d.emit({'target' => '100000'})
387
+ d.emit({'target' => '100001'})
388
+ d.emit({'target' => '0.0'})
389
+ d.emit({'target' => '-1'})
390
+ end
391
+ end
392
+ d.instance.flush_emit(60)
393
+ emits = d.emits
394
+ assert_equal 1, emits.length
395
+ data = emits[0]
396
+ assert_equal 'n.tag1', data[0] # tag
397
+ r = data[2] # message
398
+ assert_equal 120, r['u100ms_count']
399
+ assert_equal 2.0, r['u100ms_rate']
400
+ assert_equal 40.0, r['u100ms_percentage']
401
+ assert_equal 120, r['u1s_count']
402
+ assert_equal 2.0, r['u1s_rate']
403
+ assert_equal 40, r['u1s_percentage']
404
+ assert_equal 0, r['u3s_count']
405
+ assert_equal 0, r['u3s_rate']
406
+ assert_equal 0, r['u3s_percentage']
407
+ assert_equal 60, r['unmatched_count']
408
+ assert_equal 1.0, r['unmatched_rate']
409
+ assert_equal 20, r['unmatched_percentage']
410
+ assert_equal 300, r['messages']
189
411
  end
190
412
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fluent-plugin-numeric-counter
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,8 +9,24 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-07-06 00:00:00.000000000 Z
12
+ date: 2012-08-08 00:00:00.000000000 Z
13
13
  dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rake
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'
14
30
  - !ruby/object:Gem::Dependency
15
31
  name: fluentd
16
32
  requirement: !ruby/object:Gem::Requirement