Skip to content

Commit 0751673

Browse files
Andi Kleenacmel
authored andcommitted
perf tools: Add a simple expression parser for JSON
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 <[email protected]> Acked-by: Jiri Olsa <[email protected]> Link: http://lkml.kernel.org/r/[email protected] [ 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 <[email protected]>
1 parent a820e33 commit 0751673

File tree

7 files changed

+266
-0
lines changed

7 files changed

+266
-0
lines changed

tools/perf/tests/Build

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ perf-y += cpumap.o
3838
perf-y += stat.o
3939
perf-y += event_update.o
4040
perf-y += event-times.o
41+
perf-y += expr.o
4142
perf-y += backward-ring-buffer.o
4243
perf-y += sdt.o
4344
perf-y += is_printable_array.o

tools/perf/tests/builtin-test.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ static struct test generic_tests[] = {
4343
.desc = "Parse event definition strings",
4444
.func = test__parse_events,
4545
},
46+
{
47+
.desc = "Simple expression parser",
48+
.func = test__expr,
49+
},
4650
{
4751
.desc = "PERF_RECORD_* events & perf_sample fields",
4852
.func = test__PERF_RECORD,

tools/perf/tests/expr.c

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
#include "util/debug.h"
2+
#include "util/expr.h"
3+
#include "tests.h"
4+
#include <stdlib.h>
5+
6+
static int test(struct parse_ctx *ctx, const char *e, double val2)
7+
{
8+
double val;
9+
10+
if (expr__parse(&val, ctx, &e))
11+
TEST_ASSERT_VAL("parse test failed", 0);
12+
TEST_ASSERT_VAL("unexpected value", val == val2);
13+
return 0;
14+
}
15+
16+
int test__expr(int subtest __maybe_unused)
17+
{
18+
const char *p;
19+
const char **other;
20+
double val;
21+
int ret;
22+
struct parse_ctx ctx;
23+
int num_other;
24+
25+
expr__ctx_init(&ctx);
26+
expr__add_id(&ctx, "FOO", 1);
27+
expr__add_id(&ctx, "BAR", 2);
28+
29+
ret = test(&ctx, "1+1", 2);
30+
ret |= test(&ctx, "FOO+BAR", 3);
31+
ret |= test(&ctx, "(BAR/2)%2", 1);
32+
ret |= test(&ctx, "1 - -4", 5);
33+
ret |= test(&ctx, "(FOO-1)*2 + (BAR/2)%2 - -4", 5);
34+
35+
if (ret)
36+
return ret;
37+
38+
p = "FOO/0";
39+
ret = expr__parse(&val, &ctx, &p);
40+
TEST_ASSERT_VAL("division by zero", ret == 1);
41+
42+
p = "BAR/";
43+
ret = expr__parse(&val, &ctx, &p);
44+
TEST_ASSERT_VAL("missing operand", ret == 1);
45+
46+
TEST_ASSERT_VAL("find other",
47+
expr__find_other("FOO + BAR + BAZ + BOZO", "FOO", &other, &num_other) == 0);
48+
TEST_ASSERT_VAL("find other", num_other == 3);
49+
TEST_ASSERT_VAL("find other", !strcmp(other[0], "BAR"));
50+
TEST_ASSERT_VAL("find other", !strcmp(other[1], "BAZ"));
51+
TEST_ASSERT_VAL("find other", !strcmp(other[2], "BOZO"));
52+
TEST_ASSERT_VAL("find other", other[3] == NULL);
53+
free((void *)other);
54+
55+
return 0;
56+
}

tools/perf/tests/tests.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ int test__sample_parsing(int subtest);
6262
int test__keep_tracking(int subtest);
6363
int test__parse_no_sample_id_all(int subtest);
6464
int test__dwarf_unwind(int subtest);
65+
int test__expr(int subtest);
6566
int test__hists_filter(int subtest);
6667
int test__mmap_thread_lookup(int subtest);
6768
int test__thread_mg_share(int subtest);

tools/perf/util/Build

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ libperf-y += mem-events.o
9090
libperf-y += vsprintf.o
9191
libperf-y += drv_configs.o
9292
libperf-y += time-utils.o
93+
libperf-y += expr-bison.o
9394

9495
libperf-$(CONFIG_LIBBPF) += bpf-loader.o
9596
libperf-$(CONFIG_BPF_PROLOGUE) += bpf-prologue.o
@@ -142,6 +143,10 @@ $(OUTPUT)util/parse-events-bison.c: util/parse-events.y
142143
$(call rule_mkdir)
143144
$(Q)$(call echo-cmd,bison)$(BISON) -v util/parse-events.y -d $(PARSER_DEBUG_BISON) -o $@ -p parse_events_
144145

146+
$(OUTPUT)util/expr-bison.c: util/expr.y
147+
$(call rule_mkdir)
148+
$(Q)$(call echo-cmd,bison)$(BISON) -v util/expr.y -d $(PARSER_DEBUG_BISON) -o $@ -p expr__
149+
145150
$(OUTPUT)util/pmu-flex.c: util/pmu.l $(OUTPUT)util/pmu-bison.c
146151
$(call rule_mkdir)
147152
$(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
154159
CFLAGS_pmu-flex.o += -w
155160
CFLAGS_parse-events-bison.o += -DYYENABLE_NLS=0 -w
156161
CFLAGS_pmu-bison.o += -DYYENABLE_NLS=0 -DYYLTYPE_IS_TRIVIAL=0 -w
162+
CFLAGS_expr-bison.o += -DYYENABLE_NLS=0 -DYYLTYPE_IS_TRIVIAL=0 -w
157163

158164
$(OUTPUT)util/parse-events.o: $(OUTPUT)util/parse-events-flex.c $(OUTPUT)util/parse-events-bison.c
159165
$(OUTPUT)util/pmu.o: $(OUTPUT)util/pmu-flex.c $(OUTPUT)util/pmu-bison.c

tools/perf/util/expr.h

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#ifndef PARSE_CTX_H
2+
#define PARSE_CTX_H 1
3+
4+
#define EXPR_MAX_OTHER 8
5+
#define MAX_PARSE_ID EXPR_MAX_OTHER
6+
7+
struct parse_id {
8+
const char *name;
9+
double val;
10+
};
11+
12+
struct parse_ctx {
13+
int num_ids;
14+
struct parse_id ids[MAX_PARSE_ID];
15+
};
16+
17+
void expr__ctx_init(struct parse_ctx *ctx);
18+
void expr__add_id(struct parse_ctx *ctx, const char *id, double val);
19+
#ifndef IN_EXPR_Y
20+
int expr__parse(double *final_val, struct parse_ctx *ctx, const char **pp);
21+
#endif
22+
int expr__find_other(const char *p, const char *one, const char ***other,
23+
int *num_other);
24+
25+
#endif

tools/perf/util/expr.y

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
/* Simple expression parser */
2+
%{
3+
#include "util.h"
4+
#include "util/debug.h"
5+
#define IN_EXPR_Y 1
6+
#include "expr.h"
7+
#include <string.h>
8+
9+
#define MAXIDLEN 256
10+
%}
11+
12+
%pure-parser
13+
%parse-param { double *final_val }
14+
%parse-param { struct parse_ctx *ctx }
15+
%parse-param { const char **pp }
16+
%lex-param { const char **pp }
17+
18+
%union {
19+
double num;
20+
char id[MAXIDLEN+1];
21+
}
22+
23+
%token <num> NUMBER
24+
%token <id> ID
25+
%left '|'
26+
%left '^'
27+
%left '&'
28+
%left '-' '+'
29+
%left '*' '/' '%'
30+
%left NEG NOT
31+
%type <num> expr
32+
33+
%{
34+
static int expr__lex(YYSTYPE *res, const char **pp);
35+
36+
static void expr__error(double *final_val __maybe_unused,
37+
struct parse_ctx *ctx __maybe_unused,
38+
const char **pp __maybe_unused,
39+
const char *s)
40+
{
41+
pr_debug("%s\n", s);
42+
}
43+
44+
static int lookup_id(struct parse_ctx *ctx, char *id, double *val)
45+
{
46+
int i;
47+
48+
for (i = 0; i < ctx->num_ids; i++) {
49+
if (!strcasecmp(ctx->ids[i].name, id)) {
50+
*val = ctx->ids[i].val;
51+
return 0;
52+
}
53+
}
54+
return -1;
55+
}
56+
57+
%}
58+
%%
59+
60+
all_expr: expr { *final_val = $1; }
61+
;
62+
63+
expr: NUMBER
64+
| ID { if (lookup_id(ctx, $1, &$$) < 0) {
65+
pr_debug("%s not found", $1);
66+
YYABORT;
67+
}
68+
}
69+
| expr '+' expr { $$ = $1 + $3; }
70+
| expr '-' expr { $$ = $1 - $3; }
71+
| expr '*' expr { $$ = $1 * $3; }
72+
| expr '/' expr { if ($3 == 0) YYABORT; $$ = $1 / $3; }
73+
| expr '%' expr { if ((long)$3 == 0) YYABORT; $$ = (long)$1 % (long)$3; }
74+
| '-' expr %prec NEG { $$ = -$2; }
75+
| '(' expr ')' { $$ = $2; }
76+
;
77+
78+
%%
79+
80+
static int expr__symbol(YYSTYPE *res, const char *p, const char **pp)
81+
{
82+
char *dst = res->id;
83+
const char *s = p;
84+
85+
while (isalnum(*p) || *p == '_' || *p == '.') {
86+
if (p - s >= MAXIDLEN)
87+
return -1;
88+
*dst++ = *p++;
89+
}
90+
*dst = 0;
91+
*pp = p;
92+
return ID;
93+
}
94+
95+
static int expr__lex(YYSTYPE *res, const char **pp)
96+
{
97+
int tok;
98+
const char *s;
99+
const char *p = *pp;
100+
101+
while (isspace(*p))
102+
p++;
103+
s = p;
104+
switch (*p++) {
105+
case 'a' ... 'z':
106+
case 'A' ... 'Z':
107+
return expr__symbol(res, p - 1, pp);
108+
case '0' ... '9': case '.':
109+
res->num = strtod(s, (char **)&p);
110+
tok = NUMBER;
111+
break;
112+
default:
113+
tok = *s;
114+
break;
115+
}
116+
*pp = p;
117+
return tok;
118+
}
119+
120+
/* Caller must make sure id is allocated */
121+
void expr__add_id(struct parse_ctx *ctx, const char *name, double val)
122+
{
123+
int idx;
124+
assert(ctx->num_ids < MAX_PARSE_ID);
125+
idx = ctx->num_ids++;
126+
ctx->ids[idx].name = name;
127+
ctx->ids[idx].val = val;
128+
}
129+
130+
void expr__ctx_init(struct parse_ctx *ctx)
131+
{
132+
ctx->num_ids = 0;
133+
}
134+
135+
int expr__find_other(const char *p, const char *one, const char ***other,
136+
int *num_otherp)
137+
{
138+
const char *orig = p;
139+
int err = -1;
140+
int num_other;
141+
142+
*other = malloc((EXPR_MAX_OTHER + 1) * sizeof(char *));
143+
if (!*other)
144+
return -1;
145+
146+
num_other = 0;
147+
for (;;) {
148+
YYSTYPE val;
149+
int tok = expr__lex(&val, &p);
150+
if (tok == 0) {
151+
err = 0;
152+
break;
153+
}
154+
if (tok == ID && strcasecmp(one, val.id)) {
155+
if (num_other >= EXPR_MAX_OTHER - 1) {
156+
pr_debug("Too many extra events in %s\n", orig);
157+
break;
158+
}
159+
(*other)[num_other] = strdup(val.id);
160+
if (!(*other)[num_other])
161+
return -1;
162+
num_other++;
163+
}
164+
}
165+
(*other)[num_other] = NULL;
166+
*num_otherp = num_other;
167+
if (err) {
168+
*num_otherp = 0;
169+
free(*other);
170+
*other = NULL;
171+
}
172+
return err;
173+
}

0 commit comments

Comments
 (0)