fluent-plugin-groupcounter 0.1.0

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