From: Andi Kleen <andi@firstfloor.org>
To: acme@kernel.org
Cc: jolsa@kernel.org, linux-kernel@vger.kernel.org,
Andi Kleen <ak@linux.intel.com>
Subject: [PATCH 08/10] perf, tools: Add a simple expression parser for JSON
Date: Fri, 27 Jan 2017 18:03:43 -0800 [thread overview]
Message-ID: <20170128020345.19007-9-andi@firstfloor.org> (raw)
In-Reply-To: <20170128020345.19007-1-andi@firstfloor.org>
From: Andi Kleen <ak@linux.intel.com>
Add a simple expression parser good enough to parse JSON relation
expressions. The parser is implemented using bison.
Signed-off-by: Andi Kleen <ak@linux.intel.com>
---
tools/perf/tests/Build | 1 +
tools/perf/tests/builtin-test.c | 4 ++
tools/perf/tests/expr.c | 48 +++++++++++++
tools/perf/tests/tests.h | 1 +
tools/perf/util/Build | 5 ++
tools/perf/util/expr.h | 23 ++++++
tools/perf/util/expr.y | 156 ++++++++++++++++++++++++++++++++++++++++
7 files changed, 238 insertions(+)
create mode 100644 tools/perf/tests/expr.c
create mode 100644 tools/perf/util/expr.h
create mode 100644 tools/perf/util/expr.y
diff --git a/tools/perf/tests/Build b/tools/perf/tests/Build
index 1cb3d9b540e9..af58ebc243ef 100644
--- a/tools/perf/tests/Build
+++ b/tools/perf/tests/Build
@@ -38,6 +38,7 @@ perf-y += cpumap.o
perf-y += stat.o
perf-y += event_update.o
perf-y += event-times.o
+perf-y += expr.o
perf-y += backward-ring-buffer.o
perf-y += sdt.o
perf-y += is_printable_array.o
diff --git a/tools/perf/tests/builtin-test.c b/tools/perf/tests/builtin-test.c
index 37e326bfd2dc..db03e853ba7f 100644
--- a/tools/perf/tests/builtin-test.c
+++ b/tools/perf/tests/builtin-test.c
@@ -44,6 +44,10 @@ static struct test generic_tests[] = {
.func = test__parse_events,
},
{
+ .desc = "Simple expression parser",
+ .func = test__expr,
+ },
+ {
.desc = "PERF_RECORD_* events & perf_sample fields",
.func = test__PERF_RECORD,
},
diff --git a/tools/perf/tests/expr.c b/tools/perf/tests/expr.c
new file mode 100644
index 000000000000..38e4bb31692c
--- /dev/null
+++ b/tools/perf/tests/expr.c
@@ -0,0 +1,48 @@
+#include "util/debug.h"
+#include "util/expr.h"
+#include "tests.h"
+
+static int test(struct parse_ctx *ctx, const char *e, double val2)
+{
+ double val;
+
+ if (expr_parse(&val, ctx, &e))
+ TEST_ASSERT_VAL("parse test failed", 0);
+ TEST_ASSERT_VAL("unexpected value", val == val2);
+ return 0;
+}
+
+int test__expr(int subtest __maybe_unused)
+{
+ const char *p;
+ double val;
+ int ret;
+ struct parse_ctx ctx;
+
+ expr_ctx_init(&ctx);
+ expr_add_id(&ctx, "FOO", 1);
+ expr_add_id(&ctx, "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);
+
+ if (ret)
+ return ret;
+
+ p = "FOO/0";
+ ret = expr_parse(&val, &ctx, &p);
+ TEST_ASSERT_VAL("division by zero", ret == 1);
+
+ p = "BAR/";
+ ret = expr_parse(&val, &ctx, &p);
+ TEST_ASSERT_VAL("missing operand", ret == 1);
+
+ TEST_ASSERT_VAL("find other", expr_find_other("FOO + BAR", "FOO", &p) == 0);
+ TEST_ASSERT_VAL("find other", !strcmp(p, "BAR"));
+ free((void *)p);
+
+ return 0;
+}
diff --git a/tools/perf/tests/tests.h b/tools/perf/tests/tests.h
index 1fa9b9d83aa5..631859629403 100644
--- a/tools/perf/tests/tests.h
+++ b/tools/perf/tests/tests.h
@@ -62,6 +62,7 @@ int test__sample_parsing(int subtest);
int test__keep_tracking(int subtest);
int test__parse_no_sample_id_all(int subtest);
int test__dwarf_unwind(int subtest);
+int test__expr(int subtest);
int test__hists_filter(int subtest);
int test__mmap_thread_lookup(int subtest);
int test__thread_mg_share(int subtest);
diff --git a/tools/perf/util/Build b/tools/perf/util/Build
index 5da376bc1afc..887642ca609c 100644
--- a/tools/perf/util/Build
+++ b/tools/perf/util/Build
@@ -88,6 +88,7 @@ libperf-y += mem-events.o
libperf-y += vsprintf.o
libperf-y += drv_configs.o
libperf-y += time-utils.o
+libperf-y += expr-bison.o
libperf-$(CONFIG_LIBBPF) += bpf-loader.o
libperf-$(CONFIG_BPF_PROLOGUE) += bpf-prologue.o
@@ -140,6 +141,10 @@ $(OUTPUT)util/parse-events-bison.c: util/parse-events.y
$(call rule_mkdir)
$(Q)$(call echo-cmd,bison)$(BISON) -v util/parse-events.y -d $(PARSER_DEBUG_BISON) -o $@ -p parse_events_
+$(OUTPUT)util/expr-bison.c: util/expr.y
+ $(call rule_mkdir)
+ $(Q)$(call echo-cmd,bison)$(BISON) -v util/expr.y -d $(PARSER_DEBUG_BISON) -o $@ -p expr_
+
$(OUTPUT)util/pmu-flex.c: util/pmu.l $(OUTPUT)util/pmu-bison.c
$(call rule_mkdir)
$(Q)$(call echo-cmd,flex)$(FLEX) -o $@ --header-file=$(OUTPUT)util/pmu-flex.h util/pmu.l
diff --git a/tools/perf/util/expr.h b/tools/perf/util/expr.h
new file mode 100644
index 000000000000..3b99fa282059
--- /dev/null
+++ b/tools/perf/util/expr.h
@@ -0,0 +1,23 @@
+#ifndef PARSE_CTX_H
+#define PARSE_CTX_H 1
+
+#define MAX_PARSE_ID 2
+
+struct parse_id {
+ const char *name;
+ double val;
+};
+
+struct parse_ctx {
+ int num_ids;
+ struct parse_id ids[MAX_PARSE_ID];
+};
+
+void expr_ctx_init(struct parse_ctx *ctx);
+void expr_add_id(struct parse_ctx *ctx, const char *id, double val);
+#ifndef IN_EXPR_Y
+int expr_parse(double *final_val, struct parse_ctx *ctx, const char **pp);
+#endif
+int expr_find_other(const char *p, const char *one, const char **other);
+
+#endif
diff --git a/tools/perf/util/expr.y b/tools/perf/util/expr.y
new file mode 100644
index 000000000000..9c2c39512b8d
--- /dev/null
+++ b/tools/perf/util/expr.y
@@ -0,0 +1,156 @@
+/* Simple expression parser */
+%{
+#include "util.h"
+#include "util/debug.h"
+#define IN_EXPR_Y 1
+#include "expr.h"
+#include <string.h>
+
+#define MAXIDLEN 256
+%}
+
+%define api.pure full
+%parse-param { double *final_val }
+%parse-param { struct parse_ctx *ctx }
+%parse-param { const char **pp }
+%lex-param { const char **pp }
+
+%union {
+ double num;
+ char id[MAXIDLEN+1];
+}
+
+%token <num> NUMBER
+%token <id> ID
+%left '|'
+%left '^'
+%left '&'
+%left '-' '+'
+%left '*' '/' '%'
+%left NEG NOT
+%type <num> expr
+
+%{
+static int expr_lex(YYSTYPE *res, const char **pp);
+
+static void expr_error(double *final_val __maybe_unused,
+ struct parse_ctx *ctx __maybe_unused,
+ const char **pp __maybe_unused,
+ const char *s)
+{
+ pr_debug("%s", s);
+}
+
+static int lookup_id(struct 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;
+}
+
+%}
+%%
+
+all_expr: expr { *final_val = $1; }
+ ;
+
+expr: NUMBER
+ | ID { if (lookup_id(ctx, $1, &$$) < 0) {
+ pr_debug("%s not found", $1);
+ YYABORT;
+ }
+ }
+ | expr '+' expr { $$ = $1 + $3; }
+ | expr '-' expr { $$ = $1 - $3; }
+ | expr '*' expr { $$ = $1 * $3; }
+ | expr '/' expr { if ($3 == 0) YYABORT; $$ = $1 / $3; }
+ | expr '%' expr { if ((long)$3 == 0) YYABORT; $$ = (long)$1 % (long)$3; }
+ | '-' expr %prec NEG { $$ = -$2; }
+ | '(' expr ')' { $$ = $2; }
+ ;
+
+%%
+
+static int expr_symbol(YYSTYPE *res, const char *p, const char **pp)
+{
+ char *dst = res->id;
+ const char *s = p;
+
+ while (isalnum(*p) || *p == '_' || *p == '.') {
+ if (p - s >= MAXIDLEN)
+ return -1;
+ *dst++ = *p++;
+ }
+ *dst = 0;
+ *pp = p;
+ return ID;
+}
+
+static int expr_lex(YYSTYPE *res, const char **pp)
+{
+ int tok;
+ const char *s;
+ const char *p = *pp;
+
+ while (isspace(*p))
+ p++;
+ s = p;
+ switch (*p++) {
+ case 'a' ... 'z':
+ case 'A' ... 'Z':
+ return expr_symbol(res, p - 1, pp);
+ case '0' ... '9': case '.':
+ res->num = strtod(s, (char **)&p);
+ tok = NUMBER;
+ break;
+ default:
+ tok = *s;
+ break;
+ }
+ *pp = p;
+ return tok;
+}
+
+/* Caller must make sure id is allocated */
+void expr_add_id(struct parse_ctx *ctx, const char *name, double val)
+{
+ int idx;
+ assert(ctx->num_ids < MAX_PARSE_ID);
+ idx = ctx->num_ids++;
+ ctx->ids[idx].name = name;
+ ctx->ids[idx].val = val;
+}
+
+void expr_ctx_init(struct parse_ctx *ctx)
+{
+ ctx->num_ids = 0;
+}
+
+int expr_find_other(const char *p, const char *one, const char **other)
+{
+ const char *orig = p;
+
+ *other = NULL;
+ for (;;) {
+ YYSTYPE val;
+ int tok = expr_lex(&val, &p);
+ if (tok == 0)
+ break;
+ if (tok == ID && strcasecmp(one, val.id)) {
+ if (*other) {
+ pr_debug("More than one extra identifier in %s\n", orig);
+ return -1;
+ }
+ *other = strdup(val.id);
+ if (!*other)
+ return -1;
+ }
+ }
+ return 0;
+}
--
2.9.3
next prev parent reply other threads:[~2017-01-28 2:21 UTC|newest]
Thread overview: 28+ messages / expand[flat|nested] mbox.gz Atom feed top
2017-01-28 2:03 Support Intel uncore event lists Andi Kleen
2017-01-28 2:03 ` [PATCH 01/10] perf, tools: Parse eventcode as number in jevents Andi Kleen
2017-02-10 7:41 ` [tip:perf/core] perf jevents: Parse eventcode as number tip-bot for Andi Kleen
2017-01-28 2:03 ` [PATCH 02/10] perf, tools: Add support for parsing uncore json files Andi Kleen
2017-02-10 7:41 ` [tip:perf/core] perf jevents: " tip-bot for Andi Kleen
2017-01-28 2:03 ` [PATCH 03/10] perf, tools: Support per pmu json aliases Andi Kleen
2017-02-10 7:42 ` [tip:perf/core] perf pmu: " tip-bot for Andi Kleen
2017-01-28 2:03 ` [PATCH 04/10] perf, tools: Support event aliases for non cpu// pmus Andi Kleen
2017-02-10 7:42 ` [tip:perf/core] perf pmu: " tip-bot for Andi Kleen
2017-01-28 2:03 ` [PATCH 05/10] perf, tools: Add debug support for outputing alias string Andi Kleen
2017-02-10 7:43 ` [tip:perf/core] perf list: " tip-bot for Andi Kleen
2017-01-28 2:03 ` [PATCH 06/10] perf, tools: Collapse identically named events in perf stat Andi Kleen
2017-02-08 11:31 ` Jiri Olsa
2017-01-28 2:03 ` [PATCH 07/10] perf, tools: Expand PMU events by prefix match Andi Kleen
2017-02-08 11:30 ` Jiri Olsa
2017-01-28 2:03 ` Andi Kleen [this message]
2017-02-08 11:31 ` [PATCH 08/10] perf, tools: Add a simple expression parser for JSON Jiri Olsa
2017-01-28 2:03 ` [PATCH 09/10] perf, tools: Support MetricExpr header in JSON event list Andi Kleen
2017-02-08 11:31 ` Jiri Olsa
2017-01-28 2:03 ` [PATCH 10/10] perf, tools, stat: Output JSON MetricExpr metric Andi Kleen
2017-02-08 11:31 ` Jiri Olsa
2017-02-08 21:51 ` Andi Kleen
2017-02-09 11:39 ` Jiri Olsa
2017-02-09 17:00 ` Andi Kleen
2017-02-09 18:37 ` Jiri Olsa
2017-02-09 18:59 ` Andi Kleen
2017-02-10 8:22 ` Jiri Olsa
2017-02-09 11:42 ` Jiri Olsa
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20170128020345.19007-9-andi@firstfloor.org \
--to=andi@firstfloor.org \
--cc=acme@kernel.org \
--cc=ak@linux.intel.com \
--cc=jolsa@kernel.org \
--cc=linux-kernel@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.