Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 37 additions & 24 deletions src/components/flame-graph/Canvas.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ export type OwnProps = {
readonly scrollToSelectionGeneration: number;
readonly categories: CategoryList;
readonly interval: Milliseconds;
readonly startsAtBottom: boolean;
readonly callTreeSummaryStrategy: CallTreeSummaryStrategy;
readonly ctssSamples: SamplesLikeTable;
readonly ctssSampleCategoriesAndSubcategories: SampleCategoriesAndSubcategories;
Expand Down Expand Up @@ -123,8 +124,12 @@ class FlameGraphCanvasImpl extends React.PureComponent<Props> {
// If the stack depth changes (say, when changing the time range
// selection or applying a transform), move the viewport
// vertically so that its offset from the base of the flame graph
// is maintained.
if (prevProps.maxStackDepthPlusOne !== this.props.maxStackDepthPlusOne) {
// is maintained. In top-down layout the base is at the top, so no
// adjustment is needed when depth grows or shrinks.
if (
this.props.startsAtBottom &&
prevProps.maxStackDepthPlusOne !== this.props.maxStackDepthPlusOne
) {
this.props.viewport.moveViewport(
0,
(prevProps.maxStackDepthPlusOne - this.props.maxStackDepthPlusOne) *
Expand All @@ -150,16 +155,21 @@ class FlameGraphCanvasImpl extends React.PureComponent<Props> {
}

_scrollSelectionIntoView = () => {
const { selectedCallNodeIndex, maxStackDepthPlusOne, callNodeInfo } =
this.props;
const {
selectedCallNodeIndex,
maxStackDepthPlusOne,
callNodeInfo,
startsAtBottom,
} = this.props;

if (selectedCallNodeIndex === null) {
return;
}

const callNodeTable = callNodeInfo.getCallNodeTable();
const depth = callNodeTable.depth[selectedCallNodeIndex];
const y = (maxStackDepthPlusOne - depth - 1) * ROW_HEIGHT;
const depth = callNodeInfo.depthForNode(selectedCallNodeIndex);
const y = startsAtBottom
? (maxStackDepthPlusOne - depth - 1) * ROW_HEIGHT
: depth * ROW_HEIGHT;

if (y < this.props.viewport.viewportTop) {
this.props.viewport.moveViewport(0, this.props.viewport.viewportTop - y);
Expand Down Expand Up @@ -191,6 +201,7 @@ class FlameGraphCanvasImpl extends React.PureComponent<Props> {
viewportTop,
viewportBottom,
},
startsAtBottom,
} = this.props;

const { hoveredItem } = hoverInfo;
Expand Down Expand Up @@ -231,14 +242,12 @@ class FlameGraphCanvasImpl extends React.PureComponent<Props> {
fastFillStyle.set(getBackgroundColor());
ctx.fillRect(0, 0, deviceContainerWidth, deviceContainerHeight);

const callNodeTable = callNodeInfo.getCallNodeTable();

const startDepth = Math.floor(
maxStackDepthPlusOne - viewportBottom / stackFrameHeight
);
const endDepth = Math.ceil(
maxStackDepthPlusOne - viewportTop / stackFrameHeight
);
const startDepth = startsAtBottom
? Math.floor(maxStackDepthPlusOne - viewportBottom / stackFrameHeight)
: Math.floor(viewportTop / stackFrameHeight);
const endDepth = startsAtBottom
? Math.ceil(maxStackDepthPlusOne - viewportTop / stackFrameHeight)
: Math.ceil(viewportBottom / stackFrameHeight);

// Only draw the stack frames that are vertically within view.
// The graph is drawn from bottom to top, in order of increasing depth.
Expand All @@ -250,10 +259,12 @@ class FlameGraphCanvasImpl extends React.PureComponent<Props> {
// Get the timing information for a row of stack frames.
const stackTiming = flameGraphTiming.getRow(depth);

const cssRowTop: CssPixels =
(maxStackDepthPlusOne - depth - 1) * ROW_HEIGHT - viewportTop;
const cssRowBottom: CssPixels =
(maxStackDepthPlusOne - depth) * ROW_HEIGHT - viewportTop;
const cssRowTop: CssPixels = startsAtBottom
? (maxStackDepthPlusOne - depth - 1) * ROW_HEIGHT - viewportTop
: depth * ROW_HEIGHT - viewportTop;
const cssRowBottom: CssPixels = startsAtBottom
? (maxStackDepthPlusOne - depth) * ROW_HEIGHT - viewportTop
: (depth + 1) * ROW_HEIGHT - viewportTop;
const deviceRowTop: DevicePixels = snap(cssRowTop * cssToDeviceScale);
const deviceRowBottom: DevicePixels =
snap(cssRowBottom * cssToDeviceScale) - 1;
Expand Down Expand Up @@ -299,7 +310,7 @@ class FlameGraphCanvasImpl extends React.PureComponent<Props> {
i === hoveredItem.flameGraphTimingIndex;
const isHighlighted = isSelected || isRightClicked || isHovered;

const categoryIndex = callNodeTable.category[callNodeIndex];
const categoryIndex = callNodeInfo.categoryForNode(callNodeIndex);
const category = categories[categoryIndex];
const colorStyles = mapCategoryColorNameToStackChartStyles(
category.color
Expand All @@ -321,7 +332,7 @@ class FlameGraphCanvasImpl extends React.PureComponent<Props> {
deviceBoxLeft + deviceHorizontalPadding;
const deviceTextWidth: DevicePixels = deviceBoxRight - deviceTextLeft;
if (deviceTextWidth > textMeasurement.minWidth) {
const funcIndex = callNodeTable.func[callNodeIndex];
const funcIndex = callNodeInfo.funcForNode(callNodeIndex);
const funcName = thread.stringTable.getString(
thread.funcTable.name[funcIndex]
);
Expand Down Expand Up @@ -472,11 +483,13 @@ class FlameGraphCanvasImpl extends React.PureComponent<Props> {
flameGraphTiming,
maxStackDepthPlusOne,
viewport: { viewportTop, containerWidth },
startsAtBottom,
} = this.props;
const pos = x / containerWidth;
const depth = Math.floor(
maxStackDepthPlusOne - (y + viewportTop) / ROW_HEIGHT
);
const depth = startsAtBottom
? Math.floor(maxStackDepthPlusOne - (y + viewportTop) / ROW_HEIGHT)
: Math.floor((y + viewportTop) / ROW_HEIGHT);

if (depth < 0 || depth >= flameGraphTiming.rowCount) {
return null;
}
Expand Down
1 change: 1 addition & 0 deletions src/components/flame-graph/ConnectedFlameGraph.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ class ConnectedFlameGraphImpl
scrollToSelectionGeneration={scrollToSelectionGeneration}
categories={categories}
interval={interval}
startsAtBottom={true}
callTreeSummaryStrategy={callTreeSummaryStrategy}
ctssSamples={ctssSamples}
ctssSampleCategoriesAndSubcategories={
Expand Down
86 changes: 44 additions & 42 deletions src/components/flame-graph/FlameGraph.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ export type Props = {
readonly scrollToSelectionGeneration: number;
readonly categories: CategoryList;
readonly interval: Milliseconds;
readonly startsAtBottom: boolean;
readonly callTreeSummaryStrategy: CallTreeSummaryStrategy;
readonly ctssSamples: SamplesLikeTable;
readonly ctssSampleCategoriesAndSubcategories: SampleCategoriesAndSubcategories;
Expand Down Expand Up @@ -118,8 +119,7 @@ export class FlameGraph
_wideEnough = (callNodeIndex: IndexIntoCallNodeTable): boolean => {
const { flameGraphTiming, callNodeInfo } = this.props;

const callNodeTable = callNodeInfo.getCallNodeTable();
const depth = callNodeTable.depth[callNodeIndex];
const depth = callNodeInfo.depthForNode(callNodeIndex);
const row = flameGraphTiming.getRow(depth);
const columnIndex = row.callNode.indexOf(callNodeIndex);
return row.end[columnIndex] - row.start[columnIndex] > SELECTABLE_THRESHOLD;
Expand All @@ -143,8 +143,7 @@ export class FlameGraph

let callNodeIndex = startingCallNodeIndex;

const callNodeTable = callNodeInfo.getCallNodeTable();
const depth = callNodeTable.depth[callNodeIndex];
const depth = callNodeInfo.depthForNode(callNodeIndex);
const row = flameGraphTiming.getRow(depth);
let columnIndex = row.callNode.indexOf(callNodeIndex);

Expand Down Expand Up @@ -173,8 +172,8 @@ export class FlameGraph
onSelectedCallNodeChange,
onCallNodeEnterOrDoubleClick,
onKeyboardTransformShortcut,
startsAtBottom,
} = this.props;
const callNodeTable = callNodeInfo.getCallNodeTable();

if (
// Please do not forget to update the switch/case below if changing the array to allow more keys.
Expand All @@ -186,43 +185,45 @@ export class FlameGraph
return;
}

switch (event.key) {
case 'ArrowDown': {
const prefix = callNodeTable.prefix[selectedCallNodeIndex];
if (prefix !== -1) {
onSelectedCallNodeChange(prefix);
}
break;
// In top-down layout the parent is visually above the selected box, so
// ArrowUp navigates to the parent and ArrowDown to the first child.
// In bottom-up layout it's the other way around.
const isGoToParent = startsAtBottom
? event.key === 'ArrowDown'
: event.key === 'ArrowUp';
const isGoToChild = startsAtBottom
? event.key === 'ArrowUp'
: event.key === 'ArrowDown';

if (isGoToParent) {
const prefix = callNodeInfo.prefixForNode(selectedCallNodeIndex);
if (prefix !== -1) {
onSelectedCallNodeChange(prefix);
}
case 'ArrowUp': {
const [callNodeIndex] = callTree.getChildren(selectedCallNodeIndex);
// The call nodes returned from getChildren are sorted by
// total time in descending order. The first one in the
// array, which is the one we pick, has the longest time and
// thus the widest box.

if (callNodeIndex !== undefined && this._wideEnough(callNodeIndex)) {
onSelectedCallNodeChange(callNodeIndex);
}
break;
} else if (isGoToChild) {
const [callNodeIndex] = callTree.getChildren(selectedCallNodeIndex);
// The call nodes returned from getChildren are sorted by
// total time in descending order. The first one in the
// array, which is the one we pick, has the longest time and
// thus the widest box.

if (callNodeIndex !== undefined && this._wideEnough(callNodeIndex)) {
onSelectedCallNodeChange(callNodeIndex);
}
case 'ArrowLeft':
case 'ArrowRight': {
const callNodeIndex = this._nextSelectableInRow(
selectedCallNodeIndex,
event.key === 'ArrowLeft' ? -1 : 1
);

if (callNodeIndex !== undefined) {
onSelectedCallNodeChange(callNodeIndex);
}
break;
} else if (event.key === 'ArrowLeft' || event.key === 'ArrowRight') {
const callNodeIndex = this._nextSelectableInRow(
selectedCallNodeIndex,
event.key === 'ArrowLeft' ? -1 : 1
);

if (callNodeIndex !== undefined) {
onSelectedCallNodeChange(callNodeIndex);
}
default:
// We shouldn't arrive here, thanks to the if block at the top.
console.error(
`An unknown key "${event.key}" was pressed, this shouldn't happen.`
);
} else {
// We shouldn't arrive here, thanks to the if block at the top.
console.error(
`An unknown key "${event.key}" was pressed, this shouldn't happen.`
);
}
return;
}
Expand All @@ -248,9 +249,8 @@ export class FlameGraph
if (document.activeElement === this._viewport) {
event.preventDefault();
const { callNodeInfo, selectedCallNodeIndex, thread } = this.props;
const callNodeTable = callNodeInfo.getCallNodeTable();
if (selectedCallNodeIndex !== null) {
const funcIndex = callNodeTable.func[selectedCallNodeIndex];
const funcIndex = callNodeInfo.funcForNode(selectedCallNodeIndex);
const funcName = thread.stringTable.getString(
thread.funcTable.name[funcIndex]
);
Expand All @@ -275,6 +275,7 @@ export class FlameGraph
callTreeSummaryStrategy,
categories,
interval,
startsAtBottom,
innerWindowIDToPageMap,
weightType,
ctssSamples,
Expand Down Expand Up @@ -318,7 +319,7 @@ export class FlameGraph
maxViewportHeight,
maximumZoom: 1,
previewSelection,
startsAtBottom: true,
startsAtBottom,
disableHorizontalMovement: true,
viewportNeedsUpdate,
marginLeft: 0,
Expand All @@ -345,6 +346,7 @@ export class FlameGraph
onDoubleClick: onCallNodeEnterOrDoubleClick,
shouldDisplayTooltips: this._shouldDisplayTooltips,
interval,
startsAtBottom,
ctssSamples,
ctssSampleCategoriesAndSubcategories,
tracedTiming: tracedTimingNonInverted,
Expand Down
Loading