1// Copyright (C) 2024 The Android Open Source Project 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15import {getThreadInfo, ThreadInfo} from '../../components/sql_utils/thread'; 16import {addDebugSliceTrack} from '../../components/tracks/debug_tracks'; 17import {Trace} from '../../public/trace'; 18import {THREAD_STATE_TRACK_KIND} from '../../public/track_kinds'; 19import {PerfettoPlugin} from '../../public/plugin'; 20import {asUtid, Utid} from '../../components/sql_utils/core_types'; 21import {addQueryResultsTab} from '../../components/query_table/query_result_tab'; 22import {showModal} from '../../widgets/modal'; 23import { 24 CRITICAL_PATH_CMD, 25 CRITICAL_PATH_LITE_CMD, 26} from '../../public/exposed_commands'; 27import {getTimeSpanOfSelectionOrVisibleWindow} from '../../public/utils'; 28 29const criticalPathSliceColumns = { 30 ts: 'ts', 31 dur: 'dur', 32 name: 'name', 33}; 34 35const criticalPathsliceColumnNames = [ 36 'id', 37 'utid', 38 'ts', 39 'dur', 40 'name', 41 'table_name', 42]; 43 44const criticalPathsliceLiteColumns = { 45 ts: 'ts', 46 dur: 'dur', 47 name: 'thread_name', 48}; 49 50const criticalPathsliceLiteColumnNames = [ 51 'id', 52 'utid', 53 'ts', 54 'dur', 55 'thread_name', 56 'process_name', 57 'table_name', 58]; 59 60const sliceLiteColumns = {ts: 'ts', dur: 'dur', name: 'thread_name'}; 61 62const sliceLiteColumnNames = [ 63 'id', 64 'utid', 65 'ts', 66 'dur', 67 'thread_name', 68 'process_name', 69 'table_name', 70]; 71 72const sliceColumns = {ts: 'ts', dur: 'dur', name: 'name'}; 73 74const sliceColumnNames = ['id', 'utid', 'ts', 'dur', 'name', 'table_name']; 75 76function getFirstUtidOfSelectionOrVisibleWindow(trace: Trace): number { 77 const selection = trace.selection.selection; 78 if (selection.kind === 'area') { 79 for (const trackDesc of selection.tracks) { 80 if ( 81 trackDesc?.tags?.kind === THREAD_STATE_TRACK_KIND && 82 trackDesc?.tags?.utid !== undefined 83 ) { 84 return trackDesc.tags.utid; 85 } 86 } 87 } 88 89 return 0; 90} 91 92function showModalErrorAreaSelectionRequired() { 93 showModal({ 94 title: 'Error: range selection required', 95 content: 96 'This command requires an area selection over a thread state track.', 97 }); 98} 99 100function showModalErrorThreadStateRequired() { 101 showModal({ 102 title: 'Error: thread state selection required', 103 content: 'This command requires a thread state slice to be selected.', 104 }); 105} 106 107// If utid is undefined, returns the utid for the selected thread state track, 108// if any. If it's defined, looks up the info about that specific utid. 109async function getThreadInfoForUtidOrSelection( 110 trace: Trace, 111 utid?: Utid, 112): Promise<ThreadInfo | undefined> { 113 if (utid === undefined) { 114 const selection = trace.selection.selection; 115 if (selection.kind === 'track_event') { 116 if (selection.utid !== undefined) { 117 utid = asUtid(selection.utid); 118 } 119 } 120 } 121 if (utid === undefined) return undefined; 122 return getThreadInfo(trace.engine, utid); 123} 124 125export default class implements PerfettoPlugin { 126 static readonly id = 'dev.perfetto.CriticalPath'; 127 async onTraceLoad(ctx: Trace): Promise<void> { 128 // The 3 commands below are used in two contextes: 129 // 1. By clicking a slice and using the command palette. In this case the 130 // utid argument is undefined and we need to look at the selection. 131 // 2. Invoked via runCommand(...) by thread_state_tab.ts when the user 132 // clicks on the buttons in the details panel. In this case the details 133 // panel passes the utid explicitly. 134 ctx.commands.registerCommand({ 135 id: CRITICAL_PATH_LITE_CMD, 136 name: 'Critical path lite (selected thread state slice)', 137 callback: async (utid?: Utid) => { 138 const thdInfo = await getThreadInfoForUtidOrSelection(ctx, utid); 139 if (thdInfo === undefined) { 140 return showModalErrorThreadStateRequired(); 141 } 142 ctx.engine 143 .query(`INCLUDE PERFETTO MODULE sched.thread_executing_span;`) 144 .then(() => 145 addDebugSliceTrack({ 146 trace: ctx, 147 data: { 148 sqlSource: ` 149 SELECT 150 cr.id, 151 cr.utid, 152 cr.ts, 153 cr.dur, 154 thread.name AS thread_name, 155 process.name AS process_name, 156 'thread_state' AS table_name 157 FROM 158 _thread_executing_span_critical_path( 159 ${thdInfo.utid}, 160 trace_bounds.start_ts, 161 trace_bounds.end_ts - trace_bounds.start_ts) cr, 162 trace_bounds 163 JOIN thread USING(utid) 164 JOIN process USING(upid) 165 `, 166 columns: sliceLiteColumnNames, 167 }, 168 title: `${thdInfo.name}`, 169 columns: sliceLiteColumns, 170 argColumns: sliceLiteColumnNames, 171 }), 172 ); 173 }, 174 }); 175 176 ctx.commands.registerCommand({ 177 id: CRITICAL_PATH_CMD, 178 name: 'Critical path (selected thread state slice)', 179 callback: async (utid?: Utid) => { 180 const thdInfo = await getThreadInfoForUtidOrSelection(ctx, utid); 181 if (thdInfo === undefined) { 182 return showModalErrorThreadStateRequired(); 183 } 184 ctx.engine 185 .query( 186 `INCLUDE PERFETTO MODULE sched.thread_executing_span_with_slice;`, 187 ) 188 .then(() => 189 addDebugSliceTrack({ 190 trace: ctx, 191 data: { 192 sqlSource: ` 193 SELECT cr.id, cr.utid, cr.ts, cr.dur, cr.name, cr.table_name 194 FROM 195 _thread_executing_span_critical_path_stack( 196 ${thdInfo.utid}, 197 trace_bounds.start_ts, 198 trace_bounds.end_ts - trace_bounds.start_ts) cr, 199 trace_bounds WHERE name IS NOT NULL 200 `, 201 columns: sliceColumnNames, 202 }, 203 title: `${thdInfo.name}`, 204 columns: sliceColumns, 205 argColumns: sliceColumnNames, 206 }), 207 ); 208 }, 209 }); 210 211 ctx.commands.registerCommand({ 212 id: 'perfetto.CriticalPathLite_AreaSelection', 213 name: 'Critical path lite (over area selection)', 214 callback: async () => { 215 const trackUtid = getFirstUtidOfSelectionOrVisibleWindow(ctx); 216 const window = await getTimeSpanOfSelectionOrVisibleWindow(ctx); 217 if (trackUtid === 0) { 218 return showModalErrorAreaSelectionRequired(); 219 } 220 await ctx.engine.query( 221 `INCLUDE PERFETTO MODULE sched.thread_executing_span;`, 222 ); 223 await addDebugSliceTrack({ 224 trace: ctx, 225 data: { 226 sqlSource: ` 227 SELECT 228 cr.id, 229 cr.utid, 230 cr.ts, 231 cr.dur, 232 thread.name AS thread_name, 233 process.name AS process_name, 234 'thread_state' AS table_name 235 FROM 236 _thread_executing_span_critical_path( 237 ${trackUtid}, 238 ${window.start}, 239 ${window.end} - ${window.start}) cr 240 JOIN thread USING(utid) 241 JOIN process USING(upid) 242 `, 243 columns: criticalPathsliceLiteColumnNames, 244 }, 245 title: 246 (await getThreadInfo(ctx.engine, trackUtid as Utid)).name ?? 247 '<thread name>', 248 columns: criticalPathsliceLiteColumns, 249 argColumns: criticalPathsliceLiteColumnNames, 250 }); 251 }, 252 }); 253 254 ctx.commands.registerCommand({ 255 id: 'perfetto.CriticalPath_AreaSelection', 256 name: 'Critical path (over area selection)', 257 callback: async () => { 258 const trackUtid = getFirstUtidOfSelectionOrVisibleWindow(ctx); 259 const window = await getTimeSpanOfSelectionOrVisibleWindow(ctx); 260 if (trackUtid === 0) { 261 return showModalErrorAreaSelectionRequired(); 262 } 263 await ctx.engine.query( 264 `INCLUDE PERFETTO MODULE sched.thread_executing_span_with_slice;`, 265 ); 266 await addDebugSliceTrack({ 267 trace: ctx, 268 data: { 269 sqlSource: ` 270 SELECT cr.id, cr.utid, cr.ts, cr.dur, cr.name, cr.table_name 271 FROM 272 _critical_path_stack( 273 ${trackUtid}, 274 ${window.start}, 275 ${window.end} - ${window.start}, 1, 1, 1, 1) cr 276 WHERE name IS NOT NULL 277 `, 278 columns: criticalPathsliceColumnNames, 279 }, 280 title: 281 (await getThreadInfo(ctx.engine, trackUtid as Utid)).name ?? 282 '<thread name>', 283 columns: criticalPathSliceColumns, 284 argColumns: criticalPathsliceColumnNames, 285 }); 286 }, 287 }); 288 289 ctx.commands.registerCommand({ 290 id: 'perfetto.CriticalPathPprof_AreaSelection', 291 name: 'Critical path pprof (over area selection)', 292 callback: async () => { 293 const trackUtid = getFirstUtidOfSelectionOrVisibleWindow(ctx); 294 const window = await getTimeSpanOfSelectionOrVisibleWindow(ctx); 295 if (trackUtid === 0) { 296 return showModalErrorAreaSelectionRequired(); 297 } 298 addQueryResultsTab(ctx, { 299 query: ` 300 INCLUDE PERFETTO MODULE sched.thread_executing_span_with_slice; 301 SELECT * 302 FROM 303 _thread_executing_span_critical_path_graph( 304 "criical_path", 305 ${trackUtid}, 306 ${window.start}, 307 ${window.end} - ${window.start}) cr`, 308 title: 'Critical path', 309 }); 310 }, 311 }); 312 } 313} 314