mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-20 16:07:11 +08:00
feat(ql3): establish 3.0 incubation baseline
This commit is contained in:
@@ -0,0 +1,235 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const os = require('node:os');
|
||||
const path = require('node:path');
|
||||
const { performance } = require('node:perf_hooks');
|
||||
const { setTimeout: delay } = require('node:timers/promises');
|
||||
|
||||
const args = new Map(
|
||||
process.argv.slice(2).map((argument) => {
|
||||
const separator = argument.indexOf('=');
|
||||
return separator === -1
|
||||
? [argument, true]
|
||||
: [argument.slice(0, separator), argument.slice(separator + 1)];
|
||||
}),
|
||||
);
|
||||
|
||||
function numericArgument(name) {
|
||||
const raw = args.get(name);
|
||||
if (raw === undefined) return undefined;
|
||||
const value = Number(raw);
|
||||
if (!Number.isFinite(value) || value < 0) {
|
||||
throw new Error(`${name} must be a non-negative number`);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
const modulePath = path.resolve(
|
||||
__dirname,
|
||||
'../static/build/runtime/adapters/local-process/localProcessExecutor.js',
|
||||
);
|
||||
const rssBeforeModuleLoad = process.memoryUsage().rss;
|
||||
let LocalProcessExecutor;
|
||||
try {
|
||||
({ LocalProcessExecutor } = require(modulePath));
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
`Compiled QingLong 3.0 runtime was not found. Run pnpm build:back first. (${error.message})`,
|
||||
);
|
||||
}
|
||||
const rssAfterModuleLoad = process.memoryUsage().rss;
|
||||
|
||||
let idSequence = 400;
|
||||
function nextId() {
|
||||
idSequence += 1;
|
||||
return `019f7100-0000-7000-8000-${String(idSequence).padStart(12, '0')}`;
|
||||
}
|
||||
|
||||
function createSpec(overrides = {}) {
|
||||
return {
|
||||
runId: nextId(),
|
||||
attemptId: nextId(),
|
||||
projectId: 'benchmark',
|
||||
taskId: 'edge-executor-benchmark',
|
||||
taskRevision: 'benchmark-v1',
|
||||
command: {
|
||||
kind: 'argv',
|
||||
file: process.execPath,
|
||||
args: ['-e', 'process.exit(0)'],
|
||||
},
|
||||
environmentPolicy: 'isolated',
|
||||
terminationGraceMs: 100,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function createCountingSink() {
|
||||
const metrics = { bytes: 0, lines: 0, writes: 0 };
|
||||
return {
|
||||
metrics,
|
||||
context: {
|
||||
environment: {},
|
||||
output: {
|
||||
async write(output) {
|
||||
metrics.bytes += output.chunk.byteLength;
|
||||
metrics.writes += 1;
|
||||
for (const byte of output.chunk) {
|
||||
if (byte === 10) metrics.lines += 1;
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async function measuredExecution(executor, name, spec, context) {
|
||||
const baselineRssBytes = process.memoryUsage().rss;
|
||||
let peakRssBytes = baselineRssBytes;
|
||||
const sampler = setInterval(() => {
|
||||
peakRssBytes = Math.max(peakRssBytes, process.memoryUsage().rss);
|
||||
}, 5);
|
||||
sampler.unref?.();
|
||||
const startedAt = performance.now();
|
||||
try {
|
||||
const handle = await executor.start(spec, context);
|
||||
const result = await handle.completion;
|
||||
peakRssBytes = Math.max(peakRssBytes, process.memoryUsage().rss);
|
||||
return {
|
||||
name,
|
||||
durationMs: Number((performance.now() - startedAt).toFixed(3)),
|
||||
baselineRssBytes,
|
||||
peakRssBytes,
|
||||
peakRssDeltaBytes: Math.max(0, peakRssBytes - baselineRssBytes),
|
||||
outcome: result.outcome,
|
||||
exitCode: result.exitCode,
|
||||
};
|
||||
} finally {
|
||||
clearInterval(sampler);
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const executor = new LocalProcessExecutor({ createHandleId: nextId });
|
||||
|
||||
const noopOutput = createCountingSink();
|
||||
const noop = await measuredExecution(
|
||||
executor,
|
||||
'single_noop',
|
||||
createSpec(),
|
||||
noopOutput.context,
|
||||
);
|
||||
|
||||
const logOutput = createCountingSink();
|
||||
const log = await measuredExecution(
|
||||
executor,
|
||||
'stdout_10000_lines',
|
||||
createSpec({
|
||||
command: {
|
||||
kind: 'argv',
|
||||
file: process.execPath,
|
||||
args: [
|
||||
'-e',
|
||||
"for (let i = 0; i < 10000; i += 1) process.stdout.write(String(i).padStart(5, '0') + ' ql3-edge-benchmark\\n')",
|
||||
],
|
||||
},
|
||||
}),
|
||||
logOutput.context,
|
||||
);
|
||||
log.output = logOutput.metrics;
|
||||
|
||||
const cancelOutput = createCountingSink();
|
||||
const cancelHandle = await executor.start(
|
||||
createSpec({
|
||||
command: {
|
||||
kind: 'argv',
|
||||
file: process.execPath,
|
||||
args: ['-e', 'setInterval(() => undefined, 1000)'],
|
||||
},
|
||||
}),
|
||||
cancelOutput.context,
|
||||
);
|
||||
await delay(10);
|
||||
const cancelStartedAt = performance.now();
|
||||
const stop = await executor.stop(cancelHandle, {
|
||||
kind: 'user',
|
||||
requestedAtMs: Date.now(),
|
||||
});
|
||||
const cancelResult = await cancelHandle.completion;
|
||||
const cancellation = {
|
||||
durationMs: Number((performance.now() - cancelStartedAt).toFixed(3)),
|
||||
outcome: cancelResult.outcome,
|
||||
termSignalSent: stop.termSignalSent,
|
||||
killSignalSent: stop.killSignalSent,
|
||||
};
|
||||
|
||||
if (noop.outcome !== 'succeeded' || log.outcome !== 'succeeded') {
|
||||
throw new Error('Executor smoke workload did not succeed');
|
||||
}
|
||||
if (log.output.lines !== 10_000) {
|
||||
throw new Error(
|
||||
`Executor output benchmark expected 10000 lines, observed ${log.output.lines}`,
|
||||
);
|
||||
}
|
||||
if (cancellation.outcome !== 'cancelled') {
|
||||
throw new Error('Executor cancellation workload was not cancelled');
|
||||
}
|
||||
|
||||
const report = {
|
||||
schemaVersion: 1,
|
||||
profile: 'edge',
|
||||
generatedAt: new Date().toISOString(),
|
||||
host: {
|
||||
platform: process.platform,
|
||||
architecture: process.arch,
|
||||
node: process.version,
|
||||
cpuCount: os.cpus().length,
|
||||
totalMemoryBytes: os.totalmem(),
|
||||
},
|
||||
moduleLoad: {
|
||||
rssBeforeBytes: rssBeforeModuleLoad,
|
||||
rssAfterBytes: rssAfterModuleLoad,
|
||||
rssDeltaBytes: Math.max(0, rssAfterModuleLoad - rssBeforeModuleLoad),
|
||||
},
|
||||
cases: [noop, log],
|
||||
cancellation,
|
||||
};
|
||||
|
||||
const maxRssDeltaMb = numericArgument('--max-rss-delta-mb');
|
||||
const maxCancelMs = numericArgument('--max-cancel-ms');
|
||||
const observedPeakDeltaBytes = Math.max(
|
||||
report.moduleLoad.rssDeltaBytes,
|
||||
...report.cases.map((item) => item.peakRssDeltaBytes),
|
||||
);
|
||||
const violations = [];
|
||||
if (
|
||||
maxRssDeltaMb !== undefined &&
|
||||
observedPeakDeltaBytes > maxRssDeltaMb * 1024 * 1024
|
||||
) {
|
||||
violations.push(
|
||||
`peak RSS delta ${observedPeakDeltaBytes} exceeded ${maxRssDeltaMb} MiB`,
|
||||
);
|
||||
}
|
||||
if (maxCancelMs !== undefined && cancellation.durationMs > maxCancelMs) {
|
||||
violations.push(
|
||||
`cancellation ${cancellation.durationMs}ms exceeded ${maxCancelMs}ms`,
|
||||
);
|
||||
}
|
||||
report.gates = {
|
||||
maxRssDeltaMb: maxRssDeltaMb ?? null,
|
||||
maxCancelMs: maxCancelMs ?? null,
|
||||
passed: violations.length === 0,
|
||||
violations,
|
||||
};
|
||||
|
||||
if (args.has('--json')) {
|
||||
process.stdout.write(`${JSON.stringify(report)}\n`);
|
||||
} else {
|
||||
process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
|
||||
}
|
||||
if (violations.length > 0) process.exitCode = 1;
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
process.stderr.write(`ql3 edge benchmark failed: ${error.message}\n`);
|
||||
process.exitCode = 1;
|
||||
});
|
||||
Reference in New Issue
Block a user