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