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