java-topology/defects/linux/unit/Linux0006Test.java
russell@unturf.com b1e7dd87a1 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
2026-04-04 12:29:56 -04:00

322 lines
14 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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)");
}
}