linux-stable/tools/perf/util/metricgroup.c
Ian Rogers f8ea2c1524 perf pmu-events: Introduce pmu_metrics_table
Add a metrics table that is just a cast from pmu_events_table. This
changes the APIs so that event and metric usage of the underlying
table is different. For the no jevents case the tables are already
separate, later changes will separate the tables for the jevents case.

Reviewed-by: Kajol Jain <kjain@linux.ibm.com>
Signed-off-by: Ian Rogers <irogers@google.com>
Cc: Adrian Hunter <adrian.hunter@intel.com>
Cc: Alexander Shishkin <alexander.shishkin@linux.intel.com>
Cc: Caleb Biggers <caleb.biggers@intel.com>
Cc: Florian Fischer <florian.fischer@muhq.space>
Cc: Ian Rogers <irogers@google.com>
Cc: Ingo Molnar <mingo@redhat.com>
Cc: James Clark <james.clark@arm.com>
Cc: Jing Zhang <renyu.zj@linux.alibaba.com>
Cc: Jiri Olsa <jolsa@kernel.org>
Cc: John Garry <john.g.garry@oracle.com>
Cc: Kan Liang <kan.liang@linux.intel.com>
Cc: Kang Minchul <tegongkang@gmail.com>
Cc: Kim Phillips <kim.phillips@amd.com>
Cc: Leo Yan <leo.yan@linaro.org>
Cc: Mark Rutland <mark.rutland@arm.com>
Cc: Mike Leach <mike.leach@linaro.org>
Cc: Namhyung Kim <namhyung@kernel.org>
Cc: Perry Taylor <perry.taylor@intel.com>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Ravi Bangoria <ravi.bangoria@amd.com>
Cc: Rob Herring <robh@kernel.org>
Cc: Sandipan Das <sandipan.das@amd.com>
Cc: Stephane Eranian <eranian@google.com>
Cc: Will Deacon <will@kernel.org>
Cc: Xing Zhengjun <zhengjun.xing@linux.intel.com>
Cc: linux-arm-kernel@lists.infradead.org
Cc: linuxppc-dev@lists.ozlabs.org
Link: https://lore.kernel.org/r/20230126233645.200509-10-irogers@google.com
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
2023-02-03 13:54:21 -03:00

1714 lines
44 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 "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;
/**
* 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;
/**
* Is there a constraint on the group of events? In which case the
* events won't be grouped.
*/
bool has_constraint;
/**
* 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 metricgroup___watchdog_constraint_hint(const char *name, bool foot)
{
static bool violate_nmi_constraint;
if (!foot) {
pr_warning("Splitting metric group %s into standalone metrics.\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 metricgroup__has_constraint(const struct pmu_metric *pm)
{
if (!pm->metric_constraint)
return false;
if (!strcmp(pm->metric_constraint, "NO_NMI_WATCHDOG") &&
sysctl__nmi_watchdog_enabled()) {
metricgroup___watchdog_constraint_hint(pm->metric_name, false);
return true;
}
return false;
}
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_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->has_constraint = metric_no_group || metricgroup__has_constraint(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_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_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_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 has_constraint)
{
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 (!has_constraint) {
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 && !has_constraint) {
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;
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,
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,
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,
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.
* @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,
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;
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.
*/
if (expr__find_ids(pm->metric_expr, NULL, root_metric->pctx) < 0) {
/* Broken metric. */
ret = -EINVAL;
} else {
/* Resolve referenced metrics. */
ret = resolve_metric(metric_list, modifier, metric_no_group,
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,
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, 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, 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->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 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->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,
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,
.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,
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, 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.
*/
metricgroup___watchdog_constraint_hint(NULL, 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 has_constraint
* 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->has_constraint && !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.
* @has_constraint: false if events should 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 has_constraint, 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,
has_constraint);
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);
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,
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,
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,
/*has_constraint=*/true,
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->has_constraint) {
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->has_constraint, 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_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,
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,
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,
bool metric_no_group,
bool metric_no_merge,
struct rblist *metric_events)
{
return parse_groups(evlist, str, metric_no_group, metric_no_merge,
/*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 (!pm->metric_expr)
return 0;
if (match_metric(pm->metric_name, 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;
}
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;
}