Commit ded80bda authored by Ian Rogers's avatar Ian Rogers Committed by Arnaldo Carvalho de Melo
Browse files

perf expr: Migrate expr ids table to a hashmap

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

Original map suggestion by Andi Kleen:

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

and seconded by Jiri Olsa:

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



Committer notes:

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

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

Testing it:

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

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

Explicitely building without LIBBPF:

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

Signed-off-by: default avatarIan Rogers <irogers@google.com>
Tested-by: default avatarArnaldo Carvalho de Melo <acme@redhat.com>
Cc: Adrian Hunter <adrian.hunter@intel.com>
Cc: Alexander Shishkin <alexander.shishkin@linux.intel.com>
Cc: Alexei Starovoitov <ast@kernel.org>
Cc: Andi Kleen <ak@linux.intel.com>
Cc: Andrii Nakryiko <andriin@fb.com>
Cc: Cong Wang <xiyou.wangcong@gmail.com>
Cc: Daniel Borkmann <daniel@iogearbox.net>
Cc: Jin Yao <yao.jin@linux.intel.com>
Cc: Jiri Olsa <jolsa@redhat.com>
Cc: John Fastabend <john.fastabend@gmail.com>
Cc: John Garry <john.garry@huawei.com>
Cc: Kajol Jain <kjain@linux.ibm.com>
Cc: Kan Liang <kan.liang@linux.intel.com>
Cc: Kim Phillips <kim.phillips@amd.com>
Cc: Leo Yan <leo.yan@linaro.org>
Cc: Mark Rutland <mark.rutland@arm.com>
Cc: Martin KaFai Lau <kafai@fb.com>
Cc: Namhyung Kim <namhyung@kernel.org>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Song Liu <songliubraving@fb.com>
Cc: Stephane Eranian <eranian@google.com>
Cc: Yonghong Song <yhs@fb.com>
Cc: bpf@vger.kernel.org
Cc: kp singh <kpsingh@chromium.org>
Cc: netdev@vger.kernel.org
Link: http://lore.kernel.org/lkml/20200515221732.44078-8-irogers@google.com


Signed-off-by: default avatarArnaldo Carvalho de Melo <acme@redhat.com>
parent eee19501
Loading
Loading
Loading
Loading
+23 −21
Original line number Diff line number Diff line
@@ -19,15 +19,13 @@ static int test(struct expr_parse_ctx *ctx, const char *e, double val2)
int test__expr(struct test *t __maybe_unused, int subtest __maybe_unused)
{
	const char *p;
	const char **other;
	double val;
	int i, ret;
	double val, *val_ptr;
	int ret;
	struct expr_parse_ctx ctx;
	int num_other;

	expr__ctx_init(&ctx);
	expr__add_id(&ctx, "FOO", 1);
	expr__add_id(&ctx, "BAR", 2);
	expr__add_id(&ctx, strdup("FOO"), 1);
	expr__add_id(&ctx, strdup("BAR"), 2);

	ret = test(&ctx, "1+1", 2);
	ret |= test(&ctx, "FOO+BAR", 3);
@@ -52,25 +50,29 @@ int test__expr(struct test *t __maybe_unused, int subtest __maybe_unused)
	ret = expr__parse(&val, &ctx, p, 1);
	TEST_ASSERT_VAL("missing operand", ret == -1);

	expr__ctx_clear(&ctx);
	TEST_ASSERT_VAL("find other",
			expr__find_other("FOO + BAR + BAZ + BOZO", "FOO", &other, &num_other, 1) == 0);
	TEST_ASSERT_VAL("find other", num_other == 3);
	TEST_ASSERT_VAL("find other", !strcmp(other[0], "BAR"));
	TEST_ASSERT_VAL("find other", !strcmp(other[1], "BAZ"));
	TEST_ASSERT_VAL("find other", !strcmp(other[2], "BOZO"));
	TEST_ASSERT_VAL("find other", other[3] == NULL);
			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",
						    (void **)&val_ptr));
	TEST_ASSERT_VAL("find other", hashmap__find(&ctx.ids, "BAZ",
						    (void **)&val_ptr));
	TEST_ASSERT_VAL("find other", hashmap__find(&ctx.ids, "BOZO",
						    (void **)&val_ptr));

	expr__ctx_clear(&ctx);
	TEST_ASSERT_VAL("find other",
			expr__find_other("EVENT1\\,param\\=?@ + EVENT2\\,param\\=?@", NULL,
				   &other, &num_other, 3) == 0);
	TEST_ASSERT_VAL("find other", num_other == 2);
	TEST_ASSERT_VAL("find other", !strcmp(other[0], "EVENT1,param=3/"));
	TEST_ASSERT_VAL("find other", !strcmp(other[1], "EVENT2,param=3/"));
	TEST_ASSERT_VAL("find other", other[2] == NULL);
			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/",
						    (void **)&val_ptr));
	TEST_ASSERT_VAL("find other", hashmap__find(&ctx.ids, "EVENT2,param=3/",
						    (void **)&val_ptr));

	for (i = 0; i < num_other; i++)
		zfree(&other[i]);
	free((void *)other);
	expr__ctx_clear(&ctx);

	return 0;
}
+13 −12
Original line number Diff line number Diff line
@@ -437,8 +437,6 @@ static int test_parsing(void)
	struct pmu_events_map *map;
	struct pmu_event *pe;
	int i, j, k;
	const char **ids;
	int idnum;
	int ret = 0;
	struct expr_parse_ctx ctx;
	double result;
