java-topology/defects/memcached/unit/MemcachedTest.java
russell@unturf.com 9934133dcf whitepaper: 312 sites / 151 ecosystems — wave2+3 defect tables and PDF rebuild
Add 88 new defect entries to HIGH and MEDIUM tables:
  HIGH: mysql-0001/0002, mariadb-0001, redis-0001/0002, valkey-0001/0002, openvpn-0001,
        vlc-0001, prometheus-0001, otel-collector-0001, cockroachdb-0001..0004,
        tidb-0001..0008, kubernetes-0001/0002, go-0001, kotlin-0002, scala-0001,
        allegro5-0001, sdl2-0001, grafana-0001, clickhouse-0001, duckdb-0001,
        mongodb-0001, envoy-0001, istio-0001, cilium-0001, linkerd2-0001,
        linux-0001/0002/0003, tor-0002/0003, curl-0001, julia-0001, lua-0001,
        perl5-0001, nats-0001, spring-0003/0004, tomcat-0001, onos-0002, odl-0002

  MEDIUM: helm-0001, mariadb-0002, openssl-0001/0002, memcached-0001,
          cassandra-0001..0004, flink-0001, storm-0001/0002, zookeeper-0001..0003,
          pip-0001, gradle-0001, nginx-0001, haproxy-0001, caddy-0001, varnish-0001,
          ffmpeg-0001, gstreamer-0001, raylib-0001, love2d-0001, php-0001/0002,
          r-source-0001, cpython-0002, ruby-0001, rabbitmq-0003/0004, activemq-0001,
          ovs-0001, onos-0003, odl-0002, jetty-0001

PDF: 976K
2026-03-27 15:23:43 -04:00

