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