feat: write runtime slo daily reports
This commit is contained in:
@@ -87,6 +87,30 @@ function uniquePaths(paths) {
|
|||||||
return result;
|
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() {
|
function tableCountQueries() {
|
||||||
return [
|
return [
|
||||||
['h5_users', 'users'],
|
['h5_users', 'users'],
|
||||||
@@ -178,6 +202,11 @@ function summarizeRuntime(runtimeJson) {
|
|||||||
const envFile = process.env.MEMIND_ENV_FILE || path.join(process.cwd(), '.env');
|
const envFile = process.env.MEMIND_ENV_FILE || path.join(process.cwd(), '.env');
|
||||||
const appRoot = process.env.MEMIND_APP_ROOT || path.dirname(path.resolve(envFile));
|
const appRoot = process.env.MEMIND_APP_ROOT || path.dirname(path.resolve(envFile));
|
||||||
loadEnvFile(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 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';
|
const redisUrl = process.env.MEMIND_RUNTIME_REDIS_URL || 'redis://127.0.0.1:6379/0';
|
||||||
@@ -244,8 +273,67 @@ const report = {
|
|||||||
database: false,
|
database: false,
|
||||||
mindSpace: false,
|
mindSpace: false,
|
||||||
redis: 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));
|
console.log(JSON.stringify(report, null, 2));
|
||||||
process.exit(report.ok ? 0 : 1);
|
process.exit(report.ok ? 0 : 1);
|
||||||
|
|||||||
@@ -87,6 +87,30 @@ function uniquePaths(paths) {
|
|||||||
return result;
|
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() {
|
function tableCountQueries() {
|
||||||
return [
|
return [
|
||||||
['h5_users', 'users'],
|
['h5_users', 'users'],
|
||||||
@@ -178,6 +202,11 @@ function summarizeRuntime(runtimeJson) {
|
|||||||
const envFile = process.env.MEMIND_ENV_FILE || path.join(process.cwd(), '.env');
|
const envFile = process.env.MEMIND_ENV_FILE || path.join(process.cwd(), '.env');
|
||||||
const appRoot = process.env.MEMIND_APP_ROOT || path.dirname(path.resolve(envFile));
|
const appRoot = process.env.MEMIND_APP_ROOT || path.dirname(path.resolve(envFile));
|
||||||
loadEnvFile(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 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';
|
const redisUrl = process.env.MEMIND_RUNTIME_REDIS_URL || 'redis://127.0.0.1:6379/0';
|
||||||
@@ -244,8 +273,67 @@ const report = {
|
|||||||
database: false,
|
database: false,
|
||||||
mindSpace: false,
|
mindSpace: false,
|
||||||
redis: 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));
|
console.log(JSON.stringify(report, null, 2));
|
||||||
process.exit(report.ok ? 0 : 1);
|
process.exit(report.ok ? 0 : 1);
|
||||||
|
|||||||
Reference in New Issue
Block a user