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

INCLUDE PERFETTO MODULE android.process_metadata;

INCLUDE PERFETTO MODULE android.startup.startups_maxsdk28;

INCLUDE PERFETTO MODULE android.startup.startups_minsdk29;

INCLUDE PERFETTO MODULE android.startup.startups_minsdk33;

INCLUDE PERFETTO MODULE android.version;

CREATE PERFETTO TABLE _android_startups_raw AS
WITH
  version AS (
    SELECT
      CASE
        WHEN _android_sdk_version() >= 33 THEN 33
        WHEN _android_sdk_version() >= 29 THEN 29
        WHEN _android_sdk_version() IS NOT NULL THEN 28
        WHEN (SELECT count() FROM slice WHERE name GLOB 'launchingActivity#*:*')
        > 0 THEN 33
        WHEN (SELECT count() FROM slice WHERE name GLOB 'MetricsLogger:*') > 0 THEN 29
        ELSE 28
      END AS v
  )
SELECT _auto_id AS startup_id, ts, ts_end, dur, package, startup_type
FROM _startups_maxsdk28
WHERE
  (SELECT v FROM version) = 28
UNION ALL
SELECT _auto_id AS startup_id, ts, ts_end, dur, package, startup_type
FROM _startups_minsdk29
WHERE
  (SELECT v FROM version) = 29
UNION ALL
SELECT startup_id, ts, ts_end, dur, package, startup_type
FROM _startups_minsdk33
WHERE
  (SELECT v FROM version) = 33;

-- Create a table containing only the slices which are necessary for determining
-- whether a startup happened.
CREATE PERFETTO TABLE _startup_indicator_slices AS
SELECT ts, name, track_id
FROM slice
WHERE
  name IN ('bindApplication', 'activityStart', 'activityResume');

CREATE PERFETTO FUNCTION _startup_indicator_slice_count(
  start_ts TIMESTAMP,
  end_ts TIMESTAMP,
  utid JOINID(thread.id),
  name STRING
)
RETURNS LONG
AS
SELECT count(1)
FROM thread_track AS t
JOIN _startup_indicator_slices AS s ON s.track_id = t.id
WHERE
  t.utid = $utid
  AND s.ts >= $start_ts
  AND s.ts < $end_ts
  AND s.name = $name;

