diff --git a/README.md b/README.md
index a372c53..6c9ddff 100644
--- a/README.md
+++ b/README.md
@@ -9,7 +9,8 @@ This is a 6502/65C02 emulator / profiler that enables you to really get to know
* average frame rate
* see how much time is spent in which subroutine
* watch variables (single variables or pairs)
-* no dependencies except Ruby, gcc and Merlin32
+* no required dependencies except Ruby, gcc and Merlin32
+** optional: GraphViz if you want a call graph
## Usage
diff --git a/champ.rb b/champ.rb
index ac47b6f..5d8184f 100755
--- a/champ.rb
+++ b/champ.rb
@@ -51,6 +51,7 @@ class Champ
STDERR.puts ' --no-animation'
exit(1)
end
+ @have_dot = `dot -V 2>&1`.strip[0, 3] == 'dot'
@files_dir = 'report-files'
FileUtils.rm_rf(@files_dir)
FileUtils.mkpath(@files_dir)
@@ -180,6 +181,7 @@ class Champ
frame_cycles = []
@cycles_per_function = {}
@calls_per_function = {}
+ @call_graph_counts = {}
@max_cycle_count = 0
call_stack = []
last_call_stack_cycles = 0
@@ -205,10 +207,15 @@ class Champ
@max_cycle_count = cycles
@calls_per_function[pc] ||= 0
@calls_per_function[pc] += 1
+ calling_function = start_pc
unless call_stack.empty?
+ calling_function = call_stack.last
@cycles_per_function[call_stack.last] ||= 0
@cycles_per_function[call_stack.last] += cycles - last_call_stack_cycles
end
+ @call_graph_counts[calling_function] ||= {}
+ @call_graph_counts[calling_function][pc] ||= 0
+ @call_graph_counts[calling_function][pc] += 1
last_call_stack_cycles = cycles
call_stack << pc
elsif parts.first == 'rts'
@@ -595,6 +602,51 @@ class Champ
io.puts ""
report.sub!('#{cycles}', io.string)
+ if @have_dot
+ # render call graph
+ all_nodes = Set.new()
+ @call_graph_counts.each_pair do |key, entries|
+ all_nodes << key
+ entries.keys.each do |other_key|
+ all_nodes << other_key
+ end
+ end
+ io = StringIO.new
+ io.puts "digraph {"
+ io.puts "overlap = false;"
+ io.puts "rankdir = TB;"
+ io.puts "splines = true;"
+ io.puts "graph [fontname = Helvetica, fontsize = 9, size = \"14, 11\", nodesep = 0.2, ranksep = 0.3, ordering = out];"
+ io.puts "node [fontname = Helvetica, fontsize = 9, shape = rect, style = filled, fillcolor = \"#fce94f\" color = \"#c4a000\"];"
+ io.puts "edge [fontname = Helvetica, fontsize = 9, color = \"#444444\"];"
+ all_nodes.each do |node|
+ label = @label_for_pc[node] || sprintf('0x%04x', node)
+ label = "#{label}"
+ label += "
#{@cycles_per_function[node]}"
+ io.puts " _#{node} [label = <#{label}>];"
+ end
+ @call_graph_counts.each_pair do |key, entries|
+ entries.keys.each do |other_key|
+ io.puts "_#{key} -> _#{other_key} [label = \"#{entries[other_key]}x\"];"
+ end
+ end
+ io.puts "}"
+
+ dot = io.string
+
+ svg = Open3.popen2('dot -Tsvg') do |stdin, stdout, thread|
+ stdin.print(dot)
+ stdin.close
+ stdout.read
+ end
+ File::open(File.join(@files_dir, 'call_graph.svg'), 'w') do |f|
+ f.write(svg)
+ end
+ report.sub!('#{call_graph}', svg)
+ else
+ report.sub!('#{call_graph}', '(GraphViz not installed)')
+ end
+
f.puts report
end
puts ' done.'
@@ -772,6 +824,8 @@ __END__