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,106 @@
|
|
|
1
|
+
import { logger } from './api.js';
|
|
2
|
+
export class BatchPerformanceMonitor {
|
|
3
|
+
constructor() {
|
|
4
|
+
this.metrics = [];
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Start tracking a performance operation
|
|
8
|
+
*/
|
|
9
|
+
startOperation(operationType, requestCount, method) {
|
|
10
|
+
const operationId = `${operationType}_${method}_${Date.now()}`;
|
|
11
|
+
const startTime = Date.now();
|
|
12
|
+
logger.log(`š PERFORMANCE START: ${operationType} (${method}) - ${requestCount} requests`);
|
|
13
|
+
return operationId;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* End tracking a performance operation
|
|
17
|
+
*/
|
|
18
|
+
endOperation(operationId, operationType, requestCount, method, startTime, httpCalls, success = true, errorCount = 0) {
|
|
19
|
+
const endTime = Date.now();
|
|
20
|
+
const duration = endTime - startTime;
|
|
21
|
+
const averagePerRequest = duration / requestCount;
|
|
22
|
+
const metrics = {
|
|
23
|
+
operationType,
|
|
24
|
+
requestCount,
|
|
25
|
+
startTime,
|
|
26
|
+
endTime,
|
|
27
|
+
duration,
|
|
28
|
+
averagePerRequest,
|
|
29
|
+
success,
|
|
30
|
+
errorCount,
|
|
31
|
+
httpCalls,
|
|
32
|
+
method
|
|
33
|
+
};
|
|
34
|
+
this.metrics.push(metrics);
|
|
35
|
+
logger.log(`š PERFORMANCE END: ${operationType} (${method})`);
|
|
36
|
+
logger.log(` Duration: ${duration}ms`);
|
|
37
|
+
logger.log(` HTTP Calls: ${httpCalls}`);
|
|
38
|
+
logger.log(` Avg per request: ${averagePerRequest.toFixed(2)}ms`);
|
|
39
|
+
logger.log(` Success: ${success} (${errorCount} errors)`);
|
|
40
|
+
return metrics;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Compare traditional vs batched performance
|
|
44
|
+
*/
|
|
45
|
+
comparePerformance(operationType) {
|
|
46
|
+
const traditionalMetrics = this.metrics.find(m => m.operationType === operationType && m.method === 'traditional');
|
|
47
|
+
const batchedMetrics = this.metrics.find(m => m.operationType === operationType && m.method === 'batched');
|
|
48
|
+
if (!traditionalMetrics || !batchedMetrics) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
const durationReduction = ((traditionalMetrics.duration - batchedMetrics.duration) / traditionalMetrics.duration) * 100;
|
|
52
|
+
const httpCallReduction = ((traditionalMetrics.httpCalls - batchedMetrics.httpCalls) / traditionalMetrics.httpCalls) * 100;
|
|
53
|
+
const efficiencyGain = (traditionalMetrics.averagePerRequest - batchedMetrics.averagePerRequest) / traditionalMetrics.averagePerRequest * 100;
|
|
54
|
+
const result = {
|
|
55
|
+
traditional: traditionalMetrics,
|
|
56
|
+
batched: batchedMetrics,
|
|
57
|
+
improvement: {
|
|
58
|
+
durationReduction,
|
|
59
|
+
httpCallReduction,
|
|
60
|
+
efficiencyGain
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
logger.log(`š PERFORMANCE COMPARISON: ${operationType}`);
|
|
64
|
+
logger.log(` Duration: ${traditionalMetrics.duration}ms ā ${batchedMetrics.duration}ms (${durationReduction.toFixed(1)}% faster)`);
|
|
65
|
+
logger.log(` HTTP Calls: ${traditionalMetrics.httpCalls} ā ${batchedMetrics.httpCalls} (${httpCallReduction.toFixed(1)}% reduction)`);
|
|
66
|
+
logger.log(` Efficiency: ${efficiencyGain.toFixed(1)}% improvement per request`);
|
|
67
|
+
return result;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Get all metrics for a specific operation type
|
|
71
|
+
*/
|
|
72
|
+
getMetrics(operationType) {
|
|
73
|
+
if (operationType) {
|
|
74
|
+
return this.metrics.filter(m => m.operationType === operationType);
|
|
75
|
+
}
|
|
76
|
+
return [...this.metrics];
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Generate performance report
|
|
80
|
+
*/
|
|
81
|
+
generateReport() {
|
|
82
|
+
const operationTypes = [...new Set(this.metrics.map(m => m.operationType))];
|
|
83
|
+
let report = `š BATCH PERFORMANCE REPORT\n\n`;
|
|
84
|
+
operationTypes.forEach(opType => {
|
|
85
|
+
const comparison = this.comparePerformance(opType);
|
|
86
|
+
if (comparison) {
|
|
87
|
+
report += `š ${opType.toUpperCase()}\n`;
|
|
88
|
+
report += ` Traditional: ${comparison.traditional.duration}ms (${comparison.traditional.httpCalls} HTTP calls)\n`;
|
|
89
|
+
report += ` Batched: ${comparison.batched.duration}ms (${comparison.batched.httpCalls} HTTP calls)\n`;
|
|
90
|
+
report += ` š” Improvements:\n`;
|
|
91
|
+
report += ` - ${comparison.improvement.durationReduction.toFixed(1)}% faster execution\n`;
|
|
92
|
+
report += ` - ${comparison.improvement.httpCallReduction.toFixed(1)}% fewer HTTP calls\n`;
|
|
93
|
+
report += ` - ${comparison.improvement.efficiencyGain.toFixed(1)}% better efficiency\n\n`;
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
return report;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Clear all metrics
|
|
100
|
+
*/
|
|
101
|
+
clear() {
|
|
102
|
+
this.metrics = [];
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
// Singleton instance
|
|
106
|
+
export const performanceMonitor = new BatchPerformanceMonitor();
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import { performanceMonitor } from './batch-performance-monitor.js';
|
|
2
|
+
import { logger } from './api.js';
|
|
3
|
+
export class BatchTestRunner {
|
|
4
|
+
constructor(ms365Ops) {
|
|
5
|
+
this.ms365Ops = ms365Ops;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Test Scenario 1: Bulk Email Retrieval
|
|
9
|
+
*/
|
|
10
|
+
async testBulkEmailRetrieval(messageIds) {
|
|
11
|
+
logger.log(`š§Ŗ TEST SCENARIO 1: Bulk Email Retrieval (${messageIds.length} emails)`);
|
|
12
|
+
// Traditional approach - sequential calls
|
|
13
|
+
const traditionalStart = Date.now();
|
|
14
|
+
const traditionalEmails = [];
|
|
15
|
+
for (const messageId of messageIds) {
|
|
16
|
+
try {
|
|
17
|
+
const email = await this.ms365Ops.getEmail(messageId, true);
|
|
18
|
+
traditionalEmails.push(email);
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
logger.log(`ā Traditional: Failed to get email ${messageId}: ${error}`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
const traditionalMetrics = performanceMonitor.endOperation('bulk_email_retrieval', 'bulk_email_retrieval', messageIds.length, 'traditional', traditionalStart, messageIds.length * 2, // email + attachments call per email
|
|
25
|
+
true);
|
|
26
|
+
// Wait a moment to avoid rate limiting
|
|
27
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
28
|
+
// Batched approach - native JSON batching
|
|
29
|
+
const batchedStart = Date.now();
|
|
30
|
+
const batchedEmails = await this.ms365Ops.getEmailsBatch(messageIds, true);
|
|
31
|
+
const batchedMetrics = performanceMonitor.endOperation('bulk_email_retrieval', 'bulk_email_retrieval', messageIds.length, 'batched', batchedStart, Math.ceil(messageIds.length / 20), // 20 requests per batch
|
|
32
|
+
true);
|
|
33
|
+
const comparison = performanceMonitor.comparePerformance('bulk_email_retrieval');
|
|
34
|
+
return {
|
|
35
|
+
traditional: { emails: traditionalEmails, metrics: traditionalMetrics },
|
|
36
|
+
batched: { emails: batchedEmails, metrics: batchedMetrics },
|
|
37
|
+
comparison
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Test Scenario 2: Bulk Email Operations (Mark/Move/Delete)
|
|
42
|
+
*/
|
|
43
|
+
async testBulkEmailOperations(messageIds) {
|
|
44
|
+
logger.log(`š§Ŗ TEST SCENARIO 2: Bulk Email Operations (${messageIds.length} operations)`);
|
|
45
|
+
// Create operations for testing
|
|
46
|
+
const operations = messageIds.map((id, index) => ({
|
|
47
|
+
id: `op_${index}`,
|
|
48
|
+
operation: 'mark',
|
|
49
|
+
messageId: id,
|
|
50
|
+
params: { isRead: true }
|
|
51
|
+
}));
|
|
52
|
+
// Traditional approach - sequential operations
|
|
53
|
+
const traditionalStart = Date.now();
|
|
54
|
+
const traditionalResults = [];
|
|
55
|
+
for (const op of operations) {
|
|
56
|
+
try {
|
|
57
|
+
await this.ms365Ops.markEmail(op.messageId, op.params.isRead);
|
|
58
|
+
traditionalResults.push({ id: op.id, success: true });
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
logger.log(`ā Traditional: Failed to mark email ${op.messageId}: ${error}`);
|
|
62
|
+
traditionalResults.push({ id: op.id, success: false, error: String(error) });
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
const traditionalMetrics = performanceMonitor.endOperation('bulk_email_operations', 'bulk_email_operations', operations.length, 'traditional', traditionalStart, operations.length, // one call per operation
|
|
66
|
+
true);
|
|
67
|
+
// Wait a moment to avoid rate limiting
|
|
68
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
69
|
+
// Batched approach - native JSON batching
|
|
70
|
+
const batchedStart = Date.now();
|
|
71
|
+
const batchedResults = await this.ms365Ops.batchEmailOperations(operations);
|
|
72
|
+
const batchedMetrics = performanceMonitor.endOperation('bulk_email_operations', 'bulk_email_operations', operations.length, 'batched', batchedStart, Math.ceil(operations.length / 20), // 20 operations per batch
|
|
73
|
+
true);
|
|
74
|
+
const comparison = performanceMonitor.comparePerformance('bulk_email_operations');
|
|
75
|
+
return {
|
|
76
|
+
traditional: { results: traditionalResults, metrics: traditionalMetrics },
|
|
77
|
+
batched: { results: Array.from(batchedResults.entries()), metrics: batchedMetrics },
|
|
78
|
+
comparison
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Test Scenario 3: Folder + Email Parallel Fetching
|
|
83
|
+
*/
|
|
84
|
+
async testFolderWithEmails(folderId, emailCount = 10) {
|
|
85
|
+
logger.log(`š§Ŗ TEST SCENARIO 3: Folder + Emails Fetching (${emailCount} emails)`);
|
|
86
|
+
// Traditional approach - sequential calls
|
|
87
|
+
const traditionalStart = Date.now();
|
|
88
|
+
// Get folder info first - using existing folder methods
|
|
89
|
+
const folders = await this.ms365Ops.listFolders();
|
|
90
|
+
const folderInfo = folders.find(f => f.id === folderId || f.displayName.toLowerCase() === folderId.toLowerCase());
|
|
91
|
+
// Then get emails
|
|
92
|
+
const emailList = await this.ms365Ops.listEmails(folderId, emailCount);
|
|
93
|
+
const traditionalResult = {
|
|
94
|
+
folder: folderInfo,
|
|
95
|
+
emails: emailList.messages
|
|
96
|
+
};
|
|
97
|
+
const traditionalMetrics = performanceMonitor.endOperation('folder_with_emails', 'folder_with_emails', emailCount + 1, // folder + emails
|
|
98
|
+
'traditional', traditionalStart, 2, // folder call + emails call
|
|
99
|
+
true);
|
|
100
|
+
// Wait a moment to avoid rate limiting
|
|
101
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
102
|
+
// Batched approach - parallel fetching
|
|
103
|
+
const batchedStart = Date.now();
|
|
104
|
+
const batchedResult = await this.ms365Ops.getFolderWithRecentEmails(folderId, emailCount);
|
|
105
|
+
const batchedMetrics = performanceMonitor.endOperation('folder_with_emails', 'folder_with_emails', emailCount + 1, 'batched', batchedStart, 1, // single batch call
|
|
106
|
+
true);
|
|
107
|
+
const comparison = performanceMonitor.comparePerformance('folder_with_emails');
|
|
108
|
+
return {
|
|
109
|
+
traditional: { result: traditionalResult, metrics: traditionalMetrics },
|
|
110
|
+
batched: { result: batchedResult, metrics: batchedMetrics },
|
|
111
|
+
comparison
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Test Scenario 4: Mixed Operations (Real-world workflow)
|
|
116
|
+
*/
|
|
117
|
+
async testMixedOperations(messageIds) {
|
|
118
|
+
logger.log(`š§Ŗ TEST SCENARIO 4: Mixed Operations Workflow (${messageIds.length} emails)`);
|
|
119
|
+
// Take first few emails for different operations
|
|
120
|
+
const emailsToMark = messageIds.slice(0, Math.ceil(messageIds.length / 3));
|
|
121
|
+
const emailsToMove = messageIds.slice(Math.ceil(messageIds.length / 3), Math.ceil(messageIds.length * 2 / 3));
|
|
122
|
+
const emailsToDelete = messageIds.slice(Math.ceil(messageIds.length * 2 / 3));
|
|
123
|
+
// Traditional approach
|
|
124
|
+
const traditionalStart = Date.now();
|
|
125
|
+
const traditionalResults = {
|
|
126
|
+
marked: 0,
|
|
127
|
+
moved: 0,
|
|
128
|
+
deleted: 0,
|
|
129
|
+
errors: 0
|
|
130
|
+
};
|
|
131
|
+
// Mark emails as read
|
|
132
|
+
for (const id of emailsToMark) {
|
|
133
|
+
try {
|
|
134
|
+
await this.ms365Ops.markEmail(id, true);
|
|
135
|
+
traditionalResults.marked++;
|
|
136
|
+
}
|
|
137
|
+
catch (error) {
|
|
138
|
+
traditionalResults.errors++;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
// Move emails to archive (simulated)
|
|
142
|
+
for (const id of emailsToMove) {
|
|
143
|
+
try {
|
|
144
|
+
await this.ms365Ops.moveEmail(id, 'archive');
|
|
145
|
+
traditionalResults.moved++;
|
|
146
|
+
}
|
|
147
|
+
catch (error) {
|
|
148
|
+
traditionalResults.errors++;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
// Delete emails (simulated - we won't actually delete for testing)
|
|
152
|
+
for (const id of emailsToDelete) {
|
|
153
|
+
try {
|
|
154
|
+
// Simulate delete operation with mark as read instead
|
|
155
|
+
await this.ms365Ops.markEmail(id, true);
|
|
156
|
+
traditionalResults.deleted++;
|
|
157
|
+
}
|
|
158
|
+
catch (error) {
|
|
159
|
+
traditionalResults.errors++;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
const traditionalMetrics = performanceMonitor.endOperation('mixed_operations', 'mixed_operations', messageIds.length, 'traditional', traditionalStart, messageIds.length, // one call per operation
|
|
163
|
+
true);
|
|
164
|
+
// Wait a moment to avoid rate limiting
|
|
165
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
166
|
+
// Batched approach
|
|
167
|
+
const batchedStart = Date.now();
|
|
168
|
+
const mixedOperations = [
|
|
169
|
+
...emailsToMark.map((id, index) => ({
|
|
170
|
+
id: `mark_${index}`,
|
|
171
|
+
operation: 'mark',
|
|
172
|
+
messageId: id,
|
|
173
|
+
params: { isRead: true }
|
|
174
|
+
})),
|
|
175
|
+
...emailsToMove.map((id, index) => ({
|
|
176
|
+
id: `move_${index}`,
|
|
177
|
+
operation: 'move',
|
|
178
|
+
messageId: id,
|
|
179
|
+
params: { destinationFolderId: 'archive' }
|
|
180
|
+
})),
|
|
181
|
+
// Simulate delete with mark for testing
|
|
182
|
+
...emailsToDelete.map((id, index) => ({
|
|
183
|
+
id: `delete_${index}`,
|
|
184
|
+
operation: 'mark',
|
|
185
|
+
messageId: id,
|
|
186
|
+
params: { isRead: true }
|
|
187
|
+
}))
|
|
188
|
+
];
|
|
189
|
+
const batchedResults = await this.ms365Ops.batchEmailOperations(mixedOperations);
|
|
190
|
+
const batchedMetrics = performanceMonitor.endOperation('mixed_operations', 'mixed_operations', messageIds.length, 'batched', batchedStart, Math.ceil(mixedOperations.length / 20), // 20 operations per batch
|
|
191
|
+
true);
|
|
192
|
+
const comparison = performanceMonitor.comparePerformance('mixed_operations');
|
|
193
|
+
return {
|
|
194
|
+
traditional: { results: traditionalResults, metrics: traditionalMetrics },
|
|
195
|
+
batched: { results: Array.from(batchedResults.entries()), metrics: batchedMetrics },
|
|
196
|
+
comparison
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Run all test scenarios
|
|
201
|
+
*/
|
|
202
|
+
async runAllTests(messageIds) {
|
|
203
|
+
logger.log(`š STARTING COMPREHENSIVE BATCH PERFORMANCE TESTS`);
|
|
204
|
+
logger.log(`š§ Using ${messageIds.length} test emails`);
|
|
205
|
+
performanceMonitor.clear();
|
|
206
|
+
const results = {
|
|
207
|
+
bulkRetrieval: null,
|
|
208
|
+
bulkOperations: null,
|
|
209
|
+
folderWithEmails: null,
|
|
210
|
+
mixedOperations: null
|
|
211
|
+
};
|
|
212
|
+
try {
|
|
213
|
+
// Test 1: Bulk Email Retrieval
|
|
214
|
+
if (messageIds.length >= 5) {
|
|
215
|
+
const testIds = messageIds.slice(0, Math.min(10, messageIds.length));
|
|
216
|
+
results.bulkRetrieval = await this.testBulkEmailRetrieval(testIds);
|
|
217
|
+
}
|
|
218
|
+
// Test 2: Bulk Email Operations
|
|
219
|
+
if (messageIds.length >= 5) {
|
|
220
|
+
const testIds = messageIds.slice(0, Math.min(15, messageIds.length));
|
|
221
|
+
results.bulkOperations = await this.testBulkEmailOperations(testIds);
|
|
222
|
+
}
|
|
223
|
+
// Test 3: Folder + Emails
|
|
224
|
+
results.folderWithEmails = await this.testFolderWithEmails('inbox', 10);
|
|
225
|
+
// Test 4: Mixed Operations
|
|
226
|
+
if (messageIds.length >= 9) {
|
|
227
|
+
const testIds = messageIds.slice(0, Math.min(12, messageIds.length));
|
|
228
|
+
results.mixedOperations = await this.testMixedOperations(testIds);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
catch (error) {
|
|
232
|
+
logger.error('Error during batch testing:', error);
|
|
233
|
+
}
|
|
234
|
+
// Generate comprehensive report
|
|
235
|
+
const report = this.generateTestReport(results);
|
|
236
|
+
logger.log(report);
|
|
237
|
+
return report;
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Generate comprehensive test report
|
|
241
|
+
*/
|
|
242
|
+
generateTestReport(results) {
|
|
243
|
+
let report = `\nš BATCH PERFORMANCE TEST RESULTS\n`;
|
|
244
|
+
report += `${'='.repeat(50)}\n\n`;
|
|
245
|
+
if (results.bulkRetrieval?.comparison) {
|
|
246
|
+
const comp = results.bulkRetrieval.comparison;
|
|
247
|
+
report += `š§ BULK EMAIL RETRIEVAL\n`;
|
|
248
|
+
report += ` Traditional: ${comp.traditional.duration}ms (${comp.traditional.httpCalls} HTTP calls)\n`;
|
|
249
|
+
report += ` Batched: ${comp.batched.duration}ms (${comp.batched.httpCalls} HTTP calls)\n`;
|
|
250
|
+
report += ` š” Improvement: ${comp.improvement.durationReduction.toFixed(1)}% faster, ${comp.improvement.httpCallReduction.toFixed(1)}% fewer calls\n\n`;
|
|
251
|
+
}
|
|
252
|
+
if (results.bulkOperations?.comparison) {
|
|
253
|
+
const comp = results.bulkOperations.comparison;
|
|
254
|
+
report += `ā” BULK EMAIL OPERATIONS\n`;
|
|
255
|
+
report += ` Traditional: ${comp.traditional.duration}ms (${comp.traditional.httpCalls} HTTP calls)\n`;
|
|
256
|
+
report += ` Batched: ${comp.batched.duration}ms (${comp.batched.httpCalls} HTTP calls)\n`;
|
|
257
|
+
report += ` š” Improvement: ${comp.improvement.durationReduction.toFixed(1)}% faster, ${comp.improvement.httpCallReduction.toFixed(1)}% fewer calls\n\n`;
|
|
258
|
+
}
|
|
259
|
+
if (results.folderWithEmails?.comparison) {
|
|
260
|
+
const comp = results.folderWithEmails.comparison;
|
|
261
|
+
report += `š FOLDER + EMAILS FETCHING\n`;
|
|
262
|
+
report += ` Traditional: ${comp.traditional.duration}ms (${comp.traditional.httpCalls} HTTP calls)\n`;
|
|
263
|
+
report += ` Batched: ${comp.batched.duration}ms (${comp.batched.httpCalls} HTTP calls)\n`;
|
|
264
|
+
report += ` š” Improvement: ${comp.improvement.durationReduction.toFixed(1)}% faster, ${comp.improvement.httpCallReduction.toFixed(1)}% fewer calls\n\n`;
|
|
265
|
+
}
|
|
266
|
+
if (results.mixedOperations?.comparison) {
|
|
267
|
+
const comp = results.mixedOperations.comparison;
|
|
268
|
+
report += `š MIXED OPERATIONS WORKFLOW\n`;
|
|
269
|
+
report += ` Traditional: ${comp.traditional.duration}ms (${comp.traditional.httpCalls} HTTP calls)\n`;
|
|
270
|
+
report += ` Batched: ${comp.batched.duration}ms (${comp.batched.httpCalls} HTTP calls)\n`;
|
|
271
|
+
report += ` š” Improvement: ${comp.improvement.durationReduction.toFixed(1)}% faster, ${comp.improvement.httpCallReduction.toFixed(1)}% fewer calls\n\n`;
|
|
272
|
+
}
|
|
273
|
+
report += `š OVERALL PERFORMANCE INSIGHTS:\n`;
|
|
274
|
+
report += performanceMonitor.generateReport();
|
|
275
|
+
return report;
|
|
276
|
+
}
|
|
277
|
+
}
|