@@ -450,29 +448,34 @@ static int test_parsing(void)
			break;
		j = 0;
		for (;;) {
			struct hashmap_entry *cur;
			size_t bkt;

			pe = &map->table[j++];
			if (!pe->name && !pe->metric_group && !pe->metric_name)
				break;
			if (!pe->metric_expr)
				continue;
			if (expr__find_other(pe->metric_expr, NULL,
						&ids, &idnum, 0) < 0) {
			expr__ctx_init(&ctx);
			if (expr__find_other(pe->metric_expr, NULL, &ctx, 0)
				  < 0) {
				expr_failure("Parse other failed", map, pe);
				ret++;
				continue;
			}
			expr__ctx_init(&ctx);

			/*
			 * Add all ids with a made up value. The value may
			 * trigger divide by zero when subtracted and so try to
			 * make them unique.
			 */
			for (k = 0; k < idnum; k++)
				expr__add_id(&ctx, ids[k], k + 1);
			k = 1;
			hashmap__for_each_entry((&ctx.ids), cur, bkt)
				expr__add_id(&ctx, strdup(cur->key), k++);

			for (k = 0; k < idnum; k++) {
				if (check_parse_id(ids[k], map == cpus_map, pe))
			hashmap__for_each_entry((&ctx.ids), cur, bkt) {
				if (check_parse_id(cur->key, map == cpus_map,
						   pe))
					ret++;
			}

@@ -480,9 +483,7 @@ static int test_parsing(void)
				expr_failure("Parse failed", map, pe);
				ret++;
			}
			for (k = 0; k < idnum; k++)
				zfree(&ids[k]);
			free(ids);
			expr__ctx_clear(&ctx);
		}
	}
	/* TODO: fail when not ok */
+72 −57
Original line number Diff line number Diff line
@@ -4,25 +4,76 @@
#include "expr.h"
#include "expr-bison.h"
#include "expr-flex.h"
#include <linux/kernel.h>

#ifdef PARSER_DEBUG
extern int expr_debug;
#endif

static size_t key_hash(const void *key, void *ctx __maybe_unused)
{
	const char *str = (const char *)key;
	size_t hash = 0;

	while (*str != '\0') {
		hash *= 31;
		hash += *str;
		str++;
	}
	return hash;
}

static bool key_equal(const void *key1, const void *key2,
		    void *ctx __maybe_unused)
{
	return !strcmp((const char *)key1, (const char *)key2);
}

/* Caller must make sure id is allocated */
void expr__add_id(struct expr_parse_ctx *ctx, const char *name, double val)
int expr__add_id(struct expr_parse_ctx *ctx, const char *name, double val)
{
	int idx;
	double *val_ptr = NULL, *old_val = NULL;
	char *old_key = NULL;
	int ret;

	assert(ctx->num_ids < MAX_PARSE_ID);
	idx = ctx->num_ids++;
	ctx->ids[idx].name = name;
	ctx->ids[idx].val = val;
	if (val != 0.0) {
		val_ptr = malloc(sizeof(double));
		if (!val_ptr)
			return -ENOMEM;
		*val_ptr = val;
	}
	ret = hashmap__set(&ctx->ids, name, val_ptr,
			   (const void **)&old_key, (void **)&old_val);
	free(old_key);
	free(old_val);
	return ret;
}

int expr__get_id(struct expr_parse_ctx *ctx, const char *id, double *val_ptr)
{
	double *data;

	if (!hashmap__find(&ctx->ids, id, (void **)&data))
		return -1;
	*val_ptr = (data == NULL) ?  0.0 : *data;
	return 0;
}

void expr__ctx_init(struct expr_parse_ctx *ctx)
{
	ctx->num_ids = 0;
	hashmap__init(&ctx->ids, key_hash, key_equal, NULL);
}

void expr__ctx_clear(struct expr_parse_ctx *ctx)
{
	struct hashmap_entry *cur;
	size_t bkt;

	hashmap__for_each_entry((&ctx->ids), cur, bkt) {
		free((char *)cur->key);
		free(cur->value);
	}
	hashmap__clear(&ctx->ids);
}

static int
@@ -56,61 +107,25 @@ __expr__parse(double *val, struct expr_parse_ctx *ctx, const char *expr,
	return ret;
}

