blob: 94acec3b128fedf521dd2ddac1ee037cb85614d2 [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 {
getThreadInfo,
ThreadInfo,
} from '../../trace_processor/sql_utils/thread';
import {addDebugSliceTrack} from '../../public/debug_tracks';
import {Trace} from '../../public/trace';
import {THREAD_STATE_TRACK_KIND} from '../../public/track_kinds';
import {PerfettoPlugin} from '../../public/plugin';
import {asUtid, Utid} from '../../trace_processor/sql_utils/core_types';
import {addQueryResultsTab} from '../../public/lib/query_table/query_result_tab';
import {showModal} from '../../widgets/modal';
import {
CRITICAL_PATH_CMD,
CRITICAL_PATH_LITE_CMD,
} from '../../public/exposed_commands';
import {getTimeSpanOfSelectionOrVisibleWindow} from '../../public/utils';
const criticalPathSliceColumns = {
ts: 'ts',
dur: 'dur',
name: 'name',
};
const criticalPathsliceColumnNames = [
'id',
'utid',
'ts',
'dur',
'name',
'table_name',
];
const criticalPathsliceLiteColumns = {
ts: 'ts',
dur: 'dur',
name: 'thread_name',
};
const criticalPathsliceLiteColumnNames = [
'id',
'utid',
'ts',
'dur',
'thread_name',
'process_name',
'table_name',
];
const sliceLiteColumns = {ts: 'ts', dur: 'dur', name: 'thread_name'};
const sliceLiteColumnNames = [
'id',
'utid',
'ts',
'dur',
'thread_name',
'process_name',
'table_name',
];
const sliceColumns = {ts: 'ts', dur: 'dur', name: 'name'};
const sliceColumnNames = ['id', 'utid', 'ts', 'dur', 'name', 'table_name'];
function getFirstUtidOfSelectionOrVisibleWindow(trace: Trace): number {
const selection = trace.selection.selection;
if (selection.kind === 'area') {
for (const trackDesc of selection.tracks) {
if (
trackDesc?.tags?.kind === THREAD_STATE_TRACK_KIND &&
trackDesc?.tags?.utid !== undefined
) {
return trackDesc.tags.utid;
}
}
}
return 0;
}
function showModalErrorAreaSelectionRequired() {
showModal({
title: 'Error: range selection required',
content:
'This command requires an area selection over a thread state track.',
});
}
function showModalErrorThreadStateRequired() {
showModal({
title: 'Error: thread state selection required',
content: 'This command requires a thread state slice to be selected.',
});
}
// If utid is undefined, returns the utid for the selected thread state track,
// if any. If it's defined, looks up the info about that specific utid.
async function getThreadInfoForUtidOrSelection(
trace: Trace,
utid?: Utid,
): Promise<ThreadInfo | undefined> {
if (utid === undefined) {
const selection = trace.selection.selection;
if (selection.kind === 'track_event') {
if (selection.utid !== undefined) {
utid = asUtid(selection.utid);
}
}
}
if (utid === undefined) return undefined;
return getThreadInfo(trace.engine, utid);
}
export default class implements PerfettoPlugin {
static readonly id = 'dev.perfetto.CriticalPath';
async onTraceLoad(ctx: Trace): Promise<void> {
// The 3 commands below are used in two contextes:
// 1. By clicking a slice and using the command palette. In this case the
// utid argument is undefined and we need to look at the selection.
// 2. Invoked via runCommand(...) by thread_state_tab.ts when the user
// clicks on the buttons in the details panel. In this case the details
// panel passes the utid explicitly.
ctx.commands.registerCommand({
id: CRITICAL_PATH_LITE_CMD,
name: 'Critical path lite (selected thread state slice)',
callback: async (utid?: Utid) => {
const thdInfo = await getThreadInfoForUtidOrSelection(ctx, utid);
if (thdInfo === undefined) {
return showModalErrorThreadStateRequired();
}
ctx.engine
.query(`INCLUDE PERFETTO MODULE sched.thread_executing_span;`)
.then(() =>
addDebugSliceTrack({
trace: ctx,
data: {
sqlSource: `
SELECT
cr.id,
cr.utid,
cr.ts,
cr.dur,
thread.name AS thread_name,
process.name AS process_name,
'thread_state' AS table_name
FROM
_thread_executing_span_critical_path(
${thdInfo.utid},
trace_bounds.start_ts,
trace_bounds.end_ts - trace_bounds.start_ts) cr,
trace_bounds
JOIN thread USING(utid)
JOIN process USING(upid)
`,
columns: sliceLiteColumnNames,
},
title: `${thdInfo.name}`,
columns: sliceLiteColumns,
argColumns: sliceLiteColumnNames,
}),
);
},
});
ctx.commands.registerCommand({
id: CRITICAL_PATH_CMD,
name: 'Critical path (selected thread state slice)',
callback: async (utid?: Utid) => {
const thdInfo = await getThreadInfoForUtidOrSelection(ctx, utid);
if (thdInfo === undefined) {
return showModalErrorThreadStateRequired();
}
ctx.engine
.query(
`INCLUDE PERFETTO MODULE sched.thread_executing_span_with_slice;`,
)
.then(() =>
addDebugSliceTrack({
trace: ctx,
data: {
sqlSource: `
SELECT cr.id, cr.utid, cr.ts, cr.dur, cr.name, cr.table_name
FROM
_thread_executing_span_critical_path_stack(
${thdInfo.utid},
trace_bounds.start_ts,
trace_bounds.end_ts - trace_bounds.start_ts) cr,
trace_bounds WHERE name IS NOT NULL
`,
columns: sliceColumnNames,
},
title: `${thdInfo.name}`,
columns: sliceColumns,
argColumns: sliceColumnNames,
}),
);
},
});
ctx.commands.registerCommand({
id: 'perfetto.CriticalPathLite_AreaSelection',
name: 'Critical path lite (over area selection)',
callback: async () => {
const trackUtid = getFirstUtidOfSelectionOrVisibleWindow(ctx);
const window = await getTimeSpanOfSelectionOrVisibleWindow(ctx);
if (trackUtid === 0) {
return showModalErrorAreaSelectionRequired();
}
await ctx.engine.query(
`INCLUDE PERFETTO MODULE sched.thread_executing_span;`,
);
await addDebugSliceTrack({
trace: ctx,
data: {
sqlSource: `
SELECT
cr.id,
cr.utid,
cr.ts,
cr.dur,
thread.name AS thread_name,
process.name AS process_name,
'thread_state' AS table_name
FROM
_thread_executing_span_critical_path(
${trackUtid},
${window.start},
${window.end} - ${window.start}) cr
JOIN thread USING(utid)
JOIN process USING(upid)
`,
columns: criticalPathsliceLiteColumnNames,
},
title:
(await getThreadInfo(ctx.engine, trackUtid as Utid)).name ??
'<thread name>',
columns: criticalPathsliceLiteColumns,
argColumns: criticalPathsliceLiteColumnNames,
});
},
});
ctx.commands.registerCommand({
id: 'perfetto.CriticalPath_AreaSelection',
name: 'Critical path (over area selection)',
callback: async () => {
const trackUtid = getFirstUtidOfSelectionOrVisibleWindow(ctx);
const window = await getTimeSpanOfSelectionOrVisibleWindow(ctx);
if (trackUtid === 0) {
return showModalErrorAreaSelectionRequired();
}
await ctx.engine.query(
`INCLUDE PERFETTO MODULE sched.thread_executing_span_with_slice;`,
);
await addDebugSliceTrack({
trace: ctx,
data: {
sqlSource: `
SELECT cr.id, cr.utid, cr.ts, cr.dur, cr.name, cr.table_name
FROM
_critical_path_stack(
${trackUtid},
${window.start},
${window.end} - ${window.start}, 1, 1, 1, 1) cr
WHERE name IS NOT NULL
`,
columns: criticalPathsliceColumnNames,
},
title:
(await getThreadInfo(ctx.engine, trackUtid as Utid)).name ??
'<thread name>',
columns: criticalPathSliceColumns,
argColumns: criticalPathsliceColumnNames,
});
},
});
ctx.commands.registerCommand({
id: 'perfetto.CriticalPathPprof_AreaSelection',
name: 'Critical path pprof (over area selection)',
callback: async () => {
const trackUtid = getFirstUtidOfSelectionOrVisibleWindow(ctx);
const window = await getTimeSpanOfSelectionOrVisibleWindow(ctx);
if (trackUtid === 0) {
return showModalErrorAreaSelectionRequired();
}
addQueryResultsTab(ctx, {
query: `
INCLUDE PERFETTO MODULE sched.thread_executing_span_with_slice;
SELECT *
FROM
_thread_executing_span_critical_path_graph(
"criical_path",
${trackUtid},
${window.start},
${window.end} - ${window.start}) cr`,
title: 'Critical path',
});
},
});
}
}