1# frozen_string_literal: false 2#-- 3# $Release Version: 0.3$ 4# $Revision: 1.12 $ 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 64 class << self 65 # display additional debug information (defaults to false) 66 attr_accessor :verbose 67 alias verbose? verbose 68 69 # output stream used to output trace (defaults to STDOUT) 70 attr_accessor :stdout 71 72 # mutex lock used by tracer for displaying trace output 73 attr_reader :stdout_mutex 74 75 # display process id in trace output (defaults to false) 76 attr_accessor :display_process_id 77 alias display_process_id? display_process_id 78 79 # display thread id in trace output (defaults to true) 80 attr_accessor :display_thread_id 81 alias display_thread_id? display_thread_id 82 83 # display C-routine calls in trace output (defaults to false) 84 attr_accessor :display_c_call 85 alias display_c_call? display_c_call 86 end 87 88 Tracer::stdout = STDOUT 89 Tracer::verbose = false 90 Tracer::display_process_id = false 91 Tracer::display_thread_id = true 92 Tracer::display_c_call = false 93 94 @stdout_mutex = Thread::Mutex.new 95 96 # Symbol table used for displaying trace information 97 EVENT_SYMBOL = { 98 "line" => "-", 99 "call" => ">", 100 "return" => "<", 101 "class" => "C", 102 "end" => "E", 103 "raise" => "^", 104 "c-call" => "}", 105 "c-return" => "{", 106 "unknown" => "?" 107 } 108 109 def initialize # :nodoc: 110 @threads = Hash.new 111 if defined? Thread.main 112 @threads[Thread.main.object_id] = 0 113 else 114 @threads[Thread.current.object_id] = 0 115 end 116 117 @get_line_procs = {} 118 119 @filters = [] 120 end 121 122 def stdout # :nodoc: 123 Tracer.stdout 124 end 125 126 def on # :nodoc: 127 if block_given? 128 on 129 begin 130 yield 131 ensure 132 off 133 end 134 else 135 set_trace_func method(:trace_func).to_proc 136 stdout.print "Trace on\n" if Tracer.verbose? 137 end 138 end 139 140 def off # :nodoc: 141 set_trace_func nil 142 stdout.print "Trace off\n" if Tracer.verbose? 143 end 144 145 def add_filter(p = proc) # :nodoc: 146 @filters.push p 147 end 148 149 def set_get_line_procs(file, p = proc) # :nodoc: 150 @get_line_procs[file] = p 151 end 152 153 def get_line(file, line) # :nodoc: 154 if p = @get_line_procs[file] 155 return p.call(line) 156 end 157 158 unless list = SCRIPT_LINES__[file] 159 list = File.readlines(file) rescue [] 160 SCRIPT_LINES__[file] = list 161 end 162 163 if l = list[line - 1] 164 l 165 else 166 "-\n" 167 end 168 end 169 170 def get_thread_no # :nodoc: 171 if no = @threads[Thread.current.object_id] 172 no 173 else 174 @threads[Thread.current.object_id] = @threads.size 175 end 176 end 177 178 def trace_func(event, file, line, id, binding, klass, *) # :nodoc: 179 return if file == __FILE__ 180 181 for p in @filters 182 return unless p.call event, file, line, id, binding, klass 183 end 184 185 return unless Tracer::display_c_call? or 186 event != "c-call" && event != "c-return" 187 188 Tracer::stdout_mutex.synchronize do 189 if EVENT_SYMBOL[event] 190 stdout.printf("<%d>", $$) if Tracer::display_process_id? 191 stdout.printf("#%d:", get_thread_no) if Tracer::display_thread_id? 192 if line == 0 193 source = "?\n" 194 else 195 source = get_line(file, line) 196 end 197 stdout.printf("%s:%d:%s:%s: %s", 198 file, 199 line, 200 klass || '', 201 EVENT_SYMBOL[event], 202 source) 203 end 204 end 205 206 end 207 208 # Reference to singleton instance of Tracer 209 Single = new 210 211 ## 212 # Start tracing 213 # 214 # === Example 215 # 216 # Tracer.on 217 # # code to trace here 218 # Tracer.off 219 # 220 # You can also pass a block: 221 # 222 # Tracer.on { 223 # # trace everything in this block 224 # } 225 226 def Tracer.on 227 if block_given? 228 Single.on{yield} 229 else 230 Single.on 231 end 232 end 233 234 ## 235 # Disable tracing 236 237 def Tracer.off 238 Single.off 239 end 240 241 ## 242 # Register an event handler <code>p</code> which is called everytime a line 243 # in +file_name+ is executed. 244 # 245 # Example: 246 # 247 # Tracer.set_get_line_procs("example.rb", lambda { |line| 248 # puts "line number executed is #{line}" 249 # }) 250 251 def Tracer.set_get_line_procs(file_name, p = proc) 252 Single.set_get_line_procs(file_name, p) 253 end 254 255 ## 256 # Used to filter unwanted trace output 257 # 258 # Example which only outputs lines of code executed within the Kernel class: 259 # 260 # Tracer.add_filter do |event, file, line, id, binding, klass, *rest| 261 # "Kernel" == klass.to_s 262 # end 263 264 def Tracer.add_filter(p = proc) 265 Single.add_filter(p) 266 end 267end 268 269# :stopdoc: 270SCRIPT_LINES__ = {} unless defined? SCRIPT_LINES__ 271 272if $0 == __FILE__ 273 # direct call 274 275 $0 = ARGV[0] 276 ARGV.shift 277 Tracer.on 278 require $0 279else 280 # call Tracer.on only if required by -r command-line option 281 count = caller.count {|bt| %r%/rubygems/core_ext/kernel_require\.rb:% !~ bt} 282 if (defined?(Gem) and count == 0) or 283 (!defined?(Gem) and count <= 1) 284 Tracer.on 285 end 286end 287# :startdoc: 288