@buoy-gg/highlight-updates 2.1.9 → 2.1.11

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.
Files changed (65) hide show
  1. package/lib/commonjs/highlight-updates/HighlightUpdatesOverlay.js +285 -1
  2. package/lib/commonjs/highlight-updates/components/HighlightFilterView.js +1371 -1
  3. package/lib/commonjs/highlight-updates/components/HighlightUpdatesModal.js +591 -1
  4. package/lib/commonjs/highlight-updates/components/IdentifierBadge.js +267 -1
  5. package/lib/commonjs/highlight-updates/components/IsolatedRenderList.js +178 -1
  6. package/lib/commonjs/highlight-updates/components/ModalHeaderContent.js +303 -1
  7. package/lib/commonjs/highlight-updates/components/RenderCauseBadge.js +500 -1
  8. package/lib/commonjs/highlight-updates/components/RenderDetailView.js +830 -1
  9. package/lib/commonjs/highlight-updates/components/RenderHistoryViewer.js +894 -1
  10. package/lib/commonjs/highlight-updates/components/RenderListItem.js +220 -1
  11. package/lib/commonjs/highlight-updates/components/StatsDisplay.js +70 -1
  12. package/lib/commonjs/highlight-updates/components/index.js +97 -1
  13. package/lib/commonjs/highlight-updates/utils/HighlightUpdatesController.js +1435 -1
  14. package/lib/commonjs/highlight-updates/utils/PerformanceLogger.js +359 -1
  15. package/lib/commonjs/highlight-updates/utils/ProfilerInterceptor.js +371 -1
  16. package/lib/commonjs/highlight-updates/utils/RenderCauseDetector.js +1828 -1
  17. package/lib/commonjs/highlight-updates/utils/RenderTracker.js +903 -1
  18. package/lib/commonjs/highlight-updates/utils/ViewTypeMapper.js +264 -1
  19. package/lib/commonjs/highlight-updates/utils/renderExportFormatter.js +58 -1
  20. package/lib/commonjs/index.js +311 -1
  21. package/lib/commonjs/preset.js +278 -1
  22. package/lib/module/highlight-updates/HighlightUpdatesOverlay.js +278 -1
  23. package/lib/module/highlight-updates/components/HighlightFilterView.js +1365 -1
  24. package/lib/module/highlight-updates/components/HighlightUpdatesModal.js +585 -1
  25. package/lib/module/highlight-updates/components/IdentifierBadge.js +259 -1
  26. package/lib/module/highlight-updates/components/IsolatedRenderList.js +174 -1
  27. package/lib/module/highlight-updates/components/ModalHeaderContent.js +298 -1
  28. package/lib/module/highlight-updates/components/RenderCauseBadge.js +491 -1
  29. package/lib/module/highlight-updates/components/RenderDetailView.js +826 -1
  30. package/lib/module/highlight-updates/components/RenderHistoryViewer.js +888 -1
  31. package/lib/module/highlight-updates/components/RenderListItem.js +215 -1
  32. package/lib/module/highlight-updates/components/StatsDisplay.js +67 -1
  33. package/lib/module/highlight-updates/components/index.js +16 -1
  34. package/lib/module/highlight-updates/utils/HighlightUpdatesController.js +1431 -1
  35. package/lib/module/highlight-updates/utils/PerformanceLogger.js +353 -1
  36. package/lib/module/highlight-updates/utils/ProfilerInterceptor.js +358 -1
  37. package/lib/module/highlight-updates/utils/RenderCauseDetector.js +1818 -1
  38. package/lib/module/highlight-updates/utils/RenderTracker.js +900 -1
  39. package/lib/module/highlight-updates/utils/ViewTypeMapper.js +255 -1
  40. package/lib/module/highlight-updates/utils/renderExportFormatter.js +54 -1
  41. package/lib/module/index.js +71 -1
  42. package/lib/module/preset.js +272 -1
  43. package/lib/typescript/highlight-updates/HighlightUpdatesOverlay.d.ts.map +1 -0
  44. package/lib/typescript/highlight-updates/components/HighlightFilterView.d.ts.map +1 -0
  45. package/lib/typescript/highlight-updates/components/HighlightUpdatesModal.d.ts.map +1 -0
  46. package/lib/typescript/highlight-updates/components/IdentifierBadge.d.ts.map +1 -0
  47. package/lib/typescript/highlight-updates/components/IsolatedRenderList.d.ts.map +1 -0
  48. package/lib/typescript/highlight-updates/components/ModalHeaderContent.d.ts.map +1 -0
  49. package/lib/typescript/highlight-updates/components/RenderCauseBadge.d.ts.map +1 -0
  50. package/lib/typescript/highlight-updates/components/RenderDetailView.d.ts.map +1 -0
  51. package/lib/typescript/highlight-updates/components/RenderHistoryViewer.d.ts.map +1 -0
  52. package/lib/typescript/highlight-updates/components/RenderListItem.d.ts.map +1 -0
  53. package/lib/typescript/highlight-updates/components/StatsDisplay.d.ts.map +1 -0
  54. package/lib/typescript/highlight-updates/components/index.d.ts.map +1 -0
  55. package/lib/typescript/highlight-updates/utils/HighlightUpdatesController.d.ts.map +1 -0
  56. package/lib/typescript/highlight-updates/utils/PerformanceLogger.d.ts.map +1 -0
  57. package/lib/typescript/highlight-updates/utils/ProfilerInterceptor.d.ts.map +1 -0
  58. package/lib/typescript/highlight-updates/utils/RenderCauseDetector.d.ts.map +1 -0
  59. package/lib/typescript/highlight-updates/utils/RenderTracker.d.ts.map +1 -0
  60. package/lib/typescript/highlight-updates/utils/ViewTypeMapper.d.ts.map +1 -0
  61. package/lib/typescript/highlight-updates/utils/renderExportFormatter.d.ts.map +1 -0
  62. package/lib/typescript/index.d.ts.map +1 -0
  63. package/lib/typescript/preset.d.ts.map +1 -0
  64. package/package.json +16 -16
  65. package/LICENSE +0 -58
