UNPKG

@quickstat/nodejs

Version:

Effortlessly monitor Node.js metrics and export them to Prometheus for visualization in Grafana

318 lines (316 loc) 9.78 kB
import { createRequire } from 'module'; import { NativeMetricTypes, createInstanceFromRawMetric, Plugin } from '@quickstat/core'; import { PerformanceObserver } from 'perf_hooks'; createRequire(import.meta.url); var NODEJS_METRICS_RAW = [ { name: "nodejs_event_loop_lag", type: NativeMetricTypes.Gauge, description: "The event loop lag in seconds", labels: [] }, { name: "nodejs_gc_duration", type: NativeMetricTypes.Histogram, description: "The duration of the garbage collection in seconds", labels: ["kind"] }, { name: "nodejs_heap_size", type: NativeMetricTypes.Gauge, description: "The total heap size in bytes", labels: [] }, { name: "nodejs_heap_size_used", type: NativeMetricTypes.Gauge, description: "The used heap size in bytes", labels: [] }, { name: "nodejs_external_memory", type: NativeMetricTypes.Gauge, description: "The external memory size in bytes", labels: [] }, { name: "nodejs_heap_space_size", type: NativeMetricTypes.Gauge, description: "The total heap space size in bytes", labels: ["space"] }, { name: "nodejs_heap_space_size_used", type: NativeMetricTypes.Gauge, description: "The used heap space size in bytes", labels: ["space"] }, { name: "nodejs_heap_space_size_available", type: NativeMetricTypes.Gauge, description: "The available heap space size in bytes", labels: ["space"] }, { name: "nodejs_process_resident_memory", type: NativeMetricTypes.Gauge, description: "The resident memory size in bytes", labels: [] }, { name: "nodejs_process_virtual_memory", type: NativeMetricTypes.Gauge, description: "The virtual memory size in bytes", labels: [] }, { name: "nodejs_process_heap_memory", type: NativeMetricTypes.Gauge, description: "The heap memory size in bytes", labels: [] }, { name: "nodejs_process_cpu_user", type: NativeMetricTypes.Counter, description: "The total user CPU time in seconds", labels: [] }, { name: "nodejs_process_cpu_system", type: NativeMetricTypes.Counter, description: "The total system CPU time in seconds", labels: [] }, { name: "nodejs_process_cpu", type: NativeMetricTypes.Counter, description: "The total CPU time in seconds", labels: [] }, { name: "nodejs_active_handles", type: NativeMetricTypes.Gauge, description: "The number of active handles", labels: [] }, { name: "nodejs_process_max_fds", type: NativeMetricTypes.Gauge, description: "The maximum number of file descriptors", labels: [] }, { name: "nodejs_process_open_fds", type: NativeMetricTypes.Gauge, description: "The number of open file descriptors", labels: [] }, { name: "nodejs_active_requests", type: NativeMetricTypes.Gauge, description: "The number of active requests", labels: [] }, { name: "nodejs_active_resources", type: NativeMetricTypes.Gauge, description: "The number of active resources", labels: [] }, { name: "nodejs_uptime", type: NativeMetricTypes.Gauge, description: "The uptime of the node.js process in seconds", labels: [] } /* { name: 'nodejs_version', type: NativeMetricTypes.Gauge, description: 'The version of the node.js process', labels: ['version'], }, */ ]; var NODEJS_METRICS = NODEJS_METRICS_RAW.map(createInstanceFromRawMetric).filter((x) => x !== void 0); var NodeJsPlugin = class extends Plugin { /** * The node.js plugin to collect Node.js runtime metrics * @param options The options for the node.js plugin */ constructor(options = {}) { super({ ...options, metrics: NODEJS_METRICS }); } /** * Called once the client registers the plugin */ onRegister() { this.setupEventLoopMonitoring(); this.setupGCMonitoring(); return null; } /** * Sets up event loop latency monitoring */ setupEventLoopMonitoring() { let start = process.hrtime.bigint(); this.eventLoopMonitor = setInterval(() => { const delta = process.hrtime.bigint() - start; const lag = Number(delta) / 1e9; this.setMetricValue("nodejs_event_loop_lag", [], lag); start = process.hrtime.bigint(); }, 1e3); } /** * Sets up garbage collection monitoring */ setupGCMonitoring() { try { this.gcObserver = new PerformanceObserver((list) => { for (const entry of list.getEntries()) { if (entry.entryType === "gc") { const detail = entry.detail; const gcKind = this.getGCKind(detail?.kind); this.observeMetricValue("nodejs_gc_duration", [gcKind], entry.duration / 1e3); } } }); this.gcObserver.observe({ entryTypes: ["gc"] }); } catch (error) { console.warn("GC monitoring not available:", error); } } /** * Gets the GC kind name from the GC kind number * @param kind The GC kind number * @returns The GC kind name */ getGCKind(kind) { if (kind === 1) return "scavenge"; if (kind === 2) return "mark_sweep_compact"; if (kind === 4) return "incremental_marking"; if (kind === 8) return "weak_phantom"; if (kind === 15) return "all"; return "unknown"; } /** * Sets the value of a gauge or counter metric * @param metricName The name of the metric * @param labels The labels for the metric * @param value The value to set */ setMetricValue(metricName, labels, value) { const metric = this.client?.metrics.get(metricName); if (!metric) return; switch (metric.type) { case NativeMetricTypes.Counter: metric.set(labels, value); break; case NativeMetricTypes.Gauge: metric.set(labels, value); break; } } /** * Observes a value for a histogram metric * @param metricName The name of the metric * @param labels The labels for the metric * @param value The value to observe */ observeMetricValue(metricName, labels, value) { const metric = this.client?.metrics.get(metricName); if (!metric) return; if (metric.type === NativeMetricTypes.Histogram) { metric.observe(labels, value); } } /** * Collect the Node.js runtime metrics * @param timestamp The timestamp to collect the metrics at * @returns The collected metrics */ async onCollect(timestamp) { await this.collectMemoryMetrics(); await this.collectProcessMetrics(); await this.collectHandleMetrics(); this.setMetricValue("nodejs_uptime", [], process.uptime()); return super.onCollect(timestamp); } /** * Collects memory related metrics */ async collectMemoryMetrics() { const memUsage = process.memoryUsage(); this.setMetricValue("nodejs_heap_size", [], memUsage.heapTotal); this.setMetricValue("nodejs_heap_size_used", [], memUsage.heapUsed); this.setMetricValue("nodejs_external_memory", [], memUsage.external); this.setMetricValue("nodejs_process_resident_memory", [], memUsage.rss); this.setMetricValue("nodejs_process_heap_memory", [], memUsage.heapUsed); try { const v8 = await import('v8'); const heapSpaceStats = v8.getHeapSpaceStatistics(); for (const space of heapSpaceStats) { const spaceName = space.space_name; this.setMetricValue("nodejs_heap_space_size", [spaceName], space.space_size); this.setMetricValue("nodejs_heap_space_size_used", [spaceName], space.space_used_size); this.setMetricValue("nodejs_heap_space_size_available", [spaceName], space.space_available_size); } } catch { } } /** * Collects process related metrics */ async collectProcessMetrics() { const cpuUsage = process.cpuUsage(); this.setMetricValue("nodejs_process_cpu_user", [], cpuUsage.user / 1e6); this.setMetricValue("nodejs_process_cpu_system", [], cpuUsage.system / 1e6); this.setMetricValue("nodejs_process_cpu", [], (cpuUsage.user + cpuUsage.system) / 1e6); try { const fs = await import('fs'); const procStat = fs.readFileSync("/proc/self/stat", "utf8"); const stats = procStat.split(" "); const virtualMemory = parseInt(stats[22], 10); this.setMetricValue("nodejs_process_virtual_memory", [], virtualMemory); try { const limits = fs.readFileSync("/proc/self/limits", "utf8"); const maxFdsMatch = limits.match(/Max open files\s+(\d+)/); if (maxFdsMatch) { this.setMetricValue("nodejs_process_max_fds", [], parseInt(maxFdsMatch[1], 10)); } const fdDir = fs.readdirSync("/proc/self/fd"); this.setMetricValue("nodejs_process_open_fds", [], fdDir.length); } catch { } } catch { } } /** * Collects handle and request metrics */ async collectHandleMetrics() { const proc = process; const handles = proc._getActiveHandles?.()?.length ?? 0; const requests = proc._getActiveRequests?.()?.length ?? 0; this.setMetricValue("nodejs_active_handles", [], handles); this.setMetricValue("nodejs_active_requests", [], requests); try { const asyncHooks = await import('async_hooks'); const resources = asyncHooks.executionAsyncResource(); this.setMetricValue("nodejs_active_resources", [], resources ? 1 : 0); } catch { } } /** * Cleanup resources when the plugin is destroyed */ destroy() { if (this.eventLoopMonitor) { clearInterval(this.eventLoopMonitor); } if (this.gcObserver) { this.gcObserver.disconnect(); } } }; export { NODEJS_METRICS, NODEJS_METRICS_RAW, NodeJsPlugin }; //# sourceMappingURL=out.js.map //# sourceMappingURL=index.js.map