@kylincloud/flamegraph 0.35.28 → 0.35.29
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/CHANGELOG.md +18 -0
- package/dist/FlameGraph/FlameGraphComponent/Flamegraph.d.ts +16 -2
- package/dist/FlameGraph/FlameGraphComponent/Flamegraph.d.ts.map +1 -1
- package/dist/FlameGraph/FlameGraphComponent/Flamegraph_render.d.ts +15 -2
- package/dist/FlameGraph/FlameGraphComponent/Flamegraph_render.d.ts.map +1 -1
- package/dist/FlameGraph/FlameGraphComponent/Highlight.d.ts.map +1 -1
- package/dist/FlameGraph/FlameGraphComponent/index.d.ts.map +1 -1
- package/dist/FlameGraph/normalize.d.ts.map +1 -1
- package/dist/FlameGraph/uniqueness.d.ts.map +1 -1
- package/dist/ProfilerTable.d.ts.map +1 -1
- package/dist/Tooltip/Tooltip.d.ts.map +1 -1
- package/dist/flamegraphRenderWorker.js +2 -0
- package/dist/flamegraphRenderWorker.js.map +1 -0
- package/dist/index.cjs.js +4 -4
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.esm.js +4 -4
- package/dist/index.esm.js.map +1 -1
- package/dist/index.node.cjs.js +4 -4
- package/dist/index.node.cjs.js.map +1 -1
- package/dist/index.node.esm.js +4 -4
- package/dist/index.node.esm.js.map +1 -1
- package/dist/shims/Table.d.ts +15 -1
- package/dist/shims/Table.d.ts.map +1 -1
- package/dist/workers/createFlamegraphRenderWorker.d.ts +2 -0
- package/dist/workers/createFlamegraphRenderWorker.d.ts.map +1 -0
- package/dist/workers/flamegraphRenderWorker.d.ts +2 -0
- package/dist/workers/flamegraphRenderWorker.d.ts.map +1 -0
- package/dist/workers/profilerTableWorker.d.ts +73 -0
- package/dist/workers/profilerTableWorker.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/FlameGraph/FlameGraphComponent/Flamegraph.ts +33 -8
- package/src/FlameGraph/FlameGraphComponent/Flamegraph_render.ts +289 -85
- package/src/FlameGraph/FlameGraphComponent/Highlight.tsx +43 -17
- package/src/FlameGraph/FlameGraphComponent/index.tsx +150 -1
- package/src/FlameGraph/normalize.ts +9 -7
- package/src/FlameGraph/uniqueness.ts +69 -59
- package/src/ProfilerTable.tsx +463 -33
- package/src/Tooltip/Tooltip.tsx +49 -16
- package/src/shims/Table.module.scss +5 -0
- package/src/shims/Table.tsx +195 -5
- package/src/workers/createFlamegraphRenderWorker.ts +7 -0
- package/src/workers/flamegraphRenderWorker.ts +198 -0
- package/src/workers/profilerTableWorker.ts +368 -0
|
@@ -57,6 +57,116 @@ export interface CanvasI18nMessages {
|
|
|
57
57
|
isZh?: boolean;
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
const formattedValueCacheByCanvas = new WeakMap<
|
|
61
|
+
HTMLCanvasElement | OffscreenCanvas,
|
|
62
|
+
Map<number, string>
|
|
63
|
+
>();
|
|
64
|
+
const highlightCacheByNames = new WeakMap<
|
|
65
|
+
string[],
|
|
66
|
+
Map<string, Uint8Array>
|
|
67
|
+
>();
|
|
68
|
+
const colorCacheByNames = new WeakMap<
|
|
69
|
+
string[],
|
|
70
|
+
Map<string, ReturnType<typeof colorBasedOnPackageName>[]>
|
|
71
|
+
>();
|
|
72
|
+
const barIndexByLevels = new WeakMap<
|
|
73
|
+
Flamebearer['levels'],
|
|
74
|
+
Map<number, Array<{ i: number; j: number }>>
|
|
75
|
+
>();
|
|
76
|
+
|
|
77
|
+
function getFormattedValueCache(canvas: HTMLCanvasElement | OffscreenCanvas) {
|
|
78
|
+
const cached = formattedValueCacheByCanvas.get(canvas);
|
|
79
|
+
if (cached) {
|
|
80
|
+
return cached;
|
|
81
|
+
}
|
|
82
|
+
const next = new Map<number, string>();
|
|
83
|
+
formattedValueCacheByCanvas.set(canvas, next);
|
|
84
|
+
return next;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function getHighlightByNameIndex(names: string[], query: string) {
|
|
88
|
+
const cachedByQuery = highlightCacheByNames.get(names);
|
|
89
|
+
if (cachedByQuery) {
|
|
90
|
+
const cached = cachedByQuery.get(query);
|
|
91
|
+
if (cached) {
|
|
92
|
+
return cached;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
const queryLower = query.toLowerCase();
|
|
96
|
+
const matches = new Uint8Array(names.length);
|
|
97
|
+
for (let i = 1; i < names.length; i += 1) {
|
|
98
|
+
const name = names[i];
|
|
99
|
+
if (!name) {
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
if (name.toLowerCase().includes(queryLower)) {
|
|
103
|
+
matches[i] = 1;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
const nextMap = cachedByQuery ?? new Map<string, Uint8Array>();
|
|
107
|
+
nextMap.set(query, matches);
|
|
108
|
+
highlightCacheByNames.set(names, nextMap);
|
|
109
|
+
return matches;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function getColorByNameIndexCached(
|
|
113
|
+
names: string[],
|
|
114
|
+
spyName: SpyName,
|
|
115
|
+
palette: FlamegraphPalette
|
|
116
|
+
) {
|
|
117
|
+
const key = `${spyName}|${palette.name}`;
|
|
118
|
+
const cachedByPalette = colorCacheByNames.get(names);
|
|
119
|
+
if (cachedByPalette) {
|
|
120
|
+
const cached = cachedByPalette.get(key);
|
|
121
|
+
if (cached) {
|
|
122
|
+
return cached;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
const colors = new Array<ReturnType<typeof colorBasedOnPackageName>>(
|
|
126
|
+
names.length
|
|
127
|
+
);
|
|
128
|
+
for (let i = 0; i < names.length; i += 1) {
|
|
129
|
+
const name = names[i] || '';
|
|
130
|
+
const packageName = getPackageNameFromStackTrace(spyName, name) || '';
|
|
131
|
+
colors[i] = colorBasedOnPackageName(palette, packageName);
|
|
132
|
+
}
|
|
133
|
+
const nextMap =
|
|
134
|
+
cachedByPalette ??
|
|
135
|
+
new Map<string, ReturnType<typeof colorBasedOnPackageName>[]>();
|
|
136
|
+
nextMap.set(key, colors);
|
|
137
|
+
colorCacheByNames.set(names, nextMap);
|
|
138
|
+
return colors;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function getBarIndex(
|
|
142
|
+
levels: Flamebearer['levels'],
|
|
143
|
+
format: Flamebearer['format']
|
|
144
|
+
) {
|
|
145
|
+
const cached = barIndexByLevels.get(levels);
|
|
146
|
+
if (cached) {
|
|
147
|
+
return cached;
|
|
148
|
+
}
|
|
149
|
+
const ff = createFF(format);
|
|
150
|
+
const index = new Map<number, Array<{ i: number; j: number }>>();
|
|
151
|
+
for (let i = 0; i < levels.length; i += 1) {
|
|
152
|
+
const level = levels[i];
|
|
153
|
+
for (let j = 0; j < level.length; j += ff.jStep) {
|
|
154
|
+
const nameIndex = ff.getBarName(level, j);
|
|
155
|
+
if (nameIndex === undefined || nameIndex < 0) {
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
const list = index.get(nameIndex);
|
|
159
|
+
if (list) {
|
|
160
|
+
list.push({ i, j });
|
|
161
|
+
} else {
|
|
162
|
+
index.set(nameIndex, [{ i, j }]);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
barIndexByLevels.set(levels, index);
|
|
167
|
+
return index;
|
|
168
|
+
}
|
|
169
|
+
|
|
60
170
|
/** Default English messages (fallback) */
|
|
61
171
|
const defaultCanvasMessages: CanvasI18nMessages = {
|
|
62
172
|
collapsedLevelsSingular: 'total (1 level collapsed)',
|
|
@@ -65,13 +175,22 @@ const defaultCanvasMessages: CanvasI18nMessages = {
|
|
|
65
175
|
};
|
|
66
176
|
|
|
67
177
|
type CanvasRendererConfig = Flamebearer & {
|
|
68
|
-
canvas: HTMLCanvasElement;
|
|
178
|
+
canvas: HTMLCanvasElement | OffscreenCanvas;
|
|
69
179
|
focusedNode: ConstructorParameters<typeof Flamegraph>[2];
|
|
70
180
|
fitMode: ConstructorParameters<typeof Flamegraph>[3];
|
|
71
181
|
highlightQuery: ConstructorParameters<typeof Flamegraph>[4];
|
|
72
182
|
zoom: ConstructorParameters<typeof Flamegraph>[5];
|
|
73
183
|
renderRects?: boolean;
|
|
74
184
|
renderText?: boolean;
|
|
185
|
+
renderMode?: 'normal' | 'highlightOnly' | 'forceGrey';
|
|
186
|
+
levelStart?: number;
|
|
187
|
+
levelEnd?: number;
|
|
188
|
+
startI?: number;
|
|
189
|
+
startJ?: number;
|
|
190
|
+
timeBudgetMs?: number;
|
|
191
|
+
skipCanvasResize?: boolean;
|
|
192
|
+
skipDprScale?: boolean;
|
|
193
|
+
devicePixelRatio?: number;
|
|
75
194
|
|
|
76
195
|
/**
|
|
77
196
|
* Used when zooming, values between 0 and 1.
|
|
@@ -108,18 +227,25 @@ function getCollapsedText(
|
|
|
108
227
|
return messages.collapsedLevelsPlural.replace('{n}', String(levelCount));
|
|
109
228
|
}
|
|
110
229
|
|
|
111
|
-
export default function RenderCanvas(
|
|
230
|
+
export default function RenderCanvas(
|
|
231
|
+
props: CanvasRendererConfig
|
|
232
|
+
): { done: boolean; nextI: number; nextJ: number } {
|
|
233
|
+
const renderStart = performance.now();
|
|
112
234
|
const renderRects = props.renderRects !== false;
|
|
113
235
|
const renderText = props.renderText !== false;
|
|
236
|
+
const renderMode = props.renderMode ?? 'normal';
|
|
237
|
+
const timeBudgetMs = props.timeBudgetMs;
|
|
114
238
|
const { canvas, fitMode, units, tickToX, levels, palette } = props;
|
|
115
239
|
const { numTicks, sampleRate, pxPerTick } = props;
|
|
116
240
|
const { rangeMin, rangeMax } = props;
|
|
117
241
|
const { focusedNode, zoom } = props;
|
|
118
242
|
const messages = props.messages || defaultCanvasMessages;
|
|
119
243
|
|
|
120
|
-
const graphWidth = getCanvasWidth(canvas);
|
|
244
|
+
const graphWidth = props.skipCanvasResize ? canvas.width : getCanvasWidth(canvas);
|
|
121
245
|
// TODO: why is this needed? otherwise height is all messed up
|
|
122
|
-
|
|
246
|
+
if (!props.skipCanvasResize) {
|
|
247
|
+
canvas.width = graphWidth;
|
|
248
|
+
}
|
|
123
249
|
|
|
124
250
|
if (rangeMin >= rangeMax) {
|
|
125
251
|
throw new Error(`'rangeMin' should be strictly smaller than 'rangeMax'`);
|
|
@@ -151,13 +277,21 @@ export default function RenderCanvas(props: CanvasRendererConfig) {
|
|
|
151
277
|
const canvasHeight =
|
|
152
278
|
PX_PER_LEVEL * (levels.length - topLevel) + (isFocused ? BAR_HEIGHT : 0);
|
|
153
279
|
// const canvasHeight = PX_PER_LEVEL * (levels.length - topLevel);
|
|
154
|
-
|
|
280
|
+
if (!props.skipCanvasResize) {
|
|
281
|
+
canvas.height = canvasHeight;
|
|
282
|
+
}
|
|
155
283
|
|
|
156
284
|
// increase pixel ratio, otherwise it looks bad in high resolution devices
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
285
|
+
const dpr =
|
|
286
|
+
typeof props.devicePixelRatio === 'number'
|
|
287
|
+
? props.devicePixelRatio
|
|
288
|
+
: typeof devicePixelRatio !== 'undefined'
|
|
289
|
+
? devicePixelRatio
|
|
290
|
+
: 1;
|
|
291
|
+
if (!props.skipDprScale && dpr > 1) {
|
|
292
|
+
canvas.width *= dpr;
|
|
293
|
+
canvas.height *= dpr;
|
|
294
|
+
ctx.scale(dpr, dpr);
|
|
161
295
|
}
|
|
162
296
|
|
|
163
297
|
ctx.textBaseline = 'middle';
|
|
@@ -167,13 +301,13 @@ export default function RenderCanvas(props: CanvasRendererConfig) {
|
|
|
167
301
|
const characterSize = ctx.measureText('a').width;
|
|
168
302
|
|
|
169
303
|
const { names } = props;
|
|
170
|
-
const formattedValueCache =
|
|
304
|
+
const formattedValueCache = getFormattedValueCache(canvas);
|
|
171
305
|
const highlightByNameIndex = highlightModeOn
|
|
172
|
-
?
|
|
306
|
+
? getHighlightByNameIndex(names, props.highlightQuery)
|
|
173
307
|
: null;
|
|
174
308
|
const colorByNameIndex =
|
|
175
309
|
props.format === 'single'
|
|
176
|
-
?
|
|
310
|
+
? getColorByNameIndexCached(names, props.spyName as SpyName, palette)
|
|
177
311
|
: null;
|
|
178
312
|
const diffColorFn =
|
|
179
313
|
props.format === 'double'
|
|
@@ -194,7 +328,15 @@ export default function RenderCanvas(props: CanvasRendererConfig) {
|
|
|
194
328
|
// are we focused?
|
|
195
329
|
// if so, add an initial bar telling it's a collapsed one
|
|
196
330
|
// TODO clean this up
|
|
197
|
-
|
|
331
|
+
const initialStartI = props.startI ?? 0;
|
|
332
|
+
const initialStartJ = props.startJ ?? 0;
|
|
333
|
+
if (
|
|
334
|
+
isFocused &&
|
|
335
|
+
renderMode !== 'highlightOnly' &&
|
|
336
|
+
(props.levelStart ?? 0) === 0 &&
|
|
337
|
+
initialStartI === 0 &&
|
|
338
|
+
initialStartJ === 0
|
|
339
|
+
) {
|
|
198
340
|
const width = numTicks * pxPerTick;
|
|
199
341
|
if (renderRects) {
|
|
200
342
|
ctx.beginPath();
|
|
@@ -239,13 +381,120 @@ export default function RenderCanvas(props: CanvasRendererConfig) {
|
|
|
239
381
|
}
|
|
240
382
|
}
|
|
241
383
|
|
|
242
|
-
|
|
384
|
+
const levelStart = Math.max(0, props.levelStart ?? 0);
|
|
385
|
+
const maxLevels = levels.length - topLevel;
|
|
386
|
+
const levelEnd = Math.min(maxLevels, props.levelEnd ?? maxLevels);
|
|
387
|
+
const renderStartI = Math.max(levelStart, props.startI ?? levelStart);
|
|
388
|
+
const renderStartJ = Math.max(0, props.startJ ?? 0);
|
|
389
|
+
|
|
390
|
+
const { spyName } = props;
|
|
391
|
+
const getColorForBar = (
|
|
392
|
+
level: number[],
|
|
393
|
+
j: number,
|
|
394
|
+
i: number,
|
|
395
|
+
collapsed: boolean,
|
|
396
|
+
isHighlighted: boolean
|
|
397
|
+
) => {
|
|
398
|
+
const common = {
|
|
399
|
+
level,
|
|
400
|
+
j,
|
|
401
|
+
// discount for the levels we skipped
|
|
402
|
+
// otherwise it will dim out all nodes
|
|
403
|
+
i:
|
|
404
|
+
i +
|
|
405
|
+
focusedNode.mapOrElse(
|
|
406
|
+
() => 0,
|
|
407
|
+
(f) => f.i
|
|
408
|
+
),
|
|
409
|
+
names,
|
|
410
|
+
collapsed,
|
|
411
|
+
selectedLevel,
|
|
412
|
+
highlightModeOn,
|
|
413
|
+
isHighlighted,
|
|
414
|
+
// keep type narrow https://stackoverflow.com/q/54333982
|
|
415
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
|
|
416
|
+
spyName: spyName as SpyName,
|
|
417
|
+
palette,
|
|
418
|
+
colorByNameIndex,
|
|
419
|
+
diffColorFn,
|
|
420
|
+
};
|
|
421
|
+
|
|
422
|
+
switch (format) {
|
|
423
|
+
case 'single': {
|
|
424
|
+
return getColorSingle({ ...common });
|
|
425
|
+
}
|
|
426
|
+
case 'double': {
|
|
427
|
+
return getColorDouble({
|
|
428
|
+
...common,
|
|
429
|
+
leftTicks: props.leftTicks,
|
|
430
|
+
rightTicks: props.rightTicks,
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
default: {
|
|
434
|
+
throw new Error(`Unsupported format: ${format}`);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
};
|
|
438
|
+
|
|
439
|
+
if (renderMode === 'highlightOnly' && highlightModeOn && renderRects) {
|
|
440
|
+
const barIndex = getBarIndex(levels, format);
|
|
441
|
+
const highlightNameIndices: number[] = [];
|
|
442
|
+
for (let i = 1; i < names.length; i += 1) {
|
|
443
|
+
if (highlightByNameIndex && highlightByNameIndex[i] === 1) {
|
|
444
|
+
highlightNameIndices.push(i);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
for (let i = 0; i < highlightNameIndices.length; i += 1) {
|
|
448
|
+
const nameIndex = highlightNameIndices[i];
|
|
449
|
+
const entries = barIndex.get(nameIndex);
|
|
450
|
+
if (!entries) {
|
|
451
|
+
continue;
|
|
452
|
+
}
|
|
453
|
+
for (let k = 0; k < entries.length; k += 1) {
|
|
454
|
+
const entry = entries[k];
|
|
455
|
+
const relativeLevel = entry.i - topLevel;
|
|
456
|
+
if (
|
|
457
|
+
entry.i < topLevel ||
|
|
458
|
+
relativeLevel < levelStart ||
|
|
459
|
+
relativeLevel >= levelEnd
|
|
460
|
+
) {
|
|
461
|
+
continue;
|
|
462
|
+
}
|
|
463
|
+
const level = levels[entry.i];
|
|
464
|
+
const barIndexValue = ff.getBarOffset(level, entry.j);
|
|
465
|
+
const x = tickToX(barIndexValue);
|
|
466
|
+
const y = relativeLevel * PX_PER_LEVEL + focusOffset;
|
|
467
|
+
const sh = BAR_HEIGHT;
|
|
468
|
+
const numBarTicks = ff.getBarTotal(level, entry.j);
|
|
469
|
+
const collapsed = numBarTicks * pxPerTick <= COLLAPSE_THRESHOLD;
|
|
470
|
+
const sw = numBarTicks * pxPerTick - (collapsed ? 0 : GAP);
|
|
471
|
+
if (sw <= 0) {
|
|
472
|
+
continue;
|
|
473
|
+
}
|
|
474
|
+
ctx.beginPath();
|
|
475
|
+
ctx.rect(x, y, sw, sh);
|
|
476
|
+
const color = getColorForBar(
|
|
477
|
+
level,
|
|
478
|
+
entry.j,
|
|
479
|
+
relativeLevel,
|
|
480
|
+
collapsed,
|
|
481
|
+
true
|
|
482
|
+
);
|
|
483
|
+
ctx.fillStyle = color.string();
|
|
484
|
+
ctx.fill();
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
return { done: true, nextI: levelEnd, nextJ: 0 };
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
for (let i = renderStartI; i < levelEnd; i += 1) {
|
|
243
491
|
const level = levels[topLevel + i];
|
|
244
492
|
if (!level) {
|
|
245
493
|
throw new Error(`Could not find level: ${topLevel + i}`);
|
|
246
494
|
}
|
|
247
495
|
|
|
248
|
-
|
|
496
|
+
let j = i === renderStartI ? renderStartJ : 0;
|
|
497
|
+
for (; j < level.length; j += ff.jStep) {
|
|
249
498
|
const barIndex = ff.getBarOffset(level, j);
|
|
250
499
|
const x = tickToX(barIndex);
|
|
251
500
|
const y = i * PX_PER_LEVEL + focusOffset;
|
|
@@ -277,55 +526,15 @@ export default function RenderCanvas(props: CanvasRendererConfig) {
|
|
|
277
526
|
/*******************************/
|
|
278
527
|
/* D r a w R e c t */
|
|
279
528
|
/*******************************/
|
|
280
|
-
const { spyName } = props;
|
|
281
|
-
|
|
282
|
-
const getColor = () => {
|
|
283
|
-
const common = {
|
|
284
|
-
level,
|
|
285
|
-
j,
|
|
286
|
-
// discount for the levels we skipped
|
|
287
|
-
// otherwise it will dim out all nodes
|
|
288
|
-
i:
|
|
289
|
-
i +
|
|
290
|
-
focusedNode.mapOrElse(
|
|
291
|
-
() => 0,
|
|
292
|
-
(f) => f.i
|
|
293
|
-
),
|
|
294
|
-
names,
|
|
295
|
-
collapsed,
|
|
296
|
-
selectedLevel,
|
|
297
|
-
highlightModeOn,
|
|
298
|
-
isHighlighted,
|
|
299
|
-
// keep type narrow https://stackoverflow.com/q/54333982
|
|
300
|
-
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
|
|
301
|
-
spyName: spyName as SpyName,
|
|
302
|
-
palette,
|
|
303
|
-
colorByNameIndex,
|
|
304
|
-
diffColorFn,
|
|
305
|
-
};
|
|
306
|
-
|
|
307
|
-
switch (format) {
|
|
308
|
-
case 'single': {
|
|
309
|
-
return getColorSingle({ ...common });
|
|
310
|
-
}
|
|
311
|
-
case 'double': {
|
|
312
|
-
return getColorDouble({
|
|
313
|
-
...common,
|
|
314
|
-
leftTicks: props.leftTicks,
|
|
315
|
-
rightTicks: props.rightTicks,
|
|
316
|
-
});
|
|
317
|
-
}
|
|
318
|
-
default: {
|
|
319
|
-
throw new Error(`Unsupported format: ${format}`);
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
};
|
|
323
|
-
|
|
324
529
|
ctx.beginPath();
|
|
325
530
|
ctx.rect(x, y, sw, sh);
|
|
326
531
|
if (renderRects) {
|
|
327
|
-
|
|
328
|
-
|
|
532
|
+
if (renderMode === 'forceGrey') {
|
|
533
|
+
ctx.fillStyle = colorGreyscale(200, 0.66).rgb().string();
|
|
534
|
+
} else {
|
|
535
|
+
const color = getColorForBar(level, j, i, collapsed, isHighlighted);
|
|
536
|
+
ctx.fillStyle = color.string();
|
|
537
|
+
}
|
|
329
538
|
ctx.fill();
|
|
330
539
|
}
|
|
331
540
|
|
|
@@ -333,7 +542,10 @@ export default function RenderCanvas(props: CanvasRendererConfig) {
|
|
|
333
542
|
/* D r a w T e x t */
|
|
334
543
|
/*******************************/
|
|
335
544
|
// don't write text if there's not enough space for a single letter
|
|
336
|
-
if (!renderText || collapsed) {
|
|
545
|
+
if (!renderText || collapsed || renderMode === 'forceGrey') {
|
|
546
|
+
if (timeBudgetMs && performance.now() - renderStart > timeBudgetMs) {
|
|
547
|
+
return { done: false, nextI: i, nextJ: j + ff.jStep };
|
|
548
|
+
}
|
|
337
549
|
continue;
|
|
338
550
|
}
|
|
339
551
|
|
|
@@ -365,8 +577,16 @@ export default function RenderCanvas(props: CanvasRendererConfig) {
|
|
|
365
577
|
const namePosX = Math.round(Math.max(x, 0));
|
|
366
578
|
ctx.fillText(fitCalc.text, namePosX + fitCalc.marginLeft, y + sh / 2 + 1);
|
|
367
579
|
ctx.restore();
|
|
580
|
+
|
|
581
|
+
if (timeBudgetMs && performance.now() - renderStart > timeBudgetMs) {
|
|
582
|
+
return { done: false, nextI: i, nextJ: j + ff.jStep };
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
if (timeBudgetMs && performance.now() - renderStart > timeBudgetMs) {
|
|
586
|
+
return { done: false, nextI: i + 1, nextJ: 0 };
|
|
368
587
|
}
|
|
369
588
|
}
|
|
589
|
+
return { done: true, nextI: levelEnd, nextJ: 0 };
|
|
370
590
|
}
|
|
371
591
|
|
|
372
592
|
function getFunctionName(
|
|
@@ -517,26 +737,18 @@ function getColorDouble(
|
|
|
517
737
|
return colorBasedOnDiffPercent(cfg.palette, leftPercent, rightPercent).alpha(a);
|
|
518
738
|
}
|
|
519
739
|
|
|
520
|
-
function getCanvasWidth(canvas: HTMLCanvasElement) {
|
|
740
|
+
function getCanvasWidth(canvas: HTMLCanvasElement | OffscreenCanvas) {
|
|
521
741
|
// clientWidth includes padding
|
|
522
742
|
// however it's not present in node-canvas (used for testing)
|
|
523
743
|
// so we also fallback to canvas.width
|
|
524
|
-
|
|
744
|
+
if ('clientWidth' in canvas && typeof canvas.clientWidth === 'number') {
|
|
745
|
+
return canvas.clientWidth;
|
|
746
|
+
}
|
|
747
|
+
return canvas.width;
|
|
525
748
|
}
|
|
526
749
|
|
|
527
750
|
function buildHighlightByNameIndex(names: string[], query: string) {
|
|
528
|
-
|
|
529
|
-
const matches = new Uint8Array(names.length);
|
|
530
|
-
for (let i = 1; i < names.length; i += 1) {
|
|
531
|
-
const name = names[i];
|
|
532
|
-
if (!name) {
|
|
533
|
-
continue;
|
|
534
|
-
}
|
|
535
|
-
if (name.toLowerCase().includes(queryLower)) {
|
|
536
|
-
matches[i] = 1;
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
return matches;
|
|
751
|
+
return getHighlightByNameIndex(names, query);
|
|
540
752
|
}
|
|
541
753
|
|
|
542
754
|
function buildColorByNameIndex(
|
|
@@ -544,13 +756,5 @@ function buildColorByNameIndex(
|
|
|
544
756
|
spyName: SpyName,
|
|
545
757
|
palette: FlamegraphPalette
|
|
546
758
|
) {
|
|
547
|
-
|
|
548
|
-
names.length
|
|
549
|
-
);
|
|
550
|
-
for (let i = 0; i < names.length; i += 1) {
|
|
551
|
-
const name = names[i] || '';
|
|
552
|
-
const packageName = getPackageNameFromStackTrace(spyName, name) || '';
|
|
553
|
-
colors[i] = colorBasedOnPackageName(palette, packageName);
|
|
554
|
-
}
|
|
555
|
-
return colors;
|
|
759
|
+
return getColorByNameIndexCached(names, spyName, palette);
|
|
556
760
|
}
|
|
@@ -24,6 +24,9 @@ export default function Highlight(props: HighlightProps) {
|
|
|
24
24
|
height: '0px',
|
|
25
25
|
visibility: 'hidden',
|
|
26
26
|
});
|
|
27
|
+
const highlightRef = React.useRef<HTMLDivElement>(null);
|
|
28
|
+
const rafRef = React.useRef<number | null>(null);
|
|
29
|
+
const lastStyleRef = React.useRef<React.CSSProperties | null>(null);
|
|
27
30
|
|
|
28
31
|
React.useEffect(() => {
|
|
29
32
|
// stops highlighting every time a node is zoomed or unzoomed
|
|
@@ -35,27 +38,46 @@ export default function Highlight(props: HighlightProps) {
|
|
|
35
38
|
});
|
|
36
39
|
}, [zoom]);
|
|
37
40
|
|
|
38
|
-
const
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
setStyle({
|
|
45
|
-
visibility: 'visible',
|
|
46
|
-
height: `${barHeight}px`,
|
|
47
|
-
...data,
|
|
48
|
-
});
|
|
49
|
-
} else {
|
|
50
|
-
// it doesn't map to a valid xy
|
|
51
|
-
// so it means we are hovering out
|
|
52
|
-
onMouseOut();
|
|
41
|
+
const applyStyle = (next: React.CSSProperties) => {
|
|
42
|
+
const node = highlightRef.current;
|
|
43
|
+
if (!node) {
|
|
44
|
+
setStyle(next);
|
|
45
|
+
return;
|
|
53
46
|
}
|
|
47
|
+
const prev = lastStyleRef.current;
|
|
48
|
+
if (prev && prev.left === next.left && prev.top === next.top && prev.width === next.width && prev.visibility === next.visibility) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
lastStyleRef.current = next;
|
|
52
|
+
node.style.visibility = String(next.visibility || '');
|
|
53
|
+
if (next.left !== undefined) node.style.left = String(next.left);
|
|
54
|
+
if (next.top !== undefined) node.style.top = String(next.top);
|
|
55
|
+
if (next.width !== undefined) node.style.width = String(next.width);
|
|
56
|
+
if (next.height !== undefined) node.style.height = String(next.height);
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const onMouseMove = (e: MouseEvent) => {
|
|
60
|
+
if (rafRef.current) cancelAnimationFrame(rafRef.current);
|
|
61
|
+
const x = e.offsetX;
|
|
62
|
+
const y = e.offsetY;
|
|
63
|
+
rafRef.current = requestAnimationFrame(() => {
|
|
64
|
+
const opt = xyToHighlightData(x, y);
|
|
65
|
+
if (opt.isJust) {
|
|
66
|
+
const data = opt.value;
|
|
67
|
+
applyStyle({
|
|
68
|
+
visibility: 'visible',
|
|
69
|
+
height: `${barHeight}px`,
|
|
70
|
+
...data,
|
|
71
|
+
});
|
|
72
|
+
} else {
|
|
73
|
+
onMouseOut();
|
|
74
|
+
}
|
|
75
|
+
});
|
|
54
76
|
};
|
|
55
77
|
|
|
56
78
|
const onMouseOut = () => {
|
|
57
|
-
|
|
58
|
-
...
|
|
79
|
+
applyStyle({
|
|
80
|
+
...(lastStyleRef.current || {}),
|
|
59
81
|
visibility: 'hidden',
|
|
60
82
|
});
|
|
61
83
|
};
|
|
@@ -77,6 +99,9 @@ export default function Highlight(props: HighlightProps) {
|
|
|
77
99
|
return () => {
|
|
78
100
|
canvasEl.removeEventListener('mousemove', onMouseMove);
|
|
79
101
|
canvasEl.removeEventListener('mouseout', onMouseOut);
|
|
102
|
+
if (rafRef.current) {
|
|
103
|
+
cancelAnimationFrame(rafRef.current);
|
|
104
|
+
}
|
|
80
105
|
};
|
|
81
106
|
},
|
|
82
107
|
|
|
@@ -86,6 +111,7 @@ export default function Highlight(props: HighlightProps) {
|
|
|
86
111
|
|
|
87
112
|
return (
|
|
88
113
|
<div
|
|
114
|
+
ref={highlightRef}
|
|
89
115
|
className={styles.highlight}
|
|
90
116
|
style={style}
|
|
91
117
|
data-testid="flamegraph-highlight"
|