fluent-plugin-flowcounter 1.0.0 → 1.1.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 873629b9804e92822b8a2b854a5df3da2798bf45
4
- data.tar.gz: f3d79ae17a63763f9dc0ca8073982022d8b0759e
3
+ metadata.gz: 2d5e0837aff2b5b9122daa256b9eac720886e653
4
+ data.tar.gz: 5af91ae11eb092d76b87dc3669af751b6141cca7
5
5
  SHA512:
6
- metadata.gz: 9b92f7be19ad17f125244c10efab034df307f0581f6db4ba82d06551eee513e3002453080221494b8ec5e746601ee3a55a5540f87e93ded866e9222df1391d33
7
- data.tar.gz: dd56f20d75dfb69f309170fdb59a520a69da0fca58e5188c46cc98106c8448e1c20d7c00e79ea4fbf98aeccda6835d98ca3caddfe322cb440ea26f8a83958b9f
6
+ metadata.gz: 95de380ed6362234b5053bd341df61e24040c174f638cc486267a1a98a2a0d286da701f552d37223655726b45859a6499da95f9ec39a7a5d99e7ee8130a54a12
7
+ data.tar.gz: a4180307283337fb425e9aee62cb4b803c420aef397d1da4022e5d13fbb4d7f8fadb0631100bddb879bb78a184594eac6c8f394d80615a7622cbbe97e8b26b67
data/README.md CHANGED
@@ -96,6 +96,29 @@ Counts active tag, stop count records if the tag message stoped(when aggragates
96
96
  delete_idle true
97
97
  </match>
98
98
 
99
+ ### Generate Output at Zero Time
100
+
101
+ If you want to generate count results at every 0 second (for unit:minute), at every 00:00 (for unit:hour) or at every 00:00:00 (for unit:day), specify `timestamp_counting true` in your configuration.
102
+
103
+ <match target.**>
104
+ @type flowcounter
105
+ count_keys *
106
+ aggregate all
107
+ unit hour
108
+ timestamp_counting true
109
+ </match>
110
+
111
+ The configuration above emits output at 00:00:00, 01:00:00, 02:00:00, .... every day. In this use case, `unit: day` requires to configure `timestamp_timezone` to set the timezone to determine the beginning of the day.
112
+
113
+ <match target.**>
114
+ @type flowcounter
115
+ count_keys *
116
+ aggregate all
117
+ unit day
118
+ timestamp_counting true
119
+ timestamp_timezone -03:00
120
+ </match>
121
+
99
122
  ### Embedding Hostname
100
123
 
101
124
  The current version of this plugin doesn't support `${hostname}` placeholders. Use ruby code embedding for such purpose:
@@ -1,7 +1,7 @@
1
1
  # -*- encoding: utf-8 -*-
2
2
  Gem::Specification.new do |gem|
3
3
  gem.name = "fluent-plugin-flowcounter"
4
- gem.version = "1.0.0"
4
+ gem.version = "1.1.0"
5
5
  gem.authors = ["TAGOMORI Satoshi"]
6
6
  gem.email = ["tagomoris@gmail.com"]
7
7
  gem.summary = %q{Fluent plugin to count message flow}
@@ -16,5 +16,6 @@ Gem::Specification.new do |gem|
16
16
 
17
17
  gem.add_development_dependency "rake"
18
18
  gem.add_development_dependency "test-unit"
19
+ gem.add_development_dependency "timecop"
19
20
  gem.add_runtime_dependency "fluentd", "~> 0.14.0"
20
21
  end
@@ -1,4 +1,5 @@
1
1
  require 'fluent/plugin/output'
2
+ require 'time'
2
3
 
3
4
  class Fluent::Plugin::FlowCounterOutput < Fluent::Plugin::Output
4
5
  Fluent::Plugin.register_output('flowcounter', self)
@@ -6,11 +7,13 @@ class Fluent::Plugin::FlowCounterOutput < Fluent::Plugin::Output
6
7
  helpers :event_emitter, :timer
7
8
 
8
9
  config_param :unit, :enum, list: [:second, :minute, :hour, :day], default: :minute
10
+ config_param :timestamp_counting, :bool, default: false
11
+ config_param :timestamp_timezone, :string, default: nil
9
12
  config_param :aggregate, :enum, list: [:tag, :all], default: :tag
10
13
  config_param :output_style, :enum, list: [:joined, :tagged], default: :joined
11
14
  config_param :tag, :string, default: 'flowcount'
12
15
  config_param :input_tag_remove_prefix, :string, default: nil
13
- config_param :count_keys, :string, default: nil
16
+ config_param :count_keys, :array, value_type: :string, default: []
14
17
  config_param :delimiter, :string, default: '_'
15
18
  config_param :delete_idle, :bool, default: false
16
19
 
@@ -38,21 +41,51 @@ class Fluent::Plugin::FlowCounterOutput < Fluent::Plugin::Output
38
41
  @removed_length = @removed_prefix_string.length
39
42
  end
40
43
  @count_all = false
41
- if @count_keys
42
- @count_keys = @count_keys.split(',').map(&:strip)
44
+ if @count_keys && !@count_keys.empty?
43
45
  @count_all = (@count_keys == ['*'])
44
46
  @count_bytes = true
45
47
  else
46
48
  @count_bytes = false
47
49
  end
48
50
 
51
+ if @timestamp_counting
52
+ @timestamp_timezone_offset = 0
53
+ if @unit == :second
54
+ raise Fluent::ConfigError, "timestamp_counting cannot be enabled with unit: second"
55
+ elsif @unit == :day
56
+ unless @timestamp_timezone
57
+ raise Fluent::ConfigError, "timestamp_counting requires timestamp_timezone to be configured (e.g., '-0700') if unit is day"
58
+ end
59
+ @timestamp_timezone_offset = Time.zone_offset(@timestamp_timezone)
60
+ unless @timestamp_timezone_offset
61
+ raise Fluent::ConfigError, "invalid timestamp_timezone value (specify like '-0700')"
62
+ end
63
+ end
64
+ @last_checked = nil
65
+ @initializer = ->{ now = Fluent::EventTime.now.to_i; @last_checked = now - (now % @tick) - @timestamp_timezone_offset }
66
+ @checker = ->{ Fluent::EventTime.now.to_i - @last_checked >= @tick }
67
+ @updater = ->{ @last_checked += @tick; return Fluent::EventTime.new(@last_checked, 0), @tick }
68
+ else
69
+ @last_checked = nil
70
+ @initializer = ->{ @last_checked = Fluent::Clock.now }
71
+ @checker = ->{ Fluent::Clock.now - @last_checked >= @tick }
72
+ @updater = ->{ prev = @last_checked; @last_checked = Fluent::Clock.now; return Fluent::EventTime.now, @last_checked - prev }
73
+ end
74
+
49
75
  @counts = count_initialized
50
76
  @mutex = Mutex.new
51
77
  end
52
78
 
53
79
  def start
54
80
  super
55
- start_watch
81
+
82
+ @initializer.call
83
+ timer_execute(:out_flowcounter_watcher, 0.5) do
84
+ if @checker.call
85
+ now, interval = @updater.call
86
+ flush_emit(now, interval)
87
+ end
88
+ end
56
89
  end
57
90
 
58
91
  def count_initialized(keys=nil)
@@ -114,27 +147,13 @@ class Fluent::Plugin::FlowCounterOutput < Fluent::Plugin::Output
114
147
  }
