xref: /aosp_15_r20/external/perfetto/ui/src/frontend/aggregation_tab.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 size 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 {AggregationPanel} from './aggregation_panel';
17*6dbdd20aSAndroid Build Coastguard Workerimport {isEmptyData} from '../public/aggregation';
18*6dbdd20aSAndroid Build Coastguard Workerimport {DetailsShell} from '../widgets/details_shell';
19*6dbdd20aSAndroid Build Coastguard Workerimport {Button, ButtonBar} from '../widgets/button';
20*6dbdd20aSAndroid Build Coastguard Workerimport {raf} from '../core/raf_scheduler';
21*6dbdd20aSAndroid Build Coastguard Workerimport {EmptyState} from '../widgets/empty_state';
22*6dbdd20aSAndroid Build Coastguard Workerimport {FlowEventsAreaSelectedPanel} from './flow_events_panel';
23*6dbdd20aSAndroid Build Coastguard Workerimport {PivotTable} from './pivot_table';
24*6dbdd20aSAndroid Build Coastguard Workerimport {AreaSelection} from '../public/selection';
25*6dbdd20aSAndroid Build Coastguard Workerimport {Monitor} from '../base/monitor';
26*6dbdd20aSAndroid Build Coastguard Workerimport {
27*6dbdd20aSAndroid Build Coastguard Worker  CPU_PROFILE_TRACK_KIND,
28*6dbdd20aSAndroid Build Coastguard Worker  PERF_SAMPLES_PROFILE_TRACK_KIND,
29*6dbdd20aSAndroid Build Coastguard Worker  SLICE_TRACK_KIND,
30*6dbdd20aSAndroid Build Coastguard Worker} from '../public/track_kinds';
31*6dbdd20aSAndroid Build Coastguard Workerimport {
32*6dbdd20aSAndroid Build Coastguard Worker  QueryFlamegraph,
33*6dbdd20aSAndroid Build Coastguard Worker  metricsFromTableOrSubquery,
34*6dbdd20aSAndroid Build Coastguard Worker} from '../components/query_flamegraph';
35*6dbdd20aSAndroid Build Coastguard Workerimport {DisposableStack} from '../base/disposable_stack';
36*6dbdd20aSAndroid Build Coastguard Workerimport {assertExists} from '../base/logging';
37*6dbdd20aSAndroid Build Coastguard Workerimport {TraceImpl} from '../core/trace_impl';
38*6dbdd20aSAndroid Build Coastguard Workerimport {Trace} from '../public/trace';
39*6dbdd20aSAndroid Build Coastguard Workerimport {Flamegraph} from '../widgets/flamegraph';
40*6dbdd20aSAndroid Build Coastguard Worker
41*6dbdd20aSAndroid Build Coastguard Workerinterface View {
42*6dbdd20aSAndroid Build Coastguard Worker  key: string;
43*6dbdd20aSAndroid Build Coastguard Worker  name: string;
44*6dbdd20aSAndroid Build Coastguard Worker  content: m.Children;
45*6dbdd20aSAndroid Build Coastguard Worker}
46*6dbdd20aSAndroid Build Coastguard Worker
47*6dbdd20aSAndroid Build Coastguard Workerexport type AreaDetailsPanelAttrs = {trace: TraceImpl};
48*6dbdd20aSAndroid Build Coastguard Worker
49*6dbdd20aSAndroid Build Coastguard Workerclass AreaDetailsPanel implements m.ClassComponent<AreaDetailsPanelAttrs> {
50*6dbdd20aSAndroid Build Coastguard Worker  private trace: TraceImpl;
51*6dbdd20aSAndroid Build Coastguard Worker  private monitor: Monitor;
52*6dbdd20aSAndroid Build Coastguard Worker  private currentTab: string | undefined = undefined;
53*6dbdd20aSAndroid Build Coastguard Worker  private cpuProfileFlamegraph?: QueryFlamegraph;
54*6dbdd20aSAndroid Build Coastguard Worker  private perfSampleFlamegraph?: QueryFlamegraph;
55*6dbdd20aSAndroid Build Coastguard Worker  private sliceFlamegraph?: QueryFlamegraph;
56*6dbdd20aSAndroid Build Coastguard Worker
57*6dbdd20aSAndroid Build Coastguard Worker  constructor({attrs}: m.CVnode<AreaDetailsPanelAttrs>) {
58*6dbdd20aSAndroid Build Coastguard Worker    this.trace = attrs.trace;
59*6dbdd20aSAndroid Build Coastguard Worker    this.monitor = new Monitor([() => this.trace.selection.selection]);
60*6dbdd20aSAndroid Build Coastguard Worker  }
61*6dbdd20aSAndroid Build Coastguard Worker
62*6dbdd20aSAndroid Build Coastguard Worker  private getCurrentView(): string | undefined {
63*6dbdd20aSAndroid Build Coastguard Worker    const types = this.getViews().map(({key}) => key);
64*6dbdd20aSAndroid Build Coastguard Worker
65*6dbdd20aSAndroid Build Coastguard Worker    if (types.length === 0) {
66*6dbdd20aSAndroid Build Coastguard Worker      return undefined;
67*6dbdd20aSAndroid Build Coastguard Worker    }
68*6dbdd20aSAndroid Build Coastguard Worker
69*6dbdd20aSAndroid Build Coastguard Worker    if (this.currentTab === undefined) {
70*6dbdd20aSAndroid Build Coastguard Worker      return types[0];
71*6dbdd20aSAndroid Build Coastguard Worker    }
72*6dbdd20aSAndroid Build Coastguard Worker
73*6dbdd20aSAndroid Build Coastguard Worker    if (!types.includes(this.currentTab)) {
74*6dbdd20aSAndroid Build Coastguard Worker      return types[0];
75*6dbdd20aSAndroid Build Coastguard Worker    }
76*6dbdd20aSAndroid Build Coastguard Worker
77*6dbdd20aSAndroid Build Coastguard Worker    return this.currentTab;
78*6dbdd20aSAndroid Build Coastguard Worker  }
79*6dbdd20aSAndroid Build Coastguard Worker
80*6dbdd20aSAndroid Build Coastguard Worker  private getViews(): View[] {
81*6dbdd20aSAndroid Build Coastguard Worker    const views: View[] = [];
82*6dbdd20aSAndroid Build Coastguard Worker
83*6dbdd20aSAndroid Build Coastguard Worker    for (const aggregator of this.trace.selection.aggregation.aggregators) {
84*6dbdd20aSAndroid Build Coastguard Worker      const aggregatorId = aggregator.id;
85*6dbdd20aSAndroid Build Coastguard Worker      const value =
86*6dbdd20aSAndroid Build Coastguard Worker        this.trace.selection.aggregation.getAggregatedData(aggregatorId);
87*6dbdd20aSAndroid Build Coastguard Worker      if (value !== undefined && !isEmptyData(value)) {
88*6dbdd20aSAndroid Build Coastguard Worker        views.push({
89*6dbdd20aSAndroid Build Coastguard Worker          key: value.tabName,
90*6dbdd20aSAndroid Build Coastguard Worker          name: value.tabName,
91*6dbdd20aSAndroid Build Coastguard Worker          content: m(AggregationPanel, {
92*6dbdd20aSAndroid Build Coastguard Worker            aggregatorId,
93*6dbdd20aSAndroid Build Coastguard Worker            data: value,
94*6dbdd20aSAndroid Build Coastguard Worker            trace: this.trace,
95*6dbdd20aSAndroid Build Coastguard Worker          }),
96*6dbdd20aSAndroid Build Coastguard Worker        });
97*6dbdd20aSAndroid Build Coastguard Worker      }
98*6dbdd20aSAndroid Build Coastguard Worker    }
99*6dbdd20aSAndroid Build Coastguard Worker
100*6dbdd20aSAndroid Build Coastguard Worker    const pivotTableState = this.trace.pivotTable.state;
101*6dbdd20aSAndroid Build Coastguard Worker    const tree = pivotTableState.queryResult?.tree;
102*6dbdd20aSAndroid Build Coastguard Worker    if (
103*6dbdd20aSAndroid Build Coastguard Worker      pivotTableState.selectionArea != undefined &&
104*6dbdd20aSAndroid Build Coastguard Worker      (tree === undefined || tree.children.size > 0 || tree?.rows.length > 0)
105*6dbdd20aSAndroid Build Coastguard Worker    ) {
106*6dbdd20aSAndroid Build Coastguard Worker      views.push({
107*6dbdd20aSAndroid Build Coastguard Worker        key: 'pivot_table',
108*6dbdd20aSAndroid Build Coastguard Worker        name: 'Pivot Table',
109*6dbdd20aSAndroid Build Coastguard Worker        content: m(PivotTable, {
110*6dbdd20aSAndroid Build Coastguard Worker          trace: this.trace,
111*6dbdd20aSAndroid Build Coastguard Worker          selectionArea: pivotTableState.selectionArea,
112*6dbdd20aSAndroid Build Coastguard Worker        }),
113*6dbdd20aSAndroid Build Coastguard Worker      });
114*6dbdd20aSAndroid Build Coastguard Worker    }
115*6dbdd20aSAndroid Build Coastguard Worker
116*6dbdd20aSAndroid Build Coastguard Worker    this.addFlamegraphView(this.trace, this.monitor.ifStateChanged(), views);
117*6dbdd20aSAndroid Build Coastguard Worker
118*6dbdd20aSAndroid Build Coastguard Worker    // Add this after all aggregation panels, to make it appear after 'Slices'
119*6dbdd20aSAndroid Build Coastguard Worker    if (this.trace.flows.selectedFlows.length > 0) {
120*6dbdd20aSAndroid Build Coastguard Worker      views.push({
121*6dbdd20aSAndroid Build Coastguard Worker        key: 'selected_flows',
122*6dbdd20aSAndroid Build Coastguard Worker        name: 'Flow Events',
123*6dbdd20aSAndroid Build Coastguard Worker        content: m(FlowEventsAreaSelectedPanel, {trace: this.trace}),
124*6dbdd20aSAndroid Build Coastguard Worker      });
125*6dbdd20aSAndroid Build Coastguard Worker    }
126*6dbdd20aSAndroid Build Coastguard Worker
127*6dbdd20aSAndroid Build Coastguard Worker    return views;
128*6dbdd20aSAndroid Build Coastguard Worker  }
129*6dbdd20aSAndroid Build Coastguard Worker
130*6dbdd20aSAndroid Build Coastguard Worker  view(): m.Children {
131*6dbdd20aSAndroid Build Coastguard Worker    const views = this.getViews();
132*6dbdd20aSAndroid Build Coastguard Worker    const currentViewKey = this.getCurrentView();
133*6dbdd20aSAndroid Build Coastguard Worker
134*6dbdd20aSAndroid Build Coastguard Worker    const aggregationButtons = views.map(({key, name}) => {
135*6dbdd20aSAndroid Build Coastguard Worker      return m(Button, {
136*6dbdd20aSAndroid Build Coastguard Worker        onclick: () => {
137*6dbdd20aSAndroid Build Coastguard Worker          this.currentTab = key;
138*6dbdd20aSAndroid Build Coastguard Worker          raf.scheduleFullRedraw();
139*6dbdd20aSAndroid Build Coastguard Worker        },
140*6dbdd20aSAndroid Build Coastguard Worker        key,
141*6dbdd20aSAndroid Build Coastguard Worker        label: name,
142*6dbdd20aSAndroid Build Coastguard Worker        active: currentViewKey === key,
143*6dbdd20aSAndroid Build Coastguard Worker      });
144*6dbdd20aSAndroid Build Coastguard Worker    });
145*6dbdd20aSAndroid Build Coastguard Worker
146*6dbdd20aSAndroid Build Coastguard Worker    if (currentViewKey === undefined) {
147*6dbdd20aSAndroid Build Coastguard Worker      return this.renderEmptyState();
148*6dbdd20aSAndroid Build Coastguard Worker    }
149*6dbdd20aSAndroid Build Coastguard Worker
150*6dbdd20aSAndroid Build Coastguard Worker    const content = views.find(({key}) => key === currentViewKey)?.content;
151*6dbdd20aSAndroid Build Coastguard Worker    if (content === undefined) {
152*6dbdd20aSAndroid Build Coastguard Worker      return this.renderEmptyState();
153*6dbdd20aSAndroid Build Coastguard Worker    }
154*6dbdd20aSAndroid Build Coastguard Worker
155*6dbdd20aSAndroid Build Coastguard Worker    return m(
156*6dbdd20aSAndroid Build Coastguard Worker      DetailsShell,
157*6dbdd20aSAndroid Build Coastguard Worker      {
158*6dbdd20aSAndroid Build Coastguard Worker        title: 'Area Selection',
159*6dbdd20aSAndroid Build Coastguard Worker        description: m(ButtonBar, aggregationButtons),
160*6dbdd20aSAndroid Build Coastguard Worker      },
161*6dbdd20aSAndroid Build Coastguard Worker      content,
162*6dbdd20aSAndroid Build Coastguard Worker    );
163*6dbdd20aSAndroid Build Coastguard Worker  }
164*6dbdd20aSAndroid Build Coastguard Worker
165*6dbdd20aSAndroid Build Coastguard Worker  private renderEmptyState(): m.Children {
166*6dbdd20aSAndroid Build Coastguard Worker    return m(
167*6dbdd20aSAndroid Build Coastguard Worker      EmptyState,
168*6dbdd20aSAndroid Build Coastguard Worker      {
169*6dbdd20aSAndroid Build Coastguard Worker        className: 'pf-noselection',
170*6dbdd20aSAndroid Build Coastguard Worker        title: 'Unsupported area selection',
171*6dbdd20aSAndroid Build Coastguard Worker      },
172*6dbdd20aSAndroid Build Coastguard Worker      'No details available for this area selection',
173*6dbdd20aSAndroid Build Coastguard Worker    );
174*6dbdd20aSAndroid Build Coastguard Worker  }
175*6dbdd20aSAndroid Build Coastguard Worker
176*6dbdd20aSAndroid Build Coastguard Worker  private addFlamegraphView(trace: Trace, isChanged: boolean, views: View[]) {
177*6dbdd20aSAndroid Build Coastguard Worker    this.cpuProfileFlamegraph = this.computeCpuProfileFlamegraph(
178*6dbdd20aSAndroid Build Coastguard Worker      trace,
179*6dbdd20aSAndroid Build Coastguard Worker      isChanged,
180*6dbdd20aSAndroid Build Coastguard Worker    );
181*6dbdd20aSAndroid Build Coastguard Worker    if (this.cpuProfileFlamegraph !== undefined) {
182*6dbdd20aSAndroid Build Coastguard Worker      views.push({
183*6dbdd20aSAndroid Build Coastguard Worker        key: 'cpu_profile_flamegraph_selection',
184*6dbdd20aSAndroid Build Coastguard Worker        name: 'CPU Profile Sample Flamegraph',
185*6dbdd20aSAndroid Build Coastguard Worker        content: this.cpuProfileFlamegraph.render(),
186*6dbdd20aSAndroid Build Coastguard Worker      });
187*6dbdd20aSAndroid Build Coastguard Worker    }
188*6dbdd20aSAndroid Build Coastguard Worker    this.perfSampleFlamegraph = this.computePerfSampleFlamegraph(
189*6dbdd20aSAndroid Build Coastguard Worker      trace,
190*6dbdd20aSAndroid Build Coastguard Worker      isChanged,
191*6dbdd20aSAndroid Build Coastguard Worker    );
192*6dbdd20aSAndroid Build Coastguard Worker    if (this.perfSampleFlamegraph !== undefined) {
193*6dbdd20aSAndroid Build Coastguard Worker      views.push({
194*6dbdd20aSAndroid Build Coastguard Worker        key: 'perf_sample_flamegraph_selection',
195*6dbdd20aSAndroid Build Coastguard Worker        name: 'Perf Sample Flamegraph',
196*6dbdd20aSAndroid Build Coastguard Worker        content: this.perfSampleFlamegraph.render(),
197*6dbdd20aSAndroid Build Coastguard Worker      });
198*6dbdd20aSAndroid Build Coastguard Worker    }
199*6dbdd20aSAndroid Build Coastguard Worker    this.sliceFlamegraph = this.computeSliceFlamegraph(trace, isChanged);
200*6dbdd20aSAndroid Build Coastguard Worker    if (this.sliceFlamegraph !== undefined) {
201*6dbdd20aSAndroid Build Coastguard Worker      views.push({
202*6dbdd20aSAndroid Build Coastguard Worker        key: 'slice_flamegraph_selection',
203*6dbdd20aSAndroid Build Coastguard Worker        name: 'Slice Flamegraph',
204*6dbdd20aSAndroid Build Coastguard Worker        content: this.sliceFlamegraph.render(),
205*6dbdd20aSAndroid Build Coastguard Worker      });
206*6dbdd20aSAndroid Build Coastguard Worker    }
207*6dbdd20aSAndroid Build Coastguard Worker  }
208*6dbdd20aSAndroid Build Coastguard Worker
209*6dbdd20aSAndroid Build Coastguard Worker  private computeCpuProfileFlamegraph(trace: Trace, isChanged: boolean) {
210*6dbdd20aSAndroid Build Coastguard Worker    const currentSelection = trace.selection.selection;
211*6dbdd20aSAndroid Build Coastguard Worker    if (currentSelection.kind !== 'area') {
212*6dbdd20aSAndroid Build Coastguard Worker      return undefined;
213*6dbdd20aSAndroid Build Coastguard Worker    }
214*6dbdd20aSAndroid Build Coastguard Worker    if (!isChanged) {
215*6dbdd20aSAndroid Build Coastguard Worker      // If the selection has not changed, just return a copy of the last seen
216*6dbdd20aSAndroid Build Coastguard Worker      // attrs.
217*6dbdd20aSAndroid Build Coastguard Worker      return this.cpuProfileFlamegraph;
218*6dbdd20aSAndroid Build Coastguard Worker    }
219*6dbdd20aSAndroid Build Coastguard Worker    const utids = [];
220*6dbdd20aSAndroid Build Coastguard Worker    for (const trackInfo of currentSelection.tracks) {
221*6dbdd20aSAndroid Build Coastguard Worker      if (trackInfo?.tags?.kind === CPU_PROFILE_TRACK_KIND) {
222*6dbdd20aSAndroid Build Coastguard Worker        utids.push(trackInfo.tags?.utid);
223*6dbdd20aSAndroid Build Coastguard Worker      }
224*6dbdd20aSAndroid Build Coastguard Worker    }
225*6dbdd20aSAndroid Build Coastguard Worker    if (utids.length === 0) {
226*6dbdd20aSAndroid Build Coastguard Worker      return undefined;
227*6dbdd20aSAndroid Build Coastguard Worker    }
228*6dbdd20aSAndroid Build Coastguard Worker    const metrics = metricsFromTableOrSubquery(
229*6dbdd20aSAndroid Build Coastguard Worker      `
230*6dbdd20aSAndroid Build Coastguard Worker        (
231*6dbdd20aSAndroid Build Coastguard Worker          select
232*6dbdd20aSAndroid Build Coastguard Worker            id,
233*6dbdd20aSAndroid Build Coastguard Worker            parent_id as parentId,
234*6dbdd20aSAndroid Build Coastguard Worker            name,
235*6dbdd20aSAndroid Build Coastguard Worker            mapping_name,
236*6dbdd20aSAndroid Build Coastguard Worker            source_file,
237*6dbdd20aSAndroid Build Coastguard Worker            cast(line_number AS text) as line_number,
238*6dbdd20aSAndroid Build Coastguard Worker            self_count
239*6dbdd20aSAndroid Build Coastguard Worker          from _callstacks_for_callsites!((
240*6dbdd20aSAndroid Build Coastguard Worker            select p.callsite_id
241*6dbdd20aSAndroid Build Coastguard Worker            from cpu_profile_stack_sample p
242*6dbdd20aSAndroid Build Coastguard Worker            where p.ts >= ${currentSelection.start}
243*6dbdd20aSAndroid Build Coastguard Worker              and p.ts <= ${currentSelection.end}
244*6dbdd20aSAndroid Build Coastguard Worker              and p.utid in (${utids.join(',')})
245*6dbdd20aSAndroid Build Coastguard Worker          ))
246*6dbdd20aSAndroid Build Coastguard Worker        )
247*6dbdd20aSAndroid Build Coastguard Worker      `,
248*6dbdd20aSAndroid Build Coastguard Worker      [
249*6dbdd20aSAndroid Build Coastguard Worker        {
250*6dbdd20aSAndroid Build Coastguard Worker          name: 'CPU Profile Samples',
251*6dbdd20aSAndroid Build Coastguard Worker          unit: '',
252*6dbdd20aSAndroid Build Coastguard Worker          columnName: 'self_count',
253*6dbdd20aSAndroid Build Coastguard Worker        },
254*6dbdd20aSAndroid Build Coastguard Worker      ],
255*6dbdd20aSAndroid Build Coastguard Worker      'include perfetto module callstacks.stack_profile',
256*6dbdd20aSAndroid Build Coastguard Worker      [{name: 'mapping_name', displayName: 'Mapping'}],
257*6dbdd20aSAndroid Build Coastguard Worker      [
258*6dbdd20aSAndroid Build Coastguard Worker        {
259*6dbdd20aSAndroid Build Coastguard Worker          name: 'source_file',
260*6dbdd20aSAndroid Build Coastguard Worker          displayName: 'Source File',
261*6dbdd20aSAndroid Build Coastguard Worker          mergeAggregation: 'ONE_OR_NULL',
262*6dbdd20aSAndroid Build Coastguard Worker        },
263*6dbdd20aSAndroid Build Coastguard Worker        {
264*6dbdd20aSAndroid Build Coastguard Worker          name: 'line_number',
265*6dbdd20aSAndroid Build Coastguard Worker          displayName: 'Line Number',
266*6dbdd20aSAndroid Build Coastguard Worker          mergeAggregation: 'ONE_OR_NULL',
267*6dbdd20aSAndroid Build Coastguard Worker        },
268*6dbdd20aSAndroid Build Coastguard Worker      ],
269*6dbdd20aSAndroid Build Coastguard Worker    );
270*6dbdd20aSAndroid Build Coastguard Worker    return new QueryFlamegraph(trace, metrics, {
271*6dbdd20aSAndroid Build Coastguard Worker      state: Flamegraph.createDefaultState(metrics),
272*6dbdd20aSAndroid Build Coastguard Worker    });
273*6dbdd20aSAndroid Build Coastguard Worker  }
274*6dbdd20aSAndroid Build Coastguard Worker
275*6dbdd20aSAndroid Build Coastguard Worker  private computePerfSampleFlamegraph(trace: Trace, isChanged: boolean) {
276*6dbdd20aSAndroid Build Coastguard Worker    const currentSelection = trace.selection.selection;
277*6dbdd20aSAndroid Build Coastguard Worker    if (currentSelection.kind !== 'area') {
278*6dbdd20aSAndroid Build Coastguard Worker      return undefined;
279*6dbdd20aSAndroid Build Coastguard Worker    }
280*6dbdd20aSAndroid Build Coastguard Worker    if (!isChanged) {
281*6dbdd20aSAndroid Build Coastguard Worker      // If the selection has not changed, just return a copy of the last seen
282*6dbdd20aSAndroid Build Coastguard Worker      // attrs.
283*6dbdd20aSAndroid Build Coastguard Worker      return this.perfSampleFlamegraph;
284*6dbdd20aSAndroid Build Coastguard Worker    }
285*6dbdd20aSAndroid Build Coastguard Worker    const upids = getUpidsFromPerfSampleAreaSelection(currentSelection);
286*6dbdd20aSAndroid Build Coastguard Worker    const utids = getUtidsFromPerfSampleAreaSelection(currentSelection);
287*6dbdd20aSAndroid Build Coastguard Worker    if (utids.length === 0 && upids.length === 0) {
288*6dbdd20aSAndroid Build Coastguard Worker      return undefined;
289*6dbdd20aSAndroid Build Coastguard Worker    }
290*6dbdd20aSAndroid Build Coastguard Worker    const metrics = metricsFromTableOrSubquery(
291*6dbdd20aSAndroid Build Coastguard Worker      `
292*6dbdd20aSAndroid Build Coastguard Worker        (
293*6dbdd20aSAndroid Build Coastguard Worker          select id, parent_id as parentId, name, self_count
294*6dbdd20aSAndroid Build Coastguard Worker          from _callstacks_for_callsites!((
295*6dbdd20aSAndroid Build Coastguard Worker            select p.callsite_id
296*6dbdd20aSAndroid Build Coastguard Worker            from perf_sample p
297*6dbdd20aSAndroid Build Coastguard Worker            join thread t using (utid)
298*6dbdd20aSAndroid Build Coastguard Worker            where p.ts >= ${currentSelection.start}
299*6dbdd20aSAndroid Build Coastguard Worker              and p.ts <= ${currentSelection.end}
300*6dbdd20aSAndroid Build Coastguard Worker              and (
301*6dbdd20aSAndroid Build Coastguard Worker                p.utid in (${utids.join(',')})
302*6dbdd20aSAndroid Build Coastguard Worker                or t.upid in (${upids.join(',')})
303*6dbdd20aSAndroid Build Coastguard Worker              )
304*6dbdd20aSAndroid Build Coastguard Worker          ))
305*6dbdd20aSAndroid Build Coastguard Worker        )
306*6dbdd20aSAndroid Build Coastguard Worker      `,
307*6dbdd20aSAndroid Build Coastguard Worker      [
308*6dbdd20aSAndroid Build Coastguard Worker        {
309*6dbdd20aSAndroid Build Coastguard Worker          name: 'Perf Samples',
310*6dbdd20aSAndroid Build Coastguard Worker          unit: '',
311*6dbdd20aSAndroid Build Coastguard Worker          columnName: 'self_count',
312*6dbdd20aSAndroid Build Coastguard Worker        },
313*6dbdd20aSAndroid Build Coastguard Worker      ],
314*6dbdd20aSAndroid Build Coastguard Worker      'include perfetto module linux.perf.samples',
315*6dbdd20aSAndroid Build Coastguard Worker    );
316*6dbdd20aSAndroid Build Coastguard Worker    return new QueryFlamegraph(trace, metrics, {
317*6dbdd20aSAndroid Build Coastguard Worker      state: Flamegraph.createDefaultState(metrics),
318*6dbdd20aSAndroid Build Coastguard Worker    });
319*6dbdd20aSAndroid Build Coastguard Worker  }
320*6dbdd20aSAndroid Build Coastguard Worker
321*6dbdd20aSAndroid Build Coastguard Worker  private computeSliceFlamegraph(trace: Trace, isChanged: boolean) {
322*6dbdd20aSAndroid Build Coastguard Worker    const currentSelection = trace.selection.selection;
323*6dbdd20aSAndroid Build Coastguard Worker    if (currentSelection.kind !== 'area') {
324*6dbdd20aSAndroid Build Coastguard Worker      return undefined;
325*6dbdd20aSAndroid Build Coastguard Worker    }
326*6dbdd20aSAndroid Build Coastguard Worker    if (!isChanged) {
327*6dbdd20aSAndroid Build Coastguard Worker      // If the selection has not changed, just return a copy of the last seen
328*6dbdd20aSAndroid Build Coastguard Worker      // attrs.
329*6dbdd20aSAndroid Build Coastguard Worker      return this.sliceFlamegraph;
330*6dbdd20aSAndroid Build Coastguard Worker    }
331*6dbdd20aSAndroid Build Coastguard Worker    const trackIds = [];
332*6dbdd20aSAndroid Build Coastguard Worker    for (const trackInfo of currentSelection.tracks) {
333*6dbdd20aSAndroid Build Coastguard Worker      if (trackInfo?.tags?.kind !== SLICE_TRACK_KIND) {
334*6dbdd20aSAndroid Build Coastguard Worker        continue;
335*6dbdd20aSAndroid Build Coastguard Worker      }
336*6dbdd20aSAndroid Build Coastguard Worker      if (trackInfo.tags?.trackIds === undefined) {
337*6dbdd20aSAndroid Build Coastguard Worker        continue;
338*6dbdd20aSAndroid Build Coastguard Worker      }
339*6dbdd20aSAndroid Build Coastguard Worker      trackIds.push(...trackInfo.tags.trackIds);
340*6dbdd20aSAndroid Build Coastguard Worker    }
341*6dbdd20aSAndroid Build Coastguard Worker    if (trackIds.length === 0) {
342*6dbdd20aSAndroid Build Coastguard Worker      return undefined;
343*6dbdd20aSAndroid Build Coastguard Worker    }
344*6dbdd20aSAndroid Build Coastguard Worker    const metrics = metricsFromTableOrSubquery(
345*6dbdd20aSAndroid Build Coastguard Worker      `
346*6dbdd20aSAndroid Build Coastguard Worker        (
347*6dbdd20aSAndroid Build Coastguard Worker          select *
348*6dbdd20aSAndroid Build Coastguard Worker          from _viz_slice_ancestor_agg!((
349*6dbdd20aSAndroid Build Coastguard Worker            select s.id, s.dur
350*6dbdd20aSAndroid Build Coastguard Worker            from slice s
351*6dbdd20aSAndroid Build Coastguard Worker            left join slice t on t.parent_id = s.id
352*6dbdd20aSAndroid Build Coastguard Worker            where s.ts >= ${currentSelection.start}
353*6dbdd20aSAndroid Build Coastguard Worker              and s.ts <= ${currentSelection.end}
354*6dbdd20aSAndroid Build Coastguard Worker              and s.track_id in (${trackIds.join(',')})
355*6dbdd20aSAndroid Build Coastguard Worker              and t.id is null
356*6dbdd20aSAndroid Build Coastguard Worker          ))
357*6dbdd20aSAndroid Build Coastguard Worker        )
358*6dbdd20aSAndroid Build Coastguard Worker      `,
359*6dbdd20aSAndroid Build Coastguard Worker      [
360*6dbdd20aSAndroid Build Coastguard Worker        {
361*6dbdd20aSAndroid Build Coastguard Worker          name: 'Duration',
362*6dbdd20aSAndroid Build Coastguard Worker          unit: 'ns',
363*6dbdd20aSAndroid Build Coastguard Worker          columnName: 'self_dur',
364*6dbdd20aSAndroid Build Coastguard Worker        },
365*6dbdd20aSAndroid Build Coastguard Worker        {
366*6dbdd20aSAndroid Build Coastguard Worker          name: 'Samples',
367*6dbdd20aSAndroid Build Coastguard Worker          unit: '',
368*6dbdd20aSAndroid Build Coastguard Worker          columnName: 'self_count',
369*6dbdd20aSAndroid Build Coastguard Worker        },
370*6dbdd20aSAndroid Build Coastguard Worker      ],
371*6dbdd20aSAndroid Build Coastguard Worker      'include perfetto module viz.slices;',
372*6dbdd20aSAndroid Build Coastguard Worker    );
373*6dbdd20aSAndroid Build Coastguard Worker    return new QueryFlamegraph(trace, metrics, {
374*6dbdd20aSAndroid Build Coastguard Worker      state: Flamegraph.createDefaultState(metrics),
375*6dbdd20aSAndroid Build Coastguard Worker    });
376*6dbdd20aSAndroid Build Coastguard Worker  }
377*6dbdd20aSAndroid Build Coastguard Worker}
378*6dbdd20aSAndroid Build Coastguard Worker
379*6dbdd20aSAndroid Build Coastguard Workerexport class AggregationsTabs implements Disposable {
380*6dbdd20aSAndroid Build Coastguard Worker  private trash = new DisposableStack();
381*6dbdd20aSAndroid Build Coastguard Worker
382*6dbdd20aSAndroid Build Coastguard Worker  constructor(trace: TraceImpl) {
383*6dbdd20aSAndroid Build Coastguard Worker    const unregister = trace.tabs.registerDetailsPanel({
384*6dbdd20aSAndroid Build Coastguard Worker      render(selection) {
385*6dbdd20aSAndroid Build Coastguard Worker        if (selection.kind === 'area') {
386*6dbdd20aSAndroid Build Coastguard Worker          return m(AreaDetailsPanel, {trace});
387*6dbdd20aSAndroid Build Coastguard Worker        } else {
388*6dbdd20aSAndroid Build Coastguard Worker          return undefined;
389*6dbdd20aSAndroid Build Coastguard Worker        }
390*6dbdd20aSAndroid Build Coastguard Worker      },
391*6dbdd20aSAndroid Build Coastguard Worker    });
392*6dbdd20aSAndroid Build Coastguard Worker
393*6dbdd20aSAndroid Build Coastguard Worker    this.trash.use(unregister);
394*6dbdd20aSAndroid Build Coastguard Worker  }
395*6dbdd20aSAndroid Build Coastguard Worker
396*6dbdd20aSAndroid Build Coastguard Worker  [Symbol.dispose]() {
397*6dbdd20aSAndroid Build Coastguard Worker    this.trash.dispose();
398*6dbdd20aSAndroid Build Coastguard Worker  }
399*6dbdd20aSAndroid Build Coastguard Worker}
400*6dbdd20aSAndroid Build Coastguard Worker
401*6dbdd20aSAndroid Build Coastguard Workerfunction getUpidsFromPerfSampleAreaSelection(currentSelection: AreaSelection) {
402*6dbdd20aSAndroid Build Coastguard Worker  const upids = [];
403*6dbdd20aSAndroid Build Coastguard Worker  for (const trackInfo of currentSelection.tracks) {
404*6dbdd20aSAndroid Build Coastguard Worker    if (
405*6dbdd20aSAndroid Build Coastguard Worker      trackInfo?.tags?.kind === PERF_SAMPLES_PROFILE_TRACK_KIND &&
406*6dbdd20aSAndroid Build Coastguard Worker      trackInfo.tags?.utid === undefined
407*6dbdd20aSAndroid Build Coastguard Worker    ) {
408*6dbdd20aSAndroid Build Coastguard Worker      upids.push(assertExists(trackInfo.tags?.upid));
409*6dbdd20aSAndroid Build Coastguard Worker    }
410*6dbdd20aSAndroid Build Coastguard Worker  }
411*6dbdd20aSAndroid Build Coastguard Worker  return upids;
412*6dbdd20aSAndroid Build Coastguard Worker}
413*6dbdd20aSAndroid Build Coastguard Worker
414*6dbdd20aSAndroid Build Coastguard Workerfunction getUtidsFromPerfSampleAreaSelection(currentSelection: AreaSelection) {
415*6dbdd20aSAndroid Build Coastguard Worker  const utids = [];
416*6dbdd20aSAndroid Build Coastguard Worker  for (const trackInfo of currentSelection.tracks) {
417*6dbdd20aSAndroid Build Coastguard Worker    if (
418*6dbdd20aSAndroid Build Coastguard Worker      trackInfo?.tags?.kind === PERF_SAMPLES_PROFILE_TRACK_KIND &&
419*6dbdd20aSAndroid Build Coastguard Worker      trackInfo.tags?.utid !== undefined
420*6dbdd20aSAndroid Build Coastguard Worker    ) {
421*6dbdd20aSAndroid Build Coastguard Worker      utids.push(trackInfo.tags?.utid);
422*6dbdd20aSAndroid Build Coastguard Worker    }
423*6dbdd20aSAndroid Build Coastguard Worker  }
424*6dbdd20aSAndroid Build Coastguard Worker  return utids;
425*6dbdd20aSAndroid Build Coastguard Worker}
426