fix(daemon): Improve daemon log delivery and process monitor memory accounting; gate debug output and update tests to numeric ProcessId
This commit is contained in:
@@ -3,6 +3,6 @@
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@git.zone/tspm',
|
||||
version: '5.3.1',
|
||||
version: '5.3.2',
|
||||
description: 'a no fuzz process manager'
|
||||
}
|
||||
|
@@ -18,6 +18,8 @@ export class ProcessMonitor extends EventEmitter {
|
||||
private processId?: ProcessId;
|
||||
private currentLogMemorySize: number = 0;
|
||||
private readonly MAX_LOG_MEMORY_SIZE = 10 * 1024 * 1024; // 10MB
|
||||
// Track approximate size per log to avoid O(n) JSON stringify on every update
|
||||
private logSizeMap: WeakMap<IProcessLog, number> = new WeakMap();
|
||||
private restartTimer: NodeJS.Timeout | null = null;
|
||||
private lastRetryAt: number | null = null;
|
||||
private readonly MAX_RETRIES = 10;
|
||||
@@ -39,7 +41,13 @@ export class ProcessMonitor extends EventEmitter {
|
||||
const persistedLogs = await this.logPersistence.loadLogs(this.processId);
|
||||
if (persistedLogs.length > 0) {
|
||||
this.logs = persistedLogs;
|
||||
this.currentLogMemorySize = LogPersistence.calculateLogMemorySize(this.logs);
|
||||
// Recalculate size once from scratch and seed the size map
|
||||
this.currentLogMemorySize = 0;
|
||||
for (const log of this.logs) {
|
||||
const size = this.estimateLogSize(log);
|
||||
this.logSizeMap.set(log, size);
|
||||
this.currentLogMemorySize += size;
|
||||
}
|
||||
this.logger.info(`Loaded ${persistedLogs.length} persisted logs from disk`);
|
||||
|
||||
// Delete the persisted file after loading
|
||||
@@ -87,18 +95,27 @@ export class ProcessMonitor extends EventEmitter {
|
||||
this.processWrapper.on('log', (log: IProcessLog): void => {
|
||||
// Store the log in our buffer
|
||||
this.logs.push(log);
|
||||
console.error(`[ProcessMonitor:${this.config.name}] Received log (type=${log.type}): ${log.message}`);
|
||||
console.error(`[ProcessMonitor:${this.config.name}] Logs array now has ${this.logs.length} items`);
|
||||
if (process.env.TSPM_DEBUG) {
|
||||
console.error(
|
||||
`[ProcessMonitor:${this.config.name}] Received log (type=${log.type}): ${log.message}`,
|
||||
);
|
||||
console.error(
|
||||
`[ProcessMonitor:${this.config.name}] Logs array now has ${this.logs.length} items`,
|
||||
);
|
||||
}
|
||||
this.logger.debug(`ProcessMonitor received log: ${log.message}`);
|
||||
|
||||
// Update memory size tracking
|
||||
this.currentLogMemorySize = LogPersistence.calculateLogMemorySize(this.logs);
|
||||
// Update memory size tracking incrementally
|
||||
const approxSize = this.estimateLogSize(log);
|
||||
this.logSizeMap.set(log, approxSize);
|
||||
this.currentLogMemorySize += approxSize;
|
||||
|
||||
// Trim logs if they exceed memory limit (10MB)
|
||||
while (this.currentLogMemorySize > this.MAX_LOG_MEMORY_SIZE && this.logs.length > 1) {
|
||||
// Remove oldest logs until we're under the memory limit
|
||||
this.logs.shift();
|
||||
this.currentLogMemorySize = LogPersistence.calculateLogMemorySize(this.logs);
|
||||
const removed = this.logs.shift()!;
|
||||
const removedSize = this.logSizeMap.get(removed) ?? this.estimateLogSize(removed);
|
||||
this.currentLogMemorySize -= removedSize;
|
||||
}
|
||||
|
||||
// Re-emit the log event for upstream handlers
|
||||
@@ -241,12 +258,14 @@ export class ProcessMonitor extends EventEmitter {
|
||||
`Memory usage for PID ${pid}: ${this.humanReadableBytes(memoryUsage)} (${memoryUsage} bytes)`,
|
||||
);
|
||||
|
||||
// Only log to the process log at longer intervals to avoid spamming
|
||||
this.log(
|
||||
`Current memory usage for process group (PID ${pid}): ${this.humanReadableBytes(
|
||||
memoryUsage,
|
||||
)} (${memoryUsage} bytes)`,
|
||||
);
|
||||
// Only log memory usage in debug mode to avoid spamming
|
||||
if (process.env.TSPM_DEBUG) {
|
||||
this.log(
|
||||
`Current memory usage for process group (PID ${pid}): ${this.humanReadableBytes(
|
||||
memoryUsage,
|
||||
)} (${memoryUsage} bytes)`,
|
||||
);
|
||||
}
|
||||
|
||||
if (memoryUsage > memoryLimit) {
|
||||
const memoryLimitMsg = `Memory usage ${this.humanReadableBytes(
|
||||
@@ -374,7 +393,11 @@ export class ProcessMonitor extends EventEmitter {
|
||||
* Get the current logs from the process
|
||||
*/
|
||||
public getLogs(limit?: number): IProcessLog[] {
|
||||
console.error(`[ProcessMonitor:${this.config.name}] getLogs called, logs.length=${this.logs.length}, limit=${limit}`);
|
||||
if (process.env.TSPM_DEBUG) {
|
||||
console.error(
|
||||
`[ProcessMonitor:${this.config.name}] getLogs called, logs.length=${this.logs.length}, limit=${limit}`,
|
||||
);
|
||||
}
|
||||
this.logger.debug(`Getting logs, total stored: ${this.logs.length}`);
|
||||
if (limit && limit > 0) {
|
||||
return this.logs.slice(-limit);
|
||||
@@ -417,4 +440,17 @@ export class ProcessMonitor extends EventEmitter {
|
||||
const prefix = this.config.name ? `[${this.config.name}] ` : '';
|
||||
console.log(prefix + message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Estimate approximate memory size in bytes for a log entry.
|
||||
* Keeps CPU low by avoiding JSON.stringify on the full array.
|
||||
*/
|
||||
private estimateLogSize(log: IProcessLog): number {
|
||||
const messageBytes = Buffer.byteLength(log.message || '', 'utf8');
|
||||
const typeBytes = Buffer.byteLength(log.type || '', 'utf8');
|
||||
const runIdBytes = Buffer.byteLength((log as any).runId || '', 'utf8');
|
||||
// Rough overhead for object structure, keys, timestamp/seq values
|
||||
const overhead = 64;
|
||||
return messageBytes + typeBytes + runIdBytes + overhead;
|
||||
}
|
||||
}
|
||||
|
@@ -90,9 +90,19 @@ export class ProcessWrapper extends EventEmitter {
|
||||
|
||||
// Capture stdout
|
||||
if (this.process.stdout) {
|
||||
console.error(`[ProcessWrapper] Setting up stdout listener for process ${this.process.pid}`);
|
||||
if (process.env.TSPM_DEBUG) {
|
||||
console.error(
|
||||
`[ProcessWrapper] Setting up stdout listener for process ${this.process.pid}`,
|
||||
);
|
||||
}
|
||||
this.process.stdout.on('data', (data) => {
|
||||
console.error(`[ProcessWrapper] Received stdout data from PID ${this.process?.pid}: ${data.toString().substring(0, 100)}`);
|
||||
if (process.env.TSPM_DEBUG) {
|
||||
console.error(
|
||||
`[ProcessWrapper] Received stdout data from PID ${this.process?.pid}: ${data
|
||||
.toString()
|
||||
.substring(0, 100)}`,
|
||||
);
|
||||
}
|
||||
// Add data to remainder buffer and split by newlines
|
||||
const text = this.stdoutRemainder + data.toString();
|
||||
const lines = text.split('\n');
|
||||
@@ -102,7 +112,9 @@ export class ProcessWrapper extends EventEmitter {
|
||||
|
||||
// Process complete lines
|
||||
for (const line of lines) {
|
||||
console.error(`[ProcessWrapper] Processing stdout line: ${line}`);
|
||||
if (process.env.TSPM_DEBUG) {
|
||||
console.error(`[ProcessWrapper] Processing stdout line: ${line}`);
|
||||
}
|
||||
this.logger.debug(`Captured stdout: ${line}`);
|
||||
this.addLog('stdout', line);
|
||||
}
|
||||
|
@@ -97,9 +97,25 @@ export class TspmDaemon {
|
||||
this.tspmInstance.on('process:log', ({ processId, log }) => {
|
||||
// Publish to topic for this process
|
||||
const topic = `logs.${processId}`;
|
||||
// Broadcast to all connected clients subscribed to this topic
|
||||
// Deliver only to subscribed clients
|
||||
if (this.ipcServer) {
|
||||
this.ipcServer.broadcast(`topic:${topic}`, log);
|
||||
try {
|
||||
const topicIndex = (this.ipcServer as any).topicIndex as Map<string, Set<string>> | undefined;
|
||||
const subscribers = topicIndex?.get(topic);
|
||||
if (subscribers && subscribers.size > 0) {
|
||||
// Send directly to subscribers for this topic
|
||||
for (const clientId of subscribers) {
|
||||
this.ipcServer
|
||||
.sendToClient(clientId, `topic:${topic}`, log)
|
||||
.catch((err: any) => {
|
||||
// Surface but don't fail the loop
|
||||
console.error('[IPC] sendToClient error:', err?.message || err);
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (err: any) {
|
||||
console.error('[IPC] Topic delivery error:', err?.message || err);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
Reference in New Issue
Block a user