From fa747e9f843ba3a0fa4d3fabaf50c9e11aaf963f Mon Sep 17 00:00:00 2001 From: Puranjay Mohan Date: Wed, 20 May 2026 06:33:30 -0700 Subject: [PATCH 1/3] selftests/bpf: Fix cold_lru producing zero batch_hash in XDP LB benchmark batch_hash = (batch_gen ^ cpu_id) * KNUTH_HASH_MULT; When batch_gen == cpu_id the XOR produces zero, batch_hash is zero, and *saddr ^= 0 is a no-op. Every iteration hits the warm LRU entry. During validation batch_gen is 2, so running on CPU 2 triggers: [udp-v4-lru-miss] COUNTER FAIL: LRU misses=0, expected 1 Replace XOR with addition so the multiplier input is always >= 1. This also preserves the per-CPU salt for multi-producer runs. Fixes: 4b4f2229104c ("selftests/bpf: Add XDP load-balancer BPF program") Signed-off-by: Puranjay Mohan Link: https://lore.kernel.org/r/20260520133338.3392667-2-puranjay@kernel.org Signed-off-by: Alexei Starovoitov --- tools/testing/selftests/bpf/progs/xdp_lb_bench.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/testing/selftests/bpf/progs/xdp_lb_bench.c b/tools/testing/selftests/bpf/progs/xdp_lb_bench.c index b9fd848c035d..13777b3dcac8 100644 --- a/tools/testing/selftests/bpf/progs/xdp_lb_bench.c +++ b/tools/testing/selftests/bpf/progs/xdp_lb_bench.c @@ -618,7 +618,7 @@ int xdp_lb_bench(struct xdp_md *xdp) __u32 *saddr = data + saddr_off; batch_gen++; - batch_hash = (batch_gen ^ bpf_get_smp_processor_id()) * KNUTH_HASH_MULT; + batch_hash = (batch_gen + bpf_get_smp_processor_id()) * KNUTH_HASH_MULT; if ((void *)(saddr + 1) <= data_end) *saddr ^= batch_hash; } From 12e896b9794bbd88f56aeac2a5807ae8d4bb5ad8 Mon Sep 17 00:00:00 2001 From: Puranjay Mohan Date: Wed, 20 May 2026 06:33:31 -0700 Subject: [PATCH 2/3] selftests/bpf: Fix expired UDP LRU entries in XDP LB benchmark populate_lru() zero-initializes atime: struct real_pos_lru lru = { .pos = real_idx }; connection_table_lookup() treats UDP entries with cur_time - atime > 30s as expired, so every pre-populated entry expires immediately. Calibration masks this on the CPU it runs on, but if validation migrates to another CPU: [udp-v4-lru-hit] COUNTER FAIL: LRU misses=1, expected 0 Initialize atime from CLOCK_MONOTONIC for UDP flows. Fixes: a4b5ba8187cb ("selftests/bpf: Add XDP load-balancer benchmark driver") Signed-off-by: Puranjay Mohan Link: https://lore.kernel.org/r/20260520133338.3392667-3-puranjay@kernel.org Signed-off-by: Alexei Starovoitov --- tools/testing/selftests/bpf/benchs/bench_xdp_lb.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tools/testing/selftests/bpf/benchs/bench_xdp_lb.c b/tools/testing/selftests/bpf/benchs/bench_xdp_lb.c index 0b6709a2b03c..8e25bccbde92 100644 --- a/tools/testing/selftests/bpf/benchs/bench_xdp_lb.c +++ b/tools/testing/selftests/bpf/benchs/bench_xdp_lb.c @@ -563,12 +563,23 @@ static void create_per_cpu_lru_maps(struct xdp_lb_bench *skel) nr_inner_maps = nr_cpus; } +static __u64 ktime_get_ns(void) +{ + struct timespec ts; + + clock_gettime(CLOCK_MONOTONIC, &ts); + return (__u64)ts.tv_sec * 1000000000ULL + ts.tv_nsec; +} + static void populate_lru(const struct test_scenario *sc, __u32 real_idx) { struct real_pos_lru lru = { .pos = real_idx }; struct flow_key fk; int i, err; + if (sc->ip_proto == IPPROTO_UDP) + lru.atime = ktime_get_ns(); + build_flow_key(&fk, sc); /* Insert into every per-CPU inner LRU so the entry is found From abac8acb633a9448369d658889ac2bcfbd96f54b Mon Sep 17 00:00:00 2001 From: Puranjay Mohan Date: Wed, 20 May 2026 06:33:32 -0700 Subject: [PATCH 3/3] selftests/bpf: Filter timing outliers with IQR in batch-timing library System noise (timer interrupts, scheduling) can inflate the reported stddev. tcp-v4-syn showed stddev 37.86 ns without filtering vs 0.16 ns with filtering on the same run data. Filter samples outside [Q1 - 1.5*IQR, Q3 + 1.5*IQR] before computing statistics. Scenarios with genuinely wide distributions have large IQR so the fences stay wide and the filter has minimal effect. Signed-off-by: Puranjay Mohan Link: https://lore.kernel.org/r/20260520133338.3392667-4-puranjay@kernel.org Signed-off-by: Alexei Starovoitov --- .../selftests/bpf/benchs/bench_bpf_timing.c | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/tools/testing/selftests/bpf/benchs/bench_bpf_timing.c b/tools/testing/selftests/bpf/benchs/bench_bpf_timing.c index 75a39da69655..e02ad324f7bc 100644 --- a/tools/testing/selftests/bpf/benchs/bench_bpf_timing.c +++ b/tools/testing/selftests/bpf/benchs/bench_bpf_timing.c @@ -65,6 +65,31 @@ static int collect_samples(struct bpf_bench_timing *t, return total; } +static int filter_outliers_iqr(double *sorted, int n) +{ + double q1, q3, iqr, lo, hi; + int start = 0, end = n; + + if (n < 8) + return n; + + q1 = sorted[n / 4]; + q3 = sorted[3 * n / 4]; + iqr = q3 - q1; + lo = q1 - 1.5 * iqr; + hi = q3 + 1.5 * iqr; + + while (start < end && sorted[start] < lo) + start++; + while (end > start && sorted[end - 1] > hi) + end--; + + if (start > 0) + memmove(sorted, sorted + start, (end - start) * sizeof(double)); + + return end - start; +} + static void compute_stats(const double *sorted, int n, struct timing_stats *s) { @@ -150,6 +175,7 @@ void bpf_bench_timing_report(struct bpf_bench_timing *t, const char *name, const return; } + total = filter_outliers_iqr(all, total); compute_stats(all, total, &s); if (t->machine_readable) {