diff --git a/src/components/flame-graph/Canvas.tsx b/src/components/flame-graph/Canvas.tsx index c7b042fa0f..caf03dd00a 100644 --- a/src/components/flame-graph/Canvas.tsx +++ b/src/components/flame-graph/Canvas.tsx @@ -419,10 +419,10 @@ class FlameGraphCanvasImpl extends React.PureComponent { return null; } - const ratio = - stackTiming.end[flameGraphTimingIndex] - - stackTiming.start[flameGraphTimingIndex]; - + const ratio = flameGraphTiming.getRatioOfRootTotalSummary( + depth, + flameGraphTimingIndex + ); let percentage = formatPercent(ratio); if (tracedTiming) { const { total } = getSelfAndTotalForCallNode( diff --git a/src/profile-logic/call-tree.ts b/src/profile-logic/call-tree.ts index f870e0f703..0bd8f5ece6 100644 --- a/src/profile-logic/call-tree.ts +++ b/src/profile-logic/call-tree.ts @@ -51,6 +51,7 @@ export type CallTreeTimingsNonInverted = { self: Float64Array; total: Float64Array; rootTotalSummary: number; // sum of absolute values, this is used for computing percentages + flameGraphWidthTotal: number; // used as 100% reference for flame graph box widths }; type TotalAndHasChildren = { total: number; hasChildren: boolean }; @@ -723,7 +724,11 @@ export function computeCallNodeSelfAndSummary( rootTotalSummary += abs(callNodeSelf[callNodeIndex]); } - return { callNodeSelf, rootTotalSummary }; + return { + callNodeSelf, + rootTotalSummary, + flameGraphWidthTotal: rootTotalSummary, + }; } export function getSelfAndTotalForCallNode( @@ -894,7 +899,8 @@ export function computeCallTreeTimingsNonInverted( callNodeSelfAndSummary: CallNodeSelfAndSummary ): CallTreeTimingsNonInverted { const callNodeTable = callNodeInfo.getCallNodeTable(); - const { callNodeSelf, rootTotalSummary } = callNodeSelfAndSummary; + const { callNodeSelf, rootTotalSummary, flameGraphWidthTotal } = + callNodeSelfAndSummary; // Compute the following variables: const callNodeTotal = new Float64Array(callNodeTable.length); @@ -932,6 +938,7 @@ export function computeCallTreeTimingsNonInverted( total: callNodeTotal, callNodeHasChildren, rootTotalSummary, + flameGraphWidthTotal, }; } @@ -1334,5 +1341,9 @@ export function computeCallNodeTracedSelfAndSummary( } } - return { callNodeSelf, rootTotalSummary }; + return { + callNodeSelf, + rootTotalSummary, + flameGraphWidthTotal: rootTotalSummary, + }; } diff --git a/src/profile-logic/flame-graph.ts b/src/profile-logic/flame-graph.ts index 24f8347c5a..a25b674804 100644 --- a/src/profile-logic/flame-graph.ts +++ b/src/profile-logic/flame-graph.ts @@ -52,6 +52,25 @@ export interface FlameGraphTiming { readonly rowCount: number; getRow(depth: number): FlameGraphTimingRow; getAllRowsForTesting(): FlameGraphTimingRow[]; + + // For a given node / "box", get the sample percentage (as a fraction of 1) + // that should be displayed in the tooltip for this node. + getRatioOfRootTotalSummary(depth: number, indexInRow: number): number; +} + +// Returns the width of a box as a fraction of the full flame graph width. +function _getBoxWidth( + flameGraphTiming: FlameGraphTiming, + depth: number, + indexInRow: number +): number { + const row = flameGraphTiming.getRow(depth); + if (indexInRow < 0 || indexInRow >= row.length) { + throw new Error( + `Out-of-bounds call to getRatioOfRootTotalSummary: For depth ${depth}, ${indexInRow} is outside 0..${row.length}` + ); + } + return row.end[indexInRow] - row.start[indexInRow]; } class FlameGraphTimingNonInverted implements FlameGraphTiming { @@ -104,8 +123,16 @@ class FlameGraphTimingNonInverted implements FlameGraphTiming { return rows; } + // For a given node / "box", get the sample percentage (as a fraction of 1) that + // should be displayed in the tooltip for this node. + getRatioOfRootTotalSummary(depth: number, indexInRow: number): number { + const ratioOfFullWidth = _getBoxWidth(this, depth, indexInRow); + const total = ratioOfFullWidth * this._callTreeTimings.flameGraphWidthTotal; + return total / this._callTreeTimings.rootTotalSummary; + } + _buildNextTimingRow(): void { - const { total, self, rootTotalSummary } = this._callTreeTimings; + const { total, self, flameGraphWidthTotal } = this._callTreeTimings; const { prefix } = this._callNodeTable; const depth = this._timingRows.length; @@ -142,8 +169,8 @@ class FlameGraphTimingNonInverted implements FlameGraphTiming { } startPerCallNode[nodeIndex] = currentStart; - const totalRelative = abs(totalVal / rootTotalSummary); - const selfRelativeVal = abs(self[nodeIndex] / rootTotalSummary); + const totalRelative = abs(totalVal / flameGraphWidthTotal); + const selfRelativeVal = abs(self[nodeIndex] / flameGraphWidthTotal); const currentEnd = currentStart + totalRelative; start.push(currentStart); @@ -435,6 +462,14 @@ class FlameGraphTimingInverted implements FlameGraphTiming { return rows; } + // For a given node / "box", get the sample percentage (as a fraction of 1) that + // should be displayed in the tooltip for this node. In the inverted flame + // graph, box widths are computed relative to rootTotalSummary, so the box + // width is already the ratio we want. + getRatioOfRootTotalSummary(depth: number, indexInRow: number): number { + return _getBoxWidth(this, depth, indexInRow); + } + _compareCallNodesByFuncName = ( a: IndexIntoCallNodeTable, b: IndexIntoCallNodeTable diff --git a/src/test/store/__snapshots__/profile-view.test.ts.snap b/src/test/store/__snapshots__/profile-view.test.ts.snap index 702b836990..8ae46ca333 100644 --- a/src/test/store/__snapshots__/profile-view.test.ts.snap +++ b/src/test/store/__snapshots__/profile-view.test.ts.snap @@ -2362,6 +2362,7 @@ CallTree { 0, 0, ], + "flameGraphWidthTotal": 2, "rootTotalSummary": 2, "self": Float64Array [ 0, diff --git a/src/test/unit/profile-tree.test.ts b/src/test/unit/profile-tree.test.ts index 48c82e95e1..d535aadd59 100644 --- a/src/test/unit/profile-tree.test.ts +++ b/src/test/unit/profile-tree.test.ts @@ -86,6 +86,7 @@ describe('unfiltered call tree', function () { type: 'NON_INVERTED', timings: { rootTotalSummary: 3, + flameGraphWidthTotal: 3, callNodeHasChildren: new Uint8Array([1, 1, 1, 1, 0, 1, 0, 1, 0]), self: new Float64Array([0, 0, 0, 0, 1, 0, 1, 0, 1]), total: new Float64Array([3, 3, 2, 1, 1, 1, 1, 1, 1]), diff --git a/src/types/profile-derived.ts b/src/types/profile-derived.ts index 10fbae4239..6d7b2fc41f 100644 --- a/src/types/profile-derived.ts +++ b/src/types/profile-derived.ts @@ -877,6 +877,12 @@ export type CallNodeSelfAndSummary = { // The sum of absolute values in callNodeSelf. // This is used for computing the percentages displayed in the call tree. rootTotalSummary: number; + // The total used as the 100% reference for flame graph box widths. + // Usually equals rootTotalSummary, but for the upper wing it is set to the + // total of samples that contain the selected function, so that the root node + // fills the full flame graph width while percentages remain relative to all + // filtered samples. + flameGraphWidthTotal: number; }; /**