feat: enhance /api/debug/memory endpoint with comprehensive metrics

- Add getStats() method to SubagentWatcher for internal resource tracking
- Return detailed Map sizes for all server and subagent Maps
- Track file watchers, directory watchers, and transcript watchers separately
- Track all timer types (respawn, idle, pending starts)
- Add totals for quick overview of resource usage
- Useful for debugging memory leaks and resource exhaustion

The enhanced endpoint now returns:
- Memory usage (heap, external, array buffers)
- Map sizes by component (server, subagent watcher)
- Watcher counts (file, directory, transcript)
- Timer counts by type
- Spawn agent statistics

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
arkon
2026-01-28 04:44:29 +01:00
co-authored by Claude Opus 4.5
parent 596098015f
commit c659b7769e
2 changed files with 74 additions and 10 deletions
+30
View File
@@ -419,6 +419,36 @@ export class SubagentWatcher extends EventEmitter {
return this.getProjectHash(workingDir);
}
/**
* Get internal statistics for memory monitoring.
* Returns counts of internal Maps and resources.
*/
getStats(): {
agentCount: number;
fileWatcherCount: number;
dirWatcherCount: number;
idleTimerCount: number;
pendingToolCallsCount: number;
knownDirsCount: number;
filePositionsCount: number;
} {
// Count pending tool calls across all agents
let pendingToolCallsCount = 0;
for (const agentCalls of this.pendingToolCalls.values()) {
pendingToolCallsCount += agentCalls.size;
}
return {
agentCount: this.agentInfo.size,
fileWatcherCount: this.fileWatchers.size,
dirWatcherCount: this.dirWatchers.size,
idleTimerCount: this.idleTimers.size,
pendingToolCallsCount,
knownDirsCount: this.knownSubagentDirs.size,
filePositionsCount: this.filePositions.size,
};
}
/**
* Get recent subagents (modified within specified minutes)
*/
+44 -10
View File
@@ -494,9 +494,34 @@ export class WebServer extends EventEmitter {
});
// Debug/monitoring endpoint - lightweight, only runs when called
// Returns comprehensive memory metrics for debugging memory leaks
this.app.get('/api/debug/memory', async () => {
const mem = process.memoryUsage();
const spawnState = this.spawnOrchestrator.getState();
const subagentStats = subagentWatcher.getStats();
// Calculate total Map entries for memory estimation
const serverMapSizes = {
sessions: this.sessions.size,
sseClients: this.sseClients.size,
respawnControllers: this.respawnControllers.size,
runSummaryTrackers: this.runSummaryTrackers.size,
transcriptWatchers: this.transcriptWatchers.size,
scheduledRuns: this.scheduledRuns.size,
terminalBatches: this.terminalBatches.size,
outputBatches: this.outputBatches.size,
taskUpdateBatches: this.taskUpdateBatches.size,
stateUpdatePending: this.stateUpdatePending.size,
lastRecordedTokens: this.lastRecordedTokens.size,
pendingRespawnStarts: this.pendingRespawnStarts.size,
respawnTimers: this.respawnTimers.size,
activePlanOrchestrators: this.activePlanOrchestrators.size,
cleaningUp: this.cleaningUp.size,
};
const totalServerMapEntries = Object.values(serverMapSizes).reduce((a, b) => a + b, 0);
const totalSubagentMapEntries = Object.values(subagentStats).reduce((a, b) => a + b, 0);
return {
memory: {
rss: mem.rss,
@@ -510,17 +535,26 @@ export class WebServer extends EventEmitter {
arrayBuffers: mem.arrayBuffers,
arrayBuffersMB: Math.round(mem.arrayBuffers / 1024 / 1024 * 10) / 10,
},
counts: {
sessions: this.sessions.size,
sseClients: this.sseClients.size,
respawnControllers: this.respawnControllers.size,
runSummaryTrackers: this.runSummaryTrackers.size,
scheduledRuns: this.scheduledRuns.size,
terminalBatches: this.terminalBatches.size,
outputBatches: this.outputBatches.size,
pendingRespawnStarts: this.pendingRespawnStarts.size,
mapSizes: {
server: serverMapSizes,
subagentWatcher: subagentStats,
totals: {
serverEntries: totalServerMapEntries,
subagentEntries: totalSubagentMapEntries,
allEntries: totalServerMapEntries + totalSubagentMapEntries,
},
},
watchers: {
fileWatchers: subagentStats.fileWatcherCount,
dirWatchers: subagentStats.dirWatcherCount,
transcriptWatchers: this.transcriptWatchers.size,
total: subagentStats.fileWatcherCount + subagentStats.dirWatcherCount + this.transcriptWatchers.size,
},
timers: {
respawnTimers: this.respawnTimers.size,
subagents: subagentWatcher.getSubagents().length,
pendingRespawnStarts: this.pendingRespawnStarts.size,
subagentIdleTimers: subagentStats.idleTimerCount,
total: this.respawnTimers.size + this.pendingRespawnStarts.size + subagentStats.idleTimerCount,
},
spawn: {
activeAgents: spawnState.activeCount,