fluent-plugin-numeric-counter 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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