ms365-mcp-server 1.1.16 → 1.1.18

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,395 @@
1
+ import { logger } from './api.js';
2
+ export class PerformanceMonitor {
3
+ constructor() {
4
+ this.metrics = new Map();
5
+ this.timers = new Map();
6
+ this.requestCounts = new Map();
7
+ this.startTime = Date.now();
8
+ // Performance thresholds
9
+ this.thresholds = {
10
+ responseTime: {
11
+ good: 1000, // < 1s
12
+ warning: 5000, // < 5s
13
+ critical: 15000 // < 15s
14
+ },
15
+ errorRate: {
16
+ good: 0.01, // < 1%
17
+ warning: 0.05, // < 5%
18
+ critical: 0.15 // < 15%
19
+ },
20
+ memoryUsage: {
21
+ good: 0.7, // < 70%
22
+ warning: 0.85, // < 85%
23
+ critical: 0.95 // < 95%
24
+ }
25
+ };
26
+ // Start collecting system metrics every minute
27
+ setInterval(() => this.collectSystemMetrics(), 60000);
28
+ // Clean old metrics every 5 minutes
29
+ setInterval(() => this.cleanOldMetrics(), 5 * 60000);
30
+ }
31
+ static getInstance() {
32
+ if (!this.instance) {
33
+ this.instance = new PerformanceMonitor();
34
+ }
35
+ return this.instance;
36
+ }
37
+ /**
38
+ * Record a metric
39
+ */
40
+ recordMetric(name, value, type = 'gauge', tags = {}) {
41
+ const metric = {
42
+ name,
43
+ value,
44
+ timestamp: Date.now(),
45
+ tags,
46
+ type
47
+ };
48
+ if (!this.metrics.has(name)) {
49
+ this.metrics.set(name, []);
50
+ }
51
+ this.metrics.get(name).push(metric);
52
+ // Keep only last 1000 entries per metric
53
+ const entries = this.metrics.get(name);
54
+ if (entries.length > 1000) {
55
+ entries.splice(0, entries.length - 1000);
56
+ }
57
+ }
58
+ /**
59
+ * Start a timer
60
+ */
61
+ startTimer(name) {
62
+ this.timers.set(name, Date.now());
63
+ }
64
+ /**
65
+ * Stop a timer and record the duration
66
+ */
67
+ stopTimer(name, success = true, error) {
68
+ const startTime = this.timers.get(name);
69
+ if (!startTime) {
70
+ throw new Error(`Timer ${name} was not started`);
71
+ }
72
+ const duration = Date.now() - startTime;
73
+ this.timers.delete(name);
74
+ // Record timer metric
75
+ this.recordMetric(name, duration, 'timer', {
76
+ success: success.toString(),
77
+ error: error || ''
78
+ });
79
+ // Log slow operations
80
+ if (duration > this.thresholds.responseTime.warning) {
81
+ logger.log(`⚠️ Slow operation detected: ${name} took ${duration}ms`);
82
+ }
83
+ return { name, duration, success, error };
84
+ }
85
+ /**
86
+ * Record request count
87
+ */
88
+ recordRequest(endpoint, method = 'unknown') {
89
+ const key = `${method}:${endpoint}`;
90
+ this.requestCounts.set(key, (this.requestCounts.get(key) || 0) + 1);
91
+ this.recordMetric('requests', 1, 'counter', { endpoint, method });
92
+ }
93
+ /**
94
+ * Record error
95
+ */
96
+ recordError(operation, error, severity = 'medium') {
97
+ this.recordMetric('errors', 1, 'counter', {
98
+ operation,
99
+ error: error.substring(0, 100), // Truncate long errors
100
+ severity
101
+ });
102
+ if (severity === 'high') {
103
+ logger.error(`🚨 High severity error in ${operation}: ${error}`);
104
+ }
105
+ }
106
+ /**
107
+ * Record cache hit/miss
108
+ */
109
+ recordCacheHit(cacheType, hit) {
110
+ this.recordMetric('cache_operations', 1, 'counter', {
111
+ type: cacheType,
112
+ result: hit ? 'hit' : 'miss'
113
+ });
114
+ }
115
+ /**
116
+ * Record API call metrics
117
+ */
118
+ recordApiCall(endpoint, duration, success, statusCode) {
119
+ this.recordMetric('api_calls', 1, 'counter', {
120
+ endpoint,
121
+ success: success.toString(),
122
+ status_code: statusCode?.toString() || ''
123
+ });
124
+ this.recordMetric('api_call_duration', duration, 'timer', {
125
+ endpoint,
126
+ success: success.toString()
127
+ });
128
+ }
129
+ /**
130
+ * Get current performance statistics
131
+ */
132
+ getStats() {
133
+ const now = Date.now();
134
+ const oneMinuteAgo = now - 60000;
135
+ const oneHourAgo = now - 3600000;
136
+ // Calculate requests per minute
137
+ const recentRequests = this.getMetricsSince('requests', oneMinuteAgo);
138
+ const requestsPerMinute = recentRequests.length;
139
+ // Calculate average response time
140
+ const recentTimers = this.getMetricsSince('api_call_duration', oneHourAgo);
141
+ const avgResponseTime = recentTimers.length > 0
142
+ ? recentTimers.reduce((sum, m) => sum + m.value, 0) / recentTimers.length
143
+ : 0;
144
+ // Calculate error rate
145
+ const recentRequests1h = this.getMetricsSince('requests', oneHourAgo);
146
+ const recentErrors = this.getMetricsSince('errors', oneHourAgo);
147
+ const errorRate = recentRequests1h.length > 0
148
+ ? recentErrors.length / recentRequests1h.length
149
+ : 0;
150
+ // Calculate cache hit rate
151
+ const recentCacheOps = this.getMetricsSince('cache_operations', oneHourAgo);
152
+ const cacheHits = recentCacheOps.filter(m => m.tags.result === 'hit').length;
153
+ const cacheHitRate = recentCacheOps.length > 0
154
+ ? cacheHits / recentCacheOps.length
155
+ : 0;
156
+ // Get memory usage
157
+ const memoryUsage = this.getMemoryUsage();
158
+ // API call statistics
159
+ const recentApiCalls = this.getMetricsSince('api_calls', oneHourAgo);
160
+ const successfulApiCalls = recentApiCalls.filter(m => m.tags.success === 'true').length;
161
+ const avgApiDuration = recentTimers.length > 0
162
+ ? recentTimers.reduce((sum, m) => sum + m.value, 0) / recentTimers.length
163
+ : 0;
164
+ // Calculate health score (0-100)
165
+ const healthScore = this.calculateHealthScore(avgResponseTime, errorRate, memoryUsage.percentage, cacheHitRate);
166
+ return {
167
+ requestsPerMinute,
168
+ averageResponseTime: avgResponseTime,
169
+ errorRate: errorRate * 100, // Convert to percentage
170
+ cacheHitRate: cacheHitRate * 100, // Convert to percentage
171
+ memoryUsage,
172
+ apiCallStats: {
173
+ total: recentApiCalls.length,
174
+ successful: successfulApiCalls,
175
+ failed: recentApiCalls.length - successfulApiCalls,
176
+ avgDuration: avgApiDuration
177
+ },
178
+ healthScore
179
+ };
180
+ }
181
+ /**
182
+ * Get detailed metrics for a specific operation
183
+ */
184
+ getOperationMetrics(operation) {
185
+ const oneHourAgo = Date.now() - 3600000;
186
+ const timerMetrics = this.getMetricsSince(operation, oneHourAgo);
187
+ const errorMetrics = this.getMetricsSince('errors', oneHourAgo)
188
+ .filter(m => m.tags.operation === operation);
189
+ const totalCalls = timerMetrics.length;
190
+ const successfulCalls = timerMetrics.filter(m => m.tags.success === 'true').length;
191
+ const successRate = totalCalls > 0 ? successfulCalls / totalCalls : 0;
192
+ const averageDuration = totalCalls > 0
193
+ ? timerMetrics.reduce((sum, m) => sum + m.value, 0) / totalCalls
194
+ : 0;
195
+ // Error breakdown
196
+ const errorBreakdown = {};
197
+ errorMetrics.forEach(metric => {
198
+ const errorType = metric.tags.error || 'unknown';
199
+ errorBreakdown[errorType] = (errorBreakdown[errorType] || 0) + 1;
200
+ });
201
+ // Recent performance (last 50 calls)
202
+ const recentPerformance = timerMetrics
203
+ .slice(-50)
204
+ .map(m => ({
205
+ timestamp: m.timestamp,
206
+ duration: m.value,
207
+ success: m.tags.success === 'true'
208
+ }));
209
+ return {
210
+ totalCalls,
211
+ successRate: successRate * 100,
212
+ averageDuration,
213
+ errorBreakdown,
214
+ recentPerformance
215
+ };
216
+ }
217
+ /**
218
+ * Get health status
219
+ */
220
+ getHealthStatus() {
221
+ const stats = this.getStats();
222
+ const issues = [];
223
+ const recommendations = [];
224
+ let status = 'healthy';
225
+ // Check response time
226
+ if (stats.averageResponseTime > this.thresholds.responseTime.critical) {
227
+ status = 'critical';
228
+ issues.push(`Very slow response time: ${stats.averageResponseTime.toFixed(0)}ms`);
229
+ recommendations.push('Consider optimizing queries and using more aggressive caching');
230
+ }
231
+ else if (stats.averageResponseTime > this.thresholds.responseTime.warning) {
232
+ status = 'warning';
233
+ issues.push(`Slow response time: ${stats.averageResponseTime.toFixed(0)}ms`);
234
+ recommendations.push('Monitor query performance and consider optimization');
235
+ }
236
+ // Check error rate
237
+ if (stats.errorRate > this.thresholds.errorRate.critical * 100) {
238
+ status = 'critical';
239
+ issues.push(`High error rate: ${stats.errorRate.toFixed(1)}%`);
240
+ recommendations.push('Investigate errors and implement additional error handling');
241
+ }
242
+ else if (stats.errorRate > this.thresholds.errorRate.warning * 100 && status !== 'critical') {
243
+ status = 'warning';
244
+ issues.push(`Elevated error rate: ${stats.errorRate.toFixed(1)}%`);
245
+ recommendations.push('Monitor error patterns and improve error handling');
246
+ }
247
+ // Check memory usage
248
+ if (stats.memoryUsage.percentage > this.thresholds.memoryUsage.critical) {
249
+ status = 'critical';
250
+ issues.push(`Critical memory usage: ${(stats.memoryUsage.percentage * 100).toFixed(1)}%`);
251
+ recommendations.push('Implement memory optimization and consider restarting the server');
252
+ }
253
+ else if (stats.memoryUsage.percentage > this.thresholds.memoryUsage.warning && status !== 'critical') {
254
+ status = 'warning';
255
+ issues.push(`High memory usage: ${(stats.memoryUsage.percentage * 100).toFixed(1)}%`);
256
+ recommendations.push('Monitor memory usage and clear caches if needed');
257
+ }
258
+ // Check cache hit rate
259
+ if (stats.cacheHitRate < 50 && status === 'healthy') {
260
+ status = 'warning';
261
+ issues.push(`Low cache hit rate: ${stats.cacheHitRate.toFixed(1)}%`);
262
+ recommendations.push('Review caching strategy and TTL settings');
263
+ }
264
+ const uptime = Date.now() - this.startTime;
265
+ return {
266
+ status,
267
+ issues,
268
+ recommendations,
269
+ uptime
270
+ };
271
+ }
272
+ /**
273
+ * Generate performance report
274
+ */
275
+ generateReport() {
276
+ const stats = this.getStats();
277
+ const health = this.getHealthStatus();
278
+ const uptimeHours = (health.uptime / (1000 * 60 * 60)).toFixed(1);
279
+ let report = `📊 Performance Report\n\n`;
280
+ report += `🔋 Health Status: ${health.status.toUpperCase()}\n`;
281
+ report += `⏱️ Uptime: ${uptimeHours} hours\n`;
282
+ report += `🎯 Health Score: ${stats.healthScore}/100\n\n`;
283
+ report += `📈 Key Metrics:\n`;
284
+ report += `• Requests/min: ${stats.requestsPerMinute}\n`;
285
+ report += `• Avg Response Time: ${stats.averageResponseTime.toFixed(0)}ms\n`;
286
+ report += `• Error Rate: ${stats.errorRate.toFixed(2)}%\n`;
287
+ report += `• Cache Hit Rate: ${stats.cacheHitRate.toFixed(1)}%\n`;
288
+ report += `• Memory Usage: ${(stats.memoryUsage.percentage * 100).toFixed(1)}%\n\n`;
289
+ report += `🌐 API Calls (last hour):\n`;
290
+ report += `• Total: ${stats.apiCallStats.total}\n`;
291
+ report += `• Successful: ${stats.apiCallStats.successful}\n`;
292
+ report += `• Failed: ${stats.apiCallStats.failed}\n`;
293
+ report += `• Avg Duration: ${stats.apiCallStats.avgDuration.toFixed(0)}ms\n\n`;
294
+ if (health.issues.length > 0) {
295
+ report += `⚠️ Issues:\n${health.issues.map(issue => `• ${issue}`).join('\n')}\n\n`;
296
+ }
297
+ if (health.recommendations.length > 0) {
298
+ report += `💡 Recommendations:\n${health.recommendations.map(rec => `• ${rec}`).join('\n')}\n`;
299
+ }
300
+ return report;
301
+ }
302
+ /**
303
+ * Get metrics since a specific timestamp
304
+ */
305
+ getMetricsSince(name, since) {
306
+ const metrics = this.metrics.get(name) || [];
307
+ return metrics.filter(m => m.timestamp >= since);
308
+ }
309
+ /**
310
+ * Get current memory usage
311
+ */
312
+ getMemoryUsage() {
313
+ const memUsage = process.memoryUsage();
314
+ const used = memUsage.heapUsed;
315
+ const total = memUsage.heapTotal;
316
+ const percentage = total > 0 ? used / total : 0;
317
+ return { used, total, percentage };
318
+ }
319
+ /**
320
+ * Calculate overall health score
321
+ */
322
+ calculateHealthScore(responseTime, errorRate, memoryUsage, cacheHitRate) {
323
+ let score = 100;
324
+ // Response time impact (0-30 points)
325
+ if (responseTime > this.thresholds.responseTime.critical) {
326
+ score -= 30;
327
+ }
328
+ else if (responseTime > this.thresholds.responseTime.warning) {
329
+ score -= 15;
330
+ }
331
+ else if (responseTime > this.thresholds.responseTime.good) {
332
+ score -= 5;
333
+ }
334
+ // Error rate impact (0-25 points)
335
+ if (errorRate > this.thresholds.errorRate.critical) {
336
+ score -= 25;
337
+ }
338
+ else if (errorRate > this.thresholds.errorRate.warning) {
339
+ score -= 15;
340
+ }
341
+ else if (errorRate > this.thresholds.errorRate.good) {
342
+ score -= 5;
343
+ }
344
+ // Memory usage impact (0-25 points)
345
+ if (memoryUsage > this.thresholds.memoryUsage.critical) {
346
+ score -= 25;
347
+ }
348
+ else if (memoryUsage > this.thresholds.memoryUsage.warning) {
349
+ score -= 15;
350
+ }
351
+ else if (memoryUsage > this.thresholds.memoryUsage.good) {
352
+ score -= 5;
353
+ }
354
+ // Cache hit rate bonus/penalty (0-20 points)
355
+ if (cacheHitRate < 0.3) {
356
+ score -= 20;
357
+ }
358
+ else if (cacheHitRate < 0.5) {
359
+ score -= 10;
360
+ }
361
+ else if (cacheHitRate > 0.8) {
362
+ score += 5; // Bonus for good cache performance
363
+ }
364
+ return Math.max(0, Math.min(100, score));
365
+ }
366
+ /**
367
+ * Collect system metrics
368
+ */
369
+ collectSystemMetrics() {
370
+ const memUsage = this.getMemoryUsage();
371
+ this.recordMetric('memory_usage', memUsage.percentage, 'gauge');
372
+ this.recordMetric('memory_used', memUsage.used, 'gauge');
373
+ this.recordMetric('memory_total', memUsage.total, 'gauge');
374
+ // Record uptime
375
+ const uptime = Date.now() - this.startTime;
376
+ this.recordMetric('uptime', uptime, 'gauge');
377
+ }
378
+ /**
379
+ * Clean old metrics to prevent memory leaks
380
+ */
381
+ cleanOldMetrics() {
382
+ const oneHourAgo = Date.now() - 3600000;
383
+ let cleaned = 0;
384
+ for (const [name, metrics] of this.metrics) {
385
+ const filteredMetrics = metrics.filter(m => m.timestamp >= oneHourAgo);
386
+ this.metrics.set(name, filteredMetrics);
387
+ cleaned += metrics.length - filteredMetrics.length;
388
+ }
389
+ if (cleaned > 0) {
390
+ logger.log(`🧹 Cleaned ${cleaned} old performance metrics`);
391
+ }
392
+ }
393
+ }
394
+ // Export singleton instance
395
+ export const performanceMonitor = PerformanceMonitor.getInstance();