Skip to content

Commit a571084

Browse files
anakryikoAlexei Starovoitov
authored andcommitted
selftests/bpf: handle missing records in comparison mode better in veristat
When comparing two datasets, if either side is missing corresponding record with the same file and prog name, currently veristat emits misleading zeros/failures, and even tried to calculate a difference, even though there is no data to compare against. This patch improves internal logic of handling such situations. Now we'll emit "N/A" in places where data is missing and comparison is non-sensical. As an example, in an artificially truncated and mismatched Cilium results, the output looks like below: $ ./veristat -e file,prog,verdict,insns -C ~/base.csv ~/comp.csv File Program Verdict (A) Verdict (B) Verdict (DIFF) Insns (A) Insns (B) Insns (DIFF) ------------------ ------------------------------ ----------- ----------- -------------- --------- --------- -------------- bpf_alignchecker.o __send_drop_notify success N/A N/A 53 N/A N/A bpf_alignchecker.o tail_icmp6_handle_ns failure failure MATCH 33 33 +0 (+0.00%) bpf_alignchecker.o tail_icmp6_send_echo_reply N/A failure N/A N/A 74 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 __send_drop_notify success success MATCH 151 151 +0 (+0.00%) bpf_xdp.o cil_xdp_entry success success MATCH 423 423 +0 (+0.00%) bpf_xdp.o tail_handle_nat_fwd_ipv4 success success MATCH 21547 20920 -627 (-2.91%) bpf_xdp.o tail_handle_nat_fwd_ipv6 success success MATCH 16974 17039 +65 (+0.38%) bpf_xdp.o tail_lb_ipv4 success success MATCH 71736 73430 +1694 (+2.36%) bpf_xdp.o tail_lb_ipv6 N/A success N/A N/A 151895 N/A bpf_xdp.o tail_nodeport_ipv4_dsr N/A success N/A N/A 1162 N/A bpf_xdp.o tail_nodeport_ipv6_dsr N/A success N/A N/A 1206 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_nat_ingress_ipv4 success success MATCH 7658 7713 +55 (+0.72%) 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 tail_rev_nodeport_lb4 success success MATCH 7126 6934 -192 (-2.69%) bpf_xdp.o tail_rev_nodeport_lb6 success success MATCH 17954 17905 -49 (-0.27%) ------------------ ------------------------------ ----------- ----------- -------------- --------- --------- -------------- Internally veristat now separates joining two datasets and remembering the join, and actually emitting a comparison view. This will come handy when we add support for filtering and custom ordering in comparison mode. Signed-off-by: Andrii Nakryiko <andrii@kernel.org> Link: https://lore.kernel.org/r/20221103055304.2904589-9-andrii@kernel.org Signed-off-by: Alexei Starovoitov <ast@kernel.org>
1 parent 7753440 commit a571084

1 file changed

Lines changed: 110 additions & 37 deletions

File tree

tools/testing/selftests/bpf/veristat.c

Lines changed: 110 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,15 @@ struct verif_stats {
4141
long stats[NUM_STATS_CNT];
4242
};
4343

44+
/* joined comparison mode stats */
45+
struct verif_stats_join {
46+
char *file_name;
47+
char *prog_name;
48+
49+
const struct verif_stats *stats_a;
50+
const struct verif_stats *stats_b;
51+
};
52+
4453
struct stat_specs {
4554
int spec_cnt;
4655
enum stat_id ids[ALL_STATS_CNT];
@@ -97,6 +106,9 @@ static struct env {
97106
struct verif_stats *baseline_stats;
98107
int baseline_stat_cnt;
99108

109+
struct verif_stats_join *join_stats;
110+
int join_stat_cnt;
111+
100112
struct stat_specs output_spec;
101113
struct stat_specs sort_spec;
102114

@@ -518,6 +530,15 @@ static const struct stat_specs default_sort_spec = {
518530
.asc = { true, true, },
519531
};
520532

533+
/* sorting for comparison mode to join two data sets */
534+
static const struct stat_specs join_sort_spec = {
535+
.spec_cnt = 2,
536+
.ids = {
537+
FILE_NAME, PROG_NAME,
538+
},
539+
.asc = { true, true, },
540+
};
541+
521542
static struct stat_def {
522543
const char *header;
523544
const char *names[4];
@@ -934,21 +955,24 @@ static void prepare_value(const struct verif_stats *s, enum stat_id id,
934955
{
935956
switch (id) {
936957
case FILE_NAME:
937-
*str = s->file_name;
958+
*str = s ? s->file_name : "N/A";
938959
break;
939960
case PROG_NAME:
940-
*str = s->prog_name;
961+
*str = s ? s->prog_name : "N/A";
941962
break;
942963
case VERDICT:
943-
*str = s->stats[VERDICT] ? "success" : "failure";
964+
if (!s)
965+
*str = "N/A";
966+
else
967+
*str = s->stats[VERDICT] ? "success" : "failure";
944968
break;
945969
case DURATION:
946970
case TOTAL_INSNS:
947971
case TOTAL_STATES:
948972
case PEAK_STATES:
949973
case MAX_STATES_PER_INSN:
950974
case MARK_READ_MAX_LEN:
951-
*val = s->stats[id];
975+
*val = s ? s->stats[id] : 0;
952976
break;
953977
default:
954978
fprintf(stderr, "Unrecognized stat #%d\n", id);
@@ -1223,9 +1247,11 @@ static void output_comp_headers(enum resfmt fmt)
12231247
output_comp_header_underlines();
12241248
}
12251249

1226-
static void output_comp_stats(const struct verif_stats *base, const struct verif_stats *comp,
1250+
static void output_comp_stats(const struct verif_stats_join *join_stats,
12271251
enum resfmt fmt, bool last)
12281252
{
1253+
const struct verif_stats *base = join_stats->stats_a;
1254+
const struct verif_stats *comp = join_stats->stats_b;
12291255
char base_buf[1024] = {}, comp_buf[1024] = {}, diff_buf[1024] = {};
12301256
int i;
12311257

@@ -1243,33 +1269,45 @@ static void output_comp_stats(const struct verif_stats *base, const struct verif
12431269
/* normalize all the outputs to be in string buffers for simplicity */
12441270
if (is_key_stat(id)) {
12451271
/* key stats (file and program name) are always strings */
1246-
if (base != &fallback_stats)
1272+
if (base)
12471273
snprintf(base_buf, sizeof(base_buf), "%s", base_str);
12481274
else
12491275
snprintf(base_buf, sizeof(base_buf), "%s", comp_str);
12501276
} else if (base_str) {
12511277
snprintf(base_buf, sizeof(base_buf), "%s", base_str);
12521278
snprintf(comp_buf, sizeof(comp_buf), "%s", comp_str);
1253-
if (strcmp(base_str, comp_str) == 0)
1279+
if (!base || !comp)
1280+
snprintf(diff_buf, sizeof(diff_buf), "%s", "N/A");
1281+
else if (strcmp(base_str, comp_str) == 0)
12541282
snprintf(diff_buf, sizeof(diff_buf), "%s", "MATCH");
12551283
else
12561284
snprintf(diff_buf, sizeof(diff_buf), "%s", "MISMATCH");
12571285
} else {
12581286
double p = 0.0;
12591287

1260-
snprintf(base_buf, sizeof(base_buf), "%ld", base_val);
1261-
snprintf(comp_buf, sizeof(comp_buf), "%ld", comp_val);
1288+
if (base)
1289+
snprintf(base_buf, sizeof(base_buf), "%ld", base_val);
1290+
else
1291+
snprintf(base_buf, sizeof(base_buf), "%s", "N/A");
1292+
if (comp)
1293+
snprintf(comp_buf, sizeof(comp_buf), "%ld", comp_val);
1294+
else
1295+
snprintf(comp_buf, sizeof(comp_buf), "%s", "N/A");
12621296

12631297
diff_val = comp_val - base_val;
1264-
if (base == &fallback_stats || comp == &fallback_stats || base_val == 0) {
1265-
if (comp_val == base_val)
1266-
p = 0.0; /* avoid +0 (+100%) case */
1267-
else
1268-
p = comp_val < base_val ? -100.0 : 100.0;
1298+
if (!base || !comp) {
1299+
snprintf(diff_buf, sizeof(diff_buf), "%s", "N/A");
12691300
} else {
1270-
p = diff_val * 100.0 / base_val;
1301+
if (base_val == 0) {
1302+
if (comp_val == base_val)
1303+
p = 0.0; /* avoid +0 (+100%) case */
1304+
else
1305+
p = comp_val < base_val ? -100.0 : 100.0;
1306+
} else {
1307+
p = diff_val * 100.0 / base_val;
1308+
}
1309+
snprintf(diff_buf, sizeof(diff_buf), "%+ld (%+.2lf%%)", diff_val, p);
12711310
}
1272-
snprintf(diff_buf, sizeof(diff_buf), "%+ld (%+.2lf%%)", diff_val, p);
12731311
}
12741312

12751313
switch (fmt) {
@@ -1328,6 +1366,7 @@ static int cmp_stats_key(const struct verif_stats *base, const struct verif_stat
13281366
static int handle_comparison_mode(void)
13291367
{
13301368
struct stat_specs base_specs = {}, comp_specs = {};
1369+
struct stat_specs tmp_sort_spec;
13311370
enum resfmt cur_fmt;
13321371
int err, i, j;
13331372

@@ -1370,31 +1409,26 @@ static int handle_comparison_mode(void)
13701409
}
13711410
}
13721411

1412+
/* Replace user-specified sorting spec with file+prog sorting rule to
1413+
* be able to join two datasets correctly. Once we are done, we will
1414+
* restore the original sort spec.
1415+
*/
1416+
tmp_sort_spec = env.sort_spec;
1417+
env.sort_spec = join_sort_spec;
13731418
qsort(env.prog_stats, env.prog_stat_cnt, sizeof(*env.prog_stats), cmp_prog_stats);
13741419
qsort(env.baseline_stats, env.baseline_stat_cnt, sizeof(*env.baseline_stats), cmp_prog_stats);
1420+
env.sort_spec = tmp_sort_spec;
13751421

1376-
/* for human-readable table output we need to do extra pass to
1377-
* calculate column widths, so we substitute current output format
1378-
* with RESFMT_TABLE_CALCLEN and later revert it back to RESFMT_TABLE
1379-
* and do everything again.
1380-
*/
1381-
if (env.out_fmt == RESFMT_TABLE)
1382-
cur_fmt = RESFMT_TABLE_CALCLEN;
1383-
else
1384-
cur_fmt = env.out_fmt;
1385-
1386-
one_more_time:
1387-
output_comp_headers(cur_fmt);
1388-
1389-
/* If baseline and comparison datasets have different subset of rows
1390-
* (we match by 'object + prog' as a unique key) then assume
1391-
* empty/missing/zero value for rows that are missing in the opposite
1392-
* data set
1422+
/* Join two datasets together. If baseline and comparison datasets
1423+
* have different subset of rows (we match by 'object + prog' as
1424+
* a unique key) then assume empty/missing/zero value for rows that
1425+
* are missing in the opposite data set.
13931426
*/
13941427
i = j = 0;
13951428
while (i < env.baseline_stat_cnt || j < env.prog_stat_cnt) {
1396-
bool last = (i == env.baseline_stat_cnt - 1) || (j == env.prog_stat_cnt - 1);
13971429
const struct verif_stats *base, *comp;
1430+
struct verif_stats_join *join;
1431+
void *tmp;
13981432
int r;
13991433

14001434
base = i < env.baseline_stat_cnt ? &env.baseline_stats[i] : &fallback_stats;
@@ -1411,18 +1445,56 @@ static int handle_comparison_mode(void)
14111445
return -EINVAL;
14121446
}
14131447

1448+
tmp = realloc(env.join_stats, (env.join_stat_cnt + 1) * sizeof(*env.join_stats));
1449+
if (!tmp)
1450+
return -ENOMEM;
1451+
env.join_stats = tmp;
1452+
1453+
join = &env.join_stats[env.join_stat_cnt];
1454+
memset(join, 0, sizeof(*join));
1455+
14141456
r = cmp_stats_key(base, comp);
14151457
if (r == 0) {
1416-
output_comp_stats(base, comp, cur_fmt, last);
1458+
join->file_name = base->file_name;
1459+
join->prog_name = base->prog_name;
1460+
join->stats_a = base;
1461+
join->stats_b = comp;
14171462
i++;
14181463
j++;
14191464
} else if (comp == &fallback_stats || r < 0) {
1420-
output_comp_stats(base, &fallback_stats, cur_fmt, last);
1465+
join->file_name = base->file_name;
1466+
join->prog_name = base->prog_name;
1467+
join->stats_a = base;
1468+
join->stats_b = NULL;
14211469
i++;
14221470
} else {
1423-
output_comp_stats(&fallback_stats, comp, cur_fmt, last);
1471+
join->file_name = comp->file_name;
1472+
join->prog_name = comp->prog_name;
1473+
join->stats_a = NULL;
1474+
join->stats_b = comp;
14241475
j++;
14251476
}
1477+
env.join_stat_cnt += 1;
1478+
}
1479+
1480+
/* for human-readable table output we need to do extra pass to
1481+
* calculate column widths, so we substitute current output format
1482+
* with RESFMT_TABLE_CALCLEN and later revert it back to RESFMT_TABLE
1483+
* and do everything again.
1484+
*/
1485+
if (env.out_fmt == RESFMT_TABLE)
1486+
cur_fmt = RESFMT_TABLE_CALCLEN;
1487+
else
1488+
cur_fmt = env.out_fmt;
1489+
1490+
one_more_time:
1491+
output_comp_headers(cur_fmt);
1492+
1493+
for (i = 0; i < env.join_stat_cnt; i++) {
1494+
const struct verif_stats_join *join = &env.join_stats[i];
1495+
bool last = i == env.join_stat_cnt - 1;
1496+
1497+
output_comp_stats(join, cur_fmt, last);
14261498
}
14271499

14281500
if (cur_fmt == RESFMT_TABLE_CALCLEN) {
@@ -1594,6 +1666,7 @@ int main(int argc, char **argv)
15941666

15951667
free_verif_stats(env.prog_stats, env.prog_stat_cnt);
15961668
free_verif_stats(env.baseline_stats, env.baseline_stat_cnt);
1669+
free(env.join_stats);
15971670
for (i = 0; i < env.filename_cnt; i++)
15981671
free(env.filenames[i]);
15991672
free(env.filenames);

0 commit comments

Comments
 (0)