Skip to content

Commit

Permalink
heaptrace: Support mulitple sort keys
Browse files Browse the repository at this point in the history
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 <target program>

The sort orders can be separated by commas(',').

Another small change is that it removes heap traced allocation size.

Closes: #9, #11
Signed-off-by: Honggyu Kim <[email protected]>
  • Loading branch information
honggyukim committed Jan 21, 2023
1 parent f1a90ab commit 703b2ea
Show file tree
Hide file tree
Showing 6 changed files with 82 additions and 72 deletions.
8 changes: 4 additions & 4 deletions heaptrace.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand All @@ -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:
Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion heaptrace.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ struct opts {
char *exename;

int top;
const char *sortkey;
const char *sort_keys;
bool flamegraph;
char *outfile;
};
Expand Down
9 changes: 3 additions & 6 deletions libheaptrace.cc
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include <sys/mman.h>

#include <sstream>
#include <string>

#include "heaptrace.h"
#include "compiler.h"
Expand Down Expand Up @@ -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");
Expand All @@ -114,18 +115,14 @@ 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) {
pr_out("[heaptrace] finalized for /proc/%d/maps (%s)\n",
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);
Expand Down
4 changes: 2 additions & 2 deletions sighandler.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
123 changes: 71 additions & 52 deletions stacktrace.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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<std::pair<stack_trace_t, stack_info_t>>& 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<std::pair<stack_trace_t, stack_info_t>>& 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<std::pair<stack_trace_t, stack_info_t>>& 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;

Expand All @@ -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<std::pair<stack_trace_t, stack_info_t>>& sorted_stack)
Expand All @@ -322,21 +330,36 @@ static void print_dump_stackmap_flamegraph(std::vector<std::pair<stack_trace_t,
fflush(outfp);
}

void dump_stackmap(enum alloc_sort_order order, bool flamegraph)
static void sort_stack(const std::string &order,
std::vector<std::pair<stack_trace_t, stack_info_t>>& sorted_stack)
{
std::sort(sorted_stack.begin(), sorted_stack.end(),
[&order](const std::pair<stack_trace_t, stack_info_t>& p1,
const std::pair<stack_trace_t, stack_info_t>& 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<std::string> 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<std::pair<stack_trace_t, stack_info_t>> sorted_stack;
Expand All @@ -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<stack_trace_t, stack_info_t>& p1,
const std::pair<stack_trace_t, stack_info_t>& 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;
}
Expand Down
8 changes: 1 addition & 7 deletions stacktrace.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -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);

Expand Down

0 comments on commit 703b2ea

Please sign in to comment.