// Copyright (C) 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 // // http://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. import {Trace} from '../../public/trace'; import {PerfettoPlugin} from '../../public/plugin'; import {Engine} from '../../trace_processor/engine'; import {createQuerySliceTrack} from '../../components/tracks/query_slice_track'; import {CounterOptions} from '../../components/tracks/base_counter_track'; import {createQueryCounterTrack} from '../../components/tracks/query_counter_track'; import {TrackNode} from '../../public/workspace'; interface ContainedTrace { uuid: string; subscription: string; trigger: string; // NB: these are millis. ts: number; dur: number; } const PACKAGE_LOOKUP = ` create or replace perfetto table package_name_lookup as with installed as ( select uid, string_agg(package_name, ',') as name from package_list where uid >= 10000 group by 1 ), system(uid, name) as ( values (0, 'AID_ROOT'), (1000, 'AID_SYSTEM_USER'), (1001, 'AID_RADIO'), (1082, 'AID_ARTD') ) select uid, name from installed union all select uid, name from system order by uid; -- Adds a "package_name" column by joining on "uid" from the source table. create or replace perfetto macro add_package_name(src TableOrSubquery) returns TableOrSubquery as ( select A.*, ifnull(B.name, "uid=" || A.uid) as package_name from $src as A left join package_name_lookup as B on (B.uid = (A.uid % 100000)) ); `; const DEFAULT_NETWORK = ` with base as ( select ts, substr(s.name, 6) as conn from track t join slice s on t.id = s.track_id where t.name = 'battery_stats.conn' ), diff as ( select ts, conn, conn != lag(conn) over (order by ts) as keep from base ) select ts, ifnull(lead(ts) over (order by ts), (select end_ts from trace_bounds)) - ts as dur, case when conn like '-1:%' then 'Disconnected' when conn like '0:%' then 'Modem' when conn like '1:%' then 'WiFi' when conn like '4:%' then 'VPN' else conn end as name from diff where keep is null or keep`; const RADIO_TRANSPORT_TYPE = ` create or replace perfetto view radio_transport_data_conn as select ts, safe_dur AS dur, value_name as data_conn, value AS data_conn_val from android_battery_stats_state where track_name = "battery_stats.data_conn"; create or replace perfetto view radio_transport_nr_state as select ts, safe_dur AS dur, value AS nr_state_val from android_battery_stats_state where track_name = "battery_stats.nr_state"; drop table if exists radio_transport_join; create virtual table radio_transport_join using span_left_join(radio_transport_data_conn, radio_transport_nr_state); create or replace perfetto view radio_transport as select ts, dur, case data_conn_val -- On LTE with NR connected is 5G NSA. when 13 then iif(nr_state_val = 3, '5G (NSA)', data_conn) -- On NR with NR state present, is 5G SA. when 20 then iif(nr_state_val is null, '5G (SA or NSA)', '5G (SA)') else data_conn end as name from radio_transport_join;`; const TETHERING = ` with base as ( select ts as ts_end, EXTRACT_ARG(arg_set_id, 'network_tethering_reported.duration_millis') * 1000000 as dur from track t join slice s on t.id = s.track_id where t.name = 'Statsd Atoms' and s.name = 'network_tethering_reported' ) select ts_end - dur as ts, dur, 'Tethering' as name from base`; const NETWORK_SUMMARY = ` create or replace perfetto table network_summary as with base as ( select cast(ts / 5000000000 as int64) * 5000000000 AS ts, case when track_name glob '*wlan*' then 'wifi' when track_name glob '*rmnet*' then 'modem' else 'unknown' end as dev_type, package_name as pkg, sum(packet_length) AS value from android_network_packets where (track_name glob '*wlan*' or track_name glob '*rmnet*') group by 1,2,3 ), zeroes as ( select ts, dev_type, pkg, value from base union all select ts + 5000000000 as ts, dev_type, pkg, 0 as value from base ), final as ( select ts, dev_type, pkg, sum(value) as value from zeroes group by 1, 2, 3 ) select * from final where ts is not null`; const MODEM_RIL_STRENGTH = ` DROP VIEW IF EXISTS ScreenOn; CREATE VIEW ScreenOn AS SELECT ts, dur FROM ( SELECT ts, value, LEAD(ts, 1, TRACE_END()) OVER (ORDER BY ts)-ts AS dur FROM counter, track ON (counter.track_id = track.id) WHERE track.name = 'ScreenState' ) WHERE value = 2; DROP VIEW IF EXISTS RilSignalStrength; CREATE VIEW RilSignalStrength AS With RilMessages AS ( SELECT ts, slice.name, LEAD(ts, 1, TRACE_END()) OVER (ORDER BY ts)-ts AS dur FROM slice, track ON (slice.track_id = track.id) WHERE track.name = 'RIL' AND slice.name GLOB 'UNSOL_SIGNAL_STRENGTH*' ), BandTypes(band_ril, band_name) AS ( VALUES ("CellSignalStrengthLte:", "LTE"), ("CellSignalStrengthNr:", "NR") ), ValueTypes(value_ril, value_name) AS ( VALUES ("rsrp=", "rsrp"), ("rssi=", "rssi") ), Extracted AS ( SELECT ts, dur, band_name, value_name, ( SELECT CAST(SUBSTR(key_str, start_idx+1, end_idx-start_idx-1) AS INT64) AS value FROM ( SELECT key_str, INSTR(key_str, "=") AS start_idx, INSTR(key_str, " ") AS end_idx FROM ( SELECT SUBSTR(band_str, INSTR(band_str, value_ril)) AS key_str FROM (SELECT SUBSTR(name, INSTR(name, band_ril)) AS band_str) ) ) ) AS value FROM RilMessages JOIN BandTypes JOIN ValueTypes ) SELECT ts, dur, band_name, value_name, value, value_name || "=" || IIF(value = 2147483647, "unknown", ""||value) AS name, ROW_NUMBER() OVER (ORDER BY ts) as id, DENSE_RANK() OVER (ORDER BY band_name, value_name) AS track_id FROM Extracted; DROP TABLE IF EXISTS RilScreenOn; CREATE VIRTUAL TABLE RilScreenOn USING SPAN_JOIN(RilSignalStrength PARTITIONED track_id, ScreenOn)`; const MODEM_RIL_CHANNELS_PREAMBLE = ` CREATE OR REPLACE PERFETTO FUNCTION EXTRACT_KEY_VALUE(source STRING, key_name STRING) RETURNS STRING AS SELECT SUBSTR(trimmed, INSTR(trimmed, "=")+1, INSTR(trimmed, ",") - INSTR(trimmed, "=") - 1) FROM (SELECT SUBSTR($source, INSTR($source, $key_name)) AS trimmed);`; const MODEM_RIL_CHANNELS = ` With RawChannelConfig AS ( SELECT ts, slice.name AS raw_config FROM slice, track ON (slice.track_id = track.id) WHERE track.name = 'RIL' AND slice.name LIKE 'UNSOL_PHYSICAL_CHANNEL_CONFIG%' ), Attributes(attribute, attrib_name) AS ( VALUES ("mCellBandwidthDownlinkKhz", "downlink"), ("mCellBandwidthUplinkKhz", "uplink"), ("mNetworkType", "network"), ("mBand", "band") ), Slots(idx, slot_name) AS ( VALUES (0, "primary"), (1, "secondary 1"), (2, "secondary 2") ), Stage1 AS ( SELECT *, IFNULL(EXTRACT_KEY_VALUE(STR_SPLIT(raw_config, "}, {", idx), attribute), "") AS name FROM RawChannelConfig JOIN Attributes JOIN Slots ), Stage2 AS ( SELECT *, LAG(name) OVER (PARTITION BY idx, attribute ORDER BY ts) AS last_name FROM Stage1 ), Stage3 AS ( SELECT *, LEAD(ts, 1, TRACE_END()) OVER (PARTITION BY idx, attribute ORDER BY ts) - ts AS dur FROM Stage2 WHERE name != last_name ) SELECT ts, dur, slot_name || "-" || attrib_name || "=" || name AS name FROM Stage3`; const MODEM_CELL_RESELECTION = ` with base as ( select ts, s.name as raw_ril, ifnull(str_split(str_split(s.name, 'CellIdentityLte{', 1), ', operatorNames', 0), str_split(str_split(s.name, 'CellIdentityNr{', 1), ', operatorNames', 0)) as cell_id from track t join slice s on t.id = s.track_id where t.name = 'RIL' and s.name like '%DATA_REGISTRATION_STATE%' ), base2 as ( select ts, raw_ril, case when cell_id like '%earfcn%' then 'LTE ' || cell_id when cell_id like '%nrarfcn%' then 'NR ' || cell_id when cell_id is null then 'Unknown' else cell_id end as cell_id from base ), base3 as ( select ts, cell_id , lag(cell_id) over (order by ts) as lag_cell_id, raw_ril from base2 ) select ts, 0 as dur, cell_id as name, raw_ril from base3 where cell_id != lag_cell_id order by ts`; const SUSPEND_RESUME = ` SELECT ts, dur, 'Suspended' AS name 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 WHEN 1 THEN 'Screen off' WHEN 2 THEN 'Screen on' WHEN 3 THEN 'Always-on display (doze)' 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 ts, EXTRACT_ARG(arg_set_id, 'thermal_throttling_severity_state_changed.sensor_type') as sensor_type, EXTRACT_ARG(arg_set_id, 'thermal_throttling_severity_state_changed.sensor_name') as sensor_name, EXTRACT_ARG(arg_set_id, 'thermal_throttling_severity_state_changed.temperature_deci_celsius') / 10.0 as temperature_celcius, EXTRACT_ARG(arg_set_id, 'thermal_throttling_severity_state_changed.severity') as severity from track t join slice s on t.id = s.track_id where t.name = 'Statsd Atoms' and s.name = 'thermal_throttling_severity_state_changed' ), step2 as ( select ts, lead(ts) over (partition by sensor_type, sensor_name order by ts) - ts as dur, sensor_type, sensor_name, temperature_celcius, severity from step1 where sensor_type not like 'TEMPERATURE_TYPE_BCL_%' ) select ts, dur, case sensor_name when 'VIRTUAL-SKIN' then '' else sensor_name || ' is ' end || severity || ' (' || temperature_celcius || 'C)' as name from step2 where severity != 'NONE'`; const KERNEL_WAKELOCKS = ` create or replace perfetto table kernel_wakelocks as with kernel_wakelock_args as ( select arg_set_id, min(iif(key = 'kernel_wakelock.name', string_value, null)) as wakelock_name, min(iif(key = 'kernel_wakelock.count', int_value, null)) as count, min(iif(key = 'kernel_wakelock.time_micros', int_value, null)) as time_micros from args where key in ( 'kernel_wakelock.name', 'kernel_wakelock.count', 'kernel_wakelock.time_micros' ) group by 1 ), interesting as ( select wakelock_name from ( select wakelock_name, max(time_micros)-min(time_micros) as delta_us from kernel_wakelock_args group by 1 ) -- Only consider wakelocks with over 1 second of time during the whole trace where delta_us > 1e6 ), step1 as ( select ts, wakelock_name, count, time_micros from kernel_wakelock_args join interesting using (wakelock_name) join slice using (arg_set_id) ), step2 as ( select ts, wakelock_name, lead(ts) over (partition by wakelock_name order by ts) as ts_end, lead(count) over (partition by wakelock_name order by ts) - count as count, (lead(time_micros) over (partition by wakelock_name order by ts) - time_micros) * 1000 as wakelock_dur from step1 ), step3 as ( select ts, ts_end, ifnull((select sum(dur) from android_suspend_state s where power_state = 'suspended' and s.ts > step2.ts and s.ts < step2.ts_end), 0) as suspended_dur, wakelock_name, count, wakelock_dur from step2 where wakelock_dur is not null and wakelock_dur >= 0 ) select ts, ts_end - ts as dur, wakelock_name, min(100.0 * wakelock_dur / (ts_end - ts - suspended_dur), 100) as value from step3`; const KERNEL_WAKELOCKS_SUMMARY = ` select wakelock_name, max(value) as max_value from kernel_wakelocks where wakelock_name not in ('PowerManager.SuspendLockout', 'PowerManagerService.Display') group by 1 having max_value > 1 order by 1;`; const HIGH_CPU = ` create or replace perfetto table high_cpu as with cpu_cycles_args AS ( select arg_set_id, min(iif(key = 'cpu_cycles_per_uid_cluster.uid', int_value, null)) as uid, min(iif(key = 'cpu_cycles_per_uid_cluster.cluster', int_value, null)) as cluster, min(iif(key = 'cpu_cycles_per_uid_cluster.time_millis', int_value, null)) as time_millis from args where key in ( 'cpu_cycles_per_uid_cluster.uid', 'cpu_cycles_per_uid_cluster.cluster', 'cpu_cycles_per_uid_cluster.time_millis' ) group by 1 ), interesting AS ( select uid, cluster from ( select uid, cluster, max(time_millis)-min(time_millis) as delta_ms from cpu_cycles_args group by 1, 2 ) -- Only consider tracks with over 1 second of cpu during the whole trace where delta_ms > 1e3 ), base as ( select ts, uid, cluster, sum(time_millis) as time_millis from cpu_cycles_args join interesting using (uid, cluster) join slice using (arg_set_id) group by 1, 2, 3 ), with_windows as ( select ts, uid, cluster, lead(ts) over (partition by uid, cluster order by ts) - ts as dur, (lead(time_millis) over (partition by uid, cluster order by ts) - time_millis) * 1000000.0 as cpu_dur from base ), with_ratio as ( select ts, iif(dur is null, 0, max(0, 100.0 * cpu_dur / dur)) as value, case cluster when 0 then 'little' when 1 then 'mid' when 2 then 'big' else 'cl-' || cluster end as cluster, package_name as pkg from add_package_name!(with_windows) ) select ts, sum(value) as value, cluster, pkg from with_ratio group by 1, 3, 4`; const WAKEUPS = ` drop table if exists wakeups; create table wakeups as with wakeup_reason as ( select ts, substr(i.name, 0, instr(i.name, ' ')) as id_timestamp, substr(i.name, instr(i.name, ' ') + 1) as raw_wakeup from track t join instant i on t.id = i.track_id where t.name = 'wakeup_reason' ), wakeup_attribution as ( select substr(i.name, 0, instr(i.name, ' ')) as id_timestamp, substr(i.name, instr(i.name, ' ') + 1) as attribution from track t join instant i on t.id = i.track_id where t.name = 'wakeup_attribution' ), step1 as( select ts, raw_wakeup, attribution, null as raw_backoff from wakeup_reason r left outer join wakeup_attribution using(id_timestamp) union all select ts, null as raw_wakeup, null as attribution, i.name as raw_backoff from track t join instant i on t.id = i.track_id where t.name = 'suspend_backoff' ), step2 as ( select ts, raw_wakeup, attribution, lag(raw_backoff) over (order by ts) as raw_backoff from step1 ), step3 as ( select ts, raw_wakeup, attribution, str_split(raw_backoff, ' ', 0) as suspend_quality, str_split(raw_backoff, ' ', 1) as backoff_state, str_split(raw_backoff, ' ', 2) as backoff_reason, cast(str_split(raw_backoff, ' ', 3) as int) as backoff_count, cast(str_split(raw_backoff, ' ', 4) as int) as backoff_millis, false as suspend_end from step2 where raw_wakeup is not null union all select ts, null as raw_wakeup, null as attribution, null as suspend_quality, null as backoff_state, null as backoff_reason, null as backoff_count, null as backoff_millis, true as suspend_end from android_suspend_state where power_state = 'suspended' ), step4 as ( select ts, case suspend_quality when 'good' then min( lead(ts, 1, ts + 5e9) over (order by ts) - ts, 5e9 ) when 'bad' then backoff_millis * 1000000 else 0 end as dur, raw_wakeup, attribution, suspend_quality, backoff_state, backoff_reason, backoff_count, backoff_millis, suspend_end from step3 ), step5 as ( select ts, dur, raw_wakeup, attribution, suspend_quality, backoff_state, backoff_reason, backoff_count, backoff_millis from step4 where not suspend_end ), step6 as ( select ts, dur, raw_wakeup, attribution, suspend_quality, backoff_state, backoff_reason, backoff_count, backoff_millis, case when raw_wakeup like 'Abort: Pending Wakeup Sources: %' then 'abort_pending' when raw_wakeup like 'Abort: Last active Wakeup Source: %' then 'abort_last_active' when raw_wakeup like 'Abort: %' then 'abort_other' else 'normal' end as type, case when raw_wakeup like 'Abort: Pending Wakeup Sources: %' then substr(raw_wakeup, 32) when raw_wakeup like 'Abort: Last active Wakeup Source: %' then substr(raw_wakeup, 35) when raw_wakeup like 'Abort: %' then substr(raw_wakeup, 8) else raw_wakeup end as main, case when raw_wakeup like 'Abort: Pending Wakeup Sources: %' then ' ' when raw_wakeup like 'Abort: %' then 'no delimiter needed' else ':' end as delimiter from step5 ), step7 as ( select ts, dur, raw_wakeup, attribution, suspend_quality, backoff_state, backoff_reason, backoff_count, backoff_millis, type, str_split(main, delimiter, 0) as item_0, str_split(main, delimiter, 1) as item_1, str_split(main, delimiter, 2) as item_2, str_split(main, delimiter, 3) as item_3 from step6 ), step8 as ( select ts, dur, raw_wakeup, attribution, suspend_quality, backoff_state, backoff_reason, backoff_count, backoff_millis, type, item_0 as item from step7 union all select ts, dur, raw_wakeup, attribution, suspend_quality, backoff_state, backoff_reason, backoff_count, backoff_millis, type, item_1 as item from step7 where item_1 is not null union all select ts, dur, raw_wakeup, attribution, suspend_quality, backoff_state, backoff_reason, backoff_count, backoff_millis, type, item_2 as item from step7 where item_2 is not null union all select ts, dur, raw_wakeup, attribution, suspend_quality, backoff_state, backoff_reason, backoff_count, backoff_millis, type, item_3 as item from step7 where item_3 is not null ) select ts, dur, ts + dur as ts_end, raw_wakeup, attribution, suspend_quality, backoff_state, ifnull(backoff_reason, 'none') as backoff_reason, backoff_count, backoff_millis, type, case when type = 'normal' then ifnull(str_split(item, ' ', 1), item) else item end as item from step8`; const WAKEUPS_COLUMNS = [ 'item', 'type', 'raw_wakeup', 'attribution', 'suspend_quality', 'backoff_state', 'backoff_reason', 'backoff_count', 'backoff_millis', ]; function bleScanQuery(condition: string) { return ` with step1 as ( select ts, extract_arg(arg_set_id, 'ble_scan_state_changed.attribution_node[0].tag') as name, extract_arg(arg_set_id, 'ble_scan_state_changed.is_opportunistic') as opportunistic, extract_arg(arg_set_id, 'ble_scan_state_changed.is_filtered') as filtered, extract_arg(arg_set_id, 'ble_scan_state_changed.state') as state from track t join slice s on t.id = s.track_id where t.name = 'Statsd Atoms' and s.name = 'ble_scan_state_changed' ), step2 as ( select ts, name, state, opportunistic, filtered, lead(ts) over (partition by name order by ts) - ts as dur from step1 ) select ts, dur, name from step2 where state = 'ON' and ${condition} and dur is not null`; } const BLE_RESULTS = ` with step1 as ( select ts, extract_arg(arg_set_id, 'ble_scan_result_received.attribution_node[0].tag') as name, extract_arg(arg_set_id, 'ble_scan_result_received.num_results') as num_results from track t join slice s on t.id = s.track_id where t.name = 'Statsd Atoms' and s.name = 'ble_scan_result_received' ) select ts, 0 as dur, name || ' (' || num_results || ' results)' as name from step1`; const BT_A2DP_AUDIO = ` with step1 as ( select ts, EXTRACT_ARG(arg_set_id, 'bluetooth_a2dp_playback_state_changed.playback_state') as playback_state, EXTRACT_ARG(arg_set_id, 'bluetooth_a2dp_playback_state_changed.audio_coding_mode') as audio_coding_mode, EXTRACT_ARG(arg_set_id, 'bluetooth_a2dp_playback_state_changed.metric_id') as metric_id from track t join slice s on t.id = s.track_id where t.name = 'Statsd Atoms' and s.name = 'bluetooth_a2dp_playback_state_changed' ), step2 as ( select ts, lead(ts) over (partition by metric_id order by ts) - ts as dur, playback_state, audio_coding_mode, metric_id from step1 ) select ts, dur, audio_coding_mode as name from step2 where playback_state = 'PLAYBACK_STATE_PLAYING'`; const BT_CONNS_ACL = ` with acl1 as ( select ts, EXTRACT_ARG(arg_set_id, 'bluetooth_acl_connection_state_changed.state') as state, EXTRACT_ARG(arg_set_id, 'bluetooth_acl_connection_state_changed.transport') as transport, EXTRACT_ARG(arg_set_id, 'bluetooth_acl_connection_state_changed.metric_id') as metric_id from track t join slice s on t.id = s.track_id where t.name = 'Statsd Atoms' and s.name = 'bluetooth_acl_connection_state_changed' ), acl2 as ( select ts, lead(ts) over (partition by metric_id, transport order by ts) - ts as dur, state, transport, metric_id from acl1 ) select ts, dur, 'Device ' || metric_id || ' (' || case transport when 'TRANSPORT_TYPE_BREDR' then 'Classic' when 'TRANSPORT_TYPE_LE' then 'BLE' end || ')' as name from acl2 where state != 'CONNECTION_STATE_DISCONNECTED' and dur is not null`; const BT_CONNS_SCO = ` with sco1 as ( select ts, EXTRACT_ARG(arg_set_id, 'bluetooth_sco_connection_state_changed.state') as state, EXTRACT_ARG(arg_set_id, 'bluetooth_sco_connection_state_changed.codec') as codec, EXTRACT_ARG(arg_set_id, 'bluetooth_sco_connection_state_changed.metric_id') as metric_id from track t join slice s on t.id = s.track_id where t.name = 'Statsd Atoms' and s.name = 'bluetooth_sco_connection_state_changed' ), sco2 as ( select ts, lead(ts) over (partition by metric_id, codec order by ts) - ts as dur, state, codec, metric_id from sco1 ) select ts, dur, case state when 'CONNECTION_STATE_CONNECTED' then '' when 'CONNECTION_STATE_CONNECTING' then 'Connecting ' when 'CONNECTION_STATE_DISCONNECTING' then 'Disconnecting ' else 'unknown ' end || 'Device ' || metric_id || ' (' || case codec when 'SCO_CODEC_CVSD' then 'CVSD' when 'SCO_CODEC_MSBC' then 'MSBC' end || ')' as name from sco2 where state != 'CONNECTION_STATE_DISCONNECTED' and dur is not null`; const BT_LINK_LEVEL_EVENTS = ` with base as ( select ts, EXTRACT_ARG(arg_set_id, 'bluetooth_link_layer_connection_event.direction') as direction, EXTRACT_ARG(arg_set_id, 'bluetooth_link_layer_connection_event.type') as type, EXTRACT_ARG(arg_set_id, 'bluetooth_link_layer_connection_event.hci_cmd') as hci_cmd, EXTRACT_ARG(arg_set_id, 'bluetooth_link_layer_connection_event.hci_event') as hci_event, EXTRACT_ARG(arg_set_id, 'bluetooth_link_layer_connection_event.hci_ble_event') as hci_ble_event, EXTRACT_ARG(arg_set_id, 'bluetooth_link_layer_connection_event.cmd_status') as cmd_status, EXTRACT_ARG(arg_set_id, 'bluetooth_link_layer_connection_event.reason_code') as reason_code, EXTRACT_ARG(arg_set_id, 'bluetooth_link_layer_connection_event.metric_id') as metric_id from track t join slice s on t.id = s.track_id where t.name = 'Statsd Atoms' and s.name = 'bluetooth_link_layer_connection_event' ) select *, 0 as dur, 'Device '|| metric_id as name from base`; const BT_LINK_LEVEL_EVENTS_COLUMNS = [ 'direction', 'type', 'hci_cmd', 'hci_event', 'hci_ble_event', 'cmd_status', 'reason_code', 'metric_id', ]; const BT_QUALITY_REPORTS = ` with base as ( select ts, EXTRACT_ARG(arg_set_id, 'bluetooth_quality_report_reported.quality_report_id') as quality_report_id, EXTRACT_ARG(arg_set_id, 'bluetooth_quality_report_reported.packet_types') as packet_types, EXTRACT_ARG(arg_set_id, 'bluetooth_quality_report_reported.connection_handle') as connection_handle, EXTRACT_ARG(arg_set_id, 'bluetooth_quality_report_reported.connection_role') as connection_role, EXTRACT_ARG(arg_set_id, 'bluetooth_quality_report_reported.tx_power_level') as tx_power_level, EXTRACT_ARG(arg_set_id, 'bluetooth_quality_report_reported.rssi') as rssi, EXTRACT_ARG(arg_set_id, 'bluetooth_quality_report_reported.snr') as snr, EXTRACT_ARG(arg_set_id, 'bluetooth_quality_report_reported.unused_afh_channel_count') as unused_afh_channel_count, EXTRACT_ARG(arg_set_id, 'bluetooth_quality_report_reported.afh_select_unideal_channel_count') as afh_select_unideal_channel_count, EXTRACT_ARG(arg_set_id, 'bluetooth_quality_report_reported.lsto') as lsto, EXTRACT_ARG(arg_set_id, 'bluetooth_quality_report_reported.connection_piconet_clock') as connection_piconet_clock, EXTRACT_ARG(arg_set_id, 'bluetooth_quality_report_reported.retransmission_count') as retransmission_count, EXTRACT_ARG(arg_set_id, 'bluetooth_quality_report_reported.no_rx_count') as no_rx_count, EXTRACT_ARG(arg_set_id, 'bluetooth_quality_report_reported.nak_count') as nak_count, EXTRACT_ARG(arg_set_id, 'bluetooth_quality_report_reported.flow_off_count') as flow_off_count, EXTRACT_ARG(arg_set_id, 'bluetooth_quality_report_reported.buffer_overflow_bytes') as buffer_overflow_bytes, EXTRACT_ARG(arg_set_id, 'bluetooth_quality_report_reported.buffer_underflow_bytes') as buffer_underflow_bytes from track t join slice s on t.id = s.track_id where t.name = 'Statsd Atoms' and s.name = 'bluetooth_quality_report_reported' ) select *, 0 as dur, 'Connection '|| connection_handle as name from base`; const BT_QUALITY_REPORTS_COLUMNS = [ 'quality_report_id', 'packet_types', 'connection_handle', 'connection_role', 'tx_power_level', 'rssi', 'snr', 'unused_afh_channel_count', 'afh_select_unideal_channel_count', 'lsto', 'connection_piconet_clock', 'retransmission_count', 'no_rx_count', 'nak_count', 'flow_off_count', 'buffer_overflow_bytes', 'buffer_underflow_bytes', ]; const BT_RSSI_REPORTS = ` with base as ( select ts, EXTRACT_ARG(arg_set_id, 'bluetooth_device_rssi_reported.connection_handle') as connection_handle, EXTRACT_ARG(arg_set_id, 'bluetooth_device_rssi_reported.hci_status') as hci_status, EXTRACT_ARG(arg_set_id, 'bluetooth_device_rssi_reported.rssi') as rssi, EXTRACT_ARG(arg_set_id, 'bluetooth_device_rssi_reported.metric_id') as metric_id from track t join slice s on t.id = s.track_id where t.name = 'Statsd Atoms' and s.name = 'bluetooth_device_rssi_reported' ) select *, 0 as dur, 'Connection '|| connection_handle as name from base`; const BT_RSSI_REPORTS_COLUMNS = [ 'connection_handle', 'hci_status', 'rssi', 'metric_id', ]; const BT_CODE_PATH_COUNTER = ` with base as ( select ts, EXTRACT_ARG(arg_set_id, 'bluetooth_code_path_counter.key') as key, EXTRACT_ARG(arg_set_id, 'bluetooth_code_path_counter.number') as number from track t join slice s on t.id = s.track_id where t.name = 'Statsd Atoms' and s.name = 'bluetooth_code_path_counter' ) select *, 0 as dur, key as name from base`; const BT_CODE_PATH_COUNTER_COLUMNS = ['key', 'number']; const BT_HAL_CRASHES = ` with base as ( select ts, EXTRACT_ARG(arg_set_id, 'bluetooth_hal_crash_reason_reported.metric_id') as metric_id, EXTRACT_ARG(arg_set_id, 'bluetooth_hal_crash_reason_reported.error_code') as error_code, EXTRACT_ARG(arg_set_id, 'bluetooth_hal_crash_reason_reported.vendor_error_code') as vendor_error_code from track t join slice s on t.id = s.track_id where t.name = 'Statsd Atoms' and s.name = 'bluetooth_hal_crash_reason_reported' ) select *, 0 as dur, 'Device ' || metric_id as name from base`; const BT_HAL_CRASHES_COLUMNS = ['metric_id', 'error_code', 'vendor_error_code']; const BT_BYTES = ` with step1 as ( select ts, EXTRACT_ARG(arg_set_id, 'bluetooth_bytes_transfer.uid') as uid, EXTRACT_ARG(arg_set_id, 'bluetooth_bytes_transfer.tx_bytes') as tx_bytes, EXTRACT_ARG(arg_set_id, 'bluetooth_bytes_transfer.rx_bytes') as rx_bytes from track t join slice s on t.id = s.track_id where t.name = 'Statsd Atoms' and s.name = 'bluetooth_bytes_transfer' ), step2 as ( select ts, lead(ts) over (partition by uid order by ts) - ts as dur, uid, lead(tx_bytes) over (partition by uid order by ts) - tx_bytes as tx_bytes, lead(rx_bytes) over (partition by uid order by ts) - rx_bytes as rx_bytes from step1 ), step3 as ( select ts, dur, uid % 100000 as uid, sum(tx_bytes) as tx_bytes, sum(rx_bytes) as rx_bytes from step2 where tx_bytes >=0 and rx_bytes >=0 group by 1,2,3 having tx_bytes > 0 or rx_bytes > 0 ) select ts, dur, format("%s: TX %d bytes / RX %d bytes", package_name, tx_bytes, rx_bytes) as name from add_package_name!(step3) `; // See go/bt_system_context_report for reference on the bit-twiddling. const BT_ACTIVITY = ` create perfetto table bt_activity as with step1 as ( select EXTRACT_ARG(arg_set_id, 'bluetooth_activity_info.timestamp_millis') * 1000000 as ts, EXTRACT_ARG(arg_set_id, 'bluetooth_activity_info.bluetooth_stack_state') as bluetooth_stack_state, EXTRACT_ARG(arg_set_id, 'bluetooth_activity_info.controller_idle_time_millis') * 1000000 as controller_idle_dur, EXTRACT_ARG(arg_set_id, 'bluetooth_activity_info.controller_tx_time_millis') * 1000000 as controller_tx_dur, EXTRACT_ARG(arg_set_id, 'bluetooth_activity_info.controller_rx_time_millis') * 1000000 as controller_rx_dur from track t join slice s on t.id = s.track_id where t.name = 'Statsd Atoms' and s.name = 'bluetooth_activity_info' ), step2 as ( select ts, lead(ts) over (order by ts) - ts as dur, bluetooth_stack_state, lead(controller_idle_dur) over (order by ts) - controller_idle_dur as controller_idle_dur, lead(controller_tx_dur) over (order by ts) - controller_tx_dur as controller_tx_dur, lead(controller_rx_dur) over (order by ts) - controller_rx_dur as controller_rx_dur from step1 ) select ts, dur, bluetooth_stack_state & 0x0000000F as acl_active_count, bluetooth_stack_state & 0x000000F0 >> 4 as acl_sniff_count, bluetooth_stack_state & 0x00000F00 >> 8 as acl_ble_count, bluetooth_stack_state & 0x0000F000 >> 12 as advertising_count, case bluetooth_stack_state & 0x000F0000 >> 16 when 0 then 0 when 1 then 5 when 2 then 10 when 3 then 25 when 4 then 100 else -1 end as le_scan_duty_cycle, bluetooth_stack_state & 0x00100000 >> 20 as inquiry_active, bluetooth_stack_state & 0x00200000 >> 21 as sco_active, bluetooth_stack_state & 0x00400000 >> 22 as a2dp_active, bluetooth_stack_state & 0x00800000 >> 23 as le_audio_active, max(0, 100.0 * controller_idle_dur / dur) as controller_idle_pct, max(0, 100.0 * controller_tx_dur / dur) as controller_tx_pct, max(0, 100.0 * controller_rx_dur / dur) as controller_rx_pct from step2 `; export default class implements PerfettoPlugin { static readonly id = 'dev.perfetto.AndroidLongBatteryTracing'; private readonly groups = new Map(); private addTrack(ctx: Trace, track: TrackNode, groupName?: string): void { if (groupName) { const existingGroup = this.groups.get(groupName); if (existingGroup) { existingGroup.addChildInOrder(track); } else { const group = new TrackNode({title: groupName, isSummary: true}); group.addChildInOrder(track); this.groups.set(groupName, group); ctx.workspace.addChildInOrder(group); } } else { ctx.workspace.addChildInOrder(track); } } async addSliceTrack( ctx: Trace, name: string, query: string, groupName?: string, columns: string[] = [], ) { const uri = `/long_battery_tracing_${name}`; const track = await createQuerySliceTrack({ trace: ctx, uri, data: { sqlSource: query, columns: ['ts', 'dur', 'name', ...columns], }, argColumns: columns, }); ctx.tracks.registerTrack({ uri, title: name, track, }); const trackNode = new TrackNode({uri, title: name}); this.addTrack(ctx, trackNode, groupName); } async addCounterTrack( ctx: Trace, name: string, query: string, groupName: string, options?: Partial, ) { const uri = `/long_battery_tracing_${name}`; const track = await createQueryCounterTrack({ trace: ctx, uri, data: { sqlSource: query, columns: ['ts', 'value'], }, options, }); ctx.tracks.registerTrack({ uri, title: name, track, }); const trackNode = new TrackNode({uri, title: name}); this.addTrack(ctx, trackNode, groupName); } async addBatteryStatsState( ctx: Trace, name: string, track: string, groupName: string, features: Set, ) { if (!features.has(`track.${track}`)) { return; } await this.addSliceTrack( ctx, name, `SELECT ts, safe_dur AS dur, value_name AS name FROM android_battery_stats_state WHERE track_name = "${track}"`, groupName, ); } async addBatteryStatsEvent( ctx: Trace, name: string, track: string, groupName: string | undefined, features: Set, ) { if (!features.has(`track.${track}`)) { return; } await this.addSliceTrack( ctx, name, `SELECT ts, safe_dur AS dur, str_value AS name FROM android_battery_stats_event_slices WHERE track_name = "${track}"`, groupName, ); } async addDeviceState(ctx: Trace, features: Set): Promise { if (!features.has('track.battery_stats.*')) { return; } const query = (name: string, track: string) => this.addBatteryStatsEvent(ctx, name, track, undefined, features); 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 this.addSliceTrack(ctx, 'Device State: Screen state', SCREEN_STATE); await this.addSliceTrack(ctx, 'Device State: Charging', CHARGING); 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); query('Device State: Top app', 'battery_stats.top'); await this.addSliceTrack( ctx, 'Device State: Long wakelocks', `SELECT ts - 60000000000 as ts, safe_dur + 60000000000 as dur, str_value AS name, package_name as package FROM add_package_name!(( select *, int_value as uid from android_battery_stats_event_slices WHERE track_name = "battery_stats.longwake" ))`, undefined, ['package'], ); query('Device State: Foreground apps', 'battery_stats.fg'); query('Device State: Jobs', 'battery_stats.job'); if (features.has('atom.thermal_throttling_severity_state_changed')) { await this.addSliceTrack( ctx, 'Device State: Thermal throttling', THERMAL_THROTTLING, ); } } async addAtomCounters(ctx: Trace): Promise { const e = ctx.engine; try { await e.query( `INCLUDE PERFETTO MODULE google3.wireless.android.telemetry.trace_extractor.modules.atom_counters_slices`, ); } catch (e) { return; } const counters = await e.query( `select distinct ui_group, ui_name, ui_unit, counter_name from atom_counters where ui_name is not null`, ); const countersIt = counters.iter({ ui_group: 'str', ui_name: 'str', ui_unit: 'str', counter_name: 'str', }); for (; countersIt.valid(); countersIt.next()) { const unit = countersIt.ui_unit; const opts = unit === '%' ? {yOverrideMaximum: 100, unit: '%'} : unit !== undefined ? {unit} : undefined; await this.addCounterTrack( ctx, countersIt.ui_name, `select ts, ${unit === '%' ? 100.0 : 1.0} * counter_value as value from atom_counters where counter_name = '${countersIt.counter_name}'`, countersIt.ui_group, opts, ); } } async addAtomSlices(ctx: Trace): Promise { const e = ctx.engine; try { await e.query( `INCLUDE PERFETTO MODULE google3.wireless.android.telemetry.trace_extractor.modules.atom_counters_slices`, ); } catch (e) { return; } const sliceTracks = await e.query( `select distinct ui_group, ui_name, atom, field from atom_slices where ui_name is not null order by 1, 2, 3, 4`, ); const slicesIt = sliceTracks.iter({ atom: 'str', ui_group: 'str', ui_name: 'str', field: 'str', }); const tracks = new Map< string, { ui_group: string; ui_name: string; } >(); const fields = new Map(); for (; slicesIt.valid(); slicesIt.next()) { const atom = slicesIt.atom; let args = fields.get(atom); if (args === undefined) { args = []; fields.set(atom, args); } args.push(slicesIt.field); tracks.set(atom, { ui_group: slicesIt.ui_group, ui_name: slicesIt.ui_name, }); } for (const [atom, args] of fields) { function safeArg(arg: string) { return arg.replaceAll(/[[\]]/g, '').replaceAll(/\./g, '_'); } // We need to make arg names compatible with SQL here because they pass through several // layers of SQL without being quoted in "". function argSql(arg: string) { return `max(case when field = '${arg}' then ifnull(string_value, int_value) end) as ${safeArg(arg)}`; } await this.addSliceTrack( ctx, tracks.get(atom)!.ui_name, `select ts, dur, slice_name as name, ${args.map((a) => argSql(a)).join(', ')} from atom_slices where atom = '${atom}' group by ts, dur, name`, tracks.get(atom)!.ui_group, args.map((a) => safeArg(a)), ); } } async addNetworkSummary(ctx: Trace, features: Set): Promise { if (!features.has('net.modem') && !features.has('net.wifi')) { return; } const groupName = 'Network Summary'; const e = ctx.engine; await e.query(`INCLUDE PERFETTO MODULE android.battery_stats;`); await e.query(`INCLUDE PERFETTO MODULE android.network_packets;`); await e.query(NETWORK_SUMMARY); await e.query(RADIO_TRANSPORT_TYPE); await this.addSliceTrack( ctx, 'Default network', DEFAULT_NETWORK, groupName, ); if (features.has('atom.network_tethering_reported')) { await this.addSliceTrack(ctx, 'Tethering', TETHERING, groupName); } if (features.has('net.wifi')) { await this.addCounterTrack( ctx, 'Wifi total bytes', `select ts, sum(value) as value from network_summary where dev_type = 'wifi' group by 1`, groupName, {yDisplay: 'log', yRangeSharingKey: 'net_bytes', unit: 'byte'}, ); const result = await e.query( `select pkg, sum(value) from network_summary where dev_type='wifi' group by 1 order by 2 desc limit 10`, ); const it = result.iter({pkg: 'str'}); for (; it.valid(); it.next()) { await this.addCounterTrack( ctx, `Top wifi: ${it.pkg}`, `select ts, value from network_summary where dev_type = 'wifi' and pkg = '${it.pkg}'`, groupName, {yDisplay: 'log', yRangeSharingKey: 'net_bytes', unit: 'byte'}, ); } } this.addBatteryStatsState( ctx, 'Wifi interface', 'battery_stats.wifi_radio', groupName, features, ); this.addBatteryStatsState( ctx, 'Wifi supplicant state', 'battery_stats.wifi_suppl', groupName, features, ); this.addBatteryStatsState( ctx, 'Wifi strength', 'battery_stats.wifi_signal_strength', groupName, features, ); if (features.has('net.modem')) { await this.addCounterTrack( ctx, 'Modem total bytes', `select ts, sum(value) as value from network_summary where dev_type = 'modem' group by 1`, groupName, {yDisplay: 'log', yRangeSharingKey: 'net_bytes', unit: 'byte'}, ); const result = await e.query( `select pkg, sum(value) from network_summary where dev_type='modem' group by 1 order by 2 desc limit 10`, ); const it = result.iter({pkg: 'str'}); for (; it.valid(); it.next()) { await this.addCounterTrack( ctx, `Top modem: ${it.pkg}`, `select ts, value from network_summary where dev_type = 'modem' and pkg = '${it.pkg}'`, groupName, {yDisplay: 'log', yRangeSharingKey: 'net_bytes', unit: 'byte'}, ); } } this.addBatteryStatsState( ctx, 'Cellular interface', 'battery_stats.mobile_radio', groupName, features, ); await this.addSliceTrack( ctx, 'Cellular connection', `select ts, dur, name from radio_transport`, groupName, ); this.addBatteryStatsState( ctx, 'Cellular strength', 'battery_stats.phone_signal_strength', groupName, features, ); } async addModemDetail(ctx: Trace, features: Set): Promise { const groupName = 'Modem Detail'; if (features.has('track.ril')) { await this.addModemRil(ctx, groupName); } await this.addModemTeaData(ctx, groupName); } async addModemRil(ctx: Trace, groupName: string): Promise { const rilStrength = async (band: string, value: string) => await this.addSliceTrack( ctx, `Modem signal strength ${band} ${value}`, `SELECT ts, dur, name FROM RilScreenOn WHERE band_name = '${band}' AND value_name = '${value}'`, groupName, ); const e = ctx.engine; await e.query(MODEM_RIL_STRENGTH); await e.query(MODEM_RIL_CHANNELS_PREAMBLE); await rilStrength('LTE', 'rsrp'); await rilStrength('LTE', 'rssi'); await rilStrength('NR', 'rsrp'); await rilStrength('NR', 'rssi'); await this.addSliceTrack( ctx, 'Modem channel config', MODEM_RIL_CHANNELS, groupName, ); await this.addSliceTrack( ctx, 'Modem cell reselection', MODEM_CELL_RESELECTION, groupName, ['raw_ril'], ); } async addModemTeaData(ctx: Trace, groupName: string): Promise { const e = ctx.engine; try { await e.query( `INCLUDE PERFETTO MODULE google3.wireless.android.telemetry.trace_extractor.modules.modem_tea_metrics`, ); } catch { return; } const counters = await e.query( `select distinct name from pixel_modem_counters`, ); const countersIt = counters.iter({name: 'str'}); for (; countersIt.valid(); countersIt.next()) { await this.addCounterTrack( ctx, countersIt.name, `select ts, value from pixel_modem_counters where name = '${countersIt.name}'`, groupName, ); } const slices = await e.query( `select distinct track_name from pixel_modem_slices`, ); const slicesIt = slices.iter({track_name: 'str'}); for (; slicesIt.valid(); slicesIt.next()) { await this.addSliceTrack( ctx, slicesIt.track_name, `select ts, dur, slice_name as name from pixel_modem_slices where track_name = '${slicesIt.track_name}'`, groupName, ); } } async addKernelWakelocks(ctx: Trace, features: Set): Promise { if (!features.has('atom.kernel_wakelock')) { return; } const groupName = 'Kernel Wakelock Summary'; const e = ctx.engine; await e.query(`INCLUDE PERFETTO MODULE android.suspend;`); await e.query(KERNEL_WAKELOCKS); const result = await e.query(KERNEL_WAKELOCKS_SUMMARY); const it = result.iter({wakelock_name: 'str'}); for (; it.valid(); it.next()) { await this.addCounterTrack( ctx, it.wakelock_name, `select ts, dur, value from kernel_wakelocks where wakelock_name = "${it.wakelock_name}"`, groupName, {yRangeSharingKey: 'kernel_wakelock', unit: '%'}, ); } } async addWakeups(ctx: Trace, features: Set): Promise { if (!features.has('track.suspend_backoff')) { return; } const e = ctx.engine; const groupName = 'Wakeups'; await e.query(`INCLUDE PERFETTO MODULE android.suspend;`); await e.query(WAKEUPS); const result = await e.query(`select item, sum(dur) as sum_dur from wakeups group by 1 having sum_dur > 600e9`); const it = result.iter({item: 'str'}); const sqlPrefix = `select ts, dur, item || case backoff_reason when 'short' then ' (Short suspend backoff)' when 'failed' then ' (Failed suspend backoff)' else '' end as name, item, type, raw_wakeup, attribution, suspend_quality, backoff_state, backoff_reason, backoff_count, backoff_millis from wakeups`; const items = []; let labelOther = false; for (; it.valid(); it.next()) { labelOther = true; await this.addSliceTrack( ctx, `Wakeup ${it.item}`, `${sqlPrefix} where item="${it.item}"`, groupName, WAKEUPS_COLUMNS, ); items.push(it.item); } await this.addSliceTrack( ctx, labelOther ? 'Other wakeups' : 'Wakeups', `${sqlPrefix} where item not in ('${items.join("','")}')`, groupName, WAKEUPS_COLUMNS, ); } async addHighCpu(ctx: Trace, features: Set): Promise { if (!features.has('atom.cpu_cycles_per_uid_cluster')) { return; } const groupName = 'CPU per UID (major users)'; const e = ctx.engine; await e.query(HIGH_CPU); const result = await e.query( `select distinct pkg, cluster from high_cpu where value > 10 order by 1, 2`, ); const it = result.iter({pkg: 'str', cluster: 'str'}); for (; it.valid(); it.next()) { await this.addCounterTrack( ctx, `CPU (${it.cluster}): ${it.pkg}`, `select ts, value from high_cpu where pkg = "${it.pkg}" and cluster="${it.cluster}"`, groupName, {yOverrideMaximum: 100, unit: '%'}, ); } } async addBluetooth(ctx: Trace, features: Set): Promise { if ( !Array.from(features.values()).some( (f) => f.startsWith('atom.bluetooth_') || f.startsWith('atom.ble_'), ) ) { return; } const groupName = 'Bluetooth'; await this.addSliceTrack( ctx, 'BLE Scans (opportunistic)', bleScanQuery('opportunistic'), groupName, ); await this.addSliceTrack( ctx, 'BLE Scans (filtered)', bleScanQuery('filtered'), groupName, ); await this.addSliceTrack( ctx, 'BLE Scans (unfiltered)', bleScanQuery('not filtered'), groupName, ); await this.addSliceTrack(ctx, 'BLE Scan Results', BLE_RESULTS, groupName); await this.addSliceTrack(ctx, 'Connections (ACL)', BT_CONNS_ACL, groupName); await this.addSliceTrack(ctx, 'Connections (SCO)', BT_CONNS_SCO, groupName); await this.addSliceTrack( ctx, 'Link-level Events', BT_LINK_LEVEL_EVENTS, groupName, BT_LINK_LEVEL_EVENTS_COLUMNS, ); await this.addSliceTrack(ctx, 'A2DP Audio', BT_A2DP_AUDIO, groupName); await this.addSliceTrack( ctx, 'Bytes Transferred (L2CAP/RFCOMM)', BT_BYTES, groupName, ); await ctx.engine.query(BT_ACTIVITY); await this.addCounterTrack( ctx, 'ACL Classic Active Count', 'select ts, dur, acl_active_count as value from bt_activity', groupName, ); await this.addCounterTrack( ctx, 'ACL Classic Sniff Count', 'select ts, dur, acl_sniff_count as value from bt_activity', groupName, ); await this.addCounterTrack( ctx, 'ACL BLE Count', 'select ts, dur, acl_ble_count as value from bt_activity', groupName, ); await this.addCounterTrack( ctx, 'Advertising Instance Count', 'select ts, dur, advertising_count as value from bt_activity', groupName, ); await this.addCounterTrack( ctx, 'LE Scan Duty Cycle Maximum', 'select ts, dur, le_scan_duty_cycle as value from bt_activity', groupName, {unit: '%'}, ); await this.addSliceTrack( ctx, 'Inquiry Active', "select ts, dur, 'Active' as name from bt_activity where inquiry_active", groupName, ); await this.addSliceTrack( ctx, 'SCO Active', "select ts, dur, 'Active' as name from bt_activity where sco_active", groupName, ); await this.addSliceTrack( ctx, 'A2DP Active', "select ts, dur, 'Active' as name from bt_activity where a2dp_active", groupName, ); await this.addSliceTrack( ctx, 'LE Audio Active', "select ts, dur, 'Active' as name from bt_activity where le_audio_active", groupName, ); await this.addCounterTrack( ctx, 'Controller Idle Time', 'select ts, dur, controller_idle_pct as value from bt_activity', groupName, {yRangeSharingKey: 'bt_controller_time', unit: '%'}, ); await this.addCounterTrack( ctx, 'Controller TX Time', 'select ts, dur, controller_tx_pct as value from bt_activity', groupName, {yRangeSharingKey: 'bt_controller_time', unit: '%'}, ); await this.addCounterTrack( ctx, 'Controller RX Time', 'select ts, dur, controller_rx_pct as value from bt_activity', groupName, {yRangeSharingKey: 'bt_controller_time', unit: '%'}, ); await this.addSliceTrack( ctx, 'Quality reports', BT_QUALITY_REPORTS, groupName, BT_QUALITY_REPORTS_COLUMNS, ); await this.addSliceTrack( ctx, 'RSSI Reports', BT_RSSI_REPORTS, groupName, BT_RSSI_REPORTS_COLUMNS, ); await this.addSliceTrack( ctx, 'HAL Crashes', BT_HAL_CRASHES, groupName, BT_HAL_CRASHES_COLUMNS, ); await this.addSliceTrack( ctx, 'Code Path Counter', BT_CODE_PATH_COUNTER, groupName, BT_CODE_PATH_COUNTER_COLUMNS, ); } async addContainedTraces( ctx: Trace, containedTraces: ContainedTrace[], ): Promise { const bySubscription = new Map(); for (const trace of containedTraces) { if (!bySubscription.has(trace.subscription)) { bySubscription.set(trace.subscription, []); } bySubscription.get(trace.subscription)!.push(trace); } for (const [subscription, traces] of bySubscription) { await this.addSliceTrack( ctx, subscription, traces .map( (t) => `SELECT CAST(${t.ts} * 1e6 AS int) AS ts, CAST(${t.dur} * 1e6 AS int) AS dur, '${t.trigger === '' ? 'Trace' : t.trigger}' AS name, 'http://go/trace-uuid/${t.uuid}' AS link `, ) .join(' UNION ALL '), 'Other traces', ['link'], ); } } async findFeatures(e: Engine): Promise> { const features = new Set(); const addFeatures = async (q: string) => { const result = await e.query(q); const it = result.iter({feature: 'str'}); for (; it.valid(); it.next()) { features.add(it.feature); } }; await addFeatures(` select distinct 'atom.' || s.name as feature from track t join slice s on t.id = s.track_id where t.name = 'Statsd Atoms'`); await addFeatures(` select distinct case when name like '%wlan%' then 'net.wifi' when name like '%rmnet%' then 'net.modem' else 'net.other' end as feature from track where name like '%Transmitted' or name like '%Received'`); await addFeatures(` select distinct 'track.' || lower(name) as feature from track where name in ('RIL', 'suspend_backoff') or name like 'battery_stats.%'`); await addFeatures(` select distinct 'track.battery_stats.*' as feature from track where name like 'battery_stats.%'`); return features; } async addTracks(ctx: Trace): Promise { const features: Set = await this.findFeatures(ctx.engine); const containedTraces = (ctx.openerPluginArgs?.containedTraces ?? []) as ContainedTrace[]; await ctx.engine.query(PACKAGE_LOOKUP); await this.addNetworkSummary(ctx, features); await this.addBluetooth(ctx, features); await this.addAtomCounters(ctx); await this.addAtomSlices(ctx); await this.addModemDetail(ctx, features); await this.addKernelWakelocks(ctx, features); await this.addWakeups(ctx, features); await this.addDeviceState(ctx, features); await this.addHighCpu(ctx, features); await this.addContainedTraces(ctx, containedTraces); } async onTraceLoad(ctx: Trace): Promise { await this.addTracks(ctx); } }