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
data/lib/rbtrace/msgq.rb
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
require 'ffi'
|
|
2
|
+
|
|
3
|
+
module MsgQ
|
|
4
|
+
extend FFI::Library
|
|
5
|
+
ffi_lib FFI::CURRENT_PROCESS
|
|
6
|
+
|
|
7
|
+
class EventMsg < FFI::Struct
|
|
8
|
+
BUF_SIZE = RUBY_PLATFORM =~ /linux/ ? 256 : 120
|
|
9
|
+
IPC_NOWAIT = 004000
|
|
10
|
+
|
|
11
|
+
layout :mtype, :long,
|
|
12
|
+
:buf, [:char, BUF_SIZE]
|
|
13
|
+
|
|
14
|
+
def self.send_cmd(q, str)
|
|
15
|
+
msg = EventMsg.new
|
|
16
|
+
msg[:mtype] = 1
|
|
17
|
+
msg[:buf].to_ptr.put_string(0, str)
|
|
18
|
+
|
|
19
|
+
ret = MsgQ.msgsnd(q, msg, BUF_SIZE, 0)
|
|
20
|
+
FFI::LastError.raise if ret == -1
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def self.recv_cmd(q, block=true)
|
|
24
|
+
MsgQ.rb_enable_interrupt if RUBY_VERSION > '1.9' && RUBY_VERSION < '2.0'
|
|
25
|
+
|
|
26
|
+
msg = EventMsg.new
|
|
27
|
+
ret = MsgQ.msgrcv(q, msg, BUF_SIZE, 0, block ? 0 : IPC_NOWAIT)
|
|
28
|
+
if ret == -1
|
|
29
|
+
if !block and [Errno::EAGAIN, Errno::ENOMSG].include?(FFI::LastError.exception)
|
|
30
|
+
return nil
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
FFI::LastError.raise
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
msg[:buf].to_ptr.read_string_length(BUF_SIZE)
|
|
37
|
+
ensure
|
|
38
|
+
MsgQ.rb_disable_interrupt if RUBY_VERSION > '1.9' && RUBY_VERSION < '2.0'
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
attach_function :msgget, [:int, :int], :int
|
|
43
|
+
attach_function :msgrcv, [:int, EventMsg.ptr, :size_t, :long, :int], :int
|
|
44
|
+
attach_function :msgsnd, [:int, EventMsg.ptr, :size_t, :int], :int
|
|
45
|
+
|
|
46
|
+
if RUBY_VERSION > '1.9' && RUBY_VERSION < '2.0'
|
|
47
|
+
attach_function :rb_enable_interrupt, [], :void
|
|
48
|
+
attach_function :rb_disable_interrupt, [], :void
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,570 @@
|
|
|
1
|
+
require 'socket'
|
|
2
|
+
require 'fileutils'
|
|
3
|
+
require 'msgpack'
|
|
4
|
+
require 'ffi'
|
|
5
|
+
require 'rbtrace/core_ext'
|
|
6
|
+
require 'rbtrace/msgq'
|
|
7
|
+
|
|
8
|
+
class RBTracer
|
|
9
|
+
# Public: The Fixnum pid of the traced process.
|
|
10
|
+
attr_reader :pid
|
|
11
|
+
|
|
12
|
+
# Public: The IO where tracing output is written (default: STDOUT).
|
|
13
|
+
attr_accessor :out
|
|
14
|
+
|
|
15
|
+
# Public: The timeout before giving up on attaching/detaching to a process.
|
|
16
|
+
attr_accessor :timeout
|
|
17
|
+
|
|
18
|
+
# The String prefix used on nested method calls (default: ' ').
|
|
19
|
+
attr_accessor :prefix
|
|
20
|
+
|
|
21
|
+
# The Boolean flag for showing how long method calls take (default: true).
|
|
22
|
+
attr_accessor :show_duration
|
|
23
|
+
|
|
24
|
+
# The Boolean flag for showing the timestamp when method calls start (default: false).
|
|
25
|
+
attr_accessor :show_time
|
|
26
|
+
|
|
27
|
+
# Create a new tracer
|
|
28
|
+
#
|
|
29
|
+
# pid - The String of Fixnum process id
|
|
30
|
+
#
|
|
31
|
+
# Returns a tracer.
|
|
32
|
+
def initialize(pid)
|
|
33
|
+
begin
|
|
34
|
+
raise ArgumentError unless pid
|
|
35
|
+
@pid = pid.to_i
|
|
36
|
+
raise ArgumentError unless @pid > 0
|
|
37
|
+
Process.kill(0, @pid)
|
|
38
|
+
rescue TypeError, ArgumentError
|
|
39
|
+
raise ArgumentError, 'pid required'
|
|
40
|
+
rescue Errno::ESRCH
|
|
41
|
+
raise ArgumentError, 'invalid pid'
|
|
42
|
+
rescue Errno::EPERM
|
|
43
|
+
raise ArgumentError, 'could not signal process, are you running as root?'
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
path = "/tmp/rbtrace-#{@pid}.sock"
|
|
47
|
+
@sock = Socket.new Socket::AF_UNIX, Socket::SOCK_DGRAM, 0
|
|
48
|
+
@sockaddr = Socket.pack_sockaddr_un(path)
|
|
49
|
+
@sock.bind(@sockaddr)
|
|
50
|
+
FileUtils.chmod 0666, path
|
|
51
|
+
at_exit{ FileUtils.rm(path) if File.exists?(path) }
|
|
52
|
+
|
|
53
|
+
5.times do
|
|
54
|
+
signal
|
|
55
|
+
sleep 0.15 # wait for process to create msgqs
|
|
56
|
+
|
|
57
|
+
@qo = MsgQ.msgget(-@pid, 0666)
|
|
58
|
+
|
|
59
|
+
break if @qo > -1
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
if @qo == -1
|
|
63
|
+
raise ArgumentError, 'pid is not listening for messages, did you `require "rbtrace"`'
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
@klasses = {}
|
|
67
|
+
@methods = {}
|
|
68
|
+
@tracers = Hash.new{ |h,k|
|
|
69
|
+
h[k] = {
|
|
70
|
+
:query => nil,
|
|
71
|
+
:times => [],
|
|
72
|
+
:names => [],
|
|
73
|
+
:exprs => {},
|
|
74
|
+
:last => false,
|
|
75
|
+
:arglist => false
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
@max_nesting = @last_nesting = @nesting = 0
|
|
79
|
+
@last_tracer = nil
|
|
80
|
+
|
|
81
|
+
@timeout = 5
|
|
82
|
+
|
|
83
|
+
@out = STDOUT
|
|
84
|
+
@out.sync = true
|
|
85
|
+
@prefix = ' '
|
|
86
|
+
@printed_newline = true
|
|
87
|
+
|
|
88
|
+
@show_time = false
|
|
89
|
+
@show_duration = true
|
|
90
|
+
@watch_slow = false
|
|
91
|
+
|
|
92
|
+
attach
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Watch for method calls slower than a threshold.
|
|
96
|
+
#
|
|
97
|
+
# msec - The Fixnum threshold in milliseconds
|
|
98
|
+
#
|
|
99
|
+
# Returns nothing.
|
|
100
|
+
def watch(msec, cpu_only=false)
|
|
101
|
+
@watch_slow = true
|
|
102
|
+
send_cmd(cpu_only ? :watchcpu : :watch, msec)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Turn on the firehose (show all method calls).
|
|
106
|
+
#
|
|
107
|
+
# Returns nothing.
|
|
108
|
+
def firehose
|
|
109
|
+
send_cmd(:firehose)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Turn on dev mode.
|
|
113
|
+
#
|
|
114
|
+
# Returns nothing.
|
|
115
|
+
def devmode
|
|
116
|
+
send_cmd(:devmode)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Fork the process and return the copy's pid.
|
|
120
|
+
#
|
|
121
|
+
# Returns a Fixnum pid.
|
|
122
|
+
def fork
|
|
123
|
+
send_cmd(:fork)
|
|
124
|
+
if wait('for fork', 30){ !!@forked_pid }
|
|
125
|
+
@forked_pid
|
|
126
|
+
else
|
|
127
|
+
STDERR.puts '*** timed out waiting for fork'
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# Evaluate some ruby code.
|
|
132
|
+
#
|
|
133
|
+
# Returns the String result.
|
|
134
|
+
def eval(code)
|
|
135
|
+
if (err = valid_syntax?(code)) != true
|
|
136
|
+
raise ArgumentError, "#{err.class} for expression #{code.inspect}"
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
send_cmd(:eval, code)
|
|
140
|
+
|
|
141
|
+
if wait('for eval response', 15){ !!@eval_result }
|
|
142
|
+
@eval_result
|
|
143
|
+
else
|
|
144
|
+
STDERR.puts '*** timed out waiting for eval response'
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# Turn on GC tracing.
|
|
149
|
+
#
|
|
150
|
+
# Returns nothing.
|
|
151
|
+
def gc
|
|
152
|
+
send_cmd(:gc)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Restrict slow tracing to a specific list of methods.
|
|
156
|
+
#
|
|
157
|
+
# methods - The String or Array of method selectors.
|
|
158
|
+
#
|
|
159
|
+
# Returns nothing.
|
|
160
|
+
def add_slow(methods)
|
|
161
|
+
add(methods, true)
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# Add tracers for the given list of methods.
|
|
165
|
+
#
|
|
166
|
+
# methods - The String or Array of method selectors to trace.
|
|
167
|
+
#
|
|
168
|
+
# Returns nothing.
|
|
169
|
+
def add(methods, slow=false)
|
|
170
|
+
Array(methods).each do |func|
|
|
171
|
+
func = func.strip
|
|
172
|
+
next if func.empty?
|
|
173
|
+
|
|
174
|
+
if func =~ /^(.+?)\((.+)\)$/
|
|
175
|
+
name, args = $1, $2
|
|
176
|
+
args = args.split(',').map{ |a| a.strip }
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
send_cmd(:add, name || func, slow)
|
|
180
|
+
|
|
181
|
+
if args and args.any?
|
|
182
|
+
args.each do |arg|
|
|
183
|
+
if (err = valid_syntax?(arg)) != true
|
|
184
|
+
raise ArgumentError, "#{err.class} for expression #{arg.inspect} in method #{func.inspect}"
|
|
185
|
+
end
|
|
186
|
+
if arg =~ /^@/ and arg !~ /^@[_a-z][_a-z0-9]+$/i
|
|
187
|
+
# arg[0]=='@' means ivar, but if this is an expr
|
|
188
|
+
# we can hack a space in front so it gets eval'd instead
|
|
189
|
+
arg = " #{arg}"
|
|
190
|
+
end
|
|
191
|
+
send_cmd(:addexpr, arg)
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
# Attach to the process.
|
|
198
|
+
#
|
|
199
|
+
# Returns nothing.
|
|
200
|
+
def attach
|
|
201
|
+
send_cmd(:attach, Process.pid)
|
|
202
|
+
if wait('to attach'){ @attached == true }
|
|
203
|
+
STDERR.puts "*** attached to process #{pid}"
|
|
204
|
+
else
|
|
205
|
+
raise ArgumentError, 'process already being traced?'
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# Detach from the traced process.
|
|
210
|
+
#
|
|
211
|
+
# Returns nothing.
|
|
212
|
+
def detach
|
|
213
|
+
begin
|
|
214
|
+
send_cmd(:detach)
|
|
215
|
+
rescue Errno::ESRCH
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
newline
|
|
219
|
+
|
|
220
|
+
if wait('to detach cleanly'){ @attached == false }
|
|
221
|
+
newline
|
|
222
|
+
STDERR.puts "*** detached from process #{pid}"
|
|
223
|
+
else
|
|
224
|
+
newline
|
|
225
|
+
STDERR.puts "*** could not detach cleanly from process #{pid}"
|
|
226
|
+
end
|
|
227
|
+
rescue Errno::EINVAL, Errno::EIDRM
|
|
228
|
+
newline
|
|
229
|
+
STDERR.puts "*** process #{pid} is gone"
|
|
230
|
+
# STDERR.puts "*** #{$!.inspect}"
|
|
231
|
+
# STDERR.puts $!.backtrace.join("\n ")
|
|
232
|
+
rescue Interrupt, SignalException
|
|
233
|
+
retry
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
# Process events from the traced process.
|
|
237
|
+
#
|
|
238
|
+
# Returns nothing.
|
|
239
|
+
def recv_loop
|
|
240
|
+
while true
|
|
241
|
+
# block until a message arrives
|
|
242
|
+
process_line(recv_cmd)
|
|
243
|
+
|
|
244
|
+
# process any remaining messages
|
|
245
|
+
recv_lines
|
|
246
|
+
end
|
|
247
|
+
rescue Errno::EINVAL, Errno::EIDRM
|
|
248
|
+
# process went away
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
# Process events from the traced process, without blocking if
|
|
252
|
+
# there is nothing to do. This is a useful way to drain the buffer
|
|
253
|
+
# so messages do not accumulate in kernel land.
|
|
254
|
+
#
|
|
255
|
+
# Returns nothing.
|
|
256
|
+
def recv_lines
|
|
257
|
+
50.times do
|
|
258
|
+
break unless line = recv_cmd(false)
|
|
259
|
+
process_line(line)
|
|
260
|
+
end
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
def puts(arg=nil)
|
|
264
|
+
@printed_newline = true
|
|
265
|
+
arg ? @out.puts(arg) : @out.puts
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
private
|
|
269
|
+
|
|
270
|
+
def signal
|
|
271
|
+
Process.kill 'URG', @pid
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
# Process incoming events until either a timeout or a condition becomes true.
|
|
275
|
+
#
|
|
276
|
+
# time - The Fixnum timeout in seconds.
|
|
277
|
+
# block - The Block that is checked every 50ms until it returns true.
|
|
278
|
+
#
|
|
279
|
+
# Returns true when the condition was met, or false on a timeout.
|
|
280
|
+
def wait(reason, time=@timeout)
|
|
281
|
+
wait = 0.05 # polling interval
|
|
282
|
+
|
|
283
|
+
(time/wait).to_i.times do
|
|
284
|
+
begin
|
|
285
|
+
recv_lines
|
|
286
|
+
sleep(wait)
|
|
287
|
+
begin
|
|
288
|
+
signal
|
|
289
|
+
rescue Errno::ESRCH
|
|
290
|
+
break
|
|
291
|
+
end
|
|
292
|
+
time -= wait
|
|
293
|
+
|
|
294
|
+
return true if yield
|
|
295
|
+
rescue Interrupt
|
|
296
|
+
STDERR.puts "*** waiting #{reason} (#{time.to_i}s left)"
|
|
297
|
+
retry
|
|
298
|
+
end
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
false
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
def send_cmd(*cmd)
|
|
305
|
+
begin
|
|
306
|
+
msg = cmd.to_msgpack
|
|
307
|
+
raise ArgumentError, 'command is too long' if msg.bytesize > MsgQ::EventMsg::BUF_SIZE
|
|
308
|
+
MsgQ::EventMsg.send_cmd(@qo, msg)
|
|
309
|
+
rescue Errno::EINTR
|
|
310
|
+
retry
|
|
311
|
+
end
|
|
312
|
+
signal
|
|
313
|
+
recv_lines
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
def recv_cmd(block=true)
|
|
317
|
+
if block
|
|
318
|
+
@sock.recv(65536)
|
|
319
|
+
else
|
|
320
|
+
@sock.recv_nonblock(65536)
|
|
321
|
+
end
|
|
322
|
+
rescue Errno::EAGAIN
|
|
323
|
+
nil
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
def valid_syntax?(code)
|
|
327
|
+
begin
|
|
328
|
+
Kernel.eval("#{code}\nBEGIN {return true}", nil, 'rbtrace_expression', 0)
|
|
329
|
+
rescue Exception => e
|
|
330
|
+
e
|
|
331
|
+
end
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
def print(arg)
|
|
335
|
+
@printed_newline = false
|
|
336
|
+
@out.print(arg)
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
def newline
|
|
340
|
+
puts unless @printed_newline
|
|
341
|
+
@printed_newline = true
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
def parse_cmd(line)
|
|
345
|
+
unpacker = MessagePack::Unpacker.new
|
|
346
|
+
unpacker.feed(line)
|
|
347
|
+
|
|
348
|
+
obj = nil
|
|
349
|
+
unpacker.each{|o| obj = o; break }
|
|
350
|
+
obj
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
def process_line(line)
|
|
354
|
+
return unless cmd = parse_cmd(line)
|
|
355
|
+
event = cmd.shift
|
|
356
|
+
|
|
357
|
+
case event
|
|
358
|
+
when 'during_gc'
|
|
359
|
+
sleep 0.01
|
|
360
|
+
signal
|
|
361
|
+
return
|
|
362
|
+
|
|
363
|
+
when 'attached'
|
|
364
|
+
tracer_pid, = *cmd
|
|
365
|
+
if tracer_pid != Process.pid
|
|
366
|
+
STDERR.puts "*** process #{pid} is already being traced (#{tracer_pid} != #{Process.pid})"
|
|
367
|
+
exit!(-1)
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
@attached = true
|
|
371
|
+
return
|
|
372
|
+
|
|
373
|
+
when 'detached'
|
|
374
|
+
tracer_pid, = *cmd
|
|
375
|
+
if tracer_pid != Process.pid
|
|
376
|
+
STDERR.puts "*** process #{pid} detached #{tracer_pid}, but we are #{Process.pid}"
|
|
377
|
+
else
|
|
378
|
+
@attached = false
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
return
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
unless @attached
|
|
385
|
+
STDERR.puts "*** got #{event} before attaching"
|
|
386
|
+
return
|
|
387
|
+
end
|
|
388
|
+
|
|
389
|
+
case event
|
|
390
|
+
when 'forked'
|
|
391
|
+
pid, = *cmd
|
|
392
|
+
@forked_pid = pid
|
|
393
|
+
|
|
394
|
+
when 'evaled'
|
|
395
|
+
res, = *cmd
|
|
396
|
+
@eval_result = res
|
|
397
|
+
|
|
398
|
+
when 'mid'
|
|
399
|
+
mid, name = *cmd
|
|
400
|
+
@methods[mid] = name
|
|
401
|
+
|
|
402
|
+
when 'klass'
|
|
403
|
+
kid, name = *cmd
|
|
404
|
+
@klasses[kid] = name
|
|
405
|
+
|
|
406
|
+
when 'add'
|
|
407
|
+
tracer_id, query = *cmd
|
|
408
|
+
if tracer_id == -1
|
|
409
|
+
STDERR.puts "*** unable to add tracer for #{query}"
|
|
410
|
+
else
|
|
411
|
+
@tracers.delete(tracer_id)
|
|
412
|
+
@tracers[tracer_id][:query] = query
|
|
413
|
+
end
|
|
414
|
+
|
|
415
|
+
when 'newexpr'
|
|
416
|
+
tracer_id, expr_id, expr = *cmd
|
|
417
|
+
tracer = @tracers[tracer_id]
|
|
418
|
+
|
|
419
|
+
if expr_id > -1
|
|
420
|
+
tracer[:exprs][expr_id] = expr.strip
|
|
421
|
+
end
|
|
422
|
+
|
|
423
|
+
when 'exprval'
|
|
424
|
+
tracer_id, expr_id, val = *cmd
|
|
425
|
+
|
|
426
|
+
tracer = @tracers[tracer_id]
|
|
427
|
+
expr = tracer[:exprs][expr_id]
|
|
428
|
+
|
|
429
|
+
if tracer[:arglist]
|
|
430
|
+
print ', '
|
|
431
|
+
else
|
|
432
|
+
print '('
|
|
433
|
+
end
|
|
434
|
+
|
|
435
|
+
print "#{expr}="
|
|
436
|
+
print val
|
|
437
|
+
tracer[:arglist] = true
|
|
438
|
+
|
|
439
|
+
when 'call','ccall'
|
|
440
|
+
time, tracer_id, mid, is_singleton, klass = *cmd
|
|
441
|
+
|
|
442
|
+
tracer = @tracers[tracer_id]
|
|
443
|
+
klass = @klasses[klass]
|
|
444
|
+
name = klass ? "#{klass}#{ is_singleton ? '.' : '#' }" : ''
|
|
445
|
+
name += @methods[mid] || '(unknown)'
|
|
446
|
+
|
|
447
|
+
tracer[:times] << time
|
|
448
|
+
tracer[:names] << name
|
|
449
|
+
|
|
450
|
+
if @last_tracer and @last_tracer[:arglist]
|
|
451
|
+
print ')'
|
|
452
|
+
@last_tracer[:arglist] = false
|
|
453
|
+
end
|
|
454
|
+
newline
|
|
455
|
+
if @show_time
|
|
456
|
+
t = Time.at(time/1_000_000)
|
|
457
|
+
print t.strftime("%H:%M:%S.")
|
|
458
|
+
print "%06d " % (time - t.to_f*1_000_000).round
|
|
459
|
+
end
|
|
460
|
+
print @prefix*@nesting if @nesting > 0
|
|
461
|
+
print name
|
|
462
|
+
|
|
463
|
+
@nesting += 1
|
|
464
|
+
@max_nesting = @nesting if @nesting > @max_nesting
|
|
465
|
+
@last_nesting = @nesting
|
|
466
|
+
@last_tracer = tracer
|
|
467
|
+
tracer[:last] = "#{name}:#{@nesting-1}"
|
|
468
|
+
|
|
469
|
+
when 'return','creturn'
|
|
470
|
+
time, tracer_id = *cmd
|
|
471
|
+
tracer = @tracers[tracer_id]
|
|
472
|
+
|
|
473
|
+
@nesting -= 1 if @nesting > 0
|
|
474
|
+
|
|
475
|
+
if start = tracer[:times].pop
|
|
476
|
+
name = tracer[:names].pop
|
|
477
|
+
diff = time - start
|
|
478
|
+
@last_tracer[:arglist] = false if @last_tracer and @last_tracer[:last] != "#{name}:#{@nesting}"
|
|
479
|
+
|
|
480
|
+
print ')' if @last_tracer and @last_tracer[:arglist]
|
|
481
|
+
|
|
482
|
+
unless tracer == @last_tracer and @last_tracer[:last] == "#{name}:#{@nesting}"
|
|
483
|
+
newline
|
|
484
|
+
print ' '*16 if @show_time
|
|
485
|
+
print @prefix*@nesting if @nesting > 0
|
|
486
|
+
print name
|
|
487
|
+
end
|
|
488
|
+
print ' <%f>' % (diff/1_000_000.0) if @show_duration
|
|
489
|
+
newline
|
|
490
|
+
|
|
491
|
+
if @nesting == 0 and @max_nesting > 1
|
|
492
|
+
# unless tracer == @last_tracer and @last_tracer[:last] == name
|
|
493
|
+
puts
|
|
494
|
+
# end
|
|
495
|
+
end
|
|
496
|
+
end
|
|
497
|
+
|
|
498
|
+
tracer[:arglist] = false
|
|
499
|
+
@last_nesting = @nesting
|
|
500
|
+
|
|
501
|
+
when 'slow', 'cslow'
|
|
502
|
+
time, diff, nesting, mid, is_singleton, klass = *cmd
|
|
503
|
+
|
|
504
|
+
klass = @klasses[klass]
|
|
505
|
+
name = klass ? "#{klass}#{ is_singleton ? '.' : '#' }" : ''
|
|
506
|
+
name += @methods[mid] || '(unknown)'
|
|
507
|
+
|
|
508
|
+
newline
|
|
509
|
+
nesting = @nesting if @nesting > 0
|
|
510
|
+
|
|
511
|
+
if @show_time
|
|
512
|
+
t = Time.at(time/1_000_000)
|
|
513
|
+
print t.strftime("%H:%M:%S.")
|
|
514
|
+
print "%06d " % (time - t.to_f*1_000_000).round
|
|
515
|
+
end
|
|
516
|
+
|
|
517
|
+
print @prefix*nesting if nesting > 0
|
|
518
|
+
print name
|
|
519
|
+
if @show_duration
|
|
520
|
+
print ' '
|
|
521
|
+
print "<%f>" % (diff/1_000_000.0)
|
|
522
|
+
end
|
|
523
|
+
puts
|
|
524
|
+
puts if nesting == 0 and @max_nesting > 1
|
|
525
|
+
|
|
526
|
+
@max_nesting = nesting if nesting > @max_nesting
|
|
527
|
+
@last_nesting = nesting
|
|
528
|
+
|
|
529
|
+
when 'gc_start'
|
|
530
|
+
time, = *cmd
|
|
531
|
+
@gc_start = time
|
|
532
|
+
print 'garbage_collect'
|
|
533
|
+
|
|
534
|
+
when 'gc_end'
|
|
535
|
+
time, = *cmd
|
|
536
|
+
diff = time - @gc_start
|
|
537
|
+
# if @gc_mark
|
|
538
|
+
# mark = ((@gc_mark - @gc_start) * 100.0 / diff).to_i
|
|
539
|
+
# print '(mark: %d%%, sweep: %d%%)' % [mark, 100-mark]
|
|
540
|
+
# end
|
|
541
|
+
print ' <%f>' % (diff/1_000_000.0) if @show_duration
|
|
542
|
+
@gc_start = nil
|
|
543
|
+
newline
|
|
544
|
+
|
|
545
|
+
when 'gc'
|
|
546
|
+
time, = *cmd
|
|
547
|
+
@gc_mark = time
|
|
548
|
+
|
|
549
|
+
unless @gc_start
|
|
550
|
+
newline
|
|
551
|
+
if @show_time
|
|
552
|
+
t = Time.at(time/1_000_000)
|
|
553
|
+
print t.strftime("%H:%M:%S.")
|
|
554
|
+
print "%06d " % (time - t.to_f*1_000_000).round
|
|
555
|
+
end
|
|
556
|
+
print @prefix*@last_nesting if @last_nesting > 0
|
|
557
|
+
print "garbage_collect"
|
|
558
|
+
puts if @watch_slow
|
|
559
|
+
end
|
|
560
|
+
|
|
561
|
+
else
|
|
562
|
+
puts "unknown event #{event}: #{cmd.inspect}"
|
|
563
|
+
|
|
564
|
+
end
|
|
565
|
+
rescue => e
|
|
566
|
+
STDERR.puts "error on #{event}: #{cmd.inspect}"
|
|
567
|
+
raise e
|
|
568
|
+
end
|
|
569
|
+
|
|
570
|
+
end
|