feat(core): Introduce ProcessMonitor class and integrate native and external plugins
This commit is contained in:
parent
594e006a5a
commit
2ffaeff4b5
@ -1,5 +1,12 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 2025-03-01 - 1.1.0 - feat(core)
|
||||||
|
Introduce ProcessMonitor class and integrate native and external plugins
|
||||||
|
|
||||||
|
- Added a new ProcessMonitor class to manage and monitor child processes with memory constraints.
|
||||||
|
- Integrated native 'path' and external '@push.rocks/smartpath' packages in a unified plugins file.
|
||||||
|
- Adjusted index and related files for improved modular structure.
|
||||||
|
|
||||||
## 2025-02-24 - 1.0.3 - fix(core)
|
## 2025-02-24 - 1.0.3 - fix(core)
|
||||||
Corrected description in package.json and readme.md from 'task manager' to 'process manager'.
|
Corrected description in package.json and readme.md from 'task manager' to 'process manager'.
|
||||||
|
|
||||||
|
@ -21,7 +21,9 @@
|
|||||||
"@push.rocks/tapbundle": "^5.0.15",
|
"@push.rocks/tapbundle": "^5.0.15",
|
||||||
"@types/node": "^20.8.7"
|
"@types/node": "^20.8.7"
|
||||||
},
|
},
|
||||||
"dependencies": {},
|
"dependencies": {
|
||||||
|
"@push.rocks/smartpath": "^5.0.18"
|
||||||
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://code.foss.global/git.zone/tspm.git"
|
"url": "https://code.foss.global/git.zone/tspm.git"
|
||||||
|
4
pnpm-lock.yaml
generated
4
pnpm-lock.yaml
generated
@ -7,6 +7,10 @@ settings:
|
|||||||
importers:
|
importers:
|
||||||
|
|
||||||
.:
|
.:
|
||||||
|
dependencies:
|
||||||
|
'@push.rocks/smartpath':
|
||||||
|
specifier: ^5.0.18
|
||||||
|
version: 5.0.18
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@git.zone/tsbuild':
|
'@git.zone/tsbuild':
|
||||||
specifier: ^2.1.25
|
specifier: ^2.1.25
|
||||||
|
@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@git.zone/tspm',
|
name: '@git.zone/tspm',
|
||||||
version: '1.0.3',
|
version: '1.1.0',
|
||||||
description: 'a no fuzz process manager'
|
description: 'a no fuzz process manager'
|
||||||
}
|
}
|
||||||
|
178
ts/classes.processmonitor.ts
Normal file
178
ts/classes.processmonitor.ts
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
import { spawn, ChildProcess } from 'child_process';
|
||||||
|
import psTree from 'ps-tree';
|
||||||
|
import pidusage from 'pidusage';
|
||||||
|
|
||||||
|
interface IMonitorConfig {
|
||||||
|
name?: string; // Optional name to identify the instance
|
||||||
|
projectDir: string; // Directory where the command will run
|
||||||
|
command: string; // Full command to run (e.g., "npm run xyz")
|
||||||
|
args?: string[]; // Optional: arguments for the command
|
||||||
|
memoryLimitBytes: number; // Maximum allowed memory (in bytes) for the process group
|
||||||
|
monitorIntervalMs?: number; // Interval (in ms) at which memory is checked (default: 5000)
|
||||||
|
}
|
||||||
|
|
||||||
|
class ProcessMonitor {
|
||||||
|
private child: ChildProcess | null = null;
|
||||||
|
private config: IMonitorConfig;
|
||||||
|
private intervalId: NodeJS.Timeout | null = null;
|
||||||
|
private stopped: boolean = true; // Initially stopped until start() is called
|
||||||
|
|
||||||
|
constructor(config: IMonitorConfig) {
|
||||||
|
this.config = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
public start(): void {
|
||||||
|
// Reset the stopped flag so that new processes can spawn.
|
||||||
|
this.stopped = false;
|
||||||
|
this.log(`Starting process monitor.`);
|
||||||
|
this.spawnChild();
|
||||||
|
|
||||||
|
// Set the monitoring interval.
|
||||||
|
const interval = this.config.monitorIntervalMs || 5000;
|
||||||
|
this.intervalId = setInterval(() => {
|
||||||
|
if (this.child && this.child.pid) {
|
||||||
|
this.monitorProcessGroup(this.child.pid, this.config.memoryLimitBytes);
|
||||||
|
}
|
||||||
|
}, interval);
|
||||||
|
}
|
||||||
|
|
||||||
|
private spawnChild(): void {
|
||||||
|
// Don't spawn if the monitor has been stopped.
|
||||||
|
if (this.stopped) return;
|
||||||
|
|
||||||
|
if (this.config.args && this.config.args.length > 0) {
|
||||||
|
this.log(
|
||||||
|
`Spawning command "${this.config.command}" with args [${this.config.args.join(
|
||||||
|
', '
|
||||||
|
)}] in directory: ${this.config.projectDir}`
|
||||||
|
);
|
||||||
|
this.child = spawn(this.config.command, this.config.args, {
|
||||||
|
cwd: this.config.projectDir,
|
||||||
|
detached: true,
|
||||||
|
stdio: 'inherit',
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.log(
|
||||||
|
`Spawning command "${this.config.command}" in directory: ${this.config.projectDir}`
|
||||||
|
);
|
||||||
|
// Use shell mode to allow a full command string.
|
||||||
|
this.child = spawn(this.config.command, {
|
||||||
|
cwd: this.config.projectDir,
|
||||||
|
detached: true,
|
||||||
|
stdio: 'inherit',
|
||||||
|
shell: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.log(`Spawned process with PID ${this.child.pid}`);
|
||||||
|
|
||||||
|
// When the child process exits, restart it if the monitor isn't stopped.
|
||||||
|
this.child.on('exit', (code, signal) => {
|
||||||
|
this.log(`Child process exited with code ${code}, signal ${signal}.`);
|
||||||
|
if (!this.stopped) {
|
||||||
|
this.log('Restarting process...');
|
||||||
|
this.spawnChild();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Monitor the process group’s memory usage. If the total memory exceeds the limit,
|
||||||
|
* kill the process group so that the 'exit' handler can restart it.
|
||||||
|
*/
|
||||||
|
private async monitorProcessGroup(pid: number, memoryLimit: number): Promise<void> {
|
||||||
|
try {
|
||||||
|
const memoryUsage = await this.getProcessGroupMemory(pid);
|
||||||
|
this.log(
|
||||||
|
`Current memory usage for process group (PID ${pid}): ${this.humanReadableBytes(
|
||||||
|
memoryUsage
|
||||||
|
)} (${memoryUsage} bytes)`
|
||||||
|
);
|
||||||
|
if (memoryUsage > memoryLimit) {
|
||||||
|
this.log(
|
||||||
|
`Memory usage ${this.humanReadableBytes(
|
||||||
|
memoryUsage
|
||||||
|
)} exceeds limit of ${this.humanReadableBytes(memoryLimit)}. Restarting process.`
|
||||||
|
);
|
||||||
|
// Kill the entire process group by sending a signal to -PID.
|
||||||
|
process.kill(-pid, 'SIGKILL');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this.log('Error monitoring process group: ' + error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the total memory usage (in bytes) for the process group (the main process and its children).
|
||||||
|
*/
|
||||||
|
private getProcessGroupMemory(pid: number): Promise<number> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
psTree(pid, (err, children) => {
|
||||||
|
if (err) return reject(err);
|
||||||
|
// Include the main process and its children.
|
||||||
|
const pids: number[] = [pid, ...children.map(child => Number(child.PID))];
|
||||||
|
pidusage(pids, (err, stats) => {
|
||||||
|
if (err) return reject(err);
|
||||||
|
let totalMemory = 0;
|
||||||
|
for (const key in stats) {
|
||||||
|
totalMemory += stats[key].memory;
|
||||||
|
}
|
||||||
|
resolve(totalMemory);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a number of bytes into a human-readable string (e.g. "1.23 MB").
|
||||||
|
*/
|
||||||
|
private humanReadableBytes(bytes: number, decimals: number = 2): string {
|
||||||
|
if (bytes === 0) return '0 Bytes';
|
||||||
|
const k = 1024;
|
||||||
|
const dm = decimals < 0 ? 0 : decimals;
|
||||||
|
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
|
||||||
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||||
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop the monitor and prevent any further respawns.
|
||||||
|
*/
|
||||||
|
public stop(): void {
|
||||||
|
this.log('Stopping process monitor.');
|
||||||
|
this.stopped = true;
|
||||||
|
if (this.intervalId) {
|
||||||
|
clearInterval(this.intervalId);
|
||||||
|
}
|
||||||
|
if (this.child && this.child.pid) {
|
||||||
|
process.kill(-this.child.pid, 'SIGKILL');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper method for logging messages with the instance name.
|
||||||
|
*/
|
||||||
|
private log(message: string): void {
|
||||||
|
const prefix = this.config.name ? `[${this.config.name}] ` : '';
|
||||||
|
console.log(prefix + message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Example usage:
|
||||||
|
const config: IMonitorConfig = {
|
||||||
|
name: 'Project XYZ Monitor', // Identifier for the instance
|
||||||
|
projectDir: '/path/to/your/project', // Set the project directory here
|
||||||
|
command: 'npm run xyz', // Full command string (no need for args)
|
||||||
|
memoryLimitBytes: 500 * 1024 * 1024, // 500 MB memory limit
|
||||||
|
monitorIntervalMs: 5000, // Check memory usage every 5 seconds
|
||||||
|
};
|
||||||
|
|
||||||
|
const monitor = new ProcessMonitor(config);
|
||||||
|
monitor.start();
|
||||||
|
|
||||||
|
// Ensure that on process exit (e.g. Ctrl+C) we clean up the child process and prevent respawns.
|
||||||
|
process.on('SIGINT', () => {
|
||||||
|
monitor.log('Received SIGINT, stopping monitor...');
|
||||||
|
monitor.stop();
|
||||||
|
process.exit();
|
||||||
|
});
|
6
ts/classes.tspm.ts
Normal file
6
ts/classes.tspm.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import * as plugins from './plugins.js';
|
||||||
|
import * as paths from './paths.js';
|
||||||
|
|
||||||
|
export class Tspm {
|
||||||
|
|
||||||
|
}
|
@ -1,3 +1,3 @@
|
|||||||
import * as plugins from './tspm.plugins.js';
|
import * as plugins from './plugins.js';
|
||||||
|
|
||||||
export let demoExport = 'Hi there! :) This is an exported string';
|
export let demoExport = 'Hi there! :) This is an exported string';
|
||||||
|
13
ts/plugins.ts
Normal file
13
ts/plugins.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
// native scope
|
||||||
|
import * as path from 'node:path';
|
||||||
|
|
||||||
|
export {
|
||||||
|
path,
|
||||||
|
}
|
||||||
|
|
||||||
|
// @push.rocks scope
|
||||||
|
import * as smartpath from '@push.rocks/smartpath';
|
||||||
|
|
||||||
|
export {
|
||||||
|
smartpath,
|
||||||
|
}
|
@ -1,2 +0,0 @@
|
|||||||
const removeme = {};
|
|
||||||
export { removeme };
|
|
Loading…
x
Reference in New Issue
Block a user