diff --git a/ghost/prometheus-metrics/src/PrometheusClient.ts b/ghost/prometheus-metrics/src/PrometheusClient.ts index f658296f9c..57f83a31fd 100644 --- a/ghost/prometheus-metrics/src/PrometheusClient.ts +++ b/ghost/prometheus-metrics/src/PrometheusClient.ts @@ -34,6 +34,7 @@ export class PrometheusClient { public gateway: client.Pushgateway | undefined; // public for testing public queries: Map void> = new Map(); public acquires: Map void> = new Map(); + public creates: Map void> = new Map(); private config: PrometheusClientConfig; private prefix; @@ -339,6 +340,15 @@ export class PrometheusClient { pruneAgedBuckets: false }); + const createDurationSummary = this.registerSummary({ + name: `db_connection_create_duration_seconds`, + help: 'Summary of the duration of creating a connection in seconds', + percentiles: [0.5, 0.9, 0.99], + maxAgeSeconds: 60, + ageBuckets: 6, + pruneAgedBuckets: false + }); + knexInstance.on('query', (query) => { // Add the query to the map this.queries.set(query.__knexQueryUid, queryDurationSummary.startTimer()); @@ -349,6 +359,20 @@ export class PrometheusClient { this.queries.delete(query.__knexQueryUid); }); + knexInstance.client.pool.on('createRequest', (eventId: number) => { + this.creates.set(eventId, createDurationSummary.startTimer()); + }); + + knexInstance.client.pool.on('createSuccess', (eventId: number) => { + this.creates.get(eventId)?.(); + this.creates.delete(eventId); + }); + + knexInstance.client.pool.on('createFail', (eventId: number) => { + this.creates.get(eventId)?.(); + this.creates.delete(eventId); + }); + knexInstance.client.pool.on('acquireRequest', (eventId: number) => { this.acquires.set(eventId, acquireDurationSummary.startTimer()); }); diff --git a/ghost/prometheus-metrics/test/prometheus-client.test.ts b/ghost/prometheus-metrics/test/prometheus-client.test.ts index 57b244edbc..808a5e639c 100644 --- a/ghost/prometheus-metrics/test/prometheus-client.test.ts +++ b/ghost/prometheus-metrics/test/prometheus-client.test.ts @@ -308,6 +308,22 @@ describe('Prometheus Client', function () { clock.restore(); } + function simulateCreate(duration: number) { + const clock = sinon.useFakeTimers(); + poolEventEmitter.emit('createRequest'); + clock.tick(duration); + poolEventEmitter.emit('createSuccess'); + clock.restore(); + } + + function simulateCreateFail(duration: number) { + const clock = sinon.useFakeTimers(); + poolEventEmitter.emit('createRequest'); + clock.tick(duration); + poolEventEmitter.emit('createFail'); + clock.restore(); + } + beforeEach(function () { knexEventEmitter = new EventEmitter(); poolEventEmitter = new EventEmitter(); @@ -439,6 +455,24 @@ describe('Prometheus Client', function () { const metricValues = await instance.getMetricValues('db_connection_acquire_duration_seconds'); assert.equal(metricValues?.[0].value, 0.5); }); + + it('should collect the db connection create duration metrics when a connection is created', async function () { + instance = new PrometheusClient(); + instance.init(); + instance.instrumentKnex(knexMock); + simulateCreate(500); + const metricValues = await instance.getMetricValues('db_connection_create_duration_seconds'); + assert.equal(metricValues?.[0].value, 0.5); + }); + + it('should collect the db connection create duration metrics when a connection fails to be created', async function () { + instance = new PrometheusClient(); + instance.init(); + instance.instrumentKnex(knexMock); + simulateCreateFail(500); + const metricValues = await instance.getMetricValues('db_connection_create_duration_seconds'); + assert.equal(metricValues?.[0].value, 0.5); + }); }); describe('Custom Metrics', function () {