6 Commits

6 changed files with 61 additions and 9 deletions

View File

@@ -1,5 +1,25 @@
# Changelog
## 2026-03-23 - 3.0.3 - fix(prometheus)
clean up default Prometheus metric collectors on stop
- Return cleanup handlers from default metric registration for event loop and GC observers.
- Dispose registered Prometheus default metric collectors when stopping Smartmetrics to prevent lingering observers.
## 2026-03-02 - 3.0.2 - fix(pidusage)
prune history entries for PIDs not present in the requested set to avoid stale data and memory growth
- Deletes entries from the history map when a PID is not included in the current pids array
- Prevents accumulation of stale PID histories and potential memory growth
- Change implemented in ts/smartmetrics.pidusage.ts alongside the metrics result construction
## 2026-02-19 - 3.0.1 - fix(smartmetrics)
no code changes detected; no version bump or release required
- git diff contained no modifications
- current package.json version is 3.0.0
- no dependency or file changes to warrant a release
## 2026-02-19 - 3.0.0 - BREAKING CHANGE(smartmetrics)
add system-wide metrics collection, Prometheus gauges, and normalized CPU reporting

View File

@@ -1,6 +1,6 @@
{
"name": "@push.rocks/smartmetrics",
"version": "3.0.0",
"version": "3.0.3",
"private": false,
"description": "A package for easy collection and reporting of system and process metrics.",
"main": "dist_ts/index.js",

View File

@@ -3,6 +3,6 @@
*/
export const commitinfo = {
name: '@push.rocks/smartmetrics',
version: '3.0.0',
version: '3.0.3',
description: 'A package for easy collection and reporting of system and process metrics.'
}

View File

@@ -22,10 +22,11 @@ export class SmartMetrics {
// HTTP server for Prometheus endpoint
private prometheusServer?: plugins.http.Server;
private prometheusPort?: number;
private cleanupDefaultMetrics?: () => void;
public setup() {
this.registry = new plugins.prom.Registry();
plugins.prom.collectDefaultMetrics(this.registry);
this.cleanupDefaultMetrics = plugins.prom.collectDefaultMetrics(this.registry);
// Initialize custom gauges
this.cpuPercentageGauge = new plugins.prom.Gauge({
@@ -295,5 +296,9 @@ export class SmartMetrics {
public stop() {
this.started = false;
this.disablePrometheusEndpoint();
if (this.cleanupDefaultMetrics) {
this.cleanupDefaultMetrics();
this.cleanupDefaultMetrics = undefined;
}
}
}

View File

@@ -125,5 +125,12 @@ export async function getPidUsage(
});
}
// Prune history entries for PIDs no longer in the requested set
for (const histPid of history.keys()) {
if (!pids.includes(histPid)) {
history.delete(histPid);
}
}
return result;
}

View File

@@ -288,20 +288,25 @@ export class Histogram implements IMetric {
// ── Default Metrics Collectors ──────────────────────────────────────────────
export function collectDefaultMetrics(registry: Registry): void {
export function collectDefaultMetrics(registry: Registry): () => void {
registerProcessCpuTotal(registry);
registerProcessStartTime(registry);
registerProcessMemory(registry);
registerProcessOpenFds(registry);
registerProcessMaxFds(registry);
registerEventLoopLag(registry);
const cleanupEventLoop = registerEventLoopLag(registry);
registerProcessHandles(registry);
registerProcessRequests(registry);
registerProcessResources(registry);
registerHeapSizeAndUsed(registry);
registerHeapSpaces(registry);
registerVersion(registry);
registerGc(registry);
const cleanupGc = registerGc(registry);
return () => {
cleanupEventLoop();
cleanupGc();
};
}
function registerProcessCpuTotal(registry: Registry): void {
@@ -420,7 +425,7 @@ function registerProcessMaxFds(registry: Registry): void {
});
}
function registerEventLoopLag(registry: Registry): void {
function registerEventLoopLag(registry: Registry): () => void {
let histogram: ReturnType<typeof monitorEventLoopDelay> | null = null;
try {
histogram = monitorEventLoopDelay({ resolution: 10 });
@@ -496,6 +501,13 @@ function registerEventLoopLag(registry: Registry): void {
},
});
}
return () => {
if (histogram) {
histogram.disable();
histogram = null;
}
};
}
function registerProcessHandles(registry: Registry): void {
@@ -632,7 +644,7 @@ function registerVersion(registry: Registry): void {
});
}
function registerGc(registry: Registry): void {
function registerGc(registry: Registry): () => void {
const gcHistogram = new Histogram({
name: 'nodejs_gc_duration_seconds',
help: 'Garbage collection duration by kind, in seconds.',
@@ -649,8 +661,9 @@ function registerGc(registry: Registry): void {
15: 'All',
};
let obs: PerformanceObserver | null = null;
try {
const obs = new PerformanceObserver((list) => {
obs = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
const gcEntry = entry as any;
const kind = kindLabels[gcEntry.detail?.kind ?? gcEntry.kind] || 'Unknown';
@@ -661,6 +674,13 @@ function registerGc(registry: Registry): void {
} catch {
// GC observation not available
}
return () => {
if (obs) {
obs.disconnect();
obs = null;
}
};
}
// ── Helpers ─────────────────────────────────────────────────────────────────