xref: /aosp_15_r20/external/perfetto/ui/src/plugins/dev.perfetto.CriticalPath/index.ts (revision 6dbdd20afdafa5e3ca9b8809fa73465d530080dc)
1// Copyright (C) 2024 The Android Open Source Project
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//      http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15import {getThreadInfo, ThreadInfo} from '../../components/sql_utils/thread';
16import {addDebugSliceTrack} from '../../components/tracks/debug_tracks';
17import {Trace} from '../../public/trace';
18import {THREAD_STATE_TRACK_KIND} from '../../public/track_kinds';
19import {PerfettoPlugin} from '../../public/plugin';
20import {asUtid, Utid} from '../../components/sql_utils/core_types';
21import {addQueryResultsTab} from '../../components/query_table/query_result_tab';
22import {showModal} from '../../widgets/modal';
23import {
24  CRITICAL_PATH_CMD,
25  CRITICAL_PATH_LITE_CMD,
26} from '../../public/exposed_commands';
27import {getTimeSpanOfSelectionOrVisibleWindow} from '../../public/utils';
28
29const criticalPathSliceColumns = {
30  ts: 'ts',
31  dur: 'dur',
32  name: 'name',
33};
34
35const criticalPathsliceColumnNames = [
36  'id',
37  'utid',
38  'ts',
39  'dur',
40  'name',
41  'table_name',
42];
43
44const criticalPathsliceLiteColumns = {
45  ts: 'ts',
46  dur: 'dur',
47  name: 'thread_name',
48};
49
50const criticalPathsliceLiteColumnNames = [
51  'id',
52  'utid',
53  'ts',
54  'dur',
55  'thread_name',
56  'process_name',
57  'table_name',
58];
59
60const sliceLiteColumns = {ts: 'ts', dur: 'dur', name: 'thread_name'};
61
62const sliceLiteColumnNames = [
63  'id',
64  'utid',
65  'ts',
66  'dur',
67  'thread_name',
68  'process_name',
69  'table_name',
70];
71
72const sliceColumns = {ts: 'ts', dur: 'dur', name: 'name'};
73
74const sliceColumnNames = ['id', 'utid', 'ts', 'dur', 'name', 'table_name'];
75
76function getFirstUtidOfSelectionOrVisibleWindow(trace: Trace): number {
77  const selection = trace.selection.selection;
78  if (selection.kind === 'area') {
79    for (const trackDesc of selection.tracks) {
80      if (
81        trackDesc?.tags?.kind === THREAD_STATE_TRACK_KIND &&
82        trackDesc?.tags?.utid !== undefined
83      ) {
84        return trackDesc.tags.utid;
85      }
86    }
87  }
88
89  return 0;
90}
91
92function showModalErrorAreaSelectionRequired() {
93  showModal({
94    title: 'Error: range selection required',
95    content:
96      'This command requires an area selection over a thread state track.',
97  });
98}
99
100function showModalErrorThreadStateRequired() {
101  showModal({
102    title: 'Error: thread state selection required',
103    content: 'This command requires a thread state slice to be selected.',
104  });
105}
106
107// If utid is undefined, returns the utid for the selected thread state track,
108// if any. If it's defined, looks up the info about that specific utid.
109async function getThreadInfoForUtidOrSelection(
110  trace: Trace,
111  utid?: Utid,
112): Promise<ThreadInfo | undefined> {
113  if (utid === undefined) {
114    const selection = trace.selection.selection;
115    if (selection.kind === 'track_event') {
116      if (selection.utid !== undefined) {
117        utid = asUtid(selection.utid);
118      }
119    }
120  }
121  if (utid === undefined) return undefined;
122  return getThreadInfo(trace.engine, utid);
123}
124
125export default class implements PerfettoPlugin {
126  static readonly id = 'dev.perfetto.CriticalPath';
127  async onTraceLoad(ctx: Trace): Promise<void> {
128    // The 3 commands below are used in two contextes:
129    // 1. By clicking a slice and using the command palette. In this case the
130    //    utid argument is undefined and we need to look at the selection.
131    // 2. Invoked via runCommand(...) by thread_state_tab.ts when the user
132    //    clicks on the buttons in the details panel. In this case the details
133    //    panel passes the utid explicitly.
134    ctx.commands.registerCommand({
135      id: CRITICAL_PATH_LITE_CMD,
136      name: 'Critical path lite (selected thread state slice)',
137      callback: async (utid?: Utid) => {
138        const thdInfo = await getThreadInfoForUtidOrSelection(ctx, utid);
139        if (thdInfo === undefined) {
140          return showModalErrorThreadStateRequired();
141        }
142        ctx.engine
143          .query(`INCLUDE PERFETTO MODULE sched.thread_executing_span;`)
144          .then(() =>
145            addDebugSliceTrack({
146              trace: ctx,
147              data: {
148                sqlSource: `
149                SELECT
150                  cr.id,
151                  cr.utid,
152                  cr.ts,
153                  cr.dur,
154                  thread.name AS thread_name,
155                  process.name AS process_name,
156                  'thread_state' AS table_name
157                FROM
158                  _thread_executing_span_critical_path(
159                    ${thdInfo.utid},
160                    trace_bounds.start_ts,
161                    trace_bounds.end_ts - trace_bounds.start_ts) cr,
162                  trace_bounds
163                JOIN thread USING(utid)
164                JOIN process USING(upid)
165              `,
166                columns: sliceLiteColumnNames,
167              },
168              title: `${thdInfo.name}`,
169              columns: sliceLiteColumns,
170              argColumns: sliceLiteColumnNames,
171            }),
172          );
173      },
174    });
175
176    ctx.commands.registerCommand({
177      id: CRITICAL_PATH_CMD,
178      name: 'Critical path (selected thread state slice)',
179      callback: async (utid?: Utid) => {
180        const thdInfo = await getThreadInfoForUtidOrSelection(ctx, utid);
181        if (thdInfo === undefined) {
182          return showModalErrorThreadStateRequired();
183        }
184        ctx.engine
185          .query(
186            `INCLUDE PERFETTO MODULE sched.thread_executing_span_with_slice;`,
187          )
188          .then(() =>
189            addDebugSliceTrack({
190              trace: ctx,
191              data: {
192                sqlSource: `
193                SELECT cr.id, cr.utid, cr.ts, cr.dur, cr.name, cr.table_name
194                  FROM
195                    _thread_executing_span_critical_path_stack(
196                      ${thdInfo.utid},
197                      trace_bounds.start_ts,
198                      trace_bounds.end_ts - trace_bounds.start_ts) cr,
199                    trace_bounds WHERE name IS NOT NULL
200              `,
201                columns: sliceColumnNames,
202              },
203              title: `${thdInfo.name}`,
204              columns: sliceColumns,
205              argColumns: sliceColumnNames,
206            }),
207          );
208      },
209    });
210
211    ctx.commands.registerCommand({
212      id: 'perfetto.CriticalPathLite_AreaSelection',
213      name: 'Critical path lite (over area selection)',
214      callback: async () => {
215        const trackUtid = getFirstUtidOfSelectionOrVisibleWindow(ctx);
216        const window = await getTimeSpanOfSelectionOrVisibleWindow(ctx);
217        if (trackUtid === 0) {
218          return showModalErrorAreaSelectionRequired();
219        }
220        await ctx.engine.query(
221          `INCLUDE PERFETTO MODULE sched.thread_executing_span;`,
222        );
223        await addDebugSliceTrack({
224          trace: ctx,
225          data: {
226            sqlSource: `
227                SELECT
228                  cr.id,
229                  cr.utid,
230                  cr.ts,
231                  cr.dur,
232                  thread.name AS thread_name,
233                  process.name AS process_name,
234                  'thread_state' AS table_name
235                FROM
236                  _thread_executing_span_critical_path(
237                      ${trackUtid},
238                      ${window.start},
239                      ${window.end} - ${window.start}) cr
240                JOIN thread USING(utid)
241                JOIN process USING(upid)
242                `,
243            columns: criticalPathsliceLiteColumnNames,
244          },
245          title:
246            (await getThreadInfo(ctx.engine, trackUtid as Utid)).name ??
247            '<thread name>',
248          columns: criticalPathsliceLiteColumns,
249          argColumns: criticalPathsliceLiteColumnNames,
250        });
251      },
252    });
253
254    ctx.commands.registerCommand({
255      id: 'perfetto.CriticalPath_AreaSelection',
256      name: 'Critical path  (over area selection)',
257      callback: async () => {
258        const trackUtid = getFirstUtidOfSelectionOrVisibleWindow(ctx);
259        const window = await getTimeSpanOfSelectionOrVisibleWindow(ctx);
260        if (trackUtid === 0) {
261          return showModalErrorAreaSelectionRequired();
262        }
263        await ctx.engine.query(
264          `INCLUDE PERFETTO MODULE sched.thread_executing_span_with_slice;`,
265        );
266        await addDebugSliceTrack({
267          trace: ctx,
268          data: {
269            sqlSource: `
270                SELECT cr.id, cr.utid, cr.ts, cr.dur, cr.name, cr.table_name
271                FROM
272                _critical_path_stack(
273                  ${trackUtid},
274                  ${window.start},
275                  ${window.end} - ${window.start}, 1, 1, 1, 1) cr
276                WHERE name IS NOT NULL
277                `,
278            columns: criticalPathsliceColumnNames,
279          },
280          title:
281            (await getThreadInfo(ctx.engine, trackUtid as Utid)).name ??
282            '<thread name>',
283          columns: criticalPathSliceColumns,
284          argColumns: criticalPathsliceColumnNames,
285        });
286      },
287    });
288
289    ctx.commands.registerCommand({
290      id: 'perfetto.CriticalPathPprof_AreaSelection',
291      name: 'Critical path pprof (over area selection)',
292      callback: async () => {
293        const trackUtid = getFirstUtidOfSelectionOrVisibleWindow(ctx);
294        const window = await getTimeSpanOfSelectionOrVisibleWindow(ctx);
295        if (trackUtid === 0) {
296          return showModalErrorAreaSelectionRequired();
297        }
298        addQueryResultsTab(ctx, {
299          query: `
300              INCLUDE PERFETTO MODULE sched.thread_executing_span_with_slice;
301              SELECT *
302                FROM
303                  _thread_executing_span_critical_path_graph(
304                  "criical_path",
305                    ${trackUtid},
306                    ${window.start},
307                    ${window.end} - ${window.start}) cr`,
308          title: 'Critical path',
309        });
310      },
311    });
312  }
313}
314