Merge "Add casting to avoid SQLite issues with SPAN_JOIN" into main
diff --git a/ui/src/plugins/dev.perfetto.AndroidLongBatteryTracing/index.ts b/ui/src/plugins/dev.perfetto.AndroidLongBatteryTracing/index.ts
index 9c97105..9625bb1 100644
--- a/ui/src/plugins/dev.perfetto.AndroidLongBatteryTracing/index.ts
+++ b/ui/src/plugins/dev.perfetto.AndroidLongBatteryTracing/index.ts
@@ -867,8 +867,17 @@
const BT_HAL_CRASHES_COLUMNS = ['metric_id', 'error_code', 'vendor_error_code'];
-function flatten<T>(arr: T[][]): T[] {
- return arr.reduce((a, b) => a.concat(b), []);
+// Make it less painful to wrangle promises and arrays and promises of arrays
+// etc...
+type MixedResults<T> = (T|T[]|Promise<T>|Promise<T[]>)[];
+type MixedActions = MixedResults<DeferredAction<{}>>;
+async function flatten<T>(arr: MixedResults<T>): Promise<T[]> {
+ const result: T[] = [];
+ for (const elem of arr) {
+ const awaited = elem instanceof Promise ? await elem : elem;
+ Array.isArray(awaited) ? result.push(...awaited) : result.push(awaited);
+ }
+ return result;
}
class AndroidLongBatteryTracing implements Plugin {
@@ -934,12 +943,10 @@
WHERE track_name = "${track}"`,
groupId);
- return await Promise.all([
+ return flatten([
query('Top App', 'battery_stats.top'),
this.addSliceTrack(
- e,
- 'Long wakelocks',
- `SELECT
+ e, 'Long wakelocks', `SELECT
ts - 60000000000 as ts,
dur + 60000000000 as dur,
str_value AS name,
@@ -948,50 +955,72 @@
int_value) as package
FROM android_battery_stats_event_slices
WHERE track_name = "battery_stats.longwake"`,
- groupId,
- ['package']),
+ groupId, ['package']),
query('Foreground Apps', 'battery_stats.fg'),
query('Jobs', 'battery_stats.job'),
]);
}
- async addNetworkSummary(e: EngineProxy, groupId: string):
+ async addNetworkSummary(e: EngineProxy, features: Set<string>):
Promise<DeferredAction<{}>[]> {
+ if (!features.has('net.modem') && !features.has('net.wifi')) {
+ return [];
+ }
+ const {groupId, groupActions} = this.addGroup('Network Summary');
+
await e.query(NETWORK_SUMMARY);
- return await Promise.all([
+ const actions = [
+ groupActions,
this.addSliceTrack(e, 'Default network', DEFAULT_NETWORK, groupId),
- this.addSliceTrack(e, 'Tethering', TETHERING, groupId),
- this.addCounterTrack(
- e,
- 'Wifi bytes (logscale)',
- `select ts, ifnull(ln(sum(value)), 0) as value from network_summary where dev_type = 'wifi' group by 1`,
- groupId),
- this.addCounterTrack(
- e,
- 'Wifi TX bytes (logscale)',
- `select ts, ifnull(ln(value), 0) as value from network_summary where dev_type = 'wifi' and dir = 'tx'`,
- groupId),
- this.addCounterTrack(
- e,
- 'Wifi RX bytes (logscale)',
- `select ts, ifnull(ln(value), 0) as value from network_summary where dev_type = 'wifi' and dir = 'rx'`,
- groupId),
- this.addCounterTrack(
- e,
- 'Modem bytes (logscale)',
- `select ts, ifnull(ln(sum(value)), 0) as value from network_summary where dev_type = 'modem' group by 1`,
- groupId),
- this.addCounterTrack(
- e,
- 'Modem TX bytes (logscale)',
- `select ts, ifnull(ln(value), 0) as value from network_summary where dev_type = 'modem' and dir = 'tx'`,
- groupId),
- this.addCounterTrack(
- e,
- 'Modem RX bytes (logscale)',
- `select ts, ifnull(ln(value), 0) as value from network_summary where dev_type = 'modem' and dir = 'rx'`,
- groupId),
- ]);
+ ];
+ if (features.has('atom.network_tethering_reported')) {
+ actions.push(this.addSliceTrack(e, 'Tethering', TETHERING, groupId));
+ }
+ if (features.has('net.wifi')) {
+ actions.push(
+ this.addCounterTrack(
+ e, 'Wifi bytes (logscale)',
+ `select ts, ifnull(ln(sum(value)), 0) as value from network_summary where dev_type = 'wifi' group by 1`,
+ groupId),
+ this.addCounterTrack(
+ e, 'Wifi TX bytes (logscale)',
+ `select ts, ifnull(ln(value), 0) as value from network_summary where dev_type = 'wifi' and dir = 'tx'`,
+ groupId),
+ this.addCounterTrack(
+ e, 'Wifi RX bytes (logscale)',
+ `select ts, ifnull(ln(value), 0) as value from network_summary where dev_type = 'wifi' and dir = 'rx'`,
+ groupId));
+ }
+ if (features.has('net.modem')) {
+ actions.push(
+ this.addCounterTrack(
+ e, 'Modem bytes (logscale)',
+ `select ts, ifnull(ln(sum(value)), 0) as value from network_summary where dev_type = 'modem' group by 1`,
+ groupId),
+ this.addCounterTrack(
+ e, 'Modem TX bytes (logscale)',
+ `select ts, ifnull(ln(value), 0) as value from network_summary where dev_type = 'modem' and dir = 'tx'`,
+ groupId),
+ this.addCounterTrack(
+ e, 'Modem RX bytes (logscale)',
+ `select ts, ifnull(ln(value), 0) as value from network_summary where dev_type = 'modem' and dir = 'rx'`,
+ groupId),
+ );
+ }
+ return flatten(actions);
+ }
+
+ async addModemDetail(e: EngineProxy, features: Set<string>):
+ Promise<DeferredAction<{}>[]> {
+ if (!features.has('atom.modem_activity_info')) {
+ return [];
+ }
+ const {groupId, groupActions} = this.addGroup('Modem Detail');
+ const actions = [groupActions, this.addModemActivityInfo(e, groupId)];
+ if (features.has('track.ril')) {
+ actions.push(this.addModemRil(e, groupId));
+ }
+ return flatten(actions);
}
async addModemActivityInfo(e: EngineProxy, groupId: string):
@@ -1004,7 +1033,7 @@
groupId);
await e.query(MODEM_ACTIVITY_INFO);
- return await Promise.all([
+ return flatten([
query('Modem sleep', 'sleep_time'),
query('Modem controller idle', 'controller_idle_time'),
query('Modem RX time', 'controller_rx_time'),
@@ -1030,7 +1059,7 @@
await e.query(MODEM_RIL_STRENGTH);
await e.query(MODEM_RIL_CHANNELS_PREAMBLE);
- return await Promise.all([
+ return flatten([
rilStrength('LTE', 'rsrp'),
rilStrength('LTE', 'rssi'),
rilStrength('NR', 'rssi'),
@@ -1040,12 +1069,17 @@
]);
}
- async addKernelWakelocks(e: EngineProxy, groupId: string):
+ async addKernelWakelocks(e: EngineProxy, features: Set<string>):
Promise<DeferredAction<{}>[]> {
+ if (!features.has('atom.kernel_wakelock')) {
+ return [];
+ }
+ const {groupId, groupActions} = this.addGroup('Kernel Wakelock Summary');
+
await e.query(KERNEL_WAKELOCKS);
const result = await e.query(KERNEL_WAKELOCKS_SUMMARY);
const it = result.iter({wakelock_name: 'str'});
- const actions: Promise<DeferredAction<{}>>[] = [];
+ const actions: MixedActions = [groupActions];
for (; it.valid(); it.next()) {
actions.push(this.addSliceTrack(
e,
@@ -1054,11 +1088,16 @@
it.wakelock_name}"`,
groupId));
}
- return await Promise.all(actions);
+ return flatten(actions);
}
- async addWakeups(e: EngineProxy, groupId: string):
+ async addWakeups(e: EngineProxy, features: Set<string>):
Promise<DeferredAction<{}>[]> {
+ if (!features.has('track.suspend_backoff')) {
+ return [];
+ }
+
+ const {groupId, groupActions} = this.addGroup('Wakeups');
await e.query(WAKEUPS);
const result = await e.query(`select
item,
@@ -1086,8 +1125,10 @@
backoff_millis
from wakeups`;
const items = [];
- const actions: Promise<DeferredAction<{}>>[] = [];
+ const actions: MixedActions = [groupActions];
+ let labelOther = false;
for (; it.valid(); it.next()) {
+ labelOther = true;
actions.push(this.addSliceTrack(
e,
`Wakeup ${it.item}`,
@@ -1097,21 +1138,24 @@
items.push(it.item);
}
actions.push(this.addSliceTrack(
- e,
- 'Other wakeups',
- `${sqlPrefix} where item not in ('${items.join('\',\'')}')`,
- groupId,
+ e, labelOther ? 'Other wakeups' : 'Wakeups',
+ `${sqlPrefix} where item not in ('${items.join('\',\'')}')`, groupId,
WAKEUPS_COLUMNS));
- return await Promise.all(actions);
+ return flatten(actions);
}
- async addHighCpu(e: EngineProxy, groupId: string):
+ async addHighCpu(e: EngineProxy, features: Set<string>):
Promise<DeferredAction<{}>[]> {
+ if (!features.has('atom.cpu_cycles_per_uid_cluster')) {
+ return [];
+ }
+ const {groupId, groupActions} = this.addGroup('CPU per UID (major users)');
+
await e.query(HIGH_CPU);
const result = await e.query(
`select distinct pkg, cluster from high_cpu where value > 10 order by 1, 2`);
const it = result.iter({pkg: 'str', cluster: 'str'});
- const actions: Promise<DeferredAction<{}>>[] = [];
+ const actions: MixedActions = [groupActions];
for (; it.valid(); it.next()) {
actions.push(this.addCounterTrack(
e,
@@ -1120,16 +1164,22 @@
it.pkg}" and cluster="${it.cluster}"`,
groupId));
}
- return await Promise.all(actions);
+ return flatten(actions);
}
- async addBluetooth(e: EngineProxy, groupId: string):
+ async addBluetooth(e: EngineProxy, features: Set<string>):
Promise<DeferredAction<{}>[]> {
- return await Promise.all([
+ if (!Array.from(features.values())
+ .some(
+ (f) => f.startsWith('atom.bluetooth_') ||
+ f.startsWith('atom.ble_'))) {
+ return [];
+ }
+ const {groupId, groupActions} = this.addGroup('Bluetooth');
+ return flatten([
+ groupActions,
this.addSliceTrack(
- e,
- 'BLE Scans (opportunistic)',
- bleScanQuery('opportunistic'),
+ e, 'BLE Scans (opportunistic)', bleScanQuery('opportunistic'),
groupId),
this.addSliceTrack(
e, 'BLE Scans (filtered)', bleScanQuery('filtered'), groupId),
@@ -1139,27 +1189,18 @@
this.addSliceTrack(e, 'Connections (ACL)', BT_CONNS_ACL, groupId),
this.addSliceTrack(e, 'Connections (SCO)', BT_CONNS_SCO, groupId),
this.addSliceTrack(
- e,
- 'Link-level Events',
- BT_LINK_LEVEL_EVENTS,
- groupId,
+ e, 'Link-level Events', BT_LINK_LEVEL_EVENTS, groupId,
BT_LINK_LEVEL_EVENTS_COLUMNS),
this.addSliceTrack(e, 'A2DP Audio', BT_A2DP_AUDIO, groupId),
this.addSliceTrack(
- e,
- 'Quality reports',
- BT_QUALITY_REPORTS,
- groupId,
+ e, 'Quality reports', BT_QUALITY_REPORTS, groupId,
BT_QUALITY_REPORTS_COLUMNS),
this.addSliceTrack(
e, 'RSSI Reports', BT_RSSI_REPORTS, groupId, BT_RSSI_REPORTS_COLUMNS),
this.addSliceTrack(
e, 'HAL Crashes', BT_HAL_CRASHES, groupId, BT_HAL_CRASHES_COLUMNS),
this.addSliceTrack(
- e,
- 'Code Path Counter',
- BT_CODE_PATH_COUNTER,
- groupId,
+ e, 'Code Path Counter', BT_CODE_PATH_COUNTER, groupId,
BT_CODE_PATH_COUNTER_COLUMNS),
]);
}
@@ -1173,13 +1214,14 @@
throw new Error(`No group ${name} found`);
}
- addGroup(groupName: string): {id: string, actions: DeferredAction<{}>[]} {
+ addGroup(groupName: string):
+ {groupId: string, groupActions: DeferredAction<{}>[]} {
const summaryTrackKey = uuidv4();
const groupUuid = uuidv4();
return {
- id: groupUuid,
- actions: [
+ groupId: groupUuid,
+ groupActions: [
Actions.addTrack({
uri: NULL_TRACK_URI,
key: summaryTrackKey,
@@ -1197,40 +1239,71 @@
};
}
- async onTraceLoad(ctx: PluginContextTrace): Promise<void> {
- ctx.registerCommand({
- id: 'dev.perfetto.AndroidLongBatteryTracing#run',
- name: 'Add long battery tracing tracks',
- callback: async () => {
- const actions: DeferredAction<{}>[] = [];
- const addGroup = (name: string) => {
- const {id, actions: a} = this.addGroup(name);
- actions.push(...a);
- return id;
- };
- const miscGroupId = this.findGroupId('Misc Global Tracks');
- const networkId = addGroup('Network Summary');
- const wakelocksId = addGroup('Kernel Wakelocks');
- const wakeupsId = addGroup('Wakeups');
- const cpuId = addGroup('CPU');
- const btId = addGroup('Bluetooth');
- actions.push(await this.addSliceTrack(
- ctx.engine, 'Thermal throttling', THERMAL_THROTTLING, miscGroupId));
+ async findFeatures(e: EngineProxy): Promise<Set<string>> {
+ const features = new Set<string>();
- const promises: Promise<DeferredAction<{}>[]>[] = [
- this.addBatteryStatsEvents(ctx.engine, miscGroupId),
- this.addNetworkSummary(ctx.engine, networkId),
- this.addModemActivityInfo(ctx.engine, networkId),
- this.addModemRil(ctx.engine, networkId),
- this.addKernelWakelocks(ctx.engine, wakelocksId),
- this.addWakeups(ctx.engine, wakeupsId),
- this.addHighCpu(ctx.engine, cpuId),
- this.addBluetooth(ctx.engine, btId),
- ];
- const flattenedActions: DeferredAction<{}>[] =
- flatten(await Promise.all(promises));
- globals.dispatchMultiple([...actions, ...flattenedActions]);
- },
+ const addFeatures = async (q: string) => {
+ const result = await e.query(q);
+ const it = result.iter({feature: 'str'});
+ for (; it.valid(); it.next()) {
+ features.add(it.feature);
+ }
+ };
+
+ await addFeatures(`
+ select distinct 'atom.' || s.name as feature
+ from track t join slice s on t.id = s.track_id
+ where t.name = 'Statsd Atoms'`);
+
+ await addFeatures(`
+ select distinct
+ case when name like '%wlan%' then 'net.wifi'
+ when name like '%rmnet%' then 'net.modem'
+ else 'net.other'
+ end as feature
+ from track
+ where name like '%Transmitted' or name like '%Received'`);
+
+ await addFeatures(`
+ select distinct 'track.' || lower(name) as feature
+ from track where name in ('RIL', 'suspend_backoff')`);
+
+ return features;
+ }
+
+ async addTracks(ctx: PluginContextTrace): Promise<void> {
+ const actions: MixedActions = [];
+ const features: Set<string> = await this.findFeatures(ctx.engine);
+
+ const miscGroupId = this.findGroupId('Misc Global Tracks');
+
+ if (features.has('atom.thermal_throttling_severity_state_changed')) {
+ actions.push(this.addSliceTrack(
+ ctx.engine, 'Thermal throttling', THERMAL_THROTTLING, miscGroupId));
+ }
+
+ actions.push(
+ this.addBatteryStatsEvents(ctx.engine, miscGroupId),
+ this.addNetworkSummary(ctx.engine, features),
+ this.addModemDetail(ctx.engine, features),
+ this.addKernelWakelocks(ctx.engine, features),
+ this.addWakeups(ctx.engine, features),
+ this.addHighCpu(ctx.engine, features),
+ this.addBluetooth(ctx.engine, features),
+ );
+ globals.dispatchMultiple(await flatten(actions));
+ }
+
+ async onTraceLoad(ctx: PluginContextTrace): Promise<void> {
+ // If we add tracks immediately the decider will not have run and things
+ // will go rather wrong. So instead wait until the engine is ready then
+ // add our things on top. This is a bit of a hackm but the best we can
+ // do right now.
+ const disposer = globals.store.subscribe(async () => {
+ if (globals.state.engine?.ready) {
+ disposer.dispose();
+ await this.addTracks(ctx);
+ }
});
}
}