-- -- 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. -- SELECT RUN_METRIC('chrome/chrome_processes.sql') AS suppress_query_output; SELECT RUN_METRIC('chrome/chrome_event_metadata.sql') AS suppress_query_output; -- Priority order for RAIL modes where response has the highest priority and -- idle has the lowest. DROP TABLE IF EXISTS rail_modes; CREATE TABLE rail_modes ( mode TEXT UNIQUE, ordering INT, short_name TEXT ); -- RAIL_MODE_IDLE is used when no frames are visible in the renderer and so this -- interprets that as background. -- RAIL_MODE_LOAD is for the time from a navigation until the first meaningful -- paint (assuming there are no user interactions). -- RAIL_MODE_RESPONSE is used when the main thread is dealing with a -- user-interaction (but not for instance for scrolls which may be handled by -- the compositor). -- RAIL_MODE_ANIMATION is used when none of the above apply. -- The enum in chrome is defined in: -- https://source.chromium.org/chromium/chromium/src/+/master:third_party/blink/renderer/platform/scheduler/public/rail_mode_observer.h INSERT INTO rail_modes VALUES ('RAIL_MODE_IDLE', 0, 'background'), ('RAIL_MODE_ANIMATION', 1, "animation"), ('RAIL_MODE_LOAD', 2, "load"), ('RAIL_MODE_RESPONSE', 3, "response"); -- Find the max ts + dur for every process DROP TABLE IF EXISTS max_ts_per_process; CREATE TABLE max_ts_per_process AS -- MAX(dur, 0) means unclosed slices just contribute their start time. SELECT upid, MAX(ts + MAX(dur, 0)) AS ts FROM ( SELECT upid, ts, dur FROM process_track t JOIN slice s WHERE s.track_id = t.id UNION ALL SELECT upid, ts, dur FROM thread_track t JOIN thread JOIN slice WHERE slice.track_id = t.id AND thread.utid = t.utid ) GROUP BY upid; -- View containing all Scheduler.RAILMode slices across all Chrome renderer -- processes. DROP VIEW IF EXISTS original_rail_mode_slices; CREATE VIEW original_rail_mode_slices AS SELECT slice.id, slice.ts, CASE -- Add 1 to the duration to ensure you cannot get a zero-sized RAIL mode -- slice, which can throw off the later queries. WHEN dur == -1 THEN max_ts_per_process.ts - slice.ts + 1 ELSE dur END AS dur, track_id, EXTRACT_ARG( slice.arg_set_id, "chrome_renderer_scheduler_state.rail_mode" ) AS rail_mode FROM max_ts_per_process, slice, process_track WHERE slice.name = "Scheduler.RAILMode" AND slice.track_id = process_track.id AND process_track.upid = max_ts_per_process.upid; -- Detect if the trace has an unrealistic length (10 minutes) that probably -- means some trace events have faulty timestamps and which could throw off any -- metrics that use the trace. DROP VIEW IF EXISTS trace_has_realistic_length; CREATE VIEW trace_has_realistic_length AS SELECT (end_ts - start_ts) < 1e9 * 60 * 10 AS value FROM trace_bounds; -- RAIL_MODE_LOAD seems to get stuck which makes it not very useful so remap it -- to RAIL_MODE_ANIMATION so it doesn't dominate the overall RAIL mode. DROP VIEW IF EXISTS rail_mode_slices; CREATE VIEW rail_mode_slices AS SELECT ts, dur, track_id, CASE WHEN rail_mode == "RAIL_MODE_LOAD" THEN "RAIL_MODE_ANIMATION" ELSE rail_mode END AS rail_mode FROM original_rail_mode_slices; -- View containing a collapsed view of rail_mode_slices where there is only one -- RAIL mode active at a given time. The mode is derived using the priority -- order in rail_modes. DROP VIEW IF EXISTS overall_rail_mode_slices; CREATE VIEW overall_rail_mode_slices AS SELECT s.ts, s.end_ts, rail_modes.short_name AS rail_mode, MAX(rail_modes.ordering) FROM ( SELECT ts, LEAD(ts, 1, (SELECT MAX(ts + dur) FROM rail_mode_slices)) OVER ( ORDER BY ts ) AS end_ts FROM ( SELECT DISTINCT ts FROM rail_mode_slices ) start_times ) s, rail_mode_slices r, rail_modes, trace_has_realistic_length WHERE ( ( s.ts >= r.ts AND s.ts < r.ts + r.dur ) OR ( s.end_ts > r.ts AND s.end_ts <= r.ts + r.dur ) ) AND r.rail_mode == rail_modes.mode AND trace_has_realistic_length.value GROUP BY s.ts; -- Contains the same data as overall_rail_mode_slices except adjacent slices -- with the same RAIL mode are combined. DROP TABLE IF EXISTS combined_overall_rail_slices; CREATE TABLE combined_overall_rail_slices AS SELECT ROW_NUMBER() OVER () AS id, ts, end_ts - ts AS dur, rail_mode FROM ( SELECT lag(l.end_ts, 1, FIRST) OVER ( ORDER BY l.ts ) AS ts, l.end_ts, l.rail_mode FROM ( SELECT ts, end_ts, rail_mode FROM overall_rail_mode_slices s WHERE NOT EXISTS ( SELECT NULL FROM overall_rail_mode_slices s2 WHERE s.rail_mode = s2.rail_mode AND s.end_ts = s2.ts ) ) AS l, ( SELECT min(ts) AS FIRST FROM overall_rail_mode_slices ) ); -- Now we have the RAIL Mode, use other trace events to create a modified RAIL -- mode that more accurately reflects what the browser/user are doing. -- First create slices for when there's no animation as indicated by a large gap -- between vsync events (since it's easier to find gaps than runs of adjacent -- vsyncs). -- Mark any large gaps between vsyncs. -- The value in "not_animating" is always 1. It's just there to be a non-NULL -- value so the later SPAN_JOIN can find the set-difference. DROP VIEW IF EXISTS not_animating_slices; CREATE VIEW not_animating_slices AS WITH const (vsync_padding, large_gap) AS ( SELECT -- Pad 50ms either side of a vsync 50000000, -- A gap of >200ms between the adjacent vsyncs is treated as a gap in -- animation. 200000000 ) SELECT ts + const.vsync_padding AS ts, gap_to_next_vsync - const.vsync_padding * 2 AS dur, 1 AS not_animating FROM const, (SELECT name, ts, lead(ts) OVER () - ts AS gap_to_next_vsync, dur FROM slice WHERE name = "VSync") WHERE gap_to_next_vsync > const.large_gap UNION -- Insert a slice between start_ts and the first vsync (or the end of the trace -- if there are none). SELECT ts, dur, 1 FROM (SELECT start_ts AS ts, COALESCE(( SELECT MIN(ts) FROM slice WHERE name = "VSync" ) - start_ts - const.vsync_padding, end_ts - start_ts ) AS dur FROM trace_bounds, const) WHERE dur > 0 UNION -- Insert a slice between the last vsync and end_ts SELECT last_vsync AS ts, end_ts - last_vsync AS dur, 1 FROM ( SELECT MAX(ts) + const.vsync_padding AS last_vsync FROM slice, const WHERE name = "VSync" ), trace_bounds WHERE last_vsync < end_ts; -- There are two types of InputLatency:: events: -- 1) Simple ones that begin at ts and end at ts+dur -- 2) Paired ones that begin with a "begin" slice and end at an "end" slice. -- -- Paired events are even trickier because we can't guarantee that the "begin" -- slice will even be in the trace and because it's possible for multiple begin -- slices to appear without an intervening end slice. -- Table of begin and end events along with the increment/decrement to be -- applied to the appropriate counter (one for each type of paired event). Final -- column dur_multiplier is used to find the timestamp to mark the event at in -- the equation event_ts = ts + dur * dur_multiplier. End events have -- dur_multiplier of 1, which makes their ts the end of the slice rather than -- the start. DROP TABLE IF EXISTS input_latency_begin_end_names; CREATE TABLE input_latency_begin_end_names ( full_name TEXT UNIQUE, prefix TEXT, scroll_increment INT, pinch_increment INT, touch_increment INT, fling_increment INT, pointer_increment INT, dur_multiplier INT ); INSERT OR IGNORE INTO input_latency_begin_end_names VALUES ("InputLatency::GestureScrollBegin", "InputLatency::GestureScroll", 1, 0, 0, 0, 0, 0), ("InputLatency::GestureScrollEnd", "InputLatency::GestureScroll", -1, 0, 0, 0, 0, 1), ("InputLatency::GesturePinchBegin", "InputLatency::GesturePinch", 0, 1, 0, 0, 0, 0), ("InputLatency::GesturePinchEnd", "InputLatency::GesturePinch", 0, -1, 0, 0, 0, 1), ("InputLatency::TouchStart", "InputLatency::Touch", 0, 0, 1, 0, 0, 0), ("InputLatency::TouchEnd", "InputLatency::Touch", 0, 0, -1, 0, 0, 1), ("InputLatency::GestureFlingStart", "InputLatency::GestureFling", 0, 0, 0, 1, 0, 0), ("InputLatency::GestureFlingCancel", "InputLatency::GestureFling", 0, 0, 0, -1, 0, 1), ("InputLatency::PointerDown", "InputLatency::Pointer", 0, 0, 0, 0, 1, 0), ("InputLatency::PointerUp", "InputLatency::Pointer", 0, 0, 0, 0, -1, 1), ("InputLatency::PointerCancel", "InputLatency::Pointer", 0, 0, 0, 0, -1, 1); -- Find all the slices that have split "begin" and "end" slices and maintain a -- running total for each type, where >0 means that type of input event is -- ongoing. DROP VIEW IF EXISTS input_begin_end_slices; CREATE VIEW input_begin_end_slices AS SELECT prefix, -- Mark the change at the start of "start" slices and the end of "end" slices. ts + dur * dur_multiplier AS ts, scroll_increment, pinch_increment, touch_increment, fling_increment, pointer_increment FROM slice JOIN input_latency_begin_end_names ON name = full_name ORDER BY ts; -- Combine all the paired input events to get an indication of when any paired -- input event is ongoing. DROP VIEW IF EXISTS unified_input_pair_increments; CREATE VIEW unified_input_pair_increments AS SELECT ts, scroll_increment + pinch_increment + touch_increment + fling_increment + pointer_increment AS increment FROM input_begin_end_slices; -- It's possible there's an end slice without a start slice (as it occurred -- before the trace started) which would result in (starts - ends) going -- negative at some point. So find an offset that shifts up all counts so the -- lowest values becomes zero. It's possible this could still do the wrong thing -- if there were start AND end slices that are outside the trace bounds, in -- which case it should count as covering the entire trace, but it's impossible -- to compensate for that without augmenting the trace events themselves. DROP VIEW IF EXISTS initial_paired_increment; CREATE VIEW initial_paired_increment AS SELECT ts, MIN(0, MIN(scroll_total)) + MIN(0, MIN(pinch_total)) + MIN(0, MIN(touch_total)) + MIN(0, MIN(fling_total)) + MIN(0, MIN(pointer_total)) AS offset FROM ( SELECT ts, SUM(scroll_increment) OVER(ROWS UNBOUNDED PRECEDING) AS scroll_total, SUM(pinch_increment) OVER(ROWS UNBOUNDED PRECEDING) AS pinch_total, SUM(touch_increment) OVER(ROWS UNBOUNDED PRECEDING) AS touch_total, SUM(fling_increment) OVER(ROWS UNBOUNDED PRECEDING) AS fling_total, SUM(pointer_increment) OVER(ROWS UNBOUNDED PRECEDING) AS pointer_total FROM input_begin_end_slices ); -- Now find all the simple input slices that fully enclose the input they're -- marking (i.e. not the start or end of a pair). DROP VIEW IF EXISTS simple_input_slices; CREATE VIEW simple_input_slices AS SELECT id, name, ts, dur FROM slice s WHERE name GLOB "InputLatency::*" AND NOT EXISTS ( SELECT 1 FROM slice JOIN input_latency_begin_end_names WHERE s.name == full_name ); -- Turn the simple input slices into +1s and -1s at the start and end of each -- slice. DROP VIEW IF EXISTS simple_input_increments; CREATE VIEW simple_input_increments AS SELECT ts, 1 AS increment FROM simple_input_slices UNION ALL SELECT ts + dur, -1 FROM simple_input_slices ORDER BY ts; -- Combine simple and paired inputs into one, summing all the increments at a -- given ts. DROP VIEW IF EXISTS all_input_increments; CREATE VIEW all_input_increments AS SELECT ts, SUM(increment) AS increment FROM ( SELECT * FROM simple_input_increments UNION ALL SELECT * FROM unified_input_pair_increments ORDER BY ts ) GROUP BY ts; -- Now calculate the cumulative sum of the increments as each ts, giving the -- total number of outstanding input events at a given time. DROP VIEW IF EXISTS all_input_totals; CREATE VIEW all_input_totals AS SELECT ts, SUM(increment) OVER(ROWS UNBOUNDED PRECEDING) > 0 AS input_total FROM all_input_increments; -- Now find the transitions from and to 0 and use that to create slices where -- input events were occurring. The input_active column always contains 1, but -- is there so that the SPAN_JOIN_LEFT can put NULL in it for RAIL Mode slices -- that do not have corresponding input events. DROP VIEW IF EXISTS all_input_slices; CREATE VIEW all_input_slices AS SELECT ts, dur, input_active FROM ( SELECT ts, lead(ts, 1, end_ts) OVER() - ts AS dur, input_active FROM trace_bounds, ( SELECT ts, input_total > 0 AS input_active FROM ( SELECT ts, input_total, lag(input_total) OVER() AS prev_input_total FROM all_input_totals ) WHERE (input_total > 0 <> prev_input_total > 0) OR prev_input_total IS NULL ) ) WHERE input_active > 0; -- Since the scheduler defaults to animation when none of the other RAIL modes -- apply, animation overestimates the amount of time that actual animation is -- occurring. -- So instead we try to divide up animation in other buckets based on other -- trace events. DROP VIEW IF EXISTS rail_mode_animation_slices; CREATE VIEW rail_mode_animation_slices AS SELECT * FROM combined_overall_rail_slices WHERE rail_mode = "animation"; -- Left-join rail mode animation slices with all_input_slices to find all -- "animation" slices that should actually be labelled "response". DROP TABLE IF EXISTS rail_mode_join_inputs; CREATE VIRTUAL TABLE rail_mode_join_inputs USING SPAN_LEFT_JOIN(rail_mode_animation_slices, all_input_slices); -- Left-join rail mode animation slices with not_animating_slices which is -- based on the gaps between vsync events. DROP TABLE IF EXISTS rail_mode_join_inputs_join_animation; CREATE VIRTUAL TABLE rail_mode_join_inputs_join_animation USING SPAN_LEFT_JOIN(rail_mode_join_inputs, not_animating_slices); DROP VIEW IF EXISTS has_modified_rail_slices; CREATE VIEW has_modified_rail_slices AS SELECT ( SELECT value FROM chrome_event_metadata WHERE name == "os-name" ) == "Android" AS value; -- Mapping to allow CamelCased names to be produced from the modified rail -- modes. DROP TABLE IF EXISTS modified_rail_mode_prettier; CREATE TABLE modified_rail_mode_prettier ( orig_name TEXT UNIQUE, pretty_name TEXT ); INSERT INTO modified_rail_mode_prettier VALUES ("background", "Background"), ("foreground_idle", "ForegroundIdle"), ("animation", "Animation"), ("load", "Load"), ("response", "Response"); -- When the RAIL mode is animation, use input/vsync data to conditionally change -- the mode to response or foreground_idle. DROP VIEW IF EXISTS unmerged_modified_rail_slices; CREATE VIEW unmerged_modified_rail_slices AS SELECT ROW_NUMBER() OVER () AS id, ts, dur, mode FROM ( SELECT ts, dur, CASE WHEN input_active IS NOT NULL THEN "response" WHEN not_animating IS NULL THEN "animation" ELSE "foreground_idle" END AS mode FROM rail_mode_join_inputs_join_animation UNION SELECT ts, dur, rail_mode AS mode FROM combined_overall_rail_slices WHERE rail_mode <> "animation" ) -- Since VSync events are only emitted on Android (and the concept of a -- unified RAIL mode only makes sense if there's just a single Chrome window), -- don't output anything on other platforms. This will result in all the power -- and cpu time tables being empty rather than containing bogus results. WHERE ( SELECT value FROM has_modified_rail_slices ); -- The previous query creating unmerged_modified_rail_slices, can create -- adjacent slices with the same mode. This merges them together as well as -- adding a unique id to each slice. Rather than directly merging slices -- together, this instead looks for all the transitions and uses this to -- reconstruct the slices that should occur between them. DROP TABLE IF EXISTS modified_rail_slices; CREATE TABLE modified_rail_slices AS WITH const (end_ts) AS (SELECT ts + dur FROM unmerged_modified_rail_slices ORDER BY ts DESC LIMIT 1) SELECT ROW_NUMBER() OVER () AS id, lag(next_ts) OVER() AS ts, ts + dur - lag(next_ts) OVER() AS dur, mode AS mode FROM ( -- For each row in the original table, create a new row with the information -- from the following row, since you can't use lag/lead in WHERE clause. -- -- Transition row at the beginning. "mode" is invalid, so a transition will -- always be recorded. SELECT * FROM (SELECT 0 AS ts, ts AS dur, "" AS mode, ts AS next_ts, dur AS next_dur, mode AS next_mode FROM unmerged_modified_rail_slices LIMIT 1 ) UNION ALL SELECT ts, dur, mode, lead(ts, 1, end_ts) OVER() AS next_ts, lead(dur) OVER() AS next_dur, lead(mode) OVER() AS next_mode FROM unmerged_modified_rail_slices, const UNION ALL -- Transition row at the end. "next_mode" is invalid, so a transition will -- always be recorded. SELECT * FROM (SELECT ts + dur AS ts, 0 AS dur, mode, ts + dur AS next_ts, 0, "" AS next_mode FROM unmerged_modified_rail_slices ORDER BY ts DESC LIMIT 1 ) ) WHERE mode <> next_mode -- Retrieve all but the first row. LIMIT -1 OFFSET 1;