From 401fb0e87a2cea7171e4d37da6094755eb10a972 Mon Sep 17 00:00:00 2001 From: Willy Tarreau Date: Thu, 24 Oct 2024 10:46:06 +0200 Subject: [PATCH] MINOR: activity/memprofile: show per-DSO stats On systems where many libs are loaded, it's hard to track suspected leaks. Having a per-DSO summary makes it more convenient. That's what we're doing here by summarizing all calls per DSO before showing the total. --- src/activity.c | 91 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) diff --git a/src/activity.c b/src/activity.c index c38deed085..1c43b24554 100644 --- a/src/activity.c +++ b/src/activity.c @@ -849,6 +849,97 @@ static int cli_io_handler_show_profiling(struct appctx *appctx) } } + /* last step: summarize by DSO. We create one entry per new DSO in + * tmp_memstats, which is thus destroyed. The DSO's name is allocated + * and stored into tmp_stats.info. Must be freed at the end. We store + * dso entries total. There are very few so we do that in a single + * pass and append it after the total. + */ + for (i = max = 0; i < max_lines; i++) { + struct memprof_stats *entry = &tmp_memstats[i]; + char *p; + + if (!entry->alloc_calls && !entry->free_calls) + continue; + + chunk_reset(name_buffer); + if (!entry->caller) + chunk_printf(name_buffer, "other:"); + else + resolve_sym_name(name_buffer, "", entry->caller); + + /* figure the DSO name (before colon) otherwise "*program*" */ + p = strchr(name_buffer->area, ':'); + if (p) { + *p = 0; + p = name_buffer->area; + } + else + p = "*program*"; + + /* look it up among known names (0..max) */ + for (j = 0; j < max; j++) { + if (tmp_memstats[j].info && strcmp(p, tmp_memstats[j].info) == 0) + break; + } + + if (j == max) { + /* not found, create a new entry at . We need to be + * careful as it could be the same as (i)! + */ + max++; + + if (j != i) // set max to keep min caller's address + tmp_memstats[j].caller = (void*)-1; + + tmp_memstats[j].info = strdup(p); // may fail, but checked when used + tmp_memstats[j].alloc_calls = entry->alloc_calls; + tmp_memstats[j].alloc_tot = entry->alloc_tot; + if ((entry->method != MEMPROF_METH_P_ALLOC) && + (entry->method != MEMPROF_METH_MALLOC) && + (entry->method != MEMPROF_METH_CALLOC)) { + tmp_memstats[j].free_calls = entry->free_calls; + tmp_memstats[j].free_tot = entry->free_tot; + } + } else { + tmp_memstats[j].alloc_calls += entry->alloc_calls; + tmp_memstats[j].alloc_tot += entry->alloc_tot; + if ((entry->method != MEMPROF_METH_P_ALLOC) && + (entry->method != MEMPROF_METH_MALLOC) && + (entry->method != MEMPROF_METH_CALLOC)) { + tmp_memstats[j].free_calls += entry->free_calls; + tmp_memstats[j].free_tot += entry->free_tot; + } + } + + if (entry->caller && + tmp_memstats[j].caller > entry->caller) + tmp_memstats[j].caller = entry->caller; // keep lowest address + } + + /* now we have entries 0..max-1 that are filled with per-DSO stats. This is + * compact enough to fit next to the total line in one buffer, hence no + * state kept. + */ + chunk_appendf(&trash, + "-----------------------|-----------------------------| " + " - min caller - | -- by DSO below --\n"); + + for (i = 0; i < max; i++) { + struct memprof_stats *entry = &tmp_memstats[i]; + + chunk_appendf(&trash, "%11llu %11llu %14llu %14llu| %16p DSO:%s;", + entry->alloc_calls, entry->free_calls, + entry->alloc_tot, entry->free_tot, + entry->caller, entry->info ? (const char*)entry->info : "other"); + + if (entry->alloc_tot != entry->free_tot) + chunk_appendf(&trash, " delta_calls=%lld; delta_bytes=%lld", + (long long)(entry->alloc_calls - entry->free_calls), + (long long)(entry->alloc_tot - entry->free_tot)); + chunk_appendf(&trash, "\n"); + } + chunk_appendf(&trash, "-----------------------|-----------------------------|\n" "%11llu %11llu %14llu %14llu| <- Total; Delta_calls=%lld; Delta_bytes=%lld\n",