147 lines
5.2 KiB
Java
147 lines
5.2 KiB
Java
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<MigrationInfo> needle, MigrationInfo h) {
|
|
for (MigrationInfo n : needle) {
|
|
cmpOps++;
|
|
if (h.equals(n)) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
List<MigrationInfo> getMissing(List<MigrationInfo> haystack,
|
|
List<MigrationInfo> needle) {
|
|
List<MigrationInfo> res = new ArrayList<>();
|
|
for (MigrationInfo h : haystack) {
|
|
if (!find(needle, h)) res.add(h);
|
|
}
|
|
return res;
|
|
}
|
|
|
|
long ops(List<MigrationInfo> haystack, List<MigrationInfo> 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<MigrationInfo> getMissing(List<MigrationInfo> haystack,
|
|
List<MigrationInfo> needle) {
|
|
Set<MigrationInfo> needleSet = new HashSet<>(needle);
|
|
cmpOps += needle.size(); // build cost
|
|
List<MigrationInfo> res = new ArrayList<>();
|
|
for (MigrationInfo h : haystack) {
|
|
cmpOps++;
|
|
if (!needleSet.contains(h)) res.add(h);
|
|
}
|
|
return res;
|
|
}
|
|
|
|
long ops(List<MigrationInfo> haystack, List<MigrationInfo> needle) {
|
|
cmpOps = 0;
|
|
getMissing(haystack, needle);
|
|
return cmpOps;
|
|
}
|
|
}
|
|
|
|
static List<MigrationInfo> makeMigrations(int n, int offset) {
|
|
List<MigrationInfo> 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<MigrationInfo> haystack = makeMigrations(m, 0);
|
|
List<MigrationInfo> 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<MigrationInfo> h = makeMigrations(50, 0);
|
|
List<MigrationInfo> n = makeMigrations(30, 0); // first 30 overlap
|
|
List<MigrationInfo> sr = slow.getMissing(h, n);
|
|
List<MigrationInfo> 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<MigrationInfo> big = makeMigrations(100, 0);
|
|
List<MigrationInfo> 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);
|
|
}
|
|
}
|