Tenderlove Making

I want DTrace probes in Ruby

require 'active_record'
require 'benchmark'

p ActiveRecord::VERSION::STRING

ActiveRecord::Base.establish_connection(
  :adapter  => "sqlite3",
  :database => ":memory:"
)

ActiveRecord::Base.connection.execute("CREATE TABLE active_record_models (id INTEGER UNIQUE, title STRING, text STRING)")

class ActiveRecordModel < ActiveRecord::Base; end
ActiveRecordModel.new

N = 100_000
Benchmark.bm { |x| x.report('new') { N.times { ActiveRecordModel.new } } }
def allocate_count
  GC.disable
  before = ObjectSpace.count_objects
  yield
  after = ObjectSpace.count_objects
  after.each { |k,v| after[k] = v - before[k] }
  GC.enable
  after
end

p allocate_count { 100.times { {} } }
{ ... :T_HASH=&gt;101, ... }
provider ruby {
  probe hash__alloc(const char *, int);
};
$ dtrace -o probes.h -h -s probes.d
diff --git a/hash.c b/hash.c
index b49aff8..c40d94d 100644
--- a/hash.c
+++ b/hash.c
@@ -15,6 +15,7 @@
 #include "ruby/st.h"
 #include "ruby/util.h"
 #include "ruby/encoding.h"
+#include "probes.h"
 #include <errno.h>
 
 #ifdef __APPLE__
@@ -221,6 +222,9 @@ hash_alloc(VALUE klass)
     OBJSETUP(hash, klass, T_HASH);
 
     RHASH_IFNONE(hash) = Qnil;
+    if(RUBY_HASH_ALLOC_ENABLED()) {
+       RUBY_HASH_ALLOC(rb_sourcefile(), rb_sourceline());
+    }
 
     return (VALUE)hash;
 }
ruby*:::hash-alloc
{
  printf("%s:%d", copyinstr(arg0), arg1);
}
$ sudo dtrace -s x.d -c 'ruby -I lib test.rb'
  1 126930            hash_alloc:hash-alloc /Users/aaron/git/rails/activerecord/lib/active_record/base.rb:1528
  1 126930            hash_alloc:hash-alloc /Users/aaron/git/rails/activerecord/lib/active_record/base.rb:1529
  1 126930            hash_alloc:hash-alloc /Users/aaron/git/rails/activerecord/lib/active_record/base.rb:1534
  1 126930            hash_alloc:hash-alloc /Users/aaron/git/rails/activerecord/lib/active_record/base.rb:1535
  1 126930            hash_alloc:hash-alloc /Users/aaron/git/rails/activerecord/lib/active_record/base.rb:1525
  1 126930            hash_alloc:hash-alloc /Users/aaron/git/rails/activerecord/lib/active_record/persistence.rb:322
  1 126930            hash_alloc:hash-alloc /Users/aaron/git/rails/activerecord/lib/active_record/base.rb:1527
  1 126930            hash_alloc:hash-alloc /Users/aaron/git/rails/activerecord/lib/active_record/base.rb:1528
  1 126930            hash_alloc:hash-alloc /Users/aaron/git/rails/activerecord/lib/active_record/base.rb:1529
  1 126930            hash_alloc:hash-alloc /Users/aaron/git/rails/activerecord/lib/active_record/base.rb:1534
  1 126930            hash_alloc:hash-alloc /Users/aaron/git/rails/activerecord/lib/active_record/base.rb:1535
  1 126930            hash_alloc:hash-alloc /Users/aaron/git/rails/activerecord/lib/active_record/base.rb:1525</code></pre>

<p>I was able to compare the output from this script on Rails 3.0 vs Rails 3.1.  From this output, I was able to determine:</p>

<ul>
<li>The number of hash allocations</li>
<li>Where the hashes were allocated</li>
<li>Which allocations were new in Rails 3.1</li>
</ul>

<p>Armed with this information, I was able to eliminate some allocations and return speed in Rails 3.1 to that of Rails 3.0.</p>

<h3>DTrace vs gdb vs memprof</h3>

<p>I probably could have accomplished this task with <a href="https://github.com/ice799/memprof">memprof</a>, but it requires that I use 1.8.7 from rvm.  Working with RVM on my machine is difficult (don't ask, let's just say I'm a "special needs" user), and I wanted to use 1.9.  So memprof was out the window.</p>

<p>I was able to get the same information by scripting gdb.  I set a breakpoint at the correct function, called <code>rb_sourcefile()</code> and <code>rb_sourceline()</code> and redirected to a file.  The problem with gdb is that it seemed very slow, and scripting it was a pain (though I am not a gdb expert!).</p>

<p>DTrace was fast, relatively easy to use, and very scriptable.  It made me happy!</p>

<h3>OMG!!!</h3>

<p>I would like to see dtrace probes officially added to ruby trunk.  The ruby that ships with OS X has them built in, but they don't give us information like hash literal allocations.  It looks like <a href="http://redmine.ruby-lang.org/issues/2565">they were in ruby trunk at one point, but were then reverted</a>.  I would like to see them added again.</p>

<p>If you want to play with them on ruby trunk, <a href="https://gist.github.com/1055159">here is my full patch</a>.  Make sure to run <code>make probes.h</code> before <code>make &amp;&amp; make install</code>.</p>

<p>HAPPY WEDNESDAY!!!! &lt;3 &lt;3 &lt;3 &lt;3</p>

<small>(it feels good to blog on a non "professional" blog)</small>
« go back