@sc4rfurryx/proteusjs 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/API.md +438 -0
- package/FEATURES.md +286 -0
- package/LICENSE +21 -0
- package/README.md +645 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/proteus.cjs.js +16014 -0
- package/dist/proteus.cjs.js.map +1 -0
- package/dist/proteus.d.ts +3018 -0
- package/dist/proteus.esm.js +16005 -0
- package/dist/proteus.esm.js.map +1 -0
- package/dist/proteus.esm.min.js +8 -0
- package/dist/proteus.esm.min.js.map +1 -0
- package/dist/proteus.js +16020 -0
- package/dist/proteus.js.map +1 -0
- package/dist/proteus.min.js +8 -0
- package/dist/proteus.min.js.map +1 -0
- package/package.json +98 -0
- package/src/__tests__/mvp-integration.test.ts +518 -0
- package/src/accessibility/AccessibilityEngine.ts +2106 -0
- package/src/accessibility/ScreenReaderSupport.ts +444 -0
- package/src/accessibility/__tests__/ScreenReaderSupport.test.ts +435 -0
- package/src/animations/FLIPAnimationSystem.ts +491 -0
- package/src/compatibility/BrowserCompatibility.ts +1076 -0
- package/src/containers/BreakpointSystem.ts +347 -0
- package/src/containers/ContainerBreakpoints.ts +726 -0
- package/src/containers/ContainerManager.ts +370 -0
- package/src/containers/ContainerUnits.ts +336 -0
- package/src/containers/ContextIsolation.ts +394 -0
- package/src/containers/ElementQueries.ts +411 -0
- package/src/containers/SmartContainer.ts +536 -0
- package/src/containers/SmartContainers.ts +376 -0
- package/src/containers/__tests__/ContainerBreakpoints.test.ts +411 -0
- package/src/containers/__tests__/SmartContainers.test.ts +281 -0
- package/src/content/ResponsiveImages.ts +570 -0
- package/src/core/EventSystem.ts +147 -0
- package/src/core/MemoryManager.ts +321 -0
- package/src/core/PerformanceMonitor.ts +238 -0
- package/src/core/PluginSystem.ts +275 -0
- package/src/core/ProteusJS.test.ts +164 -0
- package/src/core/ProteusJS.ts +962 -0
- package/src/developer/PerformanceProfiler.ts +567 -0
- package/src/developer/VisualDebuggingTools.ts +656 -0
- package/src/developer/ZeroConfigSystem.ts +593 -0
- package/src/index.ts +35 -0
- package/src/integration.test.ts +227 -0
- package/src/layout/AdaptiveGrid.ts +429 -0
- package/src/layout/ContentReordering.ts +532 -0
- package/src/layout/FlexboxEnhancer.ts +406 -0
- package/src/layout/FlowLayout.ts +545 -0
- package/src/layout/SpacingSystem.ts +512 -0
- package/src/observers/IntersectionObserverPolyfill.ts +289 -0
- package/src/observers/ObserverManager.ts +299 -0
- package/src/observers/ResizeObserverPolyfill.ts +179 -0
- package/src/performance/BatchDOMOperations.ts +519 -0
- package/src/performance/CSSOptimizationEngine.ts +646 -0
- package/src/performance/CacheOptimizationSystem.ts +601 -0
- package/src/performance/EfficientEventHandler.ts +740 -0
- package/src/performance/LazyEvaluationSystem.ts +532 -0
- package/src/performance/MemoryManagementSystem.ts +497 -0
- package/src/performance/PerformanceMonitor.ts +931 -0
- package/src/performance/__tests__/BatchDOMOperations.test.ts +309 -0
- package/src/performance/__tests__/EfficientEventHandler.test.ts +268 -0
- package/src/performance/__tests__/PerformanceMonitor.test.ts +422 -0
- package/src/polyfills/BrowserPolyfills.ts +586 -0
- package/src/polyfills/__tests__/BrowserPolyfills.test.ts +328 -0
- package/src/test/setup.ts +115 -0
- package/src/theming/SmartThemeSystem.ts +591 -0
- package/src/types/index.ts +134 -0
- package/src/typography/ClampScaling.ts +356 -0
- package/src/typography/FluidTypography.ts +759 -0
- package/src/typography/LineHeightOptimization.ts +430 -0
- package/src/typography/LineHeightOptimizer.ts +326 -0
- package/src/typography/TextFitting.ts +355 -0
- package/src/typography/TypographicScale.ts +428 -0
- package/src/typography/VerticalRhythm.ts +369 -0
- package/src/typography/__tests__/FluidTypography.test.ts +432 -0
- package/src/typography/__tests__/LineHeightOptimization.test.ts +436 -0
- package/src/utils/Logger.ts +173 -0
- package/src/utils/debounce.ts +259 -0
- package/src/utils/performance.ts +371 -0
- package/src/utils/support.ts +106 -0
- package/src/utils/version.ts +24 -0
@@ -0,0 +1,931 @@
|
|
1
|
+
/**
|
2
|
+
* PerformanceMonitor - Comprehensive performance monitoring and optimization
|
3
|
+
* Tracks frame rates, memory usage, and provides optimization recommendations
|
4
|
+
*/
|
5
|
+
|
6
|
+
import { logger } from '../utils/Logger';
|
7
|
+
|
8
|
+
export interface PerformanceMetrics {
|
9
|
+
frameRate: number;
|
10
|
+
averageFPS: number;
|
11
|
+
averageFrameTime: number;
|
12
|
+
memoryUsage: {
|
13
|
+
used: number;
|
14
|
+
total: number;
|
15
|
+
percentage: number;
|
16
|
+
};
|
17
|
+
domNodes: number;
|
18
|
+
eventListeners: number;
|
19
|
+
observers: number;
|
20
|
+
cacheHitRate: number;
|
21
|
+
operationsPerSecond: number;
|
22
|
+
lastMeasurement: number;
|
23
|
+
}
|
24
|
+
|
25
|
+
export interface PerformanceThresholds {
|
26
|
+
minFrameRate: number;
|
27
|
+
maxFrameTime: number;
|
28
|
+
maxMemoryUsage: number;
|
29
|
+
maxDOMNodes: number;
|
30
|
+
maxEventListeners: number;
|
31
|
+
}
|
32
|
+
|
33
|
+
export interface PerformanceAlert {
|
34
|
+
type: 'warning' | 'critical';
|
35
|
+
metric: string;
|
36
|
+
value: number;
|
37
|
+
threshold: number;
|
38
|
+
message: string;
|
39
|
+
timestamp: number;
|
40
|
+
suggestions: string[];
|
41
|
+
}
|
42
|
+
|
43
|
+
export interface PerformanceSnapshot {
|
44
|
+
timestamp: number;
|
45
|
+
metrics: PerformanceMetrics;
|
46
|
+
bottlenecks: string[];
|
47
|
+
memoryLeaks: boolean;
|
48
|
+
}
|
49
|
+
|
50
|
+
export interface BottleneckInfo {
|
51
|
+
type: 'dom' | 'memory' | 'cpu' | 'network';
|
52
|
+
severity: 'low' | 'medium' | 'high';
|
53
|
+
description: string;
|
54
|
+
impact: number;
|
55
|
+
suggestions: string[];
|
56
|
+
}
|
57
|
+
|
58
|
+
class BottleneckDetector {
|
59
|
+
private detectedBottlenecks: BottleneckInfo[] = [];
|
60
|
+
|
61
|
+
public detectBottlenecks(metrics: PerformanceMetrics): BottleneckInfo[] {
|
62
|
+
this.detectedBottlenecks = [];
|
63
|
+
|
64
|
+
// CPU bottleneck detection
|
65
|
+
if (metrics.averageFPS < 30) {
|
66
|
+
this.detectedBottlenecks.push({
|
67
|
+
type: 'cpu',
|
68
|
+
severity: 'high',
|
69
|
+
description: 'Low frame rate detected',
|
70
|
+
impact: (30 - metrics.averageFPS) / 30,
|
71
|
+
suggestions: [
|
72
|
+
'Reduce DOM manipulations',
|
73
|
+
'Optimize JavaScript execution',
|
74
|
+
'Use requestAnimationFrame for animations'
|
75
|
+
]
|
76
|
+
});
|
77
|
+
}
|
78
|
+
|
79
|
+
// Memory bottleneck detection
|
80
|
+
if (metrics.memoryUsage.percentage > 80) {
|
81
|
+
this.detectedBottlenecks.push({
|
82
|
+
type: 'memory',
|
83
|
+
severity: 'high',
|
84
|
+
description: 'High memory usage detected',
|
85
|
+
impact: metrics.memoryUsage.percentage / 100,
|
86
|
+
suggestions: [
|
87
|
+
'Clean up unused objects',
|
88
|
+
'Remove event listeners',
|
89
|
+
'Optimize image sizes'
|
90
|
+
]
|
91
|
+
});
|
92
|
+
}
|
93
|
+
|
94
|
+
// DOM bottleneck detection
|
95
|
+
if (metrics.domNodes > 5000) {
|
96
|
+
this.detectedBottlenecks.push({
|
97
|
+
type: 'dom',
|
98
|
+
severity: 'medium',
|
99
|
+
description: 'Large DOM tree detected',
|
100
|
+
impact: Math.min(metrics.domNodes / 10000, 1),
|
101
|
+
suggestions: [
|
102
|
+
'Implement virtual scrolling',
|
103
|
+
'Remove unused DOM nodes',
|
104
|
+
'Use document fragments for batch operations'
|
105
|
+
]
|
106
|
+
});
|
107
|
+
}
|
108
|
+
|
109
|
+
return this.detectedBottlenecks;
|
110
|
+
}
|
111
|
+
|
112
|
+
public getBottlenecks(): BottleneckInfo[] {
|
113
|
+
return [...this.detectedBottlenecks];
|
114
|
+
}
|
115
|
+
}
|
116
|
+
|
117
|
+
class MemoryOptimizer {
|
118
|
+
private memoryLeakDetector: Map<string, number> = new Map();
|
119
|
+
private cleanupTasks: (() => void)[] = [];
|
120
|
+
|
121
|
+
public optimizeMemory(): void {
|
122
|
+
// Force garbage collection if available
|
123
|
+
if ((window as any).gc) {
|
124
|
+
(window as any).gc();
|
125
|
+
}
|
126
|
+
|
127
|
+
// Run cleanup tasks
|
128
|
+
this.cleanupTasks.forEach(task => {
|
129
|
+
try {
|
130
|
+
task();
|
131
|
+
} catch (error) {
|
132
|
+
logger.warn('Memory cleanup task failed:', error);
|
133
|
+
}
|
134
|
+
});
|
135
|
+
|
136
|
+
// Clear cleanup tasks
|
137
|
+
this.cleanupTasks = [];
|
138
|
+
}
|
139
|
+
|
140
|
+
public detectMemoryLeaks(metrics: PerformanceMetrics): boolean {
|
141
|
+
const currentMemory = metrics.memoryUsage.used;
|
142
|
+
const timestamp = Date.now();
|
143
|
+
|
144
|
+
// Store memory usage
|
145
|
+
this.memoryLeakDetector.set(timestamp.toString(), currentMemory);
|
146
|
+
|
147
|
+
// Keep only last 10 measurements
|
148
|
+
const entries = Array.from(this.memoryLeakDetector.entries());
|
149
|
+
if (entries.length > 10) {
|
150
|
+
entries.slice(0, -10).forEach(([key]) => {
|
151
|
+
this.memoryLeakDetector.delete(key);
|
152
|
+
});
|
153
|
+
}
|
154
|
+
|
155
|
+
// Detect consistent memory growth
|
156
|
+
if (entries.length >= 5) {
|
157
|
+
const values = entries.map(([, value]) => value);
|
158
|
+
const isIncreasing = values.every((val, i) => i === 0 || val >= (values[i - 1] || 0));
|
159
|
+
const lastValue = values[values.length - 1] || 0;
|
160
|
+
const firstValue = values[0] || 1;
|
161
|
+
const growthRate = (lastValue - firstValue) / firstValue;
|
162
|
+
|
163
|
+
return isIncreasing && growthRate > 0.1; // 10% growth
|
164
|
+
}
|
165
|
+
|
166
|
+
return false;
|
167
|
+
}
|
168
|
+
|
169
|
+
public addCleanupTask(task: () => void): void {
|
170
|
+
this.cleanupTasks.push(task);
|
171
|
+
}
|
172
|
+
}
|
173
|
+
|
174
|
+
export class PerformanceMonitor {
|
175
|
+
private metrics!: PerformanceMetrics;
|
176
|
+
private thresholds!: PerformanceThresholds;
|
177
|
+
private alerts: PerformanceAlert[] = [];
|
178
|
+
private isMonitoring: boolean = false;
|
179
|
+
private rafId: number | null = null;
|
180
|
+
private lastFrameTime: number = 0;
|
181
|
+
private frameCount: number = 0;
|
182
|
+
private bottleneckDetector: BottleneckDetector;
|
183
|
+
private memoryOptimizer: MemoryOptimizer;
|
184
|
+
private performanceHistory: PerformanceSnapshot[] = [];
|
185
|
+
private callbacks: Map<string, (metrics: PerformanceMetrics) => void> = new Map();
|
186
|
+
private measurementInterval: number = 1000; // 1 second
|
187
|
+
private detailedProfiling: boolean = false;
|
188
|
+
private frameTimes: number[] = [];
|
189
|
+
private operationCount: number = 0;
|
190
|
+
private lastOperationTime: number = 0;
|
191
|
+
|
192
|
+
constructor(thresholds: Partial<PerformanceThresholds> = {}) {
|
193
|
+
// Initialize advanced monitoring components
|
194
|
+
this.bottleneckDetector = new BottleneckDetector();
|
195
|
+
this.memoryOptimizer = new MemoryOptimizer();
|
196
|
+
this.thresholds = {
|
197
|
+
minFrameRate: 55, // Target 60fps with 5fps tolerance
|
198
|
+
maxFrameTime: 16.67, // 60fps = 16.67ms per frame
|
199
|
+
maxMemoryUsage: 100 * 1024 * 1024, // 100MB
|
200
|
+
maxDOMNodes: 5000,
|
201
|
+
maxEventListeners: 1000,
|
202
|
+
...thresholds
|
203
|
+
};
|
204
|
+
|
205
|
+
this.metrics = this.createInitialMetrics();
|
206
|
+
}
|
207
|
+
|
208
|
+
/**
|
209
|
+
* Start performance monitoring (alias for start)
|
210
|
+
*/
|
211
|
+
public startMonitoring(): void {
|
212
|
+
this.start();
|
213
|
+
}
|
214
|
+
|
215
|
+
/**
|
216
|
+
* Start performance monitoring
|
217
|
+
*/
|
218
|
+
public start(): void {
|
219
|
+
if (this.isMonitoring) return;
|
220
|
+
|
221
|
+
this.isMonitoring = true;
|
222
|
+
this.lastFrameTime = performance.now();
|
223
|
+
this.lastOperationTime = performance.now();
|
224
|
+
this.startFrameMonitoring();
|
225
|
+
this.startMemoryMonitoring();
|
226
|
+
}
|
227
|
+
|
228
|
+
/**
|
229
|
+
* Stop performance monitoring
|
230
|
+
*/
|
231
|
+
public stop(): void {
|
232
|
+
if (!this.isMonitoring) return;
|
233
|
+
|
234
|
+
this.isMonitoring = false;
|
235
|
+
|
236
|
+
if (this.rafId) {
|
237
|
+
cancelAnimationFrame(this.rafId);
|
238
|
+
this.rafId = null;
|
239
|
+
}
|
240
|
+
}
|
241
|
+
|
242
|
+
/**
|
243
|
+
* Destroy the performance monitor and clean up all resources
|
244
|
+
*/
|
245
|
+
public destroy(): void {
|
246
|
+
this.stop();
|
247
|
+
this.callbacks.clear();
|
248
|
+
this.alerts = [];
|
249
|
+
this.frameTimes = [];
|
250
|
+
this.frameCount = 0;
|
251
|
+
this.operationCount = 0;
|
252
|
+
this.metrics = this.createInitialMetrics();
|
253
|
+
}
|
254
|
+
|
255
|
+
/**
|
256
|
+
* Start frame rate monitoring
|
257
|
+
*/
|
258
|
+
public startFrameRateMonitoring(): void {
|
259
|
+
if (this.isMonitoring) {
|
260
|
+
return;
|
261
|
+
}
|
262
|
+
this.start();
|
263
|
+
}
|
264
|
+
|
265
|
+
/**
|
266
|
+
* Stop frame rate monitoring
|
267
|
+
*/
|
268
|
+
public stopFrameRateMonitoring(): void {
|
269
|
+
this.stop();
|
270
|
+
}
|
271
|
+
|
272
|
+
/**
|
273
|
+
* Measure operation performance
|
274
|
+
*/
|
275
|
+
public async measureOperation<T>(name: string, operation: () => Promise<T> | T): Promise<{ result: T; duration: number; name: string }> {
|
276
|
+
const startTime = performance.now();
|
277
|
+
const result = await operation();
|
278
|
+
const duration = performance.now() - startTime;
|
279
|
+
|
280
|
+
this.recordOperation();
|
281
|
+
|
282
|
+
return { result, duration, name };
|
283
|
+
}
|
284
|
+
|
285
|
+
/**
|
286
|
+
* Detect performance bottlenecks
|
287
|
+
*/
|
288
|
+
public detectBottlenecks(): Array<{ type: string; severity: 'low' | 'medium' | 'high'; description: string }> {
|
289
|
+
const bottlenecks: Array<{ type: string; severity: 'low' | 'medium' | 'high'; description: string }> = [];
|
290
|
+
|
291
|
+
// Check frame rate
|
292
|
+
if (this.metrics.averageFPS < 30) {
|
293
|
+
bottlenecks.push({
|
294
|
+
type: 'frame-rate',
|
295
|
+
severity: 'high',
|
296
|
+
description: `Low frame rate: ${this.metrics.averageFPS.toFixed(1)} FPS`
|
297
|
+
});
|
298
|
+
} else if (this.metrics.averageFPS < 50) {
|
299
|
+
bottlenecks.push({
|
300
|
+
type: 'frame-rate',
|
301
|
+
severity: 'medium',
|
302
|
+
description: `Moderate frame rate: ${this.metrics.averageFPS.toFixed(1)} FPS`
|
303
|
+
});
|
304
|
+
}
|
305
|
+
|
306
|
+
// Check memory usage
|
307
|
+
if (this.metrics.memoryUsage.percentage > 80) {
|
308
|
+
bottlenecks.push({
|
309
|
+
type: 'memory',
|
310
|
+
severity: 'high',
|
311
|
+
description: `High memory usage: ${this.metrics.memoryUsage.percentage.toFixed(1)}%`
|
312
|
+
});
|
313
|
+
}
|
314
|
+
|
315
|
+
return bottlenecks;
|
316
|
+
}
|
317
|
+
|
318
|
+
/**
|
319
|
+
* Get current performance metrics
|
320
|
+
*/
|
321
|
+
public getMetrics(): PerformanceMetrics {
|
322
|
+
// Update metrics before returning them
|
323
|
+
this.updateMetrics();
|
324
|
+
return { ...this.metrics };
|
325
|
+
}
|
326
|
+
|
327
|
+
/**
|
328
|
+
* Get performance alerts
|
329
|
+
*/
|
330
|
+
public getAlerts(): PerformanceAlert[] {
|
331
|
+
return [...this.alerts];
|
332
|
+
}
|
333
|
+
|
334
|
+
/**
|
335
|
+
* Clear performance alerts
|
336
|
+
*/
|
337
|
+
public clearAlerts(): void {
|
338
|
+
this.alerts = [];
|
339
|
+
}
|
340
|
+
|
341
|
+
/**
|
342
|
+
* Add performance monitoring callback
|
343
|
+
*/
|
344
|
+
public addCallback(id: string, callback: (metrics: PerformanceMetrics) => void): void {
|
345
|
+
this.callbacks.set(id, callback);
|
346
|
+
}
|
347
|
+
|
348
|
+
/**
|
349
|
+
* Remove performance monitoring callback
|
350
|
+
*/
|
351
|
+
public removeCallback(id: string): void {
|
352
|
+
this.callbacks.delete(id);
|
353
|
+
}
|
354
|
+
|
355
|
+
/**
|
356
|
+
* Record an operation for performance tracking
|
357
|
+
*/
|
358
|
+
public recordOperation(): void {
|
359
|
+
this.operationCount++;
|
360
|
+
}
|
361
|
+
|
362
|
+
/**
|
363
|
+
* Update cache hit rate
|
364
|
+
*/
|
365
|
+
public updateCacheHitRate(hits: number, total: number): void {
|
366
|
+
this.metrics.cacheHitRate = total > 0 ? (hits / total) * 100 : 0;
|
367
|
+
}
|
368
|
+
|
369
|
+
/**
|
370
|
+
* Force immediate metrics update
|
371
|
+
*/
|
372
|
+
public updateMetrics(): void {
|
373
|
+
this.updateFrameMetrics();
|
374
|
+
this.updateMemoryMetrics();
|
375
|
+
this.updateDOMMetrics();
|
376
|
+
this.updateOperationMetrics();
|
377
|
+
this.checkThresholds();
|
378
|
+
this.notifyCallbacks();
|
379
|
+
}
|
380
|
+
|
381
|
+
/**
|
382
|
+
* Get performance recommendations
|
383
|
+
*/
|
384
|
+
public getRecommendations(): string[] {
|
385
|
+
const recommendations: string[] = [];
|
386
|
+
|
387
|
+
if (this.metrics.frameRate < this.thresholds.minFrameRate) {
|
388
|
+
recommendations.push('Consider reducing DOM manipulations or using requestAnimationFrame');
|
389
|
+
recommendations.push('Enable batch DOM operations to improve frame rate');
|
390
|
+
}
|
391
|
+
|
392
|
+
if (this.metrics.memoryUsage.percentage > 80) {
|
393
|
+
recommendations.push('Memory usage is high - consider implementing cleanup routines');
|
394
|
+
recommendations.push('Review event listeners and observers for potential leaks');
|
395
|
+
}
|
396
|
+
|
397
|
+
if (this.metrics.domNodes > this.thresholds.maxDOMNodes) {
|
398
|
+
recommendations.push('DOM tree is large - consider virtualization for long lists');
|
399
|
+
recommendations.push('Remove unused DOM elements to improve performance');
|
400
|
+
}
|
401
|
+
|
402
|
+
if (this.metrics.eventListeners > this.thresholds.maxEventListeners) {
|
403
|
+
recommendations.push('High number of event listeners - consider event delegation');
|
404
|
+
recommendations.push('Review and cleanup unused event listeners');
|
405
|
+
}
|
406
|
+
|
407
|
+
if (this.metrics.cacheHitRate < 70) {
|
408
|
+
recommendations.push('Cache hit rate is low - review caching strategy');
|
409
|
+
recommendations.push('Consider increasing cache size or improving cache keys');
|
410
|
+
}
|
411
|
+
|
412
|
+
return recommendations;
|
413
|
+
}
|
414
|
+
|
415
|
+
/**
|
416
|
+
* Start frame rate monitoring
|
417
|
+
*/
|
418
|
+
private startFrameMonitoring(): void {
|
419
|
+
const measureFrame = (timestamp: number) => {
|
420
|
+
if (!this.isMonitoring) return;
|
421
|
+
|
422
|
+
const deltaTime = timestamp - this.lastFrameTime;
|
423
|
+
this.frameTimes.push(deltaTime);
|
424
|
+
|
425
|
+
// Keep only last 60 frames for rolling average
|
426
|
+
if (this.frameTimes.length > 60) {
|
427
|
+
this.frameTimes.shift();
|
428
|
+
}
|
429
|
+
|
430
|
+
this.frameCount++;
|
431
|
+
this.lastFrameTime = timestamp;
|
432
|
+
|
433
|
+
// Update metrics every 60 frames or 1 second
|
434
|
+
if (this.frameCount % 60 === 0 || deltaTime > 1000) {
|
435
|
+
this.updateFrameMetrics();
|
436
|
+
this.checkThresholds();
|
437
|
+
this.notifyCallbacks();
|
438
|
+
}
|
439
|
+
|
440
|
+
this.rafId = requestAnimationFrame(measureFrame);
|
441
|
+
};
|
442
|
+
|
443
|
+
this.rafId = requestAnimationFrame(measureFrame);
|
444
|
+
}
|
445
|
+
|
446
|
+
/**
|
447
|
+
* Start memory monitoring
|
448
|
+
*/
|
449
|
+
private startMemoryMonitoring(): void {
|
450
|
+
// Update memory metrics every 5 seconds
|
451
|
+
const updateMemory = () => {
|
452
|
+
if (!this.isMonitoring) return;
|
453
|
+
|
454
|
+
this.updateMemoryMetrics();
|
455
|
+
setTimeout(updateMemory, 5000);
|
456
|
+
};
|
457
|
+
|
458
|
+
setTimeout(updateMemory, 5000);
|
459
|
+
}
|
460
|
+
|
461
|
+
/**
|
462
|
+
* Update frame-related metrics
|
463
|
+
*/
|
464
|
+
private updateFrameMetrics(): void {
|
465
|
+
if (this.frameTimes.length === 0) return;
|
466
|
+
|
467
|
+
const averageFrameTime = this.frameTimes.reduce((sum, time) => sum + time, 0) / this.frameTimes.length;
|
468
|
+
const frameRate = averageFrameTime > 0 ? 1000 / averageFrameTime : 60; // Fallback to 60fps
|
469
|
+
|
470
|
+
this.metrics.averageFrameTime = Math.round(averageFrameTime * 100) / 100;
|
471
|
+
this.metrics.frameRate = Math.round(frameRate * 100) / 100;
|
472
|
+
this.metrics.averageFPS = this.metrics.frameRate; // Keep them in sync
|
473
|
+
this.metrics.lastMeasurement = performance.now();
|
474
|
+
}
|
475
|
+
|
476
|
+
/**
|
477
|
+
* Update memory-related metrics
|
478
|
+
*/
|
479
|
+
private updateMemoryMetrics(): void {
|
480
|
+
if ('memory' in performance) {
|
481
|
+
const memory = (performance as any).memory;
|
482
|
+
this.metrics.memoryUsage = {
|
483
|
+
used: memory.usedJSHeapSize,
|
484
|
+
total: memory.totalJSHeapSize,
|
485
|
+
percentage: Math.round((memory.usedJSHeapSize / memory.totalJSHeapSize) * 100)
|
486
|
+
};
|
487
|
+
}
|
488
|
+
}
|
489
|
+
|
490
|
+
/**
|
491
|
+
* Update DOM-related metrics
|
492
|
+
*/
|
493
|
+
private updateDOMMetrics(): void {
|
494
|
+
this.metrics.domNodes = document.querySelectorAll('*').length;
|
495
|
+
|
496
|
+
// Estimate event listeners (this is approximate)
|
497
|
+
this.metrics.eventListeners = this.estimateEventListeners();
|
498
|
+
|
499
|
+
// Count observers (ResizeObserver, IntersectionObserver, etc.)
|
500
|
+
this.metrics.observers = this.countObservers();
|
501
|
+
}
|
502
|
+
|
503
|
+
/**
|
504
|
+
* Update operation metrics
|
505
|
+
*/
|
506
|
+
private updateOperationMetrics(): void {
|
507
|
+
const now = performance.now();
|
508
|
+
const timeDelta = (now - this.lastOperationTime) / 1000; // Convert to seconds
|
509
|
+
|
510
|
+
if (timeDelta > 0) {
|
511
|
+
this.metrics.operationsPerSecond = Math.round(this.operationCount / timeDelta);
|
512
|
+
}
|
513
|
+
|
514
|
+
this.operationCount = 0;
|
515
|
+
this.lastOperationTime = now;
|
516
|
+
}
|
517
|
+
|
518
|
+
/**
|
519
|
+
* Estimate number of event listeners
|
520
|
+
*/
|
521
|
+
private estimateEventListeners(): number {
|
522
|
+
// This is an approximation - actual count would require tracking
|
523
|
+
const elements = document.querySelectorAll('*');
|
524
|
+
let count = 0;
|
525
|
+
|
526
|
+
// Common events that are likely to have listeners
|
527
|
+
const commonEvents = ['click', 'scroll', 'resize', 'load', 'input', 'change'];
|
528
|
+
|
529
|
+
elements.forEach(element => {
|
530
|
+
commonEvents.forEach(event => {
|
531
|
+
if ((element as any)[`on${event}`] !== null) {
|
532
|
+
count++;
|
533
|
+
}
|
534
|
+
});
|
535
|
+
});
|
536
|
+
|
537
|
+
return count;
|
538
|
+
}
|
539
|
+
|
540
|
+
/**
|
541
|
+
* Count active observers
|
542
|
+
*/
|
543
|
+
private countObservers(): number {
|
544
|
+
// This would need to be integrated with actual observer tracking
|
545
|
+
// For now, return an estimate
|
546
|
+
return 0;
|
547
|
+
}
|
548
|
+
|
549
|
+
/**
|
550
|
+
* Check performance thresholds and generate alerts
|
551
|
+
*/
|
552
|
+
private checkThresholds(): void {
|
553
|
+
const now = performance.now();
|
554
|
+
|
555
|
+
// Check frame rate
|
556
|
+
if (this.metrics.frameRate < this.thresholds.minFrameRate) {
|
557
|
+
this.addAlert({
|
558
|
+
type: 'warning',
|
559
|
+
metric: 'frameRate',
|
560
|
+
value: this.metrics.frameRate,
|
561
|
+
threshold: this.thresholds.minFrameRate,
|
562
|
+
message: `Frame rate (${this.metrics.frameRate}fps) is below target (${this.thresholds.minFrameRate}fps)`,
|
563
|
+
timestamp: now,
|
564
|
+
suggestions: [
|
565
|
+
'Reduce DOM manipulations',
|
566
|
+
'Use requestAnimationFrame for animations',
|
567
|
+
'Enable batch DOM operations'
|
568
|
+
]
|
569
|
+
});
|
570
|
+
}
|
571
|
+
|
572
|
+
// Check memory usage
|
573
|
+
if (this.metrics.memoryUsage.used > this.thresholds.maxMemoryUsage) {
|
574
|
+
this.addAlert({
|
575
|
+
type: 'critical',
|
576
|
+
metric: 'memoryUsage',
|
577
|
+
value: this.metrics.memoryUsage.used,
|
578
|
+
threshold: this.thresholds.maxMemoryUsage,
|
579
|
+
message: `Memory usage (${Math.round(this.metrics.memoryUsage.used / 1024 / 1024)}MB) exceeds threshold`,
|
580
|
+
timestamp: now,
|
581
|
+
suggestions: [
|
582
|
+
'Implement cleanup routines',
|
583
|
+
'Review for memory leaks',
|
584
|
+
'Optimize data structures'
|
585
|
+
]
|
586
|
+
});
|
587
|
+
}
|
588
|
+
|
589
|
+
// Check DOM nodes
|
590
|
+
if (this.metrics.domNodes > this.thresholds.maxDOMNodes) {
|
591
|
+
this.addAlert({
|
592
|
+
type: 'warning',
|
593
|
+
metric: 'domNodes',
|
594
|
+
value: this.metrics.domNodes,
|
595
|
+
threshold: this.thresholds.maxDOMNodes,
|
596
|
+
message: `DOM tree size (${this.metrics.domNodes} nodes) is large`,
|
597
|
+
timestamp: now,
|
598
|
+
suggestions: [
|
599
|
+
'Consider virtualization',
|
600
|
+
'Remove unused elements',
|
601
|
+
'Optimize DOM structure'
|
602
|
+
]
|
603
|
+
});
|
604
|
+
}
|
605
|
+
}
|
606
|
+
|
607
|
+
/**
|
608
|
+
* Add performance alert
|
609
|
+
*/
|
610
|
+
private addAlert(alert: PerformanceAlert): void {
|
611
|
+
// Avoid duplicate alerts for the same metric within 30 seconds
|
612
|
+
const recentAlert = this.alerts.find(a =>
|
613
|
+
a.metric === alert.metric &&
|
614
|
+
alert.timestamp - a.timestamp < 30000
|
615
|
+
);
|
616
|
+
|
617
|
+
if (!recentAlert) {
|
618
|
+
this.alerts.push(alert);
|
619
|
+
|
620
|
+
// Keep only last 50 alerts
|
621
|
+
if (this.alerts.length > 50) {
|
622
|
+
this.alerts.shift();
|
623
|
+
}
|
624
|
+
}
|
625
|
+
}
|
626
|
+
|
627
|
+
/**
|
628
|
+
* Notify callbacks of metrics update
|
629
|
+
*/
|
630
|
+
private notifyCallbacks(): void {
|
631
|
+
this.callbacks.forEach(callback => {
|
632
|
+
try {
|
633
|
+
callback(this.metrics);
|
634
|
+
} catch (error) {
|
635
|
+
logger.error('Error in performance callback', error);
|
636
|
+
}
|
637
|
+
});
|
638
|
+
}
|
639
|
+
|
640
|
+
/**
|
641
|
+
* Create initial metrics object
|
642
|
+
*/
|
643
|
+
private createInitialMetrics(): PerformanceMetrics {
|
644
|
+
return {
|
645
|
+
frameRate: 60,
|
646
|
+
averageFPS: 60,
|
647
|
+
averageFrameTime: 16.67,
|
648
|
+
memoryUsage: {
|
649
|
+
used: 0,
|
650
|
+
total: 0,
|
651
|
+
percentage: 0
|
652
|
+
},
|
653
|
+
domNodes: 0,
|
654
|
+
eventListeners: 0,
|
655
|
+
observers: 0,
|
656
|
+
cacheHitRate: 0,
|
657
|
+
operationsPerSecond: 0,
|
658
|
+
lastMeasurement: performance.now()
|
659
|
+
};
|
660
|
+
}
|
661
|
+
|
662
|
+
/**
|
663
|
+
* Advanced performance monitoring methods
|
664
|
+
*/
|
665
|
+
|
666
|
+
/**
|
667
|
+
* Enable detailed profiling with comprehensive metrics
|
668
|
+
*/
|
669
|
+
public enableDetailedProfiling(enable: boolean = true): void {
|
670
|
+
this.detailedProfiling = enable;
|
671
|
+
|
672
|
+
if (enable) {
|
673
|
+
this.startAdvancedMonitoring();
|
674
|
+
} else {
|
675
|
+
this.stopAdvancedMonitoring();
|
676
|
+
}
|
677
|
+
}
|
678
|
+
|
679
|
+
/**
|
680
|
+
* Start advanced monitoring with bottleneck detection
|
681
|
+
*/
|
682
|
+
private startAdvancedMonitoring(): void {
|
683
|
+
// Enhanced frame rate monitoring
|
684
|
+
this.startFrameRateMonitoring();
|
685
|
+
|
686
|
+
// Memory leak detection
|
687
|
+
setInterval(() => {
|
688
|
+
const metrics = this.getMetrics();
|
689
|
+
const hasMemoryLeak = this.memoryOptimizer.detectMemoryLeaks(metrics);
|
690
|
+
|
691
|
+
if (hasMemoryLeak) {
|
692
|
+
this.addAlert({
|
693
|
+
type: 'warning',
|
694
|
+
metric: 'memory',
|
695
|
+
value: metrics.memoryUsage.percentage,
|
696
|
+
threshold: 80,
|
697
|
+
message: 'Potential memory leak detected',
|
698
|
+
timestamp: Date.now(),
|
699
|
+
suggestions: [
|
700
|
+
'Check for unreferenced objects',
|
701
|
+
'Remove unused event listeners',
|
702
|
+
'Clear intervals and timeouts'
|
703
|
+
]
|
704
|
+
});
|
705
|
+
}
|
706
|
+
}, 5000);
|
707
|
+
|
708
|
+
// Bottleneck detection
|
709
|
+
setInterval(() => {
|
710
|
+
const metrics = this.getMetrics();
|
711
|
+
const bottlenecks = this.bottleneckDetector.detectBottlenecks(metrics);
|
712
|
+
|
713
|
+
bottlenecks.forEach(bottleneck => {
|
714
|
+
if (bottleneck.severity === 'high') {
|
715
|
+
this.addAlert({
|
716
|
+
type: 'critical',
|
717
|
+
metric: bottleneck.type,
|
718
|
+
value: bottleneck.impact * 100,
|
719
|
+
threshold: 50,
|
720
|
+
message: bottleneck.description,
|
721
|
+
timestamp: Date.now(),
|
722
|
+
suggestions: bottleneck.suggestions
|
723
|
+
});
|
724
|
+
}
|
725
|
+
});
|
726
|
+
}, 2000);
|
727
|
+
}
|
728
|
+
|
729
|
+
/**
|
730
|
+
* Stop advanced monitoring
|
731
|
+
*/
|
732
|
+
private stopAdvancedMonitoring(): void {
|
733
|
+
// This would clear the intervals set in startAdvancedMonitoring
|
734
|
+
// For simplicity, we'll just log that advanced monitoring is stopped
|
735
|
+
logger.info('Advanced performance monitoring stopped');
|
736
|
+
}
|
737
|
+
|
738
|
+
/**
|
739
|
+
* Get comprehensive performance snapshot
|
740
|
+
*/
|
741
|
+
public getPerformanceSnapshot(): PerformanceSnapshot {
|
742
|
+
const metrics = this.getMetrics();
|
743
|
+
const bottlenecks = this.bottleneckDetector.getBottlenecks().map(b => b.description);
|
744
|
+
const memoryLeaks = this.memoryOptimizer.detectMemoryLeaks(metrics);
|
745
|
+
|
746
|
+
const snapshot: PerformanceSnapshot = {
|
747
|
+
timestamp: Date.now(),
|
748
|
+
metrics,
|
749
|
+
bottlenecks,
|
750
|
+
memoryLeaks
|
751
|
+
};
|
752
|
+
|
753
|
+
// Store in history
|
754
|
+
this.performanceHistory.push(snapshot);
|
755
|
+
|
756
|
+
// Keep only last 100 snapshots
|
757
|
+
if (this.performanceHistory.length > 100) {
|
758
|
+
this.performanceHistory.shift();
|
759
|
+
}
|
760
|
+
|
761
|
+
return snapshot;
|
762
|
+
}
|
763
|
+
|
764
|
+
/**
|
765
|
+
* Get performance history for analysis
|
766
|
+
*/
|
767
|
+
public getPerformanceHistory(): PerformanceSnapshot[] {
|
768
|
+
return [...this.performanceHistory];
|
769
|
+
}
|
770
|
+
|
771
|
+
/**
|
772
|
+
* Analyze performance trends
|
773
|
+
*/
|
774
|
+
public analyzePerformanceTrends(): {
|
775
|
+
frameRateTrend: 'improving' | 'degrading' | 'stable';
|
776
|
+
memoryTrend: 'improving' | 'degrading' | 'stable';
|
777
|
+
overallHealth: 'good' | 'warning' | 'critical';
|
778
|
+
} {
|
779
|
+
if (this.performanceHistory.length < 5) {
|
780
|
+
return {
|
781
|
+
frameRateTrend: 'stable',
|
782
|
+
memoryTrend: 'stable',
|
783
|
+
overallHealth: 'good'
|
784
|
+
};
|
785
|
+
}
|
786
|
+
|
787
|
+
const recent = this.performanceHistory.slice(-5);
|
788
|
+
const frameRates = recent.map(s => s.metrics.averageFPS);
|
789
|
+
const memoryUsages = recent.map(s => s.metrics.memoryUsage.percentage);
|
790
|
+
|
791
|
+
// Analyze frame rate trend
|
792
|
+
const frameRateSlope = this.calculateTrend(frameRates);
|
793
|
+
const frameRateTrend = frameRateSlope > 1 ? 'improving' :
|
794
|
+
frameRateSlope < -1 ? 'degrading' : 'stable';
|
795
|
+
|
796
|
+
// Analyze memory trend
|
797
|
+
const memorySlope = this.calculateTrend(memoryUsages);
|
798
|
+
const memoryTrend = memorySlope < -5 ? 'improving' :
|
799
|
+
memorySlope > 5 ? 'degrading' : 'stable';
|
800
|
+
|
801
|
+
// Overall health assessment
|
802
|
+
const currentMetrics = recent[recent.length - 1]?.metrics;
|
803
|
+
let overallHealth: 'good' | 'warning' | 'critical' = 'good';
|
804
|
+
|
805
|
+
if (currentMetrics) {
|
806
|
+
if (currentMetrics.averageFPS < 30 || currentMetrics.memoryUsage.percentage > 80) {
|
807
|
+
overallHealth = 'critical';
|
808
|
+
} else if (currentMetrics.averageFPS < 45 || currentMetrics.memoryUsage.percentage > 60) {
|
809
|
+
overallHealth = 'warning';
|
810
|
+
}
|
811
|
+
}
|
812
|
+
|
813
|
+
return {
|
814
|
+
frameRateTrend,
|
815
|
+
memoryTrend,
|
816
|
+
overallHealth
|
817
|
+
};
|
818
|
+
}
|
819
|
+
|
820
|
+
/**
|
821
|
+
* Calculate trend slope for a series of values
|
822
|
+
*/
|
823
|
+
private calculateTrend(values: number[]): number {
|
824
|
+
if (values.length < 2) return 0;
|
825
|
+
|
826
|
+
const n = values.length;
|
827
|
+
const sumX = (n * (n - 1)) / 2;
|
828
|
+
const sumY = values.reduce((a, b) => a + b, 0);
|
829
|
+
const sumXY = values.reduce((sum, y, x) => sum + x * y, 0);
|
830
|
+
const sumXX = values.reduce((sum, _, x) => sum + x * x, 0);
|
831
|
+
|
832
|
+
return (n * sumXY - sumX * sumY) / (n * sumXX - sumX * sumX);
|
833
|
+
}
|
834
|
+
|
835
|
+
/**
|
836
|
+
* Optimize performance based on current metrics
|
837
|
+
*/
|
838
|
+
public optimizePerformance(): void {
|
839
|
+
const metrics = this.getMetrics();
|
840
|
+
const bottlenecks = this.bottleneckDetector.detectBottlenecks(metrics);
|
841
|
+
|
842
|
+
// Apply memory optimization if needed
|
843
|
+
if (metrics.memoryUsage.percentage > 70) {
|
844
|
+
this.memoryOptimizer.optimizeMemory();
|
845
|
+
logger.info('Memory optimization applied');
|
846
|
+
}
|
847
|
+
|
848
|
+
// Apply DOM optimization
|
849
|
+
if (metrics.domNodes > 3000) {
|
850
|
+
this.optimizeDOM();
|
851
|
+
}
|
852
|
+
|
853
|
+
// Log optimization results
|
854
|
+
bottlenecks.forEach(bottleneck => {
|
855
|
+
logger.info(`Performance bottleneck detected: ${bottleneck.description}`);
|
856
|
+
logger.info(`Suggestions: ${bottleneck.suggestions.join(', ')}`);
|
857
|
+
});
|
858
|
+
}
|
859
|
+
|
860
|
+
/**
|
861
|
+
* Optimize DOM performance
|
862
|
+
*/
|
863
|
+
private optimizeDOM(): void {
|
864
|
+
// Remove unused elements
|
865
|
+
const unusedElements = document.querySelectorAll('[data-proteus-unused]');
|
866
|
+
unusedElements.forEach(el => el.remove());
|
867
|
+
|
868
|
+
// Optimize images
|
869
|
+
const images = document.querySelectorAll('img:not([loading])');
|
870
|
+
images.forEach(img => {
|
871
|
+
(img as HTMLImageElement).loading = 'lazy';
|
872
|
+
});
|
873
|
+
|
874
|
+
logger.info(`DOM optimization applied: removed ${unusedElements.length} unused elements, optimized ${images.length} images`);
|
875
|
+
}
|
876
|
+
|
877
|
+
/**
|
878
|
+
* Generate performance report
|
879
|
+
*/
|
880
|
+
public generatePerformanceReport(): {
|
881
|
+
summary: string;
|
882
|
+
metrics: PerformanceMetrics;
|
883
|
+
bottlenecks: BottleneckInfo[];
|
884
|
+
trends: {
|
885
|
+
frameRateTrend: 'improving' | 'degrading' | 'stable';
|
886
|
+
memoryTrend: 'improving' | 'degrading' | 'stable';
|
887
|
+
overallHealth: 'good' | 'warning' | 'critical';
|
888
|
+
};
|
889
|
+
recommendations: string[];
|
890
|
+
} {
|
891
|
+
const metrics = this.getMetrics();
|
892
|
+
const bottlenecks = this.bottleneckDetector.detectBottlenecks(metrics);
|
893
|
+
const trends = this.analyzePerformanceTrends();
|
894
|
+
|
895
|
+
const recommendations: string[] = [];
|
896
|
+
|
897
|
+
// Generate recommendations based on metrics
|
898
|
+
if (metrics.averageFPS < 45) {
|
899
|
+
recommendations.push('Consider reducing animation complexity');
|
900
|
+
recommendations.push('Optimize JavaScript execution with requestAnimationFrame');
|
901
|
+
}
|
902
|
+
|
903
|
+
if (metrics.memoryUsage.percentage > 60) {
|
904
|
+
recommendations.push('Implement memory cleanup strategies');
|
905
|
+
recommendations.push('Remove unused event listeners and observers');
|
906
|
+
}
|
907
|
+
|
908
|
+
if (metrics.domNodes > 2000) {
|
909
|
+
recommendations.push('Consider virtual scrolling for large lists');
|
910
|
+
recommendations.push('Remove unused DOM elements');
|
911
|
+
}
|
912
|
+
|
913
|
+
// Add bottleneck-specific recommendations
|
914
|
+
bottlenecks.forEach(bottleneck => {
|
915
|
+
recommendations.push(...bottleneck.suggestions);
|
916
|
+
});
|
917
|
+
|
918
|
+
const summary = `Performance Health: ${trends.overallHealth.toUpperCase()} | ` +
|
919
|
+
`FPS: ${metrics.averageFPS.toFixed(1)} | ` +
|
920
|
+
`Memory: ${metrics.memoryUsage.percentage.toFixed(1)}% | ` +
|
921
|
+
`DOM Nodes: ${metrics.domNodes}`;
|
922
|
+
|
923
|
+
return {
|
924
|
+
summary,
|
925
|
+
metrics,
|
926
|
+
bottlenecks,
|
927
|
+
trends,
|
928
|
+
recommendations: [...new Set(recommendations)] // Remove duplicates
|
929
|
+
};
|
930
|
+
}
|
931
|
+
}
|