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.
- package/dist/index.js +1122 -9
- package/dist/utils/batch-performance-monitor.js +106 -0
- package/dist/utils/batch-test-scenarios.js +277 -0
- package/dist/utils/context-aware-search.js +499 -0
- package/dist/utils/cross-reference-detector.js +352 -0
- package/dist/utils/document-workflow.js +433 -0
- package/dist/utils/enhanced-fuzzy-search.js +514 -0
- package/dist/utils/error-handler.js +337 -0
- package/dist/utils/intelligence-engine.js +71 -0
- package/dist/utils/intelligent-cache.js +379 -0
- package/dist/utils/large-mailbox-search.js +599 -0
- package/dist/utils/ms365-operations.js +730 -208
- package/dist/utils/performance-monitor.js +395 -0
- package/dist/utils/proactive-intelligence.js +390 -0
- package/dist/utils/rate-limiter.js +284 -0
- package/dist/utils/search-batch-pipeline.js +222 -0
- package/dist/utils/thread-reconstruction.js +700 -0
- package/package.json +1 -1
|
@@ -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();
|