1// Copyright (C) 2023 The Android Open Source Project 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use size 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 m from 'mithril'; 16import {Anchor} from '../../widgets/anchor'; 17import {Button} from '../../widgets/button'; 18import {DetailsShell} from '../../widgets/details_shell'; 19import {GridLayout} from '../../widgets/grid_layout'; 20import {Section} from '../../widgets/section'; 21import {SqlRef} from '../../widgets/sql_ref'; 22import {Tree, TreeNode} from '../../widgets/tree'; 23import {Intent} from '../../widgets/common'; 24import {SchedSqlId} from '../../components/sql_utils/core_types'; 25import { 26 getThreadState, 27 getThreadStateFromConstraints, 28 ThreadState, 29} from '../../components/sql_utils/thread_state'; 30import {DurationWidget} from '../../components/widgets/duration'; 31import {Timestamp} from '../../components/widgets/timestamp'; 32import {getProcessName} from '../../components/sql_utils/process'; 33import { 34 getFullThreadName, 35 getThreadName, 36} from '../../components/sql_utils/thread'; 37import {ThreadStateRef} from '../../components/widgets/thread_state'; 38import { 39 CRITICAL_PATH_CMD, 40 CRITICAL_PATH_LITE_CMD, 41} from '../../public/exposed_commands'; 42import {goToSchedSlice} from '../../components/widgets/sched'; 43import {TrackEventDetailsPanel} from '../../public/details_panel'; 44import {Trace} from '../../public/trace'; 45import {formatDuration} from '../../components/time_utils'; 46 47interface RelatedThreadStates { 48 prev?: ThreadState; 49 next?: ThreadState; 50 waker?: ThreadState; 51 wakerInterruptCtx?: boolean; 52 wakee?: ThreadState[]; 53} 54 55export class ThreadStateDetailsPanel implements TrackEventDetailsPanel { 56 private threadState?: ThreadState; 57 private relatedStates?: RelatedThreadStates; 58 59 constructor( 60 private readonly trace: Trace, 61 private readonly id: number, 62 ) {} 63 64 async load() { 65 const id = this.id; 66 this.threadState = await getThreadState(this.trace.engine, id); 67 68 if (!this.threadState) { 69 return; 70 } 71 72 const relatedStates: RelatedThreadStates = {}; 73 relatedStates.prev = ( 74 await getThreadStateFromConstraints(this.trace.engine, { 75 filters: [ 76 `ts + dur = ${this.threadState.ts}`, 77 `utid = ${this.threadState.thread?.utid}`, 78 ], 79 limit: 1, 80 }) 81 )[0]; 82 relatedStates.next = ( 83 await getThreadStateFromConstraints(this.trace.engine, { 84 filters: [ 85 `ts = ${this.threadState.ts + this.threadState.dur}`, 86 `utid = ${this.threadState.thread?.utid}`, 87 ], 88 limit: 1, 89 }) 90 )[0]; 91 92 // note: this might be valid even if there is no |waker| slice, in the case 93 // of an interrupt wakeup while in the idle process (which is omitted from 94 // the thread_state table). 95 relatedStates.wakerInterruptCtx = this.threadState.wakerInterruptCtx; 96 97 if (this.threadState.wakerId !== undefined) { 98 relatedStates.waker = await getThreadState( 99 this.trace.engine, 100 this.threadState.wakerId, 101 ); 102 } else if ( 103 this.threadState.state == 'Running' && 104 relatedStates.prev.wakerId != undefined 105 ) { 106 // For running slices, extract waker info from the preceding runnable. 107 relatedStates.waker = await getThreadState( 108 this.trace.engine, 109 relatedStates.prev.wakerId, 110 ); 111 relatedStates.wakerInterruptCtx = relatedStates.prev.wakerInterruptCtx; 112 } 113 114 relatedStates.wakee = await getThreadStateFromConstraints( 115 this.trace.engine, 116 { 117 filters: [ 118 `waker_id = ${id}`, 119 `(irq_context is null or irq_context = 0)`, 120 ], 121 }, 122 ); 123 this.relatedStates = relatedStates; 124 } 125 126 render() { 127 // TODO(altimin/stevegolton): Differentiate between "Current Selection" and 128 // "Pinned" views in DetailsShell. 129 return m( 130 DetailsShell, 131 {title: 'Thread State', description: this.renderLoadingText()}, 132 m( 133 GridLayout, 134 m( 135 Section, 136 {title: 'Details'}, 137 this.threadState && this.renderTree(this.threadState), 138 ), 139 m( 140 Section, 141 {title: 'Related thread states'}, 142 this.renderRelatedThreadStates(), 143 ), 144 ), 145 ); 146 } 147 148 private renderLoadingText() { 149 if (!this.threadState) { 150 return 'Loading'; 151 } 152 return this.id; 153 } 154 155 private renderTree(threadState: ThreadState) { 156 const thread = threadState.thread; 157 const process = threadState.thread?.process; 158 return m( 159 Tree, 160 m(TreeNode, { 161 left: 'Start time', 162 right: m(Timestamp, {ts: threadState.ts}), 163 }), 164 m(TreeNode, { 165 left: 'Duration', 166 right: m(DurationWidget, {dur: threadState.dur}), 167 }), 168 m(TreeNode, { 169 left: 'State', 170 right: this.renderState( 171 threadState.state, 172 threadState.cpu, 173 threadState.schedSqlId, 174 ), 175 }), 176 threadState.blockedFunction && 177 m(TreeNode, { 178 left: 'Blocked function', 179 right: threadState.blockedFunction, 180 }), 181 process && 182 m(TreeNode, { 183 left: 'Process', 184 right: getProcessName(process), 185 }), 186 thread && m(TreeNode, {left: 'Thread', right: getThreadName(thread)}), 187 threadState.priority !== undefined && 188 m(TreeNode, { 189 left: 'Priority', 190 right: threadState.priority, 191 }), 192 m(TreeNode, { 193 left: 'SQL ID', 194 right: m(SqlRef, {table: 'thread_state', id: threadState.id}), 195 }), 196 ); 197 } 198 199 private renderState( 200 state: string, 201 cpu: number | undefined, 202 id: SchedSqlId | undefined, 203 ): m.Children { 204 if (!state) { 205 return null; 206 } 207 if (id === undefined || cpu === undefined) { 208 return state; 209 } 210 return m( 211 Anchor, 212 { 213 title: 'Go to CPU slice', 214 icon: 'call_made', 215 onclick: () => goToSchedSlice(id), 216 }, 217 `${state} on CPU ${cpu}`, 218 ); 219 } 220 221 private renderRelatedThreadStates(): m.Children { 222 if (this.threadState === undefined || this.relatedStates === undefined) { 223 return 'Loading'; 224 } 225 const startTs = this.threadState.ts; 226 const renderRef = (state: ThreadState, name?: string) => 227 m(ThreadStateRef, { 228 id: state.id, 229 name, 230 }); 231 232 const nameForNextOrPrev = (threadState: ThreadState) => 233 `${threadState.state} for ${formatDuration(this.trace, threadState.dur)}`; 234 235 const renderWaker = (related: RelatedThreadStates) => { 236 // Could be absent if: 237 // * this thread state wasn't woken up (e.g. it is a running slice). 238 // * the wakeup is from an interrupt during the idle process (which 239 // isn't populated in thread_state). 240 // * at the start of the trace, before all per-cpu scheduling is known. 241 const hasWakerId = related.waker !== undefined; 242 // Interrupt context for the wakeups is absent from older traces. 243 const hasInterruptCtx = related.wakerInterruptCtx !== undefined; 244 245 if (!hasWakerId && !hasInterruptCtx) { 246 return null; 247 } 248 if (related.wakerInterruptCtx) { 249 return m(TreeNode, { 250 left: 'Woken by', 251 right: `Interrupt`, 252 }); 253 } 254 return ( 255 related.waker && 256 m(TreeNode, { 257 left: hasInterruptCtx ? 'Woken by' : 'Woken by (maybe interrupt)', 258 right: renderRef( 259 related.waker, 260 getFullThreadName(related.waker.thread), 261 ), 262 }) 263 ); 264 }; 265 266 const renderWakees = (related: RelatedThreadStates) => { 267 if (related.wakee === undefined || related.wakee.length == 0) { 268 return null; 269 } 270 const hasInterruptCtx = related.wakee[0].wakerInterruptCtx !== undefined; 271 return m( 272 TreeNode, 273 { 274 left: hasInterruptCtx 275 ? 'Woken threads' 276 : 'Woken threads (maybe interrupt)', 277 }, 278 related.wakee.map((state) => 279 m(TreeNode, { 280 left: m(Timestamp, { 281 ts: state.ts, 282 display: `+${formatDuration(this.trace, state.ts - startTs)}`, 283 }), 284 right: renderRef(state, getFullThreadName(state.thread)), 285 }), 286 ), 287 ); 288 }; 289 290 return [ 291 m( 292 Tree, 293 this.relatedStates.prev && 294 m(TreeNode, { 295 left: 'Previous state', 296 right: renderRef( 297 this.relatedStates.prev, 298 nameForNextOrPrev(this.relatedStates.prev), 299 ), 300 }), 301 this.relatedStates.next && 302 m(TreeNode, { 303 left: 'Next state', 304 right: renderRef( 305 this.relatedStates.next, 306 nameForNextOrPrev(this.relatedStates.next), 307 ), 308 }), 309 renderWaker(this.relatedStates), 310 renderWakees(this.relatedStates), 311 ), 312 this.trace.commands.hasCommand(CRITICAL_PATH_LITE_CMD) && 313 m(Button, { 314 label: 'Critical path lite', 315 intent: Intent.Primary, 316 onclick: () => { 317 this.trace.commands.runCommand( 318 CRITICAL_PATH_LITE_CMD, 319 this.threadState?.thread?.utid, 320 ); 321 }, 322 }), 323 this.trace.commands.hasCommand(CRITICAL_PATH_CMD) && 324 m(Button, { 325 label: 'Critical path', 326 intent: Intent.Primary, 327 onclick: () => { 328 this.trace.commands.runCommand( 329 CRITICAL_PATH_CMD, 330 this.threadState?.thread?.utid, 331 ); 332 }, 333 }), 334 ]; 335 } 336 337 isLoading() { 338 return this.threadState === undefined || this.relatedStates === undefined; 339 } 340} 341