// module-servermon.js — Server monitoring + self-optimization telemetry // Commands: server, servers, server-check, server-remove, monitor, system-health class ServerMonitorModule { constructor() { this.commands = {}; this.terminal = null; this.servers = this.loadServers(); this.monitorInterval = null; this._latencyLog = []; this._maxLatencyEntries = 200; } getHelpInfo() { return { 'server': 'server — Add a server to monitor', 'servers': 'servers — List all monitored servers', 'server-check': 'server-check — Check a server status', 'server-remove': 'server-remove — Remove a server', 'monitor': 'monitor — Toggle auto-monitoring (checks every 60s)', 'system-health': 'system-health — Show tool latency, module load times, VFS size, and error rates' }; } registerCommands() { this.commands['server'] = (args) => this.addServer(args); this.commands['servers'] = () => this.listServers(); this.commands['server-check'] = (args) => this.checkServer(args); this.commands['server-remove'] = (args) => this.removeServer(args); this.commands['monitor'] = (args) => this.toggleMonitor(args); this.commands['system-health'] = () => this.showSystemHealth(); } logToolCall(name, durationMs, error) { this._latencyLog.push({ name, duration: durationMs, error: !!error, time: Date.now() }); if (this._latencyLog.length > this._maxLatencyEntries) { this._latencyLog = this._latencyLog.slice(-this._maxLatencyEntries); } // Persist every 20 calls if (this._latencyLog.length % 20 === 0) { this._persistLatency(); } } _persistLatency() { try { this.terminal.fs.fsWriteJSON('/system/metrics/tool-latency.json', this._latencyLog.slice(-100)); } catch (e) {} } _loadLatency() { try { const data = this.terminal.fs.fsReadJSON('/system/metrics/tool-latency.json'); if (data) this._latencyLog = data; } catch (e) {} } showSystemHealth() { this._loadLatency(); const fs = this.terminal.fs; // Tool latency stats const toolStats = {}; for (const entry of this._latencyLog) { if (!toolStats[entry.name]) toolStats[entry.name] = { calls: 0, totalMs: 0, errors: 0, maxMs: 0 }; toolStats[entry.name].calls++; toolStats[entry.name].totalMs += entry.duration; toolStats[entry.name].errors += entry.error ? 1 : 0; toolStats[entry.name].maxMs = Math.max(toolStats[entry.name].maxMs, entry.duration); } this.terminal.print('📊 System Health', 'header'); // Tool latency table (sorted by avg desc) this.terminal.print('── Tool Latency ──', 'info'); const sorted = Object.entries(toolStats) .map(([name, s]) => ({ name, avg: (s.totalMs / s.calls).toFixed(0), ...s })) .sort((a, b) => b.avg - a.avg); for (const t of sorted.slice(0, 10)) { const avgMs = t.avg.padStart(5); const maxMs = String(t.maxMs).padStart(5); const err = t.errors > 0 ? ` ⚠️${t.errors}` : ''; this.terminal.print(` ${avgMs}ms avg / ${maxMs}ms max — ${t.name} (${t.calls} calls)${err}`, t.errors > 0 ? 'warning' : ''); } if (sorted.length > 10) this.terminal.print(` ... and ${sorted.length - 10} more tools`, 'info'); // VFS stats this.terminal.print('── VFS Stats ──', 'info'); try { const totalSize = this._vfsSize(fs.root); const fileCount = this._vfsCount(fs.root, 'file'); const dirCount = this._vfsCount(fs.root, 'dir'); this.terminal.print(` ${fileCount} files, ${dirCount} dirs, ${(totalSize / 1024).toFixed(0)} KB total`, ''); } catch (e) { this.terminal.print(' VFS stats unavailable', 'warning'); } // Module stats this.terminal.print('── Module Stats ──', 'info'); const mm = this.terminal.moduleManager; const loaded = Object.keys(mm.modules).length; const reg = (mm.moduleRegistry || []).length; this.terminal.print(` ${loaded} modules loaded, ${reg} in registry`, ''); // Memory this.terminal.print('── Memory ──', 'info'); if (performance.memory) { const heap = (performance.memory.usedJSHeapSize / 1048576).toFixed(1); const total = (performance.memory.jsHeapSizeLimit / 1048576).toFixed(0); this.terminal.print(` ${heap} MB used / ${total} MB limit`, heap > 100 ? 'warning' : ''); } else { this.terminal.print(' Memory info not available (non-Chrome browser)', ''); } // Total tool call count const totalCalls = this._latencyLog.length; const totalErrors = this._latencyLog.filter(e => e.error).length; this.terminal.print(` ${totalCalls} tool calls logged, ${totalErrors} errors (${totalCalls > 0 ? (totalErrors / totalCalls * 100).toFixed(1) : 0}% error rate)`, totalErrors > 0 ? 'warning' : ''); } _vfsSize(node) { if (!node) return 0; if (node.type === 'file') return (node.content || '').length; let s = 0; for (const k in (node.content || {})) s += this._vfsSize(node.content[k]); return s; } _vfsCount(node, type) { if (!node) return 0; let c = 0; for (const k in (node.content || {})) { const child = node.content[k]; if (child.type === type) c++; c += this._vfsCount(child, type); } return c; } loadServers() { try { const saved = localStorage.getItem('webx11_servers'); return saved ? JSON.parse(saved) : []; } catch (e) { return []; } } saveServers() { localStorage.setItem('webx11_servers', JSON.stringify(this.servers)); } addServer(args) { if (args.length < 2) { this.terminal.print("Usage: server ", "warning"); return; } const name = args[0], url = args[1]; this.servers.push({ name, url, status: 'unknown', lastCheck: null }); this.saveServers(); this.terminal.print(`Server '${name}' added`, "success"); } listServers() { if (this.servers.length === 0) { this.terminal.print("No servers configured", "info"); return; } this.terminal.print("=== Monitored Servers ===", "info"); this.servers.forEach((s, i) => { const statusIcon = s.status === 'online' ? '🟢' : s.status === 'offline' ? '🔴' : '⚪'; this.terminal.print(`${i + 1}. ${statusIcon} ${s.name} - ${s.url}`); if (s.lastCheck) { this.terminal.print(` Last check: ${s.lastCheck}`, "info"); } }); } async checkServer(args) { if (!args[0]) { this.terminal.print("Usage: server-check ", "warning"); return; } const server = this.findServer(args[0]); if (!server) { this.terminal.print("Server not found", "error"); return; } this.terminal.print(`Checking ${server.name}...`, "info"); try { const start = Date.now(); const response = await fetch(server.url, { method: 'HEAD', mode: 'no-cors' }); const latency = Date.now() - start; server.status = 'online'; server.lastCheck = new Date().toLocaleString(); server.latency = latency; this.saveServers(); this.terminal.print(`✅ ${server.name} is online (${latency}ms)`, "success"); } catch (e) { server.status = 'offline'; server.lastCheck = new Date().toLocaleString(); this.saveServers(); this.terminal.print(`❌ ${server.name} is offline`, "error"); } } removeServer(args) { if (!args[0]) { this.terminal.print("Usage: server-remove ", "warning"); return; } const index = this.findServerIndex(args[0]); if (index === -1) { this.terminal.print("Server not found", "error"); return; } this.servers.splice(index, 1); this.saveServers(); this.terminal.print("Server removed", "success"); } toggleMonitor(args) { if (this.monitorInterval) { clearInterval(this.monitorInterval); this.monitorInterval = null; this.terminal.print("Auto-monitor stopped", "warning"); } else { this.terminal.print("Auto-monitor started (every 60s)", "success"); this.monitorInterval = setInterval(() => { this.servers.forEach((s, i) => { this.checkServer([String(i + 1)]); }); }, 60000); } } findServer(identifier) { const index = this.findServerIndex(identifier); return index !== -1 ? this.servers[index] : null; } findServerIndex(identifier) { const idx = parseInt(identifier) - 1; if (!isNaN(idx) && idx >= 0 && idx < this.servers.length) { return idx; } return this.servers.findIndex(s => s.name.toLowerCase() === identifier.toLowerCase()); } }