From 441bfc5f51c7f5f80cc6491d23cbe2dc711d191f Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Thu, 4 Oct 2018 11:00:22 +1000 Subject: [PATCH] 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. --- .gitignore | 44 +- Makefile.am | 10 +- NEWS | 16 + configure.ac | 10 +- coregrind/m_libcsetjmp.c | 2 +- coregrind/m_main.c | 2 +- coregrind/pub_core_libcsetjmp.h | 2 +- {exp-dhat => dhat}/Makefile.am | 68 +- {exp-dhat => dhat}/dh_main.c | 846 +++++----- dhat/dh_test.js | 2553 ++++++++++++++++++++++++++++++ dhat/dh_view.css | 130 ++ dhat/dh_view.html | 10 + dhat/dh_view.js | 1445 +++++++++++++++++ dhat/docs/dh-manual.xml | 654 ++++++++ dhat/tests/Makefile.am | 23 + dhat/tests/acc.c | 74 + dhat/tests/acc.stderr.exp | 7 + dhat/tests/acc.vgtest | 3 + dhat/tests/basic.c | 26 + dhat/tests/basic.stderr.exp | 7 + dhat/tests/basic.vgtest | 3 + dhat/tests/big.c | 61 + dhat/tests/big.stderr.exp | 7 + dhat/tests/big.vgtest | 3 + dhat/tests/empty.c | 6 + dhat/tests/empty.stderr.exp | 7 + dhat/tests/empty.vgtest | 3 + dhat/tests/filter_stderr | 9 + dhat/tests/sig.c | 76 + dhat/tests/sig.stderr.exp | 7 + dhat/tests/sig.vgtest | 3 + dhat/tests/single.c | 11 + dhat/tests/single.stderr.exp | 7 + dhat/tests/single.vgtest | 3 + docs/Makefile.am | 1 + docs/README | 16 +- docs/images/dh-tree.png | Bin 0 -> 196802 bytes docs/xml/manual-core.xml | 4 +- docs/xml/manual.xml | 10 +- exp-dhat/docs/dh-manual.xml | 401 ----- exp-dhat/tests/Makefile.am | 1 - include/pub_tool_libcsetjmp.h | 2 +- massif/ms_main.c | 6 +- solaris/valgrind.p5m | 8 +- tests/check_headers_and_includes | 16 +- 45 files changed, 5738 insertions(+), 865 deletions(-) rename {exp-dhat => dhat}/Makefile.am (50%) rename {exp-dhat => dhat}/dh_main.c (66%) create mode 100644 dhat/dh_test.js create mode 100644 dhat/dh_view.css create mode 100644 dhat/dh_view.html create mode 100644 dhat/dh_view.js create mode 100644 dhat/docs/dh-manual.xml create mode 100644 dhat/tests/Makefile.am create mode 100644 dhat/tests/acc.c create mode 100644 dhat/tests/acc.stderr.exp create mode 100644 dhat/tests/acc.vgtest create mode 100644 dhat/tests/basic.c create mode 100644 dhat/tests/basic.stderr.exp create mode 100644 dhat/tests/basic.vgtest create mode 100644 dhat/tests/big.c create mode 100644 dhat/tests/big.stderr.exp create mode 100644 dhat/tests/big.vgtest create mode 100644 dhat/tests/empty.c create mode 100644 dhat/tests/empty.stderr.exp create mode 100644 dhat/tests/empty.vgtest create mode 100755 dhat/tests/filter_stderr create mode 100644 dhat/tests/sig.c create mode 100644 dhat/tests/sig.stderr.exp create mode 100644 dhat/tests/sig.vgtest create mode 100644 dhat/tests/single.c create mode 100644 dhat/tests/single.stderr.exp create mode 100644 dhat/tests/single.vgtest create mode 100644 docs/images/dh-tree.png delete mode 100644 exp-dhat/docs/dh-manual.xml delete mode 100644 exp-dhat/tests/Makefile.am diff --git a/.gitignore b/.gitignore index 3e35b982ce..1d9850086c 100644 --- a/.gitignore +++ b/.gitignore @@ -246,6 +246,34 @@ /coregrind/m_ume/.deps /coregrind/m_ume/.dirstamp +# /dhat/ +/dhat/*.dSYM +/dhat/.deps +/dhat/dhat-*-darwin +/dhat/dhat-*-linux +/dhat/dhat-*-solaris +/dhat/Makefile +/dhat/Makefile.in +/dhat/vgpreload_dhat-*-linux.so +/dhat/vgpreload_dhat-*-darwin.so +/dhat/vgpreload_dhat-*-solaris.so + +# /dhat/tests/ +/dhat/tests/Makefile +/dhat/tests/Makefile.in +/dhat/tests/*.dSYM +/dhat/tests/*.so +/dhat/tests/*.stderr.diff* +/dhat/tests/*.stderr.out +/dhat/tests/*.stdout.diff* +/dhat/tests/*.stdout.out +/dhat/tests/.deps +/dhat/tests/acc +/dhat/tests/basic +/dhat/tests/big +/dhat/tests/empty +/dhat/tests/single + # /docs/ /docs/FAQ.txt /docs/html @@ -496,22 +524,6 @@ /exp-bbv/tests/x86-linux/Makefile /exp-bbv/tests/x86-linux/Makefile.in -# /exp-dhat/ -/exp-dhat/*.dSYM -/exp-dhat/.deps -/exp-dhat/exp-dhat-*-darwin -/exp-dhat/exp-dhat-*-linux -/exp-dhat/exp-dhat-*-solaris -/exp-dhat/Makefile -/exp-dhat/Makefile.in -/exp-dhat/vgpreload_exp-dhat-*-linux.so -/exp-dhat/vgpreload_exp-dhat-*-darwin.so -/exp-dhat/vgpreload_exp-dhat-*-solaris.so - -# /exp-dhat/tests/ -/exp-dhat/tests/Makefile -/exp-dhat/tests/Makefile.in - # /exp-sgcheck/ /exp-sgcheck/*.dSYM /exp-sgcheck/.deps diff --git a/Makefile.am b/Makefile.am index 154f68f5fd..631c845ab2 100644 --- a/Makefile.am +++ b/Makefile.am @@ -6,15 +6,15 @@ include $(top_srcdir)/Makefile.all.am TOOLS = memcheck \ cachegrind \ callgrind \ + helgrind \ + drd \ massif \ + dhat \ lackey \ - none \ - helgrind \ - drd + none EXP_TOOLS = exp-sgcheck \ - exp-bbv \ - exp-dhat + exp-bbv # Put docs last because building the HTML is slow and we want to get # everything else working before we try it. diff --git a/NEWS b/NEWS index 7014fb038d..4a75a0aa3d 100644 --- a/NEWS +++ b/NEWS @@ -20,6 +20,20 @@ support for X86/macOS 10.13, AMD64/macOS 10.13. * ==================== TOOL CHANGES ==================== +* DHAT: + + - DHAT been thoroughly overhauled and improved. As a result, it has been + promoted from an experimental tool to a regular tool. Run it with + --tool=dhat instead of --tool=exp-dhat. + + - DHAT now prints only minimal data when the program ends, instead writing + the bulk of the profiling data to a file. As a result, the --show-top-n and + --sort-by options have been removed. + + - Data files can be viewed with the new viewer, dh_view.html. + + - See the documentation for more details. + * Cachegrind: - cg_annotate has a new option, --show-percs, which prints percentages next @@ -94,6 +108,8 @@ n-i-bz Fix callgrind_annotate non deterministic order for equal total n-i-bz callgrind_annotate --threshold=100 does not print all functions. n-i-bz callgrind_annotate Use of uninitialized value in numeric gt (>) + + Release 3.14.0 (9 October 2018) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/configure.ac b/configure.ac index 35c6f744b5..a02a76b2ac 100644 --- a/configure.ac +++ b/configure.ac @@ -4636,9 +4636,14 @@ AC_CONFIG_FILES([ callgrind/tests/Makefile helgrind/Makefile helgrind/tests/Makefile + drd/Makefile + drd/scripts/download-and-build-splash2 + drd/tests/Makefile massif/Makefile massif/tests/Makefile massif/ms_print + dhat/Makefile + dhat/tests/Makefile lackey/Makefile lackey/tests/Makefile none/Makefile @@ -4664,9 +4669,6 @@ AC_CONFIG_FILES([ none/tests/x86-solaris/Makefile exp-sgcheck/Makefile exp-sgcheck/tests/Makefile - drd/Makefile - drd/scripts/download-and-build-splash2 - drd/tests/Makefile exp-bbv/Makefile exp-bbv/tests/Makefile exp-bbv/tests/x86/Makefile @@ -4674,8 +4676,6 @@ AC_CONFIG_FILES([ exp-bbv/tests/amd64-linux/Makefile exp-bbv/tests/ppc32-linux/Makefile exp-bbv/tests/arm-linux/Makefile - exp-dhat/Makefile - exp-dhat/tests/Makefile shared/Makefile solaris/Makefile ]) diff --git a/coregrind/m_libcsetjmp.c b/coregrind/m_libcsetjmp.c index c731806402..85ffc12262 100644 --- a/coregrind/m_libcsetjmp.c +++ b/coregrind/m_libcsetjmp.c @@ -7,7 +7,7 @@ This file is part of Valgrind, a dynamic binary instrumentation framework. - Copyright (C) 2010-2017 Mozilla Inc + Copyright (C) 2010-2017 Mozilla Foundation This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as diff --git a/coregrind/m_main.c b/coregrind/m_main.c index 7d987ea266..21df6791a7 100644 --- a/coregrind/m_main.c +++ b/coregrind/m_main.c @@ -1454,7 +1454,7 @@ Int valgrind_main ( Int argc, HChar **argv, HChar **envp ) || 0 == VG_(strcmp)(VG_(clo_toolname), "helgrind") || 0 == VG_(strcmp)(VG_(clo_toolname), "drd") || 0 == VG_(strcmp)(VG_(clo_toolname), "massif") - || 0 == VG_(strcmp)(VG_(clo_toolname), "exp-dhat")) { + || 0 == VG_(strcmp)(VG_(clo_toolname), "dhat")) { /* Change the default setting. Later on (just below) main_process_cmd_line_options should pick up any user-supplied setting for it and will override the default diff --git a/coregrind/pub_core_libcsetjmp.h b/coregrind/pub_core_libcsetjmp.h index 91c6801917..494703fe11 100644 --- a/coregrind/pub_core_libcsetjmp.h +++ b/coregrind/pub_core_libcsetjmp.h @@ -7,7 +7,7 @@ This file is part of Valgrind, a dynamic binary instrumentation framework. - Copyright (C) 2010-2017 Mozilla Inc + Copyright (C) 2010-2017 Mozilla Foundation This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as diff --git a/exp-dhat/Makefile.am b/dhat/Makefile.am similarity index 50% rename from exp-dhat/Makefile.am rename to dhat/Makefile.am index b74529858d..74bf2cd1cc 100644 --- a/exp-dhat/Makefile.am +++ b/dhat/Makefile.am @@ -11,89 +11,89 @@ EXTRA_DIST = docs/dh-manual.xml #bin_SCRIPTS = dh_print #---------------------------------------------------------------------------- -# exp_dhat- +# dhat- #---------------------------------------------------------------------------- -noinst_PROGRAMS = exp-dhat-@VGCONF_ARCH_PRI@-@VGCONF_OS@ +noinst_PROGRAMS = dhat-@VGCONF_ARCH_PRI@-@VGCONF_OS@ if VGCONF_HAVE_PLATFORM_SEC -noinst_PROGRAMS += exp-dhat-@VGCONF_ARCH_SEC@-@VGCONF_OS@ +noinst_PROGRAMS += dhat-@VGCONF_ARCH_SEC@-@VGCONF_OS@ endif EXP_DHAT_SOURCES_COMMON = dh_main.c -exp_dhat_@VGCONF_ARCH_PRI@_@VGCONF_OS@_SOURCES = \ +dhat_@VGCONF_ARCH_PRI@_@VGCONF_OS@_SOURCES = \ $(EXP_DHAT_SOURCES_COMMON) -exp_dhat_@VGCONF_ARCH_PRI@_@VGCONF_OS@_CPPFLAGS = \ +dhat_@VGCONF_ARCH_PRI@_@VGCONF_OS@_CPPFLAGS = \ $(AM_CPPFLAGS_@VGCONF_PLATFORM_PRI_CAPS@) -exp_dhat_@VGCONF_ARCH_PRI@_@VGCONF_OS@_CFLAGS = $(LTO_CFLAGS) \ +dhat_@VGCONF_ARCH_PRI@_@VGCONF_OS@_CFLAGS = $(LTO_CFLAGS) \ $(AM_CFLAGS_@VGCONF_PLATFORM_PRI_CAPS@) -exp_dhat_@VGCONF_ARCH_PRI@_@VGCONF_OS@_DEPENDENCIES = \ +dhat_@VGCONF_ARCH_PRI@_@VGCONF_OS@_DEPENDENCIES = \ $(TOOL_DEPENDENCIES_@VGCONF_PLATFORM_PRI_CAPS@) -exp_dhat_@VGCONF_ARCH_PRI@_@VGCONF_OS@_LDADD = \ +dhat_@VGCONF_ARCH_PRI@_@VGCONF_OS@_LDADD = \ $(TOOL_LDADD_@VGCONF_PLATFORM_PRI_CAPS@) -exp_dhat_@VGCONF_ARCH_PRI@_@VGCONF_OS@_LDFLAGS = \ +dhat_@VGCONF_ARCH_PRI@_@VGCONF_OS@_LDFLAGS = \ $(TOOL_LDFLAGS_@VGCONF_PLATFORM_PRI_CAPS@) -exp_dhat_@VGCONF_ARCH_PRI@_@VGCONF_OS@_LINK = \ +dhat_@VGCONF_ARCH_PRI@_@VGCONF_OS@_LINK = \ $(top_builddir)/coregrind/link_tool_exe_@VGCONF_OS@ \ @VALT_LOAD_ADDRESS_PRI@ \ $(LINK) \ - $(exp_dhat_@VGCONF_ARCH_PRI@_@VGCONF_OS@_CFLAGS) \ - $(exp_dhat_@VGCONF_ARCH_PRI@_@VGCONF_OS@_LDFLAGS) + $(dhat_@VGCONF_ARCH_PRI@_@VGCONF_OS@_CFLAGS) \ + $(dhat_@VGCONF_ARCH_PRI@_@VGCONF_OS@_LDFLAGS) if VGCONF_HAVE_PLATFORM_SEC -exp_dhat_@VGCONF_ARCH_SEC@_@VGCONF_OS@_SOURCES = \ +dhat_@VGCONF_ARCH_SEC@_@VGCONF_OS@_SOURCES = \ $(EXP_DHAT_SOURCES_COMMON) -exp_dhat_@VGCONF_ARCH_SEC@_@VGCONF_OS@_CPPFLAGS = \ +dhat_@VGCONF_ARCH_SEC@_@VGCONF_OS@_CPPFLAGS = \ $(AM_CPPFLAGS_@VGCONF_PLATFORM_SEC_CAPS@) -exp_dhat_@VGCONF_ARCH_SEC@_@VGCONF_OS@_CFLAGS = $(LTO_CFLAGS) \ +dhat_@VGCONF_ARCH_SEC@_@VGCONF_OS@_CFLAGS = $(LTO_CFLAGS) \ $(AM_CFLAGS_@VGCONF_PLATFORM_SEC_CAPS@) -exp_dhat_@VGCONF_ARCH_SEC@_@VGCONF_OS@_DEPENDENCIES = \ +dhat_@VGCONF_ARCH_SEC@_@VGCONF_OS@_DEPENDENCIES = \ $(TOOL_DEPENDENCIES_@VGCONF_PLATFORM_SEC_CAPS@) -exp_dhat_@VGCONF_ARCH_SEC@_@VGCONF_OS@_LDADD = \ +dhat_@VGCONF_ARCH_SEC@_@VGCONF_OS@_LDADD = \ $(TOOL_LDADD_@VGCONF_PLATFORM_SEC_CAPS@) -exp_dhat_@VGCONF_ARCH_SEC@_@VGCONF_OS@_LDFLAGS = \ +dhat_@VGCONF_ARCH_SEC@_@VGCONF_OS@_LDFLAGS = \ $(TOOL_LDFLAGS_@VGCONF_PLATFORM_SEC_CAPS@) -exp_dhat_@VGCONF_ARCH_SEC@_@VGCONF_OS@_LINK = \ +dhat_@VGCONF_ARCH_SEC@_@VGCONF_OS@_LINK = \ $(top_builddir)/coregrind/link_tool_exe_@VGCONF_OS@ \ @VALT_LOAD_ADDRESS_SEC@ \ $(LINK) \ - $(exp_dhat_@VGCONF_ARCH_SEC@_@VGCONF_OS@_CFLAGS) \ - $(exp_dhat_@VGCONF_ARCH_SEC@_@VGCONF_OS@_LDFLAGS) + $(dhat_@VGCONF_ARCH_SEC@_@VGCONF_OS@_CFLAGS) \ + $(dhat_@VGCONF_ARCH_SEC@_@VGCONF_OS@_LDFLAGS) endif #---------------------------------------------------------------------------- -# vgpreload_exp_dhat-.so +# vgpreload_dhat-.so #---------------------------------------------------------------------------- -noinst_PROGRAMS += vgpreload_exp-dhat-@VGCONF_ARCH_PRI@-@VGCONF_OS@.so +noinst_PROGRAMS += vgpreload_dhat-@VGCONF_ARCH_PRI@-@VGCONF_OS@.so if VGCONF_HAVE_PLATFORM_SEC -noinst_PROGRAMS += vgpreload_exp-dhat-@VGCONF_ARCH_SEC@-@VGCONF_OS@.so +noinst_PROGRAMS += vgpreload_dhat-@VGCONF_ARCH_SEC@-@VGCONF_OS@.so endif if VGCONF_OS_IS_DARWIN noinst_DSYMS = $(noinst_PROGRAMS) endif -vgpreload_exp_dhat_@VGCONF_ARCH_PRI@_@VGCONF_OS@_so_SOURCES = -vgpreload_exp_dhat_@VGCONF_ARCH_PRI@_@VGCONF_OS@_so_CPPFLAGS = \ +vgpreload_dhat_@VGCONF_ARCH_PRI@_@VGCONF_OS@_so_SOURCES = +vgpreload_dhat_@VGCONF_ARCH_PRI@_@VGCONF_OS@_so_CPPFLAGS = \ $(AM_CPPFLAGS_@VGCONF_PLATFORM_PRI_CAPS@) -vgpreload_exp_dhat_@VGCONF_ARCH_PRI@_@VGCONF_OS@_so_CFLAGS = \ +vgpreload_dhat_@VGCONF_ARCH_PRI@_@VGCONF_OS@_so_CFLAGS = \ $(AM_CFLAGS_PSO_@VGCONF_PLATFORM_PRI_CAPS@) -vgpreload_exp_dhat_@VGCONF_ARCH_PRI@_@VGCONF_OS@_so_DEPENDENCIES = \ +vgpreload_dhat_@VGCONF_ARCH_PRI@_@VGCONF_OS@_so_DEPENDENCIES = \ $(LIBREPLACEMALLOC_@VGCONF_PLATFORM_PRI_CAPS@) -vgpreload_exp_dhat_@VGCONF_ARCH_PRI@_@VGCONF_OS@_so_LDFLAGS = \ +vgpreload_dhat_@VGCONF_ARCH_PRI@_@VGCONF_OS@_so_LDFLAGS = \ $(PRELOAD_LDFLAGS_@VGCONF_PLATFORM_PRI_CAPS@) \ $(LIBREPLACEMALLOC_LDFLAGS_@VGCONF_PLATFORM_PRI_CAPS@) if VGCONF_HAVE_PLATFORM_SEC -vgpreload_exp_dhat_@VGCONF_ARCH_SEC@_@VGCONF_OS@_so_SOURCES = -vgpreload_exp_dhat_@VGCONF_ARCH_SEC@_@VGCONF_OS@_so_CPPFLAGS = \ +vgpreload_dhat_@VGCONF_ARCH_SEC@_@VGCONF_OS@_so_SOURCES = +vgpreload_dhat_@VGCONF_ARCH_SEC@_@VGCONF_OS@_so_CPPFLAGS = \ $(AM_CPPFLAGS_@VGCONF_PLATFORM_SEC_CAPS@) -vgpreload_exp_dhat_@VGCONF_ARCH_SEC@_@VGCONF_OS@_so_CFLAGS = \ +vgpreload_dhat_@VGCONF_ARCH_SEC@_@VGCONF_OS@_so_CFLAGS = \ $(AM_CFLAGS_PSO_@VGCONF_PLATFORM_SEC_CAPS@) -vgpreload_exp_dhat_@VGCONF_ARCH_SEC@_@VGCONF_OS@_so_DEPENDENCIES = \ +vgpreload_dhat_@VGCONF_ARCH_SEC@_@VGCONF_OS@_so_DEPENDENCIES = \ $(LIBREPLACEMALLOC_@VGCONF_PLATFORM_SEC_CAPS@) -vgpreload_exp_dhat_@VGCONF_ARCH_SEC@_@VGCONF_OS@_so_LDFLAGS = \ +vgpreload_dhat_@VGCONF_ARCH_SEC@_@VGCONF_OS@_so_LDFLAGS = \ $(PRELOAD_LDFLAGS_@VGCONF_PLATFORM_SEC_CAPS@) \ $(LIBREPLACEMALLOC_LDFLAGS_@VGCONF_PLATFORM_SEC_CAPS@) endif diff --git a/exp-dhat/dh_main.c b/dhat/dh_main.c similarity index 66% rename from exp-dhat/dh_main.c rename to dhat/dh_main.c index c23134b2c6..ffcf874619 100644 --- a/exp-dhat/dh_main.c +++ b/dhat/dh_main.c @@ -1,13 +1,13 @@ -//--------------------------------------------------------------------*/ -//--- DHAT: a Dynamic Heap Analysis Tool dh_main.c ---*/ -//--------------------------------------------------------------------*/ +//--------------------------------------------------------------------// +//--- DHAT: a Dynamic Heap Analysis Tool dh_main.c ---// +//--------------------------------------------------------------------// /* This file is part of DHAT, a Valgrind tool for profiling the heap usage of programs. - Copyright (C) 2010-2017 Mozilla Inc + Copyright (C) 2010-2018 Mozilla Foundation This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as @@ -31,9 +31,12 @@ #include "pub_tool_basics.h" +#include "pub_tool_clientstate.h" #include "pub_tool_libcbase.h" #include "pub_tool_libcassert.h" +#include "pub_tool_libcfile.h" #include "pub_tool_libcprint.h" +#include "pub_tool_libcproc.h" #include "pub_tool_machine.h" // VG_(fnptr_to_fnentry) #include "pub_tool_mallocfree.h" #include "pub_tool_options.h" @@ -43,25 +46,27 @@ #define HISTOGRAM_SIZE_LIMIT 1024 - //------------------------------------------------------------// //--- Globals ---// //------------------------------------------------------------// -// Number of guest instructions executed so far. This is -// incremented directly from the generated code. -static ULong g_guest_instrs_executed = 0; - -// Summary statistics for the entire run. -static ULong g_tot_blocks = 0; // total blocks allocated -static ULong g_tot_bytes = 0; // total bytes allocated +// Values for the entire run. +static ULong g_total_blocks = 0; +static ULong g_total_bytes = 0; -static ULong g_cur_blocks_live = 0; // curr # blocks live -static ULong g_cur_bytes_live = 0; // curr # bytes live +// Current values. +static ULong g_curr_blocks = 0; +static ULong g_curr_bytes = 0; +static ULong g_curr_instrs = 0; // incremented from generated code -static ULong g_max_blocks_live = 0; // bytes and blocks at -static ULong g_max_bytes_live = 0; // the max residency point +// Values at the global max, i.e. when g_curr_bytes peaks. +static ULong g_max_blocks = 0; +static ULong g_max_bytes = 0; +static ULong g_max_instrs = 0; +// Values for the entire run. Computed at the end. +static ULong g_reads_bytes = 0; +static ULong g_writes_bytes = 0; //------------------------------------------------------------// //--- an Interval Tree of live blocks ---// @@ -74,8 +79,8 @@ typedef SizeT req_szB; ExeContext* ap; /* allocation ec */ ULong allocd_at; /* instruction number */ - ULong n_reads; - ULong n_writes; + ULong reads_bytes; + ULong writes_bytes; /* Approx histogram, one byte per payload byte. Counts latch up therefore at 0xFFFF. Can be NULL if the block is resized or if the block is larger than HISTOGRAM_SIZE_LIMIT. */ @@ -112,14 +117,14 @@ static UWord stats__n_fBc_notfound = 0; static Block* find_Block_containing ( Addr a ) { if (LIKELY(fbc_cache0 - && fbc_cache0->payload <= a + && fbc_cache0->payload <= a && a < fbc_cache0->payload + fbc_cache0->req_szB)) { // found at 0 stats__n_fBc_cached++; return fbc_cache0; } if (LIKELY(fbc_cache1 - && fbc_cache1->payload <= a + && fbc_cache1->payload <= a && a < fbc_cache1->payload + fbc_cache1->req_szB)) { // found at 1; swap 0 and 1 Block* tmp = fbc_cache0; @@ -170,31 +175,37 @@ static void delete_Block_starting_at ( Addr a ) typedef struct { - // the allocation point that we're summarising stats for + // The allocation point that we're summarising stats for. ExeContext* ap; - // used when printing results - Bool shown; - // The current number of blocks and bytes live for this AP - ULong cur_blocks_live; - ULong cur_bytes_live; - // The number of blocks and bytes live at the max-liveness - // point. Note this is a bit subtle. max_blocks_live is not - // the maximum number of live blocks, but rather the number of - // blocks live at the point of maximum byte liveness. These are - // not necessarily the same thing. - ULong max_blocks_live; - ULong max_bytes_live; + // Total number of blocks and bytes allocated by this AP. - ULong tot_blocks; - ULong tot_bytes; - // Sum of death ages for all blocks allocated by this AP, - // that have subsequently been freed. - ULong death_ages_sum; - ULong deaths; + ULong total_blocks; + ULong total_bytes; + + // The current number of blocks and bytes live for this AP. + ULong curr_blocks; + ULong curr_bytes; + + // Values at the AP max, i.e. when this AP's curr_bytes peaks. + ULong max_blocks; // Blocks at the AP max. + ULong max_bytes; // The AP max, measured in bytes. + + // Values at the global max. + ULong at_tgmax_blocks; + ULong at_tgmax_bytes; + + // Total lifetimes of all blocks allocated by this AP. Includes blocks + // explicitly freed and blocks implicitly freed at termination. + ULong total_lifetimes_instrs; + + // Number of blocks freed by this AP. (Only used in assertions.) + ULong freed_blocks; + // Total number of reads and writes in all blocks allocated // by this AP. - ULong n_reads; - ULong n_writes; + ULong reads_bytes; + ULong writes_bytes; + /* Histogram information. We maintain a histogram aggregated for all retiring Blocks allocated by this AP, but only if: - this AP has only ever allocated objects of one size @@ -203,7 +214,7 @@ typedef has only ever allocated blocks of one size. 3 states: - Unknown because no retirement yet + Unknown because no retirement yet Exactly xsize all retiring blocks are of this size Mixed multiple different sizes seen */ @@ -218,6 +229,26 @@ typedef static WordFM* apinfo = NULL; /* WordFM* ExeContext* APInfo* */ +// Are we at peak memory? If so, update at_tgmax_blocks and at_tgmax_bytes in +// all APInfos. Note that this is moderately expensive so we avoid calling it +// on every allocation. +static void check_for_peak(void) +{ + if (g_curr_bytes == g_max_bytes) { + // It's a peak. (If there are multiple equal peaks we record the latest + // one.) + UWord keyW, valW; + VG_(initIterFM)(apinfo); + while (VG_(nextIterFM)(apinfo, &keyW, &valW)) { + APInfo* api = (APInfo*)valW; + tl_assert(api && api->ap == (ExeContext*)keyW); + api->at_tgmax_blocks = api->curr_blocks; + api->at_tgmax_bytes = api->curr_bytes; + } + VG_(doneIterFM)(apinfo); + } +} + /* 'bk' is being introduced (has just been allocated). Find the relevant APInfo entry for it, or create one, based on the block's allocation EC. Then, update the APInfo to the extent that we @@ -236,14 +267,14 @@ static void intro_Block ( Block* bk ) api = (APInfo*)valW; tl_assert(keyW == (UWord)bk->ap); } else { - api = VG_(malloc)( "dh.main.intro_Block.1", sizeof(APInfo) ); + api = VG_(malloc)( "dh.intro_Block.1", sizeof(APInfo) ); VG_(memset)(api, 0, sizeof(*api)); api->ap = bk->ap; Bool present = VG_(addToFM)( apinfo, (UWord)bk->ap, (UWord)api ); tl_assert(!present); // histo stuff - tl_assert(api->deaths == 0); + tl_assert(api->freed_blocks == 0); api->xsize_tag = Unknown; api->xsize = 0; if (0) VG_(printf)("api %p --> Unknown\n", api); @@ -251,35 +282,32 @@ static void intro_Block ( Block* bk ) tl_assert(api->ap == bk->ap); - /* So: update stats to reflect an allocation */ + // Update global stats first. - // # live blocks - api->cur_blocks_live++; + g_total_blocks++; + g_total_bytes += bk->req_szB; - // # live bytes - api->cur_bytes_live += bk->req_szB; - if (api->cur_bytes_live > api->max_bytes_live) { - api->max_bytes_live = api->cur_bytes_live; - api->max_blocks_live = api->cur_blocks_live; + g_curr_blocks++; + g_curr_bytes += bk->req_szB; + if (g_curr_bytes > g_max_bytes) { + g_max_blocks = g_curr_blocks; + g_max_bytes = g_curr_bytes; + g_max_instrs = g_curr_instrs; } - // total blocks and bytes allocated here - api->tot_blocks++; - api->tot_bytes += bk->req_szB; + // Now update APInfo stats. - // update summary globals - g_tot_blocks++; - g_tot_bytes += bk->req_szB; + api->total_blocks++; + api->total_bytes += bk->req_szB; - g_cur_blocks_live++; - g_cur_bytes_live += bk->req_szB; - if (g_cur_bytes_live > g_max_bytes_live) { - g_max_bytes_live = g_cur_bytes_live; - g_max_blocks_live = g_cur_blocks_live; + api->curr_blocks++; + api->curr_bytes += bk->req_szB; + if (api->curr_bytes > api->max_bytes) { + api->max_blocks = api->curr_blocks; + api->max_bytes = api->curr_bytes; } } - /* 'bk' is retiring (being freed). Find the relevant APInfo entry for it, which must already exist. Then, fold info from 'bk' into that entry. 'because_freed' is True if the block is retiring because @@ -305,52 +333,57 @@ static void retire_Block ( Block* bk, Bool because_freed ) // update stats following this free. if (0) - VG_(printf)("ec %p api->c_by_l %llu bk->rszB %llu\n", - bk->ap, api->cur_bytes_live, (ULong)bk->req_szB); + VG_(printf)("ec %p api->c_by_l %llu bk->rszB %llu\n", + bk->ap, api->curr_bytes, (ULong)bk->req_szB); - // update total blocks live etc for this AP if (because_freed) { - tl_assert(api->cur_blocks_live >= 1); - tl_assert(api->cur_bytes_live >= bk->req_szB); - api->cur_blocks_live--; - api->cur_bytes_live -= bk->req_szB; - - api->deaths++; - - tl_assert(bk->allocd_at <= g_guest_instrs_executed); - api->death_ages_sum += (g_guest_instrs_executed - bk->allocd_at); - - // update global summary stats - tl_assert(g_cur_blocks_live > 0); - g_cur_blocks_live--; - tl_assert(g_cur_bytes_live >= bk->req_szB); - g_cur_bytes_live -= bk->req_szB; + // Total bytes is coming down from a possible peak. + check_for_peak(); + + // Then update global stats. + tl_assert(g_curr_blocks >= 1); + tl_assert(g_curr_bytes >= bk->req_szB); + g_curr_blocks--; + g_curr_bytes -= bk->req_szB; + + // Then update APInfo stats. + tl_assert(api->curr_blocks >= 1); + tl_assert(api->curr_bytes >= bk->req_szB); + api->curr_blocks--; + api->curr_bytes -= bk->req_szB; + + api->freed_blocks++; } + tl_assert(bk->allocd_at <= g_curr_instrs); + api->total_lifetimes_instrs += (g_curr_instrs - bk->allocd_at); + // access counts - api->n_reads += bk->n_reads; - api->n_writes += bk->n_writes; + api->reads_bytes += bk->reads_bytes; + api->writes_bytes += bk->writes_bytes; + g_reads_bytes += bk->reads_bytes; + g_writes_bytes += bk->writes_bytes; // histo stuff. First, do state transitions for xsize/xsize_tag. switch (api->xsize_tag) { case Unknown: tl_assert(api->xsize == 0); - tl_assert(api->deaths == 1 || api->deaths == 0); + tl_assert(api->freed_blocks == 1 || api->freed_blocks == 0); tl_assert(!api->histo); api->xsize_tag = Exactly; api->xsize = bk->req_szB; if (0) VG_(printf)("api %p --> Exactly(%lu)\n", api, api->xsize); // and allocate the histo if (bk->histoW) { - api->histo = VG_(malloc)("dh.main.retire_Block.1", + api->histo = VG_(malloc)("dh.retire_Block.1", api->xsize * sizeof(UInt)); VG_(memset)(api->histo, 0, api->xsize * sizeof(UInt)); } break; case Exactly: - //tl_assert(api->deaths > 1); + //tl_assert(api->freed_blocks > 1); if (bk->req_szB != api->xsize) { if (0) VG_(printf)("api %p --> Mixed(%lu -> %lu)\n", api, api->xsize, bk->req_szB); @@ -365,7 +398,7 @@ static void retire_Block ( Block* bk, Bool because_freed ) break; case Mixed: - //tl_assert(api->deaths > 1); + //tl_assert(api->freed_blocks > 1); break; default: @@ -386,8 +419,6 @@ static void retire_Block ( Block* bk, Bool because_freed ) if (0) VG_(printf)("fold in, AP = %p\n", api); } - - #if 0 if (bk->histoB) { VG_(printf)("block retiring, histo %lu: ", bk->req_szB); @@ -403,8 +434,9 @@ static void retire_Block ( Block* bk, Bool because_freed ) /* This handles block resizing. When a block with AP 'ec' has a size change of 'delta', call here to update the APInfo. */ -static void apinfo_change_cur_bytes_live( ExeContext* ec, Long delta ) +static void resize_Block(ExeContext* ec, SizeT old_req_szB, SizeT new_req_szB) { + Long delta = (Long)new_req_szB - (Long)old_req_szB; APInfo* api = NULL; UWord keyW = 0; UWord valW = 0; @@ -416,30 +448,46 @@ static void apinfo_change_cur_bytes_live( ExeContext* ec, Long delta ) tl_assert(api->ap == ec); if (delta < 0) { - tl_assert(api->cur_bytes_live >= -delta); - tl_assert(g_cur_bytes_live >= -delta); + tl_assert(api->curr_bytes >= -delta); + tl_assert(g_curr_bytes >= -delta); } - // adjust current live size - api->cur_bytes_live += delta; - g_cur_bytes_live += delta; - - if (delta > 0 && api->cur_bytes_live > api->max_bytes_live) { - api->max_bytes_live = api->cur_bytes_live; - api->max_blocks_live = api->cur_blocks_live; + // Total bytes might be coming down from a possible peak. + if (delta < 0) + check_for_peak(); + + // Note: we treat realloc() like malloc() + free() for total counts, i.e. we + // increment total_blocks by 1 and increment total_bytes by new_req_szB. + // + // A reasonable alternative would be to leave total_blocks unchanged and + // increment total_bytes by delta (but only if delta is positive). But then + // calls to realloc wouldn't be counted towards the total_blocks count, + // which is undesirable. + + // Update global stats first. + + g_total_blocks++; + g_total_bytes += new_req_szB; + + g_curr_blocks += 0; // unchanged + g_curr_bytes += delta; + if (g_curr_bytes > g_max_bytes) { + g_max_blocks = g_curr_blocks; + g_max_bytes = g_curr_bytes; + g_max_instrs = g_curr_instrs; } - // update global summary stats - if (delta > 0 && g_cur_bytes_live > g_max_bytes_live) { - g_max_bytes_live = g_cur_bytes_live; - g_max_blocks_live = g_cur_blocks_live; - } - if (delta > 0) - g_tot_bytes += delta; + // Now update APInfo stats. - // adjust total allocation size - if (delta > 0) - api->tot_bytes += delta; + api->total_blocks++; + api->total_bytes += new_req_szB; + + api->curr_blocks += 0; // unchanged + api->curr_bytes += delta; + if (api->curr_bytes > api->max_bytes) { + api->max_blocks = api->curr_blocks; + api->max_bytes = api->curr_bytes; + } } @@ -473,14 +521,14 @@ void* new_block ( ThreadId tid, void* p, SizeT req_szB, SizeT req_alignB, /* slop_szB = 0; */ } - // Make new HP_Chunk node, add to malloc_list + // Make new Block, add to interval_tree. Block* bk = VG_(malloc)("dh.new_block.1", sizeof(Block)); - bk->payload = (Addr)p; - bk->req_szB = req_szB; - bk->ap = VG_(record_ExeContext)(tid, 0/*first word delta*/); - bk->allocd_at = g_guest_instrs_executed; - bk->n_reads = 0; - bk->n_writes = 0; + bk->payload = (Addr)p; + bk->req_szB = req_szB; + bk->ap = VG_(record_ExeContext)(tid, 0/*first word delta*/); + bk->allocd_at = g_curr_instrs; + bk->reads_bytes = 0; + bk->writes_bytes = 0; // set up histogram array, if the block isn't too large bk->histoW = NULL; if (req_szB <= HISTOGRAM_SIZE_LIMIT) { @@ -520,7 +568,7 @@ void die_block ( void* p, Bool custom_free ) } if (0) VG_(printf)(" FREE %p %llu\n", - p, g_guest_instrs_executed - bk->allocd_at); + p, g_curr_instrs - bk->allocd_at); retire_Block(bk, True/*because_freed*/); @@ -568,8 +616,7 @@ void* renew_block ( ThreadId tid, void* p_old, SizeT new_req_szB ) if (new_req_szB <= bk->req_szB) { // New size is smaller or same; block not moved. - apinfo_change_cur_bytes_live(bk->ap, - (Long)new_req_szB - (Long)bk->req_szB); + resize_Block(bk->ap, bk->req_szB, new_req_szB); bk->req_szB = new_req_szB; return p_old; @@ -595,14 +642,13 @@ void* renew_block ( ThreadId tid, void* p_old, SizeT new_req_szB ) // is still alive // Update the metadata. - apinfo_change_cur_bytes_live(bk->ap, - (Long)new_req_szB - (Long)bk->req_szB); + resize_Block(bk->ap, bk->req_szB, new_req_szB); bk->payload = (Addr)p_new; bk->req_szB = new_req_szB; // and re-add Bool present - = VG_(addToFM)( interval_tree, (UWord)bk, (UWord)0/*no val*/); + = VG_(addToFM)( interval_tree, (UWord)bk, (UWord)0/*no val*/); tl_assert(!present); fbc_cache0 = fbc_cache1 = NULL; @@ -670,10 +716,10 @@ static void* dh_realloc ( ThreadId tid, void* p_old, SizeT new_szB ) } static SizeT dh_malloc_usable_size ( ThreadId tid, void* p ) -{ +{ Block* bk = find_Block_containing( (Addr)p ); return bk ? bk->req_szB : 0; -} +} //------------------------------------------------------------// @@ -702,7 +748,7 @@ void dh_handle_write ( Addr addr, UWord szB ) { Block* bk = find_Block_containing(addr); if (bk) { - bk->n_writes += szB; + bk->writes_bytes += szB; if (bk->histoW) inc_histo_for_block(bk, addr, szB); } @@ -713,7 +759,7 @@ void dh_handle_read ( Addr addr, UWord szB ) { Block* bk = find_Block_containing(addr); if (bk) { - bk->n_reads += szB; + bk->reads_bytes += szB; if (bk->histoW) inc_histo_for_block(bk, addr, szB); } @@ -778,13 +824,13 @@ void add_counter_update(IRSB* sbOut, Int n) #else # error "Unknown endianness" #endif - // Add code to increment 'g_guest_instrs_executed' by 'n', like this: - // WrTmp(t1, Load64(&g_guest_instrs_executed)) + // Add code to increment 'g_curr_instrs' by 'n', like this: + // WrTmp(t1, Load64(&g_curr_instrs)) // WrTmp(t2, Add64(RdTmp(t1), Const(n))) - // Store(&g_guest_instrs_executed, t2) + // Store(&g_curr_instrs, t2) IRTemp t1 = newIRTemp(sbOut->tyenv, Ity_I64); IRTemp t2 = newIRTemp(sbOut->tyenv, Ity_I64); - IRExpr* counter_addr = mkIRExpr_HWord( (HWord)&g_guest_instrs_executed ); + IRExpr* counter_addr = mkIRExpr_HWord( (HWord)&g_curr_instrs ); IRStmt* st1 = assign(t1, IRExpr_Load(END, Ity_I64, counter_addr)); IRStmt* st2 = assign(t2, binop(Iop_Add64, mkexpr(t1), mkU64(n))); @@ -850,7 +896,7 @@ void addMemEvent(IRSB* sbOut, Bool isWrite, Int szB, IRExpr* addr, addStmtToIRSB( sbOut, assign(diff, - tyAddr == Ity_I32 + tyAddr == Ity_I32 ? binop(Iop_Sub32, addr, mkexpr(sp_minus_rz)) : binop(Iop_Sub64, addr, mkexpr(sp_minus_rz))) ); @@ -859,7 +905,7 @@ void addMemEvent(IRSB* sbOut, Bool isWrite, Int szB, IRExpr* addr, addStmtToIRSB( sbOut, assign(guard, - tyAddr == Ity_I32 + tyAddr == Ity_I32 ? binop(Iop_CmpLT32U, mkU32(THRESH), mkexpr(diff)) : binop(Iop_CmpLT64U, mkU64(THRESH), mkexpr(diff))) ); @@ -886,7 +932,7 @@ IRSB* dh_instrument ( VgCallbackClosure* closure, // - just before any Ist_Exit statements; // - just before the IRSB's end. // In the former case, we zero 'n' and then continue instrumenting. - + sbOut = deepCopyIRSBExceptStmts(sbIn); // Copy verbatim any IR preamble preceding the first IMark @@ -895,10 +941,10 @@ IRSB* dh_instrument ( VgCallbackClosure* closure, addStmtToIRSB( sbOut, sbIn->stmts[i] ); i++; } - + for (/*use current i*/; i < sbIn->stmts_used; i++) { IRStmt* st = sbIn->stmts[i]; - + if (!st || st->tag == Ist_NoOp) continue; switch (st->tag) { @@ -933,7 +979,7 @@ IRSB* dh_instrument ( VgCallbackClosure* closure, case Ist_Store: { IRExpr* data = st->Ist.Store.data; IRExpr* aexpr = st->Ist.Store.addr; - addMemEvent( sbOut, True/*isWrite*/, + addMemEvent( sbOut, True/*isWrite*/, sizeofIRType(typeOfIRExpr(tyenv, data)), aexpr, goff_sp ); break; @@ -1027,28 +1073,11 @@ IRSB* dh_instrument ( VgCallbackClosure* closure, //--- Command line args ---// //------------------------------------------------------------// -// FORWARDS -static Bool identify_metric ( /*OUT*/ULong(**get_metricP)(APInfo*), - /*OUT*/Bool* increasingP, - const HChar* metric_name ); - -static Int clo_show_top_n = 10; -static const HChar *clo_sort_by = "max-bytes-live"; +static const HChar* clo_dhat_out_file = "dhat.out.%p"; static Bool dh_process_cmd_line_option(const HChar* arg) { - if VG_BINT_CLO(arg, "--show-top-n", clo_show_top_n, 1, 100000) {} - - else if VG_STR_CLO(arg, "--sort-by", clo_sort_by) { - ULong (*dummyFn)(APInfo*); - Bool dummyB; - Bool ok = identify_metric( &dummyFn, &dummyB, clo_sort_by); - if (!ok) - return False; - // otherwise it's OK, in which case leave it alone. - // show_top_n_apinfos will later convert the string by a - // second call to identify_metric. - } + if VG_STR_CLO(arg, "--dhat-out-file", clo_dhat_out_file) {} else return VG_(replacement_malloc_process_cmd_line_option)(arg); @@ -1056,18 +1085,10 @@ static Bool dh_process_cmd_line_option(const HChar* arg) return True; } - static void dh_print_usage(void) { VG_(printf)( -" --show-top-n=number show the top alloc points [10]\n" -" --sort-by=string\n" -" sort the allocation points by the metric\n" -" defined by , thusly:\n" -" max-bytes-live maximum live bytes [default]\n" -" tot-bytes-allocd bytes allocated in total (turnover)\n" -" max-blocks-live maximum live blocks\n" -" tot-blocks-allocd blocks allocated in total (turnover)\n" +" --dhat-out-file= output file name [dhat.out.%%p]\n" ); } @@ -1083,204 +1104,236 @@ static void dh_print_debug_usage(void) //--- Finalisation ---// //------------------------------------------------------------// -static void show_N_div_100( /*OUT*/HChar* buf, ULong n ) +// File format notes. +// +// - The files are JSON, because it's a widely-used format and saves us having +// to write a parser in dh_view.js. +// +// - We use a comma-first style for the generated JSON. Comma-first style +// moves the special case for arrays/objects from the last item to the +// first. This helps in cases where you can't easily tell in advance the +// size of arrays/objects, such as iterating over a WordFM (because +// VG_(sizeFM) is O(n) rather than O(1)), and iterating over stack frames +// using VG_(apply_ExeContext) in combination with an InlIpCursor. +// +// - We use short field names and minimal whitespace to minimize file sizes. + +static VgFile* fp; + +#define FP(format, args...) ({ VG_(fprintf)(fp, format, ##args); }) + +// The frame table holds unique frames. +static WordFM* frame_tbl = NULL; +static UWord next_frame_n = 0; + +static Word frame_cmp(UWord a, UWord b) { - ULong nK = n / 100; - ULong nR = n % 100; - VG_(sprintf)(buf, "%llu.%s%llu", nK, - nR < 10 ? "0" : "", - nR); + return VG_(strcmp)((const HChar*)a, (const HChar*)b); } -static void show_APInfo ( APInfo* api ) +static HChar hex_digit_to_ascii_char(UChar d) { - HChar bufA[80]; // large enough - VG_(memset)(bufA, 0, sizeof(bufA)); - if (api->tot_blocks > 0) { - show_N_div_100( bufA, ((ULong)api->tot_bytes * 100ULL) - / (ULong)api->tot_blocks ); - } else { - bufA[0] = 'N'; bufA[1] = 'a'; bufA[2] = 'N'; - } + d = d & 0xf; + return (d < 10) ? ('0' + d) : ('a' + (d - 10)); +} - VG_(umsg)("max-live: %'llu in %'llu blocks\n", - api->max_bytes_live, api->max_blocks_live); - VG_(umsg)("tot-alloc: %'llu in %'llu blocks (avg size %s)\n", - api->tot_bytes, api->tot_blocks, bufA); - - tl_assert(api->tot_blocks >= api->max_blocks_live); - tl_assert(api->tot_bytes >= api->max_bytes_live); - - if (api->deaths > 0) { - // Average Age at Death - ULong aad = api->deaths == 0 - ? 0 : (api->death_ages_sum / api->deaths); - // AAD as a fraction of the total program lifetime (so far) - // measured in ten-thousand-ths (aad_frac_10k == 10000 means the - // complete lifetime of the program. - ULong aad_frac_10k - = g_guest_instrs_executed == 0 - ? 0 : (10000ULL * aad) / g_guest_instrs_executed; - HChar buf[80]; // large enough - show_N_div_100(buf, aad_frac_10k); - VG_(umsg)("deaths: %'llu, at avg age %'llu " - "(%s%% of prog lifetime)\n", - api->deaths, aad, buf ); - } else { - VG_(umsg)("deaths: none (none of these blocks were freed)\n"); +// For JSON, we must escape double quote, backslash, and 0x00..0x1f. +// +// Returns the original string if no escaping was required. Returns a pointer +// to a static buffer if escaping was required. Therefore, the return value is +// only valid until the next call to this function. +static const HChar* json_escape(const HChar* s) +{ + static HChar* buf = NULL; + static SizeT bufcap = 0; + + // Do we need any escaping? + SizeT extra = 0; + const HChar* p = s; + while (*p) { + UChar c = *p; + if (c == '"' || c == '\\') { + extra += 1; + } else if (c <= 0x1f) { + extra += 5; + } + p++; } + SizeT len = p - s; - HChar bufR[80], bufW[80]; // large enough - VG_(memset)(bufR, 0, sizeof(bufR)); - VG_(memset)(bufW, 0, sizeof(bufW)); - if (api->tot_bytes > 0) { - show_N_div_100(bufR, (100ULL * api->n_reads) / api->tot_bytes); - show_N_div_100(bufW, (100ULL * api->n_writes) / api->tot_bytes); - } else { - VG_(strcat)(bufR, "Inf"); - VG_(strcat)(bufW, "Inf"); + if (extra == 0) { + // No escaping needed. + return s; } - VG_(umsg)("acc-ratios: %s rd, %s wr " - " (%'llu b-read, %'llu b-written)\n", - bufR, bufW, - api->n_reads, api->n_writes); - - VG_(pp_ExeContext)(api->ap); + // Escaping needed. (The +1 is for the NUL terminator.) Enlarge buf if + // necessary. + SizeT newcap = len + extra + 1; + if (bufcap < newcap) { + buf = VG_(realloc)("dh.json", buf, newcap); + bufcap = newcap; + } - if (api->histo && api->xsize_tag == Exactly) { - VG_(umsg)("\nAggregated access counts by offset:\n"); - VG_(umsg)("\n"); - UWord i; - if (api->xsize > 0) - VG_(umsg)("[ 0] "); - for (i = 0; i < api->xsize; i++) { - if (i > 0 && (i % 16) == 0 && i != api->xsize-1) { - VG_(umsg)("\n"); - VG_(umsg)("[%4lu] ", i); - } - VG_(umsg)("%u ", api->histo[i]); + p = s; + HChar* q = buf; + while (*p) { + UChar c = *p; + if (c == '"') { + *q++ = '\\'; + *q++ = '"'; + } else if (c == '\\') { + *q++ = '\\'; + *q++ = '\\'; + } else if (c <= 0x1f) { + *q++ = '\\'; + *q++ = 'u'; + *q++ = '0'; + *q++ = '0'; + *q++ = hex_digit_to_ascii_char((c & 0x00f0) >> 4); + *q++ = hex_digit_to_ascii_char(c & 0x000f); + } else { + *q++ = c; } - VG_(umsg)("\n"); + p++; } -} + *q = '\0'; - -/* Metric-access functions for APInfos. */ -static ULong get_metric__max_bytes_live ( APInfo* api ) { - return api->max_bytes_live; -} -static ULong get_metric__tot_bytes ( APInfo* api ) { - return api->tot_bytes; -} -static ULong get_metric__max_blocks_live ( APInfo* api ) { - return api->max_blocks_live; -} -static ULong get_metric__tot_blocks ( APInfo* api ) { - return api->tot_blocks; + return buf; } -/* Given a string, return the metric-access function and also a Bool - indicating whether we want increasing or decreasing values of the - metric. This is used twice, once in command line processing, and - then again in show_top_n_apinfos. Returns False if the given - string could not be identified.*/ -static Bool identify_metric ( /*OUT*/ULong(**get_metricP)(APInfo*), - /*OUT*/Bool* increasingP, - const HChar* metric_name ) +static void write_APInfo_frame(UInt n, DiEpoch ep, Addr ip, void* opaque) { - if (0 == VG_(strcmp)(metric_name, "max-bytes-live")) { - *get_metricP = get_metric__max_bytes_live; - *increasingP = False; - return True; - } - if (0 == VG_(strcmp)(metric_name, "tot-bytes-allocd")) { - *get_metricP = get_metric__tot_bytes; - *increasingP = False; - return True; - } - if (0 == VG_(strcmp)(metric_name, "max-blocks-live")) { - *get_metricP = get_metric__max_blocks_live; - *increasingP = False; - return True; - } - if (0 == VG_(strcmp)(metric_name, "tot-blocks-allocd")) { - *get_metricP = get_metric__tot_blocks; - *increasingP = False; - return True; - } - return False; -} + Bool* is_first = (Bool*)opaque; + InlIPCursor* iipc = VG_(new_IIPC)(ep, ip); + do { + const HChar* buf = VG_(describe_IP)(ep, ip, iipc); -static void show_top_n_apinfos ( void ) -{ - Int i; - UWord keyW, valW; - ULong (*get_metric)(APInfo*); - Bool increasing; + // Skip entries in vg_replace_malloc.c (e.g. `malloc`, `calloc`, + // `realloc`, `operator new`) because they're boring and clog up the + // output. + if (VG_(strstr)(buf, "vg_replace_malloc.c")) { + continue; + } - const HChar* metric_name = clo_sort_by; - tl_assert(metric_name); // ensured by clo processing + // If this description has been seen before, get its number. Otherwise, + // give it a new number and put it in the table. + UWord keyW = 0, valW = 0; + UWord frame_n = 0; + Bool found = VG_(lookupFM)(frame_tbl, &keyW, &valW, (UWord)buf); + if (found) { + //const HChar* str = (const HChar*)keyW; + //tl_assert(0 == VG_(strcmp)(buf, str)); + frame_n = valW; + } else { + // `buf` is a static buffer, we must copy it. + const HChar* str = VG_(strdup)("dh.frame_tbl.3", buf); + frame_n = next_frame_n++; + Bool present = VG_(addToFM)(frame_tbl, (UWord)str, frame_n); + tl_assert(!present); + } - Bool ok = identify_metric( &get_metric, &increasing, metric_name ); - tl_assert(ok); // ensured by clo processing + FP("%c%lu", *is_first ? '[' : ',', frame_n); + *is_first = False; - VG_(umsg)("\n"); - VG_(umsg)("======== ORDERED BY %s \"%s\": " - "top %d allocators ========\n", - increasing ? "increasing" : "decreasing", - metric_name, clo_show_top_n ); + } while (VG_(next_IIPC)(iipc)); - // Clear all .shown bits - VG_(initIterFM)( apinfo ); - while (VG_(nextIterFM)( apinfo, &keyW, &valW )) { - APInfo* api = (APInfo*)valW; - tl_assert(api && api->ap == (ExeContext*)keyW); - api->shown = False; - } - VG_(doneIterFM)( apinfo ); + VG_(delete_IIPC)(iipc); +}; - // Now print the top N entries. Each one requires a - // complete scan of the set. Duh. - for (i = 0; i < clo_show_top_n; i++) { - ULong best_metric = increasing ? ~0ULL : 0ULL; - APInfo* best_api = NULL; +static void write_APInfo(APInfo* api, Bool is_first) +{ + tl_assert(api->total_blocks >= api->max_blocks); + tl_assert(api->total_bytes >= api->max_bytes); + + FP(" %c{\"tb\":%llu,\"tbk\":%llu,\"tli\":%llu\n", + is_first ? '[' : ',', + api->total_bytes, api->total_blocks, api->total_lifetimes_instrs); + FP(" ,\"mb\":%llu,\"mbk\":%llu\n", + api->max_bytes, api->max_blocks); + FP(" ,\"gb\":%llu,\"gbk\":%llu\n", + api->at_tgmax_bytes, api->at_tgmax_blocks); + FP(" ,\"fb\":%llu,\"fbk\":%llu\n", + api->curr_bytes, api->curr_blocks); + FP(" ,\"rb\":%llu,\"wb\":%llu\n", + api->reads_bytes, api->writes_bytes); - VG_(initIterFM)( apinfo ); - while (VG_(nextIterFM)( apinfo, &keyW, &valW )) { - APInfo* api = (APInfo*)valW; - if (api->shown) - continue; - ULong metric = get_metric(api); - if (increasing ? (metric < best_metric) : (metric > best_metric)) { - best_metric = metric; - best_api = api; + if (api->histo && api->xsize_tag == Exactly) { + FP(" ,\"acc\":["); + + // Simple run-length encoding: when N entries in a row have the same + // value M, we print "-N,M". If there is just one in a row, we just + // print "M". This reduces file size significantly. + UShort repval = 0; + Int reps = 0; + for (UWord i = 0; i < api->xsize; i++) { + UShort h = api->histo[i]; + if (repval == h) { + // Continue current run. + reps++; + } else { + // End of run; print it. + if (reps == 1) { + FP("%u,", repval); + } else if (reps > 1) { + FP("-%d,%u,", reps, repval); + } + reps = 1; + repval = h; } } - VG_(doneIterFM)( apinfo ); - - if (!best_api) - break; // all APIs have been shown. Stop. + // Print the final run. + if (reps == 1) { + FP("%u", repval); + } else if (reps > 1) { + FP("-%d,%u", reps, repval); + } - VG_(umsg)("\n"); - VG_(umsg)("-------------------- %d of %d --------------------\n", - i+1, clo_show_top_n ); - show_APInfo(best_api); - best_api->shown = True; + FP("]\n"); } - VG_(umsg)("\n"); + FP(" ,\"fs\":"); + Bool is_first_frame = True; + VG_(apply_ExeContext)(write_APInfo_frame, &is_first_frame, api->ap); + FP("]\n"); + + FP(" }\n"); } +static void write_APInfos(void) +{ + UWord keyW, valW; + + FP(",\"aps\":\n"); + + VG_(initIterFM)(apinfo); + Bool is_first = True; + while (VG_(nextIterFM)(apinfo, &keyW, &valW)) { + APInfo* api = (APInfo*)valW; + tl_assert(api && api->ap == (ExeContext*)keyW); + write_APInfo(api, is_first); + is_first = False; + } + VG_(doneIterFM)(apinfo); + + if (is_first) { + // We didn't print any elements. This happens if apinfo is empty. + FP(" [\n"); + } + + FP(" ]\n"); +} static void dh_fini(Int exit_status) { - // Before printing statistics, we must harvest access counts for - // all the blocks that are still alive. Not doing so gives - // access ratios which are too low (zero, in the worst case) - // for such blocks, since the accesses that do get made will - // (if we skip this step) not get folded into the AP summaries. + // This function does lots of allocations that it doesn't bother to free, + // because execution is almost over anyway. + + // Total bytes might be at a possible peak. + check_for_peak(); + + // Before printing statistics, we must harvest various stats (such as + // lifetimes and accesses) for all the blocks that are still alive. UWord keyW, valW; VG_(initIterFM)( interval_tree ); while (VG_(nextIterFM)( interval_tree, &keyW, &valW )) { @@ -1291,46 +1344,7 @@ static void dh_fini(Int exit_status) } VG_(doneIterFM)( interval_tree ); - // show results - VG_(umsg)("======== SUMMARY STATISTICS ========\n"); - VG_(umsg)("\n"); - VG_(umsg)("guest_insns: %'llu\n", g_guest_instrs_executed); - VG_(umsg)("\n"); - VG_(umsg)("max_live: %'llu in %'llu blocks\n", - g_max_bytes_live, g_max_blocks_live); - VG_(umsg)("\n"); - VG_(umsg)("tot_alloc: %'llu in %'llu blocks\n", - g_tot_bytes, g_tot_blocks); - VG_(umsg)("\n"); - if (g_tot_bytes > 0) { - VG_(umsg)("insns per allocated byte: %'llu\n", - g_guest_instrs_executed / g_tot_bytes); - VG_(umsg)("\n"); - } - - show_top_n_apinfos(); - - VG_(umsg)("\n"); - VG_(umsg)("\n"); - VG_(umsg)("==============================================================\n"); - VG_(umsg)("\n"); - VG_(umsg)("Some hints: (see --help for command line option details):\n"); - VG_(umsg)("\n"); - VG_(umsg)("* summary stats for whole program are at the top of this output\n"); - VG_(umsg)("\n"); - VG_(umsg)("* --show-top-n= controls how many alloc points are shown.\n"); - VG_(umsg)(" You probably want to set it much higher than\n"); - VG_(umsg)(" the default value (10)\n"); - VG_(umsg)("\n"); - VG_(umsg)("* --sort-by= specifies the sort key for output.\n"); - VG_(umsg)(" See --help for details.\n"); - VG_(umsg)("\n"); - VG_(umsg)("* Each allocation stack, by default 12 frames, counts as\n"); - VG_(umsg)(" a separate alloc point. This causes the data to be spread out\n"); - VG_(umsg)(" over far too many alloc points. I strongly suggest using\n"); - VG_(umsg)(" --num-callers=4 or some such, to reduce the spreading.\n"); - VG_(umsg)("\n"); - + // Stats. if (VG_(clo_stats)) { VG_(dmsg)(" dhat: find_Block_containing:\n"); VG_(dmsg)(" found: %'lu (%'lu cached + %'lu uncached)\n", @@ -1340,8 +1354,94 @@ static void dh_fini(Int exit_status) VG_(dmsg)(" notfound: %'lu\n", stats__n_fBc_notfound); VG_(dmsg)("\n"); } -} + // Create the frame table, and insert the special "[root]" node at index 0. + frame_tbl = VG_(newFM)(VG_(malloc), + "dh.frame_tbl.1", + VG_(free), + frame_cmp); + const HChar* root = VG_(strdup)("dh.frame_tbl.2", "[root]"); + Bool present = VG_(addToFM)(frame_tbl, (UWord)root, 0); + tl_assert(!present); + next_frame_n = 1; + + // Setup output filename. Nb: it's important to do this now, i.e. as late + // as possible. If we do it at start-up and the program forks and the + // output file format string contains a %p (pid) specifier, both the parent + // and child will incorrectly write to the same file; this happened in + // 3.3.0. + HChar* dhat_out_file = + VG_(expand_file_name)("--dhat-out-file", clo_dhat_out_file); + + fp = VG_(fopen)(dhat_out_file, VKI_O_CREAT|VKI_O_TRUNC|VKI_O_WRONLY, + VKI_S_IRUSR|VKI_S_IWUSR); + if (!fp) { + VG_(umsg)("error: can't open DHAT output file '%s'\n", dhat_out_file); + return; + } + + // Write to data file. + FP("{\"dhatFileVersion\":1\n"); + + // The command. + const HChar* exe = VG_(args_the_exename); + FP(",\"cmd\":\"%s", json_escape(exe)); + for (Word i = 0; i < VG_(sizeXA)(VG_(args_for_client)); i++) { + const HChar* arg = *(HChar**)VG_(indexXA)(VG_(args_for_client), i); + FP(" %s", json_escape(arg)); + } + FP("\"\n"); + + // The PID. + FP(",\"pid\":%d\n", VG_(getpid)()); + + // Times. + FP(",\"mi\":%llu,\"ei\":%llu\n", g_max_instrs, g_curr_instrs); + + // APs. + write_APInfos(); + + // Frame table. + FP(",\"ftbl\":\n"); + + // The frame table maps strings to numbers. We want to print it ordered by + // numbers. So we create an array and fill it in from the frame table, then + // print that. + UWord n_frames = next_frame_n; + const HChar** frames = + VG_(malloc)("dh.frames", n_frames * sizeof(const HChar*)); + VG_(initIterFM)(frame_tbl); + while (VG_(nextIterFM)(frame_tbl, &keyW, &valW)) { + const HChar* str = (const HChar*)keyW; + UWord n = valW; + frames[n] = str; + } + VG_(doneIterFM)(frame_tbl); + + for (UWord i = 0; i < n_frames; i++) { + FP(" %c\"%s\"\n", i == 0 ? '[' : ',', json_escape(frames[i])); + } + FP(" ]\n"); + + FP("}\n"); + + VG_(fclose)(fp); + fp = NULL; + + if (VG_(clo_verbosity) == 0) { + return; + } + + // Print brief global stats. + VG_(umsg)("Total: %'llu bytes in %'llu blocks\n", + g_total_bytes, g_total_blocks); + VG_(umsg)("At t-gmax: %'llu bytes in %'llu blocks\n", + g_max_bytes, g_max_blocks); + VG_(umsg)("At t-end: %'llu bytes in %'llu blocks\n", + g_curr_bytes, g_curr_blocks); + VG_(umsg)("Reads: %'llu bytes\n", g_reads_bytes); + VG_(umsg)("Writes: %'llu bytes\n", g_writes_bytes); +} //------------------------------------------------------------// //--- Initialisation ---// @@ -1357,7 +1457,7 @@ static void dh_pre_clo_init(void) VG_(details_version) (NULL); VG_(details_description) ("a dynamic heap analysis tool"); VG_(details_copyright_author)( - "Copyright (C) 2010-2017, and GNU GPL'd, by Mozilla Inc"); + "Copyright (C) 2010-2018, and GNU GPL'd, by Mozilla Foundation"); VG_(details_bug_reports_to) (VG_BUGS_TO); // Basic functions. @@ -1395,12 +1495,12 @@ static void dh_pre_clo_init(void) tl_assert(!fbc_cache1); interval_tree = VG_(newFM)( VG_(malloc), - "dh.main.interval_tree.1", + "dh.interval_tree.1", VG_(free), interval_tree_Cmp ); apinfo = VG_(newFM)( VG_(malloc), - "dh.main.apinfo.1", + "dh.apinfo.1", VG_(free), NULL/*unboxedcmp*/ ); } diff --git a/dhat/dh_test.js b/dhat/dh_test.js new file mode 100644 index 0000000000..7a15261c57 --- /dev/null +++ b/dhat/dh_test.js @@ -0,0 +1,2553 @@ + +//--------------------------------------------------------------------*/ +//--- DHAT: a Dynamic Heap Analysis Tool dh_test.js ---*/ +//--------------------------------------------------------------------*/ + +/* + This file is part of DHAT, a Valgrind tool for profiling the + heap usage of programs. + + Copyright (C) 2018 Mozilla Foundation + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + 02111-1307, USA. + + The GNU General Public License is contained in the file COPYING. +*/ + +// We can't fully automate testing of a web app via the normal Valgrind +// regression testing. Instead we have this code, which is executed when +// dh_view.html is loaded with a "?test=1" parameter. +// +// Things tested by this file: +// - Tree building, with multiple sort metrics. +// - Text content of the displayed tree. +// +// Things not tested by this file: +// - Output from DHAT itself (unless that output is regenerated when necessary +// and copy-and-pasted in the "input" fields in this file). +// - Interactions with the "Load" button and "Sort metric" menu. +// - File loading and parsing. +// - Non-text content of the displayed tree (e.g. node colours, sortKey +// highlighting). +// - Tree interactions (collapsing and expanding of nodes). + +"use strict"; + +// Test inputs are copied verbatim from DHAT output files, not as strings but +// as actual JavaScript code. This works because output files are JSON, and +// JSON is valid JavaScript. +// +// Expected outputs are paired with a sort metric, and copied verbatim from the +// DHAT viewer. +let tests = [] + +//--------------------------------------------------------------------------- +// empty (corresponds to dhat/tests/empty.c) +//--------------------------------------------------------------------------- + +let empty = { + name: "empty", + input: +//--------------------------------------------------------------------------- +{"dhatFileVersion":1 +,"cmd":"./empty" +,"pid":23431 +,"mi":0,"ei":248602 +,"aps": + [ + ] +,"ftbl": + ["[root]" + ] +} +//--------------------------------------------------------------------------- + , + outputs: [ + { + label: "Total (bytes)", + expected: +//--------------------------------------------------------------------------- +`\ +Invocation { + Command: ./empty + PID: 23431 +} + +Times { + t-gmax: 0 instrs (0% of program duration) + t-end: 248,602 instrs +} + +─ AP 1/1 { + Total: 0 bytes (0%, 0/Minstr) in 0 blocks (0%, 0/Minstr), avg size 0 bytes, avg lifetime 0 instrs (0% of program duration) + At t-gmax: 0 bytes (0%) in 0 blocks (0%), avg size 0 bytes + At t-end: 0 bytes (0%) in 0 blocks (0%), avg size 0 bytes + Reads: 0 bytes (0%, 0/Minstr), 0/byte + Writes: 0 bytes (0%, 0/Minstr), 0/byte + Allocated at { + #0: [root] + } + } + +AP significance threshold: total >= 0 bytes (0%) +` +//--------------------------------------------------------------------------- + } + ] +}; +tests.push(empty); + +//--------------------------------------------------------------------------- +// single (corresponds to dhat/tests/single.c) +//--------------------------------------------------------------------------- + +let single = { + name: "single", + input: +//--------------------------------------------------------------------------- +{"dhatFileVersion":1 +,"cmd":"./single" +,"pid":30563 +,"mi":242900,"ei":249824 +,"aps": + [{"tb":16,"tbk":1,"tli":6924 + ,"mb":16,"mbk":1 + ,"gb":16,"gbk":1 + ,"fb":16,"fbk":1 + ,"rb":0,"wb":12 + ,"acc":[-4,3,-12,0] + ,"fs":[1] + } + ] +,"ftbl": + ["[root]" + ,"0x10865B: main (single.cpp:4)" + ] +} +//--------------------------------------------------------------------------- + , + outputs: [ + { + label: "Total (bytes)", + expected: +//--------------------------------------------------------------------------- +`\ +Invocation { + Command: ./single + PID: 30563 +} + +Times { + t-gmax: 242,900 instrs (97.23% of program duration) + t-end: 249,824 instrs +} + +─ AP 1/1 { + Total: 16 bytes (100%, 64.05/Minstr) in 1 blocks (100%, 4/Minstr), avg size 16 bytes, avg lifetime 6,924 instrs (2.77% of program duration) + At t-gmax: 16 bytes (100%) in 1 blocks (100%), avg size 16 bytes + At t-end: 16 bytes (100%) in 1 blocks (100%), avg size 16 bytes + Reads: 0 bytes (0%, 0/Minstr), 0/byte + Writes: 12 bytes (100%, 48.03/Minstr), 0.75/byte + Accesses: { + [ 0] 3 〃 〃 〃 - - - - - - - - - - - - + } + Allocated at { + #0: [root] + #1: 0x10865B: main (single.cpp:4) + } + } + +AP significance threshold: total >= 0.16 bytes (1%) +` +//--------------------------------------------------------------------------- + } + ] +}; +tests.push(single); + +//--------------------------------------------------------------------------- +// subseqs (a synthetic test for locations that are subsequences of other +// locations, which are rare but can happen in practice, esp. with recursion) +//--------------------------------------------------------------------------- + +let subseqs = { + name: "subseqs", + input: +//--------------------------------------------------------------------------- +{"dhatFileVersion":1 +,"cmd":"subseqs" +,"pid":0 +,"mi":10000,"ei":20000 +,"aps": + [{"tb":15,"tbk":1,"tli":1000 + ,"mb":15,"mbk":1 + ,"gb":15,"gbk":1 + ,"fb":0,"fbk":0 + ,"rb":0,"wb":0 + ,"acc":[-15,0] + ,"fs":[1,2,3] + } + ,{"tb":14,"tbk":1,"tli":1000 + ,"mb":14,"mbk":1 + ,"gb":14,"gbk":1 + ,"fb":0,"fbk":0 + ,"rb":0,"wb":0 + ,"acc":[-14,0] + ,"fs":[1,2,3,3] + } + ,{"tb":13,"tbk":1,"tli":1000 + ,"mb":13,"mbk":1 + ,"gb":13,"gbk":1 + ,"fb":0,"fbk":0 + ,"rb":0,"wb":0 + ,"acc":[-13,0] + ,"fs":[1,2,3,3,3] + } + ,{"tb":12,"tbk":1,"tli":1000 + ,"mb":12,"mbk":1 + ,"gb":12,"gbk":1 + ,"fb":0,"fbk":0 + ,"rb":0,"wb":0 + ,"acc":[-12,0] + ,"fs":[4,5,6,6,6] + } + ,{"tb":11,"tbk":1,"tli":1000 + ,"mb":11,"mbk":1 + ,"gb":11,"gbk":1 + ,"fb":0,"fbk":0 + ,"rb":0,"wb":0 + ,"acc":[-11,0] + ,"fs":[4,5,6,6] + } + ,{"tb":10,"tbk":1,"tli":1000 + ,"mb":10,"mbk":1 + ,"gb":10,"gbk":1 + ,"fb":0,"fbk":0 + ,"rb":0,"wb":0 + ,"acc":[-10,0] + ,"fs":[4,5,6] + } + ,{"tb":9,"tbk":1,"tli":1000 + ,"mb":9,"mbk":1 + ,"gb":9,"gbk":1 + ,"fb":0,"fbk":0 + ,"rb":0,"wb":0 + ,"acc":[-9,0] + ,"fs":[7,8,9] + } + ,{"tb":8,"tbk":1,"tli":1000 + ,"mb":8,"mbk":1 + ,"gb":8,"gbk":1 + ,"fb":0,"fbk":0 + ,"rb":0,"wb":0 + ,"acc":[-8,0] + ,"fs":[7,8,10] + } + ,{"tb":7,"tbk":1,"tli":1000 + ,"mb":7,"mbk":1 + ,"gb":7,"gbk":1 + ,"fb":0,"fbk":0 + ,"rb":0,"wb":0 + ,"acc":[-7,0] + ,"fs":[7,8] + } + ] +,"ftbl": + ["[root]" + ,"a()" + ,"b()" + ,"c()" + ,"d()" + ,"e()" + ,"f()" + ,"g()" + ,"h()" + ,"i()" + ,"j()" + ] +} +//--------------------------------------------------------------------------- + , + outputs: [ + { + label: "Total (bytes)", + expected: +//--------------------------------------------------------------------------- +`\ +Invocation { + Command: subseqs + PID: 0 +} + +Times { + t-gmax: 10,000 instrs (50% of program duration) + t-end: 20,000 instrs +} + +▼ AP 1/1 (3 children) { + Total: 99 bytes (100%, 4,950/Minstr) in 9 blocks (100%, 450/Minstr), avg size 11 bytes, avg lifetime 1,000 instrs (5% of program duration) + At t-gmax: 99 bytes (100%) in 9 blocks (100%), avg size 11 bytes + At t-end: 0 bytes (0%) in 0 blocks (0%), avg size 0 bytes + Reads: 0 bytes (0%, 0/Minstr), 0/byte + Writes: 0 bytes (0%, 0/Minstr), 0/byte + Allocated at { + #0: [root] + } + } + ├─▼ AP 1.1/3 (2 children) { + │ Total: 42 bytes (42.42%, 2,100/Minstr) in 3 blocks (33.33%, 150/Minstr), avg size 14 bytes, avg lifetime 1,000 instrs (5% of program duration) + │ At t-gmax: 42 bytes (42.42%) in 3 blocks (33.33%), avg size 14 bytes + │ At t-end: 0 bytes (0%) in 0 blocks (0%), avg size 0 bytes + │ Reads: 0 bytes (0%, 0/Minstr), 0/byte + │ Writes: 0 bytes (0%, 0/Minstr), 0/byte + │ Allocated at { + │ #1: a() + │ #2: b() + │ #3: c() + │ } + │ } + │ ├─▼ AP 1.1.1/2 (2 children) { + │ │ Total: 27 bytes (27.27%, 1,350/Minstr) in 2 blocks (22.22%, 100/Minstr), avg size 13.5 bytes, avg lifetime 1,000 instrs (5% of program duration) + │ │ At t-gmax: 27 bytes (27.27%) in 2 blocks (22.22%), avg size 13.5 bytes + │ │ At t-end: 0 bytes (0%) in 0 blocks (0%), avg size 0 bytes + │ │ Reads: 0 bytes (0%, 0/Minstr), 0/byte + │ │ Writes: 0 bytes (0%, 0/Minstr), 0/byte + │ │ Allocated at { + │ │ ^1: a() + │ │ ^2: b() + │ │ ^3: c() + │ │ #4: c() + │ │ } + │ │ } + │ │ ├── AP 1.1.1.1/2 { + │ │ │ Total: 14 bytes (14.14%, 700/Minstr) in 1 blocks (11.11%, 50/Minstr), avg size 14 bytes, avg lifetime 1,000 instrs (5% of program duration) + │ │ │ Max: 14 bytes in 1 blocks, avg size 14 bytes + │ │ │ At t-gmax: 14 bytes (14.14%) in 1 blocks (11.11%), avg size 14 bytes + │ │ │ At t-end: 0 bytes (0%) in 0 blocks (0%), avg size 0 bytes + │ │ │ Reads: 0 bytes (0%, 0/Minstr), 0/byte + │ │ │ Writes: 0 bytes (0%, 0/Minstr), 0/byte + │ │ │ Accesses: { + │ │ │ [ 0] - - - - - - - - - - - - - - + │ │ │ } + │ │ │ Allocated at { + │ │ │ ^1: a() + │ │ │ ^2: b() + │ │ │ ^3: c() + │ │ │ ^4: c() + │ │ │ } + │ │ │ } + │ │ └── AP 1.1.1.2/2 { + │ │ Total: 13 bytes (13.13%, 650/Minstr) in 1 blocks (11.11%, 50/Minstr), avg size 13 bytes, avg lifetime 1,000 instrs (5% of program duration) + │ │ Max: 13 bytes in 1 blocks, avg size 13 bytes + │ │ At t-gmax: 13 bytes (13.13%) in 1 blocks (11.11%), avg size 13 bytes + │ │ At t-end: 0 bytes (0%) in 0 blocks (0%), avg size 0 bytes + │ │ Reads: 0 bytes (0%, 0/Minstr), 0/byte + │ │ Writes: 0 bytes (0%, 0/Minstr), 0/byte + │ │ Accesses: { + │ │ [ 0] - - - - - - - - - - - - - + │ │ } + │ │ Allocated at { + │ │ ^1: a() + │ │ ^2: b() + │ │ ^3: c() + │ │ ^4: c() + │ │ #5: c() + │ │ } + │ │ } + │ └── AP 1.1.2/2 { + │ Total: 15 bytes (15.15%, 750/Minstr) in 1 blocks (11.11%, 50/Minstr), avg size 15 bytes, avg lifetime 1,000 instrs (5% of program duration) + │ Max: 15 bytes in 1 blocks, avg size 15 bytes + │ At t-gmax: 15 bytes (15.15%) in 1 blocks (11.11%), avg size 15 bytes + │ At t-end: 0 bytes (0%) in 0 blocks (0%), avg size 0 bytes + │ Reads: 0 bytes (0%, 0/Minstr), 0/byte + │ Writes: 0 bytes (0%, 0/Minstr), 0/byte + │ Accesses: { + │ [ 0] - - - - - - - - - - - - - - - + │ } + │ Allocated at { + │ ^1: a() + │ ^2: b() + │ ^3: c() + │ } + │ } + ├─▼ AP 1.2/3 (2 children) { + │ Total: 33 bytes (33.33%, 1,650/Minstr) in 3 blocks (33.33%, 150/Minstr), avg size 11 bytes, avg lifetime 1,000 instrs (5% of program duration) + │ At t-gmax: 33 bytes (33.33%) in 3 blocks (33.33%), avg size 11 bytes + │ At t-end: 0 bytes (0%) in 0 blocks (0%), avg size 0 bytes + │ Reads: 0 bytes (0%, 0/Minstr), 0/byte + │ Writes: 0 bytes (0%, 0/Minstr), 0/byte + │ Allocated at { + │ #1: d() + │ #2: e() + │ #3: f() + │ } + │ } + │ ├─▼ AP 1.2.1/2 (2 children) { + │ │ Total: 23 bytes (23.23%, 1,150/Minstr) in 2 blocks (22.22%, 100/Minstr), avg size 11.5 bytes, avg lifetime 1,000 instrs (5% of program duration) + │ │ At t-gmax: 23 bytes (23.23%) in 2 blocks (22.22%), avg size 11.5 bytes + │ │ At t-end: 0 bytes (0%) in 0 blocks (0%), avg size 0 bytes + │ │ Reads: 0 bytes (0%, 0/Minstr), 0/byte + │ │ Writes: 0 bytes (0%, 0/Minstr), 0/byte + │ │ Allocated at { + │ │ ^1: d() + │ │ ^2: e() + │ │ ^3: f() + │ │ #4: f() + │ │ } + │ │ } + │ │ ├── AP 1.2.1.1/2 { + │ │ │ Total: 12 bytes (12.12%, 600/Minstr) in 1 blocks (11.11%, 50/Minstr), avg size 12 bytes, avg lifetime 1,000 instrs (5% of program duration) + │ │ │ Max: 12 bytes in 1 blocks, avg size 12 bytes + │ │ │ At t-gmax: 12 bytes (12.12%) in 1 blocks (11.11%), avg size 12 bytes + │ │ │ At t-end: 0 bytes (0%) in 0 blocks (0%), avg size 0 bytes + │ │ │ Reads: 0 bytes (0%, 0/Minstr), 0/byte + │ │ │ Writes: 0 bytes (0%, 0/Minstr), 0/byte + │ │ │ Accesses: { + │ │ │ [ 0] - - - - - - - - - - - - + │ │ │ } + │ │ │ Allocated at { + │ │ │ ^1: d() + │ │ │ ^2: e() + │ │ │ ^3: f() + │ │ │ ^4: f() + │ │ │ #5: f() + │ │ │ } + │ │ │ } + │ │ └── AP 1.2.1.2/2 { + │ │ Total: 11 bytes (11.11%, 550/Minstr) in 1 blocks (11.11%, 50/Minstr), avg size 11 bytes, avg lifetime 1,000 instrs (5% of program duration) + │ │ Max: 11 bytes in 1 blocks, avg size 11 bytes + │ │ At t-gmax: 11 bytes (11.11%) in 1 blocks (11.11%), avg size 11 bytes + │ │ At t-end: 0 bytes (0%) in 0 blocks (0%), avg size 0 bytes + │ │ Reads: 0 bytes (0%, 0/Minstr), 0/byte + │ │ Writes: 0 bytes (0%, 0/Minstr), 0/byte + │ │ Accesses: { + │ │ [ 0] - - - - - - - - - - - + │ │ } + │ │ Allocated at { + │ │ ^1: d() + │ │ ^2: e() + │ │ ^3: f() + │ │ ^4: f() + │ │ } + │ │ } + │ └── AP 1.2.2/2 { + │ Total: 10 bytes (10.1%, 500/Minstr) in 1 blocks (11.11%, 50/Minstr), avg size 10 bytes, avg lifetime 1,000 instrs (5% of program duration) + │ Max: 10 bytes in 1 blocks, avg size 10 bytes + │ At t-gmax: 10 bytes (10.1%) in 1 blocks (11.11%), avg size 10 bytes + │ At t-end: 0 bytes (0%) in 0 blocks (0%), avg size 0 bytes + │ Reads: 0 bytes (0%, 0/Minstr), 0/byte + │ Writes: 0 bytes (0%, 0/Minstr), 0/byte + │ Accesses: { + │ [ 0] - - - - - - - - - - + │ } + │ Allocated at { + │ ^1: d() + │ ^2: e() + │ ^3: f() + │ } + │ } + └─▼ AP 1.3/3 (3 children) { + Total: 24 bytes (24.24%, 1,200/Minstr) in 3 blocks (33.33%, 150/Minstr), avg size 8 bytes, avg lifetime 1,000 instrs (5% of program duration) + At t-gmax: 24 bytes (24.24%) in 3 blocks (33.33%), avg size 8 bytes + At t-end: 0 bytes (0%) in 0 blocks (0%), avg size 0 bytes + Reads: 0 bytes (0%, 0/Minstr), 0/byte + Writes: 0 bytes (0%, 0/Minstr), 0/byte + Allocated at { + #1: g() + #2: h() + } + } + ├── AP 1.3.1/3 { + │ Total: 9 bytes (9.09%, 450/Minstr) in 1 blocks (11.11%, 50/Minstr), avg size 9 bytes, avg lifetime 1,000 instrs (5% of program duration) + │ Max: 9 bytes in 1 blocks, avg size 9 bytes + │ At t-gmax: 9 bytes (9.09%) in 1 blocks (11.11%), avg size 9 bytes + │ At t-end: 0 bytes (0%) in 0 blocks (0%), avg size 0 bytes + │ Reads: 0 bytes (0%, 0/Minstr), 0/byte + │ Writes: 0 bytes (0%, 0/Minstr), 0/byte + │ Accesses: { + │ [ 0] - - - - - - - - - + │ } + │ Allocated at { + │ ^1: g() + │ ^2: h() + │ #3: i() + │ } + │ } + ├── AP 1.3.2/3 { + │ Total: 8 bytes (8.08%, 400/Minstr) in 1 blocks (11.11%, 50/Minstr), avg size 8 bytes, avg lifetime 1,000 instrs (5% of program duration) + │ Max: 8 bytes in 1 blocks, avg size 8 bytes + │ At t-gmax: 8 bytes (8.08%) in 1 blocks (11.11%), avg size 8 bytes + │ At t-end: 0 bytes (0%) in 0 blocks (0%), avg size 0 bytes + │ Reads: 0 bytes (0%, 0/Minstr), 0/byte + │ Writes: 0 bytes (0%, 0/Minstr), 0/byte + │ Accesses: { + │ [ 0] - - - - - - - - + │ } + │ Allocated at { + │ ^1: g() + │ ^2: h() + │ #3: j() + │ } + │ } + └── AP 1.3.3/3 { + Total: 7 bytes (7.07%, 350/Minstr) in 1 blocks (11.11%, 50/Minstr), avg size 7 bytes, avg lifetime 1,000 instrs (5% of program duration) + Max: 7 bytes in 1 blocks, avg size 7 bytes + At t-gmax: 7 bytes (7.07%) in 1 blocks (11.11%), avg size 7 bytes + At t-end: 0 bytes (0%) in 0 blocks (0%), avg size 0 bytes + Reads: 0 bytes (0%, 0/Minstr), 0/byte + Writes: 0 bytes (0%, 0/Minstr), 0/byte + Accesses: { + [ 0] - - - - - - - + } + Allocated at { + ^1: g() + ^2: h() + } + } + +AP significance threshold: total >= 0.99 bytes (1%) +` +//--------------------------------------------------------------------------- + } + ] +}; +tests.push(subseqs); + +//--------------------------------------------------------------------------- +// acc (corresponds to dhat/tests/acc.c) +//--------------------------------------------------------------------------- + +let acc = { + name: "acc", + input: +//--------------------------------------------------------------------------- +{"dhatFileVersion":1 +,"cmd":"./acc" +,"pid":23513 +,"mi":265120,"ei":1337753 +,"aps": + [{"tb":32,"tbk":1,"tli":4751 + ,"mb":32,"mbk":1 + ,"gb":0,"gbk":0 + ,"fb":0,"fbk":0 + ,"rb":0,"wb":496 + ,"acc":[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31] + ,"fs":[1] + } + ,{"tb":20,"tbk":1,"tli":106 + ,"mb":20,"mbk":1 + ,"gb":0,"gbk":0 + ,"fb":0,"fbk":0 + ,"rb":4,"wb":48 + ,"acc":[-4,2,-4,0,-4,1,-4,0,-4,10] + ,"fs":[2] + } + ,{"tb":33,"tbk":1,"tli":39 + ,"mb":33,"mbk":1 + ,"gb":0,"gbk":0 + ,"fb":0,"fbk":0 + ,"rb":0,"wb":1 + ,"acc":[-32,0,1] + ,"fs":[3] + } + ,{"tb":1024,"tbk":1,"tli":15179 + ,"mb":1024,"mbk":1 + ,"gb":0,"gbk":0 + ,"fb":0,"fbk":0 + ,"rb":1024,"wb":1124 + ,"acc":[-500,2,-100,3,-424,2] + ,"fs":[4] + } + ,{"tb":1025,"tbk":1,"tli":15415 + ,"mb":1025,"mbk":1 + ,"gb":1025,"gbk":1 + ,"fb":0,"fbk":0 + ,"rb":1025,"wb":1025 + ,"fs":[5] + } + ,{"tb":100,"tbk":1,"tli":350084 + ,"mb":100,"mbk":1 + ,"gb":0,"gbk":0 + ,"fb":0,"fbk":0 + ,"rb":0,"wb":200000 + ,"acc":[-4,50000,-96,0] + ,"fs":[6,7] + } + ,{"tb":100,"tbk":1,"tli":350072 + ,"mb":100,"mbk":1 + ,"gb":0,"gbk":0 + ,"fb":0,"fbk":0 + ,"rb":0,"wb":200000 + ,"acc":[-4,50000,-96,0] + ,"fs":[6,8] + } + ,{"tb":100,"tbk":1,"tli":700084 + ,"mb":100,"mbk":1 + ,"gb":0,"gbk":0 + ,"fb":0,"fbk":0 + ,"rb":0,"wb":400000 + ,"acc":[-4,65535,-96,0] + ,"fs":[9,10] + } + ,{"tb":100,"tbk":1,"tli":700072 + ,"mb":100,"mbk":1 + ,"gb":0,"gbk":0 + ,"fb":0,"fbk":0 + ,"rb":0,"wb":400000 + ,"acc":[-4,65535,-96,0] + ,"fs":[9,11] + } + ] +,"ftbl": + ["[root]" + ,"0x10871F: main (acc.c:14)" + ,"0x108771: main (acc.c:23)" + ,"0x1087CB: main (acc.c:32)" + ,"0x1087F0: main (acc.c:37)" + ,"0x10886F: main (acc.c:47)" + ,"0x1086F1: m1 (acc.c:7)" + ,"0x1088C3: main (acc.c:54)" + ,"0x1088D1: main (acc.c:55)" + ,"0x10870B: m2 (acc.c:9)" + ,"0x108921: main (acc.c:64)" + ,"0x10892F: main (acc.c:65)" + ] +} +//--------------------------------------------------------------------------- + , + outputs: [ + { + // All blocks are freed, which means all "At t-end" values are 0, so + // sorting by atTEndBytes results in no aggregate nodes, which is what we + // want here. + label: "At t-end (bytes)", + expected: +//--------------------------------------------------------------------------- +`\ +Invocation { + Command: ./acc + PID: 23513 +} + +Times { + t-gmax: 265,120 instrs (19.82% of program duration) + t-end: 1,337,753 instrs +} + +▼ AP 1/1 (7 children) { + Total: 2,534 bytes (100%, 1,894.22/Minstr) in 9 blocks (100%, 6.73/Minstr), avg size 281.56 bytes, avg lifetime 237,311.33 instrs (17.74% of program duration) + At t-gmax: 1,025 bytes (100%) in 1 blocks (100%), avg size 1,025 bytes + At t-end: 0 bytes (0%) in 0 blocks (0%), avg size 0 bytes + Reads: 2,053 bytes (100%, 1,534.66/Minstr), 0.81/byte + Writes: 1,202,694 bytes (100%, 899,040.41/Minstr), 474.62/byte + Allocated at { + #0: [root] + } + } + ├── AP 1.1/7 { + │ Total: 1,025 bytes (40.45%, 766.21/Minstr) in 1 blocks (11.11%, 0.75/Minstr), avg size 1,025 bytes, avg lifetime 15,415 instrs (1.15% of program duration) + │ Max: 1,025 bytes in 1 blocks, avg size 1,025 bytes + │ At t-gmax: 1,025 bytes (100%) in 1 blocks (100%), avg size 1,025 bytes + │ At t-end: 0 bytes (0%) in 0 blocks (0%), avg size 0 bytes + │ Reads: 1,025 bytes (49.93%, 766.21/Minstr), 1/byte + │ Writes: 1,025 bytes (0.09%, 766.21/Minstr), 1/byte + │ Allocated at { + │ #1: 0x10886F: main (acc.c:47) + │ } + │ } + ├── AP 1.2/7 { + │ Total: 1,024 bytes (40.41%, 765.46/Minstr) in 1 blocks (11.11%, 0.75/Minstr), avg size 1,024 bytes, avg lifetime 15,179 instrs (1.13% of program duration) + │ Max: 1,024 bytes in 1 blocks, avg size 1,024 bytes + │ At t-gmax: 0 bytes (0%) in 0 blocks (0%), avg size 0 bytes + │ At t-end: 0 bytes (0%) in 0 blocks (0%), avg size 0 bytes + │ Reads: 1,024 bytes (49.88%, 765.46/Minstr), 1/byte + │ Writes: 1,124 bytes (0.09%, 840.21/Minstr), 1.1/byte + │ Accesses: { + │ [ 0] 2 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 + │ [ 32] 2 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 + │ [ 64] 2 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 + │ [ 96] 2 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 + │ [128] 2 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 + │ [160] 2 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 + │ [192] 2 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 + │ [224] 2 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 + │ [256] 2 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 + │ [288] 2 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 + │ [320] 2 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 + │ [352] 2 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 + │ [384] 2 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 + │ [416] 2 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 + │ [448] 2 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 + │ [480] 2 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 3 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 + │ [512] 3 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 + │ [544] 3 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 + │ [576] 3 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 2 〃 〃 〃 〃 〃 〃 〃 + │ [608] 2 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 + │ [640] 2 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 + │ [672] 2 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 + │ [704] 2 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 + │ [736] 2 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 + │ [768] 2 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 + │ [800] 2 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 + │ [832] 2 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 + │ [864] 2 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 + │ [896] 2 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 + │ [928] 2 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 + │ [960] 2 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 + │ [992] 2 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 + │ } + │ Allocated at { + │ #1: 0x1087F0: main (acc.c:37) + │ } + │ } + ├─▼ AP 1.3/7 (2 children) { + │ Total: 200 bytes (7.89%, 149.5/Minstr) in 2 blocks (22.22%, 1.5/Minstr), avg size 100 bytes, avg lifetime 350,078 instrs (26.17% of program duration) + │ At t-gmax: 0 bytes (0%) in 0 blocks (0%), avg size 0 bytes + │ At t-end: 0 bytes (0%) in 0 blocks (0%), avg size 0 bytes + │ Reads: 0 bytes (0%, 0/Minstr), 0/byte + │ Writes: 400,000 bytes (33.26%, 299,008.86/Minstr), 2,000/byte + │ Accesses: { + │ [ 0] 100000 〃 〃 〃 - - - - - - - - - - - - - - - - - - - - - - - - - - - - + │ [ 32] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + │ [ 64] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + │ [ 96] - - - - + │ } + │ Allocated at { + │ #1: 0x1086F1: m1 (acc.c:7) + │ } + │ } + │ ├── AP 1.3.1/2 { + │ │ Total: 100 bytes (3.95%, 74.75/Minstr) in 1 blocks (11.11%, 0.75/Minstr), avg size 100 bytes, avg lifetime 350,084 instrs (26.17% of program duration) + │ │ Max: 100 bytes in 1 blocks, avg size 100 bytes + │ │ At t-gmax: 0 bytes (0%) in 0 blocks (0%), avg size 0 bytes + │ │ At t-end: 0 bytes (0%) in 0 blocks (0%), avg size 0 bytes + │ │ Reads: 0 bytes (0%, 0/Minstr), 0/byte + │ │ Writes: 200,000 bytes (16.63%, 149,504.43/Minstr), 2,000/byte + │ │ Accesses: { + │ │ [ 0] 50000 〃 〃 〃 - - - - - - - - - - - - - - - - - - - - - - - - - - - - + │ │ [ 32] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + │ │ [ 64] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + │ │ [ 96] - - - - + │ │ } + │ │ Allocated at { + │ │ ^1: 0x1086F1: m1 (acc.c:7) + │ │ #2: 0x1088C3: main (acc.c:54) + │ │ } + │ │ } + │ └── AP 1.3.2/2 { + │ Total: 100 bytes (3.95%, 74.75/Minstr) in 1 blocks (11.11%, 0.75/Minstr), avg size 100 bytes, avg lifetime 350,072 instrs (26.17% of program duration) + │ Max: 100 bytes in 1 blocks, avg size 100 bytes + │ At t-gmax: 0 bytes (0%) in 0 blocks (0%), avg size 0 bytes + │ At t-end: 0 bytes (0%) in 0 blocks (0%), avg size 0 bytes + │ Reads: 0 bytes (0%, 0/Minstr), 0/byte + │ Writes: 200,000 bytes (16.63%, 149,504.43/Minstr), 2,000/byte + │ Accesses: { + │ [ 0] 50000 〃 〃 〃 - - - - - - - - - - - - - - - - - - - - - - - - - - - - + │ [ 32] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + │ [ 64] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + │ [ 96] - - - - + │ } + │ Allocated at { + │ ^1: 0x1086F1: m1 (acc.c:7) + │ #2: 0x1088D1: main (acc.c:55) + │ } + │ } + ├─▼ AP 1.4/7 (2 children) { + │ Total: 200 bytes (7.89%, 149.5/Minstr) in 2 blocks (22.22%, 1.5/Minstr), avg size 100 bytes, avg lifetime 700,078 instrs (52.33% of program duration) + │ At t-gmax: 0 bytes (0%) in 0 blocks (0%), avg size 0 bytes + │ At t-end: 0 bytes (0%) in 0 blocks (0%), avg size 0 bytes + │ Reads: 0 bytes (0%, 0/Minstr), 0/byte + │ Writes: 800,000 bytes (66.52%, 598,017.72/Minstr), 4,000/byte + │ Accesses: { + │ [ 0] ∞ 〃 〃 〃 - - - - - - - - - - - - - - - - - - - - - - - - - - - - + │ [ 32] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + │ [ 64] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + │ [ 96] - - - - + │ } + │ Allocated at { + │ #1: 0x10870B: m2 (acc.c:9) + │ } + │ } + │ ├── AP 1.4.1/2 { + │ │ Total: 100 bytes (3.95%, 74.75/Minstr) in 1 blocks (11.11%, 0.75/Minstr), avg size 100 bytes, avg lifetime 700,084 instrs (52.33% of program duration) + │ │ Max: 100 bytes in 1 blocks, avg size 100 bytes + │ │ At t-gmax: 0 bytes (0%) in 0 blocks (0%), avg size 0 bytes + │ │ At t-end: 0 bytes (0%) in 0 blocks (0%), avg size 0 bytes + │ │ Reads: 0 bytes (0%, 0/Minstr), 0/byte + │ │ Writes: 400,000 bytes (33.26%, 299,008.86/Minstr), 4,000/byte + │ │ Accesses: { + │ │ [ 0] ∞ 〃 〃 〃 - - - - - - - - - - - - - - - - - - - - - - - - - - - - + │ │ [ 32] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + │ │ [ 64] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + │ │ [ 96] - - - - + │ │ } + │ │ Allocated at { + │ │ ^1: 0x10870B: m2 (acc.c:9) + │ │ #2: 0x108921: main (acc.c:64) + │ │ } + │ │ } + │ └── AP 1.4.2/2 { + │ Total: 100 bytes (3.95%, 74.75/Minstr) in 1 blocks (11.11%, 0.75/Minstr), avg size 100 bytes, avg lifetime 700,072 instrs (52.33% of program duration) + │ Max: 100 bytes in 1 blocks, avg size 100 bytes + │ At t-gmax: 0 bytes (0%) in 0 blocks (0%), avg size 0 bytes + │ At t-end: 0 bytes (0%) in 0 blocks (0%), avg size 0 bytes + │ Reads: 0 bytes (0%, 0/Minstr), 0/byte + │ Writes: 400,000 bytes (33.26%, 299,008.86/Minstr), 4,000/byte + │ Accesses: { + │ [ 0] ∞ 〃 〃 〃 - - - - - - - - - - - - - - - - - - - - - - - - - - - - + │ [ 32] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + │ [ 64] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + │ [ 96] - - - - + │ } + │ Allocated at { + │ ^1: 0x10870B: m2 (acc.c:9) + │ #2: 0x10892F: main (acc.c:65) + │ } + │ } + ├── AP 1.5/7 { + │ Total: 33 bytes (1.3%, 24.67/Minstr) in 1 blocks (11.11%, 0.75/Minstr), avg size 33 bytes, avg lifetime 39 instrs (0% of program duration) + │ Max: 33 bytes in 1 blocks, avg size 33 bytes + │ At t-gmax: 0 bytes (0%) in 0 blocks (0%), avg size 0 bytes + │ At t-end: 0 bytes (0%) in 0 blocks (0%), avg size 0 bytes + │ Reads: 0 bytes (0%, 0/Minstr), 0/byte + │ Writes: 1 bytes (0%, 0.75/Minstr), 0.03/byte + │ Accesses: { + │ [ 0] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + │ [ 32] 1 + │ } + │ Allocated at { + │ #1: 0x1087CB: main (acc.c:32) + │ } + │ } + ├── AP 1.6/7 { + │ Total: 32 bytes (1.26%, 23.92/Minstr) in 1 blocks (11.11%, 0.75/Minstr), avg size 32 bytes, avg lifetime 4,751 instrs (0.36% of program duration) + │ Max: 32 bytes in 1 blocks, avg size 32 bytes + │ At t-gmax: 0 bytes (0%) in 0 blocks (0%), avg size 0 bytes + │ At t-end: 0 bytes (0%) in 0 blocks (0%), avg size 0 bytes + │ Reads: 0 bytes (0%, 0/Minstr), 0/byte + │ Writes: 496 bytes (0.04%, 370.77/Minstr), 15.5/byte + │ Accesses: { + │ [ 0] - 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 + │ } + │ Allocated at { + │ #1: 0x10871F: main (acc.c:14) + │ } + │ } + └── AP 1.7/7 { + Total: 20 bytes (0.79%, 14.95/Minstr) in 1 blocks (11.11%, 0.75/Minstr), avg size 20 bytes, avg lifetime 106 instrs (0.01% of program duration) + Max: 20 bytes in 1 blocks, avg size 20 bytes + At t-gmax: 0 bytes (0%) in 0 blocks (0%), avg size 0 bytes + At t-end: 0 bytes (0%) in 0 blocks (0%), avg size 0 bytes + Reads: 4 bytes (0.19%, 2.99/Minstr), 0.2/byte + Writes: 48 bytes (0%, 35.88/Minstr), 2.4/byte + Accesses: { + [ 0] 2 〃 〃 〃 - - - - 1 〃 〃 〃 - - - - 10 〃 〃 〃 + } + Allocated at { + #1: 0x108771: main (acc.c:23) + } + } + +AP significance threshold: at-t-end >= 0 bytes (0%) +` +//--------------------------------------------------------------------------- + } + ] +}; +tests.push(acc); + +//--------------------------------------------------------------------------- +// big (corresponds to dhat/tests/big.c) +//--------------------------------------------------------------------------- + +let big = { + name: "big", + input: +//--------------------------------------------------------------------------- +{"dhatFileVersion":1 +,"cmd":"./big" +,"pid":3902 +,"mi":245281,"ei":253354 +,"aps": + [{"tb":706,"tbk":1,"tli":543 + ,"mb":706,"mbk":1 + ,"gb":706,"gbk":1 + ,"fb":0,"fbk":0 + ,"rb":0,"wb":0 + ,"acc":[-706,0] + ,"fs":[1,2,3,4,5] + } + ,{"tb":5,"tbk":1,"tli":7972 + ,"mb":5,"mbk":1 + ,"gb":0,"gbk":0 + ,"fb":5,"fbk":1 + ,"rb":0,"wb":0 + ,"acc":[-5,0] + ,"fs":[1,2,3,6,7] + } + ,{"tb":30,"tbk":1,"tli":7910 + ,"mb":30,"mbk":1 + ,"gb":0,"gbk":0 + ,"fb":30,"fbk":1 + ,"rb":0,"wb":0 + ,"acc":[-30,0] + ,"fs":[1,2,8,9] + } + ,{"tb":20,"tbk":1,"tli":7857 + ,"mb":20,"mbk":1 + ,"gb":0,"gbk":0 + ,"fb":20,"fbk":1 + ,"rb":0,"wb":0 + ,"acc":[-20,0] + ,"fs":[1,10,11] + } + ,{"tb":10,"tbk":1,"tli":7792 + ,"mb":10,"mbk":1 + ,"gb":0,"gbk":0 + ,"fb":10,"fbk":1 + ,"rb":0,"wb":0 + ,"acc":[-10,0] + ,"fs":[1,12,13,14,15] + } + ,{"tb":60,"tbk":1,"tli":7709 + ,"mb":60,"mbk":1 + ,"gb":0,"gbk":0 + ,"fb":60,"fbk":1 + ,"rb":0,"wb":0 + ,"acc":[-60,0] + ,"fs":[16,17,18,19,20,21,22] + } + ,{"tb":30,"tbk":1,"tli":7622 + ,"mb":30,"mbk":1 + ,"gb":0,"gbk":0 + ,"fb":30,"fbk":1 + ,"rb":0,"wb":0 + ,"acc":[-30,0] + ,"fs":[16,17,18,23,24,25,26] + } + ,{"tb":20,"tbk":1,"tli":7528 + ,"mb":20,"mbk":1 + ,"gb":0,"gbk":0 + ,"fb":20,"fbk":1 + ,"rb":0,"wb":0 + ,"acc":[-20,0] + ,"fs":[16,17,18,23,24,27,28,29] + } + ,{"tb":7,"tbk":1,"tli":7446 + ,"mb":7,"mbk":1 + ,"gb":0,"gbk":0 + ,"fb":7,"fbk":1 + ,"rb":0,"wb":0 + ,"acc":[-7,0] + ,"fs":[16,17,18,30,31,32] + } + ,{"tb":3,"tbk":1,"tli":7375 + ,"mb":3,"mbk":1 + ,"gb":0,"gbk":0 + ,"fb":3,"fbk":1 + ,"rb":0,"wb":0 + ,"acc":[-3,0] + ,"fs":[16,17,18,33,34] + } + ,{"tb":30,"tbk":1,"tli":7299 + ,"mb":30,"mbk":1 + ,"gb":0,"gbk":0 + ,"fb":30,"fbk":1 + ,"rb":0,"wb":0 + ,"acc":[-30,0] + ,"fs":[35,36,37,38,39,40] + } + ,{"tb":20,"tbk":1,"tli":7249 + ,"mb":20,"mbk":1 + ,"gb":0,"gbk":0 + ,"fb":20,"fbk":1 + ,"rb":0,"wb":0 + ,"acc":[-20,0] + ,"fs":[41,42] + } + ,{"tb":19,"tbk":1,"tli":7207 + ,"mb":19,"mbk":1 + ,"gb":0,"gbk":0 + ,"fb":19,"fbk":1 + ,"rb":0,"wb":0 + ,"acc":[-19,0] + ,"fs":[43,44] + } + ,{"tb":9,"tbk":1,"tli":7158 + ,"mb":9,"mbk":1 + ,"gb":0,"gbk":0 + ,"fb":9,"fbk":1 + ,"rb":0,"wb":0 + ,"acc":[-9,0] + ,"fs":[45,46,47] + } + ,{"tb":8,"tbk":1,"tli":7107 + ,"mb":8,"mbk":1 + ,"gb":0,"gbk":0 + ,"fb":8,"fbk":1 + ,"rb":0,"wb":0 + ,"acc":[-8,0] + ,"fs":[45,48,49] + } + ,{"tb":7,"tbk":1,"tli":7056 + ,"mb":7,"mbk":1 + ,"gb":0,"gbk":0 + ,"fb":7,"fbk":1 + ,"rb":0,"wb":0 + ,"acc":[-7,0] + ,"fs":[45,50,51] + } + ,{"tb":5,"tbk":1,"tli":7005 + ,"mb":5,"mbk":1 + ,"gb":0,"gbk":0 + ,"fb":5,"fbk":1 + ,"rb":0,"wb":0 + ,"acc":[-5,0] + ,"fs":[45,52,53] + } + ,{"tb":1,"tbk":1,"tli":6954 + ,"mb":1,"mbk":1 + ,"gb":0,"gbk":0 + ,"fb":1,"fbk":1 + ,"rb":0,"wb":0 + ,"acc":[0] + ,"fs":[45,52,54] + } + ,{"tb":10,"tbk":1,"tli":6917 + ,"mb":10,"mbk":1 + ,"gb":0,"gbk":0 + ,"fb":10,"fbk":1 + ,"rb":0,"wb":0 + ,"acc":[-10,0] + ,"fs":[55] + } + ] +,"ftbl": + ["[root]" + ,"0x1086A1: a (big.c:10)" + ,"0x1086BB: b1 (big.c:11)" + ,"0x1086D5: c1 (big.c:12)" + ,"0x1086EF: d1 (big.c:13)" + ,"0x108A43: main (big.c:38)" + ,"0x108709: d2 (big.c:14)" + ,"0x108A5D: main (big.c:41)" + ,"0x108723: c2 (big.c:15)" + ,"0x108A67: main (big.c:42)" + ,"0x10873D: b2 (big.c:16)" + ,"0x108A71: main (big.c:43)" + ,"0x108757: b3 (big.c:17)" + ,"0x108771: e (big.c:17)" + ,"0x10878B: f (big.c:17)" + ,"0x108A7B: main (big.c:44)" + ,"0x1087A5: g (big.c:18)" + ,"0x1087BF: h (big.c:18)" + ,"0x1087D9: i (big.c:18)" + ,"0x1087F3: j2 (big.c:19)" + ,"0x10880D: k (big.c:19)" + ,"0x108827: l (big.c:19)" + ,"0x108A85: main (big.c:45)" + ,"0x108841: j3 (big.c:20)" + ,"0x10885B: m (big.c:20)" + ,"0x108875: n1 (big.c:21)" + ,"0x108A8F: main (big.c:46)" + ,"0x10888F: n2 (big.c:22)" + ,"0x1088A9: o (big.c:22)" + ,"0x108A99: main (big.c:47)" + ,"0x1088C3: p (big.c:23)" + ,"0x1088DD: q (big.c:23)" + ,"0x108AA3: main (big.c:48)" + ,"0x1088F7: r (big.c:24)" + ,"0x108AAD: main (big.c:49)" + ,"0x108911: s1 (big.c:25)" + ,"0x10892B: s2 (big.c:25)" + ,"0x108945: s3 (big.c:25)" + ,"0x10895F: s4 (big.c:25)" + ,"0x108979: s5 (big.c:25)" + ,"0x108AB7: main (big.c:50)" + ,"0x108993: t (big.c:26)" + ,"0x108AC1: main (big.c:51)" + ,"0x1089AD: u (big.c:27)" + ,"0x108ACB: main (big.c:52)" + ,"0x1089C7: v (big.c:28)" + ,"0x1089E1: w (big.c:29)" + ,"0x108AD5: main (big.c:53)" + ,"0x1089FB: x (big.c:30)" + ,"0x108ADF: main (big.c:54)" + ,"0x108A15: y (big.c:31)" + ,"0x108AE9: main (big.c:55)" + ,"0x108A2F: z (big.c:32)" + ,"0x108AF3: main (big.c:56)" + ,"0x108AFD: main (big.c:57)" + ,"0x108B07: main (big.c:60)" + ] +} +//--------------------------------------------------------------------------- + , + outputs: [ + { + label: "Total (bytes)", + expected: +//--------------------------------------------------------------------------- +`\ +Invocation { + Command: ./big + PID: 3902 +} + +Times { + t-gmax: 245,281 instrs (96.81% of program duration) + t-end: 253,354 instrs +} + +▼ AP 1/1 (7 children) { + Total: 1,000 bytes (100%, 3,947.05/Minstr) in 19 blocks (100%, 74.99/Minstr), avg size 52.63 bytes, avg lifetime 7,037.16 instrs (2.78% of program duration) + At t-gmax: 706 bytes (100%) in 1 blocks (100%), avg size 706 bytes + At t-end: 294 bytes (100%) in 18 blocks (100%), avg size 16.33 bytes + Reads: 0 bytes (0%, 0/Minstr), 0/byte + Writes: 0 bytes (0%, 0/Minstr), 0/byte + Allocated at { + #0: [root] + } + } + ├─▼ AP 1.1/7 (3 children) { + │ Total: 771 bytes (77.1%, 3,043.17/Minstr) in 5 blocks (26.32%, 19.74/Minstr), avg size 154.2 bytes, avg lifetime 6,414.8 instrs (2.53% of program duration) + │ At t-gmax: 706 bytes (100%) in 1 blocks (100%), avg size 706 bytes + │ At t-end: 65 bytes (22.11%) in 4 blocks (22.22%), avg size 16.25 bytes + │ Reads: 0 bytes (0%, 0/Minstr), 0/byte + │ Writes: 0 bytes (0%, 0/Minstr), 0/byte + │ Allocated at { + │ #1: 0x1086A1: a (big.c:10) + │ } + │ } + │ ├─▼ AP 1.1.1/3 (2 children) { + │ │ Total: 741 bytes (74.1%, 2,924.76/Minstr) in 3 blocks (15.79%, 11.84/Minstr), avg size 247 bytes, avg lifetime 5,475 instrs (2.16% of program duration) + │ │ At t-gmax: 706 bytes (100%) in 1 blocks (100%), avg size 706 bytes + │ │ At t-end: 35 bytes (11.9%) in 2 blocks (11.11%), avg size 17.5 bytes + │ │ Reads: 0 bytes (0%, 0/Minstr), 0/byte + │ │ Writes: 0 bytes (0%, 0/Minstr), 0/byte + │ │ Allocated at { + │ │ ^1: 0x1086A1: a (big.c:10) + │ │ #2: 0x1086BB: b1 (big.c:11) + │ │ } + │ │ } + │ │ ├─▼ AP 1.1.1.1/2 (2 children) { + │ │ │ Total: 711 bytes (71.1%, 2,806.35/Minstr) in 2 blocks (10.53%, 7.89/Minstr), avg size 355.5 bytes, avg lifetime 4,257.5 instrs (1.68% of program duration) + │ │ │ At t-gmax: 706 bytes (100%) in 1 blocks (100%), avg size 706 bytes + │ │ │ At t-end: 5 bytes (1.7%) in 1 blocks (5.56%), avg size 5 bytes + │ │ │ Reads: 0 bytes (0%, 0/Minstr), 0/byte + │ │ │ Writes: 0 bytes (0%, 0/Minstr), 0/byte + │ │ │ Allocated at { + │ │ │ ^1: 0x1086A1: a (big.c:10) + │ │ │ ^2: 0x1086BB: b1 (big.c:11) + │ │ │ #3: 0x1086D5: c1 (big.c:12) + │ │ │ } + │ │ │ } + │ │ │ ├── AP 1.1.1.1.1/2 { + │ │ │ │ Total: 706 bytes (70.6%, 2,786.61/Minstr) in 1 blocks (5.26%, 3.95/Minstr), avg size 706 bytes, avg lifetime 543 instrs (0.21% of program duration) + │ │ │ │ Max: 706 bytes in 1 blocks, avg size 706 bytes + │ │ │ │ At t-gmax: 706 bytes (100%) in 1 blocks (100%), avg size 706 bytes + │ │ │ │ At t-end: 0 bytes (0%) in 0 blocks (0%), avg size 0 bytes + │ │ │ │ Reads: 0 bytes (0%, 0/Minstr), 0/byte + │ │ │ │ Writes: 0 bytes (0%, 0/Minstr), 0/byte + │ │ │ │ Accesses: { + │ │ │ │ [ 0] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + │ │ │ │ [ 32] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + │ │ │ │ [ 64] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + │ │ │ │ [ 96] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + │ │ │ │ [128] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + │ │ │ │ [160] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + │ │ │ │ [192] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + │ │ │ │ [224] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + │ │ │ │ [256] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + │ │ │ │ [288] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + │ │ │ │ [320] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + │ │ │ │ [352] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + │ │ │ │ [384] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + │ │ │ │ [416] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + │ │ │ │ [448] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + │ │ │ │ [480] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + │ │ │ │ [512] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + │ │ │ │ [544] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + │ │ │ │ [576] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + │ │ │ │ [608] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + │ │ │ │ [640] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + │ │ │ │ [672] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + │ │ │ │ [704] - - + │ │ │ │ } + │ │ │ │ Allocated at { + │ │ │ │ ^1: 0x1086A1: a (big.c:10) + │ │ │ │ ^2: 0x1086BB: b1 (big.c:11) + │ │ │ │ ^3: 0x1086D5: c1 (big.c:12) + │ │ │ │ #4: 0x1086EF: d1 (big.c:13) + │ │ │ │ #5: 0x108A43: main (big.c:38) + │ │ │ │ } + │ │ │ │ } + │ │ │ └── AP 1.1.1.1.2/2 { + │ │ │ Total: 5 bytes (0.5%, 19.74/Minstr) + │ │ │ Allocated at { + │ │ │ [1 insignificant] + │ │ │ } + │ │ │ } + │ │ └── AP 1.1.1.2/2 { + │ │ Total: 30 bytes (3%, 118.41/Minstr) in 1 blocks (5.26%, 3.95/Minstr), avg size 30 bytes, avg lifetime 7,910 instrs (3.12% of program duration) + │ │ Max: 30 bytes in 1 blocks, avg size 30 bytes + │ │ At t-gmax: 0 bytes (0%) in 0 blocks (0%), avg size 0 bytes + │ │ At t-end: 30 bytes (10.2%) in 1 blocks (5.56%), avg size 30 bytes + │ │ Reads: 0 bytes (0%, 0/Minstr), 0/byte + │ │ Writes: 0 bytes (0%, 0/Minstr), 0/byte + │ │ Accesses: { + │ │ [ 0] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + │ │ } + │ │ Allocated at { + │ │ ^1: 0x1086A1: a (big.c:10) + │ │ ^2: 0x1086BB: b1 (big.c:11) + │ │ #3: 0x108723: c2 (big.c:15) + │ │ #4: 0x108A67: main (big.c:42) + │ │ } + │ │ } + │ ├── AP 1.1.2/3 { + │ │ Total: 20 bytes (2%, 78.94/Minstr) in 1 blocks (5.26%, 3.95/Minstr), avg size 20 bytes, avg lifetime 7,857 instrs (3.1% of program duration) + │ │ Max: 20 bytes in 1 blocks, avg size 20 bytes + │ │ At t-gmax: 0 bytes (0%) in 0 blocks (0%), avg size 0 bytes + │ │ At t-end: 20 bytes (6.8%) in 1 blocks (5.56%), avg size 20 bytes + │ │ Reads: 0 bytes (0%, 0/Minstr), 0/byte + │ │ Writes: 0 bytes (0%, 0/Minstr), 0/byte + │ │ Accesses: { + │ │ [ 0] - - - - - - - - - - - - - - - - - - - - + │ │ } + │ │ Allocated at { + │ │ ^1: 0x1086A1: a (big.c:10) + │ │ #2: 0x10873D: b2 (big.c:16) + │ │ #3: 0x108A71: main (big.c:43) + │ │ } + │ │ } + │ └── AP 1.1.3/3 { + │ Total: 10 bytes (1%, 39.47/Minstr) in 1 blocks (5.26%, 3.95/Minstr), avg size 10 bytes, avg lifetime 7,792 instrs (3.08% of program duration) + │ Max: 10 bytes in 1 blocks, avg size 10 bytes + │ At t-gmax: 0 bytes (0%) in 0 blocks (0%), avg size 0 bytes + │ At t-end: 10 bytes (3.4%) in 1 blocks (5.56%), avg size 10 bytes + │ Reads: 0 bytes (0%, 0/Minstr), 0/byte + │ Writes: 0 bytes (0%, 0/Minstr), 0/byte + │ Accesses: { + │ [ 0] - - - - - - - - - - + │ } + │ Allocated at { + │ ^1: 0x1086A1: a (big.c:10) + │ #2: 0x108757: b3 (big.c:17) + │ #3: 0x108771: e (big.c:17) + │ #4: 0x10878B: f (big.c:17) + │ #5: 0x108A7B: main (big.c:44) + │ } + │ } + ├─▼ AP 1.2/7 (3 children) { + │ Total: 120 bytes (12%, 473.65/Minstr) in 5 blocks (26.32%, 19.74/Minstr), avg size 24 bytes, avg lifetime 7,536 instrs (2.97% of program duration) + │ At t-gmax: 0 bytes (0%) in 0 blocks (0%), avg size 0 bytes + │ At t-end: 120 bytes (40.82%) in 5 blocks (27.78%), avg size 24 bytes + │ Reads: 0 bytes (0%, 0/Minstr), 0/byte + │ Writes: 0 bytes (0%, 0/Minstr), 0/byte + │ Allocated at { + │ #1: 0x1087A5: g (big.c:18) + │ #2: 0x1087BF: h (big.c:18) + │ #3: 0x1087D9: i (big.c:18) + │ } + │ } + │ ├── AP 1.2.1/3 { + │ │ Total: 60 bytes (6%, 236.82/Minstr) in 1 blocks (5.26%, 3.95/Minstr), avg size 60 bytes, avg lifetime 7,709 instrs (3.04% of program duration) + │ │ Max: 60 bytes in 1 blocks, avg size 60 bytes + │ │ At t-gmax: 0 bytes (0%) in 0 blocks (0%), avg size 0 bytes + │ │ At t-end: 60 bytes (20.41%) in 1 blocks (5.56%), avg size 60 bytes + │ │ Reads: 0 bytes (0%, 0/Minstr), 0/byte + │ │ Writes: 0 bytes (0%, 0/Minstr), 0/byte + │ │ Accesses: { + │ │ [ 0] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + │ │ [ 32] - - - - - - - - - - - - - - - - - - - - - - - - - - - - + │ │ } + │ │ Allocated at { + │ │ ^1: 0x1087A5: g (big.c:18) + │ │ ^2: 0x1087BF: h (big.c:18) + │ │ ^3: 0x1087D9: i (big.c:18) + │ │ #4: 0x1087F3: j2 (big.c:19) + │ │ #5: 0x10880D: k (big.c:19) + │ │ #6: 0x108827: l (big.c:19) + │ │ #7: 0x108A85: main (big.c:45) + │ │ } + │ │ } + │ ├─▼ AP 1.2.2/3 (2 children) { + │ │ Total: 50 bytes (5%, 197.35/Minstr) in 2 blocks (10.53%, 7.89/Minstr), avg size 25 bytes, avg lifetime 7,575 instrs (2.99% of program duration) + │ │ At t-gmax: 0 bytes (0%) in 0 blocks (0%), avg size 0 bytes + │ │ At t-end: 50 bytes (17.01%) in 2 blocks (11.11%), avg size 25 bytes + │ │ Reads: 0 bytes (0%, 0/Minstr), 0/byte + │ │ Writes: 0 bytes (0%, 0/Minstr), 0/byte + │ │ Allocated at { + │ │ ^1: 0x1087A5: g (big.c:18) + │ │ ^2: 0x1087BF: h (big.c:18) + │ │ ^3: 0x1087D9: i (big.c:18) + │ │ #4: 0x108841: j3 (big.c:20) + │ │ #5: 0x10885B: m (big.c:20) + │ │ } + │ │ } + │ │ ├── AP 1.2.2.1/2 { + │ │ │ Total: 30 bytes (3%, 118.41/Minstr) in 1 blocks (5.26%, 3.95/Minstr), avg size 30 bytes, avg lifetime 7,622 instrs (3.01% of program duration) + │ │ │ Max: 30 bytes in 1 blocks, avg size 30 bytes + │ │ │ At t-gmax: 0 bytes (0%) in 0 blocks (0%), avg size 0 bytes + │ │ │ At t-end: 30 bytes (10.2%) in 1 blocks (5.56%), avg size 30 bytes + │ │ │ Reads: 0 bytes (0%, 0/Minstr), 0/byte + │ │ │ Writes: 0 bytes (0%, 0/Minstr), 0/byte + │ │ │ Accesses: { + │ │ │ [ 0] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + │ │ │ } + │ │ │ Allocated at { + │ │ │ ^1: 0x1087A5: g (big.c:18) + │ │ │ ^2: 0x1087BF: h (big.c:18) + │ │ │ ^3: 0x1087D9: i (big.c:18) + │ │ │ ^4: 0x108841: j3 (big.c:20) + │ │ │ ^5: 0x10885B: m (big.c:20) + │ │ │ #6: 0x108875: n1 (big.c:21) + │ │ │ #7: 0x108A8F: main (big.c:46) + │ │ │ } + │ │ │ } + │ │ └── AP 1.2.2.2/2 { + │ │ Total: 20 bytes (2%, 78.94/Minstr) in 1 blocks (5.26%, 3.95/Minstr), avg size 20 bytes, avg lifetime 7,528 instrs (2.97% of program duration) + │ │ Max: 20 bytes in 1 blocks, avg size 20 bytes + │ │ At t-gmax: 0 bytes (0%) in 0 blocks (0%), avg size 0 bytes + │ │ At t-end: 20 bytes (6.8%) in 1 blocks (5.56%), avg size 20 bytes + │ │ Reads: 0 bytes (0%, 0/Minstr), 0/byte + │ │ Writes: 0 bytes (0%, 0/Minstr), 0/byte + │ │ Accesses: { + │ │ [ 0] - - - - - - - - - - - - - - - - - - - - + │ │ } + │ │ Allocated at { + │ │ ^1: 0x1087A5: g (big.c:18) + │ │ ^2: 0x1087BF: h (big.c:18) + │ │ ^3: 0x1087D9: i (big.c:18) + │ │ ^4: 0x108841: j3 (big.c:20) + │ │ ^5: 0x10885B: m (big.c:20) + │ │ #6: 0x10888F: n2 (big.c:22) + │ │ #7: 0x1088A9: o (big.c:22) + │ │ #8: 0x108A99: main (big.c:47) + │ │ } + │ │ } + │ └── AP 1.2.3/3 { + │ Total: 10 bytes (1%, 39.47/Minstr) + │ Allocated at { + │ [2 insignificant] + │ } + │ } + ├── AP 1.3/7 { + │ Total: 30 bytes (3%, 118.41/Minstr) in 1 blocks (5.26%, 3.95/Minstr), avg size 30 bytes, avg lifetime 7,299 instrs (2.88% of program duration) + │ Max: 30 bytes in 1 blocks, avg size 30 bytes + │ At t-gmax: 0 bytes (0%) in 0 blocks (0%), avg size 0 bytes + │ At t-end: 30 bytes (10.2%) in 1 blocks (5.56%), avg size 30 bytes + │ Reads: 0 bytes (0%, 0/Minstr), 0/byte + │ Writes: 0 bytes (0%, 0/Minstr), 0/byte + │ Accesses: { + │ [ 0] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + │ } + │ Allocated at { + │ #1: 0x108911: s1 (big.c:25) + │ #2: 0x10892B: s2 (big.c:25) + │ #3: 0x108945: s3 (big.c:25) + │ #4: 0x10895F: s4 (big.c:25) + │ #5: 0x108979: s5 (big.c:25) + │ #6: 0x108AB7: main (big.c:50) + │ } + │ } + ├─▼ AP 1.4/7 (1 children) { + │ Total: 30 bytes (3%, 118.41/Minstr) in 5 blocks (26.32%, 19.74/Minstr), avg size 6 bytes, avg lifetime 7,056 instrs (2.79% of program duration) + │ At t-gmax: 0 bytes (0%) in 0 blocks (0%), avg size 0 bytes + │ At t-end: 30 bytes (10.2%) in 5 blocks (27.78%), avg size 6 bytes + │ Reads: 0 bytes (0%, 0/Minstr), 0/byte + │ Writes: 0 bytes (0%, 0/Minstr), 0/byte + │ Allocated at { + │ #1: 0x1089C7: v (big.c:28) + │ } + │ } + │ └── AP 1.4.1/1 { + │ Total: 30 bytes (3%, 118.41/Minstr) + │ Allocated at { + │ [4 insignificant] + │ } + │ } + ├── AP 1.5/7 { + │ Total: 20 bytes (2%, 78.94/Minstr) in 1 blocks (5.26%, 3.95/Minstr), avg size 20 bytes, avg lifetime 7,249 instrs (2.86% of program duration) + │ Max: 20 bytes in 1 blocks, avg size 20 bytes + │ At t-gmax: 0 bytes (0%) in 0 blocks (0%), avg size 0 bytes + │ At t-end: 20 bytes (6.8%) in 1 blocks (5.56%), avg size 20 bytes + │ Reads: 0 bytes (0%, 0/Minstr), 0/byte + │ Writes: 0 bytes (0%, 0/Minstr), 0/byte + │ Accesses: { + │ [ 0] - - - - - - - - - - - - - - - - - - - - + │ } + │ Allocated at { + │ #1: 0x108993: t (big.c:26) + │ #2: 0x108AC1: main (big.c:51) + │ } + │ } + ├── AP 1.6/7 { + │ Total: 19 bytes (1.9%, 74.99/Minstr) in 1 blocks (5.26%, 3.95/Minstr), avg size 19 bytes, avg lifetime 7,207 instrs (2.84% of program duration) + │ Max: 19 bytes in 1 blocks, avg size 19 bytes + │ At t-gmax: 0 bytes (0%) in 0 blocks (0%), avg size 0 bytes + │ At t-end: 19 bytes (6.46%) in 1 blocks (5.56%), avg size 19 bytes + │ Reads: 0 bytes (0%, 0/Minstr), 0/byte + │ Writes: 0 bytes (0%, 0/Minstr), 0/byte + │ Accesses: { + │ [ 0] - - - - - - - - - - - - - - - - - - - + │ } + │ Allocated at { + │ #1: 0x1089AD: u (big.c:27) + │ #2: 0x108ACB: main (big.c:52) + │ } + │ } + └── AP 1.7/7 { + Total: 10 bytes (1%, 39.47/Minstr) in 1 blocks (5.26%, 3.95/Minstr), avg size 10 bytes, avg lifetime 6,917 instrs (2.73% of program duration) + Max: 10 bytes in 1 blocks, avg size 10 bytes + At t-gmax: 0 bytes (0%) in 0 blocks (0%), avg size 0 bytes + At t-end: 10 bytes (3.4%) in 1 blocks (5.56%), avg size 10 bytes + Reads: 0 bytes (0%, 0/Minstr), 0/byte + Writes: 0 bytes (0%, 0/Minstr), 0/byte + Accesses: { + [ 0] - - - - - - - - - - + } + Allocated at { + #1: 0x108B07: main (big.c:60) + } + } + +AP significance threshold: total >= 10 bytes (1%) +` +//--------------------------------------------------------------------------- + }, + { + label: "Total (blocks), short-lived", + expected: +//--------------------------------------------------------------------------- +`\ +Invocation { + Command: ./big + PID: 3902 +} + +Times { + t-gmax: 245,281 instrs (96.81% of program duration) + t-end: 253,354 instrs +} + +▼ AP 1/1 (1 children) { + Total: 19 blocks (100%, 74.99/Minstr), avg lifetime 7,037.16 instrs (2.78% of program duration) + Allocated at { + #0: [root] + } + } + └── AP 1.1/1 { + Total: 19 blocks (100%, 74.99/Minstr), avg lifetime 7,037.16 instrs (2.78% of program duration) + Allocated at { + [7 insignificant] + } + } + +AP significance threshold: (total >= 0.1 blocks (0.5%)) && (total avg lifetime <= 500 instrs) +` +//--------------------------------------------------------------------------- + }, + { + label: "At t-gmax (bytes)", + expected: +//--------------------------------------------------------------------------- +`\ +Invocation { + Command: ./big + PID: 3902 +} + +Times { + t-gmax: 245,281 instrs (96.81% of program duration) + t-end: 253,354 instrs +} + +▼ AP 1/1 (2 children) { + Total: 1,000 bytes (100%, 3,947.05/Minstr) in 19 blocks (100%, 74.99/Minstr), avg size 52.63 bytes, avg lifetime 7,037.16 instrs (2.78% of program duration) + At t-gmax: 706 bytes (100%) in 1 blocks (100%), avg size 706 bytes + At t-end: 294 bytes (100%) in 18 blocks (100%), avg size 16.33 bytes + Reads: 0 bytes (0%, 0/Minstr), 0/byte + Writes: 0 bytes (0%, 0/Minstr), 0/byte + Allocated at { + #0: [root] + } + } + ├─▼ AP 1.1/2 (2 children) { + │ Total: 771 bytes (77.1%, 3,043.17/Minstr) in 5 blocks (26.32%, 19.74/Minstr), avg size 154.2 bytes, avg lifetime 6,414.8 instrs (2.53% of program duration) + │ At t-gmax: 706 bytes (100%) in 1 blocks (100%), avg size 706 bytes + │ At t-end: 65 bytes (22.11%) in 4 blocks (22.22%), avg size 16.25 bytes + │ Reads: 0 bytes (0%, 0/Minstr), 0/byte + │ Writes: 0 bytes (0%, 0/Minstr), 0/byte + │ Allocated at { + │ #1: 0x1086A1: a (big.c:10) + │ } + │ } + │ ├─▼ AP 1.1.1/2 (2 children) { + │ │ Total: 741 bytes (74.1%, 2,924.76/Minstr) in 3 blocks (15.79%, 11.84/Minstr), avg size 247 bytes, avg lifetime 5,475 instrs (2.16% of program duration) + │ │ At t-gmax: 706 bytes (100%) in 1 blocks (100%), avg size 706 bytes + │ │ At t-end: 35 bytes (11.9%) in 2 blocks (11.11%), avg size 17.5 bytes + │ │ Reads: 0 bytes (0%, 0/Minstr), 0/byte + │ │ Writes: 0 bytes (0%, 0/Minstr), 0/byte + │ │ Allocated at { + │ │ ^1: 0x1086A1: a (big.c:10) + │ │ #2: 0x1086BB: b1 (big.c:11) + │ │ } + │ │ } + │ │ ├─▼ AP 1.1.1.1/2 (2 children) { + │ │ │ Total: 711 bytes (71.1%, 2,806.35/Minstr) in 2 blocks (10.53%, 7.89/Minstr), avg size 355.5 bytes, avg lifetime 4,257.5 instrs (1.68% of program duration) + │ │ │ At t-gmax: 706 bytes (100%) in 1 blocks (100%), avg size 706 bytes + │ │ │ At t-end: 5 bytes (1.7%) in 1 blocks (5.56%), avg size 5 bytes + │ │ │ Reads: 0 bytes (0%, 0/Minstr), 0/byte + │ │ │ Writes: 0 bytes (0%, 0/Minstr), 0/byte + │ │ │ Allocated at { + │ │ │ ^1: 0x1086A1: a (big.c:10) + │ │ │ ^2: 0x1086BB: b1 (big.c:11) + │ │ │ #3: 0x1086D5: c1 (big.c:12) + │ │ │ } + │ │ │ } + │ │ │ ├── AP 1.1.1.1.1/2 { + │ │ │ │ Total: 706 bytes (70.6%, 2,786.61/Minstr) in 1 blocks (5.26%, 3.95/Minstr), avg size 706 bytes, avg lifetime 543 instrs (0.21% of program duration) + │ │ │ │ Max: 706 bytes in 1 blocks, avg size 706 bytes + │ │ │ │ At t-gmax: 706 bytes (100%) in 1 blocks (100%), avg size 706 bytes + │ │ │ │ At t-end: 0 bytes (0%) in 0 blocks (0%), avg size 0 bytes + │ │ │ │ Reads: 0 bytes (0%, 0/Minstr), 0/byte + │ │ │ │ Writes: 0 bytes (0%, 0/Minstr), 0/byte + │ │ │ │ Accesses: { + │ │ │ │ [ 0] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + │ │ │ │ [ 32] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + │ │ │ │ [ 64] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + │ │ │ │ [ 96] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + │ │ │ │ [128] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + │ │ │ │ [160] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + │ │ │ │ [192] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + │ │ │ │ [224] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + │ │ │ │ [256] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + │ │ │ │ [288] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + │ │ │ │ [320] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + │ │ │ │ [352] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + │ │ │ │ [384] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + │ │ │ │ [416] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + │ │ │ │ [448] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + │ │ │ │ [480] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + │ │ │ │ [512] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + │ │ │ │ [544] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + │ │ │ │ [576] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + │ │ │ │ [608] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + │ │ │ │ [640] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + │ │ │ │ [672] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + │ │ │ │ [704] - - + │ │ │ │ } + │ │ │ │ Allocated at { + │ │ │ │ ^1: 0x1086A1: a (big.c:10) + │ │ │ │ ^2: 0x1086BB: b1 (big.c:11) + │ │ │ │ ^3: 0x1086D5: c1 (big.c:12) + │ │ │ │ #4: 0x1086EF: d1 (big.c:13) + │ │ │ │ #5: 0x108A43: main (big.c:38) + │ │ │ │ } + │ │ │ │ } + │ │ │ └── AP 1.1.1.1.2/2 { + │ │ │ At t-gmax: 0 bytes (0%) + │ │ │ Allocated at { + │ │ │ [1 insignificant] + │ │ │ } + │ │ │ } + │ │ └── AP 1.1.1.2/2 { + │ │ At t-gmax: 0 bytes (0%) + │ │ Allocated at { + │ │ [1 insignificant] + │ │ } + │ │ } + │ └── AP 1.1.2/2 { + │ At t-gmax: 0 bytes (0%) + │ Allocated at { + │ [2 insignificant] + │ } + │ } + └── AP 1.2/2 { + At t-gmax: 0 bytes (0%) + Allocated at { + [6 insignificant] + } + } + +AP significance threshold: at-t-gmax >= 7.06 bytes (1%) +` +//--------------------------------------------------------------------------- + } + ] +}; +tests.push(big); + +//--------------------------------------------------------------------------- +// sig (corresponds to dhat/tests/sig.c) +//--------------------------------------------------------------------------- + +let sig = { + name: "sig", + input: +//--------------------------------------------------------------------------- +{"dhatFileVersion":1 +,"cmd":"./sig" +,"pid":21476 +,"mi":1311861,"ei":1318783 +,"aps": + [{"tb":11,"tbk":1,"tli":1075941 + ,"mb":11,"mbk":1 + ,"gb":11,"gbk":1 + ,"fb":11,"fbk":1 + ,"rb":11,"wb":16489 + ,"acc":[-11,1500] + ,"fs":[1,2] + } + ,{"tb":10,"tbk":1,"tli":880845 + ,"mb":10,"mbk":1 + ,"gb":10,"gbk":1 + ,"fb":10,"fbk":1 + ,"rb":10,"wb":14990 + ,"acc":[-10,1500] + ,"fs":[1,3,4] + } + ,{"tb":5,"tbk":1,"tli":702250 + ,"mb":5,"mbk":1 + ,"gb":5,"gbk":1 + ,"fb":5,"fbk":1 + ,"rb":5,"wb":7495 + ,"acc":[-5,1500] + ,"fs":[1,5,6] + } + ,{"tb":4,"tbk":1,"tli":606170 + ,"mb":4,"mbk":1 + ,"gb":4,"gbk":1 + ,"fb":4,"fbk":1 + ,"rb":4,"wb":5996 + ,"acc":[-4,1500] + ,"fs":[1,5,7] + } + ,{"tb":10,"tbk":1,"tli":510097 + ,"mb":10,"mbk":1 + ,"gb":10,"gbk":1 + ,"fb":10,"fbk":1 + ,"rb":10,"wb":14990 + ,"acc":[-10,1500] + ,"fs":[8,9] + } + ,{"tb":9,"tbk":1,"tli":331504 + ,"mb":9,"mbk":1 + ,"gb":9,"gbk":1 + ,"fb":9,"fbk":1 + ,"rb":9,"wb":13491 + ,"acc":[-9,1500] + ,"fs":[8,10,11] + } + ,{"tb":5,"tbk":1,"tli":169412 + ,"mb":5,"mbk":1 + ,"gb":5,"gbk":1 + ,"fb":5,"fbk":1 + ,"rb":0,"wb":0 + ,"acc":[-5,0] + ,"fs":[8,12,13] + } + ,{"tb":3,"tbk":1,"tli":169360 + ,"mb":3,"mbk":1 + ,"gb":3,"gbk":1 + ,"fb":3,"fbk":1 + ,"rb":0,"wb":0 + ,"acc":[-3,0] + ,"fs":[8,12,14] + } + ,{"tb":9,"tbk":1,"tli":169315 + ,"mb":9,"mbk":1 + ,"gb":9,"gbk":1 + ,"fb":9,"fbk":1 + ,"rb":9,"wb":13491 + ,"acc":[-9,1500] + ,"fs":[15,16] + } + ,{"tb":8,"tbk":1,"tli":7225 + ,"mb":8,"mbk":1 + ,"gb":8,"gbk":1 + ,"fb":8,"fbk":1 + ,"rb":0,"wb":0 + ,"acc":[-8,0] + ,"fs":[15,17,18] + } + ,{"tb":4,"tbk":1,"tli":7173 + ,"mb":4,"mbk":1 + ,"gb":4,"gbk":1 + ,"fb":4,"fbk":1 + ,"rb":0,"wb":0 + ,"acc":[-4,0] + ,"fs":[15,19,20] + } + ,{"tb":3,"tbk":1,"tli":7121 + ,"mb":3,"mbk":1 + ,"gb":3,"gbk":1 + ,"fb":3,"fbk":1 + ,"rb":0,"wb":0 + ,"acc":[-3,0] + ,"fs":[15,19,21] + } + ,{"tb":8,"tbk":1,"tli":7076 + ,"mb":8,"mbk":1 + ,"gb":8,"gbk":1 + ,"fb":8,"fbk":1 + ,"rb":0,"wb":0 + ,"acc":[-8,0] + ,"fs":[22,23] + } + ,{"tb":7,"tbk":1,"tli":7026 + ,"mb":7,"mbk":1 + ,"gb":7,"gbk":1 + ,"fb":7,"fbk":1 + ,"rb":0,"wb":0 + ,"acc":[-7,0] + ,"fs":[22,24,25] + } + ,{"tb":4,"tbk":1,"tli":6974 + ,"mb":4,"mbk":1 + ,"gb":4,"gbk":1 + ,"fb":4,"fbk":1 + ,"rb":0,"wb":0 + ,"acc":[-4,0] + ,"fs":[22,26,27] + } + ,{"tb":2,"tbk":1,"tli":6922 + ,"mb":2,"mbk":1 + ,"gb":2,"gbk":1 + ,"fb":2,"fbk":1 + ,"rb":0,"wb":0 + ,"acc":[-2,0] + ,"fs":[22,26,28] + } + ] +,"ftbl": + ["[root]" + ,"0x108681: am (sig.c:9)" + ,"0x10883C: main (sig.c:57)" + ,"0x10869B: a2 (sig.c:11)" + ,"0x10885B: main (sig.c:58)" + ,"0x1086B5: a3 (sig.c:12)" + ,"0x10887A: main (sig.c:59)" + ,"0x108899: main (sig.c:60)" + ,"0x1086CF: bm (sig.c:15)" + ,"0x1088B8: main (sig.c:62)" + ,"0x1086E9: b2 (sig.c:17)" + ,"0x1088D7: main (sig.c:63)" + ,"0x108703: b3 (sig.c:18)" + ,"0x1088F6: main (sig.c:64)" + ,"0x108904: main (sig.c:65)" + ,"0x10871D: cm (sig.c:21)" + ,"0x108912: main (sig.c:67)" + ,"0x108737: c2 (sig.c:23)" + ,"0x108931: main (sig.c:68)" + ,"0x108751: c3 (sig.c:24)" + ,"0x10893F: main (sig.c:69)" + ,"0x10894D: main (sig.c:70)" + ,"0x10876B: dm (sig.c:27)" + ,"0x10895B: main (sig.c:72)" + ,"0x108785: d2 (sig.c:29)" + ,"0x108969: main (sig.c:73)" + ,"0x10879F: d3 (sig.c:30)" + ,"0x108977: main (sig.c:74)" + ,"0x108985: main (sig.c:75)" + ] +} +//--------------------------------------------------------------------------- + , + outputs: [ + { + label: "Total (bytes), zero reads or zero writes", + expected: +//--------------------------------------------------------------------------- +`\ +Invocation { + Command: ./sig + PID: 21476 +} + +Times { + t-gmax: 1,311,861 instrs (99.48% of program duration) + t-end: 1,318,783 instrs +} + +▼ AP 1/1 (4 children) { + Total: 102 bytes (100%, 77.34/Minstr) + Reads: 58 bytes (100%, 43.98/Minstr) + Writes: 86,942 bytes (100%, 65,925.93/Minstr) + Allocated at { + #0: [root] + } + } + ├── AP 1.1/4 { + │ Total: 30 bytes (29.41%, 22.75/Minstr) + │ Reads: 30 bytes (51.72%, 22.75/Minstr) + │ Writes: 44,970 bytes (51.72%, 34,099.62/Minstr) + │ Allocated at { + │ [1 insignificant] + │ } + │ } + ├─▼ AP 1.2/4 (2 children) { + │ Total: 27 bytes (26.47%, 20.47/Minstr) + │ Reads: 19 bytes (32.76%, 14.41/Minstr) + │ Writes: 28,481 bytes (32.76%, 21,596.43/Minstr) + │ Allocated at { + │ #1: 0x1086CF: bm (sig.c:15) + │ } + │ } + │ ├── AP 1.2.1/2 { + │ │ Total: 19 bytes (18.63%, 14.41/Minstr) + │ │ Reads: 19 bytes (32.76%, 14.41/Minstr) + │ │ Writes: 28,481 bytes (32.76%, 21,596.43/Minstr) + │ │ Allocated at { + │ │ [2 insignificant] + │ │ } + │ │ } + │ └─▼ AP 1.2.2/2 (2 children) { + │ Total: 8 bytes (7.84%, 6.07/Minstr) in 2 blocks (12.5%, 1.52/Minstr), avg size 4 bytes, avg lifetime 169,386 instrs (12.84% of program duration) + │ At t-gmax: 8 bytes (7.84%) in 2 blocks (12.5%), avg size 4 bytes + │ At t-end: 8 bytes (7.84%) in 2 blocks (12.5%), avg size 4 bytes + │ Reads: 0 bytes (0%, 0/Minstr), 0/byte + │ Writes: 0 bytes (0%, 0/Minstr), 0/byte + │ Allocated at { + │ ^1: 0x1086CF: bm (sig.c:15) + │ #2: 0x108703: b3 (sig.c:18) + │ } + │ } + │ ├── AP 1.2.2.1/2 { + │ │ Total: 5 bytes (4.9%, 3.79/Minstr) in 1 blocks (6.25%, 0.76/Minstr), avg size 5 bytes, avg lifetime 169,412 instrs (12.85% of program duration) + │ │ Max: 5 bytes in 1 blocks, avg size 5 bytes + │ │ At t-gmax: 5 bytes (4.9%) in 1 blocks (6.25%), avg size 5 bytes + │ │ At t-end: 5 bytes (4.9%) in 1 blocks (6.25%), avg size 5 bytes + │ │ Reads: 0 bytes (0%, 0/Minstr), 0/byte + │ │ Writes: 0 bytes (0%, 0/Minstr), 0/byte + │ │ Accesses: { + │ │ [ 0] - - - - - + │ │ } + │ │ Allocated at { + │ │ ^1: 0x1086CF: bm (sig.c:15) + │ │ ^2: 0x108703: b3 (sig.c:18) + │ │ #3: 0x1088F6: main (sig.c:64) + │ │ } + │ │ } + │ └── AP 1.2.2.2/2 { + │ Total: 3 bytes (2.94%, 2.27/Minstr) in 1 blocks (6.25%, 0.76/Minstr), avg size 3 bytes, avg lifetime 169,360 instrs (12.84% of program duration) + │ Max: 3 bytes in 1 blocks, avg size 3 bytes + │ At t-gmax: 3 bytes (2.94%) in 1 blocks (6.25%), avg size 3 bytes + │ At t-end: 3 bytes (2.94%) in 1 blocks (6.25%), avg size 3 bytes + │ Reads: 0 bytes (0%, 0/Minstr), 0/byte + │ Writes: 0 bytes (0%, 0/Minstr), 0/byte + │ Accesses: { + │ [ 0] - - - + │ } + │ Allocated at { + │ ^1: 0x1086CF: bm (sig.c:15) + │ ^2: 0x108703: b3 (sig.c:18) + │ #3: 0x108904: main (sig.c:65) + │ } + │ } + ├─▼ AP 1.3/4 (3 children) { + │ Total: 24 bytes (23.53%, 18.2/Minstr) + │ Reads: 9 bytes (15.52%, 6.82/Minstr) + │ Writes: 13,491 bytes (15.52%, 10,229.89/Minstr) + │ Allocated at { + │ #1: 0x10871D: cm (sig.c:21) + │ } + │ } + │ ├── AP 1.3.1/3 { + │ │ Total: 9 bytes (8.82%, 6.82/Minstr) + │ │ Reads: 9 bytes (15.52%, 6.82/Minstr) + │ │ Writes: 13,491 bytes (15.52%, 10,229.89/Minstr) + │ │ Allocated at { + │ │ [1 insignificant] + │ │ } + │ │ } + │ ├── AP 1.3.2/3 { + │ │ Total: 8 bytes (7.84%, 6.07/Minstr) in 1 blocks (6.25%, 0.76/Minstr), avg size 8 bytes, avg lifetime 7,225 instrs (0.55% of program duration) + │ │ Max: 8 bytes in 1 blocks, avg size 8 bytes + │ │ At t-gmax: 8 bytes (7.84%) in 1 blocks (6.25%), avg size 8 bytes + │ │ At t-end: 8 bytes (7.84%) in 1 blocks (6.25%), avg size 8 bytes + │ │ Reads: 0 bytes (0%, 0/Minstr), 0/byte + │ │ Writes: 0 bytes (0%, 0/Minstr), 0/byte + │ │ Accesses: { + │ │ [ 0] - - - - - - - - + │ │ } + │ │ Allocated at { + │ │ ^1: 0x10871D: cm (sig.c:21) + │ │ #2: 0x108737: c2 (sig.c:23) + │ │ #3: 0x108931: main (sig.c:68) + │ │ } + │ │ } + │ └─▼ AP 1.3.3/3 (2 children) { + │ Total: 7 bytes (6.86%, 5.31/Minstr) in 2 blocks (12.5%, 1.52/Minstr), avg size 3.5 bytes, avg lifetime 7,147 instrs (0.54% of program duration) + │ At t-gmax: 7 bytes (6.86%) in 2 blocks (12.5%), avg size 3.5 bytes + │ At t-end: 7 bytes (6.86%) in 2 blocks (12.5%), avg size 3.5 bytes + │ Reads: 0 bytes (0%, 0/Minstr), 0/byte + │ Writes: 0 bytes (0%, 0/Minstr), 0/byte + │ Allocated at { + │ ^1: 0x10871D: cm (sig.c:21) + │ #2: 0x108751: c3 (sig.c:24) + │ } + │ } + │ ├── AP 1.3.3.1/2 { + │ │ Total: 4 bytes (3.92%, 3.03/Minstr) in 1 blocks (6.25%, 0.76/Minstr), avg size 4 bytes, avg lifetime 7,173 instrs (0.54% of program duration) + │ │ Max: 4 bytes in 1 blocks, avg size 4 bytes + │ │ At t-gmax: 4 bytes (3.92%) in 1 blocks (6.25%), avg size 4 bytes + │ │ At t-end: 4 bytes (3.92%) in 1 blocks (6.25%), avg size 4 bytes + │ │ Reads: 0 bytes (0%, 0/Minstr), 0/byte + │ │ Writes: 0 bytes (0%, 0/Minstr), 0/byte + │ │ Accesses: { + │ │ [ 0] - - - - + │ │ } + │ │ Allocated at { + │ │ ^1: 0x10871D: cm (sig.c:21) + │ │ ^2: 0x108751: c3 (sig.c:24) + │ │ #3: 0x10893F: main (sig.c:69) + │ │ } + │ │ } + │ └── AP 1.3.3.2/2 { + │ Total: 3 bytes (2.94%, 2.27/Minstr) in 1 blocks (6.25%, 0.76/Minstr), avg size 3 bytes, avg lifetime 7,121 instrs (0.54% of program duration) + │ Max: 3 bytes in 1 blocks, avg size 3 bytes + │ At t-gmax: 3 bytes (2.94%) in 1 blocks (6.25%), avg size 3 bytes + │ At t-end: 3 bytes (2.94%) in 1 blocks (6.25%), avg size 3 bytes + │ Reads: 0 bytes (0%, 0/Minstr), 0/byte + │ Writes: 0 bytes (0%, 0/Minstr), 0/byte + │ Accesses: { + │ [ 0] - - - + │ } + │ Allocated at { + │ ^1: 0x10871D: cm (sig.c:21) + │ ^2: 0x108751: c3 (sig.c:24) + │ #3: 0x10894D: main (sig.c:70) + │ } + │ } + └─▼ AP 1.4/4 (3 children) { + Total: 21 bytes (20.59%, 15.92/Minstr) in 4 blocks (25%, 3.03/Minstr), avg size 5.25 bytes, avg lifetime 6,999.5 instrs (0.53% of program duration) + At t-gmax: 21 bytes (20.59%) in 4 blocks (25%), avg size 5.25 bytes + At t-end: 21 bytes (20.59%) in 4 blocks (25%), avg size 5.25 bytes + Reads: 0 bytes (0%, 0/Minstr), 0/byte + Writes: 0 bytes (0%, 0/Minstr), 0/byte + Allocated at { + #1: 0x10876B: dm (sig.c:27) + } + } + ├── AP 1.4.1/3 { + │ Total: 8 bytes (7.84%, 6.07/Minstr) in 1 blocks (6.25%, 0.76/Minstr), avg size 8 bytes, avg lifetime 7,076 instrs (0.54% of program duration) + │ Max: 8 bytes in 1 blocks, avg size 8 bytes + │ At t-gmax: 8 bytes (7.84%) in 1 blocks (6.25%), avg size 8 bytes + │ At t-end: 8 bytes (7.84%) in 1 blocks (6.25%), avg size 8 bytes + │ Reads: 0 bytes (0%, 0/Minstr), 0/byte + │ Writes: 0 bytes (0%, 0/Minstr), 0/byte + │ Accesses: { + │ [ 0] - - - - - - - - + │ } + │ Allocated at { + │ ^1: 0x10876B: dm (sig.c:27) + │ #2: 0x10895B: main (sig.c:72) + │ } + │ } + ├── AP 1.4.2/3 { + │ Total: 7 bytes (6.86%, 5.31/Minstr) in 1 blocks (6.25%, 0.76/Minstr), avg size 7 bytes, avg lifetime 7,026 instrs (0.53% of program duration) + │ Max: 7 bytes in 1 blocks, avg size 7 bytes + │ At t-gmax: 7 bytes (6.86%) in 1 blocks (6.25%), avg size 7 bytes + │ At t-end: 7 bytes (6.86%) in 1 blocks (6.25%), avg size 7 bytes + │ Reads: 0 bytes (0%, 0/Minstr), 0/byte + │ Writes: 0 bytes (0%, 0/Minstr), 0/byte + │ Accesses: { + │ [ 0] - - - - - - - + │ } + │ Allocated at { + │ ^1: 0x10876B: dm (sig.c:27) + │ #2: 0x108785: d2 (sig.c:29) + │ #3: 0x108969: main (sig.c:73) + │ } + │ } + └─▼ AP 1.4.3/3 (2 children) { + Total: 6 bytes (5.88%, 4.55/Minstr) in 2 blocks (12.5%, 1.52/Minstr), avg size 3 bytes, avg lifetime 6,948 instrs (0.53% of program duration) + At t-gmax: 6 bytes (5.88%) in 2 blocks (12.5%), avg size 3 bytes + At t-end: 6 bytes (5.88%) in 2 blocks (12.5%), avg size 3 bytes + Reads: 0 bytes (0%, 0/Minstr), 0/byte + Writes: 0 bytes (0%, 0/Minstr), 0/byte + Allocated at { + ^1: 0x10876B: dm (sig.c:27) + #2: 0x10879F: d3 (sig.c:30) + } + } + ├── AP 1.4.3.1/2 { + │ Total: 4 bytes (3.92%, 3.03/Minstr) in 1 blocks (6.25%, 0.76/Minstr), avg size 4 bytes, avg lifetime 6,974 instrs (0.53% of program duration) + │ Max: 4 bytes in 1 blocks, avg size 4 bytes + │ At t-gmax: 4 bytes (3.92%) in 1 blocks (6.25%), avg size 4 bytes + │ At t-end: 4 bytes (3.92%) in 1 blocks (6.25%), avg size 4 bytes + │ Reads: 0 bytes (0%, 0/Minstr), 0/byte + │ Writes: 0 bytes (0%, 0/Minstr), 0/byte + │ Accesses: { + │ [ 0] - - - - + │ } + │ Allocated at { + │ ^1: 0x10876B: dm (sig.c:27) + │ ^2: 0x10879F: d3 (sig.c:30) + │ #3: 0x108977: main (sig.c:74) + │ } + │ } + └── AP 1.4.3.2/2 { + Total: 2 bytes (1.96%, 1.52/Minstr) in 1 blocks (6.25%, 0.76/Minstr), avg size 2 bytes, avg lifetime 6,922 instrs (0.52% of program duration) + Max: 2 bytes in 1 blocks, avg size 2 bytes + At t-gmax: 2 bytes (1.96%) in 1 blocks (6.25%), avg size 2 bytes + At t-end: 2 bytes (1.96%) in 1 blocks (6.25%), avg size 2 bytes + Reads: 0 bytes (0%, 0/Minstr), 0/byte + Writes: 0 bytes (0%, 0/Minstr), 0/byte + Accesses: { + [ 0] - - + } + Allocated at { + ^1: 0x10876B: dm (sig.c:27) + ^2: 0x10879F: d3 (sig.c:30) + #3: 0x108985: main (sig.c:75) + } + } + +AP significance threshold: (total >= 0.51 bytes (0.5%)) && ((reads == 0 bytes) || (writes == 0 bytes)) +` +//--------------------------------------------------------------------------- + }, + { + label: "Total (blocks), low-access", + expected: +//--------------------------------------------------------------------------- +`\ +Invocation { + Command: ./sig + PID: 21476 +} + +Times { + t-gmax: 1,311,861 instrs (99.48% of program duration) + t-end: 1,318,783 instrs +} + +▼ AP 1/1 (2 children) { + Total: 16 blocks (100%, 12.13/Minstr) + Reads: 0.57/byte + Writes: 852.37/byte + Allocated at { + #0: [root] + } + } + ├── AP 1.1/2 { + │ Total: 12 blocks (75%, 9.1/Minstr) + │ Reads: 0.63/byte + │ Writes: 941.68/byte + │ Allocated at { + │ [3 insignificant] + │ } + │ } + └─▼ AP 1.2/2 (1 children) { + Total: 24 bytes (23.53%, 18.2/Minstr) in 4 blocks (25%, 3.03/Minstr), avg size 6 bytes, avg lifetime 47,708.5 instrs (3.62% of program duration) + At t-gmax: 24 bytes (23.53%) in 4 blocks (25%), avg size 6 bytes + At t-end: 24 bytes (23.53%) in 4 blocks (25%), avg size 6 bytes + Reads: 9 bytes (15.52%, 6.82/Minstr), 0.38/byte + Writes: 13,491 bytes (15.52%, 10,229.89/Minstr), 562.13/byte + Allocated at { + #1: 0x10871D: cm (sig.c:21) + } + } + └── AP 1.2.1/1 { + Total: 4 blocks (25%, 3.03/Minstr) + Reads: 0.38/byte + Writes: 562.13/byte + Allocated at { + [3 insignificant] + } + } + +AP significance threshold: (total >= 0.08 blocks (0.5%)) && (reads != 0 bytes) && (writes != 0 bytes) && ((reads <= 0.4/byte) || (writes <= 0.4/byte)) +` +//--------------------------------------------------------------------------- + }, + { + label: "Writes (bytes), high-access", + expected: +//--------------------------------------------------------------------------- +`\ +Invocation { + Command: ./sig + PID: 21476 +} + +Times { + t-gmax: 1,311,861 instrs (99.48% of program duration) + t-end: 1,318,783 instrs +} + +▼ AP 1/1 (4 children) { + Writes: 86,942 bytes (100%, 65,925.93/Minstr), 852.37/byte + Allocated at { + #0: [root] + } + } + ├─▼ AP 1.1/4 (3 children) { + │ Total: 30 bytes (29.41%, 22.75/Minstr) in 4 blocks (25%, 3.03/Minstr), avg size 7.5 bytes, avg lifetime 816,301.5 instrs (61.9% of program duration) + │ At t-gmax: 30 bytes (29.41%) in 4 blocks (25%), avg size 7.5 bytes + │ At t-end: 30 bytes (29.41%) in 4 blocks (25%), avg size 7.5 bytes + │ Reads: 30 bytes (51.72%, 22.75/Minstr), 1/byte + │ Writes: 44,970 bytes (51.72%, 34,099.62/Minstr), 1,499/byte + │ Allocated at { + │ #1: 0x108681: am (sig.c:9) + │ } + │ } + │ ├── AP 1.1.1/3 { + │ │ Total: 11 bytes (10.78%, 8.34/Minstr) in 1 blocks (6.25%, 0.76/Minstr), avg size 11 bytes, avg lifetime 1,075,941 instrs (81.59% of program duration) + │ │ Max: 11 bytes in 1 blocks, avg size 11 bytes + │ │ At t-gmax: 11 bytes (10.78%) in 1 blocks (6.25%), avg size 11 bytes + │ │ At t-end: 11 bytes (10.78%) in 1 blocks (6.25%), avg size 11 bytes + │ │ Reads: 11 bytes (18.97%, 8.34/Minstr), 1/byte + │ │ Writes: 16,489 bytes (18.97%, 12,503.19/Minstr), 1,499/byte + │ │ Accesses: { + │ │ [ 0] 1500 〃 〃 〃 〃 〃 〃 〃 〃 〃 〃 + │ │ } + │ │ Allocated at { + │ │ ^1: 0x108681: am (sig.c:9) + │ │ #2: 0x10883C: main (sig.c:57) + │ │ } + │ │ } + │ ├── AP 1.1.2/3 { + │ │ Total: 10 bytes (9.8%, 7.58/Minstr) in 1 blocks (6.25%, 0.76/Minstr), avg size 10 bytes, avg lifetime 880,845 instrs (66.79% of program duration) + │ │ Max: 10 bytes in 1 blocks, avg size 10 bytes + │ │ At t-gmax: 10 bytes (9.8%) in 1 blocks (6.25%), avg size 10 bytes + │ │ At t-end: 10 bytes (9.8%) in 1 blocks (6.25%), avg size 10 bytes + │ │ Reads: 10 bytes (17.24%, 7.58/Minstr), 1/byte + │ │ Writes: 14,990 bytes (17.24%, 11,366.54/Minstr), 1,499/byte + │ │ Accesses: { + │ │ [ 0] 1500 〃 〃 〃 〃 〃 〃 〃 〃 〃 + │ │ } + │ │ Allocated at { + │ │ ^1: 0x108681: am (sig.c:9) + │ │ #2: 0x10869B: a2 (sig.c:11) + │ │ #3: 0x10885B: main (sig.c:58) + │ │ } + │ │ } + │ └─▼ AP 1.1.3/3 (2 children) { + │ Total: 9 bytes (8.82%, 6.82/Minstr) in 2 blocks (12.5%, 1.52/Minstr), avg size 4.5 bytes, avg lifetime 654,210 instrs (49.61% of program duration) + │ At t-gmax: 9 bytes (8.82%) in 2 blocks (12.5%), avg size 4.5 bytes + │ At t-end: 9 bytes (8.82%) in 2 blocks (12.5%), avg size 4.5 bytes + │ Reads: 9 bytes (15.52%, 6.82/Minstr), 1/byte + │ Writes: 13,491 bytes (15.52%, 10,229.89/Minstr), 1,499/byte + │ Allocated at { + │ ^1: 0x108681: am (sig.c:9) + │ #2: 0x1086B5: a3 (sig.c:12) + │ } + │ } + │ ├── AP 1.1.3.1/2 { + │ │ Total: 5 bytes (4.9%, 3.79/Minstr) in 1 blocks (6.25%, 0.76/Minstr), avg size 5 bytes, avg lifetime 702,250 instrs (53.25% of program duration) + │ │ Max: 5 bytes in 1 blocks, avg size 5 bytes + │ │ At t-gmax: 5 bytes (4.9%) in 1 blocks (6.25%), avg size 5 bytes + │ │ At t-end: 5 bytes (4.9%) in 1 blocks (6.25%), avg size 5 bytes + │ │ Reads: 5 bytes (8.62%, 3.79/Minstr), 1/byte + │ │ Writes: 7,495 bytes (8.62%, 5,683.27/Minstr), 1,499/byte + │ │ Accesses: { + │ │ [ 0] 1500 〃 〃 〃 〃 + │ │ } + │ │ Allocated at { + │ │ ^1: 0x108681: am (sig.c:9) + │ │ ^2: 0x1086B5: a3 (sig.c:12) + │ │ #3: 0x10887A: main (sig.c:59) + │ │ } + │ │ } + │ └── AP 1.1.3.2/2 { + │ Total: 4 bytes (3.92%, 3.03/Minstr) in 1 blocks (6.25%, 0.76/Minstr), avg size 4 bytes, avg lifetime 606,170 instrs (45.96% of program duration) + │ Max: 4 bytes in 1 blocks, avg size 4 bytes + │ At t-gmax: 4 bytes (3.92%) in 1 blocks (6.25%), avg size 4 bytes + │ At t-end: 4 bytes (3.92%) in 1 blocks (6.25%), avg size 4 bytes + │ Reads: 4 bytes (6.9%, 3.03/Minstr), 1/byte + │ Writes: 5,996 bytes (6.9%, 4,546.62/Minstr), 1,499/byte + │ Accesses: { + │ [ 0] 1500 〃 〃 〃 + │ } + │ Allocated at { + │ ^1: 0x108681: am (sig.c:9) + │ ^2: 0x1086B5: a3 (sig.c:12) + │ #3: 0x108899: main (sig.c:60) + │ } + │ } + ├─▼ AP 1.2/4 (3 children) { + │ Total: 27 bytes (26.47%, 20.47/Minstr) in 4 blocks (25%, 3.03/Minstr), avg size 6.75 bytes, avg lifetime 295,093.25 instrs (22.38% of program duration) + │ At t-gmax: 27 bytes (26.47%) in 4 blocks (25%), avg size 6.75 bytes + │ At t-end: 27 bytes (26.47%) in 4 blocks (25%), avg size 6.75 bytes + │ Reads: 19 bytes (32.76%, 14.41/Minstr), 0.7/byte + │ Writes: 28,481 bytes (32.76%, 21,596.43/Minstr), 1,054.85/byte + │ Allocated at { + │ #1: 0x1086CF: bm (sig.c:15) + │ } + │ } + │ ├── AP 1.2.1/3 { + │ │ Total: 10 bytes (9.8%, 7.58/Minstr) in 1 blocks (6.25%, 0.76/Minstr), avg size 10 bytes, avg lifetime 510,097 instrs (38.68% of program duration) + │ │ Max: 10 bytes in 1 blocks, avg size 10 bytes + │ │ At t-gmax: 10 bytes (9.8%) in 1 blocks (6.25%), avg size 10 bytes + │ │ At t-end: 10 bytes (9.8%) in 1 blocks (6.25%), avg size 10 bytes + │ │ Reads: 10 bytes (17.24%, 7.58/Minstr), 1/byte + │ │ Writes: 14,990 bytes (17.24%, 11,366.54/Minstr), 1,499/byte + │ │ Accesses: { + │ │ [ 0] 1500 〃 〃 〃 〃 〃 〃 〃 〃 〃 + │ │ } + │ │ Allocated at { + │ │ ^1: 0x1086CF: bm (sig.c:15) + │ │ #2: 0x1088B8: main (sig.c:62) + │ │ } + │ │ } + │ ├── AP 1.2.2/3 { + │ │ Total: 9 bytes (8.82%, 6.82/Minstr) in 1 blocks (6.25%, 0.76/Minstr), avg size 9 bytes, avg lifetime 331,504 instrs (25.14% of program duration) + │ │ Max: 9 bytes in 1 blocks, avg size 9 bytes + │ │ At t-gmax: 9 bytes (8.82%) in 1 blocks (6.25%), avg size 9 bytes + │ │ At t-end: 9 bytes (8.82%) in 1 blocks (6.25%), avg size 9 bytes + │ │ Reads: 9 bytes (15.52%, 6.82/Minstr), 1/byte + │ │ Writes: 13,491 bytes (15.52%, 10,229.89/Minstr), 1,499/byte + │ │ Accesses: { + │ │ [ 0] 1500 〃 〃 〃 〃 〃 〃 〃 〃 + │ │ } + │ │ Allocated at { + │ │ ^1: 0x1086CF: bm (sig.c:15) + │ │ #2: 0x1086E9: b2 (sig.c:17) + │ │ #3: 0x1088D7: main (sig.c:63) + │ │ } + │ │ } + │ └── AP 1.2.3/3 { + │ Writes: 0 bytes (0%, 0/Minstr), 0/byte + │ Allocated at { + │ [1 insignificant] + │ } + │ } + ├─▼ AP 1.3/4 (2 children) { + │ Writes: 13,491 bytes (15.52%, 10,229.89/Minstr), 562.13/byte + │ Allocated at { + │ #1: 0x10871D: cm (sig.c:21) + │ } + │ } + │ ├── AP 1.3.1/2 { + │ │ Total: 9 bytes (8.82%, 6.82/Minstr) in 1 blocks (6.25%, 0.76/Minstr), avg size 9 bytes, avg lifetime 169,315 instrs (12.84% of program duration) + │ │ Max: 9 bytes in 1 blocks, avg size 9 bytes + │ │ At t-gmax: 9 bytes (8.82%) in 1 blocks (6.25%), avg size 9 bytes + │ │ At t-end: 9 bytes (8.82%) in 1 blocks (6.25%), avg size 9 bytes + │ │ Reads: 9 bytes (15.52%, 6.82/Minstr), 1/byte + │ │ Writes: 13,491 bytes (15.52%, 10,229.89/Minstr), 1,499/byte + │ │ Accesses: { + │ │ [ 0] 1500 〃 〃 〃 〃 〃 〃 〃 〃 + │ │ } + │ │ Allocated at { + │ │ ^1: 0x10871D: cm (sig.c:21) + │ │ #2: 0x108912: main (sig.c:67) + │ │ } + │ │ } + │ └── AP 1.3.2/2 { + │ Writes: 0 bytes (0%, 0/Minstr), 0/byte + │ Allocated at { + │ [2 insignificant] + │ } + │ } + └── AP 1.4/4 { + Writes: 0 bytes (0%, 0/Minstr), 0/byte + Allocated at { + [1 insignificant] + } + } + +AP significance threshold: (writes >= 434.71 bytes (0.5%)) && ((reads >= 1,000/byte) || (writes >= 1,000/byte)) +` +//--------------------------------------------------------------------------- + } + ] +}; +tests.push(sig); + +//--------------------------------------------------------------------------- +// sig2 (doesn't corresponds to a .c file) +//--------------------------------------------------------------------------- + +let sig2 = { + name: "sig2", + input: +//--------------------------------------------------------------------------- +{"dhatFileVersion":1 +,"cmd":"subseqs" +,"pid":0 +,"mi":10000,"ei":20000 +,"aps": + [{"tb":100,"tbk":1,"tli":1000 + ,"mb":100,"mbk":1 + ,"gb":100,"gbk":1 + ,"fb":0,"fbk":0 + ,"rb":0,"wb":0 + ,"acc":[-10,0] + ,"fs":[1] + } + ,{"tb":101,"tbk":1,"tli":1000 + ,"mb":101,"mbk":1 + ,"gb":101,"gbk":1 + ,"fb":0,"fbk":0 + ,"rb":0,"wb":0 + ,"acc":[-10,0] + ,"fs":[2] + } + ,{"tb":102,"tbk":1,"tli":1000 + ,"mb":102,"mbk":1 + ,"gb":102,"gbk":1 + ,"fb":0,"fbk":0 + ,"rb":0,"wb":0 + ,"acc":[-10,0] + ,"fs":[3,4] + } + ,{"tb":103,"tbk":1,"tli":1000 + ,"mb":103,"mbk":1 + ,"gb":103,"gbk":1 + ,"fb":0,"fbk":0 + ,"rb":0,"wb":0 + ,"acc":[-10,0] + ,"fs":[3,5] + } + ,{"tb":104,"tbk":1,"tli":1000 + ,"mb":104,"mbk":1 + ,"gb":104,"gbk":1 + ,"fb":0,"fbk":0 + ,"rb":0,"wb":0 + ,"acc":[-10,0] + ,"fs":[3,6,7] + } + ,{"tb":105,"tbk":1,"tli":1000 + ,"mb":105,"mbk":1 + ,"gb":105,"gbk":1 + ,"fb":0,"fbk":0 + ,"rb":0,"wb":0 + ,"acc":[-10,0] + ,"fs":[3,6,8] + } + ,{"tb":10,"tbk":1,"tli":1000 + ,"mb":10,"mbk":1 + ,"gb":10,"gbk":1 + ,"fb":0,"fbk":0 + ,"rb":0,"wb":0 + ,"acc":[-10,0] + ,"fs":[3,6,9,10] + } + ,{"tb":106,"tbk":1,"tli":1000 + ,"mb":106,"mbk":1 + ,"gb":106,"gbk":1 + ,"fb":0,"fbk":0 + ,"rb":0,"wb":0 + ,"acc":[-10,0] + ,"fs":[3,6,9,11] + } + ,{"tb":107,"tbk":1,"tli":1000 + ,"mb":107,"mbk":1 + ,"gb":107,"gbk":1 + ,"fb":0,"fbk":0 + ,"rb":0,"wb":0 + ,"acc":[-10,0] + ,"fs":[3,6,9,12] + } + ] +,"ftbl": + ["[root]" + ,"a1()" + ,"a2()" + ,"a3()" + ,"b1()" + ,"b2()" + ,"b3()" + ,"c1()" + ,"c2()" + ,"c3()" + ,"d1()" + ,"d2()" + ,"d3()" + ] +} +//--------------------------------------------------------------------------- + , + outputs: [ + { + label: "Total (blocks), tiny", + expected: +//--------------------------------------------------------------------------- +`\ +Invocation { + Command: subseqs + PID: 0 +} + +Times { + t-gmax: 10,000 instrs (50% of program duration) + t-end: 20,000 instrs +} + +▼ AP 1/1 (2 children) { + Total: 9 blocks (100%, 450/Minstr), avg size 93.11 bytes + Allocated at { + #0: [root] + } + } + ├─▼ AP 1.1/2 (2 children) { + │ Total: 7 blocks (77.78%, 350/Minstr), avg size 91 bytes + │ Allocated at { + │ #1: a3() + │ } + │ } + │ ├─▼ AP 1.1.1/2 (2 children) { + │ │ Total: 5 blocks (55.56%, 250/Minstr), avg size 86.4 bytes + │ │ Allocated at { + │ │ #2: b3() + │ │ } + │ │ } + │ │ ├─▼ AP 1.1.1.1/2 (2 children) { + │ │ │ Total: 3 blocks (33.33%, 150/Minstr), avg size 74.33 bytes + │ │ │ Allocated at { + │ │ │ #3: c3() + │ │ │ } + │ │ │ } + │ │ │ ├── AP 1.1.1.1.1/2 { + │ │ │ │ Total: 2 blocks (22.22%, 100/Minstr), avg size 106.5 bytes + │ │ │ │ Allocated at { + │ │ │ │ [2 insignificant] + │ │ │ │ } + │ │ │ │ } + │ │ │ └── AP 1.1.1.1.2/2 { + │ │ │ Total: 10 bytes (1.19%, 500/Minstr) in 1 blocks (11.11%, 50/Minstr), avg size 10 bytes, avg lifetime 1,000 instrs (5% of program duration) + │ │ │ Max: 10 bytes in 1 blocks, avg size 10 bytes + │ │ │ At t-gmax: 10 bytes (1.19%) in 1 blocks (11.11%), avg size 10 bytes + │ │ │ At t-end: 0 bytes (0%) in 0 blocks (0%), avg size 0 bytes + │ │ │ Reads: 0 bytes (0%, 0/Minstr), 0/byte + │ │ │ Writes: 0 bytes (0%, 0/Minstr), 0/byte + │ │ │ Accesses: { + │ │ │ [ 0] - - - - - - - - - - + │ │ │ } + │ │ │ Allocated at { + │ │ │ ^1: a3() + │ │ │ ^2: b3() + │ │ │ ^3: c3() + │ │ │ #4: d1() + │ │ │ } + │ │ │ } + │ │ └── AP 1.1.1.2/2 { + │ │ Total: 2 blocks (22.22%, 100/Minstr), avg size 104.5 bytes + │ │ Allocated at { + │ │ [2 insignificant] + │ │ } + │ │ } + │ └── AP 1.1.2/2 { + │ Total: 2 blocks (22.22%, 100/Minstr), avg size 102.5 bytes + │ Allocated at { + │ [2 insignificant] + │ } + │ } + └── AP 1.2/2 { + Total: 2 blocks (22.22%, 100/Minstr), avg size 100.5 bytes + Allocated at { + [2 insignificant] + } + } + +AP significance threshold: (total >= 0.05 blocks (0.5%)) && (total avg size <= 16 bytes) +` +//--------------------------------------------------------------------------- + } + ] +}; +tests.push(sig2); + +//--------------------------------------------------------------------------- +// Code +//--------------------------------------------------------------------------- + +function runTests() { + let pre = appendElement(gTestingDiv, "pre"); + + for (let [i, test] of tests.entries()) { + let name = test.name; + gData = test.input; + + for (let output of test.outputs) { + // Set the sort metric. + let label = output.label; + let j = 0; + let labelFound = false; + for (let opt of gSelect.options) { + if (gSelectData[opt.value].label == label) { + gSelect.selectedIndex = j; + labelFound = true; + break; + } + j++; + } + assert(labelFound, "test label not found in gSelectData"); + + // Build and display the tree. + tryFunc(() => { + gFilename = "TEST MODE"; + buildTree(); + displayTree(); + }); + + // Compare actual text output against expected. + let expected = output.expected; + let actual = gMainDiv.textContent; + + let id = `Test ${i} - ${test.name} - ${label}`; + + if (expected !== actual) { + // Test failed. Do a crude diff: find the line and column of the first + // char that differs. + let j = 0, line = 1, col = 1; + while (expected[j] === actual[j]) { + if (expected[j] === "\n") { + line++; + col = 1; + } else { + col++; + } + j++; + } + + let s = `\ +FAIL - ${id} + +Expected length: ${expected.length}, actual length: ${actual.length} +First differing char at ${line}:${col} + +EXPECTED OUTPUT +<<< +`; + + // Print line numbers for the expected output, because it makes it much + // easier to find the first differing char. + for (let [n, line] of expected.split('\n').entries()) { + s += `${(n + 1).toString().padStart(3)} ${line}\n`; + } + + s += ">>>"; + + appendElementWithText(pre, "div", s); + return; // stop on the first failure + } + + // Test passed. + appendElementWithText(pre, "div", `PASS - ${id}`); + } + } + + clearMainDivWithText("All tests passed"); +} + +runTests(); + diff --git a/dhat/dh_view.css b/dhat/dh_view.css new file mode 100644 index 0000000000..3d2c12c07d --- /dev/null +++ b/dhat/dh_view.css @@ -0,0 +1,130 @@ + +/*--------------------------------------------------------------------*/ +/*--- DHAT: a Dynamic Heap Analysis Tool dh_view.css ---*/ +/*--------------------------------------------------------------------*/ + +/* + This file is part of DHAT, a Valgrind tool for profiling the + heap usage of programs. + + Copyright (C) 2018 Mozilla Foundation + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + 02111-1307, USA. + + The GNU General Public License is contained in the file COPYING. +*/ + +html { + background: #cfcfcf; /* pale grey */ +} + +.section { + border-radius: 10px; + background-color: white; + padding: 1em; + margin: 0.5em 0; +} + +div.header { + font-weight: bold; + display: inline-block; + margin: 0 1.5em 0 0; + border-radius: 10px; + padding: 0.5em; + background-color: #cfcfcf; /* pale grey */ + -moz-user-select: none; + -webkit-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.hidden { + display: none; +} + +.error { + color: red; +} + +.invocation { + background-color: #bfd7d7; /* pale blue-grey */ +} + +.times { + background-color: #efdfbf; /* pale brown */ +} + +.arrow, .treeline { + background-color: white; +} + +.internal { + cursor: pointer; +} + +/* increasingly pale shades of green */ +.leaf.lt100 { background-color: #7fff7f; } +.leaf.lt32 { background-color: #8fff8f; } +.leaf.lt16 { background-color: #9fff9f; } +.leaf.lt8 { background-color: #afffaf; } +.leaf.lt4 { background-color: #bfffbf; } +.leaf.lt2 { background-color: #cfffcf; } +.leaf.lt1 { background-color: #dfffdf; } +.leaf.insig { background-color: #efffef; } + +/* increasingly pale shades of yellow */ +.collapsed.lt100 { background-color: #ffff7f; } +.collapsed.lt32 { background-color: #ffff8f; } +.collapsed.lt16 { background-color: #ffff9f; } +.collapsed.lt8 { background-color: #ffffaf; } +.collapsed.lt4 { background-color: #ffffbf; } +.collapsed.lt2 { background-color: #ffffcf; } +.collapsed.lt1 { background-color: #ffffdf; } +.collapsed.insig { background-color: #ffffef; } + +/* increasingly pale shades of blue */ +.expanded.lt100 { background-color: #7f7fff; } +.expanded.lt32 { background-color: #8f8fff; } +.expanded.lt16 { background-color: #9f9fff; } +.expanded.lt8 { background-color: #afafff; } +.expanded.lt4 { background-color: #bfbfff; } +.expanded.lt2 { background-color: #cfcfff; } +.expanded.lt1 { background-color: #dfdfff; } +.expanded.insig { background-color: #efefff; } + +.bold { + font-weight: bold; +} + +.threshold { + background-color: #dfdfdf; /* pale grey */ +} + +.noselect { + -moz-user-select: none; + -webkit-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.legend, .timings { + font-size: 80%; + padding: 0 1em; +} + +.debug { + font-size: 80%; +} diff --git a/dhat/dh_view.html b/dhat/dh_view.html new file mode 100644 index 0000000000..72f6b9d6fc --- /dev/null +++ b/dhat/dh_view.html @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/dhat/dh_view.js b/dhat/dh_view.js new file mode 100644 index 0000000000..d0c56b7260 --- /dev/null +++ b/dhat/dh_view.js @@ -0,0 +1,1445 @@ + +//--------------------------------------------------------------------*/ +//--- DHAT: a Dynamic Heap Analysis Tool dh_view.js ---*/ +//--------------------------------------------------------------------*/ + +/* + This file is part of DHAT, a Valgrind tool for profiling the + heap usage of programs. + + Copyright (C) 2018 Mozilla Foundation + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + 02111-1307, USA. + + The GNU General Public License is contained in the file COPYING. +*/ + +/* + Parts of this file are derived from Firefox, copyright Mozilla Foundation, + and are subject to the terms of the Mozilla Public License, v. 2.0. A copy + copy of the MPL can be obtained at http://mozilla.org/MPL/2.0/. +*/ + +// Test this file by loading dh_view.html?test=1. That runs the tests in +// dh_test.js and gives pass/fail indicators. + +"use strict"; + +//------------------------------------------------------------// +//--- Globals ---// +//------------------------------------------------------------// + +// Important HTML elements. +let gInput; +let gSelect; +let gHeaderDiv, gTestingDiv, gMainDiv, gLegendDiv, gTimingsDiv; + +// The name of the loaded file. +let gFilename; + +// The object extracted from the JSON input. +let gData; + +// The root of the radix tree build from gData. A radix tree is a +// space-optimized prefix tree in which each node that is the only child is +// merged with its parent. +let gRoot; + +// Data relating to the sort metrics. +// +// - isDefault: True for the default sort metric. +// - label: Used in the drop-down menu. +// - bolds: Which fields to highlight in the output. +// - cmpField: Field used to sort the radix tree. +// - sig: Significance function used to determine aggregate nodes. +// - sigLabel: Significance threshold description function. +// +const gSelectData = [ + { + label: "Total (bytes)", + bolds: { "totalTitle": 1, "totalBytes": 1 }, + cmpField: "_totalBytes", + sig: (aT) => aT._totalBytes >= 0.01 * gRoot._totalBytes, + sigLabel: () => `\ +total >= ${bytesAndPerc(0.01 * gRoot._totalBytes, gRoot._totalBytes)}` + }, + { + isDefault: true, + label: "Total (blocks)", + bolds: { "totalTitle": 1, "totalBlocks": 1 }, + cmpField: "_totalBlocks", + sig: (aT) => aT._totalBlocks >= 0.01 * gRoot._totalBlocks, + sigLabel: () => `\ +total >= ${blocksAndPerc(0.01 * gRoot._totalBlocks, gRoot._totalBlocks)}` + }, + // No "Total (bytes), tiny" because it's extremely unlikely that an AP with a + // tiny average size will take up a significant number of bytes. + { + label: "Total (blocks), tiny", + bolds: { "totalTitle": 1, "totalBlocks": 1, "totalAvgSizeBytes": 1 }, + cmpField: "_totalBlocks", + sig: (aT) => aT._totalBlocks >= 0.005 * gRoot._totalBlocks && + aT._totalAvgSizeBytes() <= 16, + sigLabel: () => `\ +(total >= ${blocksAndPerc(0.005 * gRoot._totalBlocks, gRoot._totalBlocks)}) && \ +(total avg size <= ${bytes(16)})` + }, + // No "Total (bytes), short-lived", because an AP with few large, short-lived + // blocks is unlikely. (In contrast, "Total (blocks), short-lived" is useful, + // because an AP with many small, short-lived blocks *is* likely.) And if + // such an AP existed, it'll probably show up in "Total (bytes), zero reads + // or zero writes" or "Total (bytes), low-access" anyway, because there's + // little time for accesses in 500 instructions. + { + label: "Total (blocks), short-lived", + bolds: { "totalTitle": 1, "totalBlocks": 1, "totalAvgLifetimeInstrs": 1 }, + cmpField: "_totalBlocks", + sig: (aT) => aT._totalBlocks >= 0.005 * gRoot._totalBlocks && + aT._totalAvgLifetimeInstrs() <= 500, + sigLabel: () => `\ +(total >= ${blocksAndPerc(0.005 * gRoot._totalBlocks, gRoot._totalBlocks)}) && \ +(total avg lifetime <= ${instrs(500)})` + }, + { + label: "Total (bytes), zero reads or zero writes", + bolds: { "totalTitle": 1, "totalBytes": 1, + "readsTitle": 1, "readsBytes": 1, + "writesTitle": 1, "writesBytes": 1, + }, + cmpField: "_totalBytes", + sig: (aT) => aT._totalBytes >= 0.005 * gRoot._totalBytes && + (aT._readsBytes === 0 || aT._writesBytes === 0), + sigLabel: () => `\ +(total >= ${bytesAndPerc(0.005 * gRoot._totalBytes, gRoot._totalBytes)}) && \ +((reads == ${bytes(0)}) || (writes == ${bytes(0)}))` + }, + { + label: "Total (blocks), zero reads or zero writes", + bolds: { "totalTitle": 1, "totalBlocks": 1, + "readsTitle": 1, "readsBytes": 1, + "writesTitle": 1, "writesBytes": 1, + }, + cmpField: "_totalBlocks", + sig: (aT) => aT._totalBlocks >= 0.005 * gRoot._totalBlocks && + (aT._readsBytes === 0 || aT._writesBytes === 0), + sigLabel: () => `\ +(total >= ${blocksAndPerc(0.005 * gRoot._totalBlocks, gRoot._totalBlocks)}) && \ +((reads == ${bytes(0)}) || (writes == ${bytes(0)}))` + }, + { + label: "Total (bytes), low-access", + bolds: { "totalTitle": 1, "totalBytes": 1, + "readsTitle": 1, "readsAvgPerByte": 1, + "writesTitle": 1, "writesAvgPerByte": 1, + }, + cmpField: "_totalBytes", + sig: (aT) => aT._totalBytes >= 0.005 * gRoot._totalBytes && + aT._readsBytes !== 0 && + aT._writesBytes !== 0 && + (aT._readsAvgPerByte() <= 0.4 || + aT._writesAvgPerByte() <= 0.4), + sigLabel: () => `\ +(total >= ${bytesAndPerc(0.005 * gRoot._totalBytes, gRoot._totalBytes)}) && \ +(reads != ${bytes(0)}) && \ +(writes != ${bytes(0)}) && \ +((reads <= ${perByte(0.4)}) || (writes <= ${perByte(0.4)}))` + }, + { + label: "Total (blocks), low-access", + bolds: { "totalTitle": 1, "totalBlocks": 1, + "readsTitle": 1, "readsAvgPerByte": 1, + "writesTitle": 1, "writesAvgPerByte": 1, + }, + cmpField: "_totalBlocks", + sig: (aT) => aT._totalBlocks >= 0.005 * gRoot._totalBlocks && + aT._readsBytes !== 0 && + aT._writesBytes !== 0 && + (aT._readsAvgPerByte() <= 0.4 || + aT._writesAvgPerByte() <= 0.4), + sigLabel: () => `\ +(total >= ${blocksAndPerc(0.005 * gRoot._totalBlocks, gRoot._totalBlocks)}) && \ +(reads != ${bytes(0)}) && \ +(writes != ${bytes(0)}) && \ +((reads <= ${perByte(0.4)}) || (writes <= ${perByte(0.4)}))` + }, + // No "Total (avg size bytes)": not interesting. + // No "Total (avg lifetime instrs)": covered by "Total (blocks), short-lived". + // No "Max (bytes)": not interesting, and unclear how to sort. + // No "Max (blocks)": not interesting, and unclear how to sort. + // No "Max (avg size bytes)": not interesting, and unclear how to sort. + { + label: "At t-gmax (bytes)", + bolds: { "atTGmaxTitle": 1, "atTGmaxBytes": 1 }, + cmpField: "_atTGmaxBytes", + sig: (aT) => aT._atTGmaxBytes >= 0.01 * gRoot._atTGmaxBytes, + sigLabel: () => `\ +at-t-gmax >= ${bytesAndPerc(0.01 * gRoot._atTGmaxBytes, gRoot._atTGmaxBytes)}` + }, + // No "At t-gmax (blocks)": not interesting. + // No "At t-gmax (avg size bytes)": not interesting. + { + label: "At t-end (bytes)", + bolds: { "atTEndTitle": 1, "atTEndBytes": 1 }, + cmpField: "_atTEndBytes", + sig: (aT) => aT._atTEndBytes >= 0.01 * gRoot._atTEndBytes, + sigLabel: () => `\ +at-t-end >= ${bytesAndPerc(0.01 * gRoot._atTEndBytes, gRoot._atTEndBytes)}` + }, + // No "At t-end (blocks)": not interesting. + // No "At t-end (avg size bytes)": not interesting. + { + label: "Reads (bytes)", + bolds: { "readsTitle": 1, "readsBytes": 1 }, + cmpField: "_readsBytes", + sig: (aT) => aT._readsBytes >= 0.01 * gRoot._readsBytes, + sigLabel: () => `\ +reads >= ${bytesAndPerc(0.01 * gRoot._readsBytes, gRoot._readsBytes)}` + }, + { + label: "Reads (bytes), high-access", + bolds: { "readsTitle": 1, "readsBytes": 1, "readsAvgPerByte": 1 }, + cmpField: "_readsBytes", + sig: (aT) => aT._readsBytes >= 0.005 * gRoot._readsBytes && + (aT._readsAvgPerByte() >= 1000 || + aT._writesAvgPerByte() >= 1000), + sigLabel: () => `\ +(reads >= ${bytesAndPerc(0.005 * gRoot._readsBytes, gRoot._readsBytes)}) && \ +((reads >= ${perByte(1000)}) || (writes >= ${perByte(1000)}))` + }, + // No "Reads (avg per byte)": covered by other access-related ones. + { + label: "Writes (bytes)", + bolds: { "writesTitle": 1, "writesBytes": 1 }, + cmpField: "_writesBytes", + sig: (aT) => aT._writesBytes >= 0.01 * gRoot._writesBytes, + sigLabel: () => `\ +writes >= ${bytesAndPerc(0.01 * gRoot._writesBytes, gRoot._writesBytes)}` + }, + { + label: "Writes (bytes), high-access", + bolds: { "writesTitle": 1, "writesBytes": 1, "writesAvgPerByte": 1 }, + cmpField: "_writesBytes", + sig: (aT) => aT._writesBytes >= 0.005 * gRoot._writesBytes && + (aT._readsAvgPerByte() >= 1000 || + aT._writesAvgPerByte() >= 1000), + sigLabel: () => `\ +(writes >= ${bytesAndPerc(0.005 * gRoot._writesBytes, gRoot._writesBytes)}) && \ +((reads >= ${perByte(1000)}) || (writes >= ${perByte(1000)}))` + } + // No "Writes (avg per byte)": covered by other access-related ones. +]; + +//------------------------------------------------------------// +//--- Utilities ---// +//------------------------------------------------------------// + +// Assertion. Fails if aMsg is missing. +function assert(aCond, aMsg) { + if (!aCond || !aMsg) { + throw new Error(`assertion failed: ${aMsg}`); + } +} + +// Division function that returns 0 instead of NaN for 0/0, which is what we +// always want. +function div(aNum, aDenom) { + return aNum === 0 && aDenom === 0 ? 0 : aNum / aDenom; +} + +// Execute a function, printing any exception to the page. +function tryFunc(aFunc) { + try { + aFunc(); + } catch (ex) { + // Clear gRoot, so that any old or partially-built new value doesn't hang + // around if after this exception is thrown. + gRoot = undefined; + clearMainDivWithText(ex.toString(), "error"); + throw ex; + } +} + +// Put some text in a div at the bottom of the page. Useful for debugging. +function debug(x) { + let section = appendElement(document.body, "div", "section"); + appendElementWithText(section, "div", JSON.stringify(x), "debug noselect"); +} + +//------------------------------------------------------------// +//--- Radix tree building ---// +//------------------------------------------------------------// + +// Notes about the TreeNode kinds: +// +// -------------------------------------------------------------------- +// Leaf Internal Aggregate +// -------------------------------------------------------------------- +// Has this._kids? No Yes No +// Has this._max*? Yes No No +// Has this._accesses? Maybe Maybe No +// Allowed this._sig values? Self,None Self,Desc,None None +// How many this._add() calls? 1 1+ 1+ +// -------------------------------------------------------------------- +// +const kLeaf = 1; +const kInternal = 2; +const kAgg = 3; + +function TreeNode(aKind, aFrames) { + this._kind = aKind; + + this._totalBytes = 0; + this._totalBlocks = 0; + + this._totalLifetimesInstrs = 0; + + // These numbers only make sense for leaf nodes. Unlike total stats, which + // can be summed, _maxBytes/_maxBlocks for two APs can't be easily combined + // because the maxes may have occurred at different times. + if (this._kind === kLeaf) { + this._maxBytes = 0; + this._maxBlocks = 0; + } + + this._atTGmaxBytes = 0; + this._atTGmaxBlocks = 0; + + this._atTEndBytes = 0; + this._atTEndBlocks = 0; + + this._readsBytes = 0; + this._writesBytes = 0; + + // this._accesses is left undefined. It will be added if necessary. + // The possible values have the following meanings: + // - undefined means "unset accesses" (i.e. new node, never been set) + // - length==0 means "no accesses" (i.e. some kids have accesses and some + // don't, or all kids have accesses but in different sizes) + // - length>0 means "accesses" (i.e. all kids have accesses and all the same + // size) + + // If a node would only have a single child, we instead effectively inline it + // in the parent. Therefore a node can have multiple frames. + this._frames = aFrames; + + // this._kids is left undefined. It will be added if necessary. + + // this._sig is added later, by sigTree(). +} + +TreeNode.prototype = { + _add(aTotalBytes, aTotalBlocks, aTotalLifetimesInstrs, aMaxBytes, + aMaxBlocks, aAtTGmaxBytes, aAtTGmaxBlocks, aAtTEndBytes, + aAtTEndBlocks, aReadsBytes, aWritesBytes, aAccesses) { + + // We ignore this._kind, this._frames, and this._kids. + + this._totalBytes += aTotalBytes; + this._totalBlocks += aTotalBlocks; + this._totalLifetimesInstrs += aTotalLifetimesInstrs; + + if (this._kind === kLeaf) { + // Leaf nodes should only be added to once, because DHAT currently + // produces records with unique locations. If we remove addresses from + // frames in the future then something must be done here to sum non-zero + // _maxBytes and _maxBlocks values, but it's unclear exactly what. Range + // arithmetic is a (complicated) possibility. + assert(this._maxBytes === 0, "bad _maxBytes: " + this._maxBytes); + assert(this._maxBlocks === 0, "bad _maxBlocks: " + this._maxBlocks); + this._maxBytes += aMaxBytes; + this._maxBlocks += aMaxBlocks; + } + + this._atTGmaxBytes += aAtTGmaxBytes; + this._atTGmaxBlocks += aAtTGmaxBlocks; + + this._atTEndBytes += aAtTEndBytes; + this._atTEndBlocks += aAtTEndBlocks; + + this._readsBytes += aReadsBytes; + this._writesBytes += aWritesBytes; + + if (this._kind !== kAgg) { + if (!this._accesses && aAccesses) { + // unset accesses += accesses --> has accesses (must clone the array) + this._accesses = aAccesses.slice(); + } else if (this._accesses && aAccesses && + this._accesses.length === aAccesses.length) { + // accesses += accesses (with matching lengths) --> accesses + for (let i = 0; i < this._accesses.length; i++) { + this._accesses[i] += aAccesses[i]; + } + } else { + // any other combination --> no accesses + this._accesses = []; + } + } else { + assert(!this._accesses, "agg nodes cannot have accesses"); + } + }, + + _addAP(aAP) { + this._add(aAP.tb, aAP.tbk, aAP.tli, aAP.mb, aAP.mbk, aAP.gb, aAP.gbk, + aAP.fb, aAP.fbk, aAP.rb, aAP.wb, aAP.acc); + }, + + // This is called in two cases. + // - Splitting a node, where we are adding to a fresh node (i.e. effectively + // cloning a node). + // - Aggregating multiple nodes. + _addNode(aT) { + this._add(aT._totalBytes, aT._totalBlocks, aT._totalLifetimesInstrs, + aT._maxBytes, aT._maxBlocks, aT._atTGmaxBytes, aT._atTGmaxBlocks, + aT._atTEndBytes, aT._atTEndBlocks, + aT._readsBytes, aT._writesBytes, aT._accesses); + }, + + // Split the node after the aTi'th internal frame. The inheriting kid will + // get the post-aTi frames; the new kid will get aNewFrames. + _split(aTi, aAP, aNewFrames) { + // kid1 inherits t's kind and values. + let inheritedFrames = this._frames.splice(aTi + 1); + let kid1 = new TreeNode(this._kind, inheritedFrames); + if (this._kids) { + kid1._kids = this._kids; + } + kid1._addNode(this); + + // Put all remaining frames into kid2. + let kid2 = new TreeNode(kLeaf, aNewFrames); + kid2._addAP(aAP); + + // Update this. + if (this._kind === kLeaf) { + // Convert to an internal node. + this._kind = kInternal; + assert(this.hasOwnProperty("_maxBytes"), "missing _maxBytes"); + assert(this.hasOwnProperty("_maxBlocks"), "missing _maxBlocks"); + delete this._maxBytes; + delete this._maxBlocks; + } + this._kids = [kid1, kid2]; + this._addAP(aAP); + }, + + _totalAvgSizeBytes() { + return div(this._totalBytes, this._totalBlocks); + }, + + _totalAvgLifetimeInstrs() { + return div(this._totalLifetimesInstrs, this._totalBlocks); + }, + + _maxAvgSizeBytes() { + assert(this._kind === kLeaf, "non-leaf node"); + return div(this._maxBytes, this._maxBlocks); + }, + + _atTGmaxAvgSizeBytes() { + return div(this._atTGmaxBytes, this._atTGmaxBlocks); + }, + + _atTEndAvgSizeBytes() { + return div(this._atTEndBytes, this._atTEndBlocks); + }, + + _readsAvgPerByte() { + return div(this._readsBytes, this._totalBytes); + }, + + _writesAvgPerByte() { + return div(this._writesBytes, this._totalBytes); + } +} + +// Check if the fields in `aFields` are present in `aObj`. +function checkFields(aObj, aFields) { + for (let f of aFields) { + if (!aObj.hasOwnProperty(f)) { + throw new Error(`data file is missing a field: ${f}`); + } + } +} + +// Do basic checking of an AP read from file. +function checkAP(aAP) { + let fields = ["tb", "tbk", "tli", + "mb", "mbk", + "gb", "gbk", + "fb", "fbk", + "rb", "wb", + "fs"]; + checkFields(aAP, fields); +} + +// Access counts latch as 0xffff. Treating 0xffff as Infinity gives us exactly +// the behaviour we want, e.g. Infinity + 1 = Infinity. +function normalizeAccess(aAcc) { + if (aAcc < 0xffff) { + return aAcc; + } + if (aAcc === 0xffff) { + return Infinity; + } + assert(false, "too-large access value"); +} + +const kExpectedFileVersion = 1; + +// Build gRoot from gData. +function buildTree() { + // Check global values. + let fields = ["dhatFileVersion", + "cmd", "pid", + "mi", "ei", + "aps", "ftbl"]; + checkFields(gData, fields); + if (gData.dhatFileVersion != kExpectedFileVersion) { + throw Error(`data file has version number ${gData.dhatFileVersion}, ` + + `expected version number ${kExpectedFileVersion}`); + } + + // Build the radix tree. Nodes are in no particular order to start with. The + // algorithm is tricky because we need to use internal frames when possible. + gRoot = new TreeNode(kLeaf, [0]); // Frame 0 is always "[root]". + + for (let [i, ap] of gData.aps.entries()) { + checkAP(ap); + + // Decompress the run-length encoding in `acc`, if present. + if (ap.acc) { + let acc = []; + for (let i = 0; i < ap.acc.length; i++) { + if (ap.acc[i] < 0) { + // A negative number encodes a repeat count. The following entry has + // the value to be repeated. + let reps = -ap.acc[i++]; + let val = ap.acc[i]; + for (let j = 0; j < reps; j++) { + acc.push(normalizeAccess(val)); + } + } else { + acc.push(normalizeAccess(ap.acc[i])); + } + } + ap.acc = acc; + } + + // The first AP is a special case, because we have to build gRoot. + if (i === 0) { + gRoot._frames.push(...ap.fs); + gRoot._addAP(ap); + continue; + } + + let t = gRoot; // current node + let ti = 0; // current frame index within t + let done = false; + + // In the examples below, tree nodes have the form `abcd:N-Xs`, where + // `abcd` is a frame sequence (and `-` is an empty sequence), `N` is a node + // value, and `Xs` are the node's children. + + for (let [j, kidFrame] of ap.fs.entries()) { + + // Search for kidFrame among internal frames. + if (ti + 1 < t._frames.length) { + // t has an internal frame at the right index. + + if (t._frames[ti + 1] === kidFrame) { + // The internal frame matches. Move to t's next internal frame. + ti++; + } else { + // The internal frame doesn't match. Split the node. + // + // E.g. abcd:20-[] + abef:10 => ab:30-[cd:20-[], ef:10-[]] + t._split(ti, ap, ap.fs.slice(j)); + done = true; + break; + } + + } else { + // We've run out of internal frames in t. Consider t's kids. + + if (!t._kids) { + // No kids; this must be a supersequence of an existing sequence. + // Split t; the inheriting kid will get no frames, the new kid will + // get the leftover frames. + // + // E.g. ab:20-[] + abcd:10 => ab:30-[-:20-[], cd:10-[]] + t._split(ti, ap, ap.fs.slice(j)); + done = true; + break; + } + + t._addAP(ap); + + // Search for the frame among the kids. + let kid; + for (let k of t._kids) { + if (k._frames[0] === kidFrame) { + kid = k; + break; + } + } + if (kid) { + // Found it. Move to it. + t = kid; + ti = 0; + } else { + // Didn't find it. Put all remaining frames into a new leaf node. + // + // E.g. ab:20-[c:10-Xs, d:10-Ys] + abef:10 => + // ab:30-[c:10-Xs, d:10-Ys, ef:10-[]] + kid = new TreeNode(kLeaf, ap.fs.slice(j)); + kid._addAP(ap); + t._kids.push(kid); + done = true; + break; + } + } + } + + if (!done) { + // If we reach here, either: + // - ap's frames match an existing frame sequence, in which case we + // just need to _addAP(); or + // - ap's frames are a subsequence of an existing sequence, in which + // case we must split. + + if (ti + 1 < t._frames.length) { + // A subsequence of an existing sequence that ends within t's internal + // frames. Split, creating an empty node. + // + // E.g. abcd:20-Xs + ab:10 => ab:30-[cd:20-Xs, -:10-[]] + t._split(ti, ap, []); + + } else if (!t._kids) { + // This is impossible because DHAT currently produces records with + // unique locations. If we remove addresses from frames in the future + // then duplicate locations will occur, and the following code is how + // it must be handled. + throw Error(`data file contains a repeated location`); + + // Matches an existing sequence that doesn't end in node with empty + // frames. Add the AP. + // + // E.g. ab:20-[] + ab:10 => ab:30-[] + t._addAP(ap); + + } else { + // Look for a kid with empty frames. + let emptyKid; + for (let k of t._kids) { + if (k._frames.length === 0) { + emptyKid = k; + break; + } + } + + if (emptyKid) { + // This is impossible because DHAT currently produces records with + // unique locations. If we remove addresses from frames in the future + // then duplicate locations will occur, and the following code is how + // it must be handled. + throw Error(`data file contains a repeated location`); + + // Matches an existing sequence that ends in a node with empty + // frames. Add the AP. + // + // E.g. ab:20-[c:10-Xs, -:10-[]] + ab:10 => ab:30-[c:10-Xs, -:20-[]] + t._addAP(ap); + emptyKid._addAP(ap); + + } else { + // A subsequence of an existing sequence that ends at the end of t's + // internal frames. Append an empty node. + // + // E.g. ab:20-[c:10-Xs, d:10-Ys] + ab:10 => + // ab:30-[c:10-Xs, d:10-Ys, -:10-[]] + let newKid = new TreeNode(kLeaf, []); + newKid._addAP(ap); + + t._kids.push(newKid); + t._addAP(ap); + } + } + } + + } +} + +//------------------------------------------------------------// +//--- Pretty printers ---// +//------------------------------------------------------------// + +// Using Intl.NumberFormat makes things faster than using toLocaleString() +// repeatedly. +const kPFormat = new Intl.NumberFormat(undefined, { maximumFractionDigits: 2, style: "percent" }); +const kDFormat = new Intl.NumberFormat(undefined, { maximumFractionDigits: 2 }); // decimal +const kTFormat = new Intl.NumberFormat(); // time + +function perc(aNum, aDenom) { + return kPFormat.format(div(aNum, aDenom)); +} + +function perMinstr(aN) { + return `${kDFormat.format(div(1000000 * aN, gData.ei))}/Minstr`; +} + +function bytes(aN) { + return `${kDFormat.format(aN)} bytes`; +} + +function bytesAndPerc(aN, aTotalN) { + return `${bytes(aN)} (${perc(aN, aTotalN)})`; +} + +function bytesAndPercAndRate(aN, aTotalN) { + return `${bytes(aN)} (${perc(aN, aTotalN)}, ${perMinstr(aN)})`; +} + +function blocks(aN) { + return `${kDFormat.format(aN)} blocks`; +} + +function blocksAndPerc(aN, aTotalN) { + return `${blocks(aN)} (${perc(aN, aTotalN)})`; +} + +function blocksAndPercAndRate(aN, aTotalN) { + return `${blocks(aN)} (${perc(aN, aTotalN)}, ${perMinstr(aN)})`; +} + +function avgSizeBytes(aN) { + return `avg size ${bytes(aN)}`; +} + +function perByte(aN) { + return `${kDFormat.format(aN)}/byte`; +} + +function instrs(aN) { + return `${kDFormat.format(aN)} instrs`; +} + +function avgLifetimeInstrs(aN) { + return `avg lifetime ${instrs(aN)}`; +} + +function accesses(aAccesses) { + // Make zero stand out. + if (aAccesses === 0) { + return "-"; + } + + if (aAccesses === Infinity) { + return "∞"; + } + + // Don't use toLocaleString() -- in this case the values rarely reach + // 100,000, and the grid formatting means the separators tend to make the + // numbers harder to read. (And locales such as fr-FR use ' ' as the + // separator, which conflicts with our use of ' ' between values!) + return aAccesses.toString(); +} + +function ms(aNum) { + // This function is called only a handful of times, so there is no need to + // use Intl.NumberFormat. + return aNum !== undefined ? `${kTFormat.format(aNum)}ms` : "n/a"; +} + +//------------------------------------------------------------// +//--- DOM manipulation ---// +//------------------------------------------------------------// + +const kDocumentTitle = "DHAT Viewer"; + +document.title = kDocumentTitle; + +function appendElement(aP, aTagName, aClassName) { + let e = document.createElement(aTagName); + if (aClassName) { + e.className = aClassName; + } + aP.appendChild(e); + return e; +} + +function appendElementWithText(aP, aTagName, aText, aClassName) { + let e = appendElement(aP, aTagName, aClassName); + e.textContent = aText; + return e; +} + +function appendText(aP, aText) { + let e = document.createTextNode(aText); + aP.appendChild(e); + return e; +} + +function clearDiv(aDiv) { + // Replace aDiv with an empty node. + assert(aDiv, "no div given"); + let tmp = aDiv.cloneNode(/* deep = */ false); + aDiv.parentNode.replaceChild(tmp, aDiv); + return tmp; +} + +function clearMainDiv() { + gMainDiv = clearDiv(gMainDiv); +} + +function clearTimingsDiv() { + gTimingsDiv = clearDiv(gTimingsDiv); +} + +function clearMainDivWithText(aText, aClassName) { + clearMainDiv(); + appendElementWithText(gMainDiv, "span", aText, aClassName); +} + +function appendInvocationAndTimes(aP) { + let v, v1, v2; + + v = "Invocation {\n"; + v += ` Command: ${gData.cmd}\n`; + v += ` PID: ${gData.pid}\n`; + v += "}\n\n"; + + appendElementWithText(aP, "span", v, "invocation"); + + v = "Times {\n"; + + v1 = perc(gData.mi, gData.ei); + v += ` t-gmax: ${instrs(gData.mi)} (${v1} of program duration)\n`; + v += ` t-end: ${instrs(gData.ei)}\n`; + + v += "}\n\n"; + + appendElementWithText(aP, "span", v, "times"); +} + +// Arrows indicating what state a node is in. +const kNoKidsArrow = "─ "; // cannot change +const kHidingKidsArrow = "▶ "; // expandible +const kShowingKidsArrow = "▼ "; // collapsible + +// HTML doesn't have a tree element, so we fake one with text. One nice +// consequence is that you can copy and paste the output. The non-ASCII chars +// used (for arrows and tree lines) usually reproduce well when pasted into +// textboxes. +// +// - aT: The sub-tree to append. +// - aP: Parent HTML element to append to. +// - aBolds: Which fields to highlight in the output. +// - aPc: The percentage function. +// - aCmp: The comparison function. +// - aSig: The significance function. +// - aNodeIdNums: The node ID numbers, e.g. [1,2,3], which is printed "1.2.3". +// - aNumSibs: The number of siblings that aT has. +// - aOldFrames: Frames preceding this node's frames. +// - aTlFirst: Treeline for the first line of the node. +// - aTlRest: Treeline for the other lines of the node, and its kids. +// +function appendTreeInner(aT, aP, aBolds, aCmp, aPc, aSig, aNodeIdNums, + aNumSibs, aOldFrames, aTlFirst, aTlRest) { + // The primary element we'll be appending to. + let p; + + // We build up text fragments in up to seven groups: + // - pre-Bold1 (multiple) + // - Bold1 (single) + // - post-Bold1 (multiple) + // - Bold2 (single) + // - post-Bold2 (multiple) + // - Bold3 (single) + // - post-Bold3 (multiple) + // + // This is so that up to 3 bold sequences can be highlighted per line. + let frags, fi; + + // Clear the text fragments. + function clear() { + frags = [[], undefined, [], undefined, [], undefined, []]; + fi = 0; + } + + // Add a fragment. + // - aShowIfInsig: should we show this even in an insignificant node? + // - aIsBold: if this is shown, should it be bold? If undefined (as is + // common) it takes the same value as aShowIfInsig. + function fr(aStr, aShowIfInsig, aIsBold) { + if (!aShowIfInsig && aT._sig !== kSigSelf) { + return; + } + + if (aIsBold === undefined) { + aIsBold = aShowIfInsig; + } + + if (aIsBold) { + assert(fi === 0 || fi === 2 || fi === 4, "bad fragIndex (1)"); + assert(frags[fi + 1] === undefined, "bold already here"); + frags[fi + 1] = aStr; + fi += 2; + } else { + assert(fi === 0 || fi === 2 || fi === 4 || fi === 6, "bad fragIndex (2)"); + frags[fi].push(aStr); + } + } + + // Add a newline fragment (with a following treeline, unless aIsLast==true). + // - aShowIfInsig: should we show this even in an insignificant node? + // - aIsLast: is this the last newline for the node? + function nl(aShowIfInsig, aIsLast) { + assert(fi === 0 || fi === 2 || fi === 4 || fi === 6, "bad fragIndex (3)"); + if (!aShowIfInsig && aT._sig !== kSigSelf) { + return; + } + + frags[fi].push("\n"); + + // Alternate the non-bold fragments (each in a text node) and bold + // fragments (each in a span). + if (frags[0].length > 0) { + appendText(p, frags[0].join("")); + } + if (frags[1] !== undefined) { + appendElementWithText(p, "span", frags[1], "bold"); + } + if (frags[2].length > 0) { + appendText(p, frags[2].join("")); + } + if (frags[3] !== undefined) { + appendElementWithText(p, "span", frags[3], "bold"); + } + if (frags[4].length > 0) { + appendText(p, frags[4].join("")); + } + if (frags[5] !== undefined) { + appendElementWithText(p, "span", frags[5], "bold"); + } + if (frags[6].length > 0) { + appendText(p, frags[6].join("")); + } + + if (!aIsLast) { + appendElementWithText(p, "span", aTlRest, "treeline"); + clear(); + } + } + + clear(); + + // Traverse the kids, aggregating insignificant nodes. + let kids; + if (aT._kids) { + kids = []; + let agg, nAgg = 0; + + for (let kid of aT._kids) { + assert(kid._sig === kSigSelf || kid._sig === kSigDesc || + kid._sig === kSigNone, "kid _sig not set"); + + if (kid._sig !== kSigNone) { + // `kid` is at least partially significant. Just push it as-is. + kids.push(kid); + } else { + // `kid` is insignificant. Aggregate it. + if (!agg) { + // We fill in ._frames below, once we know how many kids were + // aggregated. + agg = new TreeNode(kAgg, undefined); + agg._sig = kSigNone; + kids.push(agg); + } + nAgg++; + agg._addNode(kid); + } + } + + if (agg) { + // Fill in agg._frames. + let insigFrame = `[${nAgg} insignificant]`; + agg._frames = [insigFrame]; + } + + kids.sort(aCmp); + } + // Note: need to use `kids` for the rest of this function, not `aT._kids`. + + // Put the percentage into a colour band. The obvious way to do this is + // with equal-sized bands (e.g. 0--20%, 20--40%, ...) but that doesn't work + // well because in practice we have few nodes with mid-to-high percentages, + // and many nodes with small percentages. So we use a logarithmic + // distribution instead, so small values are better distinguished. (This is + // reasonable in a way: a 2% node is twice as important as a 1%, a 4% node + // is twice as important as a 2% node, etc.) + let pc = aPc(aT); + let lt = (aT._sig !== kSigSelf) ? "insig" // insignificant nodes + : (pc < 1) ? "lt1" // 0% to 0.999% + : (pc < 2) ? "lt2" // 1% to 1.999% + : (pc < 4) ? "lt4" // 2% to 3.999% + : (pc < 8) ? "lt8" // 4% to 7.999% + : (pc < 16) ? "lt16" // 8% to 15.999% + : (pc < 32) ? "lt32" // 16% to 31.999% + : "lt100"; // 32% to 100% + + // Append the primary element. + let arrow; + if (kids) { + p = appendElement(aP, "span", lt + " internal expanded"); + p.onclick = toggleClass; + arrow = kShowingKidsArrow; + } else { + p = appendElement(aP, "span", lt + " leaf"); + arrow = kNoKidsArrow; + } + + // Node start: treeline and arrow. + appendElementWithText(p, "span", aTlFirst, "treeline"); + appendElementWithText(p, "span", arrow, "arrow"); + + let v1, v2, v3, v4, v5; + + // "AP" + node ID + kid count. + v1 = aNodeIdNums.join('.'); + v2 = aNumSibs + 1; + v3 = kids ? `(${kids.length} children) ` : ""; + fr(`AP ${v1}/${v2} ${v3}{`, true, false); + nl(true); + + // "Total". + v1 = bytesAndPercAndRate(aT._totalBytes, gRoot._totalBytes); + v2 = blocksAndPercAndRate(aT._totalBlocks, gRoot._totalBlocks); + v3 = avgSizeBytes(aT._totalAvgSizeBytes()); + v4 = avgLifetimeInstrs(aT._totalAvgLifetimeInstrs()); + v5 = perc(aT._totalAvgLifetimeInstrs(), gData.ei); + fr(" Total: ", aBolds.totalTitle); + fr(v1, aBolds.totalBytes); + fr(" in "); + fr(v2, aBolds.totalBlocks); + fr(", ", aBolds.totalAvgSizeBytes, false); + fr(v3, aBolds.totalAvgSizeBytes); + fr(", ", aBolds.totalAvgLifetimeInstrs, false); + fr(`${v4} (${v5} of program duration)`, aBolds.totalAvgLifetimeInstrs); + nl(aBolds.totalTitle); + + // "Max". + if (aT !== gRoot && aT._kind === kLeaf) { + assert(!kids, "leaf node has children"); + // These percentages are relative to the local totals, not the root + // totals. + v1 = bytes(aT._maxBytes); + v2 = blocks(aT._maxBlocks); + v3 = avgSizeBytes(aT._maxAvgSizeBytes()); + fr(` Max: ${v1} in ${v2}, ${v3}`); + nl(); + } + + // "At t-gmax". + v1 = bytesAndPerc(aT._atTGmaxBytes, gRoot._atTGmaxBytes); + v2 = blocksAndPerc(aT._atTGmaxBlocks, gRoot._atTGmaxBlocks); + v3 = avgSizeBytes(aT._atTGmaxAvgSizeBytes()); + fr(" At t-gmax: ", aBolds.atTGmaxTitle); + fr(v1, aBolds.atTGmaxBytes); + fr(` in ${v2}, ${v3}`); + nl(aBolds.atTGmaxTitle); + + // "At t-end". + v1 = bytesAndPerc(aT._atTEndBytes, gRoot._atTEndBytes); + v2 = blocksAndPerc(aT._atTEndBlocks, gRoot._atTEndBlocks); + v3 = avgSizeBytes(aT._atTEndAvgSizeBytes()); + fr(" At t-end: ", aBolds.atTEndTitle); + fr(v1, aBolds.atTEndBytes); + fr(` in ${v2}, ${v3}`); + nl(aBolds.atTEndTitle); + + // "Reads". + v1 = bytesAndPercAndRate(aT._readsBytes, gRoot._readsBytes); + v2 = perByte(aT._readsAvgPerByte()); + fr(" Reads: ", aBolds.readsTitle); + fr(v1, aBolds.readsBytes); + fr(", ", aBolds.readsBytes && aBolds.readsAvgPerByte, false); + fr(v2, aBolds.readsAvgPerByte); + nl(aBolds.readsTitle); + + // "Writes". + v1 = bytesAndPercAndRate(aT._writesBytes, gRoot._writesBytes); + v2 = perByte(aT._writesAvgPerByte()); + fr(" Writes: ", aBolds.writesTitle); + fr(v1, aBolds.writesBytes); + fr(", ", aBolds.writesBytes && aBolds.writesAvgPerByte, false); + fr(v2, aBolds.writesAvgPerByte); + nl(aBolds.writesTitle); + + // "Accesses". We show 32 per line (but not on aggregate nodes). + if (aT._accesses && aT._accesses.length > 0) { + let v = " Accesses: {"; + let prevN; + for (let [i, n] of aT._accesses.entries()) { + if ((i % 32) === 0) { + fr(v); + nl(); + v1 = i.toString().padStart(3, ' '); + v = ` [${v1}] `; + v += `${accesses(n)} `; + } else { + // Use a ditto mark for repeats. + v += (n === prevN && n !== 0) ? "〃 " : `${accesses(n)} `; + } + prevN = n; + } + fr(v); + nl(); + + fr(" }"); + nl(); + } + + // "Allocated at". + fr(" Allocated at {", true, false); + nl(true); + if (aT._kind === kAgg) { + // Don't print ancestor frames; just print the "insignificant" frame. + let isInsigFrame = (aFrm) => aFrm.indexOf(" insignificant]") >= 0; + assert(aT._frames.length === 1 && isInsigFrame(aT._frames[0]), + "bad aggregate node"); + fr(` ${aT._frames[0]}`, true, false); + nl(true); + } else { + // Start numbering frames from #1, unless it's the root node, in which case + // we show "#0: [root]". + let i = (aT === gRoot) ? 0 : 1; + + // Maybe show frames from ancestor nodes, excluding "[root]" (by starting + // at j=1). + for (let j = 1; j < aOldFrames.length; j++, i++) { + fr(` ^${i}: ${gData.ftbl[aOldFrames[j]]}`); + nl(false); + } + // Show frames from this node. + for (let j = 0; j < aT._frames.length; j++, i++) { + fr(` #${i}: ${gData.ftbl[aT._frames[j]]}`, true, false); + nl(true); + } + } + fr(" }", true, false); + nl(true); + + // End of node. + fr(`}`, true, false); + nl(true, true); + + // Do the kids. + if (kids) { + assert(aT._kind !== kLeaf, "leaf node has children"); + + p = appendElement(aP, "span", "kids"); + + // tlFirstFor{Most,Last} are shorter than tlRestFor{Most,Last} to allow + // space for the arrow. + let tlFirstForMost; + let tlRestForMost; + if (kids.length > 1) { + tlFirstForMost = aTlRest + "├─"; + tlRestForMost = aTlRest + "│ "; + } + let tlFirstForLast = aTlRest + "└─"; + let tlRestForLast = aTlRest + " "; + + for (let [i, kid] of kids.entries()) { + let n = aT._frames.length; + aOldFrames.push(...aT._frames); // append aT._frames to aOldFrames + aNodeIdNums.push(i + 1); + let isLast = i === kids.length - 1; + appendTreeInner(kid, p, aBolds, aCmp, aPc, aSig, aNodeIdNums, + kids.length - 1, aOldFrames, + !isLast ? tlFirstForMost : tlFirstForLast, + !isLast ? tlRestForMost : tlRestForLast); + aNodeIdNums.pop(i); + aOldFrames.splice(-n); // remove aT._frames from aOldFrames + } + } +} + +// Node significance. +// - kSigSelf: the node itself is significant. It will be shown in full. +// - kSigDesc: the node itself is insignificant, but it has one or more +// significant descendants. (This is not possible for the straightforward +// additive sort metrics like total-bytes, but it is possible for the +// non-additive ones like "Total (bytes), short-lived", "Total (bytes), +// low-access", etc.) It will be shown abbreviated. +// - kSigNone: the node itself is insignificant, and it has no significant +// descendants. It will be aggregated. +const kSigSelf = 3; +const kSigDesc = 2; +const kSigNone = 1; + +// Fill in the ._sig field of all tree nodes. +function sigTree(aT, aSig) { + let sig = false; + if (aT._kids) { + for (let kid of aT._kids) { + sig |= sigTree(kid, aSig); + } + } + + if (aSig(aT)) { + aT._sig = kSigSelf; + return true; + } + if (sig) { + aT._sig = kSigDesc; + return true; + } + aT._sig = kSigNone; + return false; +} + +function appendTree(aP, aBolds, aCmp, aPc, aSig) { + sigTree(gRoot, aSig); + + appendTreeInner(gRoot, aP, aBolds, aCmp, aPc, aSig, [1], 0, [], "", " "); +} + +function appendSignificanceThreshold(aP, aSigLabel) { + let v = `\nAP significance threshold: ${aSigLabel()}\n`; + appendElementWithText(aP, "span", v, "threshold"); +} + +// Check that aElem's class list contains at least one name from aClassNames. +function classListContains(aElem, aClassNames) { + for (let className of aClassNames) { + if (aElem.classList.contains(className)) { + return true; + } + } + return false; +} + +function assertClassListContains(aElem, aClassNames) { + assert(aElem, "undefined elem"); + assert(classListContains(aElem, aClassNames), + `none of ${JSON.stringify(aClassNames)} found in class list`); +} + +// Called when a node with kids is clicked on. +function toggleClass(aEvent) { + let clickedNode = aEvent.target; + let hasKidsNode; + if (classListContains(clickedNode, ["expanded", "collapsed"])) { + // The click must have been on a text node, so clickedNode is the node + // to toggle. + hasKidsNode = clickedNode; + } else { + // The click must have been on a span element, so the parent node is + // the node to toggle. + hasKidsNode = clickedNode.parentNode; + assertClassListContains(hasKidsNode, ["expanded", "collapsed"]); + } + hasKidsNode.classList.toggle("expanded"); + hasKidsNode.classList.toggle("collapsed"); + + // Element order: 0: treeline span, 1: arrow span, ... + let arrowSpan = hasKidsNode.childNodes[1]; + assertClassListContains(arrowSpan, ["arrow"]); + if (arrowSpan.textContent === kHidingKidsArrow) { + arrowSpan.textContent = kShowingKidsArrow; + } else if (arrowSpan.textContent === kShowingKidsArrow) { + arrowSpan.textContent = kHidingKidsArrow; + } else { + assert(false, `bad arrowSpan textContent`); + } + + // Toggle visibility of the span containing this node's kids. + let kidsSpan = hasKidsNode.nextSibling; + assertClassListContains(kidsSpan, ["kids"]); + kidsSpan.classList.toggle("hidden"); +} + +//------------------------------------------------------------// +//--- Top-level stuff ---// +//------------------------------------------------------------// + +// These arguments will be `undefined` when displayTree() is called without +// having read a file (e.g. when redisplaying with a different sort metric). +function displayTree(aTRead, aTParse, aTBuild) { + let tRead = aTRead === undefined ? 0 : aTRead; + let tParse = aTParse === undefined ? 0 : aTParse; + let tBuild = aTBuild === undefined ? 0 : aTBuild; + + // Get details relating to the chosen sort metrics. + let data = gSelectData[gSelect.selectedIndex]; + let bolds = data.bolds; + let label = data.label; + let cmpField = data.cmpField; + let sig = data.sig; + let sigLabel = data.sigLabel; + let cmp = (aT1, aT2) => { + // Try the specified sort metric. If that doesn't distinguish them, sort by + // _totalBytes. + let s1 = aT2[cmpField] - aT1[cmpField]; + return (s1 !== 0) ? s1 : aT2._totalBytes - aT1._totalBytes; + }; + let pc = (aT) => div(aT[cmpField], gRoot[cmpField]) * 100; + + // Update the page title. + document.title = `${kDocumentTitle} - ${gFilename} - ${label}`; + + // Build the main part of the page. + let now = performance.now(); + clearMainDiv(); + let pre = appendElement(gMainDiv, "pre"); + appendInvocationAndTimes(pre); + appendTree(pre, bolds, cmp, pc, sig); + appendSignificanceThreshold(pre, sigLabel); + let tDisplay = performance.now() - now; + + let tTotal = tRead + tParse + tBuild + tDisplay; + clearTimingsDiv(); + let timings = `\ +Processing time: \ +read:${ms(aTRead)} + \ +parse:${ms(aTParse)} + \ +build:${ms(aTBuild)} + \ +display:${ms(tDisplay)} = \ +total:${ms(tTotal)}\ +`; + appendElementWithText(gTimingsDiv, "p", timings); +} + +function loadFile() { + clearMainDivWithText("Loading..."); + + let now = performance.now(); + let file = gInput.files[0]; + gFilename = file.name; + + // Update the title. This will likely be overwritten very shortly, unless + // there's a file loading problem, in which case it's nice to have the + // correct filename in the title. + document.title = `${kDocumentTitle} - ${gFilename}`; + + let reader = new FileReader(); + reader.onload = function(aEvent) { + tryFunc(() => { + let tRead = performance.now() - now; + + let data = aEvent.target.result; + + now = performance.now(); + gData = JSON.parse(data); + let tParse = performance.now() - now; + + now = performance.now(); + buildTree(); + let tBuild = performance.now() - now; + + displayTree(tRead, tParse, tBuild); + }); + }; + + reader.onerror = function(aEvent) { + clearMainDivWithText("Error loading file", "error"); + }; + + reader.readAsText(file); +} + +function changeSortMetric() { + // If we have a tree, redisplay it for the new sort metric. + if (gRoot) { + tryFunc(() => { + displayTree(); + }); + } +} + +// Top-level setup when the page is first loaded. +function onLoad() { + // Check if tests should be run. + let params = new URLSearchParams(document.location.search.substring(1)); + let test = params.get("test"); + + // The header div. + gHeaderDiv = appendElement(document.body, "div", "section"); + + // The (hidden) input element. + let inputDiv = appendElement(gHeaderDiv, "div", "header"); + appendElementWithText(inputDiv, "div", "File"); + gInput = appendElement(inputDiv, "input", "hidden"); + gInput.type = "file"; + gInput.onchange = loadFile; + + // The button that triggers the hidden input element. + let b = appendElementWithText(inputDiv, "button", "Load…"); + b.onclick = () => gInput.click(); + + // The sort metric menu. + let selectDiv = appendElement(gHeaderDiv, "div", "header"); + appendElementWithText(selectDiv, "div", "Sort metric"); + gSelect = appendElement(selectDiv, "select"); + gSelect.onchange = changeSortMetric; + for (let [i, data] of gSelectData.entries()) { + let option = appendElementWithText(gSelect, "option", data.label); + option.value = i; + if (data.isDefault) { + option.selected = true; + } + } + + // The testing div, if necessary. + if (test) { + gTestingDiv = appendElement(document.body, "div", "testing"); + } + + // The main div. + gMainDiv = appendElement(document.body, "div", "section"); + appendElementWithText(gMainDiv, "span", "Load a DHAT data file to begin"); + + // The legend div. We show it even before loading a file so that new users + // are immediately aware that it exists. + gLegendDiv = appendElement(document.body, "div", "legend noselect"); + let p = appendElementWithText(gLegendDiv, "p", "Legend:"); + let ul = appendElement(p, "ul"); + appendElementWithText(ul, "li", "'t-gmax': time of global heap maximum " + + "(as measured in bytes)"); + appendElementWithText(ul, "li", "'t-end': time of program end"); + appendElementWithText(ul, "li", "'instrs': instructions"); + appendElementWithText(ul, "li", "'Minstr': mega-instruction, i.e. one " + + "million instructions"); + appendElementWithText(ul, "li", "'AP': allocation point"); + appendElementWithText(ul, "li", "'avg': average"); + appendElementWithText(ul, "li", "'-' (in accesses): zero"); + appendElementWithText(ul, "li", "'∞' (in accesses): leaf AP counts max out " + + "at 65534; larger counts are treated as " + + "infinity"); + appendElementWithText(ul, "li", "'〃' (in accesses): same as previous entry"); + + // The timings div. + gTimingsDiv = appendElement(document.body, "div", "timings noselect"); + + if (test) { + appendElementWithText(gHeaderDiv, "div", "TEST MODE", "header"); + var script = document.createElement("script"); + script.src = "dh_test.js"; + document.body.appendChild(script); + } +} + diff --git a/dhat/docs/dh-manual.xml b/dhat/docs/dh-manual.xml new file mode 100644 index 0000000000..56f5b6aee5 --- /dev/null +++ b/dhat/docs/dh-manual.xml @@ -0,0 +1,654 @@ + + %vg-entities; ]> + + + + DHAT: a dynamic heap analysis tool + +To use this tool, you must specify + on the Valgrind command line. + + + + +Overview + +DHAT is a tool for examining how programs use their heap +allocations. + +It tracks the allocated blocks, and inspects every memory access +to find which block, if any, it is to. It presents, on an allocation point +basis, information about these blocks such as sizes, lifetimes, numbers of +reads and writes, and read and write patterns. + +Using this information it is possible to identify allocation points with +the following characteristics: + + + + potential process-lifetime leaks: blocks allocated + by the point just accumulate, and are freed only at the end of the + run. + + excessive turnover: points which chew through a lot + of heap, even if it is not held onto for very long + + excessively transient: points which allocate very + short lived blocks + + useless or underused allocations: blocks which are + allocated but not completely filled in, or are filled in but not + subsequently read. + + blocks with inefficient layout -- areas never + accessed, or with hot fields scattered throughout the + block. + + +As with the Massif heap profiler, DHAT measures program progress +by counting instructions, and so presents all age/time related figures +as instruction counts. This sounds a little odd at first, but it +makes runs repeatable in a way which is not possible if CPU time is +used. + + + + + + +Using DHAT + +First off, as for normal Valgrind use, you probably want to compile with +debugging info (the option). But by contrast with normal +Valgrind use, you probably do want to turn optimisation on, since you should +profile your program as it will be normally run. + +Second, you need to run your program under DHAT to gather the profiling +information. + +Finally, you need to use DHAT's viewer (in a web browser) to get a +detailed presentation of that information. + + + +Running DHAT + +To run DHAT on a program prog, run: + + +The program will execute (slowly). Upon completion, summary statistics +that look like this will be printed: + + + +The first line shows how many heap blocks and bytes were allocated over +the entire execution. + +The second line shows how many heap blocks and bytes were alive at +t-gmax, i.e. the time when the heap size +reached its global maximum (as measured in bytes). + +The third line shows how many heap blocks and bytes were alive at +t-end, i.e. the end of execution. In other +words, how many blocks and bytes were not explicitly freed. + +The fourth and fifth lines show how many bytes within heap blocks were +read and written during the entire execution. + +These lines are moderately interesting at best. More useful information +can be seen with DHAT's viewer. + + + + + +Output File + +As well as printing summary information, DHAT also writes more detailed +profiling information to a file. By default this file is named +dhat.out.<pid> (where +<pid> is the program's process ID), but its name can +be changed with the option. This file is JSON, +and intended to be viewed by DHAT's viewer, which is described in the next +section. + +The default .<pid> suffix on the +output file name serves two purposes. Firstly, it means you don't have to +rename old log files that you don't want to overwrite. Secondly, and more +importantly, it allows correct profiling with the + option of programs that spawn child +processes. + +The output file can be big, many megabytes for large applications +built with full debugging information. + + + + + + + + +DHAT's Viewer + +DHAT's viewer can be run in a web browser by loading the file +dh_view.html. Use the "Load" button to choose +a DHAT output file to view. + + +The Output Header + +The first part of the output shows the program command and process ID. +For example: + + + +The second part of the output shows the +t-gmax and +t-end values again. For example: + + + + + + +The AP Tree + +The third part of the output is the largest and most interesting part, +showing the allocation point (AP) tree. + + +Structure + +The following image shows a screenshot of part of an AP tree. The font is very +small because this screenshot is intended to demonstrate the high-level +structure of the tree rather than the details within the text. + + + +Like any tree, it has a root node, leaf nodes, and non-leaf nodes. The +structure of the tree is shown by the lines connecting nodes. Child nodes are +beneath their parent and indented one level. + +The sub-trees beneath a non-leaf node can be collapsed or expanded by +clicking on the node. It is useful to collapse sub-trees that you aren't +interested in. + +Colours are meaningful, and are intended to ease tree navigation, but the +information they represent is also present within the text. (This means that +colour-blind users are not denied any information.) + +Each leaf node is coloured green. Each non-leaf node is coloured blue +and has a down arrow (▼) next to it when +its sub-tree is expanded. Each non-leaf node is coloured yellow and has a +left arrow (▶) next to it when its sub-tree +is collapsed. + +The shade of green, blue or yellow used for a node indicate its +significance. Darker shades represent greater significance (in terms of bytes +or blocks). + +Note that the entire output is text, even the arrows and lines connecting +nodes. This means you can copy and paste any part of the output easily into an +email, bug report, etc. + + + + +The Root Node + +The root node looks like this: + + + +The root node covers the entire execution. The information is a superset +of the information shown when DHAT ran, adding details such as allocation +rates, average block sizes, block lifetimes, and read and write ratios. The +next example will explain these in more detail. + + + + +Interior Nodes + +AP nodes further down the tree show information about a subset of +allocations. For example: + + (raw_vec.rs:669) + #4: 0x95CACC9: reserve (raw_vec.rs:492) + #5: 0x95CACC9: reserve (vec.rs:460) + #6: 0x95CACC9: push (vec.rs:989) + #7: 0x95CACC9: parse_token_trees_until_close_delim (tokentrees.rs:27) + #8: 0x95CACC9: syntax::parse::lexer::tokentrees::>::parse_token_tree (tokentrees.rs:81) + } +} +]]> + +The first line indicates the node's position in the tree. The +1.1 is a unique identifier for the node and +also says that it is the first child node 1 +(which is the root). The /25 says that it is +one of 25 children, i.e. it has 24 siblings. The (2 +children) says that this node node has two children of its +own. + +Allocations are aggregated by their allocation stack trace. The +Allocated at section shows the allocation +stack trace that is shared by all the blocks covered by this node. + +The Total line shows that this node +accounts for 4.02% of all bytes allocated during execution, and 7.72% of all +blocks. These percentages are useful for comparing the significance of +different nodes within a single profile; an AP that accounts for 10% of bytes +allocated is likely to be more interesting than one that accounts for +2%. + +The Total line also shows allocation +rates, measured in bytes and blocks per million instructions. These rates are +useful for comparing the significance of nodes across profiles made with +different workloads. + +Finally, the Total line shows the +average size and lifetimes of these blocks. + +The At t-gmax line says shows that no +blocks from this AP were alive when the global heap peak occurred. In other +words, these blocks do not contribute at all to the global heap peak. + +The At t-end line shows that no blocks +were from this AP were alive at shutdown. In other words, all those blocks were +explicitly freed before termination. + +The Reads and +Writes lines show how many bytes were read +within this AP's blocks, the fraction this represents of all heap reads, and +the read rate. Finally, it shows the read ratio, which is the number of reads +per byte. In this case the number is 0.29, which is quite low -- if no byte was +read twice, then only 29% of the allocated bytes, which means that at least 71% +of the bytes were never read! This suggests that the blocks are being +underutilized and might be worth optimizing. + +The Writes lines is similar to the +Reads line. In this case, at most 38% of the +bytes are ever written, and at least 62% of the bytes were never written. + + +The Reads and +Writes measurements suggest that the blocks +are being under-utilised and might be worth optimizing. Having said that, this +kind of under-utilisation is common in data structures that grow, such as +vectors and hash tables, and isn't always fixable. + + + + +Leaf Nodes + +This is a leaf node: + + (raw_vec.rs:669) + ^4: 0x95CACC9: reserve (raw_vec.rs:492) + ^5: 0x95CACC9: reserve (vec.rs:460) + ^6: 0x95CACC9: push (vec.rs:989) + ^7: 0x95CACC9: parse_token_trees_until_close_delim (tokentrees.rs:27) + ^8: 0x95CACC9: syntax::parse::lexer::tokentrees::>::parse_token_tree (tokentrees.rs:81) + ^9: 0x95CAC39: parse_token_trees_until_close_delim (tokentrees.rs:26) + ^10: 0x95CAC39: syntax::parse::lexer::tokentrees::>::parse_token_tree (tokentrees.rs:81) + #11: 0x95CAC39: parse_token_trees_until_close_delim (tokentrees.rs:26) + #12: 0x95CAC39: syntax::parse::lexer::tokentrees::>::parse_token_tree (tokentrees.rs:81) + } +} +]]> + +The 1.1.1.1/2 indicates that this node +is a great-grandchild of the root; is the first grandchild of the node in the +previous example; and has no children. + +Leaf nodes contain an additional Max +line, indicating the peak memory use for the blocks covered by this AP. (This +peak may have occurred at a time other than +t-gmax.) In this case, 31,460,298 bytes were +allocated from this AP, but the maximum size alive at once was 16,779,136 +bytes. + +Stack frames that begin with a ^ rather +than a # are copied from ancestor nodes. +(In this example, the first 8 frames are identical to those from the node in +the previous example.) These frames could be found by tracing back through +ancestor nodes, but that can be annoying, which is why they are duplicated. +This also means that each node makes complete sense on its own. + + + + +Access Counts + +If all blocks covered by an AP node have the same size, an additional +Accesses field will be present. It indicates +how the reads and writes within these blocks were distributed. For +example: + + + +Every block covered by this AP was 32 bytes. Within all of those blocks, +byte 0 was accessed (read or written) 65,547 times, byte 1 was accessed 7 +times, byte 2 was accessed 8 times, and so on. + +The ditto symbol (〃) means "same access +count as the previous byte". + +A dash (-) means "zero". (It is used +instead of 0 because it makes unaccessed +regions more easily identifiable.) + +The infinity symbol (∞, not present in +this example) means "exceeded the maximum tracked count". + +Block layout can often be inferred from counts. For example, these blocks +probably have four separate byte-sized fields, followed by a four-byte field, +and so on. + +Access counts can be useful for identifying data alignment holes or other +layout inefficiencies. + + + + +Aggregate Nodes + +The AP tree is very large and many nodes represent tiny numbers of blocks +and bytes. Therefore, DHAT's viewer aggregates insignificant nodes like +this: + + + +Much of the detail is stripped away, leaving only basic measurements, +along with an indication of how many nodes were aggregated together (5 in this +case). + + + + + + +The Output Footer + +Below the AP tree is a line like this: + += 59,434.17 blocks (1%) +]]> + +It shows the function used to determine if an AP node is significant. All +nodes that don't satisfy this function are aggregated. It is occasionally +useful if you don't understand why an AP node has been aggregated. The exact +threshold depends on the sort metric (see below). + +Finally, the bottom of the page shows a legend that explains some of the +terms, abbreviations and symbols used in the output. + + + + +Sort Metrics + +The order in which sub-trees are sorted can be changed via the "Sort +metric" drop-down menu at the top of DHAT's viewer. Different sort metrics can +be useful for finding different things. Some sort metrics also incorporate some +filtering, so that only nodes meeting a particular criteria are shown. + + + + + + Total (bytes) + The total number of bytes allocated during the execution. + Highly useful for evaluating heap churn, though not quite as useful as + "Total (blocks)". + + + + + Total (blocks) + The total number of blocks allocated during the execution. + Highly useful for evaluating heap churn; reducing the number of calls to + the allocator can significantly speed up a program. This is the default + sort metric. + + + + + Total (blocks), tiny + Like "Total (blocks)", but shows only very small blocks. + Moderately useful, because such blocks are often easy to avoid allocating. + + + + + Total (blocks), short-lived + Like "Total (blocks)", but shows only very short-lived + blocks. Moderately useful, because such blocks are often easy to avoid + allocating. + + + + + Total (bytes), zero reads or zero writes + Like "Total (bytes)", but shows only blocks that are + never read or never written to (or both). Highly useful, because such + blocks indicate poor use of memory and are often easy to avoid allocating. + For example, sometimes a block is allocated and written to but then only + read if a condition C is true; in that case, it may be possible to delay + creating the block until condition C is true. Alternatively, sometimes + blocks are created and never used; such blocks are trivial to remove. + + + + + Total (blocks), zero reads or zero writes + Like "Total (bytes), zero reads or zero writes" but for + blocks. Highly useful. + + + + + Total (bytes), low-access + Like "Total (bytes)", but shows only blocks that have low + numbers of reads or low numbers of writes (or both). Moderately useful, + because such blocks indicate poor use of memory. + + + + + Total (blocks), low-access + Like "Total (bytes), low-access", but for blocks. + + + + + At t-gmax (bytes) + This shows the breakdown of memory at the point of peak + heap memory usage. Highly useful for reducing peak memory usage. + + + + + At t-end (bytes) + This shows the breakdown of memory at program termination. + Highly useful for identifying process-lifetime leaks. + + + + + Reads (bytes) + The number of bytes read within heap blocks. Occasionally + useful. + + + + + Reads (bytes), high-access + Like "Reads (bytes)", but only shows blocks with high read + ratios. Occasionally useful for identifying hot areas of memory. + + + + + Writes (bytes) + Like "Reads (bytes)", but for writes. Occasionally useful. + + + + + Writes (bytes), high-access + Like "Reads (bytes), high-access", but for writes. + Occasionally useful. + + + + + +The values within a node that represent the chosen sort metric are shown +in bold, so they stand out. + +Here is part of an AP node found with "Total (blocks), tiny", showing +blocks with an average size of only 8.67 bytes: + + + +Here is part of an AP node found with "Total (blocks), short-lived", +showing blocks with an average lifetime of only 181.75 instructions: + + + +Here is an example of an AP identified with "Total (blocks), zero reads +or zero writes", showing blocks that are allocated but never touched: + + + +All the blocks identified by these APs are good candidates for +optimization. + + + + + + + + +DHAT Command-line Options + +DHAT-specific command-line options are: + + + + + + + + + + Write the profile data to + file rather than to the default + output file, + dhat.out.<pid>. The + and format specifiers + can be used to embed the process ID and/or the contents of an + environment variable in the name, as is the case for the core + option . + + + + + + +Note that stacks by default have 12 frames. This may be more than +necessary, in which case the flag can be used to +reduce the number, which may make DHAT run slightly faster. + + + + + + + diff --git a/dhat/tests/Makefile.am b/dhat/tests/Makefile.am new file mode 100644 index 0000000000..d1ad7e729d --- /dev/null +++ b/dhat/tests/Makefile.am @@ -0,0 +1,23 @@ + +include $(top_srcdir)/Makefile.tool-tests.am + +dist_noinst_SCRIPTS = filter_stderr + +EXTRA_DIST = \ + acc.post.exp acc.stderr.exp acc.vgtest \ + basic.post.exp basic.stderr.exp basic.vgtest \ + big.post.exp big.stderr.exp big.vgtest \ + empty.post.exp empty.stderr.exp empty.vgtest \ + single.post.exp single.stderr.exp single.vgtest + +check_PROGRAMS = \ + acc \ + basic \ + big \ + empty \ + sig \ + single + +AM_CFLAGS += $(AM_FLAG_M3264_PRI) +AM_CXXFLAGS += $(AM_FLAG_M3264_PRI) + diff --git a/dhat/tests/acc.c b/dhat/tests/acc.c new file mode 100644 index 0000000000..ef6693e00e --- /dev/null +++ b/dhat/tests/acc.c @@ -0,0 +1,74 @@ +// Testing accesses of blocks. + +#include +#include +#include + +void* m1(size_t n) { return malloc(n); } + +void* m2(size_t n) { return malloc(n); } + +int main(void) +{ + // 0th char is written 0 times, 1st char is written once, etc. + char* a = malloc(32); + for (int i = 1; i < 32; i++) { + for (int j = 0; j < i; j++) { + a[i] = 0; + } + } + free(a); + + // Repetition and gaps. + int* b = malloc(20); + b[0] = 1; + b[2] = b[0]; + for (int i = 0; i < 10; i++) { + b[4] = 99; + } + free(b); + + // 33 bytes, goes onto a second line in dh_view. + char* c = calloc(33, 1); + c[32] = 0; + free(c); + + // 1024 bytes, accesses are shown. + char* d = malloc(1024); + for (int i = 0; i < 1024; i++) { + d[i] = d[1023 - i]; + } + for (int i = 500; i < 600; i++) { + d[i] = 0; + } + free(d); + + // 1025 bytes, accesses aren't shown. + char* e = calloc(1025, 1); + for (int i = 0; i < 1025; i++) { + e[i] += 1; + } + free(e); + + // Lots of accesses, but fewer than the 0xffff max value. + int* f1 = m1(100); + int* f2 = m1(100); + for (int i = 0; i < 50000; i++) { + f1[0] = 0; + f2[0] = 0; + } + free(f1); + free(f2); + + // Lots of accesses, more than the 0xffff max value: treated as Infinity. + int* g1 = m2(100); + int* g2 = m2(100); + for (int i = 0; i < 100000; i++) { + g1[0] = 0; + g2[0] = 0; + } + free(g1); + free(g2); + + return 0; +} diff --git a/dhat/tests/acc.stderr.exp b/dhat/tests/acc.stderr.exp new file mode 100644 index 0000000000..7732270733 --- /dev/null +++ b/dhat/tests/acc.stderr.exp @@ -0,0 +1,7 @@ + + +Total: 2,534 bytes in 9 blocks +At t-gmax: 1,025 bytes in 1 blocks +At t-end: 0 bytes in 0 blocks +Reads: 2,053 bytes +Writes: 1,202,694 bytes diff --git a/dhat/tests/acc.vgtest b/dhat/tests/acc.vgtest new file mode 100644 index 0000000000..487011ec3e --- /dev/null +++ b/dhat/tests/acc.vgtest @@ -0,0 +1,3 @@ +prog: acc +vgopts: --dhat-out-file=dhat.out +cleanup: rm dhat.out diff --git a/dhat/tests/basic.c b/dhat/tests/basic.c new file mode 100644 index 0000000000..3ac6617a40 --- /dev/null +++ b/dhat/tests/basic.c @@ -0,0 +1,26 @@ +// Some basic allocations and accesses. + +#include +#include +#include + +int main(void) +{ + int64_t* m = malloc(1000); + m[0] = 1; // write 8 bytes + m[10] = m[1]; // read and write 8 bytes + + char* c = calloc(1, 2000); + for (int i = 0; i < 1000; i++) { + c[i + 1000] = c[i]; // read and write 1000 bytes + } + + char* r = realloc(m, 3000); + for (int i = 0; i < 500; i++) { + r[i + 2000] = 99; // write 500 bytes + } + // totals: 1008 read, 1516 write + free(c); + + return 0; +} diff --git a/dhat/tests/basic.stderr.exp b/dhat/tests/basic.stderr.exp new file mode 100644 index 0000000000..bcdec65409 --- /dev/null +++ b/dhat/tests/basic.stderr.exp @@ -0,0 +1,7 @@ + + +Total: 6,000 bytes in 3 blocks +At t-gmax: 5,000 bytes in 2 blocks +At t-end: 3,000 bytes in 1 blocks +Reads: 1,008 bytes +Writes: 1,516 bytes diff --git a/dhat/tests/basic.vgtest b/dhat/tests/basic.vgtest new file mode 100644 index 0000000000..db1515fc56 --- /dev/null +++ b/dhat/tests/basic.vgtest @@ -0,0 +1,3 @@ +prog: basic +vgopts: --dhat-out-file=dhat.out +cleanup: rm dhat.out diff --git a/dhat/tests/big.c b/dhat/tests/big.c new file mode 100644 index 0000000000..12dd677314 --- /dev/null +++ b/dhat/tests/big.c @@ -0,0 +1,61 @@ +// This test implements a moderately complex call tree. The layout of these +// functions matches the layout of the tree produced by dh_view.js, when +// sorted by "total bytes". + +#include + +#define F(f, parent) void* f(size_t n) { return parent(n); } + +// Note: don't use j1 -- that is a builtin C function, believe it or not. +F(a, malloc) + F(b1, a) + F(c1, b1) + F(d1, c1) + F(d2, c1) // insig total-bytes + F(c2, b1) + F(b2, a) + F(b3, a) F(e, b3) F(f, e) +F(g, malloc) F(h, g) F(i, h) + F(j2, i) F(k, j2) F(l, k) + F(j3, i) F(m, j3) + F(n1, m) + F(n2, m) F(o, n2) + F(p, i) F(q, p) // insig total-bytes + F(r, i) // insig total-bytes +F(s1, malloc) F(s2, s1) F(s3, s2) F(s4, s3) F(s5, s4) +F(t, malloc) +F(u, malloc) +F(v, malloc) + F(w, v) // insig total-bytes + F(x, v) // insig total-bytes + F(y, v) // insig total-bytes + F(z, v) // insig total-bytes + +int main(void) +{ + // Call all the leaves in the above tree. + + int* d1p = d1(706); + free(d1p); // So the t-final numbers differ from the t-gmax/total numbers. + + d2(5); + c2(30); + b2(20); + f(10); + l(60); + n1(30); + o(20); + q(7); + r(3); + s5(30); + t(20); + u(19); + w(9); + x(8); + y(7); + z(5); + z(1); + + // And one allocation directly from main(). + malloc(10); +} diff --git a/dhat/tests/big.stderr.exp b/dhat/tests/big.stderr.exp new file mode 100644 index 0000000000..57339767ed --- /dev/null +++ b/dhat/tests/big.stderr.exp @@ -0,0 +1,7 @@ + + +Total: 1,000 bytes in 19 blocks +At t-gmax: 706 bytes in 1 blocks +At t-end: 294 bytes in 18 blocks +Reads: 0 bytes +Writes: 0 bytes diff --git a/dhat/tests/big.vgtest b/dhat/tests/big.vgtest new file mode 100644 index 0000000000..2d8fb24f79 --- /dev/null +++ b/dhat/tests/big.vgtest @@ -0,0 +1,3 @@ +prog: big +vgopts: --dhat-out-file=dhat.out +cleanup: rm dhat.out diff --git a/dhat/tests/empty.c b/dhat/tests/empty.c new file mode 100644 index 0000000000..1f59d5fa8b --- /dev/null +++ b/dhat/tests/empty.c @@ -0,0 +1,6 @@ +// No allocations. + +int main(void) +{ + return 0; +} diff --git a/dhat/tests/empty.stderr.exp b/dhat/tests/empty.stderr.exp new file mode 100644 index 0000000000..6b189fd673 --- /dev/null +++ b/dhat/tests/empty.stderr.exp @@ -0,0 +1,7 @@ + + +Total: 0 bytes in 0 blocks +At t-gmax: 0 bytes in 0 blocks +At t-end: 0 bytes in 0 blocks +Reads: 0 bytes +Writes: 0 bytes diff --git a/dhat/tests/empty.vgtest b/dhat/tests/empty.vgtest new file mode 100644 index 0000000000..9934963e63 --- /dev/null +++ b/dhat/tests/empty.vgtest @@ -0,0 +1,3 @@ +prog: empty +vgopts: --dhat-out-file=dhat.out +cleanup: rm dhat.out diff --git a/dhat/tests/filter_stderr b/dhat/tests/filter_stderr new file mode 100755 index 0000000000..6b4058be9d --- /dev/null +++ b/dhat/tests/filter_stderr @@ -0,0 +1,9 @@ +#! /bin/sh + +dir=`dirname $0` + +$dir/../../tests/filter_stderr_basic | + +# Remove "Massif, ..." line and the following copyright line. +sed "/^DHAT, a dynamic heap analysis tool/ , /./ d" + diff --git a/dhat/tests/sig.c b/dhat/tests/sig.c new file mode 100644 index 0000000000..64b0dbd94c --- /dev/null +++ b/dhat/tests/sig.c @@ -0,0 +1,76 @@ +// This test implements sorting of a tree involving a mix of significant and +// insignificant nodes. The layout of these functions matches the layout of +// the tree produced by dh_view.js, when sorted by "total bytes". + +#include + +#define F(f, parent) void* f(size_t n) { return parent(n); } + +F(am, malloc) + // main + F(a2, am) // main + F(a3, am) + // main + // main +F(bm, malloc) + // main + F(b2, bm) // main + F(b3, bm) + // main + // main +F(cm, malloc) + // main + F(c2, cm) // main + F(c3, cm) + // main + // main +F(dm, malloc) + // main + F(d2, dm) // main + F(d3, dm) + // main + // main + +char access(char* p, size_t n) +{ + for (int i = 0; i < 1499; i++) { + for (int j = 0; j < n; j++) { + p[j] = j; + } + } + char x = 0; + for (int j = 0; j < n; j++) { + x += p[j]; + } + return x; +} + +int main(void) +{ + + char* p; + + // Call all the leaves in the above tree. The pointers we pass to access() + // become significant in a high-access sort and insignificant in a + // zero-reads-or-zero-writes sort, and vice versa. + + p = am(11); access(p, 11); + p = a2(10); access(p, 10); + p = a3(5); access(p, 5); + p = a3(4); access(p, 5); + + p = bm(10); access(p, 10); + p = b2(9); access(p, 9); + p = b3(5); + p = b3(3); + + p = cm(9); access(p, 9); + p = c2(8); + p = c3(4); + p = c3(3); + + p = dm(8); + p = d2(7); + p = d3(4); + p = d3(2); +} diff --git a/dhat/tests/sig.stderr.exp b/dhat/tests/sig.stderr.exp new file mode 100644 index 0000000000..d5a50cf2b0 --- /dev/null +++ b/dhat/tests/sig.stderr.exp @@ -0,0 +1,7 @@ + + +Total: 102 bytes in 16 blocks +At t-gmax: 102 bytes in 16 blocks +At t-end: 102 bytes in 16 blocks +Reads: 58 bytes +Writes: 86,942 bytes diff --git a/dhat/tests/sig.vgtest b/dhat/tests/sig.vgtest new file mode 100644 index 0000000000..9eebdca8c4 --- /dev/null +++ b/dhat/tests/sig.vgtest @@ -0,0 +1,3 @@ +prog: sig +vgopts: --dhat-out-file=dhat.out +cleanup: rm dhat.out diff --git a/dhat/tests/single.c b/dhat/tests/single.c new file mode 100644 index 0000000000..bc608273dc --- /dev/null +++ b/dhat/tests/single.c @@ -0,0 +1,11 @@ +// A single allocation (so the root node is the only node in the tree). + +#include + +int main() { + int* a = (int*)malloc(16); + a[0] = 0; + a[0] = 1; + a[0] = 2; + return 0; +} diff --git a/dhat/tests/single.stderr.exp b/dhat/tests/single.stderr.exp new file mode 100644 index 0000000000..87e818599d --- /dev/null +++ b/dhat/tests/single.stderr.exp @@ -0,0 +1,7 @@ + + +Total: 16 bytes in 1 blocks +At t-gmax: 16 bytes in 1 blocks +At t-end: 16 bytes in 1 blocks +Reads: 0 bytes +Writes: 12 bytes diff --git a/dhat/tests/single.vgtest b/dhat/tests/single.vgtest new file mode 100644 index 0000000000..3c52ccadb2 --- /dev/null +++ b/dhat/tests/single.vgtest @@ -0,0 +1,3 @@ +prog: single +vgopts: --dhat-out-file=dhat.out +cleanup: rm dhat.out diff --git a/docs/Makefile.am b/docs/Makefile.am index e848b781d3..7a681a5df3 100644 --- a/docs/Makefile.am +++ b/docs/Makefile.am @@ -20,6 +20,7 @@ EXTRA_DIST = \ images/prev.png \ images/up.png \ images/kcachegrind_xtree.png \ + images/dh-tree.png \ internals/3_0_BUGSTATUS.txt \ internals/3_1_BUGSTATUS.txt \ internals/3_2_BUGSTATUS.txt \ diff --git a/docs/README b/docs/README index afb048d4d4..76245b5164 100644 --- a/docs/README +++ b/docs/README @@ -76,10 +76,18 @@ could just build the docs from XML when doing 'make install', which would be simpler. -Notes on building PDF / PS documents ------------------------------------- -Below are random notes and recollections about how to build PDF / PS -documents from the XML source at various times on various Linux distros. +Notes on building HTML / PDF / PS documents +------------------------------------------- +Below are random notes and recollections about how to build documents +from the XML source at various times on various Linux distros. They're +mostly about the PDF/PS documents, because they are the hardest to +build. + +Notes [Jan 2019] +----------------- +For Ubuntu 18.04, to build HTML docs I had to: + + sudo apt-get install xsltproc Notes [May 2017] ---------------- diff --git a/docs/images/dh-tree.png b/docs/images/dh-tree.png new file mode 100644 index 0000000000000000000000000000000000000000..637c7ad920b84759b21ff1a52e5e0d3395a8dc2d GIT binary patch literal 196802 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

