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