int expr__parse(double *final_val, struct expr_parse_ctx *ctx, const char *expr, int runtime)
int expr__parse(double *final_val, struct expr_parse_ctx *ctx,
		const char *expr, int runtime)
{
	return __expr__parse(final_val, ctx, expr, EXPR_PARSE, runtime) ? -1 : 0;
}

static bool
already_seen(const char *val, const char *one, const char **other,
	     int num_other)
int expr__find_other(const char *expr, const char *one,
		     struct expr_parse_ctx *ctx, int runtime)
{
	int i;

	if (one && !strcasecmp(one, val))
		return true;
	for (i = 0; i < num_other; i++)
		if (!strcasecmp(other[i], val))
			return true;
	return false;
	double *old_val = NULL;
	char *old_key = NULL;
	int ret = __expr__parse(NULL, ctx, expr, EXPR_OTHER, runtime);

	if (one) {
		hashmap__delete(&ctx->ids, one,
				(const void **)&old_key, (void **)&old_val);
		free(old_key);
		free(old_val);
	}

int expr__find_other(const char *expr, const char *one, const char ***other,
		     int *num_other, int runtime)
{
	int err, i = 0, j = 0;
	struct expr_parse_ctx ctx;

	expr__ctx_init(&ctx);
	err = __expr__parse(NULL, &ctx, expr, EXPR_OTHER, runtime);
	if (err)
		return -1;

	*other = malloc((ctx.num_ids + 1) * sizeof(char *));
	if (!*other)
		return -ENOMEM;

	for (i = 0, j = 0; i < ctx.num_ids; i++) {
		const char *str = ctx.ids[i].name;

		if (already_seen(str, one, *other, j))
			continue;

		str = strdup(str);
		if (!str)
			goto out;
		(*other)[j++] = str;
	}
	(*other)[j] = NULL;

out:
	if (i != ctx.num_ids) {
		while (--j)
			free((char *) (*other)[i]);
		free(*other);
		err = -1;
	}

	*num_other = j;
	return err;
	return ret;
}
+16 −13
Original line number Diff line number Diff line
@@ -2,17 +2,17 @@
#ifndef PARSE_CTX_H
#define PARSE_CTX_H 1

#define EXPR_MAX_OTHER 64
#define MAX_PARSE_ID EXPR_MAX_OTHER

struct expr_parse_id {
	const char *name;
	double val;
};
// There are fixes that need to land upstream before we can use libbpf's headers,
// for now use our copy uncoditionally, since the data structures at this point
// are exactly the same, no problem.
//#ifdef HAVE_LIBBPF_SUPPORT
//#include <bpf/hashmap.h>
//#else
#include "util/hashmap.h"
//#endif

struct expr_parse_ctx {
	int num_ids;
	struct expr_parse_id ids[MAX_PARSE_ID];
	struct hashmap ids;
};

struct expr_scanner_ctx {
@@ -21,9 +21,12 @@ struct expr_scanner_ctx {
};

void expr__ctx_init(struct expr_parse_ctx *ctx);
void expr__add_id(struct expr_parse_ctx *ctx, const char *id, double val);
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, const char ***other,
		int *num_other, int runtime);
void expr__ctx_clear(struct expr_parse_ctx *ctx);
int expr__add_id(struct expr_parse_ctx *ctx, const char *id, double val);
int expr__get_id(struct expr_parse_ctx *ctx, const char *id, double *val_ptr);
int expr__parse(double *final_val, struct expr_parse_ctx *ctx,
		const char *expr, int runtime);
int expr__find_other(const char *expr, const char *one,
		struct expr_parse_ctx *ids, int runtime);

#endif
+2 −20
Original line number Diff line number Diff line
@@ -47,19 +47,6 @@ static void expr_error(double *final_val __maybe_unused,
	pr_debug("%s\n", s);
}

static int lookup_id(struct expr_parse_ctx *ctx, char *id, double *val)
{
	int i;

	for (i = 0; i < ctx->num_ids; i++) {
		if (!strcasecmp(ctx->ids[i].name, id)) {
			*val = ctx->ids[i].val;
			return 0;
		}
	}
	return -1;
}

%}
%%

@@ -73,12 +60,7 @@ all_other: all_other other

other: ID
{
	if (ctx->num_ids + 1 >= EXPR_MAX_OTHER) {
		pr_err("failed: way too many variables");
		YYABORT;
	}

	ctx->ids[ctx->num_ids++].name = $1;
	expr__add_id(ctx, $1, 0.0);
}
|
MIN | MAX | IF | ELSE | SMT_ON | NUMBER | '|' | '^' | '&' | '-' | '+' | '*' | '/' | '%' | '(' | ')' | ','
@@ -93,7 +75,7 @@ if_expr:
	;

expr:	  NUMBER
	| ID			{ if (lookup_id(ctx, $1, &$$) < 0) {
	| ID			{ if (expr__get_id(ctx, $1, &$$)) {
					pr_debug("%s not found\n", $1);
					free($1);
					YYABORT;
Loading