fluent-plugin-onlineuser 0.0.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/.gitignore ADDED
@@ -0,0 +1,2 @@
1
+ Gemfile.lock
2
+ .idea
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in fluent-plugin-onlineuser.gemspec
4
+ gemspec
data/README.md ADDED
@@ -0,0 +1,2 @@
1
+ fluent-plugin-onlineuser
2
+ ========================
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+
4
+ require 'rake/testtask'
5
+ Rake::TestTask.new(:test) do |test|
6
+ test.libs << 'lib' << 'test'
7
+ test.pattern = 'test/**/test_*.rb'
8
+ test.verbose = true
9
+ end
10
+
11
+ task :default => :test
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+ Gem::Specification.new do |gem|
3
+ gem.name = "fluent-plugin-onlineuser"
4
+ gem.version = "0.0.1"
5
+ gem.authors = ["Yuyang Lan"]
6
+ gem.email = ["lanyuyang@gree.net"]
7
+ gem.summary = %q{Online Users Counter}
8
+ gem.description = %q{Fluentd plugin to count online users. It's based on Redis and the sorted set data type.}
9
+ gem.homepage = "https://github.com/lanyuyang/fluent-plugin-onlineuser"
10
+
11
+ gem.files = `git ls-files`.split($\)
12
+ gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) }
13
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
14
+ gem.require_paths = ["lib"]
15
+ gem.has_rdoc = false
16
+
17
+ gem.add_development_dependency "rake"
18
+ gem.add_development_dependency "fluentd"
19
+ gem.add_development_dependency "redis"
20
+
21
+ gem.add_runtime_dependency "fluentd"
22
+ gem.add_runtime_dependency "redis"
23
+ end
@@ -0,0 +1,186 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ module Fluent
4
+ class OnlineuserOutput < BufferedOutput
5
+ Fluent::Plugin.register_output('online_user', self)
6
+
7
+ config_param :host, :string, :default => 'localhost'
8
+ config_param :port, :integer, :default => 6379
9
+ config_param :db_index, :integer, :default => 0
10
+ config_param :redis_retry, :integer, :default => 3
11
+
12
+ config_param :session_timeout, :integer, :default => 1800
13
+
14
+ config_param :redis_key_prefix, :string, :default => 'counter:online_user:'
15
+ config_param :user_identify, :string, :default => 'uid'
16
+ config_param :segment, :string, :default => nil
17
+
18
+ config_param :tag, :string, :default => "online_user"
19
+ config_param :silent, :bool, :default => false
20
+
21
+ def initialize
22
+ super
23
+ require 'redis'
24
+ require 'msgpack'
25
+ require 'rubygems'
26
+ end
27
+
28
+ def configure(conf)
29
+ super
30
+ @host = conf.has_key?('host') ? conf['host'] : 'localhost'
31
+ @port = conf.has_key?('port') ? conf['port'].to_i : 6379
32
+ @db_number = conf.has_key?('db_number') ? conf['db_number'].to_i : nil
33
+
34
+ @user_identify = @user_identify.split '|'
35
+
36
+ if @segment
37
+ s = @segment.split /\s+/
38
+ case s.first
39
+ when 'tag'
40
+ @cal_segment = lambda { |tag, record| 'tag:' + tag }
41
+ when 'capture'
42
+ if s.length == 3
43
+ format = s[2]
44
+ if format[0] == ?/ and format[-1] == ?/
45
+ format = format[1..-2]
46
+ end
47
+ regexp = Regexp.new(format)
48
+ @cal_segment = lambda { |tag, record|
49
+ if record[s[1]] and record[s[1]].is_a? String
50
+ if c = record[s[1]].match(regexp)
51
+ 'captured_' + s[1] + ':' + (c.captures.join '_')
52
+ end
53
+ end
54
+ }
55
+ else
56
+ raise Fluent::ConfigError, "wrong segment capture, specify it like 'capture __FIELD_NAME__ __CAPTURE_REGEX__'"
57
+ end
58
+ else
59
+ @cal_segment = lambda { |tag, record|
60
+ if record[@segment]
61
+ @segment + ':' + record[@segment]
62
+ end
63
+ }
64
+ end
65
+ end
66
+ end
67
+
68
+ def start
69
+ super
70
+
71
+ begin
72
+ gem "hiredis"
73
+ @redis = Redis.new(
74
+ :host => @host, :port => @port, :driver => :hiredis,
75
+ :thread_safe => true, :db => @db_index
76
+ )
77
+ rescue LoadError
78
+ @redis = Redis.new(
79
+ :host => @host, :port => @port,
80
+ :thread_safe => true, :db => @db_index
81
+ )
82
+ end
83
+
84
+ @segments = {}
85
+
86
+ start_watch
87
+ end
88
+
89
+ def shutdown
90
+ @redis.quit
91
+ end
92
+
93
+ def to_redis_key(segment_str)
94
+ @redis_key_prefix + segment_str
95
+ end
96
+
97
+ def tryOnRedis(method, *args)
98
+ tries = 0
99
+ begin
100
+ @redis.send(method, *args) if @redis.respond_to? method
101
+ rescue Redis::CommandError => e
102
+ tries += 1
103
+ # retry 3 times
104
+ retry if tries <= @redis_retry
105
+ $log.warn %Q[redis command retry failed : #{method}(#{args.join(', ')})]
106
+ raise e.message
107
+ end
108
+ end
109
+
110
+ def start_watch
111
+ @watcher = Thread.new(&method(:watch))
112
+ end
113
+
114
+ def watch
115
+ @last_checked = Fluent::Engine.now
116
+ tick = 60
117
+ while true
118
+ sleep 0.5
119
+ if Fluent::Engine.now - @last_checked >= tick
120
+ now = Fluent::Engine.now
121
+
122
+ @segments.each_key do |segment|
123
+ user_num = user_count segment
124
+
125
+ if user_num == 0
126
+ @segments.delete segment
127
+ elsif not @silent
128
+ Fluent::Engine.emit @tag + '.' + segment, Fluent::Engine.now, {"online_user" => user_num}
129
+ end
130
+ end
131
+
132
+ @last_checked = now
133
+ end
134
+ end
135
+ end
136
+
137
+ def user_count(segment)
138
+ to_expire = Fluent::Engine.now - @session_timeout
139
+ key = to_redis_key segment
140
+ tryOnRedis 'zremrangebyscore', key, '-inf', to_expire
141
+ tryOnRedis 'zcard', key
142
+ end
143
+
144
+ def extract_uid(record)
145
+ i = @user_identify.detect { |id|
146
+ v = record[id]
147
+ (v.is_a?(String) and !v.empty?) or (v.is_a?(Numeric) and v != 0)
148
+ }
149
+ i ? record[i] : nil
150
+ end
151
+
152
+ def format(tag, time, record)
153
+ [tag, time, record].to_msgpack
154
+ end
155
+
156
+ def write(chunk)
157
+
158
+ online_user = Hash.new { |hash, key| hash[key] = Hash.new }
159
+
160
+ begin
161
+ chunk.msgpack_each do |(tag, time, record)|
162
+
163
+ if (uid = extract_uid record)
164
+ online_user['all'][uid] = time
165
+
166
+ if @cal_segment and @cal_segment != '' and segment = @cal_segment.call(tag, record)
167
+ online_user[segment][uid] = time
168
+ end
169
+ end
170
+ end
171
+
172
+ ## write to redis
173
+ online_user.each { |segment, users|
174
+ @segments[segment] = true
175
+
176
+ key = to_redis_key segment
177
+
178
+ users.each { |uid, ts|
179
+ tryOnRedis 'zadd', key, ts, uid
180
+ }
181
+ }
182
+
183
+ end
184
+ end
185
+ end
186
+ end
data/test/fluentd.conf ADDED
@@ -0,0 +1,51 @@
1
+ <source>
2
+ type forward
3
+ </source>
4
+
5
+ <match access_log.**>
6
+ type online_user
7
+ flush_interval 10s
8
+
9
+ #redis configuration
10
+ #host localhost
11
+ #port 6379
12
+ #db_index 0
13
+ #redis_try 3
14
+
15
+ #inactive timeout, default 30 minutes
16
+ #session_timeout 1800
17
+
18
+ # prefix of the keys stored into Redis
19
+ #redis_key_prefix counter:online_user:
20
+
21
+ # how do you define unique user ?
22
+ # the default is to find 'uid' field in record
23
+
24
+ # for support both members and non-members, try:
25
+ #user_identify uid|ip
26
+
27
+ #emit online user count event periodically or not
28
+ #silent false
29
+
30
+ #the tag prefix for emitted event
31
+ #tag online_user
32
+
33
+ #besides counting the overall online users
34
+ #also wanna count online users for each segment ?
35
+ #if so, use the segment directive
36
+
37
+ #segmented by tag
38
+ #segment tag
39
+
40
+ #segmented by a specified field
41
+ #segment service
42
+
43
+ #segmented by regexp-captured string
44
+ #the format is: capture __FIELD__ __REGEXP__
45
+ #segment capture name /^\w+:(\w+):/
46
+
47
+ </match>
48
+
49
+ <match **>
50
+ type stdout
51
+ </match>
@@ -0,0 +1,42 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ require 'test/unit'
4
+ require 'fluent/test'
5
+ require 'fluent/plugin/out_onlineuser'
6
+ require 'redis'
7
+
8
+ class OnlineuserOutputTest < Test::Unit::TestCase
9
+ CONFIG = %[
10
+ host localhost
11
+ port 6379
12
+ db_number 0
13
+ ]
14
+
15
+ @redis
16
+
17
+ def setup
18
+ Fluent::Test.setup
19
+ @redis = Redis.new(:host => 'localhost', :port => 6379, :thread_safe => true, :db => 0)
20
+ end
21
+
22
+ def teardown
23
+ @redis.quit
24
+ end
25
+
26
+ def create_driver(conf, tag='test')
27
+ Fluent::Test::BufferedOutputTestDriver.new(Fluent::OnlineuserOutput, tag).configure(conf)
28
+ end
29
+
30
+ def test_event_counter
31
+ d = create_driver CONFIG
32
+ t = Time.now
33
+ time = t.to_i
34
+ d.emit({"name" => "test", "uid" => 1}, time)
35
+ d.emit({"name" => "test", "uid" => 2}, time)
36
+ d.emit({"name" => "test", "uid" => 3}, time)
37
+ d.run
38
+
39
+ assert_equal 3, @redis.zcard('counter:online_user:all')
40
+ end
41
+
42
+ end
metadata ADDED
@@ -0,0 +1,136 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fluent-plugin-onlineuser
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Yuyang Lan
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-04-16 00:00:00.000000000 Z
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'
30
+ - !ruby/object:Gem::Dependency
31
+ name: fluentd
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: redis
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: fluentd
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :runtime
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ - !ruby/object:Gem::Dependency
79
+ name: redis
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ type: :runtime
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ description: Fluentd plugin to count online users. It's based on Redis and the sorted
95
+ set data type.
96
+ email:
97
+ - lanyuyang@gree.net
98
+ executables: []
99
+ extensions: []
100
+ extra_rdoc_files: []
101
+ files:
102
+ - .gitignore
103
+ - Gemfile
104
+ - README.md
105
+ - Rakefile
106
+ - fluent-plugin-onlineuser.gemspec
107
+ - lib/fluent/plugin/out_onlineuser.rb
108
+ - test/fluentd.conf
109
+ - test/plugin/test_out_onlineuser.rb
110
+ homepage: https://github.com/lanyuyang/fluent-plugin-onlineuser
111
+ licenses: []
112
+ post_install_message:
113
+ rdoc_options: []
114
+ require_paths:
115
+ - lib
116
+ required_ruby_version: !ruby/object:Gem::Requirement
117
+ none: false
118
+ requirements:
119
+ - - ! '>='
120
+ - !ruby/object:Gem::Version
121
+ version: '0'
122
+ required_rubygems_version: !ruby/object:Gem::Requirement
123
+ none: false
124
+ requirements:
125
+ - - ! '>='
126
+ - !ruby/object:Gem::Version
127
+ version: '0'
128
+ requirements: []
129
+ rubyforge_project:
130
+ rubygems_version: 1.8.24
131
+ signing_key:
132
+ specification_version: 3
133
+ summary: Online Users Counter
134
+ test_files:
135
+ - test/fluentd.conf
136
+ - test/plugin/test_out_onlineuser.rb