2021-09-18 13:10:08

by Ian Rogers

[permalink] [raw]
Subject: [PATCH v8 0/8] Don't compute events that won't be used in a metric.


For a metric like:
EVENT1 if #smt_on else EVENT2

currently EVENT1 and EVENT2 will be measured and then when the metric
is reported EVENT1 or EVENT2 will be printed depending on the value
from smt_on() during the expr parsing. Computing both events is
unnecessary and can lead to multiplexing as discussed in this thread:
https://lore.kernel.org/lkml/[email protected]/

This change modifies expression parsing so that constants are
considered when building the set of ids (events) and only events not
contributing to a constant value are measured.

v8. rebases, adds an ability to compute metrics with no events and
further breaks apart the "Don't compute unused events" part of the
change as requested by Jiri Olsa <[email protected]>.

v7. fixes the fix to be in the correct patch.

v6. rebases and fixes issues raised by Namhyung Kim <[email protected]>,
a memory leak and a function comment.

v5. uses macros to reduce boiler plate in patch 5/5 as suggested by
Andi Kleen <[email protected]>.

v4. reduces references to BOTTOM/NAN in patch 5/5 by using utility
functions. It improves comments and fixes an unnecessary union in a
peephole optimization.

v3. fixes an assignment in patch 2/5. In patch 5/5 additional comments
are added and useless frees are replaced by asserts. A new peephole
optimization is added for the case CONST IF expr ELSE CONST, where the
the constants are identical, as we don't need to evaluate the IF
condition.

v2. is a rebase.

Ian Rogers (8):
perf metric: Restructure struct expr_parse_ctx.
perf metric: Use NAN for missing event IDs.
perf expr: Modify code layout
perf metric: Rename expr__find_other.
perf metric: Add utilities to work on ids map.
perf metric: Allow metrics with no events
perf metric: Don't compute unused events.
perf test: Add metric test for eliminating events

tools/perf/tests/expr.c | 159 +++++++++++-----
tools/perf/tests/pmu-events.c | 50 ++---
tools/perf/util/expr.c | 137 ++++++++++++--
tools/perf/util/expr.h | 21 ++-
tools/perf/util/expr.l | 9 -
tools/perf/util/expr.y | 343 ++++++++++++++++++++++++++--------
tools/perf/util/metricgroup.c | 145 +++++++-------
tools/perf/util/stat-shadow.c | 54 ++++--
8 files changed, 650 insertions(+), 268 deletions(-)

--
2.33.0.464.g1972c5931b-goog


2021-09-18 13:10:45

by Ian Rogers

[permalink] [raw]
Subject: [PATCH v8 1/8] perf metric: Restructure struct expr_parse_ctx.

A later change to parsing the ids out (in expr__find_other) will
potentially drop hashmaps and so it is more convenient to move
expr_parse_ctx to have a hashmap pointer rather than a struct value. As
this pointer must be freed, rather than just going out of scope,
add expr__ctx_new and expr__ctx_free to manage expr_parse_ctx memory.
Adjust use of struct expr_parse_ctx accordingly.

Signed-off-by: Ian Rogers <[email protected]>
---
tools/perf/tests/expr.c | 81 ++++++++++++++++++-----------------
tools/perf/tests/pmu-events.c | 43 +++++++++++--------
tools/perf/util/expr.c | 39 +++++++++++++----
tools/perf/util/expr.h | 5 ++-
tools/perf/util/metricgroup.c | 44 ++++++++++---------
tools/perf/util/stat-shadow.c | 50 +++++++++++++--------
6 files changed, 155 insertions(+), 107 deletions(-)

diff --git a/tools/perf/tests/expr.c b/tools/perf/tests/expr.c
index 4d01051951cd..b0a3b5fd0c00 100644
--- a/tools/perf/tests/expr.c
+++ b/tools/perf/tests/expr.c
@@ -22,67 +22,70 @@ int test__expr(struct test *t __maybe_unused, int subtest __maybe_unused)
const char *p;
double val;
int ret;
- struct expr_parse_ctx ctx;
+ struct expr_parse_ctx *ctx;

- expr__ctx_init(&ctx);
- expr__add_id_val(&ctx, strdup("FOO"), 1);
- expr__add_id_val(&ctx, strdup("BAR"), 2);
+ ctx = expr__ctx_new();
+ TEST_ASSERT_VAL("expr__ctx_new", ctx);
+ expr__add_id_val(ctx, strdup("FOO"), 1);
+ expr__add_id_val(ctx, strdup("BAR"), 2);

- ret = test(&ctx, "1+1", 2);
- ret |= test(&ctx, "FOO+BAR", 3);
- ret |= test(&ctx, "(BAR/2)%2", 1);
- ret |= test(&ctx, "1 - -4", 5);
- ret |= test(&ctx, "(FOO-1)*2 + (BAR/2)%2 - -4", 5);
- ret |= test(&ctx, "1-1 | 1", 1);
- ret |= test(&ctx, "1-1 & 1", 0);
- ret |= test(&ctx, "min(1,2) + 1", 2);
- ret |= test(&ctx, "max(1,2) + 1", 3);
- ret |= test(&ctx, "1+1 if 3*4 else 0", 2);
- ret |= test(&ctx, "1.1 + 2.1", 3.2);
- ret |= test(&ctx, ".1 + 2.", 2.1);
- ret |= test(&ctx, "d_ratio(1, 2)", 0.5);
- ret |= test(&ctx, "d_ratio(2.5, 0)", 0);
- ret |= test(&ctx, "1.1 < 2.2", 1);
- ret |= test(&ctx, "2.2 > 1.1", 1);
- ret |= test(&ctx, "1.1 < 1.1", 0);
- ret |= test(&ctx, "2.2 > 2.2", 0);
- ret |= test(&ctx, "2.2 < 1.1", 0);
- ret |= test(&ctx, "1.1 > 2.2", 0);
+ ret = test(ctx, "1+1", 2);
+ ret |= test(ctx, "FOO+BAR", 3);
+ ret |= test(ctx, "(BAR/2)%2", 1);
+ ret |= test(ctx, "1 - -4", 5);
+ ret |= test(ctx, "(FOO-1)*2 + (BAR/2)%2 - -4", 5);
+ ret |= test(ctx, "1-1 | 1", 1);
+ ret |= test(ctx, "1-1 & 1", 0);
+ ret |= test(ctx, "min(1,2) + 1", 2);
+ ret |= test(ctx, "max(1,2) + 1", 3);
+ ret |= test(ctx, "1+1 if 3*4 else 0", 2);
+ ret |= test(ctx, "1.1 + 2.1", 3.2);
+ ret |= test(ctx, ".1 + 2.", 2.1);
+ ret |= test(ctx, "d_ratio(1, 2)", 0.5);
+ ret |= test(ctx, "d_ratio(2.5, 0)", 0);
+ ret |= test(ctx, "1.1 < 2.2", 1);
+ ret |= test(ctx, "2.2 > 1.1", 1);
+ ret |= test(ctx, "1.1 < 1.1", 0);
+ ret |= test(ctx, "2.2 > 2.2", 0);
+ ret |= test(ctx, "2.2 < 1.1", 0);
+ ret |= test(ctx, "1.1 > 2.2", 0);

- if (ret)
+ if (ret) {
+ expr__ctx_free(ctx);
return ret;
+ }

p = "FOO/0";
- ret = expr__parse(&val, &ctx, p, 1);
+ ret = expr__parse(&val, ctx, p, 1);
TEST_ASSERT_VAL("division by zero", ret == -1);

p = "BAR/";
- ret = expr__parse(&val, &ctx, p, 1);
+ ret = expr__parse(&val, ctx, p, 1);
TEST_ASSERT_VAL("missing operand", ret == -1);

