Skip to content

Commit a5c290a

Browse files
feat: add support to monitor multiple instances of pg-client and pg-pool
1 parent 4aea2f0 commit a5c290a

File tree

6 files changed

+139
-94
lines changed

6 files changed

+139
-94
lines changed

src/pgClientPrometheusExporter.ts

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ export class PgClientPrometheusExporter {
1313
private readonly options: PgClientExporterOptions
1414
private readonly defaultOptions: PgClientExporterOptions = {}
1515

16+
private readonly PG_CLIENT_ERRORS_TOTAL = 'pg_client_errors_total'
17+
private readonly PG_CLIENT_DISCONNECTS_TOTAL = 'pg_client_disconnects_total'
1618
private readonly clientErrors: Counter
1719
private readonly clientDisconnects: Counter
1820

@@ -21,19 +23,21 @@ export class PgClientPrometheusExporter {
2123
this.register = register
2224
this.options = { ...this.defaultOptions, ...options }
2325

24-
this.clientErrors = new Counter({
25-
name: 'pg_client_errors_total',
26-
help: 'The total number of connection errors with a database.',
27-
labelNames: mergeLabelNamesWithStandardLabels(['host', 'database'], this.options.defaultLabels),
28-
registers: [this.register]
29-
})
26+
this.clientErrors = (this.register.getSingleMetric(this.PG_CLIENT_ERRORS_TOTAL) ??
27+
new Counter({
28+
name: this.PG_CLIENT_ERRORS_TOTAL,
29+
help: 'The total number of connection errors with a database.',
30+
labelNames: mergeLabelNamesWithStandardLabels(['host', 'database'], this.options.defaultLabels),
31+
registers: [this.register]
32+
})) as Counter
3033

31-
this.clientDisconnects = new Counter({
32-
name: 'pg_client_disconnects_total',
33-
help: 'The total number of disconnected connections.',
34-
labelNames: mergeLabelNamesWithStandardLabels(['host', 'database'], this.options.defaultLabels),
35-
registers: [this.register]
36-
})
34+
this.clientDisconnects = (this.register.getSingleMetric(this.PG_CLIENT_DISCONNECTS_TOTAL) ??
35+
new Counter({
36+
name: this.PG_CLIENT_DISCONNECTS_TOTAL,
37+
help: 'The total number of disconnected connections.',
38+
labelNames: mergeLabelNamesWithStandardLabels(['host', 'database'], this.options.defaultLabels),
39+
registers: [this.register]
40+
})) as Counter
3741
}
3842

3943
public enableMetrics(): void {

src/pgPoolPrometheusExporter.ts

Lines changed: 99 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,15 @@ export class PgPoolPrometheusExporter {
1818
private readonly poolPort: number
1919
private readonly poolDatabase: string | undefined
2020

21+
private readonly PG_POOL_CONNECTIONS_CREATED_TOTAL = 'pg_pool_connections_created_total'
22+
private readonly PG_POOL_SIZE = 'pg_pool_size'
23+
private readonly PG_POOL_MAX = 'pg_pool_max'
24+
private readonly PG_POOL_ACTIVE_CONNECTIONS = 'pg_pool_active_connections'
25+
private readonly PG_POOL_WAITING_CONNECTIONS = 'pg_pool_waiting_connections'
26+
private readonly PG_POOL_IDLE_CONNECTIONS = 'pg_pool_idle_connections'
27+
private readonly PG_POOL_ERRORS_TOTAL = 'pg_pool_errors_total'
28+
private readonly PG_POOL_CONNECTIONS_REMOVED_TOTAL = 'pg_pool_connections_removed_total'
29+
2130
private readonly poolConnectionsCreatedTotal: Counter
2231
private readonly poolSize: Gauge
2332
private readonly poolSizeMax: Gauge
@@ -32,88 +41,96 @@ export class PgPoolPrometheusExporter {
3241
this.register = register
3342
this.options = { ...this.defaultOptions, ...options }
3443

35-
this.poolConnectionsCreatedTotal = new Counter({
36-
name: 'pg_pool_connections_created_total',
37-
help: 'The total number of created connections.',
38-
labelNames: mergeLabelNamesWithStandardLabels(['host', 'database'], this.options.defaultLabels),
39-
registers: [this.register]
40-
})
41-
42-
this.poolSize = new Gauge({
43-
name: 'pg_pool_size',
44-
help: 'The current size of the connection pool, including active and idle members.',
45-
labelNames: mergeLabelNamesWithStandardLabels(['host', 'database'], this.options.defaultLabels),
46-
registers: [this.register]
47-
})
48-
49-
this.poolSizeMax = new Gauge({
50-
name: 'pg_pool_max',
51-
help: 'The maximum size of the connection pool.',
52-
labelNames: mergeLabelNamesWithStandardLabels(['host', 'database'], this.options.defaultLabels),
53-
registers: [this.register],
54-
collect: () => {
55-
this.poolSizeMax.set(
56-
mergeLabelsWithStandardLabels(
57-
{ host: this.poolHost + ':' + this.poolPort.toString(), database: this.poolDatabase },
58-
this.options.defaultLabels
59-
),
60-
this.poolMaxSize!
61-
)
62-
}
63-
})
64-
65-
this.poolActiveConnections = new Gauge({
66-
name: 'pg_pool_active_connections',
67-
help: 'The total number of active connections.',
68-
labelNames: mergeLabelNamesWithStandardLabels(['host', 'database'], this.options.defaultLabels),
69-
registers: [this.register]
70-
})
71-
72-
this.poolWaitingConnections = new Gauge({
73-
name: 'pg_pool_waiting_connections',
74-
help: 'The total number of waiting connections.',
75-
labelNames: mergeLabelNamesWithStandardLabels(['host', 'database'], this.options.defaultLabels),
76-
registers: [this.register],
77-
collect: () => {
78-
this.poolWaitingConnections.set(
79-
mergeLabelsWithStandardLabels(
80-
{ host: this.poolHost + ':' + this.poolPort.toString(), database: this.poolDatabase },
81-
this.options.defaultLabels
82-
),
83-
this.pool.waitingCount
84-
)
85-
}
86-
})
87-
88-
this.poolIdleConnections = new Gauge({
89-
name: 'pg_pool_idle_connections',
90-
help: 'The total number of idle connections.',
91-
labelNames: mergeLabelNamesWithStandardLabels(['host', 'database'], this.options.defaultLabels),
92-
registers: [this.register],
93-
collect: () => {
94-
this.poolIdleConnections.set(
95-
mergeLabelsWithStandardLabels(
96-
{ host: this.poolHost + ':' + this.poolPort.toString(), database: this.poolDatabase },
97-
this.options.defaultLabels
98-
),
99-
this.pool.idleCount
100-
)
101-
}
102-
})
103-
104-
this.poolErrors = new Counter({
105-
name: 'pg_pool_errors_total',
106-
help: 'The total number of connection errors with a database.',
107-
labelNames: mergeLabelNamesWithStandardLabels(['host', 'database', 'error'], this.options.defaultLabels),
108-
registers: [this.register]
109-
})
110-
111-
this.poolConnectionsRemovedTotal = new Counter({
112-
name: 'pg_pool_connections_removed_total',
113-
help: 'The total number of removed connections.',
114-
labelNames: mergeLabelNamesWithStandardLabels(['host', 'database'], this.options.defaultLabels),
115-
registers: [this.register]
116-
})
44+
this.poolConnectionsCreatedTotal = (this.register.getSingleMetric(this.PG_POOL_CONNECTIONS_CREATED_TOTAL) ??
45+
new Counter({
46+
name: this.PG_POOL_CONNECTIONS_CREATED_TOTAL,
47+
help: 'The total number of created connections.',
48+
labelNames: mergeLabelNamesWithStandardLabels(['host', 'database'], this.options.defaultLabels),
49+
registers: [this.register]
50+
})) as Counter
51+
52+
this.poolSize = (this.register.getSingleMetric(this.PG_POOL_SIZE) ??
53+
new Gauge({
54+
name: this.PG_POOL_SIZE,
55+
help: 'The current size of the connection pool, including active and idle members.',
56+
labelNames: mergeLabelNamesWithStandardLabels(['host', 'database'], this.options.defaultLabels),
57+
registers: [this.register]
58+
})) as Gauge
59+
60+
this.poolSizeMax = (this.register.getSingleMetric(this.PG_POOL_MAX) ??
61+
new Gauge({
62+
name: this.PG_POOL_MAX,
63+
help: 'The maximum size of the connection pool.',
64+
labelNames: mergeLabelNamesWithStandardLabels(['host', 'database'], this.options.defaultLabels),
65+
registers: [this.register],
66+
collect: () => {
67+
this.poolSizeMax.set(
68+
mergeLabelsWithStandardLabels(
69+
{ host: this.poolHost + ':' + this.poolPort.toString(), database: this.poolDatabase },
70+
this.options.defaultLabels
71+
),
72+
this.poolMaxSize!
73+
)
74+
}
75+
})) as Gauge
76+
77+
this.poolActiveConnections = (this.register.getSingleMetric(this.PG_POOL_ACTIVE_CONNECTIONS) ??
78+
new Gauge({
79+
name: this.PG_POOL_ACTIVE_CONNECTIONS,
80+
help: 'The total number of active connections.',
81+
labelNames: mergeLabelNamesWithStandardLabels(['host', 'database'], this.options.defaultLabels),
82+
registers: [this.register]
83+
})) as Gauge
84+
85+
this.poolWaitingConnections = (this.register.getSingleMetric(this.PG_POOL_WAITING_CONNECTIONS) ??
86+
new Gauge({
87+
name: this.PG_POOL_WAITING_CONNECTIONS,
88+
help: 'The total number of waiting connections.',
89+
labelNames: mergeLabelNamesWithStandardLabels(['host', 'database'], this.options.defaultLabels),
90+
registers: [this.register],
91+
collect: () => {
92+
this.poolWaitingConnections.set(
93+
mergeLabelsWithStandardLabels(
94+
{ host: this.poolHost + ':' + this.poolPort.toString(), database: this.poolDatabase },
95+
this.options.defaultLabels
96+
),
97+
this.pool.waitingCount
98+
)
99+
}
100+
})) as Gauge
101+
102+
this.poolIdleConnections = (this.register.getSingleMetric(this.PG_POOL_IDLE_CONNECTIONS) ??
103+
new Gauge({
104+
name: this.PG_POOL_IDLE_CONNECTIONS,
105+
help: 'The total number of idle connections.',
106+
labelNames: mergeLabelNamesWithStandardLabels(['host', 'database'], this.options.defaultLabels),
107+
registers: [this.register],
108+
collect: () => {
109+
this.poolIdleConnections.set(
110+
mergeLabelsWithStandardLabels(
111+
{ host: this.poolHost + ':' + this.poolPort.toString(), database: this.poolDatabase },
112+
this.options.defaultLabels
113+
),
114+
this.pool.idleCount
115+
)
116+
}
117+
})) as Gauge
118+
119+
this.poolErrors = (this.register.getSingleMetric(this.PG_POOL_ERRORS_TOTAL) ??
120+
new Counter({
121+
name: this.PG_POOL_ERRORS_TOTAL,
122+
help: 'The total number of connection errors with a database.',
123+
labelNames: mergeLabelNamesWithStandardLabels(['host', 'database', 'error'], this.options.defaultLabels),
124+
registers: [this.register]
125+
})) as Counter
126+
127+
this.poolConnectionsRemovedTotal = (this.register.getSingleMetric(this.PG_POOL_CONNECTIONS_REMOVED_TOTAL) ??
128+
new Counter({
129+
name: this.PG_POOL_CONNECTIONS_REMOVED_TOTAL,
130+
help: 'The total number of removed connections.',
131+
labelNames: mergeLabelNamesWithStandardLabels(['host', 'database'], this.options.defaultLabels),
132+
registers: [this.register]
133+
})) as Counter
117134

118135
this.poolMaxSize = getMaxPoolSize(pool)
119136
this.poolHost = getHost(pool)

test/metricsPgClient.spec.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ describe('client metrics are created with the correct parameters', () => {
2121

2222
beforeEach(() => {
2323
jest.clearAllMocks()
24+
register.getSingleMetric = jest.fn(() => undefined)
2425
})
2526

2627
test('client metrics are created', () => {

test/metricsPgPool.spec.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ describe('metrics are created with the correct parameters', () => {
2727

2828
beforeEach(() => {
2929
jest.clearAllMocks()
30+
register.getSingleMetric = jest.fn(() => undefined)
3031
})
3132

3233
test('all pool metrics are created', () => {

test/pgClientPrometheusExporter.spec.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,15 @@ describe('tests pgClientPrometheusExporter', () => {
3131
expect(register.getSingleMetric(metric)).toBeDefined()
3232
})
3333
})
34+
35+
test('metrics are registered only once and taken from the registry', () => {
36+
// eslint-disable-next-line no-new
37+
new PgClientPrometheusExporter(client, register)
38+
// eslint-disable-next-line no-new
39+
new PgClientPrometheusExporter(client, register)
40+
expect(register.getMetricsAsArray()).toHaveLength(metrics.length)
41+
metrics.forEach((metric) => {
42+
expect(register.getSingleMetric(metric)).toBeDefined()
43+
})
44+
})
3445
})

test/pgPoolPrometheusExporter.spec.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,15 @@ describe('tests PgPoolPrometheusExporter', () => {
4040
expect(register.getSingleMetric(metric)).toBeDefined()
4141
})
4242
})
43+
44+
test('metrics are registered only once and taken from the registry', () => {
45+
// eslint-disable-next-line no-new
46+
new PgPoolPrometheusExporter(pool, register)
47+
// eslint-disable-next-line no-new
48+
new PgPoolPrometheusExporter(pool, register)
49+
expect(register.getMetricsAsArray()).toHaveLength(metrics.length)
50+
metrics.forEach((metric) => {
51+
expect(register.getSingleMetric(metric)).toBeDefined()
52+
})
53+
})
4354
})

0 commit comments

Comments
 (0)