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 '

Profile Report

<% for thread_id in @result.threads.keys.sort %> <% end %>
Thread ID Total Time
<%= thread_id %> <%= thread_time(thread_id) %>
<% for thread_id in @result.threads.keys.sort methods = @result.threads[thread_id] total_time = thread_time(thread_id) %>

Thread <%= thread_id %>

<% 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 %> <% called = "#{caller.called}/#{method.called}" %> <% end %> <% for callee in method.aggregate_children.sort_by(&:total_time).reverse %> <% next if min_time && callee.total_time < min_time %> <% called = "#{callee.called}/#{callee.target.called}" %> <% end %> <% end %>
<%= 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
    <%= 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) %><%= sprintf("%#{CALL_WIDTH}s", called) %> <%= create_link(thread_id, caller.parent.target) %> <%= file_link(caller.parent.target.source_file, caller.line) %>
<%= 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) %>
    <%= 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) %><%= sprintf("%#{CALL_WIDTH}s", called) %> <%= create_link(thread_id, callee.target) %> <%= file_link(method.source_file, callee.line) %>
<% end %> ' end end end