[PATCH] perf kmem: Report net allocation bytes per entry

From: Olivier Blin
Date: Wed Apr 17 2024 - 10:49:25 EST


This allows to detect memory leaks more easily, by reporting net
allocation bytes per caller or pointer in a new column.

This also adds a "net" sort key, so that "perf kmem --caller -s net stat"
reports the main leakers first.

Signed-off-by: Olivier Blin <olivier.blin@xxxxxxxxxxxxxx>
---
tools/perf/builtin-kmem.c | 54 ++++++++++++++++++++++++++++++---------
1 file changed, 42 insertions(+), 12 deletions(-)

diff --git a/tools/perf/builtin-kmem.c b/tools/perf/builtin-kmem.c
index 6fd95be5032b..59bdca64b22b 100644
--- a/tools/perf/builtin-kmem.c
+++ b/tools/perf/builtin-kmem.c
@@ -64,6 +64,7 @@ struct alloc_stat {
u64 bytes_req;
u64 bytes_alloc;
u64 last_alloc;
+ s64 net_alloc;
u32 hit;
u32 pingpong;

@@ -118,6 +119,7 @@ static int insert_alloc_stat(unsigned long call_site, unsigned long ptr,
data->hit = 1;
data->bytes_req = bytes_req;
data->bytes_alloc = bytes_alloc;
+ data->net_alloc = 0;

rb_link_node(&data->node, parent, node);
rb_insert_color(&data->node, &root_alloc_stat);
@@ -125,6 +127,7 @@ static int insert_alloc_stat(unsigned long call_site, unsigned long ptr,
data->call_site = call_site;
data->alloc_cpu = cpu;
data->last_alloc = bytes_alloc;
+ data->net_alloc += bytes_alloc;

return 0;
}
@@ -163,11 +166,14 @@ static int insert_caller_stat(unsigned long call_site,
data->hit = 1;
data->bytes_req = bytes_req;
data->bytes_alloc = bytes_alloc;
+ data->net_alloc = 0;

rb_link_node(&data->node, parent, node);
rb_insert_color(&data->node, &root_caller_stat);
}

+ data->net_alloc += bytes_alloc;
+
return 0;
}

@@ -254,14 +260,19 @@ static int evsel__process_free_event(struct evsel *evsel, struct perf_sample *sa

total_freed += s_alloc->last_alloc;

+ s_alloc->net_alloc -= s_alloc->last_alloc;
+
+ s_caller = search_alloc_stat(0, s_alloc->call_site,
+ &root_caller_stat,
+ slab_callsite_cmp);
+ if (!s_caller)
+ return -1;
+
+ s_caller->net_alloc -= s_alloc->last_alloc;
+
if ((short)sample->cpu != s_alloc->alloc_cpu) {
s_alloc->pingpong++;

- s_caller = search_alloc_stat(0, s_alloc->call_site,
- &root_caller_stat,
- slab_callsite_cmp);
- if (!s_caller)
- return -1;
s_caller->pingpong++;
}
s_alloc->alloc_cpu = -1;
@@ -1010,10 +1021,10 @@ static void __print_slab_result(struct rb_root *root,
struct rb_node *next;
struct machine *machine = &session->machines.host;

- printf("%.105s\n", graph_dotted_line);
+ printf("%.117s\n", graph_dotted_line);
printf(" %-34s |", is_caller ? "Callsite": "Alloc Ptr");
- printf(" Total_alloc/Per | Total_req/Per | Hit | Ping-pong | Frag\n");
- printf("%.105s\n", graph_dotted_line);
+ printf(" Total_alloc/Per | Total_req/Per | Hit | Net_alloc | Ping-pong | Frag\n");
+ printf("%.117s\n", graph_dotted_line);

next = rb_first(root);

@@ -1039,12 +1050,13 @@ static void __print_slab_result(struct rb_root *root,
snprintf(buf, sizeof(buf), "%#" PRIx64 "", addr);
printf(" %-34s |", buf);

- printf(" %9llu/%-5lu | %9llu/%-5lu | %8lu | %9lu | %6.3f%%\n",
+ printf(" %9llu/%-5lu | %9llu/%-5lu | %8lu | %9lld | %9lu | %6.3f%%\n",
(unsigned long long)data->bytes_alloc,
(unsigned long)data->bytes_alloc / data->hit,
(unsigned long long)data->bytes_req,
(unsigned long)data->bytes_req / data->hit,
(unsigned long)data->hit,
+ (long long)data->net_alloc,
(unsigned long)data->pingpong,
fragmentation(data->bytes_req, data->bytes_alloc));

@@ -1052,9 +1064,9 @@ static void __print_slab_result(struct rb_root *root,
}

if (n_lines == -1)
- printf(" ... | ... | ... | ... | ... | ... \n");
+ printf(" ... | ... | ... | ... | ... | ... | ... \n");

- printf("%.105s\n", graph_dotted_line);
+ printf("%.117s\n", graph_dotted_line);
}

static const char * const migrate_type_str[] = {
@@ -1496,6 +1508,23 @@ static struct sort_dimension bytes_sort_dimension = {
.cmp = bytes_cmp,
};

+static int net_cmp(void *a, void *b)
+{
+ struct alloc_stat *l = a;
+ struct alloc_stat *r = b;
+
+ if (l->net_alloc < r->net_alloc)
+ return -1;
+ else if (l->net_alloc > r->net_alloc)
+ return 1;
+ return 0;
+}
+
+static struct sort_dimension net_sort_dimension = {
+ .name = "net",
+ .cmp = net_cmp,
+};
+
static int frag_cmp(void *a, void *b)
{
double x, y;
@@ -1667,6 +1696,7 @@ static struct sort_dimension *slab_sorts[] = {
&callsite_sort_dimension,
&hit_sort_dimension,
&bytes_sort_dimension,
+ &net_sort_dimension,
&frag_sort_dimension,
&pingpong_sort_dimension,
};
@@ -1951,7 +1981,7 @@ int cmd_kmem(int argc, const char **argv)
OPT_CALLBACK_NOOPT(0, "alloc", NULL, NULL,
"show per-allocation statistics", parse_alloc_opt),
OPT_CALLBACK('s', "sort", NULL, "key[,key2...]",
- "sort by keys: ptr, callsite, bytes, hit, pingpong, frag, "
+ "sort by keys: ptr, callsite, bytes, hit, net, pingpong, frag, "
"page, order, migtype, gfp", parse_sort_opt),
OPT_CALLBACK('l', "line", NULL, "num", "show n lines", parse_line_opt),
OPT_BOOLEAN(0, "raw-ip", &raw_ip, "show raw ip instead of symbol"),
--
2.34.1

-- This message and any attachments herein are confidential, intended solely for the addressees and are SoftAtHome’s ownership. Any unauthorized use or dissemination is prohibited. If you are not the intended addressee of this message, please cancel it immediately and inform the sender.