115
148
  end
116
149
 
117
- def flush_emit(step)
150
+ def flush_emit(now, step)
118
151
  if @output_style == :tagged
119
152
  tagged_flush(step).each do |data|
120
- router.emit(@tag, Fluent::Engine.now, data)
153
+ router.emit(@tag, now, data)
121
154
  end
122
155
  else
123
- router.emit(@tag, Fluent::Engine.now, flush(step))
124
- end
125
- end
126
-
127
- def start_watch
128
- @last_checked = Fluent::Engine.now
129
- # for internal, or tests only
130
- timer_execute(:out_flowcounter_watcher, 0.5, &method(:watch))
131
- end
132
-
133
- def watch
134
- if Fluent::Engine.now - @last_checked >= @tick
135
- now = Fluent::Engine.now
136
- flush_emit(now - @last_checked)
137
- @last_checked = now
156
+ router.emit(@tag, now, flush(step))
138
157
  end
139
158
  end
140
159
 
@@ -17,3 +17,13 @@ require 'fluent/plugin/out_flowcounter'
17
17
 
18
18
  class Test::Unit::TestCase
19
19
  end
20
+
21
+ def waiting(seconds)
22
+ begin
23
+ Timeout.timeout(seconds) do
24
+ yield
25
+ end
26
+ rescue Timeout::Error
27
+ raise "Timed out with timeout second #{seconds}"
28
+ end
29
+ end
@@ -1,10 +1,16 @@
1
1
  require 'helper'
