diff options
Diffstat (limited to 'matrix-report.pl')
-rw-r--r-- | matrix-report.pl | 361 |
1 files changed, 361 insertions, 0 deletions
diff --git a/matrix-report.pl b/matrix-report.pl new file mode 100644 index 0000000..9269043 --- /dev/null +++ b/matrix-report.pl @@ -0,0 +1,361 @@ +#!/usr/bin/perl + +use File::Temp qw/ tempfile tempdir /; +use strict; + +my $REPORT_INTERVAL=0.1; # default to 0.1s +my $DEFAULT_PROFILE_LIMIT=7;# default number of entries in profile +my $PERF="perf"; # default perf binary +my $MIN_TASK_CPU=1; # hide tasks below this %age CPU usage in menu +my ($SAMPLE_INTERVAL, $INPUT, $OUTPUT); + +while (@ARGV) +{ + my $opt = shift; + if ($opt =~ /^--report[_-]?interval(=(.*))?$/) { $REPORT_INTERVAL = $2 // shift; } + elsif ($opt =~ /^--sample[_-]?interval(=(.*))?$/) { $SAMPLE_INTERVAL = $2 // shift; } + elsif ($opt =~ /^--sample[_-]?freq(?:uency)?(=(.*))?$/) { $SAMPLE_INTERVAL = 1 / ($2 // shift); } + elsif ($opt =~ /^--min[_-]task[_-]cpu(=(.*))?$/) { $MIN_TASK_CPU = $2 // shift; } + elsif ($opt =~ /^--output(=(.*))?$/) { $OUTPUT = $2 // shift; } + elsif ($opt =~ /^--input(=(.*))?$/) { $INPUT = $2 // shift; } + elsif ($opt =~ /^--perf(=(.*))?$/) { $PERF = $2 // shift; } + else { die "Unknown option: $opt"; } +} + +#die "Must specify --sample-interval" unless defined $SAMPLE_INTERVAL; +die "Must specify --input" unless defined $INPUT; + +my $tmpdir = tempdir( CLEANUP => 1 ); +system(qq("$PERF" script --hide-call-graph --header -i "$INPUT" > "$tmpdir/perfscript.out")) and die "perf script failed"; + +my $fh; +if (defined $OUTPUT) +{ + open $fh, ">", $OUTPUT or die "Could not open $OUTPUT: $!"; +} else { + $fh = *STDOUT; +} + +open INPUT, "<", "$tmpdir/perfscript.out" or die "Open failed: $!"; + +my (@pid, @cpu, @time, @where, %task, %pid_count); +my ($max_cpu); + +while (<INPUT>) +{ + chomp; + if (/^# event : .*{ sample_period, sample_freq } = (\d+)/) + { + my $discovered_interval = 1/$1; + if (!defined $SAMPLE_INTERVAL) + { + $SAMPLE_INTERVAL = $discovered_interval; + } else { + warn "Command line configured interval was $SAMPLE_INTERVAL, but perf.data header has $discovered_interval" if $SAMPLE_INTERVAL != $discovered_interval; + } + } + next unless /\s*(\S+)\s+(\d+)\s+\[0*(\d+)\]\s+(\d+\.\d+):\s+(\d+)\s+(\S+)\s+(\S+)\s+(.*)/; + die "Sample interval/frequency not specified and couldn't discover it from perf output. Use --sample-frequency or --sample-interval" unless defined $SAMPLE_INTERVAL; + my ($task, $pid, $cpu, $time, $where) = ($1, $2, $3, $4, $8); + + $max_cpu = $cpu if $cpu > $max_cpu; + push @cpu, $cpu; + push @time, $time; + push @where, $where; + push @pid, $pid; + $task{$pid} = $task; + $pid_count{$pid}++; +} + +close INPUT; + +if (!defined $max_cpu) +{ + die "No data found, perhaps you need to use --all-cpus with perf record"; +} + +print $fh <<EOF; +<html> +<head> +<title>perf table</title> +<style> +.bg0 { background-color: #ff0000 } +.bg10 { background-color: #df1010 } +.bg20 { background-color: #bf2f00 } +.bg30 { background-color: #af4f00 } +.bg40 { background-color: #8f6f00 } +.bg50 { background-color: #6f8f00 } +.bg60 { background-color: #4faf00 } +.bg70 { background-color: #2fbd00 } +.bg80 { background-color: #00df00 } +.bg90 { background-color: #00ff00 } + +.alignr { + text-align: right; +} + +span.hoverprofile { + visibility: hidden; + background-color: #ffa0ff; + color: black; + text-align: left; + border-radius: 6px; + padding: 5px 5px; + + position: absolute; + z-index: 1; +} + +.clickablepid:hover { + cursor:pointer; + background: rgba(255,255,255,0.4); +} + +.rotate { + writing-mode: vertical-lr; + transform:rotate(190deg); +} + +.hashover { + border-color: rgba(0,0,0,0); + border-width: 1px; + border-style: solid; +} + +.hashover:hover .hoverprofile { + visibility: visible; +} + +#pidtaskwrapper { + display: table; + table-layout: fixed; + + overflow-y: scroll + width:90%; +} + +.pidtask { + cursor:pointer; + min-width: 8em; + width: 8em; + overflow: hidden; + white-space: nowrap; + display: table-cell; + border-color: rgba(0,0,0,0); + border-style: solid; + border-width: 1px; + background-color: #cccccc; + text-align: center; + margin: 3px; + float: left; +} + +.pidtask:hover { + /*overflow:visible; + width: auto;*/ + /*box-shadow: inset 0px 0px 10000px rgba(255,255,255,0.6);*/ + background-color: #dddddd; +} + +.profiletable { + white-space:pre; + font-family: monospace; +} + +.moreless { + cursor:pointer; +} + +.lessmore { + cursor:pointer; +} + +.moreless span.more { + display: initial; +} + +.moreless span.less { + display: none; +} + +.lessmore span.less { + display: initial; +} + +.lessmore span.more { + display: none; +} + +.button { + display: inline-block; + text-decoration: none; + color: #fff; + font-weight: bold; + background-color: #5490c0; + padding: 3px; + border: 1px solid #2c6090; +} + +</style> +</head> +<body> +<script> +var styleEl = document.createElement('style'), styleSheet; + +// Append style element to head +document.head.appendChild(styleEl); + +// Grab style sheet +styleSheet = styleEl.sheet; + +var selectedpid = -1; + +function highlight_pids(list) +{ + delete_rules(); + list.forEach(function(p) { + // var style="box-shadow: inset 0px 0px 10000px rgba(255,255,255,0.6);"; + // var style="border-color: black; border-style: solid;"; + // var style="box-shadow: 0 0 0 1px black;"; + var style="border-color: black; color: white;"; + styleSheet.insertRule(".pid"+p+" { " +style+ "}"); + }); +} + +function do_click(pid) +{ + if (selectedpid != pid) + { + highlight_pids([pid]); + selectedpid = pid; + } else { + delete_rules(); + } +} + +function delete_rules() +{ + selectedpid = -1; + while (styleSheet.cssRules.length > 0) { styleSheet.deleteRule(0); } +} + +function do_moreless(elem) +{ + if (elem.className == "moreless") + { + elem.className = "lessmore"; + } else { + elem.className = "moreless"; + } +} +</script> +<div id="allcontent"> +EOF + +my $max_count=1+($time[$#time]-$time[0])/$SAMPLE_INTERVAL; +print $fh qq(<div id="pidtaskwrapper" class="moreless">); +print $fh qq(<span class="more button" onclick="do_moreless(this.parentElement)">Show all tasks</span>); +print $fh qq(<span class="less button" onclick="do_moreless(this.parentElement)">Show tasks above $MIN_TASK_CPU% cpu usage</span>); +print $fh qq(<br>); +foreach my $pid (sort keys %task) +{ + my $task_cpu = $pid_count{$pid}/$max_count; + my $class = $task_cpu > ($MIN_TASK_CPU/100) ? "" : "less"; + my $task_cpu_pct=sprintf("%.1f%%",100*$task_cpu); + print $fh qq(<span class="pidtask pid$pid $class" onclick="do_click($pid)">); + print $fh qq($pid<br>$task{$pid}<br>$task_cpu_pct); + print $fh qq(</span>); +} +print $fh qq(</div>); + +print $fh qq(<table id="matrix"><tr>); +for (my $c=0; $c<=$max_cpu; $c++) +{ + print $fh qq(<th><span class="rotate">CPU$c</span></th>); +} +print $fh "</tr>\n"; + +my $first_time=$time[0]; +my $start_time=$first_time; +my %counts; +my %locs; +my %pids; +my %loc_to_pid; + +for (my $i=0;$i<$#cpu;$i++) +{ + if ($time[$i] > ($start_time + $REPORT_INTERVAL)) + { + my $row_time=$start_time-$first_time; + # advance the start time for the next row. + $start_time+=$REPORT_INTERVAL; + + my $str; + + for (my $c=0; $c<=$max_cpu; $c++) + { + my $n = $counts{$c}; + my $pct = 100*$n/($REPORT_INTERVAL/$SAMPLE_INTERVAL); + my $col = 10*int($pct/10); + $col = $col > 90 ? 90 : $col; + my $pid_classes=join ' ', map { "pid$_" } keys %{$pids{$c}}; + $str .= qq(<td class="bg$col hashover $pid_classes">); + $str .= sprintf("%.0f%%",$pct); + if ($n > 0) { + $str .= qq(<span class="hoverprofile">); + my $lochash=$locs{$c}; + $str.="Time = ${row_time}s<p>"; + my @loc_list = sort { $lochash->{$b} <=> $lochash->{$a} } keys %{$lochash}; + my $top_part; + my $remainder; + my $count=0; + foreach my $loc (@loc_list) + { + my $prof_pct = sprintf("%5.1f%%", 100 * $lochash->{$loc} / $n); + my $pid=$loc_to_pid{$loc}; + $loc .= " - $task{$pid}"."[$pid]" if $loc=~/\(\[unknown\]\)$/; + my $line = qq($prof_pct - $loc\n); + if ($count++ > $DEFAULT_PROFILE_LIMIT) { + $remainder .= qq(<span class="pid$pid">$line</span>); + } else { + $top_part .= qq(<span class="pid$pid">$line</span>); + } + } + if ($remainder) { + my $more = $count - $DEFAULT_PROFILE_LIMIT; + $str .= qq(<p><div class="moreless" onclick="do_moreless(this)"><span class="profiletable">$top_part</span><span class="more">More... ($more more entries)</span><span class="less"><span class="profiletable">$remainder</span>Less...</span></div>); + } else { + $str .= "<p><pre>$top_part</pre>"; + } + + my $max_counts=$REPORT_INTERVAL/$SAMPLE_INTERVAL; + $str .= "<br>Threads active in this timeslot<p>"; + $str .= "<table><tr><th>PID</th><th>Task name</th><th>CPU usage</th></tr>"; + foreach my $pid (sort keys %{$pids{$c}}) + { + my $cpu = sprintf("%.1f%%", 100 * $pids{$c}->{$pid} / $max_counts); + $str .= qq(<tr><td class="alignr clickablepid" onclick="do_click($pid)">$pid</td><td>$task{$pid}</td><td class="alignr">$cpu</td></tr>); + } + $str .= "</table>"; + $str .= qq(</span>); + } + $str .= "</td>"; + } + print $fh qq(<tr>$str</tr>\n); + + %counts=(); + %locs=(); + %pids=(); + } + $counts{$cpu[$i]}++; + $locs{$cpu[$i]}->{$where[$i]}++; + $loc_to_pid{$where[$i]}=$pid[$i]; + $pids{$cpu[$i]}->{$pid[$i]}++; +} + +print $fh <<EOF; +</table> +</body> +</html> +EOF + |