xref: /aosp_15_r20/external/perfetto/ui/src/plugins/dev.perfetto.AndroidCujs/index.ts (revision 6dbdd20afdafa5e3ca9b8809fa73465d530080dc)
1// Copyright (C) 2023 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 {addDebugSliceTrack} from '../../components/tracks/debug_tracks';
16import {Trace} from '../../public/trace';
17import {PerfettoPlugin} from '../../public/plugin';
18import {addQueryResultsTab} from '../../components/query_table/query_result_tab';
19
20/**
21 * Adds the Debug Slice Track for given Jank CUJ name
22 *
23 * @param {Trace} ctx For properties and methods of trace viewer
24 * @param {string} trackName Display Name of the track
25 * @param {string | string[]} cujNames List of Jank CUJs to pin
26 */
27export function addJankCUJDebugTrack(
28  ctx: Trace,
29  trackName: string,
30  cujNames?: string | string[],
31) {
32  const jankCujTrackConfig = generateJankCujTrackConfig(cujNames);
33  addDebugSliceTrack({trace: ctx, title: trackName, ...jankCujTrackConfig});
34}
35
36const JANK_CUJ_QUERY_PRECONDITIONS = `
37  SELECT RUN_METRIC('android/android_jank_cuj.sql');
38  INCLUDE PERFETTO MODULE android.critical_blocking_calls;
39`;
40
41/**
42 * Generate the Track config for a multiple Jank CUJ slices
43 *
44 * @param {string | string[]} cujNames List of Jank CUJs to pin, default empty
45 * @returns Returns the track config for given CUJs
46 */
47function generateJankCujTrackConfig(cujNames: string | string[] = []) {
48  // This method expects the caller to have run JANK_CUJ_QUERY_PRECONDITIONS
49  // Not running the precondition query here to save time in case already run
50  const jankCujQuery = JANK_CUJ_QUERY;
51  const jankCujColumns = JANK_COLUMNS;
52  const cujNamesList = typeof cujNames === 'string' ? [cujNames] : cujNames;
53  const filterCuj =
54    cujNamesList?.length > 0
55      ? ` AND cuj.name IN (${cujNamesList
56          .map((name) => `'J<${name}>'`)
57          .join(',')})`
58      : '';
59
60  return {
61    data: {
62      sqlSource: `${jankCujQuery}${filterCuj}`,
63      columns: jankCujColumns,
64    },
65    argColumns: jankCujColumns,
66  };
67}
68
69const JANK_CUJ_QUERY = `
70    SELECT
71      CASE
72        WHEN
73          EXISTS(
74              SELECT 1
75              FROM slice AS cuj_state_marker
76                     JOIN track marker_track
77                          ON marker_track.id = cuj_state_marker.track_id
78              WHERE
79                cuj_state_marker.ts >= cuj.ts
80                AND cuj_state_marker.ts + cuj_state_marker.dur <= cuj.ts + cuj.dur
81                AND
82                ( /* e.g. J<CUJ_NAME>#FT#cancel#0 this for backward compatibility */
83                      cuj_state_marker.name GLOB(cuj.name || '#FT#cancel*')
84                    OR (marker_track.name = cuj.name AND cuj_state_marker.name GLOB 'FT#cancel*')
85                  )
86            )
87          THEN ' ❌ '
88        WHEN
89          EXISTS(
90              SELECT 1
91              FROM slice AS cuj_state_marker
92                     JOIN track marker_track
93                          ON marker_track.id = cuj_state_marker.track_id
94              WHERE
95                cuj_state_marker.ts >= cuj.ts
96                AND cuj_state_marker.ts + cuj_state_marker.dur <= cuj.ts + cuj.dur
97                AND
98                ( /* e.g. J<CUJ_NAME>#FT#end#0 this for backward compatibility */
99                      cuj_state_marker.name GLOB(cuj.name || '#FT#end*')
100                    OR (marker_track.name = cuj.name AND cuj_state_marker.name GLOB 'FT#end*')
101                  )
102            )
103          THEN ' ✅ '
104        ELSE ' ❓ '
105        END || cuj.name AS name,
106      total_frames,
107      missed_app_frames,
108      missed_sf_frames,
109      sf_callback_missed_frames,
110      hwui_callback_missed_frames,
111      cuj_layer.layer_name,
112      /* Boundaries table doesn't contain ts and dur when a CUJ didn't complete successfully.
113        In that case we still want to show that it was canceled, so let's take the slice timestamps. */
114      CASE WHEN boundaries.ts IS NOT NULL THEN boundaries.ts ELSE cuj.ts END AS ts,
115      CASE WHEN boundaries.dur IS NOT NULL THEN boundaries.dur ELSE cuj.dur END AS dur,
116      cuj.track_id,
117      cuj.slice_id
118    FROM slice AS cuj
119           JOIN process_track AS pt ON cuj.track_id = pt.id
120           LEFT JOIN android_jank_cuj jc
121                     ON pt.upid = jc.upid AND cuj.name = jc.cuj_slice_name AND cuj.ts = jc.ts
122           LEFT JOIN android_jank_cuj_main_thread_cuj_boundary boundaries using (cuj_id)
123           LEFT JOIN android_jank_cuj_layer_name cuj_layer USING (cuj_id)
124           LEFT JOIN android_jank_cuj_counter_metrics USING (cuj_id)
125    WHERE cuj.name GLOB 'J<*>'
126      AND cuj.dur > 0
127`;
128
129const JANK_COLUMNS = [
130  'name',
131  'total_frames',
132  'missed_app_frames',
133  'missed_sf_frames',
134  'sf_callback_missed_frames',
135  'hwui_callback_missed_frames',
136  'layer_name',
137  'ts',
138  'dur',
139  'track_id',
140  'slice_id',
141];
142
143const LATENCY_CUJ_QUERY = `
144    SELECT
145      CASE
146        WHEN
147          EXISTS(
148              SELECT 1
149              FROM slice AS cuj_state_marker
150                     JOIN track marker_track
151                          ON marker_track.id = cuj_state_marker.track_id
152              WHERE
153                cuj_state_marker.ts >= cuj.ts
154                AND cuj_state_marker.ts + cuj_state_marker.dur <= cuj.ts + cuj.dur
155                AND marker_track.name = cuj.name AND (
156                    cuj_state_marker.name GLOB 'cancel'
157                    OR cuj_state_marker.name GLOB 'timeout')
158            )
159          THEN ' ❌ '
160        ELSE ' ✅ '
161        END || cuj.name AS name,
162      cuj.dur / 1e6 as dur_ms,
163      cuj.ts,
164      cuj.dur,
165      cuj.track_id,
166      cuj.slice_id
167    FROM slice AS cuj
168           JOIN process_track AS pt
169                ON cuj.track_id = pt.id
170    WHERE cuj.name GLOB 'L<*>'
171      AND cuj.dur > 0
172`;
173
174const LATENCY_COLUMNS = ['name', 'dur_ms', 'ts', 'dur', 'track_id', 'slice_id'];
175
176const BLOCKING_CALLS_DURING_CUJS_QUERY = `
177    SELECT
178      s.id AS slice_id,
179      s.name,
180      max(s.ts, cuj.ts) AS ts,
181      min(s.ts + s.dur, cuj.ts_end) as ts_end,
182      min(s.ts + s.dur, cuj.ts_end) - max(s.ts, cuj.ts) AS dur,
183      cuj.cuj_id,
184      cuj.cuj_name,
185      s.process_name,
186      s.upid,
187      s.utid,
188      'slice' AS table_name
189    FROM _android_critical_blocking_calls s
190      JOIN  android_jank_cuj cuj
191      -- only when there is an overlap
192      ON s.ts + s.dur > cuj.ts AND s.ts < cuj.ts_end
193          -- and are from the same process
194          AND s.upid = cuj.upid
195`;
196
197const BLOCKING_CALLS_DURING_CUJS_COLUMNS = [
198  'slice_id',
199  'name',
200  'ts',
201  'cuj_ts',
202  'dur',
203  'cuj_id',
204  'cuj_name',
205  'process_name',
206  'upid',
207  'utid',
208  'table_name',
209];
210
211export default class implements PerfettoPlugin {
212  static readonly id = 'dev.perfetto.AndroidCujs';
213  async onTraceLoad(ctx: Trace): Promise<void> {
214    ctx.commands.registerCommand({
215      id: 'dev.perfetto.AndroidCujs#PinJankCUJs',
216      name: 'Add track: Android jank CUJs',
217      callback: () => {
218        ctx.engine.query(JANK_CUJ_QUERY_PRECONDITIONS).then(() => {
219          addJankCUJDebugTrack(ctx, 'Jank CUJs');
220        });
221      },
222    });
223
224    ctx.commands.registerCommand({
225      id: 'dev.perfetto.AndroidCujs#ListJankCUJs',
226      name: 'Run query: Android jank CUJs',
227      callback: () => {
228        ctx.engine.query(JANK_CUJ_QUERY_PRECONDITIONS).then(() =>
229          addQueryResultsTab(ctx, {
230            query: JANK_CUJ_QUERY,
231            title: 'Android Jank CUJs',
232          }),
233        );
234      },
235    });
236
237    ctx.commands.registerCommand({
238      id: 'dev.perfetto.AndroidCujs#PinLatencyCUJs',
239      name: 'Add track: Android latency CUJs',
240      callback: () => {
241        addDebugSliceTrack({
242          trace: ctx,
243          data: {
244            sqlSource: LATENCY_CUJ_QUERY,
245            columns: LATENCY_COLUMNS,
246          },
247          title: 'Latency CUJs',
248        });
249      },
250    });
251
252    ctx.commands.registerCommand({
253      id: 'dev.perfetto.AndroidCujs#ListLatencyCUJs',
254      name: 'Run query: Android Latency CUJs',
255      callback: () =>
256        addQueryResultsTab(ctx, {
257          query: LATENCY_CUJ_QUERY,
258          title: 'Android Latency CUJs',
259        }),
260    });
261
262    ctx.commands.registerCommand({
263      id: 'dev.perfetto.AndroidCujs#PinBlockingCalls',
264      name: 'Add track: Android Blocking calls during CUJs',
265      callback: () => {
266        ctx.engine.query(JANK_CUJ_QUERY_PRECONDITIONS).then(() =>
267          addDebugSliceTrack({
268            trace: ctx,
269            data: {
270              sqlSource: BLOCKING_CALLS_DURING_CUJS_QUERY,
271              columns: BLOCKING_CALLS_DURING_CUJS_COLUMNS,
272            },
273            title: 'Blocking calls during CUJs',
274            argColumns: BLOCKING_CALLS_DURING_CUJS_COLUMNS,
275          }),
276        );
277      },
278    });
279  }
280}
281