require 'erb'
module RubyProf
# Generates graph[link:files/examples/graph_html.html] profile reports as html.
# To use the graph html printer:
#
# result = RubyProf.profile do
# [code to profile]
# end
#
# printer = RubyProf::GraphHtmlPrinter.new(result)
# printer.print(STDOUT, :min_percent=>0)
#
# The constructor takes two arguments. The first is
# a RubyProf::Result object generated from a profiling
# run. The second is the minimum %total (the methods
# total time divided by the overall total time) that
# a method must take for it to be printed out in
# the report. Use this parameter to eliminate methods
# that are not important to the overall profiling results.
class GraphHtmlPrinter < AbstractPrinter
include ERB::Util
PERCENTAGE_WIDTH = 8
TIME_WIDTH = 10
CALL_WIDTH = 20
# Create a GraphPrinter. Result is a RubyProf::Result
# object generated from a profiling run.
def initialize(result)
super(result)
@thread_times = Hash.new
calculate_thread_times
end
# Print a graph html report to the provided output.
#
# output - Any IO oject, including STDOUT or a file.
# The default value is STDOUT.
#
# options - Hash of print options. See #setup_options
# for more information.
#
# unique options are:
# :filename - specify a file to use that contains the ERB
# template to use, instead of the built-in self.template
#
# :template - specify an ERB template to use, instead of the
# built-in self.template
#
def print(output = STDOUT, options = {})
@output = output
setup_options(options)
filename = options[:filename]
template = filename ? File.read(filename).untaint : (options[:template] || self.template)
_erbout = @output
erb = ERB.new(template, nil, nil)
erb.filename = filename
@output << erb.result(binding)
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
# These methods should be private but then ERB doesn't
# work. Turn off RDOC though
#--
def calculate_thread_times
# Cache thread times since this is an expensive
# operation with the required sorting
@overall_threads_time = 0.0
@thread_times = 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
@thread_times[thread_id] = thread_total_time
end
end
def thread_time(thread_id)
@thread_times[thread_id]
end
def total_percent(thread_id, method)
overall_time = self.thread_time(thread_id)
(method.total_time/overall_time) * 100
end
def self_percent(method)
overall_time = self.thread_time(method.thread_id)
(method.self_time/overall_time) * 100
end
# Creates a link to a method. Note that we do not create
# links to methods which are under the min_perecent
# specified by the user, since they will not be
# printed out.
def create_link(thread_id, method)
if self.total_percent(thread_id, method) < min_percent
# Just return name
h method.full_name
else
href = '#' + method_href(thread_id, method)
"#{h method.full_name}"
end
end
def method_href(thread_id, method)
h(method.full_name.gsub(/[><#\.\?=:]/,"_") + "_" + thread_id.to_s)
end
def file_link(path, linenum)
srcfile = File.expand_path(path)
if srcfile =~ /\/ruby_runtime$/
""
else
if RUBY_PLATFORM =~ /darwin/
"#{linenum}"
else
"#{linenum}"
end
end
end
def template
'
<%= sprintf("%#{PERCENTAGE_WIDTH}s", "%Total") %> |
<%= sprintf("%#{PERCENTAGE_WIDTH}s", "%Self") %> |
<%= sprintf("%#{TIME_WIDTH}s", "Total") %> |
<%= sprintf("%#{TIME_WIDTH}s", "Self") %> |
<%= sprintf("%#{TIME_WIDTH}s", "Wait") %> |
<%= sprintf("%#{TIME_WIDTH+2}s", "Child") %> |
<%= sprintf("%#{CALL_WIDTH}s", "Calls") %> |
Name |
Line |
<% min_time = @options[:min_time] || (@options[:nonzero] ? 0.005 : nil)
methods.sort.reverse_each do |method|
total_percentage = (method.total_time/total_time) * 100
next if total_percentage < min_percent
next if min_time && method.total_time < min_time
self_percentage = (method.self_time/total_time) * 100 %>
<% for caller in method.aggregate_parents.sort_by(&:total_time)
next unless caller.parent
next if min_time && caller.total_time < min_time %>
|
|
<%= sprintf("%#{TIME_WIDTH}.2f", caller.total_time) %> |
<%= sprintf("%#{TIME_WIDTH}.2f", caller.self_time) %> |
<%= sprintf("%#{TIME_WIDTH}.2f", caller.wait_time) %> |
<%= sprintf("%#{TIME_WIDTH}.2f", caller.children_time) %> |
<% called = "#{caller.called}/#{method.called}" %>
<%= sprintf("%#{CALL_WIDTH}s", called) %> |
<%= create_link(thread_id, caller.parent.target) %> |
<%= file_link(caller.parent.target.source_file, caller.line) %> |
<% end %>
<%= sprintf("%#{PERCENTAGE_WIDTH-1}.2f\%", total_percentage) %> |
<%= sprintf("%#{PERCENTAGE_WIDTH-1}.2f\%", self_percentage) %> |
<%= sprintf("%#{TIME_WIDTH}.2f", method.total_time) %> |
<%= sprintf("%#{TIME_WIDTH}.2f", method.self_time) %> |
<%= sprintf("%#{TIME_WIDTH}.2f", method.wait_time) %> |
<%= sprintf("%#{TIME_WIDTH}.2f", method.children_time) %> |
<%= sprintf("%#{CALL_WIDTH}i", method.called) %> |
<%= h method.full_name %> |
<%= file_link(method.source_file, method.line) %> |
<% for callee in method.aggregate_children.sort_by(&:total_time).reverse %>
<% next if min_time && callee.total_time < min_time %>
|
|
<%= sprintf("%#{TIME_WIDTH}.2f", callee.total_time) %> |
<%= sprintf("%#{TIME_WIDTH}.2f", callee.self_time) %> |
<%= sprintf("%#{TIME_WIDTH}.2f", callee.wait_time) %> |
<%= sprintf("%#{TIME_WIDTH}.2f", callee.children_time) %> |
<% called = "#{callee.called}/#{callee.target.called}" %>
<%= sprintf("%#{CALL_WIDTH}s", called) %> |
<%= create_link(thread_id, callee.target) %> |
<%= file_link(method.source_file, callee.line) %> |
<% end %>
|
<% end %>
<% end %>
'
end
end
end