Files
ITWorx-Pulse-Public/tools/analyze-wallboard-soak.mjs
ITWorx Pulse release export bd774932d5
Public source validation / validate (push) Failing after 3m8s
Publish ITWorx Pulse source
2026-09-03 02:09:19 +02:00

76 lines
3.0 KiB
JavaScript

import { readFile } from 'node:fs/promises';
import path from 'node:path';
import { buildSoakSummary } from './wallboard-soak-analysis.mjs';
const directory = path.resolve(process.argv[2] ?? 'artifacts/evidence/M10-14/raw');
async function readJSON(name) {
return JSON.parse(await readFile(path.join(directory, name), 'utf8'));
}
async function readNDJSON(name, required = true) {
try {
const content = await readFile(path.join(directory, name), 'utf8');
return content.split(/\r?\n/).filter(Boolean).map((line) => JSON.parse(line));
} catch (error) {
if (!required && error?.code === 'ENOENT') return [];
throw error;
}
}
const [metadata, recorded, samples, reconnects, events] = await Promise.all([
readJSON('metadata.json'),
readJSON('result.json'),
readNDJSON('samples.ndjson'),
readNDJSON('reconnects.ndjson', false),
readNDJSON('events.ndjson'),
]);
const completedAt = new Date(recorded.completedAt);
const completedMs = completedAt.getTime();
if (!Number.isFinite(completedMs)) throw new Error('result.json has no valid completedAt');
const relevantEvents = events.filter((event) => Date.parse(event.timestamp) <= completedMs);
const openedSockets = relevantEvents.filter((event) => event.type === 'websocket-open').length;
const closedSockets = relevantEvents.filter((event) => event.type === 'websocket-close').length;
const recomputed = buildSoakSummary({
samples,
reconnects,
startedAt: new Date(metadata.startedAt),
completedAt,
durationHours: metadata.durationHours,
sampleSeconds: metadata.sampleSeconds,
counters: {
maxActiveSockets: Math.max(0, ...relevantEvents.map((event) => Number(event.activeSockets) || 0)),
openedSockets,
closedSockets,
websocketErrors: relevantEvents.filter((event) => event.type === 'websocket-error').length,
pageErrors: relevantEvents.filter((event) => event.type === 'page-error').length,
apiFailures: relevantEvents.filter((event) => event.type === 'api-failure').length,
},
requireWallboardTitle: true,
});
const comparison = {
statusMatches: recomputed.status === recorded.status,
sampleCountMatches: recomputed.sampleCount === recorded.sampleCount,
reconnectCountMatches: recomputed.reconnectCount === recorded.reconnectCount,
criticalMetricsMatch: [
'expectedSampleCount', 'minimumSampleCount', 'maxSampleGapSeconds', 'maxAllowedSampleGapSeconds',
'reconnectFailures', 'retainedGrowthBytes', 'heapSlopeBytesPerHour', 'fpsP05',
'frameP95WorstMs', 'longestTaskMs', 'maxActiveSockets', 'openedSockets',
'closedSockets', 'websocketErrors', 'socketChurnBudget', 'pageErrors',
'apiFailures', 'maxDOMNodes', 'horizontalOverflowSamples', 'invalidWallboardSamples',
].every((field) => Object.is(recomputed[field], recorded[field])),
};
const verification = {
directory,
recordedStatus: recorded.status,
recomputedStatus: recomputed.status,
comparison,
recomputed,
};
process.stdout.write(`${JSON.stringify(verification, null, 2)}\n`);
if (!Object.values(comparison).every(Boolean) || recomputed.status !== 'pass') process.exitCode = 1;