aboutsummaryrefslogtreecommitdiff
path: root/matrix-report.pl
diff options
context:
space:
mode:
Diffstat (limited to 'matrix-report.pl')
-rw-r--r--matrix-report.pl361
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
+