Files
qinglong/scripts/ql3-plugin-package-recovery-edge-benchmark.cjs

864 lines
27 KiB
JavaScript

#!/usr/bin/env node
const crypto = require('node:crypto');
const fs = require('node:fs');
const os = require('node:os');
const path = require('node:path');
const { performance } = require('node:perf_hooks');
const MIB = 1024 * 1024;
const DEFAULT_MAX_DURATION_MS = 10_000;
const DEFAULT_MAX_RSS_DELTA_MB = 96;
const DEFAULT_MAX_DATABASE_GROWTH_BYTES = 4 * MIB;
const REPORT_KEYS = Object.freeze([
'calls',
'database',
'durable',
'evidenceClass',
'gates',
'identity',
'limitations',
'measurement',
'recovery',
'schemaVersion',
'supported',
'thresholds',
]);
const CALL_KEYS = Object.freeze([
'byteSourceClose',
'byteSourceOpen',
'byteSourceRead',
'publisherInspect',
'publisherPublish',
'stage',
]);
const DATABASE_KEYS = Object.freeze([
'allocatedBytes',
'allocatedGrowthBytes',
'files',
'integrityCheck',
'journalMode',
'logicalBytes',
'logicalGrowthBytes',
'materializedCandidateRevisions',
'synchronous',
]);
const DATABASE_FILE_KEYS = Object.freeze([
'allocatedBytes',
'logicalBytes',
'suffix',
]);
const DURABLE_KEYS = Object.freeze([
'activeLockDigestPreserved',
'failedFrom',
'failureReason',
'headInstallationIsCandidate',
'previousActiveLockDigestPreserved',
'state',
]);
const GATE_KEYS = Object.freeze(['passed', 'violations']);
const IDENTITY_KEYS = Object.freeze(['architecture', 'node', 'platform']);
const MEASUREMENT_KEYS = Object.freeze([
'durationMs',
'rssAfterBytes',
'rssBeforeBytes',
'rssDeltaBytes',
]);
const RECOVERY_KEYS = Object.freeze([
'deferred',
'manualRequired',
'pages',
'remaining',
'retry',
'safeToAdmit',
'scanned',
'settled',
'superseded',
]);
const THRESHOLD_KEYS = Object.freeze([
'maxDatabaseGrowthBytes',
'maxDurationMs',
'maxRssDeltaBytes',
]);
const EXPECTED_LIMITATIONS = Object.freeze([
'container_or_vm_resource_limits_are_not_physical_support_evidence',
'physical_power_loss_not_proven',
]);
class QingLong3PluginPackageRecoveryEdgeBenchmarkError extends Error {
constructor(message) {
super(
`QingLong 3.0 Plugin Package recovery Edge benchmark failed: ${message}`,
);
this.name = 'QingLong3PluginPackageRecoveryEdgeBenchmarkError';
}
}
function hasExactKeys(value, expected) {
return (
value !== null &&
typeof value === 'object' &&
!Array.isArray(value) &&
JSON.stringify(Object.keys(value).sort()) === JSON.stringify(expected)
);
}
function positiveNumber(value, label) {
const parsed = Number(value);
if (!Number.isFinite(parsed) || parsed <= 0) {
throw new QingLong3PluginPackageRecoveryEdgeBenchmarkError(
`${label} must be a positive number`,
);
}
return parsed;
}
function positiveSafeInteger(value, label) {
const parsed = Number(value);
if (!Number.isSafeInteger(parsed) || parsed < 1) {
throw new QingLong3PluginPackageRecoveryEdgeBenchmarkError(
`${label} must be a positive safe integer`,
);
}
return parsed;
}
function parseArguments(argv) {
const options = {
json: false,
maxDatabaseGrowthBytes: DEFAULT_MAX_DATABASE_GROWTH_BYTES,
maxDurationMs: DEFAULT_MAX_DURATION_MS,
maxRssDeltaBytes: DEFAULT_MAX_RSS_DELTA_MB * MIB,
};
for (const argument of argv) {
if (argument === '--') continue;
if (argument === '--json') {
options.json = true;
continue;
}
const [name, raw] = argument.split('=', 2);
if (raw === undefined) {
throw new QingLong3PluginPackageRecoveryEdgeBenchmarkError(
`unsupported argument ${argument}`,
);
}
if (name === '--max-duration-ms') {
options.maxDurationMs = positiveNumber(raw, name);
} else if (name === '--max-rss-delta-mb') {
options.maxRssDeltaBytes = positiveNumber(raw, name) * MIB;
} else if (name === '--max-database-growth-bytes') {
options.maxDatabaseGrowthBytes = positiveSafeInteger(raw, name);
} else {
throw new QingLong3PluginPackageRecoveryEdgeBenchmarkError(
`unsupported argument ${name}`,
);
}
}
return Object.freeze(options);
}
function round(value) {
return Math.round(value * 1_000) / 1_000;
}
function sha256(value) {
return crypto.createHash('sha256').update(value).digest('hex');
}
function storageSnapshot(databasePath) {
const files = [
['database', databasePath],
['-journal', `${databasePath}-journal`],
['-wal', `${databasePath}-wal`],
['-shm', `${databasePath}-shm`],
]
.filter(([, filePath]) => fs.existsSync(filePath))
.map(([suffix, filePath]) => {
const stat = fs.statSync(filePath);
return Object.freeze({
suffix,
logicalBytes: stat.size,
allocatedBytes: stat.blocks * 512,
});
});
return Object.freeze({
files: Object.freeze(files),
logicalBytes: files.reduce((sum, file) => sum + file.logicalBytes, 0),
allocatedBytes: files.reduce((sum, file) => sum + file.allocatedBytes, 0),
});
}
function packageFixture(runtime, generation, previous, invalidWorkflow) {
const packageArchitecture =
process.arch === 'x64'
? 'amd64'
: process.arch === 'arm'
? 'arm/v7'
: process.arch;
const task = (id) => ({
schema: 'qinglong/plugin-package-task-resource@v1',
id,
name: `Recovery ${id}`,
labels: { 'plugin.qinglong.io/source': 'edge-recovery-benchmark' },
enabled: true,
kind: 'command',
spec: {
schema: 'qinglong/command@v1',
config: {
command: {
kind: 'argv',
file: '/usr/bin/printf',
args: [id],
},
},
},
});
const workflow = {
schema: 'qinglong/plugin-package-workflow-resource@v1',
id: 'recovery',
name: 'Recovery qualification',
enabled: true,
steps: invalidWorkflow
? [
{ id: 'collect', task: 'collect', needs: ['report'] },
{ id: 'report', task: 'report', needs: ['collect'] },
]
: [
{ id: 'collect', task: 'collect', needs: [] },
{ id: 'report', task: 'report', needs: ['collect'] },
],
};
const values = {
'tasks/collect.json': task('collect'),
'tasks/report.json': task('report'),
'workflows/recovery.json': workflow,
};
const bytes = Object.fromEntries(
Object.entries(values).map(([resourcePath, value]) => [
resourcePath,
Buffer.from(JSON.stringify(value)),
]),
);
const descriptors = Object.entries(bytes)
.map(([resourcePath, material]) => ({
path: resourcePath,
bytes: material.byteLength,
digest: sha256(material),
}))
.sort((left, right) => left.path.localeCompare(right.path));
const manifest = {
apiVersion: runtime.PLUGIN_PACKAGE_API_VERSION,
kind: runtime.PLUGIN_PACKAGE_KIND,
metadata: {
name: 'edge-recovery-benchmark',
displayName: 'Edge recovery benchmark',
version: `${generation}.0.0`,
description: 'Bounded failed-upgrade qualification workload',
license: 'Apache-2.0',
},
spec: {
compatibility: {
qinglong: '>=3.0.0-0 <4.0.0',
architectures: [packageArchitecture],
deploymentProfiles: ['edge'],
},
runtimes: [],
resources: {
memory: { recommended: '16Mi' },
disk: { install: '4Mi', working: '8Mi' },
},
permissions: {
network: { allowedHosts: [] },
secrets: [],
tools: ['system.command'],
},
contents: {
tasks: ['tasks/collect.json', 'tasks/report.json'],
workflows: ['workflows/recovery.json'],
prompts: [],
tools: [],
},
},
};
const environment = {
qinglongVersion: '3.0.0-alpha.0',
architecture: packageArchitecture,
deploymentProfile: 'edge',
runtimes: [],
availableMemoryBytes: 128 * MIB,
availableDiskBytes: 256 * MIB,
};
const plan = runtime.planPluginPackageInstall(
manifest,
environment,
previous?.manifest,
);
const artifactDigest = sha256(`edge-recovery-artifact-${generation}`);
const action = {
lockId: `edge-recovery-lock-${generation}`,
projectId: 'default',
manifest,
plan,
environment,
...(previous ? { previousManifest: previous.manifest } : {}),
source: {
kind: 'offline',
locator: `offline:sha256:${artifactDigest}`,
artifactDigest,
artifactBytes: Object.values(bytes).reduce(
(sum, material) => sum + material.byteLength,
0,
),
contentDigest: runtime.pluginPackageContentTreeDigest(descriptors),
},
architecture: packageArchitecture,
deploymentProfile: 'edge',
targetGeneration: generation,
...(previous ? { previousLockDigest: previous.lock.lockDigest } : {}),
};
const lock = runtime.createPluginPackageLock({
...action,
approval: {
requestId: `edge-recovery-approval-${generation}`,
requestVersion: 1,
dispatchId: `edge-recovery-dispatch-${generation}`,
actionDigest: runtime.pluginPackageInstallActionDigest(action),
previewDigest: runtime.pluginPackageInstallPlanDigest(plan),
approvedBy: { type: 'user', id: 'edge-owner' },
approvedAtMs: 100 + generation,
expiresAtMs: 100_000,
fence: { projectVersion: 1, bindingVersion: 1 },
},
createdAtMs: 200 + generation,
});
return Object.freeze({
bytes: Object.freeze(bytes),
lock,
manifest,
manifestBytes: Buffer.from(
runtime.serializePluginPackageManifest(manifest),
),
});
}
function stage(runtime, value, record, occurredAtMs) {
return runtime.transitionPluginPackageInstall(value.lock, record, {
type: 'stage_completed',
mutationId: `edge-recovery-stage-${value.lock.targetGeneration}`,
occurredAtMs,
stageRef: `edge-recovery:${value.lock.lockDigest}`,
artifactDigest: value.lock.source.artifactDigest,
manifestDigest: value.lock.manifestDigest,
contentDigest: value.lock.source.contentDigest,
evidenceDigest: sha256(
`edge-recovery-stage-${value.lock.targetGeneration}`,
),
});
}
async function createActiveGeneration(runtime, repository, value) {
const queued = runtime.createPluginPackageInstall(value.lock, {
installationId: 'edge-recovery-install-1',
mutationId: 'edge-recovery-create-1',
occurredAtMs: 301,
});
await repository.create(
runtime.pluginPackageInstallCreate(value.lock, queued, null),
);
const staged = stage(runtime, value, queued, 302);
await repository.commit(runtime.pluginPackageInstallCommit(queued, staged));
const activating = runtime.transitionPluginPackageInstall(
value.lock,
staged,
{
type: 'activation_started',
mutationId: 'edge-recovery-activate-1',
occurredAtMs: 303,
},
);
await repository.commit(
runtime.pluginPackageInstallCommit(staged, activating),
);
const active = runtime.transitionPluginPackageInstall(
value.lock,
activating,
{
type: 'activation_committed',
mutationId: 'edge-recovery-commit-1',
occurredAtMs: 304,
activationRef: `edge-active:${value.lock.lockDigest}`,
intentDigest: runtime.pluginPackageActivationIntentDigest(
value.lock,
activating,
),
generation: 1,
contentDigest: value.lock.source.contentDigest,
},
);
await repository.commit(
runtime.pluginPackageInstallCommit(activating, active),
);
return active;
}
async function createStagedUpgrade(runtime, repository, value, active) {
const queued = runtime.createPluginPackageInstall(value.lock, {
installationId: 'edge-recovery-install-2',
mutationId: 'edge-recovery-create-2',
occurredAtMs: 401,
});
await repository.create(
runtime.pluginPackageInstallCreate(value.lock, queued, active),
);
const staged = stage(runtime, value, queued, 402);
await repository.commit(runtime.pluginPackageInstallCommit(queued, staged));
return staged;
}
function runtimeDependencies() {
const pluginPackage = require('../packages/ql3-runtime-core/dist/plugin-package/pluginPackage');
const install = require('../packages/ql3-runtime-core/dist/plugin-package/installation/pluginPackageInstall');
const bundle = require('../packages/ql3-runtime-core/dist/plugin-package/pluginPackageBundle');
const recovery = require('../packages/ql3-runtime-core/dist/plugin-package/installation/pluginPackageRecovery');
const materialization = require('../packages/ql3-runtime-core/dist/plugin-package/pluginPackageResourceMaterialization');
const generation = require('../packages/ql3-runtime-core/dist/plugin-package/pluginPackageResourceGeneration');
const semantics = require('../packages/ql3-runtime-core/dist/task-definition/taskSpecSemantic');
return {
...pluginPackage,
...install,
...bundle,
...recovery,
...materialization,
...generation,
...semantics,
};
}
async function runBenchmark(options) {
if (Number(process.versions.node.split('.')[0]) < 24) {
throw new QingLong3PluginPackageRecoveryEdgeBenchmarkError(
'Node.js 24 or newer is required',
);
}
const { DatabaseSync } = require('node:sqlite');
const runtime = runtimeDependencies();
const {
LocalSqlitePluginPackageInstallRepository,
} = require('../packages/ql3-local-sqlite/dist/plugin-package/pluginPackageInstallRepository');
const {
LocalSqlitePluginPackageMaterializedRevisionRepository,
} = require('../packages/ql3-local-sqlite/dist/plugin-package/pluginPackageMaterializedRevisionRepository');
const {
migrateLocalSqliteDatabase,
} = require('../packages/ql3-local-sqlite/dist/migration/migration');
const temporaryRoot = fs.mkdtempSync(
path.join(os.tmpdir(), 'ql3-package-recovery-edge-'),
);
fs.chmodSync(temporaryRoot, 0o700);
const databasePath = path.join(temporaryRoot, 'recovery.sqlite');
let database;
try {
database = new DatabaseSync(databasePath, {
allowExtension: false,
allowUnknownNamedParameters: false,
defensive: true,
enableDoubleQuotedStringLiterals: false,
enableForeignKeyConstraints: true,
timeout: 1_000,
});
await migrateLocalSqliteDatabase(database);
database.exec('PRAGMA journal_mode = DELETE; PRAGMA synchronous = FULL;');
const baseline = storageSnapshot(databasePath);
const repository = new LocalSqlitePluginPackageInstallRepository(database);
const registry = runtime.createBuiltInTaskSpecSemanticRegistry();
const materializedRepository =
new LocalSqlitePluginPackageMaterializedRevisionRepository(
database,
registry,
);
const first = packageFixture(runtime, 1, null, false);
const second = packageFixture(runtime, 2, first, true);
const calls = {
byteSourceClose: 0,
byteSourceOpen: 0,
byteSourceRead: 0,
publisherInspect: 0,
publisherPublish: 0,
stage: 0,
};
const rssBeforeBytes = process.memoryUsage().rss;
const startedAt = performance.now();
const active = await createActiveGeneration(runtime, repository, first);
const staged = await createStagedUpgrade(
runtime,
repository,
second,
active,
);
const prerequisite =
new runtime.PluginPackageResourceActivationPrerequisite({
byteSource: {
async open(generation) {
if (
generation.lockDigest !== second.lock.lockDigest ||
generation.installationId !== staged.installationId
) {
throw new Error('byte source received another generation');
}
calls.byteSourceOpen += 1;
return {
async read(resourcePath, maximumBytes) {
calls.byteSourceRead += 1;
const material =
resourcePath === 'package.json'
? second.manifestBytes
: second.bytes[resourcePath];
if (!material || material.byteLength > maximumBytes) {
throw new Error('byte source path or bound is invalid');
}
return new Uint8Array(material);
},
async close() {
calls.byteSourceClose += 1;
},
};
},
},
materializedRepository,
taskSpecSemanticRegistry: registry,
});
const coordinator = new runtime.PluginPackageRecoveryCoordinator({
repository,
stageProvider: {
async stage() {
calls.stage += 1;
throw new Error('staged upgrade must not be staged again');
},
},
publisher: {
async publish() {
calls.publisherPublish += 1;
throw new Error('invalid candidate must not be published');
},
async inspect() {
calls.publisherInspect += 1;
throw new Error('invalid candidate must not be inspected');
},
},
activationPrerequisite: prerequisite,
now: () => 500,
});
const recovery = await coordinator.recover({ pageSize: 1, maxPages: 2 });
const durationMs = performance.now() - startedAt;
const rssAfterBytes = process.memoryUsage().rss;
const durable = await repository.find(staged.projectId, staged.packageName);
const candidateGeneration =
runtime.createPluginPackageResourceGenerationFromReferences({
installationId: staged.installationId,
projectId: staged.projectId,
packageName: staged.packageName,
lockDigest: second.lock.lockDigest,
generation: second.lock.targetGeneration,
previousActiveLockDigest: first.lock.lockDigest,
contentDigest: second.lock.source.contentDigest,
resources: second.lock.resources,
});
const materializedCandidateRevisions = database
.prepare(
`SELECT count(*) AS count
FROM "QingLong3PluginPackageMaterializedRevisions"
WHERE generation_digest = ?`,
)
.get(candidateGeneration.generationDigest).count;
const integrityCheck = database
.prepare('PRAGMA integrity_check')
.get().integrity_check;
const finalStorage = storageSnapshot(databasePath);
const logicalGrowthBytes = Math.max(
0,
finalStorage.logicalBytes - baseline.logicalBytes,
);
const allocatedGrowthBytes = Math.max(
0,
finalStorage.allocatedBytes - baseline.allocatedBytes,
);
const rssDeltaBytes = Math.max(0, rssAfterBytes - rssBeforeBytes);
const violations = [];
if (
!durable ||
durable.installationId !== staged.installationId ||
durable.state !== 'failed' ||
durable.failure?.reason !== 'activation_fact_conflict' ||
durable.failure?.failedFrom !== 'staged'
) {
violations.push('candidate did not fail closed from staged');
}
if (
durable?.previousActiveLockDigest !== first.lock.lockDigest ||
durable?.activeLockDigest !== first.lock.lockDigest
) {
violations.push('previous active lock was not preserved');
}
if (
calls.stage !== 0 ||
calls.publisherPublish !== 0 ||
calls.publisherInspect !== 0
) {
violations.push(
'invalid candidate reached stage or activation publisher',
);
}
if (
calls.byteSourceOpen !== 1 ||
calls.byteSourceRead !== 4 ||
calls.byteSourceClose !== 1
) {
violations.push('candidate bytes were not read exactly once');
}
if (materializedCandidateRevisions !== 0) {
violations.push('invalid candidate materialized revision was published');
}
if (integrityCheck !== 'ok') {
violations.push(`SQLite integrity_check returned ${integrityCheck}`);
}
if (durationMs > options.maxDurationMs) {
violations.push('duration exceeded the configured bound');
}
if (rssDeltaBytes > options.maxRssDeltaBytes) {
violations.push('RSS delta exceeded the configured bound');
}
if (
Math.max(logicalGrowthBytes, allocatedGrowthBytes) >
options.maxDatabaseGrowthBytes
) {
violations.push('database growth exceeded the configured bound');
}
const report = Object.freeze({
schemaVersion: 1,
evidenceClass: 'plugin_package_failed_upgrade_edge_candidate',
supported: false,
identity: Object.freeze({
node: process.version,
architecture: process.arch,
platform: process.platform,
}),
measurement: Object.freeze({
durationMs: round(durationMs),
rssBeforeBytes,
rssAfterBytes,
rssDeltaBytes,
}),
thresholds: Object.freeze({
maxDurationMs: options.maxDurationMs,
maxRssDeltaBytes: options.maxRssDeltaBytes,
maxDatabaseGrowthBytes: options.maxDatabaseGrowthBytes,
}),
database: Object.freeze({
journalMode: 'delete',
synchronous: 'full',
integrityCheck,
logicalBytes: finalStorage.logicalBytes,
allocatedBytes: finalStorage.allocatedBytes,
logicalGrowthBytes,
allocatedGrowthBytes,
files: finalStorage.files,
materializedCandidateRevisions,
}),
durable: Object.freeze({
state: durable?.state ?? null,
failureReason: durable?.failure?.reason ?? null,
failedFrom: durable?.failure?.failedFrom ?? null,
headInstallationIsCandidate:
durable?.installationId === staged.installationId,
previousActiveLockDigestPreserved:
durable?.previousActiveLockDigest === first.lock.lockDigest,
activeLockDigestPreserved:
durable?.activeLockDigest === first.lock.lockDigest,
}),
recovery: Object.freeze(recovery),
calls: Object.freeze(calls),
limitations: EXPECTED_LIMITATIONS,
gates: Object.freeze({
passed: violations.length === 0,
violations: Object.freeze(violations),
}),
});
const reportViolations = validateReport(report);
if (reportViolations.length > 0) {
throw new QingLong3PluginPackageRecoveryEdgeBenchmarkError(
`report contract rejected: ${reportViolations.join('; ')}`,
);
}
if (!report.gates.passed) {
throw new QingLong3PluginPackageRecoveryEdgeBenchmarkError(
report.gates.violations.join('; '),
);
}
return report;
} finally {
database?.close();
fs.rmSync(temporaryRoot, { recursive: true, force: true });
}
}
function validateReport(report) {
const violations = [];
if (!hasExactKeys(report, REPORT_KEYS)) {
return Object.freeze(['report shape is invalid']);
}
if (
report.schemaVersion !== 1 ||
report.evidenceClass !== 'plugin_package_failed_upgrade_edge_candidate' ||
report.supported !== false
) {
violations.push('report identity is invalid');
}
if (
!hasExactKeys(report.identity, IDENTITY_KEYS) ||
typeof report.identity.node !== 'string' ||
!['arm64', 'x64', 'arm'].includes(report.identity.architecture) ||
typeof report.identity.platform !== 'string'
) {
violations.push('runtime identity is invalid');
}
if (
!hasExactKeys(report.measurement, MEASUREMENT_KEYS) ||
!Number.isFinite(report.measurement.durationMs) ||
report.measurement.durationMs < 0 ||
!Number.isSafeInteger(report.measurement.rssBeforeBytes) ||
report.measurement.rssBeforeBytes < 1 ||
!Number.isSafeInteger(report.measurement.rssAfterBytes) ||
report.measurement.rssAfterBytes < 1 ||
!Number.isSafeInteger(report.measurement.rssDeltaBytes) ||
report.measurement.rssDeltaBytes < 0
) {
violations.push('measurement is invalid');
}
if (
!hasExactKeys(report.thresholds, THRESHOLD_KEYS) ||
!Number.isFinite(report.thresholds.maxDurationMs) ||
report.thresholds.maxDurationMs <= 0 ||
!Number.isSafeInteger(report.thresholds.maxRssDeltaBytes) ||
report.thresholds.maxRssDeltaBytes < 1 ||
!Number.isSafeInteger(report.thresholds.maxDatabaseGrowthBytes) ||
report.thresholds.maxDatabaseGrowthBytes < 1
) {
violations.push('thresholds are invalid');
}
if (
!hasExactKeys(report.database, DATABASE_KEYS) ||
report.database.journalMode !== 'delete' ||
report.database.synchronous !== 'full' ||
report.database.integrityCheck !== 'ok' ||
report.database.materializedCandidateRevisions !== 0 ||
![
'logicalBytes',
'allocatedBytes',
'logicalGrowthBytes',
'allocatedGrowthBytes',
].every(
(key) =>
Number.isSafeInteger(report.database[key]) && report.database[key] >= 0,
) ||
!Array.isArray(report.database.files) ||
report.database.files.length < 1 ||
report.database.files.length > 4 ||
report.database.files.some(
(file) =>
!hasExactKeys(file, DATABASE_FILE_KEYS) ||
!['database', '-journal', '-wal', '-shm'].includes(file.suffix) ||
!Number.isSafeInteger(file.logicalBytes) ||
file.logicalBytes < 0 ||
!Number.isSafeInteger(file.allocatedBytes) ||
file.allocatedBytes < 0,
)
) {
violations.push('database evidence is invalid');
}
if (
!hasExactKeys(report.durable, DURABLE_KEYS) ||
report.durable.state !== 'failed' ||
report.durable.failureReason !== 'activation_fact_conflict' ||
report.durable.failedFrom !== 'staged' ||
report.durable.headInstallationIsCandidate !== true ||
report.durable.previousActiveLockDigestPreserved !== true ||
report.durable.activeLockDigestPreserved !== true
) {
violations.push('durable recovery facts are invalid');
}
if (
!hasExactKeys(report.recovery, RECOVERY_KEYS) ||
report.recovery.pages !== 1 ||
report.recovery.scanned !== 1 ||
report.recovery.settled !== 1 ||
report.recovery.retry !== 0 ||
report.recovery.manualRequired !== 0 ||
report.recovery.superseded !== 0 ||
report.recovery.deferred !== 0 ||
report.recovery.remaining !== false ||
report.recovery.safeToAdmit !== true
) {
violations.push('recovery outcome is invalid');
}
if (
!hasExactKeys(report.calls, CALL_KEYS) ||
report.calls.stage !== 0 ||
report.calls.publisherPublish !== 0 ||
report.calls.publisherInspect !== 0 ||
report.calls.byteSourceOpen !== 1 ||
report.calls.byteSourceRead !== 4 ||
report.calls.byteSourceClose !== 1
) {
violations.push('authority call evidence is invalid');
}
if (
JSON.stringify(report.limitations) !== JSON.stringify(EXPECTED_LIMITATIONS)
) {
violations.push('limitations are invalid');
}
if (
!hasExactKeys(report.gates, GATE_KEYS) ||
report.gates.passed !== true ||
!Array.isArray(report.gates.violations) ||
report.gates.violations.length !== 0
) {
violations.push('gates did not pass exactly');
}
if (
report.measurement?.durationMs > report.thresholds?.maxDurationMs ||
report.measurement?.rssDeltaBytes > report.thresholds?.maxRssDeltaBytes ||
Math.max(
report.database?.logicalGrowthBytes ?? Number.POSITIVE_INFINITY,
report.database?.allocatedGrowthBytes ?? Number.POSITIVE_INFINITY,
) > report.thresholds?.maxDatabaseGrowthBytes
) {
violations.push('measurement exceeded its declared threshold');
}
return Object.freeze(violations);
}
async function main() {
const options = parseArguments(process.argv.slice(2));
const report = await runBenchmark(options);
process.stdout.write(
`${JSON.stringify(report, null, options.json ? 0 : 2)}\n`,
);
}
module.exports = {
QingLong3PluginPackageRecoveryEdgeBenchmarkError,
parseArguments,
runBenchmark,
validateReport,
};
if (require.main === module) {
main().catch((error) => {
process.stderr.write(
`${error instanceof Error ? error.message : String(error)}\n`,
);
process.exitCode = 1;
});
}