class RBTracer
Constants
- VERSION
Attributes
Public: The IO where tracing output is written (default: STDOUT).
Public: The Fixnum pid of the traced process.
The String prefix used on nested method calls (default: ' ').
The Boolean flag for showing how long method calls take (default: true).
The Boolean flag for showing the timestamp when method calls start (default: false).
Public: The timeout before giving up on attaching/detaching to a process.
Public Class Methods
Create a new tracer
pid - The String of Fixnum process id
Returns a tracer.
# File lib/rbtrace/rbtracer.rb, line 32 def initialize(pid) begin raise ArgumentError unless pid @pid = pid.to_i raise ArgumentError unless @pid > 0 Process.kill(0, @pid) rescue TypeError, ArgumentError raise ArgumentError, 'pid required' rescue Errno::ESRCH raise ArgumentError, 'invalid pid' rescue Errno::EPERM raise ArgumentError, 'could not signal process, are you running as root?' end @sock = Socket.new Socket::AF_UNIX, Socket::SOCK_DGRAM, 0 @sockaddr = Socket.pack_sockaddr_un(socket_path) @sock.bind(@sockaddr) FileUtils.chmod 0666, socket_path at_exit { clean_socket_path } 5.times do signal sleep 0.15 # wait for process to create msgqs @qo = MsgQ.msgget(-@pid, 0666) break if @qo > -1 end if @qo == -1 raise ArgumentError, 'pid is not listening for messages, did you `require "rbtrace"`' end @klasses = {} @methods = {} @tracers = Hash.new{ |h,k| h[k] = { :query => nil, :times => [], :names => [], :exprs => {}, :last => false, :arglist => false } } @max_nesting = @last_nesting = @nesting = 0 @last_tracer = nil @timeout = 5 @out = STDOUT @out.sync = true @prefix = ' ' @printed_newline = true @show_time = false @show_duration = true @watch_slow = false attach end
Public Instance Methods
Add tracers for the given list of methods.
methods - The String or Array of method selectors to trace.
Returns nothing.
# File lib/rbtrace/rbtracer.rb, line 178 def add(methods, slow=false) Array(methods).each do |func| func = func.strip next if func.empty? if func =~ /^(.+?)\((.+)\)$/ name, args = $1, $2 args = args.split(',').map{ |a| a.strip } end send_cmd(:add, name || func, slow) if args and args.any? args.each do |arg| if (err = valid_syntax?(arg)) != true raise ArgumentError, "#{err.class} for expression #{arg.inspect} in method #{func.inspect}" end if arg =~ /^@/ and arg !~ /^@[_a-z][_a-z0-9]+$/i # arg[0]=='@' means ivar, but if this is an expr # we can hack a space in front so it gets eval'd instead arg = " #{arg}" end send_cmd(:addexpr, arg) end end end end
Restrict slow tracing to a specific list of methods.
methods - The String or Array of method selectors.
Returns nothing.
# File lib/rbtrace/rbtracer.rb, line 169 def add_slow(methods) add(methods, true) end
Attach to the process.
Returns nothing.
# File lib/rbtrace/rbtracer.rb, line 209 def attach send_cmd(:attach, Process.pid) if wait('to attach'){ @attached == true } STDERR.puts "*** attached to process #{pid}" else raise ArgumentError, 'process already being traced?' end end
# File lib/rbtrace/rbtracer.rb, line 98 def clean_socket_path FileUtils.rm(socket_path) if File.exists?(socket_path) end
Detach from the traced process.
Returns nothing.
# File lib/rbtrace/rbtracer.rb, line 221 def detach begin send_cmd(:detach) rescue Errno::ESRCH end newline if wait('to detach cleanly'){ @attached == false } newline STDERR.puts "*** detached from process #{pid}" else newline STDERR.puts "*** could not detach cleanly from process #{pid}" end rescue Errno::EINVAL, Errno::EIDRM newline STDERR.puts "*** process #{pid} is gone" # STDERR.puts "*** #{$!.inspect}" # STDERR.puts $!.backtrace.join("\n ") rescue Interrupt, SignalException retry ensure clean_socket_path end
Turn on dev mode.
Returns nothing.
# File lib/rbtrace/rbtracer.rb, line 122 def devmode send_cmd(:devmode) end
Evaluate some ruby code.
Returns the String result.
# File lib/rbtrace/rbtracer.rb, line 141 def eval(code) if (err = valid_syntax?(code)) != true raise ArgumentError, "#{err.class} for expression #{code.inspect}" end send_cmd(:eval, code) if wait('for eval response', timeout){ !!@eval_result } res = @eval_result @eval_result = nil res else STDERR.puts '*** timed out waiting for eval response' end end
Turn on the firehose (show all method calls).
Returns nothing.
# File lib/rbtrace/rbtracer.rb, line 115 def firehose send_cmd(:firehose) end
Fork the process and return the copy's pid.
Returns a Fixnum pid.
# File lib/rbtrace/rbtracer.rb, line 129 def fork send_cmd(:fork) if wait('for fork', 30){ !!@forked_pid } @forked_pid else STDERR.puts '*** timed out waiting for fork' end end
Turn on GC tracing.
Returns nothing.
# File lib/rbtrace/rbtracer.rb, line 160 def gc send_cmd(:gc) end
# File lib/rbtrace/rbtracer.rb, line 280 def puts(arg=nil) @printed_newline = true arg ? @out.puts(arg) : @out.puts end
Process events from the traced process, without blocking if there is nothing to do. This is a useful way to drain the buffer so messages do not accumulate in kernel land.
Returns nothing.
# File lib/rbtrace/rbtracer.rb, line 273 def recv_lines 50.times do break unless line = recv_cmd(false) process_line(line) end end
Process events from the traced process.
Returns nothing
# File lib/rbtrace/rbtracer.rb, line 250 def recv_loop while true ready = IO.select([@sock], nil, nil, 1) if ready # block until a message arrives process_line(recv_cmd) # process any remaining messages recv_lines else Process.kill(0, @pid) end end rescue Errno::EINVAL, Errno::EIDRM, Errno::ESRCH # process went away end
# File lib/rbtrace/rbtracer.rb, line 94 def socket_path "/tmp/rbtrace-#{@pid}.sock" end
Watch for method calls slower than a threshold.
msec - The Fixnum threshold in milliseconds
Returns nothing.
# File lib/rbtrace/rbtracer.rb, line 107 def watch(msec, cpu_only=false) @watch_slow = true send_cmd(cpu_only ? :watchcpu : :watch, msec) end