@arela/uploader 0.2.13 → 1.0.0
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/.env.template +66 -0
- package/README.md +263 -62
- package/docs/API_ENDPOINTS_FOR_DETECTION.md +647 -0
- package/docs/QUICK_REFERENCE_API_DETECTION.md +264 -0
- package/docs/REFACTORING_SUMMARY_DETECT_PEDIMENTOS.md +200 -0
- package/package.json +3 -2
- package/scripts/cleanup-ds-store.js +109 -0
- package/scripts/cleanup-system-files.js +69 -0
- package/scripts/tests/phase-7-features.test.js +415 -0
- package/scripts/tests/signal-handling.test.js +275 -0
- package/scripts/tests/smart-watch-integration.test.js +554 -0
- package/scripts/tests/watch-service-integration.test.js +584 -0
- package/src/commands/UploadCommand.js +31 -4
- package/src/commands/WatchCommand.js +1342 -0
- package/src/config/config.js +270 -2
- package/src/document-type-shared.js +2 -0
- package/src/document-types/support-document.js +200 -0
- package/src/file-detection.js +9 -1
- package/src/index.js +163 -4
- package/src/services/AdvancedFilterService.js +505 -0
- package/src/services/AutoProcessingService.js +749 -0
- package/src/services/BenchmarkingService.js +381 -0
- package/src/services/DatabaseService.js +1019 -539
- package/src/services/ErrorMonitor.js +275 -0
- package/src/services/LoggingService.js +419 -1
- package/src/services/MonitoringService.js +401 -0
- package/src/services/PerformanceOptimizer.js +511 -0
- package/src/services/ReportingService.js +511 -0
- package/src/services/SignalHandler.js +255 -0
- package/src/services/SmartWatchDatabaseService.js +527 -0
- package/src/services/WatchService.js +783 -0
- package/src/services/upload/ApiUploadService.js +447 -3
- package/src/services/upload/MultiApiUploadService.js +233 -0
- package/src/services/upload/SupabaseUploadService.js +12 -5
- package/src/services/upload/UploadServiceFactory.js +24 -0
- package/src/utils/CleanupManager.js +262 -0
- package/src/utils/FileOperations.js +44 -0
- package/src/utils/WatchEventHandler.js +522 -0
- package/supabase/migrations/001_create_initial_schema.sql +366 -0
- package/supabase/migrations/002_align_with_arela_api_schema.sql +145 -0
- package/.envbackup +0 -37
- package/SUPABASE_UPLOAD_FIX.md +0 -157
- package/commands.md +0 -14
|
@@ -0,0 +1,511 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PerformanceOptimizer.js
|
|
3
|
+
* Phase 6 - Task 5: Performance Optimization
|
|
4
|
+
*
|
|
5
|
+
* Optimizes signal handling, cleanup operations, and resource management.
|
|
6
|
+
* Implements monitoring, profiling, and optimization strategies.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
class PerformanceOptimizer {
|
|
10
|
+
constructor(logger, cleanupManager, errorMonitor) {
|
|
11
|
+
this.logger = logger;
|
|
12
|
+
this.cleanupManager = cleanupManager;
|
|
13
|
+
this.errorMonitor = errorMonitor;
|
|
14
|
+
|
|
15
|
+
// Performance metrics
|
|
16
|
+
this.metrics = {
|
|
17
|
+
signalResponseTimes: [],
|
|
18
|
+
cleanupTimes: [],
|
|
19
|
+
resourceCleanupTimes: {},
|
|
20
|
+
memoryUsage: [],
|
|
21
|
+
errorRate: 0,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// Performance thresholds (milliseconds)
|
|
25
|
+
this.thresholds = {
|
|
26
|
+
signalResponse: 100, // Signal should be handled within 100ms
|
|
27
|
+
totalCleanup: 5000, // Total cleanup should complete within 5 seconds
|
|
28
|
+
resourceCleanup: 2000, // Individual resource cleanup < 2 seconds
|
|
29
|
+
memoryIncrease: 50 * 1024 * 1024, // Max 50MB increase during cleanup
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
// Optimization settings
|
|
33
|
+
this.optimizations = {
|
|
34
|
+
parallelizeCleanup: false, // Can we run some cleanups in parallel?
|
|
35
|
+
cacheDependencies: true,
|
|
36
|
+
compressSessions: true,
|
|
37
|
+
pruneOldLogs: true,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Monitor signal response time
|
|
43
|
+
*/
|
|
44
|
+
startSignalMonitoring() {
|
|
45
|
+
const signalStart = Date.now();
|
|
46
|
+
|
|
47
|
+
return () => {
|
|
48
|
+
const responseTime = Date.now() - signalStart;
|
|
49
|
+
this.metrics.signalResponseTimes.push(responseTime);
|
|
50
|
+
|
|
51
|
+
if (responseTime > this.thresholds.signalResponse) {
|
|
52
|
+
this.logger.warn(
|
|
53
|
+
`Slow signal response: ${responseTime}ms (threshold: ${this.thresholds.signalResponse}ms)`,
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return responseTime;
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Analyze cleanup performance
|
|
63
|
+
*/
|
|
64
|
+
async analyzeCleanupPerformance(cleanupResult) {
|
|
65
|
+
const analysis = {
|
|
66
|
+
timestamp: new Date().toISOString(),
|
|
67
|
+
totalDuration: cleanupResult.totalDuration,
|
|
68
|
+
resourceCount: cleanupResult.results.length,
|
|
69
|
+
successCount: cleanupResult.successCount,
|
|
70
|
+
failureCount: cleanupResult.failureCount,
|
|
71
|
+
slowResources: [],
|
|
72
|
+
recommendations: [],
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
// Identify slow resources
|
|
76
|
+
for (const res of cleanupResult.results) {
|
|
77
|
+
if (res.duration > this.thresholds.resourceCleanup) {
|
|
78
|
+
analysis.slowResources.push({
|
|
79
|
+
name: res.name,
|
|
80
|
+
duration: res.duration,
|
|
81
|
+
threshold: this.thresholds.resourceCleanup,
|
|
82
|
+
overage: res.duration - this.thresholds.resourceCleanup,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Check total cleanup time
|
|
88
|
+
if (cleanupResult.totalDuration > this.thresholds.totalCleanup) {
|
|
89
|
+
analysis.recommendations.push(
|
|
90
|
+
`Total cleanup took ${cleanupResult.totalDuration}ms (threshold: ${this.thresholds.totalCleanup}ms). ` +
|
|
91
|
+
`Consider optimizing resource cleanup functions.`,
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Analyze error correlation with performance
|
|
96
|
+
const stats = this.errorMonitor.getStatistics();
|
|
97
|
+
if (stats.totalErrors > 0) {
|
|
98
|
+
const errorRate =
|
|
99
|
+
(stats.totalErrors / (stats.totalErrors + stats.recoveredErrors)) * 100;
|
|
100
|
+
analysis.errorRate = errorRate.toFixed(2) + '%';
|
|
101
|
+
|
|
102
|
+
if (errorRate > 5) {
|
|
103
|
+
analysis.recommendations.push(
|
|
104
|
+
`High error rate (${errorRate.toFixed(2)}%). ` +
|
|
105
|
+
`Improve error handling in cleanup functions.`,
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Memory analysis
|
|
111
|
+
const memUsage = process.memoryUsage();
|
|
112
|
+
if (memUsage.heapUsed > this.thresholds.memoryIncrease) {
|
|
113
|
+
analysis.recommendations.push(
|
|
114
|
+
`High memory usage (${(memUsage.heapUsed / 1024 / 1024).toFixed(2)}MB). ` +
|
|
115
|
+
`Consider clearing error records or session data periodically.`,
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
this.metrics.cleanupTimes.push(cleanupResult.totalDuration);
|
|
120
|
+
return analysis;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Get performance report
|
|
125
|
+
*/
|
|
126
|
+
getPerformanceReport() {
|
|
127
|
+
const report = {
|
|
128
|
+
timestamp: new Date().toISOString(),
|
|
129
|
+
summary: this._generateSummary(),
|
|
130
|
+
signalMetrics: this._analyzeSignalMetrics(),
|
|
131
|
+
cleanupMetrics: this._analyzeCleanupMetrics(),
|
|
132
|
+
memoryMetrics: this._analyzeMemoryMetrics(),
|
|
133
|
+
recommendations: this._getRecommendations(),
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
return report;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Optimize cleanup timeout based on historical data
|
|
141
|
+
*/
|
|
142
|
+
optimizeCleanupTimeout() {
|
|
143
|
+
if (this.metrics.cleanupTimes.length === 0) {
|
|
144
|
+
return this.cleanupManager.cleanupTimeout;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Calculate 95th percentile of cleanup times
|
|
148
|
+
const sorted = [...this.metrics.cleanupTimes].sort((a, b) => a - b);
|
|
149
|
+
const p95Index = Math.ceil(sorted.length * 0.95) - 1;
|
|
150
|
+
const p95Time = sorted[Math.max(0, p95Index)];
|
|
151
|
+
|
|
152
|
+
// Add 20% buffer for safety
|
|
153
|
+
const optimizedTimeout = Math.ceil(p95Time * 1.2);
|
|
154
|
+
|
|
155
|
+
// Don't increase timeout beyond maximum of 60 seconds
|
|
156
|
+
const finalTimeout = Math.min(optimizedTimeout, 60000);
|
|
157
|
+
|
|
158
|
+
if (finalTimeout !== this.cleanupManager.cleanupTimeout) {
|
|
159
|
+
this.logger.info(
|
|
160
|
+
`Optimizing cleanup timeout: ${this.cleanupManager.cleanupTimeout}ms → ${finalTimeout}ms ` +
|
|
161
|
+
`(based on 95th percentile: ${p95Time}ms)`,
|
|
162
|
+
);
|
|
163
|
+
this.cleanupManager.cleanupTimeout = finalTimeout;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return finalTimeout;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Monitor and detect resource leaks
|
|
171
|
+
*/
|
|
172
|
+
detectResourceLeaks() {
|
|
173
|
+
const report = {
|
|
174
|
+
timestamp: new Date().toISOString(),
|
|
175
|
+
registeredResources: this.cleanupManager.getResourceCount(),
|
|
176
|
+
resourceNames: this.cleanupManager.getResourceNames(),
|
|
177
|
+
possibleLeaks: [],
|
|
178
|
+
memoryTrend: this._analyzeMemoryTrend(),
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
// Track memory growth
|
|
182
|
+
const currentMemory = process.memoryUsage().heapUsed;
|
|
183
|
+
this.metrics.memoryUsage.push(currentMemory);
|
|
184
|
+
|
|
185
|
+
// Detect memory leaks (increasing trend over time)
|
|
186
|
+
if (this.metrics.memoryUsage.length >= 5) {
|
|
187
|
+
const recentValues = this.metrics.memoryUsage.slice(-5);
|
|
188
|
+
const isIncreasing = recentValues.every(
|
|
189
|
+
(val, i) => i === 0 || val >= recentValues[i - 1],
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
if (isIncreasing) {
|
|
193
|
+
report.possibleLeaks.push({
|
|
194
|
+
type: 'memory_leak',
|
|
195
|
+
severity: 'warning',
|
|
196
|
+
message: 'Memory usage is continuously increasing',
|
|
197
|
+
recentValues: recentValues.map(
|
|
198
|
+
(v) => (v / 1024 / 1024).toFixed(2) + 'MB',
|
|
199
|
+
),
|
|
200
|
+
recommendation: 'Check for resource leaks in cleanup functions',
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Check for unreleased resources
|
|
206
|
+
if (this.cleanupManager.getResourceCount() > 10) {
|
|
207
|
+
report.possibleLeaks.push({
|
|
208
|
+
type: 'resource_accumulation',
|
|
209
|
+
severity: 'info',
|
|
210
|
+
message: `${this.cleanupManager.getResourceCount()} resources are registered`,
|
|
211
|
+
recommendation:
|
|
212
|
+
'Consider limiting resource registrations or clearing old resources',
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return report;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Generate optimization plan
|
|
221
|
+
*/
|
|
222
|
+
generateOptimizationPlan() {
|
|
223
|
+
const currentState = {
|
|
224
|
+
avgSignalResponseTime: this._calculateAverage(
|
|
225
|
+
this.metrics.signalResponseTimes,
|
|
226
|
+
),
|
|
227
|
+
avgCleanupTime: this._calculateAverage(this.metrics.cleanupTimes),
|
|
228
|
+
totalErrors: this.errorMonitor.getStatistics().totalErrors,
|
|
229
|
+
resourceCount: this.cleanupManager.getResourceCount(),
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
const plan = {
|
|
233
|
+
timestamp: new Date().toISOString(),
|
|
234
|
+
currentState,
|
|
235
|
+
optimizationSteps: [],
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
// Recommend signal optimization if response is slow
|
|
239
|
+
if (currentState.avgSignalResponseTime > this.thresholds.signalResponse) {
|
|
240
|
+
plan.optimizationSteps.push({
|
|
241
|
+
priority: 'high',
|
|
242
|
+
area: 'signal_handling',
|
|
243
|
+
issue: `Average signal response time: ${currentState.avgSignalResponseTime.toFixed(0)}ms`,
|
|
244
|
+
recommendations: [
|
|
245
|
+
'Reduce number of operations in signal handlers',
|
|
246
|
+
'Defer non-critical cleanup to after signal handling',
|
|
247
|
+
'Use async operations more efficiently',
|
|
248
|
+
],
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Recommend cleanup optimization if too slow
|
|
253
|
+
if (currentState.avgCleanupTime > this.thresholds.totalCleanup) {
|
|
254
|
+
plan.optimizationSteps.push({
|
|
255
|
+
priority: 'high',
|
|
256
|
+
area: 'cleanup_performance',
|
|
257
|
+
issue: `Average cleanup time: ${currentState.avgCleanupTime.toFixed(0)}ms`,
|
|
258
|
+
recommendations: [
|
|
259
|
+
'Profile individual resource cleanup times',
|
|
260
|
+
'Consider parallel cleanup for independent resources',
|
|
261
|
+
'Optimize database disconnect procedures',
|
|
262
|
+
],
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Recommend error handling optimization if rate is high
|
|
267
|
+
const stats = this.errorMonitor.getStatistics();
|
|
268
|
+
if (currentState.totalErrors > 5) {
|
|
269
|
+
plan.optimizationSteps.push({
|
|
270
|
+
priority: 'medium',
|
|
271
|
+
area: 'error_handling',
|
|
272
|
+
issue: `Total errors recorded: ${currentState.totalErrors}`,
|
|
273
|
+
recommendations: [
|
|
274
|
+
'Implement retry logic for transient errors',
|
|
275
|
+
'Improve error context tracking',
|
|
276
|
+
'Create error-specific recovery procedures',
|
|
277
|
+
],
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Recommend memory optimization
|
|
282
|
+
const memUsage = process.memoryUsage();
|
|
283
|
+
if (memUsage.heapUsed > 100 * 1024 * 1024) {
|
|
284
|
+
plan.optimizationSteps.push({
|
|
285
|
+
priority: 'medium',
|
|
286
|
+
area: 'memory_management',
|
|
287
|
+
issue: `Heap memory: ${(memUsage.heapUsed / 1024 / 1024).toFixed(2)}MB`,
|
|
288
|
+
recommendations: [
|
|
289
|
+
'Clear error records periodically',
|
|
290
|
+
'Implement session data pruning',
|
|
291
|
+
'Monitor error array growth',
|
|
292
|
+
],
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
return plan;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Benchmark cleanup operations
|
|
301
|
+
*/
|
|
302
|
+
async benchmarkCleanup(iterations = 3) {
|
|
303
|
+
const results = [];
|
|
304
|
+
|
|
305
|
+
for (let i = 0; i < iterations; i++) {
|
|
306
|
+
// Reset for clean benchmark
|
|
307
|
+
this.cleanupManager.reset();
|
|
308
|
+
this.errorMonitor.reset();
|
|
309
|
+
|
|
310
|
+
// Register test resources
|
|
311
|
+
this.cleanupManager.registerResource('TestResource1', {}, async () => {
|
|
312
|
+
return new Promise((resolve) => setTimeout(resolve, 100));
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
this.cleanupManager.registerResource('TestResource2', {}, async () => {
|
|
316
|
+
return new Promise((resolve) => setTimeout(resolve, 100));
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
// Measure cleanup
|
|
320
|
+
const start = Date.now();
|
|
321
|
+
const result = await this.cleanupManager.performCleanup('benchmark');
|
|
322
|
+
const duration = Date.now() - start;
|
|
323
|
+
|
|
324
|
+
results.push({
|
|
325
|
+
iteration: i + 1,
|
|
326
|
+
duration,
|
|
327
|
+
success: result.success,
|
|
328
|
+
resourceCount: result.results.length,
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
return {
|
|
333
|
+
timestamp: new Date().toISOString(),
|
|
334
|
+
iterations,
|
|
335
|
+
results,
|
|
336
|
+
avgDuration:
|
|
337
|
+
results.reduce((sum, r) => sum + r.duration, 0) / results.length,
|
|
338
|
+
minDuration: Math.min(...results.map((r) => r.duration)),
|
|
339
|
+
maxDuration: Math.max(...results.map((r) => r.duration)),
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Health check
|
|
345
|
+
*/
|
|
346
|
+
getHealthStatus() {
|
|
347
|
+
const stats = this.errorMonitor.getStatistics();
|
|
348
|
+
const memUsage = process.memoryUsage();
|
|
349
|
+
const avgCleanup = this._calculateAverage(this.metrics.cleanupTimes);
|
|
350
|
+
|
|
351
|
+
const health = {
|
|
352
|
+
timestamp: new Date().toISOString(),
|
|
353
|
+
overall: 'healthy',
|
|
354
|
+
components: {
|
|
355
|
+
signalHandling: {
|
|
356
|
+
status: 'healthy',
|
|
357
|
+
avgResponseTime:
|
|
358
|
+
this._calculateAverage(this.metrics.signalResponseTimes).toFixed(
|
|
359
|
+
2,
|
|
360
|
+
) + 'ms',
|
|
361
|
+
threshold: this.thresholds.signalResponse + 'ms',
|
|
362
|
+
},
|
|
363
|
+
cleanup: {
|
|
364
|
+
status:
|
|
365
|
+
avgCleanup > this.thresholds.totalCleanup ? 'degraded' : 'healthy',
|
|
366
|
+
avgDuration: avgCleanup.toFixed(0) + 'ms',
|
|
367
|
+
threshold: this.thresholds.totalCleanup + 'ms',
|
|
368
|
+
},
|
|
369
|
+
errorHandling: {
|
|
370
|
+
status: stats.totalErrors > 10 ? 'degraded' : 'healthy',
|
|
371
|
+
totalErrors: stats.totalErrors,
|
|
372
|
+
recoveryRate: stats.successRate + '%',
|
|
373
|
+
},
|
|
374
|
+
memory: {
|
|
375
|
+
status: memUsage.heapUsed > 100 * 1024 * 1024 ? 'warning' : 'healthy',
|
|
376
|
+
heapUsed: (memUsage.heapUsed / 1024 / 1024).toFixed(2) + 'MB',
|
|
377
|
+
heapTotal: (memUsage.heapTotal / 1024 / 1024).toFixed(2) + 'MB',
|
|
378
|
+
},
|
|
379
|
+
},
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
// Determine overall health
|
|
383
|
+
const degradedCount = Object.values(health.components).filter(
|
|
384
|
+
(c) => c.status === 'degraded' || c.status === 'warning',
|
|
385
|
+
).length;
|
|
386
|
+
|
|
387
|
+
if (degradedCount > 1) {
|
|
388
|
+
health.overall = 'warning';
|
|
389
|
+
} else if (degradedCount > 2) {
|
|
390
|
+
health.overall = 'critical';
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
return health;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Private helper methods
|
|
398
|
+
*/
|
|
399
|
+
|
|
400
|
+
_generateSummary() {
|
|
401
|
+
return {
|
|
402
|
+
signalCount: this.metrics.signalResponseTimes.length,
|
|
403
|
+
cleanupCount: this.metrics.cleanupTimes.length,
|
|
404
|
+
totalErrors: this.errorMonitor.getStatistics().totalErrors,
|
|
405
|
+
avgMemoryUsage:
|
|
406
|
+
(
|
|
407
|
+
this._calculateAverage(this.metrics.memoryUsage) /
|
|
408
|
+
1024 /
|
|
409
|
+
1024
|
|
410
|
+
).toFixed(2) + 'MB',
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
_analyzeSignalMetrics() {
|
|
415
|
+
const times = this.metrics.signalResponseTimes;
|
|
416
|
+
return {
|
|
417
|
+
sampleCount: times.length,
|
|
418
|
+
avgTime: this._calculateAverage(times).toFixed(2) + 'ms',
|
|
419
|
+
minTime: Math.min(...times, Infinity).toFixed(2) + 'ms',
|
|
420
|
+
maxTime: Math.max(...times, -Infinity).toFixed(2) + 'ms',
|
|
421
|
+
p95Time: this._calculatePercentile(times, 0.95).toFixed(2) + 'ms',
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
_analyzeCleanupMetrics() {
|
|
426
|
+
const times = this.metrics.cleanupTimes;
|
|
427
|
+
return {
|
|
428
|
+
sampleCount: times.length,
|
|
429
|
+
avgTime: this._calculateAverage(times).toFixed(0) + 'ms',
|
|
430
|
+
minTime: Math.min(...times, Infinity).toFixed(0) + 'ms',
|
|
431
|
+
maxTime: Math.max(...times, -Infinity).toFixed(0) + 'ms',
|
|
432
|
+
p95Time: this._calculatePercentile(times, 0.95).toFixed(0) + 'ms',
|
|
433
|
+
threshold: this.thresholds.totalCleanup + 'ms',
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
_analyzeMemoryMetrics() {
|
|
438
|
+
const usage = this.metrics.memoryUsage;
|
|
439
|
+
const current = process.memoryUsage();
|
|
440
|
+
|
|
441
|
+
return {
|
|
442
|
+
currentHeapUsed: (current.heapUsed / 1024 / 1024).toFixed(2) + 'MB',
|
|
443
|
+
currentHeapTotal: (current.heapTotal / 1024 / 1024).toFixed(2) + 'MB',
|
|
444
|
+
avgMemoryUsage: this._calculateAverage(usage).toFixed(0) + ' bytes',
|
|
445
|
+
trend: this._analyzeMemoryTrend(),
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
_analyzeMemoryTrend() {
|
|
450
|
+
if (this.metrics.memoryUsage.length < 2) {
|
|
451
|
+
return 'insufficient_data';
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
const recent = this.metrics.memoryUsage.slice(-5);
|
|
455
|
+
const avg = recent.reduce((a, b) => a + b, 0) / recent.length;
|
|
456
|
+
const older = this.metrics.memoryUsage.slice(-10, -5);
|
|
457
|
+
const avgOlder =
|
|
458
|
+
older.length > 0 ? older.reduce((a, b) => a + b, 0) / older.length : avg;
|
|
459
|
+
|
|
460
|
+
if (avg > avgOlder * 1.1) {
|
|
461
|
+
return 'increasing';
|
|
462
|
+
} else if (avg < avgOlder * 0.9) {
|
|
463
|
+
return 'decreasing';
|
|
464
|
+
}
|
|
465
|
+
return 'stable';
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
_getRecommendations() {
|
|
469
|
+
const recommendations = [];
|
|
470
|
+
const stats = this.errorMonitor.getStatistics();
|
|
471
|
+
const avgCleanup = this._calculateAverage(this.metrics.cleanupTimes);
|
|
472
|
+
|
|
473
|
+
if (avgCleanup > this.thresholds.totalCleanup) {
|
|
474
|
+
recommendations.push(
|
|
475
|
+
`Cleanup performance needs improvement. ` +
|
|
476
|
+
`Average: ${avgCleanup.toFixed(0)}ms, ` +
|
|
477
|
+
`Target: ${this.thresholds.totalCleanup}ms`,
|
|
478
|
+
);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
if (stats.totalErrors > 0) {
|
|
482
|
+
recommendations.push(
|
|
483
|
+
`Review ${stats.totalErrors} recorded errors. ` +
|
|
484
|
+
`Recovery rate: ${stats.successRate}%`,
|
|
485
|
+
);
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
if (this.metrics.memoryUsage.length > 3) {
|
|
489
|
+
const trend = this._analyzeMemoryTrend();
|
|
490
|
+
if (trend === 'increasing') {
|
|
491
|
+
recommendations.push('Memory usage is increasing. Check for leaks.');
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
return recommendations;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
_calculateAverage(values) {
|
|
499
|
+
if (values.length === 0) return 0;
|
|
500
|
+
return values.reduce((a, b) => a + b, 0) / values.length;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
_calculatePercentile(values, percentile) {
|
|
504
|
+
if (values.length === 0) return 0;
|
|
505
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
506
|
+
const index = Math.ceil(sorted.length * percentile) - 1;
|
|
507
|
+
return sorted[Math.max(0, index)];
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
export default PerformanceOptimizer;
|