1-- 2-- Copyright 2022 The Android Open Source Project 3-- 4-- Licensed under the Apache License, Version 2.0 (the "License"); 5-- you may not use this file except in compliance with the License. 6-- You may obtain a copy of the License at 7-- 8-- https://www.apache.org/licenses/LICENSE-2.0 9-- 10-- Unless required by applicable law or agreed to in writing, software 11-- distributed under the License is distributed on an "AS IS" BASIS, 12-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13-- See the License for the specific language governing permissions and 14-- limitations under the License. 15 16-- Stores the min and max vsync IDs for each of the CUJs which are extracted 17-- from the CUJ markers. For backward compatibility (In case the markers don't 18-- exist), We calculate that by extracting the vsync ID from the 19-- `Choreographer#doFrame` slices that are within the CUJ markers. 20DROP TABLE IF EXISTS android_jank_cuj_vsync_boundary; 21CREATE PERFETTO TABLE android_jank_cuj_vsync_boundary AS 22SELECT 23 cuj.cuj_id, 24 cuj.upid, -- also store upid to simplify further queries 25 cuj.layer_id, -- also store layer_id to simplify further queries 26 IFNULL(cuj.begin_vsync, MIN(vsync)) AS vsync_min, 27 IFNULL(cuj.end_vsync, MAX(vsync)) AS vsync_max 28FROM android_jank_cuj cuj 29JOIN android_jank_cuj_do_frame_slice USING (cuj_id) 30GROUP BY cuj.cuj_id, cuj.upid, cuj.layer_id; 31 32-- Similarly, extract the min/max vsync for the SF from 33-- commit/compose/onMessageInvalidate slices on its main thread. 34DROP TABLE IF EXISTS android_jank_cuj_sf_vsync_boundary; 35CREATE PERFETTO TABLE android_jank_cuj_sf_vsync_boundary AS 36SELECT 37 cuj_id, 38 MIN(vsync) AS vsync_min, 39 MAX(vsync) AS vsync_max 40FROM android_jank_cuj_sf_root_slice 41GROUP BY cuj_id; 42 43-- Calculates the frame boundaries based on when we *expected* the work on 44-- a given frame to start and when the previous frame finished - not when 45-- Choreographer#doFrame actually started. 46-- We use MAX(expected time, previous frame ended) as the expected start time. 47-- Shifting the start time based on the previous frame is done to avoid having 48-- overlapping frame boundaries which would make further analysis more 49-- complicated. 50-- We also separately store the previous frame ts_end. 51-- This will allow us to look into cases where frame start was delayed due to 52-- some other work occupying the main thread (e.g. non-drawing callbacks or 53-- the previous frame taking much longer than expected). 54DROP TABLE IF EXISTS android_jank_cuj_main_thread_frame_boundary; 55CREATE PERFETTO TABLE android_jank_cuj_main_thread_frame_boundary AS 56-- intermediate table that discards unfinished slices and parses vsync as int. 57WITH expected_timeline AS ( 58 SELECT *, CAST(name AS INTEGER) AS vsync 59 FROM expected_frame_timeline_slice 60 WHERE dur > 0 61), 62-- Matches vsyncs in CUJ to expected frame timeline data. 63-- We also store the actual timeline data to handle a few edge cases where due to clock drift the frame timeline is shifted 64cuj_frame_timeline AS ( 65 SELECT 66 cuj_id, 67 vsync, 68 e.ts AS ts_expected, 69 -- In cases where we are drawing multiple layers, there will be one 70 -- expected frame timeline slice, but multiple actual frame timeline slices. 71 -- As a simplification we just take here the min(ts) and max(ts_end) of 72 -- the actual frame timeline slices. 73 MIN(a.ts) AS ts_actual_min, 74 MAX(a.ts + a.dur) AS ts_end_actual_max 75 FROM android_jank_cuj_vsync_boundary vsync_boundary 76 JOIN expected_timeline e 77 ON e.upid = vsync_boundary.upid 78 AND e.vsync >= vsync_min 79 AND e.vsync <= vsync_max 80 JOIN actual_frame_timeline_slice a 81 ON e.upid = a.upid 82 AND e.name = a.name 83 GROUP BY cuj_id, e.vsync, e.ts 84), 85-- Orders do_frame slices by vsync to calculate the ts_end of the previous frame 86-- android_jank_cuj_do_frame_slice only contains frames within the CUJ so 87-- the ts_prev_do_frame_end is always missing for the very first frame 88-- For now this is acceptable as it keeps the query simpler. 89do_frame_ordered AS ( 90 SELECT 91 *, 92 -- ts_end of the previous do_frame, or -1 if no previous do_frame found 93 COALESCE(LAG(ts_end) OVER (PARTITION BY cuj_id ORDER BY vsync ASC), -1) AS ts_prev_do_frame_end 94 FROM android_jank_cuj_do_frame_slice 95), 96-- introducing an intermediate table since we want to calculate dur = ts_end - ts 97frame_boundary_base AS ( 98 SELECT 99 do_frame.cuj_id, 100 do_frame.utid, 101 do_frame.vsync, 102 do_frame.ts AS ts_do_frame_start, 103 do_frame.ts_end, 104 do_frame.ts_prev_do_frame_end, 105 timeline.ts_expected, 106 CASE 107 WHEN timeline.ts_expected IS NULL 108 THEN do_frame.ts 109 ELSE MAX(do_frame.ts_prev_do_frame_end, timeline.ts_expected) 110 END AS ts 111 FROM do_frame_ordered do_frame 112 LEFT JOIN cuj_frame_timeline timeline 113 ON timeline.cuj_id = do_frame.cuj_id 114 AND do_frame.vsync = timeline.vsync 115 -- There are a few special cases we have to handle: 116 -- *) In rare cases there is a clock drift after device suspends 117 -- This may cause the actual/expected timeline to be misaligned with the rest 118 -- of the trace for a short period. 119 -- Do not use the timelines if it seems that this happened. 120 -- *) Actual timeline start time might also be reported slightly after doFrame 121 -- starts. We allow it to start up to 1ms later. 122 -- *) If the frame is significantly (~100s of ms) over the deadline, 123 -- expected timeline data will be dropped in SF and never recorded. In that case 124 -- the actual timeline will only report the end ts correctly. If this happens 125 -- fall back to calculating the boundaries based on doFrame slices. Ideally we 126 -- would prefer to infer the intended start time of the frame instead. 127 AND do_frame.ts >= timeline.ts_actual_min - 1e6 AND do_frame.ts <= timeline.ts_end_actual_max 128) 129SELECT 130 *, 131 ts_end - ts AS dur 132FROM frame_boundary_base; 133 134 135-- Compute the CUJ boundary on the main thread from the frame boundaries. 136DROP TABLE IF EXISTS android_jank_cuj_main_thread_cuj_boundary; 137CREATE PERFETTO TABLE android_jank_cuj_main_thread_cuj_boundary AS 138SELECT 139 cuj_id, 140 utid, 141 MIN(ts) AS ts, 142 MAX(ts_end) AS ts_end, 143 MAX(ts_end) - MIN(ts) AS dur 144FROM android_jank_cuj_main_thread_frame_boundary 145GROUP BY cuj_id, utid; 146 147-- Similar to `android_jank_cuj_main_thread_frame_boundary` but for the render 148-- thread the expected start time is the time of the first `postAndWait` slice 149-- on the main thread. 150-- The query is slightly simpler because we don't have to handle the clock drift 151-- and data loss like with the frame timeline. 152-- One difference vs main thread is that there might be multiple DrawFrames 153-- slices for a single vsync - this happens when we are drawing multiple layers 154-- (e.g. status bar & notifications). 155DROP TABLE IF EXISTS android_jank_cuj_render_thread_frame_boundary; 156CREATE PERFETTO TABLE android_jank_cuj_render_thread_frame_boundary AS 157-- see do_frame_ordered above 158-- we also order by `ts` to handle multiple DrawFrames for a single vsync 159WITH draw_frame_ordered AS ( 160 SELECT 161 *, 162 -- ts_end of the previous draw frame, or -1 if no previous draw frame found 163 COALESCE(LAG(ts_end) OVER (PARTITION BY cuj_id ORDER BY vsync ASC, ts ASC), -1) AS ts_prev_draw_frame_end 164 FROM android_jank_cuj_draw_frame_slice 165), 166-- introducing an intermediate table since we want to calculate dur = ts_end - ts 167frame_boundary_base AS ( 168 SELECT 169 draw_frame.cuj_id, 170 draw_frame.utid, 171 draw_frame.vsync, 172 MIN(post_and_wait.ts) AS ts_expected, 173 MIN(draw_frame.ts) AS ts_draw_frame_start, 174 MIN(draw_frame.ts_prev_draw_frame_end) AS ts_prev_draw_frame_end, 175 MIN( 176 MAX( 177 MIN(post_and_wait.ts), 178 MIN(draw_frame.ts_prev_draw_frame_end)), 179 MIN(draw_frame.ts)) AS ts, 180 MAX(draw_frame.ts_end) AS ts_end 181 FROM draw_frame_ordered draw_frame 182 JOIN android_jank_cuj_do_frame_slice do_frame USING (cuj_id, vsync) 183 JOIN descendant_slice(do_frame.id) post_and_wait 184 WHERE post_and_wait.name = 'postAndWait' 185 GROUP BY draw_frame.cuj_id, draw_frame.utid, draw_frame.vsync 186) 187SELECT 188 *, 189 ts_end - ts AS dur 190FROM frame_boundary_base; 191 192-- Compute the CUJ boundary on the render thread from the frame boundaries. 193DROP TABLE IF EXISTS android_jank_cuj_render_thread_cuj_boundary; 194CREATE PERFETTO TABLE android_jank_cuj_render_thread_cuj_boundary AS 195SELECT 196 cuj_id, 197 utid, 198 MIN(ts) AS ts, 199 MAX(ts_end) AS ts_end, 200 MAX(ts_end) - MIN(ts) AS dur 201FROM android_jank_cuj_render_thread_frame_boundary 202GROUP BY cuj_id, utid; 203 204-- Compute the overall CUJ boundary (in the app process) based on the main 205-- thread CUJ boundaries and the actual timeline. 206DROP TABLE IF EXISTS android_jank_cuj_boundary; 207CREATE PERFETTO TABLE android_jank_cuj_boundary AS 208-- introducing an intermediate table since we want to calculate dur = ts_end - ts 209WITH boundary_base AS ( 210 SELECT 211 cuj_id, 212 cuj.upid, 213 main_thread_boundary.ts, 214 CASE 215 WHEN timeline_slice.ts IS NOT NULL 216 THEN MAX(timeline_slice.ts + timeline_slice.dur) 217 ELSE ( 218 SELECT MAX(MAX(ts_end), cuj.ts_end) 219 FROM android_jank_cuj_do_frame_slice do_frame 220 WHERE do_frame.cuj_id = cuj.cuj_id) 221 END AS ts_end 222 FROM android_jank_cuj_main_thread_cuj_boundary main_thread_boundary 223 JOIN android_jank_cuj cuj USING (cuj_id) 224 JOIN android_jank_cuj_vsync_boundary USING (cuj_id) 225 LEFT JOIN actual_frame_timeline_slice timeline_slice 226 ON cuj.upid = timeline_slice.upid 227 -- Timeline slices for this exact VSYNC might be missing (e.g. if the last 228 -- doFrame did not actually produce anything to draw). 229 -- In that case we compute the boundary based on the last doFrame and the 230 -- CUJ markers. 231 AND vsync_max = CAST(timeline_slice.name AS INTEGER) 232 GROUP BY cuj_id, cuj.upid, main_thread_boundary.ts 233) 234SELECT 235 *, 236 ts_end - ts AS dur 237FROM boundary_base; 238 239-- Similar to `android_jank_cuj_main_thread_frame_boundary`, calculates the frame boundaries 240-- based on when we *expected* the work to start and we use the end of the `composite` slice 241-- as the end of the work on the frame. 242DROP TABLE IF EXISTS android_jank_cuj_sf_main_thread_frame_boundary; 243CREATE PERFETTO TABLE android_jank_cuj_sf_main_thread_frame_boundary AS 244-- Join `commit` and `composite` slices using vsync IDs. 245-- We treat the two slices as a single "fake slice" that starts when `commit` starts, and ends 246-- when `composite` ends. 247WITH fake_commit_composite_slice AS ( 248 SELECT 249 cuj_id, 250 commit_slice.utid, 251 vsync, 252 commit_slice.ts, 253 composite_slice.ts_end, 254 composite_slice.ts_end - commit_slice.ts AS dur 255 FROM android_jank_cuj_sf_commit_slice commit_slice 256 JOIN android_jank_cuj_sf_composite_slice composite_slice USING(cuj_id, vsync) 257), 258-- As older builds will not have separate commit/composite slices for each frame, but instead 259-- a single `onMessageInvalidate`, we UNION ALL the two tables. Exactly one of them should 260-- have data. 261main_thread_slice AS ( 262 SELECT utid, cuj_id, vsync, ts, dur, ts_end FROM fake_commit_composite_slice 263 UNION ALL 264 SELECT utid, cuj_id, vsync, ts, dur, ts_end FROM android_jank_cuj_sf_on_message_invalidate_slice 265) 266SELECT 267 cuj_id, 268 utid, 269 vsync, 270 expected_timeline.ts, 271 main_thread_slice.ts AS ts_main_thread_start, 272 main_thread_slice.ts_end, 273 main_thread_slice.ts_end - expected_timeline.ts AS dur 274FROM expected_frame_timeline_slice expected_timeline 275JOIN android_jank_cuj_sf_process USING (upid) 276JOIN main_thread_slice 277 ON main_thread_slice.vsync = CAST(expected_timeline.name AS INTEGER); 278 279-- Compute the CUJ boundary on the main thread from the frame boundaries. 280DROP TABLE IF EXISTS android_jank_cuj_sf_main_thread_cuj_boundary; 281CREATE PERFETTO TABLE android_jank_cuj_sf_main_thread_cuj_boundary AS 282SELECT 283 cuj_id, 284 utid, 285 MIN(ts) AS ts, 286 MAX(ts_end) AS ts_end, 287 MAX(ts_end) - MIN(ts) AS dur 288FROM android_jank_cuj_sf_main_thread_frame_boundary 289GROUP BY cuj_id, utid; 290 291-- RenderEngine will only work on a frame if SF falls back to client composition. 292-- Because of that we do not calculate overall CUJ boundaries so there is no 293-- `android_jank_cuj_sf_render_engine_cuj_boundary` table created. 294-- RenderEngine frame boundaries are calculated based on `composeSurfaces` slice start 295-- on the main thread (this calls into RenderEngine), and when `REThreaded::drawLayers` 296-- ends. 297DROP TABLE IF EXISTS android_jank_cuj_sf_render_engine_frame_boundary; 298CREATE PERFETTO TABLE android_jank_cuj_sf_render_engine_frame_boundary AS 299SELECT 300 cuj_id, 301 utid, 302 vsync, 303 draw_layers.ts_compose_surfaces AS ts, 304 draw_layers.ts AS ts_draw_layers_start, 305 draw_layers.ts_end, 306 draw_layers.ts_end - draw_layers.ts_compose_surfaces AS dur 307FROM android_jank_cuj_sf_draw_layers_slice draw_layers; 308 309DROP TABLE IF EXISTS android_jank_cuj_sf_boundary; 310CREATE PERFETTO TABLE android_jank_cuj_sf_boundary AS 311SELECT cuj_id, ts, ts_end, dur 312FROM android_jank_cuj_sf_main_thread_cuj_boundary; 313