| // 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 {v4 as uuidv4} from 'uuid'; |
| |
| import {Actions, DeferredAction} from '../../common/actions'; |
| import {globals} from '../../frontend/globals'; |
| import { |
| Plugin, |
| PluginContext, |
| PluginContextTrace, |
| PluginDescriptor, |
| PrimaryTrackSortKey, |
| } from '../../public'; |
| import {EngineProxy} from '../../trace_processor/engine'; |
| import {createDebugCounterTrackActions} from '../../tracks/debug/counter_track'; |
| import {NULL_TRACK_URI} from '../../tracks/null_track'; |
| import {createDebugSliceTrackActions} from '../../public'; |
| |
| 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 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 = ` |
| drop table if exists network_summary; |
| create table network_summary as |
| with base as ( |
| select |
| cast(s.ts / 5000000000 as int) * 5000000000 as ts, |
| case |
| when t.name glob '*wlan*' then 'wifi' |
| when t.name glob '*rmnet*' then 'modem' |
| else 'unknown' |
| end as dev_type, |
| lower(substr(t.name, instr(t.name, ' ') + 1, 1)) || 'x' as dir, |
| sum(EXTRACT_ARG(arg_set_id, 'packet_length')) AS value |
| from slice s join track t on s.track_id = t.id |
| where (t.name glob '*Received' or t.name glob '*Transmitted') |
| and (t.name glob '*wlan*' or t.name glob '*rmnet*') |
| group by 1,2,3 |
| ), |
| zeroes as ( |
| select |
| ts, |
| dev_type, |
| dir, |
| value |
| from base |
| union all |
| select |
| ts + 5000000000 as ts, |
| dev_type, |
| dir, |
| 0 as value |
| from base |
| ), |
| final as ( |
| select |
| ts, |
| dev_type, |
| dir, |
| sum(value) as value |
| from zeroes |
| group by 1, 2, 3 |
| ) |
| select * from final where ts is not null`; |
| |
| const MODEM_ACTIVITY_INFO = ` |
| drop table if exists modem_activity_info; |
| create table modem_activity_info as |
| with modem_raw as ( |
| select |
| ts, |
| EXTRACT_ARG(arg_set_id, 'modem_activity_info.timestamp_millis') as timestamp_millis, |
| EXTRACT_ARG(arg_set_id, 'modem_activity_info.sleep_time_millis') as sleep_time_millis, |
| EXTRACT_ARG(arg_set_id, 'modem_activity_info.controller_idle_time_millis') as controller_idle_time_millis, |
| EXTRACT_ARG(arg_set_id, 'modem_activity_info.controller_tx_time_pl0_millis') as controller_tx_time_pl0_millis, |
| EXTRACT_ARG(arg_set_id, 'modem_activity_info.controller_tx_time_pl1_millis') as controller_tx_time_pl1_millis, |
| EXTRACT_ARG(arg_set_id, 'modem_activity_info.controller_tx_time_pl2_millis') as controller_tx_time_pl2_millis, |
| EXTRACT_ARG(arg_set_id, 'modem_activity_info.controller_tx_time_pl3_millis') as controller_tx_time_pl3_millis, |
| EXTRACT_ARG(arg_set_id, 'modem_activity_info.controller_tx_time_pl4_millis') as controller_tx_time_pl4_millis, |
| EXTRACT_ARG(arg_set_id, 'modem_activity_info.controller_rx_time_millis') as controller_rx_time_millis |
| from track t join slice s on t.id = s.track_id |
| where t.name = 'Statsd Atoms' |
| and s.name = 'modem_activity_info' |
| ), |
| deltas as ( |
| select |
| timestamp_millis * 1000000 as ts, |
| lead(timestamp_millis) over (order by ts) - timestamp_millis as dur_millis, |
| lead(sleep_time_millis) over (order by ts) - sleep_time_millis as sleep_time_millis, |
| lead(controller_idle_time_millis) over (order by ts) - controller_idle_time_millis as controller_idle_time_millis, |
| lead(controller_tx_time_pl0_millis) over (order by ts) - controller_tx_time_pl0_millis as controller_tx_time_pl0_millis, |
| lead(controller_tx_time_pl1_millis) over (order by ts) - controller_tx_time_pl1_millis as controller_tx_time_pl1_millis, |
| lead(controller_tx_time_pl2_millis) over (order by ts) - controller_tx_time_pl2_millis as controller_tx_time_pl2_millis, |
| lead(controller_tx_time_pl3_millis) over (order by ts) - controller_tx_time_pl3_millis as controller_tx_time_pl3_millis, |
| lead(controller_tx_time_pl4_millis) over (order by ts) - controller_tx_time_pl4_millis as controller_tx_time_pl4_millis, |
| lead(controller_rx_time_millis) over (order by ts) - controller_rx_time_millis as controller_rx_time_millis |
| from modem_raw |
| ), |
| ratios as ( |
| select |
| ts, |
| 100.0 * sleep_time_millis / dur_millis as sleep_time_ratio, |
| 100.0 * controller_idle_time_millis / dur_millis as controller_idle_time_ratio, |
| 100.0 * controller_tx_time_pl0_millis / dur_millis as controller_tx_time_pl0_ratio, |
| 100.0 * controller_tx_time_pl1_millis / dur_millis as controller_tx_time_pl1_ratio, |
| 100.0 * controller_tx_time_pl2_millis / dur_millis as controller_tx_time_pl2_ratio, |
| 100.0 * controller_tx_time_pl3_millis / dur_millis as controller_tx_time_pl3_ratio, |
| 100.0 * controller_tx_time_pl4_millis / dur_millis as controller_tx_time_pl4_ratio, |
| 100.0 * controller_rx_time_millis / dur_millis as controller_rx_time_ratio |
| from deltas |
| ) |
| select * from ratios where sleep_time_ratio is not null and sleep_time_ratio >= 0`; |
| |
| 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 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 = ` |
| drop table if exists kernel_wakelocks; |
| create table kernel_wakelocks as |
| with step1 as ( |
| select |
| ts, |
| EXTRACT_ARG(arg_set_id, 'kernel_wakelock.name') as wakelock_name, |
| EXTRACT_ARG(arg_set_id, 'kernel_wakelock.count') as count, |
| EXTRACT_ARG(arg_set_id, 'kernel_wakelock.time_micros') as time_micros |
| from track t join slice s on t.id = s.track_id |
| where t.name = 'Statsd Atoms' |
| and s.name = 'kernel_wakelock' |
| ), |
| 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 suspend_slice_ s where 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 |
| and count >= 0 |
| ), |
| step4 as ( |
| select |
| ts, |
| ts_end, |
| suspended_dur, |
| wakelock_name, |
| count, |
| 1.0 * wakelock_dur / (ts_end - ts - suspended_dur) as ratio, |
| wakelock_dur |
| from step3 |
| ) |
| select |
| ts, |
| min(ratio, 1) * (ts_end - ts) as dur, |
| wakelock_name, |
| cast (100.0 * ratio as int) || '% (+' || count || ')' as name |
| from step4 |
| where cast (100.0 * wakelock_dur / (ts_end - ts - suspended_dur) as int) > 1`; |
| |
| const KERNEL_WAKELOCKS_SUMMARY = ` |
| select distinct wakelock_name |
| from kernel_wakelocks |
| where wakelock_name not in ('PowerManager.SuspendLockout', 'PowerManagerService.Display') |
| order by 1;`; |
| |
| const HIGH_CPU = ` |
| drop table if exists high_cpu; |
| create table high_cpu as |
| with base as ( |
| select |
| ts, |
| EXTRACT_ARG(arg_set_id, 'cpu_cycles_per_uid_cluster.uid') as uid, |
| EXTRACT_ARG(arg_set_id, 'cpu_cycles_per_uid_cluster.cluster') as cluster, |
| sum(EXTRACT_ARG(arg_set_id, 'cpu_cycles_per_uid_cluster.time_millis')) as time_millis |
| from track t join slice s on t.id = s.track_id |
| where t.name = 'Statsd Atoms' |
| and s.name = 'cpu_cycles_per_uid_cluster' |
| 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 |
| ), |
| app_package_list as ( |
| select |
| uid, |
| group_concat(package_name) as package_name |
| from package_list |
| where uid >= 10000 |
| group by 1 |
| ), |
| with_ratio as ( |
| select |
| ts, |
| 100.0 * cpu_dur / dur as value, |
| dur, |
| case cluster when 0 then 'little' when 1 then 'mid' when 2 then 'big' else 'cl-' || cluster end as cluster, |
| case |
| when uid = 0 then 'AID_ROOT' |
| when uid = 1000 then 'AID_SYSTEM_USER' |
| when uid = 1001 then 'AID_RADIO' |
| when uid = 1082 then 'AID_ARTD' |
| when pl.package_name is null then 'uid=' || uid |
| else pl.package_name |
| end as pkg |
| from with_windows left join app_package_list pl using(uid) |
| where cpu_dur is not null |
| ), |
| with_zeros as ( |
| select ts, value, cluster, pkg |
| from with_ratio |
| union all |
| select ts + dur as ts, 0 as value, cluster, pkg |
| from with_ratio |
| ) |
| select ts, sum(value) as value, cluster, pkg |
| from with_zeros |
| 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 suspend_slice_ |
| ), |
| 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']; |
| |
| // Make it less painful to wrangle promises and arrays and promises of arrays |
| // etc... |
| type MixedResults<T> = (T|T[]|Promise<T>|Promise<T[]>)[]; |
| type MixedActions = MixedResults<DeferredAction<{}>>; |
| async function flatten<T>(arr: MixedResults<T>): Promise<T[]> { |
| const result: T[] = []; |
| for (const elem of arr) { |
| const awaited = elem instanceof Promise ? await elem : elem; |
| Array.isArray(awaited) ? result.push(...awaited) : result.push(awaited); |
| } |
| return result; |
| } |
| |
| class AndroidLongBatteryTracing implements Plugin { |
| onActivate(_: PluginContext): void {} |
| |
| async addSliceTrack( |
| engine: EngineProxy, name: string, query: string, groupId: string, |
| columns: string[] = []): Promise<DeferredAction<{}>> { |
| const actions = await createDebugSliceTrackActions( |
| engine, |
| { |
| sqlSource: query, |
| columns: ['ts', 'dur', 'name', ...columns], |
| }, |
| name, |
| {ts: 'ts', dur: 'dur', name: 'name'}, |
| columns, |
| {closeable: false, pinned: false}, |
| ); |
| if (actions.length > 1) { |
| throw new Error(); |
| } |
| const action = actions[0]; |
| action.args = { |
| ...action.args, |
| trackGroup: groupId, |
| }; |
| return action; |
| } |
| |
| async addCounterTrack( |
| engine: EngineProxy, name: string, query: string, |
| groupId: string): Promise<DeferredAction<{}>> { |
| const actions = await createDebugCounterTrackActions( |
| engine, |
| { |
| sqlSource: query, |
| columns: ['ts', 'value'], |
| }, |
| name, |
| {ts: 'ts', value: 'value'}, |
| {closeable: false, pinned: false}, |
| ); |
| if (actions.length > 1) { |
| throw new Error(); |
| } |
| const action = actions[0]; |
| action.args = { |
| ...action.args, |
| trackGroup: groupId, |
| }; |
| return action; |
| } |
| |
| async addBatteryStatsEvents(e: EngineProxy, groupId: string): |
| Promise<DeferredAction<{}>[]> { |
| const query = (name: string, track: string): Promise<DeferredAction<{}>> => |
| this.addSliceTrack( |
| e, |
| name, |
| `SELECT ts, dur, str_value AS name |
| FROM android_battery_stats_event_slices |
| WHERE track_name = "${track}"`, |
| groupId); |
| |
| await e.query(`SELECT IMPORT('android.battery_stats');`); |
| return flatten([ |
| query('Top App', 'battery_stats.top'), |
| this.addSliceTrack( |
| e, 'Long wakelocks', `SELECT |
| ts - 60000000000 as ts, |
| dur + 60000000000 as dur, |
| str_value AS name, |
| ifnull( |
| (select package_name from package_list where uid = int_value % 100000), |
| int_value) as package |
| FROM android_battery_stats_event_slices |
| WHERE track_name = "battery_stats.longwake"`, |
| groupId, ['package']), |
| query('Foreground Apps', 'battery_stats.fg'), |
| query('Jobs', 'battery_stats.job'), |
| ]); |
| } |
| |
| async addNetworkSummary(e: EngineProxy, features: Set<string>): |
| Promise<DeferredAction<{}>[]> { |
| if (!features.has('net.modem') && !features.has('net.wifi')) { |
| return []; |
| } |
| const {groupId, groupActions} = this.addGroup('Network Summary'); |
| |
| await e.query(NETWORK_SUMMARY); |
| const actions = [ |
| groupActions, |
| this.addSliceTrack(e, 'Default network', DEFAULT_NETWORK, groupId), |
| ]; |
| if (features.has('atom.network_tethering_reported')) { |
| actions.push(this.addSliceTrack(e, 'Tethering', TETHERING, groupId)); |
| } |
| if (features.has('net.wifi')) { |
| actions.push( |
| this.addCounterTrack( |
| e, 'Wifi bytes (logscale)', |
| `select ts, ifnull(ln(sum(value)), 0) as value from network_summary where dev_type = 'wifi' group by 1`, |
| groupId), |
| this.addCounterTrack( |
| e, 'Wifi TX bytes (logscale)', |
| `select ts, ifnull(ln(value), 0) as value from network_summary where dev_type = 'wifi' and dir = 'tx'`, |
| groupId), |
| this.addCounterTrack( |
| e, 'Wifi RX bytes (logscale)', |
| `select ts, ifnull(ln(value), 0) as value from network_summary where dev_type = 'wifi' and dir = 'rx'`, |
| groupId)); |
| } |
| if (features.has('net.modem')) { |
| actions.push( |
| this.addCounterTrack( |
| e, 'Modem bytes (logscale)', |
| `select ts, ifnull(ln(sum(value)), 0) as value from network_summary where dev_type = 'modem' group by 1`, |
| groupId), |
| this.addCounterTrack( |
| e, 'Modem TX bytes (logscale)', |
| `select ts, ifnull(ln(value), 0) as value from network_summary where dev_type = 'modem' and dir = 'tx'`, |
| groupId), |
| this.addCounterTrack( |
| e, 'Modem RX bytes (logscale)', |
| `select ts, ifnull(ln(value), 0) as value from network_summary where dev_type = 'modem' and dir = 'rx'`, |
| groupId), |
| ); |
| } |
| return flatten(actions); |
| } |
| |
| async addModemDetail(e: EngineProxy, features: Set<string>): |
| Promise<DeferredAction<{}>[]> { |
| if (!features.has('atom.modem_activity_info')) { |
| return []; |
| } |
| const {groupId, groupActions} = this.addGroup('Modem Detail'); |
| const actions = [groupActions, this.addModemActivityInfo(e, groupId)]; |
| if (features.has('track.ril')) { |
| actions.push(this.addModemRil(e, groupId)); |
| } |
| return flatten(actions); |
| } |
| |
| async addModemActivityInfo(e: EngineProxy, groupId: string): |
| Promise<DeferredAction<{}>[]> { |
| const query = (name: string, col: string): Promise<DeferredAction<{}>> => |
| this.addCounterTrack( |
| e, |
| name, |
| `select ts, ${col}_ratio as value from modem_activity_info`, |
| groupId); |
| |
| await e.query(MODEM_ACTIVITY_INFO); |
| return flatten([ |
| query('Modem sleep', 'sleep_time'), |
| query('Modem controller idle', 'controller_idle_time'), |
| query('Modem RX time', 'controller_rx_time'), |
| query('Modem TX time power 0', 'controller_tx_time_pl0'), |
| query('Modem TX time power 1', 'controller_tx_time_pl1'), |
| query('Modem TX time power 2', 'controller_tx_time_pl2'), |
| query('Modem TX time power 3', 'controller_tx_time_pl3'), |
| query('Modem TX time power 4', 'controller_tx_time_pl4'), |
| ]); |
| } |
| |
| async addModemRil(e: EngineProxy, groupId: string): |
| Promise<DeferredAction<{}>[]> { |
| const rilStrength = |
| (band: string, value: string): Promise<DeferredAction<{}>> => |
| this.addSliceTrack( |
| e, |
| `Modem signal strength ${band} ${value}`, |
| `SELECT ts, dur, name FROM RilScreenOn WHERE band_name = '${ |
| band}' AND value_name = '${value}'`, |
| groupId); |
| |
| await e.query(MODEM_RIL_STRENGTH); |
| await e.query(MODEM_RIL_CHANNELS_PREAMBLE); |
| |
| return flatten([ |
| rilStrength('LTE', 'rsrp'), |
| rilStrength('LTE', 'rssi'), |
| rilStrength('NR', 'rssi'), |
| rilStrength('NR', 'rssi'), |
| this.addSliceTrack( |
| e, 'Modem channel config', MODEM_RIL_CHANNELS, groupId), |
| ]); |
| } |
| |
| async addKernelWakelocks(e: EngineProxy, features: Set<string>): |
| Promise<DeferredAction<{}>[]> { |
| if (!features.has('atom.kernel_wakelock')) { |
| return []; |
| } |
| const {groupId, groupActions} = this.addGroup('Kernel Wakelock Summary'); |
| |
| await e.query(KERNEL_WAKELOCKS); |
| const result = await e.query(KERNEL_WAKELOCKS_SUMMARY); |
| const it = result.iter({wakelock_name: 'str'}); |
| const actions: MixedActions = [groupActions]; |
| for (; it.valid(); it.next()) { |
| actions.push(this.addSliceTrack( |
| e, |
| it.wakelock_name, |
| `select ts, dur, name from kernel_wakelocks where wakelock_name = "${ |
| it.wakelock_name}"`, |
| groupId)); |
| } |
| return flatten(actions); |
| } |
| |
| async addWakeups(e: EngineProxy, features: Set<string>): |
| Promise<DeferredAction<{}>[]> { |
| if (!features.has('track.suspend_backoff')) { |
| return []; |
| } |
| |
| const {groupId, groupActions} = this.addGroup('Wakeups'); |
| 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 = []; |
| const actions: MixedActions = [groupActions]; |
| let labelOther = false; |
| for (; it.valid(); it.next()) { |
| labelOther = true; |
| actions.push(this.addSliceTrack( |
| e, |
| `Wakeup ${it.item}`, |
| `${sqlPrefix} where item="${it.item}"`, |
| groupId, |
| WAKEUPS_COLUMNS)); |
| items.push(it.item); |
| } |
| actions.push(this.addSliceTrack( |
| e, labelOther ? 'Other wakeups' : 'Wakeups', |
| `${sqlPrefix} where item not in ('${items.join('\',\'')}')`, groupId, |
| WAKEUPS_COLUMNS)); |
| return flatten(actions); |
| } |
| |
| async addHighCpu(e: EngineProxy, features: Set<string>): |
| Promise<DeferredAction<{}>[]> { |
| if (!features.has('atom.cpu_cycles_per_uid_cluster')) { |
| return []; |
| } |
| const {groupId, groupActions} = this.addGroup('CPU per UID (major users)'); |
| |
| 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'}); |
| const actions: MixedActions = [groupActions]; |
| for (; it.valid(); it.next()) { |
| actions.push(this.addCounterTrack( |
| e, |
| `CPU (${it.cluster}): ${it.pkg}`, |
| `select ts, value from high_cpu where pkg = "${ |
| it.pkg}" and cluster="${it.cluster}"`, |
| groupId)); |
| } |
| return flatten(actions); |
| } |
| |
| async addBluetooth(e: EngineProxy, features: Set<string>): |
| Promise<DeferredAction<{}>[]> { |
| if (!Array.from(features.values()) |
| .some( |
| (f) => f.startsWith('atom.bluetooth_') || |
| f.startsWith('atom.ble_'))) { |
| return []; |
| } |
| const {groupId, groupActions} = this.addGroup('Bluetooth'); |
| return flatten([ |
| groupActions, |
| this.addSliceTrack( |
| e, 'BLE Scans (opportunistic)', bleScanQuery('opportunistic'), |
| groupId), |
| this.addSliceTrack( |
| e, 'BLE Scans (filtered)', bleScanQuery('filtered'), groupId), |
| this.addSliceTrack( |
| e, 'BLE Scans (unfiltered)', bleScanQuery('not filtered'), groupId), |
| this.addSliceTrack(e, 'BLE Scan Results', BLE_RESULTS, groupId), |
| this.addSliceTrack(e, 'Connections (ACL)', BT_CONNS_ACL, groupId), |
| this.addSliceTrack(e, 'Connections (SCO)', BT_CONNS_SCO, groupId), |
| this.addSliceTrack( |
| e, 'Link-level Events', BT_LINK_LEVEL_EVENTS, groupId, |
| BT_LINK_LEVEL_EVENTS_COLUMNS), |
| this.addSliceTrack(e, 'A2DP Audio', BT_A2DP_AUDIO, groupId), |
| this.addSliceTrack( |
| e, 'Quality reports', BT_QUALITY_REPORTS, groupId, |
| BT_QUALITY_REPORTS_COLUMNS), |
| this.addSliceTrack( |
| e, 'RSSI Reports', BT_RSSI_REPORTS, groupId, BT_RSSI_REPORTS_COLUMNS), |
| this.addSliceTrack( |
| e, 'HAL Crashes', BT_HAL_CRASHES, groupId, BT_HAL_CRASHES_COLUMNS), |
| this.addSliceTrack( |
| e, 'Code Path Counter', BT_CODE_PATH_COUNTER, groupId, |
| BT_CODE_PATH_COUNTER_COLUMNS), |
| ]); |
| } |
| |
| findGroupId(name: string) { |
| for (const group of Object.values(globals.state.trackGroups)) { |
| if (group.name === name) { |
| return group.id; |
| } |
| } |
| throw new Error(`No group ${name} found`); |
| } |
| |
| addGroup(groupName: string): |
| {groupId: string, groupActions: DeferredAction<{}>[]} { |
| const summaryTrackKey = uuidv4(); |
| const groupUuid = uuidv4(); |
| |
| return { |
| groupId: groupUuid, |
| groupActions: [ |
| Actions.addTrack({ |
| uri: NULL_TRACK_URI, |
| key: summaryTrackKey, |
| trackSortKey: PrimaryTrackSortKey.NULL_TRACK, |
| name: groupName, |
| trackGroup: undefined, |
| }), |
| Actions.addTrackGroup({ |
| summaryTrackKey, |
| name: groupName, |
| id: groupUuid, |
| collapsed: true, |
| }), |
| ], |
| }; |
| } |
| |
| async findFeatures(e: EngineProxy): Promise<Set<string>> { |
| const features = new Set<string>(); |
| |
| 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')`); |
| |
| return features; |
| } |
| |
| async addTracks(ctx: PluginContextTrace): Promise<void> { |
| const actions: MixedActions = []; |
| const features: Set<string> = await this.findFeatures(ctx.engine); |
| |
| const miscGroupId = this.findGroupId('Misc Global Tracks'); |
| |
| if (features.has('atom.thermal_throttling_severity_state_changed')) { |
| actions.push(this.addSliceTrack( |
| ctx.engine, 'Thermal throttling', THERMAL_THROTTLING, miscGroupId)); |
| } |
| |
| actions.push( |
| this.addBatteryStatsEvents(ctx.engine, miscGroupId), |
| this.addNetworkSummary(ctx.engine, features), |
| this.addModemDetail(ctx.engine, features), |
| this.addKernelWakelocks(ctx.engine, features), |
| this.addWakeups(ctx.engine, features), |
| this.addHighCpu(ctx.engine, features), |
| this.addBluetooth(ctx.engine, features), |
| ); |
| globals.dispatchMultiple(await flatten(actions)); |
| } |
| |
| async onTraceLoad(ctx: PluginContextTrace): Promise<void> { |
| // If we add tracks immediately the decider will not have run and things |
| // will go rather wrong. So instead wait until the engine is ready then |
| // add our things on top. This is a bit of a hackm but the best we can |
| // do right now. |
| const disposer = globals.store.subscribe(async () => { |
| if (globals.state.engine?.ready) { |
| disposer.dispose(); |
| await this.addTracks(ctx); |
| } |
| }); |
| } |
| } |
| |
| export const plugin: PluginDescriptor = { |
| pluginId: 'dev.perfetto.AndroidLongBatteryTracing', |
| plugin: AndroidLongBatteryTracing, |
| }; |