mirror of
https://github.com/specht/champ.git
synced 2025-02-18 14:30:44 +00:00
render a call graph if GraphViz is installed
This commit is contained in:
parent
3143e53c8a
commit
dd236caf5c
@ -9,7 +9,8 @@ This is a 6502/65C02 emulator / profiler that enables you to really get to know
|
|||||||
* average frame rate
|
* average frame rate
|
||||||
* see how much time is spent in which subroutine
|
* see how much time is spent in which subroutine
|
||||||
* watch variables (single variables or pairs)
|
* 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
|
## Usage
|
||||||
|
|
||||||
|
54
champ.rb
54
champ.rb
@ -51,6 +51,7 @@ class Champ
|
|||||||
STDERR.puts ' --no-animation'
|
STDERR.puts ' --no-animation'
|
||||||
exit(1)
|
exit(1)
|
||||||
end
|
end
|
||||||
|
@have_dot = `dot -V 2>&1`.strip[0, 3] == 'dot'
|
||||||
@files_dir = 'report-files'
|
@files_dir = 'report-files'
|
||||||
FileUtils.rm_rf(@files_dir)
|
FileUtils.rm_rf(@files_dir)
|
||||||
FileUtils.mkpath(@files_dir)
|
FileUtils.mkpath(@files_dir)
|
||||||
@ -180,6 +181,7 @@ class Champ
|
|||||||
frame_cycles = []
|
frame_cycles = []
|
||||||
@cycles_per_function = {}
|
@cycles_per_function = {}
|
||||||
@calls_per_function = {}
|
@calls_per_function = {}
|
||||||
|
@call_graph_counts = {}
|
||||||
@max_cycle_count = 0
|
@max_cycle_count = 0
|
||||||
call_stack = []
|
call_stack = []
|
||||||
last_call_stack_cycles = 0
|
last_call_stack_cycles = 0
|
||||||
@ -205,10 +207,15 @@ class Champ
|
|||||||
@max_cycle_count = cycles
|
@max_cycle_count = cycles
|
||||||
@calls_per_function[pc] ||= 0
|
@calls_per_function[pc] ||= 0
|
||||||
@calls_per_function[pc] += 1
|
@calls_per_function[pc] += 1
|
||||||
|
calling_function = start_pc
|
||||||
unless call_stack.empty?
|
unless call_stack.empty?
|
||||||
|
calling_function = call_stack.last
|
||||||
@cycles_per_function[call_stack.last] ||= 0
|
@cycles_per_function[call_stack.last] ||= 0
|
||||||
@cycles_per_function[call_stack.last] += cycles - last_call_stack_cycles
|
@cycles_per_function[call_stack.last] += cycles - last_call_stack_cycles
|
||||||
end
|
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
|
last_call_stack_cycles = cycles
|
||||||
call_stack << pc
|
call_stack << pc
|
||||||
elsif parts.first == 'rts'
|
elsif parts.first == 'rts'
|
||||||
@ -595,6 +602,51 @@ class Champ
|
|||||||
io.puts "</table>"
|
io.puts "</table>"
|
||||||
report.sub!('#{cycles}', io.string)
|
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 = "<B>#{label}</B>"
|
||||||
|
label += "<BR/>#{@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}', '<em>(GraphViz not installed)</em>')
|
||||||
|
end
|
||||||
|
|
||||||
f.puts report
|
f.puts report
|
||||||
end
|
end
|
||||||
puts ' done.'
|
puts ' done.'
|
||||||
@ -772,6 +824,8 @@ __END__
|
|||||||
<div style='margin-left: 460px; padding-top: 5px;'>
|
<div style='margin-left: 460px; padding-top: 5px;'>
|
||||||
<h2>Watches</h2>
|
<h2>Watches</h2>
|
||||||
#{watches}
|
#{watches}
|
||||||
|
<h2>Call Graph</h2>
|
||||||
|
#{call_graph}
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 134 KiB After Width: | Height: | Size: 326 KiB |
Loading…
x
Reference in New Issue
Block a user