linux-stable/tools/perf/util/metricgroup.c
Ian Rogers a4c7d7c502 perf parse-events: Warn when events are regrouped
Use if an event is reordered or the number of groups increases to
signal that regrouping has happened and warn about it. Disable the
warning in the case wild card PMU names are used and for metrics.

Signed-off-by: Ian Rogers <irogers@google.com>
Cc: Adrian Hunter <adrian.hunter@intel.com>
Cc: Alexander Shishkin <alexander.shishkin@linux.intel.com>
Cc: Florian Fischer <florian.fischer@muhq.space>
Cc: Ingo Molnar <mingo@redhat.com>
Cc: James Clark <james.clark@arm.com>
Cc: Jiri Olsa <jolsa@kernel.org>
Cc: John Garry <john.g.garry@oracle.com>
Cc: Kajol Jain <kjain@linux.ibm.com>
Cc: Kan Liang <kan.liang@linux.intel.com>
Cc: Kim Phillips <kim.phillips@amd.com>
Cc: Leo Yan <leo.yan@linaro.org>
Cc: Mark Rutland <mark.rutland@arm.com>
Cc: Namhyung Kim <namhyung@kernel.org>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Ravi Bangoria <ravi.bangoria@amd.com>
Cc: Sean Christopherson <seanjc@google.com>
Cc: Steinar H. Gunderson <sesse@google.com>
Cc: Stephane Eranian <eranian@google.com>
Cc: Suzuki Poulouse <suzuki.poulose@arm.com>
Cc: Xing Zhengjun <zhengjun.xing@linux.intel.com>
Link: https://lore.kernel.org/r/20230312021543.3060328-2-irogers@google.com
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
2023-03-13 17:42:27 -03:00

1786 lines
46 KiB
C

// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2017, Intel Corporation.
*/
/* Manage metrics and groups of metrics from JSON files */
#include "metricgroup.h"
#include "debug.h"
#include "evlist.h"
#include "evsel.h"
#include "strbuf.h"
#include "pmu.h"
#include "pmu-hybrid.h"
#include "print-events.h"
#include "smt.h"
#include "expr.h"
#include "rblist.h"
#include <string.h>
#include <errno.h>
#include "strlist.h"
#include <assert.h>
#include <linux/ctype.h>
#include <linux/list_sort.h>
#include <linux/string.h>
#include <linux/zalloc.h>
#include <perf/cpumap.h>
#include <subcmd/parse-options.h>
#include <api/fs/fs.h>
#include "util.h"
#include <asm/bug.h>
#include "cgroup.h"
#include "util/hashmap.h"
struct metric_event *metricgroup__lookup(struct rblist *metric_events,
struct evsel *evsel,
bool create)
{
struct rb_node *nd;
struct metric_event me = {
.evsel = evsel
};
if (!metric_events)
return NULL;
nd = rblist__find(metric_events, &me);
if (nd)
return container_of(nd, struct metric_event, nd);
if (create) {
rblist__add_node(metric_events, &me);
nd = rblist__find(metric_events, &me);
if (nd)
return container_of(nd, struct metric_event, nd);
}
return NULL;
}
static int metric_event_cmp(struct rb_node *rb_node, const void *entry)
{
struct metric_event *a = container_of(rb_node,
struct metric_event,
nd);
const struct metric_event *b = entry;
if (a->evsel == b->evsel)
return 0;
if ((char *)a->evsel < (char *)b->evsel)
return -1;
return +1;
}
static struct rb_node *metric_event_new(struct rblist *rblist __maybe_unused,
const void *entry)
{
struct metric_event *me = malloc(sizeof(struct metric_event));
if (!me)
return NULL;
memcpy(me, entry, sizeof(struct metric_event));
me->evsel = ((struct metric_event *)entry)->evsel;
INIT_LIST_HEAD(&me->head);
return &me->nd;
}
static void metric_event_delete(struct rblist *rblist __maybe_unused,
struct rb_node *rb_node)
{
struct metric_event *me = container_of(rb_node, struct metric_event, nd);
struct metric_expr *expr, *tmp;
list_for_each_entry_safe(expr, tmp, &me->head, nd) {
free((char *)expr->metric_name);
free(expr->metric_refs);
free(expr->metric_events);
free(expr);
}
free(me);
}
static void metricgroup__rblist_init(struct rblist *metric_events)
{
rblist__init(metric_events);
metric_events->node_cmp = metric_event_cmp;
metric_events->node_new = metric_event_new;
metric_events->node_delete = metric_event_delete;
}
void metricgroup__rblist_exit(struct rblist *metric_events)
{
rblist__exit(metric_events);
}
/**
* The metric under construction. The data held here will be placed in a
* metric_expr.
*/
struct metric {
struct list_head nd;
/**
* The expression parse context importantly holding the IDs contained
* within the expression.
*/
struct expr_parse_ctx *pctx;
/** The name of the metric such as "IPC". */
const char *metric_name;
/** Modifier on the metric such as "u" or NULL for none. */
const char *modifier;
/** The expression to parse, for example, "instructions/cycles". */
const char *metric_expr;
/** Optional threshold expression where zero value is green, otherwise red. */
const char *metric_threshold;
/**
* The "ScaleUnit" that scales and adds a unit to the metric during
* output.
*/
const char *metric_unit;
/** Optional null terminated array of referenced metrics. */
struct metric_ref *metric_refs;
/**
* Should events of the metric be grouped?
*/
bool group_events;
/**
* Parsed events for the metric. Optional as events may be taken from a
* different metric whose group contains all the IDs necessary for this
* one.
*/
struct evlist *evlist;
};
static void metric__watchdog_constraint_hint(const char *name, bool foot)
{
static bool violate_nmi_constraint;
if (!foot) {
pr_warning("Not grouping metric %s's events.\n", name);
violate_nmi_constraint = true;
return;
}
if (!violate_nmi_constraint)
return;
pr_warning("Try disabling the NMI watchdog to comply NO_NMI_WATCHDOG metric constraint:\n"
" echo 0 > /proc/sys/kernel/nmi_watchdog\n"
" perf stat ...\n"
" echo 1 > /proc/sys/kernel/nmi_watchdog\n");
}
static bool metric__group_events(const struct pmu_metric *pm)
{
switch (pm->event_grouping) {
case MetricNoGroupEvents:
return false;
case MetricNoGroupEventsNmi:
if (!sysctl__nmi_watchdog_enabled())
return true;
metric__watchdog_constraint_hint(pm->metric_name, /*foot=*/false);
return false;
case MetricNoGroupEventsSmt:
return !smt_on();
case MetricGroupEvents:
default:
return true;
}
}
static void metric__free(struct metric *m)
{
if (!m)
return;
free(m->metric_refs);
expr__ctx_free(m->pctx);
free((char *)m->modifier);
evlist__delete(m->evlist);
free(m);
}
static struct metric *metric__new(const struct pmu_metric *pm,
const char *modifier,
bool metric_no_group,
int runtime,
const char *user_requested_cpu_list,
bool system_wide)
{
struct metric *m;
m = zalloc(sizeof(*m));
if (!m)
return NULL;
m->pctx = expr__ctx_new();
if (!m->pctx)
goto out_err;
m->metric_name = pm->metric_name;
m->modifier = NULL;
if (modifier) {
m->modifier = strdup(modifier);
if (!m->modifier)
goto out_err;
}
m->metric_expr = pm->metric_expr;
m->metric_threshold = pm->metric_threshold;
m->metric_unit = pm->unit;
m->pctx->sctx.user_requested_cpu_list = NULL;
if (user_requested_cpu_list) {
m->pctx->sctx.user_requested_cpu_list = strdup(user_requested_cpu_list);
if (!m->pctx->sctx.user_requested_cpu_list)
goto out_err;
}
m->pctx->sctx.runtime = runtime;
m->pctx->sctx.system_wide = system_wide;
m->group_events = !metric_no_group && metric__group_events(pm);
m->metric_refs = NULL;
m->evlist = NULL;
return m;
out_err:
metric__free(m);
return NULL;
}
static bool contains_metric_id(struct evsel **metric_events, int num_events,
const char *metric_id)
{
int i;
for (i = 0; i < num_events; i++) {
if (!strcmp(evsel__metric_id(metric_events[i]), metric_id))
return true;
}
return false;
}
/**
* setup_metric_events - Find a group of events in metric_evlist that correspond
* to the IDs from a parsed metric expression.
* @ids: the metric IDs to match.
* @metric_evlist: the list of perf events.
* @out_metric_events: holds the created metric events array.
*/
static int setup_metric_events(struct hashmap *ids,
struct evlist *metric_evlist,
struct evsel ***out_metric_events)
{
struct evsel **metric_events;
const char *metric_id;
struct evsel *ev;
size_t ids_size, matched_events, i;
*out_metric_events = NULL;
ids_size = hashmap__size(ids);
metric_events = calloc(sizeof(void *), ids_size + 1);
if (!metric_events)
return -ENOMEM;
matched_events = 0;
evlist__for_each_entry(metric_evlist, ev) {
struct expr_id_data *val_ptr;
/*
* Check for duplicate events with the same name. For
* example, uncore_imc/cas_count_read/ will turn into 6
* events per socket on skylakex. Only the first such
* event is placed in metric_events.
*/
metric_id = evsel__metric_id(ev);
if (contains_metric_id(metric_events, matched_events, metric_id))
continue;
/*
* Does this event belong to the parse context? For
* combined or shared groups, this metric may not care
* about this event.
*/
if (hashmap__find(ids, metric_id, &val_ptr)) {
metric_events[matched_events++] = ev;
if (matched_events >= ids_size)
break;
}
}
if (matched_events < ids_size) {
free(metric_events);
return -EINVAL;
}
for (i = 0; i < ids_size; i++) {
ev = metric_events[i];
ev->collect_stat = true;
/*
* The metric leader points to the identically named
* event in metric_events.
*/
ev->metric_leader = ev;
/*
* Mark two events with identical names in the same
* group (or globally) as being in use as uncore events
* may be duplicated for each pmu. Set the metric leader
* of such events to be the event that appears in
* metric_events.
*/
metric_id = evsel__metric_id(ev);
evlist__for_each_entry_continue(metric_evlist, ev) {
if (!strcmp(evsel__metric_id(ev), metric_id))
ev->metric_leader = metric_events[i];
}
}
*out_metric_events = metric_events;
return 0;
}
static bool match_metric(const char *n, const char *list)
{
int len;
char *m;
if (!list)
return false;
if (!strcmp(list, "all"))
return true;
if (!n)
return !strcasecmp(list, "No_group");
len = strlen(list);
m = strcasestr(n, list);
if (!m)
return false;
if ((m == n || m[-1] == ';' || m[-1] == ' ') &&
(m[len] == 0 || m[len] == ';'))
return true;
return false;
}
static bool match_pm_metric(const struct pmu_metric *pm, const char *metric)
{
return match_metric(pm->metric_group, metric) ||
match_metric(pm->metric_name, metric);
}
/** struct mep - RB-tree node for building printing information. */
struct mep {
/** nd - RB-tree element. */
struct rb_node nd;
/** @metric_group: Owned metric group name, separated others with ';'. */
char *metric_group;
const char *metric_name;
const char *metric_desc;
const char *metric_long_desc;
const char *metric_expr;
const char *metric_threshold;
const char *metric_unit;
};
static int mep_cmp(struct rb_node *rb_node, const void *entry)
{
struct mep *a = container_of(rb_node, struct mep, nd);
struct mep *b = (struct mep *)entry;
int ret;
ret = strcmp(a->metric_group, b->metric_group);
if (ret)
return ret;
return strcmp(a->metric_name, b->metric_name);
}
static struct rb_node *mep_new(struct rblist *rl __maybe_unused, const void *entry)
{
struct mep *me = malloc(sizeof(struct mep));
if (!me)
return NULL;
memcpy(me, entry, sizeof(struct mep));
return &me->nd;
}
static void mep_delete(struct rblist *rl __maybe_unused,
struct rb_node *nd)
{
struct mep *me = container_of(nd, struct mep, nd);
zfree(&me->metric_group);
free(me);
}
static struct mep *mep_lookup(struct rblist *groups, const char *metric_group,
const char *metric_name)
{
struct rb_node *nd;
struct mep me = {
.metric_group = strdup(metric_group),
.metric_name = metric_name,
};
nd = rblist__find(groups, &me);
if (nd) {
free(me.metric_group);
return container_of(nd, struct mep, nd);
}
rblist__add_node(groups, &me);
nd = rblist__find(groups, &me);
if (nd)
return container_of(nd, struct mep, nd);
return NULL;
}
static int metricgroup__add_to_mep_groups(const struct pmu_metric *pm,
struct rblist *groups)
{
const char *g;
char *omg, *mg;
mg = strdup(pm->metric_group ?: "No_group");
if (!mg)
return -ENOMEM;
omg = mg;
while ((g = strsep(&mg, ";")) != NULL) {
struct mep *me;
g = skip_spaces(g);
if (strlen(g))
me = mep_lookup(groups, g, pm->metric_name);
else
me = mep_lookup(groups, "No_group", pm->metric_name);
if (me) {
me->metric_desc = pm->desc;
me->metric_long_desc = pm->long_desc;
me->metric_expr = pm->metric_expr;
me->metric_threshold = pm->metric_threshold;
me->metric_unit = pm->unit;
}
}
free(omg);
return 0;
}
struct metricgroup_iter_data {
pmu_metric_iter_fn fn;
void *data;
};
static int metricgroup__sys_event_iter(const struct pmu_metric *pm,
const struct pmu_metrics_table *table,
void *data)
{
struct metricgroup_iter_data *d = data;
struct perf_pmu *pmu = NULL;
if (!pm->metric_expr || !pm->compat)
return 0;
while ((pmu = perf_pmu__scan(pmu))) {
if (!pmu->id || strcmp(pmu->id, pm->compat))
continue;
return d->fn(pm, table, d->data);
}
return 0;
}
static int metricgroup__add_to_mep_groups_callback(const struct pmu_metric *pm,
const struct pmu_metrics_table *table __maybe_unused,
void *vdata)
{
struct rblist *groups = vdata;
return metricgroup__add_to_mep_groups(pm, groups);
}
void metricgroup__print(const struct print_callbacks *print_cb, void *print_state)
{
struct rblist groups;
const struct pmu_metrics_table *table;
struct rb_node *node, *next;
rblist__init(&groups);
groups.node_new = mep_new;
groups.node_cmp = mep_cmp;
groups.node_delete = mep_delete;
table = pmu_metrics_table__find();
if (table) {
pmu_metrics_table_for_each_metric(table,
metricgroup__add_to_mep_groups_callback,
&groups);
}
{
struct metricgroup_iter_data data = {
.fn = metricgroup__add_to_mep_groups_callback,
.data = &groups,
};
pmu_for_each_sys_metric(metricgroup__sys_event_iter, &data);
}
for (node = rb_first_cached(&groups.entries); node; node = next) {
struct mep *me = container_of(node, struct mep, nd);
print_cb->print_metric(print_state,
me->metric_group,
me->metric_name,
me->metric_desc,
me->metric_long_desc,
me->metric_expr,
me->metric_threshold,
me->metric_unit);
next = rb_next(node);
rblist__remove_node(&groups, node);
}
}
static const char *code_characters = ",-=@";
static int encode_metric_id(struct strbuf *sb, const char *x)
{
char *c;
int ret = 0;
for (; *x; x++) {
c = strchr(code_characters, *x);
if (c) {
ret = strbuf_addch(sb, '!');
if (ret)
break;
ret = strbuf_addch(sb, '0' + (c - code_characters));
if (ret)
break;
} else {
ret = strbuf_addch(sb, *x);
if (ret)
break;
}
}
return ret;
}
static int decode_metric_id(struct strbuf *sb, const char *x)
{
const char *orig = x;
size_t i;
char c;
int ret;
for (; *x; x++) {
c = *x;
if (*x == '!') {
x++;
i = *x - '0';
if (i > strlen(code_characters)) {
pr_err("Bad metric-id encoding in: '%s'", orig);
return -1;
}
c = code_characters[i];
}
ret = strbuf_addch(sb, c);
if (ret)
return ret;
}
return 0;
}
static int decode_all_metric_ids(struct evlist *perf_evlist, const char *modifier)
{
struct evsel *ev;
struct strbuf sb = STRBUF_INIT;
char *cur;
int ret = 0;
evlist__for_each_entry(perf_evlist, ev) {
if (!ev->metric_id)
continue;
ret = strbuf_setlen(&sb, 0);
if (ret)
break;
ret = decode_metric_id(&sb, ev->metric_id);
if (ret)
break;
free((char *)ev->metric_id);
ev->metric_id = strdup(sb.buf);
if (!ev->metric_id) {
ret = -ENOMEM;
break;
}
/*
* If the name is just the parsed event, use the metric-id to
* give a more friendly display version.
*/
if (strstr(ev->name, "metric-id=")) {
bool has_slash = false;
free(ev->name);
for (cur = strchr(sb.buf, '@') ; cur; cur = strchr(++cur, '@')) {
*cur = '/';
has_slash = true;
}
if (modifier) {
if (!has_slash && !strchr(sb.buf, ':')) {
ret = strbuf_addch(&sb, ':');
if (ret)
break;
}
ret = strbuf_addstr(&sb, modifier);
if (ret)
break;
}
ev->name = strdup(sb.buf);
if (!ev->name) {
ret = -ENOMEM;
break;
}
}
}
strbuf_release(&sb);
return ret;
}
static int metricgroup__build_event_string(struct strbuf *events,
const struct expr_parse_ctx *ctx,
const char *modifier,
bool group_events)
{
struct hashmap_entry *cur;
size_t bkt;
bool no_group = true, has_tool_events = false;
bool tool_events[PERF_TOOL_MAX] = {false};
int ret = 0;
#define RETURN_IF_NON_ZERO(x) do { if (x) return x; } while (0)
hashmap__for_each_entry(ctx->ids, cur, bkt) {
const char *sep, *rsep, *id = cur->pkey;
enum perf_tool_event ev;
pr_debug("found event %s\n", id);
/* Always move tool events outside of the group. */
ev = perf_tool_event__from_str(id);
if (ev != PERF_TOOL_NONE) {
has_tool_events = true;
tool_events[ev] = true;
continue;
}
/* Separate events with commas and open the group if necessary. */
if (no_group) {
if (group_events) {
ret = strbuf_addch(events, '{');
RETURN_IF_NON_ZERO(ret);
}
no_group = false;
} else {
ret = strbuf_addch(events, ',');
RETURN_IF_NON_ZERO(ret);
}
/*
* Encode the ID as an event string. Add a qualifier for
* metric_id that is the original name except with characters
* that parse-events can't parse replaced. For example,
* 'msr@tsc@' gets added as msr/tsc,metric-id=msr!3tsc!3/
*/
sep = strchr(id, '@');
if (sep != NULL) {
ret = strbuf_add(events, id, sep - id);
RETURN_IF_NON_ZERO(ret);
ret = strbuf_addch(events, '/');
RETURN_IF_NON_ZERO(ret);
rsep = strrchr(sep, '@');
ret = strbuf_add(events, sep + 1, rsep - sep - 1);
RETURN_IF_NON_ZERO(ret);
ret = strbuf_addstr(events, ",metric-id=");
RETURN_IF_NON_ZERO(ret);
sep = rsep;
} else {
sep = strchr(id, ':');
if (sep != NULL) {
ret = strbuf_add(events, id, sep - id);
RETURN_IF_NON_ZERO(ret);
} else {
ret = strbuf_addstr(events, id);
RETURN_IF_NON_ZERO(ret);
}
ret = strbuf_addstr(events, "/metric-id=");
RETURN_IF_NON_ZERO(ret);
}
ret = encode_metric_id(events, id);
RETURN_IF_NON_ZERO(ret);
ret = strbuf_addstr(events, "/");
RETURN_IF_NON_ZERO(ret);
if (sep != NULL) {
ret = strbuf_addstr(events, sep + 1);
RETURN_IF_NON_ZERO(ret);
}
if (modifier) {
ret = strbuf_addstr(events, modifier);
RETURN_IF_NON_ZERO(ret);
}
}
if (!no_group && group_events) {
ret = strbuf_addf(events, "}:W");
RETURN_IF_NON_ZERO(ret);
}
if (has_tool_events) {
int i;
perf_tool_event__for_each_event(i) {
if (tool_events[i]) {
if (!no_group) {
ret = strbuf_addch(events, ',');
RETURN_IF_NON_ZERO(ret);
}
no_group = false;
ret = strbuf_addstr(events, perf_tool_event__to_str(i));
RETURN_IF_NON_ZERO(ret);
}
}
}
return ret;
#undef RETURN_IF_NON_ZERO
}
int __weak arch_get_runtimeparam(const struct pmu_metric *pm __maybe_unused)
{
return 1;
}
/*
* A singly linked list on the stack of the names of metrics being
* processed. Used to identify recursion.
*/
struct visited_metric {
const char *name;
const struct visited_metric *parent;
};
struct metricgroup_add_iter_data {
struct list_head *metric_list;
const char *metric_name;
const char *modifier;
int *ret;
bool *has_match;
bool metric_no_group;
bool metric_no_threshold;
const char *user_requested_cpu_list;
bool system_wide;
struct metric *root_metric;
const struct visited_metric *visited;
const struct pmu_metrics_table *table;
};
static bool metricgroup__find_metric(const char *metric,
const struct pmu_metrics_table *table,
struct pmu_metric *pm);
static int add_metric(struct list_head *metric_list,
const struct pmu_metric *pm,
const char *modifier,
bool metric_no_group,
bool metric_no_threshold,
const char *user_requested_cpu_list,
bool system_wide,
struct metric *root_metric,
const struct visited_metric *visited,
const struct pmu_metrics_table *table);
/**
* resolve_metric - Locate metrics within the root metric and recursively add
* references to them.
* @metric_list: The list the metric is added to.
* @modifier: if non-null event modifiers like "u".
* @metric_no_group: Should events written to events be grouped "{}" or
* global. Grouping is the default but due to multiplexing the
* user may override.
* @user_requested_cpu_list: Command line specified CPUs to record on.
* @system_wide: Are events for all processes recorded.
* @root_metric: Metrics may reference other metrics to form a tree. In this
* case the root_metric holds all the IDs and a list of referenced
* metrics. When adding a root this argument is NULL.
* @visited: A singly linked list of metric names being added that is used to
* detect recursion.
* @table: The table that is searched for metrics, most commonly the table for the
* architecture perf is running upon.
*/
static int resolve_metric(struct list_head *metric_list,
const char *modifier,
bool metric_no_group,
bool metric_no_threshold,
const char *user_requested_cpu_list,
bool system_wide,
struct metric *root_metric,
const struct visited_metric *visited,
const struct pmu_metrics_table *table)
{
struct hashmap_entry *cur;
size_t bkt;
struct to_resolve {
/* The metric to resolve. */
struct pmu_metric pm;
/*
* The key in the IDs map, this may differ from in case,
* etc. from pm->metric_name.
*/
const char *key;
} *pending = NULL;
int i, ret = 0, pending_cnt = 0;
/*
* Iterate all the parsed IDs and if there's a matching metric and it to
* the pending array.
*/
hashmap__for_each_entry(root_metric->pctx->ids, cur, bkt) {
struct pmu_metric pm;
if (metricgroup__find_metric(cur->pkey, table, &pm)) {
pending = realloc(pending,
(pending_cnt + 1) * sizeof(struct to_resolve));
if (!pending)
return -ENOMEM;
memcpy(&pending[pending_cnt].pm, &pm, sizeof(pm));
pending[pending_cnt].key = cur->pkey;
pending_cnt++;
}
}
/* Remove the metric IDs from the context. */
for (i = 0; i < pending_cnt; i++)
expr__del_id(root_metric->pctx, pending[i].key);
/*
* Recursively add all the metrics, IDs are added to the root metric's
* context.
*/
for (i = 0; i < pending_cnt; i++) {
ret = add_metric(metric_list, &pending[i].pm, modifier, metric_no_group,
metric_no_threshold, user_requested_cpu_list, system_wide,
root_metric, visited, table);
if (ret)
break;
}
free(pending);
return ret;
}
/**
* __add_metric - Add a metric to metric_list.
* @metric_list: The list the metric is added to.
* @pm: The pmu_metric containing the metric to be added.
* @modifier: if non-null event modifiers like "u".
* @metric_no_group: Should events written to events be grouped "{}" or
* global. Grouping is the default but due to multiplexing the
* user may override.
* @metric_no_threshold: Should threshold expressions be ignored?
* @runtime: A special argument for the parser only known at runtime.
* @user_requested_cpu_list: Command line specified CPUs to record on.
* @system_wide: Are events for all processes recorded.
* @root_metric: Metrics may reference other metrics to form a tree. In this
* case the root_metric holds all the IDs and a list of referenced
* metrics. When adding a root this argument is NULL.
* @visited: A singly linked list of metric names being added that is used to
* detect recursion.
* @table: The table that is searched for metrics, most commonly the table for the
* architecture perf is running upon.
*/
static int __add_metric(struct list_head *metric_list,
const struct pmu_metric *pm,
const char *modifier,
bool metric_no_group,
bool metric_no_threshold,
int runtime,
const char *user_requested_cpu_list,
bool system_wide,
struct metric *root_metric,
const struct visited_metric *visited,
const struct pmu_metrics_table *table)
{
const struct visited_metric *vm;
int ret;
bool is_root = !root_metric;
const char *expr;
struct visited_metric visited_node = {
.name = pm->metric_name,
.parent = visited,
};
for (vm = visited; vm; vm = vm->parent) {
if (!strcmp(pm->metric_name, vm->name)) {
pr_err("failed: recursion detected for %s\n", pm->metric_name);
return -1;
}
}
if (is_root) {
/*
* This metric is the root of a tree and may reference other
* metrics that are added recursively.
*/
root_metric = metric__new(pm, modifier, metric_no_group, runtime,
user_requested_cpu_list, system_wide);
if (!root_metric)
return -ENOMEM;
} else {
int cnt = 0;
/*
* This metric was referenced in a metric higher in the
* tree. Check if the same metric is already resolved in the
* metric_refs list.
*/
if (root_metric->metric_refs) {
for (; root_metric->metric_refs[cnt].metric_name; cnt++) {
if (!strcmp(pm->metric_name,
root_metric->metric_refs[cnt].metric_name))
return 0;
}
}
/* Create reference. Need space for the entry and the terminator. */
root_metric->metric_refs = realloc(root_metric->metric_refs,
(cnt + 2) * sizeof(struct metric_ref));
if (!root_metric->metric_refs)
return -ENOMEM;
/*
* Intentionally passing just const char pointers,
* from 'pe' object, so they never go away. We don't
* need to change them, so there's no need to create
* our own copy.
*/
root_metric->metric_refs[cnt].metric_name = pm->metric_name;
root_metric->metric_refs[cnt].metric_expr = pm->metric_expr;
/* Null terminate array. */
root_metric->metric_refs[cnt+1].metric_name = NULL;
root_metric->metric_refs[cnt+1].metric_expr = NULL;
}
/*
* For both the parent and referenced metrics, we parse
* all the metric's IDs and add it to the root context.
*/
ret = 0;
expr = pm->metric_expr;
if (is_root && pm->metric_threshold) {
/*
* Threshold expressions are built off the actual metric. Switch
* to use that in case of additional necessary events. Change
* the visited node name to avoid this being flagged as
* recursion. If the threshold events are disabled, just use the
* metric's name as a reference. This allows metric threshold
* computation if there are sufficient events.
*/
assert(strstr(pm->metric_threshold, pm->metric_name));
expr = metric_no_threshold ? pm->metric_name : pm->metric_threshold;
visited_node.name = "__threshold__";
}
if (expr__find_ids(expr, NULL, root_metric->pctx) < 0) {
/* Broken metric. */
ret = -EINVAL;
}
if (!ret) {
/* Resolve referenced metrics. */
ret = resolve_metric(metric_list, modifier, metric_no_group,
metric_no_threshold, user_requested_cpu_list,
system_wide, root_metric, &visited_node, table);
}
if (ret) {
if (is_root)
metric__free(root_metric);
} else if (is_root)
list_add(&root_metric->nd, metric_list);
return ret;
}
struct metricgroup__find_metric_data {
const char *metric;
struct pmu_metric *pm;
};
static int metricgroup__find_metric_callback(const struct pmu_metric *pm,
const struct pmu_metrics_table *table __maybe_unused,
void *vdata)
{
struct metricgroup__find_metric_data *data = vdata;
if (!match_metric(pm->metric_name, data->metric))
return 0;
memcpy(data->pm, pm, sizeof(*pm));
return 1;
}
static bool metricgroup__find_metric(const char *metric,
const struct pmu_metrics_table *table,
struct pmu_metric *pm)
{
struct metricgroup__find_metric_data data = {
.metric = metric,
.pm = pm,
};
return pmu_metrics_table_for_each_metric(table, metricgroup__find_metric_callback, &data)
? true : false;
}
static int add_metric(struct list_head *metric_list,
const struct pmu_metric *pm,
const char *modifier,
bool metric_no_group,
bool metric_no_threshold,
const char *user_requested_cpu_list,
bool system_wide,
struct metric *root_metric,
const struct visited_metric *visited,
const struct pmu_metrics_table *table)
{
int ret = 0;
pr_debug("metric expr %s for %s\n", pm->metric_expr, pm->metric_name);
if (!strstr(pm->metric_expr, "?")) {
ret = __add_metric(metric_list, pm, modifier, metric_no_group,
metric_no_threshold, 0, user_requested_cpu_list,
system_wide, root_metric, visited, table);
} else {
int j, count;
count = arch_get_runtimeparam(pm);
/* This loop is added to create multiple
* events depend on count value and add
* those events to metric_list.
*/
for (j = 0; j < count && !ret; j++)
ret = __add_metric(metric_list, pm, modifier, metric_no_group,
metric_no_threshold, j, user_requested_cpu_list,
system_wide, root_metric, visited, table);
}
return ret;
}
static int metricgroup__add_metric_sys_event_iter(const struct pmu_metric *pm,
const struct pmu_metrics_table *table __maybe_unused,
void *data)
{
struct metricgroup_add_iter_data *d = data;
int ret;
if (!match_pm_metric(pm, d->metric_name))
return 0;
ret = add_metric(d->metric_list, pm, d->modifier, d->metric_no_group,
d->metric_no_threshold, d->user_requested_cpu_list,
d->system_wide, d->root_metric, d->visited, d->table);
if (ret)
goto out;
*(d->has_match) = true;
out:
*(d->ret) = ret;
return ret;
}
/**
* metric_list_cmp - list_sort comparator that sorts metrics with more events to
* the front. tool events are excluded from the count.
*/
static int metric_list_cmp(void *priv __maybe_unused, const struct list_head *l,
const struct list_head *r)
{
const struct metric *left = container_of(l, struct metric, nd);
const struct metric *right = container_of(r, struct metric, nd);
struct expr_id_data *data;
int i, left_count, right_count;
left_count = hashmap__size(left->pctx->ids);
perf_tool_event__for_each_event(i) {
if (!expr__get_id(left->pctx, perf_tool_event__to_str(i), &data))
left_count--;
}
right_count = hashmap__size(right->pctx->ids);
perf_tool_event__for_each_event(i) {
if (!expr__get_id(right->pctx, perf_tool_event__to_str(i), &data))
right_count--;
}
return right_count - left_count;
}
struct metricgroup__add_metric_data {
struct list_head *list;
const char *metric_name;
const char *modifier;
const char *user_requested_cpu_list;
bool metric_no_group;
bool metric_no_threshold;
bool system_wide;
bool has_match;
};
static int metricgroup__add_metric_callback(const struct pmu_metric *pm,
const struct pmu_metrics_table *table,
void *vdata)
{
struct metricgroup__add_metric_data *data = vdata;
int ret = 0;
if (pm->metric_expr &&
(match_metric(pm->metric_group, data->metric_name) ||
match_metric(pm->metric_name, data->metric_name))) {
data->has_match = true;
ret = add_metric(data->list, pm, data->modifier, data->metric_no_group,
data->metric_no_threshold, data->user_requested_cpu_list,
data->system_wide, /*root_metric=*/NULL,
/*visited_metrics=*/NULL, table);
}
return ret;
}
/**
* metricgroup__add_metric - Find and add a metric, or a metric group.
* @metric_name: The name of the metric or metric group. For example, "IPC"
* could be the name of a metric and "TopDownL1" the name of a
* metric group.
* @modifier: if non-null event modifiers like "u".
* @metric_no_group: Should events written to events be grouped "{}" or
* global. Grouping is the default but due to multiplexing the
* user may override.
* @user_requested_cpu_list: Command line specified CPUs to record on.
* @system_wide: Are events for all processes recorded.
* @metric_list: The list that the metric or metric group are added to.
* @table: The table that is searched for metrics, most commonly the table for the
* architecture perf is running upon.
*/
static int metricgroup__add_metric(const char *metric_name, const char *modifier,
bool metric_no_group, bool metric_no_threshold,
const char *user_requested_cpu_list,
bool system_wide,
struct list_head *metric_list,
const struct pmu_metrics_table *table)
{
LIST_HEAD(list);
int ret;
bool has_match = false;
{
struct metricgroup__add_metric_data data = {
.list = &list,
.metric_name = metric_name,
.modifier = modifier,
.metric_no_group = metric_no_group,
.metric_no_threshold = metric_no_threshold,
.user_requested_cpu_list = user_requested_cpu_list,
.system_wide = system_wide,
.has_match = false,
};
/*
* Iterate over all metrics seeing if metric matches either the
* name or group. When it does add the metric to the list.
*/
ret = pmu_metrics_table_for_each_metric(table, metricgroup__add_metric_callback,
&data);
if (ret)
goto out;
has_match = data.has_match;
}
{
struct metricgroup_iter_data data = {
.fn = metricgroup__add_metric_sys_event_iter,
.data = (void *) &(struct metricgroup_add_iter_data) {
.metric_list = &list,
.metric_name = metric_name,
.modifier = modifier,
.metric_no_group = metric_no_group,
.user_requested_cpu_list = user_requested_cpu_list,
.system_wide = system_wide,
.has_match = &has_match,
.ret = &ret,
.table = table,
},
};
pmu_for_each_sys_metric(metricgroup__sys_event_iter, &data);
}
/* End of pmu events. */
if (!has_match)
ret = -EINVAL;
out:
/*
* add to metric_list so that they can be released
* even if it's failed
*/
list_splice(&list, metric_list);
return ret;
}
/**
* metricgroup__add_metric_list - Find and add metrics, or metric groups,
* specified in a list.
* @list: the list of metrics or metric groups. For example, "IPC,CPI,TopDownL1"
* would match the IPC and CPI metrics, and TopDownL1 would match all
* the metrics in the TopDownL1 group.
* @metric_no_group: Should events written to events be grouped "{}" or
* global. Grouping is the default but due to multiplexing the
* user may override.
* @user_requested_cpu_list: Command line specified CPUs to record on.
* @system_wide: Are events for all processes recorded.
* @metric_list: The list that metrics are added to.
* @table: The table that is searched for metrics, most commonly the table for the
* architecture perf is running upon.
*/
static int metricgroup__add_metric_list(const char *list, bool metric_no_group,
bool metric_no_threshold,
const char *user_requested_cpu_list,
bool system_wide, struct list_head *metric_list,
const struct pmu_metrics_table *table)
{
char *list_itr, *list_copy, *metric_name, *modifier;
int ret, count = 0;
list_copy = strdup(list);
if (!list_copy)
return -ENOMEM;
list_itr = list_copy;
while ((metric_name = strsep(&list_itr, ",")) != NULL) {
modifier = strchr(metric_name, ':');
if (modifier)
*modifier++ = '\0';
ret = metricgroup__add_metric(metric_name, modifier,
metric_no_group, metric_no_threshold,
user_requested_cpu_list,
system_wide, metric_list, table);
if (ret == -EINVAL)
pr_err("Cannot find metric or group `%s'\n", metric_name);
if (ret)
break;
count++;
}
free(list_copy);
if (!ret) {
/*
* Warn about nmi_watchdog if any parsed metrics had the
* NO_NMI_WATCHDOG constraint.
*/
metric__watchdog_constraint_hint(NULL, /*foot=*/true);
/* No metrics. */
if (count == 0)
return -EINVAL;
}
return ret;
}
static void metricgroup__free_metrics(struct list_head *metric_list)
{
struct metric *m, *tmp;
list_for_each_entry_safe (m, tmp, metric_list, nd) {
list_del_init(&m->nd);
metric__free(m);
}
}
/**
* find_tool_events - Search for the pressence of tool events in metric_list.
* @metric_list: List to take metrics from.
* @tool_events: Array of false values, indices corresponding to tool events set
* to true if tool event is found.
*/
static void find_tool_events(const struct list_head *metric_list,
bool tool_events[PERF_TOOL_MAX])
{
struct metric *m;
list_for_each_entry(m, metric_list, nd) {
int i;
perf_tool_event__for_each_event(i) {
struct expr_id_data *data;
if (!tool_events[i] &&
!expr__get_id(m->pctx, perf_tool_event__to_str(i), &data))
tool_events[i] = true;
}
}
}
/**
* build_combined_expr_ctx - Make an expr_parse_ctx with all !group_events
* metric IDs, as the IDs are held in a set,
* duplicates will be removed.
* @metric_list: List to take metrics from.
* @combined: Out argument for result.
*/
static int build_combined_expr_ctx(const struct list_head *metric_list,
struct expr_parse_ctx **combined)
{
struct hashmap_entry *cur;
size_t bkt;
struct metric *m;
char *dup;
int ret;
*combined = expr__ctx_new();
if (!*combined)
return -ENOMEM;
list_for_each_entry(m, metric_list, nd) {
if (!m->group_events && !m->modifier) {
hashmap__for_each_entry(m->pctx->ids, cur, bkt) {
dup = strdup(cur->pkey);
if (!dup) {
ret = -ENOMEM;
goto err_out;
}
ret = expr__add_id(*combined, dup);
if (ret)
goto err_out;
}
}
}
return 0;
err_out:
expr__ctx_free(*combined);
*combined = NULL;
return ret;
}
/**
* parse_ids - Build the event string for the ids and parse them creating an
* evlist. The encoded metric_ids are decoded.
* @metric_no_merge: is metric sharing explicitly disabled.
* @fake_pmu: used when testing metrics not supported by the current CPU.
* @ids: the event identifiers parsed from a metric.
* @modifier: any modifiers added to the events.
* @group_events: should events be placed in a weak group.
* @tool_events: entries set true if the tool event of index could be present in
* the overall list of metrics.
* @out_evlist: the created list of events.
*/
static int parse_ids(bool metric_no_merge, struct perf_pmu *fake_pmu,
struct expr_parse_ctx *ids, const char *modifier,
bool group_events, const bool tool_events[PERF_TOOL_MAX],
struct evlist **out_evlist)
{
struct parse_events_error parse_error;
struct evlist *parsed_evlist;
struct strbuf events = STRBUF_INIT;
int ret;
*out_evlist = NULL;
if (!metric_no_merge || hashmap__size(ids->ids) == 0) {
bool added_event = false;
int i;
/*
* We may fail to share events between metrics because a tool
* event isn't present in one metric. For example, a ratio of
* cache misses doesn't need duration_time but the same events
* may be used for a misses per second. Events without sharing
* implies multiplexing, that is best avoided, so place
* all tool events in every group.
*
* Also, there may be no ids/events in the expression parsing
* context because of constant evaluation, e.g.:
* event1 if #smt_on else 0
* Add a tool event to avoid a parse error on an empty string.
*/
perf_tool_event__for_each_event(i) {
if (tool_events[i]) {
char *tmp = strdup(perf_tool_event__to_str(i));
if (!tmp)
return -ENOMEM;
ids__insert(ids->ids, tmp);
added_event = true;
}
}
if (!added_event && hashmap__size(ids->ids) == 0) {
char *tmp = strdup("duration_time");
if (!tmp)
return -ENOMEM;
ids__insert(ids->ids, tmp);
}
}
ret = metricgroup__build_event_string(&events, ids, modifier,
group_events);
if (ret)
return ret;
parsed_evlist = evlist__new();
if (!parsed_evlist) {
ret = -ENOMEM;
goto err_out;
}
pr_debug("Parsing metric events '%s'\n", events.buf);
parse_events_error__init(&parse_error);
ret = __parse_events(parsed_evlist, events.buf, &parse_error, fake_pmu,
/*warn_if_reordered=*/false);
if (ret) {
parse_events_error__print(&parse_error, events.buf);
goto err_out;
}
ret = decode_all_metric_ids(parsed_evlist, modifier);
if (ret)
goto err_out;
*out_evlist = parsed_evlist;
parsed_evlist = NULL;
err_out:
parse_events_error__exit(&parse_error);
evlist__delete(parsed_evlist);
strbuf_release(&events);
return ret;
}
static int parse_groups(struct evlist *perf_evlist, const char *str,
bool metric_no_group,
bool metric_no_merge,
bool metric_no_threshold,
const char *user_requested_cpu_list,
bool system_wide,
struct perf_pmu *fake_pmu,
struct rblist *metric_events_list,
const struct pmu_metrics_table *table)
{
struct evlist *combined_evlist = NULL;
LIST_HEAD(metric_list);
struct metric *m;
bool tool_events[PERF_TOOL_MAX] = {false};
int ret;
if (metric_events_list->nr_entries == 0)
metricgroup__rblist_init(metric_events_list);
ret = metricgroup__add_metric_list(str, metric_no_group, metric_no_threshold,
user_requested_cpu_list,
system_wide, &metric_list, table);
if (ret)
goto out;
/* Sort metrics from largest to smallest. */
list_sort(NULL, &metric_list, metric_list_cmp);
if (!metric_no_merge) {
struct expr_parse_ctx *combined = NULL;
find_tool_events(&metric_list, tool_events);
ret = build_combined_expr_ctx(&metric_list, &combined);
if (!ret && combined && hashmap__size(combined->ids)) {
ret = parse_ids(metric_no_merge, fake_pmu, combined,
/*modifier=*/NULL,
/*group_events=*/false,
tool_events,
&combined_evlist);
}
if (combined)
expr__ctx_free(combined);
if (ret)
goto out;
}
list_for_each_entry(m, &metric_list, nd) {
struct metric_event *me;
struct evsel **metric_events;
struct evlist *metric_evlist = NULL;
struct metric *n;
struct metric_expr *expr;
if (combined_evlist && !m->group_events) {
metric_evlist = combined_evlist;
} else if (!metric_no_merge) {
/*
* See if the IDs for this metric are a subset of an
* earlier metric.
*/
list_for_each_entry(n, &metric_list, nd) {
if (m == n)
break;
if (n->evlist == NULL)
continue;
if ((!m->modifier && n->modifier) ||
(m->modifier && !n->modifier) ||
(m->modifier && n->modifier &&
strcmp(m->modifier, n->modifier)))
continue;
if (expr__subset_of_ids(n->pctx, m->pctx)) {
pr_debug("Events in '%s' fully contained within '%s'\n",
m->metric_name, n->metric_name);
metric_evlist = n->evlist;
break;
}
}
}
if (!metric_evlist) {
ret = parse_ids(metric_no_merge, fake_pmu, m->pctx, m->modifier,
m->group_events, tool_events, &m->evlist);
if (ret)
goto out;
metric_evlist = m->evlist;
}
ret = setup_metric_events(m->pctx->ids, metric_evlist, &metric_events);
if (ret) {
pr_debug("Cannot resolve IDs for %s: %s\n",
m->metric_name, m->metric_expr);
goto out;
}
me = metricgroup__lookup(metric_events_list, metric_events[0], true);
expr = malloc(sizeof(struct metric_expr));
if (!expr) {
ret = -ENOMEM;
free(metric_events);
goto out;
}
expr->metric_refs = m->metric_refs;
m->metric_refs = NULL;
expr->metric_expr = m->metric_expr;
if (m->modifier) {
char *tmp;
if (asprintf(&tmp, "%s:%s", m->metric_name, m->modifier) < 0)
expr->metric_name = NULL;
else
expr->metric_name = tmp;
} else
expr->metric_name = strdup(m->metric_name);
if (!expr->metric_name) {
ret = -ENOMEM;
free(metric_events);
goto out;
}
expr->metric_threshold = m->metric_threshold;
expr->metric_unit = m->metric_unit;
expr->metric_events = metric_events;
expr->runtime = m->pctx->sctx.runtime;
list_add(&expr->nd, &me->head);
}
if (combined_evlist) {
evlist__splice_list_tail(perf_evlist, &combined_evlist->core.entries);
evlist__delete(combined_evlist);
}
list_for_each_entry(m, &metric_list, nd) {
if (m->evlist)
evlist__splice_list_tail(perf_evlist, &m->evlist->core.entries);
}
out:
metricgroup__free_metrics(&metric_list);
return ret;
}
int metricgroup__parse_groups(struct evlist *perf_evlist,
const char *str,
bool metric_no_group,
bool metric_no_merge,
bool metric_no_threshold,
const char *user_requested_cpu_list,
bool system_wide,
struct rblist *metric_events)
{
const struct pmu_metrics_table *table = pmu_metrics_table__find();
if (!table)
return -EINVAL;
return parse_groups(perf_evlist, str, metric_no_group, metric_no_merge,
metric_no_threshold, user_requested_cpu_list, system_wide,
/*fake_pmu=*/NULL, metric_events, table);
}
int metricgroup__parse_groups_test(struct evlist *evlist,
const struct pmu_metrics_table *table,
const char *str,
struct rblist *metric_events)
{
return parse_groups(evlist, str,
/*metric_no_group=*/false,
/*metric_no_merge=*/false,
/*metric_no_threshold=*/false,
/*user_requested_cpu_list=*/NULL,
/*system_wide=*/false,
&perf_pmu__fake, metric_events, table);
}
static int metricgroup__has_metric_callback(const struct pmu_metric *pm,
const struct pmu_metrics_table *table __maybe_unused,
void *vdata)
{
const char *metric = vdata;
if (match_metric(pm->metric_name, metric) ||
match_metric(pm->metric_group, metric))
return 1;
return 0;
}
bool metricgroup__has_metric(const char *metric)
{
const struct pmu_metrics_table *table = pmu_metrics_table__find();
if (!table)
return false;
return pmu_metrics_table_for_each_metric(table, metricgroup__has_metric_callback,
(void *)metric) ? true : false;
}
static int metricgroup__topdown_max_level_callback(const struct pmu_metric *pm,
const struct pmu_metrics_table *table __maybe_unused,
void *data)
{
unsigned int *max_level = data;
unsigned int level;
const char *p = strstr(pm->metric_group, "TopdownL");
if (!p || p[8] == '\0')
return 0;
level = p[8] - '0';
if (level > *max_level)
*max_level = level;
return 0;
}
unsigned int metricgroups__topdown_max_level(void)
{
unsigned int max_level = 0;
const struct pmu_metrics_table *table = pmu_metrics_table__find();
if (!table)
return false;
pmu_metrics_table_for_each_metric(table, metricgroup__topdown_max_level_callback,
&max_level);
return max_level;
}
int metricgroup__copy_metric_events(struct evlist *evlist, struct cgroup *cgrp,
struct rblist *new_metric_events,
struct rblist *old_metric_events)
{
unsigned int i;
for (i = 0; i < rblist__nr_entries(old_metric_events); i++) {
struct rb_node *nd;
struct metric_event *old_me, *new_me;
struct metric_expr *old_expr, *new_expr;
struct evsel *evsel;
size_t alloc_size;
int idx, nr;
nd = rblist__entry(old_metric_events, i);
old_me = container_of(nd, struct metric_event, nd);
evsel = evlist__find_evsel(evlist, old_me->evsel->core.idx);
if (!evsel)
return -EINVAL;
new_me = metricgroup__lookup(new_metric_events, evsel, true);
if (!new_me)
return -ENOMEM;
pr_debug("copying metric event for cgroup '%s': %s (idx=%d)\n",
cgrp ? cgrp->name : "root", evsel->name, evsel->core.idx);
list_for_each_entry(old_expr, &old_me->head, nd) {
new_expr = malloc(sizeof(*new_expr));
if (!new_expr)
return -ENOMEM;
new_expr->metric_expr = old_expr->metric_expr;
new_expr->metric_name = strdup(old_expr->metric_name);
if (!new_expr->metric_name)
return -ENOMEM;
new_expr->metric_unit = old_expr->metric_unit;
new_expr->runtime = old_expr->runtime;
if (old_expr->metric_refs) {
/* calculate number of metric_events */
for (nr = 0; old_expr->metric_refs[nr].metric_name; nr++)
continue;
alloc_size = sizeof(*new_expr->metric_refs);
new_expr->metric_refs = calloc(nr + 1, alloc_size);
if (!new_expr->metric_refs) {
free(new_expr);
return -ENOMEM;
}
memcpy(new_expr->metric_refs, old_expr->metric_refs,
nr * alloc_size);
} else {
new_expr->metric_refs = NULL;
}
/* calculate number of metric_events */
for (nr = 0; old_expr->metric_events[nr]; nr++)
continue;
alloc_size = sizeof(*new_expr->metric_events);
new_expr->metric_events = calloc(nr + 1, alloc_size);
if (!new_expr->metric_events) {
free(new_expr->metric_refs);
free(new_expr);
return -ENOMEM;
}
/* copy evsel in the same position */
for (idx = 0; idx < nr; idx++) {
evsel = old_expr->metric_events[idx];
evsel = evlist__find_evsel(evlist, evsel->core.idx);
if (evsel == NULL) {
free(new_expr->metric_events);
free(new_expr->metric_refs);
free(new_expr);
return -EINVAL;
}
new_expr->metric_events[idx] = evsel;
}
list_add(&new_expr->nd, &new_me->head);
}
}
return 0;
}