1// Copyright (C) 2023 The Android Open Source Project 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15import {Trace} from '../../public/trace'; 16import {PerfettoPlugin} from '../../public/plugin'; 17import {Engine} from '../../trace_processor/engine'; 18import {createQuerySliceTrack} from '../../components/tracks/query_slice_track'; 19import {CounterOptions} from '../../components/tracks/base_counter_track'; 20import {createQueryCounterTrack} from '../../components/tracks/query_counter_track'; 21import {TrackNode} from '../../public/workspace'; 22 23interface ContainedTrace { 24 uuid: string; 25 subscription: string; 26 trigger: string; 27 // NB: these are millis. 28 ts: number; 29 dur: number; 30} 31 32const PACKAGE_LOOKUP = ` 33 create or replace perfetto table package_name_lookup as 34 with installed as ( 35 select uid, string_agg(package_name, ',') as name 36 from package_list 37 where uid >= 10000 38 group by 1 39 ), 40 system(uid, name) as ( 41 values 42 (0, 'AID_ROOT'), 43 (1000, 'AID_SYSTEM_USER'), 44 (1001, 'AID_RADIO'), 45 (1082, 'AID_ARTD') 46 ) 47 select uid, name from installed 48 union all 49 select uid, name from system 50 order by uid; 51 52 -- Adds a "package_name" column by joining on "uid" from the source table. 53 create or replace perfetto macro add_package_name(src TableOrSubquery) returns TableOrSubquery as ( 54 select A.*, ifnull(B.name, "uid=" || A.uid) as package_name 55 from $src as A 56 left join package_name_lookup as B 57 on (B.uid = (A.uid % 100000)) 58 ); 59`; 60 61const DEFAULT_NETWORK = ` 62 with base as ( 63 select 64 ts, 65 substr(s.name, 6) as conn 66 from track t join slice s on t.id = s.track_id 67 where t.name = 'battery_stats.conn' 68 ), 69 diff as ( 70 select 71 ts, 72 conn, 73 conn != lag(conn) over (order by ts) as keep 74 from base 75 ) 76 select 77 ts, 78 ifnull(lead(ts) over (order by ts), (select end_ts from trace_bounds)) - ts as dur, 79 case 80 when conn like '-1:%' then 'Disconnected' 81 when conn like '0:%' then 'Modem' 82 when conn like '1:%' then 'WiFi' 83 when conn like '4:%' then 'VPN' 84 else conn 85 end as name 86 from diff where keep is null or keep`; 87 88const RADIO_TRANSPORT_TYPE = ` 89 create or replace perfetto view radio_transport_data_conn as 90 select ts, safe_dur AS dur, value_name as data_conn, value AS data_conn_val 91 from android_battery_stats_state 92 where track_name = "battery_stats.data_conn"; 93 94 create or replace perfetto view radio_transport_nr_state as 95 select ts, safe_dur AS dur, value AS nr_state_val 96 from android_battery_stats_state 97 where track_name = "battery_stats.nr_state"; 98 99 drop table if exists radio_transport_join; 100 create virtual table radio_transport_join 101 using span_left_join(radio_transport_data_conn, radio_transport_nr_state); 102 103 create or replace perfetto view radio_transport as 104 select 105 ts, dur, 106 case data_conn_val 107 -- On LTE with NR connected is 5G NSA. 108 when 13 then iif(nr_state_val = 3, '5G (NSA)', data_conn) 109 -- On NR with NR state present, is 5G SA. 110 when 20 then iif(nr_state_val is null, '5G (SA or NSA)', '5G (SA)') 111 else data_conn 112 end as name 113 from radio_transport_join;`; 114 115const TETHERING = ` 116 with base as ( 117 select 118 ts as ts_end, 119 EXTRACT_ARG(arg_set_id, 'network_tethering_reported.duration_millis') * 1000000 as dur 120 from track t join slice s on t.id = s.track_id 121 where t.name = 'Statsd Atoms' 122 and s.name = 'network_tethering_reported' 123 ) 124 select ts_end - dur as ts, dur, 'Tethering' as name from base`; 125 126const NETWORK_SUMMARY = ` 127 create or replace perfetto table network_summary as 128 with base as ( 129 select 130 cast(ts / 5000000000 as int64) * 5000000000 AS ts, 131 case 132 when iface glob '*wlan*' then 'wifi' 133 when iface glob '*rmnet*' then 'modem' 134 else 'unknown' 135 end as dev_type, 136 package_name as pkg, 137 sum(packet_length) AS value 138 from android_network_packets 139 where (iface glob '*wlan*' or iface glob '*rmnet*') 140 group by 1,2,3 141 ), 142 zeroes as ( 143 select 144 ts, 145 dev_type, 146 pkg, 147 value 148 from base 149 union all 150 select 151 ts + 5000000000 as ts, 152 dev_type, 153 pkg, 154 0 as value 155 from base 156 ), 157 final as ( 158 select 159 ts, 160 dev_type, 161 pkg, 162 sum(value) as value 163 from zeroes 164 group by 1, 2, 3 165 ) 166 select * from final where ts is not null`; 167 168const MODEM_RIL_STRENGTH = ` 169 DROP VIEW IF EXISTS ScreenOn; 170 CREATE VIEW ScreenOn AS 171 SELECT ts, dur FROM ( 172 SELECT 173 ts, value, 174 LEAD(ts, 1, TRACE_END()) OVER (ORDER BY ts)-ts AS dur 175 FROM counter, track ON (counter.track_id = track.id) 176 WHERE track.name = 'ScreenState' 177 ) WHERE value = 2; 178 179 DROP VIEW IF EXISTS RilSignalStrength; 180 CREATE VIEW RilSignalStrength AS 181 With RilMessages AS ( 182 SELECT 183 ts, slice.name, 184 LEAD(ts, 1, TRACE_END()) OVER (ORDER BY ts)-ts AS dur 185 FROM slice, track 186 ON (slice.track_id = track.id) 187 WHERE track.name = 'RIL' 188 AND slice.name GLOB 'UNSOL_SIGNAL_STRENGTH*' 189 ), 190 BandTypes(band_ril, band_name) AS ( 191 VALUES ("CellSignalStrengthLte:", "LTE"), 192 ("CellSignalStrengthNr:", "NR") 193 ), 194 ValueTypes(value_ril, value_name) AS ( 195 VALUES ("rsrp=", "rsrp"), 196 ("rssi=", "rssi") 197 ), 198 Extracted AS ( 199 SELECT ts, dur, band_name, value_name, ( 200 SELECT CAST(SUBSTR(key_str, start_idx+1, end_idx-start_idx-1) AS INT64) AS value 201 FROM ( 202 SELECT key_str, INSTR(key_str, "=") AS start_idx, INSTR(key_str, " ") AS end_idx 203 FROM ( 204 SELECT SUBSTR(band_str, INSTR(band_str, value_ril)) AS key_str 205 FROM (SELECT SUBSTR(name, INSTR(name, band_ril)) AS band_str) 206 ) 207 ) 208 ) AS value 209 FROM RilMessages 210 JOIN BandTypes 211 JOIN ValueTypes 212 ) 213 SELECT 214 ts, dur, band_name, value_name, value, 215 value_name || "=" || IIF(value = 2147483647, "unknown", ""||value) AS name, 216 ROW_NUMBER() OVER (ORDER BY ts) as id, 217 DENSE_RANK() OVER (ORDER BY band_name, value_name) AS track_id 218 FROM Extracted; 219 220 DROP TABLE IF EXISTS RilScreenOn; 221 CREATE VIRTUAL TABLE RilScreenOn 222 USING SPAN_JOIN(RilSignalStrength PARTITIONED track_id, ScreenOn)`; 223 224const MODEM_RIL_CHANNELS_PREAMBLE = ` 225 CREATE OR REPLACE PERFETTO FUNCTION EXTRACT_KEY_VALUE(source STRING, key_name STRING) RETURNS STRING AS 226 SELECT SUBSTR(trimmed, INSTR(trimmed, "=")+1, INSTR(trimmed, ",") - INSTR(trimmed, "=") - 1) 227 FROM (SELECT SUBSTR($source, INSTR($source, $key_name)) AS trimmed);`; 228 229const MODEM_RIL_CHANNELS = ` 230 With RawChannelConfig AS ( 231 SELECT ts, slice.name AS raw_config 232 FROM slice, track 233 ON (slice.track_id = track.id) 234 WHERE track.name = 'RIL' 235 AND slice.name LIKE 'UNSOL_PHYSICAL_CHANNEL_CONFIG%' 236 ), 237 Attributes(attribute, attrib_name) AS ( 238 VALUES ("mCellBandwidthDownlinkKhz", "downlink"), 239 ("mCellBandwidthUplinkKhz", "uplink"), 240 ("mNetworkType", "network"), 241 ("mBand", "band") 242 ), 243 Slots(idx, slot_name) AS ( 244 VALUES (0, "primary"), 245 (1, "secondary 1"), 246 (2, "secondary 2") 247 ), 248 Stage1 AS ( 249 SELECT *, IFNULL(EXTRACT_KEY_VALUE(STR_SPLIT(raw_config, "}, {", idx), attribute), "") AS name 250 FROM RawChannelConfig 251 JOIN Attributes 252 JOIN Slots 253 ), 254 Stage2 AS ( 255 SELECT *, LAG(name) OVER (PARTITION BY idx, attribute ORDER BY ts) AS last_name 256 FROM Stage1 257 ), 258 Stage3 AS ( 259 SELECT *, LEAD(ts, 1, TRACE_END()) OVER (PARTITION BY idx, attribute ORDER BY ts) - ts AS dur 260 FROM Stage2 WHERE name != last_name 261 ) 262 SELECT ts, dur, slot_name || "-" || attrib_name || "=" || name AS name 263 FROM Stage3`; 264 265const MODEM_CELL_RESELECTION = ` 266 with base as ( 267 select 268 ts, 269 s.name as raw_ril, 270 ifnull(str_split(str_split(s.name, 'CellIdentityLte{', 1), ', operatorNames', 0), 271 str_split(str_split(s.name, 'CellIdentityNr{', 1), ', operatorNames', 0)) as cell_id 272 from track t join slice s on t.id = s.track_id 273 where t.name = 'RIL' and s.name like '%DATA_REGISTRATION_STATE%' 274 ), 275 base2 as ( 276 select 277 ts, 278 raw_ril, 279 case 280 when cell_id like '%earfcn%' then 'LTE ' || cell_id 281 when cell_id like '%nrarfcn%' then 'NR ' || cell_id 282 when cell_id is null then 'Unknown' 283 else cell_id 284 end as cell_id 285 from base 286 ), 287 base3 as ( 288 select ts, cell_id , lag(cell_id) over (order by ts) as lag_cell_id, raw_ril 289 from base2 290 ) 291 select ts, 0 as dur, cell_id as name, raw_ril 292 from base3 293 where cell_id != lag_cell_id 294 order by ts`; 295 296const SUSPEND_RESUME = ` 297 SELECT 298 ts, 299 dur, 300 'Suspended' AS name 301 FROM android_suspend_state 302 WHERE power_state = 'suspended'`; 303 304const THERMAL_THROTTLING = ` 305 with step1 as ( 306 select 307 ts, 308 EXTRACT_ARG(arg_set_id, 'thermal_throttling_severity_state_changed.sensor_type') as sensor_type, 309 EXTRACT_ARG(arg_set_id, 'thermal_throttling_severity_state_changed.sensor_name') as sensor_name, 310 EXTRACT_ARG(arg_set_id, 'thermal_throttling_severity_state_changed.temperature_deci_celsius') / 10.0 as temperature_celcius, 311 EXTRACT_ARG(arg_set_id, 'thermal_throttling_severity_state_changed.severity') as severity 312 from track t join slice s on t.id = s.track_id 313 where t.name = 'Statsd Atoms' 314 and s.name = 'thermal_throttling_severity_state_changed' 315 ), 316 step2 as ( 317 select 318 ts, 319 lead(ts) over (partition by sensor_type, sensor_name order by ts) - ts as dur, 320 sensor_type, 321 sensor_name, 322 temperature_celcius, 323 severity 324 from step1 325 where sensor_type not like 'TEMPERATURE_TYPE_BCL_%' 326 ) 327 select 328 ts, 329 dur, 330 case sensor_name 331 when 'VIRTUAL-SKIN' then '' 332 else sensor_name || ' is ' 333 end || severity || ' (' || temperature_celcius || 'C)' as name 334 from step2 335 where severity != 'NONE'`; 336 337const KERNEL_WAKELOCKS_STATSD = ` 338 create or replace perfetto table kernel_wakelocks_statsd as 339 with kernel_wakelock_args as ( 340 select 341 arg_set_id, 342 min(iif(key = 'kernel_wakelock.name', string_value, null)) as wakelock_name, 343 min(iif(key = 'kernel_wakelock.count', int_value, null)) as count, 344 min(iif(key = 'kernel_wakelock.time_micros', int_value, null)) as time_micros 345 from args 346 where key in ( 347 'kernel_wakelock.name', 348 'kernel_wakelock.count', 349 'kernel_wakelock.time_micros' 350 ) 351 group by 1 352 ), 353 interesting as ( 354 select wakelock_name 355 from ( 356 select wakelock_name, max(time_micros)-min(time_micros) as delta_us 357 from kernel_wakelock_args 358 group by 1 359 ) 360 -- Only consider wakelocks with over 1 second of time during the whole trace 361 where delta_us > 1e6 362 ), 363 step1 as ( 364 select ts, wakelock_name, count, time_micros 365 from kernel_wakelock_args 366 join interesting using (wakelock_name) 367 join slice using (arg_set_id) 368 ), 369 step2 as ( 370 select 371 ts, 372 wakelock_name, 373 lead(ts) over (partition by wakelock_name order by ts) as ts_end, 374 lead(count) over (partition by wakelock_name order by ts) - count as count, 375 (lead(time_micros) over (partition by wakelock_name order by ts) - time_micros) * 1000 as wakelock_dur 376 from step1 377 ), 378 step3 as ( 379 select 380 ts, 381 ts_end, 382 ifnull((select sum(dur) from android_suspend_state s 383 where power_state = 'suspended' 384 and s.ts > step2.ts 385 and s.ts < step2.ts_end), 0) as suspended_dur, 386 wakelock_name, 387 count, 388 wakelock_dur 389 from step2 390 where wakelock_dur is not null 391 and wakelock_dur >= 0 392 ) 393 select 394 ts, 395 ts_end - ts as dur, 396 wakelock_name, 397 min(100.0 * wakelock_dur / (ts_end - ts - suspended_dur), 100) as value 398 from step3`; 399 400const KERNEL_WAKELOCKS_STATSD_SUMMARY = ` 401 select wakelock_name, max(value) as max_value 402 from kernel_wakelocks_statsd 403 where wakelock_name not in ('PowerManager.SuspendLockout', 'PowerManagerService.Display') 404 group by 1 405 having max_value > 1 406 order by 1;`; 407 408const HIGH_CPU = ` 409 create or replace perfetto table high_cpu as 410 with cpu_cycles_args AS ( 411 select 412 arg_set_id, 413 min(iif(key = 'cpu_cycles_per_uid_cluster.uid', int_value, null)) as uid, 414 min(iif(key = 'cpu_cycles_per_uid_cluster.cluster', int_value, null)) as cluster, 415 min(iif(key = 'cpu_cycles_per_uid_cluster.time_millis', int_value, null)) as time_millis 416 from args 417 where key in ( 418 'cpu_cycles_per_uid_cluster.uid', 419 'cpu_cycles_per_uid_cluster.cluster', 420 'cpu_cycles_per_uid_cluster.time_millis' 421 ) 422 group by 1 423 ), 424 interesting AS ( 425 select uid, cluster 426 from ( 427 select uid, cluster, max(time_millis)-min(time_millis) as delta_ms 428 from cpu_cycles_args 429 group by 1, 2 430 ) 431 -- Only consider tracks with over 1 second of cpu during the whole trace 432 where delta_ms > 1e3 433 ), 434 base as ( 435 select ts, uid, cluster, sum(time_millis) as time_millis 436 from cpu_cycles_args 437 join interesting using (uid, cluster) 438 join slice using (arg_set_id) 439 group by 1, 2, 3 440 ), 441 with_windows as ( 442 select 443 ts, 444 uid, 445 cluster, 446 lead(ts) over (partition by uid, cluster order by ts) - ts as dur, 447 (lead(time_millis) over (partition by uid, cluster order by ts) - time_millis) * 1000000.0 as cpu_dur 448 from base 449 ), 450 with_ratio as ( 451 select 452 ts, 453 iif(dur is null, 0, max(0, 100.0 * cpu_dur / dur)) as value, 454 case cluster when 0 then 'little' when 1 then 'mid' when 2 then 'big' else 'cl-' || cluster end as cluster, 455 package_name as pkg 456 from add_package_name!(with_windows) 457 ) 458 select ts, sum(value) as value, cluster, pkg 459 from with_ratio 460 group by 1, 3, 4`; 461 462const WAKEUPS_COLUMNS = [ 463 'item', 464 'type', 465 'raw_wakeup', 466 'on_device_attribution', 467 'suspend_quality', 468 'backoff_state', 469 'backoff_reason', 470 'backoff_count', 471 'backoff_millis', 472]; 473 474function bleScanQuery(condition: string) { 475 return ` 476 with step1 as ( 477 select 478 ts, 479 extract_arg(arg_set_id, 'ble_scan_state_changed.attribution_node[0].tag') as name, 480 extract_arg(arg_set_id, 'ble_scan_state_changed.is_opportunistic') as opportunistic, 481 extract_arg(arg_set_id, 'ble_scan_state_changed.is_filtered') as filtered, 482 extract_arg(arg_set_id, 'ble_scan_state_changed.state') as state 483 from track t join slice s on t.id = s.track_id 484 where t.name = 'Statsd Atoms' 485 and s.name = 'ble_scan_state_changed' 486 ), 487 step2 as ( 488 select 489 ts, 490 name, 491 state, 492 opportunistic, 493 filtered, 494 lead(ts) over (partition by name order by ts) - ts as dur 495 from step1 496 ) 497 select ts, dur, name from step2 where state = 'ON' and ${condition} and dur is not null`; 498} 499 500const BLE_RESULTS = ` 501 with step1 as ( 502 select 503 ts, 504 extract_arg(arg_set_id, 'ble_scan_result_received.attribution_node[0].tag') as name, 505 extract_arg(arg_set_id, 'ble_scan_result_received.num_results') as num_results 506 from track t join slice s on t.id = s.track_id 507 where t.name = 'Statsd Atoms' 508 and s.name = 'ble_scan_result_received' 509 ) 510 select 511 ts, 512 0 as dur, 513 name || ' (' || num_results || ' results)' as name 514 from step1`; 515 516const BT_A2DP_AUDIO = ` 517 with step1 as ( 518 select 519 ts, 520 EXTRACT_ARG(arg_set_id, 'bluetooth_a2dp_playback_state_changed.playback_state') as playback_state, 521 EXTRACT_ARG(arg_set_id, 'bluetooth_a2dp_playback_state_changed.audio_coding_mode') as audio_coding_mode, 522 EXTRACT_ARG(arg_set_id, 'bluetooth_a2dp_playback_state_changed.metric_id') as metric_id 523 from track t join slice s on t.id = s.track_id 524 where t.name = 'Statsd Atoms' 525 and s.name = 'bluetooth_a2dp_playback_state_changed' 526 ), 527 step2 as ( 528 select 529 ts, 530 lead(ts) over (partition by metric_id order by ts) - ts as dur, 531 playback_state, 532 audio_coding_mode, 533 metric_id 534 from step1 535 ) 536 select 537 ts, 538 dur, 539 audio_coding_mode as name 540 from step2 541 where playback_state = 'PLAYBACK_STATE_PLAYING'`; 542 543const BT_CONNS_ACL = ` 544 with acl1 as ( 545 select 546 ts, 547 EXTRACT_ARG(arg_set_id, 'bluetooth_acl_connection_state_changed.state') as state, 548 EXTRACT_ARG(arg_set_id, 'bluetooth_acl_connection_state_changed.transport') as transport, 549 EXTRACT_ARG(arg_set_id, 'bluetooth_acl_connection_state_changed.metric_id') as metric_id 550 from track t join slice s on t.id = s.track_id 551 where t.name = 'Statsd Atoms' 552 and s.name = 'bluetooth_acl_connection_state_changed' 553 ), 554 acl2 as ( 555 select 556 ts, 557 lead(ts) over (partition by metric_id, transport order by ts) - ts as dur, 558 state, 559 transport, 560 metric_id 561 from acl1 562 ) 563 select 564 ts, 565 dur, 566 'Device ' || metric_id || 567 ' (' || case transport when 'TRANSPORT_TYPE_BREDR' then 'Classic' when 'TRANSPORT_TYPE_LE' then 'BLE' end || ')' as name 568 from acl2 569 where state != 'CONNECTION_STATE_DISCONNECTED' and dur is not null`; 570 571const BT_CONNS_SCO = ` 572 with sco1 as ( 573 select 574 ts, 575 EXTRACT_ARG(arg_set_id, 'bluetooth_sco_connection_state_changed.state') as state, 576 EXTRACT_ARG(arg_set_id, 'bluetooth_sco_connection_state_changed.codec') as codec, 577 EXTRACT_ARG(arg_set_id, 'bluetooth_sco_connection_state_changed.metric_id') as metric_id 578 from track t join slice s on t.id = s.track_id 579 where t.name = 'Statsd Atoms' 580 and s.name = 'bluetooth_sco_connection_state_changed' 581 ), 582 sco2 as ( 583 select 584 ts, 585 lead(ts) over (partition by metric_id, codec order by ts) - ts as dur, 586 state, 587 codec, 588 metric_id 589 from sco1 590 ) 591 select 592 ts, 593 dur, 594 case state when 'CONNECTION_STATE_CONNECTED' then '' when 'CONNECTION_STATE_CONNECTING' then 'Connecting ' when 'CONNECTION_STATE_DISCONNECTING' then 'Disconnecting ' else 'unknown ' end || 595 'Device ' || metric_id || ' (' || 596 case codec when 'SCO_CODEC_CVSD' then 'CVSD' when 'SCO_CODEC_MSBC' then 'MSBC' end || ')' as name 597 from sco2 598 where state != 'CONNECTION_STATE_DISCONNECTED' and dur is not null`; 599 600const BT_LINK_LEVEL_EVENTS = ` 601 with base as ( 602 select 603 ts, 604 EXTRACT_ARG(arg_set_id, 'bluetooth_link_layer_connection_event.direction') as direction, 605 EXTRACT_ARG(arg_set_id, 'bluetooth_link_layer_connection_event.type') as type, 606 EXTRACT_ARG(arg_set_id, 'bluetooth_link_layer_connection_event.hci_cmd') as hci_cmd, 607 EXTRACT_ARG(arg_set_id, 'bluetooth_link_layer_connection_event.hci_event') as hci_event, 608 EXTRACT_ARG(arg_set_id, 'bluetooth_link_layer_connection_event.hci_ble_event') as hci_ble_event, 609 EXTRACT_ARG(arg_set_id, 'bluetooth_link_layer_connection_event.cmd_status') as cmd_status, 610 EXTRACT_ARG(arg_set_id, 'bluetooth_link_layer_connection_event.reason_code') as reason_code, 611 EXTRACT_ARG(arg_set_id, 'bluetooth_link_layer_connection_event.metric_id') as metric_id 612 from track t join slice s on t.id = s.track_id 613 where t.name = 'Statsd Atoms' 614 and s.name = 'bluetooth_link_layer_connection_event' 615 ) 616 select 617 *, 618 0 as dur, 619 'Device '|| metric_id as name 620 from base`; 621 622const BT_LINK_LEVEL_EVENTS_COLUMNS = [ 623 'direction', 624 'type', 625 'hci_cmd', 626 'hci_event', 627 'hci_ble_event', 628 'cmd_status', 629 'reason_code', 630 'metric_id', 631]; 632 633const BT_QUALITY_REPORTS = ` 634 with base as ( 635 select 636 ts, 637 EXTRACT_ARG(arg_set_id, 'bluetooth_quality_report_reported.quality_report_id') as quality_report_id, 638 EXTRACT_ARG(arg_set_id, 'bluetooth_quality_report_reported.packet_types') as packet_types, 639 EXTRACT_ARG(arg_set_id, 'bluetooth_quality_report_reported.connection_handle') as connection_handle, 640 EXTRACT_ARG(arg_set_id, 'bluetooth_quality_report_reported.connection_role') as connection_role, 641 EXTRACT_ARG(arg_set_id, 'bluetooth_quality_report_reported.tx_power_level') as tx_power_level, 642 EXTRACT_ARG(arg_set_id, 'bluetooth_quality_report_reported.rssi') as rssi, 643 EXTRACT_ARG(arg_set_id, 'bluetooth_quality_report_reported.snr') as snr, 644 EXTRACT_ARG(arg_set_id, 'bluetooth_quality_report_reported.unused_afh_channel_count') as unused_afh_channel_count, 645 EXTRACT_ARG(arg_set_id, 'bluetooth_quality_report_reported.afh_select_unideal_channel_count') as afh_select_unideal_channel_count, 646 EXTRACT_ARG(arg_set_id, 'bluetooth_quality_report_reported.lsto') as lsto, 647 EXTRACT_ARG(arg_set_id, 'bluetooth_quality_report_reported.connection_piconet_clock') as connection_piconet_clock, 648 EXTRACT_ARG(arg_set_id, 'bluetooth_quality_report_reported.retransmission_count') as retransmission_count, 649 EXTRACT_ARG(arg_set_id, 'bluetooth_quality_report_reported.no_rx_count') as no_rx_count, 650 EXTRACT_ARG(arg_set_id, 'bluetooth_quality_report_reported.nak_count') as nak_count, 651 EXTRACT_ARG(arg_set_id, 'bluetooth_quality_report_reported.flow_off_count') as flow_off_count, 652 EXTRACT_ARG(arg_set_id, 'bluetooth_quality_report_reported.buffer_overflow_bytes') as buffer_overflow_bytes, 653 EXTRACT_ARG(arg_set_id, 'bluetooth_quality_report_reported.buffer_underflow_bytes') as buffer_underflow_bytes 654 from track t join slice s on t.id = s.track_id 655 where t.name = 'Statsd Atoms' 656 and s.name = 'bluetooth_quality_report_reported' 657 ) 658 select 659 *, 660 0 as dur, 661 'Connection '|| connection_handle as name 662 from base`; 663 664const BT_QUALITY_REPORTS_COLUMNS = [ 665 'quality_report_id', 666 'packet_types', 667 'connection_handle', 668 'connection_role', 669 'tx_power_level', 670 'rssi', 671 'snr', 672 'unused_afh_channel_count', 673 'afh_select_unideal_channel_count', 674 'lsto', 675 'connection_piconet_clock', 676 'retransmission_count', 677 'no_rx_count', 678 'nak_count', 679 'flow_off_count', 680 'buffer_overflow_bytes', 681 'buffer_underflow_bytes', 682]; 683 684const BT_RSSI_REPORTS = ` 685 with base as ( 686 select 687 ts, 688 EXTRACT_ARG(arg_set_id, 'bluetooth_device_rssi_reported.connection_handle') as connection_handle, 689 EXTRACT_ARG(arg_set_id, 'bluetooth_device_rssi_reported.hci_status') as hci_status, 690 EXTRACT_ARG(arg_set_id, 'bluetooth_device_rssi_reported.rssi') as rssi, 691 EXTRACT_ARG(arg_set_id, 'bluetooth_device_rssi_reported.metric_id') as metric_id 692 from track t join slice s on t.id = s.track_id 693 where t.name = 'Statsd Atoms' 694 and s.name = 'bluetooth_device_rssi_reported' 695 ) 696 select 697 *, 698 0 as dur, 699 'Connection '|| connection_handle as name 700 from base`; 701 702const BT_RSSI_REPORTS_COLUMNS = [ 703 'connection_handle', 704 'hci_status', 705 'rssi', 706 'metric_id', 707]; 708 709const BT_CODE_PATH_COUNTER = ` 710 with base as ( 711 select 712 ts, 713 EXTRACT_ARG(arg_set_id, 'bluetooth_code_path_counter.key') as key, 714 EXTRACT_ARG(arg_set_id, 'bluetooth_code_path_counter.number') as number 715 from track t join slice s on t.id = s.track_id 716 where t.name = 'Statsd Atoms' 717 and s.name = 'bluetooth_code_path_counter' 718 ) 719 select 720 *, 721 0 as dur, 722 key as name 723 from base`; 724 725const BT_CODE_PATH_COUNTER_COLUMNS = ['key', 'number']; 726 727const BT_HAL_CRASHES = ` 728 with base as ( 729 select 730 ts, 731 EXTRACT_ARG(arg_set_id, 'bluetooth_hal_crash_reason_reported.metric_id') as metric_id, 732 EXTRACT_ARG(arg_set_id, 'bluetooth_hal_crash_reason_reported.error_code') as error_code, 733 EXTRACT_ARG(arg_set_id, 'bluetooth_hal_crash_reason_reported.vendor_error_code') as vendor_error_code 734 from track t join slice s on t.id = s.track_id 735 where t.name = 'Statsd Atoms' 736 and s.name = 'bluetooth_hal_crash_reason_reported' 737 ) 738 select 739 *, 740 0 as dur, 741 'Device ' || metric_id as name 742 from base`; 743 744const BT_HAL_CRASHES_COLUMNS = ['metric_id', 'error_code', 'vendor_error_code']; 745 746const BT_BYTES = ` 747 with step1 as ( 748 select 749 ts, 750 EXTRACT_ARG(arg_set_id, 'bluetooth_bytes_transfer.uid') as uid, 751 EXTRACT_ARG(arg_set_id, 'bluetooth_bytes_transfer.tx_bytes') as tx_bytes, 752 EXTRACT_ARG(arg_set_id, 'bluetooth_bytes_transfer.rx_bytes') as rx_bytes 753 from track t join slice s on t.id = s.track_id 754 where t.name = 'Statsd Atoms' 755 and s.name = 'bluetooth_bytes_transfer' 756 ), 757 step2 as ( 758 select 759 ts, 760 lead(ts) over (partition by uid order by ts) - ts as dur, 761 uid, 762 lead(tx_bytes) over (partition by uid order by ts) - tx_bytes as tx_bytes, 763 lead(rx_bytes) over (partition by uid order by ts) - rx_bytes as rx_bytes 764 from step1 765 ), 766 step3 as ( 767 select 768 ts, 769 dur, 770 uid % 100000 as uid, 771 sum(tx_bytes) as tx_bytes, 772 sum(rx_bytes) as rx_bytes 773 from step2 774 where tx_bytes >=0 and rx_bytes >=0 775 group by 1,2,3 776 having tx_bytes > 0 or rx_bytes > 0 777 ) 778 select 779 ts, 780 dur, 781 format("%s: TX %d bytes / RX %d bytes", package_name, tx_bytes, rx_bytes) as name 782 from add_package_name!(step3) 783`; 784 785// See go/bt_system_context_report for reference on the bit-twiddling. 786const BT_ACTIVITY = ` 787 create perfetto table bt_activity as 788 with step1 as ( 789 select 790 EXTRACT_ARG(arg_set_id, 'bluetooth_activity_info.timestamp_millis') * 1000000 as ts, 791 EXTRACT_ARG(arg_set_id, 'bluetooth_activity_info.bluetooth_stack_state') as bluetooth_stack_state, 792 EXTRACT_ARG(arg_set_id, 'bluetooth_activity_info.controller_idle_time_millis') * 1000000 as controller_idle_dur, 793 EXTRACT_ARG(arg_set_id, 'bluetooth_activity_info.controller_tx_time_millis') * 1000000 as controller_tx_dur, 794 EXTRACT_ARG(arg_set_id, 'bluetooth_activity_info.controller_rx_time_millis') * 1000000 as controller_rx_dur 795 from track t join slice s on t.id = s.track_id 796 where t.name = 'Statsd Atoms' 797 and s.name = 'bluetooth_activity_info' 798 ), 799 step2 as ( 800 select 801 ts, 802 lead(ts) over (order by ts) - ts as dur, 803 bluetooth_stack_state, 804 lead(controller_idle_dur) over (order by ts) - controller_idle_dur as controller_idle_dur, 805 lead(controller_tx_dur) over (order by ts) - controller_tx_dur as controller_tx_dur, 806 lead(controller_rx_dur) over (order by ts) - controller_rx_dur as controller_rx_dur 807 from step1 808 ) 809 select 810 ts, 811 dur, 812 bluetooth_stack_state & 0x0000000F as acl_active_count, 813 bluetooth_stack_state & 0x000000F0 >> 4 as acl_sniff_count, 814 bluetooth_stack_state & 0x00000F00 >> 8 as acl_ble_count, 815 bluetooth_stack_state & 0x0000F000 >> 12 as advertising_count, 816 case bluetooth_stack_state & 0x000F0000 >> 16 817 when 0 then 0 818 when 1 then 5 819 when 2 then 10 820 when 3 then 25 821 when 4 then 100 822 else -1 823 end as le_scan_duty_cycle, 824 bluetooth_stack_state & 0x00100000 >> 20 as inquiry_active, 825 bluetooth_stack_state & 0x00200000 >> 21 as sco_active, 826 bluetooth_stack_state & 0x00400000 >> 22 as a2dp_active, 827 bluetooth_stack_state & 0x00800000 >> 23 as le_audio_active, 828 max(0, 100.0 * controller_idle_dur / dur) as controller_idle_pct, 829 max(0, 100.0 * controller_tx_dur / dur) as controller_tx_pct, 830 max(0, 100.0 * controller_rx_dur / dur) as controller_rx_pct 831 from step2 832`; 833 834export default class implements PerfettoPlugin { 835 static readonly id = 'dev.perfetto.AndroidLongBatteryTracing'; 836 private readonly groups = new Map<string, TrackNode>(); 837 838 private addTrack(ctx: Trace, track: TrackNode, groupName?: string): void { 839 if (groupName) { 840 const existingGroup = this.groups.get(groupName); 841 if (existingGroup) { 842 existingGroup.addChildInOrder(track); 843 } else { 844 const group = new TrackNode({title: groupName, isSummary: true}); 845 group.addChildInOrder(track); 846 this.groups.set(groupName, group); 847 ctx.workspace.addChildInOrder(group); 848 } 849 } else { 850 ctx.workspace.addChildInOrder(track); 851 } 852 } 853 854 async addSliceTrack( 855 ctx: Trace, 856 name: string, 857 query: string, 858 groupName?: string, 859 columns: string[] = [], 860 ) { 861 const uri = `/long_battery_tracing_${name}`; 862 const track = await createQuerySliceTrack({ 863 trace: ctx, 864 uri, 865 data: { 866 sqlSource: query, 867 columns: ['ts', 'dur', 'name', ...columns], 868 }, 869 argColumns: columns, 870 }); 871 ctx.tracks.registerTrack({ 872 uri, 873 title: name, 874 track, 875 }); 876 const trackNode = new TrackNode({uri, title: name}); 877 this.addTrack(ctx, trackNode, groupName); 878 } 879 880 async addCounterTrack( 881 ctx: Trace, 882 name: string, 883 query: string, 884 groupName: string, 885 options?: Partial<CounterOptions>, 886 ) { 887 const uri = `/long_battery_tracing_${name}`; 888 const track = await createQueryCounterTrack({ 889 trace: ctx, 890 uri, 891 data: { 892 sqlSource: query, 893 columns: ['ts', 'value'], 894 }, 895 options, 896 }); 897 ctx.tracks.registerTrack({ 898 uri, 899 title: name, 900 track, 901 }); 902 const trackNode = new TrackNode({uri, title: name}); 903 this.addTrack(ctx, trackNode, groupName); 904 } 905 906 async addBatteryStatsState( 907 ctx: Trace, 908 name: string, 909 track: string, 910 groupName: string, 911 features: Set<string>, 912 ) { 913 if (!features.has(`track.${track}`)) { 914 return; 915 } 916 await this.addSliceTrack( 917 ctx, 918 name, 919 `SELECT ts, safe_dur AS dur, value_name AS name 920 FROM android_battery_stats_state 921 WHERE track_name = "${track}"`, 922 groupName, 923 ); 924 } 925 926 async addBatteryStatsEvent( 927 ctx: Trace, 928 name: string, 929 track: string, 930 groupName: string | undefined, 931 features: Set<string>, 932 ) { 933 if (!features.has(`track.${track}`)) { 934 return; 935 } 936 937 await this.addSliceTrack( 938 ctx, 939 name, 940 `SELECT ts, safe_dur AS dur, str_value AS name 941 FROM android_battery_stats_event_slices 942 WHERE track_name = "${track}"`, 943 groupName, 944 ); 945 } 946 947 async addDeviceState(ctx: Trace, features: Set<string>): Promise<void> { 948 if (!features.has('track.battery_stats.*')) { 949 return; 950 } 951 952 const query = (name: string, track: string) => 953 this.addBatteryStatsEvent(ctx, name, track, undefined, features); 954 955 const e = ctx.engine; 956 await e.query(`INCLUDE PERFETTO MODULE android.battery_stats;`); 957 await e.query(`INCLUDE PERFETTO MODULE android.suspend;`); 958 await e.query(`INCLUDE PERFETTO MODULE android.battery.charging_states;`); 959 await e.query(`INCLUDE PERFETTO MODULE android.battery.doze;`); 960 await e.query(`INCLUDE PERFETTO MODULE android.screen_state;`); 961 962 await this.addSliceTrack( 963 ctx, 964 'Device State: Screen state', 965 `SELECT ts, dur, screen_state AS name FROM android_screen_state`, 966 ); 967 await this.addSliceTrack( 968 ctx, 969 'Device State: Charging', 970 `SELECT ts, dur, charging_state AS name FROM android_charging_states`, 971 ); 972 await this.addSliceTrack( 973 ctx, 974 'Device State: Suspend / resume', 975 SUSPEND_RESUME, 976 ); 977 await this.addSliceTrack( 978 ctx, 979 'Device State: Doze light state', 980 `SELECT ts, dur, light_idle_state AS name FROM android_light_idle_state`, 981 ); 982 await this.addSliceTrack( 983 ctx, 984 'Device State: Doze deep state', 985 `SELECT ts, dur, deep_idle_state AS name FROM android_deep_idle_state`, 986 ); 987 988 query('Device State: Top app', 'battery_stats.top'); 989 990 await this.addSliceTrack( 991 ctx, 992 'Device State: Long wakelocks', 993 `SELECT 994 ts - 60000000000 as ts, 995 safe_dur + 60000000000 as dur, 996 str_value AS name, 997 package_name as package 998 FROM add_package_name!(( 999 select *, int_value as uid 1000 from android_battery_stats_event_slices 1001 WHERE track_name = "battery_stats.longwake" 1002 ))`, 1003 undefined, 1004 ['package'], 1005 ); 1006 1007 query('Device State: Foreground apps', 'battery_stats.fg'); 1008 query('Device State: Jobs', 'battery_stats.job'); 1009 1010 if (features.has('atom.thermal_throttling_severity_state_changed')) { 1011 await this.addSliceTrack( 1012 ctx, 1013 'Device State: Thermal throttling', 1014 THERMAL_THROTTLING, 1015 ); 1016 } 1017 } 1018 1019 async addAtomCounters(ctx: Trace): Promise<void> { 1020 const e = ctx.engine; 1021 1022 try { 1023 await e.query( 1024 `INCLUDE PERFETTO MODULE 1025 google3.wireless.android.telemetry.trace_extractor.modules.atom_counters_slices`, 1026 ); 1027 } catch (e) { 1028 return; 1029 } 1030 1031 const counters = await e.query( 1032 `select distinct ui_group, ui_name, ui_unit, counter_name 1033 from atom_counters 1034 where ui_name is not null`, 1035 ); 1036 const countersIt = counters.iter({ 1037 ui_group: 'str', 1038 ui_name: 'str', 1039 ui_unit: 'str', 1040 counter_name: 'str', 1041 }); 1042 for (; countersIt.valid(); countersIt.next()) { 1043 const unit = countersIt.ui_unit; 1044 const opts = 1045 unit === '%' 1046 ? {yOverrideMaximum: 100, unit: '%'} 1047 : unit !== undefined 1048 ? {unit} 1049 : undefined; 1050 1051 await this.addCounterTrack( 1052 ctx, 1053 countersIt.ui_name, 1054 `select ts, ${unit === '%' ? 100.0 : 1.0} * counter_value as value 1055 from atom_counters 1056 where counter_name = '${countersIt.counter_name}'`, 1057 countersIt.ui_group, 1058 opts, 1059 ); 1060 } 1061 } 1062 1063 async addAtomSlices(ctx: Trace): Promise<void> { 1064 const e = ctx.engine; 1065 1066 try { 1067 await e.query( 1068 `INCLUDE PERFETTO MODULE 1069 google3.wireless.android.telemetry.trace_extractor.modules.atom_counters_slices`, 1070 ); 1071 } catch (e) { 1072 return; 1073 } 1074 1075 const sliceTracks = await e.query( 1076 `select distinct ui_group, ui_name, atom, field 1077 from atom_slices 1078 where ui_name is not null 1079 order by 1, 2, 3, 4`, 1080 ); 1081 const slicesIt = sliceTracks.iter({ 1082 atom: 'str', 1083 ui_group: 'str', 1084 ui_name: 'str', 1085 field: 'str', 1086 }); 1087 1088 const tracks = new Map< 1089 string, 1090 { 1091 ui_group: string; 1092 ui_name: string; 1093 } 1094 >(); 1095 const fields = new Map<string, string[]>(); 1096 for (; slicesIt.valid(); slicesIt.next()) { 1097 const atom = slicesIt.atom; 1098 let args = fields.get(atom); 1099 if (args === undefined) { 1100 args = []; 1101 fields.set(atom, args); 1102 } 1103 args.push(slicesIt.field); 1104 tracks.set(atom, { 1105 ui_group: slicesIt.ui_group, 1106 ui_name: slicesIt.ui_name, 1107 }); 1108 } 1109 1110 for (const [atom, args] of fields) { 1111 function safeArg(arg: string) { 1112 return arg.replaceAll(/[[\]]/g, '').replaceAll(/\./g, '_'); 1113 } 1114 1115 // We need to make arg names compatible with SQL here because they pass through several 1116 // layers of SQL without being quoted in "". 1117 function argSql(arg: string) { 1118 return `max(case when field = '${arg}' then ifnull(string_value, int_value) end) 1119 as ${safeArg(arg)}`; 1120 } 1121 1122 await this.addSliceTrack( 1123 ctx, 1124 tracks.get(atom)!.ui_name, 1125 `select ts, dur, slice_name as name, ${args.map((a) => argSql(a)).join(', ')} 1126 from atom_slices 1127 where atom = '${atom}' 1128 group by ts, dur, name`, 1129 tracks.get(atom)!.ui_group, 1130 args.map((a) => safeArg(a)), 1131 ); 1132 } 1133 } 1134 1135 async addNetworkSummary(ctx: Trace, features: Set<string>): Promise<void> { 1136 if (!features.has('net.modem') && !features.has('net.wifi')) { 1137 return; 1138 } 1139 1140 const groupName = 'Network Summary'; 1141 1142 const e = ctx.engine; 1143 await e.query(`INCLUDE PERFETTO MODULE android.battery_stats;`); 1144 await e.query(`INCLUDE PERFETTO MODULE android.network_packets;`); 1145 await e.query(NETWORK_SUMMARY); 1146 await e.query(RADIO_TRANSPORT_TYPE); 1147 1148 await this.addSliceTrack( 1149 ctx, 1150 'Default network', 1151 DEFAULT_NETWORK, 1152 groupName, 1153 ); 1154 1155 if (features.has('atom.network_tethering_reported')) { 1156 await this.addSliceTrack(ctx, 'Tethering', TETHERING, groupName); 1157 } 1158 if (features.has('net.wifi')) { 1159 await this.addCounterTrack( 1160 ctx, 1161 'Wifi total bytes', 1162 `select ts, sum(value) as value from network_summary where dev_type = 'wifi' group by 1`, 1163 groupName, 1164 {yDisplay: 'log', yRangeSharingKey: 'net_bytes', unit: 'byte'}, 1165 ); 1166 const result = await e.query( 1167 `select pkg, sum(value) from network_summary where dev_type='wifi' group by 1 order by 2 desc limit 10`, 1168 ); 1169 const it = result.iter({pkg: 'str'}); 1170 for (; it.valid(); it.next()) { 1171 await this.addCounterTrack( 1172 ctx, 1173 `Top wifi: ${it.pkg}`, 1174 `select ts, value from network_summary where dev_type = 'wifi' and pkg = '${it.pkg}'`, 1175 groupName, 1176 {yDisplay: 'log', yRangeSharingKey: 'net_bytes', unit: 'byte'}, 1177 ); 1178 } 1179 } 1180 this.addBatteryStatsState( 1181 ctx, 1182 'Wifi interface', 1183 'battery_stats.wifi_radio', 1184 groupName, 1185 features, 1186 ); 1187 this.addBatteryStatsState( 1188 ctx, 1189 'Wifi supplicant state', 1190 'battery_stats.wifi_suppl', 1191 groupName, 1192 features, 1193 ); 1194 this.addBatteryStatsState( 1195 ctx, 1196 'Wifi strength', 1197 'battery_stats.wifi_signal_strength', 1198 groupName, 1199 features, 1200 ); 1201 if (features.has('net.modem')) { 1202 await this.addCounterTrack( 1203 ctx, 1204 'Modem total bytes', 1205 `select ts, sum(value) as value from network_summary where dev_type = 'modem' group by 1`, 1206 groupName, 1207 {yDisplay: 'log', yRangeSharingKey: 'net_bytes', unit: 'byte'}, 1208 ); 1209 const result = await e.query( 1210 `select pkg, sum(value) from network_summary where dev_type='modem' group by 1 order by 2 desc limit 10`, 1211 ); 1212 const it = result.iter({pkg: 'str'}); 1213 for (; it.valid(); it.next()) { 1214 await this.addCounterTrack( 1215 ctx, 1216 `Top modem: ${it.pkg}`, 1217 `select ts, value from network_summary where dev_type = 'modem' and pkg = '${it.pkg}'`, 1218 groupName, 1219 {yDisplay: 'log', yRangeSharingKey: 'net_bytes', unit: 'byte'}, 1220 ); 1221 } 1222 } 1223 this.addBatteryStatsState( 1224 ctx, 1225 'Cellular interface', 1226 'battery_stats.mobile_radio', 1227 groupName, 1228 features, 1229 ); 1230 await this.addSliceTrack( 1231 ctx, 1232 'Cellular connection', 1233 `select ts, dur, name from radio_transport`, 1234 groupName, 1235 ); 1236 this.addBatteryStatsState( 1237 ctx, 1238 'Cellular strength', 1239 'battery_stats.phone_signal_strength', 1240 groupName, 1241 features, 1242 ); 1243 } 1244 1245 async addModemDetail(ctx: Trace, features: Set<string>): Promise<void> { 1246 const groupName = 'Modem Detail'; 1247 if (features.has('track.ril')) { 1248 await this.addModemRil(ctx, groupName); 1249 } 1250 await this.addModemTeaData(ctx, groupName); 1251 } 1252 1253 async addModemRil(ctx: Trace, groupName: string): Promise<void> { 1254 const rilStrength = async (band: string, value: string) => 1255 await this.addSliceTrack( 1256 ctx, 1257 `Modem signal strength ${band} ${value}`, 1258 `SELECT ts, dur, name FROM RilScreenOn WHERE band_name = '${band}' AND value_name = '${value}'`, 1259 groupName, 1260 ); 1261 1262 const e = ctx.engine; 1263 1264 await e.query(MODEM_RIL_STRENGTH); 1265 await e.query(MODEM_RIL_CHANNELS_PREAMBLE); 1266 1267 await rilStrength('LTE', 'rsrp'); 1268 await rilStrength('LTE', 'rssi'); 1269 await rilStrength('NR', 'rsrp'); 1270 await rilStrength('NR', 'rssi'); 1271 1272 await this.addSliceTrack( 1273 ctx, 1274 'Modem channel config', 1275 MODEM_RIL_CHANNELS, 1276 groupName, 1277 ); 1278 1279 await this.addSliceTrack( 1280 ctx, 1281 'Modem cell reselection', 1282 MODEM_CELL_RESELECTION, 1283 groupName, 1284 ['raw_ril'], 1285 ); 1286 } 1287 1288 async addModemTeaData(ctx: Trace, groupName: string): Promise<void> { 1289 const e = ctx.engine; 1290 1291 try { 1292 await e.query( 1293 `INCLUDE PERFETTO MODULE 1294 google3.wireless.android.telemetry.trace_extractor.modules.modem_tea_metrics`, 1295 ); 1296 } catch { 1297 return; 1298 } 1299 1300 const counters = await e.query( 1301 `select distinct name from pixel_modem_counters`, 1302 ); 1303 const countersIt = counters.iter({name: 'str'}); 1304 for (; countersIt.valid(); countersIt.next()) { 1305 await this.addCounterTrack( 1306 ctx, 1307 countersIt.name, 1308 `select ts, value from pixel_modem_counters where name = '${countersIt.name}'`, 1309 groupName, 1310 ); 1311 } 1312 const slices = await e.query( 1313 `select distinct track_name from pixel_modem_slices`, 1314 ); 1315 const slicesIt = slices.iter({track_name: 'str'}); 1316 for (; slicesIt.valid(); slicesIt.next()) { 1317 await this.addSliceTrack( 1318 ctx, 1319 slicesIt.track_name, 1320 `select ts, dur, slice_name as name from pixel_modem_slices 1321 where track_name = '${slicesIt.track_name}'`, 1322 groupName, 1323 ); 1324 } 1325 } 1326 1327 async addKernelWakelocks(ctx: Trace): Promise<void> { 1328 const e = ctx.engine; 1329 await e.query(`INCLUDE PERFETTO MODULE android.kernel_wakelocks;`); 1330 const result = await e.query( 1331 `SELECT DISTINCT name, type FROM android_kernel_wakelocks`, 1332 ); 1333 const it = result.iter({name: 'str', type: 'str'}); 1334 for (; it.valid(); it.next()) { 1335 await this.addCounterTrack( 1336 ctx, 1337 it.name, 1338 `SELECT ts, dur, held_ratio * 100 AS value 1339 FROM android_kernel_wakelocks 1340 WHERE name = "${it.name}"`, 1341 'Kernel Wakelock Summary', 1342 {yRangeSharingKey: 'kernel_wakelock', unit: '%'}, 1343 ); 1344 } 1345 } 1346 1347 async addKernelWakelocksStatsd( 1348 ctx: Trace, 1349 features: Set<string>, 1350 ): Promise<void> { 1351 if (!features.has('atom.kernel_wakelock')) { 1352 return; 1353 } 1354 const groupName = 'Kernel Wakelock Summary (statsd)'; 1355 1356 const e = ctx.engine; 1357 await e.query(`INCLUDE PERFETTO MODULE android.suspend;`); 1358 await e.query(KERNEL_WAKELOCKS_STATSD); 1359 const result = await e.query(KERNEL_WAKELOCKS_STATSD_SUMMARY); 1360 const it = result.iter({wakelock_name: 'str'}); 1361 for (; it.valid(); it.next()) { 1362 await this.addCounterTrack( 1363 ctx, 1364 `${it.wakelock_name} (statsd)`, 1365 `select ts, dur, value 1366 from kernel_wakelocks_statsd 1367 where wakelock_name = "${it.wakelock_name}"`, 1368 groupName, 1369 {yRangeSharingKey: 'kernel_wakelock_statsd', unit: '%'}, 1370 ); 1371 } 1372 } 1373 1374 async addWakeups(ctx: Trace, features: Set<string>): Promise<void> { 1375 if (!features.has('track.suspend_backoff')) { 1376 return; 1377 } 1378 1379 const e = ctx.engine; 1380 const groupName = 'Wakeups'; 1381 await e.query(`INCLUDE PERFETTO MODULE android.wakeups;`); 1382 const result = await e.query(`select 1383 item, 1384 sum(dur) as sum_dur 1385 from android_wakeups 1386 group by 1 1387 having sum_dur > 600e9`); 1388 const it = result.iter({item: 'str'}); 1389 const sqlPrefix = `select 1390 ts, 1391 dur, 1392 item || case backoff_reason 1393 when 'short' then ' (Short suspend backoff)' 1394 when 'failed' then ' (Failed suspend backoff)' 1395 else '' 1396 end as name, 1397 item, 1398 type, 1399 raw_wakeup, 1400 on_device_attribution, 1401 suspend_quality, 1402 backoff_state, 1403 backoff_reason, 1404 backoff_count, 1405 backoff_millis 1406 from android_wakeups`; 1407 const items = []; 1408 let labelOther = false; 1409 for (; it.valid(); it.next()) { 1410 labelOther = true; 1411 await this.addSliceTrack( 1412 ctx, 1413 `Wakeup ${it.item}`, 1414 `${sqlPrefix} where item="${it.item}"`, 1415 groupName, 1416 WAKEUPS_COLUMNS, 1417 ); 1418 items.push(it.item); 1419 } 1420 await this.addSliceTrack( 1421 ctx, 1422 labelOther ? 'Other wakeups' : 'Wakeups', 1423 `${sqlPrefix} where item not in ('${items.join("','")}')`, 1424 groupName, 1425 WAKEUPS_COLUMNS, 1426 ); 1427 } 1428 1429 async addHighCpu(ctx: Trace, features: Set<string>): Promise<void> { 1430 if (!features.has('atom.cpu_cycles_per_uid_cluster')) { 1431 return; 1432 } 1433 const groupName = 'CPU per UID (major users)'; 1434 1435 const e = ctx.engine; 1436 1437 await e.query(HIGH_CPU); 1438 const result = await e.query( 1439 `select distinct pkg, cluster from high_cpu where value > 10 order by 1, 2`, 1440 ); 1441 const it = result.iter({pkg: 'str', cluster: 'str'}); 1442 for (; it.valid(); it.next()) { 1443 await this.addCounterTrack( 1444 ctx, 1445 `CPU (${it.cluster}): ${it.pkg}`, 1446 `select ts, value from high_cpu where pkg = "${it.pkg}" and cluster="${it.cluster}"`, 1447 groupName, 1448 {yOverrideMaximum: 100, unit: '%'}, 1449 ); 1450 } 1451 } 1452 1453 async addBluetooth(ctx: Trace, features: Set<string>): Promise<void> { 1454 if ( 1455 !Array.from(features.values()).some( 1456 (f) => f.startsWith('atom.bluetooth_') || f.startsWith('atom.ble_'), 1457 ) 1458 ) { 1459 return; 1460 } 1461 const groupName = 'Bluetooth'; 1462 await this.addSliceTrack( 1463 ctx, 1464 'BLE Scans (opportunistic)', 1465 bleScanQuery('opportunistic'), 1466 groupName, 1467 ); 1468 await this.addSliceTrack( 1469 ctx, 1470 'BLE Scans (filtered)', 1471 bleScanQuery('filtered'), 1472 groupName, 1473 ); 1474 await this.addSliceTrack( 1475 ctx, 1476 'BLE Scans (unfiltered)', 1477 bleScanQuery('not filtered'), 1478 groupName, 1479 ); 1480 await this.addSliceTrack(ctx, 'BLE Scan Results', BLE_RESULTS, groupName); 1481 await this.addSliceTrack(ctx, 'Connections (ACL)', BT_CONNS_ACL, groupName); 1482 await this.addSliceTrack(ctx, 'Connections (SCO)', BT_CONNS_SCO, groupName); 1483 await this.addSliceTrack( 1484 ctx, 1485 'Link-level Events', 1486 BT_LINK_LEVEL_EVENTS, 1487 groupName, 1488 BT_LINK_LEVEL_EVENTS_COLUMNS, 1489 ); 1490 await this.addSliceTrack(ctx, 'A2DP Audio', BT_A2DP_AUDIO, groupName); 1491 await this.addSliceTrack( 1492 ctx, 1493 'Bytes Transferred (L2CAP/RFCOMM)', 1494 BT_BYTES, 1495 groupName, 1496 ); 1497 await ctx.engine.query(BT_ACTIVITY); 1498 await this.addCounterTrack( 1499 ctx, 1500 'ACL Classic Active Count', 1501 'select ts, dur, acl_active_count as value from bt_activity', 1502 groupName, 1503 ); 1504 await this.addCounterTrack( 1505 ctx, 1506 'ACL Classic Sniff Count', 1507 'select ts, dur, acl_sniff_count as value from bt_activity', 1508 groupName, 1509 ); 1510 await this.addCounterTrack( 1511 ctx, 1512 'ACL BLE Count', 1513 'select ts, dur, acl_ble_count as value from bt_activity', 1514 groupName, 1515 ); 1516 await this.addCounterTrack( 1517 ctx, 1518 'Advertising Instance Count', 1519 'select ts, dur, advertising_count as value from bt_activity', 1520 groupName, 1521 ); 1522 await this.addCounterTrack( 1523 ctx, 1524 'LE Scan Duty Cycle Maximum', 1525 'select ts, dur, le_scan_duty_cycle as value from bt_activity', 1526 groupName, 1527 {unit: '%'}, 1528 ); 1529 await this.addSliceTrack( 1530 ctx, 1531 'Inquiry Active', 1532 "select ts, dur, 'Active' as name from bt_activity where inquiry_active", 1533 groupName, 1534 ); 1535 await this.addSliceTrack( 1536 ctx, 1537 'SCO Active', 1538 "select ts, dur, 'Active' as name from bt_activity where sco_active", 1539 groupName, 1540 ); 1541 await this.addSliceTrack( 1542 ctx, 1543 'A2DP Active', 1544 "select ts, dur, 'Active' as name from bt_activity where a2dp_active", 1545 groupName, 1546 ); 1547 await this.addSliceTrack( 1548 ctx, 1549 'LE Audio Active', 1550 "select ts, dur, 'Active' as name from bt_activity where le_audio_active", 1551 groupName, 1552 ); 1553 await this.addCounterTrack( 1554 ctx, 1555 'Controller Idle Time', 1556 'select ts, dur, controller_idle_pct as value from bt_activity', 1557 groupName, 1558 {yRangeSharingKey: 'bt_controller_time', unit: '%'}, 1559 ); 1560 await this.addCounterTrack( 1561 ctx, 1562 'Controller TX Time', 1563 'select ts, dur, controller_tx_pct as value from bt_activity', 1564 groupName, 1565 {yRangeSharingKey: 'bt_controller_time', unit: '%'}, 1566 ); 1567 await this.addCounterTrack( 1568 ctx, 1569 'Controller RX Time', 1570 'select ts, dur, controller_rx_pct as value from bt_activity', 1571 groupName, 1572 {yRangeSharingKey: 'bt_controller_time', unit: '%'}, 1573 ); 1574 await this.addSliceTrack( 1575 ctx, 1576 'Quality reports', 1577 BT_QUALITY_REPORTS, 1578 groupName, 1579 BT_QUALITY_REPORTS_COLUMNS, 1580 ); 1581 await this.addSliceTrack( 1582 ctx, 1583 'RSSI Reports', 1584 BT_RSSI_REPORTS, 1585 groupName, 1586 BT_RSSI_REPORTS_COLUMNS, 1587 ); 1588 await this.addSliceTrack( 1589 ctx, 1590 'HAL Crashes', 1591 BT_HAL_CRASHES, 1592 groupName, 1593 BT_HAL_CRASHES_COLUMNS, 1594 ); 1595 await this.addSliceTrack( 1596 ctx, 1597 'Code Path Counter', 1598 BT_CODE_PATH_COUNTER, 1599 groupName, 1600 BT_CODE_PATH_COUNTER_COLUMNS, 1601 ); 1602 } 1603 1604 async addContainedTraces( 1605 ctx: Trace, 1606 containedTraces: ContainedTrace[], 1607 ): Promise<void> { 1608 const bySubscription = new Map<string, ContainedTrace[]>(); 1609 for (const trace of containedTraces) { 1610 if (!bySubscription.has(trace.subscription)) { 1611 bySubscription.set(trace.subscription, []); 1612 } 1613 bySubscription.get(trace.subscription)!.push(trace); 1614 } 1615 1616 for (const [subscription, traces] of bySubscription) { 1617 await this.addSliceTrack( 1618 ctx, 1619 subscription, 1620 traces 1621 .map( 1622 (t) => `SELECT 1623 CAST(${t.ts} * 1e6 AS int) AS ts, 1624 CAST(${t.dur} * 1e6 AS int) AS dur, 1625 '${t.trigger === '' ? 'Trace' : t.trigger}' AS name, 1626 'http://go/trace-uuid/${t.uuid}' AS link 1627 `, 1628 ) 1629 .join(' UNION ALL '), 1630 'Other traces', 1631 ['link'], 1632 ); 1633 } 1634 } 1635 1636 async findFeatures(e: Engine): Promise<Set<string>> { 1637 const features = new Set<string>(); 1638 1639 const addFeatures = async (q: string) => { 1640 const result = await e.query(q); 1641 const it = result.iter({feature: 'str'}); 1642 for (; it.valid(); it.next()) { 1643 features.add(it.feature); 1644 } 1645 }; 1646 1647 await addFeatures(` 1648 select distinct 'atom.' || s.name as feature 1649 from track t join slice s on t.id = s.track_id 1650 where t.name = 'Statsd Atoms'`); 1651 1652 await addFeatures(` 1653 select distinct 1654 case when name like '%wlan%' then 'net.wifi' 1655 when name like '%rmnet%' then 'net.modem' 1656 else 'net.other' 1657 end as feature 1658 from track 1659 where name like '%Transmitted' or name like '%Received'`); 1660 1661 await addFeatures(` 1662 select distinct 'track.' || lower(name) as feature 1663 from track where name in ('RIL', 'suspend_backoff') or name like 'battery_stats.%'`); 1664 1665 await addFeatures(` 1666 select distinct 'track.battery_stats.*' as feature 1667 from track where name like 'battery_stats.%'`); 1668 1669 return features; 1670 } 1671 1672 async addTracks(ctx: Trace): Promise<void> { 1673 const features: Set<string> = await this.findFeatures(ctx.engine); 1674 1675 const containedTraces = (ctx.openerPluginArgs?.containedTraces ?? 1676 []) as ContainedTrace[]; 1677 1678 await ctx.engine.query(PACKAGE_LOOKUP); 1679 await this.addNetworkSummary(ctx, features); 1680 await this.addBluetooth(ctx, features); 1681 await this.addAtomCounters(ctx); 1682 await this.addAtomSlices(ctx); 1683 await this.addModemDetail(ctx, features); 1684 await this.addKernelWakelocks(ctx); 1685 await this.addKernelWakelocksStatsd(ctx, features); 1686 await this.addWakeups(ctx, features); 1687 await this.addDeviceState(ctx, features); 1688 await this.addHighCpu(ctx, features); 1689 await this.addContainedTraces(ctx, containedTraces); 1690 } 1691 1692 async onTraceLoad(ctx: Trace): Promise<void> { 1693 await this.addTracks(ctx); 1694 } 1695} 1696