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