CpuProfiler

WIP cpu profiler for ruby 2.1, inspired by perftools.rb.

A simple example:

``` ruby
require 'cpuprofiler'

class A
  def initialize
    pow
    self.class.newobj
    math
  end

  def pow
    2 ** 100
  end

  def self.newobj
    Object.new
  end

  def math
    2.times do
      2 + 3 * 4 ^ 5 / 6
    end
  end
end

CpuProfiler.run(1000) do
  1000000.times do
    A.new
  end
end.tap do |profile|
  overall_samples = profile.delete(:overall_samples)
  profile = profile.sort_by{ |iseq, stats| -stats[:caller_samples] }

  printf "% 10s    (pct)  % 10s    (pct)     ISEQ\n" % ["TOTAL", "CALLER"]
  profile.each do |iseq, stats|
    call, total = stats.values_at(:caller_samples, :total_samples)
    stats[:name] = ''
    stats[:name] << (iseq.class_path || '')
    stats[:name] << iseq.label
    printf "% 10d % 8s  % 10d % 8s     %s\n", total, "(%2.1f%%)" % (total*100.0/overall_samples), call, "(%2.1f%%)" % (call*100.0/overall_samples), stats[:name]
  end

  File.open('out.gv', 'w') do |f|
    f.puts "digraph profile {"
    profile.each do |iseq, stats|
      call, total = stats.values_at(:caller_samples, :total_samples)
      sample = ''
      sample << "#{call} (%2.1f%%)\\rof " % (call*100.0/overall_samples) if call < total
      sample << "#{total} (%2.1f%%)\\r" % (total*100.0/overall_samples)
      size = (1.0 * call / overall_samples) * 28 + 10

      f.puts "  #{iseq.object_id} [size=#{size}] [fontsize=#{size}] [shape=box] [label=\"#{stats[:name]}\\n#{sample}\"];"
      if edges = stats[:edges]
        edges.each do |edge, weight|
          size = (1.0 * weight / overall_samples) * 28
          f.puts "  #{iseq.object_id} -> #{edge.object_id} [label=\"#{weight}\"];"
        end
      end
    end
    f.puts "}"
  end
end
```

The text output:

```
% ./ruby -I.:./.ext/x86_64-darwin12.3.0 profiletest.rb
     TOTAL    (pct)      CALLER    (pct)     ISEQ
       123  (46.1%)         123  (46.1%)     A#pow
        69  (25.8%)          69  (25.8%)     block in math
        57  (21.3%)          57  (21.3%)     A.newobj
        77  (28.8%)           8   (3.0%)     A#math
       267 (100.0%)           6   (2.2%)     block (2 levels) in <main>
       261  (97.8%)           4   (1.5%)     A#initialize
       267 (100.0%)           0   (0.0%)     <main>
       267 (100.0%)           0   (0.0%)     block in <main>
```

And the generated graphviz:

```
% cat out.gv && dot -Tpng out.gv -o out.png
digraph profile {
  70231918211400 [size=22.89887640449438] [fontsize=22.89887640449438] [shape=box] [label="A#pow\n123 (46.1%)\r"];
  70231918211100 [size=17.235955056179776] [fontsize=17.235955056179776] [shape=box] [label="block in math\n69 (25.8%)\r"];
  70231918211300 [size=15.97752808988764] [fontsize=15.97752808988764] [shape=box] [label="A.newobj\n57 (21.3%)\r"];
  70231918211200 [size=10.838951310861423] [fontsize=10.838951310861423] [shape=box] [label="A#math\n8 (3.0%)\rof 77 (28.8%)\r"];
  70231918211200 -> 70231918211100 [label="69"];
  70231918209720 [size=10.629213483146067] [fontsize=10.629213483146067] [shape=box] [label="block (2 levels) in <main>\n6 (2.2%)\rof 267 (100.0%)\r"];
  70231918209720 -> 70231918211520 [label="261"];
  70231918211520 [size=10.41947565543071] [fontsize=10.41947565543071] [shape=box] [label="A#initialize\n4 (1.5%)\rof 261 (97.8%)\r"];
  70231918211520 -> 70231918211200 [label="77"];
  70231918211520 -> 70231918211400 [label="123"];
  70231918211520 -> 70231918211300 [label="57"];
  70231918211700 [size=10.0] [fontsize=10.0] [shape=box] [label="<main>\n0 (0.0%)\rof 267 (100.0%)\r"];
  70231918211700 -> 70231918209800 [label="267"];
  70231918209800 [size=10.0] [fontsize=10.0] [shape=box] [label="block in <main>\n0 (0.0%)\rof 267 (100.0%)\r"];
  70231918209800 -> 70231918209720 [label="267"];
}
```

![out](https://f.cloud.github.com/assets/2567/595770/dc0a9d22-cb27-11e2-9b8c-ac5f077c83a1.png)