blob: 783cc2fd9db3ad66ab38284568cffaa0c662c857 [file] [log] [blame] [edit]
// 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 {SliceTrack} from '../../components/tracks/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 {SourceDataset} from '../../trace_processor/dataset';
import {LONG, LONG_NULL, NUM, STR} 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';
import SchedPlugin from '../dev.perfetto.Sched';
import {createCpuWarnings, hasWattsonSufficientCPUConfigs} from './warning';
export default class implements PerfettoPlugin {
static readonly id = `org.kernel.Wattson`;
static readonly dependencies = [SchedPlugin];
async onTraceLoad(ctx: Trace): Promise<void> {
const markersSupported = await hasWattsonMarkersSupport(ctx.engine);
const cpuSupported = await hasWattsonCpuSupport(ctx.engine);
const gpuSupported = await hasWattsonGpuSupport(ctx.engine);
const realCpuIdleCounters = await hasCpuIdleCounters(ctx.engine);
const missingEvents = markersSupported
? await hasWattsonSufficientCPUConfigs(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);
}
}
}
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 hasCpuIdleCounters(engine: Engine): Promise<boolean> {
const checkValue = await engine.query(`
INCLUDE PERFETTO MODULE wattson.cpu.idle;
SELECT COUNT(*) as numRows from _wattson_cpuidle_counters_exist
`);
return checkValue.firstRow({numRows: NUM}).numRows > 0;
}
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 wattson.cpu.idle;
SELECT COUNT(*) as numRows FROM _adjusted_deep_idle
`,
];
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 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 schedPlugin = ctx.plugins.getPlugin(SchedPlugin);
const schedCpus = schedPlugin.schedCpus;
for (const cpu of schedCpus) {
const queryKey = `cpu${cpu.ucpu}_mw`;
const uri = `/wattson/cpu_subsystem_estimate_cpu${cpu.ucpu}`;
ctx.tracks.registerTrack({
uri,
description: () => warningDesc,
renderer: new WattsonSubsystemEstimateTrack(
ctx,
uri,
queryKey,
`CpuSubsystem`,
),
tags: {
kinds: [CPUSS_ESTIMATE_TRACK_KIND],
wattson: `CPU${cpu.ucpu}`,
},
});
group.addChildInOrder(
new TrackNode({
uri,
name: `Cpu${cpu.toString()}${estimateSuffix}`,
}),
);
}
const uri = `/wattson/cpu_subsystem_estimate_dsu_scu`;
ctx.tracks.registerTrack({
uri,
renderer: new WattsonSubsystemEstimateTrack(
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.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: {
kinds: [GPUSS_ESTIMATE_TRACK_KIND],
wattson: 'Gpu',
},
});
group.addChildInOrder(new TrackNode({uri: id, name: `GPU Estimate`}));
}