@lytjs/common-performance 6.5.0 → 6.7.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/dist/index.cjs.map +1 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/src/index.ts +588 -583
package/src/index.ts
CHANGED
|
@@ -1,583 +1,588 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @lytjs/common-performance
|
|
3
|
-
* Performance monitoring API for component render timing
|
|
4
|
-
* FIX: P2-11 RUNTIME-NEW-02 - 性能监控 API
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
declare const __DEV__: boolean;
|
|
8
|
-
|
|
9
|
-
import { warn } from '@lytjs/common-error';
|
|
10
|
-
|
|
11
|
-
// ==================== Types ====================
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Performance entry for a single render operation
|
|
15
|
-
*/
|
|
16
|
-
export interface RenderPerformanceEntry {
|
|
17
|
-
/** Component name or identifier */
|
|
18
|
-
componentName: string;
|
|
19
|
-
|
|
20
|
-
/** Type of operation */
|
|
21
|
-
operation: 'mount' | 'patch' | 'unmount';
|
|
22
|
-
|
|
23
|
-
/** Start time (high resolution timestamp) */
|
|
24
|
-
startTime: number;
|
|
25
|
-
|
|
26
|
-
/** End time (high resolution timestamp) */
|
|
27
|
-
endTime: number;
|
|
28
|
-
|
|
29
|
-
/** Duration in milliseconds */
|
|
30
|
-
duration: number;
|
|
31
|
-
|
|
32
|
-
/** Additional metadata */
|
|
33
|
-
metadata?: Record<string, unknown>;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Component performance statistics
|
|
38
|
-
*/
|
|
39
|
-
export interface ComponentPerformanceStats {
|
|
40
|
-
/** Component name */
|
|
41
|
-
componentName: string;
|
|
42
|
-
|
|
43
|
-
/** Number of mount operations */
|
|
44
|
-
mountCount: number;
|
|
45
|
-
|
|
46
|
-
/** Number of patch (update) operations */
|
|
47
|
-
patchCount: number;
|
|
48
|
-
|
|
49
|
-
/** Number of unmount operations */
|
|
50
|
-
unmountCount: number;
|
|
51
|
-
|
|
52
|
-
/** Total time spent in mount operations (ms) */
|
|
53
|
-
totalMountTime: number;
|
|
54
|
-
|
|
55
|
-
/** Total time spent in patch operations (ms) */
|
|
56
|
-
totalPatchTime: number;
|
|
57
|
-
|
|
58
|
-
/** Total time spent in unmount operations (ms) */
|
|
59
|
-
totalUnmountTime: number;
|
|
60
|
-
|
|
61
|
-
/** Average mount time (ms) */
|
|
62
|
-
averageMountTime: number;
|
|
63
|
-
|
|
64
|
-
/** Average patch time (ms) */
|
|
65
|
-
averagePatchTime: number;
|
|
66
|
-
|
|
67
|
-
/** Average unmount time (ms) */
|
|
68
|
-
averageUnmountTime: number;
|
|
69
|
-
|
|
70
|
-
/** Maximum mount time (ms) */
|
|
71
|
-
maxMountTime: number;
|
|
72
|
-
|
|
73
|
-
/** Maximum patch time (ms) */
|
|
74
|
-
maxPatchTime: number;
|
|
75
|
-
|
|
76
|
-
/** Maximum unmount time (ms) */
|
|
77
|
-
maxUnmountTime: number;
|
|
78
|
-
|
|
79
|
-
/** Last render timestamp */
|
|
80
|
-
lastRenderTime: number;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Performance monitor options
|
|
85
|
-
*/
|
|
86
|
-
export interface PerformanceMonitorOptions {
|
|
87
|
-
/** Maximum number of entries to keep in history */
|
|
88
|
-
maxHistorySize?: number;
|
|
89
|
-
|
|
90
|
-
/** Whether to enable monitoring by default */
|
|
91
|
-
enabled?: boolean;
|
|
92
|
-
|
|
93
|
-
/** Callback when a new entry is recorded */
|
|
94
|
-
onEntry?: (entry: RenderPerformanceEntry) => void;
|
|
95
|
-
|
|
96
|
-
/** Callback when stats are updated */
|
|
97
|
-
onStatsUpdate?: (stats: ComponentPerformanceStats) => void;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// ==================== Performance Monitor Class ====================
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* PerformanceMonitor - Tracks component render performance
|
|
104
|
-
*
|
|
105
|
-
* @example
|
|
106
|
-
* ```ts
|
|
107
|
-
* const monitor = new PerformanceMonitor({ maxHistorySize: 100 });
|
|
108
|
-
*
|
|
109
|
-
* // Start timing a render
|
|
110
|
-
* const endTiming = monitor.startTiming('MyComponent', 'patch');
|
|
111
|
-
*
|
|
112
|
-
* // ... perform render ...
|
|
113
|
-
*
|
|
114
|
-
* // End timing
|
|
115
|
-
* endTiming();
|
|
116
|
-
*
|
|
117
|
-
* // Get stats
|
|
118
|
-
* const stats = monitor.getStats('MyComponent');
|
|
119
|
-
* console.log(`Average render time: ${stats?.averagePatchTime}ms`);
|
|
120
|
-
* ```
|
|
121
|
-
*/
|
|
122
|
-
export class PerformanceMonitor {
|
|
123
|
-
// FIX: P2-v11-17 使用环形缓冲区替代数组 + shift(),
|
|
124
|
-
// 避免 history.shift() 的 O(n) 时间复杂度
|
|
125
|
-
private historyBuffer: (RenderPerformanceEntry | null)[] = [];
|
|
126
|
-
private historyHead = 0;
|
|
127
|
-
private historyCount = 0;
|
|
128
|
-
private stats: Map<string, ComponentPerformanceStats> = new Map();
|
|
129
|
-
private options: Required<PerformanceMonitorOptions>;
|
|
130
|
-
private _enabled: boolean;
|
|
131
|
-
|
|
132
|
-
constructor(options: PerformanceMonitorOptions = {}) {
|
|
133
|
-
this.options = {
|
|
134
|
-
maxHistorySize: options.maxHistorySize ?? 1000,
|
|
135
|
-
enabled: options.enabled ?? true,
|
|
136
|
-
onEntry: options.onEntry ?? (() => {}),
|
|
137
|
-
onStatsUpdate: options.onStatsUpdate ?? (() => {}),
|
|
138
|
-
};
|
|
139
|
-
this._enabled = this.options.enabled;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* Check if monitoring is enabled
|
|
144
|
-
*/
|
|
145
|
-
get enabled(): boolean {
|
|
146
|
-
return this._enabled;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
* Enable or disable monitoring
|
|
151
|
-
*/
|
|
152
|
-
set enabled(value: boolean) {
|
|
153
|
-
this._enabled = value;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
/**
|
|
157
|
-
* Start timing a render operation.
|
|
158
|
-
* Returns a function that should be called when the operation completes.
|
|
159
|
-
*
|
|
160
|
-
* @param componentName - Name of the component being rendered
|
|
161
|
-
* @param operation - Type of operation (mount, patch, unmount)
|
|
162
|
-
* @param metadata - Optional metadata to attach to the entry
|
|
163
|
-
* @returns A function to end the timing
|
|
164
|
-
*
|
|
165
|
-
* @example
|
|
166
|
-
* ```ts
|
|
167
|
-
* const endTiming = monitor.startTiming('MyComponent', 'patch');
|
|
168
|
-
* // ... render logic ...
|
|
169
|
-
* endTiming({ propsChanged: true });
|
|
170
|
-
* ```
|
|
171
|
-
*/
|
|
172
|
-
startTiming(
|
|
173
|
-
componentName: string,
|
|
174
|
-
operation: 'mount' | 'patch' | 'unmount',
|
|
175
|
-
metadata?: Record<string, unknown>,
|
|
176
|
-
): (endMetadata?: Record<string, unknown>) => RenderPerformanceEntry | null {
|
|
177
|
-
if (!this._enabled) {
|
|
178
|
-
return () => null;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
// FIX: P2-v11-16 添加 performance.now() 回退,
|
|
182
|
-
// 在非浏览器环境(如 SSR、Node.js)中使用 Date.now() 替代
|
|
183
|
-
const getTimestamp = (): number => {
|
|
184
|
-
if (typeof performance !== 'undefined' && typeof performance.now === 'function') {
|
|
185
|
-
return performance.now();
|
|
186
|
-
}
|
|
187
|
-
return Date.now();
|
|
188
|
-
};
|
|
189
|
-
|
|
190
|
-
const startTime = getTimestamp();
|
|
191
|
-
|
|
192
|
-
return (endMetadata?: Record<string, unknown>) => {
|
|
193
|
-
const endTime = getTimestamp();
|
|
194
|
-
const duration = endTime - startTime;
|
|
195
|
-
|
|
196
|
-
const entry: RenderPerformanceEntry = {
|
|
197
|
-
componentName,
|
|
198
|
-
operation,
|
|
199
|
-
startTime,
|
|
200
|
-
endTime,
|
|
201
|
-
duration,
|
|
202
|
-
metadata: { ...metadata, ...endMetadata },
|
|
203
|
-
};
|
|
204
|
-
|
|
205
|
-
this.recordEntry(entry);
|
|
206
|
-
return entry;
|
|
207
|
-
};
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
/**
|
|
211
|
-
* Record a performance entry directly
|
|
212
|
-
*/
|
|
213
|
-
recordEntry(entry: RenderPerformanceEntry): void {
|
|
214
|
-
if (!this._enabled) return;
|
|
215
|
-
|
|
216
|
-
// FIX: P2-v11-17 使用环形缓冲区写入,O(1) 操作
|
|
217
|
-
if (this.historyBuffer.length < this.options.maxHistorySize) {
|
|
218
|
-
this.historyBuffer.push(entry);
|
|
219
|
-
this.historyCount++;
|
|
220
|
-
} else {
|
|
221
|
-
this.historyBuffer[this.historyHead] = entry;
|
|
222
|
-
this.historyHead = (this.historyHead + 1) % this.options.maxHistorySize;
|
|
223
|
-
// historyCount 保持为 maxHistorySize
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
// Update stats
|
|
227
|
-
this.updateStats(entry);
|
|
228
|
-
|
|
229
|
-
// Notify callback
|
|
230
|
-
this.options.onEntry(entry);
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
/**
|
|
234
|
-
* Update statistics for a component
|
|
235
|
-
*/
|
|
236
|
-
private updateStats(entry: RenderPerformanceEntry): void {
|
|
237
|
-
const { componentName, operation, duration } = entry;
|
|
238
|
-
|
|
239
|
-
let stats = this.stats.get(componentName);
|
|
240
|
-
if (!stats) {
|
|
241
|
-
stats = {
|
|
242
|
-
componentName,
|
|
243
|
-
mountCount: 0,
|
|
244
|
-
patchCount: 0,
|
|
245
|
-
unmountCount: 0,
|
|
246
|
-
totalMountTime: 0,
|
|
247
|
-
totalPatchTime: 0,
|
|
248
|
-
totalUnmountTime: 0,
|
|
249
|
-
averageMountTime: 0,
|
|
250
|
-
averagePatchTime: 0,
|
|
251
|
-
averageUnmountTime: 0,
|
|
252
|
-
maxMountTime: 0,
|
|
253
|
-
maxPatchTime: 0,
|
|
254
|
-
maxUnmountTime: 0,
|
|
255
|
-
lastRenderTime: Date.now(),
|
|
256
|
-
};
|
|
257
|
-
this.stats.set(componentName, stats);
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
// Update counters and times
|
|
261
|
-
switch (operation) {
|
|
262
|
-
case 'mount':
|
|
263
|
-
stats.mountCount++;
|
|
264
|
-
stats.totalMountTime += duration;
|
|
265
|
-
stats.averageMountTime = stats.totalMountTime / stats.mountCount;
|
|
266
|
-
stats.maxMountTime = Math.max(stats.maxMountTime, duration);
|
|
267
|
-
break;
|
|
268
|
-
case 'patch':
|
|
269
|
-
stats.patchCount++;
|
|
270
|
-
stats.totalPatchTime += duration;
|
|
271
|
-
stats.averagePatchTime = stats.totalPatchTime / stats.patchCount;
|
|
272
|
-
stats.maxPatchTime = Math.max(stats.maxPatchTime, duration);
|
|
273
|
-
break;
|
|
274
|
-
case 'unmount':
|
|
275
|
-
stats.unmountCount++;
|
|
276
|
-
stats.totalUnmountTime += duration;
|
|
277
|
-
stats.averageUnmountTime = stats.totalUnmountTime / stats.unmountCount;
|
|
278
|
-
stats.maxUnmountTime = Math.max(stats.maxUnmountTime, duration);
|
|
279
|
-
break;
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
stats.lastRenderTime = Date.now();
|
|
283
|
-
this.options.onStatsUpdate(stats);
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
/**
|
|
287
|
-
* Get performance statistics for a specific component
|
|
288
|
-
*/
|
|
289
|
-
getStats(componentName: string): ComponentPerformanceStats | undefined {
|
|
290
|
-
return this.stats.get(componentName);
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
/**
|
|
294
|
-
* Get all component statistics
|
|
295
|
-
*/
|
|
296
|
-
getAllStats(): ComponentPerformanceStats[] {
|
|
297
|
-
return Array.from(this.stats.values());
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
/**
|
|
301
|
-
* Get performance history
|
|
302
|
-
* FIX: P2-v11-17 从环形缓冲区读取历史记录
|
|
303
|
-
*/
|
|
304
|
-
getHistory(): RenderPerformanceEntry[] {
|
|
305
|
-
const result: RenderPerformanceEntry[] = [];
|
|
306
|
-
for (let i = 0; i < this.historyCount; i++) {
|
|
307
|
-
const idx = (this.historyHead + i) % this.historyBuffer.length;
|
|
308
|
-
const entry = this.historyBuffer[idx];
|
|
309
|
-
if (entry) result.push(entry);
|
|
310
|
-
}
|
|
311
|
-
return result;
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
/**
|
|
315
|
-
* Get history for a specific component
|
|
316
|
-
*/
|
|
317
|
-
getComponentHistory(componentName: string): RenderPerformanceEntry[] {
|
|
318
|
-
return this.getHistory().filter((entry) => entry.componentName === componentName);
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
/**
|
|
322
|
-
* Get history for a specific operation type
|
|
323
|
-
*/
|
|
324
|
-
getOperationHistory(operation: 'mount' | 'patch' | 'unmount'): RenderPerformanceEntry[] {
|
|
325
|
-
return this.getHistory().filter((entry) => entry.operation === operation);
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
/**
|
|
329
|
-
* Get the slowest renders
|
|
330
|
-
*/
|
|
331
|
-
getSlowestRenders(limit: number = 10): RenderPerformanceEntry[] {
|
|
332
|
-
return this.getHistory()
|
|
333
|
-
.sort((a, b) => b.duration - a.duration)
|
|
334
|
-
.slice(0, limit);
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
/**
|
|
338
|
-
* Get average render time across all components
|
|
339
|
-
*/
|
|
340
|
-
getGlobalAverageRenderTime(): number {
|
|
341
|
-
const history = this.getHistory();
|
|
342
|
-
if (history.length === 0) return 0;
|
|
343
|
-
const total = history.reduce((sum, entry) => sum + entry.duration, 0);
|
|
344
|
-
return total / history.length;
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
/**
|
|
348
|
-
* Get total render time across all components
|
|
349
|
-
*/
|
|
350
|
-
getGlobalTotalRenderTime(): number {
|
|
351
|
-
return this.getHistory().reduce((sum, entry) => sum + entry.duration, 0);
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
/**
|
|
355
|
-
* Clear all history and stats
|
|
356
|
-
* FIX: P2 统一 clear/clearComponent 策略:使用 fill(null) 预分配固定大小缓冲区,
|
|
357
|
-
* 与 clearComponent 保持一致的环形缓冲区重建方式,避免 clear 后缓冲区大小丢失
|
|
358
|
-
*/
|
|
359
|
-
clear(): void {
|
|
360
|
-
this.historyBuffer = new Array(this.options.maxHistorySize).fill(null);
|
|
361
|
-
this.historyHead = 0;
|
|
362
|
-
this.historyCount = 0;
|
|
363
|
-
this.stats.clear();
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
/**
|
|
367
|
-
* Clear history and stats for a specific component
|
|
368
|
-
*/
|
|
369
|
-
clearComponent(componentName: string): boolean {
|
|
370
|
-
// FIX: P2-batch2-12 修复 clearComponent 破坏环形缓冲区的问题。
|
|
371
|
-
// 原实现直接将过滤后的数组赋值给 historyBuffer 并重置 head/tail,
|
|
372
|
-
// 但丢失了环形缓冲区的固定大小约束。正确做法是重建固定大小的缓冲区。
|
|
373
|
-
const filtered = this.getHistory().filter((entry) => entry.componentName !== componentName);
|
|
374
|
-
// 重建固定大小的环形缓冲区
|
|
375
|
-
this.historyBuffer = new Array(this.options.maxHistorySize).fill(null);
|
|
376
|
-
this.historyHead = 0;
|
|
377
|
-
this.historyCount = filtered.length;
|
|
378
|
-
for (let i = 0; i < filtered.length; i++) {
|
|
379
|
-
// FIX: DTS build error - 类型匹配
|
|
380
|
-
this.historyBuffer[i] = filtered[i] as RenderPerformanceEntry | null;
|
|
381
|
-
}
|
|
382
|
-
return this.stats.delete(componentName);
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
/**
|
|
386
|
-
* Generate a performance report
|
|
387
|
-
*/
|
|
388
|
-
generateReport(): PerformanceReport {
|
|
389
|
-
const allStats = this.getAllStats();
|
|
390
|
-
const history = this.getHistory();
|
|
391
|
-
const totalRenders = history.length;
|
|
392
|
-
const totalRenderTime = this.getGlobalTotalRenderTime();
|
|
393
|
-
const averageRenderTime = this.getGlobalAverageRenderTime();
|
|
394
|
-
|
|
395
|
-
// Find slowest component
|
|
396
|
-
const slowestComponent =
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
)
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
*
|
|
541
|
-
*
|
|
542
|
-
*
|
|
543
|
-
* ```
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
(window
|
|
575
|
-
|
|
576
|
-
//
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
1
|
+
/**
|
|
2
|
+
* @lytjs/common-performance
|
|
3
|
+
* Performance monitoring API for component render timing
|
|
4
|
+
* FIX: P2-11 RUNTIME-NEW-02 - 性能监控 API
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
declare const __DEV__: boolean;
|
|
8
|
+
|
|
9
|
+
import { warn } from '@lytjs/common-error';
|
|
10
|
+
|
|
11
|
+
// ==================== Types ====================
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Performance entry for a single render operation
|
|
15
|
+
*/
|
|
16
|
+
export interface RenderPerformanceEntry {
|
|
17
|
+
/** Component name or identifier */
|
|
18
|
+
componentName: string;
|
|
19
|
+
|
|
20
|
+
/** Type of operation */
|
|
21
|
+
operation: 'mount' | 'patch' | 'unmount';
|
|
22
|
+
|
|
23
|
+
/** Start time (high resolution timestamp) */
|
|
24
|
+
startTime: number;
|
|
25
|
+
|
|
26
|
+
/** End time (high resolution timestamp) */
|
|
27
|
+
endTime: number;
|
|
28
|
+
|
|
29
|
+
/** Duration in milliseconds */
|
|
30
|
+
duration: number;
|
|
31
|
+
|
|
32
|
+
/** Additional metadata */
|
|
33
|
+
metadata?: Record<string, unknown>;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Component performance statistics
|
|
38
|
+
*/
|
|
39
|
+
export interface ComponentPerformanceStats {
|
|
40
|
+
/** Component name */
|
|
41
|
+
componentName: string;
|
|
42
|
+
|
|
43
|
+
/** Number of mount operations */
|
|
44
|
+
mountCount: number;
|
|
45
|
+
|
|
46
|
+
/** Number of patch (update) operations */
|
|
47
|
+
patchCount: number;
|
|
48
|
+
|
|
49
|
+
/** Number of unmount operations */
|
|
50
|
+
unmountCount: number;
|
|
51
|
+
|
|
52
|
+
/** Total time spent in mount operations (ms) */
|
|
53
|
+
totalMountTime: number;
|
|
54
|
+
|
|
55
|
+
/** Total time spent in patch operations (ms) */
|
|
56
|
+
totalPatchTime: number;
|
|
57
|
+
|
|
58
|
+
/** Total time spent in unmount operations (ms) */
|
|
59
|
+
totalUnmountTime: number;
|
|
60
|
+
|
|
61
|
+
/** Average mount time (ms) */
|
|
62
|
+
averageMountTime: number;
|
|
63
|
+
|
|
64
|
+
/** Average patch time (ms) */
|
|
65
|
+
averagePatchTime: number;
|
|
66
|
+
|
|
67
|
+
/** Average unmount time (ms) */
|
|
68
|
+
averageUnmountTime: number;
|
|
69
|
+
|
|
70
|
+
/** Maximum mount time (ms) */
|
|
71
|
+
maxMountTime: number;
|
|
72
|
+
|
|
73
|
+
/** Maximum patch time (ms) */
|
|
74
|
+
maxPatchTime: number;
|
|
75
|
+
|
|
76
|
+
/** Maximum unmount time (ms) */
|
|
77
|
+
maxUnmountTime: number;
|
|
78
|
+
|
|
79
|
+
/** Last render timestamp */
|
|
80
|
+
lastRenderTime: number;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Performance monitor options
|
|
85
|
+
*/
|
|
86
|
+
export interface PerformanceMonitorOptions {
|
|
87
|
+
/** Maximum number of entries to keep in history */
|
|
88
|
+
maxHistorySize?: number;
|
|
89
|
+
|
|
90
|
+
/** Whether to enable monitoring by default */
|
|
91
|
+
enabled?: boolean;
|
|
92
|
+
|
|
93
|
+
/** Callback when a new entry is recorded */
|
|
94
|
+
onEntry?: (entry: RenderPerformanceEntry) => void;
|
|
95
|
+
|
|
96
|
+
/** Callback when stats are updated */
|
|
97
|
+
onStatsUpdate?: (stats: ComponentPerformanceStats) => void;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// ==================== Performance Monitor Class ====================
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* PerformanceMonitor - Tracks component render performance
|
|
104
|
+
*
|
|
105
|
+
* @example
|
|
106
|
+
* ```ts
|
|
107
|
+
* const monitor = new PerformanceMonitor({ maxHistorySize: 100 });
|
|
108
|
+
*
|
|
109
|
+
* // Start timing a render
|
|
110
|
+
* const endTiming = monitor.startTiming('MyComponent', 'patch');
|
|
111
|
+
*
|
|
112
|
+
* // ... perform render ...
|
|
113
|
+
*
|
|
114
|
+
* // End timing
|
|
115
|
+
* endTiming();
|
|
116
|
+
*
|
|
117
|
+
* // Get stats
|
|
118
|
+
* const stats = monitor.getStats('MyComponent');
|
|
119
|
+
* console.log(`Average render time: ${stats?.averagePatchTime}ms`);
|
|
120
|
+
* ```
|
|
121
|
+
*/
|
|
122
|
+
export class PerformanceMonitor {
|
|
123
|
+
// FIX: P2-v11-17 使用环形缓冲区替代数组 + shift(),
|
|
124
|
+
// 避免 history.shift() 的 O(n) 时间复杂度
|
|
125
|
+
private historyBuffer: (RenderPerformanceEntry | null)[] = [];
|
|
126
|
+
private historyHead = 0;
|
|
127
|
+
private historyCount = 0;
|
|
128
|
+
private stats: Map<string, ComponentPerformanceStats> = new Map();
|
|
129
|
+
private options: Required<PerformanceMonitorOptions>;
|
|
130
|
+
private _enabled: boolean;
|
|
131
|
+
|
|
132
|
+
constructor(options: PerformanceMonitorOptions = {}) {
|
|
133
|
+
this.options = {
|
|
134
|
+
maxHistorySize: options.maxHistorySize ?? 1000,
|
|
135
|
+
enabled: options.enabled ?? true,
|
|
136
|
+
onEntry: options.onEntry ?? (() => {}),
|
|
137
|
+
onStatsUpdate: options.onStatsUpdate ?? (() => {}),
|
|
138
|
+
};
|
|
139
|
+
this._enabled = this.options.enabled;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Check if monitoring is enabled
|
|
144
|
+
*/
|
|
145
|
+
get enabled(): boolean {
|
|
146
|
+
return this._enabled;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Enable or disable monitoring
|
|
151
|
+
*/
|
|
152
|
+
set enabled(value: boolean) {
|
|
153
|
+
this._enabled = value;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Start timing a render operation.
|
|
158
|
+
* Returns a function that should be called when the operation completes.
|
|
159
|
+
*
|
|
160
|
+
* @param componentName - Name of the component being rendered
|
|
161
|
+
* @param operation - Type of operation (mount, patch, unmount)
|
|
162
|
+
* @param metadata - Optional metadata to attach to the entry
|
|
163
|
+
* @returns A function to end the timing
|
|
164
|
+
*
|
|
165
|
+
* @example
|
|
166
|
+
* ```ts
|
|
167
|
+
* const endTiming = monitor.startTiming('MyComponent', 'patch');
|
|
168
|
+
* // ... render logic ...
|
|
169
|
+
* endTiming({ propsChanged: true });
|
|
170
|
+
* ```
|
|
171
|
+
*/
|
|
172
|
+
startTiming(
|
|
173
|
+
componentName: string,
|
|
174
|
+
operation: 'mount' | 'patch' | 'unmount',
|
|
175
|
+
metadata?: Record<string, unknown>,
|
|
176
|
+
): (endMetadata?: Record<string, unknown>) => RenderPerformanceEntry | null {
|
|
177
|
+
if (!this._enabled) {
|
|
178
|
+
return () => null;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// FIX: P2-v11-16 添加 performance.now() 回退,
|
|
182
|
+
// 在非浏览器环境(如 SSR、Node.js)中使用 Date.now() 替代
|
|
183
|
+
const getTimestamp = (): number => {
|
|
184
|
+
if (typeof performance !== 'undefined' && typeof performance.now === 'function') {
|
|
185
|
+
return performance.now();
|
|
186
|
+
}
|
|
187
|
+
return Date.now();
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
const startTime = getTimestamp();
|
|
191
|
+
|
|
192
|
+
return (endMetadata?: Record<string, unknown>) => {
|
|
193
|
+
const endTime = getTimestamp();
|
|
194
|
+
const duration = endTime - startTime;
|
|
195
|
+
|
|
196
|
+
const entry: RenderPerformanceEntry = {
|
|
197
|
+
componentName,
|
|
198
|
+
operation,
|
|
199
|
+
startTime,
|
|
200
|
+
endTime,
|
|
201
|
+
duration,
|
|
202
|
+
metadata: { ...metadata, ...endMetadata },
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
this.recordEntry(entry);
|
|
206
|
+
return entry;
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Record a performance entry directly
|
|
212
|
+
*/
|
|
213
|
+
recordEntry(entry: RenderPerformanceEntry): void {
|
|
214
|
+
if (!this._enabled) return;
|
|
215
|
+
|
|
216
|
+
// FIX: P2-v11-17 使用环形缓冲区写入,O(1) 操作
|
|
217
|
+
if (this.historyBuffer.length < this.options.maxHistorySize) {
|
|
218
|
+
this.historyBuffer.push(entry);
|
|
219
|
+
this.historyCount++;
|
|
220
|
+
} else {
|
|
221
|
+
this.historyBuffer[this.historyHead] = entry;
|
|
222
|
+
this.historyHead = (this.historyHead + 1) % this.options.maxHistorySize;
|
|
223
|
+
// historyCount 保持为 maxHistorySize
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Update stats
|
|
227
|
+
this.updateStats(entry);
|
|
228
|
+
|
|
229
|
+
// Notify callback
|
|
230
|
+
this.options.onEntry(entry);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Update statistics for a component
|
|
235
|
+
*/
|
|
236
|
+
private updateStats(entry: RenderPerformanceEntry): void {
|
|
237
|
+
const { componentName, operation, duration } = entry;
|
|
238
|
+
|
|
239
|
+
let stats = this.stats.get(componentName);
|
|
240
|
+
if (!stats) {
|
|
241
|
+
stats = {
|
|
242
|
+
componentName,
|
|
243
|
+
mountCount: 0,
|
|
244
|
+
patchCount: 0,
|
|
245
|
+
unmountCount: 0,
|
|
246
|
+
totalMountTime: 0,
|
|
247
|
+
totalPatchTime: 0,
|
|
248
|
+
totalUnmountTime: 0,
|
|
249
|
+
averageMountTime: 0,
|
|
250
|
+
averagePatchTime: 0,
|
|
251
|
+
averageUnmountTime: 0,
|
|
252
|
+
maxMountTime: 0,
|
|
253
|
+
maxPatchTime: 0,
|
|
254
|
+
maxUnmountTime: 0,
|
|
255
|
+
lastRenderTime: Date.now(),
|
|
256
|
+
};
|
|
257
|
+
this.stats.set(componentName, stats);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Update counters and times
|
|
261
|
+
switch (operation) {
|
|
262
|
+
case 'mount':
|
|
263
|
+
stats.mountCount++;
|
|
264
|
+
stats.totalMountTime += duration;
|
|
265
|
+
stats.averageMountTime = stats.totalMountTime / stats.mountCount;
|
|
266
|
+
stats.maxMountTime = Math.max(stats.maxMountTime, duration);
|
|
267
|
+
break;
|
|
268
|
+
case 'patch':
|
|
269
|
+
stats.patchCount++;
|
|
270
|
+
stats.totalPatchTime += duration;
|
|
271
|
+
stats.averagePatchTime = stats.totalPatchTime / stats.patchCount;
|
|
272
|
+
stats.maxPatchTime = Math.max(stats.maxPatchTime, duration);
|
|
273
|
+
break;
|
|
274
|
+
case 'unmount':
|
|
275
|
+
stats.unmountCount++;
|
|
276
|
+
stats.totalUnmountTime += duration;
|
|
277
|
+
stats.averageUnmountTime = stats.totalUnmountTime / stats.unmountCount;
|
|
278
|
+
stats.maxUnmountTime = Math.max(stats.maxUnmountTime, duration);
|
|
279
|
+
break;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
stats.lastRenderTime = Date.now();
|
|
283
|
+
this.options.onStatsUpdate(stats);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Get performance statistics for a specific component
|
|
288
|
+
*/
|
|
289
|
+
getStats(componentName: string): ComponentPerformanceStats | undefined {
|
|
290
|
+
return this.stats.get(componentName);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Get all component statistics
|
|
295
|
+
*/
|
|
296
|
+
getAllStats(): ComponentPerformanceStats[] {
|
|
297
|
+
return Array.from(this.stats.values());
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Get performance history
|
|
302
|
+
* FIX: P2-v11-17 从环形缓冲区读取历史记录
|
|
303
|
+
*/
|
|
304
|
+
getHistory(): RenderPerformanceEntry[] {
|
|
305
|
+
const result: RenderPerformanceEntry[] = [];
|
|
306
|
+
for (let i = 0; i < this.historyCount; i++) {
|
|
307
|
+
const idx = (this.historyHead + i) % this.historyBuffer.length;
|
|
308
|
+
const entry = this.historyBuffer[idx];
|
|
309
|
+
if (entry) result.push(entry);
|
|
310
|
+
}
|
|
311
|
+
return result;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Get history for a specific component
|
|
316
|
+
*/
|
|
317
|
+
getComponentHistory(componentName: string): RenderPerformanceEntry[] {
|
|
318
|
+
return this.getHistory().filter((entry) => entry.componentName === componentName);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Get history for a specific operation type
|
|
323
|
+
*/
|
|
324
|
+
getOperationHistory(operation: 'mount' | 'patch' | 'unmount'): RenderPerformanceEntry[] {
|
|
325
|
+
return this.getHistory().filter((entry) => entry.operation === operation);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Get the slowest renders
|
|
330
|
+
*/
|
|
331
|
+
getSlowestRenders(limit: number = 10): RenderPerformanceEntry[] {
|
|
332
|
+
return this.getHistory()
|
|
333
|
+
.sort((a, b) => b.duration - a.duration)
|
|
334
|
+
.slice(0, limit);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Get average render time across all components
|
|
339
|
+
*/
|
|
340
|
+
getGlobalAverageRenderTime(): number {
|
|
341
|
+
const history = this.getHistory();
|
|
342
|
+
if (history.length === 0) return 0;
|
|
343
|
+
const total = history.reduce((sum, entry) => sum + entry.duration, 0);
|
|
344
|
+
return total / history.length;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Get total render time across all components
|
|
349
|
+
*/
|
|
350
|
+
getGlobalTotalRenderTime(): number {
|
|
351
|
+
return this.getHistory().reduce((sum, entry) => sum + entry.duration, 0);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Clear all history and stats
|
|
356
|
+
* FIX: P2 统一 clear/clearComponent 策略:使用 fill(null) 预分配固定大小缓冲区,
|
|
357
|
+
* 与 clearComponent 保持一致的环形缓冲区重建方式,避免 clear 后缓冲区大小丢失
|
|
358
|
+
*/
|
|
359
|
+
clear(): void {
|
|
360
|
+
this.historyBuffer = new Array(this.options.maxHistorySize).fill(null);
|
|
361
|
+
this.historyHead = 0;
|
|
362
|
+
this.historyCount = 0;
|
|
363
|
+
this.stats.clear();
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Clear history and stats for a specific component
|
|
368
|
+
*/
|
|
369
|
+
clearComponent(componentName: string): boolean {
|
|
370
|
+
// FIX: P2-batch2-12 修复 clearComponent 破坏环形缓冲区的问题。
|
|
371
|
+
// 原实现直接将过滤后的数组赋值给 historyBuffer 并重置 head/tail,
|
|
372
|
+
// 但丢失了环形缓冲区的固定大小约束。正确做法是重建固定大小的缓冲区。
|
|
373
|
+
const filtered = this.getHistory().filter((entry) => entry.componentName !== componentName);
|
|
374
|
+
// 重建固定大小的环形缓冲区
|
|
375
|
+
this.historyBuffer = new Array(this.options.maxHistorySize).fill(null);
|
|
376
|
+
this.historyHead = 0;
|
|
377
|
+
this.historyCount = filtered.length;
|
|
378
|
+
for (let i = 0; i < filtered.length; i++) {
|
|
379
|
+
// FIX: DTS build error - 类型匹配
|
|
380
|
+
this.historyBuffer[i] = filtered[i] as RenderPerformanceEntry | null;
|
|
381
|
+
}
|
|
382
|
+
return this.stats.delete(componentName);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Generate a performance report
|
|
387
|
+
*/
|
|
388
|
+
generateReport(): PerformanceReport {
|
|
389
|
+
const allStats = this.getAllStats();
|
|
390
|
+
const history = this.getHistory();
|
|
391
|
+
const totalRenders = history.length;
|
|
392
|
+
const totalRenderTime = this.getGlobalTotalRenderTime();
|
|
393
|
+
const averageRenderTime = this.getGlobalAverageRenderTime();
|
|
394
|
+
|
|
395
|
+
// Find slowest component
|
|
396
|
+
const slowestComponent =
|
|
397
|
+
allStats.length > 0
|
|
398
|
+
? allStats.reduce((slowest, current) => {
|
|
399
|
+
const currentAvg = current.averagePatchTime || current.averageMountTime;
|
|
400
|
+
const slowestAvg = slowest.averagePatchTime || slowest.averageMountTime;
|
|
401
|
+
return currentAvg > slowestAvg ? current : slowest;
|
|
402
|
+
})
|
|
403
|
+
: null;
|
|
404
|
+
|
|
405
|
+
// Find most rendered component
|
|
406
|
+
const mostRenderedComponent =
|
|
407
|
+
allStats.length > 0
|
|
408
|
+
? allStats.reduce((most, current) =>
|
|
409
|
+
current.patchCount > most.patchCount ? current : most,
|
|
410
|
+
)
|
|
411
|
+
: null;
|
|
412
|
+
|
|
413
|
+
return {
|
|
414
|
+
timestamp: Date.now(),
|
|
415
|
+
totalRenders,
|
|
416
|
+
totalRenderTime,
|
|
417
|
+
averageRenderTime,
|
|
418
|
+
componentCount: allStats.length,
|
|
419
|
+
slowestComponent: slowestComponent?.componentName ?? null,
|
|
420
|
+
mostRenderedComponent: mostRenderedComponent?.componentName ?? null,
|
|
421
|
+
componentStats: allStats,
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Performance report structure
|
|
428
|
+
*/
|
|
429
|
+
export interface PerformanceReport {
|
|
430
|
+
/** Report generation timestamp */
|
|
431
|
+
timestamp: number;
|
|
432
|
+
|
|
433
|
+
/** Total number of render operations */
|
|
434
|
+
totalRenders: number;
|
|
435
|
+
|
|
436
|
+
/** Total time spent rendering (ms) */
|
|
437
|
+
totalRenderTime: number;
|
|
438
|
+
|
|
439
|
+
/** Average render time (ms) */
|
|
440
|
+
averageRenderTime: number;
|
|
441
|
+
|
|
442
|
+
/** Number of unique components tracked */
|
|
443
|
+
componentCount: number;
|
|
444
|
+
|
|
445
|
+
/** Name of the slowest component (by average render time) */
|
|
446
|
+
slowestComponent: string | null;
|
|
447
|
+
|
|
448
|
+
/** Name of the most rendered component */
|
|
449
|
+
mostRenderedComponent: string | null;
|
|
450
|
+
|
|
451
|
+
/** Detailed stats for each component */
|
|
452
|
+
componentStats: ComponentPerformanceStats[];
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// ==================== Global Instance ====================
|
|
456
|
+
|
|
457
|
+
/** Global performance monitor instance */
|
|
458
|
+
let globalMonitor: PerformanceMonitor | null = null;
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* Get the global performance monitor instance
|
|
462
|
+
* Creates one if it doesn't exist
|
|
463
|
+
*/
|
|
464
|
+
export function getPerformanceMonitor(): PerformanceMonitor {
|
|
465
|
+
if (!globalMonitor) {
|
|
466
|
+
globalMonitor = new PerformanceMonitor();
|
|
467
|
+
}
|
|
468
|
+
return globalMonitor;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* Set the global performance monitor instance
|
|
473
|
+
*/
|
|
474
|
+
export function setPerformanceMonitor(monitor: PerformanceMonitor): void {
|
|
475
|
+
globalMonitor = monitor;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* Initialize the global performance monitor with options
|
|
480
|
+
*/
|
|
481
|
+
export function initPerformanceMonitor(
|
|
482
|
+
options: PerformanceMonitorOptions = {},
|
|
483
|
+
): PerformanceMonitor {
|
|
484
|
+
globalMonitor = new PerformanceMonitor(options);
|
|
485
|
+
return globalMonitor;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// ==================== Convenience Functions ====================
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* Start timing a component render operation using the global monitor
|
|
492
|
+
*/
|
|
493
|
+
export function startRenderTiming(
|
|
494
|
+
componentName: string,
|
|
495
|
+
operation: 'mount' | 'patch' | 'unmount',
|
|
496
|
+
metadata?: Record<string, unknown>,
|
|
497
|
+
): (endMetadata?: Record<string, unknown>) => RenderPerformanceEntry | null {
|
|
498
|
+
return getPerformanceMonitor().startTiming(componentName, operation, metadata);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
/**
|
|
502
|
+
* Record a render entry using the global monitor
|
|
503
|
+
*/
|
|
504
|
+
export function recordRenderEntry(entry: RenderPerformanceEntry): void {
|
|
505
|
+
getPerformanceMonitor().recordEntry(entry);
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
/**
|
|
509
|
+
* Get component stats from the global monitor
|
|
510
|
+
*/
|
|
511
|
+
export function getComponentStats(componentName: string): ComponentPerformanceStats | undefined {
|
|
512
|
+
return getPerformanceMonitor().getStats(componentName);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
/**
|
|
516
|
+
* Generate a performance report from the global monitor
|
|
517
|
+
*/
|
|
518
|
+
export function generatePerformanceReport(): PerformanceReport {
|
|
519
|
+
return getPerformanceMonitor().generateReport();
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
/**
|
|
523
|
+
* Check if performance monitoring is enabled
|
|
524
|
+
*/
|
|
525
|
+
export function isPerformanceMonitoringEnabled(): boolean {
|
|
526
|
+
return globalMonitor?.enabled ?? false;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
/**
|
|
530
|
+
* Enable or disable performance monitoring
|
|
531
|
+
*/
|
|
532
|
+
export function setPerformanceMonitoringEnabled(enabled: boolean): void {
|
|
533
|
+
const monitor = getPerformanceMonitor();
|
|
534
|
+
monitor.enabled = enabled;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
// ==================== Decorator / Wrapper ====================
|
|
538
|
+
|
|
539
|
+
/**
|
|
540
|
+
* Wrap a render function with performance monitoring
|
|
541
|
+
*
|
|
542
|
+
* @example
|
|
543
|
+
* ```ts
|
|
544
|
+
* const monitoredRender = withPerformanceTracking('MyComponent', (props) => {
|
|
545
|
+
* return h('div', props.content);
|
|
546
|
+
* });
|
|
547
|
+
* ```
|
|
548
|
+
*/
|
|
549
|
+
export function withPerformanceTracking<T extends (...args: unknown[]) => unknown>(
|
|
550
|
+
componentName: string,
|
|
551
|
+
renderFn: T,
|
|
552
|
+
operation: 'mount' | 'patch' | 'unmount' = 'patch',
|
|
553
|
+
): T {
|
|
554
|
+
return ((...args: unknown[]) => {
|
|
555
|
+
const endTiming = startRenderTiming(componentName, operation);
|
|
556
|
+
try {
|
|
557
|
+
const result = renderFn(...args);
|
|
558
|
+
endTiming();
|
|
559
|
+
return result;
|
|
560
|
+
} catch (error) {
|
|
561
|
+
endTiming({ error: true });
|
|
562
|
+
throw error;
|
|
563
|
+
}
|
|
564
|
+
}) as T;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
// ==================== DevTools Integration ====================
|
|
568
|
+
|
|
569
|
+
/**
|
|
570
|
+
* Connect performance monitor to browser DevTools
|
|
571
|
+
* Only works in development mode
|
|
572
|
+
*/
|
|
573
|
+
export function connectToDevTools(): void {
|
|
574
|
+
if (typeof window === 'undefined') return;
|
|
575
|
+
|
|
576
|
+
// Expose monitor to window for DevTools access
|
|
577
|
+
// FIX: DTS build error - 先转换为 unknown 再转换为 Record
|
|
578
|
+
(window as unknown as Record<string, unknown>).__LYTJS_PERFORMANCE_MONITOR__ =
|
|
579
|
+
getPerformanceMonitor();
|
|
580
|
+
|
|
581
|
+
// Mark initialization for DevTools detection
|
|
582
|
+
(window as unknown as Record<string, unknown>).__LYTJS_DEVTOOLS_HOOK__ = true;
|
|
583
|
+
|
|
584
|
+
if (__DEV__) {
|
|
585
|
+
// FIX: P1-14 删除 require 调用,直接使用顶部已导入的 warn
|
|
586
|
+
warn('Performance monitor connected to DevTools');
|
|
587
|
+
}
|
|
588
|
+
}
|