-- Copyright 2023 The Chromium Authors
-- Use of this source code is governed by a BSD-style license that can be
-- found in the LICENSE file.

INCLUDE PERFETTO MODULE chrome.chrome_scrolls;

INCLUDE PERFETTO MODULE chrome.scroll_jank.scroll_jank_v3;
INCLUDE PERFETTO MODULE chrome.event_latency;

-- Selects EventLatency slices that correspond with janks in a scroll. This is
-- based on the V3 version of scroll jank metrics.
CREATE PERFETTO TABLE chrome_janky_event_latencies_v3 (
  -- The slice id.
  id LONG,
  -- The start timestamp of the slice.
  ts TIMESTAMP,
  -- The duration of the slice.
  dur DURATION,
  -- The track_id for the slice.
  track_id LONG,
  -- The name of the slice (EventLatency).
  name STRING,
  -- The stage of EventLatency that the caused the jank.
  cause_of_jank STRING,
  -- The stage of cause_of_jank that caused the jank.
  sub_cause_of_jank STRING,
  -- How many vsyncs this frame missed its deadline by.
  delayed_frame_count LONG,
  -- The start timestamp where frame presentation was delayed.
  frame_jank_ts TIMESTAMP,
  -- The duration in ms of the delay in frame presentation.
  frame_jank_dur LONG
) AS
SELECT
  s.id,
  s.ts,
  s.dur,
  s.track_id,
  s.name,
  e.cause_of_jank,
  e.sub_cause_of_jank,
  cast_int!((e.delay_since_last_frame/e.vsync_interval) - 1) AS delayed_frame_count,
  cast_int!(s.ts + s.dur - ((e.delay_since_last_frame - e.vsync_interval) * 1e6)) AS frame_jank_ts,
  cast_int!((e.delay_since_last_frame - e.vsync_interval) * 1e6) AS frame_jank_dur
FROM chrome_gesture_scroll_updates AS s
JOIN chrome_janky_frames AS e
  ON s.id = e.event_latency_id;

-- Frame presentation interval is the delta between when the frame was supposed
-- to be presented and when it was actually presented.
CREATE PERFETTO VIEW chrome_janky_frame_presentation_intervals (
  -- Unique id.
  id LONG,
  -- The start timestamp of the slice.
  ts TIMESTAMP,
  -- The duration of the slice.
  dur DURATION,
  -- How many vsyncs this frame missed its deadline by.
  delayed_frame_count LONG,
  -- The stage of EventLatency that the caused the jank.
  cause_of_jank STRING,
  -- The stage of cause_of_jank that caused the jank.
  sub_cause_of_jank STRING,
  -- The id of the associated event latency in the slice table.
  event_latency_id LONG
) AS
SELECT
  row_number() OVER (ORDER BY frame_jank_ts) AS id,
  frame_jank_ts AS ts,
  frame_jank_dur AS dur,
  delayed_frame_count,
  cause_of_jank,
  sub_cause_of_jank,
  id AS event_latency_id
FROM chrome_janky_event_latencies_v3;

-- Scroll jank frame presentation stats for individual scrolls.
CREATE PERFETTO TABLE chrome_scroll_stats (
  -- Id of the individual scroll.
  scroll_id LONG,
  -- The number of frames in the scroll.
  frame_count LONG,
  -- The number of missed vsyncs in the scroll.
  missed_vsyncs LONG,
  -- The number presented frames in the scroll.
  presented_frame_count LONG,
  -- The number of janky frames in the scroll.
  janky_frame_count LONG,
  -- The % of frames that janked in the scroll.
  janky_frame_percent DOUBLE
) AS
WITH
  vsyncs AS (
    SELECT
      count() AS presented_vsync_count,
      scroll.id AS scroll_id
    FROM chrome_unique_frame_presentation_ts AS frame
    JOIN chrome_scrolls AS scroll
      ON frame.presentation_timestamp >= scroll.ts
      AND frame.presentation_timestamp <= scroll.ts + scroll.dur
    GROUP BY
      scroll_id
  ),
  missed_vsyncs AS (
    SELECT
      cast_int!(SUM((delay_since_last_frame / vsync_interval) - 1)) AS total_missed_vsyncs,
      scroll_id
    FROM chrome_janky_frames
    GROUP BY
      scroll_id
  ),
  frame_stats AS (
    SELECT
      scroll_id,
      num_frames AS presented_frame_count,
      coalesce(num_janky_frames, 0) AS janky_frame_count,
      round(coalesce(scroll_jank_percentage, 0), 2) AS janky_frame_percent
    FROM chrome_frames_per_scroll
  )
SELECT
  vsyncs.scroll_id,
  presented_vsync_count + coalesce(total_missed_vsyncs, 0) AS frame_count,
  total_missed_vsyncs AS missed_vsyncs,
  presented_frame_count,
  janky_frame_count,
  janky_frame_percent
FROM vsyncs
LEFT JOIN missed_vsyncs
  USING (scroll_id)
LEFT JOIN frame_stats
  USING (scroll_id);

-- Defines slices for all of janky scrolling intervals in a trace.
CREATE PERFETTO TABLE chrome_scroll_jank_intervals_v3 (
  -- The unique identifier of the janky interval.
  id LONG,
  -- The start timestamp of the janky interval.
  ts TIMESTAMP,
  -- The duration of the janky interval.
  dur DURATION
) AS
-- Sub-table to retrieve all janky slice timestamps. Ordering calculations are
-- based on timestamps rather than durations.
WITH
  janky_latencies AS (
    SELECT
      s.frame_jank_ts AS start_ts,
      s.frame_jank_ts + s.frame_jank_dur AS end_ts
    FROM chrome_janky_event_latencies_v3 AS s
  ),
  -- Determine the local maximum timestamp for janks thus far; this will allow
  -- us to coalesce all earlier events up to the maximum.
  ordered_jank_end_ts AS (
    SELECT
      *,
      max(end_ts) OVER (ORDER BY start_ts ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS max_end_ts_so_far
    FROM janky_latencies
  ),
  -- Determine the local minimum timestamp for janks thus far; this will allow
  -- us to coalesce all later events up to the nearest local maximum.
  range_starts AS (
    SELECT
      *,
      CASE
        -- This is a two-pass calculation to calculate the first event in the
        -- group. An event is considered the first event in a group if all events
        -- which started before it also finished the current one started.
        WHEN start_ts <= 1 + lag(max_end_ts_so_far) OVER (ORDER BY start_ts)
        THEN 0
        ELSE 1
      END AS range_start
    FROM ordered_jank_end_ts
  ),
  -- Assign an id to allow coalescing of individual slices.
  range_groups AS (
    SELECT
      *,
      sum(range_start) OVER (ORDER BY start_ts) AS range_group
    FROM range_starts
  )
-- Coalesce all slices within an interval.
SELECT
  range_group AS id,
  min(start_ts) AS ts,
  max(end_ts) - min(start_ts) AS dur
FROM range_groups
GROUP BY
  range_group;