9z-AD4)mVQ0MgeyjJ+4fm^SuIm(A?cCzaz87) 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 S7jRvI-&QAWEQ(M3a{Zt4^SrYF literal 0 Hc-jL100001 diff --git a/docs/xml/manual-core.xml b/docs/xml/manual-core.xml index d2ab7e28d3..cb21089591 100644 --- a/docs/xml/manual-core.xml +++ b/docs/xml/manual-core.xml @@ -613,7 +613,7 @@ in most cases. We group the available options by rough categories. Run the Valgrind tool called toolname, e.g. memcheck, cachegrind, callgrind, helgrind, drd, massif, - lackey, none, exp-sgcheck, exp-bbv, exp-dhat, etc. + dhat, lackey, none, exp-sgcheck, exp-bbv, etc. @@ -2562,7 +2562,7 @@ need to use them. malloc related functions, using the synonym somalloc. This synonym is usable for all tools doing standard replacement of malloc related functions - (e.g. memcheck, massif, drd, helgrind, exp-dhat, exp-sgcheck). + (e.g. memcheck, helgrind, drd, massif, dhat, exp-sgcheck). diff --git a/docs/xml/manual.xml b/docs/xml/manual.xml index 333e5b7786..b90514935a 100644 --- a/docs/xml/manual.xml +++ b/docs/xml/manual.xml @@ -36,15 +36,15 @@ xmlns:xi="http://www.w3.org/2001/XInclude" /> - - - + + diff --git a/exp-dhat/docs/dh-manual.xml b/exp-dhat/docs/dh-manual.xml deleted file mode 100644 index 66b7c683cf..0000000000 --- a/exp-dhat/docs/dh-manual.xml +++ /dev/null @@ -1,401 +0,0 @@ - - %vg-entities; ]> - - - - DHAT: a dynamic heap analysis tool - -To use this tool, you must specify - on the Valgrind -command line. - - - - -Overview - -DHAT is a tool for examining how programs use their heap -allocations. - -It tracks the allocated blocks, and inspects every memory access -to find which block, if any, it is to. The following data is -collected and presented per allocation point (allocation -stack): - - - Total allocation (number of bytes and - blocks) - - maximum live volume (number of bytes and - blocks) - - average block lifetime (number of instructions - between allocation and freeing) - - average number of reads and writes to each byte in - the block ("access ratios") - - for allocation points which always allocate blocks - only of one size, and that size is 4096 bytes or less: counts - showing how often each byte offset inside the block is - accessed. - - -Using these statistics it is possible to identify allocation -points with the following characteristics: - - - - potential process-lifetime leaks: blocks allocated - by the point just accumulate, and are freed only at the end of the - run. - - excessive turnover: points which chew through a lot - of heap, even if it is not held onto for very long - - excessively transient: points which allocate very - short lived blocks - - useless or underused allocations: blocks which are - allocated but not completely filled in, or are filled in but not - subsequently read. - - blocks with inefficient layout -- areas never - accessed, or with hot fields scattered throughout the - block. - - -As with the Massif heap profiler, DHAT measures program progress -by counting instructions, and so presents all age/time related figures -as instruction counts. This sounds a little odd at first, but it -makes runs repeatable in a way which is not possible if CPU time is -used. - - - - - - - -Understanding DHAT's output - - -DHAT provides a lot of useful information on dynamic heap usage. -Most of the art of using it is in interpretation of the resulting -numbers. That is best illustrated via a set of examples. - - - -Interpreting the max-live, tot-alloc and deaths fields - -A simple example - - - -Over the entire run of the program, this stack (allocation -point) allocated 29,520 blocks in total, containing 1,904,700 bytes in -total. By looking at the max-live data, we see that not many blocks -were simultaneously live, though: at the peak, there were 63,490 -allocated bytes in 984 blocks. This tells us that the program is -steadily freeing such blocks as it runs, rather than hanging on to all -of them until the end and freeing them all. - -The deaths entry tells us that 29,520 blocks allocated by this stack -died (were freed) during the run of the program. Since 29,520 is -also the number of blocks allocated in total, that tells us that -all allocated blocks were freed by the end of the program. - -It also tells us that the average age at death was 22,227,424 -instructions. From the summary statistics we see that the program ran -for 1,045,339,534 instructions, and so the average age at death is -about 2% of the program's total run time. - -Example of a potential process-lifetime leak - -This next example (from a different program than the above) -shows a potential process lifetime leak. A process lifetime leak -occurs when a program keeps allocating data, but only frees the -data just before it exits. Hence the program's heap grows constantly -in size, yet Memcheck reports no leak, because the program has -freed up everything at exit. This is particularly a hazard for -long running programs. - - - -There are two tell-tale signs that this might be a -process-lifetime leak. Firstly, the max-live and tot-alloc numbers -are identical. The only way that can happen is if these blocks are -all allocated and then all deallocated. - -Secondly, the average age at death (300 million insns) is 71% of -the total program lifetime (419 million insns), hence this is not a -transient allocation-free spike -- rather, it is spread out over a -large part of the entire run. One interpretation is, roughly, that -all 254 blocks were allocated in the first half of the run, held onto -for the second half, and then freed just before exit. - - - - - -Interpreting the acc-ratios fields - - -A fairly harmless allocation point record - - - -The acc-ratios field tells us that each byte in the blocks -allocated here is read an average of 2.13 times before the block is -deallocated. Given that the blocks have an average age at death of -34,611,026, that's one read per block per approximately every 15 -million instructions. So from that standpoint the blocks aren't -"working" very hard. - -More interesting is the write ratio: each byte is written an -average of 0.91 times. This tells us that some parts of the allocated -blocks are never written, at least 9% on average. To completely -initialise the block would require writing each byte at least once, -and that would give a write ratio of 1.0. The fact that some block -areas are evidently unused might point to data alignment holes or -other layout inefficiencies. - -Well, at least all the blocks are freed (24,240 allocations, -24,240 deaths). - -If all the blocks had been the same size, DHAT would also show -the access counts by block offset, so we could see where exactly these -unused areas are. However, that isn't the case: the blocks have -varying sizes, so DHAT can't perform such an analysis. We can see -that they must have varying sizes since the average block size, 61.13, -isn't a whole number. - - -A more suspicious looking example - - - -Here, both the read and write access ratios are zero. Hence -this point is allocating blocks which are never used, neither read nor -written. Indeed, they are also not freed ("deaths: none") and are -simply leaked. So, here is 180k of completely useless allocation that -could be removed. - -Re-running with Memcheck does indeed report the same leak. What -DHAT can tell us, that Memcheck can't, is that not only are the blocks -leaked, they are also never used. - -Another suspicious example - -Here's one where blocks are allocated, written to, -but never read from. We see this immediately from the zero read -access ratio. They do get freed, though: - - - -In the previous two examples, it is easy to see blocks that are -never written to, or never read from, or some combination of both. -Unfortunately, in C++ code, the situation is less clear. That's -because an object's constructor will write to the underlying block, -and its destructor will read from it. So the block's read and write -ratios will be non-zero even if the object, once constructed, is never -used, but only eventually destructed. - -Really, what we want is to measure only memory accesses in -between the end of an object's construction and the start of its -destruction. Unfortunately I do not know of a reliable way to -determine when those transitions are made. - - - - - -Interpreting "Aggregated access counts by offset" data - -For allocation points that always allocate blocks of the same -size, and which are 4096 bytes or smaller, DHAT counts accesses -per offset, for example: - - - -This is fairly typical, for C++ code running on a 64-bit -platform. Here, we have aggregated access statistics for 5668 blocks, -all of size 56 bytes. Each byte has been accessed at least 5668 -times, except for offsets 12--15, 36--39 and 52--55. These are likely -to be alignment holes. - -Careful interpretation of the numbers reveals useful information. -Groups of N consecutive identical numbers that begin at an N-aligned -offset, for N being 2, 4 or 8, are likely to indicate an N-byte object -in the structure at that point. For example, the first 32 bytes of -this object are likely to have the layout - - - -As a counterexample, it's also clear that, whatever is at offset 32, -it is not a 32-bit value. That's because the last number of the group -(37422) is not the same as the first three (18883 18883 18883). - -This example leads one to enquire (by reading the source code) -whether the zeroes at 12--15 and 52--55 are alignment holes, and -whether 48--51 is indeed a 32-bit type. If so, it might be possible -to place what's at 48--51 at 12--15 instead, which would reduce -the object size from 56 to 48 bytes. - -Bear in mind that the above inferences are all only "maybes". That's -because they are based on dynamic data, not static analysis of the -object layout. For example, the zeroes might not be alignment -holes, but rather just parts of the structure which were not used -at all for this particular run. Experience shows that's unlikely -to be the case, but it could happen. - - - - - - - - - - - - -DHAT Command-line Options - -DHAT-specific command-line options are: - - - - - - - - - - At the end of the run, DHAT sorts the accumulated - allocation points according to some metric, and shows the - highest scoring entries. --show-top-n - controls how many entries are shown. The default of 10 is - quite small. For realistic applications you will probably need - to set it much higher, at least several hundred. - - - - - - - - - At the end of the run, DHAT sorts the accumulated - allocation points according to some metric, and shows the - highest scoring entries. --sort-by - selects the metric used for sorting: - max-bytes-live maximum live bytes [default] - tot-bytes-allocd bytes allocates in total (turnover) - max-blocks-live maximum live blocks - tot-blocks-allocd blocks allocated in total (turnover) - This controls the order in which allocation points are - displayed. You can choose to look at allocation points with - the highest number of live bytes, or the highest total byte turnover, or - by the highest number of live blocks, or the highest total block - turnover. These give usefully different pictures of program behaviour. - For example, sorting by maximum live blocks tends to show up allocation - points creating large numbers of small objects. - - - - - -One important point to note is that each allocation stack counts -as a separate allocation point. Because stacks by default have 12 -frames, this tends to spread data out over multiple allocation points. -You may want to use the flag --num-callers=4 or some such small -number, to reduce the spreading. - - - - - - diff --git a/exp-dhat/tests/Makefile.am b/exp-dhat/tests/Makefile.am deleted file mode 100644 index 8b13789179..0000000000 --- a/exp-dhat/tests/Makefile.am +++ /dev/null @@ -1 +0,0 @@ - diff --git a/include/pub_tool_libcsetjmp.h b/include/pub_tool_libcsetjmp.h index ca96f54b5b..3cda7f8494 100644 --- a/include/pub_tool_libcsetjmp.h +++ b/include/pub_tool_libcsetjmp.h @@ -7,7 +7,7 @@ This file is part of Valgrind, a dynamic binary instrumentation framework. - Copyright (C) 2010-2017 Mozilla Inc + Copyright (C) 2010-2017 Mozilla Foundation This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as diff --git a/massif/ms_main.c b/massif/ms_main.c index b15fa5dd21..9749bd9cf8 100644 --- a/massif/ms_main.c +++ b/massif/ms_main.c @@ -1,6 +1,6 @@ -//--------------------------------------------------------------------*/ -//--- Massif: a heap profiling tool. ms_main.c ---*/ -//--------------------------------------------------------------------*/ +//--------------------------------------------------------------------// +//--- Massif: a heap profiling tool. ms_main.c ---// +//--------------------------------------------------------------------// /* This file is part of Massif, a Valgrind tool for profiling memory diff --git a/solaris/valgrind.p5m b/solaris/valgrind.p5m index 6670335529..7b65b4e93e 100644 --- a/solaris/valgrind.p5m +++ b/solaris/valgrind.p5m @@ -50,12 +50,12 @@ file path=usr/lib/valgrind/cachegrind-x86-solaris owner=r file path=usr/lib/valgrind/callgrind-amd64-solaris owner=root group=bin mode=0755 file path=usr/lib/valgrind/callgrind-x86-solaris owner=root group=bin mode=0755 file path=usr/lib/valgrind/default.supp owner=root group=bin mode=0644 +file path=usr/lib/valgrind/dhat-amd64-solaris owner=root group=bin mode=0755 +file path=usr/lib/valgrind/dhat-x86-solaris owner=root group=bin mode=0755 file path=usr/lib/valgrind/drd-amd64-solaris owner=root group=bin mode=0755 file path=usr/lib/valgrind/drd-x86-solaris owner=root group=bin mode=0755 file path=usr/lib/valgrind/exp-bbv-amd64-solaris owner=root group=bin mode=0755 file path=usr/lib/valgrind/exp-bbv-x86-solaris owner=root group=bin mode=0755 -file path=usr/lib/valgrind/exp-dhat-amd64-solaris owner=root group=bin mode=0755 -file path=usr/lib/valgrind/exp-dhat-x86-solaris owner=root group=bin mode=0755 file path=usr/lib/valgrind/exp-sgcheck-amd64-solaris owner=root group=bin mode=0755 file path=usr/lib/valgrind/exp-sgcheck-x86-solaris owner=root group=bin mode=0755 file path=usr/lib/valgrind/getoff-amd64-solaris owner=root group=bin mode=0755 @@ -75,8 +75,8 @@ file path=usr/lib/valgrind/vgpreload_core-amd64-solaris.so owner=r file path=usr/lib/valgrind/vgpreload_core-x86-solaris.so owner=root group=bin mode=0755 file path=usr/lib/valgrind/vgpreload_drd-amd64-solaris.so owner=root group=bin mode=0755 file path=usr/lib/valgrind/vgpreload_drd-x86-solaris.so owner=root group=bin mode=0755 -file path=usr/lib/valgrind/vgpreload_exp-dhat-amd64-solaris.so owner=root group=bin mode=0755 -file path=usr/lib/valgrind/vgpreload_exp-dhat-x86-solaris.so owner=root group=bin mode=0755 +file path=usr/lib/valgrind/vgpreload_dhat-amd64-solaris.so owner=root group=bin mode=0755 +file path=usr/lib/valgrind/vgpreload_dhat-x86-solaris.so owner=root group=bin mode=0755 file path=usr/lib/valgrind/vgpreload_exp-sgcheck-amd64-solaris.so owner=root group=bin mode=0755 file path=usr/lib/valgrind/vgpreload_exp-sgcheck-x86-solaris.so owner=root group=bin mode=0755 file path=usr/lib/valgrind/vgpreload_massif-amd64-solaris.so owner=root group=bin mode=0755 diff --git a/tests/check_headers_and_includes b/tests/check_headers_and_includes index 70c06d46f4..1c3e3f5d84 100755 --- a/tests/check_headers_and_includes +++ b/tests/check_headers_and_includes @@ -42,18 +42,18 @@ my %coregrind_dirs = ( ); my %tool_dirs = ( - "none" => 1, - "lackey" => 1, - "massif" => 1, "memcheck" => 1, - "drd" => 1, - "helgrind", => 1, - "callgrind" => 1, "cachegrind" => 1, - "shared" => 1, + "callgrind" => 1, + "helgrind", => 1, + "drd" => 1, + "massif" => 1, + "dhat" => 1, + "lackey" => 1, + "none" => 1, "exp-bbv" => 1, - "exp-dhat" => 1, "exp-sgcheck" => 1 + "shared" => 1, ); my %dirs_to_ignore = ( -- 2.47.2