mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-15 19:57:07 +08:00
fix: 修复任务生命周期与调度就绪,优化执行和构建开销 (#3069)
* fix: harden task lifecycle and scheduler readiness * fix: confine log writes to the configured log directory * fix: verify complete build artifacts and untracked inputs * fix: reconcile scheduler state and make stop win startup races * fix: isolate cron generations and serialize scheduler recovery
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
// Microbenchmark of the process observer, not a container or scheduler benchmark.
|
||||
const fs = require('node:fs');
|
||||
const os = require('node:os');
|
||||
const crypto = require('node:crypto');
|
||||
const { spawn, execFileSync } = require('node:child_process');
|
||||
const { performance } = require('node:perf_hooks');
|
||||
const { observeChildProcess } = require('../back/shared/childProcess');
|
||||
|
||||
const args = process.argv.slice(2).filter((arg) => arg !== '--');
|
||||
const runsArg = args.find((arg) => arg.startsWith('--runs='));
|
||||
const runs = runsArg ? Number(runsArg.slice(7)) : 30;
|
||||
if (!Number.isInteger(runs) || runs < 3 || runs > 10000)
|
||||
throw new Error('runs must be between 3 and 10000');
|
||||
const output = args.find((arg) => arg.startsWith('--output='))?.slice(9);
|
||||
const git = (...args) => execFileSync('git', args, { encoding: 'utf8' }).trim();
|
||||
const scenarios = [
|
||||
{ name: 'empty-node', command: '' },
|
||||
{
|
||||
name: 'log-1MiB',
|
||||
command: 'process.stdout.write("x".repeat(1024 * 1024))',
|
||||
bytes: 1024 * 1024,
|
||||
},
|
||||
{ name: 'spawn-failure', shell: '/nonexistent-ql-benchmark-shell' },
|
||||
];
|
||||
|
||||
(async () => {
|
||||
const report = {
|
||||
schema: 1,
|
||||
observerSha256: crypto
|
||||
.createHash('sha256')
|
||||
.update(fs.readFileSync('back/shared/childProcess.ts'))
|
||||
.digest('hex'),
|
||||
benchmarkSha256: crypto
|
||||
.createHash('sha256')
|
||||
.update(fs.readFileSync(__filename))
|
||||
.digest('hex'),
|
||||
scope:
|
||||
'local process observer only; parent CPU excludes child CPU; no panel/container load measured',
|
||||
sourceCommit: git('rev-parse', 'HEAD'),
|
||||
dirty: git('status', '--porcelain') !== '',
|
||||
lockfileSha256: crypto
|
||||
.createHash('sha256')
|
||||
.update(fs.readFileSync('pnpm-lock.yaml'))
|
||||
.digest('hex'),
|
||||
node: process.version,
|
||||
platform: process.platform,
|
||||
arch: process.arch,
|
||||
cpu: os.cpus()[0]?.model,
|
||||
cores: os.cpus().length,
|
||||
timestamp: new Date().toISOString(),
|
||||
runs,
|
||||
scenarios: [],
|
||||
};
|
||||
for (const scenario of scenarios) {
|
||||
const samples = [];
|
||||
const cpuStart = process.cpuUsage();
|
||||
for (let index = 0; index < runs; index++) {
|
||||
const start = performance.now();
|
||||
let bytes = 0;
|
||||
const child = scenario.shell
|
||||
? spawn('true', { shell: scenario.shell })
|
||||
: spawn(process.execPath, ['-e', scenario.command]);
|
||||
const result = await observeChildProcess(child, {
|
||||
onStdout: async (chunk) => {
|
||||
bytes += Buffer.byteLength(chunk);
|
||||
},
|
||||
}).completed;
|
||||
if (scenario.shell ? !result.error : result.error || result.code !== 0)
|
||||
throw new Error(`Unexpected result: ${scenario.name}`);
|
||||
if (bytes !== (scenario.bytes || 0))
|
||||
throw new Error(`Output loss: ${scenario.name}`);
|
||||
samples.push({
|
||||
wallMs: performance.now() - start,
|
||||
bytes,
|
||||
code: result.code,
|
||||
error: result.error?.message,
|
||||
});
|
||||
}
|
||||
const sorted = samples.map((sample) => sample.wallMs).sort((a, b) => a - b);
|
||||
const percentile = (p) => sorted[Math.ceil(sorted.length * p) - 1];
|
||||
report.scenarios.push({
|
||||
name: scenario.name,
|
||||
parentCpuMicros: process.cpuUsage(cpuStart),
|
||||
p50Ms: percentile(0.5),
|
||||
p95Ms: percentile(0.95),
|
||||
...(runs >= 100 ? { p99Ms: percentile(0.99) } : {}),
|
||||
samples,
|
||||
});
|
||||
}
|
||||
const json = JSON.stringify(report, null, 2) + '\n';
|
||||
if (output) fs.writeFileSync(output, json);
|
||||
else process.stdout.write(json);
|
||||
})().catch((error) => {
|
||||
console.error(error);
|
||||
process.exitCode = 1;
|
||||
});
|
||||
@@ -0,0 +1,27 @@
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const crypto = require('node:crypto');
|
||||
const { execFileSync } = require('node:child_process');
|
||||
const { collectBuildFiles } = require('../docker/build-manifest.cjs');
|
||||
|
||||
const root = process.cwd();
|
||||
for (const output of ['static/build/app.js', 'static/dist/index.html']) {
|
||||
if (!fs.existsSync(path.join(root, output)))
|
||||
throw new Error(`Missing build output: ${output}`);
|
||||
}
|
||||
const git = (...args) =>
|
||||
execFileSync('git', args, { cwd: root, encoding: 'utf8' }).trim();
|
||||
const manifest = {
|
||||
version: 1,
|
||||
files: collectBuildFiles(),
|
||||
sourceCommit: git('rev-parse', 'HEAD'),
|
||||
dirty: git('status', '--porcelain', '--untracked-files=all') !== '',
|
||||
lockfileSha256: crypto
|
||||
.createHash('sha256')
|
||||
.update(fs.readFileSync('pnpm-lock.yaml'))
|
||||
.digest('hex'),
|
||||
};
|
||||
fs.writeFileSync(
|
||||
'static/build-info.json',
|
||||
JSON.stringify(manifest, null, 2) + '\n',
|
||||
);
|
||||
Reference in New Issue
Block a user