mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-06 07:29:42 +02:00
perf: optimize for long-running Claude sessions
Critical fixes (unbounded growth): - Add 64KB line buffer limit with 100ms periodic flush in session.ts - Add task tracker cleanup (max 100 completed tasks) - Add scheduled runs auto-cleanup after 1 hour - Fix session leaks in scheduled run iterations High priority (performance): - Add SSE event batching (50ms output, 100ms task updates) - Parallelize screen stats with Promise.all() - Add 1MB buffer limit to respawn controller Medium priority (frontend): - Make xterm scrollback configurable (default 5000) - Debounce renderSessionTabs() at 100ms New feature: - Add CPU and memory usage display in frontend header - New /api/system/stats endpoint Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
+83
-3
@@ -1,4 +1,7 @@
|
||||
// Claudeman App - Tab-based Terminal UI
|
||||
// Default terminal scrollback (can be changed via settings)
|
||||
const DEFAULT_SCROLLBACK = 5000;
|
||||
|
||||
class ClaudemanApp {
|
||||
constructor() {
|
||||
this.sessions = new Map();
|
||||
@@ -20,6 +23,12 @@ class ClaudemanApp {
|
||||
this.pendingWrites = '';
|
||||
this.writeFrameScheduled = false;
|
||||
|
||||
// Render debouncing
|
||||
this.renderSessionTabsTimeout = null;
|
||||
|
||||
// System stats polling
|
||||
this.systemStatsInterval = null;
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
@@ -32,9 +41,14 @@ class ClaudemanApp {
|
||||
this.setupEventListeners();
|
||||
// Show monitor panel by default
|
||||
this.toggleMonitorPanel();
|
||||
// Start system stats polling
|
||||
this.startSystemStatsPolling();
|
||||
}
|
||||
|
||||
initTerminal() {
|
||||
// Load scrollback setting from localStorage (default 5000)
|
||||
const scrollback = parseInt(localStorage.getItem('claudeman-scrollback')) || DEFAULT_SCROLLBACK;
|
||||
|
||||
this.terminal = new Terminal({
|
||||
theme: {
|
||||
background: '#0d0d0d',
|
||||
@@ -64,7 +78,7 @@ class ClaudemanApp {
|
||||
lineHeight: 1.2,
|
||||
cursorBlink: true,
|
||||
cursorStyle: 'block',
|
||||
scrollback: 10000,
|
||||
scrollback: scrollback,
|
||||
allowTransparency: true,
|
||||
});
|
||||
|
||||
@@ -479,6 +493,16 @@ class ClaudemanApp {
|
||||
// ========== Session Tabs ==========
|
||||
|
||||
renderSessionTabs() {
|
||||
// Debounce renders at 100ms to prevent excessive DOM updates
|
||||
if (this.renderSessionTabsTimeout) {
|
||||
clearTimeout(this.renderSessionTabsTimeout);
|
||||
}
|
||||
this.renderSessionTabsTimeout = setTimeout(() => {
|
||||
this._renderSessionTabsImmediate();
|
||||
}, 100);
|
||||
}
|
||||
|
||||
_renderSessionTabsImmediate() {
|
||||
const container = document.getElementById('sessionTabs');
|
||||
|
||||
// Build tabs HTML
|
||||
@@ -528,9 +552,17 @@ class ClaudemanApp {
|
||||
try {
|
||||
const res = await fetch(`/api/sessions/${sessionId}/terminal`);
|
||||
const data = await res.json();
|
||||
this.terminal.clear();
|
||||
this.terminal.reset();
|
||||
if (data.terminalBuffer) {
|
||||
this.terminal.write(data.terminalBuffer);
|
||||
// Strip leading ANSI escape sequences and whitespace to prevent gaps
|
||||
// This handles:
|
||||
// - CSI sequences: ESC [ (params) (final) - includes ? for private modes
|
||||
// - OSC sequences: ESC ] ... BEL or ESC \
|
||||
// - Simple sequences: ESC followed by single char
|
||||
// - Whitespace, CR, LF
|
||||
let cleanBuffer = data.terminalBuffer;
|
||||
cleanBuffer = cleanBuffer.replace(/^(\x1b\[[0-9;?]*[A-Za-z@`]|\x1b\][^\x07]*\x07|\x1b[()][AB012]|\x1b[DEMNOP78>=c]|\s|\r|\n)*/g, '');
|
||||
this.terminal.write(cleanBuffer);
|
||||
}
|
||||
|
||||
// Send resize
|
||||
@@ -1583,6 +1615,54 @@ class ClaudemanApp {
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
// ========== System Stats ==========
|
||||
|
||||
startSystemStatsPolling() {
|
||||
// Initial fetch
|
||||
this.fetchSystemStats();
|
||||
|
||||
// Poll every 2 seconds
|
||||
this.systemStatsInterval = setInterval(() => {
|
||||
this.fetchSystemStats();
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
async fetchSystemStats() {
|
||||
try {
|
||||
const res = await fetch('/api/system/stats');
|
||||
const stats = await res.json();
|
||||
this.updateSystemStatsDisplay(stats);
|
||||
} catch (err) {
|
||||
// Silently fail - system stats are not critical
|
||||
}
|
||||
}
|
||||
|
||||
updateSystemStatsDisplay(stats) {
|
||||
const cpuEl = document.getElementById('statCpu');
|
||||
const memEl = document.getElementById('statMem');
|
||||
|
||||
if (cpuEl) {
|
||||
cpuEl.textContent = `CPU: ${stats.cpu}%`;
|
||||
// Color based on usage
|
||||
if (stats.cpu > 80) {
|
||||
cpuEl.classList.add('high');
|
||||
} else {
|
||||
cpuEl.classList.remove('high');
|
||||
}
|
||||
}
|
||||
|
||||
if (memEl) {
|
||||
const memGB = (stats.memory.usedMB / 1024).toFixed(1);
|
||||
memEl.textContent = `Mem: ${memGB}GB`;
|
||||
// Color based on usage
|
||||
if (stats.memory.percent > 80) {
|
||||
memEl.classList.add('high');
|
||||
} else {
|
||||
memEl.classList.remove('high');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ========== Utility ==========
|
||||
|
||||
escapeHtml(text) {
|
||||
|
||||
@@ -35,6 +35,10 @@
|
||||
<span class="status-text">Connecting</span>
|
||||
</div>
|
||||
<div class="header-tokens" id="headerTokens" title="Total tokens across all sessions">0 tokens</div>
|
||||
<div class="header-system-stats" id="headerSystemStats" title="System resource usage">
|
||||
<span class="stat-cpu" id="statCpu">CPU: --</span>
|
||||
<span class="stat-mem" id="statMem">Mem: --</span>
|
||||
</div>
|
||||
<button class="btn-icon-header" onclick="app.openAppSettings()" title="App Settings">⚙</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
@@ -187,6 +187,26 @@ body {
|
||||
font-family: 'SF Mono', Monaco, monospace;
|
||||
}
|
||||
|
||||
.header-system-stats {
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
font-size: 0.7rem;
|
||||
font-family: 'SF Mono', Monaco, monospace;
|
||||
color: var(--text-dim);
|
||||
padding: 0 0.5rem;
|
||||
border-left: 1px solid var(--border);
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
|
||||
.header-system-stats span {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.header-system-stats .high {
|
||||
color: var(--red);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.btn-icon-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
Reference in New Issue
Block a user