Shared caches for the numbers every page polls; refresh only visible pages
/api/progress and /api/crew ran heavy queries for every open tab every 30 s (about 3 requests per second) and saturated the web process; they are now computed at most every 20 s and shared. The symbol table is built at most once a minute. Pages refresh every 60 s and only while visible. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RNwtXPYqFu3oKaaXwYwNWW
This commit is contained in:
parent
3b697d9b11
commit
f291c47ce4
2 changed files with 26 additions and 9 deletions
|
|
@ -183,9 +183,12 @@ export function createApp(ctx) {
|
|||
router.on('GET', '/api/symbols', ({ req, res, query }) => {
|
||||
const v = query.get('version') ? store.get('SELECT * FROM versions WHERE id = ?', query.get('version')) : store.currentVersion();
|
||||
if (!v) throw new HttpError(404, 'unknown version');
|
||||
const rows = [...store.symbolMap(v.id).values()].map((s) => [s.id, s.kind, s.addr, s.size, s.name, s.decorated, s.library ? 1 : 0, s.unit]);
|
||||
const body = JSON.stringify({ version: v.id, columns: ['id', 'kind', 'addr', 'size', 'name', 'decorated', 'library', 'unit'], symbols: rows });
|
||||
const etag = `"${crypto.createHash('sha1').update(body).digest('hex')}"`;
|
||||
// Building it takes a while; a minute-old table is fine for agents.
|
||||
const { body, etag } = cached(`symbols:${v.id}`, 60_000, () => {
|
||||
const rows = [...store.symbolMap(v.id).values()].map((s) => [s.id, s.kind, s.addr, s.size, s.name, s.decorated, s.library ? 1 : 0, s.unit]);
|
||||
const json = JSON.stringify({ version: v.id, columns: ['id', 'kind', 'addr', 'size', 'name', 'decorated', 'library', 'unit'], symbols: rows });
|
||||
return { body: json, etag: `"${crypto.createHash('sha1').update(json).digest('hex')}"` };
|
||||
});
|
||||
const headers = { etag, 'cache-control': 'no-cache', 'x-content-type-options': 'nosniff' };
|
||||
if (req.headers['if-none-match'] === etag) {
|
||||
res.writeHead(304, headers);
|
||||
|
|
@ -198,7 +201,17 @@ export function createApp(ctx) {
|
|||
// Public: who is on the project and what their agents are doing right now. No token labels or
|
||||
// other account details; only names, roles and work.
|
||||
const ACTIVE_MS = 15 * 60_000;
|
||||
router.on('GET', '/api/crew', () => {
|
||||
// Public numbers every open page polls: computed at most once per `ttl` and shared by everyone.
|
||||
const shared = new Map();
|
||||
const cached = (key, ttl, compute) => {
|
||||
const hit = shared.get(key);
|
||||
if (hit && Date.now() - hit.at < ttl) return hit.value;
|
||||
const value = compute();
|
||||
shared.set(key, { at: Date.now(), value });
|
||||
return value;
|
||||
};
|
||||
|
||||
router.on('GET', '/api/crew', () => cached('crew', 20_000, () => {
|
||||
const since = Date.now() - ACTIVE_MS;
|
||||
const users = store.all(
|
||||
`SELECT u.name, u.role, u.approved_at AS joined,
|
||||
|
|
@ -226,9 +239,9 @@ export function createApp(ctx) {
|
|||
return { name: a.name, agents: a.agents, lastSeen: a.last_seen, units: units.map((u) => ({ unit: u.unit ?? '(no unit)', functions: u.functions })), reviewing, verifying: waiting, submittedToday: today };
|
||||
});
|
||||
return { activeMinutes: ACTIVE_MS / 60_000, users, active };
|
||||
});
|
||||
}));
|
||||
|
||||
router.on('GET', '/api/progress', () => {
|
||||
router.on('GET', '/api/progress', () => cached('progress', 20_000, () => {
|
||||
const total = store.get(`SELECT COUNT(*) AS n, COALESCE(SUM(a.size), 0) AS bytes FROM symbols s JOIN symbol_addresses a ON a.symbol_id = s.id JOIN versions v ON v.id = a.version_id AND v.is_current = 1 WHERE s.kind = 'function' AND s.library = 0`);
|
||||
const byStatus = store.all(`SELECT fs.status, COUNT(*) AS n, COALESCE(SUM(a.size), 0) AS bytes FROM func_status fs JOIN symbol_addresses a ON a.symbol_id = fs.symbol_id JOIN versions v ON v.id = a.version_id AND v.is_current = 1 GROUP BY fs.status`);
|
||||
const byTaskState = store.all('SELECT state, COUNT(*) AS n FROM tasks GROUP BY state');
|
||||
|
|
@ -236,7 +249,7 @@ export function createApp(ctx) {
|
|||
const confirmed = store.get(`SELECT COUNT(*) AS n, COALESCE(SUM(a.size), 0) AS bytes FROM func_status fs JOIN symbol_addresses a ON a.symbol_id = fs.symbol_id JOIN versions v ON v.id = a.version_id AND v.is_current = 1 WHERE fs.confirmed = 1 AND fs.status NOT IN ('exact', 'match_unresolved')`);
|
||||
const peerQueue = store.get("SELECT COUNT(*) AS n FROM attempts WHERE state = 'peer_review'").n;
|
||||
return { functions: total.n, bytes: total.bytes, byStatus, confirmed, peerQueue, byTaskState, contributors };
|
||||
});
|
||||
}));
|
||||
|
||||
// --- accounts
|
||||
|
||||
|
|
|
|||
|
|
@ -732,8 +732,12 @@ api('/api/info')
|
|||
loadProgress().catch((e) => ($('progress-body').textContent = e.message));
|
||||
loadCrew().catch((e) => ($('active-body').textContent = e.message));
|
||||
// Live numbers: progress and the crew views refresh on their own.
|
||||
setInterval(() => {
|
||||
// Live numbers, only while the page is visible (background tabs would otherwise keep polling).
|
||||
const refreshLive = () => {
|
||||
if (document.visibilityState !== 'visible') return;
|
||||
loadProgress().catch(() => {});
|
||||
loadCrew().catch(() => {});
|
||||
}, 30_000);
|
||||
};
|
||||
setInterval(refreshLive, 60_000);
|
||||
document.addEventListener('visibilitychange', refreshLive);
|
||||
refresh();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue