chromium/tools/perf/contrib/power/cpu_powerups.sql

-- Find causes for CPUs powering up.
--
-- Copyright 2022 The Chromium Authors
--
-- Use of this source code is governed by a BSD-style license that can be
-- found in the LICENSE file.

-- The scripts below analyse traces with the following tracing options
-- enabled:
--
--  - Linux kernel:
---    "power/*", "sched/*", "task/*",
--  - Chromium:
--      "toplevel", "toplevel.flow".

-- Noteworthy tables:
--
--   cpu_power_first_top_level_slice_after_powerup :: The top-level slices
--      that ran after a CPU power-up.

-- The CPU power transitions in the trace.
--
-- Schema:
--   ts          : The timestamp at the start of the slice.
--   dur         : The duration of the slice.
--   cpu         : The CPU on which the transition occurred
--   power_state : The power state that the CPU was in at time 'ts' for
--                 duration 'dur'.
--   previous_power_state : The power state that the CPU was previously in.
--   powerup_id  : A unique ID for the CPU power-up.
--
-- Power states are encoded as non-negative integers, with zero representing
-- full-power operation and positive values representing increasingly deep
-- sleep states.
--
-- On ARM systems, power state 1 represents the WFI (Wait For Interrupt) sleep
-- state that the CPU enters while idle.
DROP VIEW IF EXISTS cpu_power_slice;
CREATE VIEW cpu_power_slice AS
  WITH cpu_power_states AS (
    SELECT
      c.id AS id,
      cct.cpu AS cpu,
      c.ts AS ts,
      -- Encode the 'value' field as a power state.
      CAST((CASE c.value WHEN 4294967295 THEN 0 ELSE c.value + 1 END)
        AS INT) AS power_state
    FROM counter AS c
    JOIN cpu_counter_track AS cct
      ON c.track_id = cct.id
    WHERE cct.name = 'cpuidle'
  )
  SELECT *
  FROM (
    SELECT
      -- Fields named 'ts' and 'dur' are needed for span joins.
      ts,
      LEAD(ts) OVER (PARTITION BY cpu ORDER BY ts ASC) - ts
        AS dur,
      LEAD(ts) OVER (PARTITION BY cpu ORDER BY ts ASC) - ts
        AS power_duration,
      cpu,
      power_state,
      LAG(power_state) OVER (PARTITION BY cpu ORDER BY ts ASC)
        AS previous_power_state,
      id AS powerup_id
    FROM cpu_power_states
  )
  WHERE dur IS NOT NULL
    AND previous_power_state IS NOT NULL
    AND power_state = 0                      -- Track full-power states.
    AND power_state != previous_power_state  -- Skip missing spans.
    ORDER BY ts ASC;

-- We do not want scheduler slices with utid = 0 (the 'swapper' kernel thread).
DROP VIEW IF EXISTS cpu_power_valid_sched_slice;
CREATE VIEW cpu_power_valid_sched_slice AS
  SELECT *
  FROM sched_slice
  WHERE utid != 0;

-- Join scheduler slices with the spans with CPU power slices.
--
-- There multiple scheduler slices could fall into one CPU power slice.
--
---  CPU Power:
--   |----------------------------|....................|---------|
--   A       <cpu active>         B     <cpu idling>   C         D

--   Scheduler slices on that CPU:
--     |-----T1-----| |....T2....|                      |---T3--|
--     E            F G          H                      I       J
--
-- Here threads T1 and T2 executed in CPU power slice [A,B].  The
-- time between F and G represents time between threads in the kernel.
DROP TABLE IF EXISTS cpu_power_and_sched_slice;
CREATE VIRTUAL TABLE cpu_power_and_sched_slice
USING
  SPAN_JOIN(cpu_power_slice PARTITIONED cpu,
            cpu_power_valid_sched_slice PARTITIONED cpu);

-- The Linux scheduler slices that executed immediately after a
-- CPU power up.
--
-- Schema:
--   ts         : The timestamp at the start of the slice.
--   dur        : The duration of the slice.
--   cpu        : The cpu on which the slice executed.
--   sched_id   : Id for the sched_slice table.
--   utid       : Unique id for the thread that ran within the slice.
--   previous_power_state : The CPU's power state before this slice.
--   powerup_id : The id of the power-up slice.
--   power_duration : The duration of the underlying power slice.
DROP VIEW IF EXISTS cpu_power_first_sched_slice_after_powerup;
CREATE VIEW cpu_power_first_sched_slice_after_powerup AS
  SELECT
    ts,
    dur,
    cpu,
    id,
    utid,
    previous_power_state,
    powerup_id,
    power_duration
  FROM cpu_power_and_sched_slice
  WHERE power_state = 0     -- Power-ups only.
  GROUP BY cpu, powerup_id
  HAVING ts = MIN(ts)       -- There will only be one MIN sched slice
                            -- per CPU power up.
  ORDER BY ts ASC;

