`;
}
}
// Write Sources (#1120) — per-component counters from ingestor
if (writeSources && writeSources.sources) {
const src = writeSources.sources;
const keys = Object.keys(src).sort((a, b) => (src[b] || 0) - (src[a] || 0));
html += '
Write Sources
';
if (keys.length === 0) {
html += '
No ingestor stats yet (waiting for /tmp/corescope-ingestor-stats.json)
';
} else {
// Anomaly detection (#1120 acceptance): flag any component whose
// per-second write rate exceeds 10× its 5-minute rolling baseline.
// History is stashed on window so the detector has multi-sample
// context across the 5s refresh tick.
if (!window._perfWriteSourcesHistory) window._perfWriteSourcesHistory = [];
const history = window._perfWriteSourcesHistory;
const current = { sampleAt: writeSources.sampleAt || new Date().toISOString(), sources: { ...src } };
const anom = detectPerfAnomalies(history, current, { windowMs: 5 * 60 * 1000, factor: 10 });
// Append current and prune anything older than 6 minutes (keeps a
// little headroom past the 5-min window, bounded memory).
history.push(current);
const cutoff = Date.parse(current.sampleAt) - (6 * 60 * 1000);
while (history.length > 1 && Date.parse(history[0].sampleAt) < cutoff) history.shift();
html += '
Source
Total
Rate/s
Baseline/s
Anomaly
';
for (const k of keys) {
const v = src[k] || 0;
const rate = anom.rates[k];
const base = anom.baselineRates[k];
const flag = anom.flags[k] ? ' ' : '';
const rateStr = (rate != null && isFinite(rate)) ? rate.toFixed(2) : '—';
const baseStr = (base != null && isFinite(base)) ? base.toFixed(2) : '—';
html += `
${k}
${v.toLocaleString()}
${rateStr}
${baseStr}
${flag}
`;
}
html += '
';
if (writeSources.sampleAt) {
html += `
Sampled: ${writeSources.sampleAt} · baseline window: 5 min · threshold: 10×
`;
}
// Server endpoints table — sort by total time (count * avg) DESC.
// #1258: header claimed "sorted by total time" but JSON map order is
// undefined and the frontend was not sorting. Slow endpoints could
// appear anywhere in the table, defeating the section's whole purpose.
const eps = Object.entries(server.endpoints).sort((a, b) => {
const ta = (a[1].count || 0) * (a[1].avgMs || 0);
const tb = (b[1].count || 0) * (b[1].avgMs || 0);
return tb - ta;
});
if (eps.length) {
html += '
Server Endpoints (sorted by total time)
';
html += '
Endpoint
Count
Avg
P50
P95
Max
Total
';
for (const [path, s] of eps) {
const total = Math.round(s.count * s.avgMs);
const cls = s.p95Ms > 200 ? ' class="perf-slow"' : s.p95Ms > 50 ? ' class="perf-warn"' : '';
html += `
${path}
${s.count}
${s.avgMs}ms
${s.p50Ms}ms
${s.p95Ms}ms
${s.maxMs}ms
${total}ms
`;
}
html += '
';
}
// Client API calls
if (client && client.endpoints.length) {
html += '
Client API Calls (this session)
';
html += '
Endpoint
Count
Avg
Max
Total
';
for (const s of client.endpoints) {
const cls = s.maxMs > 500 ? ' class="perf-slow"' : s.avgMs > 200 ? ' class="perf-warn"' : '';
html += `
${s.path}
${s.count}
${s.avgMs}ms
${s.maxMs}ms
${s.totalMs}ms
`;
}
html += '
';
}
// Slow queries
if (server.slowQueries.length) {
html += '
Recent Slow Queries (>100ms)
';
html += '
Time
Path
Duration
Status
';
for (const q of server.slowQueries.slice().reverse()) {
html += `
`;
}
}
registerPage('perf', {
init(app) {
render(app);
// #1258: don't burn CPU/network rebuilding the page (and its many cards
// + 3 large tables) every 5s while the tab is hidden. Pause polling on
// visibilitychange and resume on focus. Reduces background fetch traffic
// to zero and prevents a returning user from seeing a 100+ms thrash as
// a backlog of refreshes flush.
const tick = () => {
if (document.hidden) return;
refresh();
};
interval = setInterval(tick, 5000);
const onVis = () => {
if (!document.hidden) refresh();
};
document.addEventListener('visibilitychange', onVis);
this._onVis = onVis;
},
destroy() {
if (interval) { clearInterval(interval); interval = null; }
if (this._onVis) {
document.removeEventListener('visibilitychange', this._onVis);
this._onVis = null;
}
}
});
})();