-- Copyright 2025 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.chrome_scrolls_v4;

INCLUDE PERFETTO MODULE chrome.graphics_pipeline;

INCLUDE PERFETTO MODULE time.conversion;

-- A helper macro to avoid manually filtering `scroll_frames` multiple times.
-- Returns janky rows of `scroll_frames` with a few additional statistics.
CREATE PERFETTO MACRO _prepare_janky_scroll_frames(
    -- Table containing information about frames presented by Chrome containing
    -- one or more scroll updates.
    scroll_frames TableOrSubquery
)
RETURNS TableOrSubquery AS
(
  SELECT
    *,
    (
      cast_double!(previous_last_input_to_first_input_generation_dur) / vsync_interval_dur
    ) AS previous_input_delta_to_vsync_ratio,
    (
      cast_double!(first_input_compositor_dispatch_to_on_begin_frame_delay_dur) / vsync_interval_dur
    ) AS wait_for_begin_frame_delta_to_vsync_ratio,
    (
      cast_double!(viz_wait_for_draw_dur) / vsync_interval_dur
    ) AS viz_wait_for_draw_to_vsync_ratio
  FROM $scroll_frames
  WHERE
    is_janky
);

-- List of janky scroll frames according to Chrome's v1 scroll jank metric.
CREATE PERFETTO TABLE _chrome_janky_scroll_frames AS
SELECT
  *
FROM _prepare_janky_scroll_frames!(chrome_scroll_frame_info);

-- List of janky scroll frames according to Chrome's v4 scroll jank metric.
CREATE PERFETTO TABLE _chrome_janky_scroll_frames_v4 AS
SELECT
  *
FROM _prepare_janky_scroll_frames!(chrome_scroll_frame_info_v4);

-- A helper macro to generate tags for long stages in the scroll pipeline.
CREATE PERFETTO MACRO _chrome_scroll_jank_tag_long_stage(
    -- Table containing information about janky frames.
    janky_scroll_frames TableOrSubquery,
    -- Stage (column of `chrome_scroll_frame_info`) this tag is based on.
    stage ColumnName,
    -- String tag to assign to frames that exceed the threshold.
    tag Expr,
    -- Threshold in milliseconds.
    threshold_ms Expr
)
RETURNS TableOrSubquery AS
(
  SELECT
    id AS frame_id,
    $tag AS tag
  FROM $janky_scroll_frames
  WHERE
    $stage > time_from_ms($threshold_ms)
);

