Class: RBTracer

Inherits:
Object
  • Object
show all
Defined in:
lib/rbtrace/version.rb,
lib/rbtrace/rbtracer.rb

Constant Summary collapse

VERSION =
'0.4.7'

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(pid) ⇒ RBTracer

Create a new tracer

pid - The String of Fixnum process id

Returns a tracer.



32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# 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

Instance Attribute Details

#outObject

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



13
14
15
# File 'lib/rbtrace/rbtracer.rb', line 13

def out
  @out
end

#pidObject (readonly)

Public: The Fixnum pid of the traced process.



10
11
12
# File 'lib/rbtrace/rbtracer.rb', line 10

def pid
  @pid
end

#prefixObject

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



19
20
21
# File 'lib/rbtrace/rbtracer.rb', line 19

def prefix
  @prefix
end

#show_durationObject

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



22
23
24
# File 'lib/rbtrace/rbtracer.rb', line 22

def show_duration
  @show_duration
end

#show_timeObject

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



25
26
27
# File 'lib/rbtrace/rbtracer.rb', line 25

def show_time
  @show_time
end

#timeoutObject

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



16
17
18
# File 'lib/rbtrace/rbtracer.rb', line 16

def timeout
  @timeout
end

Instance Method Details

#add(methods, slow = false) ⇒ Object

Add tracers for the given list of methods.

methods - The String or Array of method selectors to trace.

Returns nothing.



178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
# 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) ⇒ Object

Restrict slow tracing to a specific list of methods.

methods - The String or Array of method selectors.

Returns nothing.



169
170
171
# File 'lib/rbtrace/rbtracer.rb', line 169

def add_slow(methods)
  add(methods, true)
end

#attachObject

Attach to the process.

Returns nothing.



209
210
211
212
213
214
215
216
# 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_pathObject



98
99
100
# File 'lib/rbtrace/rbtracer.rb', line 98

def clean_socket_path
  FileUtils.rm(socket_path) if File.exists?(socket_path)
end

#detachObject

Detach from the traced process.

Returns nothing.



221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
# 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

#devmodeObject

Turn on dev mode.

Returns nothing.



122
123
124
# File 'lib/rbtrace/rbtracer.rb', line 122

def devmode
  send_cmd(:devmode)
end

#eval(code) ⇒ Object

Evaluate some ruby code.

Returns the String result.



141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
# 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', 15){ !!@eval_result }
    res = @eval_result
    @eval_result = nil
    res
  else
    STDERR.puts '*** timed out waiting for eval response'
  end
end

#firehoseObject

Turn on the firehose (show all method calls).

Returns nothing.



115
116
117
# File 'lib/rbtrace/rbtracer.rb', line 115

def firehose
  send_cmd(:firehose)
end

#forkObject

Fork the process and return the copy’s pid.

Returns a Fixnum pid.



129
130
131
132
133
134
135
136
# 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

#gcObject

Turn on GC tracing.

Returns nothing.



160
161
162
# File 'lib/rbtrace/rbtracer.rb', line 160

def gc
  send_cmd(:gc)
end

#puts(arg = nil) ⇒ Object



280
281
282
283
# File 'lib/rbtrace/rbtracer.rb', line 280

def puts(arg=nil)
  @printed_newline = true
  arg ? @out.puts(arg) : @out.puts
end

#recv_linesObject

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.



273
274
275
276
277
278
# 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_loopObject

Process events from the traced process.

Returns nothing



250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
# 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_pathObject



94
95
96
# File 'lib/rbtrace/rbtracer.rb', line 94

def socket_path
  "/tmp/rbtrace-#{@pid}.sock"
end

#watch(msec, cpu_only = false) ⇒ Object

Watch for method calls slower than a threshold.

msec - The Fixnum threshold in milliseconds

Returns nothing.



107
108
109
110
# File 'lib/rbtrace/rbtracer.rb', line 107

def watch(msec, cpu_only=false)
  @watch_slow = true
  send_cmd(cpu_only ? :watchcpu : :watch, msec)
end