Skip to content

Commit

Permalink
introduced prometheus metrics
Browse files Browse the repository at this point in the history
  • Loading branch information
mrq1911 committed Dec 18, 2024
1 parent 6354afb commit 824bc04
Show file tree
Hide file tree
Showing 6 changed files with 278 additions and 10 deletions.
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
18
63 changes: 62 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@
"express": "^4.21.2",
"markdown-to-ansi": "^1.0.0",
"memoizee": "^0.4.17",
"p-queue": "^8.0.1"
"p-queue": "^8.0.1",
"prom-client": "^15.1.3"
},
"devDependencies": {
"jest": "^28.1.3"
Expand Down
17 changes: 14 additions & 3 deletions src/endpoints.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,17 @@ class EndpointRegistry {
});
}

registerEndpoint(namespace, routes, middleware = []) {
/**
* Register a new endpoint
* @param {string} namespace - The namespace for this endpoint
* @param {Object} routes - Object containing route definitions
* @param {Object} options - Registration options
* @param {boolean} options.prefix - Whether to prefix with /api/ (default: true)
* @param {Array} options.middleware - Middleware to apply to the routes
*/
registerEndpoint(namespace, routes, options = {}) {
const { prefix = true, middleware = [] } = options;

if (this.registeredEndpoints.has(namespace)) {
throw new Error(`Endpoint namespace '${namespace}' is already registered`);
}
Expand All @@ -37,8 +47,9 @@ class EndpointRegistry {
});
});

this.app.use(`/api/${namespace}`, router);
this.registeredEndpoints.set(namespace, { routes, middleware });
const path = prefix ? `/api/${namespace}` : `/${namespace}`;
this.app.use(path, router);
this.registeredEndpoints.set(namespace, { routes, options });
}

start() {
Expand Down
115 changes: 115 additions & 0 deletions src/metrics.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import client from 'prom-client';
import { endpoints } from './endpoints.js';

class MetricsRegistry {
constructor() {
// Clear default metrics and initialize custom registry
client.register.clear();
this.registry = new client.Registry();
this.registeredMetrics = new Map();

// Enable collection of default metrics
this.collectDefaultMetrics();

// Register metrics endpoint
this.registerMetricsEndpoint();
}

collectDefaultMetrics() {
client.collectDefaultMetrics({
register: this.registry,
prefix: 'snakewatch_'
});
}

register(namespace, metrics) {
if (this.registeredMetrics.has(namespace)) {
throw new Error(`Metrics for namespace '${namespace}' are already registered`);
}

const initializedMetrics = {};

Object.entries(metrics).forEach(([name, config]) => {
const metricName = `${namespace}_${name}`;

let metric;
switch (config.type) {
case 'counter':
metric = new client.Counter({
name: metricName,
help: config.help,
labelNames: config.labels || [],
registers: [this.registry]
});
break;

case 'gauge':
metric = new client.Gauge({
name: metricName,
help: config.help,
labelNames: config.labels || [],
registers: [this.registry]
});
break;

case 'histogram':
metric = new client.Histogram({
name: metricName,
help: config.help,
labelNames: config.labels || [],
buckets: config.buckets || [0.1, 0.5, 1, 2, 5],
registers: [this.registry]
});
break;

case 'summary':
metric = new client.Summary({
name: metricName,
help: config.help,
labelNames: config.labels || [],
percentiles: config.percentiles || [0.01, 0.05, 0.5, 0.9, 0.95, 0.99],
registers: [this.registry]
});
break;

default:
throw new Error(`Unknown metric type: ${config.type}`);
}

initializedMetrics[name] = metric;
});

this.registeredMetrics.set(namespace, initializedMetrics);
return initializedMetrics;
}

getMetrics(namespace) {
return this.registeredMetrics.get(namespace);
}

getRegisteredMetrics() {
return Array.from(this.registeredMetrics.keys());
}

async getPrometheusMetrics() {
return this.registry.metrics();
}

getContentType() {
return this.registry.contentType;
}

registerMetricsEndpoint() {
endpoints.registerEndpoint('metrics', {
'/': {
GET: async (req, res) => {
const metrics = await this.getPrometheusMetrics();
res.set('Content-Type', this.getContentType());
res.end(metrics);
}
}
}, { prefix: false });
}
}

export const metrics = new MetricsRegistry();
Loading

0 comments on commit 824bc04

Please sign in to comment.