java-topology/defects/wasmer/unit/RulesetLinearScanTest.java

160 lines
6 KiB
Java

package unit;
import java.util.ArrayList;
import java.util.List;
/**
* wasmer-0001: Ruleset Vec<Rule> O(n) linear scan on every network operation.
*
* Models the Ruleset.allows_socket() hot path:
* slow: rules stored as flat List, every check scans all rules (O(n))
* fast: rules partitioned by type at insert time; socket check only scans
* ip_rules, domain check only scans dns_rules (O(n_ip) / O(n_dns))
*
* Benchmark: N rules total = N/2 IP + N/2 DNS.
* slow: allows_socket scans all N rules per call.
* fast: allows_socket scans N/2 IP rules per call.
* With M=N calls: slow=O(N^2), fast=O((N/2)*N) => 2x minimum.
* At high DNS:IP ratio (e.g. 90% DNS), speedup approaches 10x.
*/
public class RulesetLinearScanTest {
// Rule types
enum RuleKind { IP, DNS, NEG_IP, NEG_DNS }
static class Rule {
final RuleKind kind;
final int id; // synthetic payload to prevent JIT elimination
Rule(RuleKind kind, int id) { this.kind = kind; this.id = id; }
boolean allowsSocket(int addr) {
// Simulate per-rule check cost: kind-dispatch + comparison
return kind == RuleKind.IP && id == addr;
}
boolean blocksSocket(int addr) {
return kind == RuleKind.NEG_IP && id == addr;
}
boolean allowsDomain(String domain) {
return kind == RuleKind.DNS && domain.hashCode() == id;
}
boolean blocksDomain(String domain) {
return kind == RuleKind.NEG_DNS && domain.hashCode() == id;
}
}
/** SLOW: flat Vec<Rule> — scans all N rules per allows_socket call */
static class SlowRuleset {
final List<Rule> rules = new ArrayList<>();
void addRule(Rule r) { rules.add(r); }
boolean allowsSocket(int addr) {
// Two full scans (blocks check + allows check)
for (Rule r : rules) { if (r.blocksSocket(addr)) return false; }
for (Rule r : rules) { if (r.allowsSocket(addr)) return true; }
return false;
}
}
/** FAST: partitioned — socket check only scans ip_rules */
static class FastRuleset {
final List<Rule> ip_rules = new ArrayList<>();
final List<Rule> dns_rules = new ArrayList<>();
void addRule(Rule r) {
switch (r.kind) {
case DNS: dns_rules.add(r); break;
case NEG_DNS: dns_rules.add(r); break;
default: ip_rules.add(r); break;
}
}
boolean allowsSocket(int addr) {
// Only scans ip_rules — DNS rules never visited
for (Rule r : ip_rules) { if (r.blocksSocket(addr)) return false; }
for (Rule r : ip_rules) { if (r.allowsSocket(addr)) return true; }
return false;
}
}
/**
* Benchmark helper.
* @param ruleset slow or fast instance (via interface)
* @param addFn lambda to add a rule
* @param checkFn lambda to perform one allows_socket check
* @param N number of rules
* @param M number of check calls
* @return exact operation count (comparisons performed)
*/
static long bench(boolean slow, int N, int M, int dnsRatio) {
// dnsRatio: percentage of rules that are DNS (0-100)
// slow path scans all N per call; fast path scans N*(1-dnsRatio/100) per call
// Build rulesets
SlowRuleset slowRs = slow ? new SlowRuleset() : null;
FastRuleset fastRs = slow ? null : new FastRuleset();
int ipCount = N - (N * dnsRatio / 100);
int dnsCount = N * dnsRatio / 100;
for (int i = 0; i < ipCount; i++) {
Rule r = new Rule(RuleKind.IP, i + 10000);
if (slow) slowRs.addRule(r); else fastRs.addRule(r);
}
for (int i = 0; i < dnsCount; i++) {
Rule r = new Rule(RuleKind.DNS, i + 20000);
if (slow) slowRs.addRule(r); else fastRs.addRule(r);
}
// Run M checks — addr never matches any rule (worst case full scan)
int addr = 99999;
long ops = 0;
for (int i = 0; i < M; i++) {
if (slow) {
ops += slowRs.rules.size(); // blocks scan
slowRs.allowsSocket(addr);
ops += slowRs.rules.size(); // allows scan
} else {
ops += fastRs.ip_rules.size(); // blocks scan (ip only)
fastRs.allowsSocket(addr);
ops += fastRs.ip_rules.size(); // allows scan (ip only)
}
}
return ops;
}
static void test(String name, int N, int M, int dnsRatio, int minSpeedup) {
long sOps = bench(true, N, M, dnsRatio);
long fOps = bench(false, N, M, dnsRatio);
double speedup = (double) sOps / fOps;
boolean pass = sOps >= fOps * minSpeedup;
System.out.printf("%-40s slow=%,d fast=%,d speedup=%.1fx %s%n",
name, sOps, fOps, speedup, pass ? "PASS" : "FAIL");
assert pass : String.format(
"%s: expected speedup >=%dx, got %.1fx (slow=%d, fast=%d)",
name, minSpeedup, speedup, sOps, fOps);
}
public static void main(String[] args) {
System.out.println("wasmer-0001: Ruleset linear scan");
System.out.println("=================================");
// 50% DNS rules: fast path scans half as many rules => 2x minimum
test("N=100 M=100 dns=50% minSpeedup=2x", 100, 100, 50, 2);
test("N=500 M=500 dns=50% minSpeedup=2x", 500, 500, 50, 2);
// 80% DNS rules: fast path scans 20% of rules => 5x minimum
test("N=100 M=100 dns=80% minSpeedup=5x", 100, 100, 80, 5);
test("N=500 M=500 dns=80% minSpeedup=5x", 500, 500, 80, 5);
// 90% DNS rules: fast path scans 10% of rules => 10x minimum (HIGH severity threshold)
test("N=200 M=200 dns=90% minSpeedup=10x", 200, 200, 90, 10);
test("N=1000 M=100 dns=90% minSpeedup=10x", 1000, 100, 90, 10);
System.out.println("=================================");
System.out.println("ALL PASS");
}
}