Skip to content

Commit 1bb4ec8

Browse files
anakryikoAlexei Starovoitov
authored andcommitted
selftests/bpf: support simple filtering of stats in veristat
Define simple expressions to filter not just by file and program name, but also by resulting values of collected stats. Support usual equality and inequality operators. Verdict, which is a boolean-like field can be also filtered either as 0/1, failure/success (with f/s, fail/succ, and failure/success aliases) symbols, or as false/true (f/t). Aliases are case insensitive. Currently this filtering is honored only in verification and replay modes. Comparison mode support will be added in next patch. Here's an example of verifying a bunch of BPF object files and emitting only results for successfully validated programs that have more than 100 total instructions processed by BPF verifier, sorted by number of instructions in ascending order: $ sudo ./veristat *.bpf.o -s insns^ -f 'insns>100' There can be many filters (both allow and deny flavors), all of them are combined. Signed-off-by: Andrii Nakryiko <andrii@kernel.org> Link: https://lore.kernel.org/r/20221103055304.2904589-7-andrii@kernel.org Signed-off-by: Alexei Starovoitov <ast@kernel.org>
1 parent d68c07e commit 1bb4ec8

1 file changed

Lines changed: 157 additions & 1 deletion

File tree

tools/testing/selftests/bpf/veristat.c

Lines changed: 157 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,10 +54,30 @@ enum resfmt {
5454
RESFMT_CSV,
5555
};
5656

57+
enum filter_kind {
58+
FILTER_NAME,
59+
FILTER_STAT,
60+
};
61+
62+
enum operator_kind {
63+
OP_EQ, /* == or = */
64+
OP_NEQ, /* != or <> */
65+
OP_LT, /* < */
66+
OP_LE, /* <= */
67+
OP_GT, /* > */
68+
OP_GE, /* >= */
69+
};
70+
5771
struct filter {
72+
enum filter_kind kind;
73+
/* FILTER_NAME */
5874
char *any_glob;
5975
char *file_glob;
6076
char *prog_glob;
77+
/* FILTER_STAT */
78+
enum operator_kind op;
79+
int stat_id;
80+
long value;
6181
};
6282

