blob: 915ac095ba7379ec7c9a749c78e3e1178bb1d15a [file] [log] [blame]
// 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 {createAggregationTab} from '../../components/aggregation_adapter';
import {
BaseCounterTrack,
CounterOptions,
} from '../../components/tracks/base_counter_track';
import {createQuerySliceTrack} from '../../components/tracks/query_slice_track';
import {PerfettoPlugin} from '../../public/plugin';
import {Trace} from '../../public/trace';
import {SLICE_TRACK_KIND} from '../../public/track_kinds';
import {TrackNode} from '../../public/workspace';
import {Engine} from '../../trace_processor/engine';
import {NUM} from '../../trace_processor/query_result';
import {WattsonEstimateSelectionAggregator} from './estimate_aggregator';
import {WattsonPackageSelectionAggregator} from './package_aggregator';
import {WattsonProcessSelectionAggregator} from './process_aggregator';
import {WattsonThreadSelectionAggregator} from './thread_aggregator';
import {
CPUSS_ESTIMATE_TRACK_KIND,
GPUSS_ESTIMATE_TRACK_KIND,
} from './track_kinds';
export default class implements PerfettoPlugin {
static readonly id = `org.kernel.Wattson`;
async onTraceLoad(ctx: Trace): Promise<void> {
const markersSupported = await hasWattsonMarkersSupport(ctx.engine);
const cpuSupported = await hasWattsonCpuSupport(ctx.engine);
const gpuSupported = await hasWattsonGpuSupport(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.workspace.addChildInOrder(group);
if (markersSupported) {
await addWattsonMarkersElements(ctx, group);
}
if (cpuSupported) {
await addWattsonCpuElements(ctx, group);
}
if (gpuSupported) {
await addWattsonGpuElements(ctx, group);
}
}
}
class WattsonSubsystemEstimateTrack extends BaseCounterTrack {
readonly queryKey: string;
readonly yRangeKey: string;
constructor(trace: Trace, uri: string, queryKey: string, yRangeKey: string) {
super(trace, uri);
this.queryKey = queryKey;
this.yRangeKey = yRangeKey;
}
async onInit() {
await this.engine.query(
`INCLUDE PERFETTO MODULE wattson.ui.continuous_estimates;`,
);
}
protected getDefaultCounterOptions(): CounterOptions {
const options = super.getDefaultCounterOptions();
options.yRangeSharingKey = this.yRangeKey;
options.unit = `mW`;
return options;
}
getSqlSource() {
return `
SELECT ts, ${this.queryKey} AS value
FROM _system_state_${this.queryKey}
`;
}
}
async function hasWattsonMarkersSupport(engine: Engine): Promise<boolean> {
const checkValue = await engine.query(`
INCLUDE PERFETTO MODULE wattson.utils;
SELECT COUNT(*) as numRows from _wattson_markers_window
`);
return checkValue.firstRow({numRows: NUM}).numRows > 0;
}
async function hasWattsonCpuSupport(engine: Engine): Promise<boolean> {
// These tables are hard requirements and are the bare minimum needed for
// Wattson to run, so check that these tables are populated
const queryChecks: string[] = [
`
INCLUDE PERFETTO MODULE wattson.device_infos;
SELECT COUNT(*) as numRows FROM _wattson_device
`,
`
INCLUDE PERFETTO MODULE linux.cpu.frequency;
SELECT COUNT(*) as numRows FROM cpu_frequency_counters
`,
`
INCLUDE PERFETTO MODULE linux.cpu.idle;
SELECT COUNT(*) as numRows FROM cpu_idle_counters
`,
];
for (const queryCheck of queryChecks) {
const checkValue = await engine.query(queryCheck);
if (checkValue.firstRow({numRows: NUM}).numRows === 0) return false;
}
return true;
}
async function hasWattsonGpuSupport(engine: Engine): Promise<boolean> {
// These tables are hard requirements and are the bare minimum needed for
// Wattson to run, so check that these tables are populated
const queryChecks: string[] = [
`
INCLUDE PERFETTO MODULE android.gpu.frequency;
SELECT COUNT(*) as numRows FROM android_gpu_frequency
`,
`
INCLUDE PERFETTO MODULE android.gpu.mali_power_state;
SELECT COUNT(*) as numRows FROM android_mali_gpu_power_state
`,
];
for (const queryCheck of queryChecks) {
const checkValue = await engine.query(queryCheck);
if (checkValue.firstRow({numRows: NUM}).numRows === 0) return false;
}
return true;
}
async function addWattsonMarkersElements(ctx: Trace, group: TrackNode) {
const uri = `/wattson/markers_window`;
const track = await createQuerySliceTrack({
trace: ctx,
uri,
data: {
sqlSource: `SELECT ts, dur, name FROM _wattson_markers_window`,
},
});
ctx.tracks.registerTrack({
uri,
tags: {
kind: SLICE_TRACK_KIND,
},
renderer: track,
});
group.addChildInOrder(new TrackNode({uri, name: 'Wattson markers window'}));
}
async function addWattsonCpuElements(ctx: Trace, group: TrackNode) {
// ctx.traceInfo.cpus contains all cpus seen from all events. Filter the set
// if it's seen in sched slices.
const queryRes = await ctx.engine.query(
`select distinct ucpu from sched order by ucpu;`,
);
const ucpus = new Set<number>();
for (
const it = queryRes.iter({ucpu: NUM});
it.valid() as boolean;
it.next()
) {
ucpus.add(it.ucpu);
}
// CPUs estimate as part of CPU subsystem
const cpus = ctx.traceInfo.cpus.filter((cpu) => ucpus.has(cpu.ucpu));
for (const cpu of cpus) {
const queryKey = `cpu${cpu.ucpu}_mw`;
const uri = `/wattson/cpu_subsystem_estimate_cpu${cpu.ucpu}`;
ctx.tracks.registerTrack({
uri,
renderer: new WattsonSubsystemEstimateTrack(
ctx,
uri,
queryKey,
`CpuSubsystem`,
),
tags: {
kind: CPUSS_ESTIMATE_TRACK_KIND,
wattson: `CPU${cpu.ucpu}`,
groupName: `Wattson`,
},
});
group.addChildInOrder(
new TrackNode({
uri,
name: `Cpu${cpu.toString()} Estimate`,
}),
);
}
const uri = `/wattson/cpu_subsystem_estimate_dsu_scu`;
const title = `DSU/SCU Estimate`;
ctx.tracks.registerTrack({
uri,
renderer: new WattsonSubsystemEstimateTrack(
ctx,
uri,
`dsu_scu_mw`,
`CpuSubsystem`,
),
tags: {
kind: CPUSS_ESTIMATE_TRACK_KIND,
wattson: 'Dsu_Scu',
groupName: `Wattson`,
},
});
group.addChildInOrder(new TrackNode({uri, name: title}));
// 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.selection.registerAreaSelectionTab(
createAggregationTab(ctx, new WattsonProcessSelectionAggregator()),
);
if (await isProcessMetadataPresent(ctx.engine)) {
ctx.selection.registerAreaSelectionTab(
createAggregationTab(ctx, new WattsonPackageSelectionAggregator()),
);
}
}
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: new WattsonSubsystemEstimateTrack(
ctx,
id,
`gpu_mw`,
`GpuSubsystem`,
),
tags: {
kind: GPUSS_ESTIMATE_TRACK_KIND,
wattson: 'Gpu',
groupName: `Wattson`,
},
});
group.addChildInOrder(new TrackNode({uri: id, name: `GPU Estimate`}));
}