diff options
Diffstat (limited to 'tools/perf/util/metricgroup.c')
-rw-r--r-- | tools/perf/util/metricgroup.c | 549 |
1 files changed, 445 insertions, 104 deletions
diff --git a/tools/perf/util/metricgroup.c b/tools/perf/util/metricgroup.c index 9e21aa767e41..8831b964288f 100644 --- a/tools/perf/util/metricgroup.c +++ b/tools/perf/util/metricgroup.c @@ -24,6 +24,7 @@ #include <subcmd/parse-options.h> #include <api/fs/fs.h> #include "util.h" +#include <asm/bug.h> struct metric_event *metricgroup__lookup(struct rblist *metric_events, struct evsel *evsel, @@ -76,23 +77,78 @@ static struct rb_node *metric_event_new(struct rblist *rblist __maybe_unused, return &me->nd; } +static void metric_event_delete(struct rblist *rblist __maybe_unused, + struct rb_node *rb_node) +{ + struct metric_event *me = container_of(rb_node, struct metric_event, nd); + struct metric_expr *expr, *tmp; + + list_for_each_entry_safe(expr, tmp, &me->head, nd) { + free(expr->metric_refs); + free(expr); + } + + free(me); +} + static void metricgroup__rblist_init(struct rblist *metric_events) { rblist__init(metric_events); metric_events->node_cmp = metric_event_cmp; metric_events->node_new = metric_event_new; + metric_events->node_delete = metric_event_delete; } -struct egroup { +void metricgroup__rblist_exit(struct rblist *metric_events) +{ + rblist__exit(metric_events); +} + +/* + * A node in the list of referenced metrics. metric_expr + * is held as a convenience to avoid a search through the + * metric list. + */ +struct metric_ref_node { + const char *metric_name; + const char *metric_expr; + struct list_head list; +}; + +struct metric { struct list_head nd; struct expr_parse_ctx pctx; const char *metric_name; const char *metric_expr; const char *metric_unit; + struct list_head metric_refs; + int metric_refs_cnt; int runtime; bool has_constraint; }; +#define RECURSION_ID_MAX 1000 + +struct expr_ids { + struct expr_id id[RECURSION_ID_MAX]; + int cnt; +}; + +static struct expr_id *expr_ids__alloc(struct expr_ids *ids) +{ + if (ids->cnt >= RECURSION_ID_MAX) + return NULL; + return &ids->id[ids->cnt++]; +} + +static void expr_ids__exit(struct expr_ids *ids) +{ + int i; + + for (i = 0; i < ids->cnt; i++) + free(ids->id[i].id); +} + /** * Find a group of events in perf_evlist that correpond to those from a parsed * metric expression. Note, as find_evsel_group is called in the same order as @@ -119,7 +175,7 @@ static struct evsel *find_evsel_group(struct evlist *perf_evlist, unsigned long *evlist_used) { struct evsel *ev, *current_leader = NULL; - double *val_ptr; + struct expr_id_data *val_ptr; int i = 0, matched_events = 0, events_to_match; const int idnum = (int)hashmap__size(&pctx->ids); @@ -206,7 +262,7 @@ static int metricgroup__setup_events(struct list_head *groups, struct metric_expr *expr; int i = 0; int ret = 0; - struct egroup *eg; + struct metric *m; struct evsel *evsel, *tmp; unsigned long *evlist_used; @@ -214,22 +270,23 @@ static int metricgroup__setup_events(struct list_head *groups, if (!evlist_used) return -ENOMEM; - list_for_each_entry (eg, groups, nd) { + list_for_each_entry (m, groups, nd) { struct evsel **metric_events; + struct metric_ref *metric_refs = NULL; metric_events = calloc(sizeof(void *), - hashmap__size(&eg->pctx.ids) + 1); + hashmap__size(&m->pctx.ids) + 1); if (!metric_events) { ret = -ENOMEM; break; } - evsel = find_evsel_group(perf_evlist, &eg->pctx, + evsel = find_evsel_group(perf_evlist, &m->pctx, metric_no_merge, - eg->has_constraint, metric_events, + m->has_constraint, metric_events, evlist_used); if (!evsel) { pr_debug("Cannot resolve %s: %s\n", - eg->metric_name, eg->metric_expr); + m->metric_name, m->metric_expr); free(metric_events); continue; } @@ -247,11 +304,41 @@ static int metricgroup__setup_events(struct list_head *groups, free(metric_events); break; } - expr->metric_expr = eg->metric_expr; - expr->metric_name = eg->metric_name; - expr->metric_unit = eg->metric_unit; + + /* + * Collect and store collected nested expressions + * for metric processing. + */ + if (m->metric_refs_cnt) { + struct metric_ref_node *ref; + + metric_refs = zalloc(sizeof(struct metric_ref) * (m->metric_refs_cnt + 1)); + if (!metric_refs) { + ret = -ENOMEM; + free(metric_events); + break; + } + + i = 0; + list_for_each_entry(ref, &m->metric_refs, list) { + /* + * Intentionally passing just const char pointers, + * originally from 'struct pmu_event' object. + * We don't need to change them, so there's no + * need to create our own copy. + */ + metric_refs[i].metric_name = ref->metric_name; + metric_refs[i].metric_expr = ref->metric_expr; + i++; + } + }; + + expr->metric_refs = metric_refs; + expr->metric_expr = m->metric_expr; + expr->metric_name = m->metric_name; + expr->metric_unit = m->metric_unit; expr->metric_events = metric_events; - expr->runtime = eg->runtime; + expr->runtime = m->runtime; list_add(&expr->nd, &me->head); } @@ -552,123 +639,339 @@ int __weak arch_get_runtimeparam(void) return 1; } -static int __metricgroup__add_metric(struct list_head *group_list, - struct pmu_event *pe, - bool metric_no_group, - int runtime) +static int __add_metric(struct list_head *metric_list, + struct pmu_event *pe, + bool metric_no_group, + int runtime, + struct metric **mp, + struct expr_id *parent, + struct expr_ids *ids) { - struct egroup *eg; + struct metric_ref_node *ref; + struct metric *m; - eg = malloc(sizeof(*eg)); - if (!eg) - return -ENOMEM; + if (*mp == NULL) { + /* + * We got in here for the parent group, + * allocate it and put it on the list. + */ + m = zalloc(sizeof(*m)); + if (!m) + return -ENOMEM; + + expr__ctx_init(&m->pctx); + m->metric_name = pe->metric_name; + m->metric_expr = pe->metric_expr; + m->metric_unit = pe->unit; + m->runtime = runtime; + m->has_constraint = metric_no_group || metricgroup__has_constraint(pe); + INIT_LIST_HEAD(&m->metric_refs); + m->metric_refs_cnt = 0; + *mp = m; + + parent = expr_ids__alloc(ids); + if (!parent) { + free(m); + return -EINVAL; + } + + parent->id = strdup(pe->metric_name); + if (!parent->id) { + free(m); + return -ENOMEM; + } + } else { + /* + * We got here for the referenced metric, via the + * recursive metricgroup__add_metric call, add + * it to the parent group. + */ + m = *mp; - expr__ctx_init(&eg->pctx); - eg->metric_name = pe->metric_name; - eg->metric_expr = pe->metric_expr; - eg->metric_unit = pe->unit; - eg->runtime = runtime; - eg->has_constraint = metric_no_group || metricgroup__has_constraint(pe); + ref = malloc(sizeof(*ref)); + if (!ref) + return -ENOMEM; - if (expr__find_other(pe->metric_expr, NULL, &eg->pctx, runtime) < 0) { - expr__ctx_clear(&eg->pctx); - free(eg); + /* + * Intentionally passing just const char pointers, + * from 'pe' object, so they never go away. We don't + * need to change them, so there's no need to create + * our own copy. + */ + ref->metric_name = pe->metric_name; + ref->metric_expr = pe->metric_expr; + + list_add(&ref->list, &m->metric_refs); + m->metric_refs_cnt++; + } + + /* Force all found IDs in metric to have us as parent ID. */ + WARN_ON_ONCE(!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) { + expr__ctx_clear(&m->pctx); + free(m); return -EINVAL; } - if (list_empty(group_list)) - list_add(&eg->nd, group_list); + /* + * We add new group only in the 'parent' call, + * so bail out for referenced metric case. + */ + if (m->metric_refs_cnt) + return 0; + + if (list_empty(metric_list)) + list_add(&m->nd, metric_list); else { struct list_head *pos; /* Place the largest groups at the front. */ - list_for_each_prev(pos, group_list) { - struct egroup *old = list_entry(pos, struct egroup, nd); + list_for_each_prev(pos, metric_list) { + struct metric *old = list_entry(pos, struct metric, nd); - if (hashmap__size(&eg->pctx.ids) <= + if (hashmap__size(&m->pctx.ids) <= hashmap__size(&old->pctx.ids)) break; } - list_add(&eg->nd, pos); + list_add(&m->nd, pos); } return 0; } +#define map_for_each_event(__pe, __idx, __map) \ + for (__idx = 0, __pe = &__map->table[__idx]; \ + __pe->name || __pe->metric_group || __pe->metric_name; \ + __pe = &__map->table[++__idx]) + +#define map_for_each_metric(__pe, __idx, __map, __metric) \ + map_for_each_event(__pe, __idx, __map) \ + if (__pe->metric_expr && \ + (match_metric(__pe->metric_group, __metric) || \ + match_metric(__pe->metric_name, __metric))) + +static struct pmu_event *find_metric(const char *metric, struct pmu_events_map *map) +{ + struct pmu_event *pe; + int i; + + map_for_each_event(pe, i, map) { + if (match_metric(pe->metric_name, metric)) + return pe; + } + + return NULL; +} + +static int recursion_check(struct metric *m, const char *id, struct expr_id **parent, + struct expr_ids *ids) +{ + struct expr_id_data *data; + struct expr_id *p; + int ret; + + /* + * We get the parent referenced by 'id' argument and + * traverse through all the parent object IDs to check + * if we already processed 'id', if we did, it's recursion + * and we fail. + */ + ret = expr__get_id(&m->pctx, id, &data); + if (ret) + return ret; + + p = data->parent; + + while (p->parent) { + if (!strcmp(p->id, id)) { + pr_err("failed: recursion detected for %s\n", id); + return -1; + } + p = p->parent; + } + + /* + * If we are over the limit of static entris, the metric + * is too difficult/nested to process, fail as well. + */ + p = expr_ids__alloc(ids); + if (!p) { + pr_err("failed: too many nested metrics\n"); + return -EINVAL; + } + + p->id = strdup(id); + p->parent = data->parent; + *parent = p; + + return p->id ? 0 : -ENOMEM; +} + +static int add_metric(struct list_head *metric_list, + struct pmu_event *pe, + bool metric_no_group, + struct metric **mp, + struct expr_id *parent, + struct expr_ids *ids); + +static int __resolve_metric(struct metric *m, + bool metric_no_group, + struct list_head *metric_list, + struct pmu_events_map *map, + struct expr_ids *ids) +{ + struct hashmap_entry *cur; + size_t bkt; + bool all; + int ret; + + /* + * Iterate all the parsed IDs and if there's metric, + * add it to the context. + */ + do { + all = true; + hashmap__for_each_entry((&m->pctx.ids), cur, bkt) { + struct expr_id *parent; + struct pmu_event *pe; + + pe = find_metric(cur->key, map); + if (!pe) + continue; + + ret = recursion_check(m, cur->key, &parent, ids); + if (ret) + return ret; + + all = false; + /* The metric key itself needs to go out.. */ + 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); + if (ret) + return ret; + + /* + * We added new metric to hashmap, so we need + * to break the iteration and start over. + */ + break; + } + } while (!all); + + return 0; +} + +static int resolve_metric(bool metric_no_group, + struct list_head *metric_list, + struct pmu_events_map *map, + struct expr_ids *ids) +{ + struct metric *m; + int err; + + list_for_each_entry(m, metric_list, nd) { + err = __resolve_metric(m, metric_no_group, metric_list, map, ids); + if (err) + return err; + } + return 0; +} + +static int add_metric(struct list_head *metric_list, + struct pmu_event *pe, + bool metric_no_group, + struct metric **m, + struct expr_id *parent, + struct expr_ids *ids) +{ + struct metric *orig = *m; + int ret = 0; + + pr_debug("metric expr %s for %s\n", pe->metric_expr, pe->metric_name); + + if (!strstr(pe->metric_expr, "?")) { + ret = __add_metric(metric_list, pe, metric_no_group, 1, m, parent, ids); + } else { + int j, count; + + count = arch_get_runtimeparam(); + + /* This loop is added to create multiple + * events depend on count value and add + * those events to metric_list. + */ + + for (j = 0; j < count && !ret; j++, *m = orig) + ret = __add_metric(metric_list, pe, metric_no_group, j, m, parent, ids); + } + + return ret; +} + static int metricgroup__add_metric(const char *metric, bool metric_no_group, struct strbuf *events, - struct list_head *group_list) + struct list_head *metric_list, + struct pmu_events_map *map) { - struct pmu_events_map *map = perf_pmu__find_map(NULL); + struct expr_ids ids = { .cnt = 0, }; struct pmu_event *pe; - struct egroup *eg; + struct metric *m; + LIST_HEAD(list); int i, ret; bool has_match = false; - if (!map) - return 0; + map_for_each_metric(pe, i, map, metric) { + has_match = true; + m = NULL; - for (i = 0; ; i++) { - pe = &map->table[i]; + ret = add_metric(&list, pe, metric_no_group, &m, NULL, &ids); + if (ret) + return ret; - if (!pe->name && !pe->metric_group && !pe->metric_name) { - /* End of pmu events. */ - if (!has_match) - return -EINVAL; - break; - } - if (!pe->metric_expr) - continue; - if (match_metric(pe->metric_group, metric) || - match_metric(pe->metric_name, metric)) { - has_match = true; - pr_debug("metric expr %s for %s\n", pe->metric_expr, pe->metric_name); - - if (!strstr(pe->metric_expr, "?")) { - ret = __metricgroup__add_metric(group_list, - pe, - metric_no_group, - 1); - if (ret) - return ret; - } else { - int j, count; - - count = arch_get_runtimeparam(); - - /* This loop is added to create multiple - * events depend on count value and add - * those events to group_list. - */ - - for (j = 0; j < count; j++) { - ret = __metricgroup__add_metric( - group_list, pe, - metric_no_group, j); - if (ret) - return ret; - } - } - } + /* + * Process any possible referenced metrics + * included in the expression. + */ + ret = resolve_metric(metric_no_group, + &list, map, &ids); + if (ret) + return ret; } - list_for_each_entry(eg, group_list, nd) { + + /* End of pmu events. */ + if (!has_match) + return -EINVAL; + + list_for_each_entry(m, &list, nd) { if (events->len > 0) strbuf_addf(events, ","); - if (eg->has_constraint) { + if (m->has_constraint) { metricgroup__add_metric_non_group(events, - &eg->pctx); + &m->pctx); } else { metricgroup__add_metric_weak_group(events, - &eg->pctx); + &m->pctx); } } + + list_splice(&list, metric_list); + expr_ids__exit(&ids); return 0; } static int metricgroup__add_metric_list(const char *list, bool metric_no_group, struct strbuf *events, - struct list_head *group_list) + struct list_head *metric_list, + struct pmu_events_map *map) { char *llist, *nlist, *p; int ret = -EINVAL; @@ -683,7 +986,7 @@ static int metricgroup__add_metric_list(const char *list, bool metric_no_group, while ((p = strsep(&llist, ",")) != NULL) { ret = metricgroup__add_metric(p, metric_no_group, events, - group_list); + metric_list, map); if (ret == -EINVAL) { fprintf(stderr, "Cannot find metric or group `%s'\n", p); @@ -698,50 +1001,88 @@ static int metricgroup__add_metric_list(const char *list, bool metric_no_group, return ret; } -static void metricgroup__free_egroups(struct list_head *group_list) +static void metric__free_refs(struct metric *metric) { - struct egroup *eg, *egtmp; + struct metric_ref_node *ref, *tmp; - list_for_each_entry_safe (eg, egtmp, group_list, nd) { - expr__ctx_clear(&eg->pctx); - list_del_init(&eg->nd); - free(eg); + list_for_each_entry_safe(ref, tmp, &metric->metric_refs, list) { + list_del(&ref->list); + free(ref); } } -int metricgroup__parse_groups(const struct option *opt, - const char *str, - bool metric_no_group, - bool metric_no_merge, - struct rblist *metric_events) +static void metricgroup__free_metrics(struct list_head *metric_list) +{ + struct metric *m, *tmp; + + list_for_each_entry_safe (m, tmp, metric_list, nd) { + metric__free_refs(m); + expr__ctx_clear(&m->pctx); + list_del_init(&m->nd); + free(m); + } +} + +static int parse_groups(struct evlist *perf_evlist, const char *str, + bool metric_no_group, + bool metric_no_merge, + struct perf_pmu *fake_pmu, + struct rblist *metric_events, + struct pmu_events_map *map) { struct parse_events_error parse_error; - struct evlist *perf_evlist = *(struct evlist **)opt->value; struct strbuf extra_events; - LIST_HEAD(group_list); + LIST_HEAD(metric_list); int ret; if (metric_events->nr_entries == 0) metricgroup__rblist_init(metric_events); ret = metricgroup__add_metric_list(str, metric_no_group, - &extra_events, &group_list); + &extra_events, &metric_list, map); if (ret) return ret; pr_debug("adding %s\n", extra_events.buf); bzero(&parse_error, sizeof(parse_error)); - ret = parse_events(perf_evlist, extra_events.buf, &parse_error); + ret = __parse_events(perf_evlist, extra_events.buf, &parse_error, fake_pmu); if (ret) { parse_events_print_error(&parse_error, extra_events.buf); goto out; } strbuf_release(&extra_events); - ret = metricgroup__setup_events(&group_list, metric_no_merge, + ret = metricgroup__setup_events(&metric_list, metric_no_merge, perf_evlist, metric_events); out: - metricgroup__free_egroups(&group_list); + metricgroup__free_metrics(&metric_list); return ret; } +int metricgroup__parse_groups(const struct option *opt, + const char *str, + bool metric_no_group, + bool metric_no_merge, + struct rblist *metric_events) +{ + struct evlist *perf_evlist = *(struct evlist **)opt->value; + struct pmu_events_map *map = perf_pmu__find_map(NULL); + + if (!map) + return 0; + + return parse_groups(perf_evlist, str, metric_no_group, + metric_no_merge, NULL, metric_events, map); +} + +int metricgroup__parse_groups_test(struct evlist *evlist, + struct pmu_events_map *map, + const char *str, + bool metric_no_group, + bool metric_no_merge, + struct rblist *metric_events) +{ + return parse_groups(evlist, str, metric_no_group, + metric_no_merge, &perf_pmu__fake, metric_events, map); +} + bool metricgroup__has_metric(const char *metric) { struct pmu_events_map *map = perf_pmu__find_map(NULL); |