rbtrace 0.4.2 → 0.4.3
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 +8 -8
- data/.gitignore +2 -0
- data/Gemfile.lock +1 -1
- data/bin/rbtrace +2 -1077
- data/lib/rbtrace/cli.rb +445 -0
- data/lib/rbtrace/core_ext.rb +17 -0
- data/lib/rbtrace/msgq.rb +50 -0
- data/lib/rbtrace/rbtracer.rb +570 -0
- data/rbtrace.gemspec +1 -1
- data/test.sh +1 -1
- metadata +6 -2
checksums.yaml
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
---
|
|
2
2
|
!binary "U0hBMQ==":
|
|
3
3
|
metadata.gz: !binary |-
|
|
4
|
-
|
|
4
|
+
Y2Y5ZDY3NmVmZWY1ODA5YWVjYzJmNmI2ZDI0NWM3OTMwYmM0M2VkMA==
|
|
5
5
|
data.tar.gz: !binary |-
|
|
6
|
-
|
|
6
|
+
NWVhZWRkOGJhNzE3NTE4ZjRiM2U1YTcyOGFiMzJiODE0ZDc0MmRlYQ==
|
|
7
7
|
SHA512:
|
|
8
8
|
metadata.gz: !binary |-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
YzM0Yjk4YjViNjgwNTk1N2Y5NzY5YzBiMjg3N2MzZGE5NjIwMjU3Zjg4NzQz
|
|
10
|
+
MGNmNjM0MzUzNGUwZWVlNDI1MGVkZTlkNDQwZmJjYzliMTlmZTY2OGNhYzdh
|
|
11
|
+
NjBiNmNjOWZjNmU4MjJjMTU5MTkwYTk0N2ExYzU0ZWFkNTBmNjM=
|
|
12
12
|
data.tar.gz: !binary |-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
ODg5ZGI2NTEyZGM4Y2I1ZjcwYTQ2YzA5NWZkMTFhNDNlNWI2MTlkYWE3ZWU3
|
|
14
|
+
MGZhZTcxMWQyZmZkNDFkNmU3NGExZDAyMmU4NmNlNTU1OTIxYjIzNGRjNzUw
|
|
15
|
+
OTdlMmU5YTFjYjg1NGJmOTNiMjI0MTQ4ZjI5MDg1ZTBlODZkNGM=
|
data/.gitignore
CHANGED
data/Gemfile.lock
CHANGED
data/bin/rbtrace
CHANGED
|
@@ -1,1080 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env ruby
|
|
2
|
-
require 'socket'
|
|
3
|
-
require 'fileutils'
|
|
4
2
|
require 'rubygems'
|
|
5
|
-
require '
|
|
6
|
-
require 'msgpack'
|
|
7
|
-
require 'trollop'
|
|
3
|
+
require 'rbtrace/cli'
|
|
8
4
|
|
|
9
|
-
|
|
10
|
-
alias :bytesize :size
|
|
11
|
-
end unless ''.respond_to?(:bytesize)
|
|
12
|
-
|
|
13
|
-
module FFI::LastError
|
|
14
|
-
Errnos = Errno::constants.map(&Errno.method(:const_get)).inject({}) do |hash, c|
|
|
15
|
-
hash[ c.const_get(:Errno) ] = c
|
|
16
|
-
hash
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
def self.exception
|
|
20
|
-
Errnos[error]
|
|
21
|
-
end
|
|
22
|
-
def self.raise(msg=nil)
|
|
23
|
-
Kernel.raise exception, msg
|
|
24
|
-
end
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
module MsgQ
|
|
28
|
-
extend FFI::Library
|
|
29
|
-
ffi_lib FFI::CURRENT_PROCESS
|
|
30
|
-
|
|
31
|
-
class EventMsg < FFI::Struct
|
|
32
|
-
BUF_SIZE = RUBY_PLATFORM =~ /linux/ ? 256 : 120
|
|
33
|
-
IPC_NOWAIT = 004000
|
|
34
|
-
|
|
35
|
-
layout :mtype, :long,
|
|
36
|
-
:buf, [:char, BUF_SIZE]
|
|
37
|
-
|
|
38
|
-
def self.send_cmd(q, str)
|
|
39
|
-
msg = EventMsg.new
|
|
40
|
-
msg[:mtype] = 1
|
|
41
|
-
msg[:buf].to_ptr.put_string(0, str)
|
|
42
|
-
|
|
43
|
-
ret = MsgQ.msgsnd(q, msg, BUF_SIZE, 0)
|
|
44
|
-
FFI::LastError.raise if ret == -1
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
def self.recv_cmd(q, block=true)
|
|
48
|
-
MsgQ.rb_enable_interrupt if RUBY_VERSION > '1.9' && RUBY_VERSION < '2.0'
|
|
49
|
-
|
|
50
|
-
msg = EventMsg.new
|
|
51
|
-
ret = MsgQ.msgrcv(q, msg, BUF_SIZE, 0, block ? 0 : IPC_NOWAIT)
|
|
52
|
-
if ret == -1
|
|
53
|
-
if !block and [Errno::EAGAIN, Errno::ENOMSG].include?(FFI::LastError.exception)
|
|
54
|
-
return nil
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
FFI::LastError.raise
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
msg[:buf].to_ptr.read_string_length(BUF_SIZE)
|
|
61
|
-
ensure
|
|
62
|
-
MsgQ.rb_disable_interrupt if RUBY_VERSION > '1.9' && RUBY_VERSION < '2.0'
|
|
63
|
-
end
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
attach_function :msgget, [:int, :int], :int
|
|
67
|
-
attach_function :msgrcv, [:int, EventMsg.ptr, :size_t, :long, :int], :int
|
|
68
|
-
attach_function :msgsnd, [:int, EventMsg.ptr, :size_t, :int], :int
|
|
69
|
-
|
|
70
|
-
if RUBY_VERSION > '1.9' && RUBY_VERSION < '2.0'
|
|
71
|
-
attach_function :rb_enable_interrupt, [], :void
|
|
72
|
-
attach_function :rb_disable_interrupt, [], :void
|
|
73
|
-
end
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
class RBTracer
|
|
77
|
-
# Suggest increasing the maximum number of bytes allowed on
|
|
78
|
-
# a message queue to 1MB.
|
|
79
|
-
#
|
|
80
|
-
# This defaults to 16k on Linux, and is hardcoded to 2k in OSX kernel.
|
|
81
|
-
#
|
|
82
|
-
# Returns nothing.
|
|
83
|
-
def self.check_msgmnb
|
|
84
|
-
if File.exists?(msgmnb = "/proc/sys/kernel/msgmnb")
|
|
85
|
-
curr = File.read(msgmnb).to_i
|
|
86
|
-
max = 1024*1024
|
|
87
|
-
cmd = "sysctl kernel.msgmnb=#{max}"
|
|
88
|
-
|
|
89
|
-
if curr < max
|
|
90
|
-
if Process.uid == 0
|
|
91
|
-
STDERR.puts "*** running `#{cmd}` for you to prevent losing events (currently: #{curr} bytes)"
|
|
92
|
-
system(cmd)
|
|
93
|
-
else
|
|
94
|
-
STDERR.puts "*** run `sudo #{cmd}` to prevent losing events (currently: #{curr} bytes)"
|
|
95
|
-
end
|
|
96
|
-
end
|
|
97
|
-
end
|
|
98
|
-
end
|
|
99
|
-
|
|
100
|
-
# Look for any message queues pairs (pid/-pid) that no longer have an
|
|
101
|
-
# associated process alive, and remove them.
|
|
102
|
-
#
|
|
103
|
-
# Returns nothing.
|
|
104
|
-
def self.cleanup_queues
|
|
105
|
-
if (pids = `ps ax -o pid`.split("\n").map{ |p| p.strip.to_i }).any?
|
|
106
|
-
ipcs = `ipcs -q`.split("\n").grep(/^(q|0x)/).map{ |line| line[/(0x[a-f0-9]+)/,1] }
|
|
107
|
-
ipcs.each do |ipci|
|
|
108
|
-
next if ipci.match(/^0xf/)
|
|
109
|
-
|
|
110
|
-
qi = ipci.to_i(16)
|
|
111
|
-
qo = 0xffffffff - qi + 1
|
|
112
|
-
ipco = "0x#{qo.to_s(16)}"
|
|
113
|
-
|
|
114
|
-
if ipcs.include?(ipco) and !pids.include?(qi)
|
|
115
|
-
STDERR.puts "*** removing stale message queue pair: #{ipci}/#{ipco}"
|
|
116
|
-
system("ipcrm -Q #{ipci} -Q #{ipco}")
|
|
117
|
-
end
|
|
118
|
-
end
|
|
119
|
-
end
|
|
120
|
-
end
|
|
121
|
-
|
|
122
|
-
# Public: The Fixnum pid of the traced process.
|
|
123
|
-
attr_reader :pid
|
|
124
|
-
|
|
125
|
-
# Public: The IO where tracing output is written (default: STDOUT).
|
|
126
|
-
attr_accessor :out
|
|
127
|
-
|
|
128
|
-
# Public: The timeout before giving up on attaching/detaching to a process.
|
|
129
|
-
attr_accessor :timeout
|
|
130
|
-
|
|
131
|
-
# The String prefix used on nested method calls (default: ' ').
|
|
132
|
-
attr_accessor :prefix
|
|
133
|
-
|
|
134
|
-
# The Boolean flag for showing how long method calls take (default: true).
|
|
135
|
-
attr_accessor :show_duration
|
|
136
|
-
|
|
137
|
-
# The Boolean flag for showing the timestamp when method calls start (default: false).
|
|
138
|
-
attr_accessor :show_time
|
|
139
|
-
|
|
140
|
-
# Create a new tracer
|
|
141
|
-
#
|
|
142
|
-
# pid - The String of Fixnum process id
|
|
143
|
-
#
|
|
144
|
-
# Returns a tracer.
|
|
145
|
-
def initialize(pid)
|
|
146
|
-
begin
|
|
147
|
-
raise ArgumentError unless pid
|
|
148
|
-
@pid = pid.to_i
|
|
149
|
-
raise ArgumentError unless @pid > 0
|
|
150
|
-
Process.kill(0, @pid)
|
|
151
|
-
rescue TypeError, ArgumentError
|
|
152
|
-
raise ArgumentError, 'pid required'
|
|
153
|
-
rescue Errno::ESRCH
|
|
154
|
-
raise ArgumentError, 'invalid pid'
|
|
155
|
-
rescue Errno::EPERM
|
|
156
|
-
raise ArgumentError, 'could not signal process, are you running as root?'
|
|
157
|
-
end
|
|
158
|
-
|
|
159
|
-
path = "/tmp/rbtrace-#{@pid}.sock"
|
|
160
|
-
@sock = Socket.new Socket::AF_UNIX, Socket::SOCK_DGRAM, 0
|
|
161
|
-
@sockaddr = Socket.pack_sockaddr_un(path)
|
|
162
|
-
@sock.bind(@sockaddr)
|
|
163
|
-
FileUtils.chmod 0666, path
|
|
164
|
-
at_exit{ FileUtils.rm(path) if File.exists?(path) }
|
|
165
|
-
|
|
166
|
-
5.times do
|
|
167
|
-
signal
|
|
168
|
-
sleep 0.15 # wait for process to create msgqs
|
|
169
|
-
|
|
170
|
-
@qo = MsgQ.msgget(-pid, 0666)
|
|
171
|
-
|
|
172
|
-
break if @qo > -1
|
|
173
|
-
end
|
|
174
|
-
|
|
175
|
-
if @qo == -1
|
|
176
|
-
raise ArgumentError, 'pid is not listening for messages, did you `require "rbtrace"`'
|
|
177
|
-
end
|
|
178
|
-
|
|
179
|
-
@klasses = {}
|
|
180
|
-
@methods = {}
|
|
181
|
-
@tracers = Hash.new{ |h,k|
|
|
182
|
-
h[k] = {
|
|
183
|
-
:query => nil,
|
|
184
|
-
:times => [],
|
|
185
|
-
:names => [],
|
|
186
|
-
:exprs => {},
|
|
187
|
-
:last => false,
|
|
188
|
-
:arglist => false
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
@max_nesting = @last_nesting = @nesting = 0
|
|
192
|
-
@last_tracer = nil
|
|
193
|
-
|
|
194
|
-
@timeout = 5
|
|
195
|
-
|
|
196
|
-
@out = STDOUT
|
|
197
|
-
@out.sync = true
|
|
198
|
-
@prefix = ' '
|
|
199
|
-
@printed_newline = true
|
|
200
|
-
|
|
201
|
-
@show_time = false
|
|
202
|
-
@show_duration = true
|
|
203
|
-
@watch_slow = false
|
|
204
|
-
|
|
205
|
-
attach
|
|
206
|
-
end
|
|
207
|
-
|
|
208
|
-
# Watch for method calls slower than a threshold.
|
|
209
|
-
#
|
|
210
|
-
# msec - The Fixnum threshold in milliseconds
|
|
211
|
-
#
|
|
212
|
-
# Returns nothing.
|
|
213
|
-
def watch(msec, cpu_only=false)
|
|
214
|
-
@watch_slow = true
|
|
215
|
-
send_cmd(cpu_only ? :watchcpu : :watch, msec)
|
|
216
|
-
end
|
|
217
|
-
|
|
218
|
-
# Turn on the firehose (show all method calls).
|
|
219
|
-
#
|
|
220
|
-
# Returns nothing.
|
|
221
|
-
def firehose
|
|
222
|
-
send_cmd(:firehose)
|
|
223
|
-
end
|
|
224
|
-
|
|
225
|
-
# Turn on dev mode.
|
|
226
|
-
#
|
|
227
|
-
# Returns nothing.
|
|
228
|
-
def devmode
|
|
229
|
-
send_cmd(:devmode)
|
|
230
|
-
end
|
|
231
|
-
|
|
232
|
-
# Fork the process and return the copy's pid.
|
|
233
|
-
#
|
|
234
|
-
# Returns a Fixnum pid.
|
|
235
|
-
def fork
|
|
236
|
-
send_cmd(:fork)
|
|
237
|
-
if wait('for fork', 30){ !!@forked_pid }
|
|
238
|
-
@forked_pid
|
|
239
|
-
else
|
|
240
|
-
STDERR.puts '*** timed out waiting for fork'
|
|
241
|
-
end
|
|
242
|
-
end
|
|
243
|
-
|
|
244
|
-
# Evaluate some ruby code.
|
|
245
|
-
#
|
|
246
|
-
# Returns the String result.
|
|
247
|
-
def eval(code)
|
|
248
|
-
if (err = valid_syntax?(code)) != true
|
|
249
|
-
raise ArgumentError, "#{err.class} for expression #{code.inspect}"
|
|
250
|
-
end
|
|
251
|
-
|
|
252
|
-
send_cmd(:eval, code)
|
|
253
|
-
|
|
254
|
-
if wait('for eval response', 15){ !!@eval_result }
|
|
255
|
-
@eval_result
|
|
256
|
-
else
|
|
257
|
-
STDERR.puts '*** timed out waiting for eval response'
|
|
258
|
-
end
|
|
259
|
-
end
|
|
260
|
-
|
|
261
|
-
# Turn on GC tracing.
|
|
262
|
-
#
|
|
263
|
-
# Returns nothing.
|
|
264
|
-
def gc
|
|
265
|
-
send_cmd(:gc)
|
|
266
|
-
end
|
|
267
|
-
|
|
268
|
-
# Restrict slow tracing to a specific list of methods.
|
|
269
|
-
#
|
|
270
|
-
# methods - The String or Array of method selectors.
|
|
271
|
-
#
|
|
272
|
-
# Returns nothing.
|
|
273
|
-
def add_slow(methods)
|
|
274
|
-
add(methods, true)
|
|
275
|
-
end
|
|
276
|
-
|
|
277
|
-
# Add tracers for the given list of methods.
|
|
278
|
-
#
|
|
279
|
-
# methods - The String or Array of method selectors to trace.
|
|
280
|
-
#
|
|
281
|
-
# Returns nothing.
|
|
282
|
-
def add(methods, slow=false)
|
|
283
|
-
Array(methods).each do |func|
|
|
284
|
-
func = func.strip
|
|
285
|
-
next if func.empty?
|
|
286
|
-
|
|
287
|
-
if func =~ /^(.+?)\((.+)\)$/
|
|
288
|
-
name, args = $1, $2
|
|
289
|
-
args = args.split(',').map{ |a| a.strip }
|
|
290
|
-
end
|
|
291
|
-
|
|
292
|
-
send_cmd(:add, name || func, slow)
|
|
293
|
-
|
|
294
|
-
if args and args.any?
|
|
295
|
-
args.each do |arg|
|
|
296
|
-
if (err = valid_syntax?(arg)) != true
|
|
297
|
-
raise ArgumentError, "#{err.class} for expression #{arg.inspect} in method #{func.inspect}"
|
|
298
|
-
end
|
|
299
|
-
if arg =~ /^@/ and arg !~ /^@[_a-z][_a-z0-9]+$/i
|
|
300
|
-
# arg[0]=='@' means ivar, but if this is an expr
|
|
301
|
-
# we can hack a space in front so it gets eval'd instead
|
|
302
|
-
arg = " #{arg}"
|
|
303
|
-
end
|
|
304
|
-
send_cmd(:addexpr, arg)
|
|
305
|
-
end
|
|
306
|
-
end
|
|
307
|
-
end
|
|
308
|
-
end
|
|
309
|
-
|
|
310
|
-
# Attach to the process.
|
|
311
|
-
#
|
|
312
|
-
# Returns nothing.
|
|
313
|
-
def attach
|
|
314
|
-
send_cmd(:attach, Process.pid)
|
|
315
|
-
if wait('to attach'){ @attached == true }
|
|
316
|
-
STDERR.puts "*** attached to process #{pid}"
|
|
317
|
-
else
|
|
318
|
-
raise ArgumentError, 'process already being traced?'
|
|
319
|
-
end
|
|
320
|
-
end
|
|
321
|
-
|
|
322
|
-
# Detach from the traced process.
|
|
323
|
-
#
|
|
324
|
-
# Returns nothing.
|
|
325
|
-
def detach
|
|
326
|
-
begin
|
|
327
|
-
send_cmd(:detach)
|
|
328
|
-
rescue Errno::ESRCH
|
|
329
|
-
end
|
|
330
|
-
|
|
331
|
-
newline
|
|
332
|
-
|
|
333
|
-
if wait('to detach cleanly'){ @attached == false }
|
|
334
|
-
newline
|
|
335
|
-
STDERR.puts "*** detached from process #{pid}"
|
|
336
|
-
else
|
|
337
|
-
newline
|
|
338
|
-
STDERR.puts "*** could not detach cleanly from process #{pid}"
|
|
339
|
-
end
|
|
340
|
-
rescue Errno::EINVAL, Errno::EIDRM
|
|
341
|
-
newline
|
|
342
|
-
STDERR.puts "*** process #{pid} is gone"
|
|
343
|
-
# STDERR.puts "*** #{$!.inspect}"
|
|
344
|
-
# STDERR.puts $!.backtrace.join("\n ")
|
|
345
|
-
rescue Interrupt, SignalException
|
|
346
|
-
retry
|
|
347
|
-
end
|
|
348
|
-
|
|
349
|
-
# Process events from the traced process.
|
|
350
|
-
#
|
|
351
|
-
# Returns nothing.
|
|
352
|
-
def recv_loop
|
|
353
|
-
while true
|
|
354
|
-
# block until a message arrives
|
|
355
|
-
process_line(recv_cmd)
|
|
356
|
-
|
|
357
|
-
# process any remaining messages
|
|
358
|
-
recv_lines
|
|
359
|
-
end
|
|
360
|
-
rescue Errno::EINVAL, Errno::EIDRM
|
|
361
|
-
# process went away
|
|
362
|
-
end
|
|
363
|
-
|
|
364
|
-
# Process events from the traced process, without blocking if
|
|
365
|
-
# there is nothing to do. This is a useful way to drain the buffer
|
|
366
|
-
# so messages do not accumulate in kernel land.
|
|
367
|
-
#
|
|
368
|
-
# Returns nothing.
|
|
369
|
-
def recv_lines
|
|
370
|
-
50.times do
|
|
371
|
-
break unless line = recv_cmd(false)
|
|
372
|
-
process_line(line)
|
|
373
|
-
end
|
|
374
|
-
end
|
|
375
|
-
|
|
376
|
-
def puts(arg=nil)
|
|
377
|
-
@printed_newline = true
|
|
378
|
-
arg ? @out.puts(arg) : @out.puts
|
|
379
|
-
end
|
|
380
|
-
|
|
381
|
-
private
|
|
382
|
-
|
|
383
|
-
def signal
|
|
384
|
-
Process.kill 'URG', @pid
|
|
385
|
-
end
|
|
386
|
-
|
|
387
|
-
# Process incoming events until either a timeout or a condition becomes true.
|
|
388
|
-
#
|
|
389
|
-
# time - The Fixnum timeout in seconds.
|
|
390
|
-
# block - The Block that is checked every 50ms until it returns true.
|
|
391
|
-
#
|
|
392
|
-
# Returns true when the condition was met, or false on a timeout.
|
|
393
|
-
def wait(reason, time=@timeout)
|
|
394
|
-
wait = 0.05 # polling interval
|
|
395
|
-
|
|
396
|
-
(time/wait).to_i.times do
|
|
397
|
-
begin
|
|
398
|
-
recv_lines
|
|
399
|
-
sleep(wait)
|
|
400
|
-
begin
|
|
401
|
-
signal
|
|
402
|
-
rescue Errno::ESRCH
|
|
403
|
-
break
|
|
404
|
-
end
|
|
405
|
-
time -= wait
|
|
406
|
-
|
|
407
|
-
return true if yield
|
|
408
|
-
rescue Interrupt
|
|
409
|
-
STDERR.puts "*** waiting #{reason} (#{time.to_i}s left)"
|
|
410
|
-
retry
|
|
411
|
-
end
|
|
412
|
-
end
|
|
413
|
-
|
|
414
|
-
false
|
|
415
|
-
end
|
|
416
|
-
|
|
417
|
-
def send_cmd(*cmd)
|
|
418
|
-
begin
|
|
419
|
-
msg = cmd.to_msgpack
|
|
420
|
-
raise ArgumentError, 'command is too long' if msg.bytesize > MsgQ::EventMsg::BUF_SIZE
|
|
421
|
-
MsgQ::EventMsg.send_cmd(@qo, msg)
|
|
422
|
-
rescue Errno::EINTR
|
|
423
|
-
retry
|
|
424
|
-
end
|
|
425
|
-
signal
|
|
426
|
-
recv_lines
|
|
427
|
-
end
|
|
428
|
-
|
|
429
|
-
def recv_cmd(block=true)
|
|
430
|
-
if block
|
|
431
|
-
@sock.recv(65536)
|
|
432
|
-
else
|
|
433
|
-
@sock.recv_nonblock(65536)
|
|
434
|
-
end
|
|
435
|
-
rescue Errno::EAGAIN
|
|
436
|
-
nil
|
|
437
|
-
end
|
|
438
|
-
|
|
439
|
-
def valid_syntax?(code)
|
|
440
|
-
begin
|
|
441
|
-
Kernel.eval("#{code}\nBEGIN {return true}", nil, 'rbtrace_expression', 0)
|
|
442
|
-
rescue Exception => e
|
|
443
|
-
e
|
|
444
|
-
end
|
|
445
|
-
end
|
|
446
|
-
|
|
447
|
-
def print(arg)
|
|
448
|
-
@printed_newline = false
|
|
449
|
-
@out.print(arg)
|
|
450
|
-
end
|
|
451
|
-
|
|
452
|
-
def newline
|
|
453
|
-
puts unless @printed_newline
|
|
454
|
-
@printed_newline = true
|
|
455
|
-
end
|
|
456
|
-
|
|
457
|
-
def parse_cmd(line)
|
|
458
|
-
unpacker = MessagePack::Unpacker.new
|
|
459
|
-
unpacker.feed(line)
|
|
460
|
-
|
|
461
|
-
obj = nil
|
|
462
|
-
unpacker.each{|o| obj = o; break }
|
|
463
|
-
obj
|
|
464
|
-
end
|
|
465
|
-
|
|
466
|
-
def process_line(line)
|
|
467
|
-
return unless cmd = parse_cmd(line)
|
|
468
|
-
event = cmd.shift
|
|
469
|
-
|
|
470
|
-
case event
|
|
471
|
-
when 'during_gc'
|
|
472
|
-
sleep 0.01
|
|
473
|
-
signal
|
|
474
|
-
return
|
|
475
|
-
|
|
476
|
-
when 'attached'
|
|
477
|
-
tracer_pid, = *cmd
|
|
478
|
-
if tracer_pid != Process.pid
|
|
479
|
-
STDERR.puts "*** process #{pid} is already being traced (#{tracer_pid} != #{Process.pid})"
|
|
480
|
-
exit!(-1)
|
|
481
|
-
end
|
|
482
|
-
|
|
483
|
-
@attached = true
|
|
484
|
-
return
|
|
485
|
-
|
|
486
|
-
when 'detached'
|
|
487
|
-
tracer_pid, = *cmd
|
|
488
|
-
if tracer_pid != Process.pid
|
|
489
|
-
STDERR.puts "*** process #{pid} detached #{tracer_pid}, but we are #{Process.pid}"
|
|
490
|
-
else
|
|
491
|
-
@attached = false
|
|
492
|
-
end
|
|
493
|
-
|
|
494
|
-
return
|
|
495
|
-
end
|
|
496
|
-
|
|
497
|
-
unless @attached
|
|
498
|
-
STDERR.puts "*** got #{event} before attaching"
|
|
499
|
-
return
|
|
500
|
-
end
|
|
501
|
-
|
|
502
|
-
case event
|
|
503
|
-
when 'forked'
|
|
504
|
-
pid, = *cmd
|
|
505
|
-
@forked_pid = pid
|
|
506
|
-
|
|
507
|
-
when 'evaled'
|
|
508
|
-
res, = *cmd
|
|
509
|
-
@eval_result = res
|
|
510
|
-
|
|
511
|
-
when 'mid'
|
|
512
|
-
mid, name = *cmd
|
|
513
|
-
@methods[mid] = name
|
|
514
|
-
|
|
515
|
-
when 'klass'
|
|
516
|
-
kid, name = *cmd
|
|
517
|
-
@klasses[kid] = name
|
|
518
|
-
|
|
519
|
-
when 'add'
|
|
520
|
-
tracer_id, query = *cmd
|
|
521
|
-
if tracer_id == -1
|
|
522
|
-
STDERR.puts "*** unable to add tracer for #{query}"
|
|
523
|
-
else
|
|
524
|
-
@tracers.delete(tracer_id)
|
|
525
|
-
@tracers[tracer_id][:query] = query
|
|
526
|
-
end
|
|
527
|
-
|
|
528
|
-
when 'newexpr'
|
|
529
|
-
tracer_id, expr_id, expr = *cmd
|
|
530
|
-
tracer = @tracers[tracer_id]
|
|
531
|
-
|
|
532
|
-
if expr_id > -1
|
|
533
|
-
tracer[:exprs][expr_id] = expr.strip
|
|
534
|
-
end
|
|
535
|
-
|
|
536
|
-
when 'exprval'
|
|
537
|
-
tracer_id, expr_id, val = *cmd
|
|
538
|
-
|
|
539
|
-
tracer = @tracers[tracer_id]
|
|
540
|
-
expr = tracer[:exprs][expr_id]
|
|
541
|
-
|
|
542
|
-
if tracer[:arglist]
|
|
543
|
-
print ', '
|
|
544
|
-
else
|
|
545
|
-
print '('
|
|
546
|
-
end
|
|
547
|
-
|
|
548
|
-
print "#{expr}="
|
|
549
|
-
print val
|
|
550
|
-
tracer[:arglist] = true
|
|
551
|
-
|
|
552
|
-
when 'call','ccall'
|
|
553
|
-
time, tracer_id, mid, is_singleton, klass = *cmd
|
|
554
|
-
|
|
555
|
-
tracer = @tracers[tracer_id]
|
|
556
|
-
klass = @klasses[klass]
|
|
557
|
-
name = klass ? "#{klass}#{ is_singleton ? '.' : '#' }" : ''
|
|
558
|
-
name += @methods[mid] || '(unknown)'
|
|
559
|
-
|
|
560
|
-
tracer[:times] << time
|
|
561
|
-
tracer[:names] << name
|
|
562
|
-
|
|
563
|
-
if @last_tracer and @last_tracer[:arglist]
|
|
564
|
-
print ')'
|
|
565
|
-
@last_tracer[:arglist] = false
|
|
566
|
-
end
|
|
567
|
-
newline
|
|
568
|
-
if @show_time
|
|
569
|
-
t = Time.at(time/1_000_000)
|
|
570
|
-
print t.strftime("%H:%M:%S.")
|
|
571
|
-
print "%06d " % (time - t.to_f*1_000_000).round
|
|
572
|
-
end
|
|
573
|
-
print @prefix*@nesting if @nesting > 0
|
|
574
|
-
print name
|
|
575
|
-
|
|
576
|
-
@nesting += 1
|
|
577
|
-
@max_nesting = @nesting if @nesting > @max_nesting
|
|
578
|
-
@last_nesting = @nesting
|
|
579
|
-
@last_tracer = tracer
|
|
580
|
-
tracer[:last] = "#{name}:#{@nesting-1}"
|
|
581
|
-
|
|
582
|
-
when 'return','creturn'
|
|
583
|
-
time, tracer_id = *cmd
|
|
584
|
-
tracer = @tracers[tracer_id]
|
|
585
|
-
|
|
586
|
-
@nesting -= 1 if @nesting > 0
|
|
587
|
-
|
|
588
|
-
if start = tracer[:times].pop
|
|
589
|
-
name = tracer[:names].pop
|
|
590
|
-
diff = time - start
|
|
591
|
-
@last_tracer[:arglist] = false if @last_tracer and @last_tracer[:last] != "#{name}:#{@nesting}"
|
|
592
|
-
|
|
593
|
-
print ')' if @last_tracer and @last_tracer[:arglist]
|
|
594
|
-
|
|
595
|
-
unless tracer == @last_tracer and @last_tracer[:last] == "#{name}:#{@nesting}"
|
|
596
|
-
newline
|
|
597
|
-
print ' '*16 if @show_time
|
|
598
|
-
print @prefix*@nesting if @nesting > 0
|
|
599
|
-
print name
|
|
600
|
-
end
|
|
601
|
-
print ' <%f>' % (diff/1_000_000.0) if @show_duration
|
|
602
|
-
newline
|
|
603
|
-
|
|
604
|
-
if @nesting == 0 and @max_nesting > 1
|
|
605
|
-
# unless tracer == @last_tracer and @last_tracer[:last] == name
|
|
606
|
-
puts
|
|
607
|
-
# end
|
|
608
|
-
end
|
|
609
|
-
end
|
|
610
|
-
|
|
611
|
-
tracer[:arglist] = false
|
|
612
|
-
@last_nesting = @nesting
|
|
613
|
-
|
|
614
|
-
when 'slow', 'cslow'
|
|
615
|
-
time, diff, nesting, mid, is_singleton, klass = *cmd
|
|
616
|
-
|
|
617
|
-
klass = @klasses[klass]
|
|
618
|
-
name = klass ? "#{klass}#{ is_singleton ? '.' : '#' }" : ''
|
|
619
|
-
name += @methods[mid] || '(unknown)'
|
|
620
|
-
|
|
621
|
-
newline
|
|
622
|
-
nesting = @nesting if @nesting > 0
|
|
623
|
-
|
|
624
|
-
if @show_time
|
|
625
|
-
t = Time.at(time/1_000_000)
|
|
626
|
-
print t.strftime("%H:%M:%S.")
|
|
627
|
-
print "%06d " % (time - t.to_f*1_000_000).round
|
|
628
|
-
end
|
|
629
|
-
|
|
630
|
-
print @prefix*nesting if nesting > 0
|
|
631
|
-
print name
|
|
632
|
-
if @show_duration
|
|
633
|
-
print ' '
|
|
634
|
-
print "<%f>" % (diff/1_000_000.0)
|
|
635
|
-
end
|
|
636
|
-
puts
|
|
637
|
-
puts if nesting == 0 and @max_nesting > 1
|
|
638
|
-
|
|
639
|
-
@max_nesting = nesting if nesting > @max_nesting
|
|
640
|
-
@last_nesting = nesting
|
|
641
|
-
|
|
642
|
-
when 'gc_start'
|
|
643
|
-
time, = *cmd
|
|
644
|
-
@gc_start = time
|
|
645
|
-
print 'garbage_collect'
|
|
646
|
-
|
|
647
|
-
when 'gc_end'
|
|
648
|
-
time, = *cmd
|
|
649
|
-
diff = time - @gc_start
|
|
650
|
-
# if @gc_mark
|
|
651
|
-
# mark = ((@gc_mark - @gc_start) * 100.0 / diff).to_i
|
|
652
|
-
# print '(mark: %d%%, sweep: %d%%)' % [mark, 100-mark]
|
|
653
|
-
# end
|
|
654
|
-
print ' <%f>' % (diff/1_000_000.0) if @show_duration
|
|
655
|
-
@gc_start = nil
|
|
656
|
-
newline
|
|
657
|
-
|
|
658
|
-
when 'gc'
|
|
659
|
-
time, = *cmd
|
|
660
|
-
@gc_mark = time
|
|
661
|
-
|
|
662
|
-
unless @gc_start
|
|
663
|
-
newline
|
|
664
|
-
if @show_time
|
|
665
|
-
t = Time.at(time/1_000_000)
|
|
666
|
-
print t.strftime("%H:%M:%S.")
|
|
667
|
-
print "%06d " % (time - t.to_f*1_000_000).round
|
|
668
|
-
end
|
|
669
|
-
print @prefix*@last_nesting if @last_nesting > 0
|
|
670
|
-
print "garbage_collect"
|
|
671
|
-
puts if @watch_slow
|
|
672
|
-
end
|
|
673
|
-
|
|
674
|
-
else
|
|
675
|
-
puts "unknown event #{event}: #{cmd.inspect}"
|
|
676
|
-
|
|
677
|
-
end
|
|
678
|
-
rescue => e
|
|
679
|
-
STDERR.puts "error on #{event}: #{cmd.inspect}"
|
|
680
|
-
raise e
|
|
681
|
-
end
|
|
682
|
-
|
|
683
|
-
def self.run
|
|
684
|
-
check_msgmnb
|
|
685
|
-
cleanup_queues
|
|
686
|
-
|
|
687
|
-
parser = Trollop::Parser.new do
|
|
688
|
-
version <<-EOS
|
|
689
|
-
rbtrace: like strace, but for ruby code
|
|
690
|
-
version 0.4.0
|
|
691
|
-
(c) 2013 Aman Gupta (tmm1)
|
|
692
|
-
http://github.com/tmm1/rbtrace
|
|
693
|
-
EOS
|
|
694
|
-
|
|
695
|
-
banner <<-EOS
|
|
696
|
-
rbtrace shows you method calls happening inside another ruby process in real time.
|
|
697
|
-
|
|
698
|
-
to use rbtrace, simply `require "rbtrace"` in your ruby app.
|
|
699
|
-
|
|
700
|
-
for examples and more information, see http://github.com/tmm1/rbtrace
|
|
701
|
-
|
|
702
|
-
Usage:
|
|
703
|
-
|
|
704
|
-
rbtrace --exec <CMD> # run and trace <CMD>
|
|
705
|
-
rbtrace --pid <PID+> # trace the given process(es)
|
|
706
|
-
rbtrace --ps <CMD> # look for running <CMD> processes to trace
|
|
707
|
-
|
|
708
|
-
rbtrace -o <FILE> # write output to file
|
|
709
|
-
rbtrace -t # show method call start time
|
|
710
|
-
rbtrace -n # hide duration of each method call
|
|
711
|
-
rbtrace -r 3 # use 3 spaces to nest method calls
|
|
712
|
-
|
|
713
|
-
Tracers:
|
|
714
|
-
|
|
715
|
-
rbtrace --firehose # trace all method calls
|
|
716
|
-
rbtrace --slow=250 # trace method calls slower than 250ms
|
|
717
|
-
rbtrace --methods a b c # trace calls to given methods
|
|
718
|
-
rbtrace --gc # trace garbage collections
|
|
719
|
-
|
|
720
|
-
rbtrace -c io # trace common input/output functions
|
|
721
|
-
rbtrace -c eventmachine # trace common eventmachine functions
|
|
722
|
-
rbtrace -c my.tracer # trace all methods listed in my.tracer
|
|
723
|
-
|
|
724
|
-
Method Selectors:
|
|
725
|
-
|
|
726
|
-
sleep # any instance or class method named sleep
|
|
727
|
-
String#gsub # specific instance method
|
|
728
|
-
Process.pid # specific class method
|
|
729
|
-
Dir. # any class methods in Dir
|
|
730
|
-
Fixnum# # any instance methods of Fixnum
|
|
731
|
-
|
|
732
|
-
Trace Expressions:
|
|
733
|
-
|
|
734
|
-
method(self) # value of self at method invocation
|
|
735
|
-
method(@ivar) # value of given instance variable
|
|
736
|
-
method(arg1, arg2) # value of argument local variables
|
|
737
|
-
method(self.attr) # value of arbitrary ruby expression
|
|
738
|
-
method(__source__) # source file/line of callsite
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
All Options:\n
|
|
742
|
-
|
|
743
|
-
EOS
|
|
744
|
-
opt :exec,
|
|
745
|
-
"spawn new ruby process and attach to it",
|
|
746
|
-
:type => :strings,
|
|
747
|
-
:short => nil
|
|
748
|
-
|
|
749
|
-
opt :pid,
|
|
750
|
-
"pid of the ruby process to trace",
|
|
751
|
-
:type => :ints,
|
|
752
|
-
:short => '-p'
|
|
753
|
-
|
|
754
|
-
opt :ps,
|
|
755
|
-
"find any matching processes to trace",
|
|
756
|
-
:type => :string,
|
|
757
|
-
:short => nil
|
|
758
|
-
|
|
759
|
-
opt :firehose,
|
|
760
|
-
"show all method calls",
|
|
761
|
-
:short => '-f'
|
|
762
|
-
|
|
763
|
-
opt :slow,
|
|
764
|
-
"watch for method calls slower than 250 milliseconds",
|
|
765
|
-
:default => 250,
|
|
766
|
-
:short => '-s'
|
|
767
|
-
|
|
768
|
-
opt :slowcpu,
|
|
769
|
-
"watch for method calls slower than 250 milliseconds (cpu time only)",
|
|
770
|
-
:default => 250,
|
|
771
|
-
:short => nil
|
|
772
|
-
|
|
773
|
-
opt :slow_methods,
|
|
774
|
-
"method(s) to restrict --slow to",
|
|
775
|
-
:type => :strings
|
|
776
|
-
|
|
777
|
-
opt :methods,
|
|
778
|
-
"method(s) to trace (valid formats: sleep String#gsub Process.pid Kernel# Dir.)",
|
|
779
|
-
:type => :strings,
|
|
780
|
-
:short => '-m'
|
|
781
|
-
|
|
782
|
-
opt :gc,
|
|
783
|
-
"trace garbage collections"
|
|
784
|
-
|
|
785
|
-
opt :start_time,
|
|
786
|
-
"show start time for each method call",
|
|
787
|
-
:short => '-t'
|
|
788
|
-
|
|
789
|
-
opt :no_duration,
|
|
790
|
-
"hide time spent in each method call",
|
|
791
|
-
:default => false,
|
|
792
|
-
:short => '-n'
|
|
793
|
-
|
|
794
|
-
opt :output,
|
|
795
|
-
"write trace to filename",
|
|
796
|
-
:type => String,
|
|
797
|
-
:short => '-o'
|
|
798
|
-
|
|
799
|
-
opt :append,
|
|
800
|
-
"append to output file instead of overwriting",
|
|
801
|
-
:short => '-a'
|
|
802
|
-
|
|
803
|
-
opt :prefix,
|
|
804
|
-
"prefix nested method calls with N spaces",
|
|
805
|
-
:default => 2,
|
|
806
|
-
:short => '-r'
|
|
807
|
-
|
|
808
|
-
opt :config,
|
|
809
|
-
"config file",
|
|
810
|
-
:type => :strings,
|
|
811
|
-
:short => '-c'
|
|
812
|
-
|
|
813
|
-
opt :devmode,
|
|
814
|
-
"assume the ruby process is reloading classes and methods"
|
|
815
|
-
|
|
816
|
-
opt :fork,
|
|
817
|
-
"fork a copy of the process for debugging (so you can attach gdb.rb)"
|
|
818
|
-
|
|
819
|
-
opt :eval,
|
|
820
|
-
"evaluate a ruby expression in the process",
|
|
821
|
-
:type => String,
|
|
822
|
-
:short => '-e'
|
|
823
|
-
|
|
824
|
-
opt :backtrace,
|
|
825
|
-
"get lines from the current backtrace in the process",
|
|
826
|
-
:type => :int
|
|
827
|
-
|
|
828
|
-
opt :wait,
|
|
829
|
-
"seconds to wait before attaching to process",
|
|
830
|
-
:default => 0,
|
|
831
|
-
:short => nil
|
|
832
|
-
|
|
833
|
-
opt :timeout,
|
|
834
|
-
"seconds to wait before giving up on attach/detach",
|
|
835
|
-
:default => 5
|
|
836
|
-
end
|
|
837
|
-
|
|
838
|
-
opts = Trollop.with_standard_exception_handling(parser) do
|
|
839
|
-
raise Trollop::HelpNeeded if ARGV.empty?
|
|
840
|
-
parser.stop_on '--exec'
|
|
841
|
-
parser.parse(ARGV)
|
|
842
|
-
end
|
|
843
|
-
|
|
844
|
-
if ARGV.first == '--exec'
|
|
845
|
-
ARGV.shift
|
|
846
|
-
opts[:exec_given] = true
|
|
847
|
-
opts[:exec] = ARGV.dup
|
|
848
|
-
ARGV.clear
|
|
849
|
-
end
|
|
850
|
-
|
|
851
|
-
unless %w[ fork eval backtrace slow slowcpu firehose methods config gc ].find{ |n| opts[:"#{n}_given"] }
|
|
852
|
-
$stderr.puts "Error: --slow, --slowcpu, --gc, --firehose, --methods or --config required."
|
|
853
|
-
$stderr.puts "Try --help for help."
|
|
854
|
-
exit(-1)
|
|
855
|
-
end
|
|
856
|
-
|
|
857
|
-
if opts[:fork_given] and opts[:pid].size != 1
|
|
858
|
-
parser.die :fork, '(can only be invoked with one pid)'
|
|
859
|
-
end
|
|
860
|
-
|
|
861
|
-
if opts[:exec_given]
|
|
862
|
-
if opts[:pid_given]
|
|
863
|
-
parser.die :exec, '(cannot exec and attach to pid)'
|
|
864
|
-
end
|
|
865
|
-
if opts[:fork_given]
|
|
866
|
-
parser.die :fork, '(cannot fork inside newly execed process)'
|
|
867
|
-
end
|
|
868
|
-
end
|
|
869
|
-
|
|
870
|
-
methods, smethods = [], []
|
|
871
|
-
|
|
872
|
-
if opts[:methods_given]
|
|
873
|
-
methods += opts[:methods]
|
|
874
|
-
end
|
|
875
|
-
if opts[:slow_methods_given]
|
|
876
|
-
smethods += opts[:slow_methods]
|
|
877
|
-
end
|
|
878
|
-
|
|
879
|
-
if opts[:config_given]
|
|
880
|
-
Array(opts[:config]).each do |config|
|
|
881
|
-
file = [
|
|
882
|
-
config,
|
|
883
|
-
File.expand_path("../../tracers/#{config}.tracer", __FILE__)
|
|
884
|
-
].find{ |f| File.exists?(f) }
|
|
885
|
-
|
|
886
|
-
unless file
|
|
887
|
-
parser.die :config, '(file does not exist)'
|
|
888
|
-
end
|
|
889
|
-
|
|
890
|
-
File.readlines(file).each do |line|
|
|
891
|
-
line.strip!
|
|
892
|
-
next if line =~ /^#/
|
|
893
|
-
next if line.empty?
|
|
894
|
-
|
|
895
|
-
methods << line
|
|
896
|
-
end
|
|
897
|
-
end
|
|
898
|
-
end
|
|
899
|
-
|
|
900
|
-
tracee = nil
|
|
901
|
-
|
|
902
|
-
if opts[:ps_given]
|
|
903
|
-
list = `ps aux`.split("\n")
|
|
904
|
-
filtered = list.grep(Regexp.new opts[:ps])
|
|
905
|
-
filtered.reject! do |line|
|
|
906
|
-
line =~ /^\w+\s+(#{Process.pid}|#{Process.ppid})\s+/ # cannot trace self
|
|
907
|
-
end
|
|
908
|
-
|
|
909
|
-
if filtered.size > 0
|
|
910
|
-
max_len = filtered.size.to_s.size
|
|
911
|
-
|
|
912
|
-
STDERR.puts "*** found #{filtered.size} processes matching #{opts[:ps].inspect}"
|
|
913
|
-
filtered.each_with_index do |line, i|
|
|
914
|
-
STDERR.puts " [#{(i+1).to_s.rjust(max_len)}] #{line.strip}"
|
|
915
|
-
end
|
|
916
|
-
STDERR.puts " [#{'0'.rjust(max_len)}] all #{filtered.size} processes"
|
|
917
|
-
|
|
918
|
-
while true
|
|
919
|
-
STDERR.sync = true
|
|
920
|
-
STDERR.print "*** trace which processes? (0/1,4): "
|
|
921
|
-
|
|
922
|
-
begin
|
|
923
|
-
input = gets
|
|
924
|
-
rescue Interrupt
|
|
925
|
-
exit 1
|
|
926
|
-
end
|
|
927
|
-
|
|
928
|
-
if input =~ /^(\d+,?)+$/
|
|
929
|
-
if input.strip == '0'
|
|
930
|
-
pids = filtered.map do |line|
|
|
931
|
-
line.split[1].to_i
|
|
932
|
-
end
|
|
933
|
-
else
|
|
934
|
-
indices = input.split(',').map(&:to_i)
|
|
935
|
-
pids = indices.map do |i|
|
|
936
|
-
if i > 0 and line = filtered[i-1]
|
|
937
|
-
line.split[1].to_i
|
|
938
|
-
end
|
|
939
|
-
end
|
|
940
|
-
end
|
|
941
|
-
|
|
942
|
-
unless pids.include?(nil)
|
|
943
|
-
opts[:pid] = pids
|
|
944
|
-
break
|
|
945
|
-
end
|
|
946
|
-
end
|
|
947
|
-
end
|
|
948
|
-
else
|
|
949
|
-
STDERR.puts "*** could not find any processes matching #{opts[:ps].inspect}"
|
|
950
|
-
exit 1
|
|
951
|
-
end
|
|
952
|
-
end
|
|
953
|
-
|
|
954
|
-
if opts[:exec_given]
|
|
955
|
-
tracee = fork{
|
|
956
|
-
Process.setsid
|
|
957
|
-
ENV['RUBYOPT'] = "-r#{File.expand_path('../../lib/rbtrace',__FILE__)}"
|
|
958
|
-
exec(*opts[:exec])
|
|
959
|
-
}
|
|
960
|
-
STDERR.puts "*** spawned child #{tracee}: #{opts[:exec].inspect[1..-2]}"
|
|
961
|
-
|
|
962
|
-
if (secs = opts[:wait]) > 0
|
|
963
|
-
STDERR.puts "*** waiting #{secs} seconds for child to boot up"
|
|
964
|
-
sleep secs
|
|
965
|
-
end
|
|
966
|
-
|
|
967
|
-
elsif opts[:pid].size <= 1
|
|
968
|
-
tracee = opts[:pid].first
|
|
969
|
-
|
|
970
|
-
else
|
|
971
|
-
tracers = []
|
|
972
|
-
|
|
973
|
-
opts[:pid].each do |pid|
|
|
974
|
-
if child = fork
|
|
975
|
-
tracers << child
|
|
976
|
-
else
|
|
977
|
-
Process.setpgrp
|
|
978
|
-
STDIN.reopen '/dev/null'
|
|
979
|
-
$0 = "rbtrace -p #{pid} (parent: #{Process.ppid})"
|
|
980
|
-
|
|
981
|
-
opts[:output] += ".#{pid}" if opts[:output]
|
|
982
|
-
tracee = pid
|
|
983
|
-
|
|
984
|
-
# fall through and start tracing
|
|
985
|
-
break
|
|
986
|
-
end
|
|
987
|
-
end
|
|
988
|
-
|
|
989
|
-
if tracee.nil?
|
|
990
|
-
# this is the parent
|
|
991
|
-
while true
|
|
992
|
-
begin
|
|
993
|
-
break if tracers.empty?
|
|
994
|
-
if pid = Process.wait
|
|
995
|
-
tracers.delete(pid)
|
|
996
|
-
end
|
|
997
|
-
rescue Interrupt, SignalException
|
|
998
|
-
STDERR.puts "*** waiting on child tracers: #{tracers.inspect}"
|
|
999
|
-
tracers.each do |pid|
|
|
1000
|
-
begin
|
|
1001
|
-
Process.kill 'INT', pid
|
|
1002
|
-
rescue Errno::ESRCH
|
|
1003
|
-
end
|
|
1004
|
-
end
|
|
1005
|
-
end
|
|
1006
|
-
end
|
|
1007
|
-
|
|
1008
|
-
exit!
|
|
1009
|
-
end
|
|
1010
|
-
end
|
|
1011
|
-
|
|
1012
|
-
if out = opts[:output]
|
|
1013
|
-
output = File.open(out, opts[:append] ? 'a+' : 'w')
|
|
1014
|
-
output.sync = true
|
|
1015
|
-
end
|
|
1016
|
-
|
|
1017
|
-
begin
|
|
1018
|
-
begin
|
|
1019
|
-
tracer = RBTracer.new(tracee)
|
|
1020
|
-
rescue ArgumentError => e
|
|
1021
|
-
parser.die :pid, "(#{e.message})"
|
|
1022
|
-
end
|
|
1023
|
-
|
|
1024
|
-
if opts[:fork_given]
|
|
1025
|
-
pid = tracer.fork
|
|
1026
|
-
STDERR.puts "*** forked off a busy looping copy at #{pid} (make sure to kill -9 it when you're done)"
|
|
1027
|
-
|
|
1028
|
-
elsif opts[:backtrace_given]
|
|
1029
|
-
num = opts[:backtrace]
|
|
1030
|
-
code = "caller.first(#{num}).join('|')"
|
|
1031
|
-
|
|
1032
|
-
if res = tracer.eval(code)
|
|
1033
|
-
tracer.puts res[1..-2].split('|').join("\n ")
|
|
1034
|
-
end
|
|
1035
|
-
|
|
1036
|
-
elsif opts[:eval_given]
|
|
1037
|
-
if res = tracer.eval(code = opts[:eval])
|
|
1038
|
-
tracer.puts ">> #{code}"
|
|
1039
|
-
tracer.puts "=> #{res}"
|
|
1040
|
-
end
|
|
1041
|
-
|
|
1042
|
-
else
|
|
1043
|
-
tracer.out = output if output
|
|
1044
|
-
tracer.timeout = opts[:timeout] if opts[:timeout] > 0
|
|
1045
|
-
tracer.prefix = ' ' * opts[:prefix]
|
|
1046
|
-
tracer.show_time = opts[:start_time]
|
|
1047
|
-
tracer.show_duration = !opts[:no_duration]
|
|
1048
|
-
|
|
1049
|
-
tracer.devmode if opts[:devmode_given]
|
|
1050
|
-
tracer.gc if opts[:gc_given]
|
|
1051
|
-
|
|
1052
|
-
if opts[:firehose_given]
|
|
1053
|
-
tracer.firehose
|
|
1054
|
-
else
|
|
1055
|
-
tracer.add(methods) if methods.any?
|
|
1056
|
-
if opts[:slow_given] || opts[:slowcpu_given]
|
|
1057
|
-
tracer.watch(opts[:slowcpu_given] ? opts[:slowcpu] : opts[:slow], opts[:slowcpu_given])
|
|
1058
|
-
tracer.add_slow(smethods) if smethods.any?
|
|
1059
|
-
end
|
|
1060
|
-
end
|
|
1061
|
-
begin
|
|
1062
|
-
tracer.recv_loop
|
|
1063
|
-
rescue Interrupt, SignalException
|
|
1064
|
-
end
|
|
1065
|
-
end
|
|
1066
|
-
ensure
|
|
1067
|
-
if tracer
|
|
1068
|
-
tracer.detach
|
|
1069
|
-
end
|
|
1070
|
-
|
|
1071
|
-
if opts[:exec_given]
|
|
1072
|
-
STDERR.puts "*** waiting on spawned child #{tracee}"
|
|
1073
|
-
Process.kill 'TERM', tracee
|
|
1074
|
-
Process.waitpid(tracee)
|
|
1075
|
-
end
|
|
1076
|
-
end
|
|
1077
|
-
end
|
|
1078
|
-
end
|
|
1079
|
-
|
|
1080
|
-
RBTracer.run
|
|
5
|
+
RBTraceCLI.run
|