fluent-plugin-groupcounter 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.
data/.gitignore ADDED
@@ -0,0 +1,12 @@
1
+ /*.gem
2
+ ~*
3
+ #*
4
+ *~
5
+ .bundle
6
+ Gemfile.lock
7
+ .rbenv-version
8
+ vendor
9
+ doc/*
10
+ tmp/*
11
+ .yardoc
12
+
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
data/README.md ADDED
@@ -0,0 +1,43 @@
1
+ # fluent-plugin-groupcounter
2
+
3
+ ## Component
4
+
5
+ ### GroupCounterOutput
6
+
7
+ Fluentd plugin to count like COUNT(\*) GROUP BY
8
+
9
+ ## Configuration
10
+
11
+ ## GroupCounterOutput
12
+
13
+ <source>
14
+ type tail
15
+ path /var/log/httpd-access.log
16
+ tag apache.access
17
+ format apache
18
+ </source>
19
+
20
+ <match apache.access>
21
+ type groupcounter
22
+ count_interval 5s
23
+ aggregate tag
24
+ output_per_tag true
25
+ tag_prefix groupcounter
26
+ group_by_keys code,method,path
27
+ </match>
28
+
29
+ Output like below
30
+
31
+ groupcounter.apache.access: {"200_GET_/index.html_count":1,"200_GET_/index.html_rate":0.2,"200_GET_/index.html_percentage":100.0}
32
+
33
+ ## TODO
34
+
35
+ * tests
36
+ * documents
37
+
38
+ ## Copyright
39
+
40
+ * Copyright
41
+ * Copyright (c) 2012- Ryosuke IWANAGA (riywo)
42
+ * License
43
+ * Apache License, Version 2.0
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ # encoding: utf-8
2
+ require "bundler/gem_tasks"
3
+
4
+ desc 'Open an irb session preloaded with the gem library'
5
+ task :console do
6
+ sh 'irb -rubygems -I lib'
7
+ end
8
+
9
+ task :c => :console
10
+
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = "fluent-plugin-groupcounter"
6
+ s.version = "0.1.0"
7
+ s.authors = ["Ryosuke IWANAGA", "Naotoshi SEO"]
8
+ s.email = ["@riywo", "@sonots"]
9
+ s.homepage = "https://github.com/riywo/fluent-plugin-groupcounter"
10
+ s.summary = %q{Fluentd plugin to count like COUNT(\*) GROUP BY}
11
+ s.description = %q{Fluentd plugin to count like COUNT(\*) GROUP BY}
12
+
13
+ s.rubyforge_project = "fluent-plugin-groupcounter"
14
+
15
+ s.files = `git ls-files`.split("\n")
16
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
17
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
18
+ s.require_paths = ["lib"]
19
+
20
+ s.add_runtime_dependency "fluentd"
21
+ s.add_development_dependency "fluentd"
22
+ s.add_development_dependency "rake"
23
+ end
@@ -0,0 +1,201 @@
1
+ class Fluent::GroupCounterOutput < Fluent::Output
2
+ Fluent::Plugin.register_output('groupcounter', self)
3
+
4
+ config_param :count_interval, :time, :default => nil
5
+ config_param :unit, :string, :default => 'minute'
6
+ config_param :output_per_tag, :bool, :default => false
7
+ config_param :aggregate, :string, :default => 'tag'
8
+ config_param :tag, :string, :default => 'groupcount'
9
+ config_param :tag_prefix, :string, :default => nil
10
+ config_param :input_tag_remove_prefix, :string, :default => nil
11
+ config_param :group_by_keys, :string
12
+ config_param :output_messages, :bool, :default => false
13
+
14
+ attr_accessor :tick
15
+ attr_accessor :counts
16
+ attr_accessor :last_checked
17
+
18
+ def configure(conf)
19
+ super
20
+
21
+ if @count_interval
22
+ @tick = @count_interval.to_i
23
+ else
24
+ @tick = case @unit
25
+ when 'minute' then 60
26
+ when 'hour' then 3600
27
+ when 'day' then 86400
28
+ else
29
+ raise RuntimeError, "@unit must be one of minute/hour/day"
30
+ end
31
+ end
32
+
33
+ @aggregate = case @aggregate
34
+ when 'tag' then :tag
35
+ when 'all' then :all
36
+ else
37
+ raise Fluent::ConfigError, "groupcounter aggregate allows tag/all"
38
+ end
39
+
40
+ if @output_per_tag
41
+ raise Fluent::ConfigError, "tag_prefix must be specified with output_per_tag" unless @tag_prefix
42
+ @tag_prefix_string = @tag_prefix + '.'
43
+ end
44
+
45
+ if @input_tag_remove_prefix
46
+ @removed_prefix_string = @input_tag_remove_prefix + '.'
47
+ @removed_length = @removed_prefix_string.length
48
+ end
49
+
50
+ @group_by_keys = @group_by_keys.split(',')
51
+
52
+ @counts = count_initialized
53
+ @mutex = Mutex.new
54
+ end
55
+
56
+ def start
57
+ super
58
+ start_watch
59
+ end
60
+
61
+ def shutdown
62
+ super
63
+ @watcher.terminate
64
+ @watcher.join
65
+ end
66
+
67
+ def count_initialized
68
+ # counts['tag'][group_by_keys] = count
69
+ # counts['tag'][__sum] = sum
70
+ {}
71
+ end
72
+
73
+ def countups(tag, counts)
74
+ if @aggregate == :all
75
+ tag = 'all'
76
+ end
77
+ @counts[tag] ||= {}
78
+
79
+ @mutex.synchronize {
80
+ sum = 0
81
+ counts.each do |key, count|
82
+ sum += count
83
+ @counts[tag][key] ||= 0
84
+ @counts[tag][key] += count
85
+ end
86
+ @counts[tag]['__sum'] ||= 0
87
+ @counts[tag]['__sum'] += sum
88
+ }
89
+ end
90
+
91
+ def stripped_tag(tag)
92
+ return tag unless @input_tag_remove_prefix
93
+ return tag[@removed_length..-1] if tag.start_with?(@removed_prefix_string) and tag.length > @removed_length
94
+ return tag[@removed_length..-1] if tag == @input_tag_remove_prefix
95
+ tag
96
+ end
97
+
98
+ def generate_fields(step, target_counts, attr_prefix, output)
99
+ return {} unless target_counts
100
+ sum = target_counts['__sum']
101
+ messages = target_counts.delete('__sum')
102
+
103
+ target_counts.each do |key, count|
104
+ output[attr_prefix + key + '_count'] = count
105
+ output[attr_prefix + key + '_rate'] = ((count * 100.0) / (1.00 * step)).floor / 100.0
106
+ output[attr_prefix + key + '_percentage'] = count * 100.0 / (1.00 * sum) if sum > 0
107
+ if @output_messages
108
+ output[attr_prefix + 'messages'] = messages
109
+ end
110
+ end
111
+
112
+ output
113
+ end
114
+
115
+ def generate_output(counts, step)
116
+ if @aggregate == :all
117
+ return generate_fields(step, counts['all'], '', {})
118
+ end
119
+
120
+ output = {}
121
+ counts.keys.each do |tag|
122
+ generate_fields(step, counts[tag], stripped_tag(tag) + '_', output)
123
+ end
124
+ output
125
+ end
126
+
127
+ def generate_output_per_tags(counts, step)
128
+ if @aggregate == :all
129
+ return {'all' => generate_fields(step, counts['all'], '', {})}
130
+ end
131
+
132
+ output_pairs = {}
133
+ counts.keys.each do |tag|
134
+ output_pairs[stripped_tag(tag)] = generate_fields(step, counts[tag], '', {})
135
+ end
136
+ output_pairs
137
+ end
138
+
139
+ def flush(step) # returns one message
140
+ flushed,@counts = @counts,count_initialized()
141
+ generate_output(flushed, step)
142
+ end
143
+
144
+ def flush_per_tags(step) # returns map of tag - message
145
+ flushed,@counts = @counts,count_initialized()
146
+ generate_output_per_tags(flushed, step)
147
+ end
148
+
149
+ def flush_emit(step)
150
+ if @output_per_tag
151
+ # tag - message maps
152
+ time = Fluent::Engine.now
153
+ flush_per_tags(step).each do |tag,message|
154
+ Fluent::Engine.emit(@tag_prefix_string + tag, time, message)
155
+ end
156
+ else
157
+ message = flush(step)
158
+ if message.keys.size > 0
159
+ Fluent::Engine.emit(@tag, Fluent::Engine.now, message)
160
+ end
161
+ end
162
+ end
163
+
164
+ def start_watch
165
+ # for internal, or tests only
166
+ @watcher = Thread.new(&method(:watch))
167
+ end
168
+
169
+ def watch
170
+ # instance variable, and public accessable, for test
171
+ @last_checked = Fluent::Engine.now
172
+ while true
173
+ sleep 0.5
174
+ if Fluent::Engine.now - @last_checked >= @tick
175
+ now = Fluent::Engine.now
176
+ flush_emit(now - @last_checked)
177
+ @last_checked = now
178
+ end
179
+ end
180
+ end
181
+
182
+ def emit(tag, es, chain)
183
+ c = {}
184
+
185
+ es.each do |time,record|
186
+ values = []
187
+ @group_by_keys.each { |key|
188
+ v = record[key] || 'undef'
189
+ values.push(v)
190
+ }
191
+ value = values.join('_')
192
+
193
+ value = value.to_s.force_encoding('ASCII-8BIT')
194
+ c[value] ||= 0
195
+ c[value] += 1
196
+ end
197
+ countups(tag, c)
198
+
199
+ chain.next
200
+ end
201
+ end
metadata ADDED
@@ -0,0 +1,107 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fluent-plugin-groupcounter
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Ryosuke IWANAGA
9
+ - Naotoshi SEO
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2013-02-06 00:00:00.000000000 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: fluentd
17
+ requirement: !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ! '>='
21
+ - !ruby/object:Gem::Version
22
+ version: '0'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ none: false
27
+ requirements:
28
+ - - ! '>='
29
+ - !ruby/object:Gem::Version
30
+ version: '0'
31
+ - !ruby/object:Gem::Dependency
32
+ name: fluentd
33
+ requirement: !ruby/object:Gem::Requirement
34
+ none: false
35
+ requirements:
36
+ - - ! '>='
37
+ - !ruby/object:Gem::Version
38
+ version: '0'
39
+ type: :development
40
+ prerelease: false
41
+ version_requirements: !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ! '>='
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ - !ruby/object:Gem::Dependency
48
+ name: rake
49
+ requirement: !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: !ruby/object:Gem::Requirement
58
+ none: false
59
+ requirements:
60
+ - - ! '>='
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ description: Fluentd plugin to count like COUNT(\*) GROUP BY
64
+ email:
65
+ - ! '@riywo'
66
+ - ! '@sonots'
67
+ executables: []
68
+ extensions: []
69
+ extra_rdoc_files: []
70
+ files:
71
+ - .gitignore
72
+ - Gemfile
73
+ - README.md
74
+ - Rakefile
75
+ - fluent-plugin-groupcounter.gemspec
76
+ - lib/fluent/plugin/out_groupcounter.rb
77
+ homepage: https://github.com/riywo/fluent-plugin-groupcounter
78
+ licenses: []
79
+ post_install_message:
80
+ rdoc_options: []
81
+ require_paths:
82
+ - lib
83
+ required_ruby_version: !ruby/object:Gem::Requirement
84
+ none: false
85
+ requirements:
86
+ - - ! '>='
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ segments:
90
+ - 0
91
+ hash: -1667358742827062990
92
+ required_rubygems_version: !ruby/object:Gem::Requirement
93
+ none: false
94
+ requirements:
95
+ - - ! '>='
96
+ - !ruby/object:Gem::Version
97
+ version: '0'
98
+ segments:
99
+ - 0
100
+ hash: -1667358742827062990
101
+ requirements: []
102
+ rubyforge_project: fluent-plugin-groupcounter
103
+ rubygems_version: 1.8.23
104
+ signing_key:
105
+ specification_version: 3
106
+ summary: Fluentd plugin to count like COUNT(\*) GROUP BY
107
+ test_files: []