Re: [PATCH v9 5/9] perf c2c: add function view stats merge and memory management
From: Ian Rogers
Date: Mon Aug 17 2026 - 17:06:01 EST
On Mon, Aug 17, 2026 at 2:40 AM Jiebin Sun <jiebin.sun@xxxxxxxxx> wrote:
>
> Add the per-entry stats/cstats aggregation helpers and hierarchy teardown.
> Child common fields are released through hist_entry__delete(), while the
> function-view free callback handles the private child tree and containing
> allocation. Also add a helper for pruning writer entries with no stores or
> cacheline children.
>
> These are used by the entry-creation and builder patches that follow and
> are __maybe_unused until then.
>
> Signed-off-by: Jiebin Sun <jiebin.sun@xxxxxxxxx>
> Cc: Adrian Hunter <adrian.hunter@xxxxxxxxx>
> Cc: Alexander Shishkin <alexander.shishkin@xxxxxxxxxxxxxxx>
> Cc: Arnaldo Carvalho de Melo <acme@xxxxxxxxxx>
> Cc: Dapeng Mi <dapeng1.mi@xxxxxxxxxxxxxxx>
> Cc: Ian Rogers <irogers@xxxxxxxxxx>
> Cc: Ingo Molnar <mingo@xxxxxxxxxx>
> Cc: James Clark <james.clark@xxxxxxxxxx>
> Cc: Jiri Olsa <jolsa@xxxxxxxxxx>
> Cc: Mark Rutland <mark.rutland@xxxxxxx>
> Cc: Namhyung Kim <namhyung@xxxxxxxxxx>
> Cc: Peter Zijlstra <peterz@xxxxxxxxxxxxx>
> Cc: Thomas Falcon <thomas.falcon@xxxxxxxxx>
> Reviewed-by: Tianyou Li <tianyou.li@xxxxxxxxx>
> Reviewed-by: Wangyang Guo <wangyang.guo@xxxxxxxxx>
Reviewed-by: Ian Rogers <irogers@xxxxxxxxxx>
Thanks,
Ian
> ---
> tools/perf/util/c2c-function.c | 163 +++++++++++++++++++++++++++++++++
> 1 file changed, 163 insertions(+)
>
> diff --git a/tools/perf/util/c2c-function.c b/tools/perf/util/c2c-function.c
> index d27544016660..aa6be7c72977 100644
> --- a/tools/perf/util/c2c-function.c
> +++ b/tools/perf/util/c2c-function.c
> @@ -680,3 +680,166 @@ c2c_function_hists__reinit(struct c2c_hists *c2c_hists,
>
> return function_hpp_list__parse(&c2c_hists->list, output, sort, env);
> }
> +
> +/* Welford online merge of two "stats" (from util/stat.h) accumulators. */
> +static void c2c_stats_merge(struct stats *dest, const struct stats *src)
> +{
> + double delta;
> +
> + if (src->n == 0)
> + return;
> +
> + if (dest->n == 0) {
> + *dest = *src;
> + return;
> + }
> +
> + delta = src->mean - dest->mean;
> + dest->M2 += src->M2 + delta * delta * dest->n * src->n / (dest->n + src->n);
> + dest->mean = (dest->mean * dest->n + src->mean * src->n) / (dest->n + src->n);
> + dest->n += src->n;
> +
> + /* Update min/max */
> + if (src->max > dest->max)
> + dest->max = src->max;
> + if (src->min < dest->min)
> + dest->min = src->min;
> +}
> +
> +/* Merge compute_stats during function aggregation. */
> +static void __maybe_unused c2c_add_cstats(struct compute_stats *dest,
> + const struct compute_stats *src)
> +{
> + c2c_stats_merge(&dest->rmt_hitm, &src->rmt_hitm);
> + c2c_stats_merge(&dest->lcl_hitm, &src->lcl_hitm);
> + c2c_stats_merge(&dest->rmt_peer, &src->rmt_peer);
> + c2c_stats_merge(&dest->lcl_peer, &src->lcl_peer);
> + c2c_stats_merge(&dest->load, &src->load);
> +}
> +
> +static bool __maybe_unused hist_entry__add_c2c_stats(struct hist_entry *he,
> + const struct c2c_stats *stats)
> +{
> + u64 nr_events = c2c_hitm_count(stats) + stats->rmt_peer + stats->lcl_peer;
> + u64 weight1 = c2c_hitm_count(stats);
> +
> + /*
> + * Allocate before touching he->stat, so a failure here leaves the
> + * entry unmodified and the caller can bail out without having
> + * half-updated the statistics.
> + */
> + if (symbol_conf.cumulate_callchain && !he->stat_acc) {
> + he->stat_acc = calloc(1, sizeof(struct he_stat));
> + if (!he->stat_acc)
> + return false;
> + }
> +
> + he->stat.nr_events += nr_events;
> + he->stat.period += nr_events;
> + he->stat.weight1 += weight1;
> +
> + if (!symbol_conf.cumulate_callchain)
> + return true;
> +
> + he->stat_acc->nr_events += nr_events;
> + he->stat_acc->period += nr_events;
> + he->stat_acc->weight1 += weight1;
> +
> + return true;
> +}
> +
> +static void c2c_he__free_hierarchy(struct hist_entry *he);
> +
> +/*
> + * Free a function-view histogram entry (hist_entry_ops::free).
> + */
> +static void __maybe_unused c2c_function_he_free(void *ptr)
> +{
> + struct hist_entry *he = ptr;
> + struct c2c_hist_entry *c2c_he;
> +
> + c2c_he = container_of(he, struct c2c_hist_entry, he);
> +
> + if (c2c_he->hists) {
> + perf_hpp__reset_output_field(&c2c_he->hists->list);
> + hists__delete_all_entries(&c2c_he->hists->hists);
> + zfree(&c2c_he->hists);
> + }
> +
> + c2c_he__free_hierarchy(he);
> +
> + zfree(&c2c_he->nodeset);
> + zfree(&c2c_he->cpuset);
> + zfree(&c2c_he->nodestr);
> + zfree(&c2c_he->node_stats);
> +
> + free(c2c_he);
> +}
> +
> +static void c2c_he__free_hierarchy(struct hist_entry *he)
> +{
> + struct rb_node *nd;
> + struct hist_entry *child_he;
> +
> + /*
> + * A leaf entry stores its callchains in the sorted_chain member, which
> + * shares a union with the hroot_in/hroot_out child trees, so its
> + * hroot_out is not a valid subtree to walk. Leaf entries never have a
> + * child hierarchy here, so stop before touching hroot_out.
> + */
> + if (he->leaf)
> + return;
> +
> + if (RB_EMPTY_ROOT(&he->hroot_out.rb_root))
> + return;
> +
> + nd = rb_first_cached(&he->hroot_out);
> + while (nd) {
> + struct rb_node *next = rb_next(nd);
> +
> + child_he = rb_entry(nd, struct hist_entry, rb_node);
> + rb_erase_cached(&child_he->rb_node, &he->hroot_out);
> + hist_entry__delete(child_he);
> +
> + nd = next;
> + }
> +
> + /* All children erased; clear the tree (and its cached leftmost). */
> + he->hroot_out = RB_ROOT_CACHED;
> +}
> +
> +/*
> + * Drop level-2 writing functions that carry no stores or
> + * no cacheline children. Writers are only added when they store into a shared
> + * line, so this is mainly a safety net. Returns the number of surviving
> + * writers.
> + */
> +static int __maybe_unused c2c_he__prune_empty_writers(struct hist_entry *l1_he)
> +{
> + struct rb_node *nd;
> + int surviving = 0;
> +
> + if (!l1_he->has_children)
> + return 0;
> +
> + nd = rb_first_cached(&l1_he->hroot_out);
> + while (nd) {
> + struct rb_node *next = rb_next(nd);
> + struct hist_entry *l2_he = rb_entry(nd, struct hist_entry, rb_node);
> +
> + if (l2_he->has_children && hist_entry__displayed_stores(l2_he) > 0) {
> + surviving++;
> + } else {
> + rb_erase_cached(&l2_he->rb_node, &l1_he->hroot_out);
> + hist_entry__delete(l2_he);
> + }
> + nd = next;
> + }
> +
> + if (!surviving) {
> + l1_he->hroot_out = RB_ROOT_CACHED;
> + l1_he->has_children = false;
> + l1_he->unfolded = false;
> + }
> + return surviving;
> +}
> --
> 2.52.0
>