@@ -1 +1,353 @@
1
- "use strict";let lastEventTimestamp=0,pendingBatchId=null;export function markEventReceived(){return lastEventTimestamp=performance.now(),lastEventTimestamp}export function markOverlayRendered(t,e){lastEventTimestamp>0&&t>0&&(lastEventTimestamp=0)}class PerformanceLoggerSingleton{enabled=!1;batchCounter=0;listeners=new Set;rollingStats={batchCount:0,totalNodes:0,totalFiltered:0,totalProcessed:0,totalTime:0,maxTime:0,minTime:1/0,avgMeasurementTime:0};recentBatches=[];MAX_HISTORY=100;summaryInterval=null;SUMMARY_INTERVAL_MS=1e4;setEnabled(t){const e=this.enabled;this.enabled=t,t&&!e?this.resetStats():!t&&e&&this.stopSummaryInterval()}isEnabled(){return this.enabled}startBatch(t,e){const a="batch_"+ ++this.batchCounter,n=performance.now();let r,i=null,s=null,o=null,l=null,m=null,c=0,h=0,d=0,g=0;return{markFilteringComplete:(t,e)=>{i=performance.now(),c=t,h=e},markMeasurementStart:()=>{s=performance.now()},markMeasurementComplete:(t,e)=>{o=performance.now(),d=t,g=e},markTrackingComplete:()=>{l=performance.now()},markCallbackComplete:()=>{m=performance.now()},setOverlayRenderTime:t=>{r=t},getBatchId:()=>a,finish:()=>{const u=performance.now(),T={batchId:a,timestamp:Date.now(),nodesReceived:t,nodesFiltered:c,nodesToProcess:h,batchSize:e,nodesInBatch:Math.min(h,e),filteringTime:i?i-n:0,measurementTime:s&&o?o-s:0,trackingTime:o&&l?l-o:0,callbackTime:l&&m?m-l:0,totalTime:u-n,measurementSuccessCount:d,measurementFailCount:g,overlayRenderTime:r};return this.enabled&&this.recordMetrics(T),T}}}recordMetrics(t){this.rollingStats.batchCount++,this.rollingStats.totalNodes+=t.nodesReceived,this.rollingStats.totalFiltered+=t.nodesFiltered,this.rollingStats.totalProcessed+=t.nodesInBatch,this.rollingStats.totalTime+=t.totalTime,this.rollingStats.maxTime=Math.max(this.rollingStats.maxTime,t.totalTime),this.rollingStats.minTime=Math.min(this.rollingStats.minTime,t.totalTime);const e=this.rollingStats.avgMeasurementTime,a=this.rollingStats.batchCount;this.rollingStats.avgMeasurementTime=e+(t.measurementTime-e)/a,this.recentBatches.push(t),this.recentBatches.length>this.MAX_HISTORY&&this.recentBatches.shift(),this.notifyListeners(t)}logBatch(t){const{batchId:e,nodesReceived:a,nodesFiltered:n,nodesInBatch:r,batchSize:i,filteringTime:s,measurementTime:o,trackingTime:l,callbackTime:m,totalTime:c,measurementSuccessCount:h,measurementFailCount:d,overlayRenderTime:g}=t;console.log(`[HighlightPerf] ${e} | In:${a} Filt:${n} Proc:${r}/${i} | Filter:${s.toFixed(1)}ms Measure:${o.toFixed(1)}ms Track:${l.toFixed(1)}ms Callback:${m.toFixed(1)}ms | Total:${c.toFixed(1)}ms`+(g?` Render:${g.toFixed(1)}ms`:"")+` | Success:${h} Fail:${d}`),c>100&&console.warn(`[HighlightPerf] ⚠️ SLOW BATCH: ${c.toFixed(1)}ms - Measurement phase: ${o.toFixed(1)}ms (${(o/c*100).toFixed(0)}%)`)}logSummary(){if(0===this.rollingStats.batchCount)return;const t=this.rollingStats,e=t.totalTime/t.batchCount,a=t.totalNodes/t.batchCount;console.log(`\n[HighlightPerf] ════════ SUMMARY (last ${this.SUMMARY_INTERVAL_MS/1e3}s) ════════\n Batches: ${t.batchCount}\n Avg nodes/batch: ${a.toFixed(1)} (filtered: ${(t.totalFiltered/t.batchCount).toFixed(1)})\n Avg total time: ${e.toFixed(1)}ms\n Avg measurement time: ${t.avgMeasurementTime.toFixed(1)}ms\n Min/Max time: ${t.minTime.toFixed(1)}ms / ${t.maxTime.toFixed(1)}ms\n══════════════════════════════════════════════\n`),this.resetStats()}resetStats(){this.rollingStats={batchCount:0,totalNodes:0,totalFiltered:0,totalProcessed:0,totalTime:0,maxTime:0,minTime:1/0,avgMeasurementTime:0}}startSummaryInterval(){this.stopSummaryInterval(),this.summaryInterval=setInterval(()=>{this.logSummary()},this.SUMMARY_INTERVAL_MS)}stopSummaryInterval(){this.summaryInterval&&(clearInterval(this.summaryInterval),this.summaryInterval=null)}subscribe(t){return this.listeners.add(t),()=>{this.listeners.delete(t)}}notifyListeners(t){for(const e of this.listeners)try{e(t)}catch(t){console.error("[HighlightPerf] Error in metrics listener:",t)}}getRecentBatches(){return[...this.recentBatches]}getRollingStats(){return{...this.rollingStats}}clear(){this.recentBatches=[],this.resetStats(),this.batchCounter=0}generateReport(){const t=this.recentBatches;if(0===t.length)return"No performance data collected yet.";const e=t.reduce((t,e)=>t+e.totalTime,0)/t.length,a=t.reduce((t,e)=>t+e.measurementTime,0)/t.length,n=t.filter(t=>t.totalTime>100),r=t.filter(t=>t.totalTime<20);return`\n╔══════════════════════════════════════════════════════════════╗\n║ HIGHLIGHT UPDATES PERFORMANCE REPORT ║\n╠══════════════════════════════════════════════════════════════╣\n║ Total batches analyzed: ${t.length.toString().padStart(5)} ║\n║ Average total time: ${e.toFixed(1).padStart(5)}ms ║\n║ Average measure time: ${a.toFixed(1).padStart(5)}ms (${(a/e*100).toFixed(0)}% of total) ║\n╠══════════════════════════════════════════════════════════════╣\n║ Fast batches (<20ms): ${r.length.toString().padStart(5)} (${(r.length/t.length*100).toFixed(0)}%) ║\n║ Slow batches (>100ms): ${n.length.toString().padStart(5)} (${(n.length/t.length*100).toFixed(0)}%) ║\n╠══════════════════════════════════════════════════════════════╣\n║ Time breakdown (avg): ║\n║ Filtering: ${t.reduce((t,e)=>t+e.filteringTime,0/t.length).toFixed(1).padStart(6)}ms ║\n║ Measurement: ${a.toFixed(1).padStart(6)}ms ← Primary bottleneck ║\n║ Tracking: ${(t.reduce((t,e)=>t+e.trackingTime,0)/t.length).toFixed(1).padStart(6)}ms ║\n║ Callback: ${(t.reduce((t,e)=>t+e.callbackTime,0)/t.length).toFixed(1).padStart(6)}ms ║\n╚══════════════════════════════════════════════════════════════╝\n`}}export const PerformanceLogger=new PerformanceLoggerSingleton;export default PerformanceLogger;
1
+ /**
2
+ * PerformanceLogger
3
+ *
4
+ * Dedicated performance measurement utility for the Highlight Updates feature.
5
+ * Tracks timing metrics across the render detection pipeline to identify bottlenecks
6
+ * and measure optimization improvements.
7
+ *
8
+ * Usage:
9
+ * PerformanceLogger.setEnabled(true); // Enable logging
10
+ * const batch = PerformanceLogger.startBatch(nodesReceived);
11
+ * batch.markFilteringComplete(nodesFiltered, nodesToProcess);
12
+ * batch.markMeasurementComplete(successCount, failCount);
13
+ * batch.markTrackingComplete();
14
+ * batch.markCallbackComplete();
15
+ * batch.finish(); // Logs the complete metrics
16
+ */
17
+
18
+ "use strict";
19
+
20
+ // Declare performance API available in React Native's JavaScript environment
21
+ // Track the last event timestamp to measure end-to-end latency
22
+ let lastEventTimestamp = 0;
23
+ let pendingBatchId = null;
24
+
25
+ /**
26
+ * Call this when a traceUpdates event is received to track end-to-end latency
27
+ */
28
+ export function markEventReceived() {
29
+ lastEventTimestamp = performance.now();
30
+ return lastEventTimestamp;
31
+ }
32
+
33
+ /**
34
+ * Call this from the overlay when highlights are actually rendered
35
+ */
36
+ export function markOverlayRendered(highlightCount, renderTime) {
37
+ if (lastEventTimestamp > 0 && highlightCount > 0) {
38
+ // Reset to avoid double-counting (end-to-end logging removed - silent by default)
39
+ lastEventTimestamp = 0;
40
+ }
41
+ }
42
+
43
+ // Rolling statistics for summary logging
44
+
45
+ class PerformanceLoggerSingleton {
46
+ enabled = false;
47
+ batchCounter = 0;
48
+ listeners = new Set();
49
+
50
+ // Rolling stats for periodic summaries
51
+ rollingStats = {
52
+ batchCount: 0,
53
+ totalNodes: 0,
54
+ totalFiltered: 0,
55
+ totalProcessed: 0,
56
+ totalTime: 0,
57
+ maxTime: 0,
58
+ minTime: Infinity,
59
+ avgMeasurementTime: 0
60
+ };
61
+
62
+ // Recent batch history for analysis
63
+ recentBatches = [];
64
+ MAX_HISTORY = 100;
65
+
66
+ // Summary logging interval
67
+ summaryInterval = null;
68
+ SUMMARY_INTERVAL_MS = 10000; // Log summary every 10s
69
+
70
+ /**
71
+ * Enable or disable performance logging
72
+ */
73
+ setEnabled(enabled) {
74
+ const wasEnabled = this.enabled;
75
+ this.enabled = enabled;
76
+ if (enabled && !wasEnabled) {
77
+ this.resetStats();
78
+ // Summary interval removed - no periodic logging
79
+ } else if (!enabled && wasEnabled) {
80
+ this.stopSummaryInterval();
81
+ }
82
+ // Silent enable/disable - no console logs
83
+ }
84
+
85
+ /**
86
+ * Check if logging is enabled
87
+ */
88
+ isEnabled() {
89
+ return this.enabled;
90
+ }
91
+
92
+ /**
93
+ * Start timing a new batch of render updates
94
+ */
95
+ startBatch(nodesReceived, batchSize) {
96
+ const batchId = `batch_${++this.batchCounter}`;
97
+ const startTime = performance.now();
98
+ let filteringEndTime = null;
99
+ let measurementStartTime = null;
100
+ let measurementEndTime = null;
101
+ let trackingEndTime = null;
102
+ let callbackEndTime = null;
103
+ let nodesFiltered = 0;
104
+ let nodesToProcess = 0;
105
+ let measurementSuccessCount = 0;
106
+ let measurementFailCount = 0;
107
+ let overlayRenderTime;
108
+ const timer = {
109
+ markFilteringComplete: (filtered, toProcess) => {
110
+ filteringEndTime = performance.now();
111
+ nodesFiltered = filtered;
112
+ nodesToProcess = toProcess;
113
+ },
114
+ markMeasurementStart: () => {
115
+ measurementStartTime = performance.now();
116
+ },
117
+ markMeasurementComplete: (success, fail) => {
118
+ measurementEndTime = performance.now();
119
+ measurementSuccessCount = success;
120
+ measurementFailCount = fail;
121
+ },
122
+ markTrackingComplete: () => {
123
+ trackingEndTime = performance.now();
124
+ },
125
+ markCallbackComplete: () => {
126
+ callbackEndTime = performance.now();
127
+ },
128
+ setOverlayRenderTime: timeMs => {
129
+ overlayRenderTime = timeMs;
130
+ },
131
+ getBatchId: () => batchId,
132
+ finish: () => {
133
+ const endTime = performance.now();
134
+ const metrics = {
135
+ batchId,
136
+ timestamp: Date.now(),
137
+ nodesReceived,
138
+ nodesFiltered,
139
+ nodesToProcess,
140
+ batchSize,
141
+ nodesInBatch: Math.min(nodesToProcess, batchSize),
142
+ filteringTime: filteringEndTime ? filteringEndTime - startTime : 0,
143
+ measurementTime: measurementStartTime && measurementEndTime ? measurementEndTime - measurementStartTime : 0,
144
+ trackingTime: measurementEndTime && trackingEndTime ? trackingEndTime - measurementEndTime : 0,
145
+ callbackTime: trackingEndTime && callbackEndTime ? callbackEndTime - trackingEndTime : 0,
146
+ totalTime: endTime - startTime,
147
+ measurementSuccessCount,
148
+ measurementFailCount,
149
+ overlayRenderTime
150
+ };
151
+ if (this.enabled) {
152
+ this.recordMetrics(metrics);
153
+ }
154
+ return metrics;
155
+ }
156
+ };
157
+ return timer;
158
+ }
159
+
160
+ /**
161
+ * Record metrics and log them
162
+ */
163
+ recordMetrics(metrics) {
164
+ // Update rolling stats
165
+ this.rollingStats.batchCount++;
166
+ this.rollingStats.totalNodes += metrics.nodesReceived;
167
+ this.rollingStats.totalFiltered += metrics.nodesFiltered;
168
+ this.rollingStats.totalProcessed += metrics.nodesInBatch;
169
+ this.rollingStats.totalTime += metrics.totalTime;
170
+ this.rollingStats.maxTime = Math.max(this.rollingStats.maxTime, metrics.totalTime);
171
+ this.rollingStats.minTime = Math.min(this.rollingStats.minTime, metrics.totalTime);
172
+
173
+ // Update rolling average for measurement time
174
+ const prevAvg = this.rollingStats.avgMeasurementTime;
175
+ const n = this.rollingStats.batchCount;
176
+ this.rollingStats.avgMeasurementTime = prevAvg + (metrics.measurementTime - prevAvg) / n;
177
+
178
+ // Store in history
179
+ this.recentBatches.push(metrics);
180
+ if (this.recentBatches.length > this.MAX_HISTORY) {
181
+ this.recentBatches.shift();
182
+ }
183
+
184
+ // Notify listeners
185
+ this.notifyListeners(metrics);
186
+ }
187
+
188
+ /**
189
+ * Log a single batch's metrics
190
+ */
191
+ logBatch(metrics) {
192
+ const {
193
+ batchId,
194
+ nodesReceived,
195
+ nodesFiltered,
196
+ nodesInBatch,
197
+ batchSize,
198
+ filteringTime,
199
+ measurementTime,
200
+ trackingTime,
201
+ callbackTime,
202
+ totalTime,
203
+ measurementSuccessCount,
204
+ measurementFailCount,
205
+ overlayRenderTime
206
+ } = metrics;
207
+
208
+ // Compact single-line log for quick scanning
209
+ console.log(`[HighlightPerf] ${batchId} | ` + `In:${nodesReceived} Filt:${nodesFiltered} Proc:${nodesInBatch}/${batchSize} | ` + `Filter:${filteringTime.toFixed(1)}ms Measure:${measurementTime.toFixed(1)}ms ` + `Track:${trackingTime.toFixed(1)}ms Callback:${callbackTime.toFixed(1)}ms | ` + `Total:${totalTime.toFixed(1)}ms` + (overlayRenderTime ? ` Render:${overlayRenderTime.toFixed(1)}ms` : "") + ` | Success:${measurementSuccessCount} Fail:${measurementFailCount}`);
210
+
211
+ // Flag slow batches
212
+ if (totalTime > 100) {
213
+ console.warn(`[HighlightPerf] ⚠️ SLOW BATCH: ${totalTime.toFixed(1)}ms - ` + `Measurement phase: ${measurementTime.toFixed(1)}ms (${(measurementTime / totalTime * 100).toFixed(0)}%)`);
214
+ }
215
+ }
216
+
217
+ /**
218
+ * Log periodic summary stats
219
+ */
220
+ logSummary() {
221
+ if (this.rollingStats.batchCount === 0) return;
222
+ const stats = this.rollingStats;
223
+ const avgTime = stats.totalTime / stats.batchCount;
224
+ const avgNodes = stats.totalNodes / stats.batchCount;
225
+ console.log(`\n[HighlightPerf] ════════ SUMMARY (last ${this.SUMMARY_INTERVAL_MS / 1000}s) ════════\n` + ` Batches: ${stats.batchCount}\n` + ` Avg nodes/batch: ${avgNodes.toFixed(1)} (filtered: ${(stats.totalFiltered / stats.batchCount).toFixed(1)})\n` + ` Avg total time: ${avgTime.toFixed(1)}ms\n` + ` Avg measurement time: ${stats.avgMeasurementTime.toFixed(1)}ms\n` + ` Min/Max time: ${stats.minTime.toFixed(1)}ms / ${stats.maxTime.toFixed(1)}ms\n` + `══════════════════════════════════════════════\n`);
226
+
227
+ // Reset rolling stats for next interval
228
+ this.resetStats();
229
+ }
230
+
231
+ /**
232
+ * Reset rolling statistics
233
+ */
234
+ resetStats() {
235
+ this.rollingStats = {
236
+ batchCount: 0,
237
+ totalNodes: 0,
238
+ totalFiltered: 0,
239
+ totalProcessed: 0,
240
+ totalTime: 0,
241
+ maxTime: 0,
242
+ minTime: Infinity,
243
+ avgMeasurementTime: 0
244
+ };
245
+ }
246
+
247
+ /**
248
+ * Start the summary logging interval
249
+ */
250
+ startSummaryInterval() {
251
+ this.stopSummaryInterval();
252
+ this.summaryInterval = setInterval(() => {
253
+ this.logSummary();
254
+ }, this.SUMMARY_INTERVAL_MS);
255
+ }
256
+
257
+ /**
258
+ * Stop the summary logging interval
259
+ */
260
+ stopSummaryInterval() {
261
+ if (this.summaryInterval) {
262
+ clearInterval(this.summaryInterval);
263
+ this.summaryInterval = null;
264
+ }
265
+ }
266
+
267
+ /**
268
+ * Subscribe to metrics updates
269
+ */
270
+ subscribe(listener) {
271
+ this.listeners.add(listener);
272
+ return () => {
273
+ this.listeners.delete(listener);
274
+ };
275
+ }
276
+
277
+ /**
278
+ * Notify all listeners of new metrics
279
+ */
280
+ notifyListeners(metrics) {
281
+ for (const listener of this.listeners) {
282
+ try {
283
+ listener(metrics);
284
+ } catch (error) {
285
+ console.error("[HighlightPerf] Error in metrics listener:", error);
286
+ }
287
+ }
288
+ }
289
+
290
+ /**
291
+ * Get recent batch history
292
+ */
293
+ getRecentBatches() {
294
+ return [...this.recentBatches];
295
+ }
296
+
297
+ /**
298
+ * Get current rolling stats
299
+ */
300
+ getRollingStats() {
301
+ return {
302
+ ...this.rollingStats
303
+ };
304
+ }
305
+
306
+ /**
307
+ * Clear all history and stats
308
+ */
309
+ clear() {
310
+ this.recentBatches = [];
311
+ this.resetStats();
312
+ this.batchCounter = 0;
313
+ }
314
+
315
+ /**
316
+ * Generate a detailed report of recent performance
317
+ */
318
+ generateReport() {
319
+ const batches = this.recentBatches;
320
+ if (batches.length === 0) {
321
+ return "No performance data collected yet.";
322
+ }
323
+ const totalTime = batches.reduce((sum, b) => sum + b.totalTime, 0);
324
+ const avgTime = totalTime / batches.length;
325
+ const totalMeasureTime = batches.reduce((sum, b) => sum + b.measurementTime, 0);
326
+ const avgMeasureTime = totalMeasureTime / batches.length;
327
+ const slowBatches = batches.filter(b => b.totalTime > 100);
328
+ const fastBatches = batches.filter(b => b.totalTime < 20);
329
+ const report = `
330
+ ╔══════════════════════════════════════════════════════════════╗
331
+ ║ HIGHLIGHT UPDATES PERFORMANCE REPORT ║
332
+ ╠══════════════════════════════════════════════════════════════╣
333
+ ║ Total batches analyzed: ${batches.length.toString().padStart(5)} ║
334
+ ║ Average total time: ${avgTime.toFixed(1).padStart(5)}ms ║
335
+ ║ Average measure time: ${avgMeasureTime.toFixed(1).padStart(5)}ms (${(avgMeasureTime / avgTime * 100).toFixed(0)}% of total) ║
336
+ ╠══════════════════════════════════════════════════════════════╣
337
+ ║ Fast batches (<20ms): ${fastBatches.length.toString().padStart(5)} (${(fastBatches.length / batches.length * 100).toFixed(0)}%) ║
338
+ ║ Slow batches (>100ms): ${slowBatches.length.toString().padStart(5)} (${(slowBatches.length / batches.length * 100).toFixed(0)}%) ║
339
+ ╠══════════════════════════════════════════════════════════════╣
340
+ ║ Time breakdown (avg): ║
341
+ ║ Filtering: ${batches.reduce((s, b) => s + b.filteringTime, 0 / batches.length).toFixed(1).padStart(6)}ms ║
342
+ ║ Measurement: ${avgMeasureTime.toFixed(1).padStart(6)}ms ← Primary bottleneck ║
343
+ ║ Tracking: ${(batches.reduce((s, b) => s + b.trackingTime, 0) / batches.length).toFixed(1).padStart(6)}ms ║
344
+ ║ Callback: ${(batches.reduce((s, b) => s + b.callbackTime, 0) / batches.length).toFixed(1).padStart(6)}ms ║
345
+ ╚══════════════════════════════════════════════════════════════╝
346
+ `;
347
+ return report;
348
+ }
349
+ }
350
+
351
+ // Export singleton instance
352
+ export const PerformanceLogger = new PerformanceLoggerSingleton();
353
+ export default PerformanceLogger;