// 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 '../../components/sql_utils/thread'; import {addDebugSliceTrack} from '../../components/tracks/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 '../../components/sql_utils/core_types'; import {addQueryResultsTab} from '../../components/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 { 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 { // 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 ?? '', 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 ?? '', 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', }); }, }); } }