From: Arnaldo Carvalho de Melo Date: Sat, 6 Jun 2026 14:33:41 +0000 (-0300) Subject: perf sched: Free callchain nodes in idle thread cleanup X-Git-Url: http://git.ipfire.org/gitweb/?a=commitdiff_plain;h=c3e51ed45ffa7547495a851e33ce332f81ef3665;p=thirdparty%2Fkernel%2Flinux.git perf sched: Free callchain nodes in idle thread cleanup 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 Cc: Namhyung Kim Assisted-by: Claude:claude-opus-4.6 Signed-off-by: Arnaldo Carvalho de Melo --- diff --git a/tools/perf/builtin-sched.c b/tools/perf/builtin-sched.c index b7ccdc6a985d1..1ff01f03d2ad1 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 8981ae879ebb8..31c675cbab631 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 0eb5d7a1a41d8..8b1e405d1cdf4 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);