[PATCH 6/7] perf sched: Free callchain nodes in idle thread cleanup
From: Arnaldo Carvalho de Melo
Date: Sat Jun 06 2026 - 16:10:38 EST
From: Arnaldo Carvalho de Melo <acme@xxxxxxxxxx>
free_idle_threads() relies on the thread priv destructor (free()) to
clean up idle_thread_runtime structs. But free() doesn't walk the
callchain_cursor linked list or the callchain_root tree allocated
by callchain_cursor__copy() and callchain_append() during --idle-hist
processing. Every idle thread with callchain data leaks these nodes.
Introduce callchain_cursor_cleanup() to free the cursor's linked list
of callchain_cursor_node entries, and call it together with
free_callchain() in free_idle_threads() before thread__put().
Fixes: 225b24f569980ac9 ("perf sched timehist: Save callchain when entering idle")
Reported-by: sashiko-bot <sashiko-bot@xxxxxxxxxx>
Cc: Namhyung Kim <namhyung@xxxxxxxxxx>
Assisted-by: Claude Opus 4.6 <noreply@xxxxxxxxxxxxx>
Signed-off-by: Arnaldo Carvalho de Melo <acme@xxxxxxxxxx>
---
tools/perf/builtin-sched.c | 5 ++++-
tools/perf/util/callchain.c | 15 +++++++++++++++
tools/perf/util/callchain.h | 1 +
3 files changed, 20 insertions(+), 1 deletion(-)
diff --git a/tools/perf/builtin-sched.c b/tools/perf/builtin-sched.c
index b7ccdc6a985d1c7b..1ff01f03d2ad1ad3 100644
--- a/tools/perf/builtin-sched.c
+++ b/tools/perf/builtin-sched.c
@@ -2500,8 +2500,11 @@ static void free_idle_threads(void)
struct idle_thread_runtime *itr;
itr = thread__priv(idle);
- if (itr)
+ if (itr) {
thread__put(itr->last_thread);
+ free_callchain(&itr->callchain);
+ callchain_cursor_cleanup(&itr->cursor);
+ }
thread__put(idle);
}
diff --git a/tools/perf/util/callchain.c b/tools/perf/util/callchain.c
index 8981ae879ebb887c..31c675cbab63187b 100644
--- a/tools/perf/util/callchain.c
+++ b/tools/perf/util/callchain.c
@@ -1578,6 +1578,21 @@ void free_callchain(struct callchain_root *root)
free_callchain_node(&root->node);
}
+void callchain_cursor_cleanup(struct callchain_cursor *cursor)
+{
+ struct callchain_cursor_node *node, *next;
+
+ callchain_cursor_reset(cursor);
+
+ for (node = cursor->first; node; node = next) {
+ next = node->next;
+ free(node);
+ }
+ cursor->first = NULL;
+ cursor->last = &cursor->first;
+ cursor->curr = NULL;
+}
+
static u64 decay_callchain_node(struct callchain_node *node)
{
struct callchain_node *child;
diff --git a/tools/perf/util/callchain.h b/tools/perf/util/callchain.h
index 0eb5d7a1a41d81e1..8b1e405d1cdf4660 100644
--- a/tools/perf/util/callchain.h
+++ b/tools/perf/util/callchain.h
@@ -286,6 +286,7 @@ int callchain_list_counts__printf_value(struct callchain_list *clist,
FILE *fp, char *bf, int bfsize);
void free_callchain(struct callchain_root *root);
+void callchain_cursor_cleanup(struct callchain_cursor *cursor);
void decay_callchain(struct callchain_root *root);
int callchain_node__make_parent_list(struct callchain_node *node);
--
2.54.0