-- A view with counts of power-ups grouped by Linux process & thread.
--
-- Schema:
--   process_name   : The Linux process that ran after a power up.
--   thread_name    : The thread in the Linux process that powered up.
--   powerup_count  : The counts for the (process, thread) pair.
DROP VIEW IF EXISTS cpu_power_powerup_count_by_process_and_thread;
CREATE VIEW cpu_power_powerup_count_by_process_and_thread AS
  SELECT
    process.name AS process_name,
    thread.name AS thread_name,
    count() AS powerup_count
  FROM cpu_power_first_sched_slice_after_powerup
  JOIN thread using (utid)
  JOIN process using (upid)
  GROUP BY process_name, thread_name
  ORDER BY powerup_count DESC;

-- A view joining thread tracks and top-level slices.
--
-- This view is intended to be intersected by time with the scheduler
-- slices scheduled after a CPU power up.
--
-- Schema:
--   utid     : Thread unique id.
--   slice_id : The slice_id for the top-level slice.
--   ts       : Starting timestamp for the slice.
--   dur      : The duration for the slice.
DROP VIEW IF EXISTS cpu_power_thread_and_toplevel_slice;
CREATE VIEW cpu_power_thread_and_toplevel_slice AS
  SELECT t.utid, s.id AS slice_id, s.ts AS ts, s.dur AS dur
  FROM slice AS s
  JOIN thread_track AS t
    ON s.track_id = t.id
  WHERE s.depth = 0   -- Top-level slices only.
  ORDER BY ts ASC;

-- A table holding the slices that executed within the scheduler
-- slice that ran on a CPU immediately after power-up.
--
-- Schema:
--   ts       : Timestamp of the resulting slice
--   dur      : Duration of the slice.
--   cpu      : The CPU the sched slice ran on.
--   utid     : Unique thread id for the slice.
--   sched_id : 'id' field from the sched_slice table.
--   type     : From the sched_slice table, always 'sched_slice'.
--   end_state : The ending state for the sched_slice
--   priority : The kernel thread priority
--   slice_id : Id of the top-level slice for this (sched) slice.
DROP TABLE IF EXISTS cpu_power_post_powerup_slice;
CREATE VIRTUAL TABLE cpu_power_post_powerup_slice
USING
  SPAN_JOIN(cpu_power_first_sched_slice_after_powerup PARTITIONED utid,
            cpu_power_thread_and_toplevel_slice PARTITIONED utid);

-- The first top-level slice that ran after a CPU power-up.
DROP VIEW IF EXISTS cpu_power_first_top_level_slice_after_powerup;
CREATE VIEW cpu_power_first_top_level_slice_after_powerup AS
  SELECT slice_id, previous_power_state, power_duration
  FROM cpu_power_post_powerup_slice
  GROUP BY cpu, powerup_id
  HAVING ts = MIN(ts)
  ORDER BY ts ASC;

--
-- Chrome-specific analyses.
--

-- Bring in the Chrome tasks table.
SELECT RUN_METRIC("chrome/chrome_tasks.sql");

-- Rename fields of the 'chrome_tasks' table, so that a subsequent
-- 'span_join' can work.
DROP VIEW IF EXISTS chrome_tasks_view_with_renamed_fields;
CREATE VIEW chrome_tasks_view_with_renamed_fields AS
  SELECT
    ts,
    dur,
    slice_id,
    name AS slice_name,
    full_name,
    task_type,
    utid,
    thread_name,
    process_name,
    upid
  FROM chrome_tasks;

-- A table with Chrome-related slices that executed within the scheduler
-- slice that ran on a CPU after a power-up.
--
-- Schema:
--   ts           : Timestamp of the slice.
--   dur          : Duration of the slice.
--   slice_id     : The ID of the Chrome slice in the 'slice' table.
--   slice_name   : The name of the slice.
--   full_name    : The complete name of the Chrome task that ran in the slice.
--   task_type    : The type of Chrome task that ran in the slice.
--   utid         : The thread in which this slice executed.
--   thread_name  : The name of the thread in which this slice executed.
--   upid         : The process in which the slice executed.
--   process_name : The name of the process in which this slice executed.
DROP TABLE IF EXISTS cpu_power_chrome_slices_after_powerup;
CREATE VIRTUAL TABLE cpu_power_chrome_slices_after_powerup
USING
  SPAN_JOIN(cpu_power_first_sched_slice_after_powerup PARTITIONED utid,
            chrome_tasks_view_with_renamed_fields PARTITIONED utid);

-- The first Chrome slice that executed after a CPU power-up.
DROP VIEW IF EXISTS cpu_power_first_chrome_slice_after_powerup;
CREATE VIEW cpu_power_first_chrome_slice_after_powerup AS
  SELECT
    *
  FROM cpu_power_chrome_slices_after_powerup
  GROUP BY cpu, powerup_id
  HAVING ts = MIN(ts)
  ORDER BY ts ASC;

SELECT "cpu_powerups"; -- Keep the Perfetto trace processor's |.read| happy.