1 | "use strict";
|
2 | Object.defineProperty(exports, "__esModule", { value: true });
|
3 | const prom_client_1 = require("prom-client");
|
4 | const prometheus_extended_gauge_1 = require("prometheus-extended-gauge");
|
5 | const generic_pool_1 = require("generic-pool");
|
6 |
|
7 |
|
8 |
|
9 | exports.DEFAULT_CONNECTION_POOL_OPTIONS = {
|
10 | max: 10,
|
11 | min: 2,
|
12 | maxWaitingClients: 10,
|
13 | testOnBorrow: true,
|
14 | acquireTimeoutMillis: 1000,
|
15 | evictionRunIntervalMillis: 30000,
|
16 | numTestsPerRun: 2,
|
17 | idleTimeoutMillis: 30000,
|
18 | };
|
19 | class ConnectionPool {
|
20 | }
|
21 | exports.ConnectionPool = ConnectionPool;
|
22 | const connectErrorsCounter = new prom_client_1.Counter({
|
23 | name: 'db_pool_connect_errors_counter',
|
24 | help: 'Number of times a connection attempt fails',
|
25 | labelNames: ['poolName', 'readonly'],
|
26 | });
|
27 | const acquireErrorsCounter = new prom_client_1.Counter({
|
28 | name: 'db_pool_acquire_errors_counter',
|
29 | help: 'Number of times an acquire attempt fails',
|
30 | labelNames: ['poolName', 'readonly'],
|
31 | });
|
32 | const validateFailedCounter = new prom_client_1.Counter({
|
33 | name: 'db_pool_validate_failed_counter',
|
34 | help: 'Number of times a validation fails',
|
35 | labelNames: ['poolName', 'readonly'],
|
36 | });
|
37 | const activeGauge = new prometheus_extended_gauge_1.ExtendedGauge({
|
38 | name: 'db_pool_active_connections_gauge',
|
39 | help: 'Number of active connections in the pool',
|
40 | labelNames: ['poolName', 'readonly'],
|
41 | average: true,
|
42 | max: true,
|
43 | min: false,
|
44 | bucketSizeMillis: 1000,
|
45 | numBuckets: 60,
|
46 | });
|
47 | const totalGauge = new prom_client_1.Gauge({
|
48 | name: 'db_pool_total_connections_gauge',
|
49 | help: 'Number of total connections in the pool',
|
50 | labelNames: ['poolName', 'readonly'],
|
51 | });
|
52 | const acquireTimeHistogram = new prom_client_1.Histogram({
|
53 | name: 'db_pool_acquire_time_histogram',
|
54 | help: 'Time required to acquire a connection from the pool',
|
55 | labelNames: ['poolName', 'readonly'],
|
56 | buckets: [0.003, 0.005, 0.01, 0.05, 0.1, 0.3],
|
57 | });
|
58 | const connectTimeHistogram = new prom_client_1.Histogram({
|
59 | name: 'db_pool_connect_time_histogram',
|
60 | help: 'Time required to establish a new connection to the DB',
|
61 | labelNames: ['poolName', 'readonly'],
|
62 | buckets: [0.003, 0.005, 0.01, 0.05, 0.1, 0.3],
|
63 | });
|
64 | const useTimeHistogram = new prom_client_1.Histogram({
|
65 | name: 'db_pool_use_time_histogram',
|
66 | help: 'Time a connection is kept out of the pool',
|
67 | labelNames: ['poolName', 'readonly'],
|
68 | buckets: [0.003, 0.005, 0.01, 0.05, 0.1, 0.3],
|
69 | });
|
70 | const maxConnectionLimitGauge = new prom_client_1.Gauge({
|
71 | name: 'db_pool_max_pool_size_gauge',
|
72 | help: 'Max number of total connections in the pool',
|
73 | labelNames: ['poolName', 'readonly'],
|
74 | });
|
75 | class InstrumentedFactory {
|
76 | constructor(factory, name, readonly) {
|
77 | this.factory = factory;
|
78 | const labels = [name, readonly ? 'true' : 'false'];
|
79 | this.connectTimeHistogram = connectTimeHistogram.labels(...labels);
|
80 | this.connectErrorsCounter = connectErrorsCounter.labels(...labels);
|
81 | this.totalGauge = totalGauge.labels(...labels);
|
82 | this.validateFailedCounter = validateFailedCounter.labels(...labels);
|
83 | }
|
84 | async create() {
|
85 | const timer = this.connectTimeHistogram.startTimer();
|
86 | try {
|
87 | const connection = await this.factory.create();
|
88 | this.totalGauge.inc();
|
89 | return connection;
|
90 | }
|
91 | catch (e) {
|
92 | this.connectErrorsCounter.inc();
|
93 | throw e;
|
94 | }
|
95 | finally {
|
96 | timer();
|
97 | }
|
98 | }
|
99 | async destroy(client) {
|
100 | try {
|
101 | return await this.factory.destroy(client);
|
102 | }
|
103 | finally {
|
104 | this.totalGauge.dec();
|
105 | }
|
106 | }
|
107 | async validate(client) {
|
108 | if (this.factory.validate) {
|
109 | const resp = await this.factory.validate(client);
|
110 | if (!resp) {
|
111 | this.validateFailedCounter.inc();
|
112 | }
|
113 | return resp;
|
114 | }
|
115 | return true;
|
116 | }
|
117 | }
|
118 | exports.InstrumentedFactory = InstrumentedFactory;
|
119 | var PoolStatus;
|
120 | (function (PoolStatus) {
|
121 | PoolStatus[PoolStatus["NOT_STARTED"] = 0] = "NOT_STARTED";
|
122 | PoolStatus[PoolStatus["STARTED"] = 1] = "STARTED";
|
123 | PoolStatus[PoolStatus["STOPPED"] = 2] = "STOPPED";
|
124 | })(PoolStatus || (PoolStatus = {}));
|
125 | class InstrumentedConnectionPool extends ConnectionPool {
|
126 | constructor(factory, options, name, readonly) {
|
127 | super();
|
128 | this.status = PoolStatus.NOT_STARTED;
|
129 | this.name = name;
|
130 | this.readonly = readonly;
|
131 | this.options = Object.assign({}, exports.DEFAULT_CONNECTION_POOL_OPTIONS, options);
|
132 |
|
133 | const maxString = this.options.max.toString();
|
134 | this.options.max = parseInt(maxString, 10);
|
135 | const labels = [name, readonly ? 'true' : 'false'];
|
136 | const instrumentedFactory = new InstrumentedFactory(factory, name, readonly);
|
137 | this.pool = generic_pool_1.createPool(instrumentedFactory, this.getGenericPoolOptions());
|
138 | this.acquireTimeHistogram = acquireTimeHistogram.labels(...labels);
|
139 | this.acquireErrorsCounter = acquireErrorsCounter.labels(...labels);
|
140 | this.useTimeHistogram = useTimeHistogram.labels(...labels);
|
141 | this.activeGauge = activeGauge.labels(...labels);
|
142 | this.maxConnectionLimitGauge = maxConnectionLimitGauge.labels(...labels);
|
143 | }
|
144 | getName() {
|
145 | return this.name;
|
146 | }
|
147 | isReadonly() {
|
148 | return this.readonly;
|
149 | }
|
150 | async getConnection() {
|
151 | if (this.status !== PoolStatus.STARTED) {
|
152 | throw new Error(`Can't acquire connections from connection pool ${this.name}. The pool is not started`);
|
153 | }
|
154 | const timer = this.acquireTimeHistogram.startTimer();
|
155 | try {
|
156 | const connection = await this.pool.acquire();
|
157 | connection['__useTimer'] = this.useTimeHistogram.startTimer();
|
158 | this.activeGauge.inc();
|
159 | this.maxConnectionLimitGauge.set(this.options.max);
|
160 | return connection;
|
161 | }
|
162 | catch (e) {
|
163 | this.acquireErrorsCounter.inc();
|
164 | throw e;
|
165 | }
|
166 | finally {
|
167 | timer();
|
168 | }
|
169 | }
|
170 | getGenericPoolOptions() {
|
171 | return {
|
172 | acquireTimeoutMillis: this.options.acquireTimeoutMillis,
|
173 | autostart: false,
|
174 | evictionRunIntervalMillis: this.options.evictionRunIntervalMillis,
|
175 | fifo: false,
|
176 | idleTimeoutMillis: this.options.idleTimeoutMillis,
|
177 | max: this.options.max,
|
178 | maxWaitingClients: this.options.maxWaitingClients,
|
179 | min: this.options.min,
|
180 | numTestsPerRun: this.options.numTestsPerRun,
|
181 | softIdleTimeoutMillis: this.options.softIdleTimeoutMillis,
|
182 | testOnBorrow: this.options.testOnBorrow,
|
183 | };
|
184 | }
|
185 | release(connection) {
|
186 | this.activeGauge.dec();
|
187 | if (connection['__useTimer']) {
|
188 | connection['__useTimer']();
|
189 | }
|
190 | this.pool.release(connection);
|
191 | }
|
192 | async start() {
|
193 | if (this.status !== PoolStatus.NOT_STARTED) {
|
194 | throw new Error(`Can't start a connection pool that isn't in NOT_STARTED state. Pool: ${this.name}, Current Status: ${this.status}`);
|
195 | }
|
196 | this.status = PoolStatus.STARTED;
|
197 | this.pool['start']();
|
198 |
|
199 | return new Promise((resolve) => setTimeout(resolve, 30));
|
200 | }
|
201 | async stop() {
|
202 | if (this.status !== PoolStatus.STARTED) {
|
203 | throw new Error(`Can't stop a connection pool that isn't in STARTED state. Pool: ${this.name}, Current Status: ${this.status}`);
|
204 | }
|
205 | this.status = PoolStatus.STOPPED;
|
206 | await this.pool.drain();
|
207 | await this.pool.clear();
|
208 | }
|
209 | getOptions() {
|
210 | return Object.assign({}, this.options);
|
211 | }
|
212 | }
|
213 | exports.InstrumentedConnectionPool = InstrumentedConnectionPool;
|
214 |
|
\ | No newline at end of file |