1#--
2# $Release Version: 0.3$
3# $Revision: 1.12 $
4require "thread"
5
6##
7# Outputs a source level execution trace of a Ruby program.
8#
9# It does this by registering an event handler with Kernel#set_trace_func for
10# processing incoming events.  It also provides methods for filtering unwanted
11# trace output (see Tracer.add_filter, Tracer.on, and Tracer.off).
12#
13# == Example
14#
15# Consider the following ruby script
16#
17#   class A
18#     def square(a)
19#       return a*a
20#     end
21#   end
22#
23#   a = A.new
24#   a.square(5)
25#
26# Running the above script using <code>ruby -r tracer example.rb</code> will
27# output the following trace to STDOUT (Note you can also explicitly
28# <code>require 'tracer'</code>)
29#
30#   #0:<internal:lib/rubygems/custom_require>:38:Kernel:<: -
31#   #0:example.rb:3::-: class A
32#   #0:example.rb:3::C: class A
33#   #0:example.rb:4::-:   def square(a)
34#   #0:example.rb:7::E: end
35#   #0:example.rb:9::-: a = A.new
36#   #0:example.rb:10::-: a.square(5)
37#   #0:example.rb:4:A:>:   def square(a)
38#   #0:example.rb:5:A:-:     return a*a
39#   #0:example.rb:6:A:<:   end
40#    |  |         | |  |
41#    |  |         | |   ---------------------+ event
42#    |  |         |  ------------------------+ class
43#    |  |          --------------------------+ line
44#    |   ------------------------------------+ filename
45#     ---------------------------------------+ thread
46#
47# Symbol table used for displaying incoming events:
48#
49# +}+:: call a C-language routine
50# +{+:: return from a C-language routine
51# +>+:: call a Ruby method
52# +C+:: start a class or module definition
53# +E+:: finish a class or module definition
54# +-+:: execute code on a new line
55# +^+:: raise an exception
56# +<+:: return from a Ruby method
57#
58# == Copyright
59#
60# by Keiju ISHITSUKA(keiju@ishitsuka.com)
61#
62class Tracer
63  class << self
64    # display additional debug information (defaults to false)
65    attr_accessor :verbose
66    alias verbose? verbose
67
68    # output stream used to output trace (defaults to STDOUT)
69    attr_accessor :stdout
70
71    # mutex lock used by tracer for displaying trace output
72    attr_reader :stdout_mutex
73
74    # display process id in trace output (defaults to false)
75    attr_accessor :display_process_id
76    alias display_process_id? display_process_id
77
78    # display thread id in trace output (defaults to true)
79    attr_accessor :display_thread_id
80    alias display_thread_id? display_thread_id
81
82    # display C-routine calls in trace output (defaults to false)
83    attr_accessor :display_c_call
84    alias display_c_call? display_c_call
85  end
86
87  Tracer::stdout = STDOUT
88  Tracer::verbose = false
89  Tracer::display_process_id = false
90  Tracer::display_thread_id = true
91  Tracer::display_c_call = false
92
93  @stdout_mutex = Mutex.new
94
95  # Symbol table used for displaying trace information
96  EVENT_SYMBOL = {
97    "line" => "-",
98    "call" => ">",
99    "return" => "<",
100    "class" => "C",
101    "end" => "E",
102    "raise" => "^",
103    "c-call" => "}",
104    "c-return" => "{",
105    "unknown" => "?"
106  }
107
108  def initialize # :nodoc:
109    @threads = Hash.new
110    if defined? Thread.main
111      @threads[Thread.main.object_id] = 0
112    else
113      @threads[Thread.current.object_id] = 0
114    end
115
116    @get_line_procs = {}
117
118    @filters = []
119  end
120
121  def stdout # :nodoc:
122    Tracer.stdout
123  end
124
125  def on # :nodoc:
126    if block_given?
127      on
128      begin
129        yield
130      ensure
131        off
132      end
133    else
134      set_trace_func method(:trace_func).to_proc
135      stdout.print "Trace on\n" if Tracer.verbose?
136    end
137  end
138
139  def off # :nodoc:
140    set_trace_func nil
141    stdout.print "Trace off\n" if Tracer.verbose?
142  end
143
144  def add_filter(p = proc) # :nodoc:
145    @filters.push p
146  end
147
148  def set_get_line_procs(file, p = proc) # :nodoc:
149    @get_line_procs[file] = p
150  end
151
152  def get_line(file, line) # :nodoc:
153    if p = @get_line_procs[file]
154      return p.call(line)
155    end
156
157    unless list = SCRIPT_LINES__[file]
158      begin
159        f = File::open(file)
160        begin
161          SCRIPT_LINES__[file] = list = f.readlines
162        ensure
163          f.close
164        end
165      rescue
166        SCRIPT_LINES__[file] = list = []
167      end
168    end
169
170    if l = list[line - 1]
171      l
172    else
173      "-\n"
174    end
175  end
176
177  def get_thread_no # :nodoc:
178    if no = @threads[Thread.current.object_id]
179      no
180    else
181      @threads[Thread.current.object_id] = @threads.size
182    end
183  end
184
185  def trace_func(event, file, line, id, binding, klass, *) # :nodoc:
186    return if file == __FILE__
187
188    for p in @filters
189      return unless p.call event, file, line, id, binding, klass
190    end
191
192    return unless Tracer::display_c_call? or
193      event != "c-call" && event != "c-return"
194
195    Tracer::stdout_mutex.synchronize do
196      if EVENT_SYMBOL[event]
197        stdout.printf("<%d>", $$) if Tracer::display_process_id?
198        stdout.printf("#%d:", get_thread_no) if Tracer::display_thread_id?
199        if line == 0
200          source = "?\n"
201        else
202          source = get_line(file, line)
203        end
204        stdout.printf("%s:%d:%s:%s: %s",
205               file,
206               line,
207               klass || '',
208               EVENT_SYMBOL[event],
209               source)
210      end
211    end
212
213  end
214
215  # Reference to singleton instance of Tracer
216  Single = new
217
218  ##
219  # Start tracing
220  #
221  # === Example
222  #
223  #   Tracer.on
224  #   # code to trace here
225  #   Tracer.off
226  #
227  # You can also pass a block:
228  #
229  #   Tracer.on {
230  #     # trace everything in this block
231  #   }
232
233  def Tracer.on
234    if block_given?
235      Single.on{yield}
236    else
237      Single.on
238    end
239  end
240
241  ##
242  # Disable tracing
243
244  def Tracer.off
245    Single.off
246  end
247
248  ##
249  # Register an event handler <code>p</code> which is called everytime a line
250  # in +file_name+ is executed.
251  #
252  # Example:
253  #
254  #   Tracer.set_get_line_procs("example.rb", lambda { |line|
255  #     puts "line number executed is #{line}"
256  #   })
257
258  def Tracer.set_get_line_procs(file_name, p = proc)
259    Single.set_get_line_procs(file_name, p)
260  end
261
262  ##
263  # Used to filter unwanted trace output
264  #
265  # Example which only outputs lines of code executed within the Kernel class:
266  #
267  #   Tracer.add_filter do |event, file, line, id, binding, klass, *rest|
268  #     "Kernel" == klass.to_s
269  #   end
270
271  def Tracer.add_filter(p = proc)
272    Single.add_filter(p)
273  end
274end
275
276# :stopdoc:
277SCRIPT_LINES__ = {} unless defined? SCRIPT_LINES__
278
279if $0 == __FILE__
280  # direct call
281
282  $0 = ARGV[0]
283  ARGV.shift
284  Tracer.on
285  require $0
286else
287  # call Tracer.on only if required by -r command-line option
288  count = caller.count {|bt| %r%/rubygems/core_ext/kernel_require\.rb:% !~ bt}
289  if (defined?(Gem) and count == 0) or
290     (!defined?(Gem) and count <= 1)
291    Tracer.on
292  end
293end
294# :startdoc:
295