From 7c3197455f039db33bb9582974e1dcaa3c7b480d Mon Sep 17 00:00:00 2001 From: Juergen Kunz Date: Thu, 19 Feb 2026 09:46:46 +0000 Subject: [PATCH] update --- package.json | 4 +-- pnpm-lock.yaml | 6 ++-- readme.md | 6 ++-- test/test.ts | 2 +- ts/smartmetrics.classes.smartmetrics.ts | 47 ++++++++++++++----------- 5 files changed, 36 insertions(+), 29 deletions(-) diff --git a/package.json b/package.json index f0b3a57..0887ed7 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,8 @@ "@git.zone/tsbundle": "^2.8.3", "@git.zone/tsrun": "^2.0.1", "@git.zone/tstest": "^3.1.8", - "@types/node": "^25.3.0" + "@types/node": "^25.3.0", + "@types/pidusage": "^2.0.2" }, "browserslist": [ "last 1 chrome versions" @@ -37,7 +38,6 @@ "dependencies": { "@push.rocks/smartdelay": "^3.0.5", "@push.rocks/smartlog": "^3.1.11", - "@types/pidusage": "^2.0.2", "pidtree": "^0.6.0", "pidusage": "^4.0.1", "prom-client": "^15.1.3" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 512a4b3..159a373 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,9 +14,6 @@ importers: '@push.rocks/smartlog': specifier: ^3.1.11 version: 3.1.11 - '@types/pidusage': - specifier: ^2.0.2 - version: 2.0.5 pidtree: specifier: ^0.6.0 version: 0.6.0 @@ -42,6 +39,9 @@ importers: '@types/node': specifier: ^25.3.0 version: 25.3.0 + '@types/pidusage': + specifier: ^2.0.2 + version: 2.0.5 packages: diff --git a/readme.md b/readme.md index 9dc7e37..256d503 100644 --- a/readme.md +++ b/readme.md @@ -71,7 +71,7 @@ SmartMetrics doesn't just monitor your main process – it automatically discove The library automatically detects available memory whether running on bare metal, in Docker containers, or with Node.js heap restrictions. It picks the most restrictive of: 1. **System total memory** (`os.totalmem()`) -2. **Docker cgroup limit** (`/sys/fs/cgroup/memory/memory.limit_in_bytes`) +2. **Docker cgroup limit** – supports both cgroup v2 (`/sys/fs/cgroup/memory.max`) and cgroup v1 (`/sys/fs/cgroup/memory/memory.limit_in_bytes`) 3. **V8 heap size limit** (`v8.getHeapStatistics().heap_size_limit`) This ensures accurate percentage calculations regardless of environment. @@ -101,8 +101,8 @@ Retrieves current system metrics as a structured object. ```typescript { process_cpu_seconds_total: number; // Total CPU time in seconds - nodejs_active_handles_total: number; // Active handles count - nodejs_active_requests_total: number; // Active requests count + nodejs_active_handles_total: number; // Always 0 (deprecated Node.js API; real values tracked by Prometheus default collectors) + nodejs_active_requests_total: number; // Always 0 (deprecated Node.js API; real values tracked by Prometheus default collectors) nodejs_heap_size_total_bytes: number; // V8 heap size in bytes cpuPercentage: number; // Aggregated CPU usage across all child processes cpuUsageText: string; // Human-readable CPU usage (e.g. "12.5 %") diff --git a/test/test.ts b/test/test.ts index 08c5a3d..d7c04a4 100644 --- a/test/test.ts +++ b/test/test.ts @@ -75,4 +75,4 @@ tap.test('should disable Prometheus endpoint', async () => { } }); -tap.start(); +export default tap.start(); diff --git a/ts/smartmetrics.classes.smartmetrics.ts b/ts/smartmetrics.classes.smartmetrics.ts index 31ef434..4f2bff3 100644 --- a/ts/smartmetrics.classes.smartmetrics.ts +++ b/ts/smartmetrics.classes.smartmetrics.ts @@ -17,7 +17,7 @@ export class SmartMetrics { private prometheusServer?: plugins.http.Server; private prometheusPort?: number; - public async setup() { + public setup() { const collectDefaultMetrics = plugins.promClient.collectDefaultMetrics; this.registry = new plugins.promClient.Registry(); collectDefaultMetrics({ register: this.registry }); @@ -50,28 +50,32 @@ export class SmartMetrics { } private checkMemoryLimits() { - let heapStats = plugins.v8.getHeapStatistics(); - let maxHeapSizeMB = heapStats.heap_size_limit / 1024 / 1024; - let totalSystemMemoryMB = plugins.os.totalmem() / 1024 / 1024; + const heapStats = plugins.v8.getHeapStatistics(); + const maxHeapSizeMB = heapStats.heap_size_limit / 1024 / 1024; + const totalSystemMemoryMB = plugins.os.totalmem() / 1024 / 1024; let dockerMemoryLimitMB = totalSystemMemoryMB; + + // Try cgroup v2 first, then fall back to cgroup v1 try { - let dockerMemoryLimitBytes = plugins.fs.readFileSync( - '/sys/fs/cgroup/memory/memory.limit_in_bytes', - 'utf8' - ); - dockerMemoryLimitMB = parseInt(dockerMemoryLimitBytes, 10) / 1024 / 1024; - } catch (error) { - // Ignore - this will fail if not running in a Docker container + const cgroupV2 = plugins.fs.readFileSync('/sys/fs/cgroup/memory.max', 'utf8').trim(); + if (cgroupV2 !== 'max') { + dockerMemoryLimitMB = parseInt(cgroupV2, 10) / 1024 / 1024; + } + } catch { + try { + const cgroupV1 = plugins.fs.readFileSync( + '/sys/fs/cgroup/memory/memory.limit_in_bytes', + 'utf8' + ).trim(); + dockerMemoryLimitMB = parseInt(cgroupV1, 10) / 1024 / 1024; + } catch { + // Not running in a container — use system memory + } } - // Set the maximum memory to the lower value between the Docker limit and the total system memory + // Pick the most restrictive limit this.maxMemoryMB = Math.min(totalSystemMemoryMB, dockerMemoryLimitMB, maxHeapSizeMB); - - // If the maximum old space size limit is greater than the maximum available memory, throw an error - if (maxHeapSizeMB > this.maxMemoryMB) { - throw new Error('Node.js process can use more memory than is available'); - } } public start() { @@ -104,7 +108,12 @@ export class SmartMetrics { } public async getMetrics() { - const pids = await plugins.pidtree(process.pid); + let pids: number[] = []; + try { + pids = await plugins.pidtree(process.pid); + } catch { + // pidtree can fail if process tree cannot be read + } const stats = await plugins.pidusage([process.pid, ...pids]); let cpuPercentage = 0; @@ -126,8 +135,6 @@ export class SmartMetrics { memoryUsageBytes )} / ${this.formatBytes(this.maxMemoryMB * 1024 * 1024)}`; - console.log(`${cpuUsageText} ||| ${memoryUsageText} `); - // Update Prometheus gauges with current values if (this.cpuPercentageGauge) { this.cpuPercentageGauge.set(cpuPercentage);