diff --git a/Android.bp b/Android.bp index a17dd76e7e..45184bfc61 100644 --- a/Android.bp +++ b/Android.bp @@ -13782,6 +13782,7 @@ genrule { "src/trace_processor/perfetto_sql/stdlib/android/auto/multiuser.sql", "src/trace_processor/perfetto_sql/stdlib/android/battery.sql", "src/trace_processor/perfetto_sql/stdlib/android/battery/charging_states.sql", + "src/trace_processor/perfetto_sql/stdlib/android/battery/doze.sql", "src/trace_processor/perfetto_sql/stdlib/android/battery_stats.sql", "src/trace_processor/perfetto_sql/stdlib/android/binder.sql", "src/trace_processor/perfetto_sql/stdlib/android/binder_breakdown.sql", @@ -13822,6 +13823,7 @@ genrule { "src/trace_processor/perfetto_sql/stdlib/android/oom_adjuster.sql", "src/trace_processor/perfetto_sql/stdlib/android/power_rails.sql", "src/trace_processor/perfetto_sql/stdlib/android/process_metadata.sql", + "src/trace_processor/perfetto_sql/stdlib/android/screen_state.sql", "src/trace_processor/perfetto_sql/stdlib/android/screenshots.sql", "src/trace_processor/perfetto_sql/stdlib/android/services.sql", "src/trace_processor/perfetto_sql/stdlib/android/slices.sql", diff --git a/BUILD b/BUILD index 41e7b6a9b2..4903704948 100644 --- a/BUILD +++ b/BUILD @@ -3011,6 +3011,7 @@ perfetto_filegroup( name = "src_trace_processor_perfetto_sql_stdlib_android_battery_battery", srcs = [ "src/trace_processor/perfetto_sql/stdlib/android/battery/charging_states.sql", + "src/trace_processor/perfetto_sql/stdlib/android/battery/doze.sql", ], ) @@ -3127,6 +3128,7 @@ perfetto_filegroup( "src/trace_processor/perfetto_sql/stdlib/android/oom_adjuster.sql", "src/trace_processor/perfetto_sql/stdlib/android/power_rails.sql", "src/trace_processor/perfetto_sql/stdlib/android/process_metadata.sql", + "src/trace_processor/perfetto_sql/stdlib/android/screen_state.sql", "src/trace_processor/perfetto_sql/stdlib/android/screenshots.sql", "src/trace_processor/perfetto_sql/stdlib/android/services.sql", "src/trace_processor/perfetto_sql/stdlib/android/slices.sql", diff --git a/src/trace_processor/perfetto_sql/stdlib/android/BUILD.gn b/src/trace_processor/perfetto_sql/stdlib/android/BUILD.gn index d3538db284..96d78fa301 100644 --- a/src/trace_processor/perfetto_sql/stdlib/android/BUILD.gn +++ b/src/trace_processor/perfetto_sql/stdlib/android/BUILD.gn @@ -49,6 +49,7 @@ perfetto_sql_source_set("android") { "oom_adjuster.sql", "power_rails.sql", "process_metadata.sql", + "screen_state.sql", "screenshots.sql", "services.sql", "slices.sql", diff --git a/src/trace_processor/perfetto_sql/stdlib/android/battery/BUILD.gn b/src/trace_processor/perfetto_sql/stdlib/android/battery/BUILD.gn index 828c10b747..661e3150e7 100644 --- a/src/trace_processor/perfetto_sql/stdlib/android/battery/BUILD.gn +++ b/src/trace_processor/perfetto_sql/stdlib/android/battery/BUILD.gn @@ -15,5 +15,8 @@ import("../../../../../../gn/perfetto_sql.gni") perfetto_sql_source_set("battery") { - sources = [ "charging_states.sql" ] + sources = [ + "charging_states.sql", + "doze.sql", + ] } diff --git a/src/trace_processor/perfetto_sql/stdlib/android/battery/charging_states.sql b/src/trace_processor/perfetto_sql/stdlib/android/battery/charging_states.sql index 4cbd579e25..d7530665a2 100644 --- a/src/trace_processor/perfetto_sql/stdlib/android/battery/charging_states.sql +++ b/src/trace_processor/perfetto_sql/stdlib/android/battery/charging_states.sql @@ -12,6 +12,7 @@ -- 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 counters.intervals; -- Device charging states. @@ -23,49 +24,53 @@ CREATE PERFETTO TABLE android_charging_states( ts TIMESTAMP, -- Duration of the device charging state. dur DURATION, + -- One of: charging, discharging, not_charging, full, unknown. + short_charging_state STRING, -- Device charging state, one of: Charging, Discharging, Not charging -- (when the charger is present but battery is not charging), -- Full, Unknown charging_state STRING -) AS SELECT - id, - ts, - dur, - charging_state - FROM ( - WITH - _counter AS ( - SELECT counter.id, ts, 0 AS track_id, value - FROM counter - JOIN counter_track - ON counter_track.id = counter.track_id - WHERE counter_track.name = 'BatteryStatus' - ) - SELECT - id, - ts, - dur, - CASE - value - -- 0 and 1 are both 'Unknown' - WHEN 2 THEN 'Charging' - WHEN 3 THEN 'Discharging' - -- special case when charger is present but battery isn't charging - WHEN 4 THEN 'Not charging' - WHEN 5 THEN 'Full' - ELSE 'Unknown' - END AS charging_state - FROM counter_leading_intervals !(_counter) - WHERE dur > 0 - -- Either the above select statement is populated or the - -- select statement after the union is populated but not both. - UNION - -- When the trace does not have a slice in the charging state track then - -- we will assume that the charging state for the entire trace is Unknown. - -- This ensures that we still have job data even if the charging state is - -- not known. The following statement will only ever return a single row. - SELECT 1, TRACE_START(), TRACE_DUR(), 'Unknown' - WHERE NOT EXISTS ( - SELECT * FROM _counter - ) AND TRACE_DUR() > 0 - ); +) AS +WITH + _counter AS ( + SELECT counter.id, ts, 0 AS track_id, value + FROM counter + JOIN counter_track + ON counter_track.id = counter.track_id + WHERE counter_track.name = 'BatteryStatus' + ) +SELECT + id, + ts, + dur, + CASE + value + WHEN 2 THEN 'charging' + WHEN 3 THEN 'discharging' + WHEN 4 THEN 'not_charging' + WHEN 5 THEN 'full' + ELSE 'unknown' + END AS short_charging_state, + CASE + value + -- 0 and 1 are both 'Unknown' + WHEN 2 THEN 'Charging' + WHEN 3 THEN 'Discharging' + -- special case when charger is present but battery isn't charging + WHEN 4 THEN 'Not charging' + WHEN 5 THEN 'Full' + ELSE 'Unknown' + END AS charging_state +FROM counter_leading_intervals !(_counter) +WHERE dur > 0 +-- Either the above select statement is populated or the +-- select statement after the union is populated but not both. +UNION +-- When the trace does not have a slice in the charging state track then +-- we will assume that the charging state for the entire trace is Unknown. +-- This ensures that we still have job data even if the charging state is +-- not known. The following statement will only ever return a single row. +SELECT 1, TRACE_START(), TRACE_DUR(), 'unknown', 'Unknown' +WHERE NOT EXISTS ( + SELECT * FROM _counter +) AND TRACE_DUR() > 0; diff --git a/src/trace_processor/perfetto_sql/stdlib/android/battery/doze.sql b/src/trace_processor/perfetto_sql/stdlib/android/battery/doze.sql new file mode 100644 index 0000000000..eae080ebda --- /dev/null +++ b/src/trace_processor/perfetto_sql/stdlib/android/battery/doze.sql @@ -0,0 +1,96 @@ +-- Copyright 2025 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 counters.intervals; + +-- Light idle states. This is the state machine that quickly detects the +-- device is unused and restricts background activity. +-- See https://developer.android.com/training/monitoring-device-state/doze-standby +CREATE PERFETTO TABLE android_light_idle_state( + -- ID + id LONG, + -- Timestamp. + ts TIMESTAMP, + -- Duration. + dur DURATION, + -- Description of the light idle state. + light_idle_state STRING +) AS +WITH _counter AS ( + SELECT counter.id, ts, 0 AS track_id, value + FROM counter + JOIN counter_track ON counter_track.id = counter.track_id + WHERE name = 'DozeLightState' +) +SELECT + id, + ts, + dur, + CASE value + -- device is used or on power + WHEN 0 THEN 'active' + -- device is waiting to go idle + WHEN 1 THEN 'inactive' + -- device is idle + WHEN 4 THEN 'idle' + -- waiting for connectivity before maintenance + WHEN 5 THEN 'waiting_for_network' + -- maintenance running + WHEN 6 THEN 'idle_maintenance' + -- device has gone deep idle, light idle state irrelevant + WHEN 7 THEN 'override' + ELSE 'unmapped' + END AS light_idle_state +FROM counter_leading_intervals!(_counter); + +-- Deep idle states. This is the state machine that more slowly detects deeper +-- levels of device unuse and restricts background activity further. +-- See https://developer.android.com/training/monitoring-device-state/doze-standby +CREATE PERFETTO TABLE android_deep_idle_state( + -- ID + id LONG, + -- Timestamp. + ts TIMESTAMP, + -- Duration. + dur DURATION, + -- Description of the deep idle state. + deep_idle_state STRING +) AS +WITH _counter AS ( + SELECT counter.id, ts, 0 AS track_id, value + FROM counter + JOIN counter_track ON counter_track.id = counter.track_id + WHERE name = 'DozeDeepState' +) +SELECT + id, + ts, + dur, + CASE value + WHEN 0 THEN 'active' + WHEN 1 THEN 'inactive' + -- waiting for next idle period + WHEN 2 THEN 'idle_pending' + -- device is sensing motion + WHEN 3 THEN 'sensing' + -- device is finding location + WHEN 4 THEN 'locating' + WHEN 5 THEN 'idle' + WHEN 6 THEN 'idle_maintenance' + -- inactive, should go straight to idle without motion / location + -- sensing. + WHEN 7 THEN 'quick_doze_delay' + ELSE 'unmapped' + END AS deep_idle_state +FROM counter_leading_intervals!(_counter); diff --git a/src/trace_processor/perfetto_sql/stdlib/android/job_scheduler_states.sql b/src/trace_processor/perfetto_sql/stdlib/android/job_scheduler_states.sql index 807eefab65..66e35aa820 100644 --- a/src/trace_processor/perfetto_sql/stdlib/android/job_scheduler_states.sql +++ b/src/trace_processor/perfetto_sql/stdlib/android/job_scheduler_states.sql @@ -15,47 +15,9 @@ INCLUDE PERFETTO MODULE counters.intervals; INCLUDE PERFETTO MODULE android.battery.charging_states; +INCLUDE PERFETTO MODULE android.screen_state; INCLUDE PERFETTO MODULE intervals.intersect; -CREATE PERFETTO TABLE _screen_states AS -SELECT - id, - ts, - dur, - screen_state -FROM ( - WITH _screen_state_span AS ( - SELECT * - FROM counter_leading_intervals!(( - SELECT counter.id, ts, 0 AS track_id, value - FROM counter - JOIN counter_track ON counter_track.id = counter.track_id - WHERE name = 'ScreenState' - ))) SELECT - id, - ts, - dur, - CASE value - WHEN 1 THEN 'Screen off' - WHEN 2 THEN 'Screen on' - WHEN 3 THEN 'Always-on display (doze)' - ELSE 'Unknown' - END AS screen_state - FROM _screen_state_span - WHERE dur > 0 - -- Either the above select statement is populated or the - -- select statement after the union is populated but not both. - UNION - -- When the trace does not have a slice in the screen state track then - -- we will assume that the screen state for the entire trace is Unknown. - -- This ensures that we still have job data even if the screen state is - -- not known. The following statement will only ever return a single row. - SELECT 1, TRACE_START() as ts, TRACE_DUR() as dur, 'Unknown' - WHERE NOT EXISTS ( - SELECT * FROM _screen_state_span - ) AND TRACE_DUR() > 0 -); - CREATE PERFETTO TABLE _job_states AS SELECT t.id as track_id, @@ -182,11 +144,11 @@ SELECT c.charging_state, s.screen_state FROM _interval_intersect!( - (android_charging_states, _screen_states), + (android_charging_states, android_screen_state), () ) ii JOIN android_charging_states c ON c.id = ii.id_0 -JOIN _screen_states s ON s.id = ii.id_1; +JOIN android_screen_state s ON s.id = ii.id_1; -- This table returns constraint changes that a -- job will go through in a single trace. diff --git a/src/trace_processor/perfetto_sql/stdlib/android/screen_state.sql b/src/trace_processor/perfetto_sql/stdlib/android/screen_state.sql new file mode 100644 index 0000000000..a05f87bbf3 --- /dev/null +++ b/src/trace_processor/perfetto_sql/stdlib/android/screen_state.sql @@ -0,0 +1,88 @@ +-- Copyright 2025 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 counters.intervals; +INCLUDE PERFETTO MODULE intervals.intersect; + +-- Table of the screen state - on, off or doze (always on display). +CREATE PERFETTO TABLE android_screen_state( + -- ID. + id ID, + -- Timestamp. + ts TIMESTAMP, + -- Duration. + dur DURATION, + -- Simplified screen state: 'unknown', 'off', 'doze' (AoD) or 'on' + simple_screen_state STRING, + -- Full screen state, adding VR and suspended-while-displaying states. + short_screen_state STRING, + -- Human-readable string. + screen_state STRING +) AS +WITH screen_state_span AS ( + SELECT * + FROM counter_leading_intervals!(( + SELECT counter.id, ts, 0 AS track_id, value + FROM counter + JOIN counter_track ON counter_track.id = counter.track_id + WHERE name = 'ScreenState' + )) +) +-- Case when we have data. +SELECT + id, + ts, + dur, + -- Should be kept in sync with the enums in Display.java + CASE value + WHEN 1 THEN 'off' -- Display.STATE_OFF + WHEN 2 THEN 'on' -- Display.STATE_ON + WHEN 3 THEN 'doze' -- Display.STATE_DOZE + WHEN 4 THEN 'doze' -- Display.STATE_DOZE_SUSPEND + WHEN 5 THEN 'on' -- Display.STATE_VR + WHEN 6 THEN 'on' -- Display.STATE_ON_SUSPEND + ELSE 'unknown' + END AS simple_screen_state, + CASE value + WHEN 1 THEN 'off' -- Display.STATE_OFF + WHEN 2 THEN 'on' -- Display.STATE_ON + WHEN 3 THEN 'doze' -- Display.STATE_DOZE + WHEN 4 THEN 'doze-suspend' -- Display.STATE_DOZE_SUSPEND + WHEN 5 THEN 'on-vr' -- Display.STATE_VR + WHEN 6 THEN 'on-suspend' -- Display.STATE_ON_SUSPEND + ELSE 'unknown' + END AS short_screen_state, + CASE value + WHEN 1 THEN 'Screen off' -- Display.STATE_OFF + WHEN 2 THEN 'Screen on' -- Display.STATE_ON + WHEN 3 THEN 'Always-on display (doze)' -- Display.STATE_DOZE + WHEN 4 THEN 'Always-on display (doze-suspend)' -- Display.STATE_DOZE_SUSPEND + WHEN 5 THEN 'Screen on (VR)' -- Display.STATE_VR + WHEN 6 THEN 'Screen on (suspend)' -- Display.STATE_ON_SUSPEND + ELSE 'Unknown' + END AS screen_state + FROM screen_state_span +WHERE dur > 0 +UNION +-- Case when we do not have data. +SELECT + 1, + TRACE_START() as ts, + TRACE_DUR() as dur, + 'unknown' as simple_screen_state, + 'unknown' as short_screen_state, + 'Unknown' as screen_state +WHERE NOT EXISTS ( + SELECT * FROM screen_state_span +) AND TRACE_DUR() > 0; diff --git a/ui/src/plugins/dev.perfetto.AndroidLongBatteryTracing/index.ts b/ui/src/plugins/dev.perfetto.AndroidLongBatteryTracing/index.ts index e45ea55e07..fc3ccfe902 100644 --- a/ui/src/plugins/dev.perfetto.AndroidLongBatteryTracing/index.ts +++ b/ui/src/plugins/dev.perfetto.AndroidLongBatteryTracing/index.ts @@ -301,95 +301,6 @@ const SUSPEND_RESUME = ` FROM android_suspend_state WHERE power_state = 'suspended'`; -const SCREEN_STATE = ` - WITH _counter AS ( - SELECT counter.id, ts, 0 AS track_id, value - FROM counter - JOIN counter_track ON counter_track.id = counter.track_id - WHERE name = 'ScreenState' - ) - SELECT - ts, - dur, - CASE value - -- Should be kept in sync with the enums in Display.java - WHEN 1 THEN 'Screen off' -- Display.STATE_OFF - WHEN 2 THEN 'Screen on' -- Display.STATE_ON - WHEN 3 THEN 'Always-on display (doze)' -- Display.STATE_DOZE - WHEN 4 THEN 'Always-on display (doze-suspend)' -- Display.STATE_DOZE_SUSPEND - WHEN 5 THEN 'Screen on (VR)' -- Display.STATE_VR - WHEN 6 THEN 'Screen on (suspend)' -- Display.STATE_ON_SUSPEND - ELSE 'unknown' - END AS name - FROM counter_leading_intervals!(_counter)`; - -// See DeviceIdleController.java for where these states come from and how -// they transition. -const DOZE_LIGHT = ` - WITH _counter AS ( - SELECT counter.id, ts, 0 AS track_id, value - FROM counter - JOIN counter_track ON counter_track.id = counter.track_id - WHERE name = 'DozeLightState' - ) - SELECT - ts, - dur, - CASE value - WHEN 0 THEN 'active' - WHEN 1 THEN 'inactive' - WHEN 4 THEN 'idle' - WHEN 5 THEN 'waiting_for_network' - WHEN 6 THEN 'idle_maintenance' - WHEN 7 THEN 'override' - ELSE 'unknown' - END AS name - FROM counter_leading_intervals!(_counter)`; - -const DOZE_DEEP = ` - WITH _counter AS ( - SELECT counter.id, ts, 0 AS track_id, value - FROM counter - JOIN counter_track ON counter_track.id = counter.track_id - WHERE name = 'DozeDeepState' - ) - SELECT - ts, - dur, - CASE value - WHEN 0 THEN 'active' - WHEN 1 THEN 'inactive' - WHEN 2 THEN 'idle_pending' - WHEN 3 THEN 'sensing' - WHEN 4 THEN 'locating' - WHEN 5 THEN 'idle' - WHEN 6 THEN 'idle_maintenance' - WHEN 7 THEN 'quick_doze_delay' - ELSE 'unknown' - END AS name - FROM counter_leading_intervals!(_counter)`; - -const CHARGING = ` - WITH _counter AS ( - SELECT counter.id, ts, 0 AS track_id, value - FROM counter - JOIN counter_track ON counter_track.id = counter.track_id - WHERE name = 'BatteryStatus' - ) - SELECT - ts, - dur, - CASE value - -- 0 and 1 are both unknown - WHEN 2 THEN 'Charging' - WHEN 3 THEN 'Discharging' - -- special case when charger is present but battery isn't charging - WHEN 4 THEN 'Not charging' - WHEN 5 THEN 'Full' - ELSE 'unknown' - END AS name - FROM counter_leading_intervals!(_counter)`; - const THERMAL_THROTTLING = ` with step1 as ( select @@ -1044,17 +955,35 @@ export default class implements PerfettoPlugin { const e = ctx.engine; await e.query(`INCLUDE PERFETTO MODULE android.battery_stats;`); await e.query(`INCLUDE PERFETTO MODULE android.suspend;`); - await e.query(`INCLUDE PERFETTO MODULE counters.intervals;`); + await e.query(`INCLUDE PERFETTO MODULE android.battery.charging_states;`); + await e.query(`INCLUDE PERFETTO MODULE android.battery.doze;`); + await e.query(`INCLUDE PERFETTO MODULE android.screen_state;`); - await this.addSliceTrack(ctx, 'Device State: Screen state', SCREEN_STATE); - await this.addSliceTrack(ctx, 'Device State: Charging', CHARGING); + await this.addSliceTrack( + ctx, + 'Device State: Screen state', + `SELECT ts, dur, screen_state AS name FROM android_screen_state`, + ); + await this.addSliceTrack( + ctx, + 'Device State: Charging', + `SELECT ts, dur, charging_state AS name FROM android_charging_states`, + ); await this.addSliceTrack( ctx, 'Device State: Suspend / resume', SUSPEND_RESUME, ); - await this.addSliceTrack(ctx, 'Device State: Doze light state', DOZE_LIGHT); - await this.addSliceTrack(ctx, 'Device State: Doze deep state', DOZE_DEEP); + await this.addSliceTrack( + ctx, + 'Device State: Doze light state', + `SELECT ts, dur, light_idle_state AS name FROM android_light_idle_state`, + ); + await this.addSliceTrack( + ctx, + 'Device State: Doze deep state', + `SELECT ts, dur, deep_idle_state AS name FROM android_deep_idle_state`, + ); query('Device State: Top app', 'battery_stats.top');