From fa50ce40c820338adcf0381eb477c8cc429a45b9 Mon Sep 17 00:00:00 2001 From: Juergen Kunz Date: Sat, 30 Aug 2025 23:36:26 +0000 Subject: [PATCH] feat(processmonitor): Add CPU monitoring and display CPU in process list --- changelog.md | 8 ++++++++ ts/00_commitinfo_data.ts | 2 +- ts/cli/commands/process/list.ts | 15 ++++++++++----- ts/daemon/processmanager.ts | 7 +++++++ ts/daemon/processmonitor.ts | 30 ++++++++++++++++++++++++++---- 5 files changed, 52 insertions(+), 10 deletions(-) diff --git a/changelog.md b/changelog.md index be42dbb..7a94490 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,13 @@ # Changelog +## 2025-08-30 - 5.6.0 - feat(processmonitor) +Add CPU monitoring and display CPU in process list + +- CLI: show a CPU column in the `tspm list` output (adds formatting and placeholder name display) +- Daemon: ProcessMonitor now collects CPU usage for the process group in addition to memory +- Daemon: ProcessMonitor exposes getLastCpuUsage() and ProcessManager syncs CPU values into IProcessInfo +- Non-breaking: UI and internal stats enriched to surface CPU metrics for processes + ## 2025-08-30 - 5.5.0 - feat(logs) Improve logs streaming and backlog delivery; add CLI filters and ndjson output diff --git a/ts/00_commitinfo_data.ts b/ts/00_commitinfo_data.ts index 3ac853c..ed7a4a4 100644 --- a/ts/00_commitinfo_data.ts +++ b/ts/00_commitinfo_data.ts @@ -3,6 +3,6 @@ */ export const commitinfo = { name: '@git.zone/tspm', - version: '5.5.0', + version: '5.6.0', description: 'a no fuzz process manager' } diff --git a/ts/cli/commands/process/list.ts b/ts/cli/commands/process/list.ts index 4011059..ffbf4c9 100644 --- a/ts/cli/commands/process/list.ts +++ b/ts/cli/commands/process/list.ts @@ -20,13 +20,13 @@ export function registerListCommand(smartcli: plugins.smartcli.Smartcli) { console.log('Process List:'); console.log( - '┌─────────┬─────────────┬───────────┬───────────┬──────────┬──────────┐', + '┌─────────┬─────────────┬───────────┬───────────┬──────────┬──────────┬─────────┐', ); console.log( - '│ ID │ Name │ Status │ PID │ Memory │ Restarts │', + '│ ID │ Name │ Status │ PID │ Memory │ CPU │ Restarts │', ); console.log( - '├─────────┼─────────────┼───────────┼───────────┼──────────┼──────────┤', + '├─────────┼─────────────┼───────────┼───────────┼──────────┼──────────┼──────────┤', ); for (const proc of processes) { @@ -38,13 +38,18 @@ export function registerListCommand(smartcli: plugins.smartcli.Smartcli) { : '\x1b[33m'; const resetColor = '\x1b[0m'; + const cpuStr = typeof proc.cpu === 'number' && isFinite(proc.cpu) + ? `${proc.cpu.toFixed(1)}%` + : '-'; + // Name is not part of IProcessInfo; show ID as placeholder for now + const nameDisplay = String(proc.id); console.log( - `│ ${pad(String(proc.id), 7)} │ ${pad(String(proc.id), 11)} │ ${statusColor}${pad(proc.status, 9)}${resetColor} │ ${pad((proc.pid || '-').toString(), 9)} │ ${pad(formatMemory(proc.memory), 8)} │ ${pad(proc.restarts.toString(), 8)} │`, + `│ ${pad(String(proc.id), 7)} │ ${pad(nameDisplay, 11)} │ ${statusColor}${pad(proc.status, 9)}${resetColor} │ ${pad((proc.pid || '-').toString(), 9)} │ ${pad(formatMemory(proc.memory), 8)} │ ${pad(cpuStr, 8)} │ ${pad(proc.restarts.toString(), 8)} │`, ); } console.log( - '└─────────┴─────────────┴───────────┴───────────┴──────────┴──────────┘', + '└─────────┴─────────────┴───────────┴───────────┴──────────┴──────────┴──────────┘', ); }, { actionLabel: 'list processes' }, diff --git a/ts/daemon/processmanager.ts b/ts/daemon/processmanager.ts index b5558d5..3783b67 100644 --- a/ts/daemon/processmanager.ts +++ b/ts/daemon/processmanager.ts @@ -438,6 +438,13 @@ export class ProcessManager extends EventEmitter { info.uptime = uptime; } + // Update memory and cpu from latest monitor readings + info.memory = monitor.getLastMemoryUsage(); + const cpu = monitor.getLastCpuUsage(); + if (Number.isFinite(cpu)) { + info.cpu = cpu; + } + // Update restart count info.restarts = monitor.getRestartCount(); diff --git a/ts/daemon/processmonitor.ts b/ts/daemon/processmonitor.ts index be7a188..aa2735c 100644 --- a/ts/daemon/processmonitor.ts +++ b/ts/daemon/processmonitor.ts @@ -24,6 +24,8 @@ export class ProcessMonitor extends EventEmitter { private lastRetryAt: number | null = null; private readonly MAX_RETRIES = 10; private readonly RESET_WINDOW_MS = 60 * 60 * 1000; // 1 hour + private lastMemoryUsage: number = 0; + private lastCpuUsage: number = 0; constructor(config: IMonitorConfig & { id?: ProcessId }) { super(); @@ -260,12 +262,16 @@ export class ProcessMonitor extends EventEmitter { memoryLimit: number, ): Promise { try { - const memoryUsage = await this.getProcessGroupMemory(pid); + const { memory: memoryUsage, cpu: cpuUsage } = await this.getProcessGroupStats(pid); this.logger.debug( `Memory usage for PID ${pid}: ${this.humanReadableBytes(memoryUsage)} (${memoryUsage} bytes)`, ); + // Store latest readings + this.lastMemoryUsage = memoryUsage; + this.lastCpuUsage = cpuUsage; + // Only log memory usage in debug mode to avoid spamming if (process.env.TSPM_DEBUG) { this.log( @@ -303,7 +309,7 @@ export class ProcessMonitor extends EventEmitter { /** * Get the total memory usage (in bytes) for the process group (the main process and its children). */ - private getProcessGroupMemory(pid: number): Promise { + private getProcessGroupStats(pid: number): Promise<{ memory: number; cpu: number }> { return new Promise((resolve, reject) => { this.logger.debug( `Getting memory usage for process group with PID ${pid}`, @@ -333,7 +339,7 @@ export class ProcessMonitor extends EventEmitter { plugins.pidusage( pids, - (err: Error | null, stats: Record) => { + (err: Error | null, stats: Record) => { if (err) { const processError = new ProcessError( `Failed to get process usage stats: ${err.message}`, @@ -345,14 +351,16 @@ export class ProcessMonitor extends EventEmitter { } let totalMemory = 0; + let totalCpu = 0; for (const key in stats) { totalMemory += stats[key].memory; + totalCpu += Number.isFinite(stats[key].cpu) ? stats[key].cpu : 0; } this.logger.debug( `Total memory for process group: ${this.humanReadableBytes(totalMemory)}`, ); - resolve(totalMemory); + resolve({ memory: totalMemory, cpu: totalCpu }); }, ); }, @@ -448,6 +456,20 @@ export class ProcessMonitor extends EventEmitter { return this.processWrapper?.isRunning() || false; } + /** + * Get last measured memory usage for the process group (bytes) + */ + public getLastMemoryUsage(): number { + return this.lastMemoryUsage; + } + + /** + * Get last measured CPU usage for the process group (sum of group, percent) + */ + public getLastCpuUsage(): number { + return this.lastCpuUsage; + } + /** * Helper method for logging messages with the instance name. */