perf evlist: Make uniquifying counter names consistent

'perf stat' has different uniquification logic to 'perf record' and perf
top. In the case of perf record and 'perf top' all hybrid event names
are uniquified.

'perf stat' is more disciplined respecting name config terms, libpfm4
events, etc.

'perf stat' will uniquify hybrid events and the non-core PMU cases
shouldn't apply to perf record or 'perf top'.

For consistency, remove the uniquification for 'perf record' and 'perf
top' and reuse the 'perf stat' uniquification, making the code more
globally visible for this.

Fix the detection of cross-PMU for disabling uniquify by correctly
setting last_pmu.

When setting uniquify on an evsel, make sure the PMUs between the 2
considered events differ otherwise the uniquify isn't adding value.

Signed-off-by: Ian Rogers <irogers@google.com>
Tested-by: Chun-Tse Shao <ctshao@google.com>
Cc: Adrian Hunter <adrian.hunter@intel.com>
Cc: Alexander Shishkin <alexander.shishkin@linux.intel.com>
Cc: Andi Kleen <ak@linux.intel.com>
Cc: Dr. David Alan Gilbert <linux@treblig.org>
Cc: Howard Chu <howardchu95@gmail.com>
Cc: Ingo Molnar <mingo@redhat.com>
Cc: James Clark <james.clark@linaro.org>
Cc: Jiri Olsa <jolsa@kernel.org>
Cc: Kan Liang <kan.liang@linux.intel.com>
Cc: Levi Yun <yeoreum.yun@arm.com>
Cc: Mark Rutland <mark.rutland@arm.com>
Cc: Namhyung Kim <namhyung@kernel.org>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Weilin Wang <weilin.wang@intel.com>
Link: https://lore.kernel.org/r/20250513215401.2315949-2-ctshao@google.com
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
This commit is contained in:
Ian Rogers
2025-05-13 14:45:01 -07:00
committed by Arnaldo Carvalho de Melo
parent ef60b8f572
commit 7d45f402d3
8 changed files with 180 additions and 177 deletions

View File

@@ -26,6 +26,7 @@
#include "util/target.h"
#include "util/session.h"
#include "util/tool.h"
#include "util/stat.h"
#include "util/symbol.h"
#include "util/record.h"
#include "util/cpumap.h"
@@ -2484,7 +2485,11 @@ static int __cmd_record(struct record *rec, int argc, const char **argv)
pr_warning("WARNING: --timestamp-filename option is not available in pipe mode.\n");
}
evlist__uniquify_name(rec->evlist);
/*
* Use global stat_config that is zero meaning aggr_mode is AGGR_NONE
* and hybrid_merge is false.
*/
evlist__uniquify_evsel_names(rec->evlist, &stat_config);
evlist__config(rec->evlist, opts, &callchain_param);

View File

@@ -35,6 +35,7 @@
#include "util/mmap.h"
#include "util/session.h"
#include "util/thread.h"
#include "util/stat.h"
#include "util/symbol.h"
#include "util/synthetic-events.h"
#include "util/top.h"
@@ -1309,7 +1310,11 @@ static int __cmd_top(struct perf_top *top)
}
}
evlist__uniquify_name(top->evlist);
/*
* Use global stat_config that is zero meaning aggr_mode is AGGR_NONE
* and hybrid_merge is false.
*/
evlist__uniquify_evsel_names(top->evlist, &stat_config);
ret = perf_top__start_counters(top);
if (ret)
return ret;

View File

@@ -2565,34 +2565,56 @@ void evlist__warn_user_requested_cpus(struct evlist *evlist, const char *cpu_lis
perf_cpu_map__put(user_requested_cpus);
}
void evlist__uniquify_name(struct evlist *evlist)
/* Should uniquify be disabled for the evlist? */
static bool evlist__disable_uniquify(const struct evlist *evlist)
{
char *new_name, empty_attributes[2] = ":", *attributes;
struct evsel *pos;
struct evsel *counter;
struct perf_pmu *last_pmu = NULL;
bool first = true;
if (perf_pmus__num_core_pmus() == 1)
return;
evlist__for_each_entry(evlist, pos) {
if (!evsel__is_hybrid(pos))
continue;
if (strchr(pos->name, '/'))
continue;
attributes = strchr(pos->name, ':');
if (attributes)
*attributes = '\0';
else
attributes = empty_attributes;
if (asprintf(&new_name, "%s/%s/%s", pos->pmu ? pos->pmu->name : "",
pos->name, attributes + 1)) {
free(pos->name);
pos->name = new_name;
} else {
*attributes = ':';
evlist__for_each_entry(evlist, counter) {
/* If PMUs vary then uniquify can be useful. */
if (!first && counter->pmu != last_pmu)
return false;
first = false;
if (counter->pmu) {
/* Allow uniquify for uncore PMUs. */
if (!counter->pmu->is_core)
return false;
/* Keep hybrid event names uniquified for clarity. */
if (perf_pmus__num_core_pmus() > 1)
return false;
}
last_pmu = counter->pmu;
}
return true;
}
static bool evlist__set_needs_uniquify(struct evlist *evlist, const struct perf_stat_config *config)
{
struct evsel *counter;
bool needs_uniquify = false;
if (evlist__disable_uniquify(evlist)) {
evlist__for_each_entry(evlist, counter)
counter->uniquified_name = true;
return false;
}
evlist__for_each_entry(evlist, counter) {
if (evsel__set_needs_uniquify(counter, config))
needs_uniquify = true;
}
return needs_uniquify;
}
void evlist__uniquify_evsel_names(struct evlist *evlist, const struct perf_stat_config *config)
{
if (evlist__set_needs_uniquify(evlist, config)) {
struct evsel *pos;
evlist__for_each_entry(evlist, pos)
evsel__uniquify_counter(pos);
}
}

View File

@@ -19,6 +19,7 @@
struct pollfd;
struct thread_map;
struct perf_cpu_map;
struct perf_stat_config;
struct record_opts;
struct strbuf;
struct target;
@@ -434,7 +435,7 @@ struct evsel *evlist__find_evsel(struct evlist *evlist, int idx);
void evlist__format_evsels(struct evlist *evlist, struct strbuf *sb, size_t max_length);
void evlist__check_mem_load_aux(struct evlist *evlist);
void evlist__warn_user_requested_cpus(struct evlist *evlist, const char *cpu_list);
void evlist__uniquify_name(struct evlist *evlist);
void evlist__uniquify_evsel_names(struct evlist *evlist, const struct perf_stat_config *config);
bool evlist__has_bpf_output(struct evlist *evlist);
bool evlist__needs_bpf_sb_event(struct evlist *evlist);

View File

@@ -3954,3 +3954,116 @@ void evsel__remove_from_group(struct evsel *evsel, struct evsel *leader)
leader->core.nr_members--;
}
}
bool evsel__set_needs_uniquify(struct evsel *counter, const struct perf_stat_config *config)
{
struct evsel *evsel;
if (counter->needs_uniquify) {
/* Already set. */
return true;
}
if (counter->merged_stat) {
/* Counter won't be shown. */
return false;
}
if (counter->use_config_name || counter->is_libpfm_event) {
/* Original name will be used. */
return false;
}
if (!config->hybrid_merge && evsel__is_hybrid(counter)) {
/* Unique hybrid counters necessary. */
counter->needs_uniquify = true;
return true;
}
if (counter->core.attr.type < PERF_TYPE_MAX && counter->core.attr.type != PERF_TYPE_RAW) {
/* Legacy event, don't uniquify. */
return false;
}
if (counter->pmu && counter->pmu->is_core &&
counter->alternate_hw_config != PERF_COUNT_HW_MAX) {
/* A sysfs or json event replacing a legacy event, don't uniquify. */
return false;
}
if (config->aggr_mode == AGGR_NONE) {
/* Always unique with no aggregation. */
counter->needs_uniquify = true;
return true;
}
/*
* Do other non-merged events in the evlist have the same name? If so
* uniquify is necessary.
*/
evlist__for_each_entry(counter->evlist, evsel) {
if (evsel == counter || evsel->merged_stat || evsel->pmu == counter->pmu)
continue;
if (evsel__name_is(counter, evsel__name(evsel))) {
counter->needs_uniquify = true;
return true;
}
}
return false;
}
void evsel__uniquify_counter(struct evsel *counter)
{
const char *name, *pmu_name;
char *new_name, *config;
int ret;
/* No uniquification necessary. */
if (!counter->needs_uniquify)
return;
/* The evsel was already uniquified. */
if (counter->uniquified_name)
return;
/* Avoid checking to uniquify twice. */
counter->uniquified_name = true;
name = evsel__name(counter);
pmu_name = counter->pmu->name;
/* Already prefixed by the PMU name. */
if (!strncmp(name, pmu_name, strlen(pmu_name)))
return;
config = strchr(name, '/');
if (config) {
int len = config - name;
if (config[1] == '/') {
/* case: event// */
ret = asprintf(&new_name, "%s/%.*s/%s", pmu_name, len, name, config + 2);
} else {
/* case: event/.../ */
ret = asprintf(&new_name, "%s/%.*s,%s", pmu_name, len, name, config + 1);
}
} else {
config = strchr(name, ':');
if (config) {
/* case: event:.. */
int len = config - name;
ret = asprintf(&new_name, "%s/%.*s/%s", pmu_name, len, name, config + 1);
} else {
/* case: event */
ret = asprintf(&new_name, "%s/%s/", pmu_name, name);
}
}
if (ret > 0) {
free(counter->name);
counter->name = new_name;
} else {
/* ENOMEM from asprintf. */
counter->uniquified_name = false;
}
}