- expr__ctx_clear(&ctx);
+ expr__ctx_clear(ctx);
TEST_ASSERT_VAL("find other",
expr__find_other("FOO + BAR + BAZ + BOZO", "FOO",
- &ctx, 1) == 0);
- TEST_ASSERT_VAL("find other", hashmap__size(&ctx.ids) == 3);
- TEST_ASSERT_VAL("find other", hashmap__find(&ctx.ids, "BAR",
+ ctx, 1) == 0);
+ TEST_ASSERT_VAL("find other", hashmap__size(ctx->ids) == 3);
+ TEST_ASSERT_VAL("find other", hashmap__find(ctx->ids, "BAR",
(void **)&val_ptr));
- TEST_ASSERT_VAL("find other", hashmap__find(&ctx.ids, "BAZ",
+ TEST_ASSERT_VAL("find other", hashmap__find(ctx->ids, "BAZ",
(void **)&val_ptr));
- TEST_ASSERT_VAL("find other", hashmap__find(&ctx.ids, "BOZO",
+ TEST_ASSERT_VAL("find other", hashmap__find(ctx->ids, "BOZO",
(void **)&val_ptr));

- expr__ctx_clear(&ctx);
+ expr__ctx_clear(ctx);
TEST_ASSERT_VAL("find other",
expr__find_other("EVENT1\\,param\\=?@ + EVENT2\\,param\\=?@",
- NULL, &ctx, 3) == 0);
- TEST_ASSERT_VAL("find other", hashmap__size(&ctx.ids) == 2);
- TEST_ASSERT_VAL("find other", hashmap__find(&ctx.ids, "EVENT1,param=3/",
+ NULL, ctx, 3) == 0);
+ TEST_ASSERT_VAL("find other", hashmap__size(ctx->ids) == 2);
+ TEST_ASSERT_VAL("find other", hashmap__find(ctx->ids, "EVENT1,param=3/",
(void **)&val_ptr));
- TEST_ASSERT_VAL("find other", hashmap__find(&ctx.ids, "EVENT2,param=3/",
+ TEST_ASSERT_VAL("find other", hashmap__find(ctx->ids, "EVENT2,param=3/",
(void **)&val_ptr));

- expr__ctx_clear(&ctx);
+ expr__ctx_free(ctx);

return 0;
}
diff --git a/tools/perf/tests/pmu-events.c b/tools/perf/tests/pmu-events.c
index 43743cf719ef..001da2909668 100644
--- a/tools/perf/tests/pmu-events.c
+++ b/tools/perf/tests/pmu-events.c
@@ -781,7 +781,7 @@ static int resolve_metric_simple(struct expr_parse_ctx *pctx,

do {
all = true;
- hashmap__for_each_entry_safe((&pctx->ids), cur, cur_tmp, bkt) {
+ hashmap__for_each_entry_safe(pctx->ids, cur, cur_tmp, bkt) {
struct metric_ref *ref;
struct pmu_event *pe;

@@ -835,9 +835,14 @@ static int test_parsing(void)
struct pmu_event *pe;
int i, j, k;
int ret = 0;
- struct expr_parse_ctx ctx;
+ struct expr_parse_ctx *ctx;
double result;

+ ctx = expr__ctx_new();
+ if (!ctx) {
+ pr_debug("expr__ctx_new failed");
+ return TEST_FAIL;
+ }
i = 0;
for (;;) {
map = &pmu_events_map[i++];
@@ -855,15 +860,15 @@ static int test_parsing(void)
break;
if (!pe->metric_expr)
continue;
- expr__ctx_init(&ctx);
- if (expr__find_other(pe->metric_expr, NULL, &ctx, 0)
+ expr__ctx_clear(ctx);
+ if (expr__find_other(pe->metric_expr, NULL, ctx, 0)
< 0) {
expr_failure("Parse other failed", map, pe);
ret++;
continue;
}

- if (resolve_metric_simple(&ctx, &compound_list, map,
+ if (resolve_metric_simple(ctx, &compound_list, map,
pe->metric_name)) {
expr_failure("Could not resolve metrics", map, pe);
ret++;
@@ -876,27 +881,27 @@ static int test_parsing(void)
* make them unique.
*/
k = 1;
- hashmap__for_each_entry((&ctx.ids), cur, bkt)
- expr__add_id_val(&ctx, strdup(cur->key), k++);
+ hashmap__for_each_entry(ctx->ids, cur, bkt)
+ expr__add_id_val(ctx, strdup(cur->key), k++);

- hashmap__for_each_entry((&ctx.ids), cur, bkt) {
+ hashmap__for_each_entry(ctx->ids, cur, bkt) {
if (check_parse_cpu(cur->key, map == cpus_map,
pe))
ret++;
}

list_for_each_entry_safe(metric, tmp, &compound_list, list) {
- expr__add_ref(&ctx, &metric->metric_ref);
+ expr__add_ref(ctx, &metric->metric_ref);
free(metric);
}

- if (expr__parse(&result, &ctx, pe->metric_expr, 0)) {
+ if (expr__parse(&result, ctx, pe->metric_expr, 0)) {
expr_failure("Parse failed", map, pe);
ret++;
}
- expr__ctx_clear(&ctx);
}
}
+ expr__ctx_free(ctx);
/* TODO: fail when not ok */
exit:
return ret == 0 ? TEST_OK : TEST_SKIP;
@@ -916,7 +921,7 @@ static struct test_metric metrics[] = {

static int metric_parse_fake(const char *str)
{
- struct expr_parse_ctx ctx;
+ struct expr_parse_ctx *ctx;
struct hashmap_entry *cur;
double result;
int ret = -1;
@@ -925,8 +930,8 @@ static int metric_parse_fake(const char *str)

pr_debug("parsing '%s'\n", str);

- expr__ctx_init(&ctx);
- if (expr__find_other(str, NULL, &ctx, 0) < 0) {
+ ctx = expr__ctx_new();
+ if (expr__find_other(str, NULL, ctx, 0) < 0) {
pr_err("expr__find_other failed\n");
return -1;
}
@@ -937,23 +942,23 @@ static int metric_parse_fake(const char *str)
* make them unique.
*/
i = 1;
- hashmap__for_each_entry((&ctx.ids), cur, bkt)
- expr__add_id_val(&ctx, strdup(cur->key), i++);
+ hashmap__for_each_entry(ctx->ids, cur, bkt)
+ expr__add_id_val(ctx, strdup(cur->key), i++);

- hashmap__for_each_entry((&ctx.ids), cur, bkt) {
+ hashmap__for_each_entry(ctx->ids, cur, bkt) {
if (check_parse_fake(cur->key)) {
pr_err("check_parse_fake failed\n");
goto out;
}
}

- if (expr__parse(&result, &ctx, str, 0))
+ if (expr__parse(&result, ctx, str, 0))
pr_err("expr__parse failed\n");
else
ret = 0;

out:
- expr__ctx_clear(&ctx);
+ expr__ctx_free(ctx);
return ret;
}

diff --git a/tools/perf/util/expr.c b/tools/perf/util/expr.c
index a850fd0be3ee..7b1c06772a49 100644
--- a/tools/perf/util/expr.c
+++ b/tools/perf/util/expr.c
@@ -73,7 +73,7 @@ int expr__add_id(struct expr_parse_ctx *ctx, const char *id)
data_ptr->parent = ctx->parent;
data_ptr->kind = EXPR_ID_DATA__PARENT;

- ret = hashmap__set(&ctx->ids, id, data_ptr,
+ ret = hashmap__set(ctx->ids, id, data_ptr,
(const void **)&old_key, (void **)&old_data);
if (ret)
free(data_ptr);
@@ -95,7 +95,7 @@ int expr__add_id_val(struct expr_parse_ctx *ctx, const char *id, double val)
data_ptr->val = val;
data_ptr->kind = EXPR_ID_DATA__VALUE;

- ret = hashmap__set(&ctx->ids, id, data_ptr,
+ ret = hashmap__set(ctx->ids, id, data_ptr,
(const void **)&old_key, (void **)&old_data);
if (ret)
free(data_ptr);
@@ -140,7 +140,7 @@ int expr__add_ref(struct expr_parse_ctx *ctx, struct metric_ref *ref)
data_ptr->ref.metric_expr = ref->metric_expr;
data_ptr->kind = EXPR_ID_DATA__REF;

- ret = hashmap__set(&ctx->ids, name, data_ptr,
+ ret = hashmap__set(ctx->ids, name, data_ptr,
(const void **)&old_key, (void **)&old_data);
if (ret)
free(data_ptr);
@@ -156,7 +156,7 @@ int expr__add_ref(struct expr_parse_ctx *ctx, struct metric_ref *ref)
int expr__get_id(struct expr_parse_ctx *ctx, const char *id,
struct expr_id_data **data)
{
- return hashmap__find(&ctx->ids, id, (void **)data) ? 0 : -1;
+ return hashmap__find(ctx->ids, id, (void **)data) ? 0 : -1;
}

int expr__resolve_id(struct expr_parse_ctx *ctx, const char *id,
@@ -205,15 +205,23 @@ void expr__del_id(struct expr_parse_ctx *ctx, const char *id)
struct expr_id_data *old_val = NULL;
char *old_key = NULL;

- hashmap__delete(&ctx->ids, id,
+ hashmap__delete(ctx->ids, id,
(const void **)&old_key, (void **)&old_val);
free(old_key);
free(old_val);
}

-void expr__ctx_init(struct expr_parse_ctx *ctx)
+struct expr_parse_ctx *expr__ctx_new(void)
{
- hashmap__init(&ctx->ids, key_hash, key_equal, NULL);
+ struct expr_parse_ctx *ctx;
+
+ ctx = malloc(sizeof(struct expr_parse_ctx));
+ if (!ctx)
+ return NULL;
+
+ ctx->ids = hashmap__new(key_hash, key_equal, NULL);
+ ctx->parent = NULL;
+ return ctx;
}

void expr__ctx_clear(struct expr_parse_ctx *ctx)
@@ -221,11 +229,24 @@ void expr__ctx_clear(struct expr_parse_ctx *ctx)
struct hashmap_entry *cur;
size_t bkt;

- hashmap__for_each_entry((&ctx->ids), cur, bkt) {
+ hashmap__for_each_entry(ctx->ids, cur, bkt) {
+ free((char *)cur->key);
+ free(cur->value);
+ }
+ hashmap__clear(ctx->ids);
+}
+
+void expr__ctx_free(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);
+ hashmap__free(ctx->ids);
+ free(ctx);
}

static int
diff --git a/tools/perf/util/expr.h b/tools/perf/util/expr.h
index 85df3e4771e4..5fa394f10418 100644
--- a/tools/perf/util/expr.h
+++ b/tools/perf/util/expr.h
@@ -19,7 +19,7 @@ struct expr_id {
};

struct expr_parse_ctx {
- struct hashmap ids;
+ struct hashmap *ids;
struct expr_id *parent;
};

@@ -30,8 +30,9 @@ struct expr_scanner_ctx {
int runtime;
};

-void expr__ctx_init(struct expr_parse_ctx *ctx);
+struct expr_parse_ctx *expr__ctx_new(void);
void expr__ctx_clear(struct expr_parse_ctx *ctx);
+void expr__ctx_free(struct expr_parse_ctx *ctx);
void expr__del_id(struct expr_parse_ctx *ctx, const char *id);
int expr__add_id(struct expr_parse_ctx *ctx, const char *id);
int expr__add_id_val(struct expr_parse_ctx *ctx, const char *id, double val);
diff --git a/tools/perf/util/metricgroup.c b/tools/perf/util/metricgroup.c
index 29b747ac31c1..b7924a2f1f45 100644
--- a/tools/perf/util/metricgroup.c
+++ b/tools/perf/util/metricgroup.c
@@ -118,7 +118,7 @@ struct metric_ref_node {

struct metric {
struct list_head nd;
- struct expr_parse_ctx pctx;
+ struct expr_parse_ctx *pctx;
const char *metric_name;
const char *metric_expr;
const char *metric_unit;
@@ -198,7 +198,7 @@ static struct evsel *find_evsel_group(struct evlist *perf_evlist,
struct evsel *ev, *current_leader = NULL;
struct expr_id_data *val_ptr;
int i = 0, matched_events = 0, events_to_match;
- const int idnum = (int)hashmap__size(&pctx->ids);
+ const int idnum = (int)hashmap__size(pctx->ids);

/*
* duration_time is always grouped separately, when events are grouped
@@ -206,7 +206,7 @@ static struct evsel *find_evsel_group(struct evlist *perf_evlist,
* add it to metric_events at the end.
*/
if (!has_constraint &&
- hashmap__find(&pctx->ids, "duration_time", (void **)&val_ptr))
+ hashmap__find(pctx->ids, "duration_time", (void **)&val_ptr))
events_to_match = idnum - 1;
else
events_to_match = idnum;
@@ -242,7 +242,7 @@ static struct evsel *find_evsel_group(struct evlist *perf_evlist,
if (contains_event(metric_events, matched_events, ev->name))
continue;
/* Does this event belong to the parse context? */
- if (hashmap__find(&pctx->ids, ev->name, (void **)&val_ptr))
+ if (hashmap__find(pctx->ids, ev->name, (void **)&val_ptr))
metric_events[matched_events++] = ev;

if (matched_events == events_to_match)
@@ -322,12 +322,12 @@ static int metricgroup__setup_events(struct list_head *groups,
struct metric_ref *metric_refs = NULL;

metric_events = calloc(sizeof(void *),
- hashmap__size(&m->pctx.ids) + 1);
+ hashmap__size(m->pctx->ids) + 1);
if (!metric_events) {
ret = -ENOMEM;
break;
}
- evsel = find_evsel_group(perf_evlist, &m->pctx,
+ evsel = find_evsel_group(perf_evlist, m->pctx,
metric_no_merge,
m->has_constraint, metric_events,
evlist_used);
@@ -693,7 +693,7 @@ static void metricgroup__add_metric_weak_group(struct strbuf *events,
size_t bkt;
bool no_group = true, has_duration = false;

- hashmap__for_each_entry((&ctx->ids), cur, bkt) {
+ hashmap__for_each_entry(ctx->ids, cur, bkt) {
pr_debug("found event %s\n", (const char *)cur->key);
/*
* Duration time maps to a software event and can make
@@ -724,7 +724,7 @@ static void metricgroup__add_metric_non_group(struct strbuf *events,
size_t bkt;
bool first = true;

- hashmap__for_each_entry((&ctx->ids), cur, bkt) {
+ hashmap__for_each_entry(ctx->ids, cur, bkt) {
if (!first)
strbuf_addf(events, ",");
strbuf_addf(events, "%s", (const char *)cur->key);
@@ -799,7 +799,11 @@ static int __add_metric(struct list_head *metric_list,
if (!m)
return -ENOMEM;

- expr__ctx_init(&m->pctx);
+ m->pctx = expr__ctx_new();
+ if (!m->pctx) {
+ free(m);
+ return -ENOMEM;
+ }
m->metric_name = pe->metric_name;
m->metric_expr = pe->metric_expr;
m->metric_unit = pe->unit;
@@ -847,15 +851,15 @@ static int __add_metric(struct list_head *metric_list,

/* Force all found IDs in metric to have us as parent ID. */
WARN_ON_ONCE(!parent);
- m->pctx.parent = parent;
+ m->pctx->parent = parent;

/*
* For both the parent and referenced metrics, we parse
* all the metric's IDs and add it to the parent context.
*/
- if (expr__find_other(pe->metric_expr, NULL, &m->pctx, runtime) < 0) {
+ if (expr__find_other(pe->metric_expr, NULL, m->pctx, runtime) < 0) {
if (m->metric_refs_cnt == 0) {
- expr__ctx_clear(&m->pctx);
+ expr__ctx_free(m->pctx);
free(m);
*mp = NULL;
}
@@ -878,8 +882,8 @@ static int __add_metric(struct list_head *metric_list,
list_for_each_prev(pos, metric_list) {
struct metric *old = list_entry(pos, struct metric, nd);

- if (hashmap__size(&m->pctx.ids) <=
- hashmap__size(&old->pctx.ids))
+ if (hashmap__size(m->pctx->ids) <=
+ hashmap__size(old->pctx->ids))
break;
}
list_add(&m->nd, pos);
@@ -927,7 +931,7 @@ static int recursion_check(struct metric *m, const char *id, struct expr_id **pa
* if we already processed 'id', if we did, it's recursion
* and we fail.
*/
- ret = expr__get_id(&m->pctx, id, &data);
+ ret = expr__get_id(m->pctx, id, &data);
if (ret)
return ret;

@@ -982,7 +986,7 @@ static int __resolve_metric(struct metric *m,
*/
do {
all = true;
- hashmap__for_each_entry((&m->pctx.ids), cur, bkt) {
+ hashmap__for_each_entry(m->pctx->ids, cur, bkt) {
struct expr_id *parent;
struct pmu_event *pe;

@@ -996,7 +1000,7 @@ static int __resolve_metric(struct metric *m,

all = false;
/* The metric key itself needs to go out.. */
- expr__del_id(&m->pctx, cur->key);
+ expr__del_id(m->pctx, cur->key);

/* ... and it gets resolved to the parent context. */
ret = add_metric(metric_list, pe, metric_no_group, &m, parent, ids);
@@ -1144,10 +1148,10 @@ static int metricgroup__add_metric(const char *metric, bool metric_no_group,

if (m->has_constraint) {
metricgroup__add_metric_non_group(events,
- &m->pctx);
+ m->pctx);
} else {
metricgroup__add_metric_weak_group(events,
- &m->pctx);
+ m->pctx);
}
}

@@ -1210,7 +1214,7 @@ static void metricgroup__free_metrics(struct list_head *metric_list)

list_for_each_entry_safe (m, tmp, metric_list, nd) {
metric__free_refs(m);
- expr__ctx_clear(&m->pctx);
+ expr__ctx_free(m->pctx);
list_del_init(&m->nd);
free(m);
}
diff --git a/tools/perf/util/stat-shadow.c b/tools/perf/util/stat-shadow.c
index 34a7f5c1fff7..c9fa07e49e72 100644
--- a/tools/perf/util/stat-shadow.c
+++ b/tools/perf/util/stat-shadow.c
@@ -1,8 +1,10 @@
// SPDX-License-Identifier: GPL-2.0
+#include <math.h>
#include <stdio.h>
#include "evsel.h"
#include "stat.h"
#include "color.h"
+#include "debug.h"
#include "pmu.h"
#include "rblist.h"
#include "evlist.h"
@@ -370,12 +372,16 @@ void perf_stat__collect_metric_expr(struct evlist *evsel_list)
{
struct evsel *counter, *leader, **metric_events, *oc;
bool found;
- struct expr_parse_ctx ctx;
+ struct expr_parse_ctx *ctx;
struct hashmap_entry *cur;
size_t bkt;
int i;

- expr__ctx_init(&ctx);
+ ctx = expr__ctx_new();
+ if (!ctx) {
+ pr_debug("expr__ctx_new failed");
+ return;
+ }
evlist__for_each_entry(evsel_list, counter) {
bool invalid = false;

@@ -383,25 +389,25 @@ void perf_stat__collect_metric_expr(struct evlist *evsel_list)
if (!counter->metric_expr)
continue;

- expr__ctx_clear(&ctx);
+ expr__ctx_clear(ctx);
metric_events = counter->metric_events;
if (!metric_events) {
if (expr__find_other(counter->metric_expr,
counter->name,
- &ctx, 1) < 0)
+ ctx, 1) < 0)
continue;

metric_events = calloc(sizeof(struct evsel *),
- hashmap__size(&ctx.ids) + 1);
+ hashmap__size(ctx->ids) + 1);
if (!metric_events) {
- expr__ctx_clear(&ctx);
+ expr__ctx_free(ctx);
return;
}
counter->metric_events = metric_events;
}

i = 0;
- hashmap__for_each_entry((&ctx.ids), cur, bkt) {
+ hashmap__for_each_entry(ctx->ids, cur, bkt) {
const char *metric_name = (const char *)cur->key;

found = false;
@@ -453,7 +459,7 @@ void perf_stat__collect_metric_expr(struct evlist *evsel_list)
counter->metric_expr = NULL;
}
}
- expr__ctx_clear(&ctx);
+ expr__ctx_free(ctx);
}

static double runtime_stat_avg(struct runtime_stat *st,
@@ -818,7 +824,6 @@ static int prepare_metric(struct evsel **metric_events,
char *n, *pn;
int i, j, ret;

- expr__ctx_init(pctx);
for (i = 0; metric_events[i]; i++) {
struct saved_value *v;
struct stats *stats;
@@ -880,17 +885,22 @@ static void generic_metric(struct perf_stat_config *config,
struct runtime_stat *st)
{
print_metric_t print_metric = out->print_metric;
- struct expr_parse_ctx pctx;
+ struct expr_parse_ctx *pctx;
double ratio, scale;
int i;
void *ctxp = out->ctx;

- i = prepare_metric(metric_events, metric_refs, &pctx, cpu, st);
- if (i < 0)
+ pctx = expr__ctx_new();
+ if (!pctx)
return;

+ i = prepare_metric(metric_events, metric_refs, pctx, cpu, st);
+ if (i < 0) {
+ expr__ctx_free(pctx);
+ return;
+ }
if (!metric_events[i]) {
- if (expr__parse(&ratio, &pctx, metric_expr, runtime) == 0) {
+ if (expr__parse(&ratio, pctx, metric_expr, runtime) == 0) {
char *unit;
char metric_bf[64];

@@ -926,22 +936,26 @@ static void generic_metric(struct perf_stat_config *config,
(metric_name ? metric_name : name) : "", 0);
}

- expr__ctx_clear(&pctx);
+ expr__ctx_free(pctx);
}

double test_generic_metric(struct metric_expr *mexp, int cpu, struct runtime_stat *st)
{
- struct expr_parse_ctx pctx;
+ struct expr_parse_ctx *pctx;
double ratio = 0.0;

- if (prepare_metric(mexp->metric_events, mexp->metric_refs, &pctx, cpu, st) < 0)
+ pctx = expr__ctx_new();
+ if (!pctx)
+ return NAN;
+
+ if (prepare_metric(mexp->metric_events, mexp->metric_refs, pctx, cpu, st) < 0)
goto out;

- if (expr__parse(&ratio, &pctx, mexp->metric_expr, 1))
+ if (expr__parse(&ratio, pctx, mexp->metric_expr, 1))
ratio = 0.0;

out:
- expr__ctx_clear(&pctx);
+ expr__ctx_free(pctx);
return ratio;
}

--
2.33.0.464.g1972c5931b-goog

2021-09-18 13:12:10

by Ian Rogers

[permalink] [raw]
Subject: [PATCH v8 3/8] perf expr: Modify code layout

No functional change. Alter whitespace. Use helper macros that will be
made more complex in a later change.

Signed-off-by: Ian Rogers <[email protected]>
---
tools/perf/util/expr.y | 149 +++++++++++++++++++++++------------------
1 file changed, 85 insertions(+), 64 deletions(-)

diff --git a/tools/perf/util/expr.y b/tools/perf/util/expr.y
index 41c9cd4efadd..51f4f0aa1955 100644
--- a/tools/perf/util/expr.y
+++ b/tools/perf/util/expr.y
@@ -2,23 +2,10 @@
%{
#define YYDEBUG 1
#include <math.h>
-#include <stdio.h>
-#include "util.h"
#include "util/debug.h"
-#include <stdlib.h> // strtod()
+#include "smt.h"
#define IN_EXPR_Y 1
#include "expr.h"
-#include "smt.h"
-#include <string.h>
-
-static double d_ratio(double val0, double val1)
-{
- if (val1 == 0) {
- return 0;
- }
- return val0 / val1;
-}
-
%}

%define api.pure full
@@ -33,11 +20,7 @@ static double d_ratio(double val0, double val1)
char *str;
}

-%token EXPR_PARSE EXPR_OTHER EXPR_ERROR
-%token <num> NUMBER
-%token <str> ID
-%destructor { free ($$); } <str>
-%token MIN MAX IF ELSE SMT_ON D_RATIO
+%token ID NUMBER MIN MAX IF ELSE SMT_ON D_RATIO EXPR_ERROR EXPR_PARSE EXPR_OTHER
%left MIN MAX IF
%left '|'
%left '^'
@@ -46,6 +29,9 @@ static double d_ratio(double val0, double val1)
%left '-' '+'
%left '*' '/' '%'
%left NEG NOT
+%type <num> NUMBER
+%type <str> ID
+%destructor { free ($$); } <str>
%type <num> expr if_expr

%{
@@ -57,6 +43,12 @@ static void expr_error(double *final_val __maybe_unused,
pr_debug("%s\n", s);
}

+#define BINARY_LONG_OP(RESULT, OP, LHS, RHS) \
+ RESULT = (long)LHS OP (long)RHS;
+
+#define BINARY_OP(RESULT, OP, LHS, RHS) \
+ RESULT = LHS OP RHS;
+
%}
%%

@@ -78,49 +70,78 @@ MIN | MAX | IF | ELSE | SMT_ON | NUMBER | '|' | '^' | '&' | '-' | '+' | '*' | '/
'<' | '>' | D_RATIO

all_expr: if_expr { *final_val = $1; }
- ;
-
-if_expr:
- expr IF expr ELSE expr { $$ = $3 ? $1 : $5; }
- | expr
- ;
-
-expr: NUMBER
- | ID {
- struct expr_id_data *data;
-
- $$ = NAN;
- if (expr__resolve_id(ctx, $1, &data) == 0)
- $$ = expr_id_data__value(data);
-
- free($1);
- }
- | expr '|' expr { $$ = (long)$1 | (long)$3; }
- | expr '&' expr { $$ = (long)$1 & (long)$3; }
- | expr '^' expr { $$ = (long)$1 ^ (long)$3; }
- | expr '<' expr { $$ = $1 < $3; }
- | expr '>' expr { $$ = $1 > $3; }
- | expr '+' expr { $$ = $1 + $3; }
- | expr '-' expr { $$ = $1 - $3; }
- | expr '*' expr { $$ = $1 * $3; }
- | expr '/' expr { if ($3 == 0) {
- pr_debug("division by zero\n");
- YYABORT;
- }
- $$ = $1 / $3;
- }
- | expr '%' expr { if ((long)$3 == 0) {
- pr_debug("division by zero\n");
- YYABORT;
- }
- $$ = (long)$1 % (long)$3;
- }
- | '-' expr %prec NEG { $$ = -$2; }
- | '(' if_expr ')' { $$ = $2; }
- | MIN '(' expr ',' expr ')' { $$ = $3 < $5 ? $3 : $5; }
- | MAX '(' expr ',' expr ')' { $$ = $3 > $5 ? $3 : $5; }
- | SMT_ON { $$ = smt_on() > 0; }
- | D_RATIO '(' expr ',' expr ')' { $$ = d_ratio($3,$5); }
- ;

-%%
+if_expr: expr IF expr ELSE expr
+{
+ $$ = $3 ? $1 : $5;
+}
+| expr
+;
+
+expr: NUMBER
+{
+ $$ = $1;
+}
+| ID
+{
+ struct expr_id_data *data;
+
+ $$ = NAN;
+ if (expr__resolve_id(ctx, $1, &data) == 0)
+ $$ = expr_id_data__value(data);
+
+ free($1);
+}
+| expr '|' expr { BINARY_LONG_OP($$, |, $1, $3); }
+| expr '&' expr { BINARY_LONG_OP($$, &, $1, $3); }
+| expr '^' expr { BINARY_LONG_OP($$, ^, $1, $3); }
+| expr '<' expr { BINARY_OP($$, <, $1, $3); }
+| expr '>' expr { BINARY_OP($$, >, $1, $3); }
+| expr '+' expr { BINARY_OP($$, +, $1, $3); }
+| expr '-' expr { BINARY_OP($$, -, $1, $3); }
+| expr '*' expr { BINARY_OP($$, *, $1, $3); }
+| expr '/' expr
+{
+ if ($3 == 0) {
+ pr_debug("division by zero\n");
+ YYABORT;
+ }
+ $$ = $1 / $3;
+}
+| expr '%' expr
+{
+ if ((long)$3 == 0) {
+ pr_debug("division by zero\n");
+ YYABORT;
+ }
+ $$ = (long)$1 % (long)$3;
+}
+| D_RATIO '(' expr ',' expr ')'
+{
+ if ($5 == 0) {
+ $$ = 0;
+ } else {
+ $$ = $3 / $5;
+ }
+}
+| '-' expr %prec NEG
+{
+ $$ = -$2;
+}
+| '(' if_expr ')'
+{
+ $$ = $2;
+}
+| MIN '(' expr ',' expr ')'
+{
+ $$ = $3 < $5 ? $3 : $5;
+}
+| MAX '(' expr ',' expr ')'
+{
+ $$ = $3 > $5 ? $3 : $5;
+}
+| SMT_ON
+{
+ $$ = smt_on() > 0 ? 1.0 : 0.0;
+}
+;
--
2.33.0.464.g1972c5931b-goog

2021-09-18 13:13:47

by Ian Rogers

[permalink] [raw]
Subject: [PATCH v8 5/8] perf metric: Add utilities to work on ids map.

Add utilities to new/free an ids hashmap, as well as to union. Add
testing of the union. Unioning hashmaps will be used when parsing the
metric, if a value is known then the hashmap is unnecessary, otherwise
we need to union together all the event ids to compute their values for
reporting.

Signed-off-by: Ian Rogers <[email protected]>
---
tools/perf/tests/expr.c | 47 ++++++++++++++++++++++
tools/perf/util/expr.c | 87 +++++++++++++++++++++++++++++++++++++++--
tools/perf/util/expr.h | 13 ++++++
3 files changed, 143 insertions(+), 4 deletions(-)

diff --git a/tools/perf/tests/expr.c b/tools/perf/tests/expr.c
index 7ccb97c73347..1c881bea7fca 100644
--- a/tools/perf/tests/expr.c
+++ b/tools/perf/tests/expr.c
@@ -6,6 +6,51 @@
#include <string.h>
#include <linux/zalloc.h>

+static int test_ids_union(void)
+{
+ struct hashmap *ids1, *ids2;
+
+ /* Empty union. */
+ ids1 = ids__new();
+ TEST_ASSERT_VAL("ids__new", ids1);
+ ids2 = ids__new();
+ TEST_ASSERT_VAL("ids__new", ids2);
+
+ ids1 = ids__union(ids1, ids2);
+ TEST_ASSERT_EQUAL("union", (int)hashmap__size(ids1), 0);
+
+ /* Union {foo, bar} against {}. */
+ ids2 = ids__new();
+ TEST_ASSERT_VAL("ids__new", ids2);
+
+ TEST_ASSERT_EQUAL("ids__insert", ids__insert(ids1, strdup("foo"), NULL), 0);
+ TEST_ASSERT_EQUAL("ids__insert", ids__insert(ids1, strdup("bar"), NULL), 0);
+
+ ids1 = ids__union(ids1, ids2);
+ TEST_ASSERT_EQUAL("union", (int)hashmap__size(ids1), 2);
+
+ /* Union {foo, bar} against {foo}. */
+ ids2 = ids__new();
+ TEST_ASSERT_VAL("ids__new", ids2);
+ TEST_ASSERT_EQUAL("ids__insert", ids__insert(ids2, strdup("foo"), NULL), 0);
+
+ ids1 = ids__union(ids1, ids2);
+ TEST_ASSERT_EQUAL("union", (int)hashmap__size(ids1), 2);
+
+ /* Union {foo, bar} against {bar,baz}. */
+ ids2 = ids__new();
+ TEST_ASSERT_VAL("ids__new", ids2);
+ TEST_ASSERT_EQUAL("ids__insert", ids__insert(ids2, strdup("bar"), NULL), 0);
+ TEST_ASSERT_EQUAL("ids__insert", ids__insert(ids2, strdup("baz"), NULL), 0);
+
+ ids1 = ids__union(ids1, ids2);
+ TEST_ASSERT_EQUAL("union", (int)hashmap__size(ids1), 3);
+
+ ids__free(ids1);
+
+ return 0;
+}
+
static int test(struct expr_parse_ctx *ctx, const char *e, double val2)
{
double val;
@@ -24,6 +69,8 @@ int test__expr(struct test *t __maybe_unused, int subtest __maybe_unused)
int ret;
struct expr_parse_ctx *ctx;

+ TEST_ASSERT_EQUAL("ids_union", test_ids_union(), 0);
+
ctx = expr__ctx_new();
TEST_ASSERT_VAL("expr__ctx_new", ctx);
expr__add_id_val(ctx, strdup("FOO"), 1);
diff --git a/tools/perf/util/expr.c b/tools/perf/util/expr.c
index adf16bb7571a..34b51ca5e87f 100644
--- a/tools/perf/util/expr.c
+++ b/tools/perf/util/expr.c
@@ -59,8 +59,48 @@ static bool key_equal(const void *key1, const void *key2,
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 *id)
+struct hashmap *ids__new(void)
+{
+ return hashmap__new(key_hash, key_equal, NULL);
+}
+
+void ids__free(struct hashmap *ids)
+{
+ struct hashmap_entry *cur;
+ size_t bkt;
+
+ if (ids == NULL)
+ return;
+
+#ifdef PARSER_DEBUG
+ fprintf(stderr, "freeing ids: ");
+ ids__print(ids);
+ fprintf(stderr, "\n");
+#endif
+
+ hashmap__for_each_entry(ids, cur, bkt) {
+ free((char *)cur->key);
+ free(cur->value);
+ }
+
+ hashmap__free(ids);
+}
+
+void ids__print(struct hashmap *ids)
+{
+ size_t bkt;
+ struct hashmap_entry *cur;
+
+ if (!ids)
+ return;
+
+ hashmap__for_each_entry(ids, cur, bkt) {
+ fprintf(stderr, "key:%s, ", (const char *)cur->key);
+ }
+}
+
+int ids__insert(struct hashmap *ids, const char *id,
+ struct expr_id *parent)
{
struct expr_id_data *data_ptr = NULL, *old_data = NULL;
char *old_key = NULL;
@@ -70,10 +110,10 @@ int expr__add_id(struct expr_parse_ctx *ctx, const char *id)
if (!data_ptr)
return -ENOMEM;

- data_ptr->parent = ctx->parent;
+ data_ptr->parent = parent;
data_ptr->kind = EXPR_ID_DATA__PARENT;

- ret = hashmap__set(ctx->ids, id, data_ptr,
+ ret = hashmap__set(ids, id, data_ptr,
(const void **)&old_key, (void **)&old_data);
if (ret)
free(data_ptr);
@@ -82,6 +122,45 @@ int expr__add_id(struct expr_parse_ctx *ctx, const char *id)
return ret;
}

+struct hashmap *ids__union(struct hashmap *ids1, struct hashmap *ids2)
+{
+ size_t bkt;
+ struct hashmap_entry *cur;
+ int ret;
+ struct expr_id_data *old_data = NULL;
+ char *old_key = NULL;
+
+ if (!ids1)
+ return ids2;
+
+ if (!ids2)
+ return ids1;
+
+ if (hashmap__size(ids1) < hashmap__size(ids2)) {
+ struct hashmap *tmp = ids1;
+
+ ids1 = ids2;
+ ids2 = tmp;
+ }
+ hashmap__for_each_entry(ids2, cur, bkt) {
+ ret = hashmap__set(ids1, cur->key, cur->value,
+ (const void **)&old_key, (void **)&old_data);
+ free(old_key);
+ free(old_data);
+
+ if (ret)
+ break;
+ }
+ hashmap__free(ids2);
+ return ids1;
+}
+
+/* Caller must make sure id is allocated */
+int expr__add_id(struct expr_parse_ctx *ctx, const char *id)
+{
+ return ids__insert(ctx->ids, id, ctx->parent);
+}
+
/* Caller must make sure id is allocated */
int expr__add_id_val(struct expr_parse_ctx *ctx, const char *id, double val)
{
diff --git a/tools/perf/util/expr.h b/tools/perf/util/expr.h
index de109c2ab917..a3508c5a427a 100644
--- a/tools/perf/util/expr.h
+++ b/tools/perf/util/expr.h
@@ -30,9 +30,20 @@ struct expr_scanner_ctx {
int runtime;
};

+struct hashmap *ids__new(void);
+void ids__free(struct hashmap *ids);
+void ids__print(struct hashmap *ids);
+int ids__insert(struct hashmap *ids, const char *id, struct expr_id *parent);
+/*
+ * Union two sets of ids (hashmaps) and construct a third, freeing ids1 and
+ * ids2.
+ */
+struct hashmap *ids__union(struct hashmap *ids1, struct hashmap *ids2);
+
struct expr_parse_ctx *expr__ctx_new(void);
void expr__ctx_clear(struct expr_parse_ctx *ctx);
void expr__ctx_free(struct expr_parse_ctx *ctx);
+
void expr__del_id(struct expr_parse_ctx *ctx, const char *id);
int expr__add_id(struct expr_parse_ctx *ctx, const char *id);
int expr__add_id_val(struct expr_parse_ctx *ctx, const char *id, double val);
@@ -41,8 +52,10 @@ int expr__get_id(struct expr_parse_ctx *ctx, const char *id,
struct expr_id_data **data);
int expr__resolve_id(struct expr_parse_ctx *ctx, const char *id,
struct expr_id_data **datap);
+
int expr__parse(double *final_val, struct expr_parse_ctx *ctx,
const char *expr, int runtime);
+
int expr__find_ids(const char *expr, const char *one,
struct expr_parse_ctx *ids, int runtime);

--
2.33.0.464.g1972c5931b-goog

2021-09-18 13:15:10

by Ian Rogers

[permalink] [raw]
Subject: [PATCH v8 6/8] perf metric: Allow metrics with no events

A metric may be a constant value, for example, some SMT metrics are
constant 0 if #smt_on is 0. If we eliminate all the events then there is
no printing. Fix this by forcing metrics like this to have a
duration_time tool event, previously the metric would fail when parsing
the events with a parse error.

Signed-off-by: Ian Rogers <[email protected]>
---
tools/perf/util/metricgroup.c | 109 ++++++++++++++++++----------------
1 file changed, 59 insertions(+), 50 deletions(-)

diff --git a/tools/perf/util/metricgroup.c b/tools/perf/util/metricgroup.c
index 046fb3fe1700..34956977e907 100644
--- a/tools/perf/util/metricgroup.c
+++ b/tools/perf/util/metricgroup.c
@@ -198,65 +198,69 @@ static struct evsel *find_evsel_group(struct evlist *perf_evlist,
struct evsel *ev, *current_leader = NULL;
struct expr_id_data *val_ptr;
int i = 0, matched_events = 0, events_to_match;
- const int idnum = (int)hashmap__size(pctx->ids);
+ int idnum = (int)hashmap__size(pctx->ids);

- /*
- * duration_time is always grouped separately, when events are grouped
- * (ie has_constraint is false) then ignore it in the matching loop and
- * add it to metric_events at the end.
- */
- if (!has_constraint &&
- hashmap__find(pctx->ids, "duration_time", (void **)&val_ptr))
- events_to_match = idnum - 1;
- else
- events_to_match = idnum;
-
- evlist__for_each_entry (perf_evlist, ev) {
+ if (idnum != 0) {
/*
- * Events with a constraint aren't grouped and match the first
- * events available.
+ * duration_time is always grouped separately, when events are
+ * grouped (ie has_constraint is false) then ignore it in the
+ * matching loop and add it to metric_events at the end.
*/
- if (has_constraint && ev->weak_group)
- continue;
- /* Ignore event if already used and merging is disabled. */
- if (metric_no_merge && test_bit(ev->core.idx, evlist_used))
- continue;
- if (!has_constraint && !evsel__has_leader(ev, current_leader)) {
+ events_to_match = idnum;
+ if (!has_constraint && hashmap__find(pctx->ids, "duration_time", (void **)&val_ptr))
+ events_to_match--;
+
+ evlist__for_each_entry(perf_evlist, ev) {
+ /*
+ * Events with a constraint aren't grouped and match the
+ * first events available.
+ */
+ if (has_constraint && ev->weak_group)
+ continue;
+ /* Ignore event if already used and merging is disabled. */
+ if (metric_no_merge && test_bit(ev->core.idx, evlist_used))
+ continue;
+ if (!has_constraint && !evsel__has_leader(ev, current_leader)) {
+ /*
+ * Start of a new group, discard the whole match
+ * and start again.
+ */
+ matched_events = 0;
+ memset(metric_events, 0, sizeof(struct evsel *) * idnum);
+ current_leader = evsel__leader(ev);
+ }
/*
- * Start of a new group, discard the whole match and
- * start again.
+ * 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. If events aren't
+ * grouped then this also ensures that the same event in
+ * different sibling groups aren't both added to
+ * metric_events.
*/
- matched_events = 0;
- memset(metric_events, 0,
- sizeof(struct evsel *) * idnum);
- current_leader = evsel__leader(ev);
+ if (contains_event(metric_events, matched_events, ev->name))
+ continue;
+ /* Does this event belong to the parse context? */
+ if (hashmap__find(pctx->ids, ev->name, (void **)&val_ptr))
+ metric_events[matched_events++] = ev;
+
+ if (matched_events == events_to_match)
+ break;
}
+ } else {
/*
- * 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. If events aren't grouped then this also
- * ensures that the same event in different sibling groups
- * aren't both added to metric_events.
+ * There are no events to match, but we need to associate the
+ * metric with an event for printing. A duration_time event was
+ * parsed for this.
*/
- if (contains_event(metric_events, matched_events, ev->name))
- continue;
- /* Does this event belong to the parse context? */
- if (hashmap__find(pctx->ids, ev->name, (void **)&val_ptr))
- metric_events[matched_events++] = ev;
-
- if (matched_events == events_to_match)
- break;
+ idnum = 1;
+ events_to_match = 0;
}
-
if (events_to_match != idnum) {
/* Add the first duration_time. */
- evlist__for_each_entry(perf_evlist, ev) {
- if (!strcmp(ev->name, "duration_time")) {
- metric_events[matched_events++] = ev;
- break;
- }
- }
+ ev = evlist__find_evsel_by_str(perf_evlist, "duration_time");
+ if (ev)
+ metric_events[matched_events++] = ev;
}

if (matched_events != idnum) {
@@ -320,9 +324,10 @@ static int metricgroup__setup_events(struct list_head *groups,
list_for_each_entry (m, groups, nd) {
struct evsel **metric_events;
struct metric_ref *metric_refs = NULL;
+ const size_t ids_size = hashmap__size(m->pctx->ids);

metric_events = calloc(sizeof(void *),
- hashmap__size(m->pctx->ids) + 1);
+ ids_size == 0 ? 2 : ids_size + 1);
if (!metric_events) {
ret = -ENOMEM;
break;
@@ -1240,7 +1245,11 @@ static int parse_groups(struct evlist *perf_evlist, const char *str,
goto out;
pr_debug("adding %s\n", extra_events.buf);
bzero(&parse_error, sizeof(parse_error));
- ret = __parse_events(perf_evlist, extra_events.buf, &parse_error, fake_pmu);
+ ret = __parse_events(perf_evlist,
+ extra_events.len > 0
+ ? extra_events.buf
+ : "duration_time",
+ &parse_error, fake_pmu);
if (ret) {
parse_events_print_error(&parse_error, extra_events.buf);
goto out;
--
2.33.0.464.g1972c5931b-goog

2021-09-18 13:15:58

by Ian Rogers

[permalink] [raw]
Subject: [PATCH v8 7/8] perf metric: Don't compute unused events.

For a metric like:
EVENT1 if #smt_on else EVENT2

currently EVENT1 and EVENT2 will be measured and then when the metric is
reported EVENT1 or EVENT2 will be printed depending on the value from
smt_on() during the expr parsing. Computing both events is unnecessary and
can lead to multiplexing as discussed in this thread:
https://lore.kernel.org/lkml/[email protected]/

This change modifies the expression parsing code by:
- getting rid of the "other" parsing and introducing a boolean argument
to say whether ids should be computed or not.
- expressions are changed so that a pair of value and ids are returned.
- when computing the metric value the ids are unused.
- when computing the ids, constant values and smt_on are assigned to
the value.
- If the value is from an event ID then the event is added to the ids
hashmap and the value set to bottom (encoded as NAN).
- Typically operators union IDs for their inputs and set the value to
bottom, however, if the inputs are constant then these are computed and
propagated as the value.
- If the input is constant to certain operators like:
IDS1 if CONST else IDS2
then the result will be either IDS1 or IDS2 depending on CONST (which
may be evaluated from an entire expression), and so IDS1 or IDS2 may
be discarded avoiding events from being programmed.
- The ids at the end of parsing are added to the context.

Signed-off-by: Ian Rogers <[email protected]>
---
tools/perf/util/expr.c | 9 +-
tools/perf/util/expr.h | 1 -
tools/perf/util/expr.l | 9 --
tools/perf/util/expr.y | 237 ++++++++++++++++++++++++++++++++++-------
4 files changed, 203 insertions(+), 53 deletions(-)

diff --git a/tools/perf/util/expr.c b/tools/perf/util/expr.c
index 34b51ca5e87f..e9396a309fb7 100644
--- a/tools/perf/util/expr.c
+++ b/tools/perf/util/expr.c
@@ -330,10 +330,9 @@ void expr__ctx_free(struct expr_parse_ctx *ctx)

static int
__expr__parse(double *val, struct expr_parse_ctx *ctx, const char *expr,
- int start, int runtime)
+ bool compute_ids, int runtime)
{
struct expr_scanner_ctx scanner_ctx = {
- .start_token = start,
.runtime = runtime,
};
YY_BUFFER_STATE buffer;
@@ -353,7 +352,7 @@ __expr__parse(double *val, struct expr_parse_ctx *ctx, const char *expr,
expr_set_debug(1, scanner);
#endif

- ret = expr_parse(val, ctx, scanner);
+ ret = expr_parse(val, ctx, compute_ids, scanner);

expr__flush_buffer(buffer, scanner);
expr__delete_buffer(buffer, scanner);
@@ -364,13 +363,13 @@ __expr__parse(double *val, struct expr_parse_ctx *ctx, const char *expr,
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, /*compute_ids=*/false, runtime) ? -1 : 0;
}

int expr__find_ids(const char *expr, const char *one,
struct expr_parse_ctx *ctx, int runtime)
{
- int ret = __expr__parse(NULL, ctx, expr, EXPR_OTHER, runtime);
+ int ret = __expr__parse(NULL, ctx, expr, /*compute_ids=*/true, runtime);

if (one)
expr__del_id(ctx, one);
diff --git a/tools/perf/util/expr.h b/tools/perf/util/expr.h
index a3508c5a427a..eb4cd9563b1a 100644
--- a/tools/perf/util/expr.h
+++ b/tools/perf/util/expr.h
@@ -26,7 +26,6 @@ struct expr_parse_ctx {
struct expr_id_data;

struct expr_scanner_ctx {
- int start_token;
int runtime;
};

diff --git a/tools/perf/util/expr.l b/tools/perf/util/expr.l
index 13e5e3c75f56..702fdf6456ca 100644
--- a/tools/perf/util/expr.l
+++ b/tools/perf/util/expr.l
@@ -91,15 +91,6 @@ symbol ({spec}|{sym})+
%%
struct expr_scanner_ctx *sctx = expr_get_extra(yyscanner);

- {
- int start_token = sctx->start_token;
-
- if (sctx->start_token) {
- sctx->start_token = 0;
- return start_token;
- }
- }
-
d_ratio { return D_RATIO; }
max { return MAX; }
min { return MIN; }
diff --git a/tools/perf/util/expr.y b/tools/perf/util/expr.y
index 51f4f0aa1955..907fe135c080 100644
--- a/tools/perf/util/expr.y
+++ b/tools/perf/util/expr.y
@@ -1,6 +1,7 @@
/* Simple expression parser */
%{
#define YYDEBUG 1
+#include <assert.h>
#include <math.h>
#include "util/debug.h"
#include "smt.h"
@@ -12,15 +13,43 @@

%parse-param { double *final_val }
%parse-param { struct expr_parse_ctx *ctx }
+%parse-param { bool compute_ids }
%parse-param {void *scanner}
%lex-param {void* scanner}

%union {
double num;
char *str;
+ struct ids {
+ /*
+ * When creating ids, holds the working set of event ids. NULL
+ * implies the set is empty.
+ */
+ struct hashmap *ids;
+ /*
+ * The metric value. When not creating ids this is the value
+ * read from a counter, a constant or some computed value. When
+ * creating ids the value is either a constant or BOTTOM. NAN is
+ * used as the special BOTTOM value, representing a "set of all
+ * values" case. Consider:
+ * 1.0 if event1 > 50.0 else 2.0
+ * The set of values {1.0, 2.0} is possible from this
+ * expression, but we conservatively use BOTTOM to say any value
+ * is possible. By tracking constants an expression like:
+ * 1.0 if event1 > 50.0 else 1.0
+ * can avoid computing event1 as only the value 1.0 is a
+ * possible value.
+ * It is much more likely we're evaluating:
+ * EVENT1 if #smt_on else EVENT2
+ * where we don't want to compute two events, but we track
+ * constants in a more general framework to allow for more
+ * complicated expressions.
+ */
+ double val;
+ } ids;
}

-%token ID NUMBER MIN MAX IF ELSE SMT_ON D_RATIO EXPR_ERROR EXPR_PARSE EXPR_OTHER
+%token ID NUMBER MIN MAX IF ELSE SMT_ON D_RATIO EXPR_ERROR
%left MIN MAX IF
%left '|'
%left '^'
@@ -32,65 +61,150 @@
%type <num> NUMBER
%type <str> ID
%destructor { free ($$); } <str>
-%type <num> expr if_expr
+%type <ids> expr if_expr
+%destructor { ids__free($$.ids); } <ids>

%{
static void expr_error(double *final_val __maybe_unused,
struct expr_parse_ctx *ctx __maybe_unused,
+ bool compute_ids __maybe_unused,
void *scanner,
const char *s)
{
pr_debug("%s\n", s);
}

+/*
+ * During compute ids, the special "bottom" value uses NAN to represent the set
+ * of all values. NAN is selected as it isn't a useful constant value.
+ */
+#define BOTTOM NAN
+
+/* During computing ids, does val represent a constant (non-BOTTOM) value? */
+static bool is_const(double val)
+{
+ return isfinite(val);
+}
+
+static struct ids union_expr(struct ids ids1, struct ids ids2)
+{
+ struct ids result = {
+ .val = BOTTOM,
+ .ids = ids__union(ids1.ids, ids2.ids),
+ };
+ return result;
+}
+
+/*
+ * If we're not computing ids or $1 and $3 are constants, compute the new
+ * constant value using OP. Its invariant that there are no ids. If computing
+ * ids for non-constants union the set of IDs that must be computed.
+ */
#define BINARY_LONG_OP(RESULT, OP, LHS, RHS) \
- RESULT = (long)LHS OP (long)RHS;
+ if (!compute_ids || (is_const(LHS.val) && is_const(RHS.val))) { \
+ assert(LHS.ids == NULL); \
+ assert(RHS.ids == NULL); \
+ RESULT.val = (long)LHS.val OP (long)RHS.val; \
+ RESULT.ids = NULL; \
+ } else { \
+ RESULT = union_expr(LHS, RHS); \
+ }

#define BINARY_OP(RESULT, OP, LHS, RHS) \
- RESULT = LHS OP RHS;
+ if (!compute_ids || (is_const(LHS.val) && is_const(RHS.val))) { \
+ assert(LHS.ids == NULL); \
+ assert(RHS.ids == NULL); \
+ RESULT.val = LHS.val OP RHS.val; \
+ RESULT.ids = NULL; \
+ } else { \
+ RESULT = union_expr(LHS, RHS); \
+ }

%}
%%

-start:
-EXPR_PARSE all_expr
-|
-EXPR_OTHER all_other
-
-all_other: all_other other
-|
-
-other: ID
+start: if_expr
{
- expr__add_id(ctx, $1);
-}
-|
-MIN | MAX | IF | ELSE | SMT_ON | NUMBER | '|' | '^' | '&' | '-' | '+' | '*' | '/' | '%' | '(' | ')' | ','
-|
-'<' | '>' | D_RATIO
+ if (compute_ids)
+ ctx->ids = ids__union($1.ids, ctx->ids);

-all_expr: if_expr { *final_val = $1; }
+ if (final_val)
+ *final_val = $1.val;
+}
+;

if_expr: expr IF expr ELSE expr
{
- $$ = $3 ? $1 : $5;
+ if (fpclassify($3.val) == FP_ZERO) {
+ /*
+ * The IF expression evaluated to 0 so treat as false, take the
+ * ELSE and discard everything else.
+ */
+ $$.val = $5.val;
+ $$.ids = $5.ids;
+ ids__free($1.ids);
+ ids__free($3.ids);
+ } else if (!compute_ids || is_const($3.val)) {
+ /*
+ * If ids aren't computed then treat the expression as true. If
+ * ids are being computed and the IF expr is a non-zero
+ * constant, then also evaluate the true case.
+ */
+ $$.val = $1.val;
+ $$.ids = $1.ids;
+ ids__free($3.ids);
+ ids__free($5.ids);
+ } else if ($1.val == $5.val) {
+ /*
+ * LHS == RHS, so both are an identical constant. No need to
+ * evaluate any events.
+ */
+ $$.val = $1.val;
+ $$.ids = NULL;
+ ids__free($1.ids);
+ ids__free($3.ids);
+ ids__free($5.ids);
+ } else {
+ /*
+ * Value is either the LHS or RHS and we need the IF expression
+ * to compute it.
+ */
+ $$ = union_expr($1, union_expr($3, $5));
+ }
}
| expr
;

expr: NUMBER
{
- $$ = $1;
+ $$.val = $1;
+ $$.ids = NULL;
}
| ID
{
- struct expr_id_data *data;
+ if (!compute_ids) {
+ /*
+ * Compute the event's value from ID. If the ID isn't known then
+ * it isn't used to compute the formula so set to NAN.
+ */
+ struct expr_id_data *data;

- $$ = NAN;
- if (expr__resolve_id(ctx, $1, &data) == 0)
- $$ = expr_id_data__value(data);
+ $$.val = NAN;
+ if (expr__resolve_id(ctx, $1, &data) == 0)
+ $$.val = expr_id_data__value(data);

- free($1);
+ $$.ids = NULL;
+ free($1);
+ } else {
+ /*
+ * Set the value to BOTTOM to show that any value is possible
+ * when the event is computed. Create a set of just the ID.
+ */
+ $$.val = BOTTOM;
+ $$.ids = ids__new();
+ if (!$$.ids || ids__insert($$.ids, $1, ctx->parent))
+ YYABORT;
+ }
}
| expr '|' expr { BINARY_LONG_OP($$, |, $1, $3); }
| expr '&' expr { BINARY_LONG_OP($$, &, $1, $3); }
@@ -102,31 +216,59 @@ expr: NUMBER
| expr '*' expr { BINARY_OP($$, *, $1, $3); }
| expr '/' expr
{
- if ($3 == 0) {
+ if (fpclassify($3.val) == FP_ZERO) {
pr_debug("division by zero\n");
YYABORT;
+ } else if (!compute_ids || (is_const($1.val) && is_const($3.val))) {
+ assert($1.ids == NULL);
+ assert($3.ids == NULL);
+ $$.val = $1.val / $3.val;
+ $$.ids = NULL;
+ } else {
+ /* LHS and/or RHS need computing from event IDs so union. */
+ $$ = union_expr($1, $3);
}
- $$ = $1 / $3;
}
| expr '%' expr
{
- if ((long)$3 == 0) {
+ if (fpclassify($3.val) == FP_ZERO) {
pr_debug("division by zero\n");
YYABORT;
+ } else if (!compute_ids || (is_const($1.val) && is_const($3.val))) {
+ assert($1.ids == NULL);
+ assert($3.ids == NULL);
+ $$.val = (long)$1.val % (long)$3.val;
+ $$.ids = NULL;
+ } else {
+ /* LHS and/or RHS need computing from event IDs so union. */
+ $$ = union_expr($1, $3);
}
- $$ = (long)$1 % (long)$3;
}
| D_RATIO '(' expr ',' expr ')'
{
- if ($5 == 0) {
- $$ = 0;
+ if (fpclassify($5.val) == FP_ZERO) {
+ /*
+ * Division by constant zero always yields zero and no events
+ * are necessary.
+ */
+ assert($5.ids == NULL);
+ $$.val = 0.0;
+ $$.ids = NULL;
+ ids__free($3.ids);
+ } else if (!compute_ids || (is_const($3.val) && is_const($5.val))) {
+ assert($3.ids == NULL);
+ assert($5.ids == NULL);
+ $$.val = $3.val / $5.val;
+ $$.ids = NULL;
} else {
- $$ = $3 / $5;
+ /* LHS and/or RHS need computing from event IDs so union. */
+ $$ = union_expr($3, $5);
}
}
| '-' expr %prec NEG
{
- $$ = -$2;
+ $$.val = -$2.val;
+ $$.ids = $2.ids;
}
| '(' if_expr ')'
{
@@ -134,14 +276,33 @@ expr: NUMBER
}
| MIN '(' expr ',' expr ')'
{
- $$ = $3 < $5 ? $3 : $5;
+ if (!compute_ids || (is_const($3.val) && is_const($5.val))) {
+ assert($3.ids == NULL);
+ assert($5.ids == NULL);
+ $$.val = $3.val < $5.val ? $3.val : $5.val;
+ $$.ids = NULL;
+ } else {
+ /* LHS and/or RHS need computing from event IDs so union. */
+ $$ = union_expr($3, $5);
+ }
}
| MAX '(' expr ',' expr ')'
{
- $$ = $3 > $5 ? $3 : $5;
+ if (!compute_ids || (is_const($3.val) && is_const($5.val))) {
+ assert($3.ids == NULL);
+ assert($5.ids == NULL);
+ $$.val = $3.val > $5.val ? $3.val : $5.val;
+ $$.ids = NULL;
+ } else {
+ /* LHS and/or RHS need computing from event IDs so union. */
+ $$ = union_expr($3, $5);
+ }
}
| SMT_ON
{
- $$ = smt_on() > 0 ? 1.0 : 0.0;
+ $$.val = smt_on() > 0 ? 1.0 : 0.0;
+ $$.ids = NULL;
}
;
+
+%%
--
2.33.0.464.g1972c5931b-goog

2021-09-18 13:18:29

by Ian Rogers

[permalink] [raw]
Subject: [PATCH v8 8/8] perf test: Add metric test for eliminating events

Add test that ensures we remove events based on #smt_on, or if
evaluating the event has no impact on the output.

Signed-off-by: Ian Rogers <[email protected]>
---
tools/perf/tests/expr.c | 17 +++++++++++++++++
1 file changed, 17 insertions(+)

diff --git a/tools/perf/tests/expr.c b/tools/perf/tests/expr.c
index 1c881bea7fca..5cab5960b257 100644
--- a/tools/perf/tests/expr.c
+++ b/tools/perf/tests/expr.c
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: GPL-2.0
#include "util/debug.h"
#include "util/expr.h"
+#include "util/smt.h"
#include "tests.h"
#include <stdlib.h>
#include <string.h>
@@ -132,6 +133,22 @@ int test__expr(struct test *t __maybe_unused, int subtest __maybe_unused)
TEST_ASSERT_VAL("find ids", hashmap__find(ctx->ids, "EVENT2,param=3/",
(void **)&val_ptr));

+ /* Only EVENT1 or EVENT2 need be measured depending on the value of smt_on. */
+ expr__ctx_clear(ctx);
+ TEST_ASSERT_VAL("find ids",
+ expr__find_ids("EVENT1 if #smt_on else EVENT2",
+ NULL, ctx, 0) == 0);
+ TEST_ASSERT_VAL("find ids", hashmap__size(ctx->ids) == 1);
+ TEST_ASSERT_VAL("find ids", hashmap__find(ctx->ids,
+ smt_on() ? "EVENT1" : "EVENT2",
+ (void **)&val_ptr));
+
+ /* The expression is a constant 1.0 without needing to evaluate EVENT1. */
+ expr__ctx_clear(ctx);
+ TEST_ASSERT_VAL("find ids",
+ expr__find_ids("1.0 if EVENT1 > 100.0 else 1.0",
+ NULL, ctx, 0) == 0);
+ TEST_ASSERT_VAL("find ids", hashmap__size(ctx->ids) == 0);
expr__ctx_free(ctx);

return 0;
--
2.33.0.464.g1972c5931b-goog

2021-09-18 15:00:20

by Ian Rogers

[permalink] [raw]
Subject: [PATCH v8 4/8] perf metric: Rename expr__find_other.

A later change will remove the notion of other, rename the function to
expr__find_ids as this is what it populates.

Signed-off-by: Ian Rogers <[email protected]>
---
tools/perf/tests/expr.c | 26 +++++++++++++-------------
tools/perf/tests/pmu-events.c | 11 +++++------
tools/perf/util/expr.c | 4 ++--
tools/perf/util/expr.h | 2 +-
tools/perf/util/metricgroup.c | 2 +-
tools/perf/util/stat-shadow.c | 6 +++---
6 files changed, 25 insertions(+), 26 deletions(-)

diff --git a/tools/perf/tests/expr.c b/tools/perf/tests/expr.c
index b0a3b5fd0c00..7ccb97c73347 100644
--- a/tools/perf/tests/expr.c
+++ b/tools/perf/tests/expr.c
@@ -64,25 +64,25 @@ int test__expr(struct test *t __maybe_unused, int subtest __maybe_unused)
TEST_ASSERT_VAL("missing operand", ret == -1);

expr__ctx_clear(ctx);
- TEST_ASSERT_VAL("find other",
- expr__find_other("FOO + BAR + BAZ + BOZO", "FOO",
- ctx, 1) == 0);
- TEST_ASSERT_VAL("find other", hashmap__size(ctx->ids) == 3);
- TEST_ASSERT_VAL("find other", hashmap__find(ctx->ids, "BAR",
+ TEST_ASSERT_VAL("find ids",
+ expr__find_ids("FOO + BAR + BAZ + BOZO", "FOO",
+ ctx, 1) == 0);
+ TEST_ASSERT_VAL("find ids", hashmap__size(ctx->ids) == 3);
+ TEST_ASSERT_VAL("find ids", hashmap__find(ctx->ids, "BAR",
(void **)&val_ptr));
- TEST_ASSERT_VAL("find other", hashmap__find(ctx->ids, "BAZ",
+ TEST_ASSERT_VAL("find ids", hashmap__find(ctx->ids, "BAZ",
(void **)&val_ptr));
- TEST_ASSERT_VAL("find other", hashmap__find(ctx->ids, "BOZO",
+ TEST_ASSERT_VAL("find ids", hashmap__find(ctx->ids, "BOZO",
(void **)&val_ptr));

expr__ctx_clear(ctx);
- TEST_ASSERT_VAL("find other",
- expr__find_other("EVENT1\\,param\\=?@ + EVENT2\\,param\\=?@",
- NULL, ctx, 3) == 0);
- TEST_ASSERT_VAL("find other", hashmap__size(ctx->ids) == 2);
- TEST_ASSERT_VAL("find other", hashmap__find(ctx->ids, "EVENT1,param=3/",
+ TEST_ASSERT_VAL("find ids",
+ expr__find_ids("EVENT1\\,param\\=?@ + EVENT2\\,param\\=?@",
+ NULL, ctx, 3) == 0);
+ TEST_ASSERT_VAL("find ids", hashmap__size(ctx->ids) == 2);
+ TEST_ASSERT_VAL("find ids", hashmap__find(ctx->ids, "EVENT1,param=3/",
(void **)&val_ptr));
- TEST_ASSERT_VAL("find other", hashmap__find(ctx->ids, "EVENT2,param=3/",
+ TEST_ASSERT_VAL("find ids", hashmap__find(ctx->ids, "EVENT2,param=3/",
(void **)&val_ptr));

expr__ctx_free(ctx);
diff --git a/tools/perf/tests/pmu-events.c b/tools/perf/tests/pmu-events.c
index 001da2909668..42e6e5199438 100644
--- a/tools/perf/tests/pmu-events.c
+++ b/tools/perf/tests/pmu-events.c
@@ -811,7 +811,7 @@ static int resolve_metric_simple(struct expr_parse_ctx *pctx,
ref->metric_expr = pe->metric_expr;
list_add_tail(&metric->list, compound_list);

- rc = expr__find_other(pe->metric_expr, NULL, pctx, 0);
+ rc = expr__find_ids(pe->metric_expr, NULL, pctx, 0);
if (rc)
goto out_err;
break; /* The hashmap has been modified, so restart */
@@ -861,9 +861,8 @@ static int test_parsing(void)
if (!pe->metric_expr)
continue;
expr__ctx_clear(ctx);
- if (expr__find_other(pe->metric_expr, NULL, ctx, 0)
- < 0) {
- expr_failure("Parse other failed", map, pe);
+ if (expr__find_ids(pe->metric_expr, NULL, ctx, 0) < 0) {
+ expr_failure("Parse find ids failed", map, pe);
ret++;
continue;
}
@@ -931,8 +930,8 @@ static int metric_parse_fake(const char *str)
pr_debug("parsing '%s'\n", str);

ctx = expr__ctx_new();
- if (expr__find_other(str, NULL, ctx, 0) < 0) {
- pr_err("expr__find_other failed\n");
+ if (expr__find_ids(str, NULL, ctx, 0) < 0) {
+ pr_err("expr__find_ids failed\n");
return -1;
}

diff --git a/tools/perf/util/expr.c b/tools/perf/util/expr.c
index 7b1c06772a49..adf16bb7571a 100644
--- a/tools/perf/util/expr.c
+++ b/tools/perf/util/expr.c
@@ -288,8 +288,8 @@ int expr__parse(double *final_val, struct expr_parse_ctx *ctx,
return __expr__parse(final_val, ctx, expr, EXPR_PARSE, runtime) ? -1 : 0;
}

-int expr__find_other(const char *expr, const char *one,
- struct expr_parse_ctx *ctx, int runtime)
+int expr__find_ids(const char *expr, const char *one,
+ struct expr_parse_ctx *ctx, int runtime)
{
int ret = __expr__parse(NULL, ctx, expr, EXPR_OTHER, runtime);

diff --git a/tools/perf/util/expr.h b/tools/perf/util/expr.h
index 5fa394f10418..de109c2ab917 100644
--- a/tools/perf/util/expr.h
+++ b/tools/perf/util/expr.h
@@ -43,7 +43,7 @@ int expr__resolve_id(struct expr_parse_ctx *ctx, const char *id,
struct expr_id_data **datap);
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,
+int expr__find_ids(const char *expr, const char *one,
struct expr_parse_ctx *ids, int runtime);

double expr_id_data__value(const struct expr_id_data *data);
diff --git a/tools/perf/util/metricgroup.c b/tools/perf/util/metricgroup.c
index b7924a2f1f45..046fb3fe1700 100644
--- a/tools/perf/util/metricgroup.c
+++ b/tools/perf/util/metricgroup.c
@@ -857,7 +857,7 @@ static int __add_metric(struct list_head *metric_list,
* For both the parent and referenced metrics, we parse
* all the metric's IDs and add it to the parent context.
*/
- if (expr__find_other(pe->metric_expr, NULL, m->pctx, runtime) < 0) {
+ if (expr__find_ids(pe->metric_expr, NULL, m->pctx, runtime) < 0) {
if (m->metric_refs_cnt == 0) {
expr__ctx_free(m->pctx);
free(m);
diff --git a/tools/perf/util/stat-shadow.c b/tools/perf/util/stat-shadow.c
index c9fa07e49e72..9bc841e09a0c 100644
--- a/tools/perf/util/stat-shadow.c
+++ b/tools/perf/util/stat-shadow.c
@@ -392,9 +392,9 @@ void perf_stat__collect_metric_expr(struct evlist *evsel_list)
expr__ctx_clear(ctx);
metric_events = counter->metric_events;
if (!metric_events) {
- if (expr__find_other(counter->metric_expr,
- counter->name,
- ctx, 1) < 0)
+ if (expr__find_ids(counter->metric_expr,
+ counter->name,
+ ctx, 1) < 0)
continue;

metric_events = calloc(sizeof(struct evsel *),
--
2.33.0.464.g1972c5931b-goog

2021-09-18 15:02:01

by Ian Rogers

[permalink] [raw]
Subject: [PATCH v8 2/8] perf metric: Use NAN for missing event IDs.

If during computing a metric an event (id) is missing the parsing
aborts. A later patch will make it so that events that aren't used in
the output are deliberately omitted, in which case we don't want the
abort. Modify the missing ID case to report NAN for these cases.

Signed-off-by: Ian Rogers <[email protected]>
---
tools/perf/util/expr.y | 9 ++++-----
1 file changed, 4 insertions(+), 5 deletions(-)

diff --git a/tools/perf/util/expr.y b/tools/perf/util/expr.y
index b2ada8f8309a..41c9cd4efadd 100644
--- a/tools/perf/util/expr.y
+++ b/tools/perf/util/expr.y
@@ -1,6 +1,7 @@
/* Simple expression parser */
%{
#define YYDEBUG 1
+#include <math.h>
#include <stdio.h>
#include "util.h"
#include "util/debug.h"
@@ -88,12 +89,10 @@ expr: NUMBER
| ID {
struct expr_id_data *data;

- if (expr__resolve_id(ctx, $1, &data)) {
- free($1);
- YYABORT;
- }
+ $$ = NAN;
+ if (expr__resolve_id(ctx, $1, &data) == 0)
+ $$ = expr_id_data__value(data);

- $$ = expr_id_data__value(data);
free($1);
}
| expr '|' expr { $$ = (long)$1 | (long)$3; }
--
2.33.0.464.g1972c5931b-goog

2021-09-20 23:59:30

by Andi Kleen

[permalink] [raw]
Subject: Re: [PATCH v8 0/8] Don't compute events that won't be used in a metric.


On 9/17/2021 11:35 PM, Ian Rogers wrote:
>
> For a metric like:
> EVENT1 if #smt_on else EVENT2
>
> currently EVENT1 and EVENT2 will be measured and then when the metric
> is reported EVENT1 or EVENT2 will be printed depending on the value
> from smt_on() during the expr parsing. Computing both events is
> unnecessary and can lead to multiplexing as discussed in this thread:
> https://lore.kernel.org/lkml/[email protected]/
>
> This change modifies expression parsing so that constants are
> considered when building the set of ids (events) and only events not
> contributing to a constant value are measured.


The series looks good to me.


Reviewed-by: Andi Kleen <[email protected]>


-Andi


2021-09-22 21:00:34

by Jiri Olsa

[permalink] [raw]
Subject: Re: [PATCH v8 3/8] perf expr: Modify code layout

On Fri, Sep 17, 2021 at 11:35:08PM -0700, Ian Rogers wrote:
> No functional change. Alter whitespace. Use helper macros that will be
> made more complex in a later change.

please split into more patches, with the spacing changes
it's not easy to check there's no functional change

thanks,
jirka

>
> Signed-off-by: Ian Rogers <[email protected]>
> ---
> tools/perf/util/expr.y | 149 +++++++++++++++++++++++------------------
> 1 file changed, 85 insertions(+), 64 deletions(-)
>
> diff --git a/tools/perf/util/expr.y b/tools/perf/util/expr.y
> index 41c9cd4efadd..51f4f0aa1955 100644
> --- a/tools/perf/util/expr.y
> +++ b/tools/perf/util/expr.y
> @@ -2,23 +2,10 @@
> %{
> #define YYDEBUG 1
> #include <math.h>
> -#include <stdio.h>
> -#include "util.h"
> #include "util/debug.h"
> -#include <stdlib.h> // strtod()
> +#include "smt.h"
> #define IN_EXPR_Y 1
> #include "expr.h"
> -#include "smt.h"
> -#include <string.h>
> -
> -static double d_ratio(double val0, double val1)
> -{
> - if (val1 == 0) {
> - return 0;
> - }
> - return val0 / val1;
> -}
> -
> %}
>
> %define api.pure full
> @@ -33,11 +20,7 @@ static double d_ratio(double val0, double val1)
> char *str;
> }
>
> -%token EXPR_PARSE EXPR_OTHER EXPR_ERROR
> -%token <num> NUMBER
> -%token <str> ID
> -%destructor { free ($$); } <str>
> -%token MIN MAX IF ELSE SMT_ON D_RATIO
> +%token ID NUMBER MIN MAX IF ELSE SMT_ON D_RATIO EXPR_ERROR EXPR_PARSE EXPR_OTHER
> %left MIN MAX IF
> %left '|'
> %left '^'
> @@ -46,6 +29,9 @@ static double d_ratio(double val0, double val1)
> %left '-' '+'
> %left '*' '/' '%'
> %left NEG NOT
> +%type <num> NUMBER
> +%type <str> ID
> +%destructor { free ($$); } <str>
> %type <num> expr if_expr
>
> %{
> @@ -57,6 +43,12 @@ static void expr_error(double *final_val __maybe_unused,
> pr_debug("%s\n", s);
> }
>
> +#define BINARY_LONG_OP(RESULT, OP, LHS, RHS) \
> + RESULT = (long)LHS OP (long)RHS;
> +
> +#define BINARY_OP(RESULT, OP, LHS, RHS) \
> + RESULT = LHS OP RHS;
> +
> %}
> %%
>
> @@ -78,49 +70,78 @@ MIN | MAX | IF | ELSE | SMT_ON | NUMBER | '|' | '^' | '&' | '-' | '+' | '*' | '/
> '<' | '>' | D_RATIO
>
> all_expr: if_expr { *final_val = $1; }
> - ;
> -
> -if_expr:
> - expr IF expr ELSE expr { $$ = $3 ? $1 : $5; }
> - | expr
> - ;
> -
> -expr: NUMBER
> - | ID {
> - struct expr_id_data *data;
> -
> - $$ = NAN;
> - if (expr__resolve_id(ctx, $1, &data) == 0)
> - $$ = expr_id_data__value(data);
> -
> - free($1);
> - }
> - | expr '|' expr { $$ = (long)$1 | (long)$3; }
> - | expr '&' expr { $$ = (long)$1 & (long)$3; }
> - | expr '^' expr { $$ = (long)$1 ^ (long)$3; }
> - | expr '<' expr { $$ = $1 < $3; }
> - | expr '>' expr { $$ = $1 > $3; }
> - | expr '+' expr { $$ = $1 + $3; }
> - | expr '-' expr { $$ = $1 - $3; }
> - | expr '*' expr { $$ = $1 * $3; }
> - | expr '/' expr { if ($3 == 0) {
> - pr_debug("division by zero\n");
> - YYABORT;
> - }
> - $$ = $1 / $3;
> - }
> - | expr '%' expr { if ((long)$3 == 0) {
> - pr_debug("division by zero\n");
> - YYABORT;
> - }
> - $$ = (long)$1 % (long)$3;
> - }
> - | '-' expr %prec NEG { $$ = -$2; }
> - | '(' if_expr ')' { $$ = $2; }
> - | MIN '(' expr ',' expr ')' { $$ = $3 < $5 ? $3 : $5; }
> - | MAX '(' expr ',' expr ')' { $$ = $3 > $5 ? $3 : $5; }
> - | SMT_ON { $$ = smt_on() > 0; }
> - | D_RATIO '(' expr ',' expr ')' { $$ = d_ratio($3,$5); }
> - ;
>
> -%%
> +if_expr: expr IF expr ELSE expr
> +{
> + $$ = $3 ? $1 : $5;
> +}
> +| expr
> +;
> +
> +expr: NUMBER
> +{
> + $$ = $1;
> +}
> +| ID
> +{
> + struct expr_id_data *data;
> +
> + $$ = NAN;
> + if (expr__resolve_id(ctx, $1, &data) == 0)
> + $$ = expr_id_data__value(data);
> +
> + free($1);
> +}
> +| expr '|' expr { BINARY_LONG_OP($$, |, $1, $3); }
> +| expr '&' expr { BINARY_LONG_OP($$, &, $1, $3); }
> +| expr '^' expr { BINARY_LONG_OP($$, ^, $1, $3); }
> +| expr '<' expr { BINARY_OP($$, <, $1, $3); }
> +| expr '>' expr { BINARY_OP($$, >, $1, $3); }
> +| expr '+' expr { BINARY_OP($$, +, $1, $3); }
> +| expr '-' expr { BINARY_OP($$, -, $1, $3); }
> +| expr '*' expr { BINARY_OP($$, *, $1, $3); }
> +| expr '/' expr
> +{
> + if ($3 == 0) {
> + pr_debug("division by zero\n");
> + YYABORT;
> + }
> + $$ = $1 / $3;
> +}
> +| expr '%' expr
> +{
> + if ((long)$3 == 0) {
> + pr_debug("division by zero\n");
> + YYABORT;
> + }
> + $$ = (long)$1 % (long)$3;
> +}
> +| D_RATIO '(' expr ',' expr ')'
> +{
> + if ($5 == 0) {
> + $$ = 0;
> + } else {
> + $$ = $3 / $5;
> + }
> +}
> +| '-' expr %prec NEG
> +{
> + $$ = -$2;
> +}
> +| '(' if_expr ')'
> +{
> + $$ = $2;
> +}
> +| MIN '(' expr ',' expr ')'
> +{
> + $$ = $3 < $5 ? $3 : $5;
> +}
> +| MAX '(' expr ',' expr ')'
> +{
> + $$ = $3 > $5 ? $3 : $5;
> +}
> +| SMT_ON
> +{
> + $$ = smt_on() > 0 ? 1.0 : 0.0;
> +}
> +;
> --
> 2.33.0.464.g1972c5931b-goog
>

2021-09-22 21:00:42

by Jiri Olsa

[permalink] [raw]
Subject: Re: [PATCH v8 5/8] perf metric: Add utilities to work on ids map.

On Fri, Sep 17, 2021 at 11:35:10PM -0700, Ian Rogers wrote:

SNIP

>
> +struct hashmap *ids__union(struct hashmap *ids1, struct hashmap *ids2)
> +{
> + size_t bkt;
> + struct hashmap_entry *cur;
> + int ret;
> + struct expr_id_data *old_data = NULL;
> + char *old_key = NULL;
> +
> + if (!ids1)
> + return ids2;
> +
> + if (!ids2)
> + return ids1;
> +
> + if (hashmap__size(ids1) < hashmap__size(ids2)) {
> + struct hashmap *tmp = ids1;
> +
> + ids1 = ids2;
> + ids2 = tmp;
> + }
> + hashmap__for_each_entry(ids2, cur, bkt) {
> + ret = hashmap__set(ids1, cur->key, cur->value,
> + (const void **)&old_key, (void **)&old_data);
> + free(old_key);
> + free(old_data);
> +
> + if (ret)
> + break;

should we return NULL in here?

jirka

> + }
> + hashmap__free(ids2);
> + return ids1;
> +}
> +
> +/* Caller must make sure id is allocated */
> +int expr__add_id(struct expr_parse_ctx *ctx, const char *id)
> +{
> + return ids__insert(ctx->ids, id, ctx->parent);
> +}
> +

SNIP

2021-09-22 21:02:43

by Jiri Olsa

[permalink] [raw]
Subject: Re: [PATCH v8 5/8] perf metric: Add utilities to work on ids map.

SNIP

> diff --git a/tools/perf/util/expr.c b/tools/perf/util/expr.c
> index adf16bb7571a..34b51ca5e87f 100644
> --- a/tools/perf/util/expr.c
> +++ b/tools/perf/util/expr.c
> @@ -59,8 +59,48 @@ static bool key_equal(const void *key1, const void *key2,
> 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 *id)
> +struct hashmap *ids__new(void)
> +{
> + return hashmap__new(key_hash, key_equal, NULL);
> +}
> +
> +void ids__free(struct hashmap *ids)
> +{
> + struct hashmap_entry *cur;
> + size_t bkt;
> +
> + if (ids == NULL)
> + return;
> +
> +#ifdef PARSER_DEBUG
> + fprintf(stderr, "freeing ids: ");
> + ids__print(ids);
> + fprintf(stderr, "\n");
> +#endif

hum, is this intended or forgotten debug leftover?

jirka

> +
> + hashmap__for_each_entry(ids, cur, bkt) {
> + free((char *)cur->key);
> + free(cur->value);
> + }
> +
> + hashmap__free(ids);
> +}
> +
> +void ids__print(struct hashmap *ids)

SNIP

2021-09-22 21:03:15

by Jiri Olsa

[permalink] [raw]
Subject: Re: [PATCH v8 1/8] perf metric: Restructure struct expr_parse_ctx.

On Fri, Sep 17, 2021 at 11:35:06PM -0700, Ian Rogers wrote:
> A later change to parsing the ids out (in expr__find_other) will
> potentially drop hashmaps and so it is more convenient to move
> expr_parse_ctx to have a hashmap pointer rather than a struct value. As
> this pointer must be freed, rather than just going out of scope,
> add expr__ctx_new and expr__ctx_free to manage expr_parse_ctx memory.
> Adjust use of struct expr_parse_ctx accordingly.
>
> Signed-off-by: Ian Rogers <[email protected]>
> ---
> tools/perf/tests/expr.c | 81 ++++++++++++++++++-----------------
> tools/perf/tests/pmu-events.c | 43 +++++++++++--------
> tools/perf/util/expr.c | 39 +++++++++++++----
> tools/perf/util/expr.h | 5 ++-
> tools/perf/util/metricgroup.c | 44 ++++++++++---------
> tools/perf/util/stat-shadow.c | 50 +++++++++++++--------
> 6 files changed, 155 insertions(+), 107 deletions(-)
>
> diff --git a/tools/perf/tests/expr.c b/tools/perf/tests/expr.c
> index 4d01051951cd..b0a3b5fd0c00 100644
> --- a/tools/perf/tests/expr.c
> +++ b/tools/perf/tests/expr.c
> @@ -22,67 +22,70 @@ int test__expr(struct test *t __maybe_unused, int subtest __maybe_unused)
> const char *p;
> double val;
> int ret;
> - struct expr_parse_ctx ctx;
> + struct expr_parse_ctx *ctx;
>
> - expr__ctx_init(&ctx);
> - expr__add_id_val(&ctx, strdup("FOO"), 1);
> - expr__add_id_val(&ctx, strdup("BAR"), 2);
> + ctx = expr__ctx_new();

missing ctx check

> + TEST_ASSERT_VAL("expr__ctx_new", ctx);
> + expr__add_id_val(ctx, strdup("FOO"), 1);
> + expr__add_id_val(ctx, strdup("BAR"), 2);
>
> - ret = test(&ctx, "1+1", 2);
> - ret |= test(&ctx, "FOO+BAR", 3);
> - ret |= test(&ctx, "(BAR/2)%2", 1);
> - ret |= test(&ctx, "1 - -4", 5);
> - ret |= test(&ctx, "(FOO-1)*2 + (BAR/2)%2 - -4", 5);
> - ret |= test(&ctx, "1-1 | 1", 1);
> - ret |= test(&ctx, "1-1 & 1", 0);
> - ret |= test(&ctx, "min(1,2) + 1", 2);
> - ret |= test(&ctx, "max(1,2) + 1", 3);
> - ret |= test(&ctx, "1+1 if 3*4 else 0", 2);
> - ret |= test(&ctx, "1.1 + 2.1", 3.2);
> - ret |= test(&ctx, ".1 + 2.", 2.1);
> - ret |= test(&ctx, "d_ratio(1, 2)", 0.5);
> - ret |= test(&ctx, "d_ratio(2.5, 0)", 0);
> - ret |= test(&ctx, "1.1 < 2.2", 1);
> - ret |= test(&ctx, "2.2 > 1.1", 1);
> - ret |= test(&ctx, "1.1 < 1.1", 0);
> - ret |= test(&ctx, "2.2 > 2.2", 0);
> - ret |= test(&ctx, "2.2 < 1.1", 0);
> - ret |= test(&ctx, "1.1 > 2.2", 0);
> + ret = test(ctx, "1+1", 2);
> + ret |= test(ctx, "FOO+BAR", 3);
> + ret |= test(ctx, "(BAR/2)%2", 1);
> + ret |= test(ctx, "1 - -4", 5);
> + ret |= test(ctx, "(FOO-1)*2 + (BAR/2)%2 - -4", 5);
> + ret |= test(ctx, "1-1 | 1", 1);
> + ret |= test(ctx, "1-1 & 1", 0);
> + ret |= test(ctx, "min(1,2) + 1", 2);
> + ret |= test(ctx, "max(1,2) + 1", 3);
> + ret |= test(ctx, "1+1 if 3*4 else 0", 2);
> + ret |= test(ctx, "1.1 + 2.1", 3.2);
> + ret |= test(ctx, ".1 + 2.", 2.1);
> + ret |= test(ctx, "d_ratio(1, 2)", 0.5);
> + ret |= test(ctx, "d_ratio(2.5, 0)", 0);
> + ret |= test(ctx, "1.1 < 2.2", 1);
> + ret |= test(ctx, "2.2 > 1.1", 1);
> + ret |= test(ctx, "1.1 < 1.1", 0);
> + ret |= test(ctx, "2.2 > 2.2", 0);
> + ret |= test(ctx, "2.2 < 1.1", 0);
> + ret |= test(ctx, "1.1 > 2.2", 0);


SNIP


> ret++;
> @@ -876,27 +881,27 @@ static int test_parsing(void)
> * make them unique.
> */
> k = 1;
> - hashmap__for_each_entry((&ctx.ids), cur, bkt)
> - expr__add_id_val(&ctx, strdup(cur->key), k++);
> + hashmap__for_each_entry(ctx->ids, cur, bkt)
> + expr__add_id_val(ctx, strdup(cur->key), k++);
>
> - hashmap__for_each_entry((&ctx.ids), cur, bkt) {
> + hashmap__for_each_entry(ctx->ids, cur, bkt) {
> if (check_parse_cpu(cur->key, map == cpus_map,
> pe))
> ret++;
> }
>
> list_for_each_entry_safe(metric, tmp, &compound_list, list) {
> - expr__add_ref(&ctx, &metric->metric_ref);
> + expr__add_ref(ctx, &metric->metric_ref);
> free(metric);
> }
>
> - if (expr__parse(&result, &ctx, pe->metric_expr, 0)) {
> + if (expr__parse(&result, ctx, pe->metric_expr, 0)) {
> expr_failure("Parse failed", map, pe);
> ret++;
> }
> - expr__ctx_clear(&ctx);
> }
> }
> + expr__ctx_free(ctx);
> /* TODO: fail when not ok */
> exit:
> return ret == 0 ? TEST_OK : TEST_SKIP;
> @@ -916,7 +921,7 @@ static struct test_metric metrics[] = {
>
> static int metric_parse_fake(const char *str)
> {
> - struct expr_parse_ctx ctx;
> + struct expr_parse_ctx *ctx;
> struct hashmap_entry *cur;
> double result;
> int ret = -1;
> @@ -925,8 +930,8 @@ static int metric_parse_fake(const char *str)
>
> pr_debug("parsing '%s'\n", str);
>
> - expr__ctx_init(&ctx);
> - if (expr__find_other(str, NULL, &ctx, 0) < 0) {
> + ctx = expr__ctx_new();

missing ctx check

jirka

> + if (expr__find_other(str, NULL, ctx, 0) < 0) {
> pr_err("expr__find_other failed\n");
> return -1;
> }
> @@ -937,23 +942,23 @@ static int metric_parse_fake(const char *str)
> * make them unique.
> */
> i = 1;
> - hashmap__for_each_entry((&ctx.ids), cur, bkt)
> - expr__add_id_val(&ctx, strdup(cur->key), i++);
> + hashmap__for_each_entry(ctx->ids, cur, bkt)
> + expr__add_id_val(ctx, strdup(cur->key), i++);
>
> - hashmap__for_each_entry((&ctx.ids), cur, bkt) {
> + hashmap__for_each_entry(ctx->ids, cur, bkt) {
> if (check_parse_fake(cur->key)) {
> pr_err("check_parse_fake failed\n");
> goto out;
> }
> }
>
> - if (expr__parse(&result, &ctx, str, 0))
> + if (expr__parse(&result, ctx, str, 0))
> pr_err("expr__parse failed\n");
> else
> ret = 0;
>
> out:
> - expr__ctx_clear(&ctx);
> + expr__ctx_free(ctx);
> return ret;
> }
>
> diff --git a/tools/perf/util/expr.c b/tools/perf/util/expr.c
> index a850fd0be3ee..7b1c06772a49 100644
> --- a/tools/perf/util/expr.c
> +++ b/tools/perf/util/expr.c
> @@ -73,7 +73,7 @@ int expr__add_id(struct expr_parse_ctx *ctx, const char *id)
> data_ptr->parent = ctx->parent;
> data_ptr->kind = EXPR_ID_DATA__PARENT;
>
> - ret = hashmap__set(&ctx->ids, id, data_ptr,
> + ret = hashmap__set(ctx->ids, id, data_ptr,
> (const void **)&old_key, (void **)&old_data);
> if (ret)
> free(data_ptr);
> @@ -95,7 +95,7 @@ int expr__add_id_val(struct expr_parse_ctx *ctx, const char *id, double val)
> data_ptr->val = val;
> data_ptr->kind = EXPR_ID_DATA__VALUE;
>
> - ret = hashmap__set(&ctx->ids, id, data_ptr,
> + ret = hashmap__set(ctx->ids, id, data_ptr,
> (const void **)&old_key, (void **)&old_data);
> if (ret)
> free(data_ptr);
> @@ -140,7 +140,7 @@ int expr__add_ref(struct expr_parse_ctx *ctx, struct metric_ref *ref)
> data_ptr->ref.metric_expr = ref->metric_expr;
> data_ptr->kind = EXPR_ID_DATA__REF;
>
> - ret = hashmap__set(&ctx->ids, name, data_ptr,
> + ret = hashmap__set(ctx->ids, name, data_ptr,
> (const void **)&old_key, (void **)&old_data);
> if (ret)
> free(data_ptr);
> @@ -156,7 +156,7 @@ int expr__add_ref(struct expr_parse_ctx *ctx, struct metric_ref *ref)
> int expr__get_id(struct expr_parse_ctx *ctx, const char *id,
> struct expr_id_data **data)
> {
> - return hashmap__find(&ctx->ids, id, (void **)data) ? 0 : -1;
> + return hashmap__find(ctx->ids, id, (void **)data) ? 0 : -1;
> }
>
> int expr__resolve_id(struct expr_parse_ctx *ctx, const char *id,
> @@ -205,15 +205,23 @@ void expr__del_id(struct expr_parse_ctx *ctx, const char *id)
> struct expr_id_data *old_val = NULL;
> char *old_key = NULL;
>
> - hashmap__delete(&ctx->ids, id,
> + hashmap__delete(ctx->ids, id,
> (const void **)&old_key, (void **)&old_val);
> free(old_key);
> free(old_val);
> }
>
> -void expr__ctx_init(struct expr_parse_ctx *ctx)
> +struct expr_parse_ctx *expr__ctx_new(void)
> {
> - hashmap__init(&ctx->ids, key_hash, key_equal, NULL);
> + struct expr_parse_ctx *ctx;
> +
> + ctx = malloc(sizeof(struct expr_parse_ctx));
> + if (!ctx)
> + return NULL;
> +
> + ctx->ids = hashmap__new(key_hash, key_equal, NULL);
> + ctx->parent = NULL;
> + return ctx;
> }
>
> void expr__ctx_clear(struct expr_parse_ctx *ctx)
> @@ -221,11 +229,24 @@ void expr__ctx_clear(struct expr_parse_ctx *ctx)
> struct hashmap_entry *cur;
> size_t bkt;
>
> - hashmap__for_each_entry((&ctx->ids), cur, bkt) {
> + hashmap__for_each_entry(ctx->ids, cur, bkt) {
> + free((char *)cur->key);
> + free(cur->value);
> + }
> + hashmap__clear(ctx->ids);
> +}
> +
> +void expr__ctx_free(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);
> + hashmap__free(ctx->ids);
> + free(ctx);
> }
>
> static int
> diff --git a/tools/perf/util/expr.h b/tools/perf/util/expr.h
> index 85df3e4771e4..5fa394f10418 100644
> --- a/tools/perf/util/expr.h
> +++ b/tools/perf/util/expr.h
> @@ -19,7 +19,7 @@ struct expr_id {
> };
>
> struct expr_parse_ctx {
> - struct hashmap ids;
> + struct hashmap *ids;
> struct expr_id *parent;
> };
>
> @@ -30,8 +30,9 @@ struct expr_scanner_ctx {
> int runtime;
> };
>
> -void expr__ctx_init(struct expr_parse_ctx *ctx);
> +struct expr_parse_ctx *expr__ctx_new(void);
> void expr__ctx_clear(struct expr_parse_ctx *ctx);
> +void expr__ctx_free(struct expr_parse_ctx *ctx);
> void expr__del_id(struct expr_parse_ctx *ctx, const char *id);
> int expr__add_id(struct expr_parse_ctx *ctx, const char *id);
> int expr__add_id_val(struct expr_parse_ctx *ctx, const char *id, double val);
> diff --git a/tools/perf/util/metricgroup.c b/tools/perf/util/metricgroup.c
> index 29b747ac31c1..b7924a2f1f45 100644
> --- a/tools/perf/util/metricgroup.c
> +++ b/tools/perf/util/metricgroup.c
> @@ -118,7 +118,7 @@ struct metric_ref_node {
>
> struct metric {
> struct list_head nd;
> - struct expr_parse_ctx pctx;
> + struct expr_parse_ctx *pctx;
> const char *metric_name;
> const char *metric_expr;
> const char *metric_unit;
> @@ -198,7 +198,7 @@ static struct evsel *find_evsel_group(struct evlist *perf_evlist,
> struct evsel *ev, *current_leader = NULL;
> struct expr_id_data *val_ptr;
> int i = 0, matched_events = 0, events_to_match;
> - const int idnum = (int)hashmap__size(&pctx->ids);
> + const int idnum = (int)hashmap__size(pctx->ids);
>
> /*
> * duration_time is always grouped separately, when events are grouped
> @@ -206,7 +206,7 @@ static struct evsel *find_evsel_group(struct evlist *perf_evlist,
> * add it to metric_events at the end.
> */
> if (!has_constraint &&
> - hashmap__find(&pctx->ids, "duration_time", (void **)&val_ptr))
> + hashmap__find(pctx->ids, "duration_time", (void **)&val_ptr))
> events_to_match = idnum - 1;
> else
> events_to_match = idnum;
> @@ -242,7 +242,7 @@ static struct evsel *find_evsel_group(struct evlist *perf_evlist,
> if (contains_event(metric_events, matched_events, ev->name))
> continue;
> /* Does this event belong to the parse context? */
> - if (hashmap__find(&pctx->ids, ev->name, (void **)&val_ptr))
> + if (hashmap__find(pctx->ids, ev->name, (void **)&val_ptr))
> metric_events[matched_events++] = ev;
>
> if (matched_events == events_to_match)
> @@ -322,12 +322,12 @@ static int metricgroup__setup_events(struct list_head *groups,
> struct metric_ref *metric_refs = NULL;
>
> metric_events = calloc(sizeof(void *),
> - hashmap__size(&m->pctx.ids) + 1);
> + hashmap__size(m->pctx->ids) + 1);
> if (!metric_events) {
> ret = -ENOMEM;
> break;
> }
> - evsel = find_evsel_group(perf_evlist, &m->pctx,
> + evsel = find_evsel_group(perf_evlist, m->pctx,
> metric_no_merge,
> m->has_constraint, metric_events,
> evlist_used);
> @@ -693,7 +693,7 @@ static void metricgroup__add_metric_weak_group(struct strbuf *events,
> size_t bkt;
> bool no_group = true, has_duration = false;
>
> - hashmap__for_each_entry((&ctx->ids), cur, bkt) {
> + hashmap__for_each_entry(ctx->ids, cur, bkt) {
> pr_debug("found event %s\n", (const char *)cur->key);
> /*
> * Duration time maps to a software event and can make
> @@ -724,7 +724,7 @@ static void metricgroup__add_metric_non_group(struct strbuf *events,
> size_t bkt;
> bool first = true;
>
> - hashmap__for_each_entry((&ctx->ids), cur, bkt) {
> + hashmap__for_each_entry(ctx->ids, cur, bkt) {
> if (!first)
> strbuf_addf(events, ",");
> strbuf_addf(events, "%s", (const char *)cur->key);
> @@ -799,7 +799,11 @@ static int __add_metric(struct list_head *metric_list,
> if (!m)
> return -ENOMEM;
>
> - expr__ctx_init(&m->pctx);
> + m->pctx = expr__ctx_new();
> + if (!m->pctx) {
> + free(m);
> + return -ENOMEM;
> + }
> m->metric_name = pe->metric_name;
> m->metric_expr = pe->metric_expr;
> m->metric_unit = pe->unit;
> @@ -847,15 +851,15 @@ static int __add_metric(struct list_head *metric_list,
>
> /* Force all found IDs in metric to have us as parent ID. */
> WARN_ON_ONCE(!parent);
> - m->pctx.parent = parent;
> + m->pctx->parent = parent;
>
> /*
> * For both the parent and referenced metrics, we parse
> * all the metric's IDs and add it to the parent context.
> */
> - if (expr__find_other(pe->metric_expr, NULL, &m->pctx, runtime) < 0) {
> + if (expr__find_other(pe->metric_expr, NULL, m->pctx, runtime) < 0) {
> if (m->metric_refs_cnt == 0) {
> - expr__ctx_clear(&m->pctx);
> + expr__ctx_free(m->pctx);
> free(m);
> *mp = NULL;
> }
> @@ -878,8 +882,8 @@ static int __add_metric(struct list_head *metric_list,
> list_for_each_prev(pos, metric_list) {
> struct metric *old = list_entry(pos, struct metric, nd);
>
> - if (hashmap__size(&m->pctx.ids) <=
> - hashmap__size(&old->pctx.ids))
> + if (hashmap__size(m->pctx->ids) <=
> + hashmap__size(old->pctx->ids))
> break;
> }
> list_add(&m->nd, pos);
> @@ -927,7 +931,7 @@ static int recursion_check(struct metric *m, const char *id, struct expr_id **pa
> * if we already processed 'id', if we did, it's recursion
> * and we fail.
> */
> - ret = expr__get_id(&m->pctx, id, &data);
> + ret = expr__get_id(m->pctx, id, &data);
> if (ret)
> return ret;
>
> @@ -982,7 +986,7 @@ static int __resolve_metric(struct metric *m,
> */
> do {
> all = true;
> - hashmap__for_each_entry((&m->pctx.ids), cur, bkt) {
> + hashmap__for_each_entry(m->pctx->ids, cur, bkt) {
> struct expr_id *parent;
> struct pmu_event *pe;
>
> @@ -996,7 +1000,7 @@ static int __resolve_metric(struct metric *m,
>
> all = false;
> /* The metric key itself needs to go out.. */
> - expr__del_id(&m->pctx, cur->key);
> + expr__del_id(m->pctx, cur->key);
>
> /* ... and it gets resolved to the parent context. */
> ret = add_metric(metric_list, pe, metric_no_group, &m, parent, ids);
> @@ -1144,10 +1148,10 @@ static int metricgroup__add_metric(const char *metric, bool metric_no_group,
>
> if (m->has_constraint) {
> metricgroup__add_metric_non_group(events,
> - &m->pctx);
> + m->pctx);
> } else {
> metricgroup__add_metric_weak_group(events,
> - &m->pctx);
> + m->pctx);
> }
> }
>
> @@ -1210,7 +1214,7 @@ static void metricgroup__free_metrics(struct list_head *metric_list)
>
> list_for_each_entry_safe (m, tmp, metric_list, nd) {
> metric__free_refs(m);
> - expr__ctx_clear(&m->pctx);
> + expr__ctx_free(m->pctx);
> list_del_init(&m->nd);
> free(m);
> }
> diff --git a/tools/perf/util/stat-shadow.c b/tools/perf/util/stat-shadow.c
> index 34a7f5c1fff7..c9fa07e49e72 100644
> --- a/tools/perf/util/stat-shadow.c
> +++ b/tools/perf/util/stat-shadow.c
> @@ -1,8 +1,10 @@
> // SPDX-License-Identifier: GPL-2.0
> +#include <math.h>
> #include <stdio.h>
> #include "evsel.h"
> #include "stat.h"
> #include "color.h"
> +#include "debug.h"
> #include "pmu.h"
> #include "rblist.h"
> #include "evlist.h"
> @@ -370,12 +372,16 @@ void perf_stat__collect_metric_expr(struct evlist *evsel_list)
> {
> struct evsel *counter, *leader, **metric_events, *oc;
> bool found;
> - struct expr_parse_ctx ctx;
> + struct expr_parse_ctx *ctx;
> struct hashmap_entry *cur;
> size_t bkt;
> int i;
>
> - expr__ctx_init(&ctx);
> + ctx = expr__ctx_new();
> + if (!ctx) {
> + pr_debug("expr__ctx_new failed");
> + return;
> + }
> evlist__for_each_entry(evsel_list, counter) {
> bool invalid = false;
>
> @@ -383,25 +389,25 @@ void perf_stat__collect_metric_expr(struct evlist *evsel_list)
> if (!counter->metric_expr)
> continue;
>
> - expr__ctx_clear(&ctx);
> + expr__ctx_clear(ctx);
> metric_events = counter->metric_events;
> if (!metric_events) {
> if (expr__find_other(counter->metric_expr,
> counter->name,
> - &ctx, 1) < 0)
> + ctx, 1) < 0)
> continue;
>
> metric_events = calloc(sizeof(struct evsel *),
> - hashmap__size(&ctx.ids) + 1);
> + hashmap__size(ctx->ids) + 1);
> if (!metric_events) {
> - expr__ctx_clear(&ctx);
> + expr__ctx_free(ctx);
> return;
> }
> counter->metric_events = metric_events;
> }
>
> i = 0;
> - hashmap__for_each_entry((&ctx.ids), cur, bkt) {
> + hashmap__for_each_entry(ctx->ids, cur, bkt) {
> const char *metric_name = (const char *)cur->key;
>
> found = false;
> @@ -453,7 +459,7 @@ void perf_stat__collect_metric_expr(struct evlist *evsel_list)
> counter->metric_expr = NULL;
> }
> }
> - expr__ctx_clear(&ctx);
> + expr__ctx_free(ctx);
> }
>
> static double runtime_stat_avg(struct runtime_stat *st,
> @@ -818,7 +824,6 @@ static int prepare_metric(struct evsel **metric_events,
> char *n, *pn;
> int i, j, ret;
>
> - expr__ctx_init(pctx);
> for (i = 0; metric_events[i]; i++) {
> struct saved_value *v;
> struct stats *stats;
> @@ -880,17 +885,22 @@ static void generic_metric(struct perf_stat_config *config,
> struct runtime_stat *st)
> {
> print_metric_t print_metric = out->print_metric;
> - struct expr_parse_ctx pctx;
> + struct expr_parse_ctx *pctx;
> double ratio, scale;
> int i;
> void *ctxp = out->ctx;
>
> - i = prepare_metric(metric_events, metric_refs, &pctx, cpu, st);
> - if (i < 0)
> + pctx = expr__ctx_new();
> + if (!pctx)
> return;
>
> + i = prepare_metric(metric_events, metric_refs, pctx, cpu, st);
> + if (i < 0) {
> + expr__ctx_free(pctx);
> + return;
> + }
> if (!metric_events[i]) {
> - if (expr__parse(&ratio, &pctx, metric_expr, runtime) == 0) {
> + if (expr__parse(&ratio, pctx, metric_expr, runtime) == 0) {
> char *unit;
> char metric_bf[64];
>
> @@ -926,22 +936,26 @@ static void generic_metric(struct perf_stat_config *config,
> (metric_name ? metric_name : name) : "", 0);
> }
>
> - expr__ctx_clear(&pctx);
> + expr__ctx_free(pctx);
> }
>
> double test_generic_metric(struct metric_expr *mexp, int cpu, struct runtime_stat *st)
> {
> - struct expr_parse_ctx pctx;
> + struct expr_parse_ctx *pctx;
> double ratio = 0.0;
>
> - if (prepare_metric(mexp->metric_events, mexp->metric_refs, &pctx, cpu, st) < 0)
> + pctx = expr__ctx_new();
> + if (!pctx)
> + return NAN;
> +
> + if (prepare_metric(mexp->metric_events, mexp->metric_refs, pctx, cpu, st) < 0)
> goto out;
>
> - if (expr__parse(&ratio, &pctx, mexp->metric_expr, 1))
> + if (expr__parse(&ratio, pctx, mexp->metric_expr, 1))
> ratio = 0.0;
>
> out:
> - expr__ctx_clear(&pctx);
> + expr__ctx_free(pctx);
> return ratio;
> }
>
> --
> 2.33.0.464.g1972c5931b-goog
>

2021-09-23 00:23:35

by Ian Rogers

[permalink] [raw]
Subject: Re: [PATCH v8 1/8] perf metric: Restructure struct expr_parse_ctx.

On Wed, Sep 22, 2021 at 1:59 PM Jiri Olsa <[email protected]> wrote:
>
> On Fri, Sep 17, 2021 at 11:35:06PM -0700, Ian Rogers wrote:
> > A later change to parsing the ids out (in expr__find_other) will
> > potentially drop hashmaps and so it is more convenient to move
> > expr_parse_ctx to have a hashmap pointer rather than a struct value. As
> > this pointer must be freed, rather than just going out of scope,
> > add expr__ctx_new and expr__ctx_free to manage expr_parse_ctx memory.
> > Adjust use of struct expr_parse_ctx accordingly.
> >
> > Signed-off-by: Ian Rogers <[email protected]>
> > ---
> > tools/perf/tests/expr.c | 81 ++++++++++++++++++-----------------
> > tools/perf/tests/pmu-events.c | 43 +++++++++++--------
> > tools/perf/util/expr.c | 39 +++++++++++++----
> > tools/perf/util/expr.h | 5 ++-
> > tools/perf/util/metricgroup.c | 44 ++++++++++---------
> > tools/perf/util/stat-shadow.c | 50 +++++++++++++--------
> > 6 files changed, 155 insertions(+), 107 deletions(-)
> >
> > diff --git a/tools/perf/tests/expr.c b/tools/perf/tests/expr.c
> > index 4d01051951cd..b0a3b5fd0c00 100644
> > --- a/tools/perf/tests/expr.c
> > +++ b/tools/perf/tests/expr.c
> > @@ -22,67 +22,70 @@ int test__expr(struct test *t __maybe_unused, int subtest __maybe_unused)
> > const char *p;
> > double val;
> > int ret;
> > - struct expr_parse_ctx ctx;
> > + struct expr_parse_ctx *ctx;
> >
> > - expr__ctx_init(&ctx);
> > - expr__add_id_val(&ctx, strdup("FOO"), 1);
> > - expr__add_id_val(&ctx, strdup("BAR"), 2);
> > + ctx = expr__ctx_new();
>
> missing ctx check

It is covered in the assert below.

> > + TEST_ASSERT_VAL("expr__ctx_new", ctx);
> > + expr__add_id_val(ctx, strdup("FOO"), 1);
> > + expr__add_id_val(ctx, strdup("BAR"), 2);
> >
> > - ret = test(&ctx, "1+1", 2);
> > - ret |= test(&ctx, "FOO+BAR", 3);
> > - ret |= test(&ctx, "(BAR/2)%2", 1);
> > - ret |= test(&ctx, "1 - -4", 5);
> > - ret |= test(&ctx, "(FOO-1)*2 + (BAR/2)%2 - -4", 5);
> > - ret |= test(&ctx, "1-1 | 1", 1);
> > - ret |= test(&ctx, "1-1 & 1", 0);
> > - ret |= test(&ctx, "min(1,2) + 1", 2);
> > - ret |= test(&ctx, "max(1,2) + 1", 3);
> > - ret |= test(&ctx, "1+1 if 3*4 else 0", 2);
> > - ret |= test(&ctx, "1.1 + 2.1", 3.2);
> > - ret |= test(&ctx, ".1 + 2.", 2.1);
> > - ret |= test(&ctx, "d_ratio(1, 2)", 0.5);
> > - ret |= test(&ctx, "d_ratio(2.5, 0)", 0);
> > - ret |= test(&ctx, "1.1 < 2.2", 1);
> > - ret |= test(&ctx, "2.2 > 1.1", 1);
> > - ret |= test(&ctx, "1.1 < 1.1", 0);
> > - ret |= test(&ctx, "2.2 > 2.2", 0);
> > - ret |= test(&ctx, "2.2 < 1.1", 0);
> > - ret |= test(&ctx, "1.1 > 2.2", 0);
> > + ret = test(ctx, "1+1", 2);
> > + ret |= test(ctx, "FOO+BAR", 3);
> > + ret |= test(ctx, "(BAR/2)%2", 1);
> > + ret |= test(ctx, "1 - -4", 5);
> > + ret |= test(ctx, "(FOO-1)*2 + (BAR/2)%2 - -4", 5);
> > + ret |= test(ctx, "1-1 | 1", 1);
> > + ret |= test(ctx, "1-1 & 1", 0);
> > + ret |= test(ctx, "min(1,2) + 1", 2);
> > + ret |= test(ctx, "max(1,2) + 1", 3);
> > + ret |= test(ctx, "1+1 if 3*4 else 0", 2);
> > + ret |= test(ctx, "1.1 + 2.1", 3.2);
> > + ret |= test(ctx, ".1 + 2.", 2.1);
> > + ret |= test(ctx, "d_ratio(1, 2)", 0.5);
> > + ret |= test(ctx, "d_ratio(2.5, 0)", 0);
> > + ret |= test(ctx, "1.1 < 2.2", 1);
> > + ret |= test(ctx, "2.2 > 1.1", 1);
> > + ret |= test(ctx, "1.1 < 1.1", 0);
> > + ret |= test(ctx, "2.2 > 2.2", 0);
> > + ret |= test(ctx, "2.2 < 1.1", 0);
> > + ret |= test(ctx, "1.1 > 2.2", 0);
>
>
> SNIP
>
>
> > ret++;
> > @@ -876,27 +881,27 @@ static int test_parsing(void)
> > * make them unique.
> > */
> > k = 1;
> > - hashmap__for_each_entry((&ctx.ids), cur, bkt)
> > - expr__add_id_val(&ctx, strdup(cur->key), k++);
> > + hashmap__for_each_entry(ctx->ids, cur, bkt)
> > + expr__add_id_val(ctx, strdup(cur->key), k++);
> >
> > - hashmap__for_each_entry((&ctx.ids), cur, bkt) {
> > + hashmap__for_each_entry(ctx->ids, cur, bkt) {
> > if (check_parse_cpu(cur->key, map == cpus_map,
> > pe))
> > ret++;
> > }
> >
> > list_for_each_entry_safe(metric, tmp, &compound_list, list) {
> > - expr__add_ref(&ctx, &metric->metric_ref);
> > + expr__add_ref(ctx, &metric->metric_ref);
> > free(metric);
> > }
> >
> > - if (expr__parse(&result, &ctx, pe->metric_expr, 0)) {
> > + if (expr__parse(&result, ctx, pe->metric_expr, 0)) {
> > expr_failure("Parse failed", map, pe);
> > ret++;
> > }
> > - expr__ctx_clear(&ctx);
> > }
> > }
> > + expr__ctx_free(ctx);
> > /* TODO: fail when not ok */
> > exit:
> > return ret == 0 ? TEST_OK : TEST_SKIP;
> > @@ -916,7 +921,7 @@ static struct test_metric metrics[] = {
> >
> > static int metric_parse_fake(const char *str)
> > {
> > - struct expr_parse_ctx ctx;
> > + struct expr_parse_ctx *ctx;
> > struct hashmap_entry *cur;
> > double result;
> > int ret = -1;
> > @@ -925,8 +930,8 @@ static int metric_parse_fake(const char *str)
> >
> > pr_debug("parsing '%s'\n", str);
> >
> > - expr__ctx_init(&ctx);
> > - if (expr__find_other(str, NULL, &ctx, 0) < 0) {
> > + ctx = expr__ctx_new();
>
> missing ctx check

Done.

Thanks,
Ian

> jirka
>
> > + if (expr__find_other(str, NULL, ctx, 0) < 0) {
> > pr_err("expr__find_other failed\n");
> > return -1;
> > }
> > @@ -937,23 +942,23 @@ static int metric_parse_fake(const char *str)
> > * make them unique.
> > */
> > i = 1;
> > - hashmap__for_each_entry((&ctx.ids), cur, bkt)
> > - expr__add_id_val(&ctx, strdup(cur->key), i++);
> > + hashmap__for_each_entry(ctx->ids, cur, bkt)
> > + expr__add_id_val(ctx, strdup(cur->key), i++);
> >
> > - hashmap__for_each_entry((&ctx.ids), cur, bkt) {
> > + hashmap__for_each_entry(ctx->ids, cur, bkt) {
> > if (check_parse_fake(cur->key)) {
> > pr_err("check_parse_fake failed\n");
> > goto out;
> > }
> > }
> >
> > - if (expr__parse(&result, &ctx, str, 0))
> > + if (expr__parse(&result, ctx, str, 0))
> > pr_err("expr__parse failed\n");
> > else
> > ret = 0;
> >
> > out:
> > - expr__ctx_clear(&ctx);
> > + expr__ctx_free(ctx);
> > return ret;
> > }
> >
> > diff --git a/tools/perf/util/expr.c b/tools/perf/util/expr.c
> > index a850fd0be3ee..7b1c06772a49 100644
> > --- a/tools/perf/util/expr.c
> > +++ b/tools/perf/util/expr.c
> > @@ -73,7 +73,7 @@ int expr__add_id(struct expr_parse_ctx *ctx, const char *id)
> > data_ptr->parent = ctx->parent;
> > data_ptr->kind = EXPR_ID_DATA__PARENT;
> >
> > - ret = hashmap__set(&ctx->ids, id, data_ptr,
> > + ret = hashmap__set(ctx->ids, id, data_ptr,
> > (const void **)&old_key, (void **)&old_data);
> > if (ret)
> > free(data_ptr);
> > @@ -95,7 +95,7 @@ int expr__add_id_val(struct expr_parse_ctx *ctx, const char *id, double val)
> > data_ptr->val = val;
> > data_ptr->kind = EXPR_ID_DATA__VALUE;
> >
> > - ret = hashmap__set(&ctx->ids, id, data_ptr,
> > + ret = hashmap__set(ctx->ids, id, data_ptr,
> > (const void **)&old_key, (void **)&old_data);
> > if (ret)
> > free(data_ptr);
> > @@ -140,7 +140,7 @@ int expr__add_ref(struct expr_parse_ctx *ctx, struct metric_ref *ref)
> > data_ptr->ref.metric_expr = ref->metric_expr;
> > data_ptr->kind = EXPR_ID_DATA__REF;
> >
> > - ret = hashmap__set(&ctx->ids, name, data_ptr,
> > + ret = hashmap__set(ctx->ids, name, data_ptr,
> > (const void **)&old_key, (void **)&old_data);
> > if (ret)
> > free(data_ptr);
> > @@ -156,7 +156,7 @@ int expr__add_ref(struct expr_parse_ctx *ctx, struct metric_ref *ref)
> > int expr__get_id(struct expr_parse_ctx *ctx, const char *id,
> > struct expr_id_data **data)
> > {
> > - return hashmap__find(&ctx->ids, id, (void **)data) ? 0 : -1;
> > + return hashmap__find(ctx->ids, id, (void **)data) ? 0 : -1;
> > }
> >
> > int expr__resolve_id(struct expr_parse_ctx *ctx, const char *id,
> > @@ -205,15 +205,23 @@ void expr__del_id(struct expr_parse_ctx *ctx, const char *id)
> > struct expr_id_data *old_val = NULL;
> > char *old_key = NULL;
> >
> > - hashmap__delete(&ctx->ids, id,
> > + hashmap__delete(ctx->ids, id,
> > (const void **)&old_key, (void **)&old_val);
> > free(old_key);
> > free(old_val);
> > }
> >
> > -void expr__ctx_init(struct expr_parse_ctx *ctx)
> > +struct expr_parse_ctx *expr__ctx_new(void)
> > {
> > - hashmap__init(&ctx->ids, key_hash, key_equal, NULL);
> > + struct expr_parse_ctx *ctx;
> > +
> > + ctx = malloc(sizeof(struct expr_parse_ctx));
> > + if (!ctx)
> > + return NULL;
> > +
> > + ctx->ids = hashmap__new(key_hash, key_equal, NULL);
> > + ctx->parent = NULL;
> > + return ctx;
> > }
> >
> > void expr__ctx_clear(struct expr_parse_ctx *ctx)
> > @@ -221,11 +229,24 @@ void expr__ctx_clear(struct expr_parse_ctx *ctx)
> > struct hashmap_entry *cur;
> > size_t bkt;
> >
> > - hashmap__for_each_entry((&ctx->ids), cur, bkt) {
> > + hashmap__for_each_entry(ctx->ids, cur, bkt) {
> > + free((char *)cur->key);
> > + free(cur->value);
> > + }
> > + hashmap__clear(ctx->ids);
> > +}
> > +
> > +void expr__ctx_free(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);
> > + hashmap__free(ctx->ids);
> > + free(ctx);
> > }
> >
> > static int
> > diff --git a/tools/perf/util/expr.h b/tools/perf/util/expr.h
> > index 85df3e4771e4..5fa394f10418 100644
> > --- a/tools/perf/util/expr.h
> > +++ b/tools/perf/util/expr.h
> > @@ -19,7 +19,7 @@ struct expr_id {
> > };
> >
> > struct expr_parse_ctx {
> > - struct hashmap ids;
> > + struct hashmap *ids;
> > struct expr_id *parent;
> > };
> >
> > @@ -30,8 +30,9 @@ struct expr_scanner_ctx {
> > int runtime;
> > };
> >
> > -void expr__ctx_init(struct expr_parse_ctx *ctx);
> > +struct expr_parse_ctx *expr__ctx_new(void);
> > void expr__ctx_clear(struct expr_parse_ctx *ctx);
> > +void expr__ctx_free(struct expr_parse_ctx *ctx);
> > void expr__del_id(struct expr_parse_ctx *ctx, const char *id);
> > int expr__add_id(struct expr_parse_ctx *ctx, const char *id);
> > int expr__add_id_val(struct expr_parse_ctx *ctx, const char *id, double val);
> > diff --git a/tools/perf/util/metricgroup.c b/tools/perf/util/metricgroup.c
> > index 29b747ac31c1..b7924a2f1f45 100644
> > --- a/tools/perf/util/metricgroup.c
> > +++ b/tools/perf/util/metricgroup.c
> > @@ -118,7 +118,7 @@ struct metric_ref_node {
> >
> > struct metric {
> > struct list_head nd;
> > - struct expr_parse_ctx pctx;
> > + struct expr_parse_ctx *pctx;
> > const char *metric_name;
> > const char *metric_expr;
> > const char *metric_unit;
> > @@ -198,7 +198,7 @@ static struct evsel *find_evsel_group(struct evlist *perf_evlist,
> > struct evsel *ev, *current_leader = NULL;
> > struct expr_id_data *val_ptr;
> > int i = 0, matched_events = 0, events_to_match;
> > - const int idnum = (int)hashmap__size(&pctx->ids);
> > + const int idnum = (int)hashmap__size(pctx->ids);
> >
> > /*
> > * duration_time is always grouped separately, when events are grouped
> > @@ -206,7 +206,7 @@ static struct evsel *find_evsel_group(struct evlist *perf_evlist,
> > * add it to metric_events at the end.
> > */
> > if (!has_constraint &&
> > - hashmap__find(&pctx->ids, "duration_time", (void **)&val_ptr))
> > + hashmap__find(pctx->ids, "duration_time", (void **)&val_ptr))
> > events_to_match = idnum - 1;
> > else
> > events_to_match = idnum;
> > @@ -242,7 +242,7 @@ static struct evsel *find_evsel_group(struct evlist *perf_evlist,
> > if (contains_event(metric_events, matched_events, ev->name))
> > continue;
> > /* Does this event belong to the parse context? */
> > - if (hashmap__find(&pctx->ids, ev->name, (void **)&val_ptr))
> > + if (hashmap__find(pctx->ids, ev->name, (void **)&val_ptr))
> > metric_events[matched_events++] = ev;
> >
> > if (matched_events == events_to_match)
> > @@ -322,12 +322,12 @@ static int metricgroup__setup_events(struct list_head *groups,
> > struct metric_ref *metric_refs = NULL;
> >
> > metric_events = calloc(sizeof(void *),
> > - hashmap__size(&m->pctx.ids) + 1);
> > + hashmap__size(m->pctx->ids) + 1);
> > if (!metric_events) {
> > ret = -ENOMEM;
> > break;
> > }
> > - evsel = find_evsel_group(perf_evlist, &m->pctx,
> > + evsel = find_evsel_group(perf_evlist, m->pctx,
> > metric_no_merge,
> > m->has_constraint, metric_events,
> > evlist_used);
> > @@ -693,7 +693,7 @@ static void metricgroup__add_metric_weak_group(struct strbuf *events,
> > size_t bkt;
> > bool no_group = true, has_duration = false;
> >
> > - hashmap__for_each_entry((&ctx->ids), cur, bkt) {
> > + hashmap__for_each_entry(ctx->ids, cur, bkt) {
> > pr_debug("found event %s\n", (const char *)cur->key);
> > /*
> > * Duration time maps to a software event and can make
> > @@ -724,7 +724,7 @@ static void metricgroup__add_metric_non_group(struct strbuf *events,
> > size_t bkt;
> > bool first = true;
> >
> > - hashmap__for_each_entry((&ctx->ids), cur, bkt) {
> > + hashmap__for_each_entry(ctx->ids, cur, bkt) {
> > if (!first)
> > strbuf_addf(events, ",");
> > strbuf_addf(events, "%s", (const char *)cur->key);
> > @@ -799,7 +799,11 @@ static int __add_metric(struct list_head *metric_list,
> > if (!m)
> > return -ENOMEM;
> >
> > - expr__ctx_init(&m->pctx);
> > + m->pctx = expr__ctx_new();
> > + if (!m->pctx) {
> > + free(m);
> > + return -ENOMEM;
> > + }
> > m->metric_name = pe->metric_name;
> > m->metric_expr = pe->metric_expr;
> > m->metric_unit = pe->unit;
> > @@ -847,15 +851,15 @@ static int __add_metric(struct list_head *metric_list,
> >
> > /* Force all found IDs in metric to have us as parent ID. */
> > WARN_ON_ONCE(!parent);
> > - m->pctx.parent = parent;
> > + m->pctx->parent = parent;
> >
> > /*
> > * For both the parent and referenced metrics, we parse
> > * all the metric's IDs and add it to the parent context.
> > */
> > - if (expr__find_other(pe->metric_expr, NULL, &m->pctx, runtime) < 0) {
> > + if (expr__find_other(pe->metric_expr, NULL, m->pctx, runtime) < 0) {
> > if (m->metric_refs_cnt == 0) {
> > - expr__ctx_clear(&m->pctx);
> > + expr__ctx_free(m->pctx);
> > free(m);
> > *mp = NULL;
> > }
> > @@ -878,8 +882,8 @@ static int __add_metric(struct list_head *metric_list,
> > list_for_each_prev(pos, metric_list) {
> > struct metric *old = list_entry(pos, struct metric, nd);
> >
> > - if (hashmap__size(&m->pctx.ids) <=
> > - hashmap__size(&old->pctx.ids))
> > + if (hashmap__size(m->pctx->ids) <=
> > + hashmap__size(old->pctx->ids))
> > break;
> > }
> > list_add(&m->nd, pos);
> > @@ -927,7 +931,7 @@ static int recursion_check(struct metric *m, const char *id, struct expr_id **pa
> > * if we already processed 'id', if we did, it's recursion
> > * and we fail.
> > */
> > - ret = expr__get_id(&m->pctx, id, &data);
> > + ret = expr__get_id(m->pctx, id, &data);
> > if (ret)
> > return ret;
> >
> > @@ -982,7 +986,7 @@ static int __resolve_metric(struct metric *m,
> > */
> > do {
> > all = true;
> > - hashmap__for_each_entry((&m->pctx.ids), cur, bkt) {
> > + hashmap__for_each_entry(m->pctx->ids, cur, bkt) {
> > struct expr_id *parent;
> > struct pmu_event *pe;
> >
> > @@ -996,7 +1000,7 @@ static int __resolve_metric(struct metric *m,
> >
> > all = false;
> > /* The metric key itself needs to go out.. */
> > - expr__del_id(&m->pctx, cur->key);
> > + expr__del_id(m->pctx, cur->key);
> >
> > /* ... and it gets resolved to the parent context. */
> > ret = add_metric(metric_list, pe, metric_no_group, &m, parent, ids);
> > @@ -1144,10 +1148,10 @@ static int metricgroup__add_metric(const char *metric, bool metric_no_group,
> >
> > if (m->has_constraint) {
> > metricgroup__add_metric_non_group(events,
> > - &m->pctx);
> > + m->pctx);
> > } else {
> > metricgroup__add_metric_weak_group(events,
> > - &m->pctx);
> > + m->pctx);
> > }
> > }
> >
> > @@ -1210,7 +1214,7 @@ static void metricgroup__free_metrics(struct list_head *metric_list)
> >
> > list_for_each_entry_safe (m, tmp, metric_list, nd) {
> > metric__free_refs(m);
> > - expr__ctx_clear(&m->pctx);
> > + expr__ctx_free(m->pctx);
> > list_del_init(&m->nd);
> > free(m);
> > }
> > diff --git a/tools/perf/util/stat-shadow.c b/tools/perf/util/stat-shadow.c
> > index 34a7f5c1fff7..c9fa07e49e72 100644
> > --- a/tools/perf/util/stat-shadow.c
> > +++ b/tools/perf/util/stat-shadow.c
> > @@ -1,8 +1,10 @@
> > // SPDX-License-Identifier: GPL-2.0
> > +#include <math.h>
> > #include <stdio.h>
> > #include "evsel.h"
> > #include "stat.h"
> > #include "color.h"
> > +#include "debug.h"
> > #include "pmu.h"
> > #include "rblist.h"
> > #include "evlist.h"
> > @@ -370,12 +372,16 @@ void perf_stat__collect_metric_expr(struct evlist *evsel_list)
> > {
> > struct evsel *counter, *leader, **metric_events, *oc;
> > bool found;
> > - struct expr_parse_ctx ctx;
> > + struct expr_parse_ctx *ctx;
> > struct hashmap_entry *cur;
> > size_t bkt;
> > int i;
> >
> > - expr__ctx_init(&ctx);
> > + ctx = expr__ctx_new();
> > + if (!ctx) {
> > + pr_debug("expr__ctx_new failed");
> > + return;
> > + }
> > evlist__for_each_entry(evsel_list, counter) {
> > bool invalid = false;
> >
> > @@ -383,25 +389,25 @@ void perf_stat__collect_metric_expr(struct evlist *evsel_list)
> > if (!counter->metric_expr)
> > continue;
> >
> > - expr__ctx_clear(&ctx);
> > + expr__ctx_clear(ctx);
> > metric_events = counter->metric_events;
> > if (!metric_events) {
> > if (expr__find_other(counter->metric_expr,
> > counter->name,
> > - &ctx, 1) < 0)
> > + ctx, 1) < 0)
> > continue;
> >
> > metric_events = calloc(sizeof(struct evsel *),
> > - hashmap__size(&ctx.ids) + 1);
> > + hashmap__size(ctx->ids) + 1);
> > if (!metric_events) {
> > - expr__ctx_clear(&ctx);
> > + expr__ctx_free(ctx);
> > return;
> > }
> > counter->metric_events = metric_events;
> > }
> >
> > i = 0;
> > - hashmap__for_each_entry((&ctx.ids), cur, bkt) {
> > + hashmap__for_each_entry(ctx->ids, cur, bkt) {
> > const char *metric_name = (const char *)cur->key;
> >
> > found = false;
> > @@ -453,7 +459,7 @@ void perf_stat__collect_metric_expr(struct evlist *evsel_list)
> > counter->metric_expr = NULL;
> > }
> > }
> > - expr__ctx_clear(&ctx);
> > + expr__ctx_free(ctx);
> > }
> >
> > static double runtime_stat_avg(struct runtime_stat *st,
> > @@ -818,7 +824,6 @@ static int prepare_metric(struct evsel **metric_events,
> > char *n, *pn;
> > int i, j, ret;
> >
> > - expr__ctx_init(pctx);
> > for (i = 0; metric_events[i]; i++) {
> > struct saved_value *v;
> > struct stats *stats;
> > @@ -880,17 +885,22 @@ static void generic_metric(struct perf_stat_config *config,
> > struct runtime_stat *st)
> > {
> > print_metric_t print_metric = out->print_metric;
> > - struct expr_parse_ctx pctx;
> > + struct expr_parse_ctx *pctx;
> > double ratio, scale;
> > int i;
> > void *ctxp = out->ctx;
> >
> > - i = prepare_metric(metric_events, metric_refs, &pctx, cpu, st);
> > - if (i < 0)
> > + pctx = expr__ctx_new();
> > + if (!pctx)
> > return;
> >
> > + i = prepare_metric(metric_events, metric_refs, pctx, cpu, st);
> > + if (i < 0) {
> > + expr__ctx_free(pctx);
> > + return;
> > + }
> > if (!metric_events[i]) {
> > - if (expr__parse(&ratio, &pctx, metric_expr, runtime) == 0) {
> > + if (expr__parse(&ratio, pctx, metric_expr, runtime) == 0) {
> > char *unit;
> > char metric_bf[64];
> >
> > @@ -926,22 +936,26 @@ static void generic_metric(struct perf_stat_config *config,
> > (metric_name ? metric_name : name) : "", 0);
> > }
> >
> > - expr__ctx_clear(&pctx);
> > + expr__ctx_free(pctx);
> > }
> >
> > double test_generic_metric(struct metric_expr *mexp, int cpu, struct runtime_stat *st)
> > {
> > - struct expr_parse_ctx pctx;
> > + struct expr_parse_ctx *pctx;
> > double ratio = 0.0;
> >
> > - if (prepare_metric(mexp->metric_events, mexp->metric_refs, &pctx, cpu, st) < 0)
> > + pctx = expr__ctx_new();
> > + if (!pctx)
> > + return NAN;
> > +
> > + if (prepare_metric(mexp->metric_events, mexp->metric_refs, pctx, cpu, st) < 0)
> > goto out;
> >
> > - if (expr__parse(&ratio, &pctx, mexp->metric_expr, 1))
> > + if (expr__parse(&ratio, pctx, mexp->metric_expr, 1))
> > ratio = 0.0;
> >
> > out:
> > - expr__ctx_clear(&pctx);
> > + expr__ctx_free(pctx);
> > return ratio;
> > }
> >
> > --
> > 2.33.0.464.g1972c5931b-goog
> >
>