diff --git a/changelog.md b/changelog.md index 73c8435..f1e0ecb 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,14 @@ # Changelog +## 2025-03-01 - 1.2.0 - feat(core) +Introduce ProcessMonitor with memory management and spawning features + +- Added ProcessMonitor class with functionality to manage process execution and memory usage. +- Implemented process spawning with ability to handle command arguments and directories. +- Added periodic memory monitoring and automatic restarts when memory thresholds are exceeded. +- ProcessMonitor now logs its actions with optional configuration name for better identification. +- Updated test file to include example usage of ProcessMonitor. + ## 2025-03-01 - 1.1.1 - fix(package) Update dependencies and pnpm configuration diff --git a/package.json b/package.json index ff3d47d..1e532e1 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,10 @@ "@types/node": "^22.13.8" }, "dependencies": { - "@push.rocks/smartpath": "^5.0.18" + "@push.rocks/smartcli": "^4.0.11", + "@push.rocks/smartpath": "^5.0.18", + "pidusage": "^4.0.0", + "ps-tree": "^1.2.0" }, "repository": { "type": "git", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b44d26d..1e69f62 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,9 +8,18 @@ importers: .: dependencies: + '@push.rocks/smartcli': + specifier: ^4.0.11 + version: 4.0.11 '@push.rocks/smartpath': specifier: ^5.0.18 version: 5.0.18 + pidusage: + specifier: ^4.0.0 + version: 4.0.0 + ps-tree: + specifier: ^1.2.0 + version: 1.2.0 devDependencies: '@git.zone/tsbuild': specifier: ^2.1.25 @@ -2071,6 +2080,9 @@ packages: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} + duplexer@0.1.2: + resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==} + duplexify@3.7.1: resolution: {integrity: sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==} @@ -2206,6 +2218,9 @@ packages: resolution: {integrity: sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=} engines: {node: '>= 0.6'} + event-stream@3.3.4: + resolution: {integrity: sha1-SrTJoPWlTbkzi0w02Gv86PSzVXE=} + eventemitter3@4.0.7: resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} @@ -2349,6 +2364,9 @@ packages: from2@2.3.0: resolution: {integrity: sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=} + from@0.1.7: + resolution: {integrity: sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4=} + fs-constants@1.0.0: resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} @@ -2930,6 +2948,9 @@ packages: make-error@1.3.6: resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} + map-stream@0.1.0: + resolution: {integrity: sha1-5WqpTEyAVaFkBKBnS3jyFffI4ZQ=} + markdown-table@3.0.4: resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} @@ -3431,6 +3452,9 @@ packages: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} + pause-stream@0.0.11: + resolution: {integrity: sha1-/lo0sMvOErWqaitAPuLnO2AvFEU=} + pdf-lib@1.17.1: resolution: {integrity: sha512-V/mpyJAoTsN4cnP31vc0wfNA1+p20evqqnap0KLoRUN0Yk/p3wN52DOEsL4oBFcLdb76hlpKPtzJIgo67j/XLw==} @@ -3458,6 +3482,10 @@ packages: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} + pidusage@4.0.0: + resolution: {integrity: sha512-89hVJc5gq157puLYZaO3CH0qfGyDfbDG1KFCE4lCSwK0l1EuEbNa4pIJJXL93ltU5SsYia/DHJUgMY2qE4XRQg==} + engines: {node: '>=18'} + ping@0.4.4: resolution: {integrity: sha512-56ZMC0j7SCsMMLdOoUg12VZCfj/+ZO+yfOSjaNCRrmZZr6GLbN2X/Ui56T15dI8NhiHckaw5X2pvyfAomanwqQ==} engines: {node: '>=4.0.0'} @@ -3506,6 +3534,11 @@ packages: proxy-from-env@1.1.0: resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + ps-tree@1.2.0: + resolution: {integrity: sha512-0VnamPPYHl4uaU/nSFeZZpR21QAWRz+sRv4iW9+v/GS/J5U5iZB5BNN6J0RMoOvdx2gWM2+ZFMIm58q24e4UYA==} + engines: {node: '>= 0.10'} + hasBin: true + public-ip@6.0.2: resolution: {integrity: sha512-+6bkjnf0yQ4+tZV0zJv1017DiIF7y6R4yg17Mrhhkc25L7dtQtXWHgSCrz9BbLL4OeTFbPK4EALXqJUrwCIWXw==} engines: {node: '>=14.16'} @@ -3796,6 +3829,9 @@ packages: resolution: {integrity: sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==} engines: {node: '>=8'} + split@0.3.3: + resolution: {integrity: sha1-zQ7qXmOiEd//frDwkcQTPi0N0o8=} + sprintf-js@1.0.3: resolution: {integrity: sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=} @@ -3817,6 +3853,9 @@ packages: resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} engines: {node: '>= 0.8'} + stream-combiner@0.0.4: + resolution: {integrity: sha1-TV5DPBhSYd3mI8o/RMWGvPXErRQ=} + stream-shift@1.0.3: resolution: {integrity: sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==} @@ -3923,6 +3962,9 @@ packages: through2@4.0.2: resolution: {integrity: sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==} + through@2.3.8: + resolution: {integrity: sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=} + tiny-worker@2.3.0: resolution: {integrity: sha512-pJ70wq5EAqTAEl9IkGzA+fN0836rycEuz2Cn6yeZ6FRzlVS5IDOkFHpIoEsksPRQV34GDqXm65+OlnZqUSyK2g==} @@ -7345,6 +7387,8 @@ snapshots: es-errors: 1.3.0 gopd: 1.2.0 + duplexer@0.1.2: {} + duplexify@3.7.1: dependencies: end-of-stream: 1.4.4 @@ -7516,6 +7560,16 @@ snapshots: etag@1.8.1: {} + event-stream@3.3.4: + dependencies: + duplexer: 0.1.2 + from: 0.1.7 + map-stream: 0.1.0 + pause-stream: 0.0.11 + split: 0.3.3 + stream-combiner: 0.0.4 + through: 2.3.8 + eventemitter3@4.0.7: {} execa@5.1.1: @@ -7712,6 +7766,8 @@ snapshots: inherits: 2.0.4 readable-stream: 2.3.8 + from@0.1.7: {} + fs-constants@1.0.0: {} fs-extra@10.1.0: @@ -8377,6 +8433,8 @@ snapshots: make-error@1.3.6: {} + map-stream@0.1.0: {} + markdown-table@3.0.4: {} matcher@3.0.0: @@ -9020,6 +9078,10 @@ snapshots: path-type@4.0.0: {} + pause-stream@0.0.11: + dependencies: + through: 2.3.8 + pdf-lib@1.17.1: dependencies: '@pdf-lib/standard-fonts': 1.0.0 @@ -9043,6 +9105,10 @@ snapshots: picomatch@2.3.1: {} + pidusage@4.0.0: + dependencies: + safe-buffer: 5.2.1 + ping@0.4.4: {} pkg-dir@4.2.0: @@ -9095,6 +9161,10 @@ snapshots: proxy-from-env@1.1.0: {} + ps-tree@1.2.0: + dependencies: + event-stream: 3.3.4 + public-ip@6.0.2: dependencies: aggregate-error: 4.0.1 @@ -9504,6 +9574,10 @@ snapshots: signal-exit: 3.0.7 which: 2.0.2 + split@0.3.3: + dependencies: + through: 2.3.8 + sprintf-js@1.0.3: {} sprintf-js@1.1.3: {} @@ -9518,6 +9592,10 @@ snapshots: statuses@2.0.1: {} + stream-combiner@0.0.4: + dependencies: + duplexer: 0.1.2 + stream-shift@1.0.3: {} streamsearch@0.1.2: {} @@ -9652,6 +9730,8 @@ snapshots: dependencies: readable-stream: 3.6.2 + through@2.3.8: {} + tiny-worker@2.3.0: dependencies: esm: 3.2.25 diff --git a/test/test.ts b/test/test.ts index db9904a..572ccbd 100644 --- a/test/test.ts +++ b/test/test.ts @@ -6,3 +6,22 @@ tap.test('first test', async () => { }); tap.start(); + +// 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(); +}); \ No newline at end of file diff --git a/ts/00_commitinfo_data.ts b/ts/00_commitinfo_data.ts index 93d838a..f304609 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: '1.1.1', + version: '1.2.0', description: 'a no fuzz process manager' } diff --git a/ts/classes.processmonitor.ts b/ts/classes.processmonitor.ts index db99a06..d6ca44d 100644 --- a/ts/classes.processmonitor.ts +++ b/ts/classes.processmonitor.ts @@ -1,8 +1,6 @@ -import { spawn, ChildProcess } from 'child_process'; -import psTree from 'ps-tree'; -import pidusage from 'pidusage'; +import * as plugins from './plugins.js'; -interface IMonitorConfig { +export 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") @@ -11,8 +9,8 @@ interface IMonitorConfig { monitorIntervalMs?: number; // Interval (in ms) at which memory is checked (default: 5000) } -class ProcessMonitor { - private child: ChildProcess | null = null; +export class ProcessMonitor { + private child: plugins.childProcess.ChildProcess | null = null; private config: IMonitorConfig; private intervalId: NodeJS.Timeout | null = null; private stopped: boolean = true; // Initially stopped until start() is called @@ -46,7 +44,7 @@ class ProcessMonitor { ', ' )}] in directory: ${this.config.projectDir}` ); - this.child = spawn(this.config.command, this.config.args, { + this.child = plugins.childProcess.spawn(this.config.command, this.config.args, { cwd: this.config.projectDir, detached: true, stdio: 'inherit', @@ -56,7 +54,7 @@ class ProcessMonitor { `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, { + this.child = plugins.childProcess.spawn(this.config.command, { cwd: this.config.projectDir, detached: true, stdio: 'inherit', @@ -107,11 +105,11 @@ class ProcessMonitor { */ private getProcessGroupMemory(pid: number): Promise { return new Promise((resolve, reject) => { - psTree(pid, (err, children) => { + plugins.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) => { + plugins.pidusage(pids, (err, stats) => { if (err) return reject(err); let totalMemory = 0; for (const key in stats) { @@ -157,22 +155,3 @@ class ProcessMonitor { 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(); -}); \ No newline at end of file diff --git a/ts/index.ts b/ts/index.ts index 8f1f224..01f12a3 100644 --- a/ts/index.ts +++ b/ts/index.ts @@ -1,3 +1,2 @@ -import * as plugins from './plugins.js'; - -export let demoExport = 'Hi there! :) This is an exported string'; +export * from './classes.tspm.js'; +export * from './classes.processmonitor.js'; diff --git a/ts/paths.ts b/ts/paths.ts new file mode 100644 index 0000000..0aa4d1a --- /dev/null +++ b/ts/paths.ts @@ -0,0 +1,4 @@ +import * as plugins from './plugins.js'; + +export const packageDir = plugins.path.join(plugins.smartpath.get.dirnameFromImportMetaUrl(import.meta.url), '..'); +export const cwd = process.cwd(); \ No newline at end of file diff --git a/ts/plugins.ts b/ts/plugins.ts index 5585cdd..5e71080 100644 --- a/ts/plugins.ts +++ b/ts/plugins.ts @@ -1,13 +1,26 @@ // native scope +import * as childProcess from 'child_process'; import * as path from 'node:path'; export { + childProcess, path, } // @push.rocks scope import * as smartpath from '@push.rocks/smartpath'; +import * as smartcli from '@push.rocks/smartcli'; export { smartpath, + smartcli, +} + +// third-party scope +import psTree from 'ps-tree'; +import pidusage from 'pidusage'; + +export { + psTree, + pidusage, } \ No newline at end of file