fluentd 0.10.4 → 0.10.5

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of fluentd might be problematic. Click here for more details.

@@ -0,0 +1,143 @@
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 ExecFilterOutput < Output
22
+ Plugin.register_output('exec_filter', self)
23
+
24
+ def initialize
25
+ super
26
+ end
27
+
28
+ config_param :command, :string
29
+ config_param :in_keys, :string
30
+ config_param :out_keys, :string
31
+ config_param :tag, :string, :default => nil
32
+ config_param :tag_key, :string, :default => nil
33
+ config_param :time_key, :string, :default => nil
34
+ config_param :time_format, :string, :default => nil
35
+ config_param :localtime, :bool, :default => true
36
+
37
+ def configure(conf)
38
+ super
39
+
40
+ if localtime = conf['localtime']
41
+ @localtime = true
42
+ elsif utc = conf['utc']
43
+ @localtime = false
44
+ end
45
+
46
+ if !@tag && !@tag_key
47
+ raise ConfigError, "'tag' or 'tag_key' option is required on exec_filter output"
48
+ end
49
+
50
+ @in_keys = @in_keys.split(',')
51
+ @out_keys = @out_keys.split(',')
52
+
53
+ if @time_key
54
+ if @time_format
55
+ f = @time_format
56
+ tf = TimeFormatter.new(f, @localtime)
57
+ @time_format_proc = tf.method(:format)
58
+ @time_parse_proc = Proc.new {|str| Time.strptime(str, f).to_i }
59
+ else
60
+ @time_format_proc = Proc.new {|time| time.to_s }
61
+ @time_parse_proc = Proc.new {|str| str.to_i }
62
+ end
63
+ end
64
+ end
65
+
66
+ def start
67
+ @io = IO.popen(@command, "r+")
68
+ @pid = @io.pid
69
+ @io.sync = true
70
+ @thread = Thread.new(&method(:run))
71
+ end
72
+
73
+ def shutdown
74
+ Process.kill(:TERM, @pid)
75
+ if @thread.join(60) # TODO wait time
76
+ return
77
+ end
78
+ Process.kill(:KILL, @pid)
79
+ @thread.join
80
+ nil
81
+ end
82
+
83
+ def emit(tag, es, chain)
84
+ out = ''
85
+ es.each {|time,record|
86
+ last = @in_keys.length-1
87
+ for i in 0..last
88
+ key = @in_keys[i]
89
+ if key == @time_key
90
+ out << @time_format_proc.call(time)
91
+ elsif key == @tag_key
92
+ out << tag
93
+ else
94
+ out << record[key].to_s
95
+ end
96
+ out << "\t" if i != last
97
+ end
98
+ out << "\n"
99
+ }
100
+ @io.write out
101
+ chain.next
102
+ end
103
+
104
+ def run
105
+ @io.each_line {|line|
106
+ begin
107
+ line.chomp!
108
+ vals = line.split("\t")
109
+
110
+ tag = @tag
111
+ time = nil
112
+ record = {}
113
+ for i in 0..@out_keys.length-1
114
+ key = @out_keys[i]
115
+ val = vals[i]
116
+ if key == @time_key
117
+ time = @time_parse_proc.call(val)
118
+ elsif key == @tag_key
119
+ tag = val
120
+ else
121
+ record[key] = val
122
+ end
123
+ end
124
+
125
+ if tag
126
+ time ||= Engine.now
127
+ Engine.emit(tag, time, record)
128
+ end
129
+ rescue
130
+ $log.error "exec_filter failed to emit", :error=>$!, :line=>line
131
+ $log.warn_backtrace $!.backtrace
132
+ end
133
+ }
134
+ Process.waitpid(@pid)
135
+ rescue
136
+ $log.error "exec_filter process exited", :error=>$!
137
+ $log.warn_backtrace $!.backtrace
138
+ end
139
+ end
140
+
141
+
142
+ end
143
+
@@ -0,0 +1,356 @@
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 ForwardOutput < BufferedOutput
22
+ Plugin.register_output('forward', self)
23
+
24
+ def initialize
25
+ super
26
+ require 'socket'
27
+ require 'fileutils'
28
+ @nodes = {} #=> {sockaddr => Node}
29
+ end
30
+
31
+ config_param :send_timeout, :time, :default => 60
32
+ config_param :heartbeat_interval, :time, :default => 1
33
+ config_param :recover_wait, :time, :default => 10
34
+ config_param :hard_timeout, :time, :default => nil
35
+ config_param :phi_threshold, :integer, :default => 8
36
+ attr_reader :nodes
37
+
38
+ # backward compatibility
39
+ config_param :port, :integer, :default => DEFAULT_LISTEN_PORT
40
+ config_param :host, :string, :default => nil
41
+
42
+ def configure(conf)
43
+ super
44
+
45
+ # backward compatibility
46
+ if host = conf['host']
47
+ $log.warn "'host' option in forward output is obsoleted. Use '<server> host xxx </server>' instead."
48
+ port = conf['port']
49
+ port = port ? port.to_i : DEFAULT_LISTEN_PORT
50
+ e = conf.add_element('server')
51
+ e['host'] = host
52
+ e['port'] = port.to_s
53
+ end
54
+
55
+ @hard_timeout ||= @send_timeout
56
+
57
+ recover_sample_size = @recover_wait / @heartbeat_interval
58
+
59
+ conf.elements.each {|e|
60
+ next if e.name != "server"
61
+
62
+ host = e['host']
63
+ port = e['port']
64
+ port = port ? port.to_i : DEFAULT_LISTEN_PORT
65
+
66
+ weight = e['weight']
67
+ weight = weight ? weight.to_i : 60
68
+
69
+ name = e['name']
70
+ unless name
71
+ name = "#{host}:#{port}"
72
+ end
73
+
74
+ failure = FailureDetector.new(@heartbeat_interval.to_f/2, @hard_timeout)
75
+ sockaddr = Socket.pack_sockaddr_in(port, host)
76
+ port, host = Socket.unpack_sockaddr_in(sockaddr)
77
+ @nodes[sockaddr] = Node.new(name, host, port, weight, failure,
78
+ @phi_threshold, recover_sample_size)
79
+ $log.info "adding forwarding server '#{name}'", :host=>host, :port=>port, :weight=>weight
80
+ }
81
+ end
82
+
83
+ def start
84
+ super
85
+
86
+ @weight_array = []
87
+ gcd = @nodes.values.map {|n| n.weight }.inject(0) {|r,w| r.gcd(w) }
88
+ @nodes.each_value {|n|
89
+ (n.weight / gcd).times {
90
+ @weight_array << n
91
+ }
92
+ }
93
+ @weight_array.sort_by { rand }
94
+
95
+ @rr = 0
96
+
97
+ @loop = Coolio::Loop.new
98
+
99
+ @usock = UDPSocket.new
100
+ @hb = HeartbeatHandler.new(@usock, method(:on_heartbeat))
101
+ @loop.attach(@hb)
102
+
103
+ @timer = HeartbeatRequestTimer.new(@heartbeat_interval, method(:on_timer))
104
+ @loop.attach(@timer)
105
+
106
+ @thread = Thread.new(&method(:run))
107
+ end
108
+
109
+ def shutdown
110
+ @finished = true
111
+ @usock.close
112
+ @loop.stop
113
+ @thread.join
114
+ end
115
+
116
+ def run
117
+ @loop.run
118
+ rescue
119
+ $log.error "unexpected error", :error=>$!.to_s
120
+ $log.error_backtrace
121
+ end
122
+
123
+ # override BufferedOutput#emit
124
+ def emit(tag, es, chain)
125
+ data = es.to_msgpack_stream
126
+ if @buffer.emit(tag, data, chain) # use key = tag
127
+ submit_flush
128
+ end
129
+ end
130
+
131
+ def write(chunk)
132
+ wlen = @weight_array.length
133
+ wlen.times do
134
+ node = @weight_array[@rr]
135
+ @rr = (@rr + 1) % wlen
136
+
137
+ if node.available?
138
+ send_data(node, chunk)
139
+ return
140
+ end
141
+ end
142
+
143
+ raise "no nodes are available" # TODO message
144
+ end
145
+
146
+ private
147
+ # MessagePack FixArray length = 2
148
+ FORWARD_HEADER = [0x92].pack('C')
149
+
150
+ def send_data(node, chunk)
151
+ sock = connect(node)
152
+ begin
153
+ opt = [1, @send_timeout.to_i].pack('I!I!') # { int l_onoff; int l_linger; }
154
+ sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_LINGER, opt)
155
+
156
+ opt = [@send_timeout.to_i, 0].pack('L!L!') # struct timeval
157
+ sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_SNDTIMEO, opt)
158
+
159
+ # beginArray(2)
160
+ sock.write FORWARD_HEADER
161
+
162
+ # writeRaw(tag)
163
+ sock.write chunk.key.to_msgpack # tag
164
+
165
+ # beginRaw(size)
166
+ sz = chunk.size
167
+ #if sz < 32
168
+ # # FixRaw
169
+ # sock.write [0xa0 | sz].pack('C')
170
+ #elsif sz < 65536
171
+ # # raw 16
172
+ # sock.write [0xda, sz].pack('Cn')
173
+ #else
174
+ # raw 32
175
+ sock.write [0xdb, sz].pack('CN')
176
+ #end
177
+
178
+ # writeRawBody(packed_es)
179
+ chunk.write_to(sock)
180
+ ensure
181
+ sock.close
182
+ end
183
+ end
184
+
185
+ def connect(node)
186
+ # TODO unix socket?
187
+ TCPSocket.new(node.host, node.port)
188
+ end
189
+
190
+ class HeartbeatRequestTimer < Coolio::TimerWatcher
191
+ def initialize(interval, callback)
192
+ super(interval, true)
193
+ @callback = callback
194
+ end
195
+
196
+ def on_timer
197
+ @callback.call
198
+ end
199
+ end
200
+
201
+ def on_timer
202
+ return if @finished
203
+ @nodes.each_pair {|sockaddr,n|
204
+ n.tick
205
+ @usock.send "", 0, sockaddr
206
+ }
207
+ end
208
+
209
+ class HeartbeatHandler < Coolio::IO
210
+ def initialize(io, callback)
211
+ super(io)
212
+ @io = io
213
+ @callback = callback
214
+ end
215
+
216
+ def on_readable
217
+ msg, addr = @io.recvfrom(1024)
218
+ host = addr[3]
219
+ port = addr[1]
220
+ sockaddr = Socket.pack_sockaddr_in(port, host)
221
+ @callback.call(sockaddr, msg)
222
+ end
223
+ end
224
+
225
+ def on_heartbeat(sockaddr, msg)
226
+ if node = @nodes[sockaddr]
227
+ $log.trace "heartbeat from '#{node.name}'", :host=>node.host, :port=>node.port
228
+ node.heartbeat
229
+ end
230
+ end
231
+
232
+ class Node
233
+ def initialize(name, host, port, weight, failure,
234
+ phi_threshold, recover_sample_size)
235
+ @name = name
236
+ @host = host
237
+ @port = port
238
+ @weight = weight
239
+ @failure = failure
240
+ @phi_threshold = phi_threshold
241
+ @recover_sample_size = recover_sample_size
242
+ @available = true
243
+ end
244
+
245
+ attr_reader :name, :host, :port
246
+ attr_reader :weight
247
+ attr_writer :available
248
+
249
+ def available?
250
+ @available
251
+ end
252
+
253
+ def tick
254
+ now = Time.now.to_f
255
+ phi = @failure.phi(now)
256
+ $log.trace "phi '#{@name}'", :host=>@host, :port=>@port, :phi=>phi
257
+ if phi > @phi_threshold
258
+ $log.info "detached forwarding server '#{@name}'", :host=>@host, :port=>@port, :phi=>phi
259
+ @available = false
260
+ @failure.clear
261
+ end
262
+ end
263
+
264
+ def heartbeat
265
+ now = Time.now.to_f
266
+ @failure.add(now)
267
+ if !@available && @failure.sample_size > @recover_sample_size &&
268
+ @failure.phi(now) <= @phi_threshold
269
+ $log.info "recovered forwarding server '#{@name}'", :host=>@host, :port=>@port
270
+ @available = true
271
+ end
272
+ end
273
+
274
+ def to_msgpack(out = '')
275
+ [@host, @port, @weight, @available].to_msgpack(out)
276
+ end
277
+ end
278
+
279
+ class FailureDetector
280
+ PHI_FACTOR = 1.0 / Math.log(10.0)
281
+ SAMPLE_SIZE = 1000
282
+
283
+ def initialize(init_int, hard_timeout)
284
+ @last = 0
285
+ @init_int = init_int
286
+ @hard_timeout = hard_timeout
287
+ @window = []
288
+ @failure = false
289
+ end
290
+
291
+ def add(now)
292
+ if @last > 0
293
+ int = now - @last
294
+ else
295
+ int = @init_int
296
+ end
297
+ if int <= @hard_timeout
298
+ @window << int
299
+ @window.shift if @window.length > SAMPLE_SIZE
300
+ @last = now
301
+ true
302
+ else
303
+ false
304
+ end
305
+ end
306
+
307
+ def phi(now)
308
+ size = @window.size
309
+ return 0.0 if size == 0
310
+ t = now - @last
311
+ mean = @window.inject(0) {|r,v| r + v } / size
312
+ return PHI_FACTOR * t / mean
313
+ end
314
+
315
+ def sample_size
316
+ @window.size
317
+ end
318
+
319
+ def clear
320
+ @window.clear
321
+ @last = 0
322
+ end
323
+ end
324
+
325
+ ## TODO
326
+ #class RPC
327
+ # def initialize(this)
328
+ # @this = this
329
+ # end
330
+ #
331
+ # def list_nodes
332
+ # @this.nodes
333
+ # end
334
+ #
335
+ # def list_fault_nodes
336
+ # list_nodes.select {|n| !n.available? }
337
+ # end
338
+ #
339
+ # def list_available_nodes
340
+ # list_nodes.select {|n| n.available? }
341
+ # end
342
+ #
343
+ # def add_node(name, host, port, weight)
344
+ # end
345
+ #
346
+ # def recover_node(host, port)
347
+ # end
348
+ #
349
+ # def remove_node(host, port)
350
+ # end
351
+ #end
352
+ end
353
+
354
+
355
+ end
356
+