perf tools: Add a simple expression parser for JSON
authorAndi Kleen <ak@linux.intel.com>
Mon, 20 Mar 2017 20:17:05 +0000 (13:17 -0700)
committerArnaldo Carvalho de Melo <acme@redhat.com>
Thu, 23 Mar 2017 14:39:27 +0000 (11:39 -0300)
Add a simple expression parser good enough to parse JSON relation
expressions. The parser is implemented using bison.

This is just intended as an simple parser for internal usage in the
event lists, not the beginning of a "perf scripting language"

v2: Use expr__ prefix instead of expr_
    Support multiple free variables for parser

Committer note:

The v2 patch had:

  %define api.pure full

In expr.y, that is a feature introduced in bison 2.7, to have reentrant
parsers, not using global variables, which would make tools/perf stop
building with the bison version shipped in older distros, so Andi
realised that the other parsers (e.g. parse-events.y) were using:

  %pure-parser

Which is present in older versions of bison and fits the bill.

I added:

  CFLAGS_expr-bison.o += -DYYENABLE_NLS=0 -DYYLTYPE_IS_TRIVIAL=0 -w

To finally make it build, copying what was there for pmu-bison.o,
another parser.

Signed-off-by: Andi Kleen <ak@linux.intel.com>
Acked-by: Jiri Olsa <jolsa@kernel.org>
Link: http://lkml.kernel.org/r/20170320201711.14142-8-andi@firstfloor.org
[ stdlib.h is needed in tests/expr.c for free() fixing build in systems such as ubuntu:16.04-x-s390 ]
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
tools/perf/tests/Build
tools/perf/tests/builtin-test.c
tools/perf/tests/expr.c [new file with mode: 0644]
tools/perf/tests/tests.h
tools/perf/util/Build
tools/perf/util/expr.h [new file with mode: 0644]
tools/perf/util/expr.y [new file with mode: 0644]

index 1cb3d9b540e9eda133f4548efc9316ad0d014898..af58ebc243ef635ff2ddc6efcd26b377cc24a30b 100644 (file)
@@ -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
index 83c4669cbc5b9e30576321026dd38d68aa117fde..86822969e8a8d40a5523cbfc66b5812f3ec77208 100644 (file)
@@ -43,6 +43,10 @@ static struct test generic_tests[] = {
                .desc = "Parse event definition strings",
                .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 (file)
index 0000000..6c6a374
--- /dev/null
@@ -0,0 +1,56 @@
+#include "util/debug.h"
+#include "util/expr.h"
+#include "tests.h"
+#include <stdlib.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;
+       const char **other;
+       double val;
+       int ret;
+       struct parse_ctx ctx;
+       int num_other;
+
+       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 + BAZ + BOZO", "FOO", &other, &num_other) == 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);
+       free((void *)other);
+
+       return 0;
+}
index 1fa9b9d83aa51ba80beb9df29b48ee58be7f4619..6318596294032602b2858acf42c2aa80993fdac9 100644 (file)
@@ -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);
index fb4f42f1bb3858fd463f3ac4f286d7e897f7d663..2ae92da613dd61ad1d6302c7ff4b1ccdc033e6df 100644 (file)
@@ -90,6 +90,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
@@ -142,6 +143,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
@@ -154,6 +159,7 @@ CFLAGS_parse-events-flex.o  += -w
 CFLAGS_pmu-flex.o           += -w
 CFLAGS_parse-events-bison.o += -DYYENABLE_NLS=0 -w
 CFLAGS_pmu-bison.o          += -DYYENABLE_NLS=0 -DYYLTYPE_IS_TRIVIAL=0 -w
+CFLAGS_expr-bison.o         += -DYYENABLE_NLS=0 -DYYLTYPE_IS_TRIVIAL=0 -w
 
 $(OUTPUT)util/parse-events.o: $(OUTPUT)util/parse-events-flex.c $(OUTPUT)util/parse-events-bison.c
 $(OUTPUT)util/pmu.o: $(OUTPUT)util/pmu-flex.c $(OUTPUT)util/pmu-bison.c
diff --git a/tools/perf/util/expr.h b/tools/perf/util/expr.h
new file mode 100644 (file)
index 0000000..9c2760a
--- /dev/null
@@ -0,0 +1,25 @@
+#ifndef PARSE_CTX_H
+#define PARSE_CTX_H 1
+
+#define EXPR_MAX_OTHER 8
+#define MAX_PARSE_ID EXPR_MAX_OTHER
+
+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,
+               int *num_other);
+
+#endif
diff --git a/tools/perf/util/expr.y b/tools/perf/util/expr.y
new file mode 100644 (file)
index 0000000..954556b
--- /dev/null
@@ -0,0 +1,173 @@
+/* Simple expression parser */
+%{
+#include "util.h"
+#include "util/debug.h"
+#define IN_EXPR_Y 1
+#include "expr.h"
+#include <string.h>
+
+#define MAXIDLEN 256
+%}
+
+%pure-parser
+%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\n", 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,
+                    int *num_otherp)
+{
+       const char *orig = p;
+       int err = -1;
+       int num_other;
+
+       *other = malloc((EXPR_MAX_OTHER + 1) * sizeof(char *));
+       if (!*other)
+               return -1;
+
+       num_other = 0;
+       for (;;) {
+               YYSTYPE val;
+               int tok = expr__lex(&val, &p);
+               if (tok == 0) {
+                       err = 0;
+                       break;
+               }
+               if (tok == ID && strcasecmp(one, val.id)) {
+                       if (num_other >= EXPR_MAX_OTHER - 1) {
+                               pr_debug("Too many extra events in %s\n", orig);
+                               break;
+                       }
+                       (*other)[num_other] = strdup(val.id);
+                       if (!(*other)[num_other])
+                               return -1;
+                       num_other++;
+               }
+       }
+       (*other)[num_other] = NULL;
+       *num_otherp = num_other;
+       if (err) {
+               *num_otherp = 0;
+               free(*other);
+               *other = NULL;
+       }
+       return err;
+}