View File

@@ -16,6 +16,7 @@
struct bpf_object;
struct cgroup;
struct perf_counts;
struct perf_stat_config;
struct perf_stat_evsel;
union perf_event;
struct bpf_counter_ops;
@@ -548,6 +549,9 @@ void evsel__remove_from_group(struct evsel *evsel, struct evsel *leader);
bool arch_evsel__must_be_in_group(const struct evsel *evsel);
bool evsel__set_needs_uniquify(struct evsel *counter, const struct perf_stat_config *config);
void evsel__uniquify_counter(struct evsel *counter);
/*
* Macro to swap the bit-field postition and size.
* Used when,

View File

@@ -2267,7 +2267,7 @@ int __parse_events(struct evlist *evlist, const char *str, const char *pmu_filte
if (verbose > 0) {
struct strbuf sb = STRBUF_INIT;
evlist__uniquify_name(evlist);
evlist__uniquify_evsel_names(evlist, &stat_config);
evlist__format_evsels(evlist, &sb, 2048);
pr_debug("evlist after sorting/fixing: '%s'\n", sb.buf);
strbuf_release(&sb);

View File

@@ -915,61 +915,6 @@ static void printout(struct perf_stat_config *config, struct outstate *os,
}
}
static void evsel__uniquify_counter(struct evsel *counter)
{
const char *name, *pmu_name;
char *new_name, *config;
int ret;
/* No uniquification necessary. */
if (!counter->needs_uniquify)
return;
/* The evsel was already uniquified. */
if (counter->uniquified_name)
return;
/* Avoid checking to uniquify twice. */
counter->uniquified_name = true;
name = evsel__name(counter);
pmu_name = counter->pmu->name;
/* Already prefixed by the PMU name. */
if (!strncmp(name, pmu_name, strlen(pmu_name)))
return;
config = strchr(name, '/');
if (config) {
int len = config - name;
if (config[1] == '/') {
/* case: event// */
ret = asprintf(&new_name, "%s/%.*s/%s", pmu_name, len, name, config + 2);
} else {
/* case: event/.../ */
ret = asprintf(&new_name, "%s/%.*s,%s", pmu_name, len, name, config + 1);
}
} else {
config = strchr(name, ':');
if (config) {
/* case: event:.. */
int len = config - name;
ret = asprintf(&new_name, "%s/%.*s/%s", pmu_name, len, name, config + 1);
} else {
/* case: event */
ret = asprintf(&new_name, "%s/%s/", pmu_name, name);
}
}
if (ret > 0) {
free(counter->name);
counter->name = new_name;
} else {
/* ENOMEM from asprintf. */
counter->uniquified_name = false;
}
}
/**
* should_skip_zero_count() - Check if the event should print 0 values.
* @config: The perf stat configuration (including aggregation mode).
@@ -1060,8 +1005,6 @@ static void print_counter_aggrdata(struct perf_stat_config *config,
if (counter->merged_stat)
return;
evsel__uniquify_counter(counter);
val = aggr->counts.val;
ena = aggr->counts.ena;
run = aggr->counts.run;
@@ -1636,96 +1579,6 @@ static void print_cgroup_counter(struct perf_stat_config *config, struct evlist
print_metric_end(config, os);
}
/* Should uniquify be disabled for the evlist? */
static bool evlist__disable_uniquify(const struct evlist *evlist)
{
struct evsel *counter;
struct perf_pmu *last_pmu = NULL;
bool first = true;
evlist__for_each_entry(evlist, counter) {
/* If PMUs vary then uniquify can be useful. */
if (!first && counter->pmu != last_pmu)
return false;
first = false;
if (counter->pmu) {
/* Allow uniquify for uncore PMUs. */
if (!counter->pmu->is_core)
return false;
/* Keep hybrid event names uniquified for clarity. */
if (perf_pmus__num_core_pmus() > 1)
return false;
}
}
return true;
}
static void evsel__set_needs_uniquify(struct evsel *counter, const struct perf_stat_config *config)
{
struct evsel *evsel;
if (counter->merged_stat) {
/* Counter won't be shown. */
return;
}
if (counter->use_config_name || counter->is_libpfm_event) {
/* Original name will be used. */
return;
}
if (!config->hybrid_merge && evsel__is_hybrid(counter)) {
/* Unique hybrid counters necessary. */
counter->needs_uniquify = true;
return;
}
if (counter->core.attr.type < PERF_TYPE_MAX && counter->core.attr.type != PERF_TYPE_RAW) {
/* Legacy event, don't uniquify. */
return;
}
if (counter->pmu && counter->pmu->is_core &&
counter->alternate_hw_config != PERF_COUNT_HW_MAX) {
/* A sysfs or json event replacing a legacy event, don't uniquify. */
return;
}
if (config->aggr_mode == AGGR_NONE) {
/* Always unique with no aggregation. */
counter->needs_uniquify = true;
return;
}
/*
* Do other non-merged events in the evlist have the same name? If so
* uniquify is necessary.
*/
evlist__for_each_entry(counter->evlist, evsel) {
if (evsel == counter || evsel->merged_stat)
continue;
if (evsel__name_is(counter, evsel__name(evsel))) {
counter->needs_uniquify = true;
return;
}
}
}
static void evlist__set_needs_uniquify(struct evlist *evlist, const struct perf_stat_config *config)
{
struct evsel *counter;
if (evlist__disable_uniquify(evlist)) {
evlist__for_each_entry(evlist, counter)
counter->uniquified_name = true;
return;
}
evlist__for_each_entry(evlist, counter)
evsel__set_needs_uniquify(counter, config);
}
void evlist__print_counters(struct evlist *evlist, struct perf_stat_config *config,
struct target *_target, struct timespec *ts,
int argc, const char **argv)
@@ -1737,7 +1590,7 @@ void evlist__print_counters(struct evlist *evlist, struct perf_stat_config *conf
.first = true,
};
evlist__set_needs_uniquify(evlist, config);
evlist__uniquify_evsel_names(evlist, config);
if (config->iostat_run)
evlist->selected = evlist__first(evlist);