ftmemsim-valgrind/tests/check_headers_and_includes
Nicholas Nethercote 441bfc5f51 Overhaul DHAT.
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.
2019-02-01 14:54:34 +11:00

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;
}