@berthojoris/mcp-mysql-server 1.9.3 → 1.10.1

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.
@@ -0,0 +1,623 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.PerformanceTools = void 0;
7
+ const connection_1 = __importDefault(require("../db/connection"));
8
+ class PerformanceTools {
9
+ constructor(security) {
10
+ this.db = connection_1.default.getInstance();
11
+ this.security = security;
12
+ }
13
+ /**
14
+ * Get comprehensive performance metrics
15
+ */
16
+ async getPerformanceMetrics() {
17
+ try {
18
+ const metrics = {};
19
+ // Get query performance metrics
20
+ const perfQuery = `
21
+ SELECT
22
+ SUM(TIMER_WAIT) / 1000000000000 as total_execution_time_sec,
23
+ SUM(LOCK_TIME) / 1000000000000 as total_lock_time_sec,
24
+ SUM(ROWS_EXAMINED) as total_rows_examined,
25
+ SUM(ROWS_SENT) as total_rows_sent,
26
+ SUM(ROWS_AFFECTED) as total_rows_affected,
27
+ SUM(SELECT_FULL_JOIN) as full_table_scans,
28
+ SUM(NO_INDEX_USED) as queries_without_indexes,
29
+ SUM(NO_GOOD_INDEX_USED) as queries_with_bad_indexes,
30
+ COUNT(*) as total_queries
31
+ FROM performance_schema.events_statements_summary_global_by_event_name
32
+ WHERE EVENT_NAME LIKE 'statement/sql/%'
33
+ `;
34
+ const perfResult = await this.db.query(perfQuery);
35
+ metrics.query_performance = perfResult[0] || {};
36
+ // Get connection metrics
37
+ const connQuery = `
38
+ SHOW GLOBAL STATUS WHERE Variable_name IN (
39
+ 'Threads_connected', 'Threads_running', 'Threads_created',
40
+ 'Connections', 'Max_used_connections', 'Aborted_connects', 'Aborted_clients'
41
+ )
42
+ `;
43
+ const connResult = await this.db.query(connQuery);
44
+ metrics.connections = {};
45
+ for (const row of connResult) {
46
+ metrics.connections[row.Variable_name.toLowerCase()] = row.Value;
47
+ }
48
+ // Get table cache metrics
49
+ const cacheQuery = `
50
+ SHOW GLOBAL STATUS WHERE Variable_name IN (
51
+ 'Open_tables', 'Opened_tables', 'Table_open_cache_hits',
52
+ 'Table_open_cache_misses', 'Table_open_cache_overflows'
53
+ )
54
+ `;
55
+ const cacheResult = await this.db.query(cacheQuery);
56
+ metrics.table_cache = {};
57
+ for (const row of cacheResult) {
58
+ metrics.table_cache[row.Variable_name.toLowerCase()] = row.Value;
59
+ }
60
+ // Get InnoDB metrics
61
+ const innodbQuery = `
62
+ SHOW GLOBAL STATUS WHERE Variable_name LIKE 'Innodb_%'
63
+ AND Variable_name IN (
64
+ 'Innodb_buffer_pool_reads', 'Innodb_buffer_pool_read_requests',
65
+ 'Innodb_buffer_pool_pages_total', 'Innodb_buffer_pool_pages_free',
66
+ 'Innodb_buffer_pool_pages_data', 'Innodb_buffer_pool_pages_dirty',
67
+ 'Innodb_row_lock_waits', 'Innodb_row_lock_time', 'Innodb_rows_read',
68
+ 'Innodb_rows_inserted', 'Innodb_rows_updated', 'Innodb_rows_deleted'
69
+ )
70
+ `;
71
+ const innodbResult = await this.db.query(innodbQuery);
72
+ metrics.innodb = {};
73
+ for (const row of innodbResult) {
74
+ metrics.innodb[row.Variable_name.toLowerCase()] = row.Value;
75
+ }
76
+ // Calculate buffer pool hit ratio
77
+ if (metrics.innodb.innodb_buffer_pool_read_requests && metrics.innodb.innodb_buffer_pool_reads) {
78
+ const requests = parseFloat(metrics.innodb.innodb_buffer_pool_read_requests);
79
+ const reads = parseFloat(metrics.innodb.innodb_buffer_pool_reads);
80
+ metrics.innodb.buffer_pool_hit_ratio = ((requests - reads) / requests * 100).toFixed(2) + '%';
81
+ }
82
+ // Get slow query metrics
83
+ const slowQuery = `
84
+ SHOW GLOBAL STATUS WHERE Variable_name IN ('Slow_queries', 'Questions')
85
+ `;
86
+ const slowResult = await this.db.query(slowQuery);
87
+ metrics.slow_queries = {};
88
+ for (const row of slowResult) {
89
+ metrics.slow_queries[row.Variable_name.toLowerCase()] = row.Value;
90
+ }
91
+ // Calculate slow query percentage
92
+ if (metrics.slow_queries.questions && metrics.slow_queries.slow_queries) {
93
+ const total = parseFloat(metrics.slow_queries.questions);
94
+ const slow = parseFloat(metrics.slow_queries.slow_queries);
95
+ metrics.slow_queries.slow_query_percentage = ((slow / total) * 100).toFixed(4) + '%';
96
+ }
97
+ return {
98
+ status: 'success',
99
+ data: metrics,
100
+ queryLog: this.db.getFormattedQueryLogs(5)
101
+ };
102
+ }
103
+ catch (error) {
104
+ return {
105
+ status: 'error',
106
+ error: error.message,
107
+ queryLog: this.db.getFormattedQueryLogs(1)
108
+ };
109
+ }
110
+ }
111
+ /**
112
+ * Get top queries by execution time
113
+ */
114
+ async getTopQueriesByTime(params) {
115
+ try {
116
+ const limit = params?.limit || 10;
117
+ if (!Number.isInteger(limit) || limit <= 0 || limit > 100) {
118
+ return { status: 'error', error: 'Limit must be a positive integer between 1 and 100' };
119
+ }
120
+ const query = `
121
+ SELECT
122
+ DIGEST_TEXT as query_pattern,
123
+ COUNT_STAR as execution_count,
124
+ ROUND(AVG_TIMER_WAIT / 1000000000000, 6) as avg_execution_time_sec,
125
+ ROUND(MAX_TIMER_WAIT / 1000000000000, 6) as max_execution_time_sec,
126
+ ROUND(SUM_TIMER_WAIT / 1000000000000, 6) as total_execution_time_sec,
127
+ ROUND(SUM_LOCK_TIME / 1000000000000, 6) as total_lock_time_sec,
128
+ SUM_ROWS_EXAMINED as rows_examined,
129
+ SUM_ROWS_SENT as rows_sent,
130
+ SUM_ROWS_AFFECTED as rows_affected,
131
+ FIRST_SEEN,
132
+ LAST_SEEN
133
+ FROM performance_schema.events_statements_summary_by_digest
134
+ WHERE DIGEST_TEXT IS NOT NULL
135
+ ORDER BY SUM_TIMER_WAIT DESC
136
+ LIMIT ${limit}
137
+ `;
138
+ const results = await this.db.query(query);
139
+ return {
140
+ status: 'success',
141
+ data: results,
142
+ queryLog: this.db.getFormattedQueryLogs(1)
143
+ };
144
+ }
145
+ catch (error) {
146
+ return {
147
+ status: 'error',
148
+ error: error.message,
149
+ queryLog: this.db.getFormattedQueryLogs(1)
150
+ };
151
+ }
152
+ }
153
+ /**
154
+ * Get top queries by execution count
155
+ */
156
+ async getTopQueriesByCount(params) {
157
+ try {
158
+ const limit = params?.limit || 10;
159
+ if (!Number.isInteger(limit) || limit <= 0 || limit > 100) {
160
+ return { status: 'error', error: 'Limit must be a positive integer between 1 and 100' };
161
+ }
162
+ const query = `
163
+ SELECT
164
+ DIGEST_TEXT as query_pattern,
165
+ COUNT_STAR as execution_count,
166
+ ROUND(AVG_TIMER_WAIT / 1000000000000, 6) as avg_execution_time_sec,
167
+ ROUND(MAX_TIMER_WAIT / 1000000000000, 6) as max_execution_time_sec,
168
+ ROUND(SUM_TIMER_WAIT / 1000000000000, 6) as total_execution_time_sec,
169
+ SUM_ROWS_EXAMINED as rows_examined,
170
+ SUM_ROWS_SENT as rows_sent,
171
+ FIRST_SEEN,
172
+ LAST_SEEN
173
+ FROM performance_schema.events_statements_summary_by_digest
174
+ WHERE DIGEST_TEXT IS NOT NULL
175
+ ORDER BY COUNT_STAR DESC
176
+ LIMIT ${limit}
177
+ `;
178
+ const results = await this.db.query(query);
179
+ return {
180
+ status: 'success',
181
+ data: results,
182
+ queryLog: this.db.getFormattedQueryLogs(1)
183
+ };
184
+ }
185
+ catch (error) {
186
+ return {
187
+ status: 'error',
188
+ error: error.message,
189
+ queryLog: this.db.getFormattedQueryLogs(1)
190
+ };
191
+ }
192
+ }
193
+ /**
194
+ * Get slow queries
195
+ */
196
+ async getSlowQueries(params) {
197
+ try {
198
+ const limit = params?.limit || 10;
199
+ const thresholdSec = params?.threshold_seconds || 1;
200
+ if (!Number.isInteger(limit) || limit <= 0 || limit > 100) {
201
+ return { status: 'error', error: 'Limit must be a positive integer between 1 and 100' };
202
+ }
203
+ if (typeof thresholdSec !== 'number' || thresholdSec <= 0) {
204
+ return { status: 'error', error: 'Threshold must be a positive number' };
205
+ }
206
+ const thresholdPico = thresholdSec * 1000000000000;
207
+ const query = `
208
+ SELECT
209
+ DIGEST_TEXT as query_pattern,
210
+ COUNT_STAR as execution_count,
211
+ ROUND(AVG_TIMER_WAIT / 1000000000000, 6) as avg_execution_time_sec,
212
+ ROUND(MAX_TIMER_WAIT / 1000000000000, 6) as max_execution_time_sec,
213
+ ROUND(SUM_TIMER_WAIT / 1000000000000, 6) as total_execution_time_sec,
214
+ ROUND(SUM_LOCK_TIME / 1000000000000, 6) as total_lock_time_sec,
215
+ SUM_ROWS_EXAMINED as rows_examined,
216
+ SUM_ROWS_SENT as rows_sent,
217
+ SUM_NO_INDEX_USED as no_index_used_count,
218
+ FIRST_SEEN,
219
+ LAST_SEEN
220
+ FROM performance_schema.events_statements_summary_by_digest
221
+ WHERE DIGEST_TEXT IS NOT NULL AND AVG_TIMER_WAIT > ${thresholdPico}
222
+ ORDER BY AVG_TIMER_WAIT DESC
223
+ LIMIT ${limit}
224
+ `;
225
+ const results = await this.db.query(query);
226
+ return {
227
+ status: 'success',
228
+ data: results,
229
+ queryLog: this.db.getFormattedQueryLogs(1)
230
+ };
231
+ }
232
+ catch (error) {
233
+ return {
234
+ status: 'error',
235
+ error: error.message,
236
+ queryLog: this.db.getFormattedQueryLogs(1)
237
+ };
238
+ }
239
+ }
240
+ /**
241
+ * Get table I/O statistics
242
+ */
243
+ async getTableIOStats(params) {
244
+ try {
245
+ const limit = params?.limit || 20;
246
+ const schema = params?.table_schema;
247
+ if (!Number.isInteger(limit) || limit <= 0 || limit > 100) {
248
+ return { status: 'error', error: 'Limit must be a positive integer between 1 and 100' };
249
+ }
250
+ let query = `
251
+ SELECT
252
+ OBJECT_SCHEMA as table_schema,
253
+ OBJECT_NAME as table_name,
254
+ COUNT_READ as read_operations,
255
+ COUNT_WRITE as write_operations,
256
+ COUNT_FETCH as fetch_operations,
257
+ COUNT_INSERT as insert_operations,
258
+ COUNT_UPDATE as update_operations,
259
+ COUNT_DELETE as delete_operations,
260
+ ROUND(SUM_TIMER_READ / 1000000000000, 6) as total_read_time_sec,
261
+ ROUND(SUM_TIMER_WRITE / 1000000000000, 6) as total_write_time_sec,
262
+ ROUND(SUM_TIMER_FETCH / 1000000000000, 6) as total_fetch_time_sec
263
+ FROM performance_schema.table_io_waits_summary_by_table
264
+ WHERE OBJECT_SCHEMA NOT IN ('mysql', 'performance_schema', 'information_schema', 'sys')
265
+ `;
266
+ if (schema) {
267
+ query += ` AND OBJECT_SCHEMA = '${schema.replace(/'/g, "''")}'`;
268
+ }
269
+ query += ` ORDER BY (COUNT_READ + COUNT_WRITE) DESC LIMIT ${limit}`;
270
+ const results = await this.db.query(query);
271
+ return {
272
+ status: 'success',
273
+ data: results,
274
+ queryLog: this.db.getFormattedQueryLogs(1)
275
+ };
276
+ }
277
+ catch (error) {
278
+ return {
279
+ status: 'error',
280
+ error: error.message,
281
+ queryLog: this.db.getFormattedQueryLogs(1)
282
+ };
283
+ }
284
+ }
285
+ /**
286
+ * Get index usage statistics
287
+ */
288
+ async getIndexUsageStats(params) {
289
+ try {
290
+ const limit = params?.limit || 20;
291
+ const schema = params?.table_schema;
292
+ if (!Number.isInteger(limit) || limit <= 0 || limit > 100) {
293
+ return { status: 'error', error: 'Limit must be a positive integer between 1 and 100' };
294
+ }
295
+ let query = `
296
+ SELECT
297
+ OBJECT_SCHEMA as table_schema,
298
+ OBJECT_NAME as table_name,
299
+ INDEX_NAME as index_name,
300
+ COUNT_STAR as usage_count,
301
+ COUNT_READ as read_count,
302
+ COUNT_WRITE as write_count,
303
+ COUNT_FETCH as fetch_count,
304
+ COUNT_INSERT as insert_count,
305
+ COUNT_UPDATE as update_count,
306
+ COUNT_DELETE as delete_count
307
+ FROM performance_schema.table_io_waits_summary_by_index_usage
308
+ WHERE OBJECT_SCHEMA NOT IN ('mysql', 'performance_schema', 'information_schema', 'sys')
309
+ AND INDEX_NAME IS NOT NULL
310
+ `;
311
+ if (schema) {
312
+ query += ` AND OBJECT_SCHEMA = '${schema.replace(/'/g, "''")}'`;
313
+ }
314
+ query += ` ORDER BY COUNT_STAR DESC LIMIT ${limit}`;
315
+ const results = await this.db.query(query);
316
+ return {
317
+ status: 'success',
318
+ data: results,
319
+ queryLog: this.db.getFormattedQueryLogs(1)
320
+ };
321
+ }
322
+ catch (error) {
323
+ return {
324
+ status: 'error',
325
+ error: error.message,
326
+ queryLog: this.db.getFormattedQueryLogs(1)
327
+ };
328
+ }
329
+ }
330
+ /**
331
+ * Get unused indexes
332
+ */
333
+ async getUnusedIndexes(params) {
334
+ try {
335
+ const schema = params?.table_schema;
336
+ let query = `
337
+ SELECT
338
+ t.TABLE_SCHEMA as table_schema,
339
+ t.TABLE_NAME as table_name,
340
+ s.INDEX_NAME as index_name,
341
+ s.COLUMN_NAME as column_name,
342
+ s.SEQ_IN_INDEX as sequence_in_index,
343
+ s.NON_UNIQUE as is_non_unique
344
+ FROM information_schema.STATISTICS s
345
+ LEFT JOIN performance_schema.table_io_waits_summary_by_index_usage p
346
+ ON s.TABLE_SCHEMA = p.OBJECT_SCHEMA
347
+ AND s.TABLE_NAME = p.OBJECT_NAME
348
+ AND s.INDEX_NAME = p.INDEX_NAME
349
+ JOIN information_schema.TABLES t
350
+ ON s.TABLE_SCHEMA = t.TABLE_SCHEMA
351
+ AND s.TABLE_NAME = t.TABLE_NAME
352
+ WHERE s.TABLE_SCHEMA NOT IN ('mysql', 'performance_schema', 'information_schema', 'sys')
353
+ AND s.INDEX_NAME != 'PRIMARY'
354
+ AND (p.INDEX_NAME IS NULL OR p.COUNT_STAR = 0)
355
+ AND t.TABLE_TYPE = 'BASE TABLE'
356
+ `;
357
+ if (schema) {
358
+ query += ` AND s.TABLE_SCHEMA = '${schema.replace(/'/g, "''")}'`;
359
+ }
360
+ query += ` ORDER BY s.TABLE_SCHEMA, s.TABLE_NAME, s.INDEX_NAME, s.SEQ_IN_INDEX`;
361
+ const results = await this.db.query(query);
362
+ return {
363
+ status: 'success',
364
+ data: results,
365
+ queryLog: this.db.getFormattedQueryLogs(1)
366
+ };
367
+ }
368
+ catch (error) {
369
+ return {
370
+ status: 'error',
371
+ error: error.message,
372
+ queryLog: this.db.getFormattedQueryLogs(1)
373
+ };
374
+ }
375
+ }
376
+ /**
377
+ * Get connection pool statistics
378
+ */
379
+ async getConnectionPoolStats() {
380
+ try {
381
+ const statusQuery = `
382
+ SHOW GLOBAL STATUS WHERE Variable_name IN (
383
+ 'Threads_connected', 'Threads_running', 'Threads_created', 'Threads_cached',
384
+ 'Connections', 'Max_used_connections', 'Max_used_connections_time',
385
+ 'Aborted_connects', 'Aborted_clients', 'Connection_errors_max_connections'
386
+ )
387
+ `;
388
+ const variablesQuery = `
389
+ SHOW GLOBAL VARIABLES WHERE Variable_name IN (
390
+ 'max_connections', 'thread_cache_size', 'connect_timeout',
391
+ 'wait_timeout', 'interactive_timeout'
392
+ )
393
+ `;
394
+ const statusResult = await this.db.query(statusQuery);
395
+ const variablesResult = await this.db.query(variablesQuery);
396
+ const stats = {
397
+ current_status: {},
398
+ configuration: {},
399
+ health_indicators: {}
400
+ };
401
+ for (const row of statusResult) {
402
+ stats.current_status[row.Variable_name.toLowerCase()] = row.Value;
403
+ }
404
+ for (const row of variablesResult) {
405
+ stats.configuration[row.Variable_name.toLowerCase()] = row.Value;
406
+ }
407
+ // Calculate health indicators
408
+ const threadsConnected = parseInt(stats.current_status.threads_connected || '0');
409
+ const maxConnections = parseInt(stats.configuration.max_connections || '0');
410
+ const maxUsedConnections = parseInt(stats.current_status.max_used_connections || '0');
411
+ if (maxConnections > 0) {
412
+ stats.health_indicators.connection_usage_percentage = ((threadsConnected / maxConnections) * 100).toFixed(2) + '%';
413
+ stats.health_indicators.max_usage_percentage = ((maxUsedConnections / maxConnections) * 100).toFixed(2) + '%';
414
+ stats.health_indicators.available_connections = maxConnections - threadsConnected;
415
+ }
416
+ // Connection efficiency
417
+ const totalConnections = parseInt(stats.current_status.connections || '0');
418
+ const abortedConnects = parseInt(stats.current_status.aborted_connects || '0');
419
+ if (totalConnections > 0) {
420
+ stats.health_indicators.aborted_connection_percentage = ((abortedConnects / totalConnections) * 100).toFixed(4) + '%';
421
+ }
422
+ // Thread cache efficiency
423
+ const threadsCreated = parseInt(stats.current_status.threads_created || '0');
424
+ if (totalConnections > 0) {
425
+ stats.health_indicators.thread_cache_hit_rate = (((totalConnections - threadsCreated) / totalConnections) * 100).toFixed(2) + '%';
426
+ }
427
+ return {
428
+ status: 'success',
429
+ data: stats,
430
+ queryLog: this.db.getFormattedQueryLogs(2)
431
+ };
432
+ }
433
+ catch (error) {
434
+ return {
435
+ status: 'error',
436
+ error: error.message,
437
+ queryLog: this.db.getFormattedQueryLogs(1)
438
+ };
439
+ }
440
+ }
441
+ /**
442
+ * Get database health check
443
+ */
444
+ async getDatabaseHealthCheck() {
445
+ try {
446
+ const health = {
447
+ overall_status: 'healthy',
448
+ checks: [],
449
+ warnings: [],
450
+ errors: []
451
+ };
452
+ // Check 1: Connection usage
453
+ const connQuery = `
454
+ SHOW GLOBAL STATUS WHERE Variable_name IN ('Threads_connected', 'Max_used_connections')
455
+ `;
456
+ const maxConnQuery = `SHOW GLOBAL VARIABLES WHERE Variable_name = 'max_connections'`;
457
+ const connResult = await this.db.query(connQuery);
458
+ const maxConnResult = await this.db.query(maxConnQuery);
459
+ let threadsConnected = 0;
460
+ let maxUsedConnections = 0;
461
+ let maxConnections = 0;
462
+ for (const row of connResult) {
463
+ if (row.Variable_name === 'Threads_connected')
464
+ threadsConnected = parseInt(row.Value);
465
+ if (row.Variable_name === 'Max_used_connections')
466
+ maxUsedConnections = parseInt(row.Value);
467
+ }
468
+ for (const row of maxConnResult) {
469
+ maxConnections = parseInt(row.Value);
470
+ }
471
+ const connUsage = (threadsConnected / maxConnections) * 100;
472
+ const maxConnUsage = (maxUsedConnections / maxConnections) * 100;
473
+ health.checks.push({
474
+ name: 'Connection Usage',
475
+ status: connUsage < 80 ? 'healthy' : connUsage < 90 ? 'warning' : 'critical',
476
+ current: threadsConnected,
477
+ max: maxConnections,
478
+ usage_percentage: connUsage.toFixed(2) + '%'
479
+ });
480
+ if (connUsage >= 90) {
481
+ health.errors.push('Connection usage is critically high (>90%)');
482
+ health.overall_status = 'critical';
483
+ }
484
+ else if (connUsage >= 80) {
485
+ health.warnings.push('Connection usage is high (>80%)');
486
+ if (health.overall_status === 'healthy')
487
+ health.overall_status = 'warning';
488
+ }
489
+ // Check 2: Buffer pool hit ratio
490
+ const bufferQuery = `
491
+ SHOW GLOBAL STATUS WHERE Variable_name IN ('Innodb_buffer_pool_read_requests', 'Innodb_buffer_pool_reads')
492
+ `;
493
+ const bufferResult = await this.db.query(bufferQuery);
494
+ let readRequests = 0;
495
+ let reads = 0;
496
+ for (const row of bufferResult) {
497
+ if (row.Variable_name === 'Innodb_buffer_pool_read_requests')
498
+ readRequests = parseInt(row.Value);
499
+ if (row.Variable_name === 'Innodb_buffer_pool_reads')
500
+ reads = parseInt(row.Value);
501
+ }
502
+ const hitRatio = readRequests > 0 ? ((readRequests - reads) / readRequests) * 100 : 100;
503
+ health.checks.push({
504
+ name: 'Buffer Pool Hit Ratio',
505
+ status: hitRatio > 95 ? 'healthy' : hitRatio > 85 ? 'warning' : 'critical',
506
+ hit_ratio: hitRatio.toFixed(2) + '%'
507
+ });
508
+ if (hitRatio <= 85) {
509
+ health.errors.push('Buffer pool hit ratio is too low (<85%)');
510
+ health.overall_status = 'critical';
511
+ }
512
+ else if (hitRatio <= 95) {
513
+ health.warnings.push('Buffer pool hit ratio could be improved (<95%)');
514
+ if (health.overall_status === 'healthy')
515
+ health.overall_status = 'warning';
516
+ }
517
+ // Check 3: Aborted connections
518
+ const abortQuery = `
519
+ SHOW GLOBAL STATUS WHERE Variable_name IN ('Connections', 'Aborted_connects')
520
+ `;
521
+ const abortResult = await this.db.query(abortQuery);
522
+ let totalConnections = 0;
523
+ let abortedConnects = 0;
524
+ for (const row of abortResult) {
525
+ if (row.Variable_name === 'Connections')
526
+ totalConnections = parseInt(row.Value);
527
+ if (row.Variable_name === 'Aborted_connects')
528
+ abortedConnects = parseInt(row.Value);
529
+ }
530
+ const abortRate = totalConnections > 0 ? (abortedConnects / totalConnections) * 100 : 0;
531
+ health.checks.push({
532
+ name: 'Aborted Connections',
533
+ status: abortRate < 1 ? 'healthy' : abortRate < 5 ? 'warning' : 'critical',
534
+ aborted: abortedConnects,
535
+ total: totalConnections,
536
+ abort_rate: abortRate.toFixed(4) + '%'
537
+ });
538
+ if (abortRate >= 5) {
539
+ health.errors.push('High rate of aborted connections (>5%)');
540
+ if (health.overall_status !== 'critical')
541
+ health.overall_status = 'warning';
542
+ }
543
+ else if (abortRate >= 1) {
544
+ health.warnings.push('Elevated rate of aborted connections (>1%)');
545
+ if (health.overall_status === 'healthy')
546
+ health.overall_status = 'warning';
547
+ }
548
+ // Check 4: Slow queries
549
+ const slowQuery = `
550
+ SHOW GLOBAL STATUS WHERE Variable_name IN ('Questions', 'Slow_queries')
551
+ `;
552
+ const slowResult = await this.db.query(slowQuery);
553
+ let questions = 0;
554
+ let slowQueries = 0;
555
+ for (const row of slowResult) {
556
+ if (row.Variable_name === 'Questions')
557
+ questions = parseInt(row.Value);
558
+ if (row.Variable_name === 'Slow_queries')
559
+ slowQueries = parseInt(row.Value);
560
+ }
561
+ const slowQueryRate = questions > 0 ? (slowQueries / questions) * 100 : 0;
562
+ health.checks.push({
563
+ name: 'Slow Queries',
564
+ status: slowQueryRate < 1 ? 'healthy' : slowQueryRate < 5 ? 'warning' : 'critical',
565
+ slow_queries: slowQueries,
566
+ total_queries: questions,
567
+ slow_query_rate: slowQueryRate.toFixed(4) + '%'
568
+ });
569
+ if (slowQueryRate >= 5) {
570
+ health.warnings.push('High rate of slow queries (>5%)');
571
+ if (health.overall_status === 'healthy')
572
+ health.overall_status = 'warning';
573
+ }
574
+ else if (slowQueryRate >= 1) {
575
+ health.warnings.push('Elevated rate of slow queries (>1%)');
576
+ if (health.overall_status === 'healthy')
577
+ health.overall_status = 'warning';
578
+ }
579
+ return {
580
+ status: 'success',
581
+ data: health,
582
+ queryLog: this.db.getFormattedQueryLogs(6)
583
+ };
584
+ }
585
+ catch (error) {
586
+ return {
587
+ status: 'error',
588
+ error: error.message,
589
+ queryLog: this.db.getFormattedQueryLogs(1)
590
+ };
591
+ }
592
+ }
593
+ /**
594
+ * Reset performance schema statistics
595
+ */
596
+ async resetPerformanceStats() {
597
+ try {
598
+ // Truncate performance schema summary tables
599
+ const tables = [
600
+ 'events_statements_summary_by_digest',
601
+ 'events_statements_summary_global_by_event_name',
602
+ 'table_io_waits_summary_by_table',
603
+ 'table_io_waits_summary_by_index_usage'
604
+ ];
605
+ for (const table of tables) {
606
+ await this.db.query(`TRUNCATE TABLE performance_schema.${table}`);
607
+ }
608
+ return {
609
+ status: 'success',
610
+ message: 'Performance statistics reset successfully',
611
+ queryLog: this.db.getFormattedQueryLogs(tables.length)
612
+ };
613
+ }
614
+ catch (error) {
615
+ return {
616
+ status: 'error',
617
+ error: error.message,
618
+ queryLog: this.db.getFormattedQueryLogs(1)
619
+ };
620
+ }
621
+ }
622
+ }
623
+ exports.PerformanceTools = PerformanceTools;