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!