mirror of
https://github.com/Zenithsiz/ftmemsim-valgrind.git
synced 2026-02-03 18:13:01 +00:00
This commit thoroughly overhauls DHAT, moving it out of the
"experimental" ghetto. It makes moderate changes to DHAT itself,
including dumping profiling data to a JSON format output file. It also
implements a new data viewer (as a web app, in dhat/dh_view.html).
The main benefits over the old DHAT are as follows.
- The separation of data collection and presentation means you can run a
program once under DHAT and then sort the data in various ways. Also,
full data is in the output file, and the viewer chooses what to omit.
- The data can be sorted in more ways than previously. Some of these
sorts involve useful filters such as "short-lived" and "zero reads or
zero writes".
- The tree structure view avoids the need to choose stack trace depth.
This avoids both the problem of not enough depth (when records that
should be distinct are combined, and may not contain enough
information to be actionable) and the problem of too much depth (when
records that should be combined are separated, making them seem less
important than they really are).
- Byte and block measures are shown with a percentage relative to the
global count, which helps gauge relative significance of different
parts of the profile.
- Byte and blocks measures are also shown with an allocation rate
(bytes and blocks per million instructions), which enables comparisons
across multiple profiles, even if those profiles represent different
workloads.
- Both global and per-node measurements are taken at the global heap
peak ("At t-gmax"), which gives Massif-like insight into the point of
peak memory use.
- The final/liftimes stats are a bit more useful than the old deaths
stats. (E.g. the old deaths stats didn't take into account lifetimes
of unfreed blocks.)
- The handling of realloc() has changed. The sequence `p = malloc(100);
realloc(p, 200);` now increases the total block count by 2 and the
total byte count by 300. Previously it increased them by 1 and 200.
The new handling is a more operational view that better reflects the
effect of allocations on performance. It makes a significant
difference in the results, giving paths involving reallocation (e.g.
repeated pushing to a growing vector) more prominence.
Other things of note:
- There is now testing, both regression tests that run within the
standard test suite, and viewer-specific tests that cannot run within
the standard test suite. The latter are run by loading
dh_view.html?test=1 in a web browser.
- The commit puts all tool lists in Makefiles (and similar files) in the
following consistent order: memcheck, cachegrind, callgrind, helgrind,
drd, massif, dhat, lackey, none; exp-sgcheck, exp-bbv.
- A lot of fields in dh_main.c have been given more descriptive names.
Those names now match those used in dh_view.js.
365 lines
10 KiB
Perl
Executable File
365 lines
10 KiB
Perl
Executable File
#!/usr/bin/env perl
|
|
|
|
#-------------------------------------------------------------------
|
|
# Check header files and #include directives
|
|
#
|
|
# (1) include/*.h must not include pub_core_...h
|
|
# (2) coregrind/pub_core_xyzzy.h may include pub_tool_xyzzy.h
|
|
# other coregrind headers may not include pub_tool_xyzzy.h
|
|
# (3) coregrind/ *.c must not include pub_tool_xyzzy.h
|
|
# (4) tool *.[ch] files must not include pub_core_...h
|
|
# (5) include pub_core/tool_clreq.h instead of valgrind.h except in tools'
|
|
# export headers
|
|
# (6) coregrind/ *.[ch] must not use tl_assert
|
|
# (7) include/*.h and tool *.[ch] must not use vg_assert
|
|
# (8) coregrind/ *.[ch] must not use VG_(tool_panic)
|
|
# (9) include/*.h and tool *.[ch] must not use VG_(core_panic)
|
|
# (10) *.S must unconditionally instantiate MARK_STACK_NO_EXEC
|
|
#
|
|
# There can be false positives as we don't really parse the source files.
|
|
# Instead we only match regular expressions.
|
|
#-------------------------------------------------------------------
|
|
|
|
use strict;
|
|
use warnings;
|
|
use File::Basename;
|
|
use Getopt::Long;
|
|
|
|
my $this_script = basename($0);
|
|
|
|
# The list of top-level directories is divided into three sets:
|
|
#
|
|
# (1) coregrind directories
|
|
# (2) tool directories
|
|
# (3) directories to ignore
|
|
#
|
|
# If a directory is found that does not belong to any of those sets, the
|
|
# script will terminate unsuccessfully.
|
|
|
|
my %coregrind_dirs = (
|
|
"include" => 1,
|
|
"coregrind" => 1,
|
|
);
|
|
|
|
my %tool_dirs = (
|
|
"memcheck" => 1,
|
|
"cachegrind" => 1,
|
|
"callgrind" => 1,
|
|
"helgrind", => 1,
|
|
"drd" => 1,
|
|
"massif" => 1,
|
|
"dhat" => 1,
|
|
"lackey" => 1,
|
|
"none" => 1,
|
|
"exp-bbv" => 1,
|
|
"exp-sgcheck" => 1
|
|
"shared" => 1,
|
|
);
|
|
|
|
my %dirs_to_ignore = (
|
|
".deps" => 1,
|
|
".git" => 1,
|
|
".in_place" => 1,
|
|
"Inst" => 1, # the nightly scripts creates this
|
|
"VEX" => 1,
|
|
"docs" => 1,
|
|
"auxprogs" => 1,
|
|
"autom4te.cache" => 1,
|
|
"nightly" => 1,
|
|
"perf" => 1,
|
|
"tests" => 1,
|
|
"gdbserver_tests" => 1,
|
|
"mpi" => 1,
|
|
"solaris" => 1
|
|
);
|
|
|
|
my %tool_export_header = (
|
|
"drd/drd.h" => 1,
|
|
"helgrind/helgrind.h" => 1,
|
|
"memcheck/memcheck.h" => 1,
|
|
"callgrind/callgrind.h" => 1
|
|
);
|
|
|
|
my $usage=<<EOF;
|
|
USAGE
|
|
|
|
$this_script
|
|
|
|
[--debug] Debugging output
|
|
|
|
dir ... Directories to process
|
|
EOF
|
|
|
|
my $debug = 0;
|
|
my $num_errors = 0;
|
|
|
|
&main;
|
|
|
|
sub main {
|
|
GetOptions( "debug" => \$debug ) || die $usage;
|
|
|
|
my $argc = $#ARGV + 1;
|
|
|
|
if ($argc < 1) {
|
|
die $usage;
|
|
}
|
|
|
|
foreach my $dir (@ARGV) {
|
|
process_dir(undef, $dir, 0);
|
|
}
|
|
|
|
my $rc = ($num_errors == 0) ? 0 : 1;
|
|
exit $rc;
|
|
}
|
|
|
|
sub process_dir {
|
|
my ($path, $dir, $depth) = @_;
|
|
my $hdir;
|
|
|
|
if ($depth == 0) {
|
|
# The root directory is always processed
|
|
} elsif ($depth == 1) {
|
|
# Toplevel directories
|
|
return if ($dirs_to_ignore{$dir});
|
|
|
|
if (! $tool_dirs{$dir} && ! $coregrind_dirs{$dir}) {
|
|
die "Unknown directory '$dir'. Please update $this_script\n";
|
|
}
|
|
} else {
|
|
# Subdirectories
|
|
return if ($dirs_to_ignore{$dir});
|
|
}
|
|
|
|
print "DIR = $dir DEPTH = $depth\n" if ($debug);
|
|
|
|
chdir($dir) || die "Cannot chdir '$dir'\n";
|
|
|
|
opendir($hdir, ".") || die "cannot open directory '.'";
|
|
|
|
while (my $file = readdir($hdir)) {
|
|
next if ($file eq ".");
|
|
next if ($file eq "..");
|
|
|
|
# Subdirectories
|
|
if (-d $file) {
|
|
my $full_path = defined $path ? "$path/$file" : $file;
|
|
process_dir($full_path, $file, $depth + 1);
|
|
next;
|
|
}
|
|
|
|
# Regular files; only interested in *.c, *.S and *.h
|
|
next if (! ($file =~ /\.[cSh]$/));
|
|
my $path_name = defined $path ? "$path/$file" : $file;
|
|
process_file($path_name);
|
|
}
|
|
close($hdir);
|
|
chdir("..") || die "Cannot chdir '..'\n";
|
|
}
|
|
|
|
#---------------------------------------------------------------------
|
|
# Return 1, if file is located in <valgrind>/include
|
|
#---------------------------------------------------------------------
|
|
sub is_coregrind_export_header {
|
|
my ($path_name) = @_;
|
|
|
|
return ($path_name =~ /^include\//) ? 1 : 0;
|
|
}
|
|
|
|
#---------------------------------------------------------------------
|
|
# Return 1, if file is located underneath <valgrind>/coregrind
|
|
#---------------------------------------------------------------------
|
|
sub is_coregrind_file {
|
|
my ($path_name) = @_;
|
|
|
|
return ($path_name =~ /^coregrind\//) ? 1 : 0;
|
|
}
|
|
|
|
#---------------------------------------------------------------------
|
|
# Return 1, if file is located underneath <valgrind>/<tool>
|
|
#---------------------------------------------------------------------
|
|
sub is_tool_file {
|
|
my ($path_name) = @_;
|
|
|
|
for my $tool (keys %tool_dirs) {
|
|
return 1 if ($path_name =~ /^$tool\//);
|
|
}
|
|
return 0
|
|
}
|
|
|
|
#---------------------------------------------------------------------
|
|
# Return array of files #include'd by file.
|
|
#---------------------------------------------------------------------
|
|
sub get_included_files {
|
|
my ($path_name) = @_;
|
|
my @includes = ();
|
|
my $file = basename($path_name);
|
|
|
|
open(FILE, "<$file") || die "Cannot open file '$file'";
|
|
|
|
while (my $line = <FILE>) {
|
|
if ($line =~ /^\s*#\s*include "([^"]*)"/) {
|
|
push @includes, $1;
|
|
}
|
|
if ($line =~ /^\s*#\s*include <([^>]*)>/) {
|
|
push @includes, $1;
|
|
}
|
|
}
|
|
close FILE;
|
|
return @includes;
|
|
}
|
|
|
|
#---------------------------------------------------------------------
|
|
# Check a file from <valgrind>/include
|
|
#---------------------------------------------------------------------
|
|
sub check_coregrind_export_header {
|
|
my ($path_name) = @_;
|
|
my $file = basename($path_name);
|
|
|
|
foreach my $inc (get_included_files($path_name)) {
|
|
$inc = basename($inc);
|
|
# Must not include pub_core_....
|
|
if ($inc =~ /pub_core_/) {
|
|
error("File $path_name must not include $inc\n");
|
|
}
|
|
# Only pub_tool_clreq.h may include valgrind.h
|
|
if (($inc eq "valgrind.h") && ($path_name ne "include/pub_tool_clreq.h")) {
|
|
error("File $path_name should include pub_tool_clreq.h instead of $inc\n");
|
|
}
|
|
}
|
|
# Must not use vg_assert
|
|
my $assert = `grep vg_assert $file`;
|
|
if ($assert ne "") {
|
|
error("File $path_name must not use vg_assert\n");
|
|
}
|
|
# Must not use VG_(core_panic)
|
|
my $panic = `grep 'VG_(core_panic)' $file`;
|
|
if ($panic ne "") {
|
|
error("File $path_name must not use VG_(core_panic)\n");
|
|
}
|
|
}
|
|
|
|
#---------------------------------------------------------------------
|
|
# Check a file from <valgrind>/coregrind
|
|
#---------------------------------------------------------------------
|
|
sub check_coregrind_file {
|
|
my ($path_name) = @_;
|
|
my $file = basename($path_name);
|
|
|
|
foreach my $inc (get_included_files($path_name)) {
|
|
print "\tINCLUDE $inc\n" if ($debug);
|
|
# Only pub_tool_xyzzy.h may include pub_core_xyzzy.h
|
|
if ($inc =~ /pub_tool_/) {
|
|
my $buddy = $inc;
|
|
$buddy =~ s/pub_tool/pub_core/;
|
|
if ($file ne $buddy) {
|
|
error("File $path_name must not include $inc\n");
|
|
}
|
|
}
|
|
# Must not include valgrind.h
|
|
if ($inc eq "valgrind.h") {
|
|
error("File $path_name should include pub_core_clreq.h instead of $inc\n");
|
|
}
|
|
}
|
|
# Must not use tl_assert
|
|
my $assert = `grep tl_assert $file`;
|
|
if ($assert ne "") {
|
|
error("File $path_name must not use tl_assert\n");
|
|
}
|
|
# Must not use VG_(tool_panic)
|
|
my $panic = `grep 'VG_(tool_panic)' $file`;
|
|
if ($panic ne "") {
|
|
chomp($panic);
|
|
# Do not complain about the definition of VG_(tool_panic)
|
|
if (($path_name eq "coregrind/m_libcassert.c") &&
|
|
($panic eq "void VG_(tool_panic) ( const HChar* str )")) {
|
|
# OK
|
|
} else {
|
|
error("File $path_name must not use VG_(tool_panic)\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
#---------------------------------------------------------------------
|
|
# Check a file from <valgrind>/<tool>
|
|
#---------------------------------------------------------------------
|
|
sub check_tool_file {
|
|
my ($path_name) = @_;
|
|
my $file = basename($path_name);
|
|
|
|
foreach my $inc (get_included_files($path_name)) {
|
|
print "\tINCLUDE $inc\n" if ($debug);
|
|
# Must not include pub_core_...
|
|
if ($inc =~ /pub_core_/) {
|
|
error("File $path_name must not include $inc\n");
|
|
}
|
|
# Must not include valgrind.h unless this is an export header
|
|
if ($inc eq "valgrind.h" && ! $tool_export_header{$path_name}) {
|
|
error("File $path_name should include pub_tool_clreq.h instead of $inc\n");
|
|
}
|
|
}
|
|
# Must not use vg_assert
|
|
my $assert = `grep vg_assert $file`;
|
|
if ($assert ne "") {
|
|
error("File $path_name must not use vg_assert\n");
|
|
}
|
|
# Must not use VG_(core_panic)
|
|
my $panic = `grep 'VG_(core_panic)' $file`;
|
|
if ($panic ne "") {
|
|
error("File $path_name must not use VG_(core_panic)\n");
|
|
}
|
|
}
|
|
|
|
#---------------------------------------------------------------------
|
|
# Check an assembler file
|
|
#---------------------------------------------------------------------
|
|
sub check_assembler_file {
|
|
my ($path_name) = @_;
|
|
my $file = basename($path_name);
|
|
my $found = 0;
|
|
|
|
open(FILE, "<$file") || die "Cannot open file '$file'";
|
|
|
|
while (my $line = <FILE>) {
|
|
if ($line =~ /^\s*MARK_STACK_NO_EXEC/) {
|
|
$found = 1;
|
|
last;
|
|
}
|
|
}
|
|
if ($found == 0) {
|
|
error("File $path_name does not instantiate MARK_STACK_NO_EXEC\n");
|
|
} else {
|
|
while (my $line = <FILE>) {
|
|
if ($line =~ /^\s*#\s*endif/) {
|
|
error("File $path_name instantiates MARK_STACK_NO_EXEC"
|
|
. " under a condition\n");
|
|
last;
|
|
}
|
|
}
|
|
}
|
|
close FILE;
|
|
}
|
|
|
|
sub process_file {
|
|
my ($path_name) = @_;
|
|
|
|
print "FILE = $path_name\n" if ($debug);
|
|
|
|
if (is_coregrind_export_header($path_name)) {
|
|
check_coregrind_export_header($path_name);
|
|
} elsif (is_coregrind_file($path_name)) {
|
|
check_coregrind_file($path_name);
|
|
} elsif (is_tool_file($path_name)) {
|
|
check_tool_file($path_name);
|
|
}
|
|
|
|
if ($path_name =~ /\.S$/) {
|
|
check_assembler_file($path_name);
|
|
}
|
|
}
|
|
|
|
sub error {
|
|
my ($message) = @_;
|
|
print STDERR "*** $message";
|
|
++$num_errors;
|
|
}
|