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