package unit; import java.util.HashMap; import java.util.Map; /** * caddy-0001: hostByHashing O(N) xxhash-per-upstream vs O(1) cached-hash fix. * * slow() models the defect: hash(up.String() + s) called once per upstream per request. * fast() models the fix: upstream hash pre-cached; combine with hash(s) in O(1). * * Assert: slowOps > fastOps * 5 for N=50 upstreams. */ public class CaddyHostByHashingAlgorithmTest { static long slowOps; static long fastOps; // ---- simulated upstream ------------------------------------------------ static class Upstream { final String addr; long cachedHash; // populated at provision time in the fix boolean available; Upstream(String addr, long cachedHash) { this.addr = addr; this.cachedHash = cachedHash; this.available = true; } } // ---- cheap hash substitute (counts as 1 op) ---------------------------- static long cheapHash(String s) { long h = 0xcbf29ce484222325L; for (char c : s.toCharArray()) { h ^= c; h *= 0x100000001b3L; } return h; } // ---- slow: O(N) hash-per-upstream (defect) ----------------------------- static Upstream slowHostByHashing(Upstream[] pool, String s) { long highest = 0; Upstream best = null; for (Upstream up : pool) { if (!up.available) continue; slowOps++; // one hash call per upstream long h = cheapHash(up.addr + s); if (h > highest) { highest = h; best = up; } } return best; } // ---- fast: O(1) hash-of-s + XOR with cached hash (fix) ---------------- static Upstream fastHostByHashing(Upstream[] pool, String s) { fastOps++; // one hash call total long sHash = cheapHash(s); long highest = 0; Upstream best = null; for (Upstream up : pool) { if (!up.available) continue; long h = up.cachedHash ^ sHash; // XOR: O(1) per upstream, no hash call if (h > highest) { highest = h; best = up; } } return best; } // ---- benchmark driver -------------------------------------------------- public static void main(String[] args) { final int N = 50; final int REQUESTS = 10_000; Upstream[] pool = new Upstream[N]; for (int i = 0; i < N; i++) { String addr = "10.0." + (i / 256) + "." + (i % 256) + ":8080"; pool[i] = new Upstream(addr, cheapHash(addr)); } String[] requestKeys = new String[REQUESTS]; for (int r = 0; r < REQUESTS; r++) { requestKeys[r] = "/path/resource/" + r; } slowOps = 0; fastOps = 0; for (int r = 0; r < REQUESTS; r++) { Upstream s = slowHostByHashing(pool, requestKeys[r]); if (s == null) throw new AssertionError("slow: no upstream selected"); } for (int r = 0; r < REQUESTS; r++) { Upstream f = fastHostByHashing(pool, requestKeys[r]); if (f == null) throw new AssertionError("fast: no upstream selected"); } long ratio = slowOps / Math.max(fastOps, 1); boolean pass = slowOps > fastOps * (N / 2); System.out.printf("caddy-0001 slow=%d fast=%d ratio=%dx %s%n", slowOps, fastOps, ratio, pass ? "PASS" : "FAIL"); if (!pass) { System.err.printf("FAIL: expected slowOps(%d) > fastOps(%d) * %d%n", slowOps, fastOps, N / 2); System.exit(1); } } }