#!/usr/bin/env perl
##########################################################
## This script is part of the Devel::NYTProf distribution
##
## Copyright, contact and other information can be found
## at the bottom of this file, or by going to:
## http://search.cpan.org/~akaplan/Devel-NYTProf
##
##########################################################
# $Id: nytprofhtml 279 2008-07-08 19:33:49Z tim.bunce $
###########################################################
use warnings;
use strict;
use Carp;
use Getopt::Long;
use List::Util qw(sum);
use Devel::NYTProf::Reader;
use Devel::NYTProf::Core;
use Devel::NYTProf::Util qw(fmt_float calculate_median_absolute_deviation);

our $VERSION = 1.02;

# These control the limits for what the script will consider ok to severe times
# specified in standard deviations from the mean time
use constant SEVERITY_SEVERE			=> 2.0; # above this deviation, a bottleneck
use constant SEVERITY_BAD					=> 1.0;
use constant SEVERITY_GOOD				=> 0.5; # within this deviation, okay

use constant NUMERIC_PRECISION    => 5;

my %opt = (
    file    => 'nytprof.out',
    out	    => 'nytprof',
);
GetOptions(\%opt,
  qw/file|f=s delete|d out|o=s lib|l=s help|h/
) or exit 1;

if (defined($opt{help})) {
	&usage;
	exit 1;
}

# handle file selection option
if (! -r $opt{file}) {
  die "$0: Unable to access $opt{file}\n";
}

# handle handle output location
if (!-e $opt{out}) {
	# will be created
} elsif (!-d $opt{out}) {
  die "$0: Specified output directory `$opt{out}' is a file. whoops!\n";
} elsif (!-w $opt{out}) {
  die "$0: Unable to write to output directory `$opt{out}'\n";
}

# handle deleting old db's
if (defined($opt{'delete'})) {
	_delete();
}

# handle custom lib path
if (defined($opt{lib})) {
    if (-d $opt{lib}) {
        unshift(@INC, $opt{lib});
    } else {
        die "$0: Specified lib directory `$opt{lib}' does not exist.\n";
    }
}

print "Generating report...\n";
my $reporter = new Devel::NYTProf::Reader($opt{file});

# place to store this crap
$reporter->output_dir($opt{out});

# set formatting for html
$reporter->set_param('header', sub {
	my ($profile, $filestr, $output_filestr, $level) = @_;

	my $profile_level_buttons = get_level_buttons($profile->get_profile_levels, $output_filestr, $level);

	my $subhead = qq{&emsp;&emsp;$profile_level_buttons<br />
		For ${ \($profile->{attribute}{application}) }
	};

	get_html_header("Profile: !~FILENAME~!") .
	get_header(profile => $profile, title => "Performance Profile", subtitle => $subhead, 
							mode => qq/-$level/) .
qq{<div class="body_content">
<br />
<table><tr>
<td class='h' align='right'>File</td><td align=\"left\">!~FILENAME~!</td></tr>
<td class='h' align='right'>Statements Executed</td><td align=\"left\">!~TOTAL_CALLS~!
</td></tr>\n
<td class='h' align='right'>Total Time</td><td align=\"left\">!~TOTAL_TIME~!
seconds</td>\n</tr></table><br/>
}});

