perf expr: Migrate expr ids table to a hashmap

Use a hashmap between a char* string and a double* value. While bpf's
hashmap entries are size_t in size, we can't guarantee sizeof(size_t) >=
sizeof(double). Avoid a memory allocation when gathering ids by making
0.0 a special value encoded as NULL.

Original map suggestion by Andi Kleen:

  https://lore.kernel.org/lkml/20200224210308.GQ160988@tassilo.jf.intel.com/

and seconded by Jiri Olsa:

  https://lore.kernel.org/lkml/20200423112915.GH1136647@krava/

Committer notes:

There are fixes that need to land upstream before we can use libbpf's
headers, for now use our copy unconditionally, since the data structures
at this point are exactly the same, no problem.

When the fixes for libbpf's hashmap land upstream, we can fix this up.

Testing it:

Building with LIBBPF=1, i.e. the default:

  $ perf -vv | grep -i bpf
                     bpf: [ on  ]  # HAVE_LIBBPF_SUPPORT
  $ nm ~/bin/perf | grep -i libbpf_ | wc -l
  39
  $ nm ~/bin/perf | grep -i hashmap_ | wc -l
  17
  $

Explicitely building without LIBBPF:

  $ perf -vv | grep -i bpf
                     bpf: [ OFF ]  # HAVE_LIBBPF_SUPPORT
  $
  $ nm ~/bin/perf | grep -i libbpf_ | wc -l
  0
  $ nm ~/bin/perf | grep -i hashmap_ | wc -l
  9
  $

Signed-off-by: Ian Rogers <irogers@google.com>
Tested-by: Arnaldo Carvalho de Melo <acme@redhat.com>
Cc: Adrian Hunter <adrian.hunter@intel.com>
Cc: Alexander Shishkin <alexander.shishkin@linux.intel.com>
Cc: Alexei Starovoitov <ast@kernel.org>
Cc: Andi Kleen <ak@linux.intel.com>
Cc: Andrii Nakryiko <andriin@fb.com>
Cc: Cong Wang <xiyou.wangcong@gmail.com>
Cc: Daniel Borkmann <daniel@iogearbox.net>
Cc: Jin Yao <yao.jin@linux.intel.com>
Cc: Jiri Olsa <jolsa@redhat.com>
Cc: John Fastabend <john.fastabend@gmail.com>
Cc: John Garry <john.garry@huawei.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: Martin KaFai Lau <kafai@fb.com>
Cc: Namhyung Kim <namhyung@kernel.org>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Song Liu <songliubraving@fb.com>
Cc: Stephane Eranian <eranian@google.com>
Cc: Yonghong Song <yhs@fb.com>
Cc: bpf@vger.kernel.org
Cc: kp singh <kpsingh@chromium.org>
Cc: netdev@vger.kernel.org
Link: http://lore.kernel.org/lkml/20200515221732.44078-8-irogers@google.com
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
This commit is contained in:
Ian Rogers 2020-05-15 15:17:32 -07:00 committed by Arnaldo Carvalho de Melo
parent eee1950192
commit ded80bda8b
7 changed files with 200 additions and 190 deletions

View file

