# encoding: utf-8
require 'erb'
require 'fileutils'
module RubyProf
# prints a HTML visualization of the call tree
class CallStackPrinter < AbstractPrinter
include ERB::Util
# Specify print options.
#
# options - Hash table
# :min_percent - Number 0 to 100 that specifes the minimum
# %self (the methods self time divided by the
# overall total time) that a method must take
# for it to be printed out in the report.
# Default value is 0.
#
# :print_file - True or false. Specifies if a method's source
# file should be printed. Default value if false.
#
# :threshold - a float from 0 to 100 that sets the threshold of
# results displayed.
# Default value is 1.0
#
# :title - a String to overide the default "ruby-prof call tree"
# title of the report.
#
# :expansion - a float from 0 to 100 that sets the threshold of
# results that are expanded, if the percent_total
# exceeds it.
# Default value is 10.0
#
# :application - a String to overide the name of the application,
# as it appears on the report.
#
def print(output = STDOUT, options = {})
@output = output
setup_options(options)
if @graph_html = options.delete(:graph)
@graph_html = "file://" + @graph_html if @graph_html[0]=="/"
end
@overall_threads_time = 0.0
@threads_totals = Hash.new
@result.threads.each do |thread_id, methods|
roots = methods.select{|m| m.root?}
thread_total_time = sum(roots.map{|r| self.total_time(r.call_infos)})
@overall_threads_time += thread_total_time
@threads_totals[thread_id] = thread_total_time
end
print_header
@result.threads.keys.sort.each do |thread_id|
@current_thread_id = thread_id
@overall_time = @threads_totals[thread_id]
@output.print "
"
@result.threads[thread_id].each do |m|
# $stderr.print m.dump
next unless m.root?
m.call_infos.each do |ci|
next unless ci.root?
print_stack ci, @threads_totals[thread_id]
end
end
@output.print "
'
end
kids.sort_by{|c| -c.total_time}.each do |callinfo|
print_stack callinfo, total_time
end
@output.print "
"
end
@output.print "
"
end
def name(call_info)
method = call_info.target
method.full_name
end
def link(call_info)
method = call_info.target
file = File.expand_path(method.source_file)
if file =~ /\/ruby_runtime$/
h(name(call_info))
else
if RUBY_PLATFORM =~ /darwin/
"#{h(name(call_info))}"
else
"#{h(name(call_info))}"
end
end
end
def graph_link(call_info)
total_calls = call_info.target.call_infos.inject(0){|t, ci| t += ci.called}
href = "#{@graph_html}##{method_href(call_info.target)}"
totals = @graph_html ? "#{total_calls}" : total_calls.to_s
"[#{call_info.called} calls, #{totals} total]"
end
def method_href(method)
h(method.full_name.gsub(/[><#\.\?=:]/,"_") + "_" + @current_thread_id.to_s)
end
def total_time(call_infos)
sum(call_infos.map{|ci| ci.total_time})
end
def sum(a)
a.inject(0.0){|s,t| s+=t}
end
def dump(ci)
$stderr.printf "%s/%d t:%f s:%f w:%f \n", ci, ci.object_id, ci.total_time, ci.self_time, ci.wait_time
end
def color(p)
case i = p.to_i
when 0..5
"01"
when 5..10
"05"
when 100
"9"
else
"#{i/10}"
end
end
def application
@options[:application] || $PROGRAM_NAME
end
def arguments
ARGV.join(' ')
end
def title
@title ||= @options.delete(:title) || "ruby-prof call tree"
end
def threshold
@options[:threshold] || 1.0
end
def expansion
@options[:expansion] || 10.0
end
def copy_image_files
if @output.is_a?(File)
target_dir = File.dirname(@output.path)
image_dir = File.dirname(__FILE__)
%w(empty plus minus).each do |img|
source_file = "#{image_dir}/#{img}.png"
target_file = "#{target_dir}/#{img}.png"
FileUtils.cp(source_file, target_file) unless File.exist?(target_file)
end
end
end
def print_header
@output.puts ""
@output.puts ''
@output.puts "#{h title}"
print_css
print_java_script
@output.puts ''
print_title_bar
print_commands
print_help
end
def print_footer
@output.puts ''
end
def print_css
@output.puts <<-'end_css'
end_css
end
def print_java_script
@output.puts <<-'end_java_script'
end_java_script
end
def print_title_bar
@output.puts <<-"end_title_bar"
Call tree for application #{h application} #{h arguments}
Generated on #{Time.now} with options #{h @options.inspect}
end_title_bar
end
def print_commands
@output.puts <<-"end_commands"
Threshold:
end_commands
end
def print_help
@output.puts <<-'end_help'
Enter a decimal value d into the threshold field and click "Apply"
to hide all nodes marked with time values lower than d.
Click on "Expand All" for full tree expansion.
Click on "Collapse All" to show only top level nodes.
Use a, s, d, w as in Quake or Urban Terror to navigate the tree.
Use f and b to navigate the tree in preorder forward and backwards.
Use x to toggle visibility of a subtree.
Use * to expand/collapse a whole subtree.
Use h to navigate to thread root.
Use n and p to navigate between threads.
Click on background to move focus to a subtree.