pms_md 1.0.0 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/BUILD_SUMMARY.md +257 -0
- package/CHANGELOG.md +176 -0
- package/DATABASE_SUPPORT.md +582 -0
- package/FINAL_CHECKLIST.md +210 -0
- package/PACKAGE_READY.txt +169 -0
- package/QUICK_DATABASE_REFERENCE.md +247 -0
- package/README.md +582 -57
- package/RELEASE_v1.0.3.md +237 -0
- package/index.js +14 -0
- package/node-monitor/GETTING_STARTED.md +35 -0
- package/node-monitor/README.md +21 -3
- package/node-monitor/examples/MYSQL_SEQUELIZE_GUIDE.md +321 -0
- package/node-monitor/examples/sequelize-mysql-example.js +147 -0
- package/node-monitor/package.json +6 -2
- package/node-monitor/src/monitors/dbConnectionMonitor.js +635 -21
- package/package.json +106 -5
- package/node-monitor/DESIGN_IMPROVEMENTS.md +0 -286
- package/node-monitor/FILTER_BUTTONS_FIX.md +0 -303
- package/node-monitor/PUBLISHING_GUIDE.md +0 -331
- package/node-monitor/READY_TO_PUBLISH.md +0 -272
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Database Connection Monitor
|
|
3
|
-
* Monitors database connections for MongoDB, PostgreSQL, MySQL,
|
|
3
|
+
* Monitors database connections for MongoDB, PostgreSQL, MySQL, MSSQL, SQLite,
|
|
4
|
+
* MariaDB, Redis, CouchDB, Cassandra, and more
|
|
4
5
|
*/
|
|
5
6
|
|
|
6
7
|
const cron = require('node-cron');
|
|
@@ -104,18 +105,51 @@ class DbConnectionMonitor {
|
|
|
104
105
|
case 'mongodb':
|
|
105
106
|
case 'mongoose':
|
|
106
107
|
return this.checkMongoConnection(connection);
|
|
107
|
-
|
|
108
|
+
|
|
108
109
|
case 'postgresql':
|
|
109
110
|
case 'postgres':
|
|
110
111
|
case 'pg':
|
|
111
112
|
return this.checkPostgresConnection(connection, testQuery);
|
|
112
|
-
|
|
113
|
+
|
|
113
114
|
case 'mysql':
|
|
115
|
+
case 'mysql2':
|
|
114
116
|
return this.checkMySqlConnection(connection, testQuery);
|
|
115
|
-
|
|
117
|
+
|
|
118
|
+
case 'mariadb':
|
|
119
|
+
return this.checkMariaDBConnection(connection, testQuery);
|
|
120
|
+
|
|
121
|
+
case 'mssql':
|
|
122
|
+
case 'sqlserver':
|
|
123
|
+
case 'tedious':
|
|
124
|
+
return this.checkMSSQLConnection(connection, testQuery);
|
|
125
|
+
|
|
126
|
+
case 'sqlite':
|
|
127
|
+
case 'sqlite3':
|
|
128
|
+
return this.checkSQLiteConnection(connection, testQuery);
|
|
129
|
+
|
|
130
|
+
case 'sequelize':
|
|
131
|
+
return this.checkSequelizeConnection(connection);
|
|
132
|
+
|
|
116
133
|
case 'redis':
|
|
117
134
|
return this.checkRedisConnection(connection);
|
|
118
|
-
|
|
135
|
+
|
|
136
|
+
case 'couchdb':
|
|
137
|
+
case 'nano':
|
|
138
|
+
return this.checkCouchDBConnection(connection);
|
|
139
|
+
|
|
140
|
+
case 'cassandra':
|
|
141
|
+
return this.checkCassandraConnection(connection);
|
|
142
|
+
|
|
143
|
+
case 'elasticsearch':
|
|
144
|
+
case 'elastic':
|
|
145
|
+
return this.checkElasticsearchConnection(connection);
|
|
146
|
+
|
|
147
|
+
case 'dynamodb':
|
|
148
|
+
return this.checkDynamoDBConnection(connection);
|
|
149
|
+
|
|
150
|
+
case 'neo4j':
|
|
151
|
+
return this.checkNeo4jConnection(connection);
|
|
152
|
+
|
|
119
153
|
default:
|
|
120
154
|
throw new Error(`Unsupported database type: ${type}`);
|
|
121
155
|
}
|
|
@@ -160,16 +194,48 @@ class DbConnectionMonitor {
|
|
|
160
194
|
}
|
|
161
195
|
|
|
162
196
|
/**
|
|
163
|
-
* Check MySQL connection
|
|
197
|
+
* Check MySQL/MySQL2 connection
|
|
164
198
|
*/
|
|
165
199
|
async checkMySqlConnection(connection, testQuery = 'SELECT 1') {
|
|
166
200
|
try {
|
|
167
|
-
|
|
168
|
-
|
|
201
|
+
// Support for mysql2 pool or connection
|
|
202
|
+
if (connection.query) {
|
|
203
|
+
const result = await Promise.race([
|
|
204
|
+
connection.query(testQuery),
|
|
205
|
+
this.timeout(this.config.database.queryTimeout)
|
|
206
|
+
]);
|
|
207
|
+
// mysql2 returns [rows, fields]
|
|
208
|
+
return Array.isArray(result) ? !!result[0] : !!result;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Support for mysql2 promise pool
|
|
212
|
+
if (connection.execute) {
|
|
213
|
+
const [rows] = await Promise.race([
|
|
214
|
+
connection.execute(testQuery),
|
|
215
|
+
this.timeout(this.config.database.queryTimeout)
|
|
216
|
+
]);
|
|
217
|
+
return !!rows;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return false;
|
|
221
|
+
} catch (error) {
|
|
222
|
+
this.logger.logWarning('mysql_check_failed', `MySQL connection check failed: ${error.message}`);
|
|
223
|
+
return false;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Check Sequelize connection
|
|
229
|
+
*/
|
|
230
|
+
async checkSequelizeConnection(connection) {
|
|
231
|
+
try {
|
|
232
|
+
await Promise.race([
|
|
233
|
+
connection.authenticate(),
|
|
169
234
|
this.timeout(this.config.database.queryTimeout)
|
|
170
235
|
]);
|
|
171
|
-
return
|
|
172
|
-
} catch {
|
|
236
|
+
return true;
|
|
237
|
+
} catch (error) {
|
|
238
|
+
this.logger.logWarning('sequelize_check_failed', `Sequelize connection check failed: ${error.message}`);
|
|
173
239
|
return false;
|
|
174
240
|
}
|
|
175
241
|
}
|
|
@@ -189,6 +255,259 @@ class DbConnectionMonitor {
|
|
|
189
255
|
}
|
|
190
256
|
}
|
|
191
257
|
|
|
258
|
+
/**
|
|
259
|
+
* Check MariaDB connection
|
|
260
|
+
*/
|
|
261
|
+
async checkMariaDBConnection(connection, testQuery = 'SELECT 1') {
|
|
262
|
+
try {
|
|
263
|
+
// MariaDB uses same interface as MySQL
|
|
264
|
+
if (connection.query) {
|
|
265
|
+
const result = await Promise.race([
|
|
266
|
+
connection.query(testQuery),
|
|
267
|
+
this.timeout(this.config.database.queryTimeout)
|
|
268
|
+
]);
|
|
269
|
+
return Array.isArray(result) ? !!result[0] : !!result;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (connection.execute) {
|
|
273
|
+
const [rows] = await Promise.race([
|
|
274
|
+
connection.execute(testQuery),
|
|
275
|
+
this.timeout(this.config.database.queryTimeout)
|
|
276
|
+
]);
|
|
277
|
+
return !!rows;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return false;
|
|
281
|
+
} catch (error) {
|
|
282
|
+
this.logger.logWarning('mariadb_check_failed', `MariaDB connection check failed: ${error.message}`);
|
|
283
|
+
return false;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Check MSSQL connection
|
|
289
|
+
*/
|
|
290
|
+
async checkMSSQLConnection(connection, testQuery = 'SELECT 1') {
|
|
291
|
+
try {
|
|
292
|
+
// For mssql package (ConnectionPool)
|
|
293
|
+
if (connection.connected !== undefined) {
|
|
294
|
+
if (!connection.connected) {
|
|
295
|
+
return false;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Try to execute a query
|
|
300
|
+
if (connection.query) {
|
|
301
|
+
const result = await Promise.race([
|
|
302
|
+
connection.query(testQuery),
|
|
303
|
+
this.timeout(this.config.database.queryTimeout)
|
|
304
|
+
]);
|
|
305
|
+
return !!result;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// For tedious connection
|
|
309
|
+
if (connection.request) {
|
|
310
|
+
const request = connection.request();
|
|
311
|
+
const result = await Promise.race([
|
|
312
|
+
request.query(testQuery),
|
|
313
|
+
this.timeout(this.config.database.queryTimeout)
|
|
314
|
+
]);
|
|
315
|
+
return !!result;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
return false;
|
|
319
|
+
} catch (error) {
|
|
320
|
+
this.logger.logWarning('mssql_check_failed', `MSSQL connection check failed: ${error.message}`);
|
|
321
|
+
return false;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Check SQLite connection
|
|
327
|
+
*/
|
|
328
|
+
async checkSQLiteConnection(connection, testQuery = 'SELECT 1') {
|
|
329
|
+
try {
|
|
330
|
+
// For sqlite3 package
|
|
331
|
+
if (connection.get) {
|
|
332
|
+
return new Promise((resolve) => {
|
|
333
|
+
const timeout = setTimeout(() => resolve(false), this.config.database.queryTimeout);
|
|
334
|
+
|
|
335
|
+
connection.get(testQuery, (err) => {
|
|
336
|
+
clearTimeout(timeout);
|
|
337
|
+
resolve(!err);
|
|
338
|
+
});
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// For better-sqlite3 package
|
|
343
|
+
if (connection.prepare) {
|
|
344
|
+
const stmt = connection.prepare(testQuery);
|
|
345
|
+
const result = stmt.get();
|
|
346
|
+
return !!result || result === undefined;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
return false;
|
|
350
|
+
} catch (error) {
|
|
351
|
+
this.logger.logWarning('sqlite_check_failed', `SQLite connection check failed: ${error.message}`);
|
|
352
|
+
return false;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Check CouchDB connection
|
|
358
|
+
*/
|
|
359
|
+
async checkCouchDBConnection(connection) {
|
|
360
|
+
try {
|
|
361
|
+
// For nano package
|
|
362
|
+
if (connection.db && connection.db.list) {
|
|
363
|
+
const result = await Promise.race([
|
|
364
|
+
connection.db.list(),
|
|
365
|
+
this.timeout(this.config.database.queryTimeout)
|
|
366
|
+
]);
|
|
367
|
+
return !!result;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// For direct HTTP client
|
|
371
|
+
if (connection.info) {
|
|
372
|
+
const result = await Promise.race([
|
|
373
|
+
connection.info(),
|
|
374
|
+
this.timeout(this.config.database.queryTimeout)
|
|
375
|
+
]);
|
|
376
|
+
return !!result;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
return false;
|
|
380
|
+
} catch (error) {
|
|
381
|
+
this.logger.logWarning('couchdb_check_failed', `CouchDB connection check failed: ${error.message}`);
|
|
382
|
+
return false;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Check Cassandra connection
|
|
388
|
+
*/
|
|
389
|
+
async checkCassandraConnection(connection) {
|
|
390
|
+
try {
|
|
391
|
+
// For cassandra-driver package
|
|
392
|
+
if (connection.execute) {
|
|
393
|
+
const result = await Promise.race([
|
|
394
|
+
connection.execute('SELECT now() FROM system.local'),
|
|
395
|
+
this.timeout(this.config.database.queryTimeout)
|
|
396
|
+
]);
|
|
397
|
+
return !!result;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// Check connection state
|
|
401
|
+
if (connection.getState) {
|
|
402
|
+
const state = connection.getState();
|
|
403
|
+
return state && state.getConnectedHosts && state.getConnectedHosts().length > 0;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
return false;
|
|
407
|
+
} catch (error) {
|
|
408
|
+
this.logger.logWarning('cassandra_check_failed', `Cassandra connection check failed: ${error.message}`);
|
|
409
|
+
return false;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* Check Elasticsearch connection
|
|
415
|
+
*/
|
|
416
|
+
async checkElasticsearchConnection(connection) {
|
|
417
|
+
try {
|
|
418
|
+
// For @elastic/elasticsearch package
|
|
419
|
+
if (connection.ping) {
|
|
420
|
+
const result = await Promise.race([
|
|
421
|
+
connection.ping(),
|
|
422
|
+
this.timeout(this.config.database.queryTimeout)
|
|
423
|
+
]);
|
|
424
|
+
return !!result;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// Alternative: check cluster health
|
|
428
|
+
if (connection.cluster && connection.cluster.health) {
|
|
429
|
+
const result = await Promise.race([
|
|
430
|
+
connection.cluster.health(),
|
|
431
|
+
this.timeout(this.config.database.queryTimeout)
|
|
432
|
+
]);
|
|
433
|
+
return !!result;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
return false;
|
|
437
|
+
} catch (error) {
|
|
438
|
+
this.logger.logWarning('elasticsearch_check_failed', `Elasticsearch connection check failed: ${error.message}`);
|
|
439
|
+
return false;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* Check DynamoDB connection
|
|
445
|
+
*/
|
|
446
|
+
async checkDynamoDBConnection(connection) {
|
|
447
|
+
try {
|
|
448
|
+
// For AWS SDK DynamoDB client
|
|
449
|
+
if (connection.listTables) {
|
|
450
|
+
const result = await Promise.race([
|
|
451
|
+
connection.listTables({ Limit: 1 }).promise(),
|
|
452
|
+
this.timeout(this.config.database.queryTimeout)
|
|
453
|
+
]);
|
|
454
|
+
return !!result;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// For AWS SDK v3
|
|
458
|
+
if (connection.send) {
|
|
459
|
+
const { ListTablesCommand } = require('@aws-sdk/client-dynamodb');
|
|
460
|
+
const result = await Promise.race([
|
|
461
|
+
connection.send(new ListTablesCommand({ Limit: 1 })),
|
|
462
|
+
this.timeout(this.config.database.queryTimeout)
|
|
463
|
+
]);
|
|
464
|
+
return !!result;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
return false;
|
|
468
|
+
} catch (error) {
|
|
469
|
+
this.logger.logWarning('dynamodb_check_failed', `DynamoDB connection check failed: ${error.message}`);
|
|
470
|
+
return false;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* Check Neo4j connection
|
|
476
|
+
*/
|
|
477
|
+
async checkNeo4jConnection(connection) {
|
|
478
|
+
try {
|
|
479
|
+
// For neo4j-driver package
|
|
480
|
+
if (connection.verifyConnectivity) {
|
|
481
|
+
await Promise.race([
|
|
482
|
+
connection.verifyConnectivity(),
|
|
483
|
+
this.timeout(this.config.database.queryTimeout)
|
|
484
|
+
]);
|
|
485
|
+
return true;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// Alternative: run a simple query
|
|
489
|
+
if (connection.session) {
|
|
490
|
+
const session = connection.session();
|
|
491
|
+
try {
|
|
492
|
+
const result = await Promise.race([
|
|
493
|
+
session.run('RETURN 1'),
|
|
494
|
+
this.timeout(this.config.database.queryTimeout)
|
|
495
|
+
]);
|
|
496
|
+
await session.close();
|
|
497
|
+
return !!result;
|
|
498
|
+
} catch (error) {
|
|
499
|
+
await session.close();
|
|
500
|
+
throw error;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
return false;
|
|
505
|
+
} catch (error) {
|
|
506
|
+
this.logger.logWarning('neo4j_check_failed', `Neo4j connection check failed: ${error.message}`);
|
|
507
|
+
return false;
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
192
511
|
/**
|
|
193
512
|
* Handle connection status changes
|
|
194
513
|
*/
|
|
@@ -275,18 +594,51 @@ class DbConnectionMonitor {
|
|
|
275
594
|
case 'mongodb':
|
|
276
595
|
case 'mongoose':
|
|
277
596
|
return this.getMongoStats(connection);
|
|
278
|
-
|
|
597
|
+
|
|
279
598
|
case 'postgresql':
|
|
280
599
|
case 'postgres':
|
|
281
600
|
case 'pg':
|
|
282
601
|
return this.getPostgresStats(connection);
|
|
283
|
-
|
|
602
|
+
|
|
284
603
|
case 'mysql':
|
|
604
|
+
case 'mysql2':
|
|
285
605
|
return this.getMySqlStats(connection);
|
|
286
|
-
|
|
606
|
+
|
|
607
|
+
case 'mariadb':
|
|
608
|
+
return this.getMariaDBStats(connection);
|
|
609
|
+
|
|
610
|
+
case 'mssql':
|
|
611
|
+
case 'sqlserver':
|
|
612
|
+
case 'tedious':
|
|
613
|
+
return this.getMSSQLStats(connection);
|
|
614
|
+
|
|
615
|
+
case 'sqlite':
|
|
616
|
+
case 'sqlite3':
|
|
617
|
+
return this.getSQLiteStats(connection);
|
|
618
|
+
|
|
619
|
+
case 'sequelize':
|
|
620
|
+
return this.getSequelizeStats(connection);
|
|
621
|
+
|
|
287
622
|
case 'redis':
|
|
288
623
|
return this.getRedisStats(connection);
|
|
289
|
-
|
|
624
|
+
|
|
625
|
+
case 'couchdb':
|
|
626
|
+
case 'nano':
|
|
627
|
+
return this.getCouchDBStats(connection);
|
|
628
|
+
|
|
629
|
+
case 'cassandra':
|
|
630
|
+
return this.getCassandraStats(connection);
|
|
631
|
+
|
|
632
|
+
case 'elasticsearch':
|
|
633
|
+
case 'elastic':
|
|
634
|
+
return this.getElasticsearchStats(connection);
|
|
635
|
+
|
|
636
|
+
case 'dynamodb':
|
|
637
|
+
return this.getDynamoDBStats(connection);
|
|
638
|
+
|
|
639
|
+
case 'neo4j':
|
|
640
|
+
return this.getNeo4jStats(connection);
|
|
641
|
+
|
|
290
642
|
default:
|
|
291
643
|
return { type, status: 'unknown' };
|
|
292
644
|
}
|
|
@@ -333,21 +685,63 @@ class DbConnectionMonitor {
|
|
|
333
685
|
}
|
|
334
686
|
|
|
335
687
|
/**
|
|
336
|
-
* Get MySQL statistics
|
|
688
|
+
* Get MySQL/MySQL2 statistics
|
|
337
689
|
*/
|
|
338
690
|
async getMySqlStats(connection) {
|
|
339
691
|
try {
|
|
340
|
-
|
|
341
|
-
|
|
692
|
+
let rows;
|
|
693
|
+
|
|
694
|
+
// Try mysql2 format first
|
|
695
|
+
if (connection.query) {
|
|
696
|
+
const result = await connection.query('SHOW STATUS LIKE "Threads_connected"');
|
|
697
|
+
rows = Array.isArray(result) ? result[0] : result;
|
|
698
|
+
} else if (connection.execute) {
|
|
699
|
+
[rows] = await connection.execute('SHOW STATUS LIKE "Threads_connected"');
|
|
700
|
+
}
|
|
701
|
+
|
|
342
702
|
return {
|
|
343
703
|
type: 'mysql',
|
|
344
|
-
threadsConnected: rows[0]?.Value || 0
|
|
704
|
+
threadsConnected: rows?.[0]?.Value || 0
|
|
345
705
|
};
|
|
346
706
|
} catch (error) {
|
|
347
707
|
return { type: 'mysql', error: error.message };
|
|
348
708
|
}
|
|
349
709
|
}
|
|
350
710
|
|
|
711
|
+
/**
|
|
712
|
+
* Get Sequelize statistics
|
|
713
|
+
*/
|
|
714
|
+
async getSequelizeStats(connection) {
|
|
715
|
+
try {
|
|
716
|
+
// Get database dialect
|
|
717
|
+
const dialect = connection.getDialect();
|
|
718
|
+
|
|
719
|
+
// Get pool information if available
|
|
720
|
+
const poolInfo = connection.connectionManager?.pool
|
|
721
|
+
? {
|
|
722
|
+
size: connection.connectionManager.pool.size || 0,
|
|
723
|
+
available: connection.connectionManager.pool.available || 0,
|
|
724
|
+
using: connection.connectionManager.pool.using || 0,
|
|
725
|
+
waiting: connection.connectionManager.pool.waiting || 0
|
|
726
|
+
}
|
|
727
|
+
: null;
|
|
728
|
+
|
|
729
|
+
// Get basic connection info
|
|
730
|
+
const dbName = connection.config?.database || 'unknown';
|
|
731
|
+
const host = connection.config?.host || 'unknown';
|
|
732
|
+
|
|
733
|
+
return {
|
|
734
|
+
type: 'sequelize',
|
|
735
|
+
dialect,
|
|
736
|
+
database: dbName,
|
|
737
|
+
host,
|
|
738
|
+
pool: poolInfo
|
|
739
|
+
};
|
|
740
|
+
} catch (error) {
|
|
741
|
+
return { type: 'sequelize', error: error.message };
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
|
|
351
745
|
/**
|
|
352
746
|
* Get Redis statistics
|
|
353
747
|
*/
|
|
@@ -356,14 +750,14 @@ class DbConnectionMonitor {
|
|
|
356
750
|
const info = await connection.info();
|
|
357
751
|
const lines = info.split('\r\n');
|
|
358
752
|
const stats = {};
|
|
359
|
-
|
|
753
|
+
|
|
360
754
|
lines.forEach(line => {
|
|
361
755
|
const [key, value] = line.split(':');
|
|
362
756
|
if (key && value) {
|
|
363
757
|
stats[key] = value;
|
|
364
758
|
}
|
|
365
759
|
});
|
|
366
|
-
|
|
760
|
+
|
|
367
761
|
return {
|
|
368
762
|
type: 'redis',
|
|
369
763
|
connectedClients: stats.connected_clients,
|
|
@@ -375,11 +769,231 @@ class DbConnectionMonitor {
|
|
|
375
769
|
}
|
|
376
770
|
}
|
|
377
771
|
|
|
772
|
+
/**
|
|
773
|
+
* Get MariaDB statistics
|
|
774
|
+
*/
|
|
775
|
+
async getMariaDBStats(connection) {
|
|
776
|
+
try {
|
|
777
|
+
let rows;
|
|
778
|
+
|
|
779
|
+
if (connection.query) {
|
|
780
|
+
const result = await connection.query('SHOW STATUS LIKE "Threads_connected"');
|
|
781
|
+
rows = Array.isArray(result) ? result[0] : result;
|
|
782
|
+
} else if (connection.execute) {
|
|
783
|
+
[rows] = await connection.execute('SHOW STATUS LIKE "Threads_connected"');
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
return {
|
|
787
|
+
type: 'mariadb',
|
|
788
|
+
threadsConnected: rows?.[0]?.Value || 0
|
|
789
|
+
};
|
|
790
|
+
} catch (error) {
|
|
791
|
+
return { type: 'mariadb', error: error.message };
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
/**
|
|
796
|
+
* Get MSSQL statistics
|
|
797
|
+
*/
|
|
798
|
+
async getMSSQLStats(connection) {
|
|
799
|
+
try {
|
|
800
|
+
let result;
|
|
801
|
+
|
|
802
|
+
if (connection.query) {
|
|
803
|
+
result = await connection.query('SELECT @@VERSION as version, @@CONNECTIONS as connections');
|
|
804
|
+
} else if (connection.request) {
|
|
805
|
+
const request = connection.request();
|
|
806
|
+
result = await request.query('SELECT @@VERSION as version, @@CONNECTIONS as connections');
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
return {
|
|
810
|
+
type: 'mssql',
|
|
811
|
+
version: result?.recordset?.[0]?.version || 'unknown',
|
|
812
|
+
connections: result?.recordset?.[0]?.connections || 0,
|
|
813
|
+
connected: connection.connected || false
|
|
814
|
+
};
|
|
815
|
+
} catch (error) {
|
|
816
|
+
return { type: 'mssql', error: error.message };
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
/**
|
|
821
|
+
* Get SQLite statistics
|
|
822
|
+
*/
|
|
823
|
+
async getSQLiteStats(connection) {
|
|
824
|
+
try {
|
|
825
|
+
let stats = { type: 'sqlite' };
|
|
826
|
+
|
|
827
|
+
// For sqlite3
|
|
828
|
+
if (connection.get) {
|
|
829
|
+
return new Promise((resolve) => {
|
|
830
|
+
connection.get('PRAGMA database_list', (err, row) => {
|
|
831
|
+
if (!err && row) {
|
|
832
|
+
stats.database = row.file || row.name;
|
|
833
|
+
}
|
|
834
|
+
resolve(stats);
|
|
835
|
+
});
|
|
836
|
+
});
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
// For better-sqlite3
|
|
840
|
+
if (connection.name) {
|
|
841
|
+
stats.database = connection.name;
|
|
842
|
+
stats.inTransaction = connection.inTransaction || false;
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
return stats;
|
|
846
|
+
} catch (error) {
|
|
847
|
+
return { type: 'sqlite', error: error.message };
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
/**
|
|
852
|
+
* Get CouchDB statistics
|
|
853
|
+
*/
|
|
854
|
+
async getCouchDBStats(connection) {
|
|
855
|
+
try {
|
|
856
|
+
let info;
|
|
857
|
+
|
|
858
|
+
if (connection.db && connection.db.list) {
|
|
859
|
+
const databases = await connection.db.list();
|
|
860
|
+
info = { type: 'couchdb', databases: databases.length };
|
|
861
|
+
} else if (connection.info) {
|
|
862
|
+
const serverInfo = await connection.info();
|
|
863
|
+
info = {
|
|
864
|
+
type: 'couchdb',
|
|
865
|
+
version: serverInfo.version,
|
|
866
|
+
vendor: serverInfo.vendor?.name
|
|
867
|
+
};
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
return info || { type: 'couchdb' };
|
|
871
|
+
} catch (error) {
|
|
872
|
+
return { type: 'couchdb', error: error.message };
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
/**
|
|
877
|
+
* Get Cassandra statistics
|
|
878
|
+
*/
|
|
879
|
+
async getCassandraStats(connection) {
|
|
880
|
+
try {
|
|
881
|
+
const stats = { type: 'cassandra' };
|
|
882
|
+
|
|
883
|
+
if (connection.getState) {
|
|
884
|
+
const state = connection.getState();
|
|
885
|
+
const hosts = state.getConnectedHosts();
|
|
886
|
+
stats.connectedHosts = hosts.length;
|
|
887
|
+
stats.hosts = hosts.map(h => h.address);
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
if (connection.metadata) {
|
|
891
|
+
stats.keyspaces = Object.keys(connection.metadata.keyspaces || {}).length;
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
return stats;
|
|
895
|
+
} catch (error) {
|
|
896
|
+
return { type: 'cassandra', error: error.message };
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
/**
|
|
901
|
+
* Get Elasticsearch statistics
|
|
902
|
+
*/
|
|
903
|
+
async getElasticsearchStats(connection) {
|
|
904
|
+
try {
|
|
905
|
+
const stats = { type: 'elasticsearch' };
|
|
906
|
+
|
|
907
|
+
// Get cluster health
|
|
908
|
+
if (connection.cluster && connection.cluster.health) {
|
|
909
|
+
const health = await connection.cluster.health();
|
|
910
|
+
stats.clusterName = health.cluster_name;
|
|
911
|
+
stats.status = health.status;
|
|
912
|
+
stats.numberOfNodes = health.number_of_nodes;
|
|
913
|
+
stats.activeShards = health.active_shards;
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
// Get cluster info
|
|
917
|
+
if (connection.info) {
|
|
918
|
+
const info = await connection.info();
|
|
919
|
+
stats.version = info.version?.number;
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
return stats;
|
|
923
|
+
} catch (error) {
|
|
924
|
+
return { type: 'elasticsearch', error: error.message };
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
/**
|
|
929
|
+
* Get DynamoDB statistics
|
|
930
|
+
*/
|
|
931
|
+
async getDynamoDBStats(connection) {
|
|
932
|
+
try {
|
|
933
|
+
const stats = { type: 'dynamodb' };
|
|
934
|
+
|
|
935
|
+
// For AWS SDK v2
|
|
936
|
+
if (connection.listTables) {
|
|
937
|
+
const result = await connection.listTables().promise();
|
|
938
|
+
stats.tableCount = result.TableNames?.length || 0;
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
// For AWS SDK v3
|
|
942
|
+
if (connection.send) {
|
|
943
|
+
const { ListTablesCommand } = require('@aws-sdk/client-dynamodb');
|
|
944
|
+
const result = await connection.send(new ListTablesCommand({}));
|
|
945
|
+
stats.tableCount = result.TableNames?.length || 0;
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
return stats;
|
|
949
|
+
} catch (error) {
|
|
950
|
+
return { type: 'dynamodb', error: error.message };
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
/**
|
|
955
|
+
* Get Neo4j statistics
|
|
956
|
+
*/
|
|
957
|
+
async getNeo4jStats(connection) {
|
|
958
|
+
try {
|
|
959
|
+
const stats = { type: 'neo4j' };
|
|
960
|
+
|
|
961
|
+
// Get server info
|
|
962
|
+
if (connection.getServerInfo) {
|
|
963
|
+
const serverInfo = await connection.getServerInfo();
|
|
964
|
+
stats.version = serverInfo.version;
|
|
965
|
+
stats.address = serverInfo.address;
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
// Run a query to get database stats
|
|
969
|
+
if (connection.session) {
|
|
970
|
+
const session = connection.session();
|
|
971
|
+
try {
|
|
972
|
+
const result = await session.run('CALL dbms.components() YIELD name, versions, edition');
|
|
973
|
+
const record = result.records[0];
|
|
974
|
+
if (record) {
|
|
975
|
+
stats.name = record.get('name');
|
|
976
|
+
stats.versions = record.get('versions');
|
|
977
|
+
stats.edition = record.get('edition');
|
|
978
|
+
}
|
|
979
|
+
await session.close();
|
|
980
|
+
} catch (error) {
|
|
981
|
+
await session.close();
|
|
982
|
+
stats.queryError = error.message;
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
return stats;
|
|
987
|
+
} catch (error) {
|
|
988
|
+
return { type: 'neo4j', error: error.message };
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
|
|
378
992
|
/**
|
|
379
993
|
* Timeout helper
|
|
380
994
|
*/
|
|
381
995
|
timeout(ms) {
|
|
382
|
-
return new Promise((_, reject) =>
|
|
996
|
+
return new Promise((_, reject) =>
|
|
383
997
|
setTimeout(() => reject(new Error('Query timeout')), ms)
|
|
384
998
|
);
|
|
385
999
|
}
|