fluent-plugin-unique-counter 0.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: f792411616af4e4d34e799b98163e9a3f994ea71
4
+ data.tar.gz: c1d9609ad4b69a358f9826a786ea82d4476071e1
5
+ SHA512:
6
+ metadata.gz: 2ad73f96b43a325faa1de85f889ce999c93021f95f43fa649407b284bcba993f7e4f963ec01e9d563d6f29b1cb258228534fdc6d34f0a29f57c7f5a036a6e4eb
7
+ data.tar.gz: 74c2ecfa0a309ede8b8483995fe594b08aad17fe665d13b05baedeb8644aabc6a6f98ddc9e4f30bacee4ce93d0397f075fa6c3bff46db2df429032a2b484a9b2
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in fluent-plugin-unique-counter.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 futoase
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,49 @@
1
+ # Fluent unique counter plugin
2
+
3
+ This plugin purpose is simple monitoring (countup only).
4
+ This was referred from [fluent-plugin-numeric-counter](https://github.com/tagomoris/fluent-plugin-numeric-counter). Thanks! :)
5
+
6
+ # Contributor
7
+
8
+ - [@takesato](https://github.com/takesato)
9
+ - [@futoase](https://github.com/futoase)
10
+
11
+ ## Installation
12
+
13
+ Add this line to your application's Gemfile:
14
+
15
+ gem 'fluent-plugin-unique-counter'
16
+
17
+ And then execute:
18
+
19
+ $ bundle
20
+
21
+ Or install it yourself as:
22
+
23
+ $ gem install fluent-plugin-unique-counter
24
+
25
+ ## confing params
26
+
27
+ - ```count_interval```
28
+
29
+ This interval time to monitoring. Default setting is ```60sec```.
30
+
31
+ - ```unit```
32
+
33
+ Monitoring specific interval an unit. Selectable setting is ```minutes```, ```hours``` and ```days```.
34
+
35
+ - ```unique_key```
36
+
37
+ Monitoring key name.
38
+
39
+ - ```tag```
40
+
41
+ Output tag name. Default setting is ```unique_count```.
42
+
43
+ ## License
44
+
45
+ Apache License v2.0.
46
+
47
+ ## Copylight
48
+
49
+ Copylight (c) 2013 Aiming Inc.
@@ -0,0 +1,11 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |test|
5
+ test.libs << "lib" << "test"
6
+ test.pattern = "test/**/test_*.rb"
7
+ test.verbose = true
8
+ end
9
+
10
+ task :default => :test
11
+
@@ -0,0 +1,25 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "fluent-plugin-unique-counter"
7
+ spec.version = "0.1.0"
8
+ spec.authors = ["Keiji Matsuzaki"]
9
+ spec.email = ["futoase@gmail.com"]
10
+ spec.summary = %q{fluentd unique counter plugin.}
11
+ spec.description = %q{This plugin is use of count up to unique attribute.}
12
+ spec.homepage = ""
13
+ spec.license = "Apache License v2.0"
14
+
15
+ spec.files = `git ls-files`.split($/)
16
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
+ spec.require_paths = ["lib"]
19
+
20
+ spec.add_development_dependency "bundler", "~> 1.3"
21
+ spec.add_development_dependency "rake"
22
+ spec.add_development_dependency "timecop"
23
+
24
+ spec.add_dependency "fluentd"
25
+ end
@@ -0,0 +1,84 @@
1
+ # coding: utf-8
2
+ #
3
+ class Fluent::UniqCounterOutput < Fluent::Output
4
+ Fluent::Plugin.register_output('unique_counter', self)
5
+
6
+ config_param :count_interval, :time, :default => 60
7
+ config_param :unit, :string, :default => nil
8
+ config_param :unique_key, :string
9
+
10
+ config_param :tag, :string, :default => 'unique_count'
11
+
12
+ attr_accessor :tick
13
+ attr_accessor :counts
14
+ attr_accessor :last_checked
15
+
16
+ UNITS = {
17
+ minutes: 60,
18
+ hours: 3600,
19
+ days: 86400
20
+ }
21
+
22
+ def configure(conf)
23
+ super
24
+
25
+ if @unit and UNITS[@unit.to_sym].nil?
26
+ raise Fluent::ConfigError, "'unit' must be one of minutes/hours/days"
27
+ end
28
+ @count_interval = UNITS[@unit.to_sym] if @unit
29
+
30
+ @counts = []
31
+ @mutex = Mutex.new
32
+ end
33
+
34
+ def start
35
+ super
36
+ start_watch
37
+ end
38
+
39
+ def shutdown
40
+ super
41
+ @watcher.terminate
42
+ @watcher.join
43
+ end
44
+
45
+ def flush_emit
46
+ flushed,@counts = @counts,[]
47
+ message = {'unique_count' => flushed.uniq.count }
48
+ Fluent::Engine.emit(@tag, Fluent::Engine.now, message)
49
+ end
50
+
51
+ def start_watch
52
+ # for internal, or tests only
53
+ @watcher = Thread.new(&method(:watch))
54
+ end
55
+
56
+ def watch
57
+ # instance variable, and public accessable, for test
58
+ @last_checked = Fluent::Engine.now
59
+ while true
60
+ sleep 0.5
61
+ if (Fluent::Engine.now - @last_checked) >= @count_interval
62
+ now = Fluent::Engine.now
63
+ flush_emit
64
+ @last_checked = now
65
+ end
66
+ end
67
+ end
68
+
69
+ def emit(tag, es, chain)
70
+ c = []
71
+
72
+ es.each do |time,record|
73
+ value = record[@unique_key]
74
+ next if value.nil?
75
+
76
+ c << value.to_s.force_encoding('ASCII-8BIT')
77
+ end
78
+ @mutex.synchronize {
79
+ @counts += c
80
+ }
81
+
82
+ chain.next
83
+ end
84
+ end
@@ -0,0 +1,12 @@
1
+ require 'bundler'
2
+
3
+ Bundler.setup(:default, :test)
4
+ Bundler.require(:default, :test)
5
+
6
+ require 'fluent/test'
7
+ require 'test/unit'
8
+
9
+ $:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
10
+ $:.unshift(File.dirname(__FILE__))
11
+
12
+ require 'fluent/plugin/out_unique_counter'
@@ -0,0 +1,157 @@
1
+ require_relative 'helper'
2
+ require 'timecop'
3
+
4
+ class OutUniqueCounterTest < Test::Unit::TestCase
5
+ def setup
6
+ Fluent::Test.setup
7
+ end
8
+
9
+ def teardown
10
+ Timecop.return
11
+ end
12
+
13
+ CONFIG_INTERVAL_10 = %[
14
+ tag api.production.login
15
+ unique_key user_id
16
+ count_interval 10
17
+ ]
18
+
19
+ CONFIG_INTERVAL_30 = %[
20
+ tag api.production.login
21
+ unique_key user_id
22
+ count_interval 30
23
+ ]
24
+
25
+ CONFIG_UNIT_MINUTES = %[
26
+ tag api.production.login
27
+ unique_key user_id
28
+ unit minutes
29
+ ]
30
+
31
+ CONFIG_UNIT_HOURS = %[
32
+ tag api.production.login
33
+ unique_key user_id
34
+ unit hours
35
+ ]
36
+
37
+ CONFIG_UNIT_DAYS = %[
38
+ tag api.production.login
39
+ unique_key user_id
40
+ unit days
41
+ ]
42
+
43
+ def create_driver(conf = CONFIG, tag = 'test')
44
+ Fluent::Test::OutputTestDriver.new(Fluent::UniqCounterOutput, tag).configure(conf)
45
+ end
46
+
47
+ def test_unique_count_interval_10
48
+ d1 = create_driver(CONFIG_INTERVAL_10, 'uniq.countup')
49
+
50
+ d1.run do
51
+ Timecop.freeze(Time.now + 10)
52
+ d1.emit({"user_id" => "1000"})
53
+ d1.emit({"user_id" => "1000"})
54
+ d1.emit({"user_id" => "1000"})
55
+ d1.emit({"user_id" => "1001"})
56
+ d1.emit({"user_id" => "1002"})
57
+ d1.emit({"user_id" => "1002"})
58
+ sleep 1
59
+ Timecop.return
60
+ end
61
+
62
+ emits = d1.emits
63
+
64
+ p emits[0]
65
+ assert_equal "api.production.login", emits[0][0]
66
+ assert_equal 3, emits[0][2]["unique_count"]
67
+ end
68
+
69
+ def test_unique_count_interval_30
70
+ d1 = create_driver(CONFIG_INTERVAL_30, 'uniq.countup')
71
+
72
+ d1.run do
73
+ Timecop.freeze(Time.now + 30)
74
+ d1.emit({"user_id" => "1000"})
75
+ d1.emit({"user_id" => "1000"})
76
+ d1.emit({"user_id" => "1000"})
77
+ d1.emit({"user_id" => "1001"})
78
+ d1.emit({"user_id" => "1002"})
79
+ d1.emit({"user_id" => "1002"})
80
+ sleep 1
81
+ Timecop.return
82
+ end
83
+
84
+ emits = d1.emits
85
+
86
+ p emits[0]
87
+ assert_equal "api.production.login", emits[0][0]
88
+ assert_equal 3, emits[0][2]["unique_count"]
89
+ end
90
+
91
+ def test_unique_count_unit_minutes
92
+ d1 = create_driver(CONFIG_UNIT_MINUTES, 'uniq.countup')
93
+
94
+ d1.run do
95
+ Timecop.freeze(Time.now + 60)
96
+ d1.emit({"user_id" => "1000"})
97
+ d1.emit({"user_id" => "1000"})
98
+ d1.emit({"user_id" => "1000"})
99
+ d1.emit({"user_id" => "1001"})
100
+ d1.emit({"user_id" => "1002"})
101
+ d1.emit({"user_id" => "1002"})
102
+ sleep 1
103
+ Timecop.return
104
+ end
105
+
106
+ emits = d1.emits
107
+
108
+ p emits[0]
109
+ assert_equal "api.production.login", emits[0][0]
110
+ assert_equal 3, emits[0][2]["unique_count"]
111
+ end
112
+
113
+ def test_unique_count_unit_hours
114
+ d1 = create_driver(CONFIG_UNIT_HOURS, 'uniq.countup')
115
+
116
+ d1.run do
117
+ Timecop.freeze(Time.now + 3600)
118
+ d1.emit({"user_id" => "1000"})
119
+ d1.emit({"user_id" => "1000"})
120
+ d1.emit({"user_id" => "1000"})
121
+ d1.emit({"user_id" => "1001"})
122
+ d1.emit({"user_id" => "1002"})
123
+ d1.emit({"user_id" => "1002"})
124
+ sleep 1
125
+ Timecop.return
126
+ end
127
+
128
+ emits = d1.emits
129
+
130
+ p emits[0]
131
+ assert_equal "api.production.login", emits[0][0]
132
+ assert_equal 3, emits[0][2]["unique_count"]
133
+ end
134
+
135
+ def test_unique_count_unit_days
136
+ d1 = create_driver(CONFIG_UNIT_DAYS, 'uniq.countup')
137
+
138
+ d1.run do
139
+ Timecop.freeze(Time.now + 86400)
140
+ d1.emit({"user_id" => "1000"})
141
+ d1.emit({"user_id" => "1000"})
142
+ d1.emit({"user_id" => "1000"})
143
+ d1.emit({"user_id" => "1001"})
144
+ d1.emit({"user_id" => "1002"})
145
+ d1.emit({"user_id" => "1002"})
146
+ sleep 1
147
+ Timecop.return
148
+ end
149
+
150
+ emits = d1.emits
151
+
152
+ p emits[0]
153
+ assert_equal "api.production.login", emits[0][0]
154
+ assert_equal 3, emits[0][2]["unique_count"]
155
+ end
156
+
157
+ end
metadata ADDED
@@ -0,0 +1,111 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fluent-plugin-unique-counter
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Keiji Matsuzaki
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-10-21 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '1.3'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '1.3'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
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'
55
+ - !ruby/object:Gem::Dependency
56
+ name: fluentd
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: This plugin is use of count up to unique attribute.
70
+ email:
71
+ - futoase@gmail.com
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - .gitignore
77
+ - Gemfile
78
+ - LICENSE.txt
79
+ - README.md
80
+ - Rakefile
81
+ - fluent-plugin-unique-counter.gemspec
82
+ - lib/fluent/plugin/out_unique_counter.rb
83
+ - test/helper.rb
84
+ - test/test_unique_counter.rb
85
+ homepage: ''
86
+ licenses:
87
+ - Apache License v2.0
88
+ metadata: {}
89
+ post_install_message:
90
+ rdoc_options: []
91
+ require_paths:
92
+ - lib
93
+ required_ruby_version: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - '>='
96
+ - !ruby/object:Gem::Version
97
+ version: '0'
98
+ required_rubygems_version: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - '>='
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
103
+ requirements: []
104
+ rubyforge_project:
105
+ rubygems_version: 2.0.3
106
+ signing_key:
107
+ specification_version: 4
108
+ summary: fluentd unique counter plugin.
109
+ test_files:
110
+ - test/helper.rb
111
+ - test/test_unique_counter.rb