package unit; import java.util.*; /** * Models Dragonfly GetMissingMigrations O(M²) std::find → O(M log M) set_difference. * * SLOW: O(M²) — for each haystack entry, scan entire needle with std::find. * FAST: O(M log M) — sort both, use merge-difference. * * CWE-407: src/server/cluster/cluster_config.cc:394 */ public class DragonflyMissingMigrationsAlgorithmTest { static class MigrationInfo { final String nodeId; final int[] slotRanges; MigrationInfo(String nodeId, int[] slotRanges) { this.nodeId = nodeId; this.slotRanges = slotRanges; } @Override public boolean equals(Object o) { if (!(o instanceof MigrationInfo)) return false; MigrationInfo m = (MigrationInfo) o; return nodeId.equals(m.nodeId) && Arrays.equals(slotRanges, m.slotRanges); } @Override public int hashCode() { return Objects.hash(nodeId, Arrays.hashCode(slotRanges)); } } // ------------------------------------------------------------------------- // Slow — O(M²) std::find analog // ------------------------------------------------------------------------- static class SlowMigrationDiff { long cmpOps = 0; boolean find(List needle, MigrationInfo h) { for (MigrationInfo n : needle) { cmpOps++; if (h.equals(n)) return true; } return false; } List getMissing(List haystack, List needle) { List res = new ArrayList<>(); for (MigrationInfo h : haystack) { if (!find(needle, h)) res.add(h); } return res; } long ops(List haystack, List needle) { cmpOps = 0; getMissing(haystack, needle); return cmpOps; } } // ------------------------------------------------------------------------- // Fast — O(M log M) sort + merge difference using HashSet by nodeId // ------------------------------------------------------------------------- static class FastMigrationDiff { long cmpOps = 0; List getMissing(List haystack, List needle) { Set needleSet = new HashSet<>(needle); cmpOps += needle.size(); // build cost List res = new ArrayList<>(); for (MigrationInfo h : haystack) { cmpOps++; if (!needleSet.contains(h)) res.add(h); } return res; } long ops(List haystack, List needle) { cmpOps = 0; getMissing(haystack, needle); return cmpOps; } } static List makeMigrations(int n, int offset) { List list = new ArrayList<>(); for (int i = 0; i < n; i++) { list.add(new MigrationInfo("node-" + (offset + i), new int[]{(offset + i) * 10, (offset + i) * 10 + 9})); } return list; } public static void main(String[] args) { SlowMigrationDiff slow = new SlowMigrationDiff(); FastMigrationDiff fast = new FastMigrationDiff(); int passed = 0, total = 0; System.out.println("=== dragonfly-0001: GetMissingMigrations O(M²) ==="); int[] sizes = {10, 50, 100, 200}; for (int m : sizes) { List haystack = makeMigrations(m, 0); List needle = makeMigrations(m / 2, 0); // half overlap long s = slow.ops(haystack, needle); long f = fast.ops(haystack, needle); double ratio = (double) s / Math.max(f, 1); total++; boolean ok = s > f && ratio >= 2.0; System.out.printf("M=%3d slow=%6d fast=%4d ratio=%5.1fx %s%n", m, s, f, ratio, ok ? "PASS" : "FAIL"); if (ok) passed++; } // Correctness: both return same missing entries List h = makeMigrations(50, 0); List n = makeMigrations(30, 0); // first 30 overlap List sr = slow.getMissing(h, n); List fr = fast.getMissing(h, n); total++; boolean correct = sr.size() == fr.size(); System.out.printf("correctness (missing=%d): %s%n", sr.size(), correct ? "PASS" : "FAIL"); if (correct) passed++; // Ratio check at M=100 >= 5x List big = makeMigrations(100, 0); List ref = makeMigrations(50, 100); // fully disjoint long sOps = slow.ops(big, ref); long fOps = fast.ops(big, ref); double ratio = (double) sOps / Math.max(fOps, 1); total++; boolean ratioOk = ratio >= 5.0; System.out.printf("M=100 ratio=%.1fx >= 5x: %s%n", ratio, ratioOk ? "PASS" : "FAIL"); if (ratioOk) passed++; System.out.printf("%n%d/%d PASS%n", passed, total); if (passed < total) System.exit(1); } }