| -- |
| -- Copyright 2022 The Android Open Source Project |
| -- |
| -- Licensed under the Apache License, Version 2.0 (the "License"); |
| -- you may not use this file except in compliance with the License. |
| -- You may obtain a copy of the License at |
| -- |
| -- https://www.apache.org/licenses/LICENSE-2.0 |
| -- |
| -- Unless required by applicable law or agreed to in writing, software |
| -- distributed under the License is distributed on an "AS IS" BASIS, |
| -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| -- See the License for the specific language governing permissions and |
| -- limitations under the License. |
| |
| -- Stores the min and max vsync IDs for each of the CUJs which are extracted |
| -- from the CUJ markers. For backward compatibility (In case the markers don't |
| -- exist), We calculate that by extracting the vsync ID from the |
| -- `Choreographer#doFrame` slices that are within the CUJ markers. |
| DROP TABLE IF EXISTS android_jank_cuj_vsync_boundary; |
| CREATE TABLE android_jank_cuj_vsync_boundary AS |
| SELECT |
| cuj.cuj_id, |
| cuj.upid, -- also store upid to simplify further queries |
| cuj.layer_id, -- also store layer_id to simplify further queries |
| IFNULL(cuj.begin_vsync, MIN(vsync)) AS vsync_min, |
| IFNULL(cuj.end_vsync, MAX(vsync)) AS vsync_max |
| FROM android_jank_cuj cuj |
| JOIN android_jank_cuj_do_frame_slice USING (cuj_id) |
| GROUP BY cuj.cuj_id, cuj.upid, cuj.layer_id; |
| |
| -- Similarly, extract the min/max vsync for the SF from |
| -- commit/compose/onMessageInvalidate slices on its main thread. |
| DROP TABLE IF EXISTS android_jank_cuj_sf_vsync_boundary; |
| CREATE TABLE android_jank_cuj_sf_vsync_boundary AS |
| SELECT |
| cuj_id, |
| MIN(vsync) AS vsync_min, |
| MAX(vsync) AS vsync_max |
| FROM android_jank_cuj_sf_root_slice |
| GROUP BY cuj_id; |
| |
| -- Calculates the frame boundaries based on when we *expected* the work on |
| -- a given frame to start and when the previous frame finished - not when |
| -- Choreographer#doFrame actually started. |
| -- We use MAX(expected time, previous frame ended) as the expected start time. |
| -- Shifting the start time based on the previous frame is done to avoid having |
| -- overlapping frame boundaries which would make further analysis more |
| -- complicated. |
| -- We also separately store the previous frame ts_end. |
| -- This will allow us to look into cases where frame start was delayed due to |
| -- some other work occupying the main thread (e.g. non-drawing callbacks or |
| -- the previous frame taking much longer than expected). |
| DROP TABLE IF EXISTS android_jank_cuj_main_thread_frame_boundary; |
| CREATE TABLE android_jank_cuj_main_thread_frame_boundary AS |
| -- intermediate table that discards unfinished slices and parses vsync as int. |
| WITH expected_timeline AS ( |
| SELECT *, CAST(name AS INTEGER) AS vsync |
| FROM expected_frame_timeline_slice |
| WHERE dur > 0 |
| ), |
| -- Matches vsyncs in CUJ to expected frame timeline data. |
| -- We also store the actual timeline data to handle a few edge cases where due to clock drift the frame timeline is shifted |
| cuj_frame_timeline AS ( |
| SELECT |
| cuj_id, |
| vsync, |
| e.ts AS ts_expected, |
| -- In cases where we are drawing multiple layers, there will be one |
| -- expected frame timeline slice, but multiple actual frame timeline slices. |
| -- As a simplification we just take here the min(ts) and max(ts_end) of |
| -- the actual frame timeline slices. |
| MIN(a.ts) AS ts_actual_min, |
| MAX(a.ts + a.dur) AS ts_end_actual_max |
| FROM android_jank_cuj_vsync_boundary vsync_boundary |
| JOIN expected_timeline e |
| ON e.upid = vsync_boundary.upid |
| AND e.vsync >= vsync_min |
| AND e.vsync <= vsync_max |
| JOIN actual_frame_timeline_slice a |
| ON e.upid = a.upid |
| AND e.name = a.name |
| GROUP BY cuj_id, e.vsync, e.ts |
| ), |
| -- Orders do_frame slices by vsync to calculate the ts_end of the previous frame |
| -- android_jank_cuj_do_frame_slice only contains frames within the CUJ so |
| -- the ts_prev_do_frame_end is always missing for the very first frame |
| -- For now this is acceptable as it keeps the query simpler. |
| do_frame_ordered AS ( |
| SELECT |
| *, |
| -- ts_end of the previous do_frame, or -1 if no previous do_frame found |
| COALESCE(LAG(ts_end) OVER (PARTITION BY cuj_id ORDER BY vsync ASC), -1) AS ts_prev_do_frame_end |
| FROM android_jank_cuj_do_frame_slice |
| ), |
| -- introducing an intermediate table since we want to calculate dur = ts_end - ts |
| frame_boundary_base AS ( |
| SELECT |
| do_frame.cuj_id, |
| do_frame.utid, |
| do_frame.vsync, |
| do_frame.ts AS ts_do_frame_start, |
| do_frame.ts_end, |
| do_frame.ts_prev_do_frame_end, |
| timeline.ts_expected, |
| CASE |
| WHEN timeline.ts_expected IS NULL |
| THEN do_frame.ts |
| ELSE MAX(do_frame.ts_prev_do_frame_end, timeline.ts_expected) |
| END AS ts |
| FROM do_frame_ordered do_frame |
| LEFT JOIN cuj_frame_timeline timeline |
| ON timeline.cuj_id = do_frame.cuj_id |
| AND do_frame.vsync = timeline.vsync |
| -- There are a few special cases we have to handle: |
| -- *) In rare cases there is a clock drift after device suspends |
| -- This may cause the actual/expected timeline to be misaligned with the rest |
| -- of the trace for a short period. |
| -- Do not use the timelines if it seems that this happened. |
| -- *) Actual timeline start time might also be reported slightly after doFrame |
| -- starts. We allow it to start up to 1ms later. |
| -- *) If the frame is significantly (~100s of ms) over the deadline, |
| -- expected timeline data will be dropped in SF and never recorded. In that case |
| -- the actual timeline will only report the end ts correctly. If this happens |
| -- fall back to calculating the boundaries based on doFrame slices. Ideally we |
| -- would prefer to infer the intended start time of the frame instead. |
| AND do_frame.ts >= timeline.ts_actual_min - 1e6 AND do_frame.ts <= timeline.ts_end_actual_max |
| ) |
| SELECT |
| *, |
| ts_end - ts AS dur |
| FROM frame_boundary_base; |
| |
| |
| -- Compute the CUJ boundary on the main thread from the frame boundaries. |
| DROP TABLE IF EXISTS android_jank_cuj_main_thread_cuj_boundary; |
| CREATE TABLE android_jank_cuj_main_thread_cuj_boundary AS |
| SELECT |
| cuj_id, |
| utid, |
| MIN(ts) AS ts, |
| MAX(ts_end) AS ts_end, |
| MAX(ts_end) - MIN(ts) AS dur |
| FROM android_jank_cuj_main_thread_frame_boundary |
| GROUP BY cuj_id, utid; |
| |
| -- Similar to `android_jank_cuj_main_thread_frame_boundary` but for the render |
| -- thread the expected start time is the time of the first `postAndWait` slice |
| -- on the main thread. |
| -- The query is slightly simpler because we don't have to handle the clock drift |
| -- and data loss like with the frame timeline. |
| -- One difference vs main thread is that there might be multiple DrawFrames |
| -- slices for a single vsync - this happens when we are drawing multiple layers |
| -- (e.g. status bar & notifications). |
| DROP TABLE IF EXISTS android_jank_cuj_render_thread_frame_boundary; |
| CREATE TABLE android_jank_cuj_render_thread_frame_boundary AS |
| -- see do_frame_ordered above |
| -- we also order by `ts` to handle multiple DrawFrames for a single vsync |
| WITH draw_frame_ordered AS ( |
| SELECT |
| *, |
| -- ts_end of the previous draw frame, or -1 if no previous draw frame found |
| COALESCE(LAG(ts_end) OVER (PARTITION BY cuj_id ORDER BY vsync ASC, ts ASC), -1) AS ts_prev_draw_frame_end |
| FROM android_jank_cuj_draw_frame_slice |
| ), |
| -- introducing an intermediate table since we want to calculate dur = ts_end - ts |
| frame_boundary_base AS ( |
| SELECT |
| draw_frame.cuj_id, |
| draw_frame.utid, |
| draw_frame.vsync, |
| MIN(post_and_wait.ts) AS ts_expected, |
| MIN(draw_frame.ts) AS ts_draw_frame_start, |
| MIN(draw_frame.ts_prev_draw_frame_end) AS ts_prev_draw_frame_end, |
| MIN( |
| MAX( |
| MIN(post_and_wait.ts), |
| MIN(draw_frame.ts_prev_draw_frame_end)), |
| MIN(draw_frame.ts)) AS ts, |
| MAX(draw_frame.ts_end) AS ts_end |
| FROM draw_frame_ordered draw_frame |
| JOIN android_jank_cuj_do_frame_slice do_frame USING (cuj_id, vsync) |
| JOIN descendant_slice(do_frame.id) post_and_wait |
| WHERE post_and_wait.name = 'postAndWait' |
| GROUP BY draw_frame.cuj_id, draw_frame.utid, draw_frame.vsync |
| ) |
| SELECT |
| *, |
| ts_end - ts AS dur |
| FROM frame_boundary_base; |
| |
| -- Compute the CUJ boundary on the render thread from the frame boundaries. |
| DROP TABLE IF EXISTS android_jank_cuj_render_thread_cuj_boundary; |
| CREATE TABLE android_jank_cuj_render_thread_cuj_boundary AS |
| SELECT |
| cuj_id, |
| utid, |
| MIN(ts) AS ts, |
| MAX(ts_end) AS ts_end, |
| MAX(ts_end) - MIN(ts) AS dur |
| FROM android_jank_cuj_render_thread_frame_boundary |
| GROUP BY cuj_id, utid; |
| |
| -- Compute the overall CUJ boundary (in the app process) based on the main |
| -- thread CUJ boundaries and the actual timeline. |
| DROP TABLE IF EXISTS android_jank_cuj_boundary; |
| CREATE TABLE android_jank_cuj_boundary AS |
| -- introducing an intermediate table since we want to calculate dur = ts_end - ts |
| WITH boundary_base AS ( |
| SELECT |
| cuj_id, |
| cuj.upid, |
| main_thread_boundary.ts, |
| CASE |
| WHEN timeline_slice.ts IS NOT NULL |
| THEN MAX(timeline_slice.ts + timeline_slice.dur) |
| ELSE ( |
| SELECT MAX(MAX(ts_end), cuj.ts_end) |
| FROM android_jank_cuj_do_frame_slice do_frame |
| WHERE do_frame.cuj_id = cuj.cuj_id) |
| END AS ts_end |
| FROM android_jank_cuj_main_thread_cuj_boundary main_thread_boundary |
| JOIN android_jank_cuj cuj USING (cuj_id) |
| JOIN android_jank_cuj_vsync_boundary USING (cuj_id) |
| LEFT JOIN actual_frame_timeline_slice timeline_slice |
| ON cuj.upid = timeline_slice.upid |
| -- Timeline slices for this exact VSYNC might be missing (e.g. if the last |
| -- doFrame did not actually produce anything to draw). |
| -- In that case we compute the boundary based on the last doFrame and the |
| -- CUJ markers. |
| AND vsync_max = CAST(timeline_slice.name AS INTEGER) |
| GROUP BY cuj_id, cuj.upid, main_thread_boundary.ts |
| ) |
| SELECT |
| *, |
| ts_end - ts AS dur |
| FROM boundary_base; |
| |
| -- Similar to `android_jank_cuj_main_thread_frame_boundary`, calculates the frame boundaries |
| -- based on when we *expected* the work to start and we use the end of the `composite` slice |
| -- as the end of the work on the frame. |
| DROP TABLE IF EXISTS android_jank_cuj_sf_main_thread_frame_boundary; |
| CREATE TABLE android_jank_cuj_sf_main_thread_frame_boundary AS |
| -- Join `commit` and `composite` slices using vsync IDs. |
| -- We treat the two slices as a single "fake slice" that starts when `commit` starts, and ends |
| -- when `composite` ends. |
| WITH fake_commit_composite_slice AS ( |
| SELECT |
| cuj_id, |
| commit_slice.utid, |
| vsync, |
| commit_slice.ts, |
| composite_slice.ts_end, |
| composite_slice.ts_end - commit_slice.ts AS dur |
| FROM android_jank_cuj_sf_commit_slice commit_slice |
| JOIN android_jank_cuj_sf_composite_slice composite_slice USING(cuj_id, vsync) |
| ), |
| -- As older builds will not have separate commit/composite slices for each frame, but instead |
| -- a single `onMessageInvalidate`, we UNION ALL the two tables. Exactly one of them should |
| -- have data. |
| main_thread_slice AS ( |
| SELECT utid, cuj_id, vsync, ts, dur, ts_end FROM fake_commit_composite_slice |
| UNION ALL |
| SELECT utid, cuj_id, vsync, ts, dur, ts_end FROM android_jank_cuj_sf_on_message_invalidate_slice |
| ) |
| SELECT |
| cuj_id, |
| utid, |
| vsync, |
| expected_timeline.ts, |
| main_thread_slice.ts AS ts_main_thread_start, |
| main_thread_slice.ts_end, |
| main_thread_slice.ts_end - expected_timeline.ts AS dur |
| FROM expected_frame_timeline_slice expected_timeline |
| JOIN android_jank_cuj_sf_process USING (upid) |
| JOIN main_thread_slice |
| ON main_thread_slice.vsync = CAST(expected_timeline.name AS INTEGER); |
| |
| -- Compute the CUJ boundary on the main thread from the frame boundaries. |
| DROP TABLE IF EXISTS android_jank_cuj_sf_main_thread_cuj_boundary; |
| CREATE TABLE android_jank_cuj_sf_main_thread_cuj_boundary AS |
| SELECT |
| cuj_id, |
| utid, |
| MIN(ts) AS ts, |
| MAX(ts_end) AS ts_end, |
| MAX(ts_end) - MIN(ts) AS dur |
| FROM android_jank_cuj_sf_main_thread_frame_boundary |
| GROUP BY cuj_id, utid; |
| |
| -- RenderEngine will only work on a frame if SF falls back to client composition. |
| -- Because of that we do not calculate overall CUJ boundaries so there is no |
| -- `android_jank_cuj_sf_render_engine_cuj_boundary` table created. |
| -- RenderEngine frame boundaries are calculated based on `composeSurfaces` slice start |
| -- on the main thread (this calls into RenderEngine), and when `REThreaded::drawLayers` |
| -- ends. |
| DROP TABLE IF EXISTS android_jank_cuj_sf_render_engine_frame_boundary; |
| CREATE TABLE android_jank_cuj_sf_render_engine_frame_boundary AS |
| SELECT |
| cuj_id, |
| utid, |
| vsync, |
| draw_layers.ts_compose_surfaces AS ts, |
| draw_layers.ts AS ts_draw_layers_start, |
| draw_layers.ts_end, |
| draw_layers.ts_end - draw_layers.ts_compose_surfaces AS dur |
| FROM android_jank_cuj_sf_draw_layers_slice draw_layers; |
| |
| DROP TABLE IF EXISTS android_jank_cuj_sf_boundary; |
| CREATE TABLE android_jank_cuj_sf_boundary AS |
| SELECT cuj_id, ts, ts_end, dur |
| FROM android_jank_cuj_sf_main_thread_cuj_boundary; |