perf c2c: add function view stats merge and memory management

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@intel.com>
Reviewed-by: Tianyou Li <tianyou.li@intel.com>
Reviewed-by: Wangyang Guo <wangyang.guo@intel.com>
Reviewed-by: Ian Rogers <irogers@google.com>
Cc: Dapeng Mi <dapeng1.mi@linux.intel.com>
Cc: James Clark <james.clark@linaro.org>
Cc: Thomas Falcon <thomas.falcon@intel.com>
Signed-off-by: Namhyung Kim <namhyung@kernel.org>
This commit is contained in:
Jiebin Sun 2026-08-17 17:46:19 +08:00 committed by Namhyung Kim
parent e888662432
commit 929c4ebb54

View File

@ -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;
}