xref: /aosp_15_r20/external/perfetto/ui/src/plugins/dev.perfetto.AndroidLongBatteryTracing/index.ts (revision 6dbdd20afdafa5e3ca9b8809fa73465d530080dc)
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