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