feat: write runtime slo daily reports

This commit is contained in:
John
2026-07-02 08:09:02 +08:00
parent 12f74eb44e
commit e3ce4032c6
2 changed files with 176 additions and 0 deletions
+88
View File
@@ -87,6 +87,30 @@ function uniquePaths(paths) {
return result;
}
function parseArgs(argv) {
const args = {
writeReport: false,
reportDir: process.env.MEMIND_RUNTIME_REPORT_DIR || '',
};
for (let i = 0; i < argv.length; i += 1) {
const item = argv[i];
if (item === '--write-report') args.writeReport = true;
else if (item === '--report-dir') args.reportDir = String(argv[++i] ?? args.reportDir);
else if (item === '--help' || item === '-h') args.help = true;
}
return args;
}
function printHelp() {
console.log([
'Usage:',
' node scripts/runtime-slo-report.mjs [--write-report] [--report-dir <dir>]',
'',
'Default behavior is read-only and writes nothing.',
'--write-report writes JSON and Markdown snapshots to an operations report directory.',
].join('\n'));
}
function tableCountQueries() {
return [
['h5_users', 'users'],
@@ -178,6 +202,11 @@ function summarizeRuntime(runtimeJson) {
const envFile = process.env.MEMIND_ENV_FILE || path.join(process.cwd(), '.env');
const appRoot = process.env.MEMIND_APP_ROOT || path.dirname(path.resolve(envFile));
loadEnvFile(envFile);
const args = parseArgs(process.argv.slice(2));
if (args.help) {
printHelp();
process.exit(0);
}
const publicBase = (process.env.H5_PUBLIC_BASE_URL || 'https://mm.tkmind.cn').replace(/\/$/, '');
const redisUrl = process.env.MEMIND_RUNTIME_REDIS_URL || 'redis://127.0.0.1:6379/0';
@@ -244,8 +273,67 @@ const report = {
database: false,
mindSpace: false,
redis: false,
report: Boolean(args.writeReport),
},
};
function markdownReport(payload) {
const workers = payload.runtime?.workers ?? [];
const lines = [
`# Memind Runtime SLO Report`,
'',
`- checkedAt: ${payload.checkedAt}`,
`- ok: ${payload.ok}`,
`- failures: ${payload.failures.length ? payload.failures.join(', ') : 'none'}`,
`- publicBase: ${payload.publicBase}`,
`- appRoot: ${payload.appRoot}`,
'',
'## Workers',
'',
'| worker | healthy | active | errors | first-token 5m p50/p95 | first-token 1h p50/p95 | metricsFresh | score |',
'| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: |',
];
for (const worker of workers) {
lines.push([
worker.id,
worker.healthy,
worker.activeStreams,
worker.streamErrorCount,
`${worker.firstToken5m?.p50Ms ?? 0}/${worker.firstToken5m?.p95Ms ?? 0}`,
`${worker.firstToken1h?.p50Ms ?? 0}/${worker.firstToken1h?.p95Ms ?? 0}`,
worker.metricsFresh,
worker.score,
].join(' | ').replace(/^/, '| ').replace(/$/, ' |'));
}
lines.push(
'',
'## Tool Queue',
'',
'```json',
JSON.stringify(payload.runtime?.toolQueue ?? null, null, 2),
'```',
'',
'## Writes',
'',
'```json',
JSON.stringify(payload.writes, null, 2),
'```',
'',
);
return lines.join('\n');
}
if (args.writeReport) {
const reportDir = path.resolve(args.reportDir || path.join(appRoot, 'reports', 'runtime-slo'));
fs.mkdirSync(reportDir, { recursive: true });
const stamp = new Date().toISOString().replace(/[:.]/g, '-');
report.reportFiles = {
json: path.join(reportDir, `${stamp}.json`),
markdown: path.join(reportDir, `${stamp}.md`),
};
fs.writeFileSync(report.reportFiles.json, `${JSON.stringify(report, null, 2)}\n`);
fs.writeFileSync(report.reportFiles.markdown, markdownReport(report));
}
console.log(JSON.stringify(report, null, 2));
process.exit(report.ok ? 0 : 1);