Ghost/ghost/prometheus-metrics/test/metrics-server.test.ts
Chris Raible 190ebcd684
Added ability to push prometheus metrics to a pushgateway (#21526)
ref
https://linear.app/ghost/issue/ENG-1746/enable-ghost-to-push-metrics-to-a-pushgateway

- We'd like to use prometheus to expose metrics from Ghost, but the
"standard" approach of having prometheus scrape the `/metrics` endpoint
adds some complexity and additional challenges on Pro.
- A suggested simpler alternative is to use a pushgateway, to have Ghost
_push_ metrics to prometheus, rather than have prometheus scrape the
running instances.
- This PR introduces this functionality behind a configuration. 
- It also includes a refactor to the current metrics-server
implementation so all the related code for prometheus is colocated, and
the configuration is a bit more organized. `@tryghost/metrics-server`
has been renamed to `@tryghost/prometheus-metrics`, and it now includes
the metrics server and prometheus-client code itself (including the
pushgateway code)
- To enable the prometheus client alone, `prometheus:enabled` must be
true. This will _not_ enable the metrics server or the pushgateway — it
will essentially collect the metrics, but not do anything with them.
- To enable the metrics server, set `prometheus:metrics_server:enabled`
to true. You can also configure the host and port that the metrics
server should export the `/metrics` endpoint on in the
`prometheus:metrics_server` block.
- To enable the pushgateway, set `prometheus:pushgateway:enabled` to
true. You can also configure the pushgateway's `url`, the `interval` it
should push metrics in (in milliseconds) and the `jobName` in the
`prometheus:pushgateway` block.
2024-11-05 11:50:39 -08:00

80 lines
2.5 KiB
TypeScript

import assert from 'assert/strict';
import {MetricsServer} from '../src';
import request from 'supertest';
import express from 'express';
import * as sinon from 'sinon';
describe('Metrics Server', function () {
let metricsServer: MetricsServer;
let serverConfig = {
host: '127.0.0.1',
port: 9416
};
let handler = (req: express.Request, res: express.Response) => {
res.send('metrics');
};
afterEach(async function () {
await metricsServer.stop();
});
after(async function () {
await metricsServer.shutdown();
});
describe('constructor', function () {
it('should create a new instance', function () {
metricsServer = new MetricsServer({serverConfig, handler});
assert.ok(metricsServer);
});
});
describe('start', function () {
before(function () {
metricsServer = new MetricsServer({serverConfig, handler});
});
it('should start the server', async function () {
const server = await metricsServer.start();
assert.ok(server);
});
it('should use the provided handler', async function () {
const {app} = await metricsServer.start();
const response = await request(app).get('/metrics');
assert.ok(response.status === 200);
assert.ok(response.text === 'metrics');
});
});
describe('stop', function () {
before(function () {
metricsServer = new MetricsServer({serverConfig, handler});
});
it('should stop the server', async function () {
const server = await metricsServer.start();
await metricsServer.stop();
assert.ok(server);
});
});
describe('shutdown', function () {
before(function () {
metricsServer = new MetricsServer({serverConfig, handler});
});
it('should shutdown the server', async function () {
const server = await metricsServer.start();
await metricsServer.shutdown();
assert.ok(server);
});
it('should not shutdown the server if it is already shutting down', async function () {
const stopSpy = sinon.spy(metricsServer, 'stop');
await metricsServer.start();
// Call shutdown multiple times simultaneously
Promise.all([metricsServer.shutdown(), metricsServer.shutdown()]);
// It should only call stop() once
sinon.assert.calledOnce(stopSpy);
});
});
});