# encoding: utf-8
require'erb'require'fileutils'require'base64'require'set'require'stringio'moduleRubyProf# 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)
classCallStackPrinter<AbstractPrinterincludeERB::Util# Specify print options.
#
# output - Any IO object, including STDOUT or a file.
#
# Keyword arguments:
# title: - a String to override the default "ruby-prof call stack"
# title of the report.
#
# threshold: - a float from 0 to 100 that sets the threshold of
# results displayed.
# Default value is 1.0
#
# 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 override the name of the application,
# as it appears on the report.
#
# Also accepts min_percent:, max_percent:, filter_by:, and sort_method:
# from AbstractPrinter.
defprint(output=STDOUT,title: "ruby-prof call stack",threshold: 1.0,expansion: 10.0,application: $PROGRAM_NAME,min_percent: 0,max_percent: 100,filter_by: :self_time,sort_method: nil,max_depth: nil,**)@min_percent=min_percent@max_percent=max_percent@filter_by=filter_by@sort_method=sort_method@max_depth=max_depth@title=title@threshold=threshold@expansion=expansion@application=applicationoutput<<ERB.new(self.template).result(binding)enddefprint_stack(output,visited,call_tree,parent_time,depth=0)total_time=call_tree.total_timepercent_parent=(total_time/parent_time)*100percent_total=(total_time/@overall_time)*100returnunlesspercent_total>min_percentcolor=self.color(percent_total)visible=percent_total>=thresholdexpanded=percent_total>=expansiondisplay=visible?"block":"none"output<<"<li class=\"color#{color}\" style=\"display:#{display}\">"<<"\n"ifvisited.include?(call_tree)output<<"<a href=\"#\" class=\"toggle empty\" ></a>"<<"\n"output<<"<span>%s %s</span>"%[link(call_tree.target,true),graph_link(call_tree)]<<"\n"elsevisited<<call_treeifcall_tree.children.empty?||(@max_depth&&depth>=@max_depth)output<<"<a href=\"#\" class=\"toggle empty\" ></a>"<<"\n"elsevisible_children=call_tree.children.any?{|ci|(ci.total_time/@overall_time)*100>=threshold}image=visible_children?(expanded?"minus":"plus"):"empty"output<<"<a href=\"#\" class=\"toggle #{image}\" ></a>"<<"\n"endoutput<<"<span>%4.2f%% (%4.2f%%) %s %s</span>"%[percent_total,percent_parent,link(call_tree.target,false),graph_link(call_tree)]<<"\n"unlesscall_tree.children.empty?||(@max_depth&&depth>=@max_depth)output<<(expanded?'<ul>':'<ul style="display:none">')<<"\n"call_tree.children.sort_by{|c|-c.total_time}.eachdo|child_call_tree|print_stack(output,visited,child_call_tree,total_time,depth+1)endoutput<<'</ul>'<<"\n"endvisited.delete(call_tree)endoutput<<'</li>'<<"\n"enddefname(call_tree)method=call_tree.targetmethod.full_nameenddeflink(method,recursive)method_name="#{recursive?'*':''}#{method.full_name}"ifmethod.source_file.nil?hmethod_nameelsefile=File.expand_path(method.source_file)"<a href=\"file://#{file}##{method.line}\">#{hmethod_name}</a>"endenddefgraph_link(call_tree)total_calls=call_tree.target.calledtotals=total_calls.to_s"[#{call_tree.called} calls, #{totals} total]"enddefmethod_href(method)h(method.full_name.gsub(/[><#\.\?=:]/,"_"))enddeftotal_time(call_trees)sum(call_trees.map{|ci|ci.total_time})enddefsum(a)a.inject(0.0){|s,t|s+=t}enddefdump(ci)$stderr.printf"%s/%d t:%f s:%f w:%f \n",ci,ci.object_id,ci.total_time,ci.self_time,ci.wait_timeenddefcolor(p)casei=p.to_iwhen0..5"01"when5..10"05"when100"9"else"#{i/10}"endendattr_reader:application,:title,:threshold,:expansiondefargumentsARGV.join(' ')enddefbase64_image@data||=beginfile=open_asset('call_stack_printer.png')Base64.encode64(file).gsub(/\n/,'')endenddeftemplateopen_asset('call_stack_printer.html.erb')endendend