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__

Watches

#{watches} +

Call Graph

+ #{call_graph}
diff --git a/doc/screenshot.png b/doc/screenshot.png index d4294bd..aff2b51 100644 Binary files a/doc/screenshot.png and b/doc/screenshot.png differ