Skip to content

Commit

Permalink
feat(queue-getters): add prometheus exporter
Browse files Browse the repository at this point in the history
  • Loading branch information
manast committed Jan 25, 2025
1 parent 676c6a4 commit 078ae9d
Show file tree
Hide file tree
Showing 2 changed files with 71 additions and 0 deletions.
28 changes: 28 additions & 0 deletions src/classes/queue-getters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -572,4 +572,32 @@ export class QueueGetters<JobBase extends Job = Job> extends QueueBase {
});
return clients;
}

/**
* Export the metrics for the queue in the Prometheus format.
* Automatically exports all the counts returned by getJobCounts().
*
* @returns - Returns a string with the metrics in the Prometheus format.
*
* @sa {@link https://prometheus.io/docs/instrumenting/exposition_formats/}
*
**/
async exportPrometheusMetrics(): Promise<string> {
const counts = await this.getJobCounts();
const metrics: string[] = [];

// Match the test's expected HELP text
metrics.push(
'# HELP bullmq_job_count Number of jobs in the queue by state',
);
metrics.push('# TYPE bullmq_job_count gauge');

for (const [state, count] of Object.entries(counts)) {
metrics.push(
`bullmq_job_count{queue="${this.name}", state="${state}"} ${count}`,
);
}

return metrics.join('\n');
}
}
43 changes: 43 additions & 0 deletions tests/test_getters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import { expect } from 'chai';
import { after } from 'lodash';
import { describe, beforeEach, it, before, after as afterAll } from 'mocha';
import * as sinon from 'sinon';

import { default as IORedis } from 'ioredis';
import { v4 } from 'uuid';
import { FlowProducer, Queue, QueueEvents, Worker } from '../src/classes';
Expand Down Expand Up @@ -1011,4 +1013,45 @@ describe('Jobs getters', function () {
await flowProducer.close();
});
});

describe('#exportPrometheusMetrics', () => {
afterEach(() => {
sinon.restore();
});

it('should export all job states in Prometheus gauge format', async () => {
const counts = {
waiting: 5,
active: 3,
completed: 10,
delayed: 2,
failed: 1,
paused: 0,
};

sinon.stub(queue, 'getJobCounts').resolves(counts);
const metrics = await queue.exportPrometheusMetrics();

expect(metrics).to.include(
'# HELP bullmq_job_count Number of jobs in the queue by state',
);
expect(metrics).to.include('# TYPE bullmq_job_count gauge');

// Verify all states are present
for (const [state, count] of Object.entries(counts)) {
const expectedLine = `bullmq_job_count{queue="${queueName}", state="${state}"} ${count}`;
expect(metrics).to.include(expectedLine);
}
});

it('should handle empty states gracefully', async () => {
const counts = {}; // Edge case (though BullMQ never returns this)
sinon.stub(queue, 'getJobCounts').resolves(counts);

const metrics = await queue.exportPrometheusMetrics();
expect(
metrics.split('\n').filter(l => l.startsWith('bullmq_job_count')),
).to.have.lengthOf(0);
});
});
});

0 comments on commit 078ae9d

Please sign in to comment.