@ -19,15 +19,13 @@ static int test(struct expr_parse_ctx *ctx, const char *e, double val2)
int test__expr(struct test *t __maybe_unused, int subtest __maybe_unused) int test__expr(struct test *t __maybe_unused, int subtest __maybe_unused)
{ {
const char *p; const char *p;
const char **other; double val, *val_ptr;
double val; int ret;
int i, ret;
struct expr_parse_ctx ctx; struct expr_parse_ctx ctx;
int num_other;
expr__ctx_init(&ctx); expr__ctx_init(&ctx);
expr__add_id(&ctx, "FOO", 1); expr__add_id(&ctx, strdup("FOO"), 1);
expr__add_id(&ctx, "BAR", 2); expr__add_id(&ctx, strdup("BAR"), 2);
ret = test(&ctx, "1+1", 2); ret = test(&ctx, "1+1", 2);
ret |= test(&ctx, "FOO+BAR", 3); ret |= test(&ctx, "FOO+BAR", 3);
@ -52,25 +50,29 @@ int test__expr(struct test *t __maybe_unused, int subtest __maybe_unused)
ret = expr__parse(&val, &ctx, p, 1); ret = expr__parse(&val, &ctx, p, 1);
TEST_ASSERT_VAL("missing operand", ret == -1); TEST_ASSERT_VAL("missing operand", ret == -1);
expr__ctx_clear(&ctx);
TEST_ASSERT_VAL("find other", TEST_ASSERT_VAL("find other",
expr__find_other("FOO + BAR + BAZ + BOZO", "FOO", &other, &num_other, 1) == 0); expr__find_other("FOO + BAR + BAZ + BOZO", "FOO",
TEST_ASSERT_VAL("find other", num_other == 3); &ctx, 1) == 0);
TEST_ASSERT_VAL("find other", !strcmp(other[0], "BAR")); TEST_ASSERT_VAL("find other", hashmap__size(&ctx.ids) == 3);
TEST_ASSERT_VAL("find other", !strcmp(other[1], "BAZ")); TEST_ASSERT_VAL("find other", hashmap__find(&ctx.ids, "BAR",
TEST_ASSERT_VAL("find other", !strcmp(other[2], "BOZO")); (void **)&val_ptr));
TEST_ASSERT_VAL("find other", other[3] == NULL); TEST_ASSERT_VAL("find other", hashmap__find(&ctx.ids, "BAZ",
(void **)&val_ptr));
TEST_ASSERT_VAL("find other", hashmap__find(&ctx.ids, "BOZO",
(void **)&val_ptr));
expr__ctx_clear(&ctx);
TEST_ASSERT_VAL("find other", TEST_ASSERT_VAL("find other",
expr__find_other("EVENT1\\,param\\=?@ + EVENT2\\,param\\=?@", NULL, expr__find_other("EVENT1\\,param\\=?@ + EVENT2\\,param\\=?@",
&other, &num_other, 3) == 0); NULL, &ctx, 3) == 0);
TEST_ASSERT_VAL("find other", num_other == 2); TEST_ASSERT_VAL("find other", hashmap__size(&ctx.ids) == 2);
TEST_ASSERT_VAL("find other", !strcmp(other[0], "EVENT1,param=3/")); TEST_ASSERT_VAL("find other", hashmap__find(&ctx.ids, "EVENT1,param=3/",
TEST_ASSERT_VAL("find other", !strcmp(other[1], "EVENT2,param=3/")); (void **)&val_ptr));
TEST_ASSERT_VAL("find other", other[2] == NULL); TEST_ASSERT_VAL("find other", hashmap__find(&ctx.ids, "EVENT2,param=3/",
(void **)&val_ptr));
for (i = 0; i < num_other; i++) expr__ctx_clear(&ctx);
zfree(&other[i]);
free((void *)other);
return 0; return 0;
} }

View file

@ -437,8 +437,6 @@ static int test_parsing(void)
struct pmu_events_map *map; struct pmu_events_map *map;
struct pmu_event *pe; struct pmu_event *pe;
int i, j, k; int i, j, k;
const char **ids;
int idnum;
int ret = 0; int ret = 0;
struct expr_parse_ctx ctx; struct expr_parse_ctx ctx;
double result; double result;
@ -450,29 +448,34 @@ static int test_parsing(void)
break; break;
j = 0; j = 0;
for (;;) { for (;;) {
struct hashmap_entry *cur;
size_t bkt;
pe = &map->table[j++]; pe = &map->table[j++];
if (!pe->name && !pe->metric_group && !pe->metric_name) if (!pe->name && !pe->metric_group && !pe->metric_name)
break; break;
if (!pe->metric_expr) if (!pe->metric_expr)
continue; continue;
if (expr__find_other(pe->metric_expr, NULL, expr__ctx_init(&ctx);
&ids, &idnum, 0) < 0) { if (expr__find_other(pe->metric_expr, NULL, &ctx, 0)
< 0) {
expr_failure("Parse other failed", map, pe); expr_failure("Parse other failed", map, pe);
ret++; ret++;
continue; continue;
} }
expr__ctx_init(&ctx);
/* /*
* Add all ids with a made up value. The value may * Add all ids with a made up value. The value may
* trigger divide by zero when subtracted and so try to * trigger divide by zero when subtracted and so try to
* make them unique. * make them unique.
*/ */
for (k = 0; k < idnum; k++) k = 1;
expr__add_id(&ctx, ids[k], k + 1); hashmap__for_each_entry((&ctx.ids), cur, bkt)
expr__add_id(&ctx, strdup(cur->key), k++);
for (k = 0; k < idnum; k++) { hashmap__for_each_entry((&ctx.ids), cur, bkt) {
if (check_parse_id(ids[k], map == cpus_map, pe)) if (check_parse_id(cur->key, map == cpus_map,
pe))
ret++; ret++;
} }
@ -480,9 +483,7 @@ static int test_parsing(void)
expr_failure("Parse failed", map, pe); expr_failure("Parse failed", map, pe);
ret++; ret++;
} }
for (k = 0; k < idnum; k++) expr__ctx_clear(&ctx);
zfree(&ids[k]);
free(ids);
} }
} }
/* TODO: fail when not ok */ /* TODO: fail when not ok */

