From 37c15afc878ebf48575e234a380ca4dd71fb625a Mon Sep 17 00:00:00 2001 From: Honggyu Kim Date: Thu, 5 Jan 2023 20:42:26 +0900 Subject: [PATCH] heaptrace: Support mulitple sort keys Bojun requested to make heaptrace support multiple sort keys because sometimes it's useful to show the memory allocation status based on both size and count orders. If someone wants to show allocation status based on count and size, then it can be requested as follows. $ heaptrace -s size,count The sort orders can be separated by commas(','). Another small change is that it removes heap traced allocation size. Closes: #9 Signed-off-by: Honggyu Kim --- heaptrace.cc | 8 ++-- heaptrace.h | 2 +- libheaptrace.cc | 9 ++-- sighandler.cc | 4 +- stacktrace.cc | 123 ++++++++++++++++++++++++++++-------------------- stacktrace.h | 8 +--- 6 files changed, 82 insertions(+), 72 deletions(-) diff --git a/heaptrace.cc b/heaptrace.cc index 6fe97de..3158ef9 100644 --- a/heaptrace.cc +++ b/heaptrace.cc @@ -31,7 +31,7 @@ enum options { static struct argp_option heaptrace_options[] = { { "help", 'h', 0, 0, "Give this help list" }, { "top", OPT_top, "NUM", 0, "Set number of top backtraces to show (default 10)" }, - { "sort", 's', "KEY", 0, "Sort backtraces based on KEY (size or count)" }, + { "sort", 's', "KEYs", 0, "Sort backtraces based on KEYs (size or count)" }, { "flame-graph", OPT_flamegraph, 0, 0, "Print heap trace info in flamegraph format" }, { "outfile", OPT_outfile, "FILE", 0, "Save log messages to this file" }, { 0 } @@ -51,7 +51,7 @@ static error_t parse_option(int key, char *arg, struct argp_state *state) break; case 's': - opts->sortkey = arg; + opts->sort_keys = arg; break; case OPT_flamegraph: @@ -102,7 +102,7 @@ static void init_options(int argc, char *argv[]) // set default option values opts.top = 10; - opts.sortkey = "size"; + opts.sort_keys = "size"; opts.flamegraph = false; argp_parse(&argp, argc, argv, ARGP_IN_ORDER, NULL, &opts); @@ -129,7 +129,7 @@ static void setup_child_environ(struct opts *opts, int argc, char *argv[]) snprintf(buf, sizeof(buf), "%d", opts->top); setenv("HEAPTRACE_NUM_TOP_BACKTRACE", buf, 1); - setenv("HEAPTRACE_SORTKEY", opts->sortkey, 1); + setenv("HEAPTRACE_SORT_KEYS", opts->sort_keys, 1); snprintf(buf, sizeof(buf), "%d", opts->flamegraph); setenv("HEAPTRACE_FLAME_GRAPH", buf, 1); diff --git a/heaptrace.h b/heaptrace.h index ce3ef8e..0172b5c 100644 --- a/heaptrace.h +++ b/heaptrace.h @@ -39,7 +39,7 @@ struct opts { char *exename; int top; - const char *sortkey; + const char *sort_keys; bool flamegraph; char *outfile; }; diff --git a/libheaptrace.cc b/libheaptrace.cc index 81462b3..0c064ca 100644 --- a/libheaptrace.cc +++ b/libheaptrace.cc @@ -13,6 +13,7 @@ #include #include +#include #include "heaptrace.h" #include "compiler.h" @@ -89,7 +90,7 @@ static void heaptrace_init() // setup option values opts.top = strtol(getenv("HEAPTRACE_NUM_TOP_BACKTRACE"), NULL, 0); - opts.sortkey = getenv("HEAPTRACE_SORTKEY"); + opts.sort_keys = getenv("HEAPTRACE_SORT_KEYS"); opts.flamegraph = strtol(getenv("HEAPTRACE_FLAME_GRAPH"), NULL, 0); opts.outfile = getenv("HEAPTRACE_OUTFILE"); @@ -114,7 +115,6 @@ static void heaptrace_fini() { auto* tfs = &thread_flags; int pid = getpid(); - enum alloc_sort_order order = ALLOC_SIZE; std::string comm = utils::get_comm_name(); if (!opts.flamegraph) { @@ -122,10 +122,7 @@ static void heaptrace_fini() pid, comm.c_str()); } - if (!strcmp(opts.sortkey, "count")) - order = ALLOC_COUNT; - - dump_stackmap(order, opts.flamegraph); + dump_stackmap(opts.sort_keys, opts.flamegraph); if (opts.outfile) fclose(outfp); diff --git a/sighandler.cc b/sighandler.cc index 2ffb30c..971483e 100644 --- a/sighandler.cc +++ b/sighandler.cc @@ -8,13 +8,13 @@ static void sigusr1_handler(int signo) { pr_dbg("\n=== sigusr1_handler(%d) ===\n", signo); - dump_stackmap(ALLOC_SIZE, opts.flamegraph); + dump_stackmap("size", opts.flamegraph); } static void sigusr2_handler(int signo) { pr_dbg("\n=== sigusr2_handler(%d) ===\n", signo); - dump_stackmap(ALLOC_COUNT, opts.flamegraph); + dump_stackmap("count", opts.flamegraph); } static void sigquit_handler(int signo) diff --git a/stacktrace.cc b/stacktrace.cc index adc0060..b991525 100644 --- a/stacktrace.cc +++ b/stacktrace.cc @@ -245,23 +245,47 @@ std::string read_statm() { return str; } -static void print_dump_stackmap(const time_point_t& current, struct mallinfo& info, - std::vector>& sorted_stack) +static void print_dump_stackmap_header(const char *sort_key) { - int cnt = 0; + pr_out("[heaptrace] dump allocation sorted by '%s' for /proc/%d/maps (%s)\n", + sort_key, utils::gettid(), utils::get_comm_name().c_str()); +} + +static void print_dump_stackmap_footer( + const std::vector>& sorted_stack) +{ + // get allocated size info from the allocator + struct mallinfo minfo = mallinfo(); + uint64_t total_size = 0; - int tid = utils::gettid(); + size_t stack_size = sorted_stack.size(); + for (int i = 0; i < stack_size; i++) { + const stack_info_t& sinfo = sorted_stack[i].second; + total_size += sinfo.total_size; + } + + pr_out("[heaptrace] heap traced num of backtrace : %zd\n", stack_size); + + pr_out("[heaptrace] heap traced allocation size : %s\n", + get_byte_unit(total_size).c_str()); - pr_out("=================================================================\n"); - pr_out("[heaptrace] dump allocation status for /proc/%d/maps (%s)\n", - tid, utils::get_comm_name().c_str()); + pr_out("[heaptrace] allocator info (virtual) : %s\n", + get_byte_unit(minfo.arena + minfo.hblkhd).c_str()); + pr_out("[heaptrace] allocator info (resident) : %s\n", + get_byte_unit(minfo.uordblks).c_str()); + + pr_out("[heaptrace] statm info (VSS/RSS/shared) : %s\n", read_statm().c_str()); +} + +static void print_dump_stackmap(std::vector>& sorted_stack) +{ + const time_point_t current = std::chrono::steady_clock::now(); + int cnt = 0; size_t stack_size = sorted_stack.size(); for (int i = 0; i < stack_size; i++) { const stack_info_t& info = sorted_stack[i].second; - total_size += info.total_size; - if (i >= opts.top) continue; @@ -280,22 +304,6 @@ static void print_dump_stackmap(const time_point_t& current, struct mallinfo& in pr_out("\n"); } - - pr_out("[heaptrace] heap traced num of backtrace : %zd\n", - stack_size); - pr_out("[heaptrace] heap traced allocation size : %s\n", - get_byte_unit(total_size).c_str()); - - pr_out("[heaptrace] allocator info (virtual) : %s\n", - get_byte_unit(info.arena + info.hblkhd).c_str()); - pr_out("[heaptrace] allocator info (resident) : %s\n", - get_byte_unit(info.uordblks).c_str()); - - pr_out("[heaptrace] statm info (VSS/RSS/shared) : %s\n", - read_statm().c_str()); - pr_out("=================================================================\n"); - - fflush(outfp); } static void print_dump_stackmap_flamegraph(std::vector>& sorted_stack) @@ -322,21 +330,36 @@ static void print_dump_stackmap_flamegraph(std::vector>& sorted_stack) +{ + std::sort(sorted_stack.begin(), sorted_stack.end(), + [&order](const std::pair& p1, + const std::pair& p2) { + if (order == "count") { + if (p1.second.count == p2.second.count) + return p1.second.total_size > p2.second.total_size; + return p1.second.count > p2.second.count; + } + else { + // sort based on size for unknown sort order. + if (p1.second.total_size == p2.second.total_size) + return p1.second.count > p2.second.count; + return p1.second.total_size > p2.second.total_size; + } + }); +} + +void dump_stackmap(const char *sort_keys, bool flamegraph) { auto* tfs = &thread_flags; - time_point_t current; if (stackmap.empty()) return; - tfs->hook_guard = true; - - // get allocated size info from the allocator - struct mallinfo info = mallinfo(); + std::vector sort_key_vec = utils::string_split(sort_keys, ','); - // get current time - current = std::chrono::steady_clock::now(); + tfs->hook_guard = true; // sort the stack trace based on the count and then total_size std::vector> sorted_stack; @@ -347,27 +370,23 @@ void dump_stackmap(enum alloc_sort_order order, bool flamegraph) for (auto& p : stackmap) sorted_stack.push_back(make_pair(p.first, p.second)); } - std::sort(sorted_stack.begin(), sorted_stack.end(), - [order](const std::pair& p1, - const std::pair& p2) { - if (order == ALLOC_COUNT) { - if (p1.second.count == p2.second.count) - return p1.second.total_size > p2.second.total_size; - return p1.second.count > p2.second.count; - } - else if (order == ALLOC_SIZE) { - if (p1.second.total_size == p2.second.total_size) - return p1.second.count > p2.second.count; - return p1.second.total_size > p2.second.total_size; - } - // not implemented yet - abort(); - }); - if (flamegraph) + if (flamegraph) { + // use only the first sort order given by -s/--sort option. + sort_stack(sort_key_vec.front(), sorted_stack); print_dump_stackmap_flamegraph(sorted_stack); - else - print_dump_stackmap(current, info, sorted_stack); + } + else { + pr_out("=================================================================\n"); + for (const auto& sort_key : sort_key_vec) { + print_dump_stackmap_header(sort_key.c_str()); + sort_stack(sort_key, sorted_stack); + print_dump_stackmap(sorted_stack); + } + print_dump_stackmap_footer(sorted_stack); + pr_out("=================================================================\n"); + fflush(outfp); + } tfs->hook_guard = false; } diff --git a/stacktrace.h b/stacktrace.h index 2265e6e..79c15aa 100644 --- a/stacktrace.h +++ b/stacktrace.h @@ -32,12 +32,6 @@ struct object_info_t { uint64_t size; }; -enum alloc_sort_order { - ALLOC_COUNT, - ALLOC_SIZE, - ALLOC_AGE, -}; - void __record_backtrace(size_t size, void* addr, stack_trace_t& stack_trace, int nptrs); @@ -58,7 +52,7 @@ inline void record_backtrace(size_t size, void* addr) void release_backtrace(void* addr); -void dump_stackmap(enum alloc_sort_order order, bool flamegraph = false); +void dump_stackmap(const char *sort_str, bool flamegraph = false); void clear_stackmap(void);