Skip to content

Commit

Permalink
ui: implement pivot for flamegraph
Browse files Browse the repository at this point in the history
This CL implements pivot support in the Perfetto UI bringing us to
full parity with the flamegraph tab of internal pprof for single
profiles.

Change-Id: I03e9b868ce6e99bf884b62a3d39ccaf9f0aaad94
  • Loading branch information
LalitMaganti committed Jul 22, 2024
1 parent 1beb087 commit e127b95
Show file tree
Hide file tree
Showing 3 changed files with 240 additions and 64 deletions.
123 changes: 105 additions & 18 deletions src/trace_processor/perfetto_sql/stdlib/viz/flamegraph.sql
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,15 @@

include perfetto module graphs.scan;

-- For each frame in |tab|, returns a row containing the result of running
-- all the filtering operations over that frame's name.
CREATE PERFETTO MACRO _viz_flamegraph_prepare_filter(
tab TableOrSubquery,
show_stack Expr,
hide_stack Expr,
show_from_frame Expr,
hide_frame Expr,
pivot Expr,
impossible_stack_bits Expr
)
RETURNS TableOrSubquery
Expand All @@ -29,11 +32,16 @@ AS (
*,
IIF($hide_stack, $impossible_stack_bits, $show_stack) AS stackBits,
$show_from_frame As showFromFrameBits,
$hide_frame = 0 AS showFrame
$hide_frame = 0 AS showFrame,
$pivot AS isPivot
FROM $tab
ORDER BY id
);

