feat: Implement Prometheus metrics exposure in SmartMetrics

- Added Prometheus gauges for CPU and memory metrics.
- Implemented HTTP server to expose metrics at /metrics endpoint.
- Created methods to enable and disable the Prometheus endpoint.
- Updated getMetrics() to set gauge values.
- Added tests for Prometheus metrics functionality.
- Updated documentation plan for Prometheus integration.
This commit is contained in:
Juergen Kunz
2025-06-09 10:31:25 +00:00
parent 9413e0323a
commit 34b09ed7a7
8 changed files with 7836 additions and 3777 deletions

View File

@ -7,11 +7,39 @@ export class SmartMetrics {
public logger: plugins.smartlog.Smartlog;
public registry: plugins.promClient.Registry;
public maxMemoryMB: number;
// Prometheus gauges for custom metrics
private cpuPercentageGauge: plugins.promClient.Gauge<string>;
private memoryPercentageGauge: plugins.promClient.Gauge<string>;
private memoryUsageBytesGauge: plugins.promClient.Gauge<string>;
// HTTP server for Prometheus endpoint
private prometheusServer?: plugins.http.Server;
private prometheusPort?: number;
public async setup() {
const collectDefaultMetrics = plugins.promClient.collectDefaultMetrics;
this.registry = new plugins.promClient.Registry();
collectDefaultMetrics({ register: this.registry });
// Initialize custom gauges
this.cpuPercentageGauge = new plugins.promClient.Gauge({
name: 'smartmetrics_cpu_percentage',
help: 'Current CPU usage percentage',
registers: [this.registry]
});
this.memoryPercentageGauge = new plugins.promClient.Gauge({
name: 'smartmetrics_memory_percentage',
help: 'Current memory usage percentage',
registers: [this.registry]
});
this.memoryUsageBytesGauge = new plugins.promClient.Gauge({
name: 'smartmetrics_memory_usage_bytes',
help: 'Current memory usage in bytes',
registers: [this.registry]
});
}
constructor(loggerArg: plugins.smartlog.Smartlog, sourceNameArg: string) {
@ -100,6 +128,17 @@ export class SmartMetrics {
)} / ${this.formatBytes(this.maxMemoryMB * 1024 * 1024)}`;
console.log(`${cpuUsageText} ||| ${memoryUsageText} `);
// Update Prometheus gauges with current values
if (this.cpuPercentageGauge) {
this.cpuPercentageGauge.set(cpuPercentage);
}
if (this.memoryPercentageGauge) {
this.memoryPercentageGauge.set(memoryPercentage);
}
if (this.memoryUsageBytesGauge) {
this.memoryUsageBytesGauge.set(memoryUsageBytes);
}
const returnMetrics: interfaces.IMetricsSnapshot = {
process_cpu_seconds_total: (
@ -127,7 +166,58 @@ export class SmartMetrics {
return returnMetrics;
}
public async getPrometheusFormattedMetrics(): Promise<string> {
// Update metrics to ensure gauges have latest values
await this.getMetrics();
// Return Prometheus text exposition format
return await this.registry.metrics();
}
public enablePrometheusEndpoint(port: number = 9090): void {
if (this.prometheusServer) {
this.logger.log('warn', 'Prometheus endpoint is already running');
return;
}
this.prometheusServer = plugins.http.createServer(async (req, res) => {
if (req.url === '/metrics' && req.method === 'GET') {
try {
const metrics = await this.getPrometheusFormattedMetrics();
res.writeHead(200, { 'Content-Type': 'text/plain; version=0.0.4' });
res.end(metrics);
} catch (error) {
res.writeHead(500, { 'Content-Type': 'text/plain' });
res.end('Error generating metrics');
this.logger.log('error', 'Error generating Prometheus metrics', error);
}
} else {
res.writeHead(404, { 'Content-Type': 'text/plain' });
res.end('Not Found');
}
});
this.prometheusPort = port;
this.prometheusServer.listen(port, () => {
this.logger.log('info', `Prometheus metrics endpoint available at http://localhost:${port}/metrics`);
});
}
public disablePrometheusEndpoint(): void {
if (!this.prometheusServer) {
return;
}
this.prometheusServer.close(() => {
this.logger.log('info', `Prometheus metrics endpoint on port ${this.prometheusPort} has been shut down`);
});
this.prometheusServer = undefined;
this.prometheusPort = undefined;
}
public stop() {
this.started = false;
this.disablePrometheusEndpoint();
}
}

View File

@ -1,4 +1,3 @@
// this might be extracted into a package @pushrocks/smartmetrics-interfaces in the future
export interface IMetricsSnapshot {
process_cpu_seconds_total: number;
nodejs_active_handles_total: number;

View File

@ -2,8 +2,9 @@
import * as v8 from 'v8';
import * as os from 'os';
import * as fs from 'fs';
import * as http from 'http';
export { v8, os, fs };
export { v8, os, fs, http };
// pushrocks scope
import * as smartdelay from '@push.rocks/smartdelay';