• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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