blob: 2fd23691d4d7d57fdeba87fdd34474a7235f232b [file]
// Copyright (C) 2024 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import './styles.scss';
import {addWattsonThreadTrack} from './wattson_thread_utils';
import type {App} from '../../public/app';
import {createAggregationTab} from '../../components/aggregation_adapter';
import {CounterTrack} from '../../components/tracks/counter_track';
import {SliceTrack} from '../../components/tracks/slice_track';
import type {PerfettoPlugin} from '../../public/plugin';
import type {Trace} from '../../public/trace';
import {SLICE_TRACK_KIND} from '../../public/track_kinds';
import {TrackNode} from '../../public/workspace';
import type {Engine} from '../../trace_processor/engine';
import {SourceDataset} from '../../trace_processor/dataset';
import {LONG, LONG_NULL, NUM, STR} from '../../trace_processor/query_result';
import type {RouteArgs} from '../../public/route_schema';
import {WattsonEstimateSelectionAggregator} from './estimate_aggregator';
import {
WattsonCpuPackageSelectionAggregator,
WattsonGpuPackageSelectionAggregator,
} from './package_aggregator';
import {WattsonProcessSelectionAggregator} from './process_aggregator';
import {WattsonThreadSelectionAggregator} from './thread_aggregator';
import {
CPUSS_ESTIMATE_TRACK_KIND,
GPUSS_ESTIMATE_TRACK_KIND,
TPUSS_ESTIMATE_TRACK_KIND,
} from './track_kinds';
import {createCpuWarnings, missingWattsonCpuConfigs} from './warning';
const WINDOW_MAP: Record<string, string> = {
perfetto_wattson_markers: 'markers',
perfetto_wattson_trace: 'trace',
perfetto_wattson_apps: 'atrace_apps',
perfetto_wattson_app_startup: 'app_startup',
};
export default class Wattson implements PerfettoPlugin {
static readonly id = `org.kernel.Wattson`;
static readonly dependencies = [];
public static windowsOfInterest = new Set<string>();
static onActivate(_app: App, args: RouteArgs): void {
const metrics: string[] = [];
if (typeof args.metrics === 'string') {
metrics.push(...args.metrics.split('--'));
}
Wattson.updateWindowsOfInterest(metrics);
}
async onTraceLoad(ctx: Trace): Promise<void> {
const [
markersSupported,
cpuSupported,
gpuSupported,
tpuSupported,
realCpuIdleCounters,
] = await Promise.all([
hasWattsonMarkersSupport(ctx.engine),
hasWattsonCpuSupport(ctx.engine),
hasWattsonGpuSupport(ctx.engine),
hasWattsonTpuSupport(ctx.engine),
hasCpuIdleCounters(ctx.engine),
]);
const missingEvents = markersSupported
? await missingWattsonCpuConfigs(ctx.engine)
: [];
// Short circuit if Wattson is not supported for this Perfetto trace
if (!(markersSupported || cpuSupported || gpuSupported)) return;
const group = new TrackNode({name: 'Wattson', isSummary: true});
ctx.defaultWorkspace.addChildInOrder(group);
if (markersSupported) {
await addWattsonMarkersElements(ctx, group);
}
if (cpuSupported || markersSupported) {
await addWattsonCpuElements(
ctx,
group,
missingEvents,
realCpuIdleCounters,
);
}
if (gpuSupported) {
await addWattsonGpuElements(ctx, group);
}
if (tpuSupported) {
await addWattsonTpuElements(ctx, group);
}
if (Wattson.windowsOfInterest.size > 0) {
await this.pinThreadPowerTracks(ctx);
Wattson.windowsOfInterest.clear();
}
}
async pinThreadPowerTracks(ctx: Trace) {
if (Wattson.windowsOfInterest.size === 0) return;
// Gather all utids from all windows
const windowQueries = Array.from(Wattson.windowsOfInterest)
.map((window) => {
const suffix = WINDOW_MAP[window];
if (suffix === undefined) return undefined;
return `
INCLUDE PERFETTO MODULE wattson.aggregation;
INCLUDE PERFETTO MODULE wattson.windows;
SELECT utid
FROM wattson_threads_aggregation!(wattson_window_${suffix})
GROUP BY utid
ORDER BY SUM(total_mws) DESC
LIMIT 3
`;
})
.filter((q): q is string => q !== undefined);
const queryResults = await Promise.all(
windowQueries.map((q) => ctx.engine.query(q)),
);
const utidsToPin = new Set<number>();
for (const result of queryResults) {
const it = result.iter({utid: NUM});
for (; it.valid(); it.next()) {
utidsToPin.add(it.utid);
}
}
// Only add tracks of unique utids
for (const utid of utidsToPin) {
await addWattsonThreadTrack(ctx, utid, {pin: true, scrollTo: false});
}
}
public static updateWindowsOfInterest(metrics: string[]) {
for (const metric of metrics) {
for (const key of Object.keys(WINDOW_MAP)) {
if (metric.includes(key)) {
Wattson.windowsOfInterest.add(key);
}
}
}
}
}
function makeWattsonEstimateTrack(
trace: Trace,
uri: string,
queryKey: string,
yRangeKey: string,
): CounterTrack {
return CounterTrack.create({
trace,
uri,
sqlSource: `SELECT ts, ${queryKey} AS value FROM _system_state_${queryKey}`,
unit: 'mW',
yRangeSharingKey: yRangeKey,
onInit: async () => {
await trace.engine.query(
`INCLUDE PERFETTO MODULE wattson.ui.continuous_estimates;`,
);
},
});
}
async function hasCpuIdleCounters(engine: Engine): Promise<boolean> {
const result = await engine.query(`
SELECT EXISTS (
SELECT 1
FROM cpu_counter_track
WHERE type = 'cpu_idle'
) AS supported
`);
return !!result.firstRow({supported: NUM}).supported;
}
async function hasWattsonMarkersSupport(engine: Engine): Promise<boolean> {
const result = await engine.query(`
INCLUDE PERFETTO MODULE wattson.windows;
SELECT EXISTS (
SELECT 1 FROM wattson_window_markers
) AS supported
`);
return !!result.firstRow({supported: NUM}).supported;
}
async function hasWattsonCpuSupport(engine: Engine): Promise<boolean> {
const result = await engine.query(`
INCLUDE PERFETTO MODULE wattson.device_infos;
SELECT
EXISTS (SELECT 1 FROM _wattson_device) as device,
EXISTS (SELECT 1 FROM cpu_counter_track WHERE type = 'cpu_frequency') as freq,
EXISTS (SELECT 1 FROM cpu_counter_track WHERE type = 'cpu_idle') as idle
`);
const row = result.firstRow({device: NUM, freq: NUM, idle: NUM});
return !!row.device && !!row.freq && !!row.idle;
}
async function hasWattsonGpuSupport(engine: Engine): Promise<boolean> {
const result = await engine.query(`
INCLUDE PERFETTO MODULE android.gpu.frequency;
INCLUDE PERFETTO MODULE wattson.gpu.freq_idle;
INCLUDE PERFETTO MODULE wattson.curves.utils;
SELECT
EXISTS (SELECT 1 FROM android_gpu_frequency) as freq,
EXISTS (SELECT 1 FROM _gpu_power_state) as idle,
EXISTS (SELECT 1 FROM _gpu_filtered_curves) as has_curves
`);
const row = result.firstRow({freq: NUM, idle: NUM, has_curves: NUM});
return !!row.freq && !!row.idle && !!row.has_curves;
}
async function hasWattsonTpuSupport(engine: Engine): Promise<boolean> {
const result = await engine.query(`
INCLUDE PERFETTO MODULE wattson.tpu.freq_idle;
SELECT EXISTS (
SELECT 1 FROM _tpu_freq WHERE dur IS NOT NULL
) AS supported
`);
return !!result.firstRow({supported: NUM}).supported;
}
async function addWattsonMarkersElements(ctx: Trace, group: TrackNode) {
const uri = `/wattson/markers_window`;
const track = await SliceTrack.createMaterialized({
trace: ctx,
uri,
dataset: new SourceDataset({
schema: {
ts: LONG,
dur: LONG_NULL,
name: STR,
},
src: '_wattson_markers_window',
}),
// Use default details panel
});
ctx.tracks.registerTrack({
uri,
tags: {
kinds: [SLICE_TRACK_KIND],
},
renderer: track,
});
group.addChildInOrder(new TrackNode({uri, name: 'Wattson markers window'}));
}
async function addWattsonCpuElements(
ctx: Trace,
group: TrackNode,
missingEvents: string[],
hasCpuIdleCounters: boolean,
) {
const warningDesc = createCpuWarnings(missingEvents, hasCpuIdleCounters);
// CPUs estimate as part of CPU subsystem
const estimateSuffix = `${hasCpuIdleCounters ? '' : ' crude'} estimate`;
const cpuResult = await ctx.engine.query(
`SELECT cpu FROM cpu WHERE machine_id = 0`,
);
const it = cpuResult.iter({cpu: NUM});
for (; it.valid(); it.next()) {
const queryKey = `cpu${it.cpu}_mw`;
const uri = `/wattson/cpu_subsystem_estimate_cpu${it.cpu}`;
ctx.tracks.registerTrack({
uri,
description: () => warningDesc,
renderer: makeWattsonEstimateTrack(ctx, uri, queryKey, `CpuSubsystem`),
tags: {
kinds: [CPUSS_ESTIMATE_TRACK_KIND],
wattson: `CPU${it.cpu}`,
},
});
group.addChildInOrder(
new TrackNode({
uri,
name: `Cpu${it.cpu}${estimateSuffix}`,
}),
);
}
const uri = `/wattson/cpu_subsystem_estimate_dsu_scu`;
ctx.tracks.registerTrack({
uri,
renderer: makeWattsonEstimateTrack(ctx, uri, `dsu_scu_mw`, `CpuSubsystem`),
tags: {
kinds: [CPUSS_ESTIMATE_TRACK_KIND],
wattson: 'Dsu_Scu',
},
});
group.addChildInOrder(new TrackNode({uri, name: `DSU/SCU${estimateSuffix}`}));
// Register selection aggregators.
// NOTE: the registration order matters because the laste two aggregators
// depend on views created by the first two.
ctx.selection.registerAreaSelectionTab(
createAggregationTab(ctx, new WattsonEstimateSelectionAggregator()),
);
ctx.selection.registerAreaSelectionTab(
createAggregationTab(ctx, new WattsonThreadSelectionAggregator(ctx)),
);
ctx.selection.registerAreaSelectionTab(
createAggregationTab(ctx, new WattsonProcessSelectionAggregator()),
);
if (await isProcessMetadataPresent(ctx.engine)) {
ctx.selection.registerAreaSelectionTab(
createAggregationTab(ctx, new WattsonCpuPackageSelectionAggregator()),
);
}
}
async function isProcessMetadataPresent(engine: Engine) {
const packageInfo = await engine.query(`
INCLUDE PERFETTO MODULE android.process_metadata;
SELECT COUNT(*) as count FROM android_process_metadata
WHERE package_name IS NOT NULL
`);
return packageInfo.firstRow({count: NUM}).count > 0;
}
async function addWattsonGpuElements(ctx: Trace, group: TrackNode) {
const id = `/wattson/gpu_subsystem_estimate`;
ctx.tracks.registerTrack({
uri: id,
renderer: makeWattsonEstimateTrack(ctx, id, `gpu_mw`, `GpuSubsystem`),
tags: {
kinds: [GPUSS_ESTIMATE_TRACK_KIND],
wattson: 'Gpu',
},
});
group.addChildInOrder(new TrackNode({uri: id, name: `GPU Estimate`}));
ctx.selection.registerAreaSelectionTab(
createAggregationTab(ctx, new WattsonGpuPackageSelectionAggregator()),
);
}
async function addWattsonTpuElements(ctx: Trace, group: TrackNode) {
const id = `/wattson/tpu_subsystem_estimate`;
ctx.tracks.registerTrack({
uri: id,
renderer: makeWattsonEstimateTrack(ctx, id, `tpu_mw`, `TpuSubsystem`),
tags: {
kinds: [TPUSS_ESTIMATE_TRACK_KIND],
wattson: 'Tpu',
},
});
group.addChildInOrder(new TrackNode({uri: id, name: `TPU Estimate`}));
}