1// Copyright (C) 2019 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'; 16 17import {Actions} from '../common/actions'; 18import {translateState} from '../common/thread_state'; 19import {Anchor} from '../widgets/anchor'; 20import {DetailsShell} from '../widgets/details_shell'; 21import {GridLayout} from '../widgets/grid_layout'; 22import {Section} from '../widgets/section'; 23import {SqlRef} from '../widgets/sql_ref'; 24import {Tree, TreeNode} from '../widgets/tree'; 25 26import {globals, SliceDetails, ThreadDesc} from './globals'; 27import {scrollToTrackAndTs} from './scroll_helper'; 28import {SlicePanel} from './slice_panel'; 29import {DurationWidget} from './widgets/duration'; 30import {Timestamp} from './widgets/timestamp'; 31import {THREAD_STATE_TRACK_KIND} from '../core/track_kinds'; 32 33const MIN_NORMAL_SCHED_PRIORITY = 100; 34 35export class SliceDetailsPanel extends SlicePanel { 36 view() { 37 const sliceInfo = globals.sliceDetails; 38 if (sliceInfo.utid === undefined) return; 39 const threadInfo = globals.threads.get(sliceInfo.utid); 40 41 return m( 42 DetailsShell, 43 { 44 title: 'CPU Sched Slice', 45 description: this.renderDescription(sliceInfo), 46 }, 47 m( 48 GridLayout, 49 this.renderDetails(sliceInfo, threadInfo), 50 this.renderSchedLatencyInfo(sliceInfo), 51 ), 52 ); 53 } 54 55 private renderDescription(sliceInfo: SliceDetails) { 56 const threadInfo = globals.threads.get(sliceInfo.wakerUtid!); 57 if (!threadInfo) { 58 return null; 59 } 60 return `${threadInfo.procName} [${threadInfo.pid}]`; 61 } 62 63 private renderSchedLatencyInfo(sliceInfo: SliceDetails): m.Children { 64 if (!this.hasSchedLatencyInfo(sliceInfo)) { 65 return null; 66 } 67 return m( 68 Section, 69 {title: 'Scheduling Latency'}, 70 m( 71 '.slice-details-latency-panel', 72 m('img.slice-details-image', { 73 src: `${globals.root}assets/scheduling_latency.png`, 74 }), 75 this.renderWakeupText(sliceInfo), 76 this.renderDisplayLatencyText(sliceInfo), 77 ), 78 ); 79 } 80 81 private renderWakeupText(sliceInfo: SliceDetails): m.Children { 82 if (sliceInfo.wakerUtid === undefined) { 83 return null; 84 } 85 const threadInfo = globals.threads.get(sliceInfo.wakerUtid!); 86 if (!threadInfo) { 87 return null; 88 } 89 return m( 90 '.slice-details-wakeup-text', 91 m( 92 '', 93 `Wakeup @ `, 94 m(Timestamp, {ts: sliceInfo.wakeupTs!}), 95 ` on CPU ${sliceInfo.wakerCpu} by`, 96 ), 97 m('', `P: ${threadInfo.procName} [${threadInfo.pid}]`), 98 m('', `T: ${threadInfo.threadName} [${threadInfo.tid}]`), 99 ); 100 } 101 102 private renderDisplayLatencyText(sliceInfo: SliceDetails): m.Children { 103 if (sliceInfo.ts === undefined || sliceInfo.wakeupTs === undefined) { 104 return null; 105 } 106 107 const latency = sliceInfo.ts - sliceInfo.wakeupTs; 108 return m( 109 '.slice-details-latency-text', 110 m('', `Scheduling latency: `, m(DurationWidget, {dur: latency})), 111 m( 112 '.text-detail', 113 `This is the interval from when the task became eligible to run 114 (e.g. because of notifying a wait queue it was suspended on) to 115 when it started running.`, 116 ), 117 ); 118 } 119 120 private hasSchedLatencyInfo({wakeupTs, wakerUtid}: SliceDetails): boolean { 121 return wakeupTs !== undefined && wakerUtid !== undefined; 122 } 123 124 private renderThreadDuration(sliceInfo: SliceDetails) { 125 if (sliceInfo.threadDur !== undefined && sliceInfo.threadTs !== undefined) { 126 return m(TreeNode, { 127 icon: 'timer', 128 left: 'Thread Duration', 129 right: m(DurationWidget, {dur: sliceInfo.threadDur}), 130 }); 131 } else { 132 return null; 133 } 134 } 135 136 private renderPriorityText(priority?: number) { 137 if (priority === undefined) { 138 return undefined; 139 } 140 return priority < MIN_NORMAL_SCHED_PRIORITY 141 ? `${priority} (real-time)` 142 : `${priority}`; 143 } 144 145 private renderDetails( 146 sliceInfo: SliceDetails, 147 threadInfo?: ThreadDesc, 148 ): m.Children { 149 if ( 150 !threadInfo || 151 sliceInfo.ts === undefined || 152 sliceInfo.dur === undefined 153 ) { 154 return null; 155 } else { 156 const extras: m.Children = []; 157 158 for (const [key, value] of this.getProcessThreadDetails(sliceInfo)) { 159 if (value !== undefined) { 160 extras.push(m(TreeNode, {left: key, right: value})); 161 } 162 } 163 164 const treeNodes = [ 165 m(TreeNode, { 166 left: 'Process', 167 right: `${threadInfo.procName} [${threadInfo.pid}]`, 168 }), 169 m(TreeNode, { 170 left: 'Thread', 171 right: m( 172 Anchor, 173 { 174 icon: 'call_made', 175 onclick: () => { 176 this.goToThread(); 177 }, 178 }, 179 `${threadInfo.threadName} [${threadInfo.tid}]`, 180 ), 181 }), 182 m(TreeNode, { 183 left: 'Cmdline', 184 right: threadInfo.cmdline, 185 }), 186 m(TreeNode, { 187 left: 'Start time', 188 right: m(Timestamp, {ts: sliceInfo.ts}), 189 }), 190 m(TreeNode, { 191 left: 'Duration', 192 right: m(DurationWidget, {dur: sliceInfo.dur}), 193 }), 194 this.renderThreadDuration(sliceInfo), 195 m(TreeNode, { 196 left: 'Priority', 197 right: this.renderPriorityText(sliceInfo.priority), 198 }), 199 m(TreeNode, { 200 left: 'End State', 201 right: translateState(sliceInfo.endState), 202 }), 203 m(TreeNode, { 204 left: 'SQL ID', 205 right: m(SqlRef, {table: 'sched', id: sliceInfo.id}), 206 }), 207 ...extras, 208 ]; 209 210 return m(Section, {title: 'Details'}, m(Tree, treeNodes)); 211 } 212 } 213 214 goToThread() { 215 const sliceInfo = globals.sliceDetails; 216 if (sliceInfo.utid === undefined) return; 217 const threadInfo = globals.threads.get(sliceInfo.utid); 218 219 if ( 220 sliceInfo.id === undefined || 221 sliceInfo.ts === undefined || 222 sliceInfo.dur === undefined || 223 sliceInfo.cpu === undefined || 224 threadInfo === undefined 225 ) { 226 return; 227 } 228 229 let trackKey: string | number | undefined; 230 for (const track of Object.values(globals.state.tracks)) { 231 const trackDesc = globals.trackManager.resolveTrackInfo(track.uri); 232 // TODO(stevegolton): Handle v2. 233 if ( 234 trackDesc && 235 trackDesc.kind === THREAD_STATE_TRACK_KIND && 236 trackDesc.utid === threadInfo.utid 237 ) { 238 trackKey = track.key; 239 } 240 } 241 242 // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions 243 if (trackKey && sliceInfo.threadStateId) { 244 globals.makeSelection( 245 Actions.selectThreadState({ 246 id: sliceInfo.threadStateId, 247 trackKey: trackKey.toString(), 248 }), 249 ); 250 251 scrollToTrackAndTs(trackKey, sliceInfo.ts, true); 252 } 253 } 254 255 renderCanvas() {} 256} 257