xref: /aosp_15_r20/external/perfetto/ui/src/components/query_flamegraph.ts (revision 6dbdd20afdafa5e3ca9b8809fa73465d530080dc)
1*6dbdd20aSAndroid Build Coastguard Worker// Copyright (C) 2024 The Android Open Source Project
2*6dbdd20aSAndroid Build Coastguard Worker//
3*6dbdd20aSAndroid Build Coastguard Worker// Licensed under the Apache License, Version 2.0 (the "License");
4*6dbdd20aSAndroid Build Coastguard Worker// you may not use this file except in compliance with the License.
5*6dbdd20aSAndroid Build Coastguard Worker// You may obtain a copy of the License at
6*6dbdd20aSAndroid Build Coastguard Worker//
7*6dbdd20aSAndroid Build Coastguard Worker//      http://www.apache.org/licenses/LICENSE-2.0
8*6dbdd20aSAndroid Build Coastguard Worker//
9*6dbdd20aSAndroid Build Coastguard Worker// Unless required by applicable law or agreed to in writing, software
10*6dbdd20aSAndroid Build Coastguard Worker// distributed under the License is distributed on an "AS IS" BASIS,
11*6dbdd20aSAndroid Build Coastguard Worker// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12*6dbdd20aSAndroid Build Coastguard Worker// See the License for the specific language governing permissions and
13*6dbdd20aSAndroid Build Coastguard Worker// limitations under the License.
14*6dbdd20aSAndroid Build Coastguard Worker
15*6dbdd20aSAndroid Build Coastguard Workerimport m from 'mithril';
16*6dbdd20aSAndroid Build Coastguard Workerimport {AsyncLimiter} from '../base/async_limiter';
17*6dbdd20aSAndroid Build Coastguard Workerimport {AsyncDisposableStack} from '../base/disposable_stack';
18*6dbdd20aSAndroid Build Coastguard Workerimport {assertExists} from '../base/logging';
19*6dbdd20aSAndroid Build Coastguard Workerimport {Monitor} from '../base/monitor';
20*6dbdd20aSAndroid Build Coastguard Workerimport {uuidv4Sql} from '../base/uuid';
21*6dbdd20aSAndroid Build Coastguard Workerimport {Engine} from '../trace_processor/engine';
22*6dbdd20aSAndroid Build Coastguard Workerimport {
23*6dbdd20aSAndroid Build Coastguard Worker  createPerfettoIndex,
24*6dbdd20aSAndroid Build Coastguard Worker  createPerfettoTable,
25*6dbdd20aSAndroid Build Coastguard Worker} from '../trace_processor/sql_utils';
26*6dbdd20aSAndroid Build Coastguard Workerimport {
27*6dbdd20aSAndroid Build Coastguard Worker  NUM,
28*6dbdd20aSAndroid Build Coastguard Worker  NUM_NULL,
29*6dbdd20aSAndroid Build Coastguard Worker  STR,
30*6dbdd20aSAndroid Build Coastguard Worker  STR_NULL,
31*6dbdd20aSAndroid Build Coastguard Worker  UNKNOWN,
32*6dbdd20aSAndroid Build Coastguard Worker} from '../trace_processor/query_result';
33*6dbdd20aSAndroid Build Coastguard Workerimport {
34*6dbdd20aSAndroid Build Coastguard Worker  Flamegraph,
35*6dbdd20aSAndroid Build Coastguard Worker  FlamegraphQueryData,
36*6dbdd20aSAndroid Build Coastguard Worker  FlamegraphState,
37*6dbdd20aSAndroid Build Coastguard Worker  FlamegraphView,
38*6dbdd20aSAndroid Build Coastguard Worker} from '../widgets/flamegraph';
39*6dbdd20aSAndroid Build Coastguard Workerimport {Trace} from '../public/trace';
40*6dbdd20aSAndroid Build Coastguard Worker
41*6dbdd20aSAndroid Build Coastguard Workerexport interface QueryFlamegraphColumn {
42*6dbdd20aSAndroid Build Coastguard Worker  // The name of the column in SQL.
43*6dbdd20aSAndroid Build Coastguard Worker  readonly name: string;
44*6dbdd20aSAndroid Build Coastguard Worker
45*6dbdd20aSAndroid Build Coastguard Worker  // The human readable name describing the contents of the column.
46*6dbdd20aSAndroid Build Coastguard Worker  readonly displayName: string;
47*6dbdd20aSAndroid Build Coastguard Worker}
48*6dbdd20aSAndroid Build Coastguard Worker
49*6dbdd20aSAndroid Build Coastguard Workerexport interface AggQueryFlamegraphColumn extends QueryFlamegraphColumn {
50*6dbdd20aSAndroid Build Coastguard Worker  // The aggregation to be run when nodes are merged together in the flamegraph.
51*6dbdd20aSAndroid Build Coastguard Worker  //
52*6dbdd20aSAndroid Build Coastguard Worker  // TODO(lalitm): consider adding extra functions here (e.g. a top 5 or similar).
53*6dbdd20aSAndroid Build Coastguard Worker  readonly mergeAggregation: 'ONE_OR_NULL' | 'SUM';
54*6dbdd20aSAndroid Build Coastguard Worker}
55*6dbdd20aSAndroid Build Coastguard Worker
56*6dbdd20aSAndroid Build Coastguard Workerexport interface QueryFlamegraphMetric {
57*6dbdd20aSAndroid Build Coastguard Worker  // The human readable name of the metric: will be shown to the user to change
58*6dbdd20aSAndroid Build Coastguard Worker  // between metrics.
59*6dbdd20aSAndroid Build Coastguard Worker  readonly name: string;
60*6dbdd20aSAndroid Build Coastguard Worker
61*6dbdd20aSAndroid Build Coastguard Worker  // The human readable SI-style unit of `selfValue`. Values will be shown to
62*6dbdd20aSAndroid Build Coastguard Worker  // the user suffixed with this.
63*6dbdd20aSAndroid Build Coastguard Worker  readonly unit: string;
64*6dbdd20aSAndroid Build Coastguard Worker
65*6dbdd20aSAndroid Build Coastguard Worker  // SQL statement which need to be run in preparation for being able to execute
66*6dbdd20aSAndroid Build Coastguard Worker  // `statement`.
67*6dbdd20aSAndroid Build Coastguard Worker  readonly dependencySql?: string;
68*6dbdd20aSAndroid Build Coastguard Worker
69*6dbdd20aSAndroid Build Coastguard Worker  // A single SQL statement which returns the columns `id`, `parentId`, `name`
70*6dbdd20aSAndroid Build Coastguard Worker  // `selfValue`, all columns specified by `unaggregatableProperties` and
71*6dbdd20aSAndroid Build Coastguard Worker  // `aggregatableProperties`.
72*6dbdd20aSAndroid Build Coastguard Worker  readonly statement: string;
73*6dbdd20aSAndroid Build Coastguard Worker
74*6dbdd20aSAndroid Build Coastguard Worker  // Additional contextual columns containing data which should not be merged
75*6dbdd20aSAndroid Build Coastguard Worker  // between sibling nodes, even if they have the same name.
76*6dbdd20aSAndroid Build Coastguard Worker  //
77*6dbdd20aSAndroid Build Coastguard Worker  // Examples include the mapping that a name comes from, the heap graph root
78*6dbdd20aSAndroid Build Coastguard Worker  // type etc.
79*6dbdd20aSAndroid Build Coastguard Worker  //
80*6dbdd20aSAndroid Build Coastguard Worker  // Note: the name is always unaggregatable and should not be specified here.
81*6dbdd20aSAndroid Build Coastguard Worker  readonly unaggregatableProperties?: ReadonlyArray<QueryFlamegraphColumn>;
82*6dbdd20aSAndroid Build Coastguard Worker
83*6dbdd20aSAndroid Build Coastguard Worker  // Additional contextual columns containing data which will be displayed to
84*6dbdd20aSAndroid Build Coastguard Worker  // the user if there is no merging. If there is merging, currently the value
85*6dbdd20aSAndroid Build Coastguard Worker  // will not be shown.
86*6dbdd20aSAndroid Build Coastguard Worker  //
87*6dbdd20aSAndroid Build Coastguard Worker  // Examples include the source file and line number.
88*6dbdd20aSAndroid Build Coastguard Worker  readonly aggregatableProperties?: ReadonlyArray<AggQueryFlamegraphColumn>;
89*6dbdd20aSAndroid Build Coastguard Worker}
90*6dbdd20aSAndroid Build Coastguard Worker
91*6dbdd20aSAndroid Build Coastguard Workerexport interface QueryFlamegraphState {
92*6dbdd20aSAndroid Build Coastguard Worker  state: FlamegraphState;
93*6dbdd20aSAndroid Build Coastguard Worker}
94*6dbdd20aSAndroid Build Coastguard Worker
95*6dbdd20aSAndroid Build Coastguard Worker// Given a table and columns on those table (corresponding to metrics),
96*6dbdd20aSAndroid Build Coastguard Worker// returns an array of `QueryFlamegraphMetric` structs which can be passed
97*6dbdd20aSAndroid Build Coastguard Worker// in QueryFlamegraph's attrs.
98*6dbdd20aSAndroid Build Coastguard Worker//
99*6dbdd20aSAndroid Build Coastguard Worker// `tableOrSubquery` should have the columns `id`, `parentId`, `name` and all
100*6dbdd20aSAndroid Build Coastguard Worker// columns specified by `tableMetrics[].name`, `unaggregatableProperties` and
101*6dbdd20aSAndroid Build Coastguard Worker// `aggregatableProperties`.
102*6dbdd20aSAndroid Build Coastguard Workerexport function metricsFromTableOrSubquery(
103*6dbdd20aSAndroid Build Coastguard Worker  tableOrSubquery: string,
104*6dbdd20aSAndroid Build Coastguard Worker  tableMetrics: ReadonlyArray<{name: string; unit: string; columnName: string}>,
105*6dbdd20aSAndroid Build Coastguard Worker  dependencySql?: string,
106*6dbdd20aSAndroid Build Coastguard Worker  unaggregatableProperties?: ReadonlyArray<QueryFlamegraphColumn>,
107*6dbdd20aSAndroid Build Coastguard Worker  aggregatableProperties?: ReadonlyArray<AggQueryFlamegraphColumn>,
108*6dbdd20aSAndroid Build Coastguard Worker): QueryFlamegraphMetric[] {
109*6dbdd20aSAndroid Build Coastguard Worker  const metrics = [];
110*6dbdd20aSAndroid Build Coastguard Worker  for (const {name, unit, columnName} of tableMetrics) {
111*6dbdd20aSAndroid Build Coastguard Worker    metrics.push({
112*6dbdd20aSAndroid Build Coastguard Worker      name,
113*6dbdd20aSAndroid Build Coastguard Worker      unit,
114*6dbdd20aSAndroid Build Coastguard Worker      dependencySql,
115*6dbdd20aSAndroid Build Coastguard Worker      statement: `
116*6dbdd20aSAndroid Build Coastguard Worker        select *, ${columnName} as value
117*6dbdd20aSAndroid Build Coastguard Worker        from ${tableOrSubquery}
118*6dbdd20aSAndroid Build Coastguard Worker      `,
119*6dbdd20aSAndroid Build Coastguard Worker      unaggregatableProperties,
120*6dbdd20aSAndroid Build Coastguard Worker      aggregatableProperties,
121*6dbdd20aSAndroid Build Coastguard Worker    });
122*6dbdd20aSAndroid Build Coastguard Worker  }
123*6dbdd20aSAndroid Build Coastguard Worker  return metrics;
124*6dbdd20aSAndroid Build Coastguard Worker}
125*6dbdd20aSAndroid Build Coastguard Worker
126*6dbdd20aSAndroid Build Coastguard Worker// A Perfetto UI component which wraps the `Flamegraph` widget and fetches the
127*6dbdd20aSAndroid Build Coastguard Worker// data for the widget by querying an `Engine`.
128*6dbdd20aSAndroid Build Coastguard Workerexport class QueryFlamegraph {
129*6dbdd20aSAndroid Build Coastguard Worker  private data?: FlamegraphQueryData;
130*6dbdd20aSAndroid Build Coastguard Worker  private readonly selMonitor = new Monitor([() => this.state.state]);
131*6dbdd20aSAndroid Build Coastguard Worker  private readonly queryLimiter = new AsyncLimiter();
132*6dbdd20aSAndroid Build Coastguard Worker
133*6dbdd20aSAndroid Build Coastguard Worker  constructor(
134*6dbdd20aSAndroid Build Coastguard Worker    private readonly trace: Trace,
135*6dbdd20aSAndroid Build Coastguard Worker    private readonly metrics: ReadonlyArray<QueryFlamegraphMetric>,
136*6dbdd20aSAndroid Build Coastguard Worker    private state: QueryFlamegraphState,
137*6dbdd20aSAndroid Build Coastguard Worker  ) {}
138*6dbdd20aSAndroid Build Coastguard Worker
139*6dbdd20aSAndroid Build Coastguard Worker  render() {
140*6dbdd20aSAndroid Build Coastguard Worker    if (this.selMonitor.ifStateChanged()) {
141*6dbdd20aSAndroid Build Coastguard Worker      const metric = assertExists(
142*6dbdd20aSAndroid Build Coastguard Worker        this.metrics.find(
143*6dbdd20aSAndroid Build Coastguard Worker          (x) => this.state.state.selectedMetricName === x.name,
144*6dbdd20aSAndroid Build Coastguard Worker        ),
145*6dbdd20aSAndroid Build Coastguard Worker      );
146*6dbdd20aSAndroid Build Coastguard Worker      const engine = this.trace.engine;
147*6dbdd20aSAndroid Build Coastguard Worker      const state = this.state;
148*6dbdd20aSAndroid Build Coastguard Worker      this.data = undefined;
149*6dbdd20aSAndroid Build Coastguard Worker      this.queryLimiter.schedule(async () => {
150*6dbdd20aSAndroid Build Coastguard Worker        this.data = undefined;
151*6dbdd20aSAndroid Build Coastguard Worker        this.data = await computeFlamegraphTree(engine, metric, state.state);
152*6dbdd20aSAndroid Build Coastguard Worker      });
153*6dbdd20aSAndroid Build Coastguard Worker    }
154*6dbdd20aSAndroid Build Coastguard Worker    return m(Flamegraph, {
155*6dbdd20aSAndroid Build Coastguard Worker      metrics: this.metrics,
156*6dbdd20aSAndroid Build Coastguard Worker      data: this.data,
157*6dbdd20aSAndroid Build Coastguard Worker      state: this.state.state,
158*6dbdd20aSAndroid Build Coastguard Worker      onStateChange: (state) => {
159*6dbdd20aSAndroid Build Coastguard Worker        this.state.state = state;
160*6dbdd20aSAndroid Build Coastguard Worker        this.trace.scheduleFullRedraw();
161*6dbdd20aSAndroid Build Coastguard Worker      },
162*6dbdd20aSAndroid Build Coastguard Worker    });
163*6dbdd20aSAndroid Build Coastguard Worker  }
164*6dbdd20aSAndroid Build Coastguard Worker}
165*6dbdd20aSAndroid Build Coastguard Worker
166*6dbdd20aSAndroid Build Coastguard Workerasync function computeFlamegraphTree(
167*6dbdd20aSAndroid Build Coastguard Worker  engine: Engine,
168*6dbdd20aSAndroid Build Coastguard Worker  {
169*6dbdd20aSAndroid Build Coastguard Worker    dependencySql,
170*6dbdd20aSAndroid Build Coastguard Worker    statement,
171*6dbdd20aSAndroid Build Coastguard Worker    unaggregatableProperties,
172*6dbdd20aSAndroid Build Coastguard Worker    aggregatableProperties,
173*6dbdd20aSAndroid Build Coastguard Worker  }: QueryFlamegraphMetric,
174*6dbdd20aSAndroid Build Coastguard Worker  {filters, view}: FlamegraphState,
175*6dbdd20aSAndroid Build Coastguard Worker): Promise<FlamegraphQueryData> {
176*6dbdd20aSAndroid Build Coastguard Worker  const showStack = filters
177*6dbdd20aSAndroid Build Coastguard Worker    .filter((x) => x.kind === 'SHOW_STACK')
178*6dbdd20aSAndroid Build Coastguard Worker    .map((x) => x.filter);
179*6dbdd20aSAndroid Build Coastguard Worker  const hideStack = filters
180*6dbdd20aSAndroid Build Coastguard Worker    .filter((x) => x.kind === 'HIDE_STACK')
181*6dbdd20aSAndroid Build Coastguard Worker    .map((x) => x.filter);
182*6dbdd20aSAndroid Build Coastguard Worker  const showFromFrame = filters
183*6dbdd20aSAndroid Build Coastguard Worker    .filter((x) => x.kind === 'SHOW_FROM_FRAME')
184*6dbdd20aSAndroid Build Coastguard Worker    .map((x) => x.filter);
185*6dbdd20aSAndroid Build Coastguard Worker  const hideFrame = filters
186*6dbdd20aSAndroid Build Coastguard Worker    .filter((x) => x.kind === 'HIDE_FRAME')
187*6dbdd20aSAndroid Build Coastguard Worker    .map((x) => x.filter);
188*6dbdd20aSAndroid Build Coastguard Worker
189*6dbdd20aSAndroid Build Coastguard Worker  // Pivot also essentially acts as a "show stack" filter so treat it like one.
190*6dbdd20aSAndroid Build Coastguard Worker  const showStackAndPivot = [...showStack];
191*6dbdd20aSAndroid Build Coastguard Worker  if (view.kind === 'PIVOT') {
192*6dbdd20aSAndroid Build Coastguard Worker    showStackAndPivot.push(view.pivot);
193*6dbdd20aSAndroid Build Coastguard Worker  }
194*6dbdd20aSAndroid Build Coastguard Worker
195*6dbdd20aSAndroid Build Coastguard Worker  const showStackFilter =
196*6dbdd20aSAndroid Build Coastguard Worker    showStackAndPivot.length === 0
197*6dbdd20aSAndroid Build Coastguard Worker      ? '0'
198*6dbdd20aSAndroid Build Coastguard Worker      : showStackAndPivot
199*6dbdd20aSAndroid Build Coastguard Worker          .map(
200*6dbdd20aSAndroid Build Coastguard Worker            (x, i) => `((name like '${makeSqlFilter(x)}' escape '\\') << ${i})`,
201*6dbdd20aSAndroid Build Coastguard Worker          )
202*6dbdd20aSAndroid Build Coastguard Worker          .join(' | ');
203*6dbdd20aSAndroid Build Coastguard Worker  const showStackBits = (1 << showStackAndPivot.length) - 1;
204*6dbdd20aSAndroid Build Coastguard Worker
205*6dbdd20aSAndroid Build Coastguard Worker  const hideStackFilter =
206*6dbdd20aSAndroid Build Coastguard Worker    hideStack.length === 0
207*6dbdd20aSAndroid Build Coastguard Worker      ? 'false'
208*6dbdd20aSAndroid Build Coastguard Worker      : hideStack
209*6dbdd20aSAndroid Build Coastguard Worker          .map((x) => `name like '${makeSqlFilter(x)}' escape '\\'`)
210*6dbdd20aSAndroid Build Coastguard Worker          .join(' OR ');
211*6dbdd20aSAndroid Build Coastguard Worker
212*6dbdd20aSAndroid Build Coastguard Worker  const showFromFrameFilter =
213*6dbdd20aSAndroid Build Coastguard Worker    showFromFrame.length === 0
214*6dbdd20aSAndroid Build Coastguard Worker      ? '0'
215*6dbdd20aSAndroid Build Coastguard Worker      : showFromFrame
216*6dbdd20aSAndroid Build Coastguard Worker          .map(
217*6dbdd20aSAndroid Build Coastguard Worker            (x, i) => `((name like '${makeSqlFilter(x)}' escape '\\') << ${i})`,
218*6dbdd20aSAndroid Build Coastguard Worker          )
219*6dbdd20aSAndroid Build Coastguard Worker          .join(' | ');
220*6dbdd20aSAndroid Build Coastguard Worker  const showFromFrameBits = (1 << showFromFrame.length) - 1;
221*6dbdd20aSAndroid Build Coastguard Worker
222*6dbdd20aSAndroid Build Coastguard Worker  const hideFrameFilter =
223*6dbdd20aSAndroid Build Coastguard Worker    hideFrame.length === 0
224*6dbdd20aSAndroid Build Coastguard Worker      ? 'false'
225*6dbdd20aSAndroid Build Coastguard Worker      : hideFrame
226*6dbdd20aSAndroid Build Coastguard Worker          .map((x) => `name like '${makeSqlFilter(x)}' escape '\\'`)
227*6dbdd20aSAndroid Build Coastguard Worker          .join(' OR ');
228*6dbdd20aSAndroid Build Coastguard Worker
229*6dbdd20aSAndroid Build Coastguard Worker  const pivotFilter = getPivotFilter(view);
230*6dbdd20aSAndroid Build Coastguard Worker
231*6dbdd20aSAndroid Build Coastguard Worker  const unagg = unaggregatableProperties ?? [];
232*6dbdd20aSAndroid Build Coastguard Worker  const unaggCols = unagg.map((x) => x.name);
233*6dbdd20aSAndroid Build Coastguard Worker
234*6dbdd20aSAndroid Build Coastguard Worker  const agg = aggregatableProperties ?? [];
235*6dbdd20aSAndroid Build Coastguard Worker  const aggCols = agg.map((x) => x.name);
236*6dbdd20aSAndroid Build Coastguard Worker
237*6dbdd20aSAndroid Build Coastguard Worker  const groupingColumns = `(${(unaggCols.length === 0 ? ['groupingColumn'] : unaggCols).join()})`;
238*6dbdd20aSAndroid Build Coastguard Worker  const groupedColumns = `(${(aggCols.length === 0 ? ['groupedColumn'] : aggCols).join()})`;
239*6dbdd20aSAndroid Build Coastguard Worker
240*6dbdd20aSAndroid Build Coastguard Worker  if (dependencySql !== undefined) {
241*6dbdd20aSAndroid Build Coastguard Worker    await engine.query(dependencySql);
242*6dbdd20aSAndroid Build Coastguard Worker  }
243*6dbdd20aSAndroid Build Coastguard Worker  await engine.query(`include perfetto module viz.flamegraph;`);
244*6dbdd20aSAndroid Build Coastguard Worker
245*6dbdd20aSAndroid Build Coastguard Worker  const uuid = uuidv4Sql();
246*6dbdd20aSAndroid Build Coastguard Worker  await using disposable = new AsyncDisposableStack();
247*6dbdd20aSAndroid Build Coastguard Worker
248*6dbdd20aSAndroid Build Coastguard Worker  disposable.use(
249*6dbdd20aSAndroid Build Coastguard Worker    await createPerfettoTable(
250*6dbdd20aSAndroid Build Coastguard Worker      engine,
251*6dbdd20aSAndroid Build Coastguard Worker      `_flamegraph_materialized_statement_${uuid}`,
252*6dbdd20aSAndroid Build Coastguard Worker      statement,
253*6dbdd20aSAndroid Build Coastguard Worker    ),
254*6dbdd20aSAndroid Build Coastguard Worker  );
255*6dbdd20aSAndroid Build Coastguard Worker  disposable.use(
256*6dbdd20aSAndroid Build Coastguard Worker    await createPerfettoIndex(
257*6dbdd20aSAndroid Build Coastguard Worker      engine,
258*6dbdd20aSAndroid Build Coastguard Worker      `_flamegraph_materialized_statement_${uuid}_index`,
259*6dbdd20aSAndroid Build Coastguard Worker      `_flamegraph_materialized_statement_${uuid}(parentId)`,
260*6dbdd20aSAndroid Build Coastguard Worker    ),
261*6dbdd20aSAndroid Build Coastguard Worker  );
262*6dbdd20aSAndroid Build Coastguard Worker
263*6dbdd20aSAndroid Build Coastguard Worker  // TODO(lalitm): this doesn't need to be called unless we have
264*6dbdd20aSAndroid Build Coastguard Worker  // a non-empty set of filters.
265*6dbdd20aSAndroid Build Coastguard Worker  disposable.use(
266*6dbdd20aSAndroid Build Coastguard Worker    await createPerfettoTable(
267*6dbdd20aSAndroid Build Coastguard Worker      engine,
268*6dbdd20aSAndroid Build Coastguard Worker      `_flamegraph_source_${uuid}`,
269*6dbdd20aSAndroid Build Coastguard Worker      `
270*6dbdd20aSAndroid Build Coastguard Worker        select *
271*6dbdd20aSAndroid Build Coastguard Worker        from _viz_flamegraph_prepare_filter!(
272*6dbdd20aSAndroid Build Coastguard Worker          (
273*6dbdd20aSAndroid Build Coastguard Worker            select
274*6dbdd20aSAndroid Build Coastguard Worker              s.id,
275*6dbdd20aSAndroid Build Coastguard Worker              s.parentId,
276*6dbdd20aSAndroid Build Coastguard Worker              s.name,
277*6dbdd20aSAndroid Build Coastguard Worker              s.value,
278*6dbdd20aSAndroid Build Coastguard Worker              ${(unaggCols.length === 0
279*6dbdd20aSAndroid Build Coastguard Worker                ? [`'' as groupingColumn`]
280*6dbdd20aSAndroid Build Coastguard Worker                : unaggCols.map((x) => `s.${x}`)
281*6dbdd20aSAndroid Build Coastguard Worker              ).join()},
282*6dbdd20aSAndroid Build Coastguard Worker              ${(aggCols.length === 0
283*6dbdd20aSAndroid Build Coastguard Worker                ? [`'' as groupedColumn`]
284*6dbdd20aSAndroid Build Coastguard Worker                : aggCols.map((x) => `s.${x}`)
285*6dbdd20aSAndroid Build Coastguard Worker              ).join()}
286*6dbdd20aSAndroid Build Coastguard Worker            from _flamegraph_materialized_statement_${uuid} s
287*6dbdd20aSAndroid Build Coastguard Worker          ),
288*6dbdd20aSAndroid Build Coastguard Worker          (${showStackFilter}),
289*6dbdd20aSAndroid Build Coastguard Worker          (${hideStackFilter}),
290*6dbdd20aSAndroid Build Coastguard Worker          (${showFromFrameFilter}),
291*6dbdd20aSAndroid Build Coastguard Worker          (${hideFrameFilter}),
292*6dbdd20aSAndroid Build Coastguard Worker          (${pivotFilter}),
293*6dbdd20aSAndroid Build Coastguard Worker          ${1 << showStackAndPivot.length},
294*6dbdd20aSAndroid Build Coastguard Worker          ${groupingColumns}
295*6dbdd20aSAndroid Build Coastguard Worker        )
296*6dbdd20aSAndroid Build Coastguard Worker      `,
297*6dbdd20aSAndroid Build Coastguard Worker    ),
298*6dbdd20aSAndroid Build Coastguard Worker  );
299*6dbdd20aSAndroid Build Coastguard Worker  // TODO(lalitm): this doesn't need to be called unless we have
300*6dbdd20aSAndroid Build Coastguard Worker  // a non-empty set of filters.
301*6dbdd20aSAndroid Build Coastguard Worker  disposable.use(
302*6dbdd20aSAndroid Build Coastguard Worker    await createPerfettoTable(
303*6dbdd20aSAndroid Build Coastguard Worker      engine,
304*6dbdd20aSAndroid Build Coastguard Worker      `_flamegraph_filtered_${uuid}`,
305*6dbdd20aSAndroid Build Coastguard Worker      `
306*6dbdd20aSAndroid Build Coastguard Worker        select *
307*6dbdd20aSAndroid Build Coastguard Worker        from _viz_flamegraph_filter_frames!(
308*6dbdd20aSAndroid Build Coastguard Worker          _flamegraph_source_${uuid},
309*6dbdd20aSAndroid Build Coastguard Worker          ${showFromFrameBits}
310*6dbdd20aSAndroid Build Coastguard Worker        )
311*6dbdd20aSAndroid Build Coastguard Worker      `,
312*6dbdd20aSAndroid Build Coastguard Worker    ),
313*6dbdd20aSAndroid Build Coastguard Worker  );
314*6dbdd20aSAndroid Build Coastguard Worker  disposable.use(
315*6dbdd20aSAndroid Build Coastguard Worker    await createPerfettoTable(
316*6dbdd20aSAndroid Build Coastguard Worker      engine,
317*6dbdd20aSAndroid Build Coastguard Worker      `_flamegraph_accumulated_${uuid}`,
318*6dbdd20aSAndroid Build Coastguard Worker      `
319*6dbdd20aSAndroid Build Coastguard Worker        select *
320*6dbdd20aSAndroid Build Coastguard Worker        from _viz_flamegraph_accumulate!(
321*6dbdd20aSAndroid Build Coastguard Worker          _flamegraph_filtered_${uuid},
322*6dbdd20aSAndroid Build Coastguard Worker          ${showStackBits}
323*6dbdd20aSAndroid Build Coastguard Worker        )
324*6dbdd20aSAndroid Build Coastguard Worker      `,
325*6dbdd20aSAndroid Build Coastguard Worker    ),
326*6dbdd20aSAndroid Build Coastguard Worker  );
327*6dbdd20aSAndroid Build Coastguard Worker  disposable.use(
328*6dbdd20aSAndroid Build Coastguard Worker    await createPerfettoTable(
329*6dbdd20aSAndroid Build Coastguard Worker      engine,
330*6dbdd20aSAndroid Build Coastguard Worker      `_flamegraph_hash_${uuid}`,
331*6dbdd20aSAndroid Build Coastguard Worker      `
332*6dbdd20aSAndroid Build Coastguard Worker        select *
333*6dbdd20aSAndroid Build Coastguard Worker        from _viz_flamegraph_downwards_hash!(
334*6dbdd20aSAndroid Build Coastguard Worker          _flamegraph_source_${uuid},
335*6dbdd20aSAndroid Build Coastguard Worker          _flamegraph_filtered_${uuid},
336*6dbdd20aSAndroid Build Coastguard Worker          _flamegraph_accumulated_${uuid},
337*6dbdd20aSAndroid Build Coastguard Worker          ${groupingColumns},
338*6dbdd20aSAndroid Build Coastguard Worker          ${groupedColumns},
339*6dbdd20aSAndroid Build Coastguard Worker          ${view.kind === 'BOTTOM_UP' ? 'FALSE' : 'TRUE'}
340*6dbdd20aSAndroid Build Coastguard Worker        )
341*6dbdd20aSAndroid Build Coastguard Worker        union all
342*6dbdd20aSAndroid Build Coastguard Worker        select *
343*6dbdd20aSAndroid Build Coastguard Worker        from _viz_flamegraph_upwards_hash!(
344*6dbdd20aSAndroid Build Coastguard Worker          _flamegraph_source_${uuid},
345*6dbdd20aSAndroid Build Coastguard Worker          _flamegraph_filtered_${uuid},
346*6dbdd20aSAndroid Build Coastguard Worker          _flamegraph_accumulated_${uuid},
347*6dbdd20aSAndroid Build Coastguard Worker          ${groupingColumns},
348*6dbdd20aSAndroid Build Coastguard Worker          ${groupedColumns}
349*6dbdd20aSAndroid Build Coastguard Worker        )
350*6dbdd20aSAndroid Build Coastguard Worker        order by hash
351*6dbdd20aSAndroid Build Coastguard Worker      `,
352*6dbdd20aSAndroid Build Coastguard Worker    ),
353*6dbdd20aSAndroid Build Coastguard Worker  );
354*6dbdd20aSAndroid Build Coastguard Worker  disposable.use(
355*6dbdd20aSAndroid Build Coastguard Worker    await createPerfettoTable(
356*6dbdd20aSAndroid Build Coastguard Worker      engine,
357*6dbdd20aSAndroid Build Coastguard Worker      `_flamegraph_merged_${uuid}`,
358*6dbdd20aSAndroid Build Coastguard Worker      `
359*6dbdd20aSAndroid Build Coastguard Worker        select *
360*6dbdd20aSAndroid Build Coastguard Worker        from _viz_flamegraph_merge_hashes!(
361*6dbdd20aSAndroid Build Coastguard Worker          _flamegraph_hash_${uuid},
362*6dbdd20aSAndroid Build Coastguard Worker          ${groupingColumns},
363*6dbdd20aSAndroid Build Coastguard Worker          ${computeGroupedAggExprs(agg)}
364*6dbdd20aSAndroid Build Coastguard Worker        )
365*6dbdd20aSAndroid Build Coastguard Worker      `,
366*6dbdd20aSAndroid Build Coastguard Worker    ),
367*6dbdd20aSAndroid Build Coastguard Worker  );
368*6dbdd20aSAndroid Build Coastguard Worker  disposable.use(
369*6dbdd20aSAndroid Build Coastguard Worker    await createPerfettoTable(
370*6dbdd20aSAndroid Build Coastguard Worker      engine,
371*6dbdd20aSAndroid Build Coastguard Worker      `_flamegraph_layout_${uuid}`,
372*6dbdd20aSAndroid Build Coastguard Worker      `
373*6dbdd20aSAndroid Build Coastguard Worker        select *
374*6dbdd20aSAndroid Build Coastguard Worker        from _viz_flamegraph_local_layout!(
375*6dbdd20aSAndroid Build Coastguard Worker          _flamegraph_merged_${uuid}
376*6dbdd20aSAndroid Build Coastguard Worker        );
377*6dbdd20aSAndroid Build Coastguard Worker      `,
378*6dbdd20aSAndroid Build Coastguard Worker    ),
379*6dbdd20aSAndroid Build Coastguard Worker  );
380*6dbdd20aSAndroid Build Coastguard Worker  const res = await engine.query(`
381*6dbdd20aSAndroid Build Coastguard Worker    select *
382*6dbdd20aSAndroid Build Coastguard Worker    from _viz_flamegraph_global_layout!(
383*6dbdd20aSAndroid Build Coastguard Worker      _flamegraph_merged_${uuid},
384*6dbdd20aSAndroid Build Coastguard Worker      _flamegraph_layout_${uuid},
385*6dbdd20aSAndroid Build Coastguard Worker      ${groupingColumns},
386*6dbdd20aSAndroid Build Coastguard Worker      ${groupedColumns}
387*6dbdd20aSAndroid Build Coastguard Worker    )
388*6dbdd20aSAndroid Build Coastguard Worker  `);
389*6dbdd20aSAndroid Build Coastguard Worker
390*6dbdd20aSAndroid Build Coastguard Worker  const it = res.iter({
391*6dbdd20aSAndroid Build Coastguard Worker    id: NUM,
392*6dbdd20aSAndroid Build Coastguard Worker    parentId: NUM,
393*6dbdd20aSAndroid Build Coastguard Worker    depth: NUM,
394*6dbdd20aSAndroid Build Coastguard Worker    name: STR,
395*6dbdd20aSAndroid Build Coastguard Worker    selfValue: NUM,
396*6dbdd20aSAndroid Build Coastguard Worker    cumulativeValue: NUM,
397*6dbdd20aSAndroid Build Coastguard Worker    parentCumulativeValue: NUM_NULL,
398*6dbdd20aSAndroid Build Coastguard Worker    xStart: NUM,
399*6dbdd20aSAndroid Build Coastguard Worker    xEnd: NUM,
400*6dbdd20aSAndroid Build Coastguard Worker    ...Object.fromEntries(unaggCols.map((m) => [m, STR_NULL])),
401*6dbdd20aSAndroid Build Coastguard Worker    ...Object.fromEntries(aggCols.map((m) => [m, UNKNOWN])),
402*6dbdd20aSAndroid Build Coastguard Worker  });
403*6dbdd20aSAndroid Build Coastguard Worker  let postiveRootsValue = 0;
404*6dbdd20aSAndroid Build Coastguard Worker  let negativeRootsValue = 0;
405*6dbdd20aSAndroid Build Coastguard Worker  let minDepth = 0;
406*6dbdd20aSAndroid Build Coastguard Worker  let maxDepth = 0;
407*6dbdd20aSAndroid Build Coastguard Worker  const nodes = [];
408*6dbdd20aSAndroid Build Coastguard Worker  for (; it.valid(); it.next()) {
409*6dbdd20aSAndroid Build Coastguard Worker    const properties = new Map<string, string>();
410*6dbdd20aSAndroid Build Coastguard Worker    for (const a of [...agg, ...unagg]) {
411*6dbdd20aSAndroid Build Coastguard Worker      const r = it.get(a.name);
412*6dbdd20aSAndroid Build Coastguard Worker      if (r !== null) {
413*6dbdd20aSAndroid Build Coastguard Worker        properties.set(a.displayName, r as string);
414*6dbdd20aSAndroid Build Coastguard Worker      }
415*6dbdd20aSAndroid Build Coastguard Worker    }
416*6dbdd20aSAndroid Build Coastguard Worker    nodes.push({
417*6dbdd20aSAndroid Build Coastguard Worker      id: it.id,
418*6dbdd20aSAndroid Build Coastguard Worker      parentId: it.parentId,
419*6dbdd20aSAndroid Build Coastguard Worker      depth: it.depth,
420*6dbdd20aSAndroid Build Coastguard Worker      name: it.name,
421*6dbdd20aSAndroid Build Coastguard Worker      selfValue: it.selfValue,
422*6dbdd20aSAndroid Build Coastguard Worker      cumulativeValue: it.cumulativeValue,
423*6dbdd20aSAndroid Build Coastguard Worker      parentCumulativeValue: it.parentCumulativeValue ?? undefined,
424*6dbdd20aSAndroid Build Coastguard Worker      xStart: it.xStart,
425*6dbdd20aSAndroid Build Coastguard Worker      xEnd: it.xEnd,
426*6dbdd20aSAndroid Build Coastguard Worker      properties,
427*6dbdd20aSAndroid Build Coastguard Worker    });
428*6dbdd20aSAndroid Build Coastguard Worker    if (it.depth === 1) {
429*6dbdd20aSAndroid Build Coastguard Worker      postiveRootsValue += it.cumulativeValue;
430*6dbdd20aSAndroid Build Coastguard Worker    } else if (it.depth === -1) {
431*6dbdd20aSAndroid Build Coastguard Worker      negativeRootsValue += it.cumulativeValue;
432*6dbdd20aSAndroid Build Coastguard Worker    }
433*6dbdd20aSAndroid Build Coastguard Worker    minDepth = Math.min(minDepth, it.depth);
434*6dbdd20aSAndroid Build Coastguard Worker    maxDepth = Math.max(maxDepth, it.depth);
435*6dbdd20aSAndroid Build Coastguard Worker  }
436*6dbdd20aSAndroid Build Coastguard Worker  const sumQuery = await engine.query(
437*6dbdd20aSAndroid Build Coastguard Worker    `select sum(value) v from _flamegraph_source_${uuid}`,
438*6dbdd20aSAndroid Build Coastguard Worker  );
439*6dbdd20aSAndroid Build Coastguard Worker  const unfilteredCumulativeValue = sumQuery.firstRow({v: NUM_NULL}).v ?? 0;
440*6dbdd20aSAndroid Build Coastguard Worker  return {
441*6dbdd20aSAndroid Build Coastguard Worker    nodes,
442*6dbdd20aSAndroid Build Coastguard Worker    allRootsCumulativeValue:
443*6dbdd20aSAndroid Build Coastguard Worker      view.kind === 'BOTTOM_UP' ? negativeRootsValue : postiveRootsValue,
444*6dbdd20aSAndroid Build Coastguard Worker    unfilteredCumulativeValue,
445*6dbdd20aSAndroid Build Coastguard Worker    minDepth,
446*6dbdd20aSAndroid Build Coastguard Worker    maxDepth,
447*6dbdd20aSAndroid Build Coastguard Worker  };
448*6dbdd20aSAndroid Build Coastguard Worker}
449*6dbdd20aSAndroid Build Coastguard Worker
450*6dbdd20aSAndroid Build Coastguard Workerfunction makeSqlFilter(x: string) {
451*6dbdd20aSAndroid Build Coastguard Worker  if (x.startsWith('^') && x.endsWith('$')) {
452*6dbdd20aSAndroid Build Coastguard Worker    return x.slice(1, -1);
453*6dbdd20aSAndroid Build Coastguard Worker  }
454*6dbdd20aSAndroid Build Coastguard Worker  return `%${x}%`;
455*6dbdd20aSAndroid Build Coastguard Worker}
456*6dbdd20aSAndroid Build Coastguard Worker
457*6dbdd20aSAndroid Build Coastguard Workerfunction getPivotFilter(view: FlamegraphView) {
458*6dbdd20aSAndroid Build Coastguard Worker  if (view.kind === 'PIVOT') {
459*6dbdd20aSAndroid Build Coastguard Worker    return `name like '${makeSqlFilter(view.pivot)}'`;
460*6dbdd20aSAndroid Build Coastguard Worker  }
461*6dbdd20aSAndroid Build Coastguard Worker  if (view.kind === 'BOTTOM_UP') {
462*6dbdd20aSAndroid Build Coastguard Worker    return 'value > 0';
463*6dbdd20aSAndroid Build Coastguard Worker  }
464*6dbdd20aSAndroid Build Coastguard Worker  return '0';
465*6dbdd20aSAndroid Build Coastguard Worker}
466*6dbdd20aSAndroid Build Coastguard Worker
467*6dbdd20aSAndroid Build Coastguard Workerfunction computeGroupedAggExprs(agg: ReadonlyArray<AggQueryFlamegraphColumn>) {
468*6dbdd20aSAndroid Build Coastguard Worker  const aggFor = (x: AggQueryFlamegraphColumn) => {
469*6dbdd20aSAndroid Build Coastguard Worker    switch (x.mergeAggregation) {
470*6dbdd20aSAndroid Build Coastguard Worker      case 'ONE_OR_NULL':
471*6dbdd20aSAndroid Build Coastguard Worker        return `IIF(COUNT() = 1, ${x.name}, NULL) AS ${x.name}`;
472*6dbdd20aSAndroid Build Coastguard Worker      case 'SUM':
473*6dbdd20aSAndroid Build Coastguard Worker        return `SUM(${x.name}) AS ${x.name}`;
474*6dbdd20aSAndroid Build Coastguard Worker    }
475*6dbdd20aSAndroid Build Coastguard Worker  };
476*6dbdd20aSAndroid Build Coastguard Worker  return `(${agg.length === 0 ? 'groupedColumn' : agg.map((x) => aggFor(x)).join(',')})`;
477*6dbdd20aSAndroid Build Coastguard Worker}
478