-- 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.

-- Checks if slice has an ancestor with provided name.
CREATE PERFETTO FUNCTION _has_parent_slice_with_name(
  -- Id of the slice to check parents of.
  id INT,
  -- Name of potential ancestor slice.
  parent_name STRING)
-- Whether `parent_name` is a name of an ancestor slice.
RETURNS BOOL AS
SELECT EXISTS(
  SELECT 1
  FROM ancestor_slice($id)
  WHERE name = $parent_name
  LIMIT 1
);

-- Returns the mojo ipc hash for a given task, looking it up from the
-- argument of descendant ScopedSetIpcHash slice.
-- This is relevant only for the older Chrome traces, where mojo IPC
-- hash was reported in a separate ScopedSetIpcHash slice.
CREATE PERFETTO FUNCTION _extract_mojo_ipc_hash(slice_id INT)
RETURNS INT AS
SELECT EXTRACT_ARG(arg_set_id, "chrome_mojo_event_info.ipc_hash")
FROM descendant_slice($slice_id)
WHERE name="ScopedSetIpcHash"
ORDER BY id
LIMIT 1;

-- Returns the frame type (main frame vs subframe) for key navigation tasks
-- which capture the associated RenderFrameHost in an argument.
CREATE PERFETTO FUNCTION _extract_frame_type(slice_id INT)
RETURNS INT AS
SELECT EXTRACT_ARG(arg_set_id, "render_frame_host.frame_type")
FROM descendant_slice($slice_id)
WHERE name IN (
  "RenderFrameHostImpl::BeginNavigation",
  "RenderFrameHostImpl::DidCommitProvisionalLoad",
  "RenderFrameHostImpl::DidCommitSameDocumentNavigation",
  "RenderFrameHostImpl::DidStopLoading"
)
LIMIT 1;

-- Human-readable aliases for a few key navigation tasks.
CREATE PERFETTO FUNCTION _human_readable_navigation_task_name(
  task_name STRING)
RETURNS STRING AS
SELECT
  CASE
    WHEN $task_name = "content.mojom.FrameHost message (hash=2168461044)"
      THEN "FrameHost::BeginNavigation"
    WHEN $task_name = "content.mojom.FrameHost message (hash=3561497419)"
      THEN "FrameHost::DidCommitProvisionalLoad"
    WHEN $task_name = "content.mojom.FrameHost message (hash=1421450774)"
      THEN "FrameHost::DidCommitSameDocumentNavigation"
    WHEN $task_name = "content.mojom.FrameHost message (hash=368650583)"
      THEN "FrameHost::DidStopLoading"
  END;

-- Takes a task name and formats it correctly for scheduler tasks.
CREATE PERFETTO FUNCTION _format_scheduler_task_name(task_name STRING)
RETURNS STRING AS
SELECT printf("RunTask(posted_from=%s)", $task_name);

-- Takes the category and determines whether it is "Java" only, as opposed to
-- "toplevel,Java".
CREATE PERFETTO FUNCTION _java_not_top_level_category(category STRING)
RETURNS BOOL AS
SELECT $category GLOB "*Java*" AND $category not GLOB "*toplevel*";

-- Takes the category and determines whether is any valid
-- toplevel category or combination of categories.
CREATE PERFETTO FUNCTION _any_top_level_category(category STRING)
RETURNS BOOL AS
SELECT $category IN ("toplevel", "toplevel,viz", "toplevel,Java");

-- TODO(altimin): the situations with kinds in this file is a bit of a mess.
-- The idea is that it should work as `type` in the `slice` table, pointing to
-- a "child" table with more information about the task (e.g. posted_from for
-- scheduler tasks). Currently this is not the case and needs a cleanup.
-- Also we should align this with how table inheritance should work for
-- `CREATE PERFETTO TABLE`.

-- Get task type for a given task kind.
CREATE PERFETTO FUNCTION _get_java_views_task_type(kind STRING)
RETURNS STRING AS
SELECT
  CASE $kind
    WHEN "Choreographer" THEN "choreographer"
    WHEN "SingleThreadProxy::BeginMainFrame" THEN "ui_thread_begin_main_frame"
  END;

