Running DTrace in Ruby on MacOS X
Since version 2 Ruby comes with DTrace probes, which lets you dynamically instrument a running program.
Unfortunately the Ruby shipped with MacOS X doesn’t have DTrace enabled, when you try to list the available probes, you’ll see this:
martin$ sudo dtrace -c "ruby -v" -l -m ruby
ID PROVIDER MODULE FUNCTION NAME
dtrace: failed to match :ruby::: No probe matches description
So you need to install one that has. I perfer rbenv and how to install it is documented
here so I am not going to show how do it.
After a successful install you should see this:
martin$ sudo dtrace -c "`rbenv which ruby` -v" -l -m ruby
ID PROVIDER MODULE FUNCTION NAME
341053 ruby87551 ruby rb_ary_drop array-create
341054 ruby87551 ruby rb_ary_product array-create
341055 ruby87551 ruby rb_ary_repeated_combination array-create
341056 ruby87551 ruby rb_ary_repeated_permutation array-create
341057 ruby87551 ruby rb_ary_combination array-create
341058 ruby87551 ruby rb_ary_permutation array-create
341059 ruby87551 ruby rb_ary_sample array-create
341060 ruby87551 ruby rb_ary_and array-create
341061 ruby87551 ruby rb_ary_diff array-create
341062 ruby87551 ruby rb_ary_times array-create
341063 ruby87551 ruby rb_ary_slice_bang array-create
341064 ruby87551 ruby rb_ary_reject array-create
341065 ruby87551 ruby empty_ary_alloc array-create
341066 ruby87551 ruby rb_ary_subseq array-create
341067 ruby87551 ruby rb_ary_new array-create
341068 ruby87551 ruby ary_new array-create
341069 ruby87551 ruby vm_call_cfunc cmethod-entry
341070 ruby87551 ruby vm_call0_body cmethod-entry
341071 ruby87551 ruby vm_exec_core cmethod-entry
341072 ruby87551 ruby vm_call_cfunc cmethod-return
341073 ruby87551 ruby vm_call0_body cmethod-return
341074 ruby87551 ruby rb_vm_pop_cfunc_frame cmethod-return
341075 ruby87551 ruby vm_exec_core cmethod-return
341076 ruby87551 ruby rb_require_internal find-require-entry
341077 ruby87551 ruby rb_require_internal find-require-return
341078 ruby87551 ruby gc_start gc-mark-begin
341079 ruby87551 ruby gc_start gc-mark-end
341080 ruby87551 ruby gc_sweep_step gc-sweep-begin
341081 ruby87551 ruby gc_sweep_step gc-sweep-end
341082 ruby87551 ruby m_core_hash_from_ary hash-create
341083 ruby87551 ruby vm_exec_core hash-create
341084 ruby87551 ruby empty_hash_alloc hash-create
341085 ruby87551 ruby rb_f_load load-entry
341086 ruby87551 ruby rb_f_load load-return
341087 ruby87551 ruby rb_clear_method_cache_by_class method-cache-clear
341088 ruby87551 ruby invoke_block_from_c method-entry
341089 ruby87551 ruby vm_exec_core method-entry
341090 ruby87551 ruby invoke_block_from_c method-return
341091 ruby87551 ruby vm_exec method-return
341092 ruby87551 ruby vm_exec_core method-return
341093 ruby87551 ruby rb_obj_alloc object-create
341094 ruby87551 ruby yycompile0 parse-begin
341095 ruby87551 ruby yycompile0 parse-end
341096 ruby87551 ruby setup_exception raise
341097 ruby87551 ruby rb_require_internal require-entry
341098 ruby87551 ruby rb_require_internal require-return
341099 ruby87551 ruby empty_str_alloc string-create
341100 ruby87551 ruby rb_str_resurrect string-create
341101 ruby87551 ruby str_new_static string-create
341102 ruby87551 ruby str_new0 string-create
341103 ruby87551 ruby register_static_symid_str symbol-create
341104 ruby87551 ruby dsymbol_alloc symbol-create
DTrace require elevated privileges and thus needs to be run as root, and to avoid having to type your password
over and over again, you can update /etc/sudoers (using visudo off course) and add the below line,
which will let you use DTrace without a password:
%admin ALL = (root) NOPASSWD: /usr/sbin/dtrace
Trying it out
Now that we have a Ruby version with DTrace probes, we can use it to e.g. see how many object creations are involved in printing a “hello world” message in.
First create a file called hello.rb
puts "hello world from Ruby #{RUBY_VERSION}!"
Next create count.d which will count the number of times a new object is created, grouped by the class.
The copyinstr() is needed to convert arg0, which is the class name, from user space to kernel space where
the DTrace probes are run.
ruby*:::object-create
{
@objects[copyinstr(arg0)] = count();
}
If you forget to use copyinstr(), you’ll get a number like 140576469837096 instead of String.
When you run this, you should get:
martin$ sudo dtrace -s count.d -c "`rbenv which ruby` `pwd`/hello.rb"
dtrace: script 'count.d' matched 2 probes
hello world from Ruby 2.2.1!
dtrace: pid 87601 has exited
ARGF.class 1
Gem::Platform 1
IOError 1
Monitor 1
NoMemoryError 1
Range 1
SystemStackError 1
ThreadGroup 1
Time 1
fatal 1
LoadError 2
Mutex 3
Object 3
Gem::Specification 6
Hash 8
Gem::Version 9
Gem::Requirement 19
Array 72
String 254
As you can see, there are a lot of String objects created!