Convert verilator_gantt to python
This commit is contained in:
parent
c678e7ec3e
commit
4b274a8d4d
|
@ -353,6 +353,7 @@ clang-format:
|
|||
PY_PROGRAMS = \
|
||||
bin/verilator_ccache_report \
|
||||
bin/verilator_difftree \
|
||||
bin/verilator_gantt \
|
||||
bin/verilator_profcfunc \
|
||||
examples/xml_py/vl_file_copy \
|
||||
examples/xml_py/vl_hier_graph \
|
||||
|
|
|
@ -1,163 +1,135 @@
|
|||
#!/usr/bin/env perl
|
||||
# See copyright, etc in below POD section.
|
||||
#!/usr/bin/env python3
|
||||
# pylint: disable=C0103,C0114,C0116,C0301,R0914,R0912,R0915,W0511,eval-used
|
||||
######################################################################
|
||||
|
||||
use warnings;
|
||||
use strict;
|
||||
use Getopt::Long;
|
||||
use IO::File;
|
||||
use Pod::Usage;
|
||||
use vars qw($Debug);
|
||||
import argparse
|
||||
import collections
|
||||
import math
|
||||
import re
|
||||
import statistics
|
||||
# from pprint import pprint
|
||||
|
||||
$Debug = 0;
|
||||
my $Opt_File;
|
||||
my $Opt_Time_Per_Char = 0; # rdtsc ticks per char in gantt chart, 0=auto
|
||||
my $opt_vcd = "profile_threads.vcd";
|
||||
|
||||
our %Threads;
|
||||
our %Mtasks;
|
||||
our %Global;
|
||||
|
||||
autoflush STDOUT 1;
|
||||
autoflush STDERR 1;
|
||||
Getopt::Long::config("no_auto_abbrev");
|
||||
if (! GetOptions(
|
||||
"help" => \&usage,
|
||||
"scale=i" => \$Opt_Time_Per_Char,
|
||||
"debug" => sub { $Debug = 1; },
|
||||
"vcd=s" => \$opt_vcd,
|
||||
"no-vcd!" => sub { $opt_vcd = undef; },
|
||||
"<>" => \¶meter,
|
||||
)) {
|
||||
die "%Error: Bad usage, try 'verilator_gantt --help'\n";
|
||||
Threads = collections.defaultdict(lambda: {})
|
||||
Mtasks = collections.defaultdict(lambda: {})
|
||||
Global = {
|
||||
'args': {},
|
||||
'cpuinfo': collections.defaultdict(lambda: {}),
|
||||
'rdtsc_cycle_time': 0,
|
||||
'stats': {}
|
||||
}
|
||||
|
||||
$Opt_File = "profile_threads.dat" if !defined $Opt_File;
|
||||
######################################################################
|
||||
|
||||
process($Opt_File);
|
||||
write_vcd($opt_vcd) if defined $opt_vcd;
|
||||
exit(0);
|
||||
|
||||
#######################################################################
|
||||
def process(filename):
|
||||
read_data(filename)
|
||||
report()
|
||||
|
||||
sub usage {
|
||||
pod2usage(-verbose=>2, -exitval=>0, -output=>\*STDOUT);
|
||||
exit(1); # Unreachable
|
||||
}
|
||||
|
||||
sub parameter {
|
||||
my $param = shift;
|
||||
if (!defined $Opt_File) {
|
||||
$Opt_File = $param;
|
||||
} else {
|
||||
die "%Error: Unknown parameter: $param\n";
|
||||
}
|
||||
}
|
||||
def read_data(filename):
|
||||
with open(filename) as fh:
|
||||
re_prof = re.compile(
|
||||
r'^VLPROF mtask\s(\d+)\sstart\s(\d+)\send\s(\d+)\selapsed\s(\d+)\spredict_time\s(\d+)\scpu\s(\d+)\son thread (\d+)'
|
||||
)
|
||||
re_arg1 = re.compile(r'VLPROF arg\s+(\S+)\+([0-9.])\s*')
|
||||
re_arg2 = re.compile(r'VLPROF arg\s+(\S+)\s+([0-9.])\s*$')
|
||||
re_stat = re.compile(r'VLPROF stat\s+(\S+)\s+([0-9.]+)')
|
||||
re_time = re.compile(r'rdtsc time = (\d+) ticks')
|
||||
re_proc_cpu = re.compile(r'VLPROFPROC processor\s*:\s*(\d+)\s*$')
|
||||
re_proc_dat = re.compile(r'VLPROFPROC ([a-z_ ]+)\s*:\s*(.*)$')
|
||||
cpu = None
|
||||
|
||||
#######################################################################
|
||||
for line in fh:
|
||||
if re_prof.match(line):
|
||||
match = re_prof.match(line)
|
||||
mtask = int(match.group(1))
|
||||
start = int(match.group(2))
|
||||
end = int(match.group(3))
|
||||
elapsed_time = int(match.group(4))
|
||||
predict_time = int(match.group(5))
|
||||
cpu = int(match.group(6))
|
||||
thread = int(match.group(7))
|
||||
if start not in Threads[thread]:
|
||||
Threads[thread][start] = {}
|
||||
Threads[thread][start]['mtask'] = mtask
|
||||
Threads[thread][start]['end'] = end
|
||||
Threads[thread][start]['cpu'] = cpu
|
||||
|
||||
sub process {
|
||||
my $filename = shift;
|
||||
if 'elapsed' not in Mtasks[mtask]:
|
||||
Mtasks[mtask] = {'end': 0, 'elapsed': 0}
|
||||
Mtasks[mtask]['elapsed'] += elapsed_time
|
||||
Mtasks[mtask]['predict'] = predict_time
|
||||
Mtasks[mtask]['end'] = max(Mtasks[mtask]['end'], end)
|
||||
elif re.match(r'^VLPROFTHREAD', line):
|
||||
None # pylint: disable=pointless-statement
|
||||
elif re_arg1.match(line):
|
||||
match = re_arg1.match(line)
|
||||
Global['args'][match.group(1)] = match.group(2)
|
||||
elif re_arg2.match(line):
|
||||
match = re_arg2.match(line)
|
||||
Global['args'][match.group(1)] = match.group(2)
|
||||
elif re_stat.match(line):
|
||||
match = re_stat.match(line)
|
||||
Global['stats'][match.group(1)] = match.group(2)
|
||||
elif re_proc_cpu.match(line):
|
||||
match = re_proc_cpu.match(line)
|
||||
cpu = int(match.group(1))
|
||||
elif cpu and re_proc_dat.match(line):
|
||||
match = re_proc_dat.match(line)
|
||||
term = match.group(1)
|
||||
value = match.group(2)
|
||||
term = re.sub(r'\s+$', '', term)
|
||||
term = re.sub(r'\s+', '_', term)
|
||||
value = re.sub(r'\s+$', '', value)
|
||||
Global['cpuinfo'][cpu][term] = value
|
||||
elif re.match(r'^#', line):
|
||||
None # pylint: disable=pointless-statement
|
||||
elif Args.debug:
|
||||
print("-Unk: %s" % line)
|
||||
# TODO -- this is parsing text printed by a client.
|
||||
# Really, verilator proper should generate this
|
||||
# if it's useful...
|
||||
if re_time.match(line):
|
||||
Global['rdtsc_cycle_time'] = re_time.group(1)
|
||||
|
||||
read_data($filename);
|
||||
report();
|
||||
}
|
||||
|
||||
#######################################################################
|
||||
def re_match_result(regexp, line, result_to):
|
||||
result_to = re.match(regexp, line)
|
||||
return result_to
|
||||
|
||||
sub read_data {
|
||||
my $filename = shift;
|
||||
|
||||
%Global = (rdtsc_cycle_time => 0);
|
||||
######################################################################
|
||||
|
||||
my $fh = IO::File->new("<$filename") or die "%Error: $! $filename,";
|
||||
|
||||
my $cpu;
|
||||
while (my $line = $fh->getline) {
|
||||
if ($line =~ m/VLPROF mtask\s(\d+)\sstart\s(\d+)\send\s(\d+)\selapsed\s(\d+)\spredict_time\s(\d+)\scpu\s(\d+)\son thread (\d+)/) {
|
||||
my $mtask = $1;
|
||||
my $start = $2;
|
||||
my $end = $3;
|
||||
my $elapsed_time = $4;
|
||||
my $predict_time = $5;
|
||||
my $cpu = $6;
|
||||
my $thread = $7;
|
||||
$Threads{$thread}{$start}{mtask} = $mtask;
|
||||
$Threads{$thread}{$start}{end} = $end;
|
||||
$Threads{$thread}{$start}{cpu} = $cpu;
|
||||
def report():
|
||||
print("Verilator Gantt report")
|
||||
|
||||
if (!exists $Mtasks{$mtask}{elapsed}) {
|
||||
$Mtasks{$mtask}{elapsed} = 0;
|
||||
}
|
||||
$Mtasks{$mtask}{elapsed} += $elapsed_time;
|
||||
$Mtasks{$mtask}{predict} = $predict_time;
|
||||
$Mtasks{$mtask}{end} = max($Mtasks{$mtask}{end}, $end);
|
||||
}
|
||||
elsif ($line =~ /^VLPROFTHREAD/) {}
|
||||
elsif ($line =~ m/VLPROF arg\s+(\S+)\+([0-9.])\s*$/
|
||||
|| $line =~ m/VLPROF arg\s+(\S+)\s+([0-9.])\s*$/) {
|
||||
$Global{args}{$1} = $2;
|
||||
}
|
||||
elsif ($line =~ m/VLPROF stat\s+(\S+)\s+([0-9.]+)/) {
|
||||
$Global{stats}{$1} = $2;
|
||||
}
|
||||
elsif ($line =~ m/^VLPROFPROC processor\s*:\s*(\d+)\s*$/) {
|
||||
$cpu = $1;
|
||||
}
|
||||
elsif (defined $cpu && $line =~ m/^VLPROFPROC ([a-z_ ]+)\s*:\s*(.*)$/) {
|
||||
my ($term, $value) = ($1, $2);
|
||||
$term =~ s/\s+$//;
|
||||
$term =~ s/\s+/_/;
|
||||
$value =~ s/\s+$//;
|
||||
$Global{cpuinfo}{$cpu}{$term} = $value;
|
||||
}
|
||||
elsif ($line =~ /^#/) {}
|
||||
elsif ($Debug) {
|
||||
chomp $line;
|
||||
print "Unk: $line\n";
|
||||
}
|
||||
# TODO -- this is parsing text printed by a client.
|
||||
# Really, verilator proper should generate this
|
||||
# if it's useful...
|
||||
if ($line =~ m/rdtsc time = (\d+) ticks/) {
|
||||
$Global{rdtsc_cycle_time} = $1;
|
||||
}
|
||||
}
|
||||
}
|
||||
print("\nArgument settings:")
|
||||
for arg in sorted(Global['args'].keys()):
|
||||
plus = "+" if re.match(r'^\+', arg) else " "
|
||||
print(" %s%s%s" % (arg, plus, Global['args'][arg]))
|
||||
|
||||
#######################################################################
|
||||
|
||||
sub report {
|
||||
print "Verilator Gantt report\n";
|
||||
|
||||
print "\nArgument settings:\n";
|
||||
foreach my $arg (sort keys %{$Global{args}}) {
|
||||
my $plus = ($arg =~ /^\+/) ? "+" : " ";
|
||||
printf " %s%s%d\n", $arg, $plus, $Global{args}{$arg};
|
||||
}
|
||||
|
||||
my $nthreads = scalar keys %Threads;
|
||||
$Global{cpus} = {};
|
||||
foreach my $thread (keys %Threads) {
|
||||
nthreads = len(Threads)
|
||||
Global['cpus'] = {}
|
||||
for thread in Threads:
|
||||
# Make potentially multiple characters per column
|
||||
foreach my $start (keys %{$Threads{$thread}}) {
|
||||
my $cpu = $Threads{$thread}{$start}{cpu};
|
||||
my $elapsed = $Threads{$thread}{$start}{end} - $start;
|
||||
$Global{cpus}{$cpu}{cpu_time} += $elapsed;
|
||||
}
|
||||
}
|
||||
for start in Threads[thread]:
|
||||
cpu = Threads[thread][start]['cpu']
|
||||
elapsed = Threads[thread][start]['end'] - start
|
||||
if cpu not in Global['cpus']:
|
||||
Global['cpus'][cpu] = {'cpu_time': 0}
|
||||
Global['cpus'][cpu]['cpu_time'] += elapsed
|
||||
|
||||
my $mt_mtask_time = 0;
|
||||
my $long_mtask_time = 0;
|
||||
my $last_end = 0;
|
||||
foreach my $mtask (keys %Mtasks) {
|
||||
$mt_mtask_time += $Mtasks{$mtask}{elapsed};
|
||||
$last_end = max($last_end, $Mtasks{$mtask}{end});
|
||||
$long_mtask_time = max($long_mtask_time, $Mtasks{$mtask}{elapsed});
|
||||
}
|
||||
$Global{last_end} = $last_end;
|
||||
mt_mtask_time = 0
|
||||
long_mtask_time = 0
|
||||
last_end = 0
|
||||
for mtask in Mtasks:
|
||||
mt_mtask_time += Mtasks[mtask]['elapsed']
|
||||
last_end = max(last_end, Mtasks[mtask]['end'])
|
||||
long_mtask_time = max(long_mtask_time, Mtasks[mtask]['elapsed'])
|
||||
Global['last_end'] = last_end
|
||||
|
||||
report_graph();
|
||||
report_graph()
|
||||
|
||||
# If we know cycle time in the same (rdtsc) units,
|
||||
# this will give us an actual utilization number,
|
||||
|
@ -167,402 +139,356 @@ sub report {
|
|||
# serial mode, to estimate the overhead of data sharing,
|
||||
# which will show up in the total elapsed time. (Overhead
|
||||
# of synchronization and scheduling should not.)
|
||||
print "\nAnalysis:\n";
|
||||
printf " Total threads = %d\n", $nthreads;
|
||||
printf " Total mtasks = %d\n", scalar(keys %Mtasks);
|
||||
my $ncpus = scalar(keys %{$Global{cpus}});
|
||||
printf " Total cpus used = %d\n", $ncpus;
|
||||
printf " Total yields = %d\n", $Global{stats}{yields};
|
||||
printf " Total eval time = %d rdtsc ticks\n", $Global{last_end};
|
||||
printf " Longest mtask time = %d rdtsc ticks\n", $long_mtask_time;
|
||||
printf " All-thread mtask time = %d rdtsc ticks\n", $mt_mtask_time;
|
||||
my $long_efficiency = $long_mtask_time/($Global{last_end} || 1);
|
||||
printf " Longest-thread efficiency = %0.1f%%\n", $long_efficiency*100;
|
||||
my $mt_efficiency = $mt_mtask_time/($Global{last_end}*$nthreads || 1);
|
||||
printf " All-thread efficiency = %0.1f%%\n", $mt_efficiency*100;
|
||||
printf " All-thread speedup = %0.1f\n", $mt_efficiency*$nthreads;
|
||||
if ($Global{rdtsc_cycle_time} > 0) {
|
||||
my $ut = $mt_mtask_time / $Global{rdtsc_cycle_time};
|
||||
print "tot_mtask_cpu=$mt_mtask_time cyc=$Global{rdtsc_cycle_time} ut=$ut\n";
|
||||
}
|
||||
print("\nAnalysis:")
|
||||
print(" Total threads = %d" % nthreads)
|
||||
print(" Total mtasks = %d" % len(Mtasks))
|
||||
ncpus = len(Global['cpus'])
|
||||
print(" Total cpus used = %d" % ncpus)
|
||||
print(" Total yields = %d" % int(Global['stats']['yields']))
|
||||
print(" Total eval time = %d rdtsc ticks" % Global['last_end'])
|
||||
print(" Longest mtask time = %d rdtsc ticks" % long_mtask_time)
|
||||
print(" All-thread mtask time = %d rdtsc ticks" % mt_mtask_time)
|
||||
long_efficiency = long_mtask_time / (Global.get('last_end', 1))
|
||||
print(" Longest-thread efficiency = %0.1f%%" % (long_efficiency * 100.0))
|
||||
mt_efficiency = mt_mtask_time / (Global.get('last_end', 1) * nthreads or 1)
|
||||
print(" All-thread efficiency = %0.1f%%" % (mt_efficiency * 100.0))
|
||||
print(" All-thread speedup = %0.1f" % (mt_efficiency * nthreads))
|
||||
if Global['rdtsc_cycle_time'] > 0:
|
||||
ut = mt_mtask_time / Global['rdtsc_cycle_time']
|
||||
print("tot_mtask_cpu=" + mt_mtask_time + " cyc=" +
|
||||
Global['rdtsc_cycle_time'] + " ut=" + ut)
|
||||
|
||||
my @p2e_ratios;
|
||||
my $min_p2e = 1000000;
|
||||
my $min_mtask;
|
||||
my $max_p2e = -1000000;
|
||||
my $max_mtask;
|
||||
foreach my $mtask (sort keys %Mtasks) {
|
||||
if ($Mtasks{$mtask}{elapsed} > 0) {
|
||||
if ($Mtasks{$mtask}{predict} == 0) {
|
||||
$Mtasks{$mtask}{predict} = 1; # don't log(0) below
|
||||
}
|
||||
my $p2e_ratio = log( $Mtasks{$mtask}{predict} / $Mtasks{$mtask}{elapsed} );
|
||||
#print "log(p2e $mtask) = $p2e_ratio (predict $Mtasks{$mtask}{predict}, elapsed $Mtasks{$mtask}{elapsed})\n";
|
||||
push @p2e_ratios, $p2e_ratio;
|
||||
p2e_ratios = []
|
||||
min_p2e = 1000000
|
||||
min_mtask = None
|
||||
max_p2e = -1000000
|
||||
max_mtask = None
|
||||
|
||||
if ($p2e_ratio > $max_p2e) {
|
||||
$max_p2e = $p2e_ratio;
|
||||
$max_mtask = $mtask;
|
||||
}
|
||||
if ($p2e_ratio < $min_p2e) {
|
||||
$min_p2e = $p2e_ratio;
|
||||
$min_mtask = $mtask;
|
||||
}
|
||||
}
|
||||
}
|
||||
for mtask in sorted(Mtasks.keys()):
|
||||
if Mtasks[mtask]['elapsed'] > 0:
|
||||
if Mtasks[mtask]['predict'] == 0:
|
||||
Mtasks[mtask]['predict'] = 1 # don't log(0) below
|
||||
p2e_ratio = math.log(Mtasks[mtask]['predict'] /
|
||||
Mtasks[mtask]['elapsed'])
|
||||
p2e_ratios.append(p2e_ratio)
|
||||
|
||||
print "\nStatistics:\n";
|
||||
printf " min log(p2e) = %0.3f", $min_p2e;
|
||||
print " from mtask $min_mtask (predict $Mtasks{$min_mtask}{predict},";
|
||||
print " elapsed $Mtasks{$min_mtask}{elapsed})\n";
|
||||
printf " max log(p2e) = %0.3f", $max_p2e;
|
||||
print " from mtask $max_mtask (predict $Mtasks{$max_mtask}{predict},";
|
||||
print " elapsed $Mtasks{$max_mtask}{elapsed})\n";
|
||||
if p2e_ratio > max_p2e:
|
||||
max_p2e = p2e_ratio
|
||||
max_mtask = mtask
|
||||
if p2e_ratio < min_p2e:
|
||||
min_p2e = p2e_ratio
|
||||
min_mtask = mtask
|
||||
|
||||
my $stddev = stddev(\@p2e_ratios);
|
||||
my $mean = mean(\@p2e_ratios);
|
||||
printf " mean = %0.3f\n", $mean;
|
||||
printf " stddev = %0.3f\n", $stddev;
|
||||
printf " e ^ stddev = %0.3f\n", exp($stddev);
|
||||
print("\nStatistics:")
|
||||
print(" min log(p2e) = %0.3f" % min_p2e, end="")
|
||||
print(" from mtask %d (predict %d," %
|
||||
(min_mtask, Mtasks[min_mtask]['predict']),
|
||||
end="")
|
||||
print(" elapsed %d)" % Mtasks[min_mtask]['elapsed'])
|
||||
print(" max log(p2e) = %0.3f" % max_p2e, end="")
|
||||
print(" from mtask %d (predict %d," %
|
||||
(max_mtask, Mtasks[max_mtask]['predict']),
|
||||
end="")
|
||||
print(" elapsed %d)" % Mtasks[max_mtask]['elapsed'])
|
||||
|
||||
report_cpus();
|
||||
stddev = statistics.pstdev(p2e_ratios)
|
||||
mean = statistics.mean(p2e_ratios)
|
||||
print(" mean = %0.3f" % mean)
|
||||
print(" stddev = %0.3f" % stddev)
|
||||
print(" e ^ stddev = %0.3f" % math.exp(stddev))
|
||||
|
||||
if ($nthreads > $ncpus) {
|
||||
print "\n";
|
||||
print "%Warning: There were fewer CPUs ($ncpus) then threads ($nthreads).\n";
|
||||
print " : See docs on use of numactl.\n";
|
||||
} else {
|
||||
if ($Global{cpu_socket_cores_warning}) {
|
||||
print "\n";
|
||||
print "%Warning: Multiple threads scheduled on same hyperthreaded core.\n";
|
||||
print " : See docs on use of numactl.\n";
|
||||
}
|
||||
if ($Global{cpu_sockets_warning}) {
|
||||
print "\n";
|
||||
print "%Warning: Threads scheduled on multiple sockets.\n";
|
||||
print " : See docs on use of numactl.\n";
|
||||
}
|
||||
}
|
||||
print "\n";
|
||||
}
|
||||
report_cpus()
|
||||
|
||||
sub report_cpus {
|
||||
print "\nCPUs:\n";
|
||||
# Test - show all cores
|
||||
# for (my $i=0; $i<73; ++$i) { $Global{cpus}{$i} ||= {cpu_time => 0}; }
|
||||
if nthreads > ncpus:
|
||||
print()
|
||||
print("%%Warning: There were fewer CPUs (%d) then threads (%d)." %
|
||||
(ncpus, nthreads))
|
||||
print(" : See docs on use of numactl.")
|
||||
else:
|
||||
if 'cpu_socket_cores_warning' in Global:
|
||||
print()
|
||||
print(
|
||||
"%Warning: Multiple threads scheduled on same hyperthreaded core."
|
||||
)
|
||||
print(" : See docs on use of numactl.")
|
||||
if 'cpu_sockets_warning' in Global:
|
||||
print()
|
||||
print("%Warning: Threads scheduled on multiple sockets.")
|
||||
print(" : See docs on use of numactl.")
|
||||
print()
|
||||
|
||||
$Global{cpu_sockets} ||= {};
|
||||
$Global{cpu_socket_cores} ||= {};
|
||||
|
||||
foreach my $cpu (sort {$a <=> $b} keys %{$Global{cpus}}) {
|
||||
printf " cpu %d: ", $cpu;
|
||||
printf "cpu_time=%d", $Global{cpus}{$cpu}{cpu_time};
|
||||
def report_cpus():
|
||||
print("\nCPUs:")
|
||||
|
||||
my $socket = $Global{cpuinfo}{$cpu}{physical_id};
|
||||
$Global{cpu_sockets}{$socket}++ if defined $socket;
|
||||
printf " socket=%d", $socket if defined $socket;
|
||||
Global['cpu_sockets'] = collections.defaultdict(lambda: 0)
|
||||
Global['cpu_socket_cores'] = collections.defaultdict(lambda: 0)
|
||||
|
||||
my $core = $Global{cpuinfo}{$cpu}{core_id};
|
||||
$Global{cpu_socket_cores}{$socket."__".$core}++ if defined $socket && defined $core;
|
||||
printf " core=%d", $core if defined $core;
|
||||
for cpu in sorted(Global['cpus'].keys()):
|
||||
print(" cpu %d: " % cpu, end='')
|
||||
print("cpu_time=%d" % Global['cpus'][cpu]['cpu_time'], end='')
|
||||
|
||||
my $model = $Global{cpuinfo}{$cpu}{model_name};
|
||||
printf " %s", $model if defined $model;
|
||||
print "\n";
|
||||
}
|
||||
socket = None
|
||||
if cpu in Global['cpuinfo']:
|
||||
socket = int(Global['cpuinfo'][cpu]['physical_id'])
|
||||
Global['cpu_sockets'][socket] += 1
|
||||
print(" socket=%d" % socket, end='')
|
||||
|
||||
$Global{cpu_sockets_warning} = 1
|
||||
if (scalar keys %{$Global{cpu_sockets}} > 1);
|
||||
foreach my $scn (values %{$Global{cpu_socket_cores}}) {
|
||||
$Global{cpu_socket_cores_warning} = 1 if $scn > 1;
|
||||
}
|
||||
}
|
||||
core = int(Global['cpuinfo'][cpu]['core_id'])
|
||||
Global['cpu_socket_cores'][str(socket) + "__" + str(core)] += 1
|
||||
print(" core=%d" % core, end='')
|
||||
|
||||
sub report_graph {
|
||||
my $time_per = $Opt_Time_Per_Char;
|
||||
if ($time_per == 0) {
|
||||
$time_per = ($Global{last_end} / 40); # Start with 40 columns
|
||||
while ($time_per > 10) {
|
||||
my ($graph, $conflicts) = _make_graph($time_per);
|
||||
last if !$conflicts;
|
||||
$time_per = int($time_per/2);
|
||||
}
|
||||
model = Global['cpuinfo'][cpu]['model_name']
|
||||
if model:
|
||||
print(" %s" % model, end='')
|
||||
print()
|
||||
|
||||
if len(Global['cpu_sockets']) > 1:
|
||||
Global['cpu_sockets_warning'] = True
|
||||
for scn in Global['cpu_socket_cores'].values():
|
||||
if scn > 1:
|
||||
Global['cpu_socket_cores_warning'] = True
|
||||
|
||||
|
||||
def report_graph():
|
||||
time_per = Args.scale
|
||||
if time_per == 0:
|
||||
time_per = Global['last_end'] / 40 # Start with 40 columns
|
||||
while time_per > 10:
|
||||
(graph, conflicts) = _make_graph(time_per)
|
||||
if not conflicts:
|
||||
break
|
||||
time_per = int(time_per / 2)
|
||||
# One more step so we can fit more labels
|
||||
$time_per = int($time_per/2);
|
||||
$time_per ||= 1;
|
||||
}
|
||||
time_per = int(time_per / 2)
|
||||
if time_per <= 0:
|
||||
time_per = 1
|
||||
|
||||
my ($graph, $conflicts) = _make_graph($time_per);
|
||||
(graph, conflicts) = _make_graph(time_per)
|
||||
|
||||
print "\nThread gantt graph:\n";
|
||||
print " Legend: One character width = $time_per rdtsc ticks\n";
|
||||
print " Legend: '&' = multiple mtasks in this period (character width)\n";
|
||||
print("\nThread gantt graph:")
|
||||
print(" Legend: One character width = %s rdtsc ticks" % time_per)
|
||||
print(" Legend: '&' = multiple mtasks in this period (character width)")
|
||||
|
||||
my $scale = " <-".$Global{last_end}." rdtsc total";
|
||||
for (my $col = length($scale); # -2 for '->' below
|
||||
$col < ($Global{last_end}/$time_per); ++$col) {
|
||||
$scale .= "-";
|
||||
}
|
||||
print " $scale->\n";
|
||||
scale = " <-%d rdtsc total" % Global['last_end']
|
||||
for col in range(len(scale), int(0.99 + (Global['last_end'] / time_per))): # pylint: disable=unused-variable
|
||||
scale += "-"
|
||||
print(" " + scale + "->")
|
||||
|
||||
foreach my $thread (sort keys %{$graph}) {
|
||||
print " t: ";
|
||||
_print_graph_line($graph->{$thread}, '');
|
||||
}
|
||||
}
|
||||
for thread in sorted(graph.keys()):
|
||||
print(" t: ", end="")
|
||||
_print_graph_line(graph[thread], '')
|
||||
|
||||
sub _make_graph {
|
||||
my $time_per = shift;
|
||||
|
||||
my $graph = {}; # {thread}{column}{char=>'x' or chars=>#}
|
||||
my $conflicts = 0;
|
||||
foreach my $thread (keys %Threads) {
|
||||
def _make_graph(time_per):
|
||||
|
||||
# [thread][column] = char or #
|
||||
graph = collections.defaultdict(
|
||||
lambda: collections.defaultdict(lambda: ''))
|
||||
|
||||
conflicts = 0
|
||||
for thread in Threads:
|
||||
# Make potentially multiple characters per column
|
||||
foreach my $start (sort {$a <=> $b} keys %{$Threads{$thread}}) {
|
||||
my $end = $Threads{$thread}{$start}{end};
|
||||
my $mtask = $Threads{$thread}{$start}{mtask};
|
||||
my $cpu = $Threads{$thread}{$start}{cpu};
|
||||
multi_at_col = collections.defaultdict(lambda: '')
|
||||
for start in sorted(Threads[thread].keys()):
|
||||
end = Threads[thread][start]['end']
|
||||
# mtask = Threads[thread][start]['mtask']
|
||||
cpu = Threads[thread][start]['cpu']
|
||||
|
||||
my $startcol = _time_col($time_per, $start);
|
||||
my $endcol = _time_col($time_per, $end);
|
||||
startcol = _time_col(time_per, start)
|
||||
endcol = _time_col(time_per, end)
|
||||
|
||||
label = "["
|
||||
label += str(cpu) # Maybe make optional in future
|
||||
width = endcol - startcol + 1
|
||||
while len(label) < (width - 1): # -1 for ']'
|
||||
label += "-"
|
||||
label += "]"
|
||||
multi_at_col[startcol] += label
|
||||
|
||||
my $label = "[";
|
||||
$label .= "$cpu"; # Maybe make optional in future
|
||||
my $width = $endcol - $startcol + 1;
|
||||
while (length($label) < ($width-1)) { # -1 for ']'
|
||||
$label .= "-";
|
||||
}
|
||||
$label .= "]";
|
||||
$graph->{$thread}[$startcol]{char} .= $label;
|
||||
}
|
||||
if ($Debug) {
|
||||
print "# Multicol: "; _print_graph_line($graph->{$thread}, '|');
|
||||
}
|
||||
# Expand line to one char per column
|
||||
for (my $col = 0; $col <= $#{$graph->{$thread}}; ++$col) {
|
||||
if (my $chars = $graph->{$thread}[$col]{char}) {
|
||||
my $ok = 1;
|
||||
for (my $coladd = 1; $coladd<length($chars); ++$coladd) {
|
||||
if ($graph->{$thread}[$col + $coladd]{char}) {
|
||||
$ok = 0; last;
|
||||
}
|
||||
}
|
||||
if (!$ok) {
|
||||
if ($chars =~ /\[.*\[/) { # Two begins or more
|
||||
$conflicts++;
|
||||
$graph->{$thread}[$col]{char} = "&";
|
||||
} else {
|
||||
$graph->{$thread}[$col]{char} = "[";
|
||||
}
|
||||
for (my $coladd = 1; $coladd<length($chars); ++$coladd) {
|
||||
if ($graph->{$thread}[$col + $coladd]{char}) {
|
||||
last;
|
||||
} else {
|
||||
$graph->{$thread}[$col + $coladd]{char} = 'x';
|
||||
}
|
||||
}
|
||||
} else {
|
||||
my $coladd = 0;
|
||||
foreach my $char (split //, $chars) {
|
||||
$graph->{$thread}[$col+$coladd]{char} = $char;
|
||||
++$coladd;
|
||||
}
|
||||
}
|
||||
}
|
||||
for col in multi_at_col:
|
||||
chars = multi_at_col[col]
|
||||
ok = True
|
||||
for coladd in range(0, len(chars)):
|
||||
if col + coladd in graph[thread]:
|
||||
ok = False
|
||||
break
|
||||
if not ok:
|
||||
if re.search(r'\[.*\[', chars): # Two begins or more
|
||||
conflicts += 1
|
||||
graph[thread][col] = "&"
|
||||
else:
|
||||
graph[thread][col] = "["
|
||||
for coladd in range(1, len(chars)):
|
||||
if col + coladd in graph[thread]:
|
||||
break
|
||||
graph[thread][col + coladd] = 'x'
|
||||
else:
|
||||
coladd = 0
|
||||
for char in chars:
|
||||
graph[thread][col + coladd] = char
|
||||
coladd += 1
|
||||
|
||||
if Args.debug:
|
||||
print("# Conflicts %d" % conflicts)
|
||||
return (graph, conflicts)
|
||||
|
||||
|
||||
def _print_graph_line(graph_thread, sep):
|
||||
at = 0
|
||||
for col in sorted(graph_thread.keys()):
|
||||
while at < col:
|
||||
print(' ', end="")
|
||||
at += 1
|
||||
c = graph_thread[col]
|
||||
print(c + sep, end="")
|
||||
at += len(c)
|
||||
print()
|
||||
|
||||
|
||||
def _time_col(time_per, time):
|
||||
return int(time / time_per)
|
||||
|
||||
|
||||
######################################################################
|
||||
|
||||
|
||||
def write_vcd(filename):
|
||||
print("Writing %s" % filename)
|
||||
with open(filename, "w") as fh:
|
||||
vcd = {
|
||||
'values':
|
||||
collections.defaultdict(lambda: {}), # {<time>}{<code>} = value
|
||||
'sigs': {
|
||||
'threads': {},
|
||||
'cpus': {},
|
||||
'mtasks': {},
|
||||
'Stats': {}
|
||||
} # {<module>}{<sig}} = code
|
||||
}
|
||||
if ($Debug) {
|
||||
print "# Singlcol: "; _print_graph_line($graph->{$thread}, '|');
|
||||
}
|
||||
}
|
||||
print "# Conflicts $conflicts\n" if $Debug;
|
||||
return ($graph, $conflicts);
|
||||
}
|
||||
code = 0
|
||||
|
||||
sub _print_graph_line {
|
||||
my $graph_thread = shift;
|
||||
my $sep = shift;
|
||||
for (my $col = 0; $col <= $#{$graph_thread}; ++$col) {
|
||||
my $c = $graph_thread->[$col]{char}; $c=' ' if !defined $c;
|
||||
print $c, $sep;
|
||||
}
|
||||
print "\n";
|
||||
}
|
||||
parallelism = collections.defaultdict(lambda: 0)
|
||||
for thread in sorted(Threads.keys()):
|
||||
sig = "thread%d_mtask" % thread
|
||||
if sig not in vcd['sigs']['threads']:
|
||||
vcd['sigs']['threads'][sig] = code
|
||||
code += 1
|
||||
mcode = vcd['sigs']['threads'][sig]
|
||||
|
||||
sub _time_col {
|
||||
my $time_per = shift;
|
||||
my $time = shift;
|
||||
return int($time/$time_per);
|
||||
}
|
||||
for start in sorted(Threads[thread]):
|
||||
end = Threads[thread][start]['end']
|
||||
mtask = Threads[thread][start]['mtask']
|
||||
cpu = Threads[thread][start]['cpu']
|
||||
vcd['values'][start][mcode] = mtask
|
||||
vcd['values'][end][mcode] = None
|
||||
parallelism[start] += 1
|
||||
parallelism[end] -= 1
|
||||
|
||||
#######################################################################
|
||||
sig = "cpu%d_thread" % cpu
|
||||
if sig not in vcd['sigs']['cpus']:
|
||||
vcd['sigs']['cpus'][sig] = code
|
||||
code += 1
|
||||
ccode = vcd['sigs']['cpus'][sig]
|
||||
vcd['values'][start][ccode] = thread
|
||||
vcd['values'][end][ccode] = None
|
||||
|
||||
sub write_vcd {
|
||||
my $filename = shift;
|
||||
print "Writing $filename\n";
|
||||
my $fh = IO::File->new(">$filename") or die "%Error: $! $filename,";
|
||||
my $vcd = {values => {}, # {<time>}{<code>} = value
|
||||
sigs => {}, # {<module>}{<sig}} = code
|
||||
code => 0,
|
||||
};
|
||||
sig = "mtask%d_cpu" % mtask
|
||||
if sig not in vcd['sigs']['mtasks']:
|
||||
vcd['sigs']['mtasks'][sig] = code
|
||||
code += 1
|
||||
ccode = vcd['sigs']['mtasks'][sig]
|
||||
vcd['values'][start][ccode] = cpu
|
||||
vcd['values'][end][ccode] = None
|
||||
|
||||
my %parallelism;
|
||||
foreach my $thread (keys %Threads) {
|
||||
my $mcode = ($vcd->{sigs}{threads}{"thread${thread}_mtask"} ||= $vcd->{code}++);
|
||||
foreach my $start (sort {$a <=> $b} keys %{$Threads{$thread}}) {
|
||||
my $end = $Threads{$thread}{$start}{end};
|
||||
my $mtask = $Threads{$thread}{$start}{mtask};
|
||||
my $cpu = $Threads{$thread}{$start}{cpu};
|
||||
$vcd->{values}{$start}{$mcode} = $mtask;
|
||||
$vcd->{values}{$end}{$mcode} = undef;
|
||||
$parallelism{$start}++;
|
||||
$parallelism{$end}--;
|
||||
# Parallelism graph
|
||||
vcd['sigs']['Stats']["parallelism"] = code
|
||||
pcode = code
|
||||
code += 1
|
||||
|
||||
my $ccode = $vcd->{sigs}{cpus}{"cpu${cpu}_thread"} ||= $vcd->{code}++;
|
||||
$vcd->{values}{$start}{$ccode} = $thread;
|
||||
$vcd->{values}{$end}{$ccode} = undef;
|
||||
value = 0
|
||||
for time in sorted(parallelism.keys()):
|
||||
value += parallelism[time]
|
||||
vcd['values'][time][pcode] = value
|
||||
|
||||
my $mcode = $vcd->{sigs}{mtasks}{"mtask${mtask}_cpu"} ||= $vcd->{code}++;
|
||||
$vcd->{values}{$start}{$mcode} = $cpu;
|
||||
$vcd->{values}{$end}{$mcode} = undef;
|
||||
}
|
||||
}
|
||||
{
|
||||
my $pcode = ($vcd->{sigs}{Stats}{"parallelism"} ||= $vcd->{code}++);
|
||||
my $value = 0;
|
||||
foreach my $time (sort {$a<=>$b} keys %parallelism) {
|
||||
$value += $parallelism{$time};
|
||||
$vcd->{values}{$time}{$pcode} = $value;
|
||||
}
|
||||
}
|
||||
fh.write("$version Generated by verilator_gantt $end\n")
|
||||
fh.write("$timescale 1ns $end\n")
|
||||
fh.write("\n")
|
||||
|
||||
$fh->print('$version Generated by verilator_gantt $end'."\n");
|
||||
$fh->print('$timescale 1ns $end'."\n");
|
||||
$fh->print("\n");
|
||||
all_codes = {}
|
||||
fh.write(" $scope module gantt $end\n")
|
||||
for module in sorted(vcd['sigs'].keys()):
|
||||
fh.write(" $scope module %s $end\n" % module)
|
||||
for sig in sorted(vcd['sigs'][module].keys()):
|
||||
code = vcd['sigs'][module][sig]
|
||||
fh.write(" $var wire 32 v%x %s [31:0] $end\n" % (code, sig))
|
||||
all_codes[code] = 1
|
||||
fh.write(" $upscope $end\n")
|
||||
fh.write(" $upscope $end\n")
|
||||
fh.write("$enddefinitions $end\n")
|
||||
fh.write("\n")
|
||||
|
||||
my %all_codes;
|
||||
$fh->print(' $scope module gantt $end'."\n");
|
||||
foreach my $module (sort keys %{$vcd->{sigs}}) {
|
||||
$fh->printf(' $scope module %s $end'."\n", $module);
|
||||
foreach my $sig (sort keys %{$vcd->{sigs}{$module}}) {
|
||||
my $code = $vcd->{sigs}{$module}{$sig};
|
||||
$fh->printf(' $var wire 32 v%x %s [31:0] $end'."\n",
|
||||
$code, $sig);
|
||||
$all_codes{$code} = 1;
|
||||
}
|
||||
$fh->print(' $upscope $end'."\n");
|
||||
}
|
||||
$fh->print(' $upscope $end'."\n");
|
||||
$fh->print('$enddefinitions $end'."\n");
|
||||
$fh->print("\n");
|
||||
first = True
|
||||
for time in sorted(vcd['values']):
|
||||
if first:
|
||||
first = False
|
||||
# Start with Z for any signals without time zero data
|
||||
for code in sorted(all_codes.keys()):
|
||||
if code not in vcd['values'][time]:
|
||||
vcd['values'][time][code] = None
|
||||
fh.write("#%d\n" % time)
|
||||
for code in sorted(vcd['values'][time].keys()):
|
||||
value = vcd['values'][time][code]
|
||||
if value is None:
|
||||
fh.write("bz v%x\n" % code)
|
||||
else:
|
||||
fh.write("b%s v%x\n" % (format(value, 'b'), code))
|
||||
|
||||
my $first = 1;
|
||||
foreach my $time (sort {$a <=> $b} keys %{$vcd->{values}}) {
|
||||
if ($first) {
|
||||
$first = 0;
|
||||
# Start with Z for any signals without time zero data
|
||||
foreach my $code (keys %all_codes) {
|
||||
if (!defined $vcd->{values}{$time}{$code}) {
|
||||
$vcd->{values}{$time}{$code} = undef;
|
||||
}
|
||||
}
|
||||
}
|
||||
$fh->printf("#%d\n", $time);
|
||||
foreach my $code (sort keys %{$vcd->{values}{$time}}) {
|
||||
my $value = $vcd->{values}{$time}{$code};
|
||||
if (defined $value) {
|
||||
$fh->printf("b%b v%x\n", $value, $code);
|
||||
} else {
|
||||
$fh->printf("bz v%x\n", $code);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#######################################################################
|
||||
# Similar to Statistics::Basic functions, but avoid a package dependency
|
||||
######################################################################
|
||||
|
||||
sub max {
|
||||
my $n = $_[0]; shift;
|
||||
while (defined $_[0]) {
|
||||
$n = $_[0] if !defined $n || $_[0] > $n;
|
||||
shift;
|
||||
}
|
||||
return $n;
|
||||
}
|
||||
|
||||
sub mean {
|
||||
my $arrayref = shift;
|
||||
my $n = 0;
|
||||
my $sum = 0;
|
||||
foreach my $v (@$arrayref) {
|
||||
$sum += $v;
|
||||
$n++;
|
||||
}
|
||||
return undef if !$n;
|
||||
return $sum/$n;
|
||||
}
|
||||
|
||||
sub stddev {
|
||||
my $arrayref = shift;
|
||||
my $n = 0;
|
||||
my $sum = 0;
|
||||
my $sumsq = 0;
|
||||
foreach my $v (@$arrayref) {
|
||||
$sum += $v;
|
||||
$sumsq += $v**2;
|
||||
$n++;
|
||||
}
|
||||
return undef if !$n;
|
||||
return sqrt(($sumsq/$n) - ($sum/$n)**2);
|
||||
}
|
||||
|
||||
#######################################################################
|
||||
__END__
|
||||
|
||||
=pod
|
||||
|
||||
=head1 NAME
|
||||
|
||||
verilator_gantt - Create Gantt chart of multi-threaded execution
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
Verilator_gantt creates a visual representation to help analyze Verilator
|
||||
multithreaded simulation performance, by showing when each macro-task
|
||||
starts and ends, and showing when each thread is busy or idle.
|
||||
parser = argparse.ArgumentParser(
|
||||
allow_abbrev=False,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
description="""Create Gantt chart of multi-threaded execution""",
|
||||
epilog=
|
||||
"""Verilator_gantt creates a visual representation to help analyze Verilator
|
||||
#xmultithreaded simulation performance, by showing when each macro-task
|
||||
#xstarts and ends, and showing when each thread is busy or idle.
|
||||
|
||||
For documentation see
|
||||
L<https://verilator.org/guide/latest/exe_verilator_gantt.html>.
|
||||
|
||||
=head1 ARGUMENT SUMMARY
|
||||
|
||||
<filename> Filename to read data from, default "profile_threads.dat".
|
||||
--help Displays this message and program version and exits.
|
||||
--scale I<n> Number of characters per time step.
|
||||
--no-vcd Do not create a VCD file.
|
||||
--vcd <filename> Set output filename for vcd dump, default "verilator_gantt.vcd."
|
||||
|
||||
=head1 DISTRIBUTION
|
||||
|
||||
The latest version is available from L<https://verilator.org>.
|
||||
https://verilator.org/guide/latest/exe_verilator_gantt.html
|
||||
|
||||
Copyright 2018-2021 by Wilson Snyder. This program is free software; you
|
||||
can redistribute it and/or modify it under the terms of either the GNU
|
||||
Lesser General Public License Version 3 or the Perl Artistic License
|
||||
Version 2.0.
|
||||
|
||||
SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0""")
|
||||
|
||||
=head1 SEE ALSO
|
||||
parser.add_argument('--debug', action='store_true', help='enable debug')
|
||||
parser.add_argument('--scale',
|
||||
help='number of time units per character in graph',
|
||||
type=int,
|
||||
default=0)
|
||||
parser.add_argument('--no-vcd',
|
||||
help='disable creating vcd',
|
||||
action='store_true')
|
||||
parser.add_argument('--vcd',
|
||||
help='filename for vcd outpue',
|
||||
default='profile_threads.vcd')
|
||||
parser.add_argument('filename',
|
||||
help='input profile_threads.dat filename to process',
|
||||
default='profile_threads.dat')
|
||||
|
||||
C<verilator>
|
||||
Args = parser.parse_args()
|
||||
|
||||
and L<https://verilator.org/guide/latest/exe_verilator_gantt.html> for
|
||||
detailed documentation.
|
||||
|
||||
=cut
|
||||
process(Args.filename)
|
||||
if not Args.no_vcd:
|
||||
write_vcd(Args.vcd)
|
||||
|
||||
######################################################################
|
||||
### Local Variables:
|
||||
### compile-command: "$V4/bin/verilator_gantt $V4/test_regress/obj_vltmt/t_gantt/vlt_sim.log"
|
||||
### End:
|
||||
# Local Variables:
|
||||
# compile-command: "./verilator_gantt ../test_regress/t/t_gantt_io.dat"
|
||||
# End:
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# pylint: disable=C0103,C0114,C0116,E0402,W0622
|
||||
# pylint: disable=C0103,C0114,C0116,C0301,E0402,W0622
|
||||
#
|
||||
# Configuration file for Verilator's Sphinx documentation builder.
|
||||
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
|
|
|
@ -6,11 +6,11 @@ Argument settings:
|
|||
--threads 2
|
||||
|
||||
Thread gantt graph:
|
||||
Legend: One character width = 200 rdtsc ticks
|
||||
Legend: One character width = 100 rdtsc ticks
|
||||
Legend: '&' = multiple mtasks in this period (character width)
|
||||
<-16065 rdtsc total------------------------------------------------------------>
|
||||
t: [1] [1] [1] [1]
|
||||
t: [xx[x[16--] [16] [16] [16] [[x[[16]
|
||||
<-16065 rdtsc total-------------------------------------------------------------------------------------------------------------------------------------------->
|
||||
t: [1] [1] [1] [1]
|
||||
t: [16-] [16][16-------] [16--] [16] [16-] [1[] [16[xxx
|
||||
|
||||
Analysis:
|
||||
Total threads = 2
|
||||
|
@ -26,7 +26,7 @@ Analysis:
|
|||
|
||||
Statistics:
|
||||
min log(p2e) = -3.332 from mtask 5 (predict 30, elapsed 840)
|
||||
max log(p2e) = -1.764 from mtask 11 (predict 30, elapsed 175)
|
||||
max log(p2e) = -1.764 from mtask 7 (predict 30, elapsed 175)
|
||||
mean = -2.365
|
||||
stddev = 0.562
|
||||
e ^ stddev = 1.754
|
||||
|
|
Loading…
Reference in New Issue