aboutsummaryrefslogtreecommitdiff
path: root/src/trace_processor/metrics/sql/chrome/rail_modes.sql
blob: d28f665e6fa1b9416c77595e503113ee37f750ba (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
--
-- 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;