• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 '../../public';
16import {Plugin, PluginContextTrace, PluginDescriptor} from '../../public';
17
18const JANK_CUJ_QUERY_PRECONDITIONS = `
19  SELECT RUN_METRIC('android/android_jank_cuj.sql');
20  SELECT RUN_METRIC('android/jank/internal/counters.sql');
21  INCLUDE PERFETTO MODULE android.critical_blocking_calls;
22`;
23
24const JANK_CUJ_QUERY = `
25    SELECT
26      CASE
27        WHEN
28          EXISTS(
29              SELECT 1
30              FROM slice AS cuj_state_marker
31                     JOIN track marker_track
32                          ON marker_track.id = cuj_state_marker.track_id
33              WHERE
34                cuj_state_marker.ts >= cuj.ts
35                AND cuj_state_marker.ts + cuj_state_marker.dur <= cuj.ts + cuj.dur
36                AND
37                ( /* e.g. J<CUJ_NAME>#FT#cancel#0 this for backward compatibility */
38                      cuj_state_marker.name GLOB(cuj.name || '#FT#cancel*')
39                    OR (marker_track.name = cuj.name AND cuj_state_marker.name GLOB 'FT#cancel*')
40                  )
41            )
42          THEN ' ❌ '
43        WHEN
44          EXISTS(
45              SELECT 1
46              FROM slice AS cuj_state_marker
47                     JOIN track marker_track
48                          ON marker_track.id = cuj_state_marker.track_id
49              WHERE
50                cuj_state_marker.ts >= cuj.ts
51                AND cuj_state_marker.ts + cuj_state_marker.dur <= cuj.ts + cuj.dur
52                AND
53                ( /* e.g. J<CUJ_NAME>#FT#end#0 this for backward compatibility */
54                      cuj_state_marker.name GLOB(cuj.name || '#FT#end*')
55                    OR (marker_track.name = cuj.name AND cuj_state_marker.name GLOB 'FT#end*')
56                  )
57            )
58          THEN ' ✅ '
59        ELSE NULL
60        END || cuj.name AS name,
61      total_frames,
62      missed_app_frames,
63      missed_sf_frames,
64      sf_callback_missed_frames,
65      hwui_callback_missed_frames,
66      cuj_layer.layer_name,
67      /* Boundaries table doesn't contain ts and dur when a CUJ didn't complete successfully.
68        In that case we still want to show that it was canceled, so let's take the slice timestamps. */
69      CASE WHEN boundaries.ts IS NOT NULL THEN boundaries.ts ELSE cuj.ts END AS ts,
70      CASE WHEN boundaries.dur IS NOT NULL THEN boundaries.dur ELSE cuj.dur END AS dur,
71      cuj.track_id,
72      cuj.slice_id
73    FROM slice AS cuj
74           JOIN process_track AS pt ON cuj.track_id = pt.id
75           LEFT JOIN android_jank_cuj jc
76                     ON pt.upid = jc.upid AND cuj.name = jc.cuj_slice_name AND cuj.ts = jc.ts
77           LEFT JOIN android_jank_cuj_main_thread_cuj_boundary boundaries using (cuj_id)
78           LEFT JOIN android_jank_cuj_layer_name cuj_layer USING (cuj_id)
79           LEFT JOIN android_jank_cuj_counter_metrics USING (cuj_id)
80    WHERE cuj.name GLOB 'J<*>'
81      AND cuj.dur > 0
82`;
83
84const JANK_COLUMNS = [
85  'name',
86  'total_frames',
87  'missed_app_frames',
88  'missed_sf_frames',
89  'sf_callback_missed_frames',
90  'hwui_callback_missed_frames',
91  'layer_name',
92  'ts',
93  'dur',
94  'track_id',
95  'slice_id',
96];
97
98const LATENCY_CUJ_QUERY = `
99    SELECT
100      CASE
101        WHEN
102          EXISTS(
103              SELECT 1
104              FROM slice AS cuj_state_marker
105                     JOIN track marker_track
106                          ON marker_track.id = cuj_state_marker.track_id
107              WHERE
108                cuj_state_marker.ts >= cuj.ts
109                AND cuj_state_marker.ts + cuj_state_marker.dur <= cuj.ts + cuj.dur
110                AND marker_track.name = cuj.name AND (
111                    cuj_state_marker.name GLOB 'cancel'
112                    OR cuj_state_marker.name GLOB 'timeout')
113            )
114          THEN ' ❌ '
115        ELSE ' ✅ '
116        END || cuj.name AS name,
117      cuj.dur / 1e6 as dur_ms,
118      cuj.ts,
119      cuj.dur,
120      cuj.track_id,
121      cuj.slice_id
122    FROM slice AS cuj
123           JOIN process_track AS pt
124                ON cuj.track_id = pt.id
125    WHERE cuj.name GLOB 'L<*>'
126      AND cuj.dur > 0
127`;
128
129const LATENCY_COLUMNS = ['name', 'dur_ms', 'ts', 'dur', 'track_id', 'slice_id'];
130
131const BLOCKING_CALLS_DURING_CUJS_QUERY = `
132    SELECT
133      s.id AS slice_id,
134      s.name,
135      max(s.ts, cuj.ts) AS ts,
136      min(s.ts + s.dur, cuj.ts_end) as ts_end,
137      min(s.ts + s.dur, cuj.ts_end) - max(s.ts, cuj.ts) AS dur,
138      cuj.cuj_id,
139      cuj.cuj_name,
140      s.process_name,
141      s.upid,
142      s.utid,
143      'slice' AS table_name
144    FROM _android_critical_blocking_calls s
145      JOIN  android_jank_cuj cuj
146      -- only when there is an overlap
147      ON s.ts + s.dur > cuj.ts AND s.ts < cuj.ts_end
148          -- and are from the same process
149          AND s.upid = cuj.upid
150`;
151
152const BLOCKING_CALLS_DURING_CUJS_COLUMNS = [
153  'slice_id',
154  'name',
155  'ts',
156  'cuj_ts',
157  'dur',
158  'cuj_id',
159  'cuj_name',
160  'process_name',
161  'upid',
162  'utid',
163  'table_name',
164];
165
166class AndroidCujs implements Plugin {
167  async onTraceLoad(ctx: PluginContextTrace): Promise<void> {
168    ctx.registerCommand({
169      id: 'dev.perfetto.AndroidCujs#PinJankCUJs',
170      name: 'Add track: Android jank CUJs',
171      callback: () => {
172        ctx.engine.query(JANK_CUJ_QUERY_PRECONDITIONS).then(() => {
173          addDebugSliceTrack(
174            ctx,
175            {
176              sqlSource: JANK_CUJ_QUERY,
177              columns: JANK_COLUMNS,
178            },
179            'Jank CUJs',
180            {ts: 'ts', dur: 'dur', name: 'name'},
181            JANK_COLUMNS,
182          );
183        });
184      },
185    });
186
187    ctx.registerCommand({
188      id: 'dev.perfetto.AndroidCujs#ListJankCUJs',
189      name: 'Run query: Android jank CUJs',
190      callback: () => {
191        ctx.engine
192          .query(JANK_CUJ_QUERY_PRECONDITIONS)
193          .then(() => ctx.tabs.openQuery(JANK_CUJ_QUERY, 'Android Jank CUJs'));
194      },
195    });
196
197    ctx.registerCommand({
198      id: 'dev.perfetto.AndroidCujs#PinLatencyCUJs',
199      name: 'Add track: Android latency CUJs',
200      callback: () => {
201        addDebugSliceTrack(
202          ctx,
203          {
204            sqlSource: LATENCY_CUJ_QUERY,
205            columns: LATENCY_COLUMNS,
206          },
207          'Latency CUJs',
208          {ts: 'ts', dur: 'dur', name: 'name'},
209          [],
210        );
211      },
212    });
213
214    ctx.registerCommand({
215      id: 'dev.perfetto.AndroidCujs#ListLatencyCUJs',
216      name: 'Run query: Android Latency CUJs',
217      callback: () =>
218        ctx.tabs.openQuery(LATENCY_CUJ_QUERY, 'Android Latency CUJs'),
219    });
220
221    ctx.registerCommand({
222      id: 'dev.perfetto.AndroidCujs#PinBlockingCalls',
223      name: 'Add track: Android Blocking calls during CUJs',
224      callback: () => {
225        ctx.engine.query(JANK_CUJ_QUERY_PRECONDITIONS).then(() =>
226          addDebugSliceTrack(
227            ctx,
228            {
229              sqlSource: BLOCKING_CALLS_DURING_CUJS_QUERY,
230              columns: BLOCKING_CALLS_DURING_CUJS_COLUMNS,
231            },
232            'Blocking calls during CUJs',
233            {ts: 'ts', dur: 'dur', name: 'name'},
234            BLOCKING_CALLS_DURING_CUJS_COLUMNS,
235          ),
236        );
237      },
238    });
239  }
240}
241
242export const plugin: PluginDescriptor = {
243  pluginId: 'dev.perfetto.AndroidCujs',
244  plugin: AndroidCujs,
245};
246