#!/usr/bin/perl
BEGIN {
if
(
grep
{
$_
eq
'--profself'
}
@ARGV
) {
our
$profself
=
"nytprof-nytprofhtml.out"
;
$ENV
{NYTPROF} .=
":file=$profself:trace=1"
;
END {
warn
"Profile of $0 written to $profself\n"
if
our
$profself
; }
}
}
fmt_float fmt_time fmt_incl_excl_time
calculate_median_absolute_deviation
get_abs_paths_alternation_regex
html_safe_filename
)
;
my
$json_any
=
eval
{
require
JSON::Any ; JSON::Any->
import
; JSON::Any->new }
or
warn
"Can't load JSON::Any module - HTML visualizations skipped.\n"
;
our
$VERSION
=
'4.06'
;
if
(
$VERSION
!=
$Devel::NYTProf::Core::VERSION
) {
die
"$0 version '$VERSION' doesn't match version '$Devel::NYTProf::Core::VERSION' of $INC{'Devel/NYTProf/Core.pm'}\n"
;
}
my
@treemap_colors
= (0,2,4,6,8,10,1,3,5,7,9);
my
@on_ready_js
;
GetOptions(
'file|f=s'
=> \(
my
$opt_file
=
'nytprof.out'
),
'lib|l=s'
=> \
my
$opt_lib
,
'out|o=s'
=> \(
my
$opt_out
=
'nytprof'
),
'delete|d!'
=> \
my
$opt_delete
,
'open!'
=> \
my
$opt_open
,
'help|h'
=>
sub
{
exit
usage() },
'minimal|m!'
=> \
my
$opt_minimal
,
'mergeevals!'
=> \(
my
$opt_mergeevals
= 1),
'profself!'
=>
sub
{ },
) or
do
{
exit
usage(); };
DB::set_option(
'blocks'
, 0)
if
$opt_minimal
;
sub
usage {
print
<<END;
usage: [perl] nytprofhtml [opts]
--file <file>, -f <file> Read profile data from the specified file [default: nytprof.out]
--out <dir>, -o <dir> Write report files to this directory [default: nytprof]
--delete, -d Delete any old report files in <dir> first
--lib <lib>, -l <lib> Add <lib> to the beginning of \@INC
--minimal, -m Don't generate graphviz .dot files or block/sub-level reports
--no-mergeevals Disable merging of string evals
--help, -h Print this message
This script of part of the Devel::NYTProf distribution.
END
return
1;
}
if
(!-e
$opt_out
) {
}
elsif
(!-d
$opt_out
) {
die
"$0: Specified output directory '$opt_out' already exists as a file!\n"
;
}
elsif
(!-w
$opt_out
) {
die
"$0: Unable to write to output directory '$opt_out'\n"
;
}
else
{
if
(
defined
(
$opt_delete
)) {
print
"Deleting existing $opt_out directory\n"
;
rmtree(
$opt_out
);
}
}
if
(
defined
(
$opt_lib
)) {
warn
"$0: Specified lib directory '$opt_lib' does not exist.\n"
unless
-d
$opt_lib
;
lib->
import
(
$opt_lib
);
}
$SIG
{USR2} = \
&Carp::cluck
;
my
$reporter
= new Devel::NYTProf::Reader(
$opt_file
, {
quiet
=> 0,
skip_collapse_evals
=> !
$opt_mergeevals
,
});
$reporter
->output_dir(
$opt_out
);
$reporter
->set_param(
'header'
,
sub
{
my
(
$profile
,
$fi
,
$output_filestr
,
$level
) =
@_
;
my
$profile_level_buttons
= (
$fi
->is_eval)
?
''
: get_level_buttons(
$profile
->get_profile_levels,
$output_filestr
,
$level
);
my
$subhead
=
qq{  $profile_level_buttons<br />
For ${ \($profile->{attribute}
{application}) }
};
my
$html_header
= get_html_header(
"Profile of "
.
$fi
->filename_without_inc);
my
$page_header
= get_page_header(
profile
=>
$profile
,
title
=>
"NYTProf Performance Profile"
,
subtitle
=>
$subhead
,
);
my
$filename_escaped
= _escape_html(
$fi
->filename);
my
@intro_rows
= (
[
"Filename"
,
$fi
->is_file
?
sprintf
(
q{<a href="file://%s ">%s</a>}
,
$fi
->filename,
$filename_escaped
)
:
$filename_escaped
],
[
"Statements"
,
sprintf
"Executed %d statements in %s"
,
$fi
->sum_of_stmts_count, fmt_time(
$fi
->sum_of_stmts_time) ],
);
if
(
$fi
->is_eval) {
push
@intro_rows
, [
"Eval Invoked At"
,
sprintf
q{<a %s>%s line %d</a>}
,
$reporter
->href_for_file(
$fi
->eval_fi,
$fi
->eval_line),
_escape_html(
$fi
->eval_fi->filename),
$fi
->eval_line
];
my
@sibling_html
;
for
my
$e_fi
(
$fi
->sibling_evals) {
if
(
$e_fi
==
$fi
) {
push
@sibling_html
, 1+
@sibling_html
;
}
else
{
push
@sibling_html
,
sprintf
qq{<a %s>%d</a>}
,
$reporter
->href_for_file(
$e_fi
),
1+
@sibling_html
;
}
}
push
@intro_rows
, [
"Sibling evals"
,
join
", "
,
@sibling_html
]
if
@sibling_html
>= 2;
}
my
$intro_table
=
join
"\n"
,
map
{
sprintf
q{<tr><td class="h">%s</td><td align="left">%s</td></tr>}
,
@$_
}
@intro_rows
;
return
join
"\n"
,
$html_header
,
$page_header
,
q{<div class="body_content"><br />}
,
qq{<table class="file_summary">$intro_table</table>}
,
}
);
$reporter
->set_param(
'taintmsg'
,
qq{<br /><div class="warn_title">WARNING!</div>\n
<div class="warn">The source file used to generate this report was modified
after the profiler data was generated.
The data might be out of sync with the modified source code so you should regenerate it.
Meanwhile, the data on this page might not make much sense!</div>\n}
);
$reporter
->set_param(
'sawampersand'
,
sub
{
my
(
$profile
,
$fi
) =
@_
;
my
$line
=
$profile
->{attribute}{sawampersand_line};
return
qq{<br /><div class="warn_title">NOTE!</div>\n
<div class="warn"><p>While profiling this file Perl noted the use of one or more special
variables that impact the performance of <i>all</i> regular expressions in the program.</p>
<p>Use of the "<tt>\$`</tt>", "<tt>\$&</tt>", and "<tt>\$'</tt>" variables should be replaced with faster alternatives.<br />
Capture Buffers section of the perlre documentation</a>.</p>
<p>The use is detected by perl at compile time but by NYTProf during execution.
NYTProf first noted it when executing <a href="#$line">line $line</a>.
That was probably the first statement executed by the program after perl
compiled the code containing the variables.
If the variables can't be found by studying the source code, try using the
modules.</p>
</div>\n}
}
);
$reporter
->set_param(
'merged_fids'
,
sub
{
my
(
$profile
,
$fi
) =
@_
;
my
$merged_fids
=
$fi
->meta->{merged_fids};
my
$evals_shown
= 1 +
scalar
@$merged_fids
;
my
@siblings
=
$fi
->sibling_evals;
my
$merged_siblings
= sum(
map
{
scalar
@{
$_
->meta->{merged_fids}||[]} }
@siblings
);
my
$evals_total
=
@siblings
+
$merged_siblings
;
my
@msg
;
push
@msg
,
sprintf
qq{
The data used to generate this report page was merged from %s<br />
of the string eval on line %d of %s.
}
, (
$evals_shown
==
$evals_total
)
?
sprintf
(
"all %d executions"
,
$evals_shown
)
:
sprintf
(
"%d of the %d executions"
,
$evals_shown
,
$evals_total
),
$fi
->eval_line,
$fi
->eval_fi->filename;
push
@msg
,
qq{
The source code shown below is the text of just one of the calls to the eval.<br />\n
This report page might not make much sense because the argument source code of those eval calls varied.<br />\n
}
if
$fi
->meta->{merged_fids_src_varied};
return
sprintf
qq{<br /><div class="warn_title">NOTE!</div>\n
<div class="warn">%s</div>
}
,
join
"<br />"
,
@msg
;
},
);
sub
calc_mad_from_objects {
my
(
$ary
,
$meth
,
$ignore_zeros
) =
@_
;
return
calculate_median_absolute_deviation([
map
{
scalar
$_
->
$meth
}
@$ary
],
$ignore_zeros
);
}
sub
subroutine_table {
my
(
$profile
,
$fi
,
$max_subs
,
$sortby
) =
@_
;
$sortby
||=
'excl_time'
;
my
$subs_in_file
= (
$fi
)
?
$profile
->subs_defined_in_file(
$fi
, 0)
:
$profile
->subname_subinfo_map;
return
""
unless
$subs_in_file
&&
%$subs_in_file
;
my
$inc_path_regex
= get_abs_paths_alternation_regex([
$profile
->inc],
qr/^|\[/
);
my
$filestr
= (
$fi
) ?
$fi
->filename :
undef
;
my
@subs
=
sort
{
$b
->
$sortby
<=>
$a
->
$sortby
or
$a
->subname cmp
$b
->subname }
values
%$subs_in_file
;
@subs
=
grep
{
$_
->calls > 0 }
@subs
if
!
$fi
;
my
$dev_incl_time
= calc_mad_from_objects(\
@subs
,
'incl_time'
, 1);
my
$dev_excl_time
= calc_mad_from_objects(\
@subs
,
'excl_time'
, 1);
my
$dev_calls
= calc_mad_from_objects(\
@subs
,
'calls'
, 1);
my
$dev_call_count
= calc_mad_from_objects(\
@subs
,
'caller_count'
, 1);
my
$dev_call_fids
= calc_mad_from_objects(\
@subs
,
'caller_fids'
, 1);
my
@subs_to_show
= (
$max_subs
) ?
splice
@subs
, 0,
$max_subs
:
@subs
;
my
$qualifier
= (
@subs
>
@subs_to_show
) ?
"Top $max_subs "
:
""
;
my
$max_pkg_name_len
= max(
map
{
length
(
$_
->
package
) }
@subs_to_show
);
my
$sub_links
;
my
$sortby_desc
= (
$sortby
eq
'excl_time'
) ?
"exclusive time"
:
"inclusive time"
;
$sub_links
.=
qq{
<table id="subs_table" border="1" cellpadding="0" class="tablesorter">
<caption>${qualifier}
Subroutines</caption>
<thead>
<
tr
>
<th>Calls</th>
<th><span title=
"Number of Places sub is called from"
>P</span></th>
<th><span title=
"Number of Files sub is called from"
>F</span></th>
<th>Exclusive<br />Time</th>
<th>Inclusive<br />Time</th>
<th>Subroutine</th>
</
tr
>
</thead>
};
my
$profiler_active
=
$profile
->{attribute}{profiler_active};
my
@rows
;
$sub_links
.=
"<tbody>\n"
;
for
my
$sub
(
@subs_to_show
) {
$sub_links
.=
"<tr>"
;
$sub_links
.= determine_severity(
$sub
->calls || 0,
$dev_calls
);
$sub_links
.= determine_severity(
$sub
->caller_count || 0,
$dev_call_count
);
$sub_links
.= determine_severity(
$sub
->caller_fids || 0,
$dev_call_fids
);
$sub_links
.= determine_severity(
$sub
->excl_time || 0,
$dev_excl_time
, 1,
sprintf
(
"%.1f%%"
,
$sub
->excl_time/
$profiler_active
*100)
);
$sub_links
.= determine_severity(
$sub
->incl_time || 0,
$dev_incl_time
, 1,
sprintf
(
"%.1f%%"
,
$sub
->incl_time/
$profiler_active
*100)
);
my
@hints
;
my
$subname
=
$sub
->subname;
if
(
my
$merged_sub_names
=
$sub
->meta->{merged_sub_names}) {
push
@hints
,
sprintf
"merge of %d subs"
, 1+
scalar
@$merged_sub_names
;
}
my
(
$pkg
,
$subr
) = (
$subname
=~ /^(.*::)(.*?)$/) ? ($1, $2) : (
''
,
$subname
);
$subr
=~ s/\Q
$filestr
\E:(\d+)/:$1/g
if
$filestr
;
$subr
=~ s/
$inc_path_regex
//;
$sub_links
.=
qq{<td class="sub_name">}
;
$sub_links
.=
sprintf
(
qq{<span style="display: none;">%s::%s</span>}
,
$pkg
,
$subr
);
if
(
$sub
->is_xsub) {
my
$is_opcode
= (
$pkg
eq
'CORE'
or
$subr
=~ /^CORE:/);
unshift
@hints
, (
$is_opcode
) ?
'opcode'
:
'xsub'
;
}
if
(
my
$recdepth
=
$sub
->recur_max_depth) {
unshift
@hints
,
sprintf
"recurses: max depth %d, inclusive time %s"
,
$recdepth
, fmt_time(
$sub
->recur_incl_time);
}
$sub_links
.=
sprintf
qq{%*s<a %s>%s</a>%s</span></td>}
,
$max_pkg_name_len
+2,
$pkg
,
$reporter
->href_for_sub(
$subname
),
$subr
,
(
@hints
) ?
" ("
.
join
(
"; "
,
@hints
).
")"
:
""
;
$sub_links
.=
"</tr>\n"
;
}
$sub_links
.=
q{</tbody>}
;
$sub_links
.=
q{</table>}
;
push
@on_ready_js
,
q{
$("#subs_table").tablesorter({
sortList: [[3,1]],
headers: {
3: { sorter: 'fmt_time' }
,
4: { sorter:
'fmt_time'
}
}
});
}
if
@subs_to_show
==
@subs
;
return
$sub_links
;
}
$reporter
->set_param(
'datastart'
,
sub
{
my
(
$profile
,
$fi
) =
@_
;
my
$filestr
=
$fi
->filename;
my
$sub_table
= subroutine_table(
$profile
,
$fi
,
undef
,
undef
);
if
(
$sub_table
and not
$opt_minimal
) {
my
$dot_file
= html_safe_filename(
$filestr
) .
".dot"
;
$sub_table
.=
qq{
Call graph for these subroutines as a
<a href="$dot_file">dot language file</a>.
}
;
our
%dot_file_generated
;
if
(
$dot_file_generated
{
$dot_file
}++) {
my
$subs_in_file
=
$profile
->subs_defined_in_file(
$filestr
, 0);
my
$sub_filter
=
sub
{
my
(
$si
,
$calledby
) =
@_
;
return
1
if
not
defined
$calledby
;
my
$subname
=
$si
->subname;
my
$include
= (
$subs_in_file
->{
$subname
}
||
$subs_in_file
->{
$calledby
});
return
$include
;
};
output_subs_callgraph_dot_file(
$reporter
,
$dot_file
,
$sub_filter
, 0);
}
}
return
qq{
$sub_table
<table border="1" cellpadding="0">
<thead>
<tr><th>Line</th>
<th><span title="Number of statements executed">State<br />ments</span></th>
<th><span title="Time spend executing statements on the line,
excluding time spent executing statements in any called subroutines">Time<br />on line</span></th>
<th><span title="Number of subroutines calls">Calls</span></th>
<th><span title="Time spent in subroutines called (inclusive)">Time<br />in subs</span></th>
<th class="left_indent_header">Code</th>
</tr>\n
</thead>
<tbody>
}
;
}
);
$reporter
->set_param(
footer
=>
sub
{
my
(
$profile
,
$fi
) =
@_
;
my
$footer
= get_footer(
$profile
);
return
"</tbody></table></div>$footer</body></html>"
;
} );
$reporter
->set_param(
mk_report_source_line
=> \
&mk_report_source_line
);
$reporter
->set_param(
mk_report_xsub_line
=> \
&mk_report_xsub_line
);
$reporter
->set_param(
mk_report_separator_line
=> \
&mk_report_separator_line
);
sub
mk_report_source_line {
my
(
$linenum
,
$line
,
$stats_for_line
,
$stats_for_file
,
$profile
,
$fi
) =
@_
;
my
$l
=
sprintf
(
qq{<td class="h"><a name="%s"></a>%s</td>}
,
$linenum
,
$linenum
);
my
$s
= report_src_line(
undef
,
$linenum
,
$line
,
$profile
,
$fi
,
$stats_for_line
);
return
"<tr>$l<td></td><td></td><td></td><td></td>$s</tr>\n"
if
not
%$stats_for_line
;
return
join
""
,
"<tr>$l"
,
determine_severity(
$stats_for_line
->{
'calls'
},
$stats_for_file
->{
'calls'
}),
determine_severity(
$stats_for_line
->{
'time'
},
$stats_for_file
->{
'time'
}, 1,
\
sprintf
(
"Avg %s"
, fmt_time(
$stats_for_line
->{
'time/call'
})||
'--'
)),
determine_severity(
$stats_for_line
->{
'subcall_count'
},
$stats_for_file
->{subcall_count}, 0),
determine_severity(
$stats_for_line
->{
'subcall_time'
},
$stats_for_file
->{subcall_time}, 1),
$s
,
"</tr>\n"
;
}
sub
mk_report_xsub_line {
my
(
$subname
,
$line
,
$stats_for_line
,
$stats_for_file
,
$profile
,
$fi
) =
@_
;
(
my
$anchor
=
$subname
) =~ s/\W/_/g;
return
join
""
,
sprintf
(
qq{<tr><td class="h"><a name="%s"></a>%s</td>}
,
$anchor
,
''
),
"<td></td><td></td><td></td><td></td>"
,
report_src_line(
undef
,
undef
,
$line
,
$profile
,
$fi
,
$stats_for_line
),
"</tr>\n"
;
}
sub
mk_report_separator_line {
my
(
$profile
,
$fi
) =
@_
;
return
join
""
,
sprintf
(
qq{<tr><td class="s"><a name="%s"></a>%s</td>}
,
''
,
' '
),
"<td></td><td></td><td></td><td></td>"
,
'<td class="s"></td>'
,
"</tr>\n"
;
}
sub
_escape_html {
local
$_
=
shift
;
s/\t/ /g;
s/&/
&
;/g;
s/</
<
;/g;
s/>/
>
;/g;
s{\n}{<br />}g;
s{
"}{"}g; # for attributes like title="
..."
return
$_
;
}
sub
report_src_line {
my
(
$value
,
undef
,
$linesrc
,
$profile
,
$fi
,
$stats_for_line
) =
@_
;
$linesrc
= _escape_html(
$linesrc
);
our
$inc_path_regex
||= get_abs_paths_alternation_regex([
$profile
->inc]);
my
@prologue
;
my
$subdef_info
=
$stats_for_line
->{subdef_info} || [];
for
my
$sub_info
(
@$subdef_info
) {
my
$callers
=
$sub_info
->caller_fid_line_places;
next
unless
$callers
&&
%$callers
;
my
$subname
=
$sub_info
->subname;
my
@callers
;
while
(
my
(
$fid
,
$fid_line_info
) =
each
%$callers
) {
for
my
$line
(
keys
%$fid_line_info
) {
my
$sc
=
$fid_line_info
->{
$line
};
warn
"$linesrc $subname caller info missing"
if
!
@$sc
;
next
if
!
@$sc
;
push
@callers
, [
$fid
,
$line
,
@$sc
];
}
}
my
$total_calls
= sum(
my
@caller_calls
=
map
{
$_
->[2] }
@callers
);
push
@prologue
,
sprintf
"# spent %s within %s which was called%s:"
,
fmt_incl_excl_time(
$sub_info
->incl_time,
$sub_info
->excl_time),
$subname
,
(
$total_calls
<= 1) ?
""
:
sprintf
(
" %d times, avg %s/call"
,
$total_calls
, fmt_time(
$sub_info
->incl_time /
$total_calls
));
push
@prologue
,
sprintf
"# (data for this subroutine includes %d others that were merged with it)"
,
scalar
@{
$sub_info
->meta->{merged_sub_names}}
if
$sub_info
->meta->{merged_sub_names};
my
$max_calls
= max(
@caller_calls
);
@callers
=
sort
{
$b
->[2] <=>
$a
->[2] ||
$b
->[3] <=>
$a
->[3] }
@callers
;
for
my
$caller
(
@callers
) {
my
(
$fid
,
$line
,
$count
,
$incl_time
,
$excl_time
,
undef
,
undef
,
undef
,
undef
,
$calling_subs
) =
@$caller
;
my
@subnames
=
sort
keys
%{
$calling_subs
|| {}};
my
$subname
= (
@subnames
) ?
" by "
.
join
(
" or "
,
@subnames
) :
""
;
my
$caller_fi
=
$profile
->fileinfo_of(
$fid
);
if
(!
$caller_fi
) {
warn
sprintf
"Caller of %s, from fid %d line %d has no fileinfo (%s)"
,
$sub_info
,
$fid
,
$line
,
$subname
;
die
2;
next
;
}
my
$avg_time
=
""
;
$avg_time
=
sprintf
", avg %s/call"
, fmt_time(
$incl_time
/
$count
)
if
$count
> 1;
my
$times
=
sprintf
" (%s+%s)"
, fmt_time(
$excl_time
),
fmt_time(
$incl_time
-
$excl_time
);
my
$filename
=
$caller_fi
->filename(
$fid
);
my
$line_desc
=
"line $line of $filename"
;
$line_desc
=~ s/ of \Q
$filename
\E$//g
if
$filename
eq
$fi
->filename;
$line_desc
=~ s/
$inc_path_regex
//g;
my
$href
=
$reporter
->href_for_file(
$caller_fi
,
$line
);
push
@prologue
,
sprintf
q{# %*s times%s%s at <a %s>%s</a>%s}
,
length
(
$max_calls
),
$count
,
$times
,
$subname
,
$href
,
$line_desc
,
$avg_time
;
$prologue
[-1] =~ s/^(
}
}
my
$prologue
=
''
;
$prologue
=
sprintf
qq{<div class="calls"><div class="calls_in">%s</div></div>}
,
join
(
"\n"
,
@prologue
)
if
@prologue
;
my
$epilogue
=
''
;
my
$ws
;
my
$subcall_info
=
$stats_for_line
->{subcall_info};
if
(
$subcall_info
&&
%$subcall_info
) {
my
@calls_to
=
sort
{
$subcall_info
->{
$b
}[1] <=>
$subcall_info
->{
$a
}[1] or
$a
cmp
$b
}
keys
%$subcall_info
;
my
$max_calls_to
= max(
map
{
$_
->[0] }
values
%$subcall_info
);
$ws
||= (
$linesrc
=~ m/^((?:
 
;|\s)+)/) ? $1 :
''
;
my
$subs_called_html
=
join
"\n"
,
map
{
my
$subname
=
$_
;
my
(
$count
,
$incl_time
,
$reci_time
,
$rec_depth
) = (@{
$subcall_info
->{
$subname
}})[0,1,5,6];
my
$html
=
sprintf
qq{%s# spent %s making %*d call%s to }
,
$ws
,
fmt_time(
$incl_time
+
$reci_time
, 5),
length
(
$max_calls_to
),
$count
,
$count
== 1 ?
""
:
"s"
;
(
my
$subname_trimmed
=
$subname
) =~ s/
$inc_path_regex
//g;
$html
.=
sprintf
qq{<a %s>%s</a>}
,
$reporter
->href_for_sub(
$subname
),
$subname_trimmed
;
$html
.=
sprintf
qq{, avg %s/call}
, fmt_time((
$incl_time
+
$reci_time
) /
$count
),
if
$count
> 1;
if
(
$rec_depth
) {
$html
.=
sprintf
qq{, recursion: max depth %d, sum of overlapping time %s}
,
$rec_depth
, fmt_time(
$reci_time
);
}
$html
;
}
@calls_to
;
$epilogue
.=
sprintf
qq{<div class="calls"><div class="calls_out">%s</div></div>}
,
$subs_called_html
;
}
my
$evals_called
=
$stats_for_line
->{evalcall_info};
if
(
$evals_called
&&
%$evals_called
) {
$ws
||= (
$linesrc
=~ m/^((?:
 
;|\s)+)/) ? $1 :
''
;
my
@eval_fis
=
sort
{
$b
->sum_of_stmts_time(1) <=>
$a
->sum_of_stmts_time(1) or
$a
->filename cmp
$b
->filename
}
values
%$evals_called
;
my
$evals_called_html
=
join
"\n"
,
map
{
my
$eval_fi
=
$_
;
my
$sum_of_stmts_time
=
$eval_fi
->sum_of_stmts_time;
my
(
$what
,
$extra
) = (
"string eval"
,
""
);
my
$merged_fids
=
$eval_fi
->meta->{merged_fids};
if
(
$merged_fids
) {
$what
=
sprintf
"%d string evals (merged)"
, 1+
@$merged_fids
;
}
my
@nested_evals
=
$eval_fi
->has_evals(1);
my
$nest_eval_time
= 0;
if
(
@nested_evals
) {
$nest_eval_time
= sum
map
{
$_
->sum_of_stmts_time }
@nested_evals
;
$extra
.=
sprintf
", %s here plus %s in %d nested evals"
,
fmt_time(
$sum_of_stmts_time
), fmt_time(
$nest_eval_time
),
scalar
@nested_evals
if
$nest_eval_time
;
}
if
(
my
@subs_defined
=
$eval_fi
->subs_defined(1)) {
my
$sub_count
=
@subs_defined
;
my
$call_count
= sum
map
{
$_
->calls }
@subs_defined
;
my
$excl_time
= sum
map
{
$_
->excl_time }
@subs_defined
;
$extra
.=
sprintf
"<br />%s# includes %s spent executing %d call%s to %d sub%s defined therein."
,
$ws
, fmt_time(
$excl_time
, 2),
$call_count
, (
$call_count
!= 1) ?
's'
:
''
,
$sub_count
, (
$sub_count
!= 1) ?
's'
:
''
if
$call_count
;
}
my
$link
=
sprintf
(
q{<a %s>%s</a>}
,
$reporter
->href_for_file(
$eval_fi
),
$what
);
my
$html
=
sprintf
qq{%s# spent %s executing statements in %s%s}
,
$ws
, fmt_time(
$sum_of_stmts_time
+
$nest_eval_time
, 5),
$link
,
$extra
;
$html
;
}
@eval_fis
;
$epilogue
.=
sprintf
qq{<div class="calls"><div class="calls_out">%s</div></div>}
,
$evals_called_html
;
}
return
qq{<td class="s">$prologue$linesrc$epilogue</td>}
;
}
$reporter
->set_param(
'suffix'
,
'.html'
);
$reporter
->_output_additional(
'style.css'
, get_css());
$reporter
->report();
output_subs_index_page(
$reporter
,
"index-subs-excl.html"
,
'excl_time'
);
output_index_page(
$reporter
,
"index.html"
);
output_js_files(
$reporter
);
open_browser_on(
"$opt_out/index.html"
)
if
$opt_open
;
exit
0;
sub
output_subs_index_page {
my
(
$r
,
$filename
,
$sortby
) =
@_
;
my
$profile
=
$reporter
->{profile};
open
my
$fh
,
'>'
,
"$opt_out/$filename"
or croak
"Unable to open file $opt_out/$filename: $!"
;
print
$fh
get_html_header(
"Subroutine Index - NYTProf"
);
print
$fh
get_page_header(
profile
=>
$profile
,
title
=>
"Performance Profile Subroutine Index"
);
print
$fh
qq{<div class="body_content"><br />}
;
print
$fh
subroutine_table(
$profile
,
undef
, 0,
$sortby
);
my
$footer
= get_footer(
$profile
);
print
$fh
"</div>$footer</body></html>"
;
close
$fh
;
}
sub
output_index_page {
my
(
$r
,
$filename
) =
@_
;
my
$profile
=
$reporter
->{profile};
open
my
$fh
,
'>'
,
"$opt_out/$filename"
or croak
"Unable to open file $opt_out/$filename: $!"
;
my
$application
=
$profile
->{attribute}{application};
(
my
$app
=
$application
) =~ s:.*/::;
$app
=~ s/ .*//;
print
$fh
get_html_header(
"NYTProf $app"
);
print
$fh
get_page_header(
profile
=>
$profile
,
title
=>
"Performance Profile Index"
,
skip_link_to_index
=>1);
print
$fh
qq{
<div class="body_content"><br />
}
;
my
@all_fileinfos
=
$profile
->all_fileinfos;
my
$eval_fileinfos
=
$profile
->eval_fileinfos;
my
$summary
=
sprintf
"Profile of %s for %s (of %s),"
,
$application
,
fmt_time(
$profile
->{attribute}{profiler_active}),
fmt_time(
$profile
->{attribute}{profiler_duration});
$summary
.=
sprintf
" executing %d statements"
,
$profile
->{attribute}{total_stmts_measured}
-
$profile
->{attribute}{total_stmts_discounted};
$summary
.=
sprintf
" and %d subroutine calls"
,
$profile
->{attribute}{total_sub_calls};
$summary
.=
sprintf
" in %d source files"
,
@all_fileinfos
-
$eval_fileinfos
;
$summary
.=
sprintf
" and %d string evals"
,
$eval_fileinfos
if
$eval_fileinfos
;
printf
$fh
qq{<div class="index_summary">%s.</div>}
, _escape_html(
$summary
);
if
(
$profile
->noneval_fileinfos > 30) {
print
$fh
qq{<div class="jump_to_file"><form name="jump">}
;
print
$fh
qq{<select name="file" onChange="location.href=document.jump.file.value;">\n}
;
printf
$fh
qq{<option disabled="disabled">%s</option>\n}
,
"Jump to file..."
;
foreach
my
$fi
(
sort
{
$a
->filename cmp
$b
->filename }
$profile
->noneval_fileinfos) {
printf
$fh
qq{<option value="#f%s">%s</option>\n}
,
$fi
->fid, _escape_html(
$fi
->filename);
}
print
$fh
"</select></form></div>\n"
;
}
my
$max_subs
= 15;
my
$all_subs
=
keys
%{
$profile
->{sub_subinfo}};
print
$fh
subroutine_table(
$profile
,
undef
,
$max_subs
,
undef
);
if
(
$all_subs
>
$max_subs
) {
print
$fh
sprintf
qq{<div class="table_footer">
See <a href="%s">all %d subroutines</a>
</div>
}
,
"index-subs-excl.html"
,
$all_subs
;
}
if
(
$json_any
) {
output_subs_treemap_page(
$reporter
,
"subs-treemap-excl.html"
,
"Subroutine Exclusive Time Treemap"
,
sub
{
shift
->excl_time });
print
$fh
q{<br/>You can view a <a href="subs-treemap-excl.html">treemap of subroutine exclusive time</a>, grouped by package.<br/>}
;
}
else
{
}
if
(not
$opt_minimal
) {
output_subs_callgraph_dot_file(
$reporter
,
"packages-callgraph.dot"
,
undef
, 1);
print
$fh
q{NYTProf also generates call-graph files in }
.
q{<a href="packages-callgraph.dot">inter-package calls</a>}
;
output_subs_callgraph_dot_file(
$reporter
,
"subs-callgraph.dot"
,
undef
, 0);
print
$fh
q{, <a href="subs-callgraph.dot">all inter-subroutine calls</a>}
;
print
$fh
q{ (probably too complex to render easily)}
if
$all_subs
> 200;
print
$fh
q{.<br/>}
;
}
print
$fh
q{<br/>You can hover over some table cells and headings to view extra information.}
;
print
$fh
q{<br/>Some table column headings can be clicked on to sort the table by that column.}
;
print
$fh
q{<br/>}
;
output_file_table(
$fh
,
$profile
, 1);
my
$footer
= get_footer(
$profile
);
print
$fh
"</div>$footer</body></html>"
;
close
$fh
;
}
sub
js_for_new_treemap {
my
(
$name
,
$new_args
,
$tree_data
) =
@_
;
return
''
unless
$json_any
;
my
$default_new_args
= {
titleHeight
=> 0,
addLeftClickHandler
=> 1,
offset
=> 0,
Color
=> {
allow
=> 1,
minValue
=> 0,
maxValue
=>
scalar
@treemap_colors
,
minColorValue
=> [0, 255, 50],
maxColorValue
=> [255, 0, 50],
},
Tips
=> {
allow
=> 1,
offsetX
=> 20,
offsetY
=> 20,
},
selectPathOnHover
=> 1,
};
exists
$new_args
->{
$_
} or
$new_args
->{
$_
} =
$default_new_args
->{
$_
}
for
keys
%$default_new_args
;
my
$new_args_json
=
$json_any
->objToJson(
$new_args
);
my
$tree_data_json
=
$json_any
->objToJson(
$tree_data
);
my
$js
=
qq{
function init_$name() {
var tm_args = $new_args_json;
//This method is invoked when a DOM element is created.
//Its useful to set DOM event handlers here or manipulate
//the DOM Treemap nodes.
tm_args.onCreateElement = function(content, tree, isLeaf, leaf){
//Add background image for cushion effect
if(isLeaf) {
var style = leaf.style,
width = parseInt(style.width) - 2,
height = parseInt(style.height) - 2;
// don't add gradient if too small to be worth the cost
if (width < 10 || height < 10) { // is narrow
if (width < 50 && height < 50) // is small
return;
}
leaf.innerHTML = tree.name +
"<img src=\\"
js/jit/gradient-cushion1.png\\
" "
+
" style=\\"
position:absolute;top:0;left:0;width:" +
width+
"px;height:"
+ height+
"px;\\"
/>";
style.width = width +
"px"
;
style.height = height +
"px"
;
}
};
// add content to the tooltip
when
a node is hovered
// move to separate function later
tm_args.Tips.onShow = function(tip, node, isLeaf, domElement) {
tip.innerHTML = node.data.tip;
};
TM.Squarified.implement({
'onLeftClick'
: function(elem) { // zoom in one level
//
if
is leaf
var node = TreeUtil.getSubtree(this.tree, elem.parentNode.id);
if
(node.children && node.children.
length
== 0) {
var oldparent = node, newparent = node;
while
(newparent.id != this.shownTree.id) {
oldparent = newparent;
newparent = TreeUtil.getParent(this.tree, newparent.id);
}
this.view(oldparent.id);
}
else
{
this.enter(elem);
}
}
});
TM.Squarified.implement({
createBox: function(json, coord, html) {
if
((coord.width * coord.height > 1) && json.data.\
$area
> 0) {
if
(!this.leaf(json))
var box = this.headBox(json, coord) + this.bodyBox(html, coord);
else
var box = this.leafBox(json, coord);
return
this.contentBox(json, coord, box);
}
else
{
return
""
; //
return
empty string
}
}
});
var
$name
= new TM.Squarified(tm_args);
var json =
$tree_data_json
;
$name
.loadJSON(json);
}
};
return
$js
;
}
sub
pl {
my
(
$fmt
,
$n
) =
@_
;
sprintf
$fmt
.(
$n
== 1 ?
""
:
"s"
),
$n
;
}
sub
package_subinfo_map_to_tm_data {
my
(
$package_tree_subinfo_map
,
$area_sub
) =
@_
;
my
$sub_tip_html
=
sub
{
my
$si
=
shift
;
my
@html
;
push
@html
,
sprintf
"<p><b>%s</b></p><p>"
,
$si
->subname;
push
@html
,
sprintf
"Called %s from %s in %s"
,
pl(
"%d time"
,
$si
->calls),
pl(
"%d place"
,
scalar
$si
->caller_places),
pl(
"%d file"
,
scalar
$si
->caller_fids);
my
$total_time
=
$si
->profile->{attribute}{profiler_duration};
my
$incl_time
=
$si
->incl_time;
push
@html
,
sprintf
"Inclusive time: %s, %.2f%%"
,
fmt_time(
$incl_time
),
$incl_time
/
$total_time
*100;
my
$excl_time
=
$si
->excl_time;
push
@html
,
sprintf
"Exclusive time: %s, %.2f%%"
,
fmt_time(
$excl_time
),
$excl_time
/
$total_time
*100
if
$excl_time
ne
$incl_time
;
if
(
my
$mrd
=
$si
->recur_max_depth) {
push
@html
,
sprintf
"Recursion: max depth %d, recursive inclusive time %s"
,
$mrd
, fmt_time(
$si
->recur_incl_time);
}
return
join
(
"<br />"
,
@html
).
"</p>"
;
};
my
$leaf_data_sub
=
sub
{
my
(
$subinfo
,
$area_from
,
$color
) =
@_
;
my
$data
= {
'$area'
=>
$area_from
->(
$subinfo
),
'$color'
=>
$color
,
tip
=>
$sub_tip_html
->(
$subinfo
),
map
({
$_
=>
$subinfo
->
$_
() }
qw(subname incl_time excl_time)
)
};
return
$data
;
};
our
$nid
;
my
$node_mapper
;
$node_mapper
=
sub
{
my
(
$k
,
$v
,
$title
) =
@_
;
$title
= (
$title
) ?
'::'
.
$k
:
$k
;
my
$n
= {
id
=>
"n"
.++
$nid
,
name
=>
$title
,
};
my
@kids
;
for
my
$pkg_elem
(
keys
%$v
) {
my
$infos
=
$v
->{
$pkg_elem
};
if
(
ref
$infos
eq
'HASH'
) {
push
@kids
,
$node_mapper
->(
$pkg_elem
,
$infos
,
$title
);
next
;
}
our
$color_seqn
;
my
$color
=
$treemap_colors
[
$color_seqn
++ %
@treemap_colors
];
for
my
$info
(
@$infos
) {
next
if
$area_sub
->(
$info
) <= 0;
push
@kids
, {
id
=> ++
$nid
.
"-"
.
$info
->subname,
name
=>
$info
->subname_without_package,
data
=>
$leaf_data_sub
->(
$info
,
$area_sub
,
$color
),
children
=> [],
};
}
}
$n
->{data}{
'$area'
} = (
@kids
) ? sum(
map
{
$_
->{data}{
'$area'
} }
@kids
) : 0
if
not
defined
$n
->{data}{
'$area'
};
$n
->{children} = \
@kids
;
return
$n
;
};
return
$node_mapper
->(
''
,
$package_tree_subinfo_map
,
''
);
}
sub
output_treemap_code {
my
(
%spec
) =
@_
;
my
$fh
=
$spec
{fh};
my
$tm_id
=
'tm'
.
$spec
{id};
my
$root_id
=
'infovis'
.
$spec
{id};
my
$treemap_data
=
$spec
{get_data}->();
$treemap_data
->{name} =
$spec
{title}
if
$spec
{title};
my
$tm_js
= js_for_new_treemap(
$tm_id
, {
rootId
=>
$root_id
},
$treemap_data
);
print
$fh
qq{<script type="text/javascript">$tm_js\n</script>\n}
;
push
@on_ready_js
,
qq{init_$tm_id(); }
;
return
$root_id
;
}
sub
output_subs_treemap_page {
my
(
$r
,
$filename
,
$title
,
$area_sub
) =
@_
;
my
$profile
=
$reporter
->{profile};
open
(
my
$fh
,
'>'
,
"$opt_out/$filename"
)
or croak
"Unable to open file $opt_out/$filename: $!"
;
$title
||=
"Subroutine Time Treemap"
;
print
$fh
get_html_header(
"$title - NYTProf"
, {
add_jit
=>
"Treemap"
});
print
$fh
get_page_header(
profile
=>
$profile
,
title
=>
$title
);
my
@specs
;
push
@specs
, {
id
=> 1,
title
=>
"Treemap of subroutine exclusive time"
,
get_data
=>
sub
{
package_subinfo_map_to_tm_data(
$profile
->package_subinfo_map(0,1),
$area_sub
||
sub
{
shift
->excl_time }, 0);
}
};
my
@root_ids
;
for
my
$spec
(
@specs
) {
push
@root_ids
, output_treemap_code(
fh
=>
$fh
,
profile
=>
$profile
,
%$spec
);
}
print
$fh
qq{<div class="vis_header"><br/>Boxes represent time spent in a subroutine. Coloring represents packages. Click to drill-down into package hierarchy, reload page to reset.</div>\n}
;
print
$fh
qq{<div id="infovis">\n}
;
print
$fh
qq{<br /><div id="$_"></div>\n}
for
@root_ids
;
print
$fh
qq{</div>\n}
;
my
$footer
= get_footer(
$profile
);
print
$fh
"$footer</body></html>"
;
close
$fh
;
}
sub
output_subs_callgraph_dot_file {
my
(
$r
,
$filename
,
$sub_filter
,
$only_show_packages
) =
@_
;
my
$profile
=
$reporter
->{profile};
my
$subinfos
=
$profile
->subname_subinfo_map;
my
$dot_file
=
"$opt_out/$filename"
;
open
my
$fh
,
'>'
,
$dot_file
or croak
"Unable to open file $dot_file: $!"
;
my
$inc_path_regex
= get_abs_paths_alternation_regex([
$profile
->inc],
qr/^|\[/
);
my
$dotnode
=
sub
{
my
$name
=
shift
;
$name
=~ s/
$inc_path_regex
//;
$name
=~ s/
"/\\"
/g;
return
'"'
.
$name
.
'"'
;
};
print
$fh
"digraph {\n"
;
print
$fh
"graph [overlap=false]\n"
;
my
%sub2called_by
;
for
my
$subname
(
keys
%$subinfos
) {
my
$si
=
$subinfos
->{
$subname
};
next
unless
$si
->calls;
next
if
$sub_filter
and not
$sub_filter
->(
$si
,
undef
);
my
$called_by_subnames
=
$si
->called_by_subnames;
if
(!
%$called_by_subnames
) {
warn
sprintf
"%s has no caller subnames but a call count of %d\n"
,
$subname
,
$si
->calls;
next
;
}
if
(
$sub_filter
) {
my
@delete
=
grep
{ !
$sub_filter
->(
$si
,
$_
) }
keys
%$called_by_subnames
;
if
(
@delete
) {
$called_by_subnames
= {
%$called_by_subnames
};
delete
@{
$called_by_subnames
}{
@delete
};
}
next
if
!
keys
%$called_by_subnames
;
}
$sub2called_by
{
$subname
} =
$called_by_subnames
;
}
my
%pkg_subs
;
for
(
keys
%sub2called_by
,
map
{
keys
%$_
}
values
%sub2called_by
) {
m/^(.*)::(.*)?$/ or
warn
"Strange sub name '$_'"
;
$pkg_subs
{$1}{
$_
} =
$sub2called_by
{
$_
} || {};
}
if
(
$only_show_packages
) {
my
%once
;
print
$fh
"node [shape=doublecircle];\n"
;
while
(
my
(
$pkg
,
$subs
) =
each
%pkg_subs
) {
my
@called_by
=
map
{
keys
%$_
}
values
%$subs
;
for
my
$called_by
(
@called_by
) {
(
my
$called_by_pkg
=
$called_by
) =~ s/^(.*)::.*?$/$1/;
my
$link
=
sprintf
qq{%s -> %s;\n}
,
$dotnode
->(
"$called_by_pkg"
),
$dotnode
->(
"$pkg"
);
$once
{
$link
} = 1;
}
}
print
$fh
$_
for
keys
%once
;
}
else
{
while
(
my
(
$pkg
,
$pkg_subs
) =
each
%pkg_subs
) {
(
my
$pkgmangled
=
$pkg
) =~ s/\W+/_/g;
printf
$fh
"subgraph cluster_%s {\n"
,
$pkgmangled
;
printf
$fh
"\tlabel=%s;\n"
,
$dotnode
->(
$pkg
);
for
my
$subname
(
keys
%$pkg_subs
) {
printf
$fh
qq{\t%s;\n}
,
$dotnode
->(
$subname
);
}
printf
$fh
"}\n"
;
}
while
(
my
(
$subname
,
$called_by_subnames
) =
each
%sub2called_by
) {
for
my
$called_by
(
keys
%$called_by_subnames
) {
printf
$fh
qq{%s -> %s;\n}
,
$dotnode
->(
$called_by
),
$dotnode
->(
$subname
);
}
}
}
print
$fh
"}\n"
;
close
$fh
;
return
;
}
sub
output_js_files {
my
(
$profile
) =
@_
;
(
my
$lib
=
$INC
{
"Devel/NYTProf/Data.pm"
}) =~ s/\/Data\.pm$//;
_copy_dir(
"$lib/js"
,
"$opt_out/js"
);
}
sub
_copy_dir {
my
(
$srcdir
,
$dstdir
) =
@_
;
mkdir
$dstdir
or
die
"Can't create $dstdir directory: $!\n"
unless
-d
$dstdir
;
for
my
$src
(
glob
(
"$srcdir/*"
)) {
(
my
$name
=
$src
) =~ s{.*/}{};
next
if
$name
=~ m/^\./;
my
$dstname
=
"$dstdir/$name"
;
if
(not -f
$src
) {
_copy_dir(
$src
,
$dstname
)
if
-d
$src
;
next
;
}
unlink
$dstname
;
copy(
$src
,
$dstname
)
or
warn
"Unable to copy $src to $dstname: $!"
;
}
}
sub
open_browser_on {
my
$index
=
shift
;
ActiveState::Browser::
open
(
$index
);
}
else
{
my
$BROWSER
;
if
($^O eq
"MSWin32"
) {
$BROWSER
=
"start %s"
;
}
elsif
($^O eq
"darwin"
) {
$BROWSER
=
"/usr/bin/open %s"
;
}
else
{
my
@try
=
qw(xdg-open)
;
if
(
$ENV
{BROWSER}) {
push
(
@try
,
split
(/:/,
$ENV
{BROWSER}));
}
else
{
push
(
@try
,
qw(firefox galeon mozilla opera netscape)
);
}
unshift
(
@try
,
"kfmclient"
)
if
$ENV
{KDE_FULL_SESSION};
unshift
(
@try
,
"gnome-open"
)
if
$ENV
{GNOME_DESKTOP_SESSION_ID};
for
(
@try
) {
if
(have_prog(
$_
)) {
if
(
$_
eq
"kfmclient"
) {
$BROWSER
.=
" openURL %s"
;
}
elsif
(
$_
eq
"gnome-open"
||
$_
eq
"opera"
) {
$BROWSER
=
"$_ %s"
;
}
else
{
$BROWSER
=
"$_ %s &"
;
}
last
;
}
}
}
if
(
$BROWSER
) {
(
my
$cmd
=
$BROWSER
) =~ s/
%s
/
"$index"
/;
system
(
$cmd
);
}
else
{
warn
"Don't know how to invoke your web browser.\nPlease visit $index yourself!\n"
;
}
}
}
sub
have_prog {
my
$prog
=
shift
;
for
(
split
(
":"
,
$ENV
{PATH})) {
return
1
if
-x
"$_/$prog"
;
}
return
0;
}
sub
output_file_table {
my
(
$fh
,
$profile
,
$add_totals
) =
@_
;
print
$fh
qq{
<table id="filestable" border="1" cellspacing="0" class="tablesorter">
<caption>Source Code Files — ordered by exclusive time then name</caption>
}
;
print
$fh
qq{
<thead><tr class="index">
<th>Stmts</th><th>Exclusive<br />Time</th>
<th>Reports</th><th>Source File</th>
</tr></thead>
<tbody>
}
;
my
$inc_path_regex
= get_abs_paths_alternation_regex([
$profile
->inc],
qr/^|\[/
);
my
$allTimes
=
$profile
->{attribute}{total_stmts_duration};
my
$allCalls
=
$profile
->{attribute}{total_stmts_measured}
-
$profile
->{attribute}{total_stmts_discounted};
my
$sawampersand_fi
=
$profile
->fileinfo_of(
$profile
->{attribute}{sawampersand_fid}, 1);
my
(
@t_stmt_exec
,
@t_stmt_time
);
my
@fis
=
$profile
->noneval_fileinfos;
@fis
=
sort
{
$b
->meta->{
'time'
} <=>
$a
->meta->{
'time'
} }
@fis
;
my
$dev_time
= calculate_median_absolute_deviation([
map
{
scalar
$_
->meta->{
'time'
} }
@fis
], 1);
foreach
my
$fi
(
@fis
) {
my
$meta
=
$fi
->meta;
my
$fid
=
$fi
->fid;
my
@extra
;
my
$css_class
=
'index'
;
my
(
$eval_stmts
,
$eval_time
) = (0,0);
if
(
my
@has_evals
=
$fi
->has_evals(1)) {
my
$n_evals
=
scalar
@has_evals
;
my
$msg
=
sprintf
"including %d string eval%s"
,
$n_evals
, (
$n_evals
>1) ?
"s"
:
""
;
if
(
my
@nested
=
grep
{
$_
->eval_fid !=
$fid
}
@has_evals
) {
$msg
.=
sprintf
": %d direct plus %d nested"
,
$n_evals
-
@nested
,
scalar
@nested
;
}
push
@extra
,
$msg
;
$eval_stmts
= sum(
map
{
$_
->sum_of_stmts_count }
@has_evals
);
$eval_time
= sum(
map
{
$_
->sum_of_stmts_time }
@has_evals
);
}
if
(
$sawampersand_fi
&&
$fi
== (
$sawampersand_fi
->outer ||
$sawampersand_fi
)) {
my
$in_eval
= (
$fi
==
$sawampersand_fi
)
?
'here'
:
sprintf
q{<a %s>in eval here</a>}
,
$reporter
->href_for_file(
$sawampersand_fi
,
undef
,
'line'
);
push
@extra
,
sprintf
qq{variables that impact regex performance for whole application seen $in_eval}
,
$css_class
=
"warn $css_class"
;
}
print
$fh
qq{<tr class="$css_class">}
;
my
$stmts
=
$meta
->{
'calls'
} +
$eval_stmts
;
print
$fh
determine_severity(
$stmts
,
undef
, 0,
(
$allCalls
) ?
sprintf
(
"%.1f%%"
,
$stmts
/
$allCalls
*100) :
''
);
push
@t_stmt_exec
,
$stmts
;
my
$time
=
$meta
->{
'time'
} +
$eval_time
;
print
$fh
determine_severity(
$time
,
$dev_time
, 1,
(
$allTimes
) ?
sprintf
(
"%.1f%%"
,
$time
/
$allTimes
*100) :
''
);
push
@t_stmt_time
,
$time
;
my
%levels
=
reverse
%{
$profile
->get_profile_levels};
my
$rep_links
=
join
' • '
,
map
{
sprintf
(
qq{<a %s>%s</a>}
,
$reporter
->href_for_file(
$fi
,
undef
,
$_
),
$_
)
}
grep
{
$levels
{
$_
} }
qw(line block sub)
;
print
$fh
"<td>$rep_links</td>"
;
print
$fh
sprintf
q{<td><a name="f%s" title="%s">%s</a> %s</td>}
,
$fi
->fid,
$fi
->abs_filename,
$fi
->filename_without_inc,
(
@extra
) ?
sprintf
(
"(%s)"
,
join
"; "
,
@extra
) :
""
;
print
$fh
"</tr>\n"
;
}
print
$fh
"</tbody>\n"
;
if
(
$add_totals
) {
print
$fh
"<tfoot>\n"
;
my
$stats_fmt
=
qq{<tr class="index"><td class="n">%s</td><td class="n">%s</td><td colspan="2" style="font-style: italic">%s</td></tr>}
;
my
$t_notes
=
""
;
my
$stmt_time_diff
=
$allTimes
- sum(
@t_stmt_time
);
if
(sum(
@t_stmt_exec
) !=
$allCalls
or
$stmt_time_diff
> 0.001) {
$stmt_time_diff
= (
$stmt_time_diff
> 0.001)
?
sprintf
(
" and %s"
, fmt_time(
$stmt_time_diff
)) :
""
;
$t_notes
=
sprintf
"(%d statements%s are unaccounted for)"
,
$allCalls
- sum(
@t_stmt_exec
),
$stmt_time_diff
;
}
print
$fh
sprintf
$stats_fmt
, fmt_float(sum(
@t_stmt_exec
)), fmt_time(sum(
@t_stmt_time
)),
"Total $t_notes"
if
@t_stmt_exec
> 1 or
$t_notes
;
if
(
@t_stmt_exec
> 1) {
print
$fh
sprintf
$stats_fmt
,
int
(fmt_float(sum(
@t_stmt_exec
) /
@t_stmt_exec
)),
fmt_time( sum(
@t_stmt_time
) /
@t_stmt_time
),
"Average"
;
print
$fh
sprintf
$stats_fmt
,
''
, fmt_time(
$dev_time
->[1]),
"Median"
;
print
$fh
sprintf
$stats_fmt
,
''
, fmt_float(
$dev_time
->[0]),
"Deviation"
if
$dev_time
->[0];
}
print
$fh
"</tfoot>\n"
;
}
print
$fh
'</table>'
;
push
@on_ready_js
,
q{
$("#filestable").tablesorter({
sortList: [[1,1],[3,1]],
headers: {
1: { sorter: 'fmt_time' }
,
2: { sorter: false }
}
});
};
return
""
;
}
sub
determine_severity {
my
$val
=
shift
;
return
"<td></td>"
unless
defined
$val
;
my
$stats
=
shift
;
my
$is_time
=
shift
;
my
$title
=
shift
;
my
$fmt_val
= (
$is_time
)
? fmt_time(
$val
)
: fmt_float(
$val
, NUMERIC_PRECISION);
my
$class
;
if
(
defined
$stats
) {
my
$devs
= (
$val
-
$stats
->[1]);
$devs
/=
$stats
->[0]
if
$stats
->[0];
if
(
$devs
< 0) {
$class
=
'c3'
;
}
elsif
(
$devs
< SEVERITY_GOOD) {
$class
=
'c3'
;
}
elsif
(
$devs
< SEVERITY_BAD) {
$class
=
'c2'
;
}
elsif
(
$devs
< SEVERITY_SEVERE) {
$class
=
'c1'
;
}
else
{
$class
=
'c0'
;
}
}
else
{
$class
=
'n'
;
}
if
(
$title
) {
$title
= (
ref
$title
) ?
$$title
: _escape_html(
$title
);
$fmt_val
=
qq{<span title="$title">$fmt_val</span>}
;
}
return
qq{<td class="$class">$fmt_val</td>}
;
}
sub
get_level_buttons {
my
$mode_ref
=
shift
;
my
$file
=
shift
;
my
$level
=
shift
;
my
$html
=
join
' • '
,
map
{
my
$mode
=
$mode_ref
->{
$_
};
if
(
$mode
eq
$level
) {
qq{<span class="mode_btn mode_btn_selected">$mode view</span>}
;
}
else
{
my
$mode_file
=
$file
;
$mode_file
=~ s/(.*-).*?\.html/$1
$mode
.html/o;
qq{<span class="mode_btn"><a href="$mode_file">$mode view</a></span>}
;
}
}
keys
%$mode_ref
;
return
qq{<span>« $html »</span>}
;
}
sub
get_footer {
my
(
$profile
) =
@_
;
my
$version
=
$Devel::NYTProf::Core::VERSION
;
my
$js
=
''
;
if
(
@on_ready_js
) {
@on_ready_js
=
reverse
@on_ready_js
;
$js
=
sprintf
q{
<script type="text/javascript"> $(document).ready(function() { %s }
); </script>
},
join
(
"\n"
,
''
,
@on_ready_js
,
''
);
@on_ready_js
= ();
};
my
$spacing
=
"<br />"
x 10;
return
qq{
$js
<div class="footer">Report produced by the
Perl profiler, developed by
</div>
$spacing
}
;
}
sub
get_html_header {
my
$title
=
shift
||
"Profile Index - NYTProf"
;
my
$opts
=
shift
|| {};
$title
= _escape_html(
$title
);
my
$html
=
<<EOD;
EOD
$html
=
"<html>"
if
$opts
->{not_xhtml};
$html
.=
<<EOD;
<!--
This file was generated by Devel::NYTProf version $Devel::NYTProf::Core::VERSION
-->
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta http-equiv="Content-Language" content="en-us" />
<title>$title</title>
EOD
$html
.=
qq{<link rel="stylesheet" type="text/css" href="style.css" />\n}
unless
$opts
->{skip_style};
if
(
my
$css
=
$opts
->{add_jit}) {
$html
.=
qq{<link rel="stylesheet" type="text/css" href="js/jit/$css.css" />\n}
;
$html
.=
qq{<script language="JavaScript" src="js/jit/jit.js"></script>\n}
;
}
$html
.=
<<EOD unless $opts->{skip_jquery};
<script type="text/javascript" src="js/jquery-min.js"></script>
<script type="text/javascript" src="js/jquery-tablesorter-min.js"></script>
<link rel="stylesheet" type="text/css" href="js/style-tablesorter.css" />
<script type="text/javascript">
// when a column is first clicked on to sort it, use descending order
// XXX doesn't seem to work (and not just because the tablesorter formatSortingOrder() is broken)
\$.tablesorter.defaults.sortInitialOrder = "desc";
// add parser through the tablesorter addParser method
\$.tablesorter.addParser({
id: 'fmt_time', // name of this parser
is: function(s) {
return false; // return false so this parser is not auto detected
},
format: function(orig) { // format data for normalization
// console.log(orig);
val = orig.replace(/ns/,'');
if (val != orig) { return val / (1000*1000*1000); }
val = orig.replace(/µs/,''); /* XXX use µ ? */
if (val != orig) { return val / (1000*1000); }
var val = orig.replace(/ms/,'');
if (val != orig) { return val / (1000); }
var val = orig.replace(/s/,'');
if (val != orig) { return val; }
if (orig == '0') { return orig; }
console.log('no match for fmt_time of '.concat(orig));
return orig;
},
type: 'numeric' // set type, either numeric or text
});
</script>
EOD
$html
.=
$opts
->{head_epilogue}
if
$opts
->{head_epilogue};
$html
.=
<<EOD;
</head>
EOD
return
$html
;
}
sub
get_page_header {
my
%args
=
@_
;
my
(
$profile
,
$head1
,
$head2
,
$right1
,
$right2
,
$skip_link_to_index
) = (
$args
{profile},
$args
{title},
$args
{subtitle},
$args
{title2},
$args
{subtitle2},
$args
{skip_link_to_index}
);
$head2
||=
qq{<br />For ${ \($profile->{attribute}
{application}) }};
$right1
||=
" "
;
$right2
||=
"Run on ${ \scalar localtime($profile->{attribute}{basetime}) }<br />Reported on "
.
localtime
(
time
);
my
$back_link
=
q//
;
unless
(
$skip_link_to_index
) {
$back_link
=
qq{<div class="header_back">
<a href="index.html">← Index</a>
</div>}
;
}
my
@body_attribs
;
push
@body_attribs
,
qq{onload="$args{body_onload}
"}
if
$args
{body_onload};
my
$body_attribs
=
join
"; "
,
@body_attribs
;
return
qq{<body $body_attribs>
<div class="header" style="position: relative; overflow-x: hidden; overflow-y: hidden; z-index: 0; ">
$back_link
<div class="headerForeground" style="float: left">
<span class="siteTitle">$head1</span>
<span class="siteSubtitle">$head2</span>
</div>
<div class="headerForeground" style="float: right; text-align: right">
<span class="siteTitle">$right1</span>
<span class="siteSubtitle">$right2</span>
</div>
<div style="position: absolute; left: 0px; top: 0%; width: 100%; height: 101%; z-index: -1; background-color: rgb(17, 136, 255); "></div>
<div style="position: absolute; left: 0px; top: 2%; width: 100%; height: 99%; z-index: -1; background-color: rgb(16, 134, 253); "></div>
<div style="position: absolute; left: 0px; top: 4%; width: 100%; height: 97%; z-index: -1; background-color: rgb(16, 133, 252); "></div>
<div style="position: absolute; left: 0px; top: 6%; width: 100%; height: 95%; z-index: -1; background-color: rgb(15, 131, 250); "></div>
<div style="position: absolute; left: 0px; top: 8%; width: 100%; height: 93%; z-index: -1; background-color: rgb(15, 130, 249); "></div>
<div style="position: absolute; left: 0px; top: 10%; width: 100%; height: 91%; z-index: -1; background-color: rgb(15, 129, 248); "></div>
<div style="position: absolute; left: 0px; top: 12%; width: 100%; height: 89%; z-index: -1; background-color: rgb(14, 127, 246); "></div>
<div style="position: absolute; left: 0px; top: 14%; width: 100%; height: 87%; z-index: -1; background-color: rgb(14, 126, 245); "></div>
<div style="position: absolute; left: 0px; top: 16%; width: 100%; height: 85%; z-index: -1; background-color: rgb(14, 125, 244); "></div>
<div style="position: absolute; left: 0px; top: 18%; width: 100%; height: 83%; z-index: -1; background-color: rgb(13, 123, 242); "></div>
<div style="position: absolute; left: 0px; top: 20%; width: 100%; height: 81%; z-index: -1; background-color: rgb(13, 122, 241); "></div>
<div style="position: absolute; left: 0px; top: 22%; width: 100%; height: 79%; z-index: -1; background-color: rgb(13, 121, 240); "></div>
<div style="position: absolute; left: 0px; top: 24%; width: 100%; height: 77%; z-index: -1; background-color: rgb(12, 119, 238); "></div>
<div style="position: absolute; left: 0px; top: 26%; width: 100%; height: 75%; z-index: -1; background-color: rgb(12, 118, 237); "></div>
<div style="position: absolute; left: 0px; top: 28%; width: 100%; height: 73%; z-index: -1; background-color: rgb(12, 116, 235); "></div>
<div style="position: absolute; left: 0px; top: 30%; width: 100%; height: 71%; z-index: -1; background-color: rgb(11, 115, 234); "></div>
<div style="position: absolute; left: 0px; top: 32%; width: 100%; height: 69%; z-index: -1; background-color: rgb(11, 114, 233); "></div>
<div style="position: absolute; left: 0px; top: 34%; width: 100%; height: 67%; z-index: -1; background-color: rgb(11, 112, 231); "></div>
<div style="position: absolute; left: 0px; top: 36%; width: 100%; height: 65%; z-index: -1; background-color: rgb(10, 111, 230); "></div>
<div style="position: absolute; left: 0px; top: 38%; width: 100%; height: 63%; z-index: -1; background-color: rgb(10, 110, 229); "></div>
<div style="position: absolute; left: 0px; top: 40%; width: 100%; height: 61%; z-index: -1; background-color: rgb(10, 108, 227); "></div>
<div style="position: absolute; left: 0px; top: 42%; width: 100%; height: 59%; z-index: -1; background-color: rgb(9, 107, 226); "></div>
<div style="position: absolute; left: 0px; top: 44%; width: 100%; height: 57%; z-index: -1; background-color: rgb(9, 106, 225); "></div>
<div style="position: absolute; left: 0px; top: 46%; width: 100%; height: 55%; z-index: -1; background-color: rgb(9, 104, 223); "></div>
<div style="position: absolute; left: 0px; top: 48%; width: 100%; height: 53%; z-index: -1; background-color: rgb(8, 103, 222); "></div>
<div style="position: absolute; left: 0px; top: 50%; width: 100%; height: 51%; z-index: -1; background-color: rgb(8, 102, 221); "></div>
<div style="position: absolute; left: 0px; top: 52%; width: 100%; height: 49%; z-index: -1; background-color: rgb(8, 100, 219); "></div>
<div style="position: absolute; left: 0px; top: 54%; width: 100%; height: 47%; z-index: -1; background-color: rgb(7, 99, 218); "></div>
<div style="position: absolute; left: 0px; top: 56%; width: 100%; height: 45%; z-index: -1; background-color: rgb(7, 97, 216); "></div>
<div style="position: absolute; left: 0px; top: 58%; width: 100%; height: 43%; z-index: -1; background-color: rgb(7, 96, 215); "></div>
<div style="position: absolute; left: 0px; top: 60%; width: 100%; height: 41%; z-index: -1; background-color: rgb(6, 95, 214); "></div>
<div style="position: absolute; left: 0px; top: 62%; width: 100%; height: 39%; z-index: -1; background-color: rgb(6, 93, 212); "></div>
<div style="position: absolute; left: 0px; top: 64%; width: 100%; height: 37%; z-index: -1; background-color: rgb(6, 92, 211); "></div>
<div style="position: absolute; left: 0px; top: 66%; width: 100%; height: 35%; z-index: -1; background-color: rgb(5, 91, 210); "></div>
<div style="position: absolute; left: 0px; top: 68%; width: 100%; height: 33%; z-index: -1; background-color: rgb(5, 89, 208); "></div>
<div style="position: absolute; left: 0px; top: 70%; width: 100%; height: 31%; z-index: -1; background-color: rgb(5, 88, 207); "></div>
<div style="position: absolute; left: 0px; top: 72%; width: 100%; height: 29%; z-index: -1; background-color: rgb(4, 87, 206); "></div>
<div style="position: absolute; left: 0px; top: 74%; width: 100%; height: 27%; z-index: -1; background-color: rgb(4, 85, 204); "></div>
<div style="position: absolute; left: 0px; top: 76%; width: 100%; height: 25%; z-index: -1; background-color: rgb(4, 84, 203); "></div>
<div style="position: absolute; left: 0px; top: 78%; width: 100%; height: 23%; z-index: -1; background-color: rgb(3, 82, 201); "></div>
<div style="position: absolute; left: 0px; top: 80%; width: 100%; height: 21%; z-index: -1; background-color: rgb(3, 81, 200); "></div>
<div style="position: absolute; left: 0px; top: 82%; width: 100%; height: 19%; z-index: -1; background-color: rgb(3, 80, 199); "></div>
<div style="position: absolute; left: 0px; top: 84%; width: 100%; height: 17%; z-index: -1; background-color: rgb(2, 78, 197); "></div>
<div style="position: absolute; left: 0px; top: 86%; width: 100%; height: 15%; z-index: -1; background-color: rgb(2, 77, 196); "></div>
<div style="position: absolute; left: 0px; top: 88%; width: 100%; height: 13%; z-index: -1; background-color: rgb(2, 76, 195); "></div>
<div style="position: absolute; left: 0px; top: 90%; width: 100%; height: 11%; z-index: -1; background-color: rgb(1, 74, 193); "></div>
<div style="position: absolute; left: 0px; top: 92%; width: 100%; height: 9%; z-index: -1; background-color: rgb(1, 73, 192); "></div>
<div style="position: absolute; left: 0px; top: 94%; width: 100%; height: 7%; z-index: -1; background-color: rgb(1, 72, 191); "></div>
<div style="position: absolute; left: 0px; top: 96%; width: 100%; height: 5%; z-index: -1; background-color: rgb(0, 70, 189); "></div>
<div style="position: absolute; left: 0px; top: 98%; width: 100%; height: 3%; z-index: -1; background-color: rgb(0, 69, 188); "></div>
<div style="position: absolute; left: 0px; top: 100%; width: 100%; height: 1%; z-index: -1; background-color: rgb(0, 68, 187); "></div>
</div>\n}
;
}
sub
get_css {
return
<<'EOD';
/* Stylesheet for Devel::NYTProf::Reader HTML reports */
/* You may modify this file to alter the appearance of your coverage
* reports. If you do, you should probably flag it read-only to prevent
* future runs from overwriting it.
*/
/* Note: default values use the color-safe web palette. */
a:visited { color: #6d00E6; }
a:hover { color: red; }
body { font-family: sans-serif; margin: 0px; }
.body_content { margin: 8px; }
.header { font-family: sans-serif; padding-left: 0.5em; padding-right: 0.5em; }
.headerForeground { color: white; padding: 10px; padding-top: 50px; }
.siteTitle { font-size: 2em; }
.siteSubTitle { font-size: 1.2em; }
.header_back {
position: absolute;
padding: 10px;
}
.header_back > a:link,
.header_back > a:visited {
color: white;
text-decoration: none;
font-size: 0.75em;
}
.jump_to_file {
margin-top: 20px;
}
.footer,
.footer > a:link,
.footer > a:visited {
color: #cccccc;
}
.footer { margin: 30px; }
table {
border-collapse: collapse;
border-spacing: 0px;
margin-top: 20px;
}
tr {
text-align : center;
vertical-align: top;
}
th,.h {
background-color: #dddddd;
border: solid 1px #666666;
padding: 0em 0.4em 0em 0.4em;
font-size:0.8em;
}
td {
border: solid 1px #cccccc;
padding: 0em 0.4em 0em 0.4em;
}
caption {
background-color: #dddddd;
text-align: left;
white-space: pre;
padding: 0.4em;
}
.table_footer { color: gray; }
.table_footer > a:link,
.table_footer > a:visited { color: gray; }
.table_footer > a:hover { color: red; }
.index { text-align: left; }
.mode_btn_selected {
font-style: italic;
}
/* subroutine dispatch table */
.sub_name {
text-align: left;
font-family: monospace;
white-space: pre;
color: gray;
}
/* source code */
th.left_indent_header {
padding-left: 15px;
text-align: left;
}
pre,.s {
text-align: left;
font-family: monospace;
white-space: pre;
}
/* plain number */
.n { text-align: right }
/* Classes for color-coding profiling information:
* c0 : code not hit
* c1 : coverage >= 75%
* c2 : coverage >= 90%
* c3 : path covered or coverage = 100%
*/
.c0, .c1, .c2, .c3 { text-align: right; }
.c0 { background-color: #ffb3b3; } /* red */
.c1 { background-color: #ffd9b4; } /* orange */
.c2 { background-color: #ffffB4; } /* yellow */
.c3 { background-color: #B4ffB4; } /* green */
/* warnings */
.warn {
background-color: #FFFFAA;
border: 0;
width: 96%;
text-align: center;
padding: 5px 0;
}
.warn_title {
background-color: #FFFFAA;
border: 0;
color: red;
width: 96%;
font-size: 2em;
text-align: center;
padding: 5px 0;
}
/* summary of calls into and out of a sub */
.calls {
display: block;
color: gray;
padding-top: 5px;
padding-bottom: 5px;
text-decoration: none;
}
.calls:hover {
background-color: #e8e8e8;
color: black;
}
.calls a { color: gray; text-decoration: none; }
.calls:hover a { color: black; text-decoration: underline; }
.calls:hover a:hover { color: red; }
/* give a little headroom to the summary of calls into a sub */
.calls .calls_in { margin-top: 5px; }
.vis_header {
text-align:center;
font-style: italic;
padding-top: 5px; color: gray;
}
EOD
}
Hide Show 176 lines of Pod