blob: 860c5a51f87311419e003ba19ed74499dab8b2f5 [file] [log] [blame]
--
-- Copyright 2020 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.
--
-- A collection of metrics related to GestureScrollUpdate events.
--
-- We define a GestureScrollUpdate to be janky if comparing forwards or
-- backwards (ignoring coalesced updates) a given GestureScrollUpdate exceeds
-- the duration of its predecessor or successor by 50% of a vsync interval
-- (defaulted to 60 FPS).
--
-- WARNING: This metric should not be used as a source of truth. It is under
-- active development and the values & meaning might change without
-- notice.
-- Get all chrome processes and threads tables set up.
SELECT RUN_METRIC('chrome/chrome_processes.sql');
-- When working on GestureScrollUpdate events we need to ensure we have all the
-- events from the browser, renderer, and GPU processes. This query isn't quite
-- perfect. In system tracing we could have 3 browser processes all in the
-- background and this would match, but for now its the best we can do (renderer
-- and GPU names on android are quite complicated, but this should filter 99% (
-- citation needed) of what we want.
--
-- See b/151077536 for historical context.
DROP VIEW IF EXISTS sufficient_chrome_processes;
CREATE VIEW sufficient_chrome_processes AS
SELECT
CASE WHEN (
SELECT COUNT(*) FROM chrome_process) = 0
THEN
FALSE
ELSE (
SELECT COUNT(*) >= 3 FROM (
SELECT name FROM chrome_process
WHERE
name LIKE "Browser" OR
name LIKE "Renderer" OR
name LIKE "Gpu" OR
name LIKE 'com.android.chrome%' OR
name LIKE 'com.chrome.beta%' OR
name LIKE 'com.chrome.dev%' OR
name LIKE 'com.chrome.canary%' OR
name LIKE 'com.google.android.apps.chrome%' OR
name LIKE 'org.chromium.chrome%'
GROUP BY name
)) END AS have_enough_chrome_processes;
-- A simple table that checks the time between VSync (this can be used to
-- determine if we're scrolling at 90 FPS or 60 FPS.
--
-- Note: In traces without the "Java" category there will be no VSync
-- TraceEvents and this table will be empty.
--
-- Note: Must be a TABLE because it uses a window function which can behave
-- strangely in views.
DROP TABLE IF EXISTS vsync_intervals;
CREATE TABLE vsync_intervals AS
SELECT
slice_id,
ts,
dur,
track_id,
LEAD(ts) OVER(PARTITION BY track_id ORDER BY ts) - ts AS time_to_next_vsync
FROM slice
WHERE name = "VSync"
ORDER BY track_id, ts;
-- Get all the GestureScrollBegin and GestureScrollEnd events. We take their
-- IDs to group them together into scrolls later and the timestamp and duration
-- to compute the duration of the scroll.
DROP VIEW IF EXISTS scroll_begin_and_end;
CREATE VIEW scroll_begin_and_end AS
SELECT
slice.name,
slice.id,
slice.ts,
slice.dur,
slice.track_id,
EXTRACT_ARG(arg_set_id, 'chrome_latency_info.gesture_scroll_id')
AS gesture_scroll_id,
EXTRACT_ARG(arg_set_id, "chrome_latency_info.trace_id") AS trace_id
FROM
slice
WHERE
slice.name IN (
'InputLatency::GestureScrollBegin',
'InputLatency::GestureScrollEnd'
)
ORDER BY ts;
-- Now we take the GestureScrollBegin and the GestureScrollEnd events and join
-- the information into a single row per scroll. We also compute the average
-- Vysnc interval of the scroll (hopefully this would be either 60 FPS for the
-- whole scroll or 90 FPS but that isn't always the case). If the trace doesn't
-- contain the VSync TraceEvent we just fall back on assuming its 60 FPS (this
-- is the 1.6e+7 in the COALESCE which corresponds to 16 ms or 60 FPS).
DROP VIEW IF EXISTS joined_scroll_begin_and_end;
CREATE VIEW joined_scroll_begin_and_end AS
SELECT
begin.id AS begin_id,
begin.ts AS begin_ts,
begin.dur AS begin_dur,
begin.track_id AS begin_track_id,
begin.trace_id AS begin_trace_id,
COALESCE(begin.gesture_scroll_id, begin.trace_id)
AS begin_gesture_scroll_id,
end.ts AS end_ts,
end.ts + end.dur AS end_ts_and_dur,
end.trace_id AS end_trace_id,
COALESCE((
SELECT
CAST(AVG(time_to_next_vsync) AS FLOAT)
FROM vsync_intervals in_query
WHERE
time_to_next_vsync IS NOT NULL AND
in_query.ts > begin.ts AND
in_query.ts < end.ts
), 1e+9 / 60) AS avg_vsync_interval
FROM scroll_begin_and_end begin JOIN scroll_begin_and_end end ON
begin.trace_id < end.trace_id AND
begin.name = 'InputLatency::GestureScrollBegin' AND
end.name = 'InputLatency::GestureScrollEnd' AND (
(
begin.gesture_scroll_id IS NULL AND
end.trace_id = (
SELECT MIN(trace_id)
FROM scroll_begin_and_end in_query
WHERE
name = 'InputLatency::GestureScrollEnd' AND
in_query.trace_id > begin.trace_id
)
) OR
end.gesture_scroll_id = begin.gesture_scroll_id
)
ORDER BY begin.ts;
-- Get the GestureScrollUpdate events by name ordered by the
-- |gesture_scroll_id|, and timestamp. Then compute the number of frames (
-- relative to vsync interval) that each event took. 1.6e+7 is 16 ms in
-- nanoseconds and is used in case there are no VSync events to default to 60
-- fps. We join each GestureScrollUpdate event to the information about it'
-- begin and end events for easy computation later.
--
-- We remove updates with |dur| == -1 because this means we have no end event
-- and can't reasonably determine what it should be. We have separate tracking
-- to ensure this only happens at the end of the trace where its expected.
--
-- Note: Must be a TABLE because it uses a window function which can behave
-- strangely in views.
DROP TABLE IF EXISTS gesture_scroll_update;
CREATE TABLE gesture_scroll_update AS
SELECT
ROW_NUMBER() OVER (
ORDER BY gesture_scroll_id ASC, ts ASC) AS row_number,
begin_id,
begin_ts,
begin_dur,
begin_track_id,
begin_trace_id,
COALESCE(gesture_scroll_id, begin_trace_id) AS gesture_scroll_id,
CASE WHEN
end_ts_and_dur > ts + dur THEN
end_ts_and_dur
ELSE
ts + dur
END AS maybe_scroll_end,
id,
ts,
dur,
track_id,
trace_id,
dur/avg_vsync_interval AS scroll_frames_exact
FROM joined_scroll_begin_and_end begin_and_end JOIN (
SELECT
EXTRACT_ARG(arg_set_id, "chrome_latency_info.trace_id") AS trace_id,
EXTRACT_ARG(arg_set_id, 'chrome_latency_info.gesture_scroll_id')
AS gesture_scroll_id,
*
FROM
slice JOIN track ON slice.track_id = track.id
WHERE
slice.name = 'InputLatency::GestureScrollUpdate' AND
slice.dur != -1 AND
NOT COALESCE(
EXTRACT_ARG(arg_set_id, "chrome_latency_info.is_coalesced"),
TRUE)
) scroll_update ON
scroll_update.ts <= begin_and_end.end_ts AND
scroll_update.ts >= begin_and_end.begin_ts AND
scroll_update.trace_id > begin_and_end.begin_trace_id AND
scroll_update.trace_id < begin_and_end.end_trace_id AND (
scroll_update.gesture_scroll_id IS NULL OR
scroll_update.gesture_scroll_id = begin_and_end.begin_gesture_scroll_id
);
-- This takes the GestureScrollUpdate events and joins it to the previous
-- GestureScrollUpdate event (previous row and NULL if there isn't one) and the
-- next GestureScrollUpdate event (next row and again NULL if there isn't one).
-- Then we compute the duration of the event (relative to fps) and see if it
-- increased by more then 0.5 (which is 1/2 of 16 ms at 60 fps, and so on).
--
-- We only compare a GestureScrollUpdate event to another event within the same
-- scroll (gesture_scroll_id == prev/next gesture_scroll_id). This controls
-- somewhat for variability of scrolls.
--
-- Note: Must be a TABLE because it uses a window function which can behave
-- strangely in views.
DROP TABLE IF EXISTS scroll_jank_maybe_null_prev_and_next;
CREATE TABLE scroll_jank_maybe_null_prev_and_next AS
SELECT
currprev.*,
CASE WHEN
currprev.gesture_scroll_id != prev_gesture_scroll_id OR
prev_ts IS NULL OR
prev_ts < currprev.begin_ts OR
prev_ts > currprev.maybe_scroll_end
THEN
FALSE
ELSE
currprev.scroll_frames_exact > prev_scroll_frames_exact + 0.5
END AS prev_jank,
CASE WHEN
currprev.gesture_scroll_id != next.gesture_scroll_id OR
next.ts IS NULL OR
next.ts < currprev.begin_ts OR
next.ts > currprev.maybe_scroll_end
THEN
FALSE
ELSE
currprev.scroll_frames_exact > next.scroll_frames_exact + 0.5
END AS next_jank,
next.scroll_frames_exact AS next_scroll_frames_exact
FROM (
SELECT
curr.*,
curr.maybe_scroll_end - curr.begin_ts AS scroll_dur,
prev.ts AS prev_ts,
prev.gesture_scroll_id AS prev_gesture_scroll_id,
prev.scroll_frames_exact AS prev_scroll_frames_exact
FROM
gesture_scroll_update curr LEFT JOIN
gesture_scroll_update prev ON prev.row_number + 1 = curr.row_number
) currprev LEFT JOIN
gesture_scroll_update next ON currprev.row_number + 1 = next.row_number
ORDER BY currprev.gesture_scroll_id ASC, currprev.ts ASC;
-- This just uses prev_jank and next_jank to see if each GestureScrollUpdate
-- event is a jank.
DROP VIEW IF EXISTS scroll_jank;
CREATE VIEW scroll_jank AS
SELECT
id AS slice_id,
(next_jank IS NOT NULL AND next_jank) OR
(prev_jank IS NOT NULL AND prev_jank)
AS jank,
*
FROM scroll_jank_maybe_null_prev_and_next
ORDER BY gesture_scroll_id ASC, ts ASC;
DROP VIEW IF EXISTS scroll_jank_ms;
DROP VIEW IF EXISTS scroll_jank_output;
CREATE VIEW scroll_jank_output AS
SELECT
ScrollJank(
'scroll_jank_percentage', (
SELECT
(
SUM(CASE WHEN jank THEN dur ELSE 0 END)/CAST(SUM(dur) AS REAL)
) * 100.0
FROM scroll_jank
),
'scroll_ms', (
SELECT
CAST(SUM(scroll_dur)/1e6 AS REAL)
FROM (
SELECT
MAX(scroll_dur) AS scroll_dur
FROM scroll_jank
GROUP BY gesture_scroll_id
)
),
'scroll_processing_ms', CAST(SUM(dur)/1e6 AS REAL),
'scroll_jank_processing_ms', (
SELECT CAST(SUM(dur)/1e6 AS REAL) FROM scroll_jank WHERE jank
),
'num_scroll_update_count', COUNT(*),
'num_scroll_update_jank_count', SUM(jank)
)
FROM scroll_jank;