@buoy-gg/benchmark 2.1.3 → 2.1.4-beta.1
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/lib/commonjs/benchmarking/BenchmarkComparator.js +221 -1
- package/lib/commonjs/benchmarking/BenchmarkRecorder.js +497 -1
- package/lib/commonjs/benchmarking/BenchmarkStorage.js +235 -1
- package/lib/commonjs/benchmarking/index.js +83 -1
- package/lib/commonjs/benchmarking/types.js +13 -1
- package/lib/commonjs/components/BenchmarkCompareView.js +475 -1
- package/lib/commonjs/components/BenchmarkDetailView.js +346 -1
- package/lib/commonjs/components/BenchmarkModal.js +505 -1
- package/lib/commonjs/components/BenchmarkSessionCard.js +193 -1
- package/lib/commonjs/index.js +62 -1
- package/lib/commonjs/preset.js +86 -1
- package/lib/module/benchmarking/BenchmarkComparator.js +216 -1
- package/lib/module/benchmarking/BenchmarkRecorder.js +493 -1
- package/lib/module/benchmarking/BenchmarkStorage.js +227 -1
- package/lib/module/benchmarking/index.js +48 -1
- package/lib/module/benchmarking/types.js +13 -1
- package/lib/module/components/BenchmarkCompareView.js +469 -1
- package/lib/module/components/BenchmarkDetailView.js +340 -1
- package/lib/module/components/BenchmarkModal.js +499 -1
- package/lib/module/components/BenchmarkSessionCard.js +187 -1
- package/lib/module/index.js +39 -1
- package/lib/module/preset.js +81 -1
- package/lib/typescript/benchmarking/BenchmarkComparator.d.ts.map +1 -0
- package/lib/typescript/benchmarking/BenchmarkRecorder.d.ts.map +1 -0
- package/lib/typescript/benchmarking/BenchmarkStorage.d.ts.map +1 -0
- package/lib/typescript/benchmarking/index.d.ts.map +1 -0
- package/lib/typescript/benchmarking/types.d.ts.map +1 -0
- package/lib/typescript/components/BenchmarkCompareView.d.ts.map +1 -0
- package/lib/typescript/components/BenchmarkDetailView.d.ts.map +1 -0
- package/lib/typescript/components/BenchmarkModal.d.ts.map +1 -0
- package/lib/typescript/components/BenchmarkSessionCard.d.ts.map +1 -0
- package/lib/typescript/index.d.ts.map +1 -0
- package/lib/typescript/preset.d.ts.map +1 -0
- package/package.json +2 -2
|
@@ -1 +1,497 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* BenchmarkRecorder
|
|
3
|
+
*
|
|
4
|
+
* Records performance metrics during a benchmark session. Uses performance.now()
|
|
5
|
+
* for high-resolution timing (the only Performance API available in React Native).
|
|
6
|
+
* Collects batch metrics, overlay render times, memory snapshots, and custom marks/measures.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* const recorder = new BenchmarkRecorder();
|
|
10
|
+
* recorder.startSession({ name: 'MyBenchmark' });
|
|
11
|
+
*
|
|
12
|
+
* // Record batch metrics (called by HighlightUpdatesController)
|
|
13
|
+
* recorder.recordBatch(batchMetrics);
|
|
14
|
+
*
|
|
15
|
+
* // Record overlay renders (called by HighlightUpdatesOverlay)
|
|
16
|
+
* recorder.recordOverlayRender(count, timeMs);
|
|
17
|
+
*
|
|
18
|
+
* // Add custom marks/measures
|
|
19
|
+
* recorder.mark('customEvent');
|
|
20
|
+
* recorder.startMeasure('apiCall');
|
|
21
|
+
* recorder.endMeasure('apiCall');
|
|
22
|
+
*
|
|
23
|
+
* // Stop and get report
|
|
24
|
+
* const report = recorder.stopSession();
|
|
25
|
+
*
|
|
26
|
+
* @packageDocumentation
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
"use strict";
|
|
30
|
+
|
|
31
|
+
Object.defineProperty(exports, "__esModule", {
|
|
32
|
+
value: true
|
|
33
|
+
});
|
|
34
|
+
exports.default = exports.benchmarkRecorder = exports.BenchmarkRecorder = void 0;
|
|
35
|
+
var _reactNative = require("react-native");
|
|
36
|
+
// React Native only provides performance.now()
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Generate a unique session ID
|
|
40
|
+
*/
|
|
41
|
+
function generateSessionId(name) {
|
|
42
|
+
const timestamp = Date.now();
|
|
43
|
+
const random = Math.random().toString(36).substring(2, 8);
|
|
44
|
+
return `${name}_${timestamp}_${random}`;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Calculate percentile from sorted array
|
|
49
|
+
*/
|
|
50
|
+
function percentile(sortedArr, p) {
|
|
51
|
+
if (sortedArr.length === 0) return 0;
|
|
52
|
+
const index = Math.ceil(p / 100 * sortedArr.length) - 1;
|
|
53
|
+
return sortedArr[Math.max(0, Math.min(index, sortedArr.length - 1))];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Capture current memory snapshot (may not be available in all environments)
|
|
58
|
+
*/
|
|
59
|
+
function captureMemorySnapshot() {
|
|
60
|
+
// Memory API is not available in React Native
|
|
61
|
+
// Could potentially use native modules in the future
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Get current benchmark context
|
|
67
|
+
*/
|
|
68
|
+
function getBenchmarkContext(batchSize, showRenderCount) {
|
|
69
|
+
let platform = "unknown";
|
|
70
|
+
if (_reactNative.Platform.OS === "ios") platform = "ios";else if (_reactNative.Platform.OS === "android") platform = "android";else if (_reactNative.Platform.OS === "web") platform = "web";
|
|
71
|
+
return {
|
|
72
|
+
platform,
|
|
73
|
+
osVersion: _reactNative.Platform.Version?.toString(),
|
|
74
|
+
isDev: __DEV__ ?? false,
|
|
75
|
+
batchSize,
|
|
76
|
+
showRenderCount
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Compute aggregated statistics from batch and overlay metrics
|
|
82
|
+
*/
|
|
83
|
+
function computeStats(batches, overlayRenders) {
|
|
84
|
+
const batchCount = batches.length;
|
|
85
|
+
if (batchCount === 0) {
|
|
86
|
+
return {
|
|
87
|
+
batchCount: 0,
|
|
88
|
+
totalNodesReceived: 0,
|
|
89
|
+
totalNodesFiltered: 0,
|
|
90
|
+
totalNodesProcessed: 0,
|
|
91
|
+
avgFilterTime: 0,
|
|
92
|
+
avgMeasureTime: 0,
|
|
93
|
+
avgTrackTime: 0,
|
|
94
|
+
avgCallbackTime: 0,
|
|
95
|
+
avgTotalTime: 0,
|
|
96
|
+
minTotalTime: 0,
|
|
97
|
+
maxTotalTime: 0,
|
|
98
|
+
p50TotalTime: 0,
|
|
99
|
+
p95TotalTime: 0,
|
|
100
|
+
p99TotalTime: 0,
|
|
101
|
+
avgOverlayRenderTime: 0,
|
|
102
|
+
avgHighlightsPerRender: 0
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Sum up totals
|
|
107
|
+
let totalNodesReceived = 0;
|
|
108
|
+
let totalNodesFiltered = 0;
|
|
109
|
+
let totalNodesProcessed = 0;
|
|
110
|
+
let totalFilterTime = 0;
|
|
111
|
+
let totalMeasureTime = 0;
|
|
112
|
+
let totalTrackTime = 0;
|
|
113
|
+
let totalCallbackTime = 0;
|
|
114
|
+
let totalPipelineTime = 0;
|
|
115
|
+
const totalTimes = [];
|
|
116
|
+
for (const batch of batches) {
|
|
117
|
+
totalNodesReceived += batch.nodesReceived;
|
|
118
|
+
totalNodesFiltered += batch.nodesFiltered;
|
|
119
|
+
totalNodesProcessed += batch.nodesInBatch;
|
|
120
|
+
totalFilterTime += batch.filteringTime;
|
|
121
|
+
totalMeasureTime += batch.measurementTime;
|
|
122
|
+
totalTrackTime += batch.trackingTime;
|
|
123
|
+
totalCallbackTime += batch.callbackTime;
|
|
124
|
+
totalPipelineTime += batch.totalTime;
|
|
125
|
+
totalTimes.push(batch.totalTime);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Sort for percentiles
|
|
129
|
+
totalTimes.sort((a, b) => a - b);
|
|
130
|
+
|
|
131
|
+
// Overlay stats
|
|
132
|
+
let totalOverlayTime = 0;
|
|
133
|
+
let totalHighlights = 0;
|
|
134
|
+
for (const render of overlayRenders) {
|
|
135
|
+
totalOverlayTime += render.renderTime;
|
|
136
|
+
totalHighlights += render.highlightCount;
|
|
137
|
+
}
|
|
138
|
+
return {
|
|
139
|
+
batchCount,
|
|
140
|
+
totalNodesReceived,
|
|
141
|
+
totalNodesFiltered,
|
|
142
|
+
totalNodesProcessed,
|
|
143
|
+
avgFilterTime: totalFilterTime / batchCount,
|
|
144
|
+
avgMeasureTime: totalMeasureTime / batchCount,
|
|
145
|
+
avgTrackTime: totalTrackTime / batchCount,
|
|
146
|
+
avgCallbackTime: totalCallbackTime / batchCount,
|
|
147
|
+
avgTotalTime: totalPipelineTime / batchCount,
|
|
148
|
+
minTotalTime: totalTimes[0],
|
|
149
|
+
maxTotalTime: totalTimes[totalTimes.length - 1],
|
|
150
|
+
p50TotalTime: percentile(totalTimes, 50),
|
|
151
|
+
p95TotalTime: percentile(totalTimes, 95),
|
|
152
|
+
p99TotalTime: percentile(totalTimes, 99),
|
|
153
|
+
avgOverlayRenderTime: overlayRenders.length > 0 ? totalOverlayTime / overlayRenders.length : 0,
|
|
154
|
+
avgHighlightsPerRender: overlayRenders.length > 0 ? totalHighlights / overlayRenders.length : 0
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* BenchmarkRecorder - Records performance metrics during a benchmark session
|
|
160
|
+
*/
|
|
161
|
+
class BenchmarkRecorder {
|
|
162
|
+
state = "idle";
|
|
163
|
+
sessionId = "";
|
|
164
|
+
sessionName = "";
|
|
165
|
+
sessionStartTime = 0;
|
|
166
|
+
verbose = false;
|
|
167
|
+
captureMemory = true;
|
|
168
|
+
|
|
169
|
+
// Collected data
|
|
170
|
+
batches = [];
|
|
171
|
+
overlayRenders = [];
|
|
172
|
+
memoryStart = null;
|
|
173
|
+
memoryEnd = null;
|
|
174
|
+
|
|
175
|
+
// Internal marks and measures (since React Native doesn't have full Performance API)
|
|
176
|
+
marks = [];
|
|
177
|
+
measures = [];
|
|
178
|
+
|
|
179
|
+
// Context
|
|
180
|
+
batchSize = 150;
|
|
181
|
+
showRenderCount = true;
|
|
182
|
+
|
|
183
|
+
// Event listeners
|
|
184
|
+
listeners = new Set();
|
|
185
|
+
|
|
186
|
+
// Active measures (for startMeasure/endMeasure)
|
|
187
|
+
activeMeasures = new Map();
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Get current session state
|
|
191
|
+
*/
|
|
192
|
+
getState() {
|
|
193
|
+
return this.state;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Check if currently recording
|
|
198
|
+
*/
|
|
199
|
+
isRecording() {
|
|
200
|
+
return this.state === "recording";
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Set the batch size context (for report metadata)
|
|
205
|
+
*/
|
|
206
|
+
setBatchSize(size) {
|
|
207
|
+
this.batchSize = size;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Set the showRenderCount context (for report metadata)
|
|
212
|
+
*/
|
|
213
|
+
setShowRenderCount(enabled) {
|
|
214
|
+
this.showRenderCount = enabled;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Start a new benchmark session
|
|
219
|
+
*/
|
|
220
|
+
startSession(options) {
|
|
221
|
+
if (this.state === "recording") {
|
|
222
|
+
console.warn("[BenchmarkRecorder] Session already recording. Stop it first.");
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Reset state
|
|
227
|
+
this.sessionId = generateSessionId(options.name);
|
|
228
|
+
this.sessionName = options.name;
|
|
229
|
+
this.sessionDescription = options.description;
|
|
230
|
+
this.verbose = options.verbose ?? false;
|
|
231
|
+
this.captureMemory = options.captureMemory ?? true;
|
|
232
|
+
this.batches = [];
|
|
233
|
+
this.overlayRenders = [];
|
|
234
|
+
this.marks = [];
|
|
235
|
+
this.measures = [];
|
|
236
|
+
this.activeMeasures.clear();
|
|
237
|
+
|
|
238
|
+
// Capture start memory
|
|
239
|
+
if (this.captureMemory) {
|
|
240
|
+
this.memoryStart = captureMemorySnapshot();
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Record start time
|
|
244
|
+
this.sessionStartTime = performance.now();
|
|
245
|
+
|
|
246
|
+
// Add start mark
|
|
247
|
+
this.marks.push({
|
|
248
|
+
name: "session_start",
|
|
249
|
+
startTime: 0,
|
|
250
|
+
detail: {
|
|
251
|
+
name: this.sessionName
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
this.state = "recording";
|
|
255
|
+
if (this.verbose) {
|
|
256
|
+
console.log(`[BenchmarkRecorder] Session started: ${this.sessionName}`);
|
|
257
|
+
console.log(` ID: ${this.sessionId}`);
|
|
258
|
+
}
|
|
259
|
+
this.notifyListeners("start");
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Stop the current session and generate a report
|
|
264
|
+
*/
|
|
265
|
+
stopSession() {
|
|
266
|
+
if (this.state !== "recording") {
|
|
267
|
+
console.warn("[BenchmarkRecorder] No active session to stop.");
|
|
268
|
+
return null;
|
|
269
|
+
}
|
|
270
|
+
const endTime = performance.now();
|
|
271
|
+
const duration = endTime - this.sessionStartTime;
|
|
272
|
+
|
|
273
|
+
// Add end mark
|
|
274
|
+
this.marks.push({
|
|
275
|
+
name: "session_end",
|
|
276
|
+
startTime: duration
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
// Add total measure
|
|
280
|
+
this.measures.push({
|
|
281
|
+
name: "session_total",
|
|
282
|
+
startTime: 0,
|
|
283
|
+
duration
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
// Capture end memory
|
|
287
|
+
if (this.captureMemory) {
|
|
288
|
+
this.memoryEnd = captureMemorySnapshot();
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Calculate memory delta
|
|
292
|
+
let memoryDelta = null;
|
|
293
|
+
if (this.memoryStart?.usedJSHeapSize != null && this.memoryEnd?.usedJSHeapSize != null) {
|
|
294
|
+
memoryDelta = this.memoryEnd.usedJSHeapSize - this.memoryStart.usedJSHeapSize;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Compute aggregated stats
|
|
298
|
+
const stats = computeStats(this.batches, this.overlayRenders);
|
|
299
|
+
|
|
300
|
+
// Build report
|
|
301
|
+
const report = {
|
|
302
|
+
version: "1.0",
|
|
303
|
+
id: this.sessionId,
|
|
304
|
+
name: this.sessionName,
|
|
305
|
+
description: this.sessionDescription,
|
|
306
|
+
createdAt: Date.now(),
|
|
307
|
+
duration,
|
|
308
|
+
context: getBenchmarkContext(this.batchSize, this.showRenderCount),
|
|
309
|
+
batches: [...this.batches],
|
|
310
|
+
overlayRenders: [...this.overlayRenders],
|
|
311
|
+
marks: [...this.marks],
|
|
312
|
+
measures: [...this.measures],
|
|
313
|
+
stats,
|
|
314
|
+
memoryStart: this.memoryStart,
|
|
315
|
+
memoryEnd: this.memoryEnd,
|
|
316
|
+
memoryDelta
|
|
317
|
+
};
|
|
318
|
+
this.state = "stopped";
|
|
319
|
+
if (this.verbose) {
|
|
320
|
+
this.logReport(report);
|
|
321
|
+
}
|
|
322
|
+
this.notifyListeners("stop");
|
|
323
|
+
return report;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Record a batch of highlight updates
|
|
328
|
+
*/
|
|
329
|
+
recordBatch(metrics) {
|
|
330
|
+
if (this.state !== "recording") return;
|
|
331
|
+
this.batches.push(metrics);
|
|
332
|
+
|
|
333
|
+
// Add a mark for this batch
|
|
334
|
+
this.marks.push({
|
|
335
|
+
name: `batch_${metrics.batchId}`,
|
|
336
|
+
startTime: performance.now() - this.sessionStartTime,
|
|
337
|
+
detail: {
|
|
338
|
+
nodesReceived: metrics.nodesReceived,
|
|
339
|
+
nodesProcessed: metrics.nodesInBatch,
|
|
340
|
+
totalTime: metrics.totalTime
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
if (this.verbose) {
|
|
344
|
+
console.log(`[BenchmarkRecorder] Batch ${metrics.batchId}: ` + `${metrics.nodesInBatch} nodes in ${metrics.totalTime.toFixed(1)}ms`);
|
|
345
|
+
}
|
|
346
|
+
this.notifyListeners("batch", metrics);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Record an overlay render
|
|
351
|
+
*/
|
|
352
|
+
recordOverlayRender(highlightCount, renderTime) {
|
|
353
|
+
if (this.state !== "recording") return;
|
|
354
|
+
const metrics = {
|
|
355
|
+
highlightCount,
|
|
356
|
+
renderTime,
|
|
357
|
+
timestamp: performance.now()
|
|
358
|
+
};
|
|
359
|
+
this.overlayRenders.push(metrics);
|
|
360
|
+
this.marks.push({
|
|
361
|
+
name: "overlay_render",
|
|
362
|
+
startTime: performance.now() - this.sessionStartTime,
|
|
363
|
+
detail: {
|
|
364
|
+
highlightCount,
|
|
365
|
+
renderTime
|
|
366
|
+
}
|
|
367
|
+
});
|
|
368
|
+
if (this.verbose) {
|
|
369
|
+
console.log(`[BenchmarkRecorder] Overlay render: ${highlightCount} highlights in ${renderTime.toFixed(1)}ms`);
|
|
370
|
+
}
|
|
371
|
+
this.notifyListeners("overlay", metrics);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Add a custom mark at the current time
|
|
376
|
+
*/
|
|
377
|
+
mark(name, detail) {
|
|
378
|
+
if (this.state !== "recording") return;
|
|
379
|
+
this.marks.push({
|
|
380
|
+
name,
|
|
381
|
+
startTime: performance.now() - this.sessionStartTime,
|
|
382
|
+
detail
|
|
383
|
+
});
|
|
384
|
+
if (this.verbose) {
|
|
385
|
+
console.log(`[BenchmarkRecorder] Mark: ${name}`);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Start a custom measure
|
|
391
|
+
*/
|
|
392
|
+
startMeasure(name) {
|
|
393
|
+
if (this.state !== "recording") return;
|
|
394
|
+
this.activeMeasures.set(name, performance.now());
|
|
395
|
+
if (this.verbose) {
|
|
396
|
+
console.log(`[BenchmarkRecorder] Measure started: ${name}`);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* End a custom measure
|
|
402
|
+
*/
|
|
403
|
+
endMeasure(name, detail) {
|
|
404
|
+
if (this.state !== "recording") return null;
|
|
405
|
+
const startTime = this.activeMeasures.get(name);
|
|
406
|
+
if (startTime === undefined) {
|
|
407
|
+
console.warn(`[BenchmarkRecorder] No active measure: ${name}`);
|
|
408
|
+
return null;
|
|
409
|
+
}
|
|
410
|
+
const endTime = performance.now();
|
|
411
|
+
const duration = endTime - startTime;
|
|
412
|
+
this.measures.push({
|
|
413
|
+
name,
|
|
414
|
+
startTime: startTime - this.sessionStartTime,
|
|
415
|
+
duration,
|
|
416
|
+
detail
|
|
417
|
+
});
|
|
418
|
+
this.activeMeasures.delete(name);
|
|
419
|
+
if (this.verbose) {
|
|
420
|
+
console.log(`[BenchmarkRecorder] Measure ended: ${name} = ${duration.toFixed(1)}ms`);
|
|
421
|
+
}
|
|
422
|
+
return duration;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* Subscribe to benchmark events
|
|
427
|
+
*/
|
|
428
|
+
subscribe(listener) {
|
|
429
|
+
this.listeners.add(listener);
|
|
430
|
+
return () => {
|
|
431
|
+
this.listeners.delete(listener);
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* Notify event listeners
|
|
437
|
+
*/
|
|
438
|
+
notifyListeners(event, data) {
|
|
439
|
+
for (const listener of this.listeners) {
|
|
440
|
+
try {
|
|
441
|
+
listener(event, data);
|
|
442
|
+
} catch (error) {
|
|
443
|
+
console.error("[BenchmarkRecorder] Error in event listener:", error);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* Log a summary of the report
|
|
450
|
+
*/
|
|
451
|
+
logReport(report) {
|
|
452
|
+
const {
|
|
453
|
+
stats,
|
|
454
|
+
memoryDelta
|
|
455
|
+
} = report;
|
|
456
|
+
console.log("\n╔══════════════════════════════════════════════════════════════╗");
|
|
457
|
+
console.log("║ BENCHMARK REPORT ║");
|
|
458
|
+
console.log("╠══════════════════════════════════════════════════════════════╣");
|
|
459
|
+
console.log(`║ Name: ${report.name.padEnd(55)}║`);
|
|
460
|
+
console.log(`║ ID: ${report.id.substring(0, 57).padEnd(57)}║`);
|
|
461
|
+
console.log(`║ Duration: ${report.duration.toFixed(1).padStart(8)}ms ║`);
|
|
462
|
+
console.log("╠══════════════════════════════════════════════════════════════╣");
|
|
463
|
+
console.log("║ BATCH STATS ║");
|
|
464
|
+
console.log(`║ Count: ${stats.batchCount.toString().padStart(6)} ║`);
|
|
465
|
+
console.log(`║ Nodes received: ${stats.totalNodesReceived.toString().padStart(8)} ║`);
|
|
466
|
+
console.log(`║ Nodes processed: ${stats.totalNodesProcessed.toString().padStart(7)} ║`);
|
|
467
|
+
console.log("╠══════════════════════════════════════════════════════════════╣");
|
|
468
|
+
console.log("║ TIMING (avg per batch) ║");
|
|
469
|
+
console.log(`║ Filter: ${stats.avgFilterTime.toFixed(1).padStart(8)}ms ║`);
|
|
470
|
+
console.log(`║ Measure: ${stats.avgMeasureTime.toFixed(1).padStart(7)}ms ← Primary bottleneck ║`);
|
|
471
|
+
console.log(`║ Track: ${stats.avgTrackTime.toFixed(1).padStart(9)}ms ║`);
|
|
472
|
+
console.log(`║ Callback: ${stats.avgCallbackTime.toFixed(1).padStart(6)}ms ║`);
|
|
473
|
+
console.log(`║ Total: ${stats.avgTotalTime.toFixed(1).padStart(9)}ms ║`);
|
|
474
|
+
console.log("╠══════════════════════════════════════════════════════════════╣");
|
|
475
|
+
console.log("║ PERCENTILES ║");
|
|
476
|
+
console.log(`║ P50: ${stats.p50TotalTime.toFixed(1).padStart(8)}ms ║`);
|
|
477
|
+
console.log(`║ P95: ${stats.p95TotalTime.toFixed(1).padStart(8)}ms ║`);
|
|
478
|
+
console.log(`║ P99: ${stats.p99TotalTime.toFixed(1).padStart(8)}ms ║`);
|
|
479
|
+
console.log("╠══════════════════════════════════════════════════════════════╣");
|
|
480
|
+
console.log("║ OVERLAY RENDERS ║");
|
|
481
|
+
console.log(`║ Avg time: ${stats.avgOverlayRenderTime.toFixed(1).padStart(7)}ms ║`);
|
|
482
|
+
console.log(`║ Avg highlights: ${stats.avgHighlightsPerRender.toFixed(0).padStart(5)} ║`);
|
|
483
|
+
if (memoryDelta != null) {
|
|
484
|
+
const deltaMB = (memoryDelta / 1024 / 1024).toFixed(2);
|
|
485
|
+
const sign = memoryDelta >= 0 ? "+" : "";
|
|
486
|
+
console.log("╠══════════════════════════════════════════════════════════════╣");
|
|
487
|
+
console.log("║ MEMORY ║");
|
|
488
|
+
console.log(`║ Delta: ${sign}${deltaMB.padStart(7)}MB ║`);
|
|
489
|
+
}
|
|
490
|
+
console.log("╚══════════════════════════════════════════════════════════════╝\n");
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// Export singleton instance for convenience
|
|
495
|
+
exports.BenchmarkRecorder = BenchmarkRecorder;
|
|
496
|
+
const benchmarkRecorder = exports.benchmarkRecorder = new BenchmarkRecorder();
|
|
497
|
+
var _default = exports.default = BenchmarkRecorder;
|