]> git.ipfire.org Git - thirdparty/valgrind.git/commitdiff
Add support for copy and ad hoc profiling to DHAT.
authorNicholas Nethercote <nnethercote@mozilla.com>
Mon, 9 Sep 2019 04:13:35 +0000 (14:13 +1000)
committerNicholas Nethercote <nnethercote@mozilla.com>
Mon, 7 Dec 2020 08:57:56 +0000 (19:57 +1100)
23 files changed:
.gitignore
NEWS
coregrind/m_replacemalloc/replacemalloc_core.c
dhat/Makefile.am
dhat/dh_main.c
dhat/dh_replace_strmem.c [new file with mode: 0644]
dhat/dh_test.js
dhat/dh_view.js
dhat/dhat.h [new file with mode: 0644]
dhat/docs/dh-manual.xml
dhat/tests/Makefile.am
dhat/tests/ad-hoc.c [new file with mode: 0644]
dhat/tests/ad-hoc.stderr.exp [new file with mode: 0644]
dhat/tests/ad-hoc.vgtest [new file with mode: 0644]
dhat/tests/basic.c
dhat/tests/copy.c [new file with mode: 0644]
dhat/tests/copy.stderr.exp [new file with mode: 0644]
dhat/tests/copy.vgtest [new file with mode: 0644]
dhat/tests/filter_copy [new file with mode: 0755]
dhat/tests/filter_stderr
include/pub_tool_replacemalloc.h
none/nl_main.c
shared/vg_replace_strmem.c

index 47e53a9542f4bfed0bd1f40909ab16046c16280a..edb8edd22b44ff89225b431c3bfe0bed3f1ed44d 100644 (file)
 /dhat/tests/*.stdout.out
 /dhat/tests/.deps
 /dhat/tests/acc
+/dhat/tests/ad-hoc
 /dhat/tests/basic
 /dhat/tests/big
+/dhat/tests/copy
 /dhat/tests/empty
 /dhat/tests/sig
 /dhat/tests/single
diff --git a/NEWS b/NEWS
index 5861058b16d8092697ac88cbd83868183a93cc87..e191139ec2811cb4cdb7ed95055fe2f882ec0455 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -16,6 +16,18 @@ support for X86/macOS 10.13, AMD64/macOS 10.13 and nanoMIPS/Linux.
 
 * DHAT:
 
+  - DHAT has been extended, with two new modes of operation. The new
+    --mode=copy flag triggers copy profiling, which records calls to memcpy,
+    strcpy, and similar functions. The new --mode=ad-hoc flag triggers ad hoc
+    profiling, which records calls to the DHAT_AD_HOC_EVENT client request in
+    the new dhat/dhat.h file. This is useful for learning more about hot code
+    paths. See the user manual for more information about the new modes.
+
+  - Because of these changes, DHAT's file format has changed. DHAT output
+    files produced with earlier versions of DHAT will not work with this
+    version of DHAT's viewer, and DHAT output files produced with this version
+    of DHAT will not work with earlier versions of DHAT's viewer.
+
 * Cachegrind:
 
 * Callgrind:
index 281f1a1b0c5d95c6d2970150a646682b4b25ccf4..4b7d8ae609e36a9d6a916fc0981d232931e97d1b 100644 (file)
@@ -92,29 +92,35 @@ SizeT VG_(malloc_effective_client_redzone_size)(void)
 /*--- Useful functions                                     ---*/
 /*------------------------------------------------------------*/
 
