fluent-plugin-measure_time 0.1.0

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