fluent-plugin-measure_time 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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 193e1f487b71b8b3e05144ada9ef36651b95d230
4
+ data.tar.gz: d0a5363194ca0c40b26d242dff97d703e7850c11
5
+ SHA512:
6
+ metadata.gz: c76cda2d36d046e9496509fe3f139394660b1e2791294489bcfbb1c78706af03830886e972f081ef6c07a77512d059a12b7a405ddfd64e49c35f93eeb204b038
7
+ data.tar.gz: cbdd31735b323db8d30d5292b66cf939131f491b07968312f00691684859310722d497c3cc20ae55c87fc3012414e2c672db4e1d4eb70b1d1ec4650bf49a2d1c
data/.gitignore ADDED
@@ -0,0 +1,15 @@
1
+ /*.gem
2
+ ~*
3
+ #*
4
+ *~
5
+ .bundle
6
+ Gemfile.lock
7
+ .rbenv-version
8
+ vendor
9
+ doc/*
10
+ tmp/*
11
+ coverage
12
+ .yardoc
13
+ pkg/
14
+ .ruby-version
15
+ *.log
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --colour
2
+ --format documentation
data/.travis.yml ADDED
@@ -0,0 +1,6 @@
1
+ rvm:
2
+ - 1.9.2
3
+ - 1.9.3
4
+ - 2.0.0
5
+ gemfile:
6
+ - Gemfile
data/CHANGELOG.md ADDED
@@ -0,0 +1,3 @@
1
+ ## 0.1.0 (2014/04/12)
2
+
3
+ First release
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Naotoshi SEO
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.
data/README.md ADDED
@@ -0,0 +1,108 @@
1
+ # fluent-plugin-measure_time
2
+
3
+ [![Build Status](https://secure.travis-ci.org/sonots/fluent-plugin-measure_time.png?branch=master)](http://travis-ci.org/sonots/fluent-plugin-measure_time)
4
+ [![Code Climate](https://codeclimate.com/github/sonots/fluent-plugin-measure_time.png)](https://codeclimate.com/github/sonots/fluent-plugin-measure_time)
5
+
6
+ Fluentd plugin to measure elapsed time to process messages
7
+
8
+ ## Installation
9
+
10
+ Use RubyGems:
11
+
12
+ gem install fluent-plugin-measure_time
13
+
14
+ ## Configuration
15
+
16
+ This plugin is doing something tricky, which extends arbitrary plugins so that it can use `<measure_time></measure_time>` directive to measure elapsed times.
17
+
18
+ Example:
19
+
20
+ ```apache
21
+ <source>
22
+ type measure_time
23
+ # This makes available the `masure_time` directive for all plugins
24
+ </source>
25
+
26
+ <source>
27
+ type forward
28
+ port 24224
29
+ <measure_time>
30
+ tag measure_time
31
+ hook on_message
32
+ </measure_time>
33
+ </source>
34
+
35
+ <match measure_time>
36
+ type stdout
37
+ </match>
38
+ ```
39
+
40
+ This example hooks the [on_message](https://github.com/fluent/fluentd/blob/e5a9a4ca03d18b45fdb89061d8251592a044e9fc/lib/fluent/plugin/in_forward.rb#L112) method of in_forward plugin, and measures how long it takes for processing. Output becomes as below:
41
+
42
+ ```
43
+ measure_time: {"time":0.000849735,"class":"Fluent::ForwardInput","hook":"on_message","object_id":83935080}
44
+ ```
45
+
46
+ where `time` denotes the measured elapsed time, and `class`, `hook`, and `object_id` denotes the hooked class, the hooked method, and the object id of the plugin instance.
47
+
48
+ Example: interval
49
+
50
+ With `interval` option, this plugin compute statistics of measured elapsed times in each interval
51
+
52
+ ```apache
53
+ <source>
54
+ type measure_time
55
+ </source>
56
+
57
+ <source>
58
+ type forward
59
+ port 24224
60
+ <measure_time>
61
+ tag measure_time
62
+ interval 60
63
+ hook on_message
64
+ </measure_time>
65
+ </source>
66
+
67
+ <match measure_time>
68
+ type stdout
69
+ </match>
70
+ ```
71
+
72
+ Output becomes as below:
73
+
74
+ ```
75
+ measure_time: {"max":1.011,"avg":0.002","num":10,"class":"Fluent::ForwardInput","hook":"on_message","object_id":83935080}
76
+ ```
77
+
78
+ where `max` and `avg` are the maximum and average elapsed times, and `num` is the number of being called in each interval.
79
+
80
+ ## Parameters
81
+
82
+ * tag
83
+
84
+ The output tag name. Default is `measure_time`
85
+
86
+ * hook (required)
87
+
88
+ Specify the method to measure time.
89
+
90
+ * interval
91
+
92
+ The time interval to emit measurement results. Default is nil which do not compute statistics and emit the time in each measurement.
93
+
94
+ ## ChangeLog
95
+
96
+ See [CHANGELOG.md](CHANGELOG.md) for details.
97
+
98
+ ## Contributing
99
+
100
+ 1. Fork it
101
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
102
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
103
+ 4. Push to the branch (`git push origin my-new-feature`)
104
+ 5. Create new [Pull Request](../../pull/new/master)
105
+
106
+ ## Copyright
107
+
108
+ Copyright (c) 2014 Naotoshi Seo. See [LICENSE](LICENSE) for details.
data/Rakefile ADDED
@@ -0,0 +1,15 @@
1
+ # encoding: utf-8
2
+ require "bundler/gem_tasks"
3
+
4
+ require 'rspec/core'
5
+ require 'rspec/core/rake_task'
6
+ RSpec::Core::RakeTask.new(:spec) do |spec|
7
+ spec.pattern = FileList['spec/**/*_spec.rb']
8
+ end
9
+ task :default => :spec
10
+
11
+ desc 'Open an irb session preloaded with the gem library'
12
+ task :console do
13
+ sh 'irb -rubygems -I lib'
14
+ end
15
+ task :c => :console
data/benchmark/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'fluentd'
4
+ gem 'dummer'
5
+ gem 'fluent-plugin-keep-forward'
6
+ gem 'fluent-plugin-flowcounter-simple'
7
+ gem 'fluent-plugin-measure_time', path: '../'
@@ -0,0 +1,93 @@
1
+ # Fluentd benchmark - forward
2
+
3
+ This benchmarks following architecture scenario:
4
+
5
+ ```
6
+ Agent Node Receiver Node
7
+ +-----------------------------------+ +-----------------+
8
+ | +-----------+ +-----------+ | | +-----------+ |
9
+ | | | | | | | | | |
10
+ | | Log File +----->| Fluentd +--------------->| Fluentd | |
11
+ | | | | | | | | | |
12
+ | +-----------+ in_tail ----- out_forward in_forward -----+ |
13
+ +-----------------------------------+ +-----------------+
14
+ ```
15
+
16
+ ## Setup Fluentd Receiver
17
+
18
+ Assum ruby is installed
19
+
20
+ ```
21
+ git clone https://github.com/sonots/fluentd-benchmark
22
+ cd fluentd-benchmark/one_forward
23
+ bundle
24
+ bundle exec fluentd -c receiver.conf
25
+ ```
26
+
27
+ ## Setup Fluentd Agent
28
+
29
+ Assume ruby is installed
30
+
31
+ ```
32
+ git clone https://github.com/sonots/fluentd-benchmark
33
+ cd fluentd-benchmark/one_forward
34
+ bundle
35
+ bundle exec fluentd -c agent.conf
36
+ ```
37
+
38
+ ## Run benchmark tool and measure
39
+
40
+ Run at Fluentd agent server.
41
+
42
+ This tool generates a log file to dummy.log and Fluentd agent will read and send data to receiver.
43
+
44
+ ```
45
+ cd fluentd-benchmark/one_forward
46
+ bundle exec dummer -c dummer.conf
47
+ ```
48
+
49
+ You may increase the rate (messages/sec) of generating log by -r option to benchmark.
50
+
51
+ ```
52
+ bundle exec dummer -c dummer.conf -r 100000
53
+ ```
54
+
55
+ You should see an output on Fluentd receiver as following. This will tell you the performance of fluentd processing.
56
+
57
+ ```
58
+ 2014-02-20 17:20:55 +0900 [info]: plugin:out_flowcounter_simple count:500 indicator:num unit:second
59
+ 2014-02-20 17:20:56 +0900 [info]: plugin:out_flowcounter_simple count:500 indicator:num unit:second
60
+ 2014-02-20 17:20:57 +0900 [info]: plugin:out_flowcounter_simple count:500 indicator:num unit:second
61
+ ```
62
+
63
+ You may use `iostat -dkxt 1`, `vmstat 1`, `top -c`, `free`, or `dstat` commands to measure system resources.
64
+
65
+ ## Sample Result
66
+
67
+ This is a sample result running on my environement
68
+
69
+
70
+ Machine Spec
71
+
72
+ ```
73
+ CPU Xeon E5-2670 2.60GHz x 2 (32 Cores)
74
+ Memory 24G
75
+ Disk 300G(10000rpm) x 2 [SAS-HDD]
76
+ OS CentOS release 6.2 (Final)
77
+ ```
78
+
79
+ Result
80
+
81
+
82
+ | rate of writing (lines/sec) | reading (lines/sec) | CPU (%) | Memory (kB) | Remarks |
83
+ |-----------------------------|-----------------------|---------|-------------|---------|
84
+ | 10 | 10 | 0.2 | 29304 | |
85
+ | 100 | 100 | 0.3 | 35812 | |
86
+ | 1000 | 1000 | 1.3 | 37864 | |
87
+ | 10000 | 10000 | 6.6 | 39912 | |
88
+ | 100000 | 100000 | 62 | 39912 | |
89
+ | 200000 | 157148 | 100.4 | 36280 | MAX |
90
+ | 300000 | N/A | | | |
91
+ | 400000 | N/A | | | |
92
+ | 5247047 | N/A | | | MAX of dummer tool |
93
+
@@ -0,0 +1,24 @@
1
+ <source>
2
+ type tail
3
+ path dummy.log
4
+ pos_file /var/tmp/_var_log_dummy.pos
5
+ format none
6
+ tag dummy
7
+ </source>
8
+ <match dummy>
9
+ type copy
10
+ <store>
11
+ type keep_forward
12
+ flush_interval 0
13
+ try_flush_interval 1
14
+ buffer_chunk_limit 1m
15
+ buffer_queue_limit 64
16
+ num_threads 3
17
+ keepforward thread
18
+ keepalive true
19
+ <server>
20
+ host 127.0.0.1 # FIX ME
21
+ port 24224
22
+ </server>
23
+ </store>
24
+ </match>
@@ -0,0 +1,4 @@
1
+ configure 'sample' do
2
+ output "dummy.log"
3
+ message "time:2013-11-25 00:23:52 +0900\tlevel:ERROR\tmethod:POST\turi:/api/v1/people\treqtime:3.1983877060667103"
4
+ end
@@ -0,0 +1,19 @@
1
+ <source>
2
+ type measure_time
3
+ </source>
4
+
5
+ <source>
6
+ type forward
7
+ port 24224
8
+ <measure_time>
9
+ hook on_message
10
+ </measure_time>
11
+ </source>
12
+ <match dummy>
13
+ type flowcounter_simple
14
+ unit second
15
+ </match>
16
+
17
+ <match measure_time>
18
+ type stdout
19
+ </match>
@@ -0,0 +1,8 @@
1
+ <source>
2
+ type forward
3
+ port 24224
4
+ </source>
5
+ <match dummy>
6
+ type flowcounter_simple
7
+ unit second
8
+ </match>
@@ -0,0 +1,16 @@
1
+ # bundle exec fluentd -c patched_in_forward.conf -p plugin
2
+ <source>
3
+ type forward
4
+ port 24224
5
+ <elapsed>
6
+ hook on_message
7
+ </elapsed>
8
+ </source>
9
+ <match dummy>
10
+ type flowcounter_simple
11
+ unit second
12
+ </match>
13
+
14
+ <match elapsed>
15
+ type stdout
16
+ </match>
@@ -0,0 +1,345 @@
1
+ #
2
+ # Fluent
3
+ #
4
+ # Copyright (C) 2011 FURUHASHI Sadayuki
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+ module Fluent
19
+
20
+
21
+ class ForwardInput < Input
22
+ Plugin.register_input('forward', self)
23
+
24
+ def initialize
25
+ super
26
+ require 'fluent/plugin/socket_util'
27
+ end
28
+
29
+ config_param :port, :integer, :default => DEFAULT_LISTEN_PORT
30
+ config_param :bind, :string, :default => '0.0.0.0'
31
+ config_param :backlog, :integer, :default => nil
32
+ # SO_LINGER 0 to send RST rather than FIN to avoid lots of connections sitting in TIME_WAIT at src
33
+ config_param :linger_timeout, :integer, :default => 0
34
+ attr_reader :elapsed # for test
35
+
36
+ def configure(conf)
37
+ super
38
+
39
+ if element = conf.elements.first { |element| element.name == 'elapsed' }
40
+ tag = element["tag"] || 'elapsed'
41
+ interval = element["interval"].to_i || 60
42
+ hook = element['hook'] || 'on_message'
43
+ @elapsed = ElapsedMeasure.new(log, tag, interval, hook)
44
+ end
45
+ end
46
+
47
+ def start
48
+ @loop = Coolio::Loop.new
49
+
50
+ @lsock = listen
51
+ @loop.attach(@lsock)
52
+
53
+ @usock = SocketUtil.create_udp_socket(@bind)
54
+ @usock.bind(@bind, @port)
55
+ @usock.fcntl(Fcntl::F_SETFL, Fcntl::O_NONBLOCK)
56
+ @hbr = HeartbeatRequestHandler.new(@usock, method(:on_heartbeat_request))
57
+ @loop.attach(@hbr)
58
+
59
+ @thread = Thread.new(&method(:run))
60
+ @elapsed.start if @elapsed
61
+ @cached_unpacker = $use_msgpack_5 ? nil : MessagePack::Unpacker.new
62
+ end
63
+
64
+ def shutdown
65
+ @loop.watchers.each {|w| w.detach }
66
+ @loop.stop
67
+ @usock.close
68
+ listen_address = (@bind == '0.0.0.0' ? '127.0.0.1' : @bind)
69
+ # This line is for connecting listen socket to stop the event loop.
70
+ # We should use more better approach, e.g. using pipe, fixing cool.io with timeout, etc.
71
+ TCPSocket.open(listen_address, @port) {|sock| } # FIXME @thread.join blocks without this line
72
+ @thread.join
73
+ @lsock.close
74
+ @elapsed.stop if @elapsed
75
+ end
76
+
77
+ class ElapsedMeasure
78
+ attr_reader :tag, :interval, :hook, :times, :sizes, :mutex, :thread, :log
79
+ def initialize(log, tag, interval, hook)
80
+ @log = log
81
+ @tag = tag
82
+ @interval = interval
83
+ @hook = hook.split(',')
84
+ @times = []
85
+ @sizes = []
86
+ @mutex = Mutex.new
87
+ end
88
+
89
+ def add(time, size)
90
+ @times << time
91
+ @sizes << size
92
+ end
93
+
94
+ def clear
95
+ @times.clear
96
+ @sizes.clear
97
+ end
98
+
99
+ def hookable?(caller)
100
+ @hook.include?(caller.to_s)
101
+ end
102
+
103
+ def measure_time(caller, size)
104
+ if hookable?(caller)
105
+ started = Time.now
106
+ yield
107
+ elapsed = (Time.now - started).to_f
108
+ log.debug "in_forward: elapsed time at #{caller} is #{elapsed} sec for #{size} bytes"
109
+ @mutex.synchronize { self.add(elapsed, size) }
110
+ else
111
+ yield
112
+ end
113
+ end
114
+
115
+ def start
116
+ @thread = Thread.new(&method(:run))
117
+ end
118
+
119
+ def stop
120
+ @thread.terminate
121
+ @thread.join
122
+ end
123
+
124
+ def run
125
+ @last_checked ||= Engine.now
126
+ while (sleep 0.5)
127
+ begin
128
+ now = Engine.now
129
+ if now - @last_checked >= @interval
130
+ flush(now)
131
+ @last_checked = now
132
+ end
133
+ rescue => e
134
+ log.warn "in_forward: #{e.class} #{e.message} #{e.backtrace.first}"
135
+ end
136
+ end
137
+ end
138
+
139
+ def flush(now)
140
+ times, sizes = [], []
141
+ @mutex.synchronize do
142
+ times = @times.dup
143
+ sizes = @sizes.dup
144
+ self.clear
145
+ end
146
+ if !times.empty? and !sizes.empty?
147
+ num = times.size
148
+ max = num == 0 ? 0 : times.max
149
+ avg = num == 0 ? 0 : times.map(&:to_f).inject(:+) / num.to_f
150
+ size_max = num == 0 ? 0 : sizes.max
151
+ size_avg = num == 0 ? 0 : sizes.map(&:to_f).inject(:+) / num.to_f
152
+ Engine.emit(@tag, now, {:num => num, :max => max, :avg => avg, :size_max => size_max, :size_avg => size_avg})
153
+ end
154
+ end
155
+ end
156
+
157
+ def listen
158
+ log.info "listening fluent socket on #{@bind}:#{@port}"
159
+ s = Coolio::TCPServer.new(@bind, @port, Handler, @linger_timeout, log, method(:on_message), @elapsed)
160
+ s.listen(@backlog) unless @backlog.nil?
161
+ s
162
+ end
163
+
164
+ #config_param :path, :string, :default => DEFAULT_SOCKET_PATH
165
+ #def listen
166
+ # if File.exist?(@path)
167
+ # File.unlink(@path)
168
+ # end
169
+ # FileUtils.mkdir_p File.dirname(@path)
170
+ # log.debug "listening fluent socket on #{@path}"
171
+ # Coolio::UNIXServer.new(@path, Handler, method(:on_message))
172
+ #end
173
+
174
+ def run
175
+ @loop.run
176
+ rescue => e
177
+ log.error "unexpected error", :error => e, :error_class => e.class
178
+ log.error_backtrace
179
+ end
180
+
181
+ protected
182
+ # message Entry {
183
+ # 1: long time
184
+ # 2: object record
185
+ # }
186
+ #
187
+ # message Forward {
188
+ # 1: string tag
189
+ # 2: list<Entry> entries
190
+ # }
191
+ #
192
+ # message PackedForward {
193
+ # 1: string tag
194
+ # 2: raw entries # msgpack stream of Entry
195
+ # }
196
+ #
197
+ # message Message {
198
+ # 1: string tag
199
+ # 2: long? time
200
+ # 3: object record
201
+ # }
202
+ def on_message(msg)
203
+ if msg.nil?
204
+ # for future TCP heartbeat_request
205
+ return
206
+ end
207
+
208
+ # TODO format error
209
+ tag = msg[0].to_s
210
+ entries = msg[1]
211
+
212
+ if entries.class == String
213
+ # PackedForward
214
+ bytesize = tag.bytesize + entries.bytesize
215
+ measure_time(:on_message, bytesize) do
216
+ es = MessagePackEventStream.new(entries, @cached_unpacker)
217
+ Engine.emit_stream(tag, es)
218
+ end
219
+
220
+ elsif entries.class == Array
221
+ # Forward
222
+ es = MultiEventStream.new
223
+ entries.each {|e|
224
+ record = e[1]
225
+ next if record.nil?
226
+ time = e[0].to_i
227
+ time = (now ||= Engine.now) if time == 0
228
+ es.add(time, record)
229
+ }
230
+ measure_time(:on_message, 0) do
231
+ Engine.emit_stream(tag, es)
232
+ end
233
+
234
+ else
235
+ # Message
236
+ record = msg[2]
237
+ return if record.nil?
238
+ time = msg[1]
239
+ time = Engine.now if time == 0
240
+ bytesize = time.size + record.to_s.bytesize
241
+ measure_time(:on_message, bytesize) do
242
+ Engine.emit(tag, time, record)
243
+ end
244
+ end
245
+ end
246
+
247
+ def measure_time(caller, size)
248
+ @elapsed ? @elapsed.measure_time(caller, size) { yield } : yield
249
+ end
250
+
251
+ class Handler < Coolio::Socket
252
+ def initialize(io, linger_timeout, log, on_message, elapsed)
253
+ super(io)
254
+ if io.is_a?(TCPSocket)
255
+ opt = [1, linger_timeout].pack('I!I!') # { int l_onoff; int l_linger; }
256
+ io.setsockopt(Socket::SOL_SOCKET, Socket::SO_LINGER, opt)
257
+ end
258
+ @on_message = on_message
259
+ @log = log
260
+ @log.trace {
261
+ remote_port, remote_addr = *Socket.unpack_sockaddr_in(@_io.getpeername) rescue nil
262
+ "accepted fluent socket from '#{remote_addr}:#{remote_port}': object_id=#{self.object_id}"
263
+ }
264
+ @elapsed = elapsed
265
+ end
266
+
267
+ def on_connect
268
+ end
269
+
270
+ def on_read(data)
271
+ first = data[0]
272
+ if first == '{' || first == '['
273
+ m = method(:on_read_json)
274
+ @y = Yajl::Parser.new
275
+ @y.on_parse_complete = @on_message
276
+ else
277
+ m = method(:on_read_msgpack)
278
+ @u = MessagePack::Unpacker.new
279
+ end
280
+
281
+ (class << self; self; end).module_eval do
282
+ define_method(:on_read, m)
283
+ end
284
+ m.call(data)
285
+ end
286
+
287
+ def measure_time(caller, size)
288
+ @elapsed ? @elapsed.measure_time(caller, size) { yield } : yield
289
+ end
290
+
291
+ def on_read_json(data)
292
+ measure_time(:on_read, data.bytesize) do
293
+ @y << data
294
+ end
295
+ rescue => e
296
+ @log.error "forward error", :error => e, :error_class => e.class
297
+ @log.error_backtrace
298
+ close
299
+ end
300
+
301
+ def on_read_msgpack(data)
302
+ measure_time(:on_read, data.bytesize) do
303
+ @u.feed_each(data, &@on_message)
304
+ end
305
+ rescue => e
306
+ @log.error "forward error", :error => e, :error_class => e.class
307
+ @log.error_backtrace
308
+ close
309
+ end
310
+
311
+ def on_close
312
+ @log.trace { "closed fluent socket object_id=#{self.object_id}" }
313
+ end
314
+ end
315
+
316
+ class HeartbeatRequestHandler < Coolio::IO
317
+ def initialize(io, callback)
318
+ super(io)
319
+ @io = io
320
+ @callback = callback
321
+ end
322
+
323
+ def on_readable
324
+ begin
325
+ msg, addr = @io.recvfrom(1024)
326
+ rescue Errno::EAGAIN, Errno::EWOULDBLOCK, Errno::EINTR
327
+ return
328
+ end
329
+ host = addr[3]
330
+ port = addr[1]
331
+ @callback.call(host, port, msg)
332
+ rescue
333
+ # TODO log?
334
+ end
335
+ end
336
+
337
+ def on_heartbeat_request(host, port, msg)
338
+ #log.trace "heartbeat request from #{host}:#{port}"
339
+ begin
340
+ @usock.send "\0", 0, host, port
341
+ rescue Errno::EAGAIN, Errno::EWOULDBLOCK, Errno::EINTR
342
+ end
343
+ end
344
+ end
345
+ end
@@ -0,0 +1,25 @@
1
+ # encoding: utf-8
2
+ $:.push File.expand_path('../lib', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.name = "fluent-plugin-measure_time"
6
+ gem.version = "0.1.0"
7
+ gem.authors = ["Naotoshi Seo"]
8
+ gem.email = "sonots@gmail.com"
9
+ gem.homepage = "https://github.com/sonots/fluent-plugin-measure_time"
10
+ gem.description = "Fluentd plugin to measure elapsed time to process messages"
11
+ gem.summary = gem.description
12
+ gem.licenses = ["MIT"]
13
+ gem.has_rdoc = false
14
+
15
+ gem.files = `git ls-files`.split("\n")
16
+ gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
17
+ gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
18
+ gem.require_paths = ['lib']
19
+
20
+ gem.add_dependency "fluentd", "~> 0.10.17"
21
+ gem.add_development_dependency "rake"
22
+ gem.add_development_dependency "rspec"
23
+ gem.add_development_dependency "pry"
24
+ gem.add_development_dependency "pry-nav"
25
+ end
@@ -0,0 +1,135 @@
1
+ require 'fluent/input'
2
+
3
+ module Fluent
4
+ class MeasureTimeInput < Input
5
+ Plugin.register_input('measure_time', self)
6
+
7
+ def configure(conf)
8
+ ::Fluent::Input.__send__(:include, MeasureTimable)
9
+ ::Fluent::Output.__send__(:include, MeasureTimable)
10
+ end
11
+ end
12
+
13
+ module MeasureTimable
14
+ def self.included(klass)
15
+ unless klass.method_defined?(:configure_without_measure_time)
16
+ klass.__send__(:alias_method, :configure_without_measure_time, :configure)
17
+ klass.__send__(:alias_method, :configure, :configure_with_measure_time)
18
+ end
19
+ end
20
+
21
+ attr_reader :measure_time
22
+
23
+ def configure_with_measure_time(conf)
24
+ configure_without_measure_time(conf)
25
+ if element = conf.elements.select { |element| element.name == 'measure_time' }.first
26
+ @measure_time = MeasureTime.new(self, log)
27
+ @measure_time.configure(element)
28
+ end
29
+ end
30
+ end
31
+
32
+ class MeasureTime
33
+ attr_reader :plugin, :log, :times, :mutex, :thread, :tag, :interval, :hook
34
+ def initialize(plugin, log)
35
+ @plugin = plugin
36
+ @klass = @plugin.class
37
+ @log = log
38
+ @times = []
39
+ @mutex = Mutex.new
40
+ end
41
+
42
+ def configure(conf)
43
+ @tag = conf['tag'] || 'measure_time'
44
+ unless @hook = conf['hook']
45
+ raise Fluent::ConfigError, '`hook` option must be specified in <measure_time></measure_time> directive'
46
+ end
47
+ @hook_msg = {"class" => @klass, "hook" => @hook, "object_id" => @plugin.object_id}
48
+ @interval = conf['interval'].to_i if conf['interval']
49
+ @add_or_emit_proc =
50
+ if @interval
51
+ # add to calculate statistics in each interval
52
+ Proc.new {|elapsed|
53
+ @mutex.synchronize { @times << elapsed }
54
+ }
55
+ else
56
+ # emit information immediately
57
+ Proc.new {|elapsed|
58
+ msg = {"time" => elapsed}.merge(@hook_msg)
59
+ ::Fluent::Engine.emit(@tag, ::Fluent::Engine.now, msg)
60
+ }
61
+ end
62
+ apply_hook
63
+ end
64
+
65
+ def apply_hook
66
+ @plugin.instance_eval <<EOF
67
+ def #{@hook}(*args)
68
+ measure_time.measure_time do
69
+ super
70
+ end
71
+ end
72
+ def start
73
+ super
74
+ measure_time.start
75
+ end
76
+ def stop
77
+ super
78
+ measure_time.stop
79
+ end
80
+ EOF
81
+ end
82
+
83
+ def measure_time
84
+ started = Time.now
85
+ output = yield
86
+ elapsed = (Time.now - started).to_f
87
+ log.debug "elapsed time at #{@klass}##{@hook} is #{elapsed} sec"
88
+ @add_or_emit_proc.call(elapsed)
89
+ output
90
+ end
91
+
92
+ def start
93
+ return unless @interval
94
+ @thread = Thread.new(&method(:run))
95
+ end
96
+
97
+ def stop
98
+ return unless @interval
99
+ @thread.terminate
100
+ @thread.join
101
+ end
102
+
103
+ def run
104
+ @last_checked ||= Engine.now
105
+ while (sleep 0.5)
106
+ begin
107
+ now = Engine.now
108
+ if now - @last_checked >= @interval
109
+ flush(now)
110
+ @last_checked = now
111
+ end
112
+ rescue => e
113
+ log.warn "in_measure_time: hook #{@klass}##{@hook} #{e.class} #{e.message} #{e.backtrace.first}"
114
+ end
115
+ end
116
+ end
117
+
118
+ def flush(now)
119
+ times = []
120
+ @mutex.synchronize do
121
+ times = @times.dup
122
+ @times.clear
123
+ end
124
+ triple = nil
125
+ unless times.empty?
126
+ num = times.size
127
+ max = num == 0 ? 0 : times.max
128
+ avg = num == 0 ? 0 : times.map(&:to_f).inject(:+) / num.to_f
129
+ triple = [@tag, now, {:max => max, :avg => avg, :num => num}.merge(@hook_msg)]
130
+ Engine.emit(*triple)
131
+ end
132
+ triple
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,129 @@
1
+ # encoding: UTF-8
2
+ require_relative 'spec_helper'
3
+ require 'fluent/plugin/in_forward'
4
+ require 'fluent/plugin/out_stdout'
5
+
6
+ describe Fluent::MeasureTimeInput do
7
+ before { Fluent::Test.setup }
8
+
9
+ def create_driver(conf=%[])
10
+ Fluent::Test::InputTestDriver.new(Fluent::MeasureTimeInput).configure(conf)
11
+ end
12
+
13
+ describe 'test configure' do
14
+ it { expect { create_driver }.not_to raise_error }
15
+ end
16
+ end
17
+
18
+ describe "extends Fluent::ForwardInput" do
19
+ before { Fluent::Test.setup }
20
+
21
+ def create_driver(conf=CONFIG)
22
+ Fluent::Test::InputTestDriver.new(Fluent::ForwardInput).configure(conf)
23
+ end
24
+
25
+ def connect
26
+ TCPSocket.new('127.0.0.1', PORT)
27
+ end
28
+
29
+ def send_data(data)
30
+ io = connect
31
+ begin
32
+ io.write data
33
+ ensure
34
+ io.close
35
+ end
36
+ end
37
+
38
+ def self.unused_port
39
+ s = TCPServer.open(0)
40
+ port = s.addr[1]
41
+ s.close
42
+ port
43
+ end
44
+
45
+ PORT = unused_port
46
+ CONFIG = %[
47
+ port #{PORT}
48
+ bind 127.0.0.1
49
+ ]
50
+
51
+ let(:driver) { create_driver(config) }
52
+
53
+ describe 'test configure' do
54
+ let(:config) {CONFIG + %[
55
+ <measure>
56
+ tag test
57
+ interval 10
58
+ hook on_message
59
+ </measure>
60
+ ]}
61
+ let(:subject) { driver.instance.measure }
62
+ its(:tag) { should == 'test' }
63
+ its(:interval) { should == 10 }
64
+ its(:hook) { should == 'on_message' }
65
+ end
66
+
67
+ describe 'test emit' do
68
+ let(:config) {CONFIG + %[
69
+ <measure>
70
+ tag measure
71
+ interval 1
72
+ # hook Fluent::ForwardInput::Handler.on_read # not support inner class yet
73
+ hook Fluent::ForwardInput.on_message
74
+ </measure>
75
+ ]}
76
+ it 'should flush' do
77
+ d = driver.instance
78
+ d.__send__(:on_message, ['tag1', 0, {'a'=>1}].to_msgpack)
79
+ triple = d.measure.flush(0)
80
+ triple[0].should == 'measure'
81
+ triple[2].keys.should =~ [:num, :max, :avg]
82
+ end
83
+ end
84
+ end
85
+
86
+ describe "extends Fluent::StdoutOutput" do
87
+ before { Fluent::Test.setup }
88
+
89
+ def create_driver(conf=CONFIG, tag = 'test')
90
+ Fluent::Test::OutputTestDriver.new(Fluent::StdoutOutput, tag).configure(conf)
91
+ end
92
+
93
+ CONFIG = %[
94
+ ]
95
+
96
+ let(:driver) { create_driver(config) }
97
+
98
+ describe 'test configure' do
99
+ let(:config) {CONFIG + %[
100
+ <measure>
101
+ tag test
102
+ interval 10
103
+ hook emit
104
+ </measure>
105
+ ]}
106
+ let(:subject) { driver.instance.instance_variable_get(:@measure) }
107
+ its(:tag) { should == 'test' }
108
+ its(:interval) { should == 10 }
109
+ its(:hook) { should == 'emit' }
110
+ end
111
+
112
+ describe 'test emit' do
113
+ let(:config) {CONFIG + %[
114
+ <measure>
115
+ tag measure
116
+ interval 1
117
+ hook emit
118
+ </measure>
119
+ ]}
120
+ it 'should flush' do
121
+ d = driver.instance
122
+ d.emit('tag1', Fluent::OneEventStream.new(0, {'a'=>1}), Fluent::NullOutputChain.instance)
123
+ triple = d.measure.flush(0)
124
+ triple[0].should == 'measure'
125
+ triple[2].keys.should =~ [:num, :max, :avg]
126
+ end
127
+ end
128
+ end
129
+
@@ -0,0 +1,14 @@
1
+ # encoding: UTF-8
2
+ require 'rubygems'
3
+ require 'bundler'
4
+ require 'fluent/load'
5
+ Bundler.setup(:default, :test)
6
+ Bundler.require(:default, :test)
7
+
8
+ require 'fluent/test'
9
+ require 'rspec'
10
+ require 'pry'
11
+
12
+ $TESTING=true
13
+ $:.unshift File.join(File.dirname(__FILE__), '..', 'lib')
14
+ require 'fluent/plugin/in_measure_time'
metadata ADDED
@@ -0,0 +1,135 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fluent-plugin-measure_time
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Naotoshi Seo
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-04-12 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: fluentd
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: 0.10.17
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: 0.10.17
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: rspec
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: pry
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: pry-nav
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ description: Fluentd plugin to measure elapsed time to process messages
84
+ email: sonots@gmail.com
85
+ executables: []
86
+ extensions: []
87
+ extra_rdoc_files: []
88
+ files:
89
+ - .gitignore
90
+ - .rspec
91
+ - .travis.yml
92
+ - CHANGELOG.md
93
+ - Gemfile
94
+ - LICENSE
95
+ - README.md
96
+ - Rakefile
97
+ - benchmark/Gemfile
98
+ - benchmark/README.md
99
+ - benchmark/agent.conf
100
+ - benchmark/dummer.conf
101
+ - benchmark/hooked_in_forward.conf
102
+ - benchmark/in_forward.conf
103
+ - benchmark/patched_in_forward.conf
104
+ - benchmark/plugin/in_forward.rb
105
+ - fluent-plugin-measure_time.gemspec
106
+ - lib/fluent/plugin/in_measure_time.rb
107
+ - spec/in_measure_time_spec.rb
108
+ - spec/spec_helper.rb
109
+ homepage: https://github.com/sonots/fluent-plugin-measure_time
110
+ licenses:
111
+ - MIT
112
+ metadata: {}
113
+ post_install_message:
114
+ rdoc_options: []
115
+ require_paths:
116
+ - lib
117
+ required_ruby_version: !ruby/object:Gem::Requirement
118
+ requirements:
119
+ - - '>='
120
+ - !ruby/object:Gem::Version
121
+ version: '0'
122
+ required_rubygems_version: !ruby/object:Gem::Requirement
123
+ requirements:
124
+ - - '>='
125
+ - !ruby/object:Gem::Version
126
+ version: '0'
127
+ requirements: []
128
+ rubyforge_project:
129
+ rubygems_version: 2.0.3
130
+ signing_key:
131
+ specification_version: 4
132
+ summary: Fluentd plugin to measure elapsed time to process messages
133
+ test_files:
134
+ - spec/in_measure_time_spec.rb
135
+ - spec/spec_helper.rb