6383
static struct env {
@@ -271,6 +291,8 @@ static bool should_process_file_prog(const char *filename, const char *prog_name
271291

272292
for (i = 0; i < env.deny_filter_cnt; i++) {
273293
f = &env.deny_filters[i];
294+
if (f->kind != FILTER_NAME)
295+
continue;
274296

275297
if (f->any_glob && glob_matches(filename, f->any_glob))
276298
return false;
@@ -284,8 +306,10 @@ static bool should_process_file_prog(const char *filename, const char *prog_name
284306

285307
for (i = 0; i < env.allow_filter_cnt; i++) {
286308
f = &env.allow_filters[i];
287-
allow_cnt++;
309+
if (f->kind != FILTER_NAME)
310+
continue;
288311

312+
allow_cnt++;
289313
if (f->any_glob) {
290314
if (glob_matches(filename, f->any_glob))
291315
return true;
@@ -306,11 +330,32 @@ static bool should_process_file_prog(const char *filename, const char *prog_name
306330
return allow_cnt == 0;
307331
}
308332

333+
static struct {
334+
enum operator_kind op_kind;
335+
const char *op_str;
336+
} operators[] = {
337+
/* Order of these definitions matter to avoid situations like '<'
338+
* matching part of what is actually a '<>' operator. That is,
339+
* substrings should go last.
340+
*/
341+
{ OP_EQ, "==" },
342+
{ OP_NEQ, "!=" },
343+
{ OP_NEQ, "<>" },
344+
{ OP_LE, "<=" },
345+
{ OP_LT, "<" },
346+
{ OP_GE, ">=" },
347+
{ OP_GT, ">" },
348+
{ OP_EQ, "=" },
349+
};
350+
351+
static bool parse_stat_id(const char *name, size_t len, int *id);
352+
309353
static int append_filter(struct filter **filters, int *cnt, const char *str)
310354
{
311355
struct filter *f;
312356
void *tmp;
313357
const char *p;
358+
int i;
314359

315360
tmp = realloc(*filters, (*cnt + 1) * sizeof(**filters));
316361
if (!tmp)
@@ -320,6 +365,67 @@ static int append_filter(struct filter **filters, int *cnt, const char *str)
320365
f = &(*filters)[*cnt];
321366
memset(f, 0, sizeof(*f));
322367

368+
/* First, let's check if it's a stats filter of the following form:
369+
* <stat><op><value, where:
370+
* - <stat> is one of supported numerical stats (verdict is also
371+
* considered numerical, failure == 0, success == 1);
372+
* - <op> is comparison operator (see `operators` definitions);
373+
* - <value> is an integer (or failure/success, or false/true as
374+
* special aliases for 0 and 1, respectively).
375+
* If the form doesn't match what user provided, we assume file/prog
376+
* glob filter.
377+
*/
378+
for (i = 0; i < ARRAY_SIZE(operators); i++) {
379+
int id;
380+
long val;
381+
const char *end = str;
382+
const char *op_str;
383+
384+
op_str = operators[i].op_str;
385+
p = strstr(str, op_str);
386+
if (!p)
387+
continue;
388+
389+
if (!parse_stat_id(str, p - str, &id)) {
390+
fprintf(stderr, "Unrecognized stat name in '%s'!\n", str);
391+
return -EINVAL;
392+
}
393+
if (id >= FILE_NAME) {
394+
fprintf(stderr, "Non-integer stat is specified in '%s'!\n", str);
395+
return -EINVAL;
396+
}
397+
398+
p += strlen(op_str);
399+
400+
if (strcasecmp(p, "true") == 0 ||
401+
strcasecmp(p, "t") == 0 ||
402+
strcasecmp(p, "success") == 0 ||
403+
strcasecmp(p, "succ") == 0 ||
404+
strcasecmp(p, "s") == 0) {
405+
val = 1;
406+
} else if (strcasecmp(p, "false") == 0 ||
407+
strcasecmp(p, "f") == 0 ||
408+
strcasecmp(p, "failure") == 0 ||
409+
strcasecmp(p, "fail") == 0) {
410+
val = 0;
411+
} else {
412+
errno = 0;
413+
val = strtol(p, (char **)&end, 10);
414+
if (errno || end == p || *end != '\0' ) {
415+
fprintf(stderr, "Invalid integer value in '%s'!\n", str);
416+
return -EINVAL;
417+
}
418+
}
419+
420+
f->kind = FILTER_STAT;
421+
f->stat_id = id;
422+
f->op = operators[i].op_kind;
423+
f->value = val;
424+
425+
*cnt += 1;
426+
return 0;
427+
}
428+
323429
/* File/prog filter can be specified either as '<glob>' or
324430
* '<file-glob>/<prog-glob>'. In the former case <glob> is applied to
325431
* both file and program names. This seems to be way more useful in
@@ -328,6 +434,7 @@ static int append_filter(struct filter **filters, int *cnt, const char *str)
328434
* name. But usually common <glob> seems to be the most useful and
329435
* ergonomic way.
330436
*/
437+
f->kind = FILTER_NAME;
331438
p = strchr(str, '/');
332439
if (!p) {
333440
f->any_glob = strdup(str);
@@ -1317,6 +1424,51 @@ static int handle_comparison_mode(void)
13171424
return 0;
13181425
}
13191426

1427+
static bool is_stat_filter_matched(struct filter *f, const struct verif_stats *stats)
1428+
{
1429+
long value = stats->stats[f->stat_id];
1430+
1431+
switch (f->op) {
1432+
case OP_EQ: return value == f->value;
1433+
case OP_NEQ: return value != f->value;
1434+
case OP_LT: return value < f->value;
1435+
case OP_LE: return value <= f->value;
1436+
case OP_GT: return value > f->value;
1437+
case OP_GE: return value >= f->value;
1438+
}
1439+
1440+
fprintf(stderr, "BUG: unknown filter op %d!\n", f->op);
1441+
return false;
1442+
}
1443+
1444+
static bool should_output_stats(const struct verif_stats *stats)
1445+
{
1446+
struct filter *f;
1447+
int i, allow_cnt = 0;
1448+
1449+
for (i = 0; i < env.deny_filter_cnt; i++) {
1450+
f = &env.deny_filters[i];
1451+
if (f->kind != FILTER_STAT)
1452+
continue;
1453+
1454+
if (is_stat_filter_matched(f, stats))
1455+
return false;
1456+
}
1457+
1458+
for (i = 0; i < env.allow_filter_cnt; i++) {
1459+
f = &env.allow_filters[i];
1460+
if (f->kind != FILTER_STAT)
1461+
continue;
1462+
allow_cnt++;
1463+
1464+
if (is_stat_filter_matched(f, stats))
1465+
return true;
1466+
}
1467+
1468+
/* if there are no stat allowed filters, pass everything through */
1469+
return allow_cnt == 0;
1470+
}
1471+
13201472
static void output_prog_stats(void)
13211473
{
13221474
const struct verif_stats *stats;
@@ -1327,6 +1479,8 @@ static void output_prog_stats(void)
13271479
output_headers(RESFMT_TABLE_CALCLEN);
13281480
for (i = 0; i < env.prog_stat_cnt; i++) {
13291481
stats = &env.prog_stats[i];
1482+
if (!should_output_stats(stats))
1483+
continue;
13301484
output_stats(stats, RESFMT_TABLE_CALCLEN, false);
13311485
last_stat_idx = i;
13321486
}
@@ -1336,6 +1490,8 @@ static void output_prog_stats(void)
13361490
output_headers(env.out_fmt);
13371491
for (i = 0; i < env.prog_stat_cnt; i++) {
13381492
stats = &env.prog_stats[i];
1493+
if (!should_output_stats(stats))
1494+
continue;
13391495
output_stats(stats, env.out_fmt, i == last_stat_idx);
13401496
}
13411497
}

0 commit comments

Comments
 (0)