linux/tools/perf/util/metricgroup.c
Kajol Jain 58fc90fda0 perf metricgroup: Fix printing event names of metric group with multiple events incase of overlapping events
Commit f01642e491 ("perf metricgroup: Support multiple events for
metricgroup") introduced support for multiple events in a metric group.
But with the current upstream, metric events names are not printed
properly incase we try to run multiple metric groups with overlapping
event.

With current upstream version, incase of overlapping metric events issue
is, we always start our comparision logic from start.  So, the events
which already matched with some metric group also take part in
comparision logic. Because of that when we have overlapping events, we
end up matching current metric group event with already matched one.

For example, in skylake machine we have metric event CoreIPC and
Instructions. Both of them need 'inst_retired.any' event value.  As
events in Instructions is subset of events in CoreIPC, they endup in
pointing to same 'inst_retired.any' value.

In skylake platform:

command:# ./perf stat -M CoreIPC,Instructions  -C 0 sleep 1

 Performance counter stats for 'CPU(s) 0':

     1,254,992,790      inst_retired.any          # 1254992790.0
                                                    Instructions
                                                  #      1.3 CoreIPC
       977,172,805      cycles
     1,254,992,756      inst_retired.any

       1.000802596 seconds time elapsed

command:# sudo ./perf stat -M UPI,IPC sleep 1

   Performance counter stats for 'sleep 1':
           948,650      uops_retired.retire_slots
           866,182      inst_retired.any          #      0.7 IPC
           866,182      inst_retired.any
         1,175,671      cpu_clk_unhalted.thread

Patch fixes the issue by adding a new bool pointer 'evlist_used' to keep
track of events which already matched with some group by setting it
true.  So, we skip all used events in list when we start comparision
logic.  Patch also make some changes in comparision logic, incase we get
a match miss, we discard the whole match and start again with first
event id in metric event.

With this patch:

In skylake platform:

command:# ./perf stat -M CoreIPC,Instructions  -C 0 sleep 1

 Performance counter stats for 'CPU(s) 0':

         3,348,415      inst_retired.any          #      0.3 CoreIPC
        11,779,026      cycles
         3,348,381      inst_retired.any          # 3348381.0
                                                    Instructions

       1.001649056 seconds time elapsed

command:# ./perf stat -M UPI,IPC sleep 1

 Performance counter stats for 'sleep 1':

         1,023,148      uops_retired.retire_slots #      1.1 UPI
           924,976      inst_retired.any
           924,976      inst_retired.any          #      0.6 IPC
         1,489,414      cpu_clk_unhalted.thread

       1.003064672 seconds time elapsed

Signed-off-by: Kajol Jain <kjain@linux.ibm.com>
Acked-by: Jiri Olsa <jolsa@kernel.org>
Cc: Alexander Shishkin <alexander.shishkin@linux.intel.com>
Cc: Andi Kleen <ak@linux.intel.com>
Cc: Anju T Sudhakar <anju@linux.vnet.ibm.com>
Cc: Jin Yao <yao.jin@linux.intel.com>
Cc: Kan Liang <kan.liang@linux.intel.com>
Cc: Madhavan Srinivasan <maddy@linux.vnet.ibm.com>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Ravi Bangoria <ravi.bangoria@linux.ibm.com>
Link: http://lore.kernel.org/lkml/20200221101121.28920-1-kjain@linux.ibm.com
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
2020-03-24 09:37:27 -03:00

636 lines
14 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 "expr.h"
#include "rblist.h"
#include <string.h>
#include <errno.h>
#include "pmu-events/pmu-events.h"
#include "strlist.h"
#include <assert.h>
#include <linux/ctype.h>
#include <linux/string.h>
#include <linux/zalloc.h>
#include <subcmd/parse-options.h>
#include <api/fs/fs.h>
#include "util.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 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;
}
struct egroup {
struct list_head nd;
int idnum;
const char **ids;
const char *metric_name;
const char *metric_expr;
const char *metric_unit;
};
static struct evsel *find_evsel_group(struct evlist *perf_evlist,
const char **ids,
int idnum,
struct evsel **metric_events,
bool *evlist_used)
{
struct evsel *ev;
int i = 0, j = 0;
bool leader_found;
evlist__for_each_entry (perf_evlist, ev) {
if (evlist_used[j++])
continue;
if (!strcmp(ev->name, ids[i])) {
if (!metric_events[i])
metric_events[i] = ev;
i++;
if (i == idnum)
break;
} else {
/* Discard the whole match and start again */
i = 0;
memset(metric_events, 0,
sizeof(struct evsel *) * idnum);
if (!strcmp(ev->name, ids[i])) {
if (!metric_events[i])
metric_events[i] = ev;
i++;
if (i == idnum)
break;
}
}
}
if (i != idnum) {
/* Not whole match */
return NULL;
}
metric_events[idnum] = NULL;
for (i = 0; i < idnum; i++) {
leader_found = false;
evlist__for_each_entry(perf_evlist, ev) {
if (!leader_found && (ev == metric_events[i]))
leader_found = true;
if (leader_found &&
!strcmp(ev->name, metric_events[i]->name)) {
ev->metric_leader = metric_events[i];
}
j++;
}
ev = metric_events[i];
evlist_used[ev->idx] = true;
}
return metric_events[0];
}
static int metricgroup__setup_events(struct list_head *groups,
struct evlist *perf_evlist,
struct rblist *metric_events_list)
{
struct metric_event *me;
struct metric_expr *expr;
int i = 0;
int ret = 0;
struct egroup *eg;
struct evsel *evsel;
bool *evlist_used;
evlist_used = calloc(perf_evlist->core.nr_entries, sizeof(bool));
if (!evlist_used) {
ret = -ENOMEM;
return ret;
}
list_for_each_entry (eg, groups, nd) {
struct evsel **metric_events;
metric_events = calloc(sizeof(void *), eg->idnum + 1);
if (!metric_events) {
ret = -ENOMEM;
break;
}
evsel = find_evsel_group(perf_evlist, eg->ids, eg->idnum,
metric_events, evlist_used);
if (!evsel) {
pr_debug("Cannot resolve %s: %s\n",
eg->metric_name, eg->metric_expr);
continue;
}
for (i = 0; i < eg->idnum; i++)
metric_events[i]->collect_stat = true;
me = metricgroup__lookup(metric_events_list, evsel, true);
if (!me) {
ret = -ENOMEM;
break;
}
expr = malloc(sizeof(struct metric_expr));
if (!expr) {
ret = -ENOMEM;
break;
}
expr->metric_expr = eg->metric_expr;
expr->metric_name = eg->metric_name;
expr->metric_unit = eg->metric_unit;
expr->metric_events = metric_events;
list_add(&expr->nd, &me->head);
}
free(evlist_used);
return ret;
}
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;
}
struct mep {
struct rb_node nd;
const char *name;
struct strlist *metrics;
};
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;
return strcmp(a->name, b->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));
me->name = strdup(me->name);
if (!me->name)
goto out_me;
me->metrics = strlist__new(NULL, NULL);
if (!me->metrics)
goto out_name;
return &me->nd;
out_name:
zfree(&me->name);
out_me:
free(me);
return NULL;
}
static struct mep *mep_lookup(struct rblist *groups, const char *name)
{
struct rb_node *nd;
struct mep me = {
.name = name
};
nd = rblist__find(groups, &me);
if (nd)
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 void mep_delete(struct rblist *rl __maybe_unused,
struct rb_node *nd)
{
struct mep *me = container_of(nd, struct mep, nd);
strlist__delete(me->metrics);
zfree(&me->name);
free(me);
}
static void metricgroup__print_strlist(struct strlist *metrics, bool raw)
{
struct str_node *sn;
int n = 0;
strlist__for_each_entry (sn, metrics) {
if (raw)
printf("%s%s", n > 0 ? " " : "", sn->s);
else
printf(" %s\n", sn->s);
n++;
}
if (raw)
putchar('\n');
}
void metricgroup__print(bool metrics, bool metricgroups, char *filter,
bool raw, bool details)
{
struct pmu_events_map *map = perf_pmu__find_map(NULL);
struct pmu_event *pe;
int i;
struct rblist groups;
struct rb_node *node, *next;
struct strlist *metriclist = NULL;
if (!map)
return;
if (!metricgroups) {
metriclist = strlist__new(NULL, NULL);
if (!metriclist)
return;
}
rblist__init(&groups);
groups.node_new = mep_new;
groups.node_cmp = mep_cmp;
groups.node_delete = mep_delete;
for (i = 0; ; i++) {
const char *g;
pe = &map->table[i];
if (!pe->name && !pe->metric_group && !pe->metric_name)
break;
if (!pe->metric_expr)
continue;
g = pe->metric_group;
if (!g && pe->metric_name) {
if (pe->name)
continue;
g = "No_group";
}
if (g) {
char *omg;
char *mg = strdup(g);
if (!mg)
return;
omg = mg;
while ((g = strsep(&mg, ";")) != NULL) {
struct mep *me;
char *s;
g = skip_spaces(g);
if (*g == 0)
g = "No_group";
if (filter && !strstr(g, filter))
continue;
if (raw)
s = (char *)pe->metric_name;
else {
if (asprintf(&s, "%s\n%*s%s]",
pe->metric_name, 8, "[", pe->desc) < 0)
return;
if (details) {
if (asprintf(&s, "%s\n%*s%s]",
s, 8, "[", pe->metric_expr) < 0)
return;
}
}
if (!s)
continue;
if (!metricgroups) {
strlist__add(metriclist, s);
} else {
me = mep_lookup(&groups, g);
if (!me)
continue;
strlist__add(me->metrics, s);
}
}
free(omg);
}
}
if (metricgroups && !raw)
printf("\nMetric Groups:\n\n");
else if (metrics && !raw)
printf("\nMetrics:\n\n");
for (node = rb_first_cached(&groups.entries); node; node = next) {
struct mep *me = container_of(node, struct mep, nd);
if (metricgroups)
printf("%s%s%s", me->name, metrics && !raw ? ":" : "", raw ? " " : "\n");
if (metrics)
metricgroup__print_strlist(me->metrics, raw);
next = rb_next(node);
rblist__remove_node(&groups, node);
}
if (!metricgroups)
metricgroup__print_strlist(metriclist, raw);
strlist__delete(metriclist);
}
static void metricgroup__add_metric_weak_group(struct strbuf *events,
const char **ids,
int idnum)
{
bool no_group = false;
int i;
for (i = 0; i < idnum; i++) {
pr_debug("found event %s\n", ids[i]);
/*
* Duration time maps to a software event and can make
* groups not count. Always use it outside a
* group.
*/
if (!strcmp(ids[i], "duration_time")) {
if (i > 0)
strbuf_addf(events, "}:W,");
strbuf_addf(events, "duration_time");
no_group = true;
continue;
}
strbuf_addf(events, "%s%s",
i == 0 || no_group ? "{" : ",",
ids[i]);
no_group = false;
}
if (!no_group)
strbuf_addf(events, "}:W");
}
static void metricgroup__add_metric_non_group(struct strbuf *events,
const char **ids,
int idnum)
{
int i;
for (i = 0; i < idnum; i++)
strbuf_addf(events, ",%s", ids[i]);
}
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(struct pmu_event *pe)
{
if (!pe->metric_constraint)
return false;
if (!strcmp(pe->metric_constraint, "NO_NMI_WATCHDOG") &&
sysctl__nmi_watchdog_enabled()) {
metricgroup___watchdog_constraint_hint(pe->metric_name, false);
return true;
}
return false;
}
static int metricgroup__add_metric(const char *metric, struct strbuf *events,
struct list_head *group_list)
{
struct pmu_events_map *map = perf_pmu__find_map(NULL);
struct pmu_event *pe;
int i, ret = -EINVAL;
if (!map)
return 0;
for (i = 0; ; i++) {
pe = &map->table[i];
if (!pe->name && !pe->metric_group && !pe->metric_name)
break;
if (!pe->metric_expr)
continue;
if (match_metric(pe->metric_group, metric) ||
match_metric(pe->metric_name, metric)) {
const char **ids;
int idnum;
struct egroup *eg;
pr_debug("metric expr %s for %s\n", pe->metric_expr, pe->metric_name);
if (expr__find_other(pe->metric_expr,
NULL, &ids, &idnum) < 0)
continue;
if (events->len > 0)
strbuf_addf(events, ",");
if (metricgroup__has_constraint(pe))
metricgroup__add_metric_non_group(events, ids, idnum);
else
metricgroup__add_metric_weak_group(events, ids, idnum);
eg = malloc(sizeof(struct egroup));
if (!eg) {
ret = -ENOMEM;
break;
}
eg->ids = ids;
eg->idnum = idnum;
eg->metric_name = pe->metric_name;
eg->metric_expr = pe->metric_expr;
eg->metric_unit = pe->unit;
list_add_tail(&eg->nd, group_list);
ret = 0;
}
}
return ret;
}
static int metricgroup__add_metric_list(const char *list, struct strbuf *events,
struct list_head *group_list)
{
char *llist, *nlist, *p;
int ret = -EINVAL;
nlist = strdup(list);
if (!nlist)
return -ENOMEM;
llist = nlist;
strbuf_init(events, 100);
strbuf_addf(events, "%s", "");
while ((p = strsep(&llist, ",")) != NULL) {
ret = metricgroup__add_metric(p, events, group_list);
if (ret == -EINVAL) {
fprintf(stderr, "Cannot find metric or group `%s'\n",
p);
break;
}
}
free(nlist);
if (!ret)
metricgroup___watchdog_constraint_hint(NULL, true);
return ret;
}
static void metricgroup__free_egroups(struct list_head *group_list)
{
struct egroup *eg, *egtmp;
int i;
list_for_each_entry_safe (eg, egtmp, group_list, nd) {
for (i = 0; i < eg->idnum; i++)
zfree(&eg->ids[i]);
zfree(&eg->ids);
list_del_init(&eg->nd);
free(eg);
}
}
int metricgroup__parse_groups(const struct option *opt,
const char *str,
struct rblist *metric_events)
{
struct parse_events_error parse_error;
struct evlist *perf_evlist = *(struct evlist **)opt->value;
struct strbuf extra_events;
LIST_HEAD(group_list);
int ret;
if (metric_events->nr_entries == 0)
metricgroup__rblist_init(metric_events);
ret = metricgroup__add_metric_list(str, &extra_events, &group_list);
if (ret)
return ret;
pr_debug("adding %s\n", extra_events.buf);
bzero(&parse_error, sizeof(parse_error));
ret = parse_events(perf_evlist, extra_events.buf, &parse_error);
if (ret) {
parse_events_print_error(&parse_error, extra_events.buf);
goto out;
}
strbuf_release(&extra_events);
ret = metricgroup__setup_events(&group_list, perf_evlist,
metric_events);
out:
metricgroup__free_egroups(&group_list);
return ret;
}
bool metricgroup__has_metric(const char *metric)
{
struct pmu_events_map *map = perf_pmu__find_map(NULL);
struct pmu_event *pe;
int i;
if (!map)
return false;
for (i = 0; ; i++) {
pe = &map->table[i];
if (!pe->name && !pe->metric_group && !pe->metric_name)
break;
if (!pe->metric_expr)
continue;
if (match_metric(pe->metric_name, metric))
return true;
}
return false;
}