2
+ require 'timecop'
2
3
 
3
4
  class FlowCounterOutputTest < Test::Unit::TestCase
4
5
  def setup
5
6
  Fluent::Test.setup
6
7
  end
7
8
 
9
+ def teardown
10
+ Fluent::Clock.return
11
+ Timecop.return
12
+ end
13
+
8
14
  CONFIG = %[
9
15
  unit day
10
16
  aggregate tag
@@ -245,7 +251,7 @@ count_keys message
245
251
  d2.feed(time, {'f1' => 'abcde', 'f2' => 'vwxyz', 'f3' => '0123456789'})
246
252
  d2.feed(time, {'f1' => 'abcde', 'f2' => 'vwxyz', 'f3' => '0123456789'})
247
253
  end
248
- d2.instance.flush_emit(60)
254
+ d2.instance.flush_emit(Fluent::EventTime.now, 60)
249
255
  end
250
256
  events = d2.events
251
257
  assert_equal 1, events.length
@@ -272,7 +278,7 @@ count_keys message
272
278
  d3.feed(time, {'f1' => 'abcde', 'f2' => 'vwxyz', 'f3' => '0123456789'})
273
279
  d3.feed(time, {'f1' => 'abcde', 'f2' => 'vwxyz', 'f3' => '0123456789'})
274
280
  end
275
- d3.instance.flush_emit(60)
281
+ d3.instance.flush_emit(Fluent::EventTime.now, 60)
276
282
  end
277
283
  events = d3.events
278
284
  assert_equal 1, events.length
@@ -364,7 +370,7 @@ count_keys message
364
370
  d3.feed(time, {'f1' => 'abcde', 'f2' => 'vwxyz', 'f3' => '0123456789'})
365
371
  d3.feed(time, {'f1' => 'abcde', 'f2' => 'vwxyz', 'f3' => '0123456789'})
366
372
  end
367
- d3.instance.flush_emit(60)
373
+ d3.instance.flush_emit(Fluent::EventTime.now, 60)
368
374
  end
369
375
  events = d3.events
370
376
  assert_equal 1, events.length
@@ -373,4 +379,198 @@ count_keys message
373
379
  assert_equal 60*5, data[2]['count']
374
380
  assert_equal 0, data[2]['bytes']
375
381
  end
382
+
383
+ data(
384
+ minute: ["minute", 59, 61, 90, 100, 110, 122],
385
+ hour: ["hour", 3500, 3601, 3800, 6900, 7180, 7202],
386
+ day: ["day", 86350, 86401, 86400+20000, 86400+65000, 86400+86350, 86400+86402],
387
+ )
388
+ test 'emit timing' do |data|
389
+ unit, time1, time2, time3, time4, time5, time6 = data
390
+
391
+ d = create_driver %[
392
+ unit #{unit}
393
+ timestamp_counting false
394
+ aggregate all
395
+ tag flowcount
396
+ count_keys message
397
+ ]
398
+
399
+ start = Fluent::Clock.now
400
+ Fluent::Clock.freeze(start)
401
+
402
+ first_emit_before = nil
403
+ first_emit_after = nil
404
+ second_emit_before = nil
405
+ second_emit_after = nil
406
+
407
+ prev_emit_count = 0
408
+ wait_next_emit = ->{
409
+ waiting(10) do
410
+ sleep 0.1 while d.emit_count == prev_emit_count
411
+ prev_emit_count = d.emit_count
412
+ end
413
+ }
414
+
415
+ d.run(default_tag: 'test', timeout: 300_000) do
416
+ 3600.times do
417
+ d.feed({'message'=> 'a' * 100})
418
+ d.feed({'message'=> 'b' * 100})
419
+ d.feed({'message'=> 'c' * 100})
420
+ end
421
+
422
+ Fluent::Clock.freeze(start + time1)
423
+ sleep 2 # nothing emitted here - clock is frozen
424
+ assert_equal 0, d.emit_count
425
+
426
+ 3600.times do
427
+ d.feed({'message'=> 'a' * 100})
428
+ d.feed({'message'=> 'b' * 100})
429
+ d.feed({'message'=> 'c' * 100})
430
+ end
431
+
432
+ first_emit_before = Fluent::EventTime.now
433
+ Fluent::Clock.freeze(start + time2)
434
+ wait_next_emit.call # emitted from 7200 * 3 events
435
+ first_emit_after = Fluent::EventTime.now
436
+
437
+ Fluent::Clock.freeze(start + time3)
438
+
439
+ 3600.times do
440
+ d.feed({'message'=> 'a' * 100})
441
+ d.feed({'message'=> 'b' * 100})
442
+ d.feed({'message'=> 'c' * 100})
443
+ end
444
+
445
+ Fluent::Clock.freeze(start + time4)
446
+ sleep 1
447
+ assert_equal prev_emit_count, d.emit_count
448
+
449
+ Fluent::Clock.freeze(start + time5)
450
+ sleep 1
451
+ assert_equal prev_emit_count, d.emit_count
452
+
453
+ second_emit_before = Fluent::EventTime.now
454
+ Fluent::Clock.freeze(start + time6)
455
+ wait_next_emit.call # emitted from 3600 events
456
+ second_emit_after = Fluent::EventTime.now
457
+ end
458
+
459
+ events = d.events
460
+ assert_equal 2, events.size
461
+ tag, t1, r1 = events[0]
462
+ assert{ t1 >= first_emit_before }
463
+ assert{ t1 <= first_emit_after }
464
+ assert_equal 3600*3*2, r1["count"]
465
+ tag, t2, r2 = events[1]
466
+ assert{ t2 >= second_emit_before }
467
+ assert{ t2 <= second_emit_after }
468
+ assert_equal 3600*3, r2["count"]
469
+ end
470
+
471
+ data(
472
+ minute: [
473
+ "minute",
474
+ "",
475
+ Time.utc(2017,7,26,13,49, 3),
476
+ Time.utc(2017,7,26,13,49,55),
477
+ Time.utc(2017,7,26,13,50, 0),
478
+ Time.utc(2017,7,26,13,50, 3),
479
+ Time.utc(2017,7,26,13,50,49),
480
+ Time.utc(2017,7,26,13,51, 0)],
481
+ hour: [
482
+ "hour",
483
+ "",
484
+ Time.utc(2017,7,26,13,49, 3),
485
+ Time.utc(2017,7,26,13,59,58),
486
+ Time.utc(2017,7,26,14, 0, 0),
487
+ Time.utc(2017,7,26,14,15,49),
488
+ Time.utc(2017,7,26,14,58, 0),
489
+ Time.utc(2017,7,26,15, 0, 0)],
490
+ day: [
491
+ "day",
492
+ "timestamp_timezone +0000",
493
+ Time.utc(2017,7,26,13,49, 3),
494
+ Time.utc(2017,7,26,23,59,58),
495
+ Time.utc(2017,7,27, 0, 0, 0),
496
+ Time.utc(2017,7,27, 1,30,10),
497
+ Time.utc(2017,7,27,23,38,50),
498
+ Time.utc(2017,7,28, 0, 0, 0)],
499
+ dayz: [
500
+ "day",
501
+ "timestamp_timezone -0700",
502
+ Time.new(2017,7,26,13,49, 3, "-07:00"),
503
+ Time.new(2017,7,26,23,59,58, "-07:00"),
504
+ Time.new(2017,7,27, 0, 0, 0, "-07:00"),
505
+ Time.new(2017,7,27, 1,30,10, "-07:00"),
506
+ Time.new(2017,7,27,23,38,50, "-07:00"),
507
+ Time.new(2017,7,28, 0, 0, 0, "-07:00")],
508
+ )
509
+ test 'emit timing with timestamp_counting' do |data|
510
+ unit, conf, start, time1, time2, time3, time4, time5 = data
511
+
512
+ d = create_driver %[
513
+ unit #{unit}
514
+ timestamp_counting true
515
+ #{conf}
516
+ aggregate all
517
+ tag flowcount
518
+ count_keys message
519
+ ]
520
+
521
+ Timecop.freeze(start)
522
+
523
+ prev_emit_count = 0
524
+ wait_next_emit = ->{
525
+ waiting(10) do
526
+ sleep 0.1 while d.emit_count == prev_emit_count
527
+ prev_emit_count = d.emit_count
528
+ end
529
+ }
530
+
531
+ d.run(default_tag: 'test', timeout: 300_000) do
532
+ 3600.times do
533
+ d.feed({'message'=> 'a' * 100})
534
+ d.feed({'message'=> 'b' * 100})
535
+ d.feed({'message'=> 'c' * 100})
536
+ end
537
+
538
+ Timecop.freeze(time1)
539
+ sleep 2 # nothing emitted here - time is frozen
540
+ assert_equal 0, d.emit_count
541
+
542
+ 3600.times do
543
+ d.feed({'message'=> 'a' * 100})
544
+ d.feed({'message'=> 'b' * 100})
545
+ d.feed({'message'=> 'c' * 100})
546
+ end
547
+
548
+ Timecop.freeze(time2)
549
+ wait_next_emit.call # emitted from 7200 * 3 events
550
+
551
+ Timecop.freeze(time3)
552
+
553
+ 3600.times do
554
+ d.feed({'message'=> 'a' * 100})
555
+ d.feed({'message'=> 'b' * 100})
556
+ d.feed({'message'=> 'c' * 100})
557
+ end
558
+
559
+ Timecop.freeze(time4)
560
+ sleep 1
561
+ assert_equal prev_emit_count, d.emit_count
562
+
563
+ Timecop.freeze(time5)
564
+ wait_next_emit.call # emitted from 3600 events
565
+ end
566
+
567
+ events = d.events
568
+ assert_equal 2, events.size
569
+ tag, t1, r1 = events[0]
570
+ assert_equal Fluent::EventTime.new(time2.to_i, 0), t1
571
+ assert_equal 3600*3*2, r1["count"]
572
+ tag, t2, r2 = events[1]
573
+ assert_equal Fluent::EventTime.new(time5.to_i, 0), t2
574
+ assert_equal 3600*3, r2["count"]
575
+ end
376
576
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fluent-plugin-flowcounter
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - TAGOMORI Satoshi
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-07-25 00:00:00.000000000 Z
11
+ date: 2017-07-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -38,6 +38,20 @@ dependencies:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: timecop
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
41
55
  - !ruby/object:Gem::Dependency
42
56
  name: fluentd
43
57
  requirement: !ruby/object:Gem::Requirement