-void* VG_(cli_malloc) ( SizeT align, SizeT nbytes )                 
-{                                                                             
+void* VG_(cli_malloc) ( SizeT align, SizeT nbytes )
+{
    // 'align' should be valid (ie. big enough and a power of two) by now.
    // VG_(arena_memalign)() will abort if it's not.
    if (VG_MIN_MALLOC_SZB == align)
-      return VG_(arena_malloc)   ( VG_AR_CLIENT, "replacemalloc.cm.1", 
-                                   nbytes ); 
-   else                                                                       
-      return VG_(arena_memalign) ( VG_AR_CLIENT, "replacemalloc.cm.2", 
+      return VG_(arena_malloc)   ( VG_AR_CLIENT, "replacemalloc.cm.1",
+                                   nbytes );
+   else
+      return VG_(arena_memalign) ( VG_AR_CLIENT, "replacemalloc.cm.2",
                                    align, nbytes );
-}                                                                             
+}
 
-void VG_(cli_free) ( void* p )                                   
-{                                                                             
-   VG_(arena_free) ( VG_AR_CLIENT, p );                          
+void* VG_(cli_realloc) ( void* ptr, SizeT nbytes )
+{
+   return VG_(arena_realloc) ( VG_AR_CLIENT, "replacemalloc.cr.1",
+                               ptr, nbytes );
 }
 
-// Useful for querying user blocks.           
-SizeT VG_(cli_malloc_usable_size) ( void* p )                    
-{                                                            
+void VG_(cli_free) ( void* p )
+{
+   VG_(arena_free) ( VG_AR_CLIENT, p );
+}
+
+// Useful for querying user blocks.
+SizeT VG_(cli_malloc_usable_size) ( void* p )
+{
    return VG_(arena_malloc_usable_size)(VG_AR_CLIENT, p);
-}                                                            
-  
+}
+
 Bool VG_(addr_is_in_block)( Addr a, Addr start, SizeT size, SizeT rz_szB )
 {
    return ( start - rz_szB <= a  &&  a < start + size + rz_szB );
index 2aa4ac99e03a2d368c3eaea7d0257317d4f6e3d6..815352cd80e78cf48215ff5e336eacac04008b90 100644 (file)
@@ -1,13 +1,14 @@
 include $(top_srcdir)/Makefile.tool.am
 
-#SUBDIRS += perf
-
 EXTRA_DIST = docs/dh-manual.xml dh_view.html dh_view.css dh_view.js
 
 #----------------------------------------------------------------------------
 # Headers, etc
 #----------------------------------------------------------------------------
 
+pkginclude_HEADERS = \
+       dhat.h
+
 # Ensure the viewer components get copied into the install tree.
 dhatdir = $(pkglibexecdir)
 dhat_DATA = dh_view.html dh_view.css dh_view.js
@@ -21,10 +22,10 @@ if VGCONF_HAVE_PLATFORM_SEC
 noinst_PROGRAMS += dhat-@VGCONF_ARCH_SEC@-@VGCONF_OS@
 endif
 
-EXP_DHAT_SOURCES_COMMON = dh_main.c
+DHAT_SOURCES_COMMON = dh_main.c
 
 dhat_@VGCONF_ARCH_PRI@_@VGCONF_OS@_SOURCES      = \
-       $(EXP_DHAT_SOURCES_COMMON)
+       $(DHAT_SOURCES_COMMON)
 dhat_@VGCONF_ARCH_PRI@_@VGCONF_OS@_CPPFLAGS     = \
        $(AM_CPPFLAGS_@VGCONF_PLATFORM_PRI_CAPS@)
 dhat_@VGCONF_ARCH_PRI@_@VGCONF_OS@_CFLAGS       = $(LTO_CFLAGS) \
@@ -45,7 +46,7 @@ dhat_@VGCONF_ARCH_PRI@_@VGCONF_OS@_LINK = \
 
 if VGCONF_HAVE_PLATFORM_SEC
 dhat_@VGCONF_ARCH_SEC@_@VGCONF_OS@_SOURCES      = \
-       $(EXP_DHAT_SOURCES_COMMON)
+       $(DHAT_SOURCES_COMMON)
 dhat_@VGCONF_ARCH_SEC@_@VGCONF_OS@_CPPFLAGS     = \
        $(AM_CPPFLAGS_@VGCONF_PLATFORM_SEC_CAPS@)
 dhat_@VGCONF_ARCH_SEC@_@VGCONF_OS@_CFLAGS       = $(LTO_CFLAGS) \
@@ -78,11 +79,16 @@ if VGCONF_OS_IS_DARWIN
 noinst_DSYMS = $(noinst_PROGRAMS)
 endif
 
-vgpreload_dhat_@VGCONF_ARCH_PRI@_@VGCONF_OS@_so_SOURCES      = 
+# dh_replace_strmem.c runs on the simulated CPU, and is built with
+# AM_CFLAGS_PSO_* (see $(top_srcdir)/Makefile.all.am).
+VGPRELOAD_DHAT_SOURCES_COMMON = dh_replace_strmem.c
+
+vgpreload_dhat_@VGCONF_ARCH_PRI@_@VGCONF_OS@_so_SOURCES      = \
+       $(VGPRELOAD_DHAT_SOURCES_COMMON)
 vgpreload_dhat_@VGCONF_ARCH_PRI@_@VGCONF_OS@_so_CPPFLAGS     = \
        $(AM_CPPFLAGS_@VGCONF_PLATFORM_PRI_CAPS@)
 vgpreload_dhat_@VGCONF_ARCH_PRI@_@VGCONF_OS@_so_CFLAGS       = \
-       $(AM_CFLAGS_PSO_@VGCONF_PLATFORM_PRI_CAPS@)
+       $(AM_CFLAGS_PSO_@VGCONF_PLATFORM_PRI_CAPS@) -O2
 vgpreload_dhat_@VGCONF_ARCH_PRI@_@VGCONF_OS@_so_DEPENDENCIES = \
        $(LIBREPLACEMALLOC_@VGCONF_PLATFORM_PRI_CAPS@)
 vgpreload_dhat_@VGCONF_ARCH_PRI@_@VGCONF_OS@_so_LDFLAGS      = \
@@ -90,11 +96,12 @@ vgpreload_dhat_@VGCONF_ARCH_PRI@_@VGCONF_OS@_so_LDFLAGS      = \
        $(LIBREPLACEMALLOC_LDFLAGS_@VGCONF_PLATFORM_PRI_CAPS@)
 
 if VGCONF_HAVE_PLATFORM_SEC
-vgpreload_dhat_@VGCONF_ARCH_SEC@_@VGCONF_OS@_so_SOURCES      = 
+vgpreload_dhat_@VGCONF_ARCH_SEC@_@VGCONF_OS@_so_SOURCES      = \
+       $(VGPRELOAD_DHAT_SOURCES_COMMON)
 vgpreload_dhat_@VGCONF_ARCH_SEC@_@VGCONF_OS@_so_CPPFLAGS     = \
        $(AM_CPPFLAGS_@VGCONF_PLATFORM_SEC_CAPS@)
 vgpreload_dhat_@VGCONF_ARCH_SEC@_@VGCONF_OS@_so_CFLAGS       = \
-       $(AM_CFLAGS_PSO_@VGCONF_PLATFORM_SEC_CAPS@)
+       $(AM_CFLAGS_PSO_@VGCONF_PLATFORM_SEC_CAPS@) -O2
 vgpreload_dhat_@VGCONF_ARCH_SEC@_@VGCONF_OS@_so_DEPENDENCIES = \
        $(LIBREPLACEMALLOC_@VGCONF_PLATFORM_SEC_CAPS@)
 vgpreload_dhat_@VGCONF_ARCH_SEC@_@VGCONF_OS@_so_LDFLAGS      = \
index 5d1e89b592cd12086758317ba08a8700ed170e26..90b1a965ac70841c33873dfd5c055abdfec37c7e 100644 (file)
@@ -27,9 +27,9 @@
 
 /* Contributed by Julian Seward <jseward@acm.org> */
 
-
 #include "pub_tool_basics.h"
 #include "pub_tool_clientstate.h"
+#include "pub_tool_clreq.h"
 #include "pub_tool_libcbase.h"
 #include "pub_tool_libcassert.h"
 #include "pub_tool_libcfile.h"
@@ -42,6 +42,8 @@
 #include "pub_tool_tooliface.h"
 #include "pub_tool_wordfm.h"
 
+#include "dhat.h"
+
 #define HISTOGRAM_SIZE_LIMIT 1024
 
 //------------------------------------------------------------//
 static ULong g_total_blocks = 0;
 static ULong g_total_bytes  = 0;
 
-// Current values.
+// Current values. g_curr_blocks and g_curr_bytes are only used with
+// clo_mode=Heap.
 static ULong g_curr_blocks = 0;
 static ULong g_curr_bytes  = 0;
 static ULong g_curr_instrs = 0;  // incremented from generated code
 
 // Values at the global max, i.e. when g_curr_bytes peaks.
+// Only used with clo_mode=Heap.
 static ULong g_max_blocks = 0;
 static ULong g_max_bytes  = 0;
-static ULong g_max_instrs = 0;
+
+// Time of the global max.
+static ULong g_tgmax_instrs = 0;
 
 // Values for the entire run. Updated each time a block is retired.
+// Only used with clo_mode=Heap.
 static ULong g_reads_bytes = 0;
 static ULong g_writes_bytes = 0;
 
+//------------------------------------------------------------//
+//--- Command line args                                    ---//
+//------------------------------------------------------------//
+
+typedef enum { Heap=55, Copy, AdHoc } ProfileKind;
+
+static ProfileKind clo_mode = Heap;
+
+static const HChar* clo_dhat_out_file = "dhat.out.%p";
+
+static Bool dh_process_cmd_line_option(const HChar* arg)
+{
+   if VG_STR_CLO(arg, "--dhat-out-file", clo_dhat_out_file) {
+
+   } else if (VG_XACT_CLO(arg, "--mode=heap",   clo_mode, Heap)) {
+   } else if (VG_XACT_CLO(arg, "--mode=copy",   clo_mode, Copy)) {
+   } else if (VG_XACT_CLO(arg, "--mode=ad-hoc", clo_mode, AdHoc)) {
+
+   } else {
+      return VG_(replacement_malloc_process_cmd_line_option)(arg);
+   }
+
+   return True;
+}
+
+static void dh_print_usage(void)
+{
+   VG_(printf)(
+"    --dhat-out-file=<file>    output file name [dhat.out.%%p]\n"
+"    --mode=heap|copy|ad-hoc   profiling mode\n"
+   );
+}
+
+static void dh_print_debug_usage(void)
+{
+   VG_(printf)(
+"    (none)\n"
+   );
+}
+
 //------------------------------------------------------------//
 //--- an Interval Tree of live blocks                      ---//
 //------------------------------------------------------------//
@@ -75,7 +122,7 @@ typedef
    struct {
       Addr        payload;
       SizeT       req_szB;
-      ExeContext* ap;  /* allocation ec */
+      ExeContext* ec;  /* allocation ec */
       ULong       allocd_at; /* instruction number */
       ULong       reads_bytes;
       ULong       writes_bytes;
@@ -114,6 +161,8 @@ static UWord stats__n_fBc_notfound = 0;
 
 static Block* find_Block_containing ( Addr a )
 {
+   tl_assert(clo_mode == Heap);
+
    if (LIKELY(fbc_cache0
               && fbc_cache0->payload <= a
               && a < fbc_cache0->payload + fbc_cache0->req_szB)) {
@@ -157,6 +206,8 @@ static Block* find_Block_containing ( Addr a )
 // known to be present.)
 static void delete_Block_starting_at ( Addr a )
 {
+   tl_assert(clo_mode == Heap);
+
    Block fake;
    fake.payload = a;
    fake.req_szB = 1;
@@ -166,179 +217,184 @@ static void delete_Block_starting_at ( Addr a )
    fbc_cache0 = fbc_cache1 = NULL;
 }
 
-
 //------------------------------------------------------------//
 //--- a FM of allocation points (APs)                      ---//
 //------------------------------------------------------------//
 
 typedef
    struct {
-      // The allocation point that we're summarising stats for.
-      ExeContext* ap;
+      // The program point that we're summarising stats for.
+      ExeContext* ec;
 
-      // Total number of blocks and bytes allocated by this AP.
+      // Total number of blocks and bytes allocated by this PP.
       ULong total_blocks;
       ULong total_bytes;
 
-      // The current number of blocks and bytes live for this AP.
+      // The current number of blocks and bytes live for this PP.
+      // Only used with clo_mode=Heap.
       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 PP max, i.e. when this PP's curr_bytes peaks.
+      // Only used with clo_mode=Heap.
+      ULong max_blocks;     // Blocks at the PP max.
+      ULong max_bytes;      // The PP max, measured in bytes.
 
       // Values at the global max.
+      // Only used with clo_mode=Heap.
       ULong at_tgmax_blocks;
       ULong at_tgmax_bytes;
 
-      // Total lifetimes of all blocks allocated by this AP. Includes blocks
+      // Total lifetimes of all blocks allocated by this PP. Includes blocks
       // explicitly freed and blocks implicitly freed at termination.
+      // Only used with clo_mode=Heap.
       ULong total_lifetimes_instrs;
 
-      // Number of blocks freed by this AP. (Only used in assertions.)
+      // Number of blocks freed by this PP. (Only used in assertions.)
+      // Only used with clo_mode=Heap.
       ULong freed_blocks;
 
       // Total number of reads and writes in all blocks allocated
-      // by this AP.
+      // by this PP. Only used with clo_mode=Heap.
       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
+         all retiring Blocks allocated by this PP, but only if:
+         - this PP has only ever allocated objects of one size
          - that size is <= HISTOGRAM_SIZE_LIMIT
-         What we need therefore is a mechanism to see if this AP
+         What we need therefore is a mechanism to see if this PP
          has only ever allocated blocks of one size.
 
          3 states:
             Unknown          because no retirement yet
             Exactly xsize    all retiring blocks are of this size
             Mixed            multiple different sizes seen
+
+         Only used with clo_mode=Heap.
       */
       enum { Unknown=999, Exactly, Mixed } xsize_tag;
       SizeT xsize;
       UInt* histo; /* [0 .. xsize-1] */
    }
-   APInfo;
-
-/* maps ExeContext*'s to APInfo*'s.  Note that the keys must match the
-   .ap field in the values. */
-static WordFM* apinfo = NULL;  /* WordFM* ExeContext* APInfo* */
+   PPInfo;
 
+/* maps ExeContext*'s to PPInfo*'s.  Note that the keys must match the
+   .ec field in the values. */
+static WordFM* ppinfo = NULL;  /* WordFM* ExeContext* PPInfo* */
 
 // 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
+// all PPInfos. Note that this is moderately expensive so we avoid calling it
 // on every allocation.
 static void check_for_peak(void)
 {
+   tl_assert(clo_mode == Heap);
+
    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_(initIterFM)(ppinfo);
+      while (VG_(nextIterFM)(ppinfo, &keyW, &valW)) {
+         PPInfo* ppi = (PPInfo*)valW;
+         tl_assert(ppi && ppi->ec == (ExeContext*)keyW);
+         ppi->at_tgmax_blocks = ppi->curr_blocks;
+         ppi->at_tgmax_bytes = ppi->curr_bytes;
       }
-      VG_(doneIterFM)(apinfo);
+      VG_(doneIterFM)(ppinfo);
    }
 }
 
 /* '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
+   relevant PPInfo entry for it, or create one, based on the block's
+   allocation EC.  Then, update the PPInfo to the extent that we
    actually can, to reflect the allocation. */
-static void intro_Block ( Block* bk )
+static void intro_Block(Block* bk)
 {
    tl_assert(bk);
-   tl_assert(bk->ap);
+   tl_assert(bk->ec);
 
-   APInfo* api   = NULL;
+   PPInfo* ppi   = NULL;
    UWord   keyW  = 0;
    UWord   valW  = 0;
-   Bool    found = VG_(lookupFM)( apinfo,
-                                  &keyW, &valW, (UWord)bk->ap );
+   Bool    found = VG_(lookupFM)( ppinfo,
+                                  &keyW, &valW, (UWord)bk->ec );
    if (found) {
-      api = (APInfo*)valW;
-      tl_assert(keyW == (UWord)bk->ap);
+      ppi = (PPInfo*)valW;
+      tl_assert(keyW == (UWord)bk->ec);
    } else {
-      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 );
+      ppi = VG_(malloc)( "dh.intro_Block.1", sizeof(PPInfo) );
+      VG_(memset)(ppi, 0, sizeof(*ppi));
+      ppi->ec = bk->ec;
+      Bool present = VG_(addToFM)( ppinfo,
+                                   (UWord)bk->ec, (UWord)ppi );
       tl_assert(!present);
-      // histo stuff
-      tl_assert(api->freed_blocks == 0);
-      api->xsize_tag = Unknown;
-      api->xsize = 0;
-      if (0) VG_(printf)("api %p   -->  Unknown\n", api);
+      if (clo_mode == Heap) {
+         // histo stuff
+         tl_assert(ppi->freed_blocks == 0);
+         ppi->xsize_tag = Unknown;
+         ppi->xsize = 0;
+         if (0) VG_(printf)("ppi %p   -->  Unknown\n", ppi);
+      }
    }
 
-   tl_assert(api->ap == bk->ap);
+   tl_assert(ppi->ec == bk->ec);
 
-   // Update global stats first.
+   // Update global stats and PPInfo stats.
 
    g_total_blocks++;
    g_total_bytes += bk->req_szB;
 
-   g_curr_blocks++;
-   g_curr_bytes += bk->req_szB;
-
-   // The use of `>=` rather than `>` means that if there are multiple equal
-   // peaks we record the latest one, like `check_for_peak` does.
-   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;
-   }
+   ppi->total_blocks++;
+   ppi->total_bytes += bk->req_szB;
 
-   // Now update APInfo stats.
+   if (clo_mode == Heap) {
+      g_curr_blocks++;
+      g_curr_bytes += bk->req_szB;
 
-   api->total_blocks++;
-   api->total_bytes += bk->req_szB;
+      ppi->curr_blocks++;
+      ppi->curr_bytes += bk->req_szB;
 
-   api->curr_blocks++;
-   api->curr_bytes += bk->req_szB;
+      // The use of `>=` rather than `>` means that if there are multiple equal
+      // peaks we record the latest one, like `check_for_peak` does.
+      if (g_curr_bytes >= g_max_bytes) {
+         g_max_blocks = g_curr_blocks;
+         g_max_bytes = g_curr_bytes;
+         g_tgmax_instrs = g_curr_instrs;
 
-   // The use of `>=` rather than `>` means that if there are multiple equal
-   // peaks we record the latest one, like `check_for_peak` does.
-   if (api->curr_bytes >= api->max_bytes) {
-      api->max_blocks = api->curr_blocks;
-      api->max_bytes  = api->curr_bytes;
+         ppi->max_blocks = ppi->curr_blocks;
+         ppi->max_bytes  = ppi->curr_bytes;
+      }
    }
 }
 
-/* 'bk' is retiring (being freed).  Find the relevant APInfo entry for
+/* 'bk' is retiring (being freed).  Find the relevant PPInfo 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
    the client has freed it.  If it is False then the block is retiring
    because the program has finished, in which case we want to skip the
-   updates of the total blocks live etc for this AP, but still fold in
+   updates of the total blocks live etc for this PP, but still fold in
    the access counts and histo data that have so far accumulated for
    the block. */
-static void retire_Block ( Block* bk, Bool because_freed )
+static void retire_Block(Block* bk, Bool because_freed)
 {
+   tl_assert(clo_mode == Heap);
    tl_assert(bk);
-   tl_assert(bk->ap);
+   tl_assert(bk->ec);
 
-   APInfo* api   = NULL;
+   PPInfo* ppi   = NULL;
    UWord   keyW  = 0;
    UWord   valW  = 0;
-   Bool    found = VG_(lookupFM)( apinfo,
-                                  &keyW, &valW, (UWord)bk->ap );
-
+   Bool    found = VG_(lookupFM)( ppinfo,
+                                  &keyW, &valW, (UWord)bk->ec );
    tl_assert(found);
-   api = (APInfo*)valW;
-   tl_assert(api->ap == bk->ap);
+   ppi = (PPInfo*)valW;
+   tl_assert(ppi->ec == bk->ec);
 
    // update stats following this free.
    if (0)
-      VG_(printf)("ec %p  api->c_by_l %llu  bk->rszB %llu\n",
-                  bk->ap, api->curr_bytes, (ULong)bk->req_szB);
+      VG_(printf)("ec %p  ppi->c_by_l %llu  bk->rszB %llu\n",
+                  bk->ec, ppi->curr_bytes, (ULong)bk->req_szB);
 
    if (because_freed) {
       // Total bytes is coming down from a possible peak.
@@ -350,59 +406,59 @@ static void retire_Block ( Block* bk, Bool because_freed )
       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;
+      // Then update PPInfo stats.
+      tl_assert(ppi->curr_blocks >= 1);
+      tl_assert(ppi->curr_bytes >= bk->req_szB);
+      ppi->curr_blocks--;
+      ppi->curr_bytes -= bk->req_szB;
 
-      api->freed_blocks++;
+      ppi->freed_blocks++;
    }
 
    tl_assert(bk->allocd_at <= g_curr_instrs);
-   api->total_lifetimes_instrs += (g_curr_instrs - bk->allocd_at);
+   ppi->total_lifetimes_instrs += (g_curr_instrs - bk->allocd_at);
 
    // access counts
-   api->reads_bytes += bk->reads_bytes;
-   api->writes_bytes += bk->writes_bytes;
+   ppi->reads_bytes += bk->reads_bytes;
+   ppi->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) {
+   switch (ppi->xsize_tag) {
 
       case Unknown:
-         tl_assert(api->xsize == 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);
+         tl_assert(ppi->xsize == 0);
+         tl_assert(ppi->freed_blocks == 1 || ppi->freed_blocks == 0);
+         tl_assert(!ppi->histo);
+         ppi->xsize_tag = Exactly;
+         ppi->xsize = bk->req_szB;
+         if (0) VG_(printf)("ppi %p   -->  Exactly(%lu)\n", ppi, ppi->xsize);
          // and allocate the histo
          if (bk->histoW) {
-            api->histo = VG_(malloc)("dh.retire_Block.1",
-                                     api->xsize * sizeof(UInt));
-            VG_(memset)(api->histo, 0, api->xsize * sizeof(UInt));
+            ppi->histo = VG_(malloc)("dh.retire_Block.1",
+                                     ppi->xsize * sizeof(UInt));
+            VG_(memset)(ppi->histo, 0, ppi->xsize * sizeof(UInt));
          }
          break;
 
       case Exactly:
-         //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);
-            api->xsize_tag = Mixed;
-            api->xsize = 0;
+         //tl_assert(ppi->freed_blocks > 1);
+         if (bk->req_szB != ppi->xsize) {
+            if (0) VG_(printf)("ppi %p   -->  Mixed(%lu -> %lu)\n",
+                               ppi, ppi->xsize, bk->req_szB);
+            ppi->xsize_tag = Mixed;
+            ppi->xsize = 0;
             // deallocate the histo, if any
-            if (api->histo) {
-               VG_(free)(api->histo);
-               api->histo = NULL;
+            if (ppi->histo) {
+               VG_(free)(ppi->histo);
+               ppi->histo = NULL;
             }
          }
          break;
 
       case Mixed:
-         //tl_assert(api->freed_blocks > 1);
+         //tl_assert(ppi->freed_blocks > 1);
          break;
 
       default:
@@ -410,17 +466,17 @@ static void retire_Block ( Block* bk, Bool because_freed )
    }
 
    // See if we can fold the histo data from this block into
-   // the data for the AP
-   if (api->xsize_tag == Exactly && api->histo && bk->histoW) {
-      tl_assert(api->xsize == bk->req_szB);
+   // the data for the PP.
+   if (ppi->xsize_tag == Exactly && ppi->histo && bk->histoW) {
+      tl_assert(ppi->xsize == bk->req_szB);
       UWord i;
-      for (i = 0; i < api->xsize; i++) {
-         // FIXME: do something better in case of overflow of api->histo[..]
+      for (i = 0; i < ppi->xsize; i++) {
+         // FIXME: do something better in case of overflow of ppi->histo[..]
          // Right now, at least don't let it overflow/wrap around
-         if (api->histo[i] <= 0xFFFE0000)
-            api->histo[i] += (UInt)bk->histoW[i];
+         if (ppi->histo[i] <= 0xFFFE0000)
+            ppi->histo[i] += (UInt)bk->histoW[i];
       }
-      if (0) VG_(printf)("fold in, AP = %p\n", api);
+      if (0) VG_(printf)("fold in, PP = %p\n", ppi);
    }
 
 #if 0
@@ -436,29 +492,30 @@ static void retire_Block ( Block* bk, Bool because_freed )
 #endif
 }
 
-/* This handles block resizing.  When a block with AP 'ec' has a
-   size change of 'delta', call here to update the APInfo. */
+/* This handles block resizing.  When a block with PP 'ec' has a
+   size change of 'delta', call here to update the PPInfo. */
 static void resize_Block(ExeContext* ec, SizeT old_req_szB, SizeT new_req_szB)
 {
+   tl_assert(clo_mode == Heap);
+
    Long    delta = (Long)new_req_szB - (Long)old_req_szB;
-   APInfo* api   = NULL;
+   PPInfo* ppi   = NULL;
    UWord   keyW  = 0;
    UWord   valW  = 0;
-   Bool    found = VG_(lookupFM)( apinfo,
+   Bool    found = VG_(lookupFM)( ppinfo,
                                   &keyW, &valW, (UWord)ec );
 
    tl_assert(found);
-   api = (APInfo*)valW;
-   tl_assert(api->ap == ec);
+   ppi = (PPInfo*)valW;
+   tl_assert(ppi->ec == ec);
 
    if (delta < 0) {
-      tl_assert(api->curr_bytes >= -delta);
+      tl_assert(ppi->curr_bytes >= -delta);
       tl_assert(g_curr_bytes >= -delta);
-   }
 
-   // Total bytes might be coming down from a possible peak.
-   if (delta < 0)
+      // Total bytes might be coming down from a possible peak.
       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.
@@ -468,41 +525,34 @@ static void resize_Block(ExeContext* ec, SizeT old_req_szB, SizeT new_req_szB)
    // calls to realloc wouldn't be counted towards the total_blocks count,
    // which is undesirable.
 
-   // Update global stats first.
+   // Update global stats and PPInfo stats.
 
    g_total_blocks++;
    g_total_bytes += new_req_szB;
 
+   ppi->total_blocks++;
+   ppi->total_bytes += new_req_szB;
+
    g_curr_blocks += 0;  // unchanged
    g_curr_bytes += delta;
 
+   ppi->curr_blocks += 0;  // unchanged
+   ppi->curr_bytes += delta;
+
    // The use of `>=` rather than `>` means that if there are multiple equal
    // peaks we record the latest one, like `check_for_peak` does.
    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;
-   }
-
-   // Now update APInfo stats.
-
-   api->total_blocks++;
-   api->total_bytes += new_req_szB;
+      g_tgmax_instrs = g_curr_instrs;
 
-   api->curr_blocks += 0;  // unchanged
-   api->curr_bytes += delta;
-
-   // The use of `>=` rather than `>` means that if there are multiple equal
-   // peaks we record the latest one, like `check_for_peak` does.
-   if (api->curr_bytes >= api->max_bytes) {
-      api->max_blocks = api->curr_blocks;
-      api->max_bytes  = api->curr_bytes;
+      ppi->max_blocks = ppi->curr_blocks;
+      ppi->max_bytes  = ppi->curr_bytes;
    }
 }
 
-
 //------------------------------------------------------------//
-//--- update both Block and APInfos after {m,re}alloc/free ---//
+//--- update both Block and PPInfos after {m,re}alloc/free ---//
 //------------------------------------------------------------//
 
 static
@@ -510,12 +560,13 @@ void* new_block ( ThreadId tid, void* p, SizeT req_szB, SizeT req_alignB,
                   Bool is_zeroed )
 {
    tl_assert(p == NULL); // don't handle custom allocators right now
-   SizeT actual_szB /*, slop_szB*/;
+   SizeT actual_szB;
 
    if ((SSizeT)req_szB < 0) return NULL;
 
-   if (req_szB == 0)
+   if (req_szB == 0) {
       req_szB = 1;  /* can't allow zero-sized blocks in the interval tree */
+   }
 
    // Allocate and zero if necessary
    if (!p) {
@@ -526,20 +577,21 @@ void* new_block ( ThreadId tid, void* p, SizeT req_szB, SizeT req_alignB,
       if (is_zeroed) VG_(memset)(p, 0, req_szB);
       actual_szB = VG_(cli_malloc_usable_size)(p);
       tl_assert(actual_szB >= req_szB);
-      /* slop_szB = actual_szB - req_szB; */
-   } else {
-      /* slop_szB = 0; */
+   }
+
+   if (clo_mode != Heap) {
+      return p;
    }
 
    // 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->ec           = 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
+   // Set up histogram array, if the block isn't too large.
    bk->histoW = NULL;
    if (req_szB <= HISTOGRAM_SIZE_LIMIT) {
       bk->histoW = VG_(malloc)("dh.new_block.2", req_szB * sizeof(UShort));
@@ -552,18 +604,19 @@ void* new_block ( ThreadId tid, void* p, SizeT req_szB, SizeT req_alignB,
 
    intro_Block(bk);
 
-   if (0) VG_(printf)("ALLOC %lu -> %p\n", req_szB, p);
-
    return p;
 }
 
 static
-void die_block ( void* p, Bool custom_free )
+void die_block ( void* p )
 {
-   tl_assert(!custom_free);  // at least for now
+   VG_(cli_free)(p);
 
-   Block* bk = find_Block_containing( (Addr)p );
+   if (clo_mode != Heap) {
+      return;
+   }
 
+   Block* bk = find_Block_containing( (Addr)p );
    if (!bk) {
      return; // bogus free
    }
@@ -577,12 +630,8 @@ void die_block ( void* p, Bool custom_free )
       return; // bogus free
    }
 
-   if (0) VG_(printf)(" FREE %p %llu\n",
-                      p, g_curr_instrs - bk->allocd_at);
-
    retire_Block(bk, True/*because_freed*/);
 
-   VG_(cli_free)( (void*)bk->payload );
    delete_Block_starting_at( bk->payload );
    if (bk->histoW) {
       VG_(free)( bk->histoW );
@@ -591,15 +640,24 @@ void die_block ( void* p, Bool custom_free )
    VG_(free)( bk );
 }
 
-
 static
 void* renew_block ( ThreadId tid, void* p_old, SizeT new_req_szB )
 {
-   if (0) VG_(printf)("REALL %p %lu\n", p_old, new_req_szB);
    void* p_new = NULL;
 
    tl_assert(new_req_szB > 0); // map 0 to 1
 
+   if (clo_mode != Heap) {
+      SizeT old_actual_szB = VG_(cli_malloc_usable_size)(p_old);
+      p_new = VG_(cli_malloc)(VG_(clo_alignment), new_req_szB);
+      if (!p_new) {
+         return NULL;
+      }
+      VG_(memmove)(p_new, p_old, VG_MIN(old_actual_szB, new_req_szB));
+      VG_(cli_free)(p_old);
+      return p_new;
+   }
+
    // Find the old block.
    Block* bk = find_Block_containing( (Addr)p_old );
    if (!bk) {
@@ -607,7 +665,7 @@ void* renew_block ( ThreadId tid, void* p_old, SizeT new_req_szB )
    }
 
    tl_assert(bk->req_szB > 0);
-   // assert the block finder is behaving sanely
+   // Assert the block finder is behaving sanely.
    tl_assert(bk->payload <= (Addr)p_old);
    tl_assert( (Addr)p_old < bk->payload + bk->req_szB );
 
@@ -624,9 +682,8 @@ void* renew_block ( ThreadId tid, void* p_old, SizeT new_req_szB )
 
    // Actually do the allocation, if necessary.
    if (new_req_szB <= bk->req_szB) {
-
       // New size is smaller or same; block not moved.
-      resize_Block(bk->ap, bk->req_szB, new_req_szB);
+      resize_Block(bk->ec, bk->req_szB, new_req_szB);
       bk->req_szB = new_req_szB;
 
       // Update reads/writes for the implicit copy. Even though we didn't
@@ -635,10 +692,9 @@ void* renew_block ( ThreadId tid, void* p_old, SizeT new_req_szB )
       bk->reads_bytes += new_req_szB;
       bk->writes_bytes += new_req_szB;
 
-      return p_old;
+      p_new = p_old;
 
    } else {
-
       // New size is bigger;  make new block, copy shared contents, free old.
       p_new = VG_(cli_malloc)(VG_(clo_alignment), new_req_szB);
       if (!p_new) {
@@ -663,7 +719,7 @@ void* renew_block ( ThreadId tid, void* p_old, SizeT new_req_szB )
       bk->writes_bytes += bk->req_szB;
 
       // Update the metadata.
-      resize_Block(bk->ap, bk->req_szB, new_req_szB);
+      resize_Block(bk->ec, bk->req_szB, new_req_szB);
       bk->payload = (Addr)p_new;
       bk->req_szB = new_req_szB;
 
@@ -672,13 +728,10 @@ void* renew_block ( ThreadId tid, void* p_old, SizeT new_req_szB )
          = VG_(addToFM)( interval_tree, (UWord)bk, (UWord)0/*no val*/);
       tl_assert(!present);
       fbc_cache0 = fbc_cache1 = NULL;
-
-      return p_new;
    }
-   /*NOTREACHED*/
-   tl_assert(0);
-}
 
+   return p_new;
+}
 
 //------------------------------------------------------------//
 //--- malloc() et al replacement wrappers                  ---//
@@ -711,17 +764,17 @@ static void *dh_memalign ( ThreadId tid, SizeT alignB, SizeT szB )
 
 static void dh_free ( ThreadId tid __attribute__((unused)), void* p )
 {
-   die_block( p, /*custom_free*/False );
+   die_block(p);
 }
 
 static void dh___builtin_delete ( ThreadId tid, void* p )
 {
-   die_block( p, /*custom_free*/False);
+   die_block(p);
 }
 
 static void dh___builtin_vec_delete ( ThreadId tid, void* p )
 {
-   die_block( p, /*custom_free*/False );
+   die_block(p);
 }
 
 static void* dh_realloc ( ThreadId tid, void* p_old, SizeT new_szB )
@@ -738,11 +791,14 @@ static void* dh_realloc ( ThreadId tid, void* p_old, SizeT new_szB )
 
 static SizeT dh_malloc_usable_size ( ThreadId tid, void* p )
 {
+   if (clo_mode != Heap) {
+      return VG_(cli_malloc_usable_size)(p);
+   }
+
    Block* bk = find_Block_containing( (Addr)p );
    return bk ? bk->req_szB : 0;
 }
 
-
 //------------------------------------------------------------//
 //--- memory references                                    ---//
 //------------------------------------------------------------//
@@ -767,6 +823,8 @@ void inc_histo_for_block ( Block* bk, Addr addr, UWord szB )
 static VG_REGPARM(2)
 void dh_handle_write ( Addr addr, UWord szB )
 {
+   tl_assert(clo_mode == Heap);
+
    Block* bk = find_Block_containing(addr);
    if (bk) {
       bk->writes_bytes += szB;
@@ -778,6 +836,8 @@ void dh_handle_write ( Addr addr, UWord szB )
 static VG_REGPARM(2)
 void dh_handle_read ( Addr addr, UWord szB )
 {
+   tl_assert(clo_mode == Heap);
+
    Block* bk = find_Block_containing(addr);
    if (bk) {
       bk->reads_bytes += szB;
@@ -786,7 +846,6 @@ void dh_handle_read ( Addr addr, UWord szB )
    }
 }
 
-
 // Handle reads and writes by syscalls (read == kernel
 // reads user space, write == kernel writes user space).
 // Assumes no such read or write spans a heap block
@@ -796,6 +855,8 @@ static
 void dh_handle_noninsn_read ( CorePart part, ThreadId tid, const HChar* s,
                               Addr base, SizeT size )
 {
+   tl_assert(clo_mode == Heap);
+
    switch (part) {
       case Vg_CoreSysCall:
          dh_handle_read(base, size);
@@ -809,10 +870,22 @@ void dh_handle_noninsn_read ( CorePart part, ThreadId tid, const HChar* s,
    }
 }
 
+static
+void dh_handle_noninsn_read_asciiz(CorePart part, ThreadId tid, const HChar* s,
+                                   Addr str)
+{
+   tl_assert(clo_mode == Heap);
+
+   tl_assert(part == Vg_CoreSysCall);
+   dh_handle_noninsn_read(part, tid, s, str, VG_(strlen)((const HChar*)str+1));
+}
+
 static
 void dh_handle_noninsn_write ( CorePart part, ThreadId tid,
                                Addr base, SizeT size )
 {
+   tl_assert(clo_mode == Heap);
+
    switch (part) {
       case Vg_CoreSysCall:
       case Vg_CoreClientReq:
@@ -825,7 +898,6 @@ void dh_handle_noninsn_write ( CorePart part, ThreadId tid,
    }
 }
 
-
 //------------------------------------------------------------//
 //--- Instrumentation                                      ---//
 //------------------------------------------------------------//
@@ -867,6 +939,10 @@ static
 void addMemEvent(IRSB* sbOut, Bool isWrite, Int szB, IRExpr* addr,
                  Int goff_sp)
 {
+   if (clo_mode != Heap) {
+      return;
+   }
+
    IRType   tyAddr   = Ity_INVALID;
    const HChar* hName= NULL;
    void*    hAddr    = NULL;
@@ -1090,37 +1166,58 @@ IRSB* dh_instrument ( VgCallbackClosure* closure,
 #undef mkU64
 #undef assign
 
-
 //------------------------------------------------------------//
-//--- Command line args                                    ---//
+//--- Client requests                                      ---//
 //------------------------------------------------------------//
 
-static const HChar* clo_dhat_out_file = "dhat.out.%p";
-
-static Bool dh_process_cmd_line_option(const HChar* arg)
+static Bool dh_handle_client_request(ThreadId tid, UWord* arg, UWord* ret)
 {
-   if VG_STR_CLO(arg, "--dhat-out-file", clo_dhat_out_file) {}
+   switch (arg[0]) {
+   case VG_USERREQ__DHAT_AD_HOC_EVENT: {
+      if (clo_mode != AdHoc) {
+         return False;
+      }
 
-   else
-      return VG_(replacement_malloc_process_cmd_line_option)(arg);
+      SizeT len = (SizeT)arg[1];
 
-   return True;
-}
+      // Only the ec and req_szB fields are used by intro_Block().
+      Block bk;
+      VG_(memset)(&bk, 0, sizeof(bk));
+      bk.req_szB = len;
+      bk.ec      = VG_(record_ExeContext)(tid, 0/*first word delta*/);
 
-static void dh_print_usage(void)
-{
-   VG_(printf)(
-"    --dhat-out-file=<file>  output file name [dhat.out.%%p]\n"
-   );
-}
+      intro_Block(&bk);
 
-static void dh_print_debug_usage(void)
-{
-   VG_(printf)(
-"    (none)\n"
-   );
-}
+      return True;
+   }
+
+   case _VG_USERREQ__DHAT_COPY: {
+      SizeT len = (SizeT)arg[1];
+
+      if (clo_mode != Copy) {
+         return False;
+      }
+
+      // Only the ec and req_szB fields are used by intro_Block().
+      Block bk;
+      VG_(memset)(&bk, 0, sizeof(bk));
+      bk.req_szB = len;
+      bk.ec      = VG_(record_ExeContext)(tid, 0/*first word delta*/);
 
+      intro_Block(&bk);
+
+      return True;
+   }
+
+   default:
+      VG_(message)(
+         Vg_UserMsg,
+         "Warning: unknown DHAT client request code %llx\n",
+         (ULong)arg[0]
+      );
+      return False;
+   }
+}
 
 //------------------------------------------------------------//
 //--- Finalisation                                         ---//
@@ -1139,6 +1236,108 @@ static void dh_print_debug_usage(void)
 //   using VG_(apply_ExeContext) in combination with an InlIpCursor.
 //
 // - We use short field names and minimal whitespace to minimize file sizes.
+//
+// Sample output:
+//
+// {
+//   // Version number of the format. Incremented on each
+//   // backwards-incompatible change. A mandatory integer.
+//   "dhatFileVersion": 2,
+//
+//   // The invocation mode. A mandatory, free-form string.
+//   "mode": "heap",
+//
+//   // The verb used before above stack frames, i.e. "<verb> at {". A
+//   // mandatory string.
+//   "verb": "Allocated",
+//
+//   // Are block lifetimes recorded? Affects whether some other fields are
+//   // present. A mandatory boolean.
+//   "bklt": true,
+//
+//   // Are block accesses recorded? Affects whether some other fields are
+//   // present. A mandatory boolean.
+//   "bkacc": true,
+//
+//   // Byte/bytes/blocks-position units. Optional strings. "byte", "bytes",
+//   // and "blocks" are the values used if these fields are omitted.
+//   "bu": "byte", "bsu": "bytes", "bksu": "blocks",
+//
+//   // Time units (individual and 1,000,000x). Mandatory strings.
+//   "tu": "instrs", "Mtu": "Minstr"
+//
+//   // The "short-lived" time threshold, measures in "tu"s.
+//   // - bklt=true: a mandatory integer.
+//   // - bklt=false: omitted.
+//   "tuth": 500,
+//
+//   // The executed command. A mandatory string.
+//   "cmd": "date",
+//
+//   // The process ID. A mandatory integer.
+//   "pid": 61129
+//
+//   // The time at the end of execution (t-end). A mandatory integer.
+//   "te": 350682
+//
+//   // The time of the global max (t-gmax).
+//   // - bklt=true: a mandatory integer.
+//   // - bklt=false: omitted.
+//   "tg": 331312,
+//
+//   // The program points. A mandatory array.
+//   "pps": [
+//    {
+//     // Total bytes and blocks. Mandatory integers.
+//     "tb": 5, "tbk": 1,
+//
+//     // Total lifetimes of all blocks allocated at this PP.
+//     // - bklt=true: a mandatory integer.
+//     // - bklt=false: omitted.
+//     "tl": 274,
+//
+//     // The maximum bytes and blocks for this PP.
+//     // - bklt=true: mandatory integers.
+//     // - bklt=false: omitted.
+//     "mb": 5, "mbk": 1,
+//
+//     // The bytes and blocks at t-gmax for this PP.
+//     // - bklt=true: mandatory integers.
+//     // - bklt=false: omitted.
+//     "gb": 0, "gbk": 0,
+//
+//     // The bytes and blocks at t-end for this PP.
+//     // - bklt=true: mandatory integers.
+//     // - bklt=false: omitted.
+//     "eb": 0, "ebk": 0,
+//
+//     // The reads and writes of blocks for this PP.
+//     // - bkacc=true: mandatory integers.
+//     // - bkacc=false: omitted.
+//     "rb": 41, "wb": 5,
+//
+//     // The exact accesses of blocks for this PP. Only used when all
+//     // allocations are the same size and sufficiently small. A negative
+//     // element indicates run-length encoding of the following integer.
+//     // E.g. `-3, 4` means "three 4s in a row".
+//     // - bkacc=true: an optional array of integers.
+//     // - bkacc=false: omitted.
+//     "acc": [5, -3, 4, 2],
+//
+//     // Frames. Each element is an index into the "ftbl" array below.
+//     // - All modes: A mandatory array of integers.
+//     "fs": [1, 2, 3]
+//    }
+//   ],
+//
+//   // Frame table. A mandatory array of strings.
+//   "ftbl": [
+//     "[root]",
+//     "0x4AA1D9F: _nl_normalize_codeset (l10nflist.c:332)",
+//     "0x4A9B414: _nl_load_locale_from_archive (loadarchive.c:173)",
+//     "0x4A9A2BE: _nl_find_locale (findlocale.c:153)"
+//   ]
+// }
 
 static VgFile* fp;
 
@@ -1223,7 +1422,7 @@ static const HChar* json_escape(const HChar* s)
    return buf;
 }
 
-static void write_APInfo_frame(UInt n, DiEpoch ep, Addr ip, void* opaque)
+static void write_PPInfo_frame(UInt n, DiEpoch ep, Addr ip, void* opaque)
 {
    Bool* is_first = (Bool*)opaque;
    InlIPCursor* iipc = VG_(new_IIPC)(ep, ip);
@@ -1263,83 +1462,102 @@ static void write_APInfo_frame(UInt n, DiEpoch ep, Addr ip, void* opaque)
    VG_(delete_IIPC)(iipc);
 };
 
-static void write_APInfo(APInfo* api, Bool is_first)
+static void write_PPInfo(PPInfo* ppi, 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",
+   FP(" %c{\"tb\":%llu,\"tbk\":%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);
-
-   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);
+      ppi->total_bytes, ppi->total_blocks);
+
+   if (clo_mode == Heap) {
+      tl_assert(ppi->total_blocks >= ppi->max_blocks);
+      tl_assert(ppi->total_bytes >= ppi->max_bytes);
+
+      FP("  ,\"tl\":%llu\n",
+         ppi->total_lifetimes_instrs);
+      FP("  ,\"mb\":%llu,\"mbk\":%llu\n",
+         ppi->max_bytes, ppi->max_blocks);
+      FP("  ,\"gb\":%llu,\"gbk\":%llu\n",
+         ppi->at_tgmax_bytes, ppi->at_tgmax_blocks);
+      FP("  ,\"eb\":%llu,\"ebk\":%llu\n",
+         ppi->curr_bytes, ppi->curr_blocks);
+      FP("  ,\"rb\":%llu,\"wb\":%llu\n",
+         ppi->reads_bytes, ppi->writes_bytes);
+
+      if (ppi->histo && ppi->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 < ppi->xsize; i++) {
+            UShort h = ppi->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;
             }
-            reps = 1;
-            repval = h;
          }
-      }
-      // Print the final run.
-      if (reps == 1) {
-         FP("%u", repval);
-      } else if (reps > 1) {
-         FP("-%d,%u", reps, repval);
-      }
+         // Print the final run.
+         if (reps == 1) {
+            FP("%u", repval);
+         } else if (reps > 1) {
+            FP("-%d,%u", reps, repval);
+         }
 
-      FP("]\n");
+         FP("]\n");
+      }
+   } else {
+      tl_assert(ppi->curr_bytes == 0);
+      tl_assert(ppi->curr_blocks == 0);
+      tl_assert(ppi->max_bytes == 0);
+      tl_assert(ppi->max_blocks == 0);
+      tl_assert(ppi->at_tgmax_bytes == 0);
+      tl_assert(ppi->at_tgmax_blocks == 0);
+      tl_assert(ppi->total_lifetimes_instrs == 0);
+      tl_assert(ppi->freed_blocks == 0);
+      tl_assert(ppi->reads_bytes == 0);
+      tl_assert(ppi->writes_bytes == 0);
+      tl_assert(ppi->xsize_tag == 0);
+      tl_assert(ppi->xsize == 0);
+      tl_assert(ppi->histo == NULL);
    }
 
    FP("  ,\"fs\":");
    Bool is_first_frame = True;
-   VG_(apply_ExeContext)(write_APInfo_frame, &is_first_frame, api->ap);
+   VG_(apply_ExeContext)(write_PPInfo_frame, &is_first_frame, ppi->ec);
    FP("]\n");
 
    FP("  }\n");
 }
 
-static void write_APInfos(void)
+static void write_PPInfos(void)
 {
    UWord keyW, valW;
 
-   FP(",\"aps\":\n");
+   FP(",\"pps\":\n");
 
-   VG_(initIterFM)(apinfo);
+   VG_(initIterFM)(ppinfo);
    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);
+   while (VG_(nextIterFM)(ppinfo, &keyW, &valW)) {
+      PPInfo* ppi = (PPInfo*)valW;
+      tl_assert(ppi && ppi->ec == (ExeContext*)keyW);
+      write_PPInfo(ppi, is_first);
       is_first = False;
    }
-   VG_(doneIterFM)(apinfo);
+   VG_(doneIterFM)(ppinfo);
 
    if (is_first) {
-      // We didn't print any elements. This happens if apinfo is empty.
+      // We didn't print any elements. This happens if ppinfo is empty.
       FP(" [\n");
    }
 
@@ -1351,30 +1569,33 @@ static void dh_fini(Int exit_status)
    // This function does lots of allocations that it doesn't bother to free,
    // because execution is almost over anyway.
 
+   UWord keyW, valW;
+
    // Total bytes might be at a possible peak.
-   check_for_peak();
+   if (clo_mode == Heap) {
+      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 )) {
-      Block* bk = (Block*)keyW;
-      tl_assert(valW == 0);
-      tl_assert(bk);
-      retire_Block(bk, False/*!because_freed*/);
-   }
-   VG_(doneIterFM)( interval_tree );
-
-   // Stats.
-   if (VG_(clo_stats)) {
-      VG_(dmsg)(" dhat: find_Block_containing:\n");
-      VG_(dmsg)("             found: %'lu (%'lu cached + %'lu uncached)\n",
-                stats__n_fBc_cached + stats__n_fBc_uncached,
-                stats__n_fBc_cached,
-                stats__n_fBc_uncached);
-      VG_(dmsg)("          notfound: %'lu\n", stats__n_fBc_notfound);
-      VG_(dmsg)("\n");
+      // Before printing statistics, we must harvest various stats (such as
+      // lifetimes and accesses) for all the blocks that are still alive.
+      VG_(initIterFM)( interval_tree );
+      while (VG_(nextIterFM)( interval_tree, &keyW, &valW )) {
+         Block* bk = (Block*)keyW;
+         tl_assert(valW == 0);
+         tl_assert(bk);
+         retire_Block(bk, False/*!because_freed*/);
+      }
+      VG_(doneIterFM)( interval_tree );
+
+      // Stats.
+      if (VG_(clo_stats)) {
+         VG_(dmsg)(" dhat: find_Block_containing:\n");
+         VG_(dmsg)("             found: %'lu (%'lu cached + %'lu uncached)\n",
+                   stats__n_fBc_cached + stats__n_fBc_uncached,
+                   stats__n_fBc_cached,
+                   stats__n_fBc_uncached);
+         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.
@@ -1404,7 +1625,28 @@ static void dh_fini(Int exit_status)
    }
 
    // Write to data file.
-   FP("{\"dhatFileVersion\":1\n");
+   FP("{\"dhatFileVersion\":2\n");
+
+   // The output mode, block booleans, and byte/block units.
+   if (clo_mode == Heap) {
+      FP(",\"mode\":\"heap\",\"verb\":\"Allocated\"\n");
+      FP(",\"bklt\":true,\"bkacc\":true\n");
+   } else if (clo_mode == Copy) {
+      FP(",\"mode\":\"copy\",\"verb\":\"Copied\"\n");
+      FP(",\"bklt\":false,\"bkacc\":false\n");
+   } else if (clo_mode == AdHoc) {
+      FP(",\"mode\":\"ad-hoc\",\"verb\":\"Occurred\"\n");
+      FP(",\"bklt\":false,\"bkacc\":false\n");
+      FP(",\"bu\":\"unit\",\"bsu\":\"units\",\"bksu\":\"events\"\n");
+   } else {
+      tl_assert(False);
+   }
+
+   // The time units.
+   FP(",\"tu\":\"instrs\",\"Mtu\":\"Minstr\"\n");
+   if (clo_mode == Heap) {
+      FP(",\"tuth\":500\n");
+   }
 
    // The command.
    const HChar* exe = VG_(args_the_exename);
@@ -1419,10 +1661,15 @@ static void dh_fini(Int exit_status)
    FP(",\"pid\":%d\n", VG_(getpid)());
 
    // Times.
-   FP(",\"mi\":%llu,\"ei\":%llu\n", g_max_instrs, g_curr_instrs);
+   FP(",\"te\":%llu\n", g_curr_instrs);
+   if (clo_mode == Heap) {
+      FP(",\"tg\":%llu\n", g_tgmax_instrs);
+   } else {
+      tl_assert(g_tgmax_instrs == 0);
+   }
 
    // APs.
-   write_APInfos();
+   write_PPInfos();
 
    // Frame table.
    FP(",\"ftbl\":\n");
@@ -1457,24 +1704,34 @@ static void dh_fini(Int exit_status)
    }
 
    // 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);
+   VG_(umsg)("Total:     %'llu %s in %'llu %s\n",
+             g_total_bytes, clo_mode == AdHoc ? "units" : "bytes",
+             g_total_blocks, clo_mode == AdHoc ? "events" : "blocks");
+   if (clo_mode == Heap) {
+      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);
+   } else {
+      tl_assert(g_max_bytes == 0);
+      tl_assert(g_max_blocks == 0);
+      tl_assert(g_curr_bytes == 0);
+      tl_assert(g_curr_blocks == 0);
+      tl_assert(g_reads_bytes == 0);
+      tl_assert(g_writes_bytes == 0);
+   }
 
    // Print a how-to-view-the-profile hint.
    VG_(umsg)("\n");
    VG_(umsg)("To view the resulting profile, open\n");
    VG_(umsg)("  file://%s/%s\n", DHAT_VIEW_DIR, "dh_view.html");
-   VG_(umsg)("in a web browser, click on \"Load...\" "
+   VG_(umsg)("in a web browser, click on \"Load...\", "
              "and then select the file\n");
    VG_(umsg)("  %s\n", dhat_out_file);
-   VG_(umsg)("Scroll to the end the displayed page to see a short\n");
-   VG_(umsg)("explanation of some of the abbreviations used in the page.\n");
+   VG_(umsg)("The text at the bottom explains the abbreviations used in the "
+             "output.\n");
 
    VG_(free)(dhat_out_file);
 }
@@ -1485,6 +1742,11 @@ static void dh_fini(Int exit_status)
 
 static void dh_post_clo_init(void)
 {
+   if (clo_mode == Heap) {
+      VG_(track_pre_mem_read)        ( dh_handle_noninsn_read );
+      VG_(track_pre_mem_read_asciiz) ( dh_handle_noninsn_read_asciiz );
+      VG_(track_post_mem_write)      ( dh_handle_noninsn_write );
+   }
 }
 
 static void dh_pre_clo_init(void)
@@ -1500,31 +1762,27 @@ static void dh_pre_clo_init(void)
    VG_(basic_tool_funcs)          (dh_post_clo_init,
                                    dh_instrument,
                                    dh_fini);
-//zz
+
    // Needs.
    VG_(needs_libc_freeres)();
    VG_(needs_cxx_freeres)();
    VG_(needs_command_line_options)(dh_process_cmd_line_option,
                                    dh_print_usage,
                                    dh_print_debug_usage);
-//zz   VG_(needs_client_requests)     (dh_handle_client_request);
-//zz   VG_(needs_sanity_checks)       (dh_cheap_sanity_check,
-//zz                                   dh_expensive_sanity_check);
-   VG_(needs_malloc_replacement)  (dh_malloc,
-                                   dh___builtin_new,
-                                   dh___builtin_vec_new,
-                                   dh_memalign,
-                                   dh_calloc,
-                                   dh_free,
-                                   dh___builtin_delete,
-                                   dh___builtin_vec_delete,
-                                   dh_realloc,
-                                   dh_malloc_usable_size,
-                                   0 );
-
-   VG_(track_pre_mem_read)        ( dh_handle_noninsn_read );
-   //VG_(track_pre_mem_read_asciiz) ( check_mem_is_defined_asciiz );
-   VG_(track_post_mem_write)      ( dh_handle_noninsn_write );
+   VG_(needs_client_requests)     (dh_handle_client_request);
+// VG_(needs_sanity_checks)       (dh_cheap_sanity_check,
+//                                 dh_expensive_sanity_check);
+   VG_(needs_malloc_replacement)(dh_malloc,
+                                 dh___builtin_new,
+                                 dh___builtin_vec_new,
+                                 dh_memalign,
+                                 dh_calloc,
+                                 dh_free,
+                                 dh___builtin_delete,
+                                 dh___builtin_vec_delete,
+                                 dh_realloc,
+                                 dh_malloc_usable_size,
+                                 0 );
 
    tl_assert(!interval_tree);
    tl_assert(!fbc_cache0);
@@ -1535,8 +1793,8 @@ static void dh_pre_clo_init(void)
                                VG_(free),
                                interval_tree_Cmp );
 
-   apinfo = VG_(newFM)( VG_(malloc),
-                        "dh.apinfo.1",
+   ppinfo = VG_(newFM)( VG_(malloc),
+                        "dh.ppinfo.1",
                         VG_(free),
                         NULL/*unboxedcmp*/ );
 }
diff --git a/dhat/dh_replace_strmem.c b/dhat/dh_replace_strmem.c
new file mode 100644 (file)
index 0000000..a509925
--- /dev/null
@@ -0,0 +1,41 @@
+/*--------------------------------------------------------------------*/
+/*--- Replacements for memcpy(), which run on the simulated CPU    ---*/
+/*--- simulated CPU.                                               ---*/
+/*---                                          dh_replace_strmem.c ---*/
+/*--------------------------------------------------------------------*/
+
+/*
+   This file is part of DHAT, a Valgrind tool for profiling the
+   heap usage of programs.
+
+   Copyright (C) 2020-2020 Nicholas Nethercote
+
+   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.
+*/
+
+#include "dhat.h"
+
+#define RECORD_COPY(_qzz_len) \
+  VALGRIND_DO_CLIENT_REQUEST_STMT(_VG_USERREQ__DHAT_COPY, \
+                                  (_qzz_len), 0, 0, 0, 0)
+
+#include "../shared/vg_replace_strmem.c"
+
+/*--------------------------------------------------------------------*/
+/*--- end                                                          ---*/
+/*--------------------------------------------------------------------*/
index eb238de7eb2509a9c2627fe72db90e1bbbd73498..ee88e7b723257864d7044f388306de9a416dbd18 100644 (file)
@@ -60,11 +60,16 @@ let empty = {
   name: "empty",
   input:
 //---------------------------------------------------------------------------
-{"dhatFileVersion":1
+{"dhatFileVersion":2
+,"mode":"heap","verb":"Allocated"
+,"bklt":true,"bkacc":true
+,"tu":"instrs","Mtu":"Minstr"
+,"tuth":500
 ,"cmd":"./empty"
 ,"pid":23431
-,"mi":0,"ei":248602
-,"aps":
+,"te":248602
+,"tg":0
+,"pps":
  [
  ]
 ,"ftbl":
@@ -80,6 +85,7 @@ let empty = {
 //---------------------------------------------------------------------------
 `\
 Invocation {
+  Mode:    heap
   Command: ./empty
   PID:     23431
 }
@@ -89,7 +95,7 @@ Times {
   t-end:  248,602 instrs
 }
 
-─ AP 1/1 {
+─ PP 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
@@ -100,7 +106,7 @@ Times {
     }
   }
 
-AP significance threshold: total >= 0 bytes (0%)
+PP significance threshold: total >= 0 bytes (0%)
 `
 //---------------------------------------------------------------------------
     }
@@ -116,15 +122,20 @@ let single = {
   name: "single",
   input:
 //---------------------------------------------------------------------------
-{"dhatFileVersion":1
+{"dhatFileVersion":2
+,"mode":"heap","verb":"Allocated"
+,"bklt":true,"bkacc":true
+,"tu":"instrs","Mtu":"Minstr"
+,"tuth":500
 ,"cmd":"./single"
 ,"pid":30563
-,"mi":242900,"ei":249824
-,"aps":
- [{"tb":16,"tbk":1,"tli":6924
+,"te":249824
+,"tg":242900
+,"pps":
+ [{"tb":16,"tbk":1,"tl":6924
   ,"mb":16,"mbk":1
   ,"gb":16,"gbk":1
-  ,"fb":16,"fbk":1
+  ,"eb":16,"ebk":1
   ,"rb":0,"wb":12
   ,"acc":[-4,3,-12,0]
   ,"fs":[1]
@@ -144,6 +155,7 @@ let single = {
 //---------------------------------------------------------------------------
 `\
 Invocation {
+  Mode:    heap
   Command: ./single
   PID:     30563
 }
@@ -153,7 +165,7 @@ Times {
   t-end:  249,824 instrs
 }
 
-─ AP 1/1 {
+─ PP 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
@@ -168,7 +180,7 @@ Times {
     }
   }
 
-AP significance threshold: total >= 0.16 bytes (1%)
+PP significance threshold: total >= 0.16 bytes (1%)
 `
 //---------------------------------------------------------------------------
     }
@@ -185,79 +197,84 @@ let subseqs = {
   name: "subseqs",
   input:
 //---------------------------------------------------------------------------
-{"dhatFileVersion":1
+{"dhatFileVersion":2
+,"mode":"heap","verb":"Allocated"
+,"bklt":true,"bkacc":true
+,"tu":"instrs","Mtu":"Minstr"
+,"tuth":500
 ,"cmd":"subseqs"
 ,"pid":0
-,"mi":10000,"ei":20000
-,"aps":
- [{"tb":15,"tbk":1,"tli":1000
+,"te":20000
+,"tg":10000
+,"pps":
+ [{"tb":15,"tbk":1,"tl":1000
   ,"mb":15,"mbk":1
   ,"gb":15,"gbk":1
-  ,"fb":0,"fbk":0
+  ,"eb":0,"ebk":0
   ,"rb":0,"wb":0
   ,"acc":[-15,0]
   ,"fs":[1,2,3]
   }
- ,{"tb":14,"tbk":1,"tli":1000
+ ,{"tb":14,"tbk":1,"tl":1000
   ,"mb":14,"mbk":1
   ,"gb":14,"gbk":1
-  ,"fb":0,"fbk":0
+  ,"eb":0,"ebk":0
   ,"rb":0,"wb":0
   ,"acc":[-14,0]
   ,"fs":[1,2,3,3]
   }
- ,{"tb":13,"tbk":1,"tli":1000
+ ,{"tb":13,"tbk":1,"tl":1000
   ,"mb":13,"mbk":1
   ,"gb":13,"gbk":1
-  ,"fb":0,"fbk":0
+  ,"eb":0,"ebk":0
   ,"rb":0,"wb":0
   ,"acc":[-13,0]
   ,"fs":[1,2,3,3,3]
   }
- ,{"tb":12,"tbk":1,"tli":1000
+ ,{"tb":12,"tbk":1,"tl":1000
   ,"mb":12,"mbk":1
   ,"gb":12,"gbk":1
-  ,"fb":0,"fbk":0
+  ,"eb":0,"ebk":0
   ,"rb":0,"wb":0
   ,"acc":[-12,0]
   ,"fs":[4,5,6,6,6]
   }
- ,{"tb":11,"tbk":1,"tli":1000
+ ,{"tb":11,"tbk":1,"tl":1000
   ,"mb":11,"mbk":1
   ,"gb":11,"gbk":1
-  ,"fb":0,"fbk":0
+  ,"eb":0,"ebk":0
   ,"rb":0,"wb":0
   ,"acc":[-11,0]
   ,"fs":[4,5,6,6]
   }
- ,{"tb":10,"tbk":1,"tli":1000
+ ,{"tb":10,"tbk":1,"tl":1000
   ,"mb":10,"mbk":1
   ,"gb":10,"gbk":1
-  ,"fb":0,"fbk":0
+  ,"eb":0,"ebk":0
   ,"rb":0,"wb":0
   ,"acc":[-10,0]
   ,"fs":[4,5,6]
   }
- ,{"tb":9,"tbk":1,"tli":1000
+ ,{"tb":9,"tbk":1,"tl":1000
   ,"mb":9,"mbk":1
   ,"gb":9,"gbk":1
-  ,"fb":0,"fbk":0
+  ,"eb":0,"ebk":0
   ,"rb":0,"wb":0
   ,"acc":[-9,0]
   ,"fs":[7,8,9]
   }
- ,{"tb":8,"tbk":1,"tli":1000
+ ,{"tb":8,"tbk":1,"tl":1000
   ,"mb":8,"mbk":1
   ,"gb":8,"gbk":1
-  ,"fb":0,"fbk":0
+  ,"eb":0,"ebk":0
   ,"rb":0,"wb":0
   ,"acc":[-8,0]
   ,"fs":[7,8,10]
   }
- ,{"tb":7,"tbk":1,"tli":1000
+ ,{"tb":7,"tbk":1,"tl":1000
   ,"mb":7,"mbk":1
   ,"gb":7,"gbk":1
-  ,"fb":0,"fbk":0
+  ,"eb":0,"ebk":0
   ,"rb":0,"wb":0
   ,"acc":[-7,0]
   ,"fs":[7,8]
@@ -286,6 +303,7 @@ let subseqs = {
 //---------------------------------------------------------------------------
 `\
 Invocation {
+  Mode:    heap
   Command: subseqs
   PID:     0
 }
@@ -295,7 +313,7 @@ Times {
   t-end:  20,000 instrs
 }
 
-â–¼ AP 1/1 (3 children) {
+â–¼ PP 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
@@ -305,7 +323,7 @@ Times {
       #0: [root]
     }
   }
-  â”œâ”€â–¼ AP 1.1/3 (2 children) {
+  â”œâ”€â–¼ PP 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
@@ -317,7 +335,7 @@ Times {
   â”‚       #3: c()
   â”‚     }
   â”‚   }
-  â”‚   â”œâ”€â–¼ AP 1.1.1/2 (2 children) {
+  â”‚   â”œâ”€â–¼ PP 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
@@ -330,7 +348,7 @@ Times {
   â”‚   â”‚       #4: c()
   â”‚   â”‚     }
   â”‚   â”‚   }
-  â”‚   â”‚   â”œâ”€â”€ AP 1.1.1.1/2 {
+  â”‚   â”‚   â”œâ”€â”€ PP 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
@@ -347,7 +365,7 @@ Times {
   â”‚   â”‚   â”‚       ^4: c()
   â”‚   â”‚   â”‚     }
   â”‚   â”‚   â”‚   }
-  â”‚   â”‚   â””── AP 1.1.1.2/2 {
+  â”‚   â”‚   â””── PP 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
@@ -365,7 +383,7 @@ Times {
   â”‚   â”‚           #5: c()
   â”‚   â”‚         }
   â”‚   â”‚       }
-  â”‚   â””── AP 1.1.2/2 {
+  â”‚   â””── PP 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
@@ -381,7 +399,7 @@ Times {
   â”‚           ^3: c()
   â”‚         }
   â”‚       }
-  â”œâ”€â–¼ AP 1.2/3 (2 children) {
+  â”œâ”€â–¼ PP 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
@@ -393,7 +411,7 @@ Times {
   â”‚       #3: f()
   â”‚     }
   â”‚   }
-  â”‚   â”œâ”€â–¼ AP 1.2.1/2 (2 children) {
+  â”‚   â”œâ”€â–¼ PP 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
@@ -406,7 +424,7 @@ Times {
   â”‚   â”‚       #4: f()
   â”‚   â”‚     }
   â”‚   â”‚   }
-  â”‚   â”‚   â”œâ”€â”€ AP 1.2.1.1/2 {
+  â”‚   â”‚   â”œâ”€â”€ PP 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
@@ -424,7 +442,7 @@ Times {
   â”‚   â”‚   â”‚       #5: f()
   â”‚   â”‚   â”‚     }
   â”‚   â”‚   â”‚   }
-  â”‚   â”‚   â””── AP 1.2.1.2/2 {
+  â”‚   â”‚   â””── PP 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
@@ -441,7 +459,7 @@ Times {
   â”‚   â”‚           ^4: f()
   â”‚   â”‚         }
   â”‚   â”‚       }
-  â”‚   â””── AP 1.2.2/2 {
+  â”‚   â””── PP 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
@@ -457,7 +475,7 @@ Times {
   â”‚           ^3: f()
   â”‚         }
   â”‚       }
-  â””─▼ AP 1.3/3 (3 children) {
+  â””─▼ PP 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
@@ -468,7 +486,7 @@ Times {
           #2: h()
         }
       }
-      â”œâ”€â”€ AP 1.3.1/3 {
+      â”œâ”€â”€ PP 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
@@ -484,7 +502,7 @@ Times {
       â”‚       #3: i()
       â”‚     }
       â”‚   }
-      â”œâ”€â”€ AP 1.3.2/3 {
+      â”œâ”€â”€ PP 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
@@ -500,7 +518,7 @@ Times {
       â”‚       #3: j()
       â”‚     }
       â”‚   }
-      â””── AP 1.3.3/3 {
+      â””── PP 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
@@ -516,7 +534,7 @@ Times {
             }
           }
 
-AP significance threshold: total >= 0.99 bytes (1%)
+PP significance threshold: total >= 0.99 bytes (1%)
 `
 //---------------------------------------------------------------------------
     }
@@ -532,78 +550,83 @@ let acc = {
   name: "acc",
   input:
 //---------------------------------------------------------------------------
-{"dhatFileVersion":1
+{"dhatFileVersion":2
+,"mode":"heap","verb":"Allocated"
+,"bklt":true,"bkacc":true
+,"tu":"instrs","Mtu":"Minstr"
+,"tuth":500
 ,"cmd":"./acc"
 ,"pid":23513
-,"mi":265120,"ei":1337753
-,"aps":
- [{"tb":32,"tbk":1,"tli":4751
+,"te":1337753
+,"tg":265120
+,"pps":
+ [{"tb":32,"tbk":1,"tl":4751
   ,"mb":32,"mbk":1
   ,"gb":0,"gbk":0
-  ,"fb":0,"fbk":0
+  ,"eb":0,"ebk":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
+ ,{"tb":20,"tbk":1,"tl":106
   ,"mb":20,"mbk":1
   ,"gb":0,"gbk":0
-  ,"fb":0,"fbk":0
+  ,"eb":0,"ebk":0
   ,"rb":4,"wb":48
   ,"acc":[-4,2,-4,0,-4,1,-4,0,-4,10]
   ,"fs":[2]
   }
- ,{"tb":33,"tbk":1,"tli":39
+ ,{"tb":33,"tbk":1,"tl":39
   ,"mb":33,"mbk":1
   ,"gb":0,"gbk":0
-  ,"fb":0,"fbk":0
+  ,"eb":0,"ebk":0
   ,"rb":0,"wb":1
   ,"acc":[-32,0,1]
   ,"fs":[3]
   }
- ,{"tb":1024,"tbk":1,"tli":15179
+ ,{"tb":1024,"tbk":1,"tl":15179
   ,"mb":1024,"mbk":1
   ,"gb":0,"gbk":0
-  ,"fb":0,"fbk":0
+  ,"eb":0,"ebk":0
   ,"rb":1024,"wb":1124
   ,"acc":[-500,2,-100,3,-424,2]
   ,"fs":[4]
   }
- ,{"tb":1025,"tbk":1,"tli":15415
+ ,{"tb":1025,"tbk":1,"tl":15415
   ,"mb":1025,"mbk":1
   ,"gb":1025,"gbk":1
-  ,"fb":0,"fbk":0
+  ,"eb":0,"ebk":0
   ,"rb":1025,"wb":1025
   ,"fs":[5]
   }
- ,{"tb":100,"tbk":1,"tli":350084
+ ,{"tb":100,"tbk":1,"tl":350084
   ,"mb":100,"mbk":1
   ,"gb":0,"gbk":0
-  ,"fb":0,"fbk":0
+  ,"eb":0,"ebk":0
   ,"rb":0,"wb":200000
   ,"acc":[-4,50000,-96,0]
   ,"fs":[6,7]
   }
- ,{"tb":100,"tbk":1,"tli":350072
+ ,{"tb":100,"tbk":1,"tl":350072
   ,"mb":100,"mbk":1
   ,"gb":0,"gbk":0
-  ,"fb":0,"fbk":0
+  ,"eb":0,"ebk":0
   ,"rb":0,"wb":200000
   ,"acc":[-4,50000,-96,0]
   ,"fs":[6,8]
   }
- ,{"tb":100,"tbk":1,"tli":700084
+ ,{"tb":100,"tbk":1,"tl":700084
   ,"mb":100,"mbk":1
   ,"gb":0,"gbk":0
-  ,"fb":0,"fbk":0
+  ,"eb":0,"ebk":0
   ,"rb":0,"wb":400000
   ,"acc":[-4,65535,-96,0]
   ,"fs":[9,10]
   }
- ,{"tb":100,"tbk":1,"tli":700072
+ ,{"tb":100,"tbk":1,"tl":700072
   ,"mb":100,"mbk":1
   ,"gb":0,"gbk":0
-  ,"fb":0,"fbk":0
+  ,"eb":0,"ebk":0
   ,"rb":0,"wb":400000
   ,"acc":[-4,65535,-96,0]
   ,"fs":[9,11]
@@ -636,6 +659,7 @@ let acc = {
 //---------------------------------------------------------------------------
 `\
 Invocation {
+  Mode:    heap
   Command: ./acc
   PID:     23513
 }
@@ -645,7 +669,7 @@ Times {
   t-end:  1,337,753 instrs
 }
 
-â–¼ AP 1/1 (7 children) {
+â–¼ PP 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
@@ -655,7 +679,7 @@ Times {
       #0: [root]
     }
   }
-  â”œâ”€â”€ AP 1.1/7 {
+  â”œâ”€â”€ PP 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
@@ -666,7 +690,7 @@ Times {
   â”‚       #1: 0x10886F: main (acc.c:47)
   â”‚     }
   â”‚   }
-  â”œâ”€â”€ AP 1.2/7 {
+  â”œâ”€â”€ PP 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
@@ -711,7 +735,7 @@ Times {
   â”‚       #1: 0x1087F0: main (acc.c:37)
   â”‚     }
   â”‚   }
-  â”œâ”€â–¼ AP 1.3/7 (2 children) {
+  â”œâ”€â–¼ PP 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
@@ -727,7 +751,7 @@ Times {
   â”‚       #1: 0x1086F1: m1 (acc.c:7)
   â”‚     }
   â”‚   }
-  â”‚   â”œâ”€â”€ AP 1.3.1/2 {
+  â”‚   â”œâ”€â”€ PP 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
@@ -745,7 +769,7 @@ Times {
   â”‚   â”‚       #2: 0x1088C3: main (acc.c:54)
   â”‚   â”‚     }
   â”‚   â”‚   }
-  â”‚   â””── AP 1.3.2/2 {
+  â”‚   â””── PP 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
@@ -763,7 +787,7 @@ Times {
   â”‚           #2: 0x1088D1: main (acc.c:55)
   â”‚         }
   â”‚       }
-  â”œâ”€â–¼ AP 1.4/7 (2 children) {
+  â”œâ”€â–¼ PP 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
@@ -779,7 +803,7 @@ Times {
   â”‚       #1: 0x10870B: m2 (acc.c:9)
   â”‚     }
   â”‚   }
-  â”‚   â”œâ”€â”€ AP 1.4.1/2 {
+  â”‚   â”œâ”€â”€ PP 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
@@ -797,7 +821,7 @@ Times {
   â”‚   â”‚       #2: 0x108921: main (acc.c:64)
   â”‚   â”‚     }
   â”‚   â”‚   }
-  â”‚   â””── AP 1.4.2/2 {
+  â”‚   â””── PP 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
@@ -815,7 +839,7 @@ Times {
   â”‚           #2: 0x10892F: main (acc.c:65)
   â”‚         }
   â”‚       }
-  â”œâ”€â”€ AP 1.5/7 {
+  â”œâ”€â”€ PP 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
@@ -830,7 +854,7 @@ Times {
   â”‚       #1: 0x1087CB: main (acc.c:32)
   â”‚     }
   â”‚   }
-  â”œâ”€â”€ AP 1.6/7 {
+  â”œâ”€â”€ PP 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
@@ -844,7 +868,7 @@ Times {
   â”‚       #1: 0x10871F: main (acc.c:14)
   â”‚     }
   â”‚   }
-  â””── AP 1.7/7 {
+  â””── PP 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
@@ -859,7 +883,7 @@ Times {
         }
       }
 
-AP significance threshold: at-t-end >= 0 bytes (0%)
+PP significance threshold: at-t-end >= 0 bytes (0%)
 `
 //---------------------------------------------------------------------------
     }
@@ -875,159 +899,164 @@ let big = {
   name: "big",
   input:
 //---------------------------------------------------------------------------
-{"dhatFileVersion":1
+{"dhatFileVersion":2
+,"mode":"heap","verb":"Allocated"
+,"bklt":true,"bkacc":true
+,"tu":"instrs","Mtu":"Minstr"
+,"tuth":500
 ,"cmd":"./big"
 ,"pid":3902
-,"mi":245281,"ei":253354
-,"aps":
- [{"tb":706,"tbk":1,"tli":543
+,"te":253354
+,"tg":245281
+,"pps":
+ [{"tb":706,"tbk":1,"tl":543
   ,"mb":706,"mbk":1
   ,"gb":706,"gbk":1
-  ,"fb":0,"fbk":0
+  ,"eb":0,"ebk":0
   ,"rb":0,"wb":0
   ,"acc":[-706,0]
   ,"fs":[1,2,3,4,5]
   }
- ,{"tb":5,"tbk":1,"tli":7972
+ ,{"tb":5,"tbk":1,"tl":7972
   ,"mb":5,"mbk":1
   ,"gb":0,"gbk":0
-  ,"fb":5,"fbk":1
+  ,"eb":5,"ebk":1
   ,"rb":0,"wb":0
   ,"acc":[-5,0]
   ,"fs":[1,2,3,6,7]
   }
- ,{"tb":30,"tbk":1,"tli":7910
+ ,{"tb":30,"tbk":1,"tl":7910
   ,"mb":30,"mbk":1
   ,"gb":0,"gbk":0
-  ,"fb":30,"fbk":1
+  ,"eb":30,"ebk":1
   ,"rb":0,"wb":0
   ,"acc":[-30,0]
   ,"fs":[1,2,8,9]
   }
- ,{"tb":20,"tbk":1,"tli":7857
+ ,{"tb":20,"tbk":1,"tl":7857
   ,"mb":20,"mbk":1
   ,"gb":0,"gbk":0
-  ,"fb":20,"fbk":1
+  ,"eb":20,"ebk":1
   ,"rb":0,"wb":0
   ,"acc":[-20,0]
   ,"fs":[1,10,11]
   }
- ,{"tb":10,"tbk":1,"tli":7792
+ ,{"tb":10,"tbk":1,"tl":7792
   ,"mb":10,"mbk":1
   ,"gb":0,"gbk":0
-  ,"fb":10,"fbk":1
+  ,"eb":10,"ebk":1
   ,"rb":0,"wb":0
   ,"acc":[-10,0]
   ,"fs":[1,12,13,14,15]
   }
- ,{"tb":60,"tbk":1,"tli":7709
+ ,{"tb":60,"tbk":1,"tl":7709
   ,"mb":60,"mbk":1
   ,"gb":0,"gbk":0
-  ,"fb":60,"fbk":1
+  ,"eb":60,"ebk":1
   ,"rb":0,"wb":0
   ,"acc":[-60,0]
   ,"fs":[16,17,18,19,20,21,22]
   }
- ,{"tb":30,"tbk":1,"tli":7622
+ ,{"tb":30,"tbk":1,"tl":7622
   ,"mb":30,"mbk":1
   ,"gb":0,"gbk":0
-  ,"fb":30,"fbk":1
+  ,"eb":30,"ebk":1
   ,"rb":0,"wb":0
   ,"acc":[-30,0]
   ,"fs":[16,17,18,23,24,25,26]
   }
- ,{"tb":20,"tbk":1,"tli":7528
+ ,{"tb":20,"tbk":1,"tl":7528
   ,"mb":20,"mbk":1
   ,"gb":0,"gbk":0
-  ,"fb":20,"fbk":1
+  ,"eb":20,"ebk":1
   ,"rb":0,"wb":0
   ,"acc":[-20,0]
   ,"fs":[16,17,18,23,24,27,28,29]
   }
- ,{"tb":7,"tbk":1,"tli":7446
+ ,{"tb":7,"tbk":1,"tl":7446
   ,"mb":7,"mbk":1
   ,"gb":0,"gbk":0
-  ,"fb":7,"fbk":1
+  ,"eb":7,"ebk":1
   ,"rb":0,"wb":0
   ,"acc":[-7,0]
   ,"fs":[16,17,18,30,31,32]
   }
- ,{"tb":3,"tbk":1,"tli":7375
+ ,{"tb":3,"tbk":1,"tl":7375
   ,"mb":3,"mbk":1
   ,"gb":0,"gbk":0
-  ,"fb":3,"fbk":1
+  ,"eb":3,"ebk":1
   ,"rb":0,"wb":0
   ,"acc":[-3,0]
   ,"fs":[16,17,18,33,34]
   }
- ,{"tb":30,"tbk":1,"tli":7299
+ ,{"tb":30,"tbk":1,"tl":7299
   ,"mb":30,"mbk":1
   ,"gb":0,"gbk":0
-  ,"fb":30,"fbk":1
+  ,"eb":30,"ebk":1
   ,"rb":0,"wb":0
   ,"acc":[-30,0]
   ,"fs":[35,36,37,38,39,40]
   }
- ,{"tb":20,"tbk":1,"tli":7249
+ ,{"tb":20,"tbk":1,"tl":7249
   ,"mb":20,"mbk":1
   ,"gb":0,"gbk":0
-  ,"fb":20,"fbk":1
+  ,"eb":20,"ebk":1
   ,"rb":0,"wb":0
   ,"acc":[-20,0]
   ,"fs":[41,42]
   }
- ,{"tb":19,"tbk":1,"tli":7207
+ ,{"tb":19,"tbk":1,"tl":7207
   ,"mb":19,"mbk":1
   ,"gb":0,"gbk":0
-  ,"fb":19,"fbk":1
+  ,"eb":19,"ebk":1
   ,"rb":0,"wb":0
   ,"acc":[-19,0]
   ,"fs":[43,44]
   }
- ,{"tb":9,"tbk":1,"tli":7158
+ ,{"tb":9,"tbk":1,"tl":7158
   ,"mb":9,"mbk":1
   ,"gb":0,"gbk":0
-  ,"fb":9,"fbk":1
+  ,"eb":9,"ebk":1
   ,"rb":0,"wb":0
   ,"acc":[-9,0]
   ,"fs":[45,46,47]
   }
- ,{"tb":8,"tbk":1,"tli":7107
+ ,{"tb":8,"tbk":1,"tl":7107
   ,"mb":8,"mbk":1
   ,"gb":0,"gbk":0
-  ,"fb":8,"fbk":1
+  ,"eb":8,"ebk":1
   ,"rb":0,"wb":0
   ,"acc":[-8,0]
   ,"fs":[45,48,49]
   }
- ,{"tb":7,"tbk":1,"tli":7056
+ ,{"tb":7,"tbk":1,"tl":7056
   ,"mb":7,"mbk":1
   ,"gb":0,"gbk":0
-  ,"fb":7,"fbk":1
+  ,"eb":7,"ebk":1
   ,"rb":0,"wb":0
   ,"acc":[-7,0]
   ,"fs":[45,50,51]
   }
- ,{"tb":5,"tbk":1,"tli":7005
+ ,{"tb":5,"tbk":1,"tl":7005
   ,"mb":5,"mbk":1
   ,"gb":0,"gbk":0
-  ,"fb":5,"fbk":1
+  ,"eb":5,"ebk":1
   ,"rb":0,"wb":0
   ,"acc":[-5,0]
   ,"fs":[45,52,53]
   }
- ,{"tb":1,"tbk":1,"tli":6954
+ ,{"tb":1,"tbk":1,"tl":6954
   ,"mb":1,"mbk":1
   ,"gb":0,"gbk":0
-  ,"fb":1,"fbk":1
+  ,"eb":1,"ebk":1
   ,"rb":0,"wb":0
   ,"acc":[0]
   ,"fs":[45,52,54]
   }
- ,{"tb":10,"tbk":1,"tli":6917
+ ,{"tb":10,"tbk":1,"tl":6917
   ,"mb":10,"mbk":1
   ,"gb":0,"gbk":0
-  ,"fb":10,"fbk":1
+  ,"eb":10,"ebk":1
   ,"rb":0,"wb":0
   ,"acc":[-10,0]
   ,"fs":[55]
@@ -1101,6 +1130,7 @@ let big = {
 //---------------------------------------------------------------------------
 `\
 Invocation {
+  Mode:    heap
   Command: ./big
   PID:     3902
 }
@@ -1110,7 +1140,7 @@ Times {
   t-end:  253,354 instrs
 }
 
-â–¼ AP 1/1 (7 children) {
+â–¼ PP 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
@@ -1120,7 +1150,7 @@ Times {
       #0: [root]
     }
   }
-  â”œâ”€â–¼ AP 1.1/7 (3 children) {
+  â”œâ”€â–¼ PP 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
@@ -1130,7 +1160,7 @@ Times {
   â”‚       #1: 0x1086A1: a (big.c:10)
   â”‚     }
   â”‚   }
-  â”‚   â”œâ”€â–¼ AP 1.1.1/3 (2 children) {
+  â”‚   â”œâ”€â–¼ PP 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
@@ -1141,7 +1171,7 @@ Times {
   â”‚   â”‚       #2: 0x1086BB: b1 (big.c:11)
   â”‚   â”‚     }
   â”‚   â”‚   }
-  â”‚   â”‚   â”œâ”€â–¼ AP 1.1.1.1/2 (2 children) {
+  â”‚   â”‚   â”œâ”€â–¼ PP 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
@@ -1153,7 +1183,7 @@ Times {
   â”‚   â”‚   â”‚       #3: 0x1086D5: c1 (big.c:12)
   â”‚   â”‚   â”‚     }
   â”‚   â”‚   â”‚   }
-  â”‚   â”‚   â”‚   â”œâ”€â”€ AP 1.1.1.1.1/2 {
+  â”‚   â”‚   â”‚   â”œâ”€â”€ PP 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
@@ -1193,13 +1223,13 @@ Times {
   â”‚   â”‚   â”‚   â”‚       #5: 0x108A43: main (big.c:38)
   â”‚   â”‚   â”‚   â”‚     }
   â”‚   â”‚   â”‚   â”‚   }
-  â”‚   â”‚   â”‚   â””── AP 1.1.1.1.2/2 {
+  â”‚   â”‚   â”‚   â””── PP 1.1.1.1.2/2 {
   â”‚   â”‚   â”‚         Total:     5 bytes (0.5%, 19.74/Minstr)
   â”‚   â”‚   â”‚         Allocated at {
   â”‚   â”‚   â”‚           [1 insignificant]
   â”‚   â”‚   â”‚         }
   â”‚   â”‚   â”‚       }
-  â”‚   â”‚   â””── AP 1.1.1.2/2 {
+  â”‚   â”‚   â””── PP 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
@@ -1216,7 +1246,7 @@ Times {
   â”‚   â”‚           #4: 0x108A67: main (big.c:42)
   â”‚   â”‚         }
   â”‚   â”‚       }
-  â”‚   â”œâ”€â”€ AP 1.1.2/3 {
+  â”‚   â”œâ”€â”€ PP 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
@@ -1232,7 +1262,7 @@ Times {
   â”‚   â”‚       #3: 0x108A71: main (big.c:43)
   â”‚   â”‚     }
   â”‚   â”‚   }
-  â”‚   â””── AP 1.1.3/3 {
+  â”‚   â””── PP 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
@@ -1250,7 +1280,7 @@ Times {
   â”‚           #5: 0x108A7B: main (big.c:44)
   â”‚         }
   â”‚       }
-  â”œâ”€â–¼ AP 1.2/7 (3 children) {
+  â”œâ”€â–¼ PP 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
@@ -1262,7 +1292,7 @@ Times {
   â”‚       #3: 0x1087D9: i (big.c:18)
   â”‚     }
   â”‚   }
-  â”‚   â”œâ”€â”€ AP 1.2.1/3 {
+  â”‚   â”œâ”€â”€ PP 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
@@ -1283,7 +1313,7 @@ Times {
   â”‚   â”‚       #7: 0x108A85: main (big.c:45)
   â”‚   â”‚     }
   â”‚   â”‚   }
-  â”‚   â”œâ”€â–¼ AP 1.2.2/3 (2 children) {
+  â”‚   â”œâ”€â–¼ PP 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
@@ -1297,7 +1327,7 @@ Times {
   â”‚   â”‚       #5: 0x10885B: m (big.c:20)
   â”‚   â”‚     }
   â”‚   â”‚   }
-  â”‚   â”‚   â”œâ”€â”€ AP 1.2.2.1/2 {
+  â”‚   â”‚   â”œâ”€â”€ PP 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
@@ -1317,7 +1347,7 @@ Times {
   â”‚   â”‚   â”‚       #7: 0x108A8F: main (big.c:46)
   â”‚   â”‚   â”‚     }
   â”‚   â”‚   â”‚   }
-  â”‚   â”‚   â””── AP 1.2.2.2/2 {
+  â”‚   â”‚   â””── PP 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
@@ -1338,13 +1368,13 @@ Times {
   â”‚   â”‚           #8: 0x108A99: main (big.c:47)
   â”‚   â”‚         }
   â”‚   â”‚       }
-  â”‚   â””── AP 1.2.3/3 {
+  â”‚   â””── PP 1.2.3/3 {
   â”‚         Total:     10 bytes (1%, 39.47/Minstr)
   â”‚         Allocated at {
   â”‚           [2 insignificant]
   â”‚         }
   â”‚       }
-  â”œâ”€â”€ AP 1.3/7 {
+  â”œâ”€â”€ PP 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
@@ -1363,7 +1393,7 @@ Times {
   â”‚       #6: 0x108AB7: main (big.c:50)
   â”‚     }
   â”‚   }
-  â”œâ”€â–¼ AP 1.4/7 (1 children) {
+  â”œâ”€â–¼ PP 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
@@ -1373,13 +1403,13 @@ Times {
   â”‚       #1: 0x1089C7: v (big.c:28)
   â”‚     }
   â”‚   }
-  â”‚   â””── AP 1.4.1/1 {
+  â”‚   â””── PP 1.4.1/1 {
   â”‚         Total:     30 bytes (3%, 118.41/Minstr)
   â”‚         Allocated at {
   â”‚           [4 insignificant]
   â”‚         }
   â”‚       }
-  â”œâ”€â”€ AP 1.5/7 {
+  â”œâ”€â”€ PP 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
@@ -1394,7 +1424,7 @@ Times {
   â”‚       #2: 0x108AC1: main (big.c:51)
   â”‚     }
   â”‚   }
-  â”œâ”€â”€ AP 1.6/7 {
+  â”œâ”€â”€ PP 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
@@ -1409,7 +1439,7 @@ Times {
   â”‚       #2: 0x108ACB: main (big.c:52)
   â”‚     }
   â”‚   }
-  â””── AP 1.7/7 {
+  â””── PP 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
@@ -1424,7 +1454,7 @@ Times {
         }
       }
 
-AP significance threshold: total >= 10 bytes (1%)
+PP significance threshold: total >= 10 bytes (1%)
 `
 //---------------------------------------------------------------------------
     },
@@ -1434,6 +1464,7 @@ AP significance threshold: total >= 10 bytes (1%)
 //---------------------------------------------------------------------------
 `\
 Invocation {
+  Mode:    heap
   Command: ./big
   PID:     3902
 }
@@ -1443,20 +1474,20 @@ Times {
   t-end:  253,354 instrs
 }
 
-â–¼ AP 1/1 (1 children) {
+â–¼ PP 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 {
+  â””── PP 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)
+PP significance threshold: (total >= 0.1 blocks (0.5%)) && (avg lifetime <= 500 instrs)
 `
 //---------------------------------------------------------------------------
     },
@@ -1466,6 +1497,7 @@ AP significance threshold: (total >= 0.1 blocks (0.5%)) && (total avg lifetime <
 //---------------------------------------------------------------------------
 `\
 Invocation {
+  Mode:    heap
   Command: ./big
   PID:     3902
 }
@@ -1475,7 +1507,7 @@ Times {
   t-end:  253,354 instrs
 }
 
-â–¼ AP 1/1 (2 children) {
+â–¼ PP 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
@@ -1485,7 +1517,7 @@ Times {
       #0: [root]
     }
   }
-  â”œâ”€â–¼ AP 1.1/2 (2 children) {
+  â”œâ”€â–¼ PP 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
@@ -1495,7 +1527,7 @@ Times {
   â”‚       #1: 0x1086A1: a (big.c:10)
   â”‚     }
   â”‚   }
-  â”‚   â”œâ”€â–¼ AP 1.1.1/2 (2 children) {
+  â”‚   â”œâ”€â–¼ PP 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
@@ -1506,7 +1538,7 @@ Times {
   â”‚   â”‚       #2: 0x1086BB: b1 (big.c:11)
   â”‚   â”‚     }
   â”‚   â”‚   }
-  â”‚   â”‚   â”œâ”€â–¼ AP 1.1.1.1/2 (2 children) {
+  â”‚   â”‚   â”œâ”€â–¼ PP 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
@@ -1518,7 +1550,7 @@ Times {
   â”‚   â”‚   â”‚       #3: 0x1086D5: c1 (big.c:12)
   â”‚   â”‚   â”‚     }
   â”‚   â”‚   â”‚   }
-  â”‚   â”‚   â”‚   â”œâ”€â”€ AP 1.1.1.1.1/2 {
+  â”‚   â”‚   â”‚   â”œâ”€â”€ PP 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
@@ -1558,32 +1590,32 @@ Times {
   â”‚   â”‚   â”‚   â”‚       #5: 0x108A43: main (big.c:38)
   â”‚   â”‚   â”‚   â”‚     }
   â”‚   â”‚   â”‚   â”‚   }
-  â”‚   â”‚   â”‚   â””── AP 1.1.1.1.2/2 {
+  â”‚   â”‚   â”‚   â””── PP 1.1.1.1.2/2 {
   â”‚   â”‚   â”‚         At t-gmax: 0 bytes (0%)
   â”‚   â”‚   â”‚         Allocated at {
   â”‚   â”‚   â”‚           [1 insignificant]
   â”‚   â”‚   â”‚         }
   â”‚   â”‚   â”‚       }
-  â”‚   â”‚   â””── AP 1.1.1.2/2 {
+  â”‚   â”‚   â””── PP 1.1.1.2/2 {
   â”‚   â”‚         At t-gmax: 0 bytes (0%)
   â”‚   â”‚         Allocated at {
   â”‚   â”‚           [1 insignificant]
   â”‚   â”‚         }
   â”‚   â”‚       }
-  â”‚   â””── AP 1.1.2/2 {
+  â”‚   â””── PP 1.1.2/2 {
   â”‚         At t-gmax: 0 bytes (0%)
   â”‚         Allocated at {
   â”‚           [2 insignificant]
   â”‚         }
   â”‚       }
-  â””── AP 1.2/2 {
+  â””── PP 1.2/2 {
         At t-gmax: 0 bytes (0%)
         Allocated at {
           [6 insignificant]
         }
       }
 
-AP significance threshold: at-t-gmax >= 7.06 bytes (1%)
+PP significance threshold: at-t-gmax >= 7.06 bytes (1%)
 `
 //---------------------------------------------------------------------------
     }
@@ -1599,135 +1631,140 @@ let sig = {
   name: "sig",
   input:
 //---------------------------------------------------------------------------
-{"dhatFileVersion":1
+{"dhatFileVersion":2
+,"mode":"heap","verb":"Allocated"
+,"bklt":true,"bkacc":true
+,"tu":"instrs","Mtu":"Minstr"
+,"tuth":500
 ,"cmd":"./sig"
 ,"pid":21476
-,"mi":1311861,"ei":1318783
-,"aps":
- [{"tb":11,"tbk":1,"tli":1075941
+,"te":1318783
+,"tg":1311861
+,"pps":
+ [{"tb":11,"tbk":1,"tl":1075941
   ,"mb":11,"mbk":1
   ,"gb":11,"gbk":1
-  ,"fb":11,"fbk":1
+  ,"eb":11,"ebk":1
   ,"rb":11,"wb":16489
   ,"acc":[-11,1500]
   ,"fs":[1,2]
   }
- ,{"tb":10,"tbk":1,"tli":880845
+ ,{"tb":10,"tbk":1,"tl":880845
   ,"mb":10,"mbk":1
   ,"gb":10,"gbk":1
-  ,"fb":10,"fbk":1
+  ,"eb":10,"ebk":1
   ,"rb":10,"wb":14990
   ,"acc":[-10,1500]
   ,"fs":[1,3,4]
   }
- ,{"tb":5,"tbk":1,"tli":702250
+ ,{"tb":5,"tbk":1,"tl":702250
   ,"mb":5,"mbk":1
   ,"gb":5,"gbk":1
-  ,"fb":5,"fbk":1
+  ,"eb":5,"ebk":1
   ,"rb":5,"wb":7495
   ,"acc":[-5,1500]
   ,"fs":[1,5,6]
   }
- ,{"tb":4,"tbk":1,"tli":606170
+ ,{"tb":4,"tbk":1,"tl":606170
   ,"mb":4,"mbk":1
   ,"gb":4,"gbk":1
-  ,"fb":4,"fbk":1
+  ,"eb":4,"ebk":1
   ,"rb":4,"wb":5996
   ,"acc":[-4,1500]
   ,"fs":[1,5,7]
   }
- ,{"tb":10,"tbk":1,"tli":510097
+ ,{"tb":10,"tbk":1,"tl":510097
   ,"mb":10,"mbk":1
   ,"gb":10,"gbk":1
-  ,"fb":10,"fbk":1
+  ,"eb":10,"ebk":1
   ,"rb":10,"wb":14990
   ,"acc":[-10,1500]
   ,"fs":[8,9]
   }
- ,{"tb":9,"tbk":1,"tli":331504
+ ,{"tb":9,"tbk":1,"tl":331504
   ,"mb":9,"mbk":1
   ,"gb":9,"gbk":1
-  ,"fb":9,"fbk":1
+  ,"eb":9,"ebk":1
   ,"rb":9,"wb":13491
   ,"acc":[-9,1500]
   ,"fs":[8,10,11]
   }
- ,{"tb":5,"tbk":1,"tli":169412
+ ,{"tb":5,"tbk":1,"tl":169412
   ,"mb":5,"mbk":1
   ,"gb":5,"gbk":1
-  ,"fb":5,"fbk":1
+  ,"eb":5,"ebk":1
   ,"rb":0,"wb":0
   ,"acc":[-5,0]
   ,"fs":[8,12,13]
   }
- ,{"tb":3,"tbk":1,"tli":169360
+ ,{"tb":3,"tbk":1,"tl":169360
   ,"mb":3,"mbk":1
   ,"gb":3,"gbk":1
-  ,"fb":3,"fbk":1
+  ,"eb":3,"ebk":1
   ,"rb":0,"wb":0
   ,"acc":[-3,0]
   ,"fs":[8,12,14]
   }
- ,{"tb":9,"tbk":1,"tli":169315
+ ,{"tb":9,"tbk":1,"tl":169315
   ,"mb":9,"mbk":1
   ,"gb":9,"gbk":1
-  ,"fb":9,"fbk":1
+  ,"eb":9,"ebk":1
   ,"rb":9,"wb":13491
   ,"acc":[-9,1500]
   ,"fs":[15,16]
   }
- ,{"tb":8,"tbk":1,"tli":7225
+ ,{"tb":8,"tbk":1,"tl":7225
   ,"mb":8,"mbk":1
   ,"gb":8,"gbk":1
-  ,"fb":8,"fbk":1
+  ,"eb":8,"ebk":1
   ,"rb":0,"wb":0
   ,"acc":[-8,0]
   ,"fs":[15,17,18]
   }
- ,{"tb":4,"tbk":1,"tli":7173
+ ,{"tb":4,"tbk":1,"tl":7173
   ,"mb":4,"mbk":1
   ,"gb":4,"gbk":1
-  ,"fb":4,"fbk":1
+  ,"eb":4,"ebk":1
   ,"rb":0,"wb":0
   ,"acc":[-4,0]
   ,"fs":[15,19,20]
   }
- ,{"tb":3,"tbk":1,"tli":7121
+ ,{"tb":3,"tbk":1,"tl":7121
   ,"mb":3,"mbk":1
   ,"gb":3,"gbk":1
-  ,"fb":3,"fbk":1
+  ,"eb":3,"ebk":1
   ,"rb":0,"wb":0
   ,"acc":[-3,0]
   ,"fs":[15,19,21]
   }
- ,{"tb":8,"tbk":1,"tli":7076
+ ,{"tb":8,"tbk":1,"tl":7076
   ,"mb":8,"mbk":1
   ,"gb":8,"gbk":1
-  ,"fb":8,"fbk":1
+  ,"eb":8,"ebk":1
   ,"rb":0,"wb":0
   ,"acc":[-8,0]
   ,"fs":[22,23]
   }
- ,{"tb":7,"tbk":1,"tli":7026
+ ,{"tb":7,"tbk":1,"tl":7026
   ,"mb":7,"mbk":1
   ,"gb":7,"gbk":1
-  ,"fb":7,"fbk":1
+  ,"eb":7,"ebk":1
   ,"rb":0,"wb":0
   ,"acc":[-7,0]
   ,"fs":[22,24,25]
   }
- ,{"tb":4,"tbk":1,"tli":6974
+ ,{"tb":4,"tbk":1,"tl":6974
   ,"mb":4,"mbk":1
   ,"gb":4,"gbk":1
-  ,"fb":4,"fbk":1
+  ,"eb":4,"ebk":1
   ,"rb":0,"wb":0
   ,"acc":[-4,0]
   ,"fs":[22,26,27]
   }
- ,{"tb":2,"tbk":1,"tli":6922
+ ,{"tb":2,"tbk":1,"tl":6922
   ,"mb":2,"mbk":1
   ,"gb":2,"gbk":1
-  ,"fb":2,"fbk":1
+  ,"eb":2,"ebk":1
   ,"rb":0,"wb":0
   ,"acc":[-2,0]
   ,"fs":[22,26,28]
@@ -1774,6 +1811,7 @@ let sig = {
 //---------------------------------------------------------------------------
 `\
 Invocation {
+  Mode:    heap
   Command: ./sig
   PID:     21476
 }
@@ -1783,7 +1821,7 @@ Times {
   t-end:  1,318,783 instrs
 }
 
-â–¼ AP 1/1 (4 children) {
+â–¼ PP 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)
@@ -1791,7 +1829,7 @@ Times {
       #0: [root]
     }
   }
-  â”œâ”€â”€ AP 1.1/4 {
+  â”œâ”€â”€ PP 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)
@@ -1799,7 +1837,7 @@ Times {
   â”‚       [1 insignificant]
   â”‚     }
   â”‚   }
-  â”œâ”€â–¼ AP 1.2/4 (2 children) {
+  â”œâ”€â–¼ PP 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)
@@ -1807,7 +1845,7 @@ Times {
   â”‚       #1: 0x1086CF: bm (sig.c:15)
   â”‚     }
   â”‚   }
-  â”‚   â”œâ”€â”€ AP 1.2.1/2 {
+  â”‚   â”œâ”€â”€ PP 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)
@@ -1815,7 +1853,7 @@ Times {
   â”‚   â”‚       [2 insignificant]
   â”‚   â”‚     }
   â”‚   â”‚   }
-  â”‚   â””─▼ AP 1.2.2/2 (2 children) {
+  â”‚   â””─▼ PP 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
@@ -1826,7 +1864,7 @@ Times {
   â”‚           #2: 0x108703: b3 (sig.c:18)
   â”‚         }
   â”‚       }
-  â”‚       â”œâ”€â”€ AP 1.2.2.1/2 {
+  â”‚       â”œâ”€â”€ PP 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
@@ -1842,7 +1880,7 @@ Times {
   â”‚       â”‚       #3: 0x1088F6: main (sig.c:64)
   â”‚       â”‚     }
   â”‚       â”‚   }
-  â”‚       â””── AP 1.2.2.2/2 {
+  â”‚       â””── PP 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
@@ -1858,7 +1896,7 @@ Times {
   â”‚               #3: 0x108904: main (sig.c:65)
   â”‚             }
   â”‚           }
-  â”œâ”€â–¼ AP 1.3/4 (3 children) {
+  â”œâ”€â–¼ PP 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)
@@ -1866,7 +1904,7 @@ Times {
   â”‚       #1: 0x10871D: cm (sig.c:21)
   â”‚     }
   â”‚   }
-  â”‚   â”œâ”€â”€ AP 1.3.1/3 {
+  â”‚   â”œâ”€â”€ PP 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)
@@ -1874,7 +1912,7 @@ Times {
   â”‚   â”‚       [1 insignificant]
   â”‚   â”‚     }
   â”‚   â”‚   }
-  â”‚   â”œâ”€â”€ AP 1.3.2/3 {
+  â”‚   â”œâ”€â”€ PP 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
@@ -1890,7 +1928,7 @@ Times {
   â”‚   â”‚       #3: 0x108931: main (sig.c:68)
   â”‚   â”‚     }
   â”‚   â”‚   }
-  â”‚   â””─▼ AP 1.3.3/3 (2 children) {
+  â”‚   â””─▼ PP 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
@@ -1901,7 +1939,7 @@ Times {
   â”‚           #2: 0x108751: c3 (sig.c:24)
   â”‚         }
   â”‚       }
-  â”‚       â”œâ”€â”€ AP 1.3.3.1/2 {
+  â”‚       â”œâ”€â”€ PP 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
@@ -1917,7 +1955,7 @@ Times {
   â”‚       â”‚       #3: 0x10893F: main (sig.c:69)
   â”‚       â”‚     }
   â”‚       â”‚   }
-  â”‚       â””── AP 1.3.3.2/2 {
+  â”‚       â””── PP 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
@@ -1933,7 +1971,7 @@ Times {
   â”‚               #3: 0x10894D: main (sig.c:70)
   â”‚             }
   â”‚           }
-  â””─▼ AP 1.4/4 (3 children) {
+  â””─▼ PP 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
@@ -1943,7 +1981,7 @@ Times {
           #1: 0x10876B: dm (sig.c:27)
         }
       }
-      â”œâ”€â”€ AP 1.4.1/3 {
+      â”œâ”€â”€ PP 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
@@ -1958,7 +1996,7 @@ Times {
       â”‚       #2: 0x10895B: main (sig.c:72)
       â”‚     }
       â”‚   }
-      â”œâ”€â”€ AP 1.4.2/3 {
+      â”œâ”€â”€ PP 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
@@ -1974,7 +2012,7 @@ Times {
       â”‚       #3: 0x108969: main (sig.c:73)
       â”‚     }
       â”‚   }
-      â””─▼ AP 1.4.3/3 (2 children) {
+      â””─▼ PP 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
@@ -1985,7 +2023,7 @@ Times {
               #2: 0x10879F: d3 (sig.c:30)
             }
           }
-          â”œâ”€â”€ AP 1.4.3.1/2 {
+          â”œâ”€â”€ PP 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
@@ -2001,7 +2039,7 @@ Times {
           â”‚       #3: 0x108977: main (sig.c:74)
           â”‚     }
           â”‚   }
-          â””── AP 1.4.3.2/2 {
+          â””── PP 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
@@ -2018,7 +2056,7 @@ Times {
                 }
               }
 
-AP significance threshold: (total >= 0.51 bytes (0.5%)) && ((reads == 0 bytes) || (writes == 0 bytes))
+PP significance threshold: (total >= 0.51 bytes (0.5%)) && ((reads == 0 bytes) || (writes == 0 bytes))
 `
 //---------------------------------------------------------------------------
     },
@@ -2028,6 +2066,7 @@ AP significance threshold: (total >= 0.51 bytes (0.5%)) && ((reads == 0 bytes) |
 //---------------------------------------------------------------------------
 `\
 Invocation {
+  Mode:    heap
   Command: ./sig
   PID:     21476
 }
@@ -2037,7 +2076,7 @@ Times {
   t-end:  1,318,783 instrs
 }
 
-â–¼ AP 1/1 (2 children) {
+â–¼ PP 1/1 (2 children) {
     Total:     16 blocks (100%, 12.13/Minstr)
     Reads:     0.57/byte
     Writes:    852.37/byte
@@ -2045,7 +2084,7 @@ Times {
       #0: [root]
     }
   }
-  â”œâ”€â”€ AP 1.1/2 {
+  â”œâ”€â”€ PP 1.1/2 {
   â”‚     Total:     12 blocks (75%, 9.1/Minstr)
   â”‚     Reads:     0.63/byte
   â”‚     Writes:    941.68/byte
@@ -2053,7 +2092,7 @@ Times {
   â”‚       [3 insignificant]
   â”‚     }
   â”‚   }
-  â””─▼ AP 1.2/2 (1 children) {
+  â””─▼ PP 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
@@ -2063,7 +2102,7 @@ Times {
           #1: 0x10871D: cm (sig.c:21)
         }
       }
-      â””── AP 1.2.1/1 {
+      â””── PP 1.2.1/1 {
             Total:     4 blocks (25%, 3.03/Minstr)
             Reads:     0.38/byte
             Writes:    562.13/byte
@@ -2072,7 +2111,7 @@ Times {
             }
           }
 
-AP significance threshold: (total >= 0.08 blocks (0.5%)) && (reads != 0 bytes) && (writes != 0 bytes) && ((reads <= 0.4/byte) || (writes <= 0.4/byte))
+PP significance threshold: (total >= 0.08 blocks (0.5%)) && (reads != 0 bytes) && (writes != 0 bytes) && ((reads <= 0.4/byte) || (writes <= 0.4/byte))
 `
 //---------------------------------------------------------------------------
     },
@@ -2082,6 +2121,7 @@ AP significance threshold: (total >= 0.08 blocks (0.5%)) && (reads != 0 bytes) &
 //---------------------------------------------------------------------------
 `\
 Invocation {
+  Mode:    heap
   Command: ./sig
   PID:     21476
 }
@@ -2091,13 +2131,13 @@ Times {
   t-end:  1,318,783 instrs
 }
 
-â–¼ AP 1/1 (4 children) {
+â–¼ PP 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) {
+  â”œâ”€â–¼ PP 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
@@ -2107,7 +2147,7 @@ Times {
   â”‚       #1: 0x108681: am (sig.c:9)
   â”‚     }
   â”‚   }
-  â”‚   â”œâ”€â”€ AP 1.1.1/3 {
+  â”‚   â”œâ”€â”€ PP 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
@@ -2122,7 +2162,7 @@ Times {
   â”‚   â”‚       #2: 0x10883C: main (sig.c:57)
   â”‚   â”‚     }
   â”‚   â”‚   }
-  â”‚   â”œâ”€â”€ AP 1.1.2/3 {
+  â”‚   â”œâ”€â”€ PP 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
@@ -2138,7 +2178,7 @@ Times {
   â”‚   â”‚       #3: 0x10885B: main (sig.c:58)
   â”‚   â”‚     }
   â”‚   â”‚   }
-  â”‚   â””─▼ AP 1.1.3/3 (2 children) {
+  â”‚   â””─▼ PP 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
@@ -2149,7 +2189,7 @@ Times {
   â”‚           #2: 0x1086B5: a3 (sig.c:12)
   â”‚         }
   â”‚       }
-  â”‚       â”œâ”€â”€ AP 1.1.3.1/2 {
+  â”‚       â”œâ”€â”€ PP 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
@@ -2165,7 +2205,7 @@ Times {
   â”‚       â”‚       #3: 0x10887A: main (sig.c:59)
   â”‚       â”‚     }
   â”‚       â”‚   }
-  â”‚       â””── AP 1.1.3.2/2 {
+  â”‚       â””── PP 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
@@ -2181,7 +2221,7 @@ Times {
   â”‚               #3: 0x108899: main (sig.c:60)
   â”‚             }
   â”‚           }
-  â”œâ”€â–¼ AP 1.2/4 (3 children) {
+  â”œâ”€â–¼ PP 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
@@ -2191,7 +2231,7 @@ Times {
   â”‚       #1: 0x1086CF: bm (sig.c:15)
   â”‚     }
   â”‚   }
-  â”‚   â”œâ”€â”€ AP 1.2.1/3 {
+  â”‚   â”œâ”€â”€ PP 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
@@ -2206,7 +2246,7 @@ Times {
   â”‚   â”‚       #2: 0x1088B8: main (sig.c:62)
   â”‚   â”‚     }
   â”‚   â”‚   }
-  â”‚   â”œâ”€â”€ AP 1.2.2/3 {
+  â”‚   â”œâ”€â”€ PP 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
@@ -2222,19 +2262,19 @@ Times {
   â”‚   â”‚       #3: 0x1088D7: main (sig.c:63)
   â”‚   â”‚     }
   â”‚   â”‚   }
-  â”‚   â””── AP 1.2.3/3 {
+  â”‚   â””── PP 1.2.3/3 {
   â”‚         Writes:    0 bytes (0%, 0/Minstr), 0/byte
   â”‚         Allocated at {
   â”‚           [1 insignificant]
   â”‚         }
   â”‚       }
-  â”œâ”€â–¼ AP 1.3/4 (2 children) {
+  â”œâ”€â–¼ PP 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 {
+  â”‚   â”œâ”€â”€ PP 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
@@ -2249,20 +2289,20 @@ Times {
   â”‚   â”‚       #2: 0x108912: main (sig.c:67)
   â”‚   â”‚     }
   â”‚   â”‚   }
-  â”‚   â””── AP 1.3.2/2 {
+  â”‚   â””── PP 1.3.2/2 {
   â”‚         Writes:    0 bytes (0%, 0/Minstr), 0/byte
   â”‚         Allocated at {
   â”‚           [2 insignificant]
   â”‚         }
   â”‚       }
-  â””── AP 1.4/4 {
+  â””── PP 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))
+PP significance threshold: (writes >= 434.71 bytes (0.5%)) && ((reads >= 1,000/byte) || (writes >= 1,000/byte))
 `
 //---------------------------------------------------------------------------
     }
@@ -2278,79 +2318,84 @@ let sig2 = {
   name: "sig2",
   input:
 //---------------------------------------------------------------------------
-{"dhatFileVersion":1
+{"dhatFileVersion":2
+,"mode":"heap","verb":"Allocated"
+,"bklt":true,"bkacc":true
+,"tu":"instrs","Mtu":"Minstr"
+,"tuth":500
 ,"cmd":"subseqs"
 ,"pid":0
-,"mi":10000,"ei":20000
-,"aps":
- [{"tb":100,"tbk":1,"tli":1000
+,"te":20000
+,"tg":10000
+,"pps":
+ [{"tb":100,"tbk":1,"tl":1000
   ,"mb":100,"mbk":1
   ,"gb":100,"gbk":1
-  ,"fb":0,"fbk":0
+  ,"eb":0,"ebk":0
   ,"rb":0,"wb":0
   ,"acc":[-10,0]
   ,"fs":[1]
   }
- ,{"tb":101,"tbk":1,"tli":1000
+ ,{"tb":101,"tbk":1,"tl":1000
   ,"mb":101,"mbk":1
   ,"gb":101,"gbk":1
-  ,"fb":0,"fbk":0
+  ,"eb":0,"ebk":0
   ,"rb":0,"wb":0
   ,"acc":[-10,0]
   ,"fs":[2]
   }
- ,{"tb":102,"tbk":1,"tli":1000
+ ,{"tb":102,"tbk":1,"tl":1000
   ,"mb":102,"mbk":1
   ,"gb":102,"gbk":1
-  ,"fb":0,"fbk":0
+  ,"eb":0,"ebk":0
   ,"rb":0,"wb":0
   ,"acc":[-10,0]
   ,"fs":[3,4]
   }
- ,{"tb":103,"tbk":1,"tli":1000
+ ,{"tb":103,"tbk":1,"tl":1000
   ,"mb":103,"mbk":1
   ,"gb":103,"gbk":1
-  ,"fb":0,"fbk":0
+  ,"eb":0,"ebk":0
   ,"rb":0,"wb":0
   ,"acc":[-10,0]
   ,"fs":[3,5]
   }
- ,{"tb":104,"tbk":1,"tli":1000
+ ,{"tb":104,"tbk":1,"tl":1000
   ,"mb":104,"mbk":1
   ,"gb":104,"gbk":1
-  ,"fb":0,"fbk":0
+  ,"eb":0,"ebk":0
   ,"rb":0,"wb":0
   ,"acc":[-10,0]
   ,"fs":[3,6,7]
   }
- ,{"tb":105,"tbk":1,"tli":1000
+ ,{"tb":105,"tbk":1,"tl":1000
   ,"mb":105,"mbk":1
   ,"gb":105,"gbk":1
-  ,"fb":0,"fbk":0
+  ,"eb":0,"ebk":0
   ,"rb":0,"wb":0
   ,"acc":[-10,0]
   ,"fs":[3,6,8]
   }
- ,{"tb":10,"tbk":1,"tli":1000
+ ,{"tb":10,"tbk":1,"tl":1000
   ,"mb":10,"mbk":1
   ,"gb":10,"gbk":1
-  ,"fb":0,"fbk":0
+  ,"eb":0,"ebk":0
   ,"rb":0,"wb":0
   ,"acc":[-10,0]
   ,"fs":[3,6,9,10]
   }
- ,{"tb":106,"tbk":1,"tli":1000
+ ,{"tb":106,"tbk":1,"tl":1000
   ,"mb":106,"mbk":1
   ,"gb":106,"gbk":1
-  ,"fb":0,"fbk":0
+  ,"eb":0,"ebk":0
   ,"rb":0,"wb":0
   ,"acc":[-10,0]
   ,"fs":[3,6,9,11]
   }
- ,{"tb":107,"tbk":1,"tli":1000
+ ,{"tb":107,"tbk":1,"tl":1000
   ,"mb":107,"mbk":1
   ,"gb":107,"gbk":1
-  ,"fb":0,"fbk":0
+  ,"eb":0,"ebk":0
   ,"rb":0,"wb":0
   ,"acc":[-10,0]
   ,"fs":[3,6,9,12]
@@ -2381,6 +2426,7 @@ let sig2 = {
 //---------------------------------------------------------------------------
 `\
 Invocation {
+  Mode:    heap
   Command: subseqs
   PID:     0
 }
@@ -2390,37 +2436,37 @@ Times {
   t-end:  20,000 instrs
 }
 
-â–¼ AP 1/1 (2 children) {
+â–¼ PP 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) {
+  â”œâ”€â–¼ PP 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) {
+  â”‚   â”œâ”€â–¼ PP 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) {
+  â”‚   â”‚   â”œâ”€â–¼ PP 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 {
+  â”‚   â”‚   â”‚   â”œâ”€â”€ PP 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 {
+  â”‚   â”‚   â”‚   â””── PP 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
@@ -2437,26 +2483,26 @@ Times {
   â”‚   â”‚   â”‚           #4: d1()
   â”‚   â”‚   â”‚         }
   â”‚   â”‚   â”‚       }
-  â”‚   â”‚   â””── AP 1.1.1.2/2 {
+  â”‚   â”‚   â””── PP 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 {
+  â”‚   â””── PP 1.1.2/2 {
   â”‚         Total:     2 blocks (22.22%, 100/Minstr), avg size 102.5 bytes
   â”‚         Allocated at {
   â”‚           [2 insignificant]
   â”‚         }
   â”‚       }
-  â””── AP 1.2/2 {
+  â””── PP 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)
+PP significance threshold: (total >= 0.05 blocks (0.5%)) && (avg size <= 16 bytes)
 `
 //---------------------------------------------------------------------------
     }
@@ -2464,6 +2510,494 @@ AP significance threshold: (total >= 0.05 blocks (0.5%)) && (total avg size <= 1
 };
 tests.push(sig2);
 
+//---------------------------------------------------------------------------
+// copy (corresponds to dhat/tests/copy.c, with `pps` elements for copies
+// from system libs removed, but with no removal to `fbtl` elements)
+//---------------------------------------------------------------------------
+
+let copy = {
+  name: "copy",
+  input:
+//---------------------------------------------------------------------------
+{"dhatFileVersion":2
+,"mode":"copy","verb":"Copied"
+,"bklt":false,"bkacc":false
+,"tu":"instrs","Mtu":"Minstr"
+,"cmd":"./copy"
+,"pid":8568
+,"te":4033604
+,"pps":
+ [{"tb":100000,"tbk":100
+  ,"fs":[19,20,21]
+  }
+ ,{"tb":100000,"tbk":100
+  ,"fs":[19,22,21]
+  }
+ ,{"tb":100000,"tbk":100
+  ,"fs":[19,23,21]
+  }
+ ,{"tb":100000,"tbk":100
+  ,"fs":[24,25,21]
+  }
+ ,{"tb":100000,"tbk":100
+  ,"fs":[19,26,21]
+  }
+ ,{"tb":100000,"tbk":100
+  ,"fs":[27,28,21]
+  }
+ ,{"tb":100000,"tbk":100
+  ,"fs":[29,30,21]
+  }
+ ,{"tb":100000,"tbk":100
+  ,"fs":[27,31,21]
+  }
+ ,{"tb":100000,"tbk":100
+  ,"fs":[32,33,21]
+  }
+ ,{"tb":100000,"tbk":100
+  ,"fs":[34,35,21]
+  }
+ ]
+,"ftbl":
+ ["[root]"
+ ,"0x4843690: mempcpy (vg_replace_strmem.c:1566)"
+ ,"0x4008B4F: open_path (dl-load.c:1810)"
+ ,"0x400A7A2: _dl_map_object (dl-load.c:2067)"
+ ,"0x400F504: openaux (dl-deps.c:64)"
+ ,"0x401DC19: _dl_catch_exception (dl-error-skeleton.c:208)"
+ ,"0x400F952: _dl_map_object_deps (dl-deps.c:248)"
+ ,"0x4004063: dl_main (rtld.c:1805)"
+ ,"0x401CBAA: _dl_sysdep_start (dl-sysdep.c:252)"
+ ,"0x400204B: _dl_start_final (rtld.c:449)"
+ ,"0x400204B: _dl_start (rtld.c:539)"
+ ,"0x4001107: ??? (in /lib/x86_64-linux-gnu/ld-2.31.so)"
+ ,"0x4008BCC: open_path (dl-load.c:1818)"
+ ,"0x4008BE2: open_path (dl-load.c:1818)"
+ ,"0x400D4B4: _dl_new_object (dl-object.c:242)"
+ ,"0x4006E96: _dl_map_object_from_fd (dl-load.c:997)"
+ ,"0x400A61A: _dl_map_object (dl-load.c:2236)"
+ ,"0x401486E: _dl_allocate_tls_init (dl-tls.c:507)"
+ ,"0x400469D: dl_main (rtld.c:2292)"
+ ,"0x4842524: memmove (vg_replace_strmem.c:1289)"
+ ,"0x10930E: f (copy.c:40)"
+ ,"0x1092C9: main (copy.c:31)"
+ ,"0x109326: f (copy.c:41)"
+ ,"0x10933E: f (copy.c:42)"
+ ,"0x4843390: mempcpy (vg_replace_strmem.c:1562)"
+ ,"0x109356: f (copy.c:43)"
+ ,"0x10936E: f (copy.c:44)"
+ ,"0x483EBB0: strcpy (vg_replace_strmem.c:523)"
+ ,"0x109381: f (copy.c:45)"
+ ,"0x483ECD7: strncpy (vg_replace_strmem.c:564)"
+ ,"0x109399: f (copy.c:46)"
+ ,"0x1093AC: f (copy.c:47)"
+ ,"0x4842368: stpncpy (vg_replace_strmem.c:1219)"
+ ,"0x1093C4: f (copy.c:48)"
+ ,"0x4843DD2: wcscpy (vg_replace_strmem.c:2018)"
+ ,"0x1093D7: f (copy.c:49)"
+ ]
+}
+//---------------------------------------------------------------------------
+  ,
+  outputs: [
+    {
+      label: "Total (blocks)",
+      expected:
+//---------------------------------------------------------------------------
+`\
+Invocation {
+  Mode:    copy
+  Command: ./copy
+  PID:     8568
+}
+
+Times {
+  t-end:  4,033,604 instrs
+}
+
+â–¼ PP 1/1 (6 children) {
+    Total:     1,000,000 bytes (100%, 247,917.25/Minstr) in 1,000 blocks (100%, 247.92/Minstr), avg size 1,000 bytes
+    Copied at {
+      #0: [root]
+    }
+  }
+  â”œâ”€â–¼ PP 1.1/6 (4 children) {
+  â”‚     Total:     400,000 bytes (40%, 99,166.9/Minstr) in 400 blocks (40%, 99.17/Minstr), avg size 1,000 bytes
+  â”‚     Copied at {
+  â”‚       #1: 0x4842524: memmove (vg_replace_strmem.c:1289)
+  â”‚     }
+  â”‚   }
+  â”‚   â”œâ”€â”€ PP 1.1.1/4 {
+  â”‚   â”‚     Total:     100,000 bytes (10%, 24,791.72/Minstr) in 100 blocks (10%, 24.79/Minstr), avg size 1,000 bytes
+  â”‚   â”‚     Copied at {
+  â”‚   â”‚       ^1: 0x4842524: memmove (vg_replace_strmem.c:1289)
+  â”‚   â”‚       #2: 0x10930E: f (copy.c:40)
+  â”‚   â”‚       #3: 0x1092C9: main (copy.c:31)
+  â”‚   â”‚     }
+  â”‚   â”‚   }
+  â”‚   â”œâ”€â”€ PP 1.1.2/4 {
+  â”‚   â”‚     Total:     100,000 bytes (10%, 24,791.72/Minstr) in 100 blocks (10%, 24.79/Minstr), avg size 1,000 bytes
+  â”‚   â”‚     Copied at {
+  â”‚   â”‚       ^1: 0x4842524: memmove (vg_replace_strmem.c:1289)
+  â”‚   â”‚       #2: 0x109326: f (copy.c:41)
+  â”‚   â”‚       #3: 0x1092C9: main (copy.c:31)
+  â”‚   â”‚     }
+  â”‚   â”‚   }
+  â”‚   â”œâ”€â”€ PP 1.1.3/4 {
+  â”‚   â”‚     Total:     100,000 bytes (10%, 24,791.72/Minstr) in 100 blocks (10%, 24.79/Minstr), avg size 1,000 bytes
+  â”‚   â”‚     Copied at {
+  â”‚   â”‚       ^1: 0x4842524: memmove (vg_replace_strmem.c:1289)
+  â”‚   â”‚       #2: 0x10933E: f (copy.c:42)
+  â”‚   â”‚       #3: 0x1092C9: main (copy.c:31)
+  â”‚   â”‚     }
+  â”‚   â”‚   }
+  â”‚   â””── PP 1.1.4/4 {
+  â”‚         Total:     100,000 bytes (10%, 24,791.72/Minstr) in 100 blocks (10%, 24.79/Minstr), avg size 1,000 bytes
+  â”‚         Copied at {
+  â”‚           ^1: 0x4842524: memmove (vg_replace_strmem.c:1289)
+  â”‚           #2: 0x10936E: f (copy.c:44)
+  â”‚           #3: 0x1092C9: main (copy.c:31)
+  â”‚         }
+  â”‚       }
+  â”œâ”€â–¼ PP 1.2/6 (2 children) {
+  â”‚     Total:     200,000 bytes (20%, 49,583.45/Minstr) in 200 blocks (20%, 49.58/Minstr), avg size 1,000 bytes
+  â”‚     Copied at {
+  â”‚       #1: 0x483EBB0: strcpy (vg_replace_strmem.c:523)
+  â”‚     }
+  â”‚   }
+  â”‚   â”œâ”€â”€ PP 1.2.1/2 {
+  â”‚   â”‚     Total:     100,000 bytes (10%, 24,791.72/Minstr) in 100 blocks (10%, 24.79/Minstr), avg size 1,000 bytes
+  â”‚   â”‚     Copied at {
+  â”‚   â”‚       ^1: 0x483EBB0: strcpy (vg_replace_strmem.c:523)
+  â”‚   â”‚       #2: 0x109381: f (copy.c:45)
+  â”‚   â”‚       #3: 0x1092C9: main (copy.c:31)
+  â”‚   â”‚     }
+  â”‚   â”‚   }
+  â”‚   â””── PP 1.2.2/2 {
+  â”‚         Total:     100,000 bytes (10%, 24,791.72/Minstr) in 100 blocks (10%, 24.79/Minstr), avg size 1,000 bytes
+  â”‚         Copied at {
+  â”‚           ^1: 0x483EBB0: strcpy (vg_replace_strmem.c:523)
+  â”‚           #2: 0x1093AC: f (copy.c:47)
+  â”‚           #3: 0x1092C9: main (copy.c:31)
+  â”‚         }
+  â”‚       }
+  â”œâ”€â”€ PP 1.3/6 {
+  â”‚     Total:     100,000 bytes (10%, 24,791.72/Minstr) in 100 blocks (10%, 24.79/Minstr), avg size 1,000 bytes
+  â”‚     Copied at {
+  â”‚       #1: 0x4843390: mempcpy (vg_replace_strmem.c:1562)
+  â”‚       #2: 0x109356: f (copy.c:43)
+  â”‚       #3: 0x1092C9: main (copy.c:31)
+  â”‚     }
+  â”‚   }
+  â”œâ”€â”€ PP 1.4/6 {
+  â”‚     Total:     100,000 bytes (10%, 24,791.72/Minstr) in 100 blocks (10%, 24.79/Minstr), avg size 1,000 bytes
+  â”‚     Copied at {
+  â”‚       #1: 0x483ECD7: strncpy (vg_replace_strmem.c:564)
+  â”‚       #2: 0x109399: f (copy.c:46)
+  â”‚       #3: 0x1092C9: main (copy.c:31)
+  â”‚     }
+  â”‚   }
+  â”œâ”€â”€ PP 1.5/6 {
+  â”‚     Total:     100,000 bytes (10%, 24,791.72/Minstr) in 100 blocks (10%, 24.79/Minstr), avg size 1,000 bytes
+  â”‚     Copied at {
+  â”‚       #1: 0x4842368: stpncpy (vg_replace_strmem.c:1219)
+  â”‚       #2: 0x1093C4: f (copy.c:48)
+  â”‚       #3: 0x1092C9: main (copy.c:31)
+  â”‚     }
+  â”‚   }
+  â””── PP 1.6/6 {
+        Total:     100,000 bytes (10%, 24,791.72/Minstr) in 100 blocks (10%, 24.79/Minstr), avg size 1,000 bytes
+        Copied at {
+          #1: 0x4843DD2: wcscpy (vg_replace_strmem.c:2018)
+          #2: 0x1093D7: f (copy.c:49)
+          #3: 0x1092C9: main (copy.c:31)
+        }
+      }
+
+PP significance threshold: total >= 10 blocks (1%)
+`
+//---------------------------------------------------------------------------
+    }
+  ]
+};
+tests.push(copy);
+
+//---------------------------------------------------------------------------
+// ad-hoc (corresponds to dhat/tests/ad-hoc.c)
+//---------------------------------------------------------------------------
+
+let ad_hoc = {
+  name: "ad_hoc",
+  input:
+//---------------------------------------------------------------------------
+{"dhatFileVersion":2
+,"mode":"ad-hoc","verb":"Occurred"
+,"bklt":false,"bkacc":false
+,"bu":"unit","bsu":"units","bksu":"events"
+,"tu":"instrs","Mtu":"Minstr"
+,"cmd":"./ad-hoc"
+,"pid":26995
+,"te":150455
+,"pps":
+ [{"tb":30,"tbk":1
+  ,"fs":[1,2,3]
+  }
+ ,{"tb":20,"tbk":1
+  ,"fs":[4,3]
+  }
+ ,{"tb":30,"tbk":1
+  ,"fs":[1,5,3]
+  }
+ ,{"tb":10,"tbk":1
+  ,"fs":[6]
+  }
+ ,{"tb":30,"tbk":1
+  ,"fs":[1,2,7]
+  }
+ ,{"tb":20,"tbk":1
+  ,"fs":[4,7]
+  }
+ ,{"tb":30,"tbk":1
+  ,"fs":[1,5,7]
+  }
+ ]
+,"ftbl":
+ ["[root]"
+ ,"0x1093A7: g (ad-hoc.c:4)"
+ ,"0x1093C5: f (ad-hoc.c:8)"
+ ,"0x109437: main (ad-hoc.c:14)"
+ ,"0x109414: f (ad-hoc.c:9)"
+ ,"0x109423: f (ad-hoc.c:10)"
+ ,"0x109486: main (ad-hoc.c:15)"
+ ,"0x109495: main (ad-hoc.c:16)"
+ ]
+}
+//---------------------------------------------------------------------------
+  ,
+  outputs: [
+    {
+      label: "Total (events)",
+      expected:
+//---------------------------------------------------------------------------
+`\
+Invocation {
+  Mode:    ad-hoc
+  Command: ./ad-hoc
+  PID:     26995
+}
+
+Times {
+  t-end:  150,455 instrs
+}
+
+â–¼ PP 1/1 (3 children) {
+    Total:     170 units (100%, 1,129.91/Minstr) in 7 events (100%, 46.53/Minstr), avg size 24.29 units
+    Occurred at {
+      #0: [root]
+    }
+  }
+  â”œâ”€â–¼ PP 1.1/3 (2 children) {
+  â”‚     Total:     120 units (70.59%, 797.58/Minstr) in 4 events (57.14%, 26.59/Minstr), avg size 30 units
+  â”‚     Occurred at {
+  â”‚       #1: 0x1093A7: g (ad-hoc.c:4)
+  â”‚     }
+  â”‚   }
+  â”‚   â”œâ”€â–¼ PP 1.1.1/2 (2 children) {
+  â”‚   â”‚     Total:     60 units (35.29%, 398.79/Minstr) in 2 events (28.57%, 13.29/Minstr), avg size 30 units
+  â”‚   â”‚     Occurred at {
+  â”‚   â”‚       ^1: 0x1093A7: g (ad-hoc.c:4)
+  â”‚   â”‚       #2: 0x1093C5: f (ad-hoc.c:8)
+  â”‚   â”‚     }
+  â”‚   â”‚   }
+  â”‚   â”‚   â”œâ”€â”€ PP 1.1.1.1/2 {
+  â”‚   â”‚   â”‚     Total:     30 units (17.65%, 199.4/Minstr) in 1 events (14.29%, 6.65/Minstr), avg size 30 units
+  â”‚   â”‚   â”‚     Occurred at {
+  â”‚   â”‚   â”‚       ^1: 0x1093A7: g (ad-hoc.c:4)
+  â”‚   â”‚   â”‚       ^2: 0x1093C5: f (ad-hoc.c:8)
+  â”‚   â”‚   â”‚       #3: 0x109437: main (ad-hoc.c:14)
+  â”‚   â”‚   â”‚     }
+  â”‚   â”‚   â”‚   }
+  â”‚   â”‚   â””── PP 1.1.1.2/2 {
+  â”‚   â”‚         Total:     30 units (17.65%, 199.4/Minstr) in 1 events (14.29%, 6.65/Minstr), avg size 30 units
+  â”‚   â”‚         Occurred at {
+  â”‚   â”‚           ^1: 0x1093A7: g (ad-hoc.c:4)
+  â”‚   â”‚           ^2: 0x1093C5: f (ad-hoc.c:8)
+  â”‚   â”‚           #3: 0x109495: main (ad-hoc.c:16)
+  â”‚   â”‚         }
+  â”‚   â”‚       }
+  â”‚   â””─▼ PP 1.1.2/2 (2 children) {
+  â”‚         Total:     60 units (35.29%, 398.79/Minstr) in 2 events (28.57%, 13.29/Minstr), avg size 30 units
+  â”‚         Occurred at {
+  â”‚           ^1: 0x1093A7: g (ad-hoc.c:4)
+  â”‚           #2: 0x109423: f (ad-hoc.c:10)
+  â”‚         }
+  â”‚       }
+  â”‚       â”œâ”€â”€ PP 1.1.2.1/2 {
+  â”‚       â”‚     Total:     30 units (17.65%, 199.4/Minstr) in 1 events (14.29%, 6.65/Minstr), avg size 30 units
+  â”‚       â”‚     Occurred at {
+  â”‚       â”‚       ^1: 0x1093A7: g (ad-hoc.c:4)
+  â”‚       â”‚       ^2: 0x109423: f (ad-hoc.c:10)
+  â”‚       â”‚       #3: 0x109437: main (ad-hoc.c:14)
+  â”‚       â”‚     }
+  â”‚       â”‚   }
+  â”‚       â””── PP 1.1.2.2/2 {
+  â”‚             Total:     30 units (17.65%, 199.4/Minstr) in 1 events (14.29%, 6.65/Minstr), avg size 30 units
+  â”‚             Occurred at {
+  â”‚               ^1: 0x1093A7: g (ad-hoc.c:4)
+  â”‚               ^2: 0x109423: f (ad-hoc.c:10)
+  â”‚               #3: 0x109495: main (ad-hoc.c:16)
+  â”‚             }
+  â”‚           }
+  â”œâ”€â–¼ PP 1.2/3 (2 children) {
+  â”‚     Total:     40 units (23.53%, 265.86/Minstr) in 2 events (28.57%, 13.29/Minstr), avg size 20 units
+  â”‚     Occurred at {
+  â”‚       #1: 0x109414: f (ad-hoc.c:9)
+  â”‚     }
+  â”‚   }
+  â”‚   â”œâ”€â”€ PP 1.2.1/2 {
+  â”‚   â”‚     Total:     20 units (11.76%, 132.93/Minstr) in 1 events (14.29%, 6.65/Minstr), avg size 20 units
+  â”‚   â”‚     Occurred at {
+  â”‚   â”‚       ^1: 0x109414: f (ad-hoc.c:9)
+  â”‚   â”‚       #2: 0x109437: main (ad-hoc.c:14)
+  â”‚   â”‚     }
+  â”‚   â”‚   }
+  â”‚   â””── PP 1.2.2/2 {
+  â”‚         Total:     20 units (11.76%, 132.93/Minstr) in 1 events (14.29%, 6.65/Minstr), avg size 20 units
+  â”‚         Occurred at {
+  â”‚           ^1: 0x109414: f (ad-hoc.c:9)
+  â”‚           #2: 0x109495: main (ad-hoc.c:16)
+  â”‚         }
+  â”‚       }
+  â””── PP 1.3/3 {
+        Total:     10 units (5.88%, 66.47/Minstr) in 1 events (14.29%, 6.65/Minstr), avg size 10 units
+        Occurred at {
+          #1: 0x109486: main (ad-hoc.c:15)
+        }
+      }
+
+PP significance threshold: total >= 0.07 events (1%)
+`
+//---------------------------------------------------------------------------
+    }
+  ]
+};
+tests.push(ad_hoc);
+
+//---------------------------------------------------------------------------
+// rust-heap. Input came from this Rust program:
+//
+//   use dhat::{Dhat, DhatAlloc};
+//   #[global_allocator]
+//   static ALLOC: DhatAlloc = DhatAlloc;
+//   fn main() {
+//       let _dhat = Dhat::start_heap_profiling();
+//       let _v: Vec<u8> = Vec::with_capacity(1000);
+//   }
+//---------------------------------------------------------------------------
+
+let rust_heap = {
+  name: "rust_heap",
+  input:
+//---------------------------------------------------------------------------
+{
+  "dhatFileVersion": 2,
+  "mode": "rust-heap",
+  "verb": "Allocated",
+  "bklt": true,
+  "bkacc": false,
+  "tu": "µs",
+  "Mtu": "s",
+  "tuth": 10,
+  "cmd": "target/debug/dhatter",
+  "pid": 85218,
+  "tg": 174,
+  "te": 201,
+  "pps": [
+    {
+      "tb": 1000,
+      "tbk": 1,
+      "tl": 16,
+      "mb": 1000,
+      "mbk": 1,
+      "gb": 1000,
+      "gbk": 1,
+      "eb": 0,
+      "ebk": 0,
+      "fs": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]
+    }
+  ],
+  "ftbl": [
+    "[root]",
+    "0x10e13fb2b: <alloc::alloc::Global as core::alloc::AllocRef>::alloc (alloc.rs:203:9)",
+    "0x10e13cae4: alloc::raw_vec::RawVec<T,A>::allocate_in (raw_vec.rs:186:45)",
+    "0x10e13d09d: alloc::raw_vec::RawVec<T,A>::with_capacity_in (raw_vec.rs:161:9)",
+    "0x10e13c921: alloc::raw_vec::RawVec<T>::with_capacity (raw_vec.rs:92:9)",
+    "0x10e13f40f: alloc::vec::Vec<T>::with_capacity (vec.rs:355:20)",
+    "0x10e067a08: dhatter::main (main.rs:8:23)",
+    "0x10e06794e: core::ops::function::FnOnce::call_once (function.rs:227:5)",
+    "0x10e067801: std::sys_common::backtrace::__rust_begin_short_backtrace (backtrace.rs:137:18)",
+    "0x10e0677d4: std::rt::lang_start::{{closure}} (rt.rs:66:18)",
+    "0x10e17b220: core::ops::function::impls::<impl core::ops::function::FnOnce<A> for &F>::call_once (function.rs:259:13)",
+    "0x10e17b220: std::panicking::try::do_call (panicking.rs:373:40)",
+    "0x10e17b220: std::panicking::try (panicking.rs:337:19)",
+    "0x10e17b220: std::panic::catch_unwind (panic.rs:379:14)",
+    "0x10e17b220: std::rt::lang_start_internal (rt.rs:51:25)",
+    "0x10e0677b1: std::rt::lang_start (rt.rs:65:5)",
+    "0x10e067bb2: _main (???:0:0)"
+  ]
+}
+//---------------------------------------------------------------------------
+  ,
+  outputs: [
+    {
+      label: "Total (blocks)",
+      expected:
+//---------------------------------------------------------------------------
+`\
+Invocation {
+  Mode:    rust-heap
+  Command: target/debug/dhatter
+  PID:     85218
+}
+
+Times {
+  t-gmax: 174 Âµs (86.57% of program duration)
+  t-end:  201 Âµs
+}
+
+─ PP 1/1 {
+    Total:     1,000 bytes (100%, 4,975,124.38/s) in 1 blocks (100%, 4,975.12/s), avg size 1,000 bytes, avg lifetime 16 Âµs (7.96% of program duration)
+    At t-gmax: 1,000 bytes (100%) in 1 blocks (100%), avg size 1,000 bytes
+    At t-end:  0 bytes (0%) in 0 blocks (0%), avg size 0 bytes
+    Allocated at {
+      #0: [root]
+      #1: 0x10e13fb2b: <alloc::alloc::Global as core::alloc::AllocRef>::alloc (alloc.rs:203:9)
+      #2: 0x10e13cae4: alloc::raw_vec::RawVec<T,A>::allocate_in (raw_vec.rs:186:45)
+      #3: 0x10e13d09d: alloc::raw_vec::RawVec<T,A>::with_capacity_in (raw_vec.rs:161:9)
+      #4: 0x10e13c921: alloc::raw_vec::RawVec<T>::with_capacity (raw_vec.rs:92:9)
+      #5: 0x10e13f40f: alloc::vec::Vec<T>::with_capacity (vec.rs:355:20)
+      #6: 0x10e067a08: dhatter::main (main.rs:8:23)
+      #7: 0x10e06794e: core::ops::function::FnOnce::call_once (function.rs:227:5)
+      #8: 0x10e067801: std::sys_common::backtrace::__rust_begin_short_backtrace (backtrace.rs:137:18)
+      #9: 0x10e0677d4: std::rt::lang_start::{{closure}} (rt.rs:66:18)
+      #10: 0x10e17b220: core::ops::function::impls::<impl core::ops::function::FnOnce<A> for &F>::call_once (function.rs:259:13)
+      #11: 0x10e17b220: std::panicking::try::do_call (panicking.rs:373:40)
+      #12: 0x10e17b220: std::panicking::try (panicking.rs:337:19)
+      #13: 0x10e17b220: std::panic::catch_unwind (panic.rs:379:14)
+      #14: 0x10e17b220: std::rt::lang_start_internal (rt.rs:51:25)
+      #15: 0x10e0677b1: std::rt::lang_start (rt.rs:65:5)
+      #16: 0x10e067bb2: _main (???:0:0)
+    }
+  }
+
+PP significance threshold: total >= 0.01 blocks (1%)
+`
+//---------------------------------------------------------------------------
+    }
+  ]
+};
+tests.push(rust_heap);
+
 //---------------------------------------------------------------------------
 // Code
 //---------------------------------------------------------------------------
@@ -2481,14 +3015,14 @@ function runTests() {
       let j = 0;
       let labelFound = false;
       for (let opt of gSelect.options) {
-        if (gSelectData[opt.value].label == label) {
+        if (gSelectData[opt.value].label() == label) {
           gSelect.selectedIndex = j;
           labelFound = true;
           break;
         }
         j++;
       }
-      assert(labelFound, "test label not found in gSelectData");
+      assert(labelFound, `test label not found in gSelectData: ${label}`);
 
       // Build and display the tree.
       tryFunc(() => {
index 920dcad45c38398a5b284e46b2b28eadebbeb773..ffdea9303318faf84affc23bd5a853068e93c46a 100644 (file)
@@ -51,7 +51,7 @@ let gHeaderDiv, gTestingDiv, gMainDiv, gLegendDiv, gTimingsDiv;
 let gFilename;
 
 // The object extracted from the JSON input.
-let gData;
+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
@@ -64,62 +64,68 @@ let gRoot;
 // - label: Used in the drop-down menu.
 // - bolds: Which fields to highlight in the output.
 // - cmpField: Field used to sort the radix tree.
+// - enable: Function saying whether this option is enabled.
 // - sig: Significance function used to determine aggregate nodes.
 // - sigLabel: Significance threshold description function.
 //
 const gSelectData = [
   {
-    label: "Total (bytes)",
+    label: () => `Total (${bytesUnit()})`,
     bolds: { "totalTitle": 1, "totalBytes": 1 },
     cmpField: "_totalBytes",
+    enable: (aBkLt, aBkAcc) => true,
     sig: (aT) => aT._totalBytes >= 0.01 * gRoot._totalBytes,
     sigLabel: () => `\
 total >= ${bytesAndPerc(0.01 * gRoot._totalBytes, gRoot._totalBytes)}`
   },
   {
     isDefault: true,
-    label: "Total (blocks)",
+    label: () => `Total (${blocksUnit()})`,
     bolds: { "totalTitle": 1, "totalBlocks": 1 },
     cmpField: "_totalBlocks",
+    enable: (aBkLt, aBkAcc) => true,
     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
+  // No "Total (bytes), tiny" because it's extremely unlikely that a PP with a
   // tiny average size will take up a significant number of bytes.
   {
-    label: "Total (blocks), tiny",
+    label: () => `Total (${blocksUnit()}), tiny`,
     bolds: { "totalTitle": 1, "totalBlocks": 1, "totalAvgSizeBytes": 1 },
     cmpField: "_totalBlocks",
+    enable: (aBkLt, aBkAcc) => true,
     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)})`
+(avg size <= ${bytes(16)})`
   },
-  // No "Total (bytes), short-lived", because an AP with few large, short-lived
+  // No "Total (bytes), short-lived", because a PP 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
+  // because a PP with many small, short-lived blocks *is* likely.) And if
+  // such a PP 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.
+  // little time for accesses in a small number of instructions.
   {
-    label: "Total (blocks), short-lived",
-    bolds: { "totalTitle": 1, "totalBlocks": 1, "totalAvgLifetimeInstrs": 1 },
+    label: () => "Total (blocks), short-lived",
+    bolds: { "totalTitle": 1, "totalBlocks": 1, "totalAvgLifetime": 1 },
     cmpField: "_totalBlocks",
+    enable: (aBkLt, aBkAcc) => aBkLt,
     sig: (aT) => aT._totalBlocks >= 0.005 * gRoot._totalBlocks &&
-                 aT._totalAvgLifetimeInstrs() <= 500,
+                 aT._totalAvgLifetimes() <= gData.tuth,
     sigLabel: () => `\
 (total >= ${blocksAndPerc(0.005 * gRoot._totalBlocks, gRoot._totalBlocks)}) && \
-(total avg lifetime <= ${instrs(500)})`
+(avg lifetime <= ${time(gData.tuth)})`
   },
   {
-    label: "Total (bytes), zero reads or zero writes",
+    label: () => "Total (bytes), zero reads or zero writes",
     bolds: { "totalTitle": 1, "totalBytes": 1,
              "readsTitle": 1, "readsBytes": 1,
              "writesTitle": 1, "writesBytes": 1,
            },
     cmpField: "_totalBytes",
+    enable: (aBkLt, aBkAcc) => aBkAcc,
     sig: (aT) => aT._totalBytes >= 0.005 * gRoot._totalBytes &&
                  (aT._readsBytes === 0 || aT._writesBytes === 0),
     sigLabel: () => `\
@@ -127,12 +133,13 @@ total >= ${blocksAndPerc(0.01 * gRoot._totalBlocks, gRoot._totalBlocks)}`
 ((reads == ${bytes(0)}) || (writes == ${bytes(0)}))`
   },
   {
-    label: "Total (blocks), zero reads or zero writes",
+    label: () => "Total (blocks), zero reads or zero writes",
     bolds: { "totalTitle": 1, "totalBlocks": 1,
              "readsTitle": 1, "readsBytes": 1,
              "writesTitle": 1, "writesBytes": 1,
            },
     cmpField: "_totalBlocks",
+    enable: (aBkLt, aBkAcc) => aBkAcc,
     sig: (aT) => aT._totalBlocks >= 0.005 * gRoot._totalBlocks &&
                  (aT._readsBytes === 0 || aT._writesBytes === 0),
     sigLabel: () => `\
@@ -140,12 +147,13 @@ total >= ${blocksAndPerc(0.01 * gRoot._totalBlocks, gRoot._totalBlocks)}`
 ((reads == ${bytes(0)}) || (writes == ${bytes(0)}))`
   },
   {
-    label: "Total (bytes), low-access",
+    label: () => "Total (bytes), low-access",
     bolds: { "totalTitle": 1, "totalBytes": 1,
              "readsTitle": 1, "readsAvgPerByte": 1,
              "writesTitle": 1, "writesAvgPerByte": 1,
            },
     cmpField: "_totalBytes",
+    enable: (aBkLt, aBkAcc) => aBkAcc,
     sig: (aT) => aT._totalBytes >= 0.005 * gRoot._totalBytes &&
                  aT._readsBytes !== 0 &&
                  aT._writesBytes !== 0 &&
@@ -158,12 +166,13 @@ total >= ${blocksAndPerc(0.01 * gRoot._totalBlocks, gRoot._totalBlocks)}`
 ((reads <= ${perByte(0.4)}) || (writes <= ${perByte(0.4)}))`
   },
   {
-    label: "Total (blocks), low-access",
+    label: () => "Total (blocks), low-access",
     bolds: { "totalTitle": 1, "totalBlocks": 1,
              "readsTitle": 1, "readsAvgPerByte": 1,
              "writesTitle": 1, "writesAvgPerByte": 1,
            },
     cmpField: "_totalBlocks",
+    enable: (aBkLt, aBkAcc) => aBkAcc,
     sig: (aT) => aT._totalBlocks >= 0.005 * gRoot._totalBlocks &&
                  aT._readsBytes !== 0 &&
                  aT._writesBytes !== 0 &&
@@ -176,14 +185,15 @@ total >= ${blocksAndPerc(0.01 * gRoot._totalBlocks, gRoot._totalBlocks)}`
 ((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 "Total (avg lifetime)": 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)",
+    label: () => "At t-gmax (bytes)",
     bolds: { "atTGmaxTitle": 1, "atTGmaxBytes": 1 },
     cmpField: "_atTGmaxBytes",
+    enable: (aBkLt, aBkAcc) => aBkLt,
     sig: (aT) => aT._atTGmaxBytes >= 0.01 * gRoot._atTGmaxBytes,
     sigLabel: () => `\
 at-t-gmax >= ${bytesAndPerc(0.01 * gRoot._atTGmaxBytes, gRoot._atTGmaxBytes)}`
@@ -191,9 +201,10 @@ 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)",
+    label: () => "At t-end (bytes)",
     bolds: { "atTEndTitle": 1, "atTEndBytes": 1 },
     cmpField: "_atTEndBytes",
+    enable: (aBkLt, aBkAcc) => aBkLt,
     sig: (aT) => aT._atTEndBytes >= 0.01 * gRoot._atTEndBytes,
     sigLabel: () => `\
 at-t-end >= ${bytesAndPerc(0.01 * gRoot._atTEndBytes, gRoot._atTEndBytes)}`
@@ -201,17 +212,19 @@ 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)",
+    label: () => "Reads (bytes)",
     bolds: { "readsTitle": 1, "readsBytes": 1 },
     cmpField: "_readsBytes",
+    enable: (aBkLt, aBkAcc) => aBkAcc,
     sig: (aT) => aT._readsBytes >= 0.01 * gRoot._readsBytes,
     sigLabel: () => `\
 reads >= ${bytesAndPerc(0.01 * gRoot._readsBytes, gRoot._readsBytes)}`
   },
   {
-    label: "Reads (bytes), high-access",
+    label: () => "Reads (bytes), high-access",
     bolds: { "readsTitle": 1, "readsBytes": 1, "readsAvgPerByte": 1 },
     cmpField: "_readsBytes",
+    enable: (aBkLt, aBkAcc) => aBkAcc,
     sig: (aT) => aT._readsBytes >= 0.005 * gRoot._readsBytes &&
                  (aT._readsAvgPerByte() >= 1000 ||
                   aT._writesAvgPerByte() >= 1000),
@@ -221,17 +234,19 @@ reads >= ${bytesAndPerc(0.01 * gRoot._readsBytes, gRoot._readsBytes)}`
   },
   // No "Reads (avg per byte)": covered by other access-related ones.
   {
-    label: "Writes (bytes)",
+    label: () => "Writes (bytes)",
     bolds: { "writesTitle": 1, "writesBytes": 1 },
     cmpField: "_writesBytes",
+    enable: (aBkLt, aBkAcc) => aBkAcc,
     sig: (aT) => aT._writesBytes >= 0.01 * gRoot._writesBytes,
     sigLabel: () => `\
 writes >= ${bytesAndPerc(0.01 * gRoot._writesBytes, gRoot._writesBytes)}`
   },
   {
-    label: "Writes (bytes), high-access",
+    label: () => "Writes (bytes), high-access",
     bolds: { "writesTitle": 1, "writesBytes": 1, "writesAvgPerByte": 1 },
     cmpField: "_writesBytes",
+    enable: (aBkLt, aBkAcc) => aBkAcc,
     sig: (aT) => aT._writesBytes >= 0.005 * gRoot._writesBytes &&
                  (aT._readsAvgPerByte() >= 1000 ||
                   aT._writesAvgPerByte() >= 1000),
@@ -304,10 +319,10 @@ function TreeNode(aKind, aFrames) {
   this._totalBytes = 0;
   this._totalBlocks = 0;
 
-  this._totalLifetimesInstrs = 0;
+  this._totalLifetimes = 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
+  // can be summed, _maxBytes/_maxBlocks for two PPs can't be easily combined
   // because the maxes may have occurred at different times.
   if (this._kind === kLeaf) {
     this._maxBytes = 0;
@@ -341,15 +356,20 @@ function TreeNode(aKind, aFrames) {
 }
 
 TreeNode.prototype = {
-  _add(aTotalBytes, aTotalBlocks, aTotalLifetimesInstrs, aMaxBytes,
+  _add(aTotalBytes, aTotalBlocks, aTotalLifetimes, aMaxBytes,
        aMaxBlocks, aAtTGmaxBytes, aAtTGmaxBlocks, aAtTEndBytes,
        aAtTEndBlocks, aReadsBytes, aWritesBytes, aAccesses) {
 
     // We ignore this._kind, this._frames, and this._kids.
 
+    // Note: if !gData.bklt and/or !gData.bkacc, some of these fields these
+    // values come from will be missing in the input file, so the values will
+    // be `undefined`, and the fields will end up as `NaN`. But this is ok
+    // because we don't show them.
+
     this._totalBytes += aTotalBytes;
     this._totalBlocks += aTotalBlocks;
-    this._totalLifetimesInstrs += aTotalLifetimesInstrs;
+    this._totalLifetimes += aTotalLifetimes;
 
     if (this._kind === kLeaf) {
       // Leaf nodes should only be added to once, because DHAT currently
@@ -391,9 +411,9 @@ TreeNode.prototype = {
     }
   },
 
-  _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);
+  _addPP(aPP) {
+    this._add(aPP.tb, aPP.tbk, aPP.tl, aPP.mb, aPP.mbk, aPP.gb, aPP.gbk,
+              aPP.eb, aPP.ebk, aPP.rb, aPP.wb, aPP.acc);
   },
 
   // This is called in two cases.
@@ -401,7 +421,7 @@ TreeNode.prototype = {
   //   cloning a node).
   // - Aggregating multiple nodes.
   _addNode(aT) {
-    this._add(aT._totalBytes, aT._totalBlocks, aT._totalLifetimesInstrs,
+    this._add(aT._totalBytes, aT._totalBlocks, aT._totalLifetimes,
               aT._maxBytes, aT._maxBlocks, aT._atTGmaxBytes, aT._atTGmaxBlocks,
               aT._atTEndBytes, aT._atTEndBlocks,
               aT._readsBytes, aT._writesBytes, aT._accesses);
@@ -409,7 +429,7 @@ TreeNode.prototype = {
 
   // 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) {
+  _split(aTi, aPP, aNewFrames) {
     // kid1 inherits t's kind and values.
     let inheritedFrames = this._frames.splice(aTi + 1);
     let kid1 = new TreeNode(this._kind, inheritedFrames);
@@ -420,7 +440,7 @@ TreeNode.prototype = {
 
     // Put all remaining frames into kid2.
     let kid2 = new TreeNode(kLeaf, aNewFrames);
-    kid2._addAP(aAP);
+    kid2._addPP(aPP);
 
     // Update this.
     if (this._kind === kLeaf) {
@@ -432,15 +452,15 @@ TreeNode.prototype = {
       delete this._maxBlocks;
     }
     this._kids = [kid1, kid2];
-    this._addAP(aAP);
+    this._addPP(aPP);
   },
 
   _totalAvgSizeBytes() {
     return div(this._totalBytes, this._totalBlocks);
   },
 
-  _totalAvgLifetimeInstrs() {
-    return div(this._totalLifetimesInstrs, this._totalBlocks);
+  _totalAvgLifetimes() {
+    return div(this._totalLifetimes, this._totalBlocks);
   },
 
   _maxAvgSizeBytes() {
@@ -474,15 +494,15 @@ function checkFields(aObj, aFields) {
   }
 }
 
-// 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);
+// Do basic checking of a PP read from file.
+function checkPP(aPP) {
+  checkFields(aPP, ["tb", "tbk", "fs"]);
+  if (gData.bklt) {
+    checkFields(aPP, ["mb", "mbk", "gb", "gbk", "eb", "ebk"]);
+  }
+  if (gData.bkacc) {
+    checkFields(aPP, ["rb", "wb"]);
+  }
 }
 
 // Access counts latch as 0xffff. Treating 0xffff as Infinity gives us exactly
@@ -497,51 +517,78 @@ function normalizeAccess(aAcc) {
   assert(false, "too-large access value");
 }
 
-const kExpectedFileVersion = 1;
+const kExpectedFileVersion = 2;
 
 // Build gRoot from gData.
 function buildTree() {
   // Check global values.
-  let fields = ["dhatFileVersion",
+  let fields = ["dhatFileVersion", "mode", "verb",
+                "bklt", "bkacc",
+                "tu", "Mtu",
                 "cmd", "pid",
-                "mi", "ei",
-                "aps", "ftbl"];
+                "te", "pps", "ftbl"];
   checkFields(gData, fields);
   if (gData.dhatFileVersion != kExpectedFileVersion) {
-      throw Error(`data file has version number ${gData.dhatFileVersion}, ` +
-                  `expected version number ${kExpectedFileVersion}`);
+      throw new Error(
+        `data file has version number ${gData.dhatFileVersion}, ` +
+        `expected version number ${kExpectedFileVersion}`);
+  }
+
+  if (gData.bklt) {
+    checkFields(gData, ["tg", "tuth"]);
+  }
+
+  // Update sort metric labels, and disable sort metrics that aren't allowed
+  // for this data.
+  for (let [i, option] of gSelect.childNodes.entries()) {
+    let data = gSelectData[i];
+    option.label = data.label();
+    option.disabled = !data.enable(gData.bklt, gData.bkacc);
+  }
+
+  // If the selected sort metric was just disabled, switch the sort metric
+  // back to the default (which is never disabled).
+  let option = gSelect.childNodes[gSelect.selectedIndex];
+  if (option.disabled) {
+    for (let [i, data] of gSelectData.entries()) {
+      let option = gSelect.childNodes[i];
+      if (data.isDefault) {
+        option.selected = true;
+        break;
+      }
+    }
   }
 
   // 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);
+  for (let [i, pp] of gData.pps.entries()) {
+    checkPP(pp);
 
     // Decompress the run-length encoding in `acc`, if present.
-    if (ap.acc) {
+    if (pp.acc) {
       let acc = [];
-      for (let i = 0; i < ap.acc.length; i++) {
-        if (ap.acc[i] < 0) {
+      for (let i = 0; i < pp.acc.length; i++) {
+        if (pp.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];
+          let reps = -pp.acc[i++];
+          let val = pp.acc[i];
           for (let j = 0; j < reps; j++) {
             acc.push(normalizeAccess(val));
           }
         } else {
-          acc.push(normalizeAccess(ap.acc[i]));
+          acc.push(normalizeAccess(pp.acc[i]));
         }
       }
-      ap.acc = acc;
+      pp.acc = acc;
     }
 
-    // The first AP is a special case, because we have to build gRoot.
+    // The first PP is a special case, because we have to build gRoot.
     if (i === 0) {
-      gRoot._frames.push(...ap.fs);
-      gRoot._addAP(ap);
+      gRoot._frames.push(...pp.fs);
+      gRoot._addPP(pp);
       continue;
     }
 
@@ -553,8 +600,7 @@ function buildTree() {
     // `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()) {
-
+    for (let [j, kidFrame] of pp.fs.entries()) {
       // Search for kidFrame among internal frames.
       if (ti + 1 < t._frames.length) {
         // t has an internal frame at the right index.
@@ -566,7 +612,7 @@ function buildTree() {
           // 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));
+          t._split(ti, pp, pp.fs.slice(j));
           done = true;
           break;
         }
@@ -580,12 +626,12 @@ function buildTree() {
           // get the leftover frames.
           //
           // E.g. ab:20-[] + abcd:10 => ab:30-[-:20-[], cd:10-[]]
-          t._split(ti, ap, ap.fs.slice(j));
+          t._split(ti, pp, pp.fs.slice(j));
           done = true;
           break;
         }
 
-        t._addAP(ap);
+        t._addPP(pp);
 
         // Search for the frame among the kids.
         let kid;
@@ -604,8 +650,8 @@ function buildTree() {
           //
           // 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);
+          kid = new TreeNode(kLeaf, pp.fs.slice(j));
+          kid._addPP(pp);
           t._kids.push(kid);
           done = true;
           break;
@@ -615,9 +661,9 @@ function buildTree() {
 
     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
+      // - pp's frames match an existing frame sequence, in which case we
+      //   just need to _addPP(); or
+      // - pp's frames are a subsequence of an existing sequence, in which
       //   case we must split.
 
       if (ti + 1 < t._frames.length) {
@@ -625,20 +671,20 @@ function buildTree() {
         // frames. Split, creating an empty node.
         //
         // E.g. abcd:20-Xs + ab:10 => ab:30-[cd:20-Xs, -:10-[]]
-        t._split(ti, ap, []);
+        t._split(ti, pp, []);
 
       } 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`);
+        throw new Error(`data file contains a repeated location (1)`);
 
         // Matches an existing sequence that doesn't end in node with empty
-        // frames. Add the AP.
+        // frames. Add the PP.
         //
         // E.g. ab:20-[] + ab:10 => ab:30-[]
-        t._addAP(ap);
+        t._addPP(pp);
 
       } else {
         // Look for a kid with empty frames.
@@ -655,14 +701,14 @@ function buildTree() {
           // 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`);
+          throw new Error(`data file contains a repeated location (2)`);
 
           // Matches an existing sequence that ends in a node with empty
-          // frames. Add the AP.
+          // frames. Add the PP.
           //
           // E.g. ab:20-[c:10-Xs, -:10-[]] + ab:10 => ab:30-[c:10-Xs, -:20-[]]
-          t._addAP(ap);
-          emptyKid._addAP(ap);
+          t._addPP(pp);
+          emptyKid._addPP(pp);
 
         } else {
           // A subsequence of an existing sequence that ends at the end of t's
@@ -671,14 +717,13 @@ function buildTree() {
           // 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);
+          newKid._addPP(pp);
 
           t._kids.push(newKid);
-          t._addAP(ap);
+          t._addPP(pp);
         }
       }
     }
-
   }
 }
 
@@ -697,11 +742,23 @@ function perc(aNum, aDenom) {
 }
 
 function perMinstr(aN) {
-  return `${kDFormat.format(div(1000000 * aN, gData.ei))}/Minstr`;
+  return `${kDFormat.format(div(1000000 * aN, gData.te))}/${gData.Mtu}`;
+}
+
+function byteUnit() {
+    return gData.hasOwnProperty("bu") ? gData.bsu : "byte";
+}
+
+function bytesUnit() {
+    return gData.hasOwnProperty("bsu") ? gData.bsu : "bytes";
+}
+
+function blocksUnit() {
+    return gData.hasOwnProperty("bksu") ? gData.bksu : "blocks";
 }
 
 function bytes(aN) {
-  return `${kDFormat.format(aN)} bytes`;
+  return `${kDFormat.format(aN)} ${bytesUnit()}`;
 }
 
 function bytesAndPerc(aN, aTotalN) {
@@ -713,7 +770,7 @@ function bytesAndPercAndRate(aN, aTotalN) {
 }
 
 function blocks(aN) {
-  return `${kDFormat.format(aN)} blocks`;
+  return `${kDFormat.format(aN)} ${blocksUnit()}`;
 }
 
 function blocksAndPerc(aN, aTotalN) {
@@ -729,15 +786,15 @@ function avgSizeBytes(aN) {
 }
 
 function perByte(aN) {
-  return `${kDFormat.format(aN)}/byte`;
+  return `${kDFormat.format(aN)}/${byteUnit()}`;
 }
 
-function instrs(aN) {
-  return `${kDFormat.format(aN)} instrs`;
+function time(aN) {
+  return `${kDFormat.format(aN)} ${gData.tu}`;
 }
 
-function avgLifetimeInstrs(aN) {
-  return `avg lifetime ${instrs(aN)}`;
+function avgLifetime(aN) {
+  return `avg lifetime ${time(aN)}`;
 }
 
 function accesses(aAccesses) {
@@ -817,6 +874,7 @@ function appendInvocationAndTimes(aP) {
   let v, v1, v2;
 
   v = "Invocation {\n";
+  v += `  Mode:    ${gData.mode}\n`;
   v += `  Command: ${gData.cmd}\n`;
   v += `  PID:     ${gData.pid}\n`;
   v += "}\n\n";
@@ -825,9 +883,11 @@ function appendInvocationAndTimes(aP) {
 
   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`;
+  v1 = perc(gData.tg, gData.te);
+  if (gData.bklt) {
+    v += `  t-gmax: ${time(gData.tg)} (${v1} of program duration)\n`;
+  }
+  v += `  t-end:  ${time(gData.te)}\n`;
 
   v += "}\n\n";
 
@@ -1017,103 +1077,109 @@ function appendTreeInner(aT, aP, aBolds, aCmp, aPc, aSig, aNodeIdNums,
 
   let v1, v2, v3, v4, v5;
 
-  // "AP" + node ID + kid count.
+  // "PP" + node ID + kid count.
   v1 = aNodeIdNums.join('.');
   v2 = aNumSibs + 1;
   v3 = kids ? `(${kids.length} children) ` : "";
-  fr(`AP ${v1}/${v2} ${v3}{`, true, false);
+  fr(`PP ${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);
+  v4 = avgLifetime(aT._totalAvgLifetimes());
+  v5 = perc(aT._totalAvgLifetimes(), gData.te);
   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);
+  if (gData.bklt) {
+    fr(", ", aBolds.totalAvgLifetime, false);
+    fr(`${v4} (${v5} of program duration)`, aBolds.totalAvgLifetime);
+  }
   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();
+  if (gData.bklt) {
+    // "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);
   }
 
-  // "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)} `;
+  if (gData.bkacc) {
+    // "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;
       }
-      prevN = n;
-    }
-    fr(v);
-    nl();
+      fr(v);
+      nl();
 
-    fr("  }");
-    nl();
+      fr("  }");
+      nl();
+    }
   }
 
   // "Allocated at".
-  fr("  Allocated at {", true, false);
+  fr(`  ${gData.verb} at {`, true, false);
   nl(true);
   if (aT._kind === kAgg) {
     // Don't print ancestor frames; just print the "insignificant" frame.
@@ -1219,7 +1285,7 @@ function appendTree(aP, aBolds, aCmp, aPc, aSig) {
 }
 
 function appendSignificanceThreshold(aP, aSigLabel) {
-  let v = `\nAP significance threshold: ${aSigLabel()}\n`;
+  let v = `\nPP significance threshold: ${aSigLabel()}\n`;
   appendElementWithText(aP, "span", v, "threshold");
 }
 
@@ -1287,7 +1353,7 @@ function displayTree(aTRead, aTParse, aTBuild) {
   // Get details relating to the chosen sort metrics.
   let data = gSelectData[gSelect.selectedIndex];
   let bolds = data.bolds;
-  let label = data.label;
+  let label = data.label();
   let cmpField = data.cmpField;
   let sig = data.sig;
   let sigLabel = data.sigLabel;
@@ -1397,7 +1463,7 @@ function onLoad() {
   gSelect = appendElement(selectDiv, "select");
   gSelect.onchange = changeSortMetric;
   for (let [i, data] of gSelectData.entries()) {
-    let option = appendElementWithText(gSelect, "option", data.label);
+    let option = appendElementWithText(gSelect, "option", data.label());
     option.value = i;
     if (data.isDefault) {
       option.selected = true;
@@ -1421,13 +1487,15 @@ function onLoad() {
   appendElementWithText(ul, "li", "'t-gmax': time of global heap maximum " +
                                   "(as measured in bytes)");
   appendElementWithText(ul, "li", "'t-end': time of program end");
+  // The file may use different units (via the `tu` and `Mtu` fields), but
+  // these are the standard units so mention them here.
   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", "'PP': program point");
   appendElementWithText(ul, "li", "'avg': average");
   appendElementWithText(ul, "li", "'-' (in accesses): zero");
-  appendElementWithText(ul, "li", "'∞' (in accesses): leaf AP counts max out " +
+  appendElementWithText(ul, "li", "'∞' (in accesses): leaf PP counts max out " +
                                   "at 65534; larger counts are treated as " +
                                   "infinity");
   appendElementWithText(ul, "li", "'〃' (in accesses): same as previous entry");
diff --git a/dhat/dhat.h b/dhat/dhat.h
new file mode 100644 (file)
index 0000000..653ed41
--- /dev/null
@@ -0,0 +1,75 @@
+
+/*
+   ----------------------------------------------------------------
+
+   Notice that the following BSD-style license applies to this one
+   file (dhat.h) only.  The rest of Valgrind is licensed under the
+   terms of the GNU General Public License, version 2, unless
+   otherwise indicated.  See the COPYING file in the source
+   distribution for details.
+
+   ----------------------------------------------------------------
+
+   This file is part of DHAT, a Valgrind tool for profiling the
+   heap usage of programs.
+
+   Copyright (C) 2020 Nicholas Nethercote.  All rights reserved.
+
+   Redistribution and use in source and binary forms, with or without
+   modification, are permitted provided that the following conditions
+   are met:
+
+   1. Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+   2. The origin of this software must not be misrepresented; you must
+      not claim that you wrote the original software.  If you use this
+      software in a product, an acknowledgment in the product
+      documentation would be appreciated but is not required.
+
+   3. Altered source versions must be plainly marked as such, and must
+      not be misrepresented as being the original software.
+
+   4. The name of the author may not be used to endorse or promote
+      products derived from this software without specific prior written
+      permission.
+
+   THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
+   OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+   WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+   ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+   DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+   DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+   GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+   INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+   WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+   NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+   SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+   ----------------------------------------------------------------
+
+   Notice that the above BSD-style license applies to this one file
+   (memcheck.h) only.  The entire rest of Valgrind is licensed under
+   the terms of the GNU General Public License, version 2.  See the
+   COPYING file in the source distribution for details.
+
+   ----------------------------------------------------------------
+*/
+
+#include "valgrind.h"
+
+typedef
+   enum {
+      VG_USERREQ__DHAT_AD_HOC_EVENT = VG_USERREQ_TOOL_BASE('D', 'H'),
+
+      // This is just for DHAT's internal use. Don't use it.
+      _VG_USERREQ__DHAT_COPY = VG_USERREQ_TOOL_BASE('D','H') + 256
+   } Vg_DHATClientRequest;
+
+// Record an ad hoc event. The meaning of the weight argument will depend on
+// what the event represents, which is up to the user. If no meaningful weight
+// argument exists, just use 1.
+#define DHAT_AD_HOC_EVENT(_qzz_weight) \
+    VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__DHAT_AD_HOC_EVENT, \
+                                    (_qzz_weight), 0, 0, 0, 0)
+
index 6b9f1cb3f94de70f150189ec7da636616476e456..17aa9ab61cc3bf8e36cc3b7888f87c30de062d0d 100644 (file)
 <sect1 id="dh-manual.overview" xreflabel="Overview">
 <title>Overview</title>
 
-<para>DHAT is a tool for examining how programs use their heap
+<para>DHAT is primarily a tool for examining how programs use their heap
 allocations.</para>
 
 <para>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
+to find which block, if any, it is to. It presents, on a program point
 basis, information about these blocks such as sizes, lifetimes, numbers of
 reads and writes, and read and write patterns.</para>
 
-<para>Using this information it is possible to identify allocation points with
+<para>Using this information it is possible to identify program points with
 the following characteristics:</para>
 
 <itemizedlist>
@@ -54,6 +54,9 @@ 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.</para>
 
+<para>DHAT also has support for copy profiling and ad hoc profiling. These are
+described below.</para>
+
 </sect1>
 
 
@@ -155,11 +158,12 @@ because this can significantly reduce the size of DHAT's output files.</para>
 
 <sect2 id="dh-output-header"><title>The Output Header</title>
 
-<para>The first part of the output shows the program command and process ID.
-For example:</para>
+<para>The first part of the output shows the mode, program command and process
+ID. For example:</para>
 
 <programlisting><![CDATA[
 Invocation {
+  Mode:    heap
   Command: /home/njn/moz/rust0/build/x86_64-unknown-linux-gnu/stage2/bin/rustc --crate-name tuple_stress src/main.rs
   PID:     18816
 }
@@ -179,18 +183,19 @@ Times {
 </sect2>
 
 
-<sect2 id="dh-ap-tree"><title>The AP Tree</title>
+<sect2 id="dh-ap-tree"><title>The PP Tree</title>
 
 <para>The third part of the output is the largest and most interesting part,
-showing the allocation point (AP) tree.</para>
+showing the program point (PP) tree.</para>
 
 
 <sect3 id="dh-structure"><title>Structure</title>
 
-<para>The following image shows a screenshot of part of an AP
+<para>The following image shows a screenshot of part of a PP
 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.</para>
+details within the text. (It is also slightly out-of-date, and doesn't quite
+match the current output produced by DHAT's viewer.)</para>
 
 <graphic fileref="images/dh-tree.png" scalefit="1"/>
 
@@ -228,7 +233,7 @@ email, bug report, etc.</para>
 <para>The root node looks like this:</para>
 
 <programlisting><![CDATA[
-AP 1/1 (25 children) {
+PP 1/1 (25 children) {
   Total:     1,355,253,987 bytes (100%, 67,454.81/Minstr) in 5,943,417 blocks (100%, 295.82/Minstr), avg size 228.03 bytes, avg lifetime 3,134,692,250.67 instrs (15.6% of program duration)
   At t-gmax: 423,930,307 bytes (100%) in 1,575,682 blocks (100%), avg size 269.05 bytes
   At t-end:  258,002 bytes (100%) in 2,129 blocks (100%), avg size 121.18 bytes
@@ -250,11 +255,11 @@ next example will explain these in more detail.</para>
 
 <sect3 id="dh-interior-nodes"><title>Interior Nodes</title>
 
-<para>AP nodes further down the tree show information about a subset of
+<para>PP nodes further down the tree show information about a subset of
 allocations. For example:</para>
 
 <programlisting><![CDATA[
-AP 1.1/25 (2 children) {
+PP 1.1/25 (2 children) {
   Total:     54,533,440 bytes (4.02%, 2,714.28/Minstr) in 458,839 blocks (7.72%, 22.84/Minstr), avg size 118.85 bytes, avg lifetime 1,127,259,403.64 instrs (5.61% 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
@@ -288,7 +293,7 @@ stack trace that is shared by all the blocks covered by this node.</para>
 <para>The <computeroutput>Total</computeroutput> 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
+different nodes within a single profile; a PP that accounts for 10% of bytes
 allocated is likely to be more interesting than one that accounts for
 2%.</para>
 
@@ -301,16 +306,16 @@ different workloads.</para>
 average size and lifetimes of these blocks.</para>
 
 <para>The <computeroutput>At t-gmax</computeroutput> line says shows that no
-blocks from this AP were alive when the global heap peak occurred. In other
+blocks from this PP were alive when the global heap peak occurred. In other
 words, these blocks do not contribute at all to the global heap peak.</para>
 
 <para>The <computeroutput>At t-end</computeroutput> line shows that no blocks
-were from this AP were alive at shutdown. In other words, all those blocks were
+were from this PP were alive at shutdown. In other words, all those blocks were
 explicitly freed before termination.</para>
 
 <para>The <computeroutput>Reads</computeroutput> and
 <computeroutput>Writes</computeroutput> lines show how many bytes were read 
-within this AP's blocks, the fraction this represents of all heap reads, and
+within this PP'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%
@@ -336,7 +341,7 @@ vectors and hash tables, and isn't always fixable. </para>
 <para>This is a leaf node:</para>
 
 <programlisting><![CDATA[
-AP 1.1.1.1/2 {
+PP 1.1.1.1/2 {
   Total:     31,460,928 bytes (2.32%, 1,565.9/Minstr) in 262,171 blocks (4.41%, 13.05/Minstr), avg size 120 bytes, avg lifetime 986,406,885.05 instrs (4.91% of program duration)
   Max:       16,779,136 bytes in 65,543 blocks, avg size 256 bytes
   At t-gmax: 0 bytes (0%) in 0 blocks (0%), avg size 0 bytes
@@ -365,10 +370,10 @@ is a great-grandchild of the root; is the first grandchild of the node in the
 previous example; and has no children.</para>
 
 <para>Leaf nodes contain an additional <computeroutput>Max</computeroutput>
-line, indicating the peak memory use for the blocks covered by this AP. (This
+line, indicating the peak memory use for the blocks covered by this PP. (This
 peak may have occurred at a time other than
 <computeroutput>t-gmax</computeroutput>.) In this case, 31,460,298 bytes were
-allocated from this AP, but the maximum size alive at once was 16,779,136
+allocated from this PP, but the maximum size alive at once was 16,779,136
 bytes.</para>
 
 <para>Stack frames that begin with a <computeroutput>^</computeroutput> rather
@@ -383,7 +388,7 @@ This also means that each node makes complete sense on its own.</para>
 
 <sect3 id="dh-access-counts"><title>Access Counts</title>
 
-<para>If all blocks covered by an AP node have the same size, an additional
+<para>If all blocks covered by a PP node have the same size, an additional
 <computeroutput>Accesses</computeroutput> field will be present. It indicates
 how the reads and writes within these blocks were distributed. For
 example:</para>
@@ -399,7 +404,7 @@ Accesses: {
 }
 ]]></programlisting>
 
-<para>Every block covered by this AP was 32 bytes. Within all of those blocks,
+<para>Every block covered by this PP 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.</para>
 
@@ -425,12 +430,12 @@ layout inefficiencies.</para>
 
 <sect3 id="aggregate-nodes"><title>Aggregate Nodes</title>
 
-<para>The AP tree is very large and many nodes represent tiny numbers of blocks
+<para>The PP tree is very large and many nodes represent tiny numbers of blocks
 and bytes. Therefore, DHAT's viewer aggregates insignificant nodes like
 this:</para>
 
 <programlisting><![CDATA[
-AP 1.14.2/2 {
+PP 1.14.2/2 {
   Total:     5,175 blocks (0.09%, 0.26/Minstr)
   Allocated at {
     [5 insignificant]
@@ -449,15 +454,15 @@ case).</para>
 
 <sect2 id="dh-output-footer"><title>The Output Footer</title>
 
-<para>Below the AP tree is a line like this:</para>
+<para>Below the PP tree is a line like this:</para>
 
 <programlisting><![CDATA[
-AP significance threshold: total >= 59,434.17 blocks (1%)
+PP significance threshold: total >= 59,434.17 blocks (1%)
 ]]></programlisting>
 
-<para>It shows the function used to determine if an AP node is significant. All
+<para>It shows the function used to determine if a PP 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
+useful if you don't understand why a PP node has been aggregated. The exact
 threshold depends on the sort metric (see below).</para>
 
 <para>Finally, the bottom of the page shows a legend that explains some of the
@@ -587,21 +592,21 @@ filtering, so that only nodes meeting a particular criteria are shown.</para>
 <para>The values within a node that represent the chosen sort metric are shown
 in bold, so they stand out.</para>
 
-<para>Here is part of an AP node found with "Total (blocks), tiny", showing
+<para>Here is part of a PP node found with "Total (blocks), tiny", showing
 blocks with an average size of only 8.67 bytes:</para>
 
 <programlisting><![CDATA[
 Total:     3,407,848 bytes (0.25%, 169.62/Minstr) in 393,214 blocks (6.62%, 19.57/Minstr), avg size 8.67 bytes, avg lifetime 1,167,795,629.1 instrs (5.81% of program duration)
 ]]></programlisting>
 
-<para>Here is part of an AP node found with "Total (blocks), short-lived",
+<para>Here is part of a PP node found with "Total (blocks), short-lived",
 showing blocks with an average lifetime of only 181.75 instructions:</para>
 
 <programlisting><![CDATA[
 Total:     23,068,584 bytes (1.7%, 1,148.19/Minstr) in 262,143 blocks (4.41%, 13.05/Minstr), avg size 88 bytes, avg lifetime 181.75 instrs (0% of program duration)
 ]]></programlisting>
 
-<para>Here is an example of an AP identified with "Total (blocks), zero reads
+<para>Here is an example of a PP identified with "Total (blocks), zero reads
 or zero writes", showing blocks that are allocated but never touched:</para>
 
 <programlisting><![CDATA[
@@ -613,7 +618,7 @@ Reads:     0 bytes (0%, 0/Minstr), 0/byte
 Writes:    0 bytes (0%, 0/Minstr), 0/byte
 ]]></programlisting>
 
-<para>All the blocks identified by these APs are good candidates for
+<para>All the blocks identified by these PPs are good candidates for
 optimization.</para>
 
 </sect2>
@@ -648,10 +653,10 @@ increasing the current heap size by 200 bytes and then decreasing it by 100
 bytes.) As a result, it can only increase the global heap peak (if indeed,
 this results in a new peak) by 100 bytes.</para>
 
-<para>Finally, the allocation point assigned to the block allocated by the
+<para>Finally, the program point assigned to the block allocated by the
 <computeroutput>malloc(100)</computeroutput> call is retained once the block
 is reallocated. Which means that all 300 bytes are attributed to that
-allocation point, and no separate allocation point is created for the
+program point, and no separate program point is created for the
 <computeroutput>realloc(200)</computeroutput> call. This may be surprising,
 but it has one large benefit.</para>
 
@@ -659,12 +664,84 @@ but it has one large benefit.</para>
 adds data to that buffer from numerous different points in the code,
 reallocating the buffer each time it gets full. (E.g. code generation in a
 compiler might work this way.) With the described approach, the first heap
-block and all subsequent heap blocks are attributed to the same allocation
-point. While this is something of a lie -- the first allocation point isn't
-actually responsible for the other allocations -- it is arguably better than
-having the allocation points spread around, in a distribution
-that unpredictably depends on whenever the reallocation points were
-triggered.</para>
+block and all subsequent heap blocks are attributed to the same program point.
+While this is something of a lie -- the first program point isn't actually
+responsible for the other allocations -- it is arguably better than having the
+program points spread around in a distribution that unpredictably depends on
+whenever the reallocations were triggered.</para>
+
+</sect1>
+
+
+<sect1 id="dh-manual.copy-profiling" xreflabel="Copy profiling">
+<title>Copy profiling</title>
+
+<para>If DHAT is invoked with <option>--mode=copy</option>, instead of
+profiling heap operations (allocations and deallocations), it profiles copy
+operations, such as <computeroutput>memcpy</computeroutput>,
+<computeroutput>memmove</computeroutput>,
+<computeroutput>strcpy</computeroutput>, and
+<computeroutput>bcopy</computeroutput>. This is sometimes useful.</para>
+
+<para>Here is an example PP node from this mode:</para>
+
+<programlisting><![CDATA[
+PP 1.1.2/5 (4 children) {
+  Total:     1,210,925 bytes (10.03%, 4,358.66/Minstr) in 112,717 blocks (35.2%, 405.72/Minstr), avg size 10.74 bytes
+  Copied at {
+    ^1: 0x4842524: memmove (vg_replace_strmem.c:1289)
+    #2: 0x1F0A0D: copy_nonoverlapping<u8> (intrinsics.rs:1858)
+    #3: 0x1F0A0D: copy_from_slice<u8> (mod.rs:2524)
+    #4: 0x1F0A0D: spec_extend<u8> (vec.rs:2227)
+    #5: 0x1F0A0D: extend_from_slice<u8> (vec.rs:1619)
+    #6: 0x1F0A0D: push_str (string.rs:821)
+    #7: 0x1F0A0D: write_str (string.rs:2418)
+    #8: 0x1F0A0D: <&mut W as core::fmt::Write>::write_str (mod.rs:195)
+  }
+}
+]]></programlisting>
+
+<para>It is very similar to the PP nodes for heap profiling, but with less
+information, because copy profiling doesn't involve any tracking of memory
+regions with lifetimes.</para>
+
+</sect1>
+
+
+<sect1 id="dh-manual.ad-hoc-profiling" xreflabel="Ad hoc profiling">
+<title>Ad hoc profiling</title>
+
+<para>If DHAT is invoked with <option>--mode=ad-hoc</option>, instead of
+profiling heap operations (allocations and deallocations), it profiles calls to
+the <computeroutput>DHAT_AD_HOC_EVENT</computeroutput> client request, which is
+declared in <filename>dhat/dhat.h</filename>.</para>
+
+<para>Here is an example PP node from this mode:</para>
+
+<programlisting><![CDATA[
+PP 1.1.1.1/2 {
+  Total:     30 units (17.65%, 115.97/Minstr) in 1 events (14.29%, 3.87/Minstr), avg size 30 units
+  Occurred at {
+    ^1: 0x109407: g (ad-hoc.c:4)
+    ^2: 0x109425: f (ad-hoc.c:8)
+    #3: 0x109497: main (ad-hoc.c:14)
+  }
+}
+]]></programlisting>
+
+<para>This kind of profiling is useful when you know a code path is hot but you
+want to know more about it.</para>
+
+<para>For example, you might want to know which callsites of a hot function
+account for most of the calls. You could put a
+<computeroutput>DHAT_AD_HOC_EVENT(1);</computeroutput> call at the start of
+that function.</para>
+
+<para>Alternatively, you might want to know the typical length of a vector in a
+hot location. You could put a
+<computeroutput>DHAT_AD_HOC_EVENT(len);</computeroutput> call at the
+appropriate location, when <computeroutput>len</computeroutput> is the length
+of the vector.</para>
 
 </sect1>
 
@@ -694,6 +771,17 @@ triggered.</para>
     </listitem>
   </varlistentry>
 
+  <varlistentry id="opt.mode" xreflabel="--mode">
+    <term>
+      <option><![CDATA[--mode=<heap|copy|ad-hoc> [default: heap] ]]></option>
+    </term>
+    <listitem>
+      <para>The profiling mode: heap profiling, copy profiling, or ad hoc
+            profiling.
+      </para>
+    </listitem>
+  </varlistentry>
+
 </variablelist>
 
 <para>Note that stacks by default have 12 frames. This may be more than
index 2cb842b695e047bb771ab4051d6148a2fcc7b20b..fb9acb41b38d8d742d786f8211f9e312574a04a3 100644 (file)
@@ -5,16 +5,20 @@ dist_noinst_SCRIPTS = filter_stderr
 
 EXTRA_DIST = \
        acc.stderr.exp acc.vgtest \
+       ad-hoc.stderr.exp ad-hoc.vgtest \
        basic.stderr.exp basic.vgtest \
        big.stderr.exp big.vgtest \
+       copy.stderr.exp copy.vgtest \
        empty.stderr.exp empty.vgtest \
        sig.stderr.exp sig.vgtest \
        single.stderr.exp single.vgtest
 
 check_PROGRAMS = \
        acc \
+       ad-hoc \
        basic \
        big \
+       copy \
        empty \
        sig \
        single
diff --git a/dhat/tests/ad-hoc.c b/dhat/tests/ad-hoc.c
new file mode 100644 (file)
index 0000000..51244dc
--- /dev/null
@@ -0,0 +1,27 @@
+#include "dhat/dhat.h"
+#include <stdlib.h>
+void g(void) {
+   DHAT_AD_HOC_EVENT(30);
+}
+
+void f(void) {
+   g();
+   DHAT_AD_HOC_EVENT(20);
+   g();
+}
+
+int main(void) {
+   f();
+   DHAT_AD_HOC_EVENT(10);
+   f();
+
+   // At one point malloc was broken with --mode=ad-hoc(!), and Valgrind was
+   // printing messages like "VG_USERREQ__CLIENT_CALL1: func=0x0" when malloc
+   // was called. So check that it's basically working...
+   char* p = malloc(100);
+   p = realloc(p, 200);
+   free(p);
+
+   return 0;
+}
+
diff --git a/dhat/tests/ad-hoc.stderr.exp b/dhat/tests/ad-hoc.stderr.exp
new file mode 100644 (file)
index 0000000..14e4ff0
--- /dev/null
@@ -0,0 +1 @@
+Total:     170 units in 7 events
diff --git a/dhat/tests/ad-hoc.vgtest b/dhat/tests/ad-hoc.vgtest
new file mode 100644 (file)
index 0000000..bd41f1b
--- /dev/null
@@ -0,0 +1,3 @@
+prog: ad-hoc
+vgopts: --mode=ad-hoc --dhat-out-file=dhat.out
+cleanup: rm dhat.out
index abb7ef43641acd361182d51a56b3b447676c126a..96b2410aa159069c6e48484ddcb785cabb99ecae 100644 (file)
@@ -3,6 +3,7 @@
 #include <stdint.h>
 #include <stdlib.h>
 #include <string.h>
+#include "dhat/dhat.h"
 
 int main(void)
 {
@@ -24,5 +25,9 @@ int main(void)
 
    free(c);
                                  // totals: 3008 read, 3516 write
+
+   // Should be ignored because we're not in ad hoc mode.
+   DHAT_AD_HOC_EVENT(100);
+
    return 0;
 }
diff --git a/dhat/tests/copy.c b/dhat/tests/copy.c
new file mode 100644 (file)
index 0000000..8e6b7de
--- /dev/null
@@ -0,0 +1,60 @@
+// This tests --mode=copy with various copying functions.
+
+#define _GNU_SOURCE // For mempcpy.
+#include <stdlib.h>
+#include <string.h>
+#include <wchar.h>
+
+void f(char* a, char* b, wchar_t* wa, wchar_t* wb);
+void test_malloc();
+
+int main(void) {
+   char a[1000];
+   char b[1000];
+   for (int i = 0; i < 1000; i++) {
+      a[i] = 'a';
+      b[i] = 'b';
+   }
+   a[999] = '\0';
+   b[999] = '\0';
+
+   wchar_t wa[250];
+   wchar_t wb[250];
+   for (int i = 0; i < 250; i++) {
+      wa[i] = 'A';
+      wb[i] = 'B';
+   }
+   wa[249] = '\0';
+   wb[249] = '\0';
+
+   for (int i = 0; i < 100; i++) {
+      f(a, b, wa, wb);
+   }
+
+   test_malloc();
+   return 0;
+}
+
+void f(char* a, char* b, wchar_t* wa, wchar_t* wb) {
+   // The memcpy is duplicated so we have 10 calls, which makes for nice round
+   // numbers in the totals.
+   memcpy (a, b, 1000); // Redirects to memmove
+   memcpy (a, b, 1000); // Redirects to memmove
+   memmove(a, b, 1000);
+   mempcpy(a, b, 1000);
+   bcopy  (a, b, 1000); // Redirects to memmove
+   strcpy (a, b);
+   strncpy(a, b, 1000);
+   stpcpy (a, b);       // Redirects to strcpy
+   stpncpy(a, b, 1000);
+   wcscpy (wa, wb);
+}
+
+void test_malloc() {
+   // At one point malloc was broken with --mode=copy(!), and Valgrind was
+   // printing messages like "VG_USERREQ__CLIENT_CALL1: func=0x0" when malloc
+   // was called. So check that it's basically working...
+   char* p = malloc(100);
+   p = realloc(p, 200);
+   free(p);
+}
diff --git a/dhat/tests/copy.stderr.exp b/dhat/tests/copy.stderr.exp
new file mode 100644 (file)
index 0000000..2e35f69
--- /dev/null
@@ -0,0 +1 @@
+Total:     1,000,... bytes in 1,0.. blocks
diff --git a/dhat/tests/copy.vgtest b/dhat/tests/copy.vgtest
new file mode 100644 (file)
index 0000000..aa71da6
--- /dev/null
@@ -0,0 +1,4 @@
+prog: copy
+vgopts: --mode=copy --dhat-out-file=dhat.out
+stderr_filter: filter_copy
+cleanup: rm dhat.out
diff --git a/dhat/tests/filter_copy b/dhat/tests/filter_copy
new file mode 100755 (executable)
index 0000000..57b6536
--- /dev/null
@@ -0,0 +1,9 @@
+#! /bin/sh
+
+# It's impossible to get exact matches for copy counts because even trivial C
+# programs do a few memcpy/strcpy calls. So we allow some fuzzy matching.
+# So we allow 1,000,000..1,009,999 bytes and 1,000..1,099 blocks.
+
+./filter_stderr "$@" |
+sed -e "s/1,00.,... bytes in 1,0.. blocks/1,000,... bytes in 1,0.. blocks/"
+
index e02ecc84488ed8787aba51260261f191bc470635..9a3dd3242ee38c07e6e5c4adddaedba8ff5e8c08 100755 (executable)
@@ -21,8 +21,7 @@ sed "/^  file:\/\/\// d" |
 sed "/^in a web browser/ d" |
 sed "/^  \// d" |                # This is pretty feeble, but I don't see
                                  # how to do better
-sed "/^Scroll to the end/ d" |
-sed "/^explanation of some/ d" |
+sed "/^The text at the bottom/ d" |
 
 # and remove any blank lines in the output
 sed "/^[[:space:]]*$/d"
index 659d65f7eeabbadccee8bbac6fc638949b0e9fde..f9f39b7d64dcbe0e973add046067f181e57df9cb 100644 (file)
@@ -39,6 +39,7 @@
 /* Can be called from VG_(tdict).malloc_malloc et al to do the actual
  * alloc/freeing. */
 extern void* VG_(cli_malloc) ( SizeT align, SizeT nbytes );
+extern void* VG_(cli_realloc)( void* ptr, SizeT nbytes );
 extern void  VG_(cli_free)   ( void* p );
 // Returns the usable size of a heap-block.  It's the asked-for size plus
 // possibly some more due to rounding up.
index 1adf9f793d6130d9634256d6ccf1070c6ce7e805..10aef96314111ff3d885d38ea4674b2d30f17e73 100644 (file)
@@ -41,7 +41,7 @@ IRSB* nl_instrument ( VgCallbackClosure* closure,
                       const VexArchInfo* archinfo_host,
                       IRType gWordTy, IRType hWordTy )
 {
-    return bb;
+   return bb;
 }
 
 static void nl_fini(Int exitcode)
index 423216ae1805d4fa486cf48d44b49969a0963989..ca1c7ea742583104c9bef2592ae14c4b7704a40e 100644 (file)
 #include "pub_tool_clreq.h"
 
 /* ---------------------------------------------------------------------
-   We have our own versions of these functions for two reasons:
+   We have our own versions of these functions for multiple reasons:
    (a) it allows us to do overlap checking
-   (b) some of the normal versions are hyper-optimised, which fools
+   (b) it allows us to do copy tracking
+   (c) some of the normal versions are hyper-optimised, which fools
        Memcheck and cause spurious value warnings.  Our versions are
        simpler.
-   (c) the glibc SSE-variants can read past the end of the input data
+   (d) the glibc SSE-variants can read past the end of the input data
        ranges. This can cause false-positive Memcheck / Helgrind / DRD
        reports.
 
@@ -173,6 +174,15 @@ static inline void my_exit ( int x )
 #ifndef RECORD_OVERLAP_ERROR
 #define RECORD_OVERLAP_ERROR(s, src, dst, len) do { } while (0)
 #endif
+
+// Used for tools that record bulk copies: memcpy, strcpy, etc.
+#ifndef RECORD_COPY
+#define RECORD_COPY(len) do { } while (0)
+#define FOR_COPY(x)
+#else
+#define FOR_COPY(x) x
+#endif
+
 #ifndef VALGRIND_CHECK_VALUE_IS_DEFINED
 #define VALGRIND_CHECK_VALUE_IS_DEFINED(__lvalue) 1
 #endif
@@ -496,12 +506,14 @@ static inline void my_exit ( int x )
       while (*src) *dst++ = *src++; \
       *dst = 0; \
       \
-      /* This checks for overlap after copying, unavoidable without */ \
+      /* This happens after copying, unavoidable without */ \
       /* pre-counting length... should be ok */ \
+      SizeT srclen = (Addr)src-(Addr)src_orig+1; \
+      RECORD_COPY(srclen); \
       if (is_overlap(dst_orig,  \
                      src_orig,  \
                      (Addr)dst-(Addr)dst_orig+1, \
-                     (Addr)src-(Addr)src_orig+1)) \
+                     srclen)) \
          RECORD_OVERLAP_ERROR("strcpy", dst_orig, src_orig, 0); \
       \
       return dst_orig; \
@@ -539,7 +551,9 @@ static inline void my_exit ( int x )
       while (m   < n && *src) { m++; *dst++ = *src++; } \
       /* Check for overlap after copying; all n bytes of dst are relevant, */ \
       /* but only m+1 bytes of src if terminator was found */ \
-      if (is_overlap(dst_orig, src_orig, n, (m < n) ? m+1 : n)) \
+      SizeT srclen = (m < n) ? m+1 : n; \
+      RECORD_COPY(srclen); \
+      if (is_overlap(dst_orig, src_orig, n, srclen)) \
          RECORD_OVERLAP_ERROR("strncpy", dst, src, n); \
       while (m++ < n) *dst++ = 0;         /* must pad remainder with nulls */ \
       \
@@ -585,7 +599,9 @@ static inline void my_exit ( int x )
       /* m non-nul bytes have now been copied, and m <= n-1. */ \
       /* Check for overlap after copying; all n bytes of dst are relevant, */ \
       /* but only m+1 bytes of src if terminator was found */ \
-      if (is_overlap(dst_orig, src_orig, n, (m < n) ? m+1 : n)) \
+      SizeT srclen = (m < n) ? m+1 : n; \
+      RECORD_COPY(srclen); \
+      if (is_overlap(dst_orig, src_orig, n, srclen)) \
           RECORD_OVERLAP_ERROR("strlcpy", dst, src, n); \
       /* Nul-terminate dst. */ \
       if (n > 0) *dst = 0; \
@@ -943,6 +959,7 @@ static inline void my_exit ( int x )
    void* VG_REPLACE_FUNCTION_EZZ(becTag,soname,fnname) \
             ( void *dst, const void *src, SizeT len ) \
    { \
+      RECORD_COPY(len); \
       if (do_ol_check && is_overlap(dst, src, len, len)) \
          RECORD_OVERLAP_ERROR("memcpy", dst, src, len); \
       \
@@ -1034,6 +1051,7 @@ static inline void my_exit ( int x )
  MEMCPY(VG_Z_LIBC_SONAME,  memcpy) /* fallback case */
  MEMCPY(VG_Z_LIBC_SONAME,    __GI_memcpy)
  MEMCPY(VG_Z_LIBC_SONAME,    __memcpy_sse2)
+ MEMCPY(VG_Z_LIBC_SONAME, __memcpy_avx_unaligned_erms)
  MEMCPY(VG_Z_LD_SO_1,      memcpy) /* ld.so.1 */
  MEMCPY(VG_Z_LD64_SO_1,    memcpy) /* ld64.so.1 */
  /* icc9 blats these around all over the place.  Not only in the main
@@ -1142,10 +1160,12 @@ static inline void my_exit ( int x )
       \
       /* This checks for overlap after copying, unavoidable without */ \
       /* pre-counting length... should be ok */ \
+      SizeT srclen = (Addr)src-(Addr)src_orig+1; \
+      RECORD_COPY(srclen); \
       if (is_overlap(dst_orig,  \
                      src_orig,  \
                      (Addr)dst-(Addr)dst_orig+1,  \
-                     (Addr)src-(Addr)src_orig+1)) \
+                     srclen)) \
          RECORD_OVERLAP_ERROR("stpcpy", dst_orig, src_orig, 0); \
       \
       return dst; \
@@ -1185,7 +1205,9 @@ static inline void my_exit ( int x )
       while (m   < n && *src) { m++; *dst++ = *src++; } \
       /* Check for overlap after copying; all n bytes of dst are relevant, */ \
       /* but only m+1 bytes of src if terminator was found */ \
-      if (is_overlap(dst_str, src_orig, n, (m < n) ? m+1 : n)) \
+      SizeT srclen = (m < n) ? m+1 : n; \
+      RECORD_COPY(srclen); \
+      if (is_overlap(dst_str, src_orig, n, srclen)) \
          RECORD_OVERLAP_ERROR("stpncpy", dst, src, n); \
       dst_str = dst; \
       while (m++ < n) *dst++ = 0;         /* must pad remainder with nulls */ \
@@ -1200,9 +1222,6 @@ static inline void my_exit ( int x )
 
 /*---------------------- memset ----------------------*/
 
-/* Why are we bothering to intercept this?  It seems entirely
-   pointless. */
-
 #define MEMSET(soname, fnname) \
    void* VG_REPLACE_FUNCTION_EZZ(20210,soname,fnname) \
             (void *s, Int c, SizeT n); \
@@ -1301,6 +1320,7 @@ static inline void my_exit ( int x )
    void VG_REPLACE_FUNCTION_EZU(20230,soname,fnname) \
             (const void *srcV, void *dstV, SizeT n) \
    { \
+      RECORD_COPY(n); \
       SizeT i; \
       HChar* dst = dstV; \
       const HChar* src = srcV; \
@@ -1338,6 +1358,7 @@ static inline void my_exit ( int x )
    void* VG_REPLACE_FUNCTION_EZU(20240,soname,fnname) \
             (void *dstV, const void *srcV, SizeT n, SizeT destlen) \
    { \
+      RECORD_COPY(n); \
       SizeT i; \
       HChar* dst = dstV;        \
       const HChar* src = srcV; \
@@ -1438,12 +1459,14 @@ static inline void my_exit ( int x )
    char* VG_REPLACE_FUNCTION_EZU(20270,soname,fnname) \
             (char* dst, const char* src, SizeT len) \
    { \
+      FOR_COPY(const HChar* src_orig = src); \
       HChar* ret = dst; \
       if (! len) \
          goto badness; \
       while ((*dst++ = *src++) != '\0') \
          if (--len == 0) \
             goto badness; \
+      RECORD_COPY((Addr)src-(Addr)src_orig); \
       return ret; \
      badness: \
       VALGRIND_PRINTF_BACKTRACE( \
@@ -1474,11 +1497,13 @@ static inline void my_exit ( int x )
    char* VG_REPLACE_FUNCTION_EZU(20280,soname,fnname) \
             (char* dst, const char* src, SizeT len) \
    { \
+      FOR_COPY(const HChar* src_orig = src); \
       if (! len) \
          goto badness; \
       while ((*dst++ = *src++) != '\0') \
          if (--len == 0) \
             goto badness; \
+      RECORD_COPY((Addr)src-(Addr)src_orig); \
       return dst - 1; \
      badness: \
       VALGRIND_PRINTF_BACKTRACE( \
@@ -1508,6 +1533,7 @@ static inline void my_exit ( int x )
    void* VG_REPLACE_FUNCTION_EZU(20290,soname,fnname) \
             ( void *dst, const void *src, SizeT len ) \
    { \
+      RECORD_COPY(len); \
       SizeT len_saved = len; \
       \
       if (len == 0) \
@@ -1557,15 +1583,13 @@ static inline void my_exit ( int x )
    { \
       register HChar *d; \
       register const HChar *s; \
-      \
-      if (dstlen < len) goto badness; \
-      \
+      if (dstlen < len) \
+         goto badness; \
+      RECORD_COPY(len); \
       if (len == 0) \
          return dst; \
-      \
       if (is_overlap(dst, src, len, len)) \
          RECORD_OVERLAP_ERROR("memcpy_chk", dst, src, len); \
-      \
       if ( dst > src ) { \
          d = (HChar *)dst + len - 1; \
          s = (const HChar *)src + len - 1; \
@@ -1977,11 +2001,14 @@ static inline void my_exit ( int x )
       \
       /* This checks for overlap after copying, unavoidable without */ \
       /* pre-counting length... should be ok */ \
+      /* +4 because sizeof(wchar_t) == 4 */ \
+      SizeT srclen = (Addr)src-(Addr)src_orig+4; \
+      RECORD_COPY(srclen); \
       if (is_overlap(dst_orig,  \
                      src_orig,  \
                      /* +4 because sizeof(wchar_t) == 4 */ \
                      (Addr)dst-(Addr)dst_orig+4, \
-                     (Addr)src-(Addr)src_orig+4)) \
+                     srclen)) \
          RECORD_OVERLAP_ERROR("wcscpy", dst_orig, src_orig, 0); \
       \
       return dst_orig; \