class RBTracer

Constants

VERSION

Attributes

out[RW]

Public: The IO where tracing output is written (default: STDOUT).

pid[R]

Public: The Fixnum pid of the traced process.

prefix[RW]

The String prefix used on nested method calls (default: ' ').

show_duration[RW]

The Boolean flag for showing how long method calls take (default: true).

show_time[RW]

The Boolean flag for showing the timestamp when method calls start (default: false).

timeout[RW]

Public: The timeout before giving up on attaching/detaching to a process.

Public Class Methods

new(pid) click to toggle source

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(methods, slow=false) click to toggle source

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
add_slow(methods) click to toggle source

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() click to toggle source

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
clean_socket_path() click to toggle source
# File lib/rbtrace/rbtracer.rb, line 98
def clean_socket_path
  FileUtils.rm(socket_path) if File.exists?(socket_path)
end
detach() click to toggle source

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
devmode() click to toggle source

Turn on dev mode.

Returns nothing.

# File lib/rbtrace/rbtracer.rb, line 122
def devmode
  send_cmd(:devmode)
end
eval(code) click to toggle source

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
firehose() click to toggle source

Turn on the firehose (show all method calls).

Returns nothing.

# File lib/rbtrace/rbtracer.rb, line 115
def firehose
  send_cmd(:firehose)
end
fork() click to toggle source

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
gc() click to toggle source

Turn on GC tracing.

Returns nothing.

# File lib/rbtrace/rbtracer.rb, line 160
def gc
  send_cmd(:gc)
end
puts(arg=nil) click to toggle source
# File lib/rbtrace/rbtracer.rb, line 280
def puts(arg=nil)
  @printed_newline = true
  arg ? @out.puts(arg) : @out.puts
end
recv_lines() click to toggle source

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
recv_loop() click to toggle source

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
socket_path() click to toggle source
# File lib/rbtrace/rbtracer.rb, line 94
def socket_path
  "/tmp/rbtrace-#{@pid}.sock"
end
watch(msec, cpu_only=false) click to toggle source

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