-- Walks the forest from root to leaf and performs the following operations:
-- 1) removes frames which were filtered out
-- 2) make any pivot nodes become the roots
-- 3) computes whether the stack as a whole should be retained or not
CREATE PERFETTO MACRO _viz_flamegraph_filter_frames(
source TableOrSubquery,
show_from_frame_bits Expr
Expand All @@ -54,6 +62,7 @@ AS (
NULL
) AS filteredId,
NULL AS filteredParentId,
NULL AS filteredUnpivotedParentId,
IIF(
showFrame,
showFromFrameBits,
Expand All @@ -70,12 +79,13 @@ AS (
SELECT
g.filteredId AS id,
g.filteredParentId AS parentId,
g.filteredUnpivotedParentId AS unpivotedParentId,
g.stackBits,
SUM(t.value) AS value
FROM _graph_scan!(
edges,
inits,
(filteredId, filteredParentId, showFromFrameBits, stackBits),
(filteredId, filteredParentId, filteredUnpivotedParentId, showFromFrameBits, stackBits),
(
SELECT
t.id,
Expand All @@ -86,9 +96,14 @@ AS (
) AS filteredId,
IIF(
x.showFrame AND (t.showFromFrameBits | x.showFromFrameBits) = $show_from_frame_bits,
t.filteredId,
IIF(x.isPivot, NULL, t.filteredId),
t.filteredParentId
) AS filteredParentId,
IIF(
x.showFrame AND (t.showFromFrameBits | x.showFromFrameBits) = $show_from_frame_bits,
t.filteredId,
t.filteredParentId
) AS filteredUnpivotedParentId,
IIF(
x.showFrame,
(t.showFromFrameBits | x.showFromFrameBits),
Expand All @@ -109,6 +124,10 @@ AS (
ORDER BY filteredId
);

-- Walks the forest from leaves to root and does the following:
-- 1) removes nodes whose stacks are filtered out
-- 2) computes the cumulative value for each node (i.e. the sum of the self
-- value of the node and all descendants).
CREATE PERFETTO MACRO _viz_flamegraph_accumulate(
filtered TableOrSubquery,
showStackBits Expr
Expand Down Expand Up @@ -149,34 +168,96 @@ AS (
ORDER BY id
);

CREATE PERFETTO MACRO _viz_flamegraph_hash(
-- Propogates the cumulative value of the pivot nodes to the roots
-- and computes the "fingerprint" of the path.
CREATE PERFETTO MACRO _viz_flamegraph_upwards_hash(
source TableOrSubquery,
filtered TableOrSubquery,
accumulated TableOrSubquery,
show_from_frame_bits Expr
accumulated TableOrSubquery
)
RETURNS TableOrSubquery
AS (
WITH edges AS (
SELECT id AS source_node_id, unpivotedParentId AS dest_node_id
FROM $filtered
WHERE unpivotedParentId IS NOT NULL
),
inits AS (
SELECT
f.id,
HASH(-1, s.name) AS hash,
NULL AS parentHash,
-1 AS depth,
a.cumulativeValue
FROM $filtered f
JOIN $source s USING (id)
JOIN $accumulated a USING (id)
WHERE f.parentId IS NULL
AND f.unpivotedParentId IS NOT NULL
AND a.cumulativeValue > 0
)
SELECT
g.id,
g.hash,
g.parentHash,
g.depth,
s.name,
f.value,
a.cumulativeValue
g.cumulativeValue
FROM _graph_scan!(
edges,
inits,
(hash, parentHash, depth, cumulativeValue),
(
SELECT parentId AS source_node_id, id AS dest_node_id
FROM $filtered
WHERE parentId IS NOT NULL
),
(
SELECT f.id, HASH(s.name) AS hash, NULL AS parentHash, 0 AS depth
FROM $filtered f
JOIN $source s USING (id)
WHERE f.parentId IS NULL
),
SELECT
t.id,
HASH(t.hash, x.name) AS hash,
t.hash AS parentHash,
t.depth - 1 AS depth,
t.cumulativeValue
FROM $table t
JOIN $source x USING (id)
)
) g
JOIN $source s USING (id)
JOIN $filtered f USING (id)
);

-- Computes the "fingerprint" of the path by walking from the laves
-- to the root.
CREATE PERFETTO MACRO _viz_flamegraph_downwards_hash(
source TableOrSubquery,
filtered TableOrSubquery,
accumulated TableOrSubquery
)
RETURNS TableOrSubquery
AS (
WITH edges AS (
SELECT parentId AS source_node_id, id AS dest_node_id
FROM $filtered
WHERE parentId IS NOT NULL
),
inits AS (
SELECT
f.id,
HASH(1, s.name) AS hash,
NULL AS parentHash,
1 AS depth
FROM $filtered f
JOIN $source s USING (id)
WHERE f.parentId IS NULL
)
SELECT
g.id,
g.hash,
g.parentHash,
g.depth,
s.name,
f.value,
a.cumulativeValue
FROM _graph_scan!(
edges,
inits,
(hash, parentHash, depth),
(
SELECT
Expand All @@ -194,6 +275,8 @@ AS (
ORDER BY hash
);

-- Converts a table of hashes and paretn hashes into ids and parent
-- ids, grouping all hashes together.
CREATE PERFETTO MACRO _viz_flamegraph_merge_hashes(
hashed TableOrSubquery
)
Expand All @@ -215,6 +298,8 @@ AS (
GROUP BY hash
);

-- Performs a "layout" of nodes in the flamegraph relative to their
-- siblings.
CREATE PERFETTO MACRO _viz_flamegraph_local_layout(
merged TableOrSubquery
)
Expand All @@ -228,7 +313,7 @@ AS (
FROM $merged
WHERE cumulativeValue > 0
WINDOW win AS (
PARTITION BY parentId
PARTITION BY parentId, depth
ORDER BY cumulativeValue DESC
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
)
Expand All @@ -238,6 +323,8 @@ AS (
ORDER BY id
);

-- Walks the graph from root to leaf, propogating the layout of
-- parents to their children.
CREATE PERFETTO MACRO _viz_flamegraph_global_layout(
merged TableOrSubquery,
layout TableOrSubquery
Expand Down
44 changes: 33 additions & 11 deletions ui/src/core/query_flamegraph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ export class QueryFlamegraph implements m.ClassComponent<QueryFlamegraphAttrs> {
hideStack: [],
showFromFrame: [],
hideFrame: [],
pivot: undefined,
};
private attrs: QueryFlamegraphAttrs;
private selMonitor = new Monitor([() => this.attrs.metrics]);
Expand Down Expand Up @@ -143,13 +144,21 @@ async function computeFlamegraphTree(
engine: Engine,
dependencySql: string | undefined,
sql: string,
{showStack, hideStack, showFromFrame, hideFrame}: FlamegraphFilters,
{showStack, hideStack, showFromFrame, hideFrame, pivot}: FlamegraphFilters,
) {
// Pivot also essentially acts as a "show stack" filter so treat it like one.
const showStackAndPivot = [...showStack];
if (pivot !== undefined) {
showStackAndPivot.push(pivot);
}

const showStackFilter =
showStack.length === 0
showStackAndPivot.length === 0
? '0'
: showStack.map((x, i) => `((name like '%${x}%') << ${i})`).join(' | ');
const showStackBits = (1 << showStack.length) - 1;
: showStackAndPivot
.map((x, i) => `((name like '%${x}%') << ${i})`)
.join(' | ');
const showStackBits = (1 << showStackAndPivot.length) - 1;

const hideStackFilter =
hideStack.length === 0
Expand All @@ -169,6 +178,8 @@ async function computeFlamegraphTree(
? 'false'
: hideFrame.map((x) => `name like '%${x}%'`).join(' OR ');

const pivotFilter = pivot === undefined ? '0' : `name like '%${pivot}%'`;

if (dependencySql !== undefined) {
await engine.query(dependencySql);
}
Expand All @@ -190,7 +201,8 @@ async function computeFlamegraphTree(
(${hideStackFilter}),
(${showFromFrameFilter}),
(${hideFrameFilter}),
${1 << showStack.length}
(${pivotFilter}),
${1 << showStackAndPivot.length}
)
`,
),
Expand Down Expand Up @@ -229,12 +241,19 @@ async function computeFlamegraphTree(
`_flamegraph_hash_${uuid}`,
`
select *
from _viz_flamegraph_hash!(
from _viz_flamegraph_downwards_hash!(
_flamegraph_source_${uuid},
_flamegraph_filtered_${uuid},
_flamegraph_accumulated_${uuid},
${showStackBits}
_flamegraph_accumulated_${uuid}
)
union all
select *
from _viz_flamegraph_upwards_hash!(
_flamegraph_source_${uuid},
_flamegraph_filtered_${uuid},
_flamegraph_accumulated_${uuid}
)
order by hash
`,
),
);
Expand All @@ -243,7 +262,8 @@ async function computeFlamegraphTree(
engine,
`_flamegraph_merged_${uuid}`,
`
select * from _viz_flamegraph_merge_hashes!(
select *
from _viz_flamegraph_merge_hashes!(
_flamegraph_hash_${uuid}
)
`,
Expand Down Expand Up @@ -279,6 +299,7 @@ async function computeFlamegraphTree(
xEnd: NUM,
});
let allRootsCumulativeValue = 0;
let minDepth = 0;
let maxDepth = 0;
const nodes = [];
for (; it.valid(); it.next()) {
Expand All @@ -292,12 +313,13 @@ async function computeFlamegraphTree(
xStart: it.xStart,
xEnd: it.xEnd,
});
if (it.parentId === -1) {
if (it.depth === 1) {
allRootsCumulativeValue += it.cumulativeValue;
}
minDepth = Math.min(minDepth, it.depth);
maxDepth = Math.max(maxDepth, it.depth);
}
return {nodes, allRootsCumulativeValue, maxDepth};
return {nodes, allRootsCumulativeValue, minDepth, maxDepth};
}

export const USE_NEW_FLAMEGRAPH_IMPL = featureFlags.register({
Expand Down
Loading

0 comments on commit e127b95

Please sign in to comment.