$reporter->set_param('taintmsg', 
"<div class='warn_title'>WARNING!</div>\n
<div class='warn'>The source file used to generate this report was modified
after the profiler database was generated. The database might be out of sync, you should regenerate it.  This page might not make any sense!</div><br/>\n");


sub subroutine_table {
  my ($profile, $filestr) = @_;

  my $subs_in_file = $profile->subs_defined_in_file( $filestr, 0 );
  return "" unless $subs_in_file && %$subs_in_file;

  my @subs = sort {
    $b->{incl_time} <=> $a->{incl_time} or
    $a->{subname}   cmp $b->{subname}
  } values %$subs_in_file;

  # XXX would be nice to color code these according to the overall time spent in the sub
  my $sub_links = qq{<table border=1 cellpadding=0>
    <tr><th>Calls</th><th>Inclusive<br />Time</th><th colspan=2 class="left_indent_header">Subroutine</th></tr>
  };

	my $dev_incl_time = calculate_median_absolute_deviation(
		[ map { $_->{incl_time} } @subs ]
	);
	my $dev_calls = calculate_median_absolute_deviation(
		[ map { $_->{calls} } @subs ]
	);

	my @rows;
	for my $sub (@subs) {
		$sub_links .= "<tr>";
		# calls
		$sub_links .= determine_severity(undef, $sub->{calls} || 0, $dev_calls);
		# incl_time
		$sub_links .= determine_severity(undef, $sub->{incl_time} || 0, $dev_incl_time);
		# package and subname
		my $subname = $sub->{subname};
		# remove own filename from eg __ANON__[(eval 3)[/long/path/name.pm:99]:53]
		# XXX doesn't work right because $filestr isn't full filename
		$subname =~ s/\Q$filestr\E:(\d+)/:$1/g;
		my ($pkg, $subr) = ($subname =~ /^(.*::)(.*?)$/) ? ($1,$2) : ('',$subname);
		$sub_links .= sprintf qq{<td class="sub_pkg">%s</td>}, $pkg;
		$sub_links .= sprintf(qq{<td class="sub_sub"><a href="#%d">%s</a></td>},
			$sub->{first_line}, $subr);
		$sub_links .= "<tr>\n";
	}
  $sub_links .= "</table>\n";

  return $sub_links;
}

$reporter->set_param('datastart', sub {
    my ($profile, $filestr) = @_;

    my $sub_links = subroutine_table($profile, $filestr);

    return qq{$sub_links<br>
      <table border=1 cellpadding=0>
      <tr><th>Line</th><th>Stmts.</th><th>Time</th><th>Avg.</th><th class="left_indent_header">Code</th>
      </tr>\n
    };
});

$reporter->set_param('footer', '</table></div></body></html>');

$reporter->set_param('linestart', {
    func => sub { my ($value, $linenum, $linesrc) = @_;
      sprintf qq{<tr><td class="h"><a name="%d"></a>%d</td>}, $linenum, $linenum;
    },
});

#$reporter->set_param('column1', 
#	{start => '<td>', value => 'calls', end => '</td>', default => ''});
#$reporter->set_param('column2', 
#	{start => '<td>', value => 'time', end => '</td>', default => ''});
#$reporter->set_param('column3', 
#	{start => '<td>', value => 'time/call', end => '</td>', default => ''});
$reporter->set_param('column4', {
    func => sub {
      my ($value, $linenum, $linesrc, $profile, $subs, $calls) = @_;

      my @prologue;
      for my $sub_info (@$subs) {
        my $callers = $sub_info->{callers};
        next unless $callers && %$callers;

        my @callers;
        while ( my ($fid, $fid_line_info) = each %$callers ) {
          push @callers, [ $fid, $_, @{$fid_line_info->{$_}} ]
            for keys %$fid_line_info;
        }
        my $total_calls = sum(map { $_->[2] } @callers);
				my $avg_per_call = fmt_float($sub_info->{incl_time} / $total_calls);

        push @prologue, sprintf "# spent %ss within %s which was called%s:",
            fmt_float($sub_info->{incl_time}), $sub_info->{subname},
            ($total_calls <= 1) ? ""
							: " $total_calls times, avg ${avg_per_call}s/call";

        # order by most frequent caller first
        for my $caller (sort { $b->[2] <=> $a->[2] } @callers) {
          my ($fid, $line, $count, $incl_time) = @$caller;

          my @subnames = $profile->subname_at_file_line($fid, $line);
          my $subname = (@subnames) ? " by ".join(" or ",@subnames) : "";
					my $avg_time = ($count <= 1) ? ""
						: sprintf ", avg %ss/call", fmt_float($incl_time/$count);
          $incl_time = sprintf " (%ss)", fmt_float($incl_time);

          my $filename = $profile->fid_filename($fid);
          my $href = $reporter->get_file_stats()->{$filename}{html_safe} || "unknown";
          my $caller_filename = $profile->fid_filename($fid);

          push @prologue, sprintf q{# %d times%s%s at <a href="%s#%d">line %d</a> of %s%s},
            $count, $incl_time, $subname,
						"$href.html", $line, $line, $caller_filename,
						$avg_time;
        }
      }
      my $prologue = join("\n",@prologue);
      $prologue = sprintf qq{<div class="calls">%s</div>}, $prologue
        if $prologue;

      my $epilogue = '';
      if (%$calls) {
				my @calls_to = sort {
					$calls->{$b}[1] <=> $calls->{$a}[1] or # incl_time
					$a cmp $b
				} keys %$calls;
        my $ws = ($linesrc =~ m/^((?:&nbsp;|\s)+)/) ? $1 : '';
        $epilogue = join "\n", map {
          my ($count, $incl_time) = @{$calls->{$_}};
          my $href = $reporter->href_for_sub($_);
					my $html = sprintf qq{%s# spent %ss making %d calls to },
						$ws, fmt_float($incl_time), $count;
					$html .= ($href) ? sprintf(qq{<a href="%s">%s</a>}, $href, $_) : $_;
          $html .= sprintf qq{, avg %ss/call}, fmt_float($incl_time/$count)
						if $count > 1;
					$html;
        } @calls_to;
        $epilogue = sprintf qq{<div class="calls">%s</div>}, $epilogue;
      }

      sprintf qq{<td class="s">%s%s%s</td>}, $prologue, $linesrc, $epilogue;
    },
});


#$reporter->set_param('linestart', {func => \&myfunc});
$reporter->set_param('column1', 
										{value => 'calls', func => \&determine_severity});
$reporter->set_param('column2', 
										{value => 'time', func => \&determine_severity});
$reporter->set_param('column3', 
										{value => 'time/call', func => \&determine_severity});
#$reporter->set_param('column4', {func => \&myfunc});

$reporter->set_param('lineend', {start => "</tr>\n"});

# set output options
$reporter->set_param('suffix', '.html');
$reporter->add_regexp('\t', ' 'x8);
$reporter->add_regexp('<', '&lt;');
$reporter->add_regexp('>', '&gt;');

# generate the files
$reporter->report({
		level_additional => sub {
			my ($profile, $level) = @_;
			output_level_indexpage($reporter, $level, "index-$level.html");
		},
});

# output a css file too (optional, but good for pretty pages)
$reporter->_output_additional('style.css', [ <DATA> ]);

#
# SUBROUTINES
#

# output an html indexing page with some information to help navigate potential
# large numbers of profiled files. Optional, recommended
sub output_level_indexpage {
	my ($r, $level, $filename) = @_;
	my $profile = $reporter->{profile};
	
	my $stats = $r->get_file_stats();

	for (values %$stats) {
		next if not $_;
		$_->{'time/call'} = ($_->{calls}) ?  $_->{'time'} / $_->{calls} : 0;
	}

	# Calculate the mean and deviation using a good generic formula.
  # Note: Using Median Absolute Deviation because it is fairly resistant to 
  # the extreme values likely to be found in code and therefore provides better
  # resolution
	my $dev_time = calculate_median_absolute_deviation(
		[ map { $_->{'time'} } values %$stats ]
	);
	my $dev_avgt = calculate_median_absolute_deviation(
		[ map { $_->{'time/call'} } values %$stats ]
	);


	###
	my $profile_level_buttons = get_level_buttons($profile->get_profile_levels, $filename, $level);

	open (OUT, '>', "$opt{out}/$filename")
		or croak "Unable to open file $opt{out}/$filename: $!";

	print OUT get_html_header();
	my $subhead = qq{&emsp;&emsp;$profile_level_buttons<br />
		For ${ \($profile->{attribute}{application}) }
	};
	print OUT get_header(profile => $profile, title => "Performance Profile Index", subtitle => $subhead);
	print OUT qq{
		<div class="body_content"><br/>
		Jump to a file: &nbsp;<select>
	};

	# generate name-sorted select options for namespace groupings
	foreach (sort keys %$stats) {
		my $safe = $stats->{$_}->{html_safe};
		print OUT "<option onClick=location.href=('#$safe');>$_</option>\n"
				if $safe;
	}
	print OUT "</select><br/><br/>\n";

	# generate time-sorted sections for files
	print OUT "<table border=1 cellspacing=0>";
	print OUT "<tr class='index'><th>Stmts</th><th>Time</th>"
						."<th>Avg.</th><th>File</th></tr>";

	my $allTimes = 0; # for stats table at the bottom only
	my $allCalls = 0; # for stats table at the bottom only

	foreach (sort { $b->{'time'} <=> $a->{'time'} } values %$stats) {
		my $safe = $_->{html_safe};
		print OUT  "<tr class='index'>";

		my $html_time = determine_severity('time', $_->{'time'}, $dev_time);
		my $html_calltime = determine_severity('time/call', $_->{'time/call'}, $dev_avgt);

		print OUT "<td class='n'>$_->{calls}</td>"
							."$html_time"
							."$html_calltime\n";
		print OUT  "<td><a name='$safe' href='$safe.html'>$_->{filename}</a></td>";
		print OUT  "</tr>\n";

		# stats collection
		$allTimes += $_->{'time'}; 
		$allCalls += $_->{calls};
	}
	my $stats_fmt = qq{<tr class="index"><td class="n">%s</td><td class="n">%s</td><td class="n">%s</td><td style="font-style: italic">%s</td></tr>};
	print OUT sprintf $stats_fmt,
		fmt_float($allCalls), fmt_float($allTimes), '', "Total";
	print OUT sprintf $stats_fmt,
		int(fmt_float($allCalls/keys %$stats)), fmt_float($allTimes/keys %$stats), '', "Average";
	print OUT sprintf $stats_fmt,
		'', fmt_float($dev_time->[1]), fmt_float($dev_avgt->[1]), "Median";
	print OUT sprintf $stats_fmt,
		'', fmt_float($dev_time->[0]), fmt_float($dev_avgt->[0]), "Deviation";
	print OUT '</table><br/><br/>';

	print OUT "</div></body></html>";
	close OUT;
}

# calculates how good or bad the time is for a file based on the others
sub determine_severity {
	my (undef, $val, $stats) = @_; # @_[3] is like arrayref (deviation, mean)
	return "<td></td>" unless defined $val;

	# normalize the width/precision so that the tables look good.
	$val = fmt_float($val, NUMERIC_PRECISION);
	return qq{<td class="n">$val</td>} unless defined $stats;

	my $devs = ($val - $stats->[1]); #stats->[1] is the mean.
	$devs /= $stats->[0] if $stats->[0]; # no divide by zero when all values equal

	my $class;
	if ($devs < 0) { # fast
		$class = 'c3';
	} elsif ($devs < SEVERITY_GOOD) {
		$class = 'c3';
	} elsif ($devs < SEVERITY_BAD) {
		$class = 'c2';
	} elsif ($devs < SEVERITY_SEVERE) {
		$class = 'c1';
	} else {
		$class = 'c0';
	}
	return "<td class='$class'>$val</td>";
}

# Delete the previous database/directory if it exists
sub _delete {
	if (-d $opt{out}) {
		print "Deleting $opt{out}\n";
		unlink glob($opt{out}."/*");
		unlink glob($opt{out}."/.*");
		rmdir $opt{out} or confess "Delete of $opt{out} failed: $!\n";
	}
}

sub usage {
	print <<END
usage: [perl] nytprofhtml [opts]
 --file <file>, -f <file>  Use the specified file as Devel::NYTProf database
                            file. [default: ./nytprof.out]
 --out <dir>,   -o <dir>   Place generated files here [default: ./nytprof]
 --delete,      -d         Delete the old nytprofhtml output [uses --out]
 --lib,         -l         Add a path to the beginning of \@INC
 --help,        -h         Print this message

This script of part of the Devel::NYTProf::Reader package by Adam J Kaplan.
Copyright 2008 Adam J Kaplan, http://search.cpan.org/~akaplan, Released under
the same terms as Perl itself.
END
}


# return an html string with buttons for switching between profile levels of detail
sub get_level_buttons {
  my $mode_ref = shift;
  my $file = shift;
  my $level = shift;

  my $html = join '&emsp;&bull;&emsp;', 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;
      # replace the mode specifier in the output file name -- file-name-MODE.html
      $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>&laquo;&emsp;$html&emsp;&raquo;</span>};
}


# returns the generic header string.  Here only to make the code more readable.
sub get_html_header {
	my $title = shift || "Profile Index";
	return <<EOD
<!DOCTYPE html
     PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
     "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<!--
This file was generated by Devel::NYTProf::Reader HTML Version $VERSION
using Devel::NYTProf Version $Devel::NYTProf::Core::VERSION
Devel::NYTProf, Devel::NYTProf::Reader and nytprofhtml are copyright 
2008, Adam Kaplan, akaplan at cpan dot org, search.cpan.org/~akaplan
These modules are free. They are licensed under the same terms as Perl itself.
-->
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"></meta>
    <meta http-equiv="Content-Language" content="en-us"></meta>
		<link rel="stylesheet" type="text/css" href="style.css"></link>
    <title>$title</title>
</head>
EOD
}

sub get_header {
	my %args = @_;
	my ($profile, 			$head1, 			$head2, 				 $right1, 			$right2, 					$mode) = 
		 ($args{profile}, $args{title}, $args{subtitle}, $args{title2}, $args{subtitle2}, $args{mode});

	$right1 ||= "&nbsp;";
	$right2 ||= "Run on ${ \scalar localtime($profile->{attribute}{basetime}) }<br />Reported on ".localtime(time);

	my $back_link = q//;
	if ($mode) {
		$back_link = qq{<div class="header_back">
			<a href="index$mode.html">&larr; Index</a>
		</div>};
	}

	return qq{<body> 
<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};
}

# The data handle contains the entire CSS file.
__DATA__
/* 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:hover { color: red; }
a:visited { color: #333333; }

body { font-family: sans-serif; margin: 0px; }
.body_content { margin: 8px; }

.header { font-family: sans-serif; padding-left: 1em; }
.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;
}

table { 
	border-collapse: collapse; 
	border-spacing: 0px; 
}
tr { 
	text-align : center;
	vertical-align: top; 
}
th,.h {
	background-color: #dddddd;
	border: solid 1px #666666;
	padding: 0em 0.4em 0em 0.4em;
}
td { 
	border: solid 1px #cccccc; 
	padding: 0em 0.4em 0em 0.4em;
}

.index { text-align: left; }

.mode_btn_selected {
  font-style: italic;
}

/* subrouting dispatch table */
td.sub_pkg {
  text-align: right;
  padding-right: 0;
  border-right: hidden;
  font-family: monospace;
  color: grey;
}

td.sub_sub {
  text-align: left;
  padding-left: 0;
  border-left: hidden;
  font-family: monospace;
}

a.sub_sub:link {
  color: blue;
  text-decoration: none;
}

a.sub_sub:hover,
a.sub_sub:visited:hover {
  text-decoration: underline;
}

a.sub_sub:visited, 
a.sub_sub:active {
  color: blue;
  text-decoration: none;
}

/* XXX
 * Can be removed if images are not used
div#top_header {
	width: 100%;
	background-image: url("../../images/header_fill.png");
}
img#top_header_left {
	float: left;
}
img#top_header_text {
	float: left;
}
img#top_header_right {
	float: right;
}
*/

/* 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: #ff9999;
}
.c1 {
	background-color: #ffcc99;
}
.c2 {
	background-color: #ffff99;
}
.c3 {
	background-color: #99ff99;
}

/* 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;
}

.calls {
  display: block;
	color: grey;
  padding-top: 5px;
  padding-bottom: 5px;
  text-decoration: none;
}
.calls:hover {
	background-color: #e8e8e8;
	color: black;
}
.calls       a       { color: grey;  text-decoration: none; }
.calls:hover a       { color: black; text-decoration: underline; }
.calls:hover a:hover { color: red; }

__END__

=head1 NAME

nytprofhtml - L<Devel::NYTProf::Reader> HTML format implementation

=head1 SYNOPSIS

 $ nytprofhtml [-h] [-d] [-o <output directory>] [-f <input file>]

 perl -d:NYTProf some_perl_app.pl
 nytprofhtml
 Generating HTML Output...

=head1 HISTORY

A bit of history and a shameless plug...

NYTProf stands for 'New York Times Profiler'. Indeed, this module was developed
by The New York Times Co. to help our developers quickly identify bottlenecks in
large Perl applications.  The NY Times loves Perl and we hope the community will benefit from our work as much as we have from theirs.

Please visit L<http://open.nytimes.com>, our open source blog to see what we are up to, L<http://code.nytimes.com> to see some of our open projects and then 
check out L<htt://nytimes.com> for the latest news!

=head1 DESCRIPTION

C<nytprofhtml> is a script that utilizes L<Devel::NYTProf::Reader> to
create colorful HTMl formatted reports from L<Devel::NYTProf> output.

The reports include dynamic runtime analysis wherein each line and each file
is analyzed based on the preformance of the other lines and files.  As a
result, you can quickly find the slowest module and the slowest line in a 
module.  Slowness is measured in three ways: total calls, total time and
average time per call.  Analysis is based on absolute deviations from the 
median.

That might sound complicated, but in reality you can just run the command and
enjoy your report!

Note: You'll need to run your app through L<Devel::NYTProf> debugger first 

=head1 COMMAND-LINE OPTIONS

These are the command line options understood by C<nytprofhtml>

=over 4

=item -f, --file <filename>

Specifies the location of the input file. Default: nytprof.out

=item -o, --out <dir>

Where to place the generated report. Default: ./nytprof/

=item -d, --delete

Purge any existing database located at whatever -o (above) is set to

=item -l, --lib <dir>

Add a path to the beginning of @INC

=item -h, --help

Print the help message

=back

=head1 SAMPLE OUTPUT

=over 4

=item L<http://adkap.com/images/Screenshot-XML-LibXML.pm.png>

=item L<http://adkap.com/images/Screenshot-Profile-Index.png>

=back

=head1 SEE ALSO

Mailing list and discussion at L<http://groups.google.com/group/develnytprof-dev>

Public SVN Repository and hacking instructions at L<http://code.google.com/p/perl-devel-nytprof/>

L<Devel::NYTProf>
L<Devel::NYTProf::Reader>
L<nytprofcsv>

=head1 AUTHOR

Adam Kaplan, akaplan at nytimes dotcom

=head1 COPYRIGHT AND LICENSE

This program is free software; you can redistribute it and/or modify
it under the same terms as Perl itself, either Perl version 5.8.8 or,
at your option, any later version of Perl 5 you may have available.

=cut

# vim:ts=2:sw=2:sts=0:noexpandtab
