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 track_name glob '*wlan*' then 'wifi' 133 when track_name 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 (track_name glob '*wlan*' or track_name 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 SCREEN_STATE = ` 305 WITH _counter AS ( 306 SELECT counter.id, ts, 0 AS track_id, value 307 FROM counter 308 JOIN counter_track ON counter_track.id = counter.track_id 309 WHERE name = 'ScreenState' 310 ) 311 SELECT 312 ts, 313 dur, 314 CASE value 315 WHEN 1 THEN 'Screen off' 316 WHEN 2 THEN 'Screen on' 317 WHEN 3 THEN 'Always-on display (doze)' 318 ELSE 'unknown' 319 END AS name 320 FROM counter_leading_intervals!(_counter)`; 321 322// See DeviceIdleController.java for where these states come from and how 323// they transition. 324const DOZE_LIGHT = ` 325 WITH _counter AS ( 326 SELECT counter.id, ts, 0 AS track_id, value 327 FROM counter 328 JOIN counter_track ON counter_track.id = counter.track_id 329 WHERE name = 'DozeLightState' 330 ) 331 SELECT 332 ts, 333 dur, 334 CASE value 335 WHEN 0 THEN 'active' 336 WHEN 1 THEN 'inactive' 337 WHEN 4 THEN 'idle' 338 WHEN 5 THEN 'waiting_for_network' 339 WHEN 6 THEN 'idle_maintenance' 340 WHEN 7 THEN 'override' 341 ELSE 'unknown' 342 END AS name 343 FROM counter_leading_intervals!(_counter)`; 344 345const DOZE_DEEP = ` 346 WITH _counter AS ( 347 SELECT counter.id, ts, 0 AS track_id, value 348 FROM counter 349 JOIN counter_track ON counter_track.id = counter.track_id 350 WHERE name = 'DozeDeepState' 351 ) 352 SELECT 353 ts, 354 dur, 355 CASE value 356 WHEN 0 THEN 'active' 357 WHEN 1 THEN 'inactive' 358 WHEN 2 THEN 'idle_pending' 359 WHEN 3 THEN 'sensing' 360 WHEN 4 THEN 'locating' 361 WHEN 5 THEN 'idle' 362 WHEN 6 THEN 'idle_maintenance' 363 WHEN 7 THEN 'quick_doze_delay' 364 ELSE 'unknown' 365 END AS name 366 FROM counter_leading_intervals!(_counter)`; 367 368const CHARGING = ` 369 WITH _counter AS ( 370 SELECT counter.id, ts, 0 AS track_id, value 371 FROM counter 372 JOIN counter_track ON counter_track.id = counter.track_id 373 WHERE name = 'BatteryStatus' 374 ) 375 SELECT 376 ts, 377 dur, 378 CASE value 379 -- 0 and 1 are both unknown 380 WHEN 2 THEN 'Charging' 381 WHEN 3 THEN 'Discharging' 382 -- special case when charger is present but battery isn't charging 383 WHEN 4 THEN 'Not charging' 384 WHEN 5 THEN 'Full' 385 ELSE 'unknown' 386 END AS name 387 FROM counter_leading_intervals!(_counter)`; 388 389const THERMAL_THROTTLING = ` 390 with step1 as ( 391 select 392 ts, 393 EXTRACT_ARG(arg_set_id, 'thermal_throttling_severity_state_changed.sensor_type') as sensor_type, 394 EXTRACT_ARG(arg_set_id, 'thermal_throttling_severity_state_changed.sensor_name') as sensor_name, 395 EXTRACT_ARG(arg_set_id, 'thermal_throttling_severity_state_changed.temperature_deci_celsius') / 10.0 as temperature_celcius, 396 EXTRACT_ARG(arg_set_id, 'thermal_throttling_severity_state_changed.severity') as severity 397 from track t join slice s on t.id = s.track_id 398 where t.name = 'Statsd Atoms' 399 and s.name = 'thermal_throttling_severity_state_changed' 400 ), 401 step2 as ( 402 select 403 ts, 404 lead(ts) over (partition by sensor_type, sensor_name order by ts) - ts as dur, 405 sensor_type, 406 sensor_name, 407 temperature_celcius, 408 severity 409 from step1 410 where sensor_type not like 'TEMPERATURE_TYPE_BCL_%' 411 ) 412 select 413 ts, 414 dur, 415 case sensor_name 416 when 'VIRTUAL-SKIN' then '' 417 else sensor_name || ' is ' 418 end || severity || ' (' || temperature_celcius || 'C)' as name 419 from step2 420 where severity != 'NONE'`; 421 422const KERNEL_WAKELOCKS = ` 423 create or replace perfetto table kernel_wakelocks as 424 with kernel_wakelock_args as ( 425 select 426 arg_set_id, 427 min(iif(key = 'kernel_wakelock.name', string_value, null)) as wakelock_name, 428 min(iif(key = 'kernel_wakelock.count', int_value, null)) as count, 429 min(iif(key = 'kernel_wakelock.time_micros', int_value, null)) as time_micros 430 from args 431 where key in ( 432 'kernel_wakelock.name', 433 'kernel_wakelock.count', 434 'kernel_wakelock.time_micros' 435 ) 436 group by 1 437 ), 438 interesting as ( 439 select wakelock_name 440 from ( 441 select wakelock_name, max(time_micros)-min(time_micros) as delta_us 442 from kernel_wakelock_args 443 group by 1 444 ) 445 -- Only consider wakelocks with over 1 second of time during the whole trace 446 where delta_us > 1e6 447 ), 448 step1 as ( 449 select ts, wakelock_name, count, time_micros 450 from kernel_wakelock_args 451 join interesting using (wakelock_name) 452 join slice using (arg_set_id) 453 ), 454 step2 as ( 455 select 456 ts, 457 wakelock_name, 458 lead(ts) over (partition by wakelock_name order by ts) as ts_end, 459 lead(count) over (partition by wakelock_name order by ts) - count as count, 460 (lead(time_micros) over (partition by wakelock_name order by ts) - time_micros) * 1000 as wakelock_dur 461 from step1 462 ), 463 step3 as ( 464 select 465 ts, 466 ts_end, 467 ifnull((select sum(dur) from android_suspend_state s 468 where power_state = 'suspended' 469 and s.ts > step2.ts 470 and s.ts < step2.ts_end), 0) as suspended_dur, 471 wakelock_name, 472 count, 473 wakelock_dur 474 from step2 475 where wakelock_dur is not null 476 and wakelock_dur >= 0 477 ) 478 select 479 ts, 480 ts_end - ts as dur, 481 wakelock_name, 482 min(100.0 * wakelock_dur / (ts_end - ts - suspended_dur), 100) as value 483 from step3`; 484 485const KERNEL_WAKELOCKS_SUMMARY = ` 486 select wakelock_name, max(value) as max_value 487 from kernel_wakelocks 488 where wakelock_name not in ('PowerManager.SuspendLockout', 'PowerManagerService.Display') 489 group by 1 490 having max_value > 1 491 order by 1;`; 492 493const HIGH_CPU = ` 494 create or replace perfetto table high_cpu as 495 with cpu_cycles_args AS ( 496 select 497 arg_set_id, 498 min(iif(key = 'cpu_cycles_per_uid_cluster.uid', int_value, null)) as uid, 499 min(iif(key = 'cpu_cycles_per_uid_cluster.cluster', int_value, null)) as cluster, 500 min(iif(key = 'cpu_cycles_per_uid_cluster.time_millis', int_value, null)) as time_millis 501 from args 502 where key in ( 503 'cpu_cycles_per_uid_cluster.uid', 504 'cpu_cycles_per_uid_cluster.cluster', 505 'cpu_cycles_per_uid_cluster.time_millis' 506 ) 507 group by 1 508 ), 509 interesting AS ( 510 select uid, cluster 511 from ( 512 select uid, cluster, max(time_millis)-min(time_millis) as delta_ms 513 from cpu_cycles_args 514 group by 1, 2 515 ) 516 -- Only consider tracks with over 1 second of cpu during the whole trace 517 where delta_ms > 1e3 518 ), 519 base as ( 520 select ts, uid, cluster, sum(time_millis) as time_millis 521 from cpu_cycles_args 522 join interesting using (uid, cluster) 523 join slice using (arg_set_id) 524 group by 1, 2, 3 525 ), 526 with_windows as ( 527 select 528 ts, 529 uid, 530 cluster, 531 lead(ts) over (partition by uid, cluster order by ts) - ts as dur, 532 (lead(time_millis) over (partition by uid, cluster order by ts) - time_millis) * 1000000.0 as cpu_dur 533 from base 534 ), 535 with_ratio as ( 536 select 537 ts, 538 iif(dur is null, 0, max(0, 100.0 * cpu_dur / dur)) as value, 539 case cluster when 0 then 'little' when 1 then 'mid' when 2 then 'big' else 'cl-' || cluster end as cluster, 540 package_name as pkg 541 from add_package_name!(with_windows) 542 ) 543 select ts, sum(value) as value, cluster, pkg 544 from with_ratio 545 group by 1, 3, 4`; 546 547const WAKEUPS = ` 548 drop table if exists wakeups; 549 create table wakeups as 550 with wakeup_reason as ( 551 select 552 ts, 553 substr(i.name, 0, instr(i.name, ' ')) as id_timestamp, 554 substr(i.name, instr(i.name, ' ') + 1) as raw_wakeup 555 from track t join instant i on t.id = i.track_id 556 where t.name = 'wakeup_reason' 557 ), 558 wakeup_attribution as ( 559 select 560 substr(i.name, 0, instr(i.name, ' ')) as id_timestamp, 561 substr(i.name, instr(i.name, ' ') + 1) as attribution 562 from track t join instant i on t.id = i.track_id 563 where t.name = 'wakeup_attribution' 564 ), 565 step1 as( 566 select 567 ts, 568 raw_wakeup, 569 attribution, 570 null as raw_backoff 571 from wakeup_reason r 572 left outer join wakeup_attribution using(id_timestamp) 573 union all 574 select 575 ts, 576 null as raw_wakeup, 577 null as attribution, 578 i.name as raw_backoff 579 from track t join instant i on t.id = i.track_id 580 where t.name = 'suspend_backoff' 581 ), 582 step2 as ( 583 select 584 ts, 585 raw_wakeup, 586 attribution, 587 lag(raw_backoff) over (order by ts) as raw_backoff 588 from step1 589 ), 590 step3 as ( 591 select 592 ts, 593 raw_wakeup, 594 attribution, 595 str_split(raw_backoff, ' ', 0) as suspend_quality, 596 str_split(raw_backoff, ' ', 1) as backoff_state, 597 str_split(raw_backoff, ' ', 2) as backoff_reason, 598 cast(str_split(raw_backoff, ' ', 3) as int) as backoff_count, 599 cast(str_split(raw_backoff, ' ', 4) as int) as backoff_millis, 600 false as suspend_end 601 from step2 602 where raw_wakeup is not null 603 union all 604 select 605 ts, 606 null as raw_wakeup, 607 null as attribution, 608 null as suspend_quality, 609 null as backoff_state, 610 null as backoff_reason, 611 null as backoff_count, 612 null as backoff_millis, 613 true as suspend_end 614 from android_suspend_state 615 where power_state = 'suspended' 616 ), 617 step4 as ( 618 select 619 ts, 620 case suspend_quality 621 when 'good' then 622 min( 623 lead(ts, 1, ts + 5e9) over (order by ts) - ts, 624 5e9 625 ) 626 when 'bad' then backoff_millis * 1000000 627 else 0 628 end as dur, 629 raw_wakeup, 630 attribution, 631 suspend_quality, 632 backoff_state, 633 backoff_reason, 634 backoff_count, 635 backoff_millis, 636 suspend_end 637 from step3 638 ), 639 step5 as ( 640 select 641 ts, 642 dur, 643 raw_wakeup, 644 attribution, 645 suspend_quality, 646 backoff_state, 647 backoff_reason, 648 backoff_count, 649 backoff_millis 650 from step4 651 where not suspend_end 652 ), 653 step6 as ( 654 select 655 ts, 656 dur, 657 raw_wakeup, 658 attribution, 659 suspend_quality, 660 backoff_state, 661 backoff_reason, 662 backoff_count, 663 backoff_millis, 664 case 665 when raw_wakeup like 'Abort: Pending Wakeup Sources: %' then 'abort_pending' 666 when raw_wakeup like 'Abort: Last active Wakeup Source: %' then 'abort_last_active' 667 when raw_wakeup like 'Abort: %' then 'abort_other' 668 else 'normal' 669 end as type, 670 case 671 when raw_wakeup like 'Abort: Pending Wakeup Sources: %' then substr(raw_wakeup, 32) 672 when raw_wakeup like 'Abort: Last active Wakeup Source: %' then substr(raw_wakeup, 35) 673 when raw_wakeup like 'Abort: %' then substr(raw_wakeup, 8) 674 else raw_wakeup 675 end as main, 676 case 677 when raw_wakeup like 'Abort: Pending Wakeup Sources: %' then ' ' 678 when raw_wakeup like 'Abort: %' then 'no delimiter needed' 679 else ':' 680 end as delimiter 681 from step5 682 ), 683 step7 as ( 684 select 685 ts, 686 dur, 687 raw_wakeup, 688 attribution, 689 suspend_quality, 690 backoff_state, 691 backoff_reason, 692 backoff_count, 693 backoff_millis, 694 type, 695 str_split(main, delimiter, 0) as item_0, 696 str_split(main, delimiter, 1) as item_1, 697 str_split(main, delimiter, 2) as item_2, 698 str_split(main, delimiter, 3) as item_3 699 from step6 700 ), 701 step8 as ( 702 select ts, dur, raw_wakeup, attribution, suspend_quality, backoff_state, backoff_reason, backoff_count, backoff_millis, type, item_0 as item from step7 703 union all 704 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 705 union all 706 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 707 union all 708 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 709 ) 710 select 711 ts, 712 dur, 713 ts + dur as ts_end, 714 raw_wakeup, 715 attribution, 716 suspend_quality, 717 backoff_state, 718 ifnull(backoff_reason, 'none') as backoff_reason, 719 backoff_count, 720 backoff_millis, 721 type, 722 case when type = 'normal' then ifnull(str_split(item, ' ', 1), item) else item end as item 723 from step8`; 724 725const WAKEUPS_COLUMNS = [ 726 'item', 727 'type', 728 'raw_wakeup', 729 'attribution', 730 'suspend_quality', 731 'backoff_state', 732 'backoff_reason', 733 'backoff_count', 734 'backoff_millis', 735]; 736 737function bleScanQuery(condition: string) { 738 return ` 739 with step1 as ( 740 select 741 ts, 742 extract_arg(arg_set_id, 'ble_scan_state_changed.attribution_node[0].tag') as name, 743 extract_arg(arg_set_id, 'ble_scan_state_changed.is_opportunistic') as opportunistic, 744 extract_arg(arg_set_id, 'ble_scan_state_changed.is_filtered') as filtered, 745 extract_arg(arg_set_id, 'ble_scan_state_changed.state') as state 746 from track t join slice s on t.id = s.track_id 747 where t.name = 'Statsd Atoms' 748 and s.name = 'ble_scan_state_changed' 749 ), 750 step2 as ( 751 select 752 ts, 753 name, 754 state, 755 opportunistic, 756 filtered, 757 lead(ts) over (partition by name order by ts) - ts as dur 758 from step1 759 ) 760 select ts, dur, name from step2 where state = 'ON' and ${condition} and dur is not null`; 761} 762 763const BLE_RESULTS = ` 764 with step1 as ( 765 select 766 ts, 767 extract_arg(arg_set_id, 'ble_scan_result_received.attribution_node[0].tag') as name, 768 extract_arg(arg_set_id, 'ble_scan_result_received.num_results') as num_results 769 from track t join slice s on t.id = s.track_id 770 where t.name = 'Statsd Atoms' 771 and s.name = 'ble_scan_result_received' 772 ) 773 select 774 ts, 775 0 as dur, 776 name || ' (' || num_results || ' results)' as name 777 from step1`; 778 779const BT_A2DP_AUDIO = ` 780 with step1 as ( 781 select 782 ts, 783 EXTRACT_ARG(arg_set_id, 'bluetooth_a2dp_playback_state_changed.playback_state') as playback_state, 784 EXTRACT_ARG(arg_set_id, 'bluetooth_a2dp_playback_state_changed.audio_coding_mode') as audio_coding_mode, 785 EXTRACT_ARG(arg_set_id, 'bluetooth_a2dp_playback_state_changed.metric_id') as metric_id 786 from track t join slice s on t.id = s.track_id 787 where t.name = 'Statsd Atoms' 788 and s.name = 'bluetooth_a2dp_playback_state_changed' 789 ), 790 step2 as ( 791 select 792 ts, 793 lead(ts) over (partition by metric_id order by ts) - ts as dur, 794 playback_state, 795 audio_coding_mode, 796 metric_id 797 from step1 798 ) 799 select 800 ts, 801 dur, 802 audio_coding_mode as name 803 from step2 804 where playback_state = 'PLAYBACK_STATE_PLAYING'`; 805 806const BT_CONNS_ACL = ` 807 with acl1 as ( 808 select 809 ts, 810 EXTRACT_ARG(arg_set_id, 'bluetooth_acl_connection_state_changed.state') as state, 811 EXTRACT_ARG(arg_set_id, 'bluetooth_acl_connection_state_changed.transport') as transport, 812 EXTRACT_ARG(arg_set_id, 'bluetooth_acl_connection_state_changed.metric_id') as metric_id 813 from track t join slice s on t.id = s.track_id 814 where t.name = 'Statsd Atoms' 815 and s.name = 'bluetooth_acl_connection_state_changed' 816 ), 817 acl2 as ( 818 select 819 ts, 820 lead(ts) over (partition by metric_id, transport order by ts) - ts as dur, 821 state, 822 transport, 823 metric_id 824 from acl1 825 ) 826 select 827 ts, 828 dur, 829 'Device ' || metric_id || 830 ' (' || case transport when 'TRANSPORT_TYPE_BREDR' then 'Classic' when 'TRANSPORT_TYPE_LE' then 'BLE' end || ')' as name 831 from acl2 832 where state != 'CONNECTION_STATE_DISCONNECTED' and dur is not null`; 833 834const BT_CONNS_SCO = ` 835 with sco1 as ( 836 select 837 ts, 838 EXTRACT_ARG(arg_set_id, 'bluetooth_sco_connection_state_changed.state') as state, 839 EXTRACT_ARG(arg_set_id, 'bluetooth_sco_connection_state_changed.codec') as codec, 840 EXTRACT_ARG(arg_set_id, 'bluetooth_sco_connection_state_changed.metric_id') as metric_id 841 from track t join slice s on t.id = s.track_id 842 where t.name = 'Statsd Atoms' 843 and s.name = 'bluetooth_sco_connection_state_changed' 844 ), 845 sco2 as ( 846 select 847 ts, 848 lead(ts) over (partition by metric_id, codec order by ts) - ts as dur, 849 state, 850 codec, 851 metric_id 852 from sco1 853 ) 854 select 855 ts, 856 dur, 857 case state when 'CONNECTION_STATE_CONNECTED' then '' when 'CONNECTION_STATE_CONNECTING' then 'Connecting ' when 'CONNECTION_STATE_DISCONNECTING' then 'Disconnecting ' else 'unknown ' end || 858 'Device ' || metric_id || ' (' || 859 case codec when 'SCO_CODEC_CVSD' then 'CVSD' when 'SCO_CODEC_MSBC' then 'MSBC' end || ')' as name 860 from sco2 861 where state != 'CONNECTION_STATE_DISCONNECTED' and dur is not null`; 862 863const BT_LINK_LEVEL_EVENTS = ` 864 with base as ( 865 select 866 ts, 867 EXTRACT_ARG(arg_set_id, 'bluetooth_link_layer_connection_event.direction') as direction, 868 EXTRACT_ARG(arg_set_id, 'bluetooth_link_layer_connection_event.type') as type, 869 EXTRACT_ARG(arg_set_id, 'bluetooth_link_layer_connection_event.hci_cmd') as hci_cmd, 870 EXTRACT_ARG(arg_set_id, 'bluetooth_link_layer_connection_event.hci_event') as hci_event, 871 EXTRACT_ARG(arg_set_id, 'bluetooth_link_layer_connection_event.hci_ble_event') as hci_ble_event, 872 EXTRACT_ARG(arg_set_id, 'bluetooth_link_layer_connection_event.cmd_status') as cmd_status, 873 EXTRACT_ARG(arg_set_id, 'bluetooth_link_layer_connection_event.reason_code') as reason_code, 874 EXTRACT_ARG(arg_set_id, 'bluetooth_link_layer_connection_event.metric_id') as metric_id 875 from track t join slice s on t.id = s.track_id 876 where t.name = 'Statsd Atoms' 877 and s.name = 'bluetooth_link_layer_connection_event' 878 ) 879 select 880 *, 881 0 as dur, 882 'Device '|| metric_id as name 883 from base`; 884 885const BT_LINK_LEVEL_EVENTS_COLUMNS = [ 886 'direction', 887 'type', 888 'hci_cmd', 889 'hci_event', 890 'hci_ble_event', 891 'cmd_status', 892 'reason_code', 893 'metric_id', 894]; 895 896const BT_QUALITY_REPORTS = ` 897 with base as ( 898 select 899 ts, 900 EXTRACT_ARG(arg_set_id, 'bluetooth_quality_report_reported.quality_report_id') as quality_report_id, 901 EXTRACT_ARG(arg_set_id, 'bluetooth_quality_report_reported.packet_types') as packet_types, 902 EXTRACT_ARG(arg_set_id, 'bluetooth_quality_report_reported.connection_handle') as connection_handle, 903 EXTRACT_ARG(arg_set_id, 'bluetooth_quality_report_reported.connection_role') as connection_role, 904 EXTRACT_ARG(arg_set_id, 'bluetooth_quality_report_reported.tx_power_level') as tx_power_level, 905 EXTRACT_ARG(arg_set_id, 'bluetooth_quality_report_reported.rssi') as rssi, 906 EXTRACT_ARG(arg_set_id, 'bluetooth_quality_report_reported.snr') as snr, 907 EXTRACT_ARG(arg_set_id, 'bluetooth_quality_report_reported.unused_afh_channel_count') as unused_afh_channel_count, 908 EXTRACT_ARG(arg_set_id, 'bluetooth_quality_report_reported.afh_select_unideal_channel_count') as afh_select_unideal_channel_count, 909 EXTRACT_ARG(arg_set_id, 'bluetooth_quality_report_reported.lsto') as lsto, 910 EXTRACT_ARG(arg_set_id, 'bluetooth_quality_report_reported.connection_piconet_clock') as connection_piconet_clock, 911 EXTRACT_ARG(arg_set_id, 'bluetooth_quality_report_reported.retransmission_count') as retransmission_count, 912 EXTRACT_ARG(arg_set_id, 'bluetooth_quality_report_reported.no_rx_count') as no_rx_count, 913 EXTRACT_ARG(arg_set_id, 'bluetooth_quality_report_reported.nak_count') as nak_count, 914 EXTRACT_ARG(arg_set_id, 'bluetooth_quality_report_reported.flow_off_count') as flow_off_count, 915 EXTRACT_ARG(arg_set_id, 'bluetooth_quality_report_reported.buffer_overflow_bytes') as buffer_overflow_bytes, 916 EXTRACT_ARG(arg_set_id, 'bluetooth_quality_report_reported.buffer_underflow_bytes') as buffer_underflow_bytes 917 from track t join slice s on t.id = s.track_id 918 where t.name = 'Statsd Atoms' 919 and s.name = 'bluetooth_quality_report_reported' 920 ) 921 select 922 *, 923 0 as dur, 924 'Connection '|| connection_handle as name 925 from base`; 926 927const BT_QUALITY_REPORTS_COLUMNS = [ 928 'quality_report_id', 929 'packet_types', 930 'connection_handle', 931 'connection_role', 932 'tx_power_level', 933 'rssi', 934 'snr', 935 'unused_afh_channel_count', 936 'afh_select_unideal_channel_count', 937 'lsto', 938 'connection_piconet_clock', 939 'retransmission_count', 940 'no_rx_count', 941 'nak_count', 942 'flow_off_count', 943 'buffer_overflow_bytes', 944 'buffer_underflow_bytes', 945]; 946 947const BT_RSSI_REPORTS = ` 948 with base as ( 949 select 950 ts, 951 EXTRACT_ARG(arg_set_id, 'bluetooth_device_rssi_reported.connection_handle') as connection_handle, 952 EXTRACT_ARG(arg_set_id, 'bluetooth_device_rssi_reported.hci_status') as hci_status, 953 EXTRACT_ARG(arg_set_id, 'bluetooth_device_rssi_reported.rssi') as rssi, 954 EXTRACT_ARG(arg_set_id, 'bluetooth_device_rssi_reported.metric_id') as metric_id 955 from track t join slice s on t.id = s.track_id 956 where t.name = 'Statsd Atoms' 957 and s.name = 'bluetooth_device_rssi_reported' 958 ) 959 select 960 *, 961 0 as dur, 962 'Connection '|| connection_handle as name 963 from base`; 964 965const BT_RSSI_REPORTS_COLUMNS = [ 966 'connection_handle', 967 'hci_status', 968 'rssi', 969 'metric_id', 970]; 971 972const BT_CODE_PATH_COUNTER = ` 973 with base as ( 974 select 975 ts, 976 EXTRACT_ARG(arg_set_id, 'bluetooth_code_path_counter.key') as key, 977 EXTRACT_ARG(arg_set_id, 'bluetooth_code_path_counter.number') as number 978 from track t join slice s on t.id = s.track_id 979 where t.name = 'Statsd Atoms' 980 and s.name = 'bluetooth_code_path_counter' 981 ) 982 select 983 *, 984 0 as dur, 985 key as name 986 from base`; 987 988const BT_CODE_PATH_COUNTER_COLUMNS = ['key', 'number']; 989 990const BT_HAL_CRASHES = ` 991 with base as ( 992 select 993 ts, 994 EXTRACT_ARG(arg_set_id, 'bluetooth_hal_crash_reason_reported.metric_id') as metric_id, 995 EXTRACT_ARG(arg_set_id, 'bluetooth_hal_crash_reason_reported.error_code') as error_code, 996 EXTRACT_ARG(arg_set_id, 'bluetooth_hal_crash_reason_reported.vendor_error_code') as vendor_error_code 997 from track t join slice s on t.id = s.track_id 998 where t.name = 'Statsd Atoms' 999 and s.name = 'bluetooth_hal_crash_reason_reported' 1000 ) 1001 select 1002 *, 1003 0 as dur, 1004 'Device ' || metric_id as name 1005 from base`; 1006 1007const BT_HAL_CRASHES_COLUMNS = ['metric_id', 'error_code', 'vendor_error_code']; 1008 1009const BT_BYTES = ` 1010 with step1 as ( 1011 select 1012 ts, 1013 EXTRACT_ARG(arg_set_id, 'bluetooth_bytes_transfer.uid') as uid, 1014 EXTRACT_ARG(arg_set_id, 'bluetooth_bytes_transfer.tx_bytes') as tx_bytes, 1015 EXTRACT_ARG(arg_set_id, 'bluetooth_bytes_transfer.rx_bytes') as rx_bytes 1016 from track t join slice s on t.id = s.track_id 1017 where t.name = 'Statsd Atoms' 1018 and s.name = 'bluetooth_bytes_transfer' 1019 ), 1020 step2 as ( 1021 select 1022 ts, 1023 lead(ts) over (partition by uid order by ts) - ts as dur, 1024 uid, 1025 lead(tx_bytes) over (partition by uid order by ts) - tx_bytes as tx_bytes, 1026 lead(rx_bytes) over (partition by uid order by ts) - rx_bytes as rx_bytes 1027 from step1 1028 ), 1029 step3 as ( 1030 select 1031 ts, 1032 dur, 1033 uid % 100000 as uid, 1034 sum(tx_bytes) as tx_bytes, 1035 sum(rx_bytes) as rx_bytes 1036 from step2 1037 where tx_bytes >=0 and rx_bytes >=0 1038 group by 1,2,3 1039 having tx_bytes > 0 or rx_bytes > 0 1040 ) 1041 select 1042 ts, 1043 dur, 1044 format("%s: TX %d bytes / RX %d bytes", package_name, tx_bytes, rx_bytes) as name 1045 from add_package_name!(step3) 1046`; 1047 1048// See go/bt_system_context_report for reference on the bit-twiddling. 1049const BT_ACTIVITY = ` 1050 create perfetto table bt_activity as 1051 with step1 as ( 1052 select 1053 EXTRACT_ARG(arg_set_id, 'bluetooth_activity_info.timestamp_millis') * 1000000 as ts, 1054 EXTRACT_ARG(arg_set_id, 'bluetooth_activity_info.bluetooth_stack_state') as bluetooth_stack_state, 1055 EXTRACT_ARG(arg_set_id, 'bluetooth_activity_info.controller_idle_time_millis') * 1000000 as controller_idle_dur, 1056 EXTRACT_ARG(arg_set_id, 'bluetooth_activity_info.controller_tx_time_millis') * 1000000 as controller_tx_dur, 1057 EXTRACT_ARG(arg_set_id, 'bluetooth_activity_info.controller_rx_time_millis') * 1000000 as controller_rx_dur 1058 from track t join slice s on t.id = s.track_id 1059 where t.name = 'Statsd Atoms' 1060 and s.name = 'bluetooth_activity_info' 1061 ), 1062 step2 as ( 1063 select 1064 ts, 1065 lead(ts) over (order by ts) - ts as dur, 1066 bluetooth_stack_state, 1067 lead(controller_idle_dur) over (order by ts) - controller_idle_dur as controller_idle_dur, 1068 lead(controller_tx_dur) over (order by ts) - controller_tx_dur as controller_tx_dur, 1069 lead(controller_rx_dur) over (order by ts) - controller_rx_dur as controller_rx_dur 1070 from step1 1071 ) 1072 select 1073 ts, 1074 dur, 1075 bluetooth_stack_state & 0x0000000F as acl_active_count, 1076 bluetooth_stack_state & 0x000000F0 >> 4 as acl_sniff_count, 1077 bluetooth_stack_state & 0x00000F00 >> 8 as acl_ble_count, 1078 bluetooth_stack_state & 0x0000F000 >> 12 as advertising_count, 1079 case bluetooth_stack_state & 0x000F0000 >> 16 1080 when 0 then 0 1081 when 1 then 5 1082 when 2 then 10 1083 when 3 then 25 1084 when 4 then 100 1085 else -1 1086 end as le_scan_duty_cycle, 1087 bluetooth_stack_state & 0x00100000 >> 20 as inquiry_active, 1088 bluetooth_stack_state & 0x00200000 >> 21 as sco_active, 1089 bluetooth_stack_state & 0x00400000 >> 22 as a2dp_active, 1090 bluetooth_stack_state & 0x00800000 >> 23 as le_audio_active, 1091 max(0, 100.0 * controller_idle_dur / dur) as controller_idle_pct, 1092 max(0, 100.0 * controller_tx_dur / dur) as controller_tx_pct, 1093 max(0, 100.0 * controller_rx_dur / dur) as controller_rx_pct 1094 from step2 1095`; 1096 1097export default class implements PerfettoPlugin { 1098 static readonly id = 'dev.perfetto.AndroidLongBatteryTracing'; 1099 private readonly groups = new Map<string, TrackNode>(); 1100 1101 private addTrack(ctx: Trace, track: TrackNode, groupName?: string): void { 1102 if (groupName) { 1103 const existingGroup = this.groups.get(groupName); 1104 if (existingGroup) { 1105 existingGroup.addChildInOrder(track); 1106 } else { 1107 const group = new TrackNode({title: groupName, isSummary: true}); 1108 group.addChildInOrder(track); 1109 this.groups.set(groupName, group); 1110 ctx.workspace.addChildInOrder(group); 1111 } 1112 } else { 1113 ctx.workspace.addChildInOrder(track); 1114 } 1115 } 1116 1117 async addSliceTrack( 1118 ctx: Trace, 1119 name: string, 1120 query: string, 1121 groupName?: string, 1122 columns: string[] = [], 1123 ) { 1124 const uri = `/long_battery_tracing_${name}`; 1125 const track = await createQuerySliceTrack({ 1126 trace: ctx, 1127 uri, 1128 data: { 1129 sqlSource: query, 1130 columns: ['ts', 'dur', 'name', ...columns], 1131 }, 1132 argColumns: columns, 1133 }); 1134 ctx.tracks.registerTrack({ 1135 uri, 1136 title: name, 1137 track, 1138 }); 1139 const trackNode = new TrackNode({uri, title: name}); 1140 this.addTrack(ctx, trackNode, groupName); 1141 } 1142 1143 async addCounterTrack( 1144 ctx: Trace, 1145 name: string, 1146 query: string, 1147 groupName: string, 1148 options?: Partial<CounterOptions>, 1149 ) { 1150 const uri = `/long_battery_tracing_${name}`; 1151 const track = await createQueryCounterTrack({ 1152 trace: ctx, 1153 uri, 1154 data: { 1155 sqlSource: query, 1156 columns: ['ts', 'value'], 1157 }, 1158 options, 1159 }); 1160 ctx.tracks.registerTrack({ 1161 uri, 1162 title: name, 1163 track, 1164 }); 1165 const trackNode = new TrackNode({uri, title: name}); 1166 this.addTrack(ctx, trackNode, groupName); 1167 } 1168 1169 async addBatteryStatsState( 1170 ctx: Trace, 1171 name: string, 1172 track: string, 1173 groupName: string, 1174 features: Set<string>, 1175 ) { 1176 if (!features.has(`track.${track}`)) { 1177 return; 1178 } 1179 await this.addSliceTrack( 1180 ctx, 1181 name, 1182 `SELECT ts, safe_dur AS dur, value_name AS name 1183 FROM android_battery_stats_state 1184 WHERE track_name = "${track}"`, 1185 groupName, 1186 ); 1187 } 1188 1189 async addBatteryStatsEvent( 1190 ctx: Trace, 1191 name: string, 1192 track: string, 1193 groupName: string | undefined, 1194 features: Set<string>, 1195 ) { 1196 if (!features.has(`track.${track}`)) { 1197 return; 1198 } 1199 1200 await this.addSliceTrack( 1201 ctx, 1202 name, 1203 `SELECT ts, safe_dur AS dur, str_value AS name 1204 FROM android_battery_stats_event_slices 1205 WHERE track_name = "${track}"`, 1206 groupName, 1207 ); 1208 } 1209 1210 async addDeviceState(ctx: Trace, features: Set<string>): Promise<void> { 1211 if (!features.has('track.battery_stats.*')) { 1212 return; 1213 } 1214 1215 const query = (name: string, track: string) => 1216 this.addBatteryStatsEvent(ctx, name, track, undefined, features); 1217 1218 const e = ctx.engine; 1219 await e.query(`INCLUDE PERFETTO MODULE android.battery_stats;`); 1220 await e.query(`INCLUDE PERFETTO MODULE android.suspend;`); 1221 await e.query(`INCLUDE PERFETTO MODULE counters.intervals;`); 1222 1223 await this.addSliceTrack(ctx, 'Device State: Screen state', SCREEN_STATE); 1224 await this.addSliceTrack(ctx, 'Device State: Charging', CHARGING); 1225 await this.addSliceTrack( 1226 ctx, 1227 'Device State: Suspend / resume', 1228 SUSPEND_RESUME, 1229 ); 1230 await this.addSliceTrack(ctx, 'Device State: Doze light state', DOZE_LIGHT); 1231 await this.addSliceTrack(ctx, 'Device State: Doze deep state', DOZE_DEEP); 1232 1233 query('Device State: Top app', 'battery_stats.top'); 1234 1235 await this.addSliceTrack( 1236 ctx, 1237 'Device State: Long wakelocks', 1238 `SELECT 1239 ts - 60000000000 as ts, 1240 safe_dur + 60000000000 as dur, 1241 str_value AS name, 1242 package_name as package 1243 FROM add_package_name!(( 1244 select *, int_value as uid 1245 from android_battery_stats_event_slices 1246 WHERE track_name = "battery_stats.longwake" 1247 ))`, 1248 undefined, 1249 ['package'], 1250 ); 1251 1252 query('Device State: Foreground apps', 'battery_stats.fg'); 1253 query('Device State: Jobs', 'battery_stats.job'); 1254 1255 if (features.has('atom.thermal_throttling_severity_state_changed')) { 1256 await this.addSliceTrack( 1257 ctx, 1258 'Device State: Thermal throttling', 1259 THERMAL_THROTTLING, 1260 ); 1261 } 1262 } 1263 1264 async addAtomCounters(ctx: Trace): Promise<void> { 1265 const e = ctx.engine; 1266 1267 try { 1268 await e.query( 1269 `INCLUDE PERFETTO MODULE 1270 google3.wireless.android.telemetry.trace_extractor.modules.atom_counters_slices`, 1271 ); 1272 } catch (e) { 1273 return; 1274 } 1275 1276 const counters = await e.query( 1277 `select distinct ui_group, ui_name, ui_unit, counter_name 1278 from atom_counters 1279 where ui_name is not null`, 1280 ); 1281 const countersIt = counters.iter({ 1282 ui_group: 'str', 1283 ui_name: 'str', 1284 ui_unit: 'str', 1285 counter_name: 'str', 1286 }); 1287 for (; countersIt.valid(); countersIt.next()) { 1288 const unit = countersIt.ui_unit; 1289 const opts = 1290 unit === '%' 1291 ? {yOverrideMaximum: 100, unit: '%'} 1292 : unit !== undefined 1293 ? {unit} 1294 : undefined; 1295 1296 await this.addCounterTrack( 1297 ctx, 1298 countersIt.ui_name, 1299 `select ts, ${unit === '%' ? 100.0 : 1.0} * counter_value as value 1300 from atom_counters 1301 where counter_name = '${countersIt.counter_name}'`, 1302 countersIt.ui_group, 1303 opts, 1304 ); 1305 } 1306 } 1307 1308 async addAtomSlices(ctx: Trace): Promise<void> { 1309 const e = ctx.engine; 1310 1311 try { 1312 await e.query( 1313 `INCLUDE PERFETTO MODULE 1314 google3.wireless.android.telemetry.trace_extractor.modules.atom_counters_slices`, 1315 ); 1316 } catch (e) { 1317 return; 1318 } 1319 1320 const sliceTracks = await e.query( 1321 `select distinct ui_group, ui_name, atom, field 1322 from atom_slices 1323 where ui_name is not null 1324 order by 1, 2, 3, 4`, 1325 ); 1326 const slicesIt = sliceTracks.iter({ 1327 atom: 'str', 1328 ui_group: 'str', 1329 ui_name: 'str', 1330 field: 'str', 1331 }); 1332 1333 const tracks = new Map< 1334 string, 1335 { 1336 ui_group: string; 1337 ui_name: string; 1338 } 1339 >(); 1340 const fields = new Map<string, string[]>(); 1341 for (; slicesIt.valid(); slicesIt.next()) { 1342 const atom = slicesIt.atom; 1343 let args = fields.get(atom); 1344 if (args === undefined) { 1345 args = []; 1346 fields.set(atom, args); 1347 } 1348 args.push(slicesIt.field); 1349 tracks.set(atom, { 1350 ui_group: slicesIt.ui_group, 1351 ui_name: slicesIt.ui_name, 1352 }); 1353 } 1354 1355 for (const [atom, args] of fields) { 1356 function safeArg(arg: string) { 1357 return arg.replaceAll(/[[\]]/g, '').replaceAll(/\./g, '_'); 1358 } 1359 1360 // We need to make arg names compatible with SQL here because they pass through several 1361 // layers of SQL without being quoted in "". 1362 function argSql(arg: string) { 1363 return `max(case when field = '${arg}' then ifnull(string_value, int_value) end) 1364 as ${safeArg(arg)}`; 1365 } 1366 1367 await this.addSliceTrack( 1368 ctx, 1369 tracks.get(atom)!.ui_name, 1370 `select ts, dur, slice_name as name, ${args.map((a) => argSql(a)).join(', ')} 1371 from atom_slices 1372 where atom = '${atom}' 1373 group by ts, dur, name`, 1374 tracks.get(atom)!.ui_group, 1375 args.map((a) => safeArg(a)), 1376 ); 1377 } 1378 } 1379 1380 async addNetworkSummary(ctx: Trace, features: Set<string>): Promise<void> { 1381 if (!features.has('net.modem') && !features.has('net.wifi')) { 1382 return; 1383 } 1384 1385 const groupName = 'Network Summary'; 1386 1387 const e = ctx.engine; 1388 await e.query(`INCLUDE PERFETTO MODULE android.battery_stats;`); 1389 await e.query(`INCLUDE PERFETTO MODULE android.network_packets;`); 1390 await e.query(NETWORK_SUMMARY); 1391 await e.query(RADIO_TRANSPORT_TYPE); 1392 1393 await this.addSliceTrack( 1394 ctx, 1395 'Default network', 1396 DEFAULT_NETWORK, 1397 groupName, 1398 ); 1399 1400 if (features.has('atom.network_tethering_reported')) { 1401 await this.addSliceTrack(ctx, 'Tethering', TETHERING, groupName); 1402 } 1403 if (features.has('net.wifi')) { 1404 await this.addCounterTrack( 1405 ctx, 1406 'Wifi total bytes', 1407 `select ts, sum(value) as value from network_summary where dev_type = 'wifi' group by 1`, 1408 groupName, 1409 {yDisplay: 'log', yRangeSharingKey: 'net_bytes', unit: 'byte'}, 1410 ); 1411 const result = await e.query( 1412 `select pkg, sum(value) from network_summary where dev_type='wifi' group by 1 order by 2 desc limit 10`, 1413 ); 1414 const it = result.iter({pkg: 'str'}); 1415 for (; it.valid(); it.next()) { 1416 await this.addCounterTrack( 1417 ctx, 1418 `Top wifi: ${it.pkg}`, 1419 `select ts, value from network_summary where dev_type = 'wifi' and pkg = '${it.pkg}'`, 1420 groupName, 1421 {yDisplay: 'log', yRangeSharingKey: 'net_bytes', unit: 'byte'}, 1422 ); 1423 } 1424 } 1425 this.addBatteryStatsState( 1426 ctx, 1427 'Wifi interface', 1428 'battery_stats.wifi_radio', 1429 groupName, 1430 features, 1431 ); 1432 this.addBatteryStatsState( 1433 ctx, 1434 'Wifi supplicant state', 1435 'battery_stats.wifi_suppl', 1436 groupName, 1437 features, 1438 ); 1439 this.addBatteryStatsState( 1440 ctx, 1441 'Wifi strength', 1442 'battery_stats.wifi_signal_strength', 1443 groupName, 1444 features, 1445 ); 1446 if (features.has('net.modem')) { 1447 await this.addCounterTrack( 1448 ctx, 1449 'Modem total bytes', 1450 `select ts, sum(value) as value from network_summary where dev_type = 'modem' group by 1`, 1451 groupName, 1452 {yDisplay: 'log', yRangeSharingKey: 'net_bytes', unit: 'byte'}, 1453 ); 1454 const result = await e.query( 1455 `select pkg, sum(value) from network_summary where dev_type='modem' group by 1 order by 2 desc limit 10`, 1456 ); 1457 const it = result.iter({pkg: 'str'}); 1458 for (; it.valid(); it.next()) { 1459 await this.addCounterTrack( 1460 ctx, 1461 `Top modem: ${it.pkg}`, 1462 `select ts, value from network_summary where dev_type = 'modem' and pkg = '${it.pkg}'`, 1463 groupName, 1464 {yDisplay: 'log', yRangeSharingKey: 'net_bytes', unit: 'byte'}, 1465 ); 1466 } 1467 } 1468 this.addBatteryStatsState( 1469 ctx, 1470 'Cellular interface', 1471 'battery_stats.mobile_radio', 1472 groupName, 1473 features, 1474 ); 1475 await this.addSliceTrack( 1476 ctx, 1477 'Cellular connection', 1478 `select ts, dur, name from radio_transport`, 1479 groupName, 1480 ); 1481 this.addBatteryStatsState( 1482 ctx, 1483 'Cellular strength', 1484 'battery_stats.phone_signal_strength', 1485 groupName, 1486 features, 1487 ); 1488 } 1489 1490 async addModemDetail(ctx: Trace, features: Set<string>): Promise<void> { 1491 const groupName = 'Modem Detail'; 1492 if (features.has('track.ril')) { 1493 await this.addModemRil(ctx, groupName); 1494 } 1495 await this.addModemTeaData(ctx, groupName); 1496 } 1497 1498 async addModemRil(ctx: Trace, groupName: string): Promise<void> { 1499 const rilStrength = async (band: string, value: string) => 1500 await this.addSliceTrack( 1501 ctx, 1502 `Modem signal strength ${band} ${value}`, 1503 `SELECT ts, dur, name FROM RilScreenOn WHERE band_name = '${band}' AND value_name = '${value}'`, 1504 groupName, 1505 ); 1506 1507 const e = ctx.engine; 1508 1509 await e.query(MODEM_RIL_STRENGTH); 1510 await e.query(MODEM_RIL_CHANNELS_PREAMBLE); 1511 1512 await rilStrength('LTE', 'rsrp'); 1513 await rilStrength('LTE', 'rssi'); 1514 await rilStrength('NR', 'rsrp'); 1515 await rilStrength('NR', 'rssi'); 1516 1517 await this.addSliceTrack( 1518 ctx, 1519 'Modem channel config', 1520 MODEM_RIL_CHANNELS, 1521 groupName, 1522 ); 1523 1524 await this.addSliceTrack( 1525 ctx, 1526 'Modem cell reselection', 1527 MODEM_CELL_RESELECTION, 1528 groupName, 1529 ['raw_ril'], 1530 ); 1531 } 1532 1533 async addModemTeaData(ctx: Trace, groupName: string): Promise<void> { 1534 const e = ctx.engine; 1535 1536 try { 1537 await e.query( 1538 `INCLUDE PERFETTO MODULE 1539 google3.wireless.android.telemetry.trace_extractor.modules.modem_tea_metrics`, 1540 ); 1541 } catch { 1542 return; 1543 } 1544 1545 const counters = await e.query( 1546 `select distinct name from pixel_modem_counters`, 1547 ); 1548 const countersIt = counters.iter({name: 'str'}); 1549 for (; countersIt.valid(); countersIt.next()) { 1550 await this.addCounterTrack( 1551 ctx, 1552 countersIt.name, 1553 `select ts, value from pixel_modem_counters where name = '${countersIt.name}'`, 1554 groupName, 1555 ); 1556 } 1557 const slices = await e.query( 1558 `select distinct track_name from pixel_modem_slices`, 1559 ); 1560 const slicesIt = slices.iter({track_name: 'str'}); 1561 for (; slicesIt.valid(); slicesIt.next()) { 1562 await this.addSliceTrack( 1563 ctx, 1564 slicesIt.track_name, 1565 `select ts, dur, slice_name as name from pixel_modem_slices 1566 where track_name = '${slicesIt.track_name}'`, 1567 groupName, 1568 ); 1569 } 1570 } 1571 1572 async addKernelWakelocks(ctx: Trace, features: Set<string>): Promise<void> { 1573 if (!features.has('atom.kernel_wakelock')) { 1574 return; 1575 } 1576 const groupName = 'Kernel Wakelock Summary'; 1577 1578 const e = ctx.engine; 1579 await e.query(`INCLUDE PERFETTO MODULE android.suspend;`); 1580 await e.query(KERNEL_WAKELOCKS); 1581 const result = await e.query(KERNEL_WAKELOCKS_SUMMARY); 1582 const it = result.iter({wakelock_name: 'str'}); 1583 for (; it.valid(); it.next()) { 1584 await this.addCounterTrack( 1585 ctx, 1586 it.wakelock_name, 1587 `select ts, dur, value from kernel_wakelocks where wakelock_name = "${it.wakelock_name}"`, 1588 groupName, 1589 {yRangeSharingKey: 'kernel_wakelock', unit: '%'}, 1590 ); 1591 } 1592 } 1593 1594 async addWakeups(ctx: Trace, features: Set<string>): Promise<void> { 1595 if (!features.has('track.suspend_backoff')) { 1596 return; 1597 } 1598 1599 const e = ctx.engine; 1600 const groupName = 'Wakeups'; 1601 await e.query(`INCLUDE PERFETTO MODULE android.suspend;`); 1602 await e.query(WAKEUPS); 1603 const result = await e.query(`select 1604 item, 1605 sum(dur) as sum_dur 1606 from wakeups 1607 group by 1 1608 having sum_dur > 600e9`); 1609 const it = result.iter({item: 'str'}); 1610 const sqlPrefix = `select 1611 ts, 1612 dur, 1613 item || case backoff_reason 1614 when 'short' then ' (Short suspend backoff)' 1615 when 'failed' then ' (Failed suspend backoff)' 1616 else '' 1617 end as name, 1618 item, 1619 type, 1620 raw_wakeup, 1621 attribution, 1622 suspend_quality, 1623 backoff_state, 1624 backoff_reason, 1625 backoff_count, 1626 backoff_millis 1627 from wakeups`; 1628 const items = []; 1629 let labelOther = false; 1630 for (; it.valid(); it.next()) { 1631 labelOther = true; 1632 await this.addSliceTrack( 1633 ctx, 1634 `Wakeup ${it.item}`, 1635 `${sqlPrefix} where item="${it.item}"`, 1636 groupName, 1637 WAKEUPS_COLUMNS, 1638 ); 1639 items.push(it.item); 1640 } 1641 await this.addSliceTrack( 1642 ctx, 1643 labelOther ? 'Other wakeups' : 'Wakeups', 1644 `${sqlPrefix} where item not in ('${items.join("','")}')`, 1645 groupName, 1646 WAKEUPS_COLUMNS, 1647 ); 1648 } 1649 1650 async addHighCpu(ctx: Trace, features: Set<string>): Promise<void> { 1651 if (!features.has('atom.cpu_cycles_per_uid_cluster')) { 1652 return; 1653 } 1654 const groupName = 'CPU per UID (major users)'; 1655 1656 const e = ctx.engine; 1657 1658 await e.query(HIGH_CPU); 1659 const result = await e.query( 1660 `select distinct pkg, cluster from high_cpu where value > 10 order by 1, 2`, 1661 ); 1662 const it = result.iter({pkg: 'str', cluster: 'str'}); 1663 for (; it.valid(); it.next()) { 1664 await this.addCounterTrack( 1665 ctx, 1666 `CPU (${it.cluster}): ${it.pkg}`, 1667 `select ts, value from high_cpu where pkg = "${it.pkg}" and cluster="${it.cluster}"`, 1668 groupName, 1669 {yOverrideMaximum: 100, unit: '%'}, 1670 ); 1671 } 1672 } 1673 1674 async addBluetooth(ctx: Trace, features: Set<string>): Promise<void> { 1675 if ( 1676 !Array.from(features.values()).some( 1677 (f) => f.startsWith('atom.bluetooth_') || f.startsWith('atom.ble_'), 1678 ) 1679 ) { 1680 return; 1681 } 1682 const groupName = 'Bluetooth'; 1683 await this.addSliceTrack( 1684 ctx, 1685 'BLE Scans (opportunistic)', 1686 bleScanQuery('opportunistic'), 1687 groupName, 1688 ); 1689 await this.addSliceTrack( 1690 ctx, 1691 'BLE Scans (filtered)', 1692 bleScanQuery('filtered'), 1693 groupName, 1694 ); 1695 await this.addSliceTrack( 1696 ctx, 1697 'BLE Scans (unfiltered)', 1698 bleScanQuery('not filtered'), 1699 groupName, 1700 ); 1701 await this.addSliceTrack(ctx, 'BLE Scan Results', BLE_RESULTS, groupName); 1702 await this.addSliceTrack(ctx, 'Connections (ACL)', BT_CONNS_ACL, groupName); 1703 await this.addSliceTrack(ctx, 'Connections (SCO)', BT_CONNS_SCO, groupName); 1704 await this.addSliceTrack( 1705 ctx, 1706 'Link-level Events', 1707 BT_LINK_LEVEL_EVENTS, 1708 groupName, 1709 BT_LINK_LEVEL_EVENTS_COLUMNS, 1710 ); 1711 await this.addSliceTrack(ctx, 'A2DP Audio', BT_A2DP_AUDIO, groupName); 1712 await this.addSliceTrack( 1713 ctx, 1714 'Bytes Transferred (L2CAP/RFCOMM)', 1715 BT_BYTES, 1716 groupName, 1717 ); 1718 await ctx.engine.query(BT_ACTIVITY); 1719 await this.addCounterTrack( 1720 ctx, 1721 'ACL Classic Active Count', 1722 'select ts, dur, acl_active_count as value from bt_activity', 1723 groupName, 1724 ); 1725 await this.addCounterTrack( 1726 ctx, 1727 'ACL Classic Sniff Count', 1728 'select ts, dur, acl_sniff_count as value from bt_activity', 1729 groupName, 1730 ); 1731 await this.addCounterTrack( 1732 ctx, 1733 'ACL BLE Count', 1734 'select ts, dur, acl_ble_count as value from bt_activity', 1735 groupName, 1736 ); 1737 await this.addCounterTrack( 1738 ctx, 1739 'Advertising Instance Count', 1740 'select ts, dur, advertising_count as value from bt_activity', 1741 groupName, 1742 ); 1743 await this.addCounterTrack( 1744 ctx, 1745 'LE Scan Duty Cycle Maximum', 1746 'select ts, dur, le_scan_duty_cycle as value from bt_activity', 1747 groupName, 1748 {unit: '%'}, 1749 ); 1750 await this.addSliceTrack( 1751 ctx, 1752 'Inquiry Active', 1753 "select ts, dur, 'Active' as name from bt_activity where inquiry_active", 1754 groupName, 1755 ); 1756 await this.addSliceTrack( 1757 ctx, 1758 'SCO Active', 1759 "select ts, dur, 'Active' as name from bt_activity where sco_active", 1760 groupName, 1761 ); 1762 await this.addSliceTrack( 1763 ctx, 1764 'A2DP Active', 1765 "select ts, dur, 'Active' as name from bt_activity where a2dp_active", 1766 groupName, 1767 ); 1768 await this.addSliceTrack( 1769 ctx, 1770 'LE Audio Active', 1771 "select ts, dur, 'Active' as name from bt_activity where le_audio_active", 1772 groupName, 1773 ); 1774 await this.addCounterTrack( 1775 ctx, 1776 'Controller Idle Time', 1777 'select ts, dur, controller_idle_pct as value from bt_activity', 1778 groupName, 1779 {yRangeSharingKey: 'bt_controller_time', unit: '%'}, 1780 ); 1781 await this.addCounterTrack( 1782 ctx, 1783 'Controller TX Time', 1784 'select ts, dur, controller_tx_pct as value from bt_activity', 1785 groupName, 1786 {yRangeSharingKey: 'bt_controller_time', unit: '%'}, 1787 ); 1788 await this.addCounterTrack( 1789 ctx, 1790 'Controller RX Time', 1791 'select ts, dur, controller_rx_pct as value from bt_activity', 1792 groupName, 1793 {yRangeSharingKey: 'bt_controller_time', unit: '%'}, 1794 ); 1795 await this.addSliceTrack( 1796 ctx, 1797 'Quality reports', 1798 BT_QUALITY_REPORTS, 1799 groupName, 1800 BT_QUALITY_REPORTS_COLUMNS, 1801 ); 1802 await this.addSliceTrack( 1803 ctx, 1804 'RSSI Reports', 1805 BT_RSSI_REPORTS, 1806 groupName, 1807 BT_RSSI_REPORTS_COLUMNS, 1808 ); 1809 await this.addSliceTrack( 1810 ctx, 1811 'HAL Crashes', 1812 BT_HAL_CRASHES, 1813 groupName, 1814 BT_HAL_CRASHES_COLUMNS, 1815 ); 1816 await this.addSliceTrack( 1817 ctx, 1818 'Code Path Counter', 1819 BT_CODE_PATH_COUNTER, 1820 groupName, 1821 BT_CODE_PATH_COUNTER_COLUMNS, 1822 ); 1823 } 1824 1825 async addContainedTraces( 1826 ctx: Trace, 1827 containedTraces: ContainedTrace[], 1828 ): Promise<void> { 1829 const bySubscription = new Map<string, ContainedTrace[]>(); 1830 for (const trace of containedTraces) { 1831 if (!bySubscription.has(trace.subscription)) { 1832 bySubscription.set(trace.subscription, []); 1833 } 1834 bySubscription.get(trace.subscription)!.push(trace); 1835 } 1836 1837 for (const [subscription, traces] of bySubscription) { 1838 await this.addSliceTrack( 1839 ctx, 1840 subscription, 1841 traces 1842 .map( 1843 (t) => `SELECT 1844 CAST(${t.ts} * 1e6 AS int) AS ts, 1845 CAST(${t.dur} * 1e6 AS int) AS dur, 1846 '${t.trigger === '' ? 'Trace' : t.trigger}' AS name, 1847 'http://go/trace-uuid/${t.uuid}' AS link 1848 `, 1849 ) 1850 .join(' UNION ALL '), 1851 'Other traces', 1852 ['link'], 1853 ); 1854 } 1855 } 1856 1857 async findFeatures(e: Engine): Promise<Set<string>> { 1858 const features = new Set<string>(); 1859 1860 const addFeatures = async (q: string) => { 1861 const result = await e.query(q); 1862 const it = result.iter({feature: 'str'}); 1863 for (; it.valid(); it.next()) { 1864 features.add(it.feature); 1865 } 1866 }; 1867 1868 await addFeatures(` 1869 select distinct 'atom.' || s.name as feature 1870 from track t join slice s on t.id = s.track_id 1871 where t.name = 'Statsd Atoms'`); 1872 1873 await addFeatures(` 1874 select distinct 1875 case when name like '%wlan%' then 'net.wifi' 1876 when name like '%rmnet%' then 'net.modem' 1877 else 'net.other' 1878 end as feature 1879 from track 1880 where name like '%Transmitted' or name like '%Received'`); 1881 1882 await addFeatures(` 1883 select distinct 'track.' || lower(name) as feature 1884 from track where name in ('RIL', 'suspend_backoff') or name like 'battery_stats.%'`); 1885 1886 await addFeatures(` 1887 select distinct 'track.battery_stats.*' as feature 1888 from track where name like 'battery_stats.%'`); 1889 1890 return features; 1891 } 1892 1893 async addTracks(ctx: Trace): Promise<void> { 1894 const features: Set<string> = await this.findFeatures(ctx.engine); 1895 1896 const containedTraces = (ctx.openerPluginArgs?.containedTraces ?? 1897 []) as ContainedTrace[]; 1898 1899 await ctx.engine.query(PACKAGE_LOOKUP); 1900 await this.addNetworkSummary(ctx, features); 1901 await this.addBluetooth(ctx, features); 1902 await this.addAtomCounters(ctx); 1903 await this.addAtomSlices(ctx); 1904 await this.addModemDetail(ctx, features); 1905 await this.addKernelWakelocks(ctx, features); 1906 await this.addWakeups(ctx, features); 1907 await this.addDeviceState(ctx, features); 1908 await this.addHighCpu(ctx, features); 1909 await this.addContainedTraces(ctx, containedTraces); 1910 } 1911 1912 async onTraceLoad(ctx: Trace): Promise<void> { 1913 await this.addTracks(ctx); 1914 } 1915} 1916