@quickstat/nodejs
Version:
Effortlessly monitor Node.js metrics and export them to Prometheus for visualization in Grafana
318 lines (316 loc) • 9.78 kB
JavaScript
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