-- Copyright 2024 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.android_input;

INCLUDE PERFETTO MODULE intervals.intersect;

INCLUDE PERFETTO MODULE slices.with_context;

-- Processing steps of the Chrome input pipeline.
CREATE PERFETTO TABLE _chrome_input_pipeline_steps_no_input_type (
  -- Id of this Chrome input pipeline (LatencyInfo).
  latency_id LONG,
  -- Slice id
  slice_id LONG,
  -- The step timestamp.
  ts TIMESTAMP,
  -- Step duration.
  dur DURATION,
  -- Utid of the thread.
  utid LONG,
  -- Step name (ChromeLatencyInfo.step).
  step STRING,
  -- Input type.
  input_type STRING,
  -- Start time of the parent Chrome scheduler task (if any) of this step.
  task_start_time_ts TIMESTAMP
) AS
WITH
  steps_with_potential_duplicates AS (
    SELECT
      extract_arg(thread_slice.arg_set_id, 'chrome_latency_info.trace_id') AS latency_id,
      id AS slice_id,
      ts,
      dur,
      utid,
      extract_arg(thread_slice.arg_set_id, 'chrome_latency_info.step') AS step,
      extract_arg(thread_slice.arg_set_id, 'chrome_latency_info.input_type') AS input_type,
      ts - (
        extract_arg(thread_slice.arg_set_id, 'current_task.event_offset_from_task_start_time_us') * 1000
      ) AS task_start_time_ts
    FROM thread_slice
    WHERE
      NOT step IS NULL AND latency_id != -1
  ),
  steps_with_ordering AS (
    SELECT
      *,
      -- Partition the steps so that, if the same step (for the same input) was
      -- emitted more than once (e.g. due to b:390406106), the step ends up in
      -- the same partition as all its duplicates. This will enable us to
      -- deduplicate the steps later.
      -- If there are multiple STEP_RESAMPLE_SCROLL_EVENTS steps, we assume that
      -- the input was only dispatched after the last resampling, so we only
      -- care about the last STEP_RESAMPLE_SCROLL_EVENTS step. We don't have any
      -- preference for other steps but, for the sake of determinsm and
      -- consistency, let's always pick the last step.
      row_number() OVER (PARTITION BY latency_id, utid, step, input_type ORDER BY ts DESC) AS ordering_within_partition
    FROM steps_with_potential_duplicates
  )
SELECT
  latency_id,
  slice_id,
  ts,
  dur,
  utid,
  step,
  input_type,
  task_start_time_ts
FROM steps_with_ordering
-- This is where we actually remove duplicate steps.
WHERE
  ordering_within_partition = 1
ORDER BY
  slice_id,
  ts;

-- Each row represents one input pipeline.
CREATE PERFETTO TABLE chrome_inputs (
  -- Id of this Chrome input pipeline (LatencyInfo).
  latency_id LONG,
  -- Input type.
  input_type STRING
) AS
SELECT
  -- Id of this Chrome input pipeline (LatencyInfo).
  latency_id,
  -- MIN selects the first non-null value.
  min(input_type) AS input_type
FROM _chrome_input_pipeline_steps_no_input_type
WHERE
  latency_id != -1
GROUP BY
  latency_id;

-- Since not all steps have associated input type (but all steps
-- for a given latency id should have the same input type),
-- populate input type for steps where it would be NULL.
CREATE PERFETTO TABLE chrome_input_pipeline_steps (
  -- Id of this Chrome input pipeline (LatencyInfo).
  latency_id LONG,
  -- Slice id
  slice_id LONG,
  -- The step timestamp.
  ts TIMESTAMP,
  -- Step duration.
  dur DURATION,
  -- Utid of the thread.
  utid LONG,
  -- Step name (ChromeLatencyInfo.step).
  step STRING,
  -- Input type.
  input_type STRING,
  -- Start time of the parent Chrome scheduler task (if any) of this step.
  task_start_time_ts TIMESTAMP
) AS
SELECT
  latency_id,
  slice_id,
  ts,
  dur,
  utid,
  step,
  chrome_inputs.input_type AS input_type,
  task_start_time_ts
FROM chrome_inputs
LEFT JOIN _chrome_input_pipeline_steps_no_input_type
  USING (latency_id)
WHERE
  chrome_inputs.input_type IS NOT NULL;

-- For each input, if it was coalesced into another input, get the other input's
-- latency id.
CREATE PERFETTO TABLE chrome_coalesced_inputs (
  -- The `latency_id` of the coalesced input.
  coalesced_latency_id LONG,
  -- The `latency_id` of the other input that the current input was coalesced
  -- into. Guaranteed to be different from `coalesced_latency_id`.
  presented_latency_id LONG
) AS
SELECT
  args.int_value AS coalesced_latency_id,
  latency_id AS presented_latency_id
FROM chrome_input_pipeline_steps AS step
JOIN slice
  USING (slice_id)
JOIN args
  USING (arg_set_id)
WHERE
  step.step = 'STEP_RESAMPLE_SCROLL_EVENTS'
  AND args.flat_key = 'chrome_latency_info.coalesced_trace_ids'
  AND coalesced_latency_id != presented_latency_id;

