# encoding: utf-8
require 'erb'
require 'fileutils'
require 'base64'
require 'set'
require 'stringio'
module RubyProf
# Prints a HTML visualization of the call tree.
#
# To use the printer:
#
# result = RubyProf.profile do
# [code to profile]
# end
#
# printer = RubyProf::CallStackPrinter.new(result)
# printer.print(STDOUT)
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.
#
# :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 = {})
setup_options(options)
output << @erb.result(binding)
end
# :enddoc:
def setup_options(options)
super(options)
@erb = ERB.new(self.template)
end
def print_stack(output, visited, call_tree, parent_time)
total_time = call_tree.total_time
percent_parent = (total_time/parent_time)*100
percent_total = (total_time/@overall_time)*100
return unless percent_total > min_percent
color = self.color(percent_total)
visible = percent_total >= threshold
expanded = percent_total >= expansion
display = visible ? "block" : "none"
output << "
" << "\n"
if visited.include?(call_tree)
output << "" << "\n"
output << "%s %s" % [link(call_tree.target, true), graph_link(call_tree)] << "\n"
else
visited << call_tree
if call_tree.children.empty?
output << "" << "\n"
else
visible_children = call_tree.children.any?{|ci| (ci.total_time/@overall_time)*100 >= threshold}
image = visible_children ? (expanded ? "minus" : "plus") : "empty"
output << "" << "\n"
end
output << "%4.2f%% (%4.2f%%) %s %s" % [percent_total, percent_parent,
link(call_tree.target, false), graph_link(call_tree)] << "\n"
unless call_tree.children.empty?
output << (expanded ? '' : '') << "\n"
call_tree.children.sort_by{|c| -c.total_time}.each do |child_call_tree|
print_stack(output, visited, child_call_tree, total_time)
end
output << '
' << "\n"
end
visited.delete(call_tree)
end
output << '
' << "\n"
end
def name(call_tree)
method = call_tree.target
method.full_name
end
def link(method, recursive)
method_name = "#{recursive ? '*' : ''}#{method.full_name}"
if method.source_file.nil?
h method_name
else
file = File.expand_path(method.source_file)
"#{h method_name}"
end
end
def graph_link(call_tree)
total_calls = call_tree.target.called
totals = total_calls.to_s
"[#{call_tree.called} calls, #{totals} total]"
end
def method_href(method)
h(method.full_name.gsub(/[><#\.\?=:]/,"_"))
end
def total_time(call_trees)
sum(call_trees.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 base64_image
@data ||= begin
file = open_asset('call_stack_printer.png')
Base64.encode64(file).gsub(/\n/, '')
end
end
def template
open_asset('call_stack_printer.html.erb')
end
end
end