This commit is contained in:
2026-02-19 09:46:46 +00:00
parent 767a40e01e
commit 7c3197455f
5 changed files with 36 additions and 29 deletions

View File

@@ -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"

6
pnpm-lock.yaml generated
View File

@@ -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:

View File

@@ -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 %")

View File

@@ -75,4 +75,4 @@ tap.test('should disable Prometheus endpoint', async () => {
}
});
tap.start();
export default tap.start();

View File

@@ -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(
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'
);
dockerMemoryLimitMB = parseInt(dockerMemoryLimitBytes, 10) / 1024 / 1024;
} catch (error) {
// Ignore - this will fail if not running in a Docker container
).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);