Skip to content

Commit 26f8559

Browse files
committed
Fix flamegraph percentages for the function list subtree views.
1 parent 7f60bf8 commit 26f8559

7 files changed

Lines changed: 74 additions & 22 deletions

File tree

src/components/flame-graph/Canvas.tsx

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,7 @@ class FlameGraphCanvasImpl extends React.PureComponent<Props> {
245245
// The graph is drawn from bottom to top, in order of increasing depth.
246246
for (let depth = startDepth; depth < endDepth; depth++) {
247247
// Get the timing information for a row of stack frames.
248-
const stackTiming = flameGraphTiming[depth];
248+
const stackTiming = flameGraphTiming.rows[depth];
249249

250250
if (!stackTiming) {
251251
continue;
@@ -373,7 +373,7 @@ class FlameGraphCanvasImpl extends React.PureComponent<Props> {
373373
return null;
374374
}
375375

376-
const stackTiming = flameGraphTiming[depth];
376+
const stackTiming = flameGraphTiming.rows[depth];
377377
if (!stackTiming) {
378378
return null;
379379
}
@@ -383,8 +383,9 @@ class FlameGraphCanvasImpl extends React.PureComponent<Props> {
383383
}
384384

385385
const ratio =
386-
stackTiming.end[flameGraphTimingIndex] -
387-
stackTiming.start[flameGraphTimingIndex];
386+
(stackTiming.end[flameGraphTimingIndex] -
387+
stackTiming.start[flameGraphTimingIndex]) *
388+
flameGraphTiming.tooltipRatioMultiplier;
388389

389390
let percentage = formatPercent(ratio);
390391
if (tracedTiming) {
@@ -443,7 +444,7 @@ class FlameGraphCanvasImpl extends React.PureComponent<Props> {
443444

444445
const { depth, flameGraphTimingIndex } = hoveredItem;
445446
const { flameGraphTiming } = this.props;
446-
const stackTiming = flameGraphTiming[depth];
447+
const stackTiming = flameGraphTiming.rows[depth];
447448
const callNodeIndex = stackTiming.callNode[flameGraphTimingIndex];
448449
return callNodeIndex;
449450
}
@@ -477,7 +478,7 @@ class FlameGraphCanvasImpl extends React.PureComponent<Props> {
477478
const depth = Math.floor(
478479
maxStackDepthPlusOne - (y + viewportTop) / ROW_HEIGHT
479480
);
480-
const stackTiming = flameGraphTiming[depth];
481+
const stackTiming = flameGraphTiming.rows[depth];
481482

482483
if (!stackTiming) {
483484
return null;

src/components/flame-graph/FlameGraph.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ class FlameGraphImpl
120120

121121
const callNodeTable = callNodeInfo.getCallNodeTable();
122122
const depth = callNodeTable.depth[callNodeIndex];
123-
const row = flameGraphTiming[depth];
123+
const row = flameGraphTiming.rows[depth];
124124
const columnIndex = row.callNode.indexOf(callNodeIndex);
125125
return row.end[columnIndex] - row.start[columnIndex] > SELECTABLE_THRESHOLD;
126126
};
@@ -145,7 +145,7 @@ class FlameGraphImpl
145145

146146
const callNodeTable = callNodeInfo.getCallNodeTable();
147147
const depth = callNodeTable.depth[callNodeIndex];
148-
const row = flameGraphTiming[depth];
148+
const row = flameGraphTiming.rows[depth];
149149
let columnIndex = row.callNode.indexOf(callNodeIndex);
150150

151151
do {

src/profile-logic/call-tree.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ export type CallTreeTimingsNonInverted = {
5151
self: Float64Array;
5252
total: Float64Array;
5353
rootTotalSummary: number; // sum of absolute values, this is used for computing percentages
54+
flameGraphTotalForScaling: number; // used as 100% reference for flame graph box widths
5455
};
5556

5657
type TotalAndHasChildren = { total: number; hasChildren: boolean };
@@ -762,7 +763,7 @@ export function computeCallNodeSelfAndSummary(
762763
rootTotalSummary += abs(callNodeSelf[callNodeIndex]);
763764
}
764765

765-
return { callNodeSelf, rootTotalSummary };
766+
return { callNodeSelf, rootTotalSummary, flameGraphTotalForScaling: rootTotalSummary };
766767
}
767768

768769
export function getSelfAndTotalForCallNode(
@@ -950,6 +951,7 @@ export function computeLowerWingTimings(
950951
timings: computeCallTreeTimingsInverted(callNodeInfo, {
951952
callNodeSelf: mappedSelf,
952953
rootTotalSummary,
954+
flameGraphTotalForScaling: rootTotalSummary,
953955
}),
954956
};
955957
}
@@ -986,7 +988,7 @@ export function computeCallTreeTimingsNonInverted(
986988
callNodeSelfAndSummary: CallNodeSelfAndSummary
987989
): CallTreeTimingsNonInverted {
988990
const callNodeTable = callNodeInfo.getCallNodeTable();
989-
const { callNodeSelf, rootTotalSummary } = callNodeSelfAndSummary;
991+
const { callNodeSelf, rootTotalSummary, flameGraphTotalForScaling } = callNodeSelfAndSummary;
990992

991993
// Compute the following variables:
992994
const callNodeTotal = new Float64Array(callNodeTable.length);
@@ -1024,6 +1026,7 @@ export function computeCallTreeTimingsNonInverted(
10241026
total: callNodeTotal,
10251027
callNodeHasChildren,
10261028
rootTotalSummary,
1029+
flameGraphTotalForScaling,
10271030
};
10281031
}
10291032

@@ -1426,5 +1429,5 @@ export function computeCallNodeTracedSelfAndSummary(
14261429
}
14271430
}
14281431

1429-
return { callNodeSelf, rootTotalSummary };
1432+
return { callNodeSelf, rootTotalSummary, flameGraphTotalForScaling: rootTotalSummary };
14301433
}

src/profile-logic/flame-graph.ts

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,28 @@ export type IndexIntoFlameGraphTiming = number;
3030
* selfRelative contains the self time relative to the total time,
3131
* which is used to color the drawn functions.
3232
*/
33-
export type FlameGraphTiming = Array<{
33+
export type FlameGraphTimingRow = {
3434
start: UnitIntervalOfProfileRange[];
3535
end: UnitIntervalOfProfileRange[];
3636
selfRelative: Array<number>;
3737
callNode: IndexIntoCallNodeTable[];
3838
length: number;
39-
}>;
39+
};
40+
41+
/**
42+
* FlameGraphTiming is an array of rows plus a scalar adjustment factor.
43+
*
44+
* tooltipRatioMultiplier converts a box's (end - start) width to a percentage
45+
* relative to all filtered samples. Multiply (end - start) by this to get the
46+
* tooltip percentage. It equals flameGraphTotalForScaling / rootTotalSummary,
47+
* which is 1.0 for normal flame graphs and < 1.0 for the upper wing (where
48+
* boxes are scaled so that the root fills the full width, but tooltips still
49+
* show percentages relative to all filtered samples).
50+
*/
51+
export type FlameGraphTiming = {
52+
rows: FlameGraphTimingRow[];
53+
tooltipRatioMultiplier: number;
54+
};
4055

4156
/**
4257
* FlameGraphRows is an array of rows, where each row is an array of call node
@@ -232,7 +247,7 @@ export function getFlameGraphTiming(
232247
callNodeTable: CallNodeTable,
233248
callTreeTimings: CallTreeTimingsNonInverted
234249
): FlameGraphTiming {
235-
const { total, self, rootTotalSummary } = callTreeTimings;
250+
const { total, self, rootTotalSummary, flameGraphTotalForScaling } = callTreeTimings;
236251
const { prefix } = callNodeTable;
237252

238253
// This is where we build up the return value, one row at a time.
@@ -284,8 +299,8 @@ export function getFlameGraphTiming(
284299
startPerCallNode[nodeIndex] = currentStart;
285300

286301
// Take the absolute value, as native deallocations can be negative.
287-
const totalRelativeVal = abs(totalVal / rootTotalSummary);
288-
const selfRelativeVal = abs(self[nodeIndex] / rootTotalSummary);
302+
const totalRelativeVal = abs(totalVal / flameGraphTotalForScaling);
303+
const selfRelativeVal = abs(self[nodeIndex] / flameGraphTotalForScaling);
289304

290305
const currentEnd = currentStart + totalRelativeVal;
291306
start.push(currentStart);
@@ -305,5 +320,8 @@ export function getFlameGraphTiming(
305320
};
306321
}
307322

308-
return timing;
323+
return {
324+
rows: timing,
325+
tooltipRatioMultiplier: flameGraphTotalForScaling / rootTotalSummary,
326+
};
309327
}

src/selectors/per-thread/stack-sample.ts

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -465,12 +465,20 @@ export function getStackAndSampleSelectorsPerThread(
465465
regularTreeSelfAndSummary
466466
) => {
467467
const { rootTotalSummary } = regularTreeSelfAndSummary;
468-
const { callNodeSelf } = CallTree.computeCallNodeSelfAndSummary(
468+
const upperWingSelfAndSummary = CallTree.computeCallNodeSelfAndSummary(
469469
samples,
470470
sampleIndexToCallNodeIndex,
471471
callNodeInfo.getCallNodeTable().length
472472
);
473-
return { rootTotalSummary, callNodeSelf };
473+
const { callNodeSelf } = upperWingSelfAndSummary;
474+
// Use the upper wing's own total as the flame graph scaling reference,
475+
// so that the root node (the selected function) fills the full flame
476+
// graph width. The rootTotalSummary from the regular tree is kept for
477+
// percentage display, so tooltips show percentages relative to all
478+
// filtered samples (e.g. "80%" if 800 of 1000 samples contain the
479+
// selected function).
480+
const flameGraphTotalForScaling = upperWingSelfAndSummary.rootTotalSummary;
481+
return { rootTotalSummary, callNodeSelf, flameGraphTotalForScaling };
474482
}
475483
);
476484

@@ -723,7 +731,23 @@ export function getStackAndSampleSelectorsPerThread(
723731
_getSelfWingSampleIndexToCallNodeIndex,
724732
(state: State) =>
725733
_getSelfWingCallNodeInfo(state).getCallNodeTable().length,
726-
CallTree.computeCallNodeSelfAndSummary
734+
getCallNodeSelfAndSummary,
735+
(samples, sampleIndexToCallNodeIndex, callNodeCount, regularTreeSelfAndSummary) => {
736+
const selfWingSelfAndSummary = CallTree.computeCallNodeSelfAndSummary(
737+
samples,
738+
sampleIndexToCallNodeIndex,
739+
callNodeCount
740+
);
741+
// Keep flameGraphTotalForScaling as the self wing's own total so the
742+
// root fills the full flame graph width. Override rootTotalSummary with
743+
// the regular tree's value so tooltips show percentages relative to all
744+
// filtered samples.
745+
return {
746+
callNodeSelf: selfWingSelfAndSummary.callNodeSelf,
747+
flameGraphTotalForScaling: selfWingSelfAndSummary.rootTotalSummary,
748+
rootTotalSummary: regularTreeSelfAndSummary.rootTotalSummary,
749+
};
750+
}
727751
);
728752

729753
const _getSelfWingCallTreeTimings: Selector<CallTree.CallTreeTimings> =

src/test/store/actions.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ describe('selectors/getFlameGraphTiming', function () {
206206
store.getState()
207207
);
208208

209-
return flameGraphTiming.map(({ callNode, end, length, start }) => {
209+
return flameGraphTiming.rows.map(({ callNode, end, length, start }) => {
210210
const lines = [];
211211
for (let i = 0; i < length; i++) {
212212
const callNodeIndex = callNode[i];
@@ -240,7 +240,7 @@ describe('selectors/getFlameGraphTiming', function () {
240240
store.getState()
241241
);
242242

243-
return flameGraphTiming.map(({ selfRelative, callNode, length }) => {
243+
return flameGraphTiming.rows.map(({ selfRelative, callNode, length }) => {
244244
const lines = [];
245245
for (let i = 0; i < length; i++) {
246246
const callNodeIndex = callNode[i];

src/types/profile-derived.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -729,6 +729,12 @@ export type CallNodeSelfAndSummary = {
729729
// The sum of absolute values in callNodeSelf.
730730
// This is used for computing the percentages displayed in the call tree.
731731
rootTotalSummary: number;
732+
// The total used as the 100% reference for flame graph box widths.
733+
// Usually equals rootTotalSummary, but for the upper wing it is set to the
734+
// total of samples that contain the selected function, so that the root node
735+
// fills the full flame graph width while percentages remain relative to all
736+
// filtered samples.
737+
flameGraphTotalForScaling: number;
732738
};
733739

734740
/**

0 commit comments

Comments
 (0)