package unit; import java.util.*; /** * victoria-metrics-0001 — streamaggr: slices.Contains O(L×K) inside Push() hot loop * * Models lib/streamaggr/streamaggr.go: * func (a *aggregator) Push(tss []prompb.TimeSeries, ...) { * for _, ts := range tss { * labels.Labels = dropSeriesLabels(labels.Labels, ts.Labels, dropLabels) * getInputOutputLabels(..., a.by, a.without) * } * } * * dropSeriesLabels — for each label, calls slices.Contains(labelNames, label.Name) → O(K) * getInputOutputLabels — for each label, calls slices.Contains(without/by, label.Name) → O(K) * * Defective: O(N × L × K) total comparisons * Fixed: O(N × L) using pre-built map[string]struct{} (built once, O(K)) * * Ratio >= 10× at N=1000, L=20, K=20 * * No JUnit. Uses assert. Prints N/N PASS. * * Compile: javac -d . VictoriaMetricsStreamAggrAlgorithm.java * Run: java -ea -cp . unit.VictoriaMetricsStreamAggrAlgorithm */ public class VictoriaMetricsStreamAggrAlgorithm { static int passed = 0; static int total = 0; static void check(String desc, boolean cond) { total++; if (cond) { passed++; System.out.println("PASS: " + desc); } else { System.out.println("FAIL: " + desc); throw new AssertionError("FAIL: " + desc); } } // ----------------------------------------------------------------------- // Slow: models dropSeriesLabels + getInputOutputLabels with slices.Contains // For each of N series, for each of L labels, scans K-element filter list // Returns total comparison operations performed. // ----------------------------------------------------------------------- static long pushSlow(int N, int L, List filterList) { long ops = 0; int K = filterList.size(); // Simulate N time series, each with L labels named "label_0".."label_{L-1}" // filterList contains the "by" or "without" set (some subset of label names) for (int s = 0; s < N; s++) { for (int l = 0; l < L; l++) { String labelName = "label_" + l; // slices.Contains: O(K) linear scan for (int k = 0; k < K; k++) { ops++; if (filterList.get(k).equals(labelName)) { break; // found — early exit (worst case: not found, full K ops) } } } } return ops; } // ----------------------------------------------------------------------- // Fast: pre-build map[string]struct{} once, then O(1) per label per series // Returns total comparison operations performed. // ----------------------------------------------------------------------- static long pushFast(int N, int L, List filterList) { long ops = 0; // Build set once — O(K) Set filterSet = new HashSet<>(filterList); ops += filterList.size(); // cost of building the set // Now O(1) per label lookup for (int s = 0; s < N; s++) { for (int l = 0; l < L; l++) { String labelName = "label_" + l; ops++; // O(1) hash lookup filterSet.contains(labelName); } } return ops; } // ----------------------------------------------------------------------- // Correctness: both methods produce the same classification of labels // ----------------------------------------------------------------------- static List classifyLabelsSlice(List labels, List byList) { List output = new ArrayList<>(); for (String label : labels) { boolean inBy = false; for (String b : byList) { if (b.equals(label)) { inBy = true; break; } } if (inBy) output.add(label); // output = "by" labels } return output; } static List classifyLabelsMap(List labels, List byList) { Set bySet = new HashSet<>(byList); List output = new ArrayList<>(); for (String label : labels) { if (bySet.contains(label)) output.add(label); } return output; } public static void main(String[] args) { System.out.println("victoria-metrics-0001: streamaggr label filter O(N×L×K) → O(N×L)"); System.out.println(); // --- Correctness --- List labels = Arrays.asList("label_0","label_1","label_2","label_3","label_4"); List byList = Arrays.asList("label_1","label_3"); List slowResult = classifyLabelsSlice(labels, byList); List fastResult = classifyLabelsMap(labels, byList); check("correctness: same labels classified", slowResult.equals(fastResult)); check("correctness: 2 labels in 'by' set", slowResult.size() == 2); check("correctness: label_1 in output", slowResult.contains("label_1")); check("correctness: label_3 in output", slowResult.contains("label_3")); check("correctness: label_0 not in output", !slowResult.contains("label_0")); // --- Performance --- // N=1000 series, L=20 labels each, K=20 filter entries (worst case: none match → full scan) int N = 1000, L = 20, K = 20; // Build a filter list of 20 entries NOT matching any of our L labels // (worst case — label not found means full K comparisons every time) List filterList = new ArrayList<>(); for (int k = 0; k < K; k++) filterList.add("filter_" + k); long slowOps = pushSlow(N, L, filterList); long fastOps = pushFast(N, L, filterList); double ratio = (double) slowOps / fastOps; System.out.println(" N=" + N + " series, L=" + L + " labels, K=" + K + " filters"); System.out.println(" Slow ops: " + slowOps); System.out.println(" Fast ops: " + fastOps); System.out.printf (" Ratio: %.1fx%n", ratio); System.out.println(); check("ops: slow is O(N*L*K) = " + (N*L*K), slowOps == (long) N * L * K); check("ops: fast is ~O(N*L+K) < 2*N*L", fastOps <= (long) 2 * N * L + K); check("speedup >= 10x at N=1000/L=20/K=20", ratio >= 10.0); // Scale: larger cluster N = 10000; L = 30; K = 20; long slowOps2 = pushSlow(N, L, filterList); // reuse same filterList (K=20 entries) List filterList2 = new ArrayList<>(); for (int k = 0; k < K; k++) filterList2.add("filter_" + k); long fastOps2 = pushFast(N, L, filterList2); double ratio2 = (double) slowOps2 / fastOps2; System.out.println(" Scale test: N=" + N + " series, L=" + L + " labels, K=" + K); System.out.println(" Slow ops: " + slowOps2); System.out.println(" Fast ops: " + fastOps2); System.out.printf (" Ratio: %.1fx%n", ratio2); System.out.println(); check("scale: speedup >= 10x at N=10000/L=30/K=20", ratio2 >= 10.0); System.out.println(passed + "/" + total + " PASS"); } }