-- Maps a startup to the set of processes that handled the activity start.
--
-- The vast majority of cases should be a single process. However it is
-- possible that the process dies during the activity startup and is respawned.
CREATE PERFETTO TABLE android_startup_processes(
  -- Startup id.
  startup_id JOINID(android_startups.startup_id),
  -- Upid of process on which activity started.
  upid JOINID(process.id),
  -- Pid of process on which activity started.
  pid LONG,
  -- Type of the startup.
  startup_type STRING
)
AS
-- This is intentionally a materialized query. For some reason, if we don't
-- materialize, we end up with a query which is an order of magnitude slower :(
WITH
  startup_with_type AS MATERIALIZED (
    SELECT
      startup_id,
      upid,
      pid,
      CASE
        -- type parsed from platform event takes precedence if available
        WHEN startup_type IS NOT NULL THEN startup_type
        WHEN bind_app > 0
        AND a_start > 0
        AND a_resume > 0 THEN 'cold'
        WHEN a_start > 0
        AND a_resume > 0 THEN 'warm'
        WHEN a_resume > 0 THEN 'hot'
        ELSE NULL
      END AS startup_type
    FROM (
      SELECT
        l.startup_id,
        l.startup_type,
        p.upid,
        p.pid,
        _startup_indicator_slice_count(
          l.ts,
          l.ts_end,
          t.utid,
          'bindApplication'
        ) AS bind_app,
        _startup_indicator_slice_count(l.ts, l.ts_end, t.utid, 'activityStart') AS a_start,
        _startup_indicator_slice_count(l.ts, l.ts_end, t.utid, 'activityResume') AS a_resume
      FROM _android_startups_raw AS l
      JOIN android_process_metadata AS p
        ON (l.package = p.package_name
        -- If the package list data source was not enabled in the trace, nothing
        -- will match the above constraint so also match any process whose name
        -- is a prefix of the package name.
        OR ((SELECT count(1) = 0 FROM package_list)
        AND p.process_name GLOB l.package || '*'))
      JOIN thread AS t
        ON (p.upid = t.upid AND t.is_main_thread)
      -- Filter out the non-startup processes with the same package name as that of a startup.
      WHERE
        a_resume > 0
    )
  )
SELECT * FROM startup_with_type WHERE startup_type IS NOT NULL;

-- All activity startups in the trace by startup id.
-- Populated by different scripts depending on the platform version/contents.
CREATE PERFETTO VIEW android_startups(
  -- Startup id.
  startup_id ID,
  -- Timestamp of startup start.
  ts TIMESTAMP,
  -- Timestamp of startup end.
  ts_end TIMESTAMP,
  -- Startup duration.
  dur DURATION,
  -- Package name.
  package STRING,
  -- Startup type.
  startup_type STRING
)
AS
SELECT
  r.startup_id,
  r.ts,
  r.ts_end,
  r.dur,
  r.package,
  coalesce(r.startup_type, max(p.startup_type)) AS startup_type
FROM _android_startups_raw AS r
LEFT JOIN android_startup_processes AS p USING (startup_id)
GROUP BY
  r.startup_id;

-- Maps a startup to the set of threads on processes that handled the
-- activity start.
CREATE PERFETTO VIEW android_startup_threads(
  -- Startup id.
  startup_id JOINID(android_startups.startup_id),
  -- Timestamp of start.
  ts TIMESTAMP,
  -- Duration of startup.
  dur DURATION,
  -- Upid of process involved in startup.
  upid JOINID(process.id),
  -- Pid if process involved in startup.
  pid LONG,
  -- Utid of the thread.
  utid JOINID(thread.id),
  -- Tid of the thread.
  tid LONG,
  -- Name of the thread.
  thread_name STRING,
  -- Thread is a main thread.
  is_main_thread BOOL
)
AS
SELECT
  startups.startup_id,
  startups.ts,
  startups.dur,
  android_startup_processes.upid,
  android_startup_processes.pid,
  thread.utid,
  thread.tid,
  thread.name AS thread_name,
  thread.is_main_thread AS is_main_thread
FROM android_startups AS startups
JOIN android_startup_processes USING (startup_id)
JOIN thread USING (upid);

---
--- Functions
---

-- All the slices for all startups in trace.
--
-- Generally, this view should not be used. Instead, use one of the view functions related
-- to the startup slices which are created from this table.
CREATE PERFETTO VIEW android_thread_slices_for_all_startups(
  -- Timestamp of startup.
  startup_ts TIMESTAMP,
  -- Timestamp of startup end.
  startup_ts_end TIMESTAMP,
  -- Startup id.
  startup_id JOINID(android_startups.startup_id),
  -- UTID of thread with slice.
  utid JOINID(thread.id),
  --Tid of thread.
  tid LONG,
  -- Name of thread.
  thread_name STRING,
  -- Whether it is main thread.
  is_main_thread BOOL,
  -- Arg set id.
  arg_set_id ARGSETID,
  -- Slice id.
  slice_id JOINID(slice.id),
  -- Name of slice.
  slice_name STRING,
  -- Timestamp of slice start.
  slice_ts TIMESTAMP,
  -- Slice duration.
  slice_dur DURATION
)
AS
SELECT
  st.ts AS startup_ts,
  st.ts + st.dur AS startup_ts_end,
  st.startup_id,
  st.utid,
  st.tid,
  st.thread_name,
  st.is_main_thread,
  slice.arg_set_id,
  slice.id AS slice_id,
  slice.name AS slice_name,
  slice.ts AS slice_ts,
  slice.dur AS slice_dur
FROM android_startup_threads AS st
JOIN thread_track USING (utid)
JOIN slice
  ON (slice.track_id = thread_track.id)
WHERE
  slice.ts BETWEEN st.ts AND st.ts + st.dur;

-- Given a startup id and GLOB for a slice name, returns matching slices with data.
CREATE PERFETTO FUNCTION android_slices_for_startup_and_slice_name(
  -- Startup id.
  startup_id JOINID(android_startups.startup_id),
  -- Glob of the slice.
  slice_name STRING
)
RETURNS TABLE(
  -- Id of the slice.
  slice_id JOINID(slice.id),
  -- Name of the slice.
  slice_name STRING,
  -- Timestamp of start of the slice.
  slice_ts TIMESTAMP,
  -- Duration of the slice.
  slice_dur DURATION,
  -- Name of the thread with the slice.
  thread_name STRING,
  -- Tid of the thread with the slice.
  tid LONG,
  -- Arg set id.
  arg_set_id ARGSETID
)
AS
SELECT slice_id, slice_name, slice_ts, slice_dur, thread_name, tid, arg_set_id
FROM android_thread_slices_for_all_startups
WHERE
  startup_id = $startup_id
  AND slice_name GLOB $slice_name;

-- A Perfetto view that lists matching slices for class loading during app startup.
CREATE PERFETTO VIEW android_class_loading_for_startup(
  -- Id of the slice.
  slice_id JOINID(slice.id),
  -- Startup id.
  startup_id JOINID(android_startups.startup_id),
  -- Name of the slice.
  slice_name STRING,
  -- Timestamp of start of the slice.
  slice_ts TIMESTAMP,
  -- Duration of the slice.
  slice_dur DURATION,
  -- Name of the thread with the slice.
  thread_name STRING,
  -- Tid of the thread with the slice.
  tid LONG,
  -- Arg set id.
  arg_set_id ARGSETID
)
AS
SELECT
  slice_id,
  startup_id,
  slice_name,
  slice_ts,
  slice_dur,
  thread_name,
  tid,
  arg_set_id
FROM android_thread_slices_for_all_startups
WHERE
  slice_name GLOB "L*;";

-- Returns binder transaction slices for a given startup id with duration over threshold.
CREATE PERFETTO FUNCTION android_binder_transaction_slices_for_startup(
  -- Startup id.
  startup_id JOINID(android_startups.startup_id),
  -- Only return slices with duration over threshold.
  threshold DOUBLE
)
RETURNS TABLE(
  -- Slice id.
  id LONG,
  -- Slice duration.
  slice_dur DURATION,
  -- Name of the thread with slice.
  thread_name STRING,
  -- Name of the process with slice.
  process STRING,
  -- Arg set id.
  arg_set_id ARGSETID,
  -- Whether is main thread.
  is_main_thread BOOL
)
AS
SELECT
  slice_id AS id,
  slice_dur,
  thread_name,
  process.name AS process,
  s.arg_set_id,
  is_main_thread
FROM android_thread_slices_for_all_startups AS s
JOIN process
  ON (extract_arg(s.arg_set_id, "destination process") = process.pid)
WHERE
  startup_id = $startup_id
  AND slice_name GLOB "binder transaction"
  AND slice_dur > $threshold;

-- Returns duration of startup for slice name.
--
-- Sums duration of all slices of startup with provided name.
CREATE PERFETTO FUNCTION android_sum_dur_for_startup_and_slice(
  -- Startup id.
  startup_id JOINID(android_startups.startup_id),
  -- Slice name.
  slice_name STRING
)
-- Sum of duration.
RETURNS LONG
AS
SELECT sum(slice_dur)
FROM android_thread_slices_for_all_startups
WHERE
  startup_id = $startup_id
  AND slice_name GLOB $slice_name;

-- Returns duration of startup for slice name on main thread.
--
-- Sums duration of all slices of startup with provided name only on main thread.
CREATE PERFETTO FUNCTION android_sum_dur_on_main_thread_for_startup_and_slice(
  -- Startup id.
  startup_id JOINID(android_startups.startup_id),
  -- Slice name.
  slice_name STRING
)
-- Sum of duration.
RETURNS LONG
AS
SELECT sum(slice_dur)
FROM android_thread_slices_for_all_startups
WHERE
  startup_id = $startup_id
  AND slice_name GLOB $slice_name
  AND is_main_thread;
