Add view for Android BatteryStats events/counters
The counters are used for things that can only have one state at a time
(like screen is on or off). This turns the counters into spans with
human readable names.
The events can be used for things that happen once, or things with a
start and stop interval that either doesn't map well to the counters or
can have multiple overlapping (e.g. jobs). This converts such events
into spans.
Test: ui/run-dev-server and query metric
Change-Id: Ib600b9426482a22e6c1a393b983e17f9eac257d6
diff --git a/Android.bp b/Android.bp
index 66715f8..58d9a62 100644
--- a/Android.bp
+++ b/Android.bp
@@ -10358,6 +10358,7 @@
name: "perfetto_src_trace_processor_stdlib_gen_amalgamated_stdlib",
srcs: [
"src/trace_processor/stdlib/android/battery.sql",
+ "src/trace_processor/stdlib/android/battery_stats.sql",
"src/trace_processor/stdlib/android/binder.sql",
"src/trace_processor/stdlib/android/monitor_contention.sql",
"src/trace_processor/stdlib/android/process_metadata.sql",
diff --git a/BUILD b/BUILD
index 4579692..a0fcf69 100644
--- a/BUILD
+++ b/BUILD
@@ -2165,6 +2165,7 @@
name = "src_trace_processor_stdlib_android_android",
srcs = [
"src/trace_processor/stdlib/android/battery.sql",
+ "src/trace_processor/stdlib/android/battery_stats.sql",
"src/trace_processor/stdlib/android/binder.sql",
"src/trace_processor/stdlib/android/monitor_contention.sql",
"src/trace_processor/stdlib/android/process_metadata.sql",
diff --git a/src/trace_processor/stdlib/android/BUILD.gn b/src/trace_processor/stdlib/android/BUILD.gn
index 86bdf6d..c7f4a9f 100644
--- a/src/trace_processor/stdlib/android/BUILD.gn
+++ b/src/trace_processor/stdlib/android/BUILD.gn
@@ -18,6 +18,7 @@
deps = [ "startup" ]
sources = [
"battery.sql",
+ "battery_stats.sql",
"binder.sql",
"monitor_contention.sql",
"process_metadata.sql",
diff --git a/src/trace_processor/stdlib/android/battery_stats.sql b/src/trace_processor/stdlib/android/battery_stats.sql
new file mode 100644
index 0000000..753fd61
--- /dev/null
+++ b/src/trace_processor/stdlib/android/battery_stats.sql
@@ -0,0 +1,210 @@
+--
+-- Copyright 2023 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.
+
+SELECT IMPORT('common.timestamps');
+
+-- Converts a battery_stats counter value to human readable string.
+--
+-- @arg track STRING The counter track name (e.g. 'battery_stats.audio').
+-- @arg value LONG The counter value.
+-- @ret STRING The human-readable name for the counter value.
+SELECT CREATE_FUNCTION(
+ 'BATTERY_STATS_COUNTER_TO_STRING(track STRING, value LONG)',
+ 'STRING',
+ '
+ SELECT
+ CASE
+ WHEN ($track = "battery_stats.wifi_scan" OR
+ $track = "battery_stats.wifi_radio" OR
+ $track = "battery_stats.mobile_radio" OR
+ $track = "battery_stats.audio" OR
+ $track = "battery_stats.video" OR
+ $track = "battery_stats.camera" OR
+ $track = "battery_stats.power_save" OR
+ $track = "battery_stats.phone_in_call")
+ THEN
+ CASE $value
+ WHEN 0 THEN "inactive"
+ WHEN 1 THEN "active"
+ ELSE "unknown"
+ END
+ WHEN $track = "battery_stats.wifi"
+ THEN
+ CASE $value
+ WHEN 0 THEN "off"
+ WHEN 1 THEN "on"
+ ELSE "unknown"
+ END
+ WHEN $track = "battery_stats.phone_state"
+ THEN
+ CASE $value
+ WHEN 0 THEN "in"
+ WHEN 1 THEN "out"
+ WHEN 2 THEN "emergency"
+ WHEN 3 THEN "off"
+ ELSE "unknown"
+ END
+ WHEN ($track = "battery_stats.phone_signal_strength" OR
+ $track = "battery_stats.wifi_signal_strength")
+ THEN
+ CASE $value
+ WHEN 0 THEN "none"
+ WHEN 1 THEN "poor"
+ WHEN 2 THEN "moderate"
+ WHEN 3 THEN "good"
+ WHEN 4 THEN "great"
+ ELSE "unknown"
+ END
+ WHEN $track = "battery_stats.wifi_suppl"
+ THEN
+ CASE $value
+ WHEN 0 THEN "invalid"
+ WHEN 1 THEN "disconn"
+ WHEN 2 THEN "disabled"
+ WHEN 3 THEN "inactive"
+ WHEN 4 THEN "scanning"
+ WHEN 5 THEN "authenticating"
+ WHEN 6 THEN "associating"
+ WHEN 7 THEN "associated"
+ WHEN 8 THEN "4-way-handshake"
+ WHEN 9 THEN "group-handshake"
+ WHEN 10 THEN "completed"
+ WHEN 11 THEN "dormant"
+ WHEN 12 THEN "uninit"
+ ELSE "unknown"
+ END
+ WHEN $track = "battery_stats.data_conn"
+ THEN
+ CASE $value
+ WHEN 0 THEN "oos"
+ WHEN 1 THEN "gprs"
+ WHEN 2 THEN "edge"
+ WHEN 3 THEN "umts"
+ WHEN 4 THEN "cdma"
+ WHEN 5 THEN "evdo_0"
+ WHEN 6 THEN "evdo_A"
+ WHEN 7 THEN "1xrtt"
+ WHEN 8 THEN "hsdpa"
+ WHEN 9 THEN "hsupa"
+ WHEN 10 THEN "hspa"
+ WHEN 11 THEN "iden"
+ WHEN 12 THEN "evdo_b"
+ WHEN 13 THEN "lte"
+ WHEN 14 THEN "ehrpd"
+ WHEN 15 THEN "hspap"
+ WHEN 16 THEN "gsm"
+ WHEN 17 THEN "td_scdma"
+ WHEN 18 THEN "iwlan"
+ WHEN 19 THEN "lte_ca"
+ WHEN 20 THEN "nr"
+ WHEN 21 THEN "emngcy"
+ WHEN 22 THEN "other"
+ ELSE "unknown"
+ END
+ ELSE CAST($value AS text)
+ END
+ '
+);
+
+
+-- View of human readable battery stats counter-based states. These are recorded
+-- by BatteryStats as a bitmap where each 'category' has a unique value at any
+-- given time.
+--
+-- @column ts Timestamp in nanoseconds.
+-- @column dur The duration the state was active.
+-- @column name The name of the counter track.
+-- @column value The counter value as a number.
+-- @column value_name The counter value as a human-readable string.
+CREATE VIEW android_battery_stats_state AS
+SELECT
+ ts,
+ name,
+ value,
+ BATTERY_STATS_VALUE_TO_STRING(name, value) AS value_name,
+ LEAD(ts, 1, TRACE_END()) OVER (PARTITION BY track_id ORDER BY ts) - ts AS dur
+FROM counter
+JOIN counter_track
+ ON counter.track_id = counter_track.id
+WHERE counter_track.name GLOB 'battery_stats.*';
+
+
+-- View of slices derived from battery_stats events. Battery stats records all
+-- events as instants, however some may indicate whether something started or
+-- stopped with a '+' or '-' prefix. Events such as jobs, top apps, foreground
+-- apps or long wakes include these details and allow drawing slices between
+-- instant events found in a trace.
+--
+-- For example, we may see an event like the following on 'battery_stats.top':
+--
+-- -top=10215:"com.google.android.apps.nexuslauncher"
+--
+-- This view will find the associated start ('+top') with the matching suffix
+-- (everything after the '=') to construct a slice. It computes the timestamp
+-- and duration from the events and extract the details as follows:
+--
+-- track_name='battery_stats.top'
+-- str_value='com.google.android.apps.nexuslauncher'
+-- int_value=10215
+--
+-- @column track_name The battery stats track name.
+-- @column ts Timestamp in nanoseconds.
+-- @column dur The duration of the event.
+-- @column str_value The string part of the event identifier.
+-- @column int_value The integer part of the event identifier.
+CREATE VIEW android_battery_stats_event_slices AS
+WITH
+ event_markers AS (
+ SELECT
+ ts,
+ track.name AS track_name,
+ str_split(slice.name, '=', 1) AS key,
+ substr(slice.name, 1, 1) = '+' AS start
+ FROM slice
+ JOIN track
+ ON slice.track_id = track.id
+ WHERE
+ track_name GLOB 'battery_stats.*'
+ AND substr(slice.name, 1, 1) IN ('+', '-')
+ ),
+ with_neighbors AS (
+ SELECT
+ *,
+ LAG(ts) OVER (PARTITION BY track_name, key ORDER BY ts) AS last_ts,
+ LEAD(ts) OVER (PARTITION BY track_name, key ORDER BY ts) AS next_ts
+ FROM event_markers
+ ),
+ -- Note: query performance depends on the ability to push down filters on
+ -- the track_name. It would be more clear below to have two queries and union
+ -- them, but doing so prevents push down through the above window functions.
+ event_spans AS (
+ SELECT
+ track_name, key,
+ IIF(start, ts, TRACE_START()) AS ts,
+ IIF(start, next_ts, ts) AS end_ts
+ FROM with_neighbors
+ -- For the majority of events, we take the `start` event and compute the dur
+ -- based on next_ts. In the off chance we get an end event with no prior
+ -- start (matched by the second half of this where), we can create an event
+ -- starting from the beginning of the trace ending at the current event.
+ WHERE (start OR last_ts IS NULL)
+ )
+SELECT
+ ts,
+ IFNULL(end_ts-ts, -1) AS dur,
+ track_name,
+ str_split(key, '"', 1) AS str_value,
+ CAST(str_split(key, ':', 0) AS INT64) AS int_value
+FROM event_spans;
diff --git a/test/trace_processor/diff_tests/android/android_battery_stats_event_slices.out b/test/trace_processor/diff_tests/android/android_battery_stats_event_slices.out
new file mode 100644
index 0000000..82cd36c
--- /dev/null
+++ b/test/trace_processor/diff_tests/android/android_battery_stats_event_slices.out
@@ -0,0 +1,4 @@
+"ts","dur","track_name","str_value","int_value"
+1000,8000,"battery_stats.top","mail",123
+3000,-1,"battery_stats.job","mail_job",456
+1000,3000,"battery_stats.job","video_job",789
diff --git a/test/trace_processor/diff_tests/android/tests.py b/test/trace_processor/diff_tests/android/tests.py
index 25190b0..cec98ae 100644
--- a/test/trace_processor/diff_tests/android/tests.py
+++ b/test/trace_processor/diff_tests/android/tests.py
@@ -111,6 +111,54 @@
""",
out=Path('android_system_property_slice.out'))
+ def test_android_battery_stats_event_slices(self):
+ # The following has three events
+ # * top (123, mail) from 1000 to 9000 explicit
+ # * job (456, mail_job) starting at 3000 (end is inferred as trace end)
+ # * job (789, video_job) ending at 4000 (start is inferred as trace start)
+ return DiffTestBlueprint(
+ trace=TextProto(r"""
+ packet {
+ ftrace_events {
+ cpu: 1
+ event {
+ timestamp: 1000
+ pid: 1
+ print {
+ buf: "N|1000|battery_stats.top|+top=123:\"mail\"\n"
+ }
+ }
+ event {
+ timestamp: 3000
+ pid: 1
+ print {
+ buf: "N|1000|battery_stats.job|+job=456:\"mail_job\"\n"
+ }
+ }
+ event {
+ timestamp: 4000
+ pid: 1
+ print {
+ buf: "N|1000|battery_stats.job|-job=789:\"video_job\"\n"
+ }
+ }
+ event {
+ timestamp: 9000
+ pid: 1
+ print {
+ buf: "N|1000|battery_stats.top|-top=123:\"mail\"\n"
+ }
+ }
+ }
+ }
+ """),
+ query="""
+ SELECT IMPORT('android.battery_stats');
+ SELECT * FROM android_battery_stats_event_slices
+ ORDER BY str_value;
+ """,
+ out=Path('android_battery_stats_event_slices.out'))
+
def test_binder_sync_binder_metrics(self):
return DiffTestBlueprint(
trace=DataPath('android_binder_metric_trace.atr'),