Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 | 4x 4x 4x 3x 9x 3x 3x 3x 3x 1x 2x 1x 1x 3x 1x 1x 1x 2x 6x 6x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 1x 4x 4x 1x 4x 4x | import { TEST_RUN_ID_PATTERN } from "@shared/input-limits";
export type RestCompileBatchOptions = {
baseUrl: string;
runId: string;
requestCount: number;
concurrency: number;
timeoutMs?: number;
};
export type RestCompileRequestResult = {
request: number;
startedAtMs: number;
completedAtMs: number;
statusCode: number;
latencyMs: number;
success: boolean;
serviceUnavailable: boolean;
};
export type RestCompileBatchMeasurement = {
requested: number;
concurrency: number;
completed: number;
successful: number;
failed: number;
serviceUnavailable: number;
averageLatencyMs: number;
p50LatencyMs: number;
p95LatencyMs: number;
p99LatencyMs: number;
maxLatencyMs: number;
latencyMs: number[];
results: RestCompileRequestResult[];
};
const MAX_REST_COMPILE_REQUESTS = 128;
const MAX_REST_COMPILE_CONCURRENCY = 64;
const DEFAULT_REST_COMPILE_TIMEOUT_MS = 120_000;
function percentile(values: readonly number[], percentileRank: number): number {
Iif (values.length === 0) return 0;
const sorted = [...values].sort((left, right) => left - right);
return sorted[Math.max(0, Math.ceil(percentileRank / 100 * sorted.length) - 1)];
}
export async function runRestCompileBatch(
options: RestCompileBatchOptions,
fetcher: typeof fetch = fetch,
now: () => number = Date.now,
): Promise<RestCompileBatchMeasurement> {
Iif (!options.baseUrl) throw new Error("A backend URL is required for the REST compile workload");
Iif (!TEST_RUN_ID_PATTERN.test(options.runId)) throw new Error("runId must be a URL-safe test run identifier");
if (!Number.isInteger(options.requestCount) || options.requestCount < 1 || options.requestCount > MAX_REST_COMPILE_REQUESTS) {
throw new Error(`requestCount must be between 1 and ${MAX_REST_COMPILE_REQUESTS}`);
}
if (!Number.isInteger(options.concurrency) || options.concurrency < 1
|| options.concurrency > MAX_REST_COMPILE_CONCURRENCY || options.concurrency > options.requestCount) {
throw new Error(`concurrency must be between 1 and min(requestCount, ${MAX_REST_COMPILE_CONCURRENCY})`);
}
const timeoutMs = options.timeoutMs ?? DEFAULT_REST_COMPILE_TIMEOUT_MS;
Iif (!Number.isInteger(timeoutMs) || timeoutMs < 1_000 || timeoutMs > 300_000) {
throw new Error("timeoutMs must be between 1000 and 300000");
}
const results = new Array<RestCompileRequestResult>(options.requestCount);
let nextRequest = 0;
const workers = Array.from({ length: options.concurrency }, async () => {
while (true) {
const request = nextRequest++;
if (request >= options.requestCount) return;
const startedAt = now();
const code = `// REST_COMPILE_REQUEST_${request} run=${options.runId}\nvoid setup() {}\nvoid loop() { delay(1); }\n`;
let statusCode = 0;
let success = false;
try {
const response = await fetcher(`${options.baseUrl.replace(/\/$/, "")}/api/compile`, {
method: "POST",
headers: {
"content-type": "application/json",
"x-test-run-id": options.runId,
},
body: JSON.stringify({ code }),
signal: AbortSignal.timeout(timeoutMs),
});
statusCode = response.status;
const body = await response.json().catch(() => null) as { success?: unknown } | null;
success = response.ok && body?.success === true;
} catch {
statusCode = 0;
}
const completedAt = now();
results[request] = {
request,
startedAtMs: startedAt,
completedAtMs: completedAt,
statusCode,
latencyMs: Math.max(0, completedAt - startedAt),
success,
serviceUnavailable: statusCode === 503,
};
}
});
await Promise.all(workers);
const latencies = results.map((result) => result.latencyMs);
const successful = results.filter((result) => result.success).length;
return {
requested: options.requestCount,
concurrency: options.concurrency,
completed: results.length,
successful,
failed: results.length - successful,
serviceUnavailable: results.filter((result) => result.serviceUnavailable).length,
averageLatencyMs: latencies.reduce((total, value) => total + value, 0) / Math.max(1, latencies.length),
p50LatencyMs: percentile(latencies, 50),
p95LatencyMs: percentile(latencies, 95),
p99LatencyMs: percentile(latencies, 99),
maxLatencyMs: latencies.length > 0 ? Math.max(...latencies) : 0,
latencyMs: latencies,
results,
};
}
|