ui: Fold flamegraph trim alive-set computation into propagation scan The previous trim pipeline ran a separate alive_set step that joined the full merged table against propagated to derive a per-node alive flag. On WASM-sized inputs (millions of merged rows) this was the single slowest step in the trim pipeline (~58s vs ~2s native). Have the propagation scan compute the alive flag inline as part of the same expression that already evaluates the per-parent ratio + absolute floor checks. The alive_set macro now becomes a one-line filter (SELECT id WHERE alive) over the propagated table, with no JOIN. The propagated index is no longer needed and is removed. Net effect on native: alive step drops from 2.1s -> 0.06s on a 2.5M-row bottom-up flamegraph. WASM should see the same kind of improvement since the elimination is structural rather than micro-optimization.
diff --git a/src/trace_processor/perfetto_sql/stdlib/viz/flamegraph.sql b/src/trace_processor/perfetto_sql/stdlib/viz/flamegraph.sql index e57d1b1..1d2a2f7 100644 --- a/src/trace_processor/perfetto_sql/stdlib/viz/flamegraph.sql +++ b/src/trace_processor/perfetto_sql/stdlib/viz/flamegraph.sql
@@ -335,15 +335,20 @@ GROUP BY hash ); --- Per-node propagation pass for the trim. For each node in |merged|, --- emits |requiredCum|: the cumulativeValue that this node's CHILDREN must --- clear in order to be kept. Roots emit 0. A node propagates +inf if it --- itself failed the join thresholds (which kills its whole subtree, since --- nothing is >= +inf). +-- Per-node propagation pass for the trim. For each node in |merged| emits: +-- alive - whether the node itself survives the join thresholds. +-- requiredCum - the cumulativeValue this node's CHILDREN must clear. +-- Roots are always alive and emit requiredCum = 0. A node that itself +-- failed the thresholds emits requiredCum = +inf, which kills its whole +-- subtree on the next step (no cumulativeValue can be >= +inf). +-- +-- Computing |alive| inside the scan rather than via a downstream JOIN of +-- merged with propagated avoids an N x N pass over the merged tree, which +-- on WASM-sized inputs (millions of rows) was the dominant cost. -- -- Caller should materialize the result into a Perfetto table with an --- index on |id|, since _viz_flamegraph_alive_set looks up parentId → --- requiredCum once per merged row. +-- index on |id|, since _viz_flamegraph_trim_with_placeholder looks up +-- parentId -> alive while emitting placeholders. CREATE PERFETTO MACRO _viz_flamegraph_trim_propagation( merged TableOrSubquery, ratio Expr, @@ -351,15 +356,18 @@ ) RETURNS TableOrSubquery AS ( - SELECT id, requiredCum + SELECT id, requiredCum, alive FROM _graph_aggregating_scan!( ( SELECT m.parentId AS source_node_id, m.id AS dest_node_id FROM $merged m WHERE m.parentId IS NOT NULL ), - (SELECT id, 0.0 AS requiredCum FROM $merged WHERE parentId IS NULL), - (requiredCum), + ( + SELECT id, 0.0 AS requiredCum, TRUE AS alive + FROM $merged WHERE parentId IS NULL + ), + (requiredCum, alive), ( SELECT x.id, @@ -368,7 +376,9 @@ AND t.cumulativeValue >= $min_value, $ratio * t.cumulativeValue, 1e308 - ) AS requiredCum + ) AS requiredCum, + (t.cumulativeValue >= x.incoming + AND t.cumulativeValue >= $min_value) AS alive FROM ( SELECT id, MIN(requiredCum) AS incoming FROM $table @@ -379,31 +389,13 @@ ) ); --- Given |merged| and the materialized + id-indexed |propagated| table from --- _viz_flamegraph_trim_propagation, emits the ids of nodes that survive a --- per-parent + absolute floor trim. A node is "alive" iff it is reachable --- from a root through edges where every step satisfies BOTH: --- child.cumulativeValue >= |ratio| * parent.cumulativeValue (encoded --- in propagated[parent].requiredCum) --- child.cumulativeValue >= |min_value| --- Roots are always kept. --- --- Caller should materialize the result with an index on |id| since --- _viz_flamegraph_trim_with_placeholder looks it up three times. +-- Returns the ids of nodes that survive the trim. Just a thin filter over +-- |propagated| now that |alive| is computed inside the propagation scan. CREATE PERFETTO MACRO _viz_flamegraph_alive_set( - merged TableOrSubquery, - propagated TableOrSubquery, - min_value Expr + propagated TableOrSubquery ) RETURNS TableOrSubquery -AS ( - SELECT m.id - FROM $merged m - LEFT JOIN $propagated pp ON pp.id = m.parentId - WHERE m.parentId IS NULL - OR (m.cumulativeValue >= pp.requiredCum - AND m.cumulativeValue >= $min_value) -); +AS (SELECT id FROM $propagated WHERE alive); -- Emits the trimmed flamegraph: kept rows from |merged| pass through, plus -- one synthetic '(merged)' placeholder per kept parent that has any
diff --git a/ui/src/components/query_flamegraph.ts b/ui/src/components/query_flamegraph.ts index 03a28c0..0c7278c 100644 --- a/ui/src/components/query_flamegraph.ts +++ b/ui/src/components/query_flamegraph.ts
@@ -492,8 +492,10 @@ // * an absolute floor derived from the root total (TRIM_FLOOR_DENOMINATOR) // prunes long invisible chain tails - critical for chain-shaped data // where the per-parent ratio alone cannot reduce row count. - // The pipeline is split into three materialized perfetto tables so - // each lookup hits an indexed table instead of an unindexed CTE. + // The propagation scan emits an `alive` flag per node so the alive set + // is a direct WHERE on propagated rather than a costly LEFT JOIN of the + // full merged table - this is the dominant cost on WASM for large + // bottom-up trees. const TRIM_RATIO_DENOMINATOR = 100; const TRIM_FLOOR_DENOMINATOR = 10000; const totalRow = await engine.query(` @@ -518,22 +520,13 @@ }), ); disposable.use( - await createPerfettoIndex({ - engine, - name: `_flamegraph_propagated_${uuid}_index`, - on: `_flamegraph_propagated_${uuid}(id)`, - }), - ); - disposable.use( await createPerfettoTable({ engine, name: `_flamegraph_alive_${uuid}`, as: ` select * from _viz_flamegraph_alive_set!( - _flamegraph_merged_${uuid}, - _flamegraph_propagated_${uuid}, - ${trimMinValue} + _flamegraph_propagated_${uuid} ) `, }),