From a01e21663fc632d560c42d5e996824a89ea26e23 Mon Sep 17 00:00:00 2001 From: Juergen Kunz Date: Mon, 23 Mar 2026 14:51:14 +0000 Subject: [PATCH] fix(prometheus): clean up default Prometheus metric collectors on stop --- changelog.md | 6 +++++ ts/00_commitinfo_data.ts | 2 +- ts/smartmetrics.classes.smartmetrics.ts | 7 +++++- ts/smartmetrics.prom.ts | 32 ++++++++++++++++++++----- 4 files changed, 39 insertions(+), 8 deletions(-) diff --git a/changelog.md b/changelog.md index cfffa4d..91d375b 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,11 @@ # 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 diff --git a/ts/00_commitinfo_data.ts b/ts/00_commitinfo_data.ts index c627098..fa13cad 100644 --- a/ts/00_commitinfo_data.ts +++ b/ts/00_commitinfo_data.ts @@ -3,6 +3,6 @@ */ export const commitinfo = { name: '@push.rocks/smartmetrics', - version: '3.0.2', + version: '3.0.3', description: 'A package for easy collection and reporting of system and process metrics.' } diff --git a/ts/smartmetrics.classes.smartmetrics.ts b/ts/smartmetrics.classes.smartmetrics.ts index b69b94e..e99efa1 100644 --- a/ts/smartmetrics.classes.smartmetrics.ts +++ b/ts/smartmetrics.classes.smartmetrics.ts @@ -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; + } } } diff --git a/ts/smartmetrics.prom.ts b/ts/smartmetrics.prom.ts index 09d5fd4..3411360 100644 --- a/ts/smartmetrics.prom.ts +++ b/ts/smartmetrics.prom.ts @@ -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 | 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 ─────────────────────────────────────────────────────────────────