Skip to content

Commit fa9bb59

Browse files
anakryikoAlexei Starovoitov
authored andcommitted
selftests/bpf: support stats ordering in comparison mode in veristat
Introduce the concept of "stat variant", by which it's possible to specify whether to use the value from A (baseline) side, B (comparison or control) side, the absolute difference value or relative (percentage) difference value. To support specifying this, veristat recognizes `_a`, `_b`, `_diff`, `_pct` suffixes, which can be appended to stat name(s). In non-comparison mode variants are ignored (there is only `_a` variant effectively), if no variant suffix is provided, `_b` is assumed, as control group is of primary interest in comparison mode. These stat variants can be flexibly combined with asc/desc orders. Here's an example of ordering results first by verdict match/mismatch (or n/a if one of the sides is missing; n/a is always considered to be the lowest value), and within each match/mismatch/n/a group further sort by number of instructions in B side. In this case we don't have MISMATCH cases, but N/A are split from MATCH, demonstrating this custom ordering. $ ./veristat -e file,prog,verdict,insns -s verdict_diff,insns_b_ -C ~/base.csv ~/comp.csv File Program Verdict (A) Verdict (B) Verdict (DIFF) Insns (A) Insns (B) Insns (DIFF) ------------------ ------------------------------ ----------- ----------- -------------- --------- --------- -------------- bpf_xdp.o tail_lb_ipv6 N/A success N/A N/A 151895 N/A bpf_xdp.o tail_nodeport_nat_egress_ipv4 N/A success N/A N/A 15619 N/A bpf_xdp.o tail_nodeport_ipv6_dsr N/A success N/A N/A 1206 N/A bpf_xdp.o tail_nodeport_ipv4_dsr N/A success N/A N/A 1162 N/A bpf_alignchecker.o tail_icmp6_send_echo_reply N/A failure N/A N/A 74 N/A bpf_alignchecker.o __send_drop_notify success N/A N/A 53 N/A N/A bpf_host.o __send_drop_notify success N/A N/A 53 N/A N/A bpf_host.o cil_from_host success N/A N/A 762 N/A N/A bpf_xdp.o tail_lb_ipv4 success success MATCH 71736 73430 +1694 (+2.36%) bpf_xdp.o tail_handle_nat_fwd_ipv4 success success MATCH 21547 20920 -627 (-2.91%) bpf_xdp.o tail_rev_nodeport_lb6 success success MATCH 17954 17905 -49 (-0.27%) bpf_xdp.o tail_handle_nat_fwd_ipv6 success success MATCH 16974 17039 +65 (+0.38%) bpf_xdp.o tail_nodeport_nat_ingress_ipv4 success success MATCH 7658 7713 +55 (+0.72%) bpf_xdp.o tail_rev_nodeport_lb4 success success MATCH 7126 6934 -192 (-2.69%) bpf_xdp.o tail_nodeport_nat_ingress_ipv6 success success MATCH 6405 6397 -8 (-0.12%) bpf_xdp.o tail_nodeport_nat_ipv6_egress failure failure MATCH 752 752 +0 (+0.00%) bpf_xdp.o cil_xdp_entry success success MATCH 423 423 +0 (+0.00%) bpf_xdp.o __send_drop_notify success success MATCH 151 151 +0 (+0.00%) bpf_alignchecker.o tail_icmp6_handle_ns failure failure MATCH 33 33 +0 (+0.00%) ------------------ ------------------------------ ----------- ----------- -------------- --------- --------- -------------- Signed-off-by: Andrii Nakryiko <andrii@kernel.org> Link: https://lore.kernel.org/r/20221103055304.2904589-10-andrii@kernel.org Signed-off-by: Alexei Starovoitov <ast@kernel.org>
1 parent a571084 commit fa9bb59

1 file changed

Lines changed: 182 additions & 10 deletions

File tree

tools/testing/selftests/bpf/veristat.c

Lines changed: 182 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include <bpf/libbpf.h>
1818
#include <libelf.h>
1919
#include <gelf.h>
20+
#include <float.h>
2021

2122
enum stat_id {
2223
VERDICT,
@@ -34,6 +35,45 @@ enum stat_id {
3435
NUM_STATS_CNT = FILE_NAME - VERDICT,
3536
};
3637

38+
/* In comparison mode each stat can specify up to four different values:
39+
* - A side value;
40+
* - B side value;
41+
* - absolute diff value;
42+
* - relative (percentage) diff value.
43+
*
44+
* When specifying stat specs in comparison mode, user can use one of the
45+
* following variant suffixes to specify which exact variant should be used for
46+
* ordering or filtering:
47+
* - `_a` for A side value;
48+
* - `_b` for B side value;
49+
* - `_diff` for absolute diff value;
50+
* - `_pct` for relative (percentage) diff value.
51+
*
52+
* If no variant suffix is provided, then `_b` (control data) is assumed.
53+
*
54+
* As an example, let's say instructions stat has the following output:
55+
*
56+
* Insns (A) Insns (B) Insns (DIFF)
57+
* --------- --------- --------------
58+
* 21547 20920 -627 (-2.91%)
59+
*
60+
* Then:
61+
* - 21547 is A side value (insns_a);
62+
* - 20920 is B side value (insns_b);
63+
* - -627 is absolute diff value (insns_diff);
64+
* - -2.91% is relative diff value (insns_pct).
65+
*
66+
* For verdict there is no verdict_pct variant.
67+
* For file and program name, _a and _b variants are equivalent and there are
68+
* no _diff or _pct variants.
69+
*/
70+
enum stat_variant {
71+
VARIANT_A,
72+
VARIANT_B,
73+
VARIANT_DIFF,
74+
VARIANT_PCT,
75+
};
76+
3777
struct verif_stats {
3878
char *file_name;
3979
char *prog_name;
@@ -53,6 +93,7 @@ struct verif_stats_join {
5393
struct stat_specs {
5494
int spec_cnt;
5595
enum stat_id ids[ALL_STATS_CNT];
96+
enum stat_variant variants[ALL_STATS_CNT];
5697
bool asc[ALL_STATS_CNT];
5798
int lens[ALL_STATS_CNT * 3]; /* 3x for comparison mode */
5899
};
@@ -86,6 +127,7 @@ struct filter {
86127
/* FILTER_STAT */
87128
enum operator_kind op;
88129
int stat_id;
130+
enum stat_variant stat_var;
89131
long value;
90132
};
91133

@@ -360,7 +402,7 @@ static struct {
360402
{ OP_EQ, "=" },
361403
};
362404

363-
static bool parse_stat_id(const char *name, size_t len, int *id);
405+
static bool parse_stat_id_var(const char *name, size_t len, int *id, enum stat_variant *var);
364406

365407
static int append_filter(struct filter **filters, int *cnt, const char *str)
366408
{
@@ -388,6 +430,7 @@ static int append_filter(struct filter **filters, int *cnt, const char *str)
388430
* glob filter.
389431
*/
390432
for (i = 0; i < ARRAY_SIZE(operators); i++) {
433+
enum stat_variant var;
391434
int id;
392435
long val;
393436
const char *end = str;
@@ -398,7 +441,7 @@ static int append_filter(struct filter **filters, int *cnt, const char *str)
398441
if (!p)
399442
continue;
400443

401-
if (!parse_stat_id(str, p - str, &id)) {
444+
if (!parse_stat_id_var(str, p - str, &id, &var)) {
402445
fprintf(stderr, "Unrecognized stat name in '%s'!\n", str);
403446
return -EINVAL;
404447
}
@@ -431,6 +474,7 @@ static int append_filter(struct filter **filters, int *cnt, const char *str)
431474

432475
f->kind = FILTER_STAT;
433476
f->stat_id = id;
477+
f->stat_var = var;
434478
f->op = operators[i].op_kind;
435479
f->value = val;
436480

@@ -556,22 +600,52 @@ static struct stat_def {
556600
[MARK_READ_MAX_LEN] = { "Max mark read length", {"max_mark_read_len", "mark_read"}, },
557601
};
558602

559-
static bool parse_stat_id(const char *name, size_t len, int *id)
603+
static bool parse_stat_id_var(const char *name, size_t len, int *id, enum stat_variant *var)
560604
{
561-
int i, j;
605+
static const char *var_sfxs[] = {
606+
[VARIANT_A] = "_a",
607+
[VARIANT_B] = "_b",
608+
[VARIANT_DIFF] = "_diff",
609+
[VARIANT_PCT] = "_pct",
610+
};
611+
int i, j, k;
562612

563613
for (i = 0; i < ARRAY_SIZE(stat_defs); i++) {
564614
struct stat_def *def = &stat_defs[i];
615+
size_t alias_len, sfx_len;
616+
const char *alias;
565617

566618
for (j = 0; j < ARRAY_SIZE(stat_defs[i].names); j++) {
619+
alias = def->names[j];
620+
if (!alias)
621+
continue;
567622

568-
if (!def->names[j] ||
569-
strlen(def->names[j]) != len ||
570-
strncmp(def->names[j], name, len) != 0)
623+
alias_len = strlen(alias);
624+
if (strncmp(name, alias, alias_len) != 0)
571625
continue;
572626

573-
*id = i;
574-
return true;
627+
if (alias_len == len) {
628+
/* If no variant suffix is specified, we
629+
* assume control group (just in case we are
630+
* in comparison mode. Variant is ignored in
631+
* non-comparison mode.
632+
*/
633+
*var = VARIANT_B;
634+
*id = i;
635+
return true;
636+
}
637+
638+
for (k = 0; k < ARRAY_SIZE(var_sfxs); k++) {
639+
sfx_len = strlen(var_sfxs[k]);
640+
if (alias_len + sfx_len != len)
641+
continue;
642+
643+
if (strncmp(name + alias_len, var_sfxs[k], sfx_len) == 0) {
644+
*var = (enum stat_variant)k;
645+
*id = i;
646+
return true;
647+
}
648+
}
575649
}
576650
}
577651

@@ -593,6 +667,7 @@ static int parse_stat(const char *stat_name, struct stat_specs *specs)
593667
int id;
594668
bool has_order = false, is_asc = false;
595669
size_t len = strlen(stat_name);
670+
enum stat_variant var;
596671

597672
if (specs->spec_cnt >= ARRAY_SIZE(specs->ids)) {
598673
fprintf(stderr, "Can't specify more than %zd stats\n", ARRAY_SIZE(specs->ids));
@@ -605,12 +680,13 @@ static int parse_stat(const char *stat_name, struct stat_specs *specs)
605680
len -= 1;
606681
}
607682

608-
if (!parse_stat_id(stat_name, len, &id)) {
683+
if (!parse_stat_id_var(stat_name, len, &id, &var)) {
609684
fprintf(stderr, "Unrecognized stat name '%s'\n", stat_name);
610685
return -ESRCH;
611686
}
612687

613688
specs->ids[specs->spec_cnt] = id;
689+
specs->variants[specs->spec_cnt] = var;
614690
specs->asc[specs->spec_cnt] = has_order ? is_asc : stat_defs[id].asc_by_default;
615691
specs->spec_cnt++;
616692

@@ -900,6 +976,99 @@ static int cmp_prog_stats(const void *v1, const void *v2)
900976
return strcmp(s1->prog_name, s2->prog_name);
901977
}
902978

979+
static void fetch_join_stat_value(const struct verif_stats_join *s,
980+
enum stat_id id, enum stat_variant var,
981+
const char **str_val,
982+
double *num_val)
983+
{
984+
long v1, v2;
985+
986+
if (id == FILE_NAME) {
987+
*str_val = s->file_name;
988+
return;
989+
}
990+
if (id == PROG_NAME) {
991+
*str_val = s->prog_name;
992+
return;
993+
}
994+
995+
v1 = s->stats_a ? s->stats_a->stats[id] : 0;
996+
v2 = s->stats_b ? s->stats_b->stats[id] : 0;
997+
998+
switch (var) {
999+
case VARIANT_A:
1000+
if (!s->stats_a)
1001+
*num_val = -DBL_MAX;
1002+
else
1003+
*num_val = s->stats_a->stats[id];
1004+
return;
1005+
case VARIANT_B:
1006+
if (!s->stats_b)
1007+
*num_val = -DBL_MAX;
1008+
else
1009+
*num_val = s->stats_b->stats[id];
1010+
return;
1011+
case VARIANT_DIFF:
1012+
if (!s->stats_a || !s->stats_b)
1013+
*num_val = -DBL_MAX;
1014+
else
1015+
*num_val = (double)(v2 - v1);
1016+
return;
1017+
case VARIANT_PCT:
1018+
if (!s->stats_a || !s->stats_b) {
1019+
*num_val = -DBL_MAX;
1020+
} else if (v1 == 0) {
1021+
if (v1 == v2)
1022+
*num_val = 0.0;
1023+
else
1024+
*num_val = v2 < v1 ? -100.0 : 100.0;
1025+
} else {
1026+
*num_val = (v2 - v1) * 100.0 / v1;
1027+
}
1028+
return;
1029+
}
1030+
}
1031+
1032+
static int cmp_join_stat(const struct verif_stats_join *s1,
1033+
const struct verif_stats_join *s2,
1034+
enum stat_id id, enum stat_variant var, bool asc)
1035+
{
1036+
const char *str1 = NULL, *str2 = NULL;
1037+
double v1, v2;
1038+
int cmp = 0;
1039+
1040+
fetch_join_stat_value(s1, id, var, &str1, &v1);
1041+
fetch_join_stat_value(s2, id, var, &str2, &v2);
1042+
1043+
if (str1)
1044+
cmp = strcmp(str1, str2);
1045+
else if (v1 != v2)
1046+
cmp = v1 < v2 ? -1 : 1;
1047+
1048+
return asc ? cmp : -cmp;
1049+
}
1050+
1051+
static int cmp_join_stats(const void *v1, const void *v2)
1052+
{
1053+
const struct verif_stats_join *s1 = v1, *s2 = v2;
1054+
int i, cmp;
1055+
1056+
for (i = 0; i < env.sort_spec.spec_cnt; i++) {
1057+
cmp = cmp_join_stat(s1, s2,
1058+
env.sort_spec.ids[i],
1059+
env.sort_spec.variants[i],
1060+
env.sort_spec.asc[i]);
1061+
if (cmp != 0)
1062+
return cmp;
1063+
}
1064+
1065+
/* always disambiguate with file+prog, which are unique */
1066+
cmp = strcmp(s1->file_name, s2->file_name);
1067+
if (cmp != 0)
1068+
return cmp;
1069+
return strcmp(s1->prog_name, s2->prog_name);
1070+
}
1071+
9031072
#define HEADER_CHAR '-'
9041073
#define COLUMN_SEP " "
9051074

@@ -1477,6 +1646,9 @@ static int handle_comparison_mode(void)
14771646
env.join_stat_cnt += 1;
14781647
}
14791648

1649+
/* now sort joined results accorsing to sort spec */
1650+
qsort(env.join_stats, env.join_stat_cnt, sizeof(*env.join_stats), cmp_join_stats);
1651+
14801652
/* for human-readable table output we need to do extra pass to
14811653
* calculate column widths, so we substitute current output format
14821654
* with RESFMT_TABLE_CALCLEN and later revert it back to RESFMT_TABLE

0 commit comments

Comments
 (0)