Ruby Minstrel
Minstrel allows you to wrap every method call for a given class so you can more easily observe how a class and its methods are being used.
Get it
- gem install minstrel
- or download versions here: http://rubygems.org/gems/minstrel
- or github: https://github.com/jordansissel/ruby-minstrel
Why?
Fun. Also, sometimes ruby-prof and similar tools are overkill when I am trying to debug or dig into how a piece of code works.
It’s a lot like strace/tcpdump/dtrace for ruby, or aims to be, anyway.
I wanted a tool that was useful for my own code as well as for helping understand and debug other code (puppet, mcollective, rails, activerecord, sinatra, etc…).
Examples
From the commandline
You can use minstrel to wrap classes with a default ‘print’ wrapper that simply prints what is called. For example:
% RUBY_INSTRUMENT=String ruby -rminstrel -e 'puts "hello world".capitalize.reverse'
enter String#capitalize([])
exit String#capitalize([])
enter String#reverse([])
exit String#reverse([])
dlrow olleH
The ‘minstrel’ tool
Since the following doesn’t work as expected in ruby 1.8 (or maybe all rubies): ruby -rrubygems -rminstrel …, I provide ‘minstrel’ as a way to run ruby programs with minstrel preloaded.
% cat test.rb
#!/usr/bin/env ruby
puts "hello world".capitalize.reverse
% RUBY_INSTRUMENT=String minstrel test.rb
enter String#capitalize([])
exit String#capitalize([])
enter String#reverse([])
exit String#reverse([])
dlrow olleH
Example: Tracing puppet storeconfigs (aka Tracing ActiveRecord queries)
ActiveRecord has a base class for most things query-related. Let’s trace that:
% sudo env RUBY_INSTRUMENT=ActiveRecord::ConnectionAdapters::DatabaseStatements minstrel puppet ...
enter ActiveRecord::ConnectionAdapters::DatabaseStatements#select_all(["SELECT DISTINCT
`hosts`.id FROM `hosts` LEFT OUTER JOIN `fact_values` ON `fact_values`.`host_id` = `h
osts`.`id` LEFT OUTER JOIN `fact_names` ON `fact_names`.`id` = `fact_values`.`fact_name_id`
...
enter ActiveRecord::ConnectionAdapters::DatabaseStatements#select_all(["SELECT `hosts`.`id` AS t0_r0, `hosts`.`name` AS t0_r1, `hosts`.`ip` AS t0_r2, `hosts`.`environment ...
So easy :)
From ruby
Boilerplate:
require "minstrel"
instrument = Minstrel::Instrument.new()
instrument.wrap(String) do |point, klass, method, *args|
# * point is either :enter or :exit depending if this function is about to be
# called or has finished being called.
# * klass is the class object (String, etc)
# * method is the method (a Symbol)
# * *args is the arguments passed
end
Example:
require "minstrel"
class Foo
def (one, &block)
yield one
end
def baz
puts "Baz!"
end
end
instrument = Minstrel::Instrument.new
instrument.wrap(Foo) do |point, klass, method, *args|
puts "#{point} #{klass.name}##{method}(#{args.inspect})"
end
foo = Foo.new
foo.(123) { |arg| puts arg }
foo.baz
Output:
enter Foo#bar([123])
123
exit Foo#bar([123])
enter Foo#baz([])
Baz!
exit Foo#baz([])
From ruby (deferred loading)
Sometimes you don’t know when a class is going to be defined. To solve this, you must use Minstrel::Instrument#wrap_classname. For example:
>> require "minstrel"
>> Minstrel::Instrument.new.wrap_classname("TCPSocket")
>> require "socket"
Wrap of TCPSocket successful
Minstrel will wrap ‘require’ and check for classes you want wrapped at each require until it finds all the classes you asked to be wrapped.
Caveats
Metaprogramming will not be often caught, necessarily, by minstrel, because they don’t show usuall up as methods. However, the things invoking metaprogramming are usually methods so in most cases you’ll get lucky enough to see what’s going on.
Some cases of metaprogramming (dynamic method generation, DSLs, etc) can be caught if you call Minstrel::Instrument#wrap() late enough in the lifetime of the program that the dynamic methods have been created.
Bugs?
If you find bugs, have feature suggestions, etc, feel free to open bugs here on github (https://github.com/jordansissel/ruby-minstrel/issues). I also read email: [email protected]