java-topology/defects/nginx/unit/NginxVariablesInitAlgorithmTest.java

186 lines
6.8 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package unit;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
/**
* nginx-0003: ngx_http_variables_init_vars O(V×K) nested scan at startup.
*
* Models the resolution of V indexed variables against K registered variable
* keys using ngx_strncmp (SLOW: nested loop) vs a HashMap (FAST: O(1) lookup).
*
* Run: javac NginxVariablesInitAlgorithmTest.java && java unit.NginxVariablesInitAlgorithmTest
*/
public class NginxVariablesInitAlgorithmTest {
static long slowOps = 0;
static long fastOps = 0;
/**
* SLOW: O(V×K) nested loop — mirrors ngx_http_variables_init_vars.
* For each indexed variable, scan all variable_keys entries with strcmp.
*
* @param indexedVars list of indexed variable names (V)
* @param keyNames list of registered key names (K)
* @param handlers map from key name to handler index (simulated)
* @return resolved handler array (one per indexed var, -1 if not found)
*/
static int[] resolveVarsSlow(List<String> indexedVars, List<String> keyNames) {
int[] handlers = new int[indexedVars.size()];
for (int i = 0; i < indexedVars.size(); i++) { // outer: V
handlers[i] = -1;
for (int n = 0; n < keyNames.size(); n++) { // inner: K
slowOps++;
if (indexedVars.get(i).equals(keyNames.get(n))) {
handlers[i] = n; // "set handler"
break;
}
}
}
return handlers;
}
/**
* FAST: O(V + K) — build a HashMap from keyNames first, then O(1) per var.
* Models the fix: build a temporary hash of variables_keys, then resolve.
*/
static int[] resolveVarsFast(List<String> indexedVars, List<String> keyNames) {
// Build map: O(K)
HashMap<String, Integer> keyMap = new HashMap<>(keyNames.size() * 2);
for (int n = 0; n < keyNames.size(); n++) {
fastOps++;
keyMap.put(keyNames.get(n), n);
}
// Resolve: O(V)
int[] handlers = new int[indexedVars.size()];
for (int i = 0; i < indexedVars.size(); i++) {
fastOps++;
Integer h = keyMap.get(indexedVars.get(i));
handlers[i] = (h != null) ? h : -1;
}
return handlers;
}
static boolean runTest(String name, int numVars, int numKeys, int expectedResolved) {
// Build keyNames: all registered variable names (built-ins + module vars)
List<String> keyNames = new ArrayList<>(numKeys);
for (int n = 0; n < numKeys; n++) {
keyNames.add("var_key_" + n);
}
// Build indexedVars: a subset of keyNames (vars referenced in config)
// First numVars entries from keyNames are referenced
List<String> indexedVars = new ArrayList<>(numVars);
for (int i = 0; i < numVars; i++) {
indexedVars.add("var_key_" + i); // all should resolve
}
slowOps = 0;
fastOps = 0;
int[] slowResult = resolveVarsSlow(indexedVars, keyNames);
long slowCount = slowOps;
slowOps = 0;
fastOps = 0;
int[] fastResult = resolveVarsFast(indexedVars, keyNames);
long fastCount = fastOps;
// Verify correctness
int resolvedSlow = 0, resolvedFast = 0;
for (int i = 0; i < numVars; i++) {
if (slowResult[i] != -1) resolvedSlow++;
if (fastResult[i] != -1) resolvedFast++;
if (slowResult[i] != fastResult[i]) {
System.out.printf("FAIL [%s] N=%d,K=%d: result mismatch at i=%d slow=%d fast=%d%n",
name, numVars, numKeys, i, slowResult[i], fastResult[i]);
return false;
}
}
if (resolvedSlow != expectedResolved) {
System.out.printf("FAIL [%s] N=%d,K=%d: expected %d resolved, got %d%n",
name, numVars, numKeys, expectedResolved, resolvedSlow);
return false;
}
double ratio = (double) slowCount / fastCount;
System.out.printf("PASS [%s] N=%d K=%d resolved=%d slow=%d fast=%d ratio=%.1fx%n",
name, numVars, numKeys, resolvedSlow, slowCount, fastCount, ratio);
if (numVars >= 50 && ratio < 5.0) {
System.out.printf("FAIL [%s] ratio %.1f < 5.0 minimum%n", name, ratio);
return false;
}
return true;
}
public static void main(String[] args) {
int passed = 0, total = 0;
// Test 1: small config — V=10, K=60 (built-ins only)
total++;
if (runTest("small-config", 10, 60, 10)) passed++;
// Test 2: medium config — V=50, K=150 (built-ins + module vars)
total++;
if (runTest("medium-config", 50, 150, 50)) passed++;
// Test 3: large config — V=150, K=300 (heavy module load)
total++;
if (runTest("large-config", 150, 300, 150)) passed++;
// Test 4: extra-large — V=300, K=500
total++;
if (runTest("xlarge-config", 300, 500, 300)) passed++;
// Test 5: partial resolution (some vars are prefix-matched, not in key list)
// Only first half of indexed vars are in keyNames
{
total++;
int numVars = 100;
int numKeys = 200;
List<String> keyNames = new ArrayList<>(numKeys);
for (int n = 0; n < numKeys; n++) {
keyNames.add("key_" + n);
}
List<String> indexedVars = new ArrayList<>(numVars);
for (int i = 0; i < numVars; i++) {
if (i < numVars / 2) {
indexedVars.add("key_" + i); // will resolve
} else {
indexedVars.add("prefix_var_" + i); // will NOT resolve via keys (prefix path)
}
}
slowOps = 0;
int[] slowResult = resolveVarsSlow(indexedVars, keyNames);
long slowCount = slowOps;
slowOps = 0;
int[] fastResult = resolveVarsFast(indexedVars, keyNames);
long fastCount = fastOps;
boolean ok = true;
for (int i = 0; i < numVars; i++) {
if (slowResult[i] != fastResult[i]) {
ok = false;
break;
}
}
double ratio = (double) slowCount / fastCount;
if (ok) {
System.out.printf("PASS [partial-resolution] N=%d K=%d slow=%d fast=%d ratio=%.1fx%n",
numVars, numKeys, slowCount, fastCount, ratio);
passed++;
} else {
System.out.printf("FAIL [partial-resolution] result mismatch%n");
}
}
System.out.printf("%n%d/%d PASS%n", passed, total);
if (passed != total) System.exit(1);
}
}