mirror of https://github.com/openwrt/openwrt.git
182 lines
4.6 KiB
Perl
Executable File
182 lines
4.6 KiB
Perl
Executable File
#!/usr/bin/env perl
|
|
#
|
|
# SPDX-License-Identifier: GPL-2.0-only
|
|
#
|
|
# Turns a BUILD_TIME_LOG file into a report.
|
|
#
|
|
# Usage: build-time-report.pl [-n <count>] [-s <stage>] <logfile>
|
|
#
|
|
# Wall share is the share of the build duration that a stage is responsible
|
|
# for. Each second of the build is divided between the stages that run in it,
|
|
# so the wall shares of all stages add up to the busy time. A stage that runs
|
|
# alone on an idle machine gets the whole second. Solo time counts only the
|
|
# seconds in which a stage is the one thing that runs, so it is the part of
|
|
# the build that a faster stage shortens directly.
|
|
|
|
use strict;
|
|
use warnings;
|
|
|
|
my $top = 20;
|
|
my $only_stage;
|
|
|
|
while (@ARGV && $ARGV[0] =~ /^-/) {
|
|
my $opt = shift @ARGV;
|
|
if ($opt eq '-n') { $top = shift @ARGV }
|
|
elsif ($opt eq '-s') { $only_stage = shift @ARGV }
|
|
else { die "Usage: $0 [-n <count>] [-s <stage>] <logfile>\n" }
|
|
}
|
|
|
|
my $file = shift @ARGV or die "Usage: $0 [-n <count>] [-s <stage>] <logfile>\n";
|
|
|
|
open(my $fh, '<', $file) or die "$0: cannot read $file: $!\n";
|
|
|
|
my (@iv, %pending);
|
|
|
|
while (my $line = <$fh>) {
|
|
chomp $line;
|
|
my ($time, $event, $stage, $name) = split(/\t/, $line, 4);
|
|
next unless defined $name;
|
|
next unless defined $time && $time =~ /^[0-9]+(\.[0-9]+)?$/;
|
|
next if defined $only_stage && $stage ne $only_stage;
|
|
|
|
my $key = "$stage\t$name";
|
|
|
|
if ($event eq 'begin') {
|
|
push @{$pending{$key}}, $time;
|
|
next;
|
|
}
|
|
|
|
next unless $event eq 'end';
|
|
my $start = $pending{$key} ? pop @{$pending{$key}} : undef;
|
|
next unless defined $start;
|
|
|
|
push @iv, {
|
|
idx => scalar @iv,
|
|
start => $start,
|
|
end => $time,
|
|
dur => $time - $start,
|
|
stage => $stage,
|
|
name => $name,
|
|
share => 0,
|
|
solo => 0,
|
|
};
|
|
}
|
|
|
|
close($fh);
|
|
|
|
my $incomplete = 0;
|
|
$incomplete += scalar @{$pending{$_}} for keys %pending;
|
|
|
|
die "$0: no completed stages in $file\n" unless @iv;
|
|
|
|
my @events;
|
|
for my $iv (@iv) {
|
|
push @events, [ $iv->{start}, 1, $iv ];
|
|
push @events, [ $iv->{end}, 0, $iv ];
|
|
}
|
|
|
|
# An end event sorts before a begin event at the same instant. One stage that
|
|
# hands over to the next one must not look like parallel work.
|
|
@events = sort { $a->[0] <=> $b->[0] || $a->[1] <=> $b->[1] } @events;
|
|
|
|
my (%active, $idle, $busy);
|
|
$idle = 0;
|
|
$busy = 0;
|
|
|
|
my $prev = $events[0][0];
|
|
|
|
for my $ev (@events) {
|
|
my ($t, $is_start, $iv) = @$ev;
|
|
my $dt = $t - $prev;
|
|
|
|
if ($dt > 0) {
|
|
my @cur = values %active;
|
|
my $n = scalar @cur;
|
|
|
|
if (!$n) {
|
|
$idle += $dt;
|
|
}
|
|
else {
|
|
$busy += $dt;
|
|
my $part = $dt / $n;
|
|
$_->{share} += $part for @cur;
|
|
$cur[0]->{solo} += $dt if $n == 1;
|
|
}
|
|
}
|
|
|
|
if ($is_start) { $active{$iv->{idx}} = $iv }
|
|
else { delete $active{$iv->{idx}} }
|
|
|
|
$prev = $t;
|
|
}
|
|
|
|
my $total = $events[-1][0] - $events[0][0];
|
|
my $sum_dur = 0;
|
|
$sum_dur += $_->{dur} for @iv;
|
|
|
|
my (%st, %pkg);
|
|
|
|
for my $iv (@iv) {
|
|
my $s = $st{$iv->{stage}} ||= { count => 0, dur => 0, share => 0, solo => 0 };
|
|
$s->{count}++;
|
|
$s->{dur} += $iv->{dur};
|
|
$s->{share} += $iv->{share};
|
|
$s->{solo} += $iv->{solo};
|
|
|
|
my $key = sprintf("%s [%s]", $iv->{name}, $iv->{stage});
|
|
my $p = $pkg{$key} ||= { dur => 0, share => 0, solo => 0 };
|
|
$p->{dur} += $iv->{dur};
|
|
$p->{share} += $iv->{share};
|
|
$p->{solo} += $iv->{solo};
|
|
}
|
|
|
|
sub hms {
|
|
my $s = shift;
|
|
return sprintf("%d:%02d:%05.2f",
|
|
int($s / 3600), int($s / 60) % 60, $s - 60 * int($s / 60));
|
|
}
|
|
|
|
sub pct {
|
|
my ($a, $b) = @_;
|
|
return $b > 0 ? 100 * $a / $b : 0;
|
|
}
|
|
|
|
printf "build window %s\n", hms($total);
|
|
printf "busy %s (%.1f%%)\n", hms($busy), pct($busy, $total);
|
|
printf "idle %s (%.1f%%)\n", hms($idle), pct($idle, $total);
|
|
printf "stage time %s\n", hms($sum_dur);
|
|
printf "mean parallelism %.2f\n", $busy > 0 ? $sum_dur / $busy : 0;
|
|
printf "stages completed %d\n", scalar @iv;
|
|
printf "stages unfinished %d\n", $incomplete if $incomplete;
|
|
print "\n";
|
|
|
|
printf "%-10s %6s %12s %12s %8s %12s %8s\n",
|
|
'stage', 'count', 'sum', 'wall share', 'of wall', 'solo', 'of wall';
|
|
|
|
for my $name (sort { $st{$b}{share} <=> $st{$a}{share} } keys %st) {
|
|
my $s = $st{$name};
|
|
printf "%-10s %6d %12s %12s %7.1f%% %12s %7.1f%%\n",
|
|
$name, $s->{count}, hms($s->{dur}), hms($s->{share}),
|
|
pct($s->{share}, $total), hms($s->{solo}), pct($s->{solo}, $total);
|
|
}
|
|
|
|
sub table {
|
|
my ($title, $key, $ref) = @_;
|
|
|
|
print "\n$title\n";
|
|
printf "%-56s %12s %12s %12s\n", 'stage', 'duration', 'wall share', 'solo';
|
|
|
|
my @k = sort { $ref->{$b}{$key} <=> $ref->{$a}{$key} } keys %$ref;
|
|
splice(@k, $top) if @k > $top;
|
|
|
|
for my $name (@k) {
|
|
my $e = $ref->{$name};
|
|
printf "%-56s %12s %12s %12s\n",
|
|
$name, hms($e->{dur}), hms($e->{share}), hms($e->{solo});
|
|
}
|
|
}
|
|
|
|
table("top $top by wall share", 'share', \%pkg);
|
|
table("top $top by solo time", 'solo', \%pkg);
|
|
table("top $top by duration", 'dur', \%pkg);
|