View file

@ -4,25 +4,76 @@
#include "expr.h" #include "expr.h"
#include "expr-bison.h" #include "expr-bison.h"
#include "expr-flex.h" #include "expr-flex.h"
#include <linux/kernel.h>
#ifdef PARSER_DEBUG #ifdef PARSER_DEBUG
extern int expr_debug; extern int expr_debug;
#endif #endif
/* Caller must make sure id is allocated */ static size_t key_hash(const void *key, void *ctx __maybe_unused)
void expr__add_id(struct expr_parse_ctx *ctx, const char *name, double val)
{ {
int idx; const char *str = (const char *)key;
size_t hash = 0;
assert(ctx->num_ids < MAX_PARSE_ID); while (*str != '\0') {
idx = ctx->num_ids++; hash *= 31;
ctx->ids[idx].name = name; hash += *str;
ctx->ids[idx].val = val; str++;
}
return hash;
}
static bool key_equal(const void *key1, const void *key2,
void *ctx __maybe_unused)
{
return !strcmp((const char *)key1, (const char *)key2);
}
/* Caller must make sure id is allocated */
int expr__add_id(struct expr_parse_ctx *ctx, const char *name, double val)
{
double *val_ptr = NULL, *old_val = NULL;
char *old_key = NULL;
int ret;
if (val != 0.0) {
val_ptr = malloc(sizeof(double));
if (!val_ptr)
return -ENOMEM;
*val_ptr = val;
}
ret = hashmap__set(&ctx->ids, name, val_ptr,
(const void **)&old_key, (void **)&old_val);
free(old_key);
free(old_val);
return ret;
}
int expr__get_id(struct expr_parse_ctx *ctx, const char *id, double *val_ptr)
{
double *data;
if (!hashmap__find(&ctx->ids, id, (void **)&data))
return -1;
*val_ptr = (data == NULL) ? 0.0 : *data;
return 0;
} }
void expr__ctx_init(struct expr_parse_ctx *ctx) void expr__ctx_init(struct expr_parse_ctx *ctx)
{ {
ctx->num_ids = 0; hashmap__init(&ctx->ids, key_hash, key_equal, NULL);
}
void expr__ctx_clear(struct expr_parse_ctx *ctx)
{
struct hashmap_entry *cur;
size_t bkt;
hashmap__for_each_entry((&ctx->ids), cur, bkt) {
free((char *)cur->key);
free(cur->value);
}
hashmap__clear(&ctx->ids);
} }
static int static int
@ -56,61 +107,25 @@ __expr__parse(double *val, struct expr_parse_ctx *ctx, const char *expr,
return ret; return ret;
} }
int expr__parse(double *final_val, struct expr_parse_ctx *ctx, const char *expr, int runtime) int expr__parse(double *final_val, struct expr_parse_ctx *ctx,
const char *expr, int runtime)
{ {
return __expr__parse(final_val, ctx, expr, EXPR_PARSE, runtime) ? -1 : 0; return __expr__parse(final_val, ctx, expr, EXPR_PARSE, runtime) ? -1 : 0;
} }
static bool int expr__find_other(const char *expr, const char *one,
already_seen(const char *val, const char *one, const char **other, struct expr_parse_ctx *ctx, int runtime)
int num_other)
{ {
int i; double *old_val = NULL;
char *old_key = NULL;
int ret = __expr__parse(NULL, ctx, expr, EXPR_OTHER, runtime);
if (one && !strcasecmp(one, val)) if (one) {
return true; hashmap__delete(&ctx->ids, one,
for (i = 0; i < num_other; i++) (const void **)&old_key, (void **)&old_val);
if (!strcasecmp(other[i], val)) free(old_key);
return true; free(old_val);
return false;
}
int expr__find_other(const char *expr, const char *one, const char ***other,
int *num_other, int runtime)
{
int err, i = 0, j = 0;
struct expr_parse_ctx ctx;
expr__ctx_init(&ctx);
err = __expr__parse(NULL, &ctx, expr, EXPR_OTHER, runtime);
if (err)
return -1;
*other = malloc((ctx.num_ids + 1) * sizeof(char *));
if (!*other)
return -ENOMEM;
for (i = 0, j = 0; i < ctx.num_ids; i++) {
const char *str = ctx.ids[i].name;
if (already_seen(str, one, *other, j))
continue;
str = strdup(str);
if (!str)
goto out;
(*other)[j++] = str;
}
(*other)[j] = NULL;
out:
if (i != ctx.num_ids) {
while (--j)
free((char *) (*other)[i]);
free(*other);
err = -1;
} }
*num_other = j; return ret;
return err;
} }

