blob: b2ddb3c6ca36ee8cfb861f05a965a57b683d6244 [file] [log] [blame] [edit]
// Copyright (C) 2025 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 {assertExists} from '../../base/logging';
import {PerfettoPlugin} from '../../public/plugin';
import {Trace} from '../../public/trace';
import {COUNTER_TRACK_KIND, SLICE_TRACK_KIND} from '../../public/track_kinds';
import {TrackNode} from '../../public/workspace';
import {
LONG_NULL,
NUM,
NUM_NULL,
STR_NULL,
} from '../../trace_processor/query_result';
import ProcessThreadGroupsPlugin from '../dev.perfetto.ProcessThreadGroups';
import StandardGroupsPlugin from '../dev.perfetto.StandardGroups';
import TraceProcessorTrackPlugin from '../dev.perfetto.TraceProcessorTrack';
import {TraceProcessorCounterTrack} from '../dev.perfetto.TraceProcessorTrack/trace_processor_counter_track';
import {createTraceProcessorSliceTrack} from '../dev.perfetto.TraceProcessorTrack/trace_processor_slice_track';
export default class implements PerfettoPlugin {
static readonly id = 'dev.perfetto.KernelTrackEvent';
static readonly dependencies = [
ProcessThreadGroupsPlugin,
StandardGroupsPlugin,
TraceProcessorTrackPlugin,
];
static readonly description =
'Renders tracks derived from ftrace events that follow a specific convention.';
private kernelTrackEventsNode?: TrackNode;
async onTraceLoad(trace: Trace): Promise<void> {
await this.addTracks(trace);
}
private async addTracks(ctx: Trace) {
// Four types of scopes for both slice and counter tracks, exactly one scope
// dimension should be set per row.
const res = await ctx.engine.query(`
WITH
kernel_tracks AS (
SELECT
id,
name,
type,
extract_arg(dimension_arg_set_id, 'utid') AS utid,
extract_arg(dimension_arg_set_id, 'upid') AS upid,
extract_arg(dimension_arg_set_id, 'cpu') AS cpu,
extract_arg(dimension_arg_set_id, 'scope') AS scope
FROM track
WHERE
type IN ('kernel_trackevent_thread_slice',
'kernel_trackevent_process_slice',
'kernel_trackevent_cpu_slice',
'kernel_trackevent_custom_slice',
'kernel_trackevent_thread_counter',
'kernel_trackevent_process_counter',
'kernel_trackevent_cpu_counter',
'kernel_trackevent_custom_counter')
)
SELECT
kt.id as trackId,
kt.name,
kt.utid,
kt.upid,
kt.cpu,
kt.scope,
kt.type IN (
'kernel_trackevent_thread_counter',
'kernel_trackevent_process_counter',
'kernel_trackevent_cpu_counter',
'kernel_trackevent_custom_counter'
) as isCounter,
t.tid as tid,
p.pid as pid
FROM kernel_tracks AS kt
LEFT JOIN process AS p
USING (upid)
LEFT JOIN thread AS t
USING (utid)
ORDER BY COALESCE(t.tid, p.pid, kt.cpu, kt.scope)
`);
const it = res.iter({
trackId: NUM,
name: STR_NULL,
utid: NUM_NULL,
upid: NUM_NULL,
cpu: NUM_NULL,
scope: NUM_NULL,
isCounter: NUM,
tid: LONG_NULL,
pid: LONG_NULL,
});
for (; it.valid(); it.next()) {
const {trackId, name, utid, upid, cpu, isCounter, scope, tid, pid} = it;
const displayTrackName = this.getTrackName(
assertExists(name),
pid ?? undefined,
tid ?? undefined,
cpu ?? undefined,
scope ?? undefined,
);
// Register track.
const uri = `/kernel_trackevent_${trackId}`;
if (isCounter) {
ctx.tracks.registerTrack({
uri,
tags: {
kinds: [COUNTER_TRACK_KIND],
trackIds: [trackId],
upid: upid ?? undefined,
utid: utid ?? undefined,
cpu: cpu ?? undefined,
trackScope: scope ?? undefined,
},
renderer: new TraceProcessorCounterTrack(
ctx,
uri,
{},
trackId,
displayTrackName,
),
});
} else {
// slice track
ctx.tracks.registerTrack({
uri,
tags: {
kinds: [SLICE_TRACK_KIND],
trackIds: [trackId],
upid: upid ?? undefined,
utid: utid ?? undefined,
cpu: cpu ?? undefined,
trackScope: scope ?? undefined,
},
renderer: await createTraceProcessorSliceTrack({
trace: ctx,
uri,
trackIds: [trackId],
}),
});
}
// Create TrackNode and add it to the relevant group.
// Order calculation copied from dev.perfetto.TraceProcessorTrack.
const order = utid !== undefined || upid !== undefined ? 20 : 0;
const track = new TrackNode({
uri,
name: displayTrackName,
sortOrder: order,
});
const parent = this.getOrCreateParentTrackNode(
ctx,
upid ?? undefined,
utid ?? undefined,
cpu ?? undefined,
);
parent.addChildInOrder(track);
}
}
private getTrackName(
name: string,
pid: bigint | undefined,
tid: bigint | undefined,
cpu: number | undefined,
scope: number | undefined,
): string {
if (pid !== undefined) {
return `${name} (${pid})`;
}
if (tid !== undefined) {
return `${name} (${tid})`;
}
if (cpu !== undefined) {
return `${name} (${cpu})`;
}
if (scope !== undefined) {
return `${name} (${scope})`;
}
return name;
}
private getOrCreateParentTrackNode(
ctx: Trace,
upid: number | undefined,
utid: number | undefined,
cpu: number | undefined,
): TrackNode {
if (upid !== undefined) {
return assertExists(
ctx.plugins
.getPlugin(ProcessThreadGroupsPlugin)
.getGroupForProcess(assertExists(upid)),
);
}
if (utid !== undefined) {
return assertExists(
ctx.plugins
.getPlugin(ProcessThreadGroupsPlugin)
.getGroupForThread(assertExists(utid)),
);
}
if (cpu !== undefined) {
return assertExists(
ctx.plugins
.getPlugin(StandardGroupsPlugin)
.getOrCreateStandardGroup(ctx.defaultWorkspace, 'CPU'),
);
}
// custom-scoped event: "Kernel -> Kernel track events".
if (this.kernelTrackEventsNode === undefined) {
const kernelGroup = assertExists(
ctx.plugins
.getPlugin(StandardGroupsPlugin)
.getOrCreateStandardGroup(ctx.defaultWorkspace, 'KERNEL'),
);
this.kernelTrackEventsNode = new TrackNode({
name: 'Kernel track events',
isSummary: true,
});
kernelGroup.addChildInOrder(this.kernelTrackEventsNode);
}
return this.kernelTrackEventsNode;
}
}