Skip to content

Instantly share code, notes, and snippets.

@joshado
Last active December 26, 2015 04:09
Show Gist options
  • Save joshado/7091534 to your computer and use it in GitHub Desktop.
Save joshado/7091534 to your computer and use it in GitHub Desktop.
DTrace / ruby method call visualisation. Forgive the hackiness, it was done over coffee :)
#pragma D option quiet
::rb_call0:function-entry {
/* copyinstr(arg0) = Object class
// copyinstr(arg1) = method
// copyinstr(arg2) = file
// arg3 = line
// arg4 ?*/
printf("%d:enter:%s:%s\n", tid, copyinstr(arg0), copyinstr(arg1));
}
::rb_call0:function-return {
printf("%d:return\n", tid);
}
#!/usr/bin/env ruby
##
# sudo dtrace -b 20m -s hooks.d > /tmp/irb-startup
# ./parse-threads.rb < /tmp/irb-startup
##
require 'digest/md5'
class StackFrame
attr_reader :klass, :method
def initialize(klass, method)
@klass, @method = klass, method
end
def to_s
"StackFrame:#{@klass}##{@method}"
end
def title
"#{@klass}##{@method}"
end
def key
"call_#{Digest::MD5.hexdigest(title)}"
end
end
class ThreadWatcher
attr_reader :id, :stack
def initialize(id)
@id = id
@stack = []
end
def function_enter(klass, method)
StackFrame.new(klass, method).tap do |frame|
@stack << frame
end
end
def function_return
@stack.pop
end
def dump
end
end
class PrintThreadWatcher < ThreadWatcher
def function_entry(klass, method)
caller_frame = stack.last
frame = super(klass, method)
puts "#{caller_frame} -> #{frame}"
end
def function_return
frame = super
puts "#{indent}Thread #{@id} leaves #{frame}"
end
def indent
' '* @stack.size
end
end
class DotThreadWatcher < ThreadWatcher
def initialize(id)
super(id)
@object_methods = []
@links = {}
end
def function_enter(klass, method)
caller_frame = stack.last
frame = super(klass, method)
@object_methods << frame
if caller_frame
link_key = [caller_frame.key, frame.key]
@links[link_key] ||= 0
@links[link_key] += 1
end
end
def function_return; end
def dump(dot_writer)
@object_methods.each do |frame|
dot_writer.node(frame.key, frame.title)
end
@links.each do |link, weight|
caller, receiver = link
dot_writer.link(caller, receiver, :weight => weight)
end
end
end
@threads = Hash.new { |h,k| h[k] = DotThreadWatcher.new(k) }
until $stdin.eof?
line = $stdin.readline.chomp
if line =~ /^(\d+):enter:([^:]+):([^:]+)$/
@threads[$1.to_i].function_enter($2, $3)
elsif line =~ /^(\d+):return$/
@threads[$1.to_i].function_return
end
end
class DotWriter
def digraph(name)
$stdout.puts "digraph #{name} {"
yield
$stdout.puts "}"
end
def link(source, target, opts={})
$stdout.puts "#{source} -> #{target} [weight=#{opts.fetch(:weight, 1)} width=#{opts.fetch(:weight,1)} xlabel=\"#{opts.fetch(:weight,1)}\"];"
end
def node(name, title=nil)
title ||= name
$stdout.puts "#{name} [label=\"#{title}\"];"
end
end
writer = DotWriter.new
writer.digraph("call_graph") do
@threads.values.each do |object|
object.dump(writer)
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment