linux: full test suite — unit/integration/functional + virtme-ng bench harness

Java simulation tests (unit/):
- Linux0006Test.java: linux-0001 (headerdep 29×) + linux-0006 (btf 500×+) — 4/4 PASS
- LinuxTest.java: fix numbering linux-0001→0002, linux-0002→0003, linux-0003→0004
  (linux-0002 audit / linux-0003 dev_alloc / linux-0004 neigh_parms)

Kernel test files (tests/):
- linux-0005-component-kunit.c: KUnit suite with unit/integration/functional cases
  Complexity gate: C=200 find_component slow must be ≥20× fast (KUnit EXPECT_GT)
- linux-0006-btf-kselftest.c: kselftest timing BPF_MAP_CREATE cold vs warm cache
- linux-0002-audit-kselftest.sh: auditctl watch + open() timing, F=50 R=20
- linux-0003-0004-net-kselftest.sh: ip link rename + ip ntable change timing
  Runs in private netns (unshare --net), no host impact
- linux-0007-pktgen-bench.sh: pktgen proc read timing, 20× gate
- linux-0008-taskstats-kselftest.c: TASKSTATS_CMD_ATTR_REGISTER_CPUMASK timing
  Gate: 100 registrations across all CPUs in <500ms

Build + bench harness (bench/):
- build-and-bench.sh: shallow clone + apply 8 patches + defconfig build +
  virtme-ng QEMU boot + run all kselftests inside VM
- update-benchmarks.py: parse bench log, write ## Benchmark Results into UNDF posts
  Run after bench to update UNDF posts with actual measured ratios

License: all test code GPLv2 (in-kernel), bench scripts public domain
This commit is contained in:
russell@unturf.com 2026-04-04 12:29:56 -04:00
parent 998e2b7b0f
commit b1e7dd87a1
10 changed files with 1838 additions and 20 deletions

View file

@ -0,0 +1,322 @@
package unit;
import java.util.*;
/**
* Linux0006Test CWE-407 benchmark for linux-0001 and linux-0006
*
* linux-0001 (HEADERDEP_HASH):
* Models scripts/headerdep.pl detect_cycles():
* SLOW: grep{} membership check O(depth) per BFS expansion
* Total: O(D × depth²) for D headers and average chain depth K
* FAST: parallel hash alongside path array exists{} check O(1)
* Total: O(D × depth)
*
* linux-0006 (BTF_MODULE_SCAN_HASH):
* Models kernel/bpf/btf.c bpf_find_btf_id():
* SLOW: idr_for_each_entry over M loaded module BTFs per lookup
* Total: O(F × M) per BPF_MAP_CREATE with F kptr fields
* FAST: secondary hash table (name_hash ^ kind) btf_id
* Total: O(F) on warm cache O(1) per field
*/
public class Linux0006Test {
// =========================================================================
// linux-0001: headerdep.pl detect_cycles O(D×depth²) vs O(D×depth)
// =========================================================================
/**
* SLOW: simulate detect_cycles grep{} membership.
* For each BFS expansion, check if new dep exists in current path via linear scan.
* @param pathDepth current path array length (simulates @$top size)
* @param expansions number of BFS expansions to simulate
* @return total comparison count (models computational work)
*/
static long detectCycles_slow(int pathDepth, int expansions) {
long ops = 0;
// Simulate a path array each expansion scans the whole path
List<String> path = new ArrayList<>(pathDepth);
for (int i = 0; i < pathDepth; i++) path.add("header_" + i);
for (int e = 0; e < expansions; e++) {
String candidate = "header_" + (e % (pathDepth + 10));
// grep{} O(depth) scan
for (String h : path) {
ops++;
if (h.equals(candidate)) break;
}
}
return ops;
}
/**
* FAST: simulate detect_cycles with parallel hash.
* exists{} check is O(1) constant cost per expansion.
* @param pathDepth current path array length (set size)
* @param expansions number of BFS expansions to simulate
* @return total comparison count (models computational work)
*/
static long detectCycles_fast(int pathDepth, int expansions) {
long ops = 0;
Set<String> pathSet = new HashSet<>(pathDepth * 2);
for (int i = 0; i < pathDepth; i++) pathSet.add("header_" + i);
for (int e = 0; e < expansions; e++) {
String candidate = "header_" + (e % (pathDepth + 10));
ops++; // O(1) hash lookup
pathSet.contains(candidate);
}
return ops;
}
// =========================================================================
// linux-0006: bpf_find_btf_id O(F×M) idr scan vs O(F) cached hash lookup
// =========================================================================
/** Simulates one BTF type entry in a module BTF. */
static class BtfType {
final String name;
final int kind; // BTF_KIND_STRUCT, BTF_KIND_TYPEDEF, etc.
final int btfId;
BtfType(String name, int kind, int btfId) {
this.name = name; this.kind = kind; this.btfId = btfId;
}
}
/** Simulates one loaded module BTF — a searchable collection of types. */
static class ModuleBtf {
final String moduleName;
final List<BtfType> types;
ModuleBtf(String moduleName, List<BtfType> types) {
this.moduleName = moduleName; this.types = types;
}
int findByNameKind(String name, int kind) {
for (BtfType t : types) {
if (t.name.equals(name) && t.kind == kind) return t.btfId;
}
return -1;
}
}
/** Simulates the btf_name_cache_entry hash table. */
static class BtfNameCache {
private final Map<Long, BtfType> cache = new HashMap<>();
private long key(String name, int kind) {
// FNV-1a approximation
long h = 2166136261L;
for (char c : name.toCharArray()) h = (h ^ c) * 16777619L;
return h ^ kind;
}
void put(String name, int kind, BtfType type) {
cache.put(key(name, kind), type);
}
BtfType get(String name, int kind) {
return cache.get(key(name, kind));
}
}
/**
* SLOW: bpf_find_btf_id without cache O(M) idr scan per field.
* Simulates idr_for_each_entry walking all module BTFs.
* @return total BTF type comparisons (models idr iteration work)
*/
static long btfFindId_slow(List<ModuleBtf> modules,
String[] kptrNames, int kind) {
long ops = 0;
for (String name : kptrNames) {
// idr_for_each_entry scan all modules
for (ModuleBtf mod : modules) {
for (BtfType t : mod.types) {
ops++;
if (t.name.equals(name) && t.kind == kind) break;
}
}
}
return ops;
}
/**
* FAST: bpf_find_btf_id with cache O(1) per field on cache hit.
* First call populates the cache; subsequent calls are O(1).
* @return total cache probe operations
*/
static long btfFindId_fast(List<ModuleBtf> modules,
String[] kptrNames, int kind,
BtfNameCache cache) {
long ops = 0;
for (String name : kptrNames) {
ops++; // O(1) hash probe
BtfType hit = cache.get(name, kind);
if (hit == null) {
// Cache miss populate (only first call per name)
for (ModuleBtf mod : modules) {
int id = mod.findByNameKind(name, kind);
if (id >= 0) {
cache.put(name, kind, new BtfType(name, kind, id));
break;
}
}
}
}
return ops;
}
// =========================================================================
// Benchmark harness
// =========================================================================
static void bench(String label, long slow, long fast) {
double ratio = fast == 0 ? Double.MAX_VALUE : (double) slow / fast;
System.out.printf(" %-55s slow=%,d fast=%,d ratio=%.0fx%n",
label, slow, fast, ratio);
}
// =========================================================================
// main
// =========================================================================
public static void main(String[] args) {
int passed = 0, total = 0;
System.out.println("Linux0006Test — CWE-407 benchmark (linux-0001 headerdep / linux-0006 btf)");
System.out.println("=".repeat(72));
// ------------------------------------------------------------------
// linux-0001: headerdep.pl detect_cycles O(D×depth²) vs O(D×depth)
// ------------------------------------------------------------------
System.out.println("\nlinux-0001: headerdep.pl detect_cycles O(depth) grep vs O(1) hash");
{
// depth=50: represents include chain depth in a large subsystem
// expansions=500: BFS visiting 500 nodes per chain
int depth = 50, expansions = 500, ITERS = 1000;
long[] sOps = {0}, fOps = {0};
Runnable slow = () -> {
for (int i = 0; i < ITERS; i++) sOps[0] += detectCycles_slow(depth, expansions);
};
Runnable fast = () -> {
for (int i = 0; i < ITERS; i++) fOps[0] += detectCycles_fast(depth, expansions);
};
slow.run(); fast.run();
bench("headerdep depth=50 expansions=500 (1k scans)", sOps[0], fOps[0]);
total++;
// slow: expansions × depth/2 (avg) = 500 × 25 = 12500 per scan
// fast: expansions × 1 = 500 × 1 = 500 per scan 25× ratio
assert sOps[0] > fOps[0] * 10
: "FAIL linux-0001 depth=50: slow=" + sOps[0] + " fast=" + fOps[0];
System.out.println(" PASS");
passed++;
}
{
// depth=100 (deep nested headers), expansions=1000
int depth = 100, expansions = 1000, ITERS = 500;
long[] sOps = {0}, fOps = {0};
Runnable slow = () -> {
for (int i = 0; i < ITERS; i++) sOps[0] += detectCycles_slow(depth, expansions);
};
Runnable fast = () -> {
for (int i = 0; i < ITERS; i++) fOps[0] += detectCycles_fast(depth, expansions);
};
slow.run(); fast.run();
bench("headerdep depth=100 expansions=1000 (500 scans)", sOps[0], fOps[0]);
total++;
assert sOps[0] > fOps[0] * 20
: "FAIL linux-0001 depth=100: slow=" + sOps[0] + " fast=" + fOps[0];
System.out.println(" PASS");
passed++;
}
// ------------------------------------------------------------------
// linux-0006: bpf_find_btf_id O(F×M) vs O(F) cached
// ------------------------------------------------------------------
System.out.println("\nlinux-0006: bpf_find_btf_id O(F×M) idr scan vs O(F) cached hash");
{
// M=100 modules, F=10 kptr fields, 100 map-create calls
int M = 100, F = 10, MAP_CREATES = 100;
int BTF_KIND_STRUCT = 6;
List<ModuleBtf> modules = new ArrayList<>(M);
// Distribute types across modules; last module has our target types
for (int m = 0; m < M; m++) {
List<BtfType> types = new ArrayList<>();
if (m == M - 1) {
// Target module contains our kptr types
for (int f = 0; f < F; f++)
types.add(new BtfType("kptr_type_" + f, BTF_KIND_STRUCT, 1000 + f));
} else {
// Other modules 5 unrelated types each
for (int t = 0; t < 5; t++)
types.add(new BtfType("mod_" + m + "_type_" + t, BTF_KIND_STRUCT, m * 10 + t));
}
modules.add(new ModuleBtf("module_" + m, types));
}
String[] kptrNames = new String[F];
for (int f = 0; f < F; f++) kptrNames[f] = "kptr_type_" + f;
long[] sOps = {0}, fOps = {0};
Runnable slow = () -> {
for (int i = 0; i < MAP_CREATES; i++)
sOps[0] += btfFindId_slow(modules, kptrNames, BTF_KIND_STRUCT);
};
// Warm cache once before timing
BtfNameCache cache = new BtfNameCache();
btfFindId_fast(modules, kptrNames, BTF_KIND_STRUCT, cache);
Runnable fast = () -> {
for (int i = 0; i < MAP_CREATES; i++)
fOps[0] += btfFindId_fast(modules, kptrNames, BTF_KIND_STRUCT, cache);
};
slow.run(); fast.run();
bench("btf_find M=100 F=10 (100 map-creates)", sOps[0], fOps[0]);
total++;
// slow: F × M × (M/2 avg scan) = 10 × (100×5/2) = 2500 per create
// fast: F × 1 = 10 per create on warm cache ~250× ratio
assert sOps[0] > fOps[0] * 20
: "FAIL linux-0006 M=100 F=10: slow=" + sOps[0] + " fast=" + fOps[0];
System.out.println(" PASS");
passed++;
}
{
// M=200 modules (loaded Kubernetes node), F=8 kptr fields
int M = 200, F = 8, MAP_CREATES = 500;
int BTF_KIND_STRUCT = 6;
List<ModuleBtf> modules = new ArrayList<>(M);
for (int m = 0; m < M; m++) {
List<BtfType> types = new ArrayList<>();
if (m == M - 1) {
for (int f = 0; f < F; f++)
types.add(new BtfType("kptr_" + f, BTF_KIND_STRUCT, 2000 + f));
} else {
for (int t = 0; t < 3; t++)
types.add(new BtfType("m" + m + "_t" + t, BTF_KIND_STRUCT, m * 10 + t));
}
modules.add(new ModuleBtf("mod_" + m, types));
}
String[] kptrNames = new String[F];
for (int f = 0; f < F; f++) kptrNames[f] = "kptr_" + f;
long[] sOps = {0}, fOps = {0};
Runnable slow = () -> {
for (int i = 0; i < MAP_CREATES; i++)
sOps[0] += btfFindId_slow(modules, kptrNames, BTF_KIND_STRUCT);
};
BtfNameCache cache = new BtfNameCache();
btfFindId_fast(modules, kptrNames, BTF_KIND_STRUCT, cache);
Runnable fast = () -> {
for (int i = 0; i < MAP_CREATES; i++)
fOps[0] += btfFindId_fast(modules, kptrNames, BTF_KIND_STRUCT, cache);
};
slow.run(); fast.run();
bench("btf_find M=200 F=8 (500 map-creates, warm cache)", sOps[0], fOps[0]);
total++;
assert sOps[0] > fOps[0] * 50
: "FAIL linux-0006 M=200 F=8: slow=" + sOps[0] + " fast=" + fOps[0];
System.out.println(" PASS");
passed++;
}
System.out.println("\n" + "=".repeat(72));
System.out.printf("Linux0006Test: %d/%d PASSED%n", passed, total);
if (passed < total) throw new AssertionError("FAILED " + (total - passed) + " test(s)");
System.out.println("linux-0001 and linux-0006 confirmed O(n²)→O(n) / O(1)");
}
}

View file

@ -2,23 +2,23 @@ package unit;
import java.util.*;
/**
* LinuxTest CWE-407 benchmark for linux-0001, linux-0002, linux-0003
* LinuxTest CWE-407 benchmark for linux-0002, linux-0003, linux-0004
*
* linux-0001 (AUDIT_FILTER_INODES_QUADRATIC):
* linux-0002 (AUDIT_FILTER_INODES_QUADRATIC):
* Models audit_filter_inodes() + audit_filter_rules():
* SLOW: for each name [O(F)]: for each rule [O(R)]: for each inode-field: scan names [O(F)]
* Total O(F * R * F) = O(F²R)
* FAST: for each name [O(F)]: hash-lookup rule by inode [O(1)]: O(1) field check
* Total O(F)
*
* linux-0002 (DEV_ALLOC_NAME_NESTED_ALTNAME):
* linux-0003 (DEV_ALLOC_NAME_NESTED_ALTNAME):
* Models __dev_alloc_name():
* SLOW: for each netdev [O(D)]: for each altname [O(A)]: sscanf+snprintf+strcmp [O(1)]
* Total O(D * A)
* FAST: maintain prefix bitmap; populate on registration; find_first_zero in O(D+A) once
* Lookup: O(1) single bitmap load
*
* linux-0003 (NEIGH_PARMS_IFINDEX_LINEAR_SCAN):
* linux-0004 (NEIGH_PARMS_IFINDEX_LINEAR_SCAN):
* Models lookup_neigh_parms():
* SLOW: list_for_each_entry(p, &tbl->parms_list) O(P) per command
* FAST: xarray / HashMap keyed by ifindex O(1) per command
@ -26,7 +26,7 @@ import java.util.*;
public class LinuxTest {
// =========================================================================
// linux-0001: audit_filter_inodes quadratic names_list re-scan
// linux-0002: audit_filter_inodes quadratic names_list re-scan
// =========================================================================
/** One audit_names entry — inode + dev pair (like struct audit_names). */
@ -122,7 +122,7 @@ public class LinuxTest {
}
// =========================================================================
// linux-0002: __dev_alloc_name nested O(D * A) altname sscanf
// linux-0003: __dev_alloc_name nested O(D * A) altname sscanf
// =========================================================================
/** One net_device with primary name and alt names. */
@ -209,7 +209,7 @@ public class LinuxTest {
}
// =========================================================================
// linux-0003: lookup_neigh_parms O(P) list scan vs O(1) map lookup
// linux-0004: lookup_neigh_parms O(P) list scan vs O(1) map lookup
// =========================================================================
/** Simulates struct neigh_parms — one per registered netdev. */
@ -267,13 +267,13 @@ public class LinuxTest {
public static void main(String[] args) {
int passed = 0, total = 0;
System.out.println("LinuxTest — CWE-407 benchmark (linux-0001 / 0002 / 0003)");
System.out.println("LinuxTest — CWE-407 benchmark (linux-0002 / 0003 / 0004)");
System.out.println("=".repeat(80));
// ------------------------------------------------------------------
// linux-0001: audit_filter_inodes quadratic re-scan
// linux-0002: audit_filter_inodes quadratic re-scan
// ------------------------------------------------------------------
System.out.println("\nlinux-0001: audit_filter_inodes O(F²R) vs O(FR)");
System.out.println("\nlinux-0002: audit_filter_inodes O(F²R) vs O(FR)");
{
// F=50 files (e.g. compiler opening many headers)
// R=20 rules, 2 AUDIT_INODE fields each
@ -307,7 +307,7 @@ public class LinuxTest {
// fast does F * R * fields = 50*20*2 = 2000 per call
// ratio should be ~F = 50x
assert sOps[0] > fOps[0] * 10
: "FAIL linux-0001 F=50 R=20: slow=" + sOps[0] + " fast=" + fOps[0];
: "FAIL linux-0002 F=50 R=20: slow=" + sOps[0] + " fast=" + fOps[0];
passed++;
}
{
@ -339,14 +339,14 @@ public class LinuxTest {
bench("audit filter F=200 R=50 (2k syscalls)", slow, fast, sOps[0], fOps[0]);
total++;
assert sOps[0] > fOps[0] * 50
: "FAIL linux-0001 F=200 R=50: slow=" + sOps[0] + " fast=" + fOps[0];
: "FAIL linux-0002 F=200 R=50: slow=" + sOps[0] + " fast=" + fOps[0];
passed++;
}
// ------------------------------------------------------------------
// linux-0002: __dev_alloc_name nested altname scan
// linux-0003: __dev_alloc_name nested altname scan
// ------------------------------------------------------------------
System.out.println("\nlinux-0002: __dev_alloc_name O(D*A) vs O(1)");
System.out.println("\nlinux-0003: __dev_alloc_name O(D*A) vs O(1)");
{
// D=300 devices, A=2 alt names each (typical container node)
int D = 300, A = 2;
@ -378,7 +378,7 @@ public class LinuxTest {
// fast: 1 op per call -> 5k total
// ratio: ~900x
assert sOps[0] > fOps[0] * 50
: "FAIL linux-0002 D=300 A=2: slow=" + sOps[0] + " fast=" + fOps[0];
: "FAIL linux-0003 D=300 A=2: slow=" + sOps[0] + " fast=" + fOps[0];
passed++;
}
{
@ -409,14 +409,14 @@ public class LinuxTest {
bench("dev_alloc_name D=800 A=3 (1k renames)", slow, fast, sOps[0], fOps[0]);
total++;
assert sOps[0] > fOps[0] * 100
: "FAIL linux-0002 D=800 A=3: slow=" + sOps[0] + " fast=" + fOps[0];
: "FAIL linux-0003 D=800 A=3: slow=" + sOps[0] + " fast=" + fOps[0];
passed++;
}
// ------------------------------------------------------------------
// linux-0003: lookup_neigh_parms linear scan vs HashMap
// linux-0004: lookup_neigh_parms linear scan vs HashMap
// ------------------------------------------------------------------
System.out.println("\nlinux-0003: lookup_neigh_parms O(P) vs O(1)");
System.out.println("\nlinux-0004: lookup_neigh_parms O(P) vs O(1)");
{
// P=400 parms (VxLAN gateway with 400 VTEPs + bridge ports)
int P = 400;
@ -450,7 +450,7 @@ public class LinuxTest {
// fast: 1 op per lookup -> 100k
// ratio: ~400x
assert sOps[0] > fOps[0] * 100
: "FAIL linux-0003 P=400: slow=" + sOps[0] + " fast=" + fOps[0];
: "FAIL linux-0004 P=400: slow=" + sOps[0] + " fast=" + fOps[0];
passed++;
}
{
@ -483,7 +483,7 @@ public class LinuxTest {
bench("neigh_parms lookup P=1000 not-found (50k)", slow, fast, sOps[0], fOps[0]);
total++;
assert sOps[0] > fOps[0] * 500
: "FAIL linux-0003 P=1000 not-found: slow=" + sOps[0] + " fast=" + fOps[0];
: "FAIL linux-0004 P=1000 not-found: slow=" + sOps[0] + " fast=" + fOps[0];
passed++;
}