blob: 879a1e48578ecb351c9842bca8d172d97f8e70e2 [file] [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 {Gpu} from '../../components/gpu';
import {
LONG,
LONG_NULL,
NUM,
NUM_NULL,
STR,
STR_NULL,
} from '../../trace_processor/query_result';
import type {PerfettoPlugin} from '../../public/plugin';
import type {Trace} from '../../public/trace';
import {TrackNode} from '../../public/workspace';
import {SLICE_TRACK_KIND} from '../../public/track_kinds';
import {SliceTrack} from '../../components/tracks/slice_track';
import {SourceDataset} from '../../trace_processor/dataset';
import GpuPlugin, {SUMMARY_GROUP_SORT_BASE} from '../dev.perfetto.Gpu';
import StandardGroupsPlugin from '../dev.perfetto.StandardGroups';
export default class implements PerfettoPlugin {
static readonly id = 'com.android.GpuWorkPeriod';
static readonly dependencies = [GpuPlugin, StandardGroupsPlugin];
async onTraceLoad(ctx: Trace): Promise<void> {
const {engine} = ctx;
const gpuCount = (
await engine.query(`select count(*) as cnt from gpu`)
).firstRow({cnt: NUM}).cnt;
const result = await engine.query(`
include perfetto module android.gpu.work_period;
with grouped_packages as materialized (
select
uid,
group_concat(package_name, ',') as package_name,
count() as cnt
from package_list
group by uid
)
select
t.id as trackId,
t.uid as uid,
t.gpu_id as gpuId,
t.ugpu as ugpu,
t.machine_id as machineId,
g.name as gpuName,
m.name as machineName,
iif(p.cnt = 1, p.package_name, 'UID ' || t.uid) as packageName
from android_gpu_work_period_track t
left join grouped_packages p using (uid)
left join gpu g on g.id = t.ugpu
left join machine m on m.id = t.machine_id
order by t.gpu_id, lower(packageName)
`);
const it = result.iter({
trackId: NUM,
uid: NUM,
gpuId: NUM,
ugpu: NUM_NULL,
machineId: NUM_NULL,
gpuName: STR_NULL,
machineName: STR_NULL,
packageName: STR,
});
let gpuGroup: TrackNode | undefined;
const getGpuGroup = () => {
return (gpuGroup ??= ctx.plugins
.getPlugin(StandardGroupsPlugin)
.getOrCreateStandardGroup(ctx.defaultWorkspace, 'GPU'));
};
// Cache the work-period group(s) by name so each is created only once.
const groupsByName = new Map<string, TrackNode>();
for (; it.valid(); it.next()) {
const {trackId, gpuId, uid, packageName} = it;
const uri = `/gpu_work_period_${gpuId}_${uid}`;
const track = await SliceTrack.createMaterialized({
trace: ctx,
uri,
dataset: new SourceDataset({
src: `
select ts, dur, name
from slice
where track_id = ${trackId}
`,
schema: {
ts: LONG,
dur: LONG_NULL,
name: STR,
},
}),
});
ctx.tracks.registerTrack({
uri,
tags: {
trackIds: [trackId],
kinds: [SLICE_TRACK_KIND],
},
renderer: track,
});
// The per-GPU split (when there is more than one GPU) is flattened into
// the group name rather than adding a second nesting level, keeping the
// tree one level deep under GPU.
let groupName = 'Work Period';
if (gpuCount > 1) {
const gpu = new Gpu(
it.ugpu ?? gpuId,
gpuId,
it.machineId ?? 0,
it.gpuName ?? undefined,
it.machineName ?? undefined,
);
groupName = `Work Period (${gpu.displayName})${gpu.maybeMachineLabel()}`;
}
let group = groupsByName.get(groupName);
if (group === undefined) {
group = new TrackNode({
name: groupName,
isSummary: true,
collapsed: true,
sortOrder: SUMMARY_GROUP_SORT_BASE,
});
groupsByName.set(groupName, group);
getGpuGroup().addChildInOrder(group);
}
group.addChildInOrder(new TrackNode({name: packageName, uri}));
}
}
}