Re: [PATCH v9 5/9] perf c2c: add function view stats merge and memory management
Ian Rogers <[email protected]>
| Newsgroups | org.kernel.vger.linux-perf-users,org.kernel.vger.linux-kernel |
|---|---|
| Message-ID | <CAP-5=fUo-EXrAqqd-gP=0ya0kHwMdrb0vhLR-YiL=zKUjVym3w@mail.gmail.com> |
On Mon, Aug 17, 2026 at 2:40 AM Jiebin Sun <[email protected]> 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 <[email protected]> > Cc: Adrian Hunter <[email protected]> > Cc: Alexander Shishkin <[email protected]> > Cc: Arnaldo Carvalho de Melo <[email protected]> > Cc: Dapeng Mi <[email protected]> > Cc: Ian Rogers <[email protected]> > Cc: Ingo Molnar <[email protected]> > Cc: James Clark <[email protected]> > Cc: Jiri Olsa <[email protected]> > Cc: Mark Rutland <[email protected]> > Cc: Namhyung Kim <[email protected]> > Cc: Peter Zijlstra <[email protected]> > Cc: Thomas Falcon <[email protected]> > Reviewed-by: Tianyou Li <[email protected]> > Reviewed-by: Wangyang Guo <[email protected]> Reviewed-by: Ian Rogers <[email protected]> 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 >