-- All slices corresponding to receiving mojo messages.
-- On the newer Chrome versions, it's just "Receive mojo message" and
-- "Receive mojo reply" slices (or "Receive {mojo_message_name}" if
-- built with `extended_tracing_enabled`. On legacy Chrome versions,
-- other appropriate messages (like "Connector::DispatchMessage") are used.
--
-- @column STRING interface_name    Name of the IPC interface.
-- @column INT ipc_hash             Hash of a message name.
-- @column STRING message_type      Either 'message' or 'reply'.
-- @column INT id                   Slice id.
--
-- Note: this might include messages received within a sync mojo call.
-- TODO(altimin): This should use EXTEND_TABLE when it becomes available.
CREATE TABLE _chrome_mojo_slices AS
WITH
-- Select all new-style (post crrev.com/c/3270337) mojo slices and
-- generate |task_name| for them.
-- If extended tracing is enabled, the slice name will have the full method
-- name (i.e. "Receive content::mojom::FrameHost::DidStopLoading") and we
-- should use it as a full name.
-- If extended tracing is not enabled, we should include the interface name
-- and method hash into the full name.
new_mojo_slices AS (
  SELECT
    EXTRACT_ARG(arg_set_id, "chrome_mojo_event_info.mojo_interface_tag") AS interface_name,
    EXTRACT_ARG(arg_set_id, "chrome_mojo_event_info.ipc_hash") AS ipc_hash,
    CASE name
      WHEN "Receive mojo message" THEN "message"
      WHEN "Receive mojo reply" THEN "reply"
    END AS message_type,
    id
  FROM slice
  WHERE
    category GLOB '*toplevel*'
    AND name GLOB 'Receive *'
),
-- Select old-style slices for channel-associated mojo events.
old_associated_mojo_slices AS (
  SELECT
    name AS interface_name,
    _extract_mojo_ipc_hash(id) AS ipc_hash,
    "message" AS message_type,
    id
  FROM slice
  WHERE
    category GLOB "*mojom*"
    AND name GLOB '*.mojom.*'
),
-- Select old-style slices for non-(channel-associated) mojo events.
old_non_associated_mojo_slices AS (
  SELECT
    COALESCE(
      EXTRACT_ARG(arg_set_id, "chrome_mojo_event_info.watcher_notify_interface_tag"),
      EXTRACT_ARG(arg_set_id, "chrome_mojo_event_info.mojo_interface_tag")
    ) AS interface_name,
    _extract_mojo_ipc_hash(id) AS ipc_hash,
    "message" AS message_type,
    id
  FROM slice
  WHERE
    category GLOB "*toplevel*" AND name = "Connector::DispatchMessage"
)
-- Merge all mojo slices.
SELECT * FROM new_mojo_slices
UNION ALL
SELECT * FROM old_associated_mojo_slices
UNION ALL
SELECT * FROM old_non_associated_mojo_slices;

-- As we lookup by ID on |_chrome_mojo_slices| table, add an index on
-- id to make lookups fast.
CREATE INDEX _chrome_mojo_slices_idx ON _chrome_mojo_slices(id);

-- This table contains a list of slices corresponding to the _representative_
-- Chrome Java view operations.
-- These are the outermost Java view slices after filtering out generic framework views
-- (like FitWindowsLinearLayout) and selecting the outermost slices from the remaining ones.
--
-- @column id INT                       Slice id.
-- @column ts INT                       Timestamp.
-- @column dur INT                      Duration.
-- @column name STRING                  Name of the view.
-- @column is_software_screenshot BOOL  Whether this slice is a part of non-accelerated
--                                      capture toolbar screenshot.
-- @column is_hardware_screenshot BOOL  Whether this slice is a part of accelerated
--                                      capture toolbar screenshot.
CREATE TABLE _chrome_java_views AS
WITH
-- .draw, .onLayout and .onMeasure parts of the java view names don't add much, strip them.
java_slices_with_trimmed_names AS (
  SELECT
    id,
    REPLACE(
      REPLACE(
        REPLACE(
          REPLACE(
            REPLACE(
              s1.name,
              ".draw", ""),
            ".onLayout", ""),
          ".onMeasure", ""),
        ".Layout", ""),
      ".Measure", "") AS name,
      ts,
      dur
    FROM
      slice s1
    -- Ensure that toplevel Java slices are not included, as they may be logged
    -- with either category = "toplevel" or category = "toplevel,Java".
    -- Also filter out the zero duration slices as an attempt to reduce noise as
    -- "Java" category contains misc events (as it's hard to add new categories).
    WHERE _java_not_top_level_category(category) AND dur > 0
  ),
  -- We filter out generic slices from various UI frameworks which don't tell us much about
  -- what exactly this view is doing.
  interesting_java_slices AS (
    SELECT
      id, name, ts, dur
    FROM java_slices_with_trimmed_names
    WHERE NOT name IN (
      -- AndroidX.
      "FitWindowsFrameLayout",
      "FitWindowsLinearLayout",
      "ContentFrameLayout",
      "CoordinatorLayout",
      -- Other non-Chrome UI libraries.
      "ComponentHost",
      -- Generic Chrome frameworks.
      "CompositorView:finalizeLayers",
      "CompositorViewHolder",
      "CompositorViewHolder:layout",
      "CompositorViewHolder:updateContentViewChildrenDimension",
      "CoordinatorLayoutForPointer",
      "OptimizedFrameLayout",
      "ViewResourceAdapter:getBitmap",
      "ViewResourceFrameLayout",
      -- Non-specific Chrome slices.
      "AppCompatImageButton",
      "ScrollingBottomViewResourceFrameLayout",
      -- Screenshots get their custom annotations below.
      "ViewResourceAdapter:captureWithHardwareDraw",
      "ViewResourceAdapter:captureWithSoftwareDraw",
      -- Non-bytecode generated slices.
      "LayoutDriver:onUpdate"
    )
)
SELECT
  s1.*,
  -- While the parent slices are too generic to be used by themselves,
  -- they can provide some useful metadata.
  _has_parent_slice_with_name(
    s1.id,
    "ViewResourceAdapter:captureWithSoftwareDraw"
  ) AS is_software_screenshot,
  _has_parent_slice_with_name(
    s1.id,
    "ViewResourceAdapter:captureWithHardwareDraw"
  ) AS is_hardware_screenshot
FROM interesting_java_slices s1
-- We select "outermost" interesting slices: interesting slices which
-- do not another interesting slice in their parent chain.
WHERE (SELECT count()
  FROM ancestor_slice(s1.id) s2
  JOIN interesting_java_slices s3 ON s2.id = s3.id) = 0;

-- A list of slices corresponding to operations on interesting (non-generic)
-- Chrome Java views. The view is considered interested if it's not a system
-- (ContentFrameLayout) or generic library (CompositorViewHolder) views.
--
-- TODO(altimin): Add "columns_from slice" annotation.
-- TODO(altimin): convert this to EXTEND_TABLE when it becomes available.
CREATE PERFETTO VIEW chrome_java_views(
  -- Name of the view.
  filtered_name STRING,
  -- Whether this slice is a part of non-accelerated capture toolbar screenshot.
  is_software_screenshot BOOL,
  -- Whether this slice is a part of accelerated capture toolbar screenshot.
  is_hardware_screenshot BOOL,
  -- Slice id.
  slice_id INT
) AS
SELECT
  java_view.name AS filtered_name,
  java_view.is_software_screenshot,
  java_view.is_hardware_screenshot,
  slice.id as slice_id
FROM _chrome_java_views java_view
JOIN slice USING (id);

-- A list of Choreographer tasks (Android frame generation) in Chrome.
CREATE PERFETTO VIEW _chrome_choreographer_tasks
AS
SELECT
  id,
  "Choreographer" AS kind,
  ts,
  dur,
  name
FROM slice
WHERE name GLOB "Looper.dispatch: android.view.Choreographer$FrameHandler*";

-- Extract task's posted_from information from task's arguments.
CREATE PERFETTO FUNCTION _get_posted_from(arg_set_id INT)
RETURNS STRING AS
WITH posted_from as (
  SELECT
    EXTRACT_ARG($arg_set_id, "task.posted_from.file_name") AS file_name,
    EXTRACT_ARG($arg_set_id, "task.posted_from.function_name") AS function_name
)
SELECT file_name || ":" || function_name as posted_from
FROM posted_from;

-- Selects the BeginMainFrame slices (which as posted from ScheduledActionSendBeginMainFrame),
-- used for root-level processing. In top-level/Java based slices, these will correspond to the
-- ancestor of descendant slices; in long-task tracking, these tasks will be
-- on a custom track and will need to be associated with children by timestamp
-- and duration. Corresponds with the Choreographer root slices in
-- chrome_choreographer_tasks below.
--
-- Schema:
-- @column is            The slice id.
-- @column kind          The type of Java slice.
-- @column ts            The timestamp of the slice.
-- @column name          The name of the slice.
CREATE PERFETTO FUNCTION _select_begin_main_frame_java_slices(
  name STRING)
RETURNS TABLE(id INT, kind STRING, ts LONG, dur LONG, name STRING) AS
SELECT
  id,
  "SingleThreadProxy::BeginMainFrame" AS kind,
  ts,
  dur,
  name
FROM slice
WHERE
  (name = $name
    AND _get_posted_from(arg_set_id) =
        "cc/trees/single_thread_proxy.cc:ScheduledActionSendBeginMainFrame");

-- A list of Chrome tasks which were performing operations with Java views,
-- together with the names of these views.
-- @column id INT            Slice id.
-- @column kind STRING       Type of the task.
-- @column java_views STRING Concatenated names of Java views used by the task.
CREATE PERFETTO VIEW _chrome_slices_with_java_views AS
WITH
  -- Select UI thread BeginMainFrames (which are Chrome scheduler tasks) and
  -- Choreographer frames (which are looper tasks).
  root_slices AS (
    SELECT id, kind
    FROM _SELECT_BEGIN_MAIN_FRAME_JAVA_SLICES('ThreadControllerImpl::RunTask')
    UNION ALL
    SELECT id, kind FROM _chrome_choreographer_tasks
  ),
  -- Intermediate step to allow us to sort java view names.
  root_slice_and_java_view_not_grouped AS (
    SELECT
      root.id, root.kind, java_view.name AS java_view_name
    FROM root_slices root
    JOIN descendant_slice(root.id) child
    JOIN _chrome_java_views java_view ON java_view.id = child.id
  )
SELECT
  root.id,
  root.kind,
  GROUP_CONCAT(DISTINCT java_view.java_view_name) AS java_views
FROM root_slices root
LEFT JOIN root_slice_and_java_view_not_grouped java_view USING (id)
GROUP BY root.id;

-- A list of tasks executed by Chrome scheduler.
CREATE TABLE _chrome_scheduler_tasks AS
SELECT
  id
FROM slice
WHERE
  category GLOB "*toplevel*"
  AND (name = "ThreadControllerImpl::RunTask" OR name = "ThreadPool_RunTask")
ORDER BY id;

-- A list of tasks executed by Chrome scheduler.
CREATE PERFETTO VIEW chrome_scheduler_tasks(
  -- Slice id.
  id INT,
  -- Type.
  type STRING,
  -- Name of the task.
  name STRING,
  -- Timestamp.
  ts INT,
  -- Duration.
  dur INT,
  -- Utid of the thread this task run on.
  utid INT,
  -- Name of the thread this task run on.
  thread_name STRING,
  -- Upid of the process of this task.
  upid INT,
  -- Name of the process of this task.
  process_name STRING,
  -- Same as slice.track_id.
  track_id INT,
  -- Same as slice.category.
  category STRING,
  -- Same as slice.depth.
  depth INT,
  -- Same as slice.parent_id.
  parent_id INT,
  -- Same as slice.arg_set_id.
  arg_set_id INT,
  -- Same as slice.thread_ts.
  thread_ts INT,
  -- Same as slice.thread_dur.
  thread_dur INT,
  -- Source location where the PostTask was called.
  posted_from STRING
) AS
SELECT
  task.id,
  "chrome_scheduler_tasks" as type,
  _format_scheduler_task_name(
    _get_posted_from(slice.arg_set_id)) as name,
  slice.ts,
  slice.dur,
  thread.utid,
  thread.name as thread_name,
  process.upid,
  process.name as process_name,
  slice.track_id,
  slice.category,
  slice.depth,
  slice.parent_id,
  slice.arg_set_id,
  slice.thread_ts,
  slice.thread_dur,
  _get_posted_from(slice.arg_set_id) as posted_from
FROM _chrome_scheduler_tasks task
JOIN slice using (id)
JOIN thread_track ON slice.track_id = thread_track.id
JOIN thread using (utid)
JOIN process using (upid)
ORDER BY task.id;

-- Select the slice that might be the descendant mojo slice for the given task
-- slice if it exists.
CREATE PERFETTO FUNCTION _get_descendant_mojo_slice_candidate(
  slice_id INT
)
RETURNS INT AS
SELECT
  id
FROM descendant_slice($slice_id)
WHERE
  -- The tricky case here is dealing with sync mojo IPCs: we do not want to
  -- pick up sync IPCs when we are in a non-IPC task.
  -- So we look at all toplevel events and pick up the first one:
  -- for sync mojo messages, it will be "Send mojo message", which then
  -- will fail.
  -- Some events are excluded as they can legimately appear under "RunTask"
  -- before "Receive mojo message".
  category GLOB "*toplevel*" AND
  name NOT IN (
    "SimpleWatcher::OnHandleReady",
    "MessagePipe peer closed")
ORDER by depth, ts
LIMIT 1;

CREATE PERFETTO FUNCTION _descendant_mojo_slice(slice_id INT)
RETURNS TABLE(task_name STRING) AS
SELECT
  printf("%s %s (hash=%d)",
    mojo.interface_name, mojo.message_type, mojo.ipc_hash) AS task_name
FROM slice task
JOIN _chrome_mojo_slices mojo
  ON mojo.id = _get_descendant_mojo_slice_candidate($slice_id)
WHERE task.id = $slice_id;

-- A list of "Chrome tasks": top-level execution units (e.g. scheduler tasks /
-- IPCs / system callbacks) run by Chrome. For a given thread, the tasks
-- will not intersect.
--
-- @column task_name STRING  Name for the given task.
-- @column task_type STRING  Type of the task (e.g. "scheduler").
-- @column scheduling_delay INT
CREATE TABLE _chrome_tasks AS
WITH
-- Select slices from "toplevel" category which do not have another
-- "toplevel" slice as ancestor. The possible cases include sync mojo messages
-- and tasks in nested runloops. Toplevel events may also be logged as with
-- the Java category.
non_embedded_toplevel_slices AS (
  SELECT * FROM slice
  WHERE
    _any_top_level_category(category)
    AND (SELECT count() FROM ancestor_slice(slice.id) anc
      WHERE anc.category GLOB "*toplevel*" or anc.category GLOB "*toplevel.viz*") = 0
),
-- Select slices from "Java" category which do not have another "Java" or
-- "toplevel" slice as parent. In the longer term they should probably belong
-- to "toplevel" category as well, but for now this will have to do. Ensure
-- that "Java" slices do not include "toplevel" slices as those would be
-- handled elsewhere.
non_embedded_java_slices AS (
  SELECT
    id,
    name AS task_name,
    "java" as task_type
  FROM slice s
  WHERE
    _java_not_top_level_category(category)
    AND (SELECT count()
      FROM ancestor_slice(s.id) s2
      WHERE s2.category GLOB "*toplevel*" OR s2.category GLOB "*Java*") = 0
),
-- Generate full names for tasks with java views.
java_views_tasks AS (
  SELECT
    id,
    printf('%s(java_views=%s)', kind, java_views) AS task_name,
    _get_java_views_task_type(kind) AS task_type
  FROM _chrome_slices_with_java_views
),
scheduler_tasks AS (
  SELECT
    id,
    name as task_name,
    "scheduler" as task_type
  FROM chrome_scheduler_tasks
),
-- Select scheduler tasks which are used to run mojo messages and use the mojo names
-- as full names for these slices.
-- We restrict this to specific scheduler tasks which are expected to run mojo
-- tasks due to sync mojo events, which also emit similar events.
scheduler_tasks_with_mojo AS (
  SELECT
    -- We use the "RunTask" as the task, and pick up the name from its child
    -- "Receive mojo message" event.
    task.id,
    receive_message.task_name,
    "mojo" AS task_type
  FROM
    chrome_scheduler_tasks task
  JOIN _DESCENDANT_MOJO_SLICE(task.id) receive_message
  WHERE
    task.posted_from IN (
      "mojo/public/cpp/system/simple_watcher.cc:Notify",
      "mojo/public/cpp/system/simple_watcher.cc:ArmOrNotify",
      "mojo/public/cpp/bindings/lib/connector.cc:PostDispatchNextMessageFromPipe",
      "ipc/ipc_mojo_bootstrap.cc:Accept")
),
navigation_tasks AS (
  WITH tasks_with_readable_names AS (
    SELECT
      id,
      _human_readable_navigation_task_name(task_name) as readable_name,
      IFNULL(_extract_frame_type(id), 'unknown frame type') as frame_type
    FROM
      scheduler_tasks_with_mojo
  )
  SELECT
    id,
    printf("%s (%s)", readable_name, frame_type) as task_name,
    'navigation_task' AS task_type
  FROM tasks_with_readable_names
  WHERE readable_name IS NOT NULL
),
-- Add scheduler and mojo full names to non-embedded slices from
-- the "toplevel" category, with mojo ones taking precedence.
non_embedded_toplevel_slices_with_task_name AS (
  SELECT
    task.id AS id,
    COALESCE(
        navigation.task_name,
        java_views.task_name,
        mojo.task_name,
        scheduler.task_name,
        task.name
    ) AS name,
    COALESCE(
        navigation.task_type,
        java_views.task_type,
        mojo.task_type,
        scheduler.task_type,
        "other"
    ) AS task_type
  FROM non_embedded_toplevel_slices task
  LEFT JOIN scheduler_tasks_with_mojo mojo ON mojo.id = task.id
  LEFT JOIN scheduler_tasks scheduler ON scheduler.id = task.id
  LEFT JOIN java_views_tasks java_views ON java_views.id = task.id
  LEFT JOIN navigation_tasks navigation ON navigation.id = task.id
)
-- Merge slices from toplevel and Java categories.
SELECT * FROM non_embedded_toplevel_slices_with_task_name
UNION ALL
SELECT * FROM non_embedded_java_slices
ORDER BY id;

-- A list of "Chrome tasks": top-level execution units (e.g. scheduler tasks /
-- IPCs / system callbacks) run by Chrome. For a given thread, the slices
-- corresponding to these tasks will not intersect.
CREATE PERFETTO VIEW chrome_tasks(
  -- Id for the given task, also the id of the slice this task corresponds to.
  id INT,
  -- Name for the given task.
  name STRING,
  -- Type of the task (e.g. "scheduler").
  task_type STRING,
  -- Thread name.
  thread_name STRING,
  -- Utid.
  utid INT,
  -- Process name.
  process_name STRING,
  -- Upid.
  upid INT,
  -- Alias of |slice.ts|.
  ts INT,
  -- Alias of |slice.dur|.
  dur INT,
  -- Alias of |slice.track_id|.
  track_id INT,
  -- Alias of |slice.category|.
  category INT,
  -- Alias of |slice.arg_set_id|.
  arg_set_id INT,
  -- Alias of |slice.thread_ts|.
  thread_ts INT,
  -- Alias of |slice.thread_dur|.
  thread_dur INT,
  -- STRING    Legacy alias for |name|.
  full_name STRING
) AS
SELECT
  cti.id,
  cti.name,
  task_type,
  thread.name AS thread_name,
  thread.utid,
  process.name AS process_name,
  thread.upid,
  s.ts,
  s.dur,
  s.track_id,
  s.category,
  s.arg_set_id,
  s.thread_ts,
  s.thread_dur,
  cti.name as full_name
FROM _chrome_tasks cti
JOIN slice s ON cti.id = s.id
JOIN thread_track tt ON s.track_id = tt.id
JOIN thread USING (utid)
JOIN process USING (upid);