-- A helper macro which assigns scroll jank causes to janky scroll frames. The
-- result contains zero or more tags for each janky frame.
CREATE PERFETTO MACRO _assign_scroll_jank_tags_to_frames(
    -- Table containing information about janky frames.
    janky_scroll_frames TableOrSubquery,
    abs_scroll_delta Expr
)
RETURNS TableOrSubquery AS
(
  -- Start of the the long stage tags. We use 16ms as the threshold for most
  -- stages, as if they take O(vsync) or more, then we have a problem.
  SELECT
    *
  FROM _chrome_scroll_jank_tag_long_stage!(
    $janky_scroll_frames,
    first_input_generation_to_browser_main_dur,
    'long_generation_to_browser_main',
    6
  )
  UNION ALL
  SELECT
    *
  FROM _chrome_scroll_jank_tag_long_stage!(
    $janky_scroll_frames,
    first_input_touch_move_processing_dur,
    'long_touch_move',
    16
  )
  UNION ALL
  SELECT
    *
  FROM _chrome_scroll_jank_tag_long_stage!(
    $janky_scroll_frames,
    first_input_browser_to_compositor_delay_dur,
    'long_browser_to_compositor',
    16
  )
  UNION ALL
  SELECT
    *
  FROM _chrome_scroll_jank_tag_long_stage!(
    $janky_scroll_frames,
    first_input_compositor_dispatch_dur,
    'long_compositor_dispatch',
    16
  )
  UNION ALL
  SELECT
    *
  FROM _chrome_scroll_jank_tag_long_stage!(
    $janky_scroll_frames,
    first_input_compositor_dispatch_to_on_begin_frame_delay_dur,
    'long_wait_for_begin_frame',
    16
  )
  UNION ALL
  SELECT
    *
  FROM _chrome_scroll_jank_tag_long_stage!(
    $janky_scroll_frames,
    compositor_on_begin_frame_dur,
    'long_on_begin_frame',
    16
  )
  UNION ALL
  SELECT
    *
  FROM _chrome_scroll_jank_tag_long_stage!(
    $janky_scroll_frames,
    compositor_on_begin_frame_to_generation_delay_dur,
    'long_on_begin_frame_to_generation',
    16
  )
  UNION ALL
  SELECT
    *
  FROM _chrome_scroll_jank_tag_long_stage!(
    $janky_scroll_frames,
    compositor_generate_frame_to_submit_frame_dur,
    'long_generate_to_submit',
    16
  )
  UNION ALL
  SELECT
    *
  FROM _chrome_scroll_jank_tag_long_stage!(
    $janky_scroll_frames,
    compositor_submit_frame_dur,
    'long_submit_frame',
    16
  )
  UNION ALL
  SELECT
    *
  FROM _chrome_scroll_jank_tag_long_stage!(
    $janky_scroll_frames,
    compositor_to_viz_delay_dur,
    'long_compositor_to_viz',
    16
  )
  UNION ALL
  SELECT
    *
  FROM _chrome_scroll_jank_tag_long_stage!(
    $janky_scroll_frames,
    viz_receive_compositor_frame_dur,
    'long_viz_receive_frame',
    16
  )
  UNION ALL
  SELECT
    *
  FROM _chrome_scroll_jank_tag_long_stage!(
    $janky_scroll_frames,
    viz_wait_for_draw_dur,
    'long_viz_wait_for_draw',
    16
  )
  UNION ALL
  SELECT
    *
  FROM _chrome_scroll_jank_tag_long_stage!(
    $janky_scroll_frames,
    viz_draw_and_swap_dur,
    'long_viz_draw_and_swap',
    16
  )
  UNION ALL
  SELECT
    *
  FROM _chrome_scroll_jank_tag_long_stage!(
    $janky_scroll_frames,
    viz_to_gpu_delay_dur,
    'long_viz_to_gpu',
    -- We use lower threshold, as jump to GPU should be fast.
    6
  )
  UNION ALL
  SELECT
    *
  FROM _chrome_scroll_jank_tag_long_stage!(
    $janky_scroll_frames,
    viz_swap_buffers_dur,
    'long_viz_swap_buffers',
    -- We use lower threshold, as swap buffers should be fast.
    6
  )
  UNION ALL
  SELECT
    *
  FROM _chrome_scroll_jank_tag_long_stage!(
    $janky_scroll_frames,
    viz_swap_buffers_to_latch_dur,
    'long_viz_swap_to_latch',
    -- This stage should be highly consistent and usually takes 16.6ms, so we
    -- use 17ms as the threshold.
    17
  )
  UNION ALL
  SELECT
    *
  FROM _chrome_scroll_jank_tag_long_stage!(
    $janky_scroll_frames,
    buffer_available_to_ready_dur,
    'long_buffer_available_to_ready',
    -- We use lower threshold, as transferring a new frame should be fast.
    6
  )
  UNION ALL
  --
  -- Start of non-stage tags.
  --
  -- We expect at least one input event per vsync, so if the delta to the
  -- previous input event is greater than 1.2 vsync intervals, we can consider
  -- this to be a cause of jank.
  SELECT
    id AS frame_id,
    'inconsistent_input' AS tag
  FROM $janky_scroll_frames
  WHERE
    previous_input_delta_to_vsync_ratio >= 1.2 AND NOT is_inertial
  UNION ALL
  -- As we control fling generation, we want to tag inconsistencies there
  -- separately.
  SELECT
    id AS frame_id,
    'inconsistent_fling_input' AS tag
  FROM $janky_scroll_frames
  WHERE
    previous_input_delta_to_vsync_ratio >= 1.2 AND is_inertial
  UNION ALL
  -- Having one input per frame makes us susceptible to scheduling issues, so if
  -- we have a spike in the time we wait to draw a frame, consider it a
  -- scheduling issue.
  SELECT
    id AS frame_id,
    'infrequent_input_cc_scheduling' AS tag
  FROM $janky_scroll_frames
  WHERE
    previous_input_delta_to_vsync_ratio BETWEEN 0.75 AND 1.2
    AND wait_for_begin_frame_delta_to_vsync_ratio > 0.66
  UNION ALL
  SELECT
    id AS frame_id,
    'infrequent_input_viz_scheduling' AS tag
  FROM $janky_scroll_frames
  WHERE
    previous_input_delta_to_vsync_ratio BETWEEN 0.75 AND 1.2
    AND viz_wait_for_draw_to_vsync_ratio > 0.66
  UNION ALL
  -- Tag janks where the delta for generation_to_browser_main is > 3 ms.
  SELECT
    id AS frame_id,
    'generation_to_browser_main_spike' AS tag
  FROM $janky_scroll_frames
  WHERE
    first_input_generation_to_browser_main_delta_dur > time_from_ms(3)
  UNION ALL
  -- Tag janks with slow input (|input_delta| <= 2.001 px).
  SELECT
    id AS frame_id,
    'slow_input' AS tag
  FROM $janky_scroll_frames
  WHERE
    $abs_scroll_delta <= 2.001
  UNION ALL
  -- According to the field traces, long_generation_to_dispatch_end_dur
  -- over 3 ms is correlated with janky frames
  -- (more details at http://b/401003093#comment15).
  SELECT
    id AS frame_id,
    'long_generation_to_dispatch_end_dur' AS tag
  FROM $janky_scroll_frames
  WHERE
    input_reader_dur + input_dispatcher_dur > time_from_ms(3)
);

-- List of scroll jank causes that apply to scroll frames which are janky
-- according to Chrome's v1 scroll jank metric. Contains zero or more tags for
-- each janky frame.
CREATE PERFETTO TABLE chrome_scroll_jank_tags (
  -- Frame ID.
  frame_id LONG,
  -- Tag of the scroll jank cause.
  tag STRING
) AS
SELECT
  *
FROM _assign_scroll_jank_tags_to_frames!(_chrome_janky_scroll_frames, abs(total_input_delta_y))
UNION ALL
-- TODO(b:460737855): Figure out whether/how we should port the logic below over
-- for the v4 metric.
SELECT
  frame.id AS frame_id,
  printf(
    'dropped_previous_frame/%s',
    extract_arg(drop_frame_slice.arg_set_id, 'chrome_graphics_pipeline.frame_skipped_reason')
  ) AS tag
FROM _chrome_janky_scroll_frames AS frame
JOIN chrome_scroll_update_refs AS previous_input
  ON previous_input.scroll_update_latency_id = last_input_before_this_frame_id
JOIN chrome_graphics_pipeline_surface_frame_steps AS drop_frame_step
  ON drop_frame_step.surface_frame_trace_id = previous_input.surface_frame_id
  AND drop_frame_step.step = 'STEP_DID_NOT_PRODUCE_COMPOSITOR_FRAME'
JOIN slice AS drop_frame_slice
  ON drop_frame_step.id = drop_frame_slice.id;

-- List of scroll jank causes that apply to scroll frames which are janky
-- according to Chrome's v4 scroll jank metric. Contains zero or more tags for
-- each janky frame.
CREATE PERFETTO TABLE chrome_scroll_jank_tags_v4 (
  -- Frame ID. Can be joined with `chrome_scroll_frame_info_v4.id`.
  frame_id JOINID(slice.id),
  -- Tag of the scroll jank cause.
  tag STRING
) AS
SELECT
  *
FROM _assign_scroll_jank_tags_to_frames!(_chrome_janky_scroll_frames_v4, real_abs_total_raw_delta_pixels);

-- A helper macro which flattens the tags for each janky scroll frame. The
-- result contains exactly one row for each frame in `janky_scroll_frames`.
CREATE PERFETTO MACRO _consolidate_tagged_janky_scroll_frames(
    -- Table containing information about janky frames.
    janky_scroll_frames TableOrSubquery,
    -- Table containing jank causes assigned to janky frames.
    jank_tags_table TableOrSubquery
)
RETURNS TableOrSubquery AS
(
  WITH
    tagged_frames AS (
      SELECT
        frame.id AS frame_id,
        GROUP_CONCAT(tag ORDER BY tag) AS tags
      FROM $janky_scroll_frames AS frame
      LEFT JOIN $jank_tags_table AS tag
        ON tag.frame_id = frame.id
      GROUP BY
        frame_id
    )
  SELECT
    frame_id,
    NOT tags IS NULL AS tagged,
    tags
  FROM tagged_frames
);

-- Scroll frames which are janky according to Chrome's v1 scroll jank metric
-- together with the assigned causes. Contains exactly one row for each janky
-- frame in `chrome_scroll_frame_info`.
CREATE PERFETTO TABLE chrome_tagged_janky_scroll_frames (
  -- Frame id.
  frame_id LONG,
  -- Whether this frame has any tags or not.
  tagged BOOL,
  -- Comma-separated list of tags for this frame.
  tags STRING
) AS
SELECT
  *
FROM _consolidate_tagged_janky_scroll_frames!(
  _chrome_janky_scroll_frames,
  chrome_scroll_jank_tags
);

-- Scroll frames which are janky according to Chrome's v4 scroll jank metric
-- together with the assigned causes. Contains exactly one row for each janky
-- frame in `chrome_scroll_frame_info_v4`.
CREATE PERFETTO TABLE chrome_tagged_janky_scroll_frames_v4 (
  -- Frame id. Can be joined with `chrome_scroll_frame_info_v4.id`.
  frame_id JOINID(slice.id),
  -- Whether this frame has any tags or not.
  tagged BOOL,
  -- Comma-separated list of tags for this frame.
  tags STRING
) AS
SELECT
  *
FROM _consolidate_tagged_janky_scroll_frames!(
  _chrome_janky_scroll_frames_v4,
  chrome_scroll_jank_tags_v4
);
