Skip to content

Commit d22f530

Browse files
committed
resize canvas according to the amount of frames
commit_hash:99f324d2b46ac4c095b1a7725e126081c5e0396d
1 parent ec2016d commit d22f530

File tree

4 files changed

+62
-13
lines changed

4 files changed

+62
-13
lines changed

perforator/ui/app/src/main.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ buildFactory().then(() => {
1616
uiFactory().configureApp();
1717
ReactDOM.createRoot(document.getElementById('root')!, {
1818
onRecoverableError: (error, errorInfo) => {
19-
uiFactory().logError(error, { errorInfo });
19+
uiFactory?.()?.logError?.(error, { errorInfo });
2020
},
2121
}).render(
2222
<React.StrictMode>

perforator/ui/packages/flamegraph/lib/components/Flamegraph/Flamegraph.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ export interface FlamegraphProps extends Pick<RenderFlamegraphOptions, 'onFinish
5757
onSearch?: (search: string) => void;
5858
onSearchReset?: () => void;
5959
onKeepOnlyFound?: (value: boolean) => void;
60+
useSelfAsScrollParent?: boolean;
6061
}
6162

6263
const MAX_FIREFOX_DEPTH = 768;
@@ -86,6 +87,7 @@ export const Flamegraph: React.FC<FlamegraphProps> = ({
8687
onSearch,
8788
onKeepOnlyFound,
8889
onSearchReset,
90+
useSelfAsScrollParent,
8991
}) => {
9092
const flamegraphContainer = React.useRef<HTMLDivElement | null>(null);
9193
const flamegraphCanvas = React.useRef<HTMLCanvasElement | null>(null);
@@ -169,6 +171,8 @@ export const Flamegraph: React.FC<FlamegraphProps> = ({
169171

170172
React.useEffect(() => {
171173
if (flamegraphContainer.current && profileData && flamegraphOffsets.current) {
174+
setHoverData(null);
175+
172176
const renderOptions: RenderFlamegraphOptions = {
173177
setState: setQuery,
174178
getState: getQuery,
@@ -183,6 +187,7 @@ export const Flamegraph: React.FC<FlamegraphProps> = ({
183187
// by default, we show highlight, e.g. after clicks
184188
// and don't show it only on first render
185189
disableHighlightRender: shouldOmitHighlight.current,
190+
scrollParent: useSelfAsScrollParent ? flamegraphContainer.current : document.documentElement,
186191
};
187192

188193
try {
@@ -266,7 +271,7 @@ export const Flamegraph: React.FC<FlamegraphProps> = ({
266271
onFrameAltClick?.(e, stringifiedNode);
267272
}
268273
}
269-
}, [profileData, onFrameClick, popupData]);
274+
}, [popupData, onFrameClick, profileData, onFrameAltClick]);
270275

271276
const handleMouseMove = React.useCallback((e: MouseEvent) => {
272277
const offsetX = e.offsetX;

perforator/ui/packages/flamegraph/lib/components/SideBySide/SideBySide.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ export function SideBySide(props: SideBySideProps) {
5757
/>
5858
<Divider orientation={'vertical'} />
5959
<Flamegraph
60+
useSelfAsScrollParent
6061
className={b('flamegraph')}
6162
{...props}
6263
/>

perforator/ui/packages/flamegraph/lib/renderer.ts

Lines changed: 54 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ export type RenderFlamegraphOptions = {
6363
reverse: boolean;
6464
keepOnlyFound: boolean;
6565
disableHighlightRender: boolean;
66+
scrollParent: HTMLElement;
6667
}
6768

6869
function makeByH(coords: Coordinate[]): Record<H, Set<I>> {
@@ -110,11 +111,13 @@ export class FlamegraphOffseter {
110111
private reverse: boolean;
111112
levelHeight: number;
112113
private shouldReverseDiff = false;
114+
private maxVerticalRow: number | undefined;
113115

114116
constructor(rows: ProfileData['rows'], options: { reverse: boolean; levelHeight: number }) {
115117
this.rows = rows;
116118
this.reverse = options.reverse;
117119
this.levelHeight = options.levelHeight;
120+
this.maxVerticalRow = this.rows.length;
118121
}
119122
fillFramesWindow([hmax, imax]: Coordinate): FramesWindow {
120123
const res: Record<number, Interval> = [];
@@ -154,7 +157,7 @@ export class FlamegraphOffseter {
154157
}
155158

156159
calcTopOffset(h: number) {
157-
return this.reverse ? h * this.levelHeight : (this.rows.length * this.levelHeight) - (h + 1) * this.levelHeight;
160+
return this.reverse ? h * this.levelHeight : (this.maxVerticalRow * this.levelHeight) - (h + 1) * this.levelHeight;
158161
}
159162

160163
createShouldDrawFrame(h: number) {
@@ -325,10 +328,13 @@ export class FlamegraphOffseter {
325328
this.widthRatio = (this.getEvents(root) - (root.omittedEventCount ?? 0)) / canvasWidth!;
326329
this.minVisibleEv = minVisibleWidth * this.widthRatio;
327330

331+
let maxDrawableLayerDepth = 0;
332+
328333
for (let h = 0; h < this.rows.length; h++) {
329334
const shouldDrawFrame = this.createShouldDrawFrame(h);
330335
const updateOffsets = this.createOffsetKeeper(h);
331336
const updateFrameWindows = this.createUpdateWindow(h);
337+
let shouldDrawLayer = false;
332338
for (let i = 0; i < this.rows[h].length; i++) {
333339
if (!shouldDrawFrame(i)) {
334340
continue;
@@ -340,13 +346,21 @@ export class FlamegraphOffseter {
340346
}
341347
const isVisible = this.visibleNode(this.rows[h][i]);
342348
updateOffsets(i, isVisible);
349+
if (isVisible) {
350+
shouldDrawLayer = true;
351+
}
352+
}
343353

354+
if (shouldDrawLayer) {
355+
maxDrawableLayerDepth = h;
344356
}
345357

346358
if (!this.framesWindow?.[h]) {
347359
break;
348360
}
349361
}
362+
this.maxVerticalRow = maxDrawableLayerDepth;
363+
return maxDrawableLayerDepth;
350364
}
351365

352366
findFrame(frames: FormatNode[], x: number, left = 0, right = frames.length - 1) {
@@ -406,7 +420,7 @@ export class FlamegraphOffseter {
406420

407421

408422
getTopOffset(offset: number) {
409-
return this.reverse ? offset : ((this.rows.length * this.levelHeight) - offset);
423+
return this.reverse ? offset : ((this.maxVerticalRow * this.levelHeight) - offset);
410424
}
411425

412426
getCoordsByPosition: (x: number, y: number) => null | { h: number; i: number } = (x, y) => {
@@ -467,7 +481,7 @@ export const renderFlamegraph: RenderFlamegraphType = (
467481
flamegraphContainer,
468482
profileData,
469483
fg,
470-
{ getState, setState, theme, isDiff, onFinishRendering, shortenFrameTexts, excludeSearchPattern, searchPattern, reverse, keepOnlyFound, disableHighlightRender },
484+
{ getState, setState, theme, isDiff, onFinishRendering, shortenFrameTexts, excludeSearchPattern, searchPattern, reverse, keepOnlyFound, disableHighlightRender, scrollParent },
471485
) => {
472486
const shouldSwapDiff = getState('flameBase') === 'diff';
473487

@@ -510,14 +524,30 @@ export const renderFlamegraph: RenderFlamegraphType = (
510524
let canvasWidth: number | undefined;
511525
let canvasHeight: number | undefined;
512526

527+
function initCanvasVertical(layersCount: number, shouldPreserveVerticalScroll?: boolean, scrollableElement: HTMLElement = document.documentElement) {
528+
// need to keep the same vertical scroll when vertically resizing reversed flamegraph
529+
const prevScroll = scrollableElement.scrollTop;
530+
const prevHeight = canvas.offsetHeight;
531+
const prevScrollHeight = getScrollHeight(scrollableElement);
532+
const prevBottomOffset = prevScrollHeight - prevScroll;
533+
534+
canvas.style.height = (layersCount ? layersCount + 1 : profileData.rows.length) * LEVEL_HEIGHT + 'px';
535+
canvasHeight = canvas.offsetHeight;
536+
canvas.height = canvasHeight * (devicePixelRatio || 1);
537+
if (devicePixelRatio) { c.scale(devicePixelRatio, devicePixelRatio); }
538+
if (shouldPreserveVerticalScroll && prevHeight !== 0) {
539+
const scrollHeight = getScrollHeight(scrollableElement);
540+
const scroll = scrollableElement.scrollTop;
541+
const newBottomOffset = scrollHeight - scroll;
542+
const diff = prevBottomOffset - newBottomOffset;
543+
544+
scrollableElement.scrollBy(0, -diff);
545+
}
546+
}
513547
function initCanvas() {
514-
canvas.style.height = profileData.rows.length * LEVEL_HEIGHT + 'px';
515548
canvasWidth = canvas.offsetWidth;
516-
canvasHeight = canvas.offsetHeight;
517549
canvas.style.width = canvasWidth + 'px';
518550
canvas.width = canvasWidth * (devicePixelRatio || 1);
519-
canvas.height = canvasHeight * (devicePixelRatio || 1);
520-
if (devicePixelRatio) { c.scale(devicePixelRatio, devicePixelRatio); }
521551
}
522552

523553
initCanvas();
@@ -870,22 +900,28 @@ export const renderFlamegraph: RenderFlamegraphType = (
870900

871901
const foundCoords = maybeSearch(searchPattern, excludeSearchPattern);
872902

873-
fg.prerenderOffsets(canvasWidth!, [h, pos], omittedStacks, foundCoords, shouldSwapDiff);
874-
render({ pattern: searchPattern, excludePattern: excludeSearchPattern });
903+
{
904+
const layerCount = fg.prerenderOffsets(canvasWidth!, [h, pos], omittedStacks, foundCoords, shouldSwapDiff);
905+
initCanvasVertical(layerCount, !reverse && !disableHighlightRender, scrollParent);
906+
}
907+
render({ pattern: searchPattern });
875908
if (!disableHighlightRender) {
876909
renderHighlightRect(h, pos);
877910
}
878911

879912

913+
// maybe ignore vertiacal resizes?
880914
const onResize = () => requestAnimationFrame(() => {
881915

916+
if (canvasWidth === canvas.offsetWidth) {return;}
882917
const initialH = parseInt(getState('frameDepth', '0'));
883918
const initialI = parseInt(getState('framePos', '0'));
884919
//@ts-ignore
885920
canvas.style.width = null;
886921
initCanvas();
887-
fg.prerenderOffsets(canvasWidth!, [initialH, initialI], omittedStacks, foundCoords, shouldSwapDiff);
888-
render({ pattern: searchPattern, excludePattern: excludeSearchPattern });
922+
const layerCount = fg.prerenderOffsets(canvasWidth!, [initialH, initialI], omittedStacks, foundCoords, shouldSwapDiff);
923+
initCanvasVertical(layerCount, !reverse, scrollParent);
924+
render({ pattern: searchPattern });
889925
});
890926
window.addEventListener('resize', onResize);
891927

@@ -903,3 +939,10 @@ export const renderFlamegraph: RenderFlamegraphType = (
903939
canvas.style.cursor = 'pointer';
904940
}
905941
};
942+
function getScrollHeight(scrollableElement: HTMLElement) {
943+
return Math.max(
944+
scrollableElement.scrollHeight,
945+
scrollableElement.offsetHeight,
946+
scrollableElement.clientHeight,
947+
);
948+
}

0 commit comments

Comments
 (0)