-- Each scroll update event (except flings) in Chrome starts its life as a touch
-- move event, which is then eventually converted to a scroll update itself.
-- Each of these events is represented by its own LatencyInfo. This table
-- contains a mapping between touch move events and scroll update events they
-- were converted into.
CREATE PERFETTO TABLE chrome_touch_move_to_scroll_update (
  -- Latency id of the touch move input (LatencyInfo).
  touch_move_latency_id LONG,
  -- Latency id of the corresponding scroll update input (LatencyInfo).
  scroll_update_latency_id LONG
) AS
WITH
  scroll_update_steps AS MATERIALIZED (
    SELECT
      *
    FROM chrome_input_pipeline_steps
    WHERE
      step = 'STEP_SEND_INPUT_EVENT_UI' AND input_type = 'GESTURE_SCROLL_UPDATE_EVENT'
  ),
  -- By default, we map a scroll update event to an ancestor touch move event with
  -- STEP_TOUCH_EVENT_HANDLED.
  default_mapping AS MATERIALIZED (
    SELECT
      touch_move_handled_step.latency_id AS touch_move_latency_id,
      scroll_update_step.latency_id AS scroll_update_latency_id
    -- Performance optimization: we are interested in finding all of the
    -- scroll_update_steps which have a "touch_move_handled" parent: to do this,
    -- we intersect them and find all of the intersections which match
    -- a scroll_update_step.
    -- We are using `slice_id` as `id` as it's unique and 32-bit (unlike latency_id,
    -- which can be 64-bit).
    FROM _interval_intersect!(
    (
      (
        SELECT slice_id AS id, *
        FROM scroll_update_steps
        WHERE dur > 0
      ),
      (
        SELECT slice_id AS id, *
        FROM chrome_input_pipeline_steps step
        WHERE step = 'STEP_TOUCH_EVENT_HANDLED'
          AND dur > 0
      )
    ),
    (utid)
  ) AS ii
    JOIN scroll_update_steps AS scroll_update_step
      ON ii.id_0 = scroll_update_step.slice_id
    JOIN chrome_input_pipeline_steps AS touch_move_handled_step
      ON ii.id_1 = touch_move_handled_step.slice_id
    -- If intersection matches the `scroll_update_step`, then it means that
    -- `touch_move_handled_step` is an ancestor of `scroll_update_step`.
    WHERE
      ii.ts = scroll_update_step.ts AND ii.dur = scroll_update_step.dur
  ),
  -- In the rare case where there are no touch handlers in the renderer, there's
  -- no ancestor touch move event with STEP_TOUCH_EVENT_HANDLED. In that case, we
  -- try to fall back to an ancestor touch move event with
  -- STEP_SEND_INPUT_EVENT_UI instead.
  fallback_mapping AS MATERIALIZED (
    SELECT
      send_touch_move_step.latency_id AS touch_move_latency_id,
      scroll_update_step.latency_id AS scroll_update_latency_id
    -- See the comment in the default_mapping CTE for an explanation what's going on here.
    FROM _interval_intersect!(
    (
      (
        SELECT slice_id AS id, *
        FROM scroll_update_steps
        WHERE dur > 0
      ),
      (
        SELECT slice_id AS id, *
        FROM chrome_input_pipeline_steps step
        WHERE step = 'STEP_SEND_INPUT_EVENT_UI'
          AND input_type = 'TOUCH_MOVE_EVENT'
          AND dur > 0
      )
    ),
    (utid)
  ) AS ii
    JOIN scroll_update_steps AS scroll_update_step
      ON ii.id_0 = scroll_update_step.slice_id
    JOIN chrome_input_pipeline_steps AS send_touch_move_step
      ON ii.id_1 = send_touch_move_step.slice_id
    WHERE
      ii.ts = scroll_update_step.ts AND ii.dur = scroll_update_step.dur
  ),
  -- We ideally would want to do a FULL JOIN here, but it is very slow in SQLite,
  -- so instead we are doing UNION + two LEFT JOINs.
  scroll_update_latency_ids AS (
    SELECT
      scroll_update_latency_id
    FROM default_mapping
    UNION
    SELECT
      scroll_update_latency_id
    FROM fallback_mapping
  )
SELECT
  coalesce(default_mapping.touch_move_latency_id, fallback_mapping.touch_move_latency_id) AS touch_move_latency_id,
  scroll_update_latency_id
FROM scroll_update_latency_ids
LEFT JOIN default_mapping
  USING (scroll_update_latency_id)
LEFT JOIN fallback_mapping
  USING (scroll_update_latency_id);

-- Matches Android input id to the corresponding touch move event.
CREATE PERFETTO TABLE chrome_dispatch_android_input_event_to_touch_move (
  -- Input id (assigned by the system, used by InputReader and InputDispatcher)
  android_input_id STRING,
  -- Latency id.
  touch_move_latency_id LONG
) AS
SELECT
  chrome_deliver_android_input_event.android_input_id,
  latency_id AS touch_move_latency_id
FROM chrome_deliver_android_input_event
LEFT JOIN chrome_input_pipeline_steps
  USING (utid)
WHERE
  chrome_input_pipeline_steps.input_type = 'TOUCH_MOVE_EVENT'
  AND chrome_input_pipeline_steps.step = 'STEP_SEND_INPUT_EVENT_UI'
  AND chrome_deliver_android_input_event.ts <= chrome_input_pipeline_steps.ts
  AND chrome_deliver_android_input_event.ts + chrome_deliver_android_input_event.dur >= chrome_input_pipeline_steps.ts + chrome_input_pipeline_steps.dur;
