fluentd 0.10.6 → 0.10.7
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.
- data/ChangeLog +9 -0
- data/README +57 -0
- data/Rakefile +42 -3
- data/VERSION +1 -1
- data/bin/fluent-cat +6 -0
- data/bin/fluent-gem +10 -0
- data/bin/fluentd +6 -0
- data/lib/fluent/buffer.rb +10 -4
- data/lib/fluent/config.rb +2 -0
- data/lib/fluent/engine.rb +32 -16
- data/lib/fluent/load.rb +1 -0
- data/lib/fluent/output.rb +215 -156
- data/lib/fluent/plugin/buf_file.rb +2 -1
- data/lib/fluent/plugin/in_forward.rb +4 -2
- data/lib/fluent/plugin/in_http.rb +15 -8
- data/lib/fluent/plugin/in_stream.rb +2 -1
- data/lib/fluent/plugin/in_tail.rb +133 -9
- data/lib/fluent/plugin/out_forward.rb +15 -9
- data/lib/fluent/process.rb +469 -0
- data/lib/fluent/version.rb +1 -1
- metadata +33 -38
- data/.gitignore +0 -20
- data/.rvmrc +0 -1
- data/Gemfile +0 -3
- data/Makefile.am +0 -50
- data/autogen.sh +0 -67
- data/configure.in +0 -52
- data/fluentd.gemspec +0 -40
@@ -50,10 +50,12 @@ class ForwardInput < Input
|
|
50
50
|
end
|
51
51
|
|
52
52
|
def shutdown
|
53
|
-
@
|
53
|
+
@loop.watchers.each {|w| w.detach }
|
54
54
|
@loop.stop
|
55
|
-
@thread.join
|
56
55
|
@usock.close
|
56
|
+
TCPSocket.open('127.0.0.1', @port) {|sock| } # FIXME @thread.join blocks without this line
|
57
|
+
@thread.join
|
58
|
+
@lsock.close
|
57
59
|
end
|
58
60
|
|
59
61
|
def listen
|
@@ -21,6 +21,8 @@ module Fluent
|
|
21
21
|
class HttpInput < Input
|
22
22
|
Plugin.register_input('http', self)
|
23
23
|
|
24
|
+
include DetachMultiProcessMixin
|
25
|
+
|
24
26
|
require 'http/parser'
|
25
27
|
|
26
28
|
def initialize
|
@@ -68,23 +70,28 @@ class HttpInput < Input
|
|
68
70
|
end
|
69
71
|
end
|
70
72
|
|
71
|
-
# TODO multithreading
|
72
73
|
def start
|
73
74
|
$log.debug "listening http on #{@bind}:#{@port}"
|
75
|
+
lsock = TCPServer.new(@bind, @port)
|
74
76
|
|
75
|
-
|
76
|
-
|
77
|
+
detach_multi_process do
|
78
|
+
super
|
79
|
+
@km = KeepaliveManager.new(@keepalive_timeout)
|
80
|
+
#@lsock = Coolio::TCPServer.new(@bind, @port, Handler, @km, method(:on_request), @body_size_limit)
|
81
|
+
@lsock = Coolio::TCPServer.new(lsock, nil, Handler, @km, method(:on_request), @body_size_limit)
|
77
82
|
|
78
|
-
|
79
|
-
|
80
|
-
|
83
|
+
@loop = Coolio::Loop.new
|
84
|
+
@loop.attach(@km)
|
85
|
+
@loop.attach(@lsock)
|
81
86
|
|
82
|
-
|
87
|
+
@thread = Thread.new(&method(:run))
|
88
|
+
end
|
83
89
|
end
|
84
90
|
|
85
91
|
def shutdown
|
86
|
-
@
|
92
|
+
@loop.watchers.each {|w| w.detach }
|
87
93
|
@loop.stop
|
94
|
+
@lsock.close
|
88
95
|
@thread.join
|
89
96
|
end
|
90
97
|
|
@@ -28,6 +28,7 @@ class TailInput < Input
|
|
28
28
|
|
29
29
|
config_param :path, :string
|
30
30
|
config_param :tag, :string
|
31
|
+
config_param :pos_file, :string, :default => nil
|
31
32
|
|
32
33
|
def configure(conf)
|
33
34
|
super
|
@@ -37,6 +38,12 @@ class TailInput < Input
|
|
37
38
|
raise ConfigError, "tail: 'path' parameter is required on tail input"
|
38
39
|
end
|
39
40
|
|
41
|
+
if @pos_file
|
42
|
+
@pf_file = File.open(@pos_file, File::RDWR|File::CREAT)
|
43
|
+
@pf_file.sync = true
|
44
|
+
@pf = PositionFile.parse(@pf_file)
|
45
|
+
end
|
46
|
+
|
40
47
|
configure_parser(conf)
|
41
48
|
end
|
42
49
|
|
@@ -47,16 +54,23 @@ class TailInput < Input
|
|
47
54
|
|
48
55
|
def start
|
49
56
|
@loop = Coolio::Loop.new
|
50
|
-
@paths.
|
57
|
+
handlers = @paths.map {|path|
|
51
58
|
$log.debug "following tail of #{path}"
|
52
|
-
@
|
59
|
+
pe = @pf ? @pf[path] : NullPositionEntry.instance
|
60
|
+
h = Handler.new(path, pe, method(:receive_lines))
|
61
|
+
@loop.attach h
|
62
|
+
}
|
63
|
+
handlers.each {|h|
|
64
|
+
h.on_change # initialize call
|
53
65
|
}
|
54
66
|
@thread = Thread.new(&method(:run))
|
55
67
|
end
|
56
68
|
|
57
69
|
def shutdown
|
70
|
+
@loop.watchers.each {|w| w.detach }
|
58
71
|
@loop.stop
|
59
72
|
@thread.join
|
73
|
+
@pf_file.close if @pf_file
|
60
74
|
end
|
61
75
|
|
62
76
|
def run
|
@@ -90,11 +104,20 @@ class TailInput < Input
|
|
90
104
|
return @parser.parse(line)
|
91
105
|
end
|
92
106
|
|
93
|
-
# seek to the end of file first.
|
94
|
-
# logs never duplicate but may be lost if fluent is down.
|
95
107
|
class Handler < Coolio::StatWatcher
|
96
|
-
def initialize(path, callback)
|
97
|
-
|
108
|
+
def initialize(path, pe, callback)
|
109
|
+
stat = File.lstat(path)
|
110
|
+
@pe = pe
|
111
|
+
@inode = stat.ino
|
112
|
+
if @inode == @pe.read_inode
|
113
|
+
# seek to the saved position
|
114
|
+
@pos = @pe.read_pos
|
115
|
+
else
|
116
|
+
# seek to the end of file first.
|
117
|
+
# logs never duplicate but may be lost if fluent is down.
|
118
|
+
@pos = stat.size
|
119
|
+
@pe.update(@inode, stat.size)
|
120
|
+
end
|
98
121
|
@buffer = ''
|
99
122
|
@callback = callback
|
100
123
|
super(path)
|
@@ -102,9 +125,13 @@ class TailInput < Input
|
|
102
125
|
|
103
126
|
def on_change
|
104
127
|
lines = []
|
128
|
+
inode = nil
|
105
129
|
|
106
130
|
File.open(path) {|f|
|
107
|
-
|
131
|
+
stat = f.lstat
|
132
|
+
inode = stat.ino
|
133
|
+
|
134
|
+
if @inode != inode || stat.size < @pos
|
108
135
|
# moved or deleted
|
109
136
|
@pos = 0
|
110
137
|
else
|
@@ -113,13 +140,13 @@ class TailInput < Input
|
|
113
140
|
|
114
141
|
line = f.gets
|
115
142
|
unless line
|
116
|
-
|
143
|
+
break
|
117
144
|
end
|
118
145
|
|
119
146
|
@buffer << line
|
120
147
|
unless line[line.length-1] == ?\n
|
121
148
|
@pos = f.pos
|
122
|
-
|
149
|
+
break
|
123
150
|
end
|
124
151
|
|
125
152
|
lines << @buffer
|
@@ -136,11 +163,108 @@ class TailInput < Input
|
|
136
163
|
@pos = f.pos
|
137
164
|
}
|
138
165
|
|
166
|
+
if @inode != inode
|
167
|
+
@pe.update(inode, @pos)
|
168
|
+
@inode = inode
|
169
|
+
else
|
170
|
+
@pe.update_pos(@pos)
|
171
|
+
end
|
172
|
+
|
139
173
|
@callback.call(lines)
|
140
174
|
|
141
175
|
rescue Errno::ENOENT
|
142
176
|
# moved or deleted
|
143
177
|
@pos = 0
|
178
|
+
# TODO rescue
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
# pos inode
|
183
|
+
# ffffffffffffffff\tffffffff\n
|
184
|
+
class PositionEntry
|
185
|
+
POS_SIZE = 16
|
186
|
+
INO_OFFSET = 17
|
187
|
+
INO_SIZE = 8
|
188
|
+
LN_OFFSET = 25
|
189
|
+
SIZE = 26
|
190
|
+
|
191
|
+
def initialize(file, seek)
|
192
|
+
@file = file
|
193
|
+
@seek = seek
|
194
|
+
end
|
195
|
+
|
196
|
+
def update(ino, pos)
|
197
|
+
@file.pos = @seek
|
198
|
+
@file.write "%016x\t%08x" % [pos, ino]
|
199
|
+
@inode = ino
|
200
|
+
end
|
201
|
+
|
202
|
+
def update_pos(pos)
|
203
|
+
@file.pos = @seek
|
204
|
+
@file.write "%016x" % pos
|
205
|
+
end
|
206
|
+
|
207
|
+
def read_inode
|
208
|
+
@file.pos = @seek + INO_OFFSET
|
209
|
+
@file.read(8).to_i(16)
|
210
|
+
end
|
211
|
+
|
212
|
+
def read_pos
|
213
|
+
@file.pos = @seek
|
214
|
+
@file.read(16).to_i(16)
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
class PositionFile
|
219
|
+
def initialize(file, map, last_pos)
|
220
|
+
@file = file
|
221
|
+
@map = map
|
222
|
+
@last_pos = last_pos
|
223
|
+
end
|
224
|
+
|
225
|
+
def [](path)
|
226
|
+
if m = @map[path]
|
227
|
+
return m
|
228
|
+
end
|
229
|
+
|
230
|
+
@file.pos = @last_pos
|
231
|
+
@file.write path
|
232
|
+
@file.write "\t"
|
233
|
+
seek = @file.pos
|
234
|
+
@file.write "0000000000000000\t00000000\n"
|
235
|
+
@last_pos = @file.pos
|
236
|
+
|
237
|
+
@map[path] = PositionEntry.new(@file, seek)
|
238
|
+
end
|
239
|
+
|
240
|
+
def self.parse(file)
|
241
|
+
map = {}
|
242
|
+
file.pos = 0
|
243
|
+
file.each_line {|line|
|
244
|
+
m = /^([^\t]+)\t([0-9a-fA-F]+)\t([0-9a-fA-F]+)/.match(line)
|
245
|
+
next unless m
|
246
|
+
path = m[1]
|
247
|
+
pos = m[2].to_i(16)
|
248
|
+
ino = m[3].to_i(16)
|
249
|
+
seek = file.pos - line.bytesize + path.bytesize + 1
|
250
|
+
map[path] = PositionEntry.new(file, seek)
|
251
|
+
}
|
252
|
+
new(file, map, file.pos)
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
class NullPositionEntry
|
257
|
+
require 'singleton'
|
258
|
+
include Singleton
|
259
|
+
def update(ino, pos)
|
260
|
+
end
|
261
|
+
def update_pos(pos)
|
262
|
+
end
|
263
|
+
def read_pos
|
264
|
+
0
|
265
|
+
end
|
266
|
+
def read_inode
|
267
|
+
0
|
144
268
|
end
|
145
269
|
end
|
146
270
|
end
|
@@ -18,7 +18,7 @@
|
|
18
18
|
module Fluent
|
19
19
|
|
20
20
|
|
21
|
-
class ForwardOutput <
|
21
|
+
class ForwardOutput < ObjectBufferedOutput
|
22
22
|
Plugin.register_output('forward', self)
|
23
23
|
|
24
24
|
def initialize
|
@@ -108,9 +108,10 @@ class ForwardOutput < BufferedOutput
|
|
108
108
|
|
109
109
|
def shutdown
|
110
110
|
@finished = true
|
111
|
-
@
|
111
|
+
@loop.watchers.each {|w| w.detach }
|
112
112
|
@loop.stop
|
113
113
|
@thread.join
|
114
|
+
@usock.close
|
114
115
|
end
|
115
116
|
|
116
117
|
def run
|
@@ -128,14 +129,14 @@ class ForwardOutput < BufferedOutput
|
|
128
129
|
end
|
129
130
|
end
|
130
131
|
|
131
|
-
def
|
132
|
+
def write_objects(tag, es)
|
132
133
|
wlen = @weight_array.length
|
133
134
|
wlen.times do
|
134
135
|
node = @weight_array[@rr]
|
135
136
|
@rr = (@rr + 1) % wlen
|
136
137
|
|
137
138
|
if node.available?
|
138
|
-
send_data(node,
|
139
|
+
send_data(node, tag, es)
|
139
140
|
return
|
140
141
|
end
|
141
142
|
end
|
@@ -147,7 +148,7 @@ class ForwardOutput < BufferedOutput
|
|
147
148
|
# MessagePack FixArray length = 2
|
148
149
|
FORWARD_HEADER = [0x92].pack('C')
|
149
150
|
|
150
|
-
def send_data(node,
|
151
|
+
def send_data(node, tag, es)
|
151
152
|
sock = connect(node)
|
152
153
|
begin
|
153
154
|
opt = [1, @send_timeout.to_i].pack('I!I!') # { int l_onoff; int l_linger; }
|
@@ -160,10 +161,10 @@ class ForwardOutput < BufferedOutput
|
|
160
161
|
sock.write FORWARD_HEADER
|
161
162
|
|
162
163
|
# writeRaw(tag)
|
163
|
-
sock.write
|
164
|
+
sock.write tag.to_msgpack # tag
|
164
165
|
|
165
166
|
# beginRaw(size)
|
166
|
-
sz =
|
167
|
+
sz = es.size
|
167
168
|
#if sz < 32
|
168
169
|
# # FixRaw
|
169
170
|
# sock.write [0xa0 | sz].pack('C')
|
@@ -176,7 +177,7 @@ class ForwardOutput < BufferedOutput
|
|
176
177
|
#end
|
177
178
|
|
178
179
|
# writeRawBody(packed_es)
|
179
|
-
|
180
|
+
es.write_to(sock)
|
180
181
|
ensure
|
181
182
|
sock.close
|
182
183
|
end
|
@@ -202,7 +203,12 @@ class ForwardOutput < BufferedOutput
|
|
202
203
|
return if @finished
|
203
204
|
@nodes.each_pair {|sockaddr,n|
|
204
205
|
n.tick
|
205
|
-
|
206
|
+
begin
|
207
|
+
@usock.send "", 0, sockaddr
|
208
|
+
rescue
|
209
|
+
# TODO log
|
210
|
+
$log.debug "failed to send heartbeat packet to #{Socket.unpack_sockaddr_in(sockaddr).reverse.join(':')}", :error=>$!
|
211
|
+
end
|
206
212
|
}
|
207
213
|
end
|
208
214
|
|
@@ -0,0 +1,469 @@
|
|
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 ProcessDetachInterrupt < StandardError
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
class DetachProcessManager
|
26
|
+
require 'singleton'
|
27
|
+
include Singleton
|
28
|
+
|
29
|
+
class Broker
|
30
|
+
def initialize
|
31
|
+
end
|
32
|
+
|
33
|
+
def engine
|
34
|
+
Engine
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def initialize
|
39
|
+
require 'drb'
|
40
|
+
DRb.start_service("drbunix:", Broker.new)
|
41
|
+
@parent_uri = DRb.uri
|
42
|
+
end
|
43
|
+
|
44
|
+
def fork(delegate_object)
|
45
|
+
ipr, ipw = IO.pipe
|
46
|
+
opr, opw = IO.pipe
|
47
|
+
|
48
|
+
pid = Process.fork
|
49
|
+
if pid
|
50
|
+
# parent process
|
51
|
+
ipw.close
|
52
|
+
opr.close
|
53
|
+
forward_thread = process_parent(ipr, opw, pid, delegate_object)
|
54
|
+
return pid, forward_thread
|
55
|
+
end
|
56
|
+
|
57
|
+
# child process
|
58
|
+
ipr.close
|
59
|
+
opw.close
|
60
|
+
forward_thread = process_child(ipw, opr, delegate_object)
|
61
|
+
return nil, forward_thread
|
62
|
+
end
|
63
|
+
|
64
|
+
def setup_delegate
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
def read_header(ipr)
|
69
|
+
sz = ipr.read(4).unpack('N')[0]
|
70
|
+
ipr.read(sz)
|
71
|
+
end
|
72
|
+
|
73
|
+
def send_header(ipw, data)
|
74
|
+
ipw.write [data.bytesize].pack('N')
|
75
|
+
ipw.write data
|
76
|
+
ipw.flush
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
def process_child(ipw, opr, delegate_object)
|
81
|
+
fwd = new_forwarder(ipw, 0.5) # TODO interval
|
82
|
+
Engine.define_singleton_method(:emit_stream) do |tag,es|
|
83
|
+
fwd.emit(tag, es)
|
84
|
+
end
|
85
|
+
|
86
|
+
DRb.start_service("drbunix:", delegate_object)
|
87
|
+
child_uri = DRb.uri
|
88
|
+
|
89
|
+
send_header(ipw, child_uri)
|
90
|
+
|
91
|
+
forward_thread = Thread.new(opr, delegate_object, &method(:output_forward_main))
|
92
|
+
|
93
|
+
override_shared_methods(@parent_uri)
|
94
|
+
|
95
|
+
return forward_thread
|
96
|
+
end
|
97
|
+
|
98
|
+
def override_shared_methods(parent_uri)
|
99
|
+
broker = DRbObject.new_with_uri(parent_uri)
|
100
|
+
shared_methods.each {|(broker_accessor,target,name)|
|
101
|
+
remote = broker.send(broker_accessor)
|
102
|
+
target.define_singleton_method(name) do |*args,&block|
|
103
|
+
remote.send(name, *args, &block)
|
104
|
+
end
|
105
|
+
}
|
106
|
+
end
|
107
|
+
|
108
|
+
def shared_methods
|
109
|
+
[
|
110
|
+
#[:engine, Engine, :flush!],
|
111
|
+
#[:engine, Engine, :stop],
|
112
|
+
]
|
113
|
+
end
|
114
|
+
|
115
|
+
def process_parent(ipr, opw, pid, delegate_object)
|
116
|
+
child_uri = read_header(ipr)
|
117
|
+
|
118
|
+
forward_thread = Thread.new(ipr, pid, &method(:input_forward_main))
|
119
|
+
|
120
|
+
#override_delegate_methods(delegate_object, child_uri)
|
121
|
+
|
122
|
+
fwd = new_forwarder(opw, 0.5) # TODO interval
|
123
|
+
#delegate_object.define_singleton_method(:emit) do |tag,es,chain|
|
124
|
+
# chain.next
|
125
|
+
# fwd.emit(tag, es)
|
126
|
+
#end
|
127
|
+
|
128
|
+
forward_thread.define_singleton_method(:forwarder) do
|
129
|
+
fwd
|
130
|
+
end
|
131
|
+
|
132
|
+
return forward_thread
|
133
|
+
end
|
134
|
+
|
135
|
+
#def override_delegate_methods(target, child_uri)
|
136
|
+
# remote = DRbObject.new_with_uri(child_uri)
|
137
|
+
# delegate_methods(target).each {|name|
|
138
|
+
# target.define_singleton_method(name) do |*args,&block|
|
139
|
+
# remote.send(name, *args, &block)
|
140
|
+
# end
|
141
|
+
# }
|
142
|
+
#end
|
143
|
+
#
|
144
|
+
#def delegate_methods(target)
|
145
|
+
# target.methods - Object.public_instance_methods
|
146
|
+
#end
|
147
|
+
|
148
|
+
def output_forward_main(opr, target)
|
149
|
+
read_event_stream(opr) {|tag,es|
|
150
|
+
# FIXME error
|
151
|
+
begin
|
152
|
+
target.emit(tag, es, NullOutputChain.instance)
|
153
|
+
rescue
|
154
|
+
$log.warn "failed to emit", :error=>$!.to_s, :pid=>Process.pid
|
155
|
+
$log.warn_backtrace
|
156
|
+
end
|
157
|
+
}
|
158
|
+
rescue
|
159
|
+
$log.error "error on output process forwarding thread", :error=>$!.to_s, :pid=>Process.pid
|
160
|
+
$log.error_backtrace
|
161
|
+
raise
|
162
|
+
end
|
163
|
+
|
164
|
+
def input_forward_main(ipr, pid)
|
165
|
+
read_event_stream(ipr) {|tag,es|
|
166
|
+
Engine.emit_stream(tag, es)
|
167
|
+
}
|
168
|
+
rescue
|
169
|
+
$log.error "error on input process forwarding thread", :error=>$!.to_s, :pid=>Process.pid
|
170
|
+
$log.error_backtrace
|
171
|
+
raise
|
172
|
+
end
|
173
|
+
|
174
|
+
def read_event_stream(r, &block)
|
175
|
+
u = MessagePack::Unpacker.new(r)
|
176
|
+
cached_unpacker = MessagePack::Unpacker.new
|
177
|
+
begin
|
178
|
+
#buf = ''
|
179
|
+
#map = {}
|
180
|
+
#while true
|
181
|
+
# r.readpartial(64*1024, buf)
|
182
|
+
# u.feed_each(buf) {|tag,ms|
|
183
|
+
# if msbuf = map[tag]
|
184
|
+
# msbuf << ms
|
185
|
+
# else
|
186
|
+
# map[tag] = ms
|
187
|
+
# end
|
188
|
+
# }
|
189
|
+
# unless map.empty?
|
190
|
+
# map.each_pair {|tag,ms|
|
191
|
+
# es = MessagePackEventStream.new(ms, cached_unpacker)
|
192
|
+
# block.call(tag, es)
|
193
|
+
# }
|
194
|
+
# map.clear
|
195
|
+
# end
|
196
|
+
#end
|
197
|
+
u.each {|tag,ms|
|
198
|
+
es = MessagePackEventStream.new(ms, cached_unpacker)
|
199
|
+
block.call(tag, es)
|
200
|
+
}
|
201
|
+
rescue EOFError
|
202
|
+
ensure
|
203
|
+
r.close
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
def new_forwarder(w, interval)
|
208
|
+
if interval < 0.2 # TODO interval
|
209
|
+
Forwarder.new(w)
|
210
|
+
else
|
211
|
+
DelayedForwarder.new(w, interval)
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
class Forwarder
|
216
|
+
def initialize(w)
|
217
|
+
@w = w
|
218
|
+
end
|
219
|
+
|
220
|
+
def emit(tag, es)
|
221
|
+
ms = es.to_msgpack_stream
|
222
|
+
#[tag, ms].to_msgpack(@w) # not thread safe
|
223
|
+
@w.write [tag, ms].to_msgpack
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
class DelayedForwarder
|
228
|
+
def initialize(w, interval)
|
229
|
+
@w = w
|
230
|
+
@interval = interval
|
231
|
+
@buffer = {}
|
232
|
+
Thread.new(&method(:run))
|
233
|
+
end
|
234
|
+
|
235
|
+
def emit(tag, es)
|
236
|
+
if ms = @buffer[tag]
|
237
|
+
ms << es.to_msgpack_stream
|
238
|
+
else
|
239
|
+
@buffer[tag] = es.to_msgpack_stream
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
def run
|
244
|
+
while true
|
245
|
+
sleep @interval
|
246
|
+
@buffer.keys.each {|tag|
|
247
|
+
if ms = @buffer.delete(tag)
|
248
|
+
[tag, ms].to_msgpack(@w)
|
249
|
+
#@w.write [tag, ms].to_msgpack
|
250
|
+
end
|
251
|
+
}
|
252
|
+
end
|
253
|
+
rescue
|
254
|
+
$log.error "error on forwerder thread", :error=>$!.to_s
|
255
|
+
$log.error_backtrace
|
256
|
+
raise
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
class MultiForwarder
|
261
|
+
def initialize(forwarders)
|
262
|
+
@forwarders = forwarders
|
263
|
+
@rr = 1
|
264
|
+
end
|
265
|
+
|
266
|
+
def emit(tag, es)
|
267
|
+
forwarder = @forwarders[@rr]
|
268
|
+
@rr = (@rr + 1) % @forwarders.length
|
269
|
+
forwarder.emit(tag, es)
|
270
|
+
end
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
|
275
|
+
module DetachProcessImpl
|
276
|
+
def on_detach_process(i)
|
277
|
+
end
|
278
|
+
|
279
|
+
protected
|
280
|
+
def detach_process_impl(num, &block)
|
281
|
+
children = []
|
282
|
+
|
283
|
+
num.times do |i|
|
284
|
+
pid, forward_thread = DetachProcessManager.instance.fork(self)
|
285
|
+
|
286
|
+
if pid
|
287
|
+
# parent process
|
288
|
+
$log.info "detached process", :class=>self.class, :pid=>pid
|
289
|
+
children << [pid, forward_thread]
|
290
|
+
next
|
291
|
+
end
|
292
|
+
|
293
|
+
# child process
|
294
|
+
begin
|
295
|
+
on_detach_process(i)
|
296
|
+
|
297
|
+
block.call
|
298
|
+
|
299
|
+
# disable Engine.stop called by signal handler
|
300
|
+
Engine.define_singleton_method(:stop) do
|
301
|
+
# do nothing
|
302
|
+
end
|
303
|
+
|
304
|
+
# override signal handlers called by parent process
|
305
|
+
fin = FinishWait.new
|
306
|
+
trap :INT do
|
307
|
+
fin.stop
|
308
|
+
end
|
309
|
+
trap :TERM do
|
310
|
+
fin.stop
|
311
|
+
end
|
312
|
+
#forward_thread.join
|
313
|
+
fin.wait
|
314
|
+
|
315
|
+
exit! 0
|
316
|
+
ensure
|
317
|
+
$log.error "unknown error while shutting down this child process", :error=>$!.to_s, :pid=>Process.pid
|
318
|
+
$log.error_backtrace
|
319
|
+
end
|
320
|
+
|
321
|
+
exit! 1
|
322
|
+
end
|
323
|
+
|
324
|
+
# parent process
|
325
|
+
# override shutdown method
|
326
|
+
define_singleton_method(:shutdown) do
|
327
|
+
begin
|
328
|
+
children.each {|pair|
|
329
|
+
pid = pair[0]
|
330
|
+
forward_thread = pair[1]
|
331
|
+
if pid
|
332
|
+
Process.kill(:TERM, pid)
|
333
|
+
forward_thread.join
|
334
|
+
Process.waitpid(pid)
|
335
|
+
pair[0] = nil
|
336
|
+
end
|
337
|
+
}
|
338
|
+
rescue
|
339
|
+
$log.error "unknown error while shutting down remote child process", :error=>$!.to_s
|
340
|
+
$log.error_backtrace
|
341
|
+
end
|
342
|
+
end
|
343
|
+
|
344
|
+
# override emit method
|
345
|
+
forwarders = children.map {|pair| pair[1].forwarder }
|
346
|
+
if forwarders.length > 1
|
347
|
+
fwd = DetachProcessManager::MultiForwarder.new(forwarders)
|
348
|
+
else
|
349
|
+
fwd = forwarders[0]
|
350
|
+
end
|
351
|
+
define_singleton_method(:emit) do |tag,es,chain|
|
352
|
+
chain.next
|
353
|
+
fwd.emit(tag, es)
|
354
|
+
end
|
355
|
+
end
|
356
|
+
|
357
|
+
class FinishWait
|
358
|
+
def initialize
|
359
|
+
@finished = false
|
360
|
+
@mutex = Mutex.new
|
361
|
+
@cond = ConditionVariable.new
|
362
|
+
end
|
363
|
+
|
364
|
+
def wait
|
365
|
+
@mutex.synchronize do
|
366
|
+
until @finished
|
367
|
+
@cond.wait(@mutex, 1.0)
|
368
|
+
end
|
369
|
+
end
|
370
|
+
end
|
371
|
+
|
372
|
+
def stop
|
373
|
+
return if @finished
|
374
|
+
@finished = true
|
375
|
+
@mutex.synchronize do
|
376
|
+
@cond.broadcast
|
377
|
+
end
|
378
|
+
end
|
379
|
+
|
380
|
+
def finished?
|
381
|
+
@finished
|
382
|
+
end
|
383
|
+
end
|
384
|
+
end
|
385
|
+
|
386
|
+
|
387
|
+
module DetachProcessMixin
|
388
|
+
include DetachProcessImpl
|
389
|
+
|
390
|
+
def configure(conf)
|
391
|
+
super
|
392
|
+
|
393
|
+
if detach_process = conf['detach_process']
|
394
|
+
b3v = Config.bool_value(detach_process)
|
395
|
+
case b3v
|
396
|
+
when nil
|
397
|
+
num = detach_process.to_i
|
398
|
+
if num > 1
|
399
|
+
$log.warn "'detach_process' parameter supports only 1 process on this plugin: #{conf}"
|
400
|
+
elsif num > 0
|
401
|
+
@detach_process = true
|
402
|
+
elsif detach_process =~ /0+/
|
403
|
+
@detach_process = false
|
404
|
+
else
|
405
|
+
@detach_process = true
|
406
|
+
end
|
407
|
+
when true
|
408
|
+
@detach_process = true
|
409
|
+
when false
|
410
|
+
@detach_process = false
|
411
|
+
end
|
412
|
+
end
|
413
|
+
end
|
414
|
+
|
415
|
+
def detach_process(&block)
|
416
|
+
if @detach_process
|
417
|
+
detach_process_impl(1, &block)
|
418
|
+
else
|
419
|
+
block.call
|
420
|
+
end
|
421
|
+
end
|
422
|
+
end
|
423
|
+
|
424
|
+
|
425
|
+
module DetachMultiProcessMixin
|
426
|
+
include DetachProcessImpl
|
427
|
+
|
428
|
+
def initialize
|
429
|
+
@detach_process_num = 2
|
430
|
+
super
|
431
|
+
end
|
432
|
+
|
433
|
+
def configure(conf)
|
434
|
+
super
|
435
|
+
|
436
|
+
if detach_process = conf['detach_process']
|
437
|
+
b3v = Config.bool_value(detach_process)
|
438
|
+
case b3v
|
439
|
+
when nil
|
440
|
+
num = detach_process.to_i
|
441
|
+
if num > 0
|
442
|
+
@detach_process = true
|
443
|
+
@detach_process_num = num
|
444
|
+
elsif detach_process =~ /0+/
|
445
|
+
@detach_process = false
|
446
|
+
else
|
447
|
+
@detach_process = true
|
448
|
+
end
|
449
|
+
when true
|
450
|
+
@detach_process = true
|
451
|
+
when false
|
452
|
+
@detach_process = false
|
453
|
+
end
|
454
|
+
end
|
455
|
+
end
|
456
|
+
|
457
|
+
protected
|
458
|
+
def detach_multi_process(&block)
|
459
|
+
if @detach_process
|
460
|
+
detach_process_impl(@detach_process_num, &block)
|
461
|
+
else
|
462
|
+
block.call
|
463
|
+
end
|
464
|
+
end
|
465
|
+
end
|
466
|
+
|
467
|
+
|
468
|
+
end
|
469
|
+
|