Running Multiple Analysis Tools at Once

Problem

You want to combine two analysis tools, like the Ruby profiler and the Ruby tracer. But when one tool calls set_trace_func, it overwrites the trace function left by the other.

Solution

Change set_trace_func so that it keeps an array of trace functions instead of just one. Heres a library called multitrace.rb that makes it possible:

# multitrace.rb $TRACE_FUNCS = [] alias :set_single_trace_func :set_trace_func def set_trace_func(proc) if (proc == nil) $TRACE_FUNCS.clear else $TRACE_FUNCS << proc end end trace_all = Proc.new do |event, file, line, symbol, binding, klass| $TRACE_FUNCS.each { |p| p.call(event, file, line, symbol, binding, klass)} end set_single_trace_func trace_all def unset_trace_func(proc) $TRACE_FUNCS.delete(proc) end

Now you can run any number of analysis tools simultaneously. However, when one of the tools stops, they will all stop:

#!/usr/bin/ruby -w # paranoia.rb require multitrace require profile require racer Tracer.on puts "I feel like Im being watched."

This programs nervousness is well-justified, since its every move is being tracked by the Ruby tracer and timed by the Ruby profiler:

$ ruby paranoia.rb #0:./multitrace.rb:9:Array:<: $TRACE_FUNCS << proc #0:./multitrace.rb:11:Object:<: end #0:paranoia.rb:9::-: puts "I feel like Im being watched." #0:paranoia.rb:9:Kernel:>: puts "I feel like Im being watched." … % cumulative self self total time seconds seconds calls ms/call ms/call name 0.00 0.00 0.00 1 0.00 0.00 Kernel.require 0.00 0.00 0.00 1 0.00 0.00 Fixnum#== 0.00 0.00 0.00 1 0.00 0.00 String#scan …

Without the include multitrace at the beginning, only the profiler will run: its trace function will override the tracers.

Discussion

This example illustrates yet again how you can benefit by replacing some built-in part of Ruby. The multitrace library creates a drop-in replacement for set_trace_func that lets you run multiple analyzers at once. You probably don really want to run the tracer and the analyzer simultaneously, since they e both monolithic tools. But if youve written some smaller, more modular analysis tools, you e more likely to want to run more than one during a single run of a program.

The standard way of stopping a tracer is to pass nil into set_trace_func. Our new set_trace_func will accept nil, but it has no way of knowing which trace function you want to stop.[4] It has no choice but to remove all of them. Of course, if you e writing your own trace functions, and you know multitrace will be in place, you don need to pass nil into set_trace_func. You can call unset_trace_func to remove one particular trace function, without stopping the rest.

[4] Well, you could do this by taking a snapshot of the call stack every time set_trace_func was called with a Proc object. When set_trace_func was called with nil, you could look at the call stack at that point (see Recipe 17.6), and only remove the Proc object(s) inserted by the same file. For instance, if a nil call comes in from profiler.rb, you could remove only the Proc object(s) inserted by calls coming from profiler.rb. This is probably not worth the trouble.

See Also

Категории