View file

@ -2,17 +2,17 @@
#ifndef PARSE_CTX_H #ifndef PARSE_CTX_H
#define PARSE_CTX_H 1 #define PARSE_CTX_H 1
#define EXPR_MAX_OTHER 64 // There are fixes that need to land upstream before we can use libbpf's headers,
#define MAX_PARSE_ID EXPR_MAX_OTHER // for now use our copy uncoditionally, since the data structures at this point
// are exactly the same, no problem.
struct expr_parse_id { //#ifdef HAVE_LIBBPF_SUPPORT
const char *name; //#include <bpf/hashmap.h>
double val; //#else
}; #include "util/hashmap.h"
//#endif
struct expr_parse_ctx { struct expr_parse_ctx {
int num_ids; struct hashmap ids;
struct expr_parse_id ids[MAX_PARSE_ID];
}; };
struct expr_scanner_ctx { struct expr_scanner_ctx {
@ -21,9 +21,12 @@ struct expr_scanner_ctx {
}; };
void expr__ctx_init(struct expr_parse_ctx *ctx); void expr__ctx_init(struct expr_parse_ctx *ctx);
void expr__add_id(struct expr_parse_ctx *ctx, const char *id, double val); void expr__ctx_clear(struct expr_parse_ctx *ctx);
int expr__parse(double *final_val, struct expr_parse_ctx *ctx, const char *expr, int runtime); int expr__add_id(struct expr_parse_ctx *ctx, const char *id, double val);
int expr__find_other(const char *expr, const char *one, const char ***other, int expr__get_id(struct expr_parse_ctx *ctx, const char *id, double *val_ptr);
int *num_other, int runtime); int expr__parse(double *final_val, struct expr_parse_ctx *ctx,
const char *expr, int runtime);
int expr__find_other(const char *expr, const char *one,
struct expr_parse_ctx *ids, int runtime);
#endif #endif

View file

@ -47,19 +47,6 @@ static void expr_error(double *final_val __maybe_unused,
pr_debug("%s\n", s); pr_debug("%s\n", s);
} }
static int lookup_id(struct expr_parse_ctx *ctx, char *id, double *val)
{
int i;
for (i = 0; i < ctx->num_ids; i++) {
if (!strcasecmp(ctx->ids[i].name, id)) {
*val = ctx->ids[i].val;
return 0;
}
}
return -1;
}
%} %}
%% %%
@ -73,12 +60,7 @@ all_other: all_other other
other: ID other: ID
{ {
if (ctx->num_ids + 1 >= EXPR_MAX_OTHER) { expr__add_id(ctx, $1, 0.0);
pr_err("failed: way too many variables");
YYABORT;
}
ctx->ids[ctx->num_ids++].name = $1;
} }
| |
MIN | MAX | IF | ELSE | SMT_ON | NUMBER | '|' | '^' | '&' | '-' | '+' | '*' | '/' | '%' | '(' | ')' | ',' MIN | MAX | IF | ELSE | SMT_ON | NUMBER | '|' | '^' | '&' | '-' | '+' | '*' | '/' | '%' | '(' | ')' | ','
@ -93,7 +75,7 @@ if_expr:
; ;
expr: NUMBER expr: NUMBER
| ID { if (lookup_id(ctx, $1, &$$) < 0) { | ID { if (expr__get_id(ctx, $1, &$$)) {
pr_debug("%s not found\n", $1); pr_debug("%s not found\n", $1);
free($1); free($1);
YYABORT; YYABORT;

View file

@ -85,8 +85,7 @@ static void metricgroup__rblist_init(struct rblist *metric_events)
struct egroup { struct egroup {
struct list_head nd; struct list_head nd;
int idnum; struct expr_parse_ctx pctx;
const char **ids;
const char *metric_name; const char *metric_name;
const char *metric_expr; const char *metric_expr;
const char *metric_unit; const char *metric_unit;
@ -94,19 +93,21 @@ struct egroup {
}; };
static struct evsel *find_evsel_group(struct evlist *perf_evlist, static struct evsel *find_evsel_group(struct evlist *perf_evlist,
const char **ids, struct expr_parse_ctx *pctx,
int idnum,
struct evsel **metric_events, struct evsel **metric_events,
bool *evlist_used) bool *evlist_used)
{ {
struct evsel *ev; struct evsel *ev;
int i = 0, j = 0;
bool leader_found; bool leader_found;
const size_t idnum = hashmap__size(&pctx->ids);
size_t i = 0;
int j = 0;
double *val_ptr;
evlist__for_each_entry (perf_evlist, ev) { evlist__for_each_entry (perf_evlist, ev) {
if (evlist_used[j++]) if (evlist_used[j++])
continue; continue;
if (!strcmp(ev->name, ids[i])) { if (hashmap__find(&pctx->ids, ev->name, (void **)&val_ptr)) {
if (!metric_events[i]) if (!metric_events[i])
metric_events[i] = ev; metric_events[i] = ev;
i++; i++;
@ -117,14 +118,6 @@ static struct evsel *find_evsel_group(struct evlist *perf_evlist,
i = 0; i = 0;
memset(metric_events, 0, memset(metric_events, 0,
sizeof(struct evsel *) * idnum); sizeof(struct evsel *) * idnum);
if (!strcmp(ev->name, ids[i])) {
if (!metric_events[i])
metric_events[i] = ev;
i++;
if (i == idnum)
break;
}
} }
} }
@ -175,19 +168,20 @@ static int metricgroup__setup_events(struct list_head *groups,
list_for_each_entry (eg, groups, nd) { list_for_each_entry (eg, groups, nd) {
struct evsel **metric_events; struct evsel **metric_events;
metric_events = calloc(sizeof(void *), eg->idnum + 1); metric_events = calloc(sizeof(void *),
hashmap__size(&eg->pctx.ids) + 1);
if (!metric_events) { if (!metric_events) {
ret = -ENOMEM; ret = -ENOMEM;
break; break;
} }
evsel = find_evsel_group(perf_evlist, eg->ids, eg->idnum, evsel = find_evsel_group(perf_evlist, &eg->pctx, metric_events,
metric_events, evlist_used); evlist_used);
if (!evsel) { if (!evsel) {
pr_debug("Cannot resolve %s: %s\n", pr_debug("Cannot resolve %s: %s\n",
eg->metric_name, eg->metric_expr); eg->metric_name, eg->metric_expr);
continue; continue;
} }
for (i = 0; i < eg->idnum; i++) for (i = 0; metric_events[i]; i++)
metric_events[i]->collect_stat = true; metric_events[i]->collect_stat = true;
me = metricgroup__lookup(metric_events_list, evsel, true); me = metricgroup__lookup(metric_events_list, evsel, true);
if (!me) { if (!me) {
@ -415,20 +409,20 @@ void metricgroup__print(bool metrics, bool metricgroups, char *filter,
} }
static void metricgroup__add_metric_weak_group(struct strbuf *events, static void metricgroup__add_metric_weak_group(struct strbuf *events,
const char **ids, struct expr_parse_ctx *ctx)
int idnum)
{ {
struct hashmap_entry *cur;
size_t bkt, i = 0;
bool no_group = false; bool no_group = false;
int i;
for (i = 0; i < idnum; i++) { hashmap__for_each_entry((&ctx->ids), cur, bkt) {
pr_debug("found event %s\n", ids[i]); pr_debug("found event %s\n", (const char *)cur->key);
/* /*
* Duration time maps to a software event and can make * Duration time maps to a software event and can make
* groups not count. Always use it outside a * groups not count. Always use it outside a
* group. * group.
*/ */
if (!strcmp(ids[i], "duration_time")) { if (!strcmp(cur->key, "duration_time")) {
if (i > 0) if (i > 0)
strbuf_addf(events, "}:W,"); strbuf_addf(events, "}:W,");
strbuf_addf(events, "duration_time"); strbuf_addf(events, "duration_time");
@ -437,21 +431,22 @@ static void metricgroup__add_metric_weak_group(struct strbuf *events,
} }
strbuf_addf(events, "%s%s", strbuf_addf(events, "%s%s",
i == 0 || no_group ? "{" : ",", i == 0 || no_group ? "{" : ",",
ids[i]); (const char *)cur->key);
no_group = false; no_group = false;
i++;
} }
if (!no_group) if (!no_group)
strbuf_addf(events, "}:W"); strbuf_addf(events, "}:W");
} }
static void metricgroup__add_metric_non_group(struct strbuf *events, static void metricgroup__add_metric_non_group(struct strbuf *events,
const char **ids, struct expr_parse_ctx *ctx)
int idnum)
{ {
int i; struct hashmap_entry *cur;
size_t bkt;
for (i = 0; i < idnum; i++) hashmap__for_each_entry((&ctx->ids), cur, bkt)
strbuf_addf(events, ",%s", ids[i]); strbuf_addf(events, ",%s", (const char *)cur->key);
} }
static void metricgroup___watchdog_constraint_hint(const char *name, bool foot) static void metricgroup___watchdog_constraint_hint(const char *name, bool foot)
@ -495,32 +490,32 @@ int __weak arch_get_runtimeparam(void)
static int __metricgroup__add_metric(struct strbuf *events, static int __metricgroup__add_metric(struct strbuf *events,
struct list_head *group_list, struct pmu_event *pe, int runtime) struct list_head *group_list, struct pmu_event *pe, int runtime)
{ {
const char **ids;
int idnum;
struct egroup *eg; struct egroup *eg;
if (expr__find_other(pe->metric_expr, NULL, &ids, &idnum, runtime) < 0)
return -EINVAL;
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(*eg)); eg = malloc(sizeof(*eg));
if (!eg) if (!eg)
return -ENOMEM; return -ENOMEM;
eg->ids = ids; expr__ctx_init(&eg->pctx);
eg->idnum = idnum;
eg->metric_name = pe->metric_name; eg->metric_name = pe->metric_name;
eg->metric_expr = pe->metric_expr; eg->metric_expr = pe->metric_expr;
eg->metric_unit = pe->unit; eg->metric_unit = pe->unit;
eg->runtime = runtime; eg->runtime = runtime;
if (expr__find_other(pe->metric_expr, NULL, &eg->pctx, runtime) < 0) {
expr__ctx_clear(&eg->pctx);
free(eg);
return -EINVAL;
}
if (events->len > 0)
strbuf_addf(events, ",");
if (metricgroup__has_constraint(pe))
metricgroup__add_metric_non_group(events, &eg->pctx);
else
metricgroup__add_metric_weak_group(events, &eg->pctx);
list_add_tail(&eg->nd, group_list); list_add_tail(&eg->nd, group_list);
return 0; return 0;
@ -603,12 +598,9 @@ static int metricgroup__add_metric_list(const char *list, struct strbuf *events,
static void metricgroup__free_egroups(struct list_head *group_list) static void metricgroup__free_egroups(struct list_head *group_list)
{ {
struct egroup *eg, *egtmp; struct egroup *eg, *egtmp;
int i;
list_for_each_entry_safe (eg, egtmp, group_list, nd) { list_for_each_entry_safe (eg, egtmp, group_list, nd) {
for (i = 0; i < eg->idnum; i++) expr__ctx_clear(&eg->pctx);
zfree(&eg->ids[i]);
zfree(&eg->ids);
list_del_init(&eg->nd); list_del_init(&eg->nd);
free(eg); free(eg);
} }

View file

@ -323,35 +323,46 @@ void perf_stat__collect_metric_expr(struct evlist *evsel_list)
{ {
struct evsel *counter, *leader, **metric_events, *oc; struct evsel *counter, *leader, **metric_events, *oc;
bool found; bool found;
const char **metric_names; struct expr_parse_ctx ctx;
struct hashmap_entry *cur;
size_t bkt;
int i; int i;
int num_metric_names;
expr__ctx_init(&ctx);
evlist__for_each_entry(evsel_list, counter) { evlist__for_each_entry(evsel_list, counter) {
bool invalid = false; bool invalid = false;
leader = counter->leader; leader = counter->leader;
if (!counter->metric_expr) if (!counter->metric_expr)
continue; continue;
expr__ctx_clear(&ctx);
metric_events = counter->metric_events; metric_events = counter->metric_events;
if (!metric_events) { if (!metric_events) {
if (expr__find_other(counter->metric_expr, counter->name, if (expr__find_other(counter->metric_expr,
&metric_names, &num_metric_names, 1) < 0) counter->name,
&ctx, 1) < 0)
continue; continue;
metric_events = calloc(sizeof(struct evsel *), metric_events = calloc(sizeof(struct evsel *),
num_metric_names + 1); hashmap__size(&ctx.ids) + 1);
if (!metric_events) if (!metric_events) {
expr__ctx_clear(&ctx);
return; return;
}
counter->metric_events = metric_events; counter->metric_events = metric_events;
} }
for (i = 0; i < num_metric_names; i++) { i = 0;
hashmap__for_each_entry((&ctx.ids), cur, bkt) {
const char *metric_name = (const char *)cur->key;
found = false; found = false;
if (leader) { if (leader) {
/* Search in group */ /* Search in group */
for_each_group_member (oc, leader) { for_each_group_member (oc, leader) {
if (!strcasecmp(oc->name, metric_names[i]) && if (!strcasecmp(oc->name,
metric_name) &&
!oc->collect_stat) { !oc->collect_stat) {
found = true; found = true;
break; break;
@ -360,7 +371,8 @@ void perf_stat__collect_metric_expr(struct evlist *evsel_list)
} }
if (!found) { if (!found) {
/* Search ignoring groups */ /* Search ignoring groups */
oc = perf_stat__find_event(evsel_list, metric_names[i]); oc = perf_stat__find_event(evsel_list,
metric_name);
} }
if (!oc) { if (!oc) {
/* Deduping one is good enough to handle duplicated PMUs. */ /* Deduping one is good enough to handle duplicated PMUs. */
@ -373,27 +385,28 @@ void perf_stat__collect_metric_expr(struct evlist *evsel_list)
* of events. So we ask the user instead to add the missing * of events. So we ask the user instead to add the missing
* events. * events.
*/ */
if (!printed || strcasecmp(printed, metric_names[i])) { if (!printed ||
strcasecmp(printed, metric_name)) {
fprintf(stderr, fprintf(stderr,
"Add %s event to groups to get metric expression for %s\n", "Add %s event to groups to get metric expression for %s\n",
metric_names[i], metric_name,
counter->name); counter->name);
printed = strdup(metric_names[i]); printed = strdup(metric_name);
} }
invalid = true; invalid = true;
continue; continue;
} }
metric_events[i] = oc; metric_events[i++] = oc;
oc->collect_stat = true; oc->collect_stat = true;
} }
metric_events[i] = NULL; metric_events[i] = NULL;
free(metric_names);
if (invalid) { if (invalid) {
free(metric_events); free(metric_events);
counter->metric_events = NULL; counter->metric_events = NULL;
counter->metric_expr = NULL; counter->metric_expr = NULL;
} }
} }
expr__ctx_clear(&ctx);
} }
static double runtime_stat_avg(struct runtime_stat *st, static double runtime_stat_avg(struct runtime_stat *st,
@ -738,7 +751,10 @@ static void generic_metric(struct perf_stat_config *config,
expr__ctx_init(&pctx); expr__ctx_init(&pctx);
/* Must be first id entry */ /* Must be first id entry */
expr__add_id(&pctx, name, avg); n = strdup(name);
if (!n)
return;
expr__add_id(&pctx, n, avg);
for (i = 0; metric_events[i]; i++) { for (i = 0; metric_events[i]; i++) {
struct saved_value *v; struct saved_value *v;
struct stats *stats; struct stats *stats;
@ -814,8 +830,7 @@ static void generic_metric(struct perf_stat_config *config,
(metric_name ? metric_name : name) : "", 0); (metric_name ? metric_name : name) : "", 0);
} }
for (i = 1; i < pctx.num_ids; i++) expr__ctx_clear(&pctx);
zfree(&pctx.ids[i].name);
} }
void perf_stat__print_shadow_stats(struct perf_stat_config *config, void perf_stat__print_shadow_stats(struct perf_stat_config *config,