210 lines
8.4 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.*;
/**
* MemcachedTest — CWE-407 benchmark for memcached-0001
*
* memcached-0001: slabs_clsid() O(n) linear scan over sorted slabclass[] array
* SLOW: O(n) — while loop walking up to 63 slab classes
* FAST: O(log n) — binary search, ⌈log₂63⌉ = 6 comparisons max
*
* slabs_clsid is called on every item allocation (do_item_alloc) making it
* a hot path under write load.
*/
public class MemcachedTest {
// -------------------------------------------------------------------------
// Benchmark harness
// -------------------------------------------------------------------------
static void bench(String label, Runnable slow, Runnable fast, long sOps, long fOps) {
slow.run(); fast.run();
long t0 = System.nanoTime(); slow.run(); long sMs = (System.nanoTime() - t0) / 1_000_000;
long t1 = System.nanoTime(); fast.run(); long fMs = (System.nanoTime() - t1) / 1_000_000;
System.out.printf(" %-56s slow:%4dms (%,d ops) fast:%4dms (%,d ops) speedup:%.0fx%n",
label, sMs, sOps, fMs, fOps, fOps > 0 ? (double) sOps / fOps : 0);
}
// =========================================================================
// Model: slab class array (mirrors slabclass[].size in memcached)
//
// Sizes are generated with factor=1.25 starting at 96 bytes (memcached
// default: chunk_size=48, align to 8 → starts at 96 with use_cas).
// MAX_NUMBER_OF_SLAB_CLASSES = 64 (1..63 + power_largest).
// =========================================================================
static final int POWER_SMALLEST = 1;
static final int MAX_CLASSES = 64;
static int[] buildSlabSizes() {
int[] sizes = new int[MAX_CLASSES];
double size = 96.0;
double factor = 1.25;
int chunkMax = 1024 * 1024; // 1 MB item_size_max
for (int i = POWER_SMALLEST; i < MAX_CLASSES - 1; i++) {
// align to 8 bytes
int aligned = ((int) size + 7) & ~7;
sizes[i] = aligned;
if (aligned >= chunkMax / factor) {
// fill remaining classes with chunkMax
for (int j = i + 1; j < MAX_CLASSES; j++) sizes[j] = chunkMax;
break;
}
size *= factor;
}
sizes[MAX_CLASSES - 1] = chunkMax;
return sizes;
}
// -------------------------------------------------------------------------
// SLOW: linear scan (current memcached code)
// -------------------------------------------------------------------------
/** Returns number of comparisons performed. */
static long slabs_clsid_slow(int[] sizes, int powerLargest, int querySize) {
int res = POWER_SMALLEST;
long ops = 0;
while (querySize > sizes[res]) {
ops++;
if (res++ == powerLargest) return ops;
}
ops++; // final comparison that passes
return ops;
}
// -------------------------------------------------------------------------
// FAST: binary search (the fix)
// -------------------------------------------------------------------------
/** Returns number of comparisons performed. */
static long slabs_clsid_fast(int[] sizes, int powerLargest, int querySize) {
int lo = POWER_SMALLEST, hi = powerLargest;
long ops = 0;
while (lo < hi) {
int mid = lo + (hi - lo) / 2;
ops++;
if (sizes[mid] < querySize)
lo = mid + 1;
else
hi = mid;
}
ops++; // final check lo==hi
return ops;
}
// =========================================================================
// Main
// =========================================================================
public static void main(String[] args) {
System.out.println("MemcachedTest — CWE-407");
System.out.println();
int[] sizes = buildSlabSizes();
// Find actual power_largest
int powerLargest = MAX_CLASSES - 1;
for (int i = POWER_SMALLEST; i < MAX_CLASSES; i++) {
if (sizes[i] == 0) { powerLargest = i - 1; break; }
}
System.out.printf(" Slab classes: POWER_SMALLEST=%d power_largest=%d%n",
POWER_SMALLEST, powerLargest);
System.out.printf(" size[1]=%d size[%d]=%d%n",
sizes[1], powerLargest, sizes[powerLargest]);
System.out.println();
int passed = 0, total = 0;
// --- Scenario 1: worst case — query fits only in the largest class ---
{
int querySize = sizes[powerLargest]; // must scan all the way
final long[] sOps = {0}, fOps = {0};
final int[] ps = {powerLargest};
final int[] qs = {querySize};
Runnable slow = () -> {
long ops = 0;
for (int r = 0; r < 5_000_000; r++)
ops += slabs_clsid_slow(sizes, ps[0], qs[0]);
sOps[0] = ops;
};
Runnable fast = () -> {
long ops = 0;
for (int r = 0; r < 5_000_000; r++)
ops += slabs_clsid_fast(sizes, ps[0], qs[0]);
fOps[0] = ops;
};
slow.run(); fast.run();
bench("memcached-0001 slabs_clsid worst-case size=max (5M allocs)", slow, fast, sOps[0], fOps[0]);
total++;
// Expect linear >> log; linear = ~63 ops, log = ~6 ops => ~10x
boolean ok = sOps[0] >= fOps[0] * 5;
System.out.printf(" [%s] slow=%,d fast=%,d ratio=%.1fx (need >=5x)%n",
ok ? "PASS" : "FAIL", sOps[0], fOps[0], (double) sOps[0] / fOps[0]);
if (ok) passed++;
}
// --- Scenario 2: mixed query sizes (realistic workload) ---
{
// Queries uniformly distributed over all slab sizes
int numSizes = powerLargest - POWER_SMALLEST + 1;
int[] queries = new int[numSizes];
for (int i = 0; i < numSizes; i++) queries[i] = sizes[POWER_SMALLEST + i];
final long[] sOps = {0}, fOps = {0};
final int[] ps = {powerLargest};
Runnable slow = () -> {
long ops = 0;
for (int r = 0; r < 200_000; r++)
for (int q : queries)
ops += slabs_clsid_slow(sizes, ps[0], q);
sOps[0] = ops;
};
Runnable fast = () -> {
long ops = 0;
for (int r = 0; r < 200_000; r++)
for (int q : queries)
ops += slabs_clsid_fast(sizes, ps[0], q);
fOps[0] = ops;
};
slow.run(); fast.run();
bench("memcached-0001 slabs_clsid mixed sizes (200k×all)", slow, fast, sOps[0], fOps[0]);
total++;
// Average linear: ~(63+1)/2 ≈ 32 ops; average binary: ~5 ops
boolean ok = sOps[0] >= fOps[0] * 4;
System.out.printf(" [%s] slow=%,d fast=%,d ratio=%.1fx (need >=4x)%n",
ok ? "PASS" : "FAIL", sOps[0], fOps[0], (double) sOps[0] / fOps[0]);
if (ok) passed++;
}
// --- Scenario 3: correctness check — both find the same class ---
{
Random rng = new Random(0);
int maxSize = sizes[powerLargest];
int mismatches = 0;
for (int i = 0; i < 100_000; i++) {
int q = 1 + rng.nextInt(maxSize);
// find expected class with slow (reference)
int res_s = POWER_SMALLEST;
while (q > sizes[res_s] && res_s < powerLargest) res_s++;
// find with fast
int lo = POWER_SMALLEST, hi = powerLargest;
while (lo < hi) {
int mid = lo + (hi - lo) / 2;
if (sizes[mid] < q) lo = mid + 1;
else hi = mid;
}
int res_f = lo;
if (res_s != res_f) mismatches++;
}
total++;
boolean ok = mismatches == 0;
System.out.printf(" %-56s [%s] mismatches=%d%n",
"memcached-0001 correctness (100k random queries)",
ok ? "PASS" : "FAIL", mismatches);
if (ok) passed++;
}
System.out.println();
System.out.printf("%d/%d PASS%n", passed, total);
if (passed < total) System.exit(1);
}
}