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'; 16import {Anchor} from '../../widgets/anchor'; 17import {DetailsShell} from '../../widgets/details_shell'; 18import {GridLayout} from '../../widgets/grid_layout'; 19import {Section} from '../../widgets/section'; 20import {SqlRef} from '../../widgets/sql_ref'; 21import {Tree, TreeNode} from '../../widgets/tree'; 22import {DurationWidget} from '../../components/widgets/duration'; 23import {Timestamp} from '../../components/widgets/timestamp'; 24import {asSchedSqlId} from '../../components/sql_utils/core_types'; 25import { 26 getSched, 27 getSchedWakeupInfo, 28 Sched, 29 SchedWakeupInfo, 30} from '../../components/sql_utils/sched'; 31import {exists} from '../../base/utils'; 32import {translateState} from '../../components/sql_utils/thread_state'; 33import {Trace} from '../../public/trace'; 34import {TrackEventDetailsPanel} from '../../public/details_panel'; 35import {TrackEventSelection} from '../../public/selection'; 36import {ThreadDesc, ThreadMap} from '../dev.perfetto.Thread/threads'; 37import {assetSrc} from '../../base/assets'; 38 39const MIN_NORMAL_SCHED_PRIORITY = 100; 40 41function getDisplayName( 42 name: string | undefined, 43 id: number | undefined, 44): string | undefined { 45 if (name === undefined) { 46 return id === undefined ? undefined : `${id}`; 47 } else { 48 return id === undefined ? name : `${name} ${id}`; 49 } 50} 51 52interface Data { 53 sched: Sched; 54 wakeup?: SchedWakeupInfo; 55} 56 57export class SchedSliceDetailsPanel implements TrackEventDetailsPanel { 58 private details?: Data; 59 60 constructor( 61 private readonly trace: Trace, 62 private readonly threads: ThreadMap, 63 ) {} 64 65 async load({eventId}: TrackEventSelection) { 66 const sched = await getSched(this.trace.engine, asSchedSqlId(eventId)); 67 if (sched === undefined) { 68 return; 69 } 70 const wakeup = await getSchedWakeupInfo(this.trace.engine, sched); 71 this.details = {sched, wakeup}; 72 this.trace.scheduleFullRedraw(); 73 } 74 75 render() { 76 if (this.details === undefined) { 77 return m(DetailsShell, {title: 'Sched', description: 'Loading...'}); 78 } 79 const threadInfo = this.threads.get(this.details.sched.thread.utid); 80 81 return m( 82 DetailsShell, 83 { 84 title: 'CPU Sched Slice', 85 description: this.renderTitle(this.details), 86 }, 87 m( 88 GridLayout, 89 this.renderDetails(this.details, threadInfo), 90 this.renderSchedLatencyInfo(this.details), 91 ), 92 ); 93 } 94 95 private renderTitle(data: Data) { 96 const threadInfo = this.threads.get(data.sched.thread.utid); 97 if (!threadInfo) { 98 return null; 99 } 100 return `${threadInfo.procName} [${threadInfo.pid}]`; 101 } 102 103 private renderSchedLatencyInfo(data: Data): m.Children { 104 if ( 105 data.wakeup?.wakeupTs === undefined || 106 data.wakeup?.wakerUtid === undefined 107 ) { 108 return null; 109 } 110 return m( 111 Section, 112 {title: 'Scheduling Latency'}, 113 m( 114 '.slice-details-latency-panel', 115 m('img.slice-details-image', { 116 src: assetSrc('assets/scheduling_latency.png'), 117 }), 118 this.renderWakeupText(data), 119 this.renderDisplayLatencyText(data), 120 ), 121 ); 122 } 123 124 private renderWakeupText(data: Data): m.Children { 125 if ( 126 data.wakeup?.wakerUtid === undefined || 127 data.wakeup?.wakeupTs === undefined || 128 data.wakeup?.wakerCpu === undefined 129 ) { 130 return null; 131 } 132 const threadInfo = this.threads.get(data.wakeup.wakerUtid); 133 if (!threadInfo) { 134 return null; 135 } 136 return m( 137 '.slice-details-wakeup-text', 138 m( 139 '', 140 `Wakeup @ `, 141 m(Timestamp, {ts: data.wakeup?.wakeupTs}), 142 ` on CPU ${data.wakeup.wakerCpu} by`, 143 ), 144 m('', `P: ${threadInfo.procName} [${threadInfo.pid}]`), 145 m('', `T: ${threadInfo.threadName} [${threadInfo.tid}]`), 146 ); 147 } 148 149 private renderDisplayLatencyText(data: Data): m.Children { 150 if (data.wakeup?.wakeupTs === undefined) { 151 return null; 152 } 153 154 const latency = data.sched.ts - data.wakeup?.wakeupTs; 155 return m( 156 '.slice-details-latency-text', 157 m('', `Scheduling latency: `, m(DurationWidget, {dur: latency})), 158 m( 159 '.text-detail', 160 `This is the interval from when the task became eligible to run 161 (e.g. because of notifying a wait queue it was suspended on) to 162 when it started running.`, 163 ), 164 ); 165 } 166 167 private renderPriorityText(priority?: number) { 168 if (priority === undefined) { 169 return undefined; 170 } 171 return priority < MIN_NORMAL_SCHED_PRIORITY 172 ? `${priority} (real-time)` 173 : `${priority}`; 174 } 175 176 protected getProcessThreadDetails(data: Data) { 177 const process = data.sched.thread.process; 178 return new Map<string, string | undefined>([ 179 ['Thread', getDisplayName(data.sched.thread.name, data.sched.thread.tid)], 180 ['Process', getDisplayName(process?.name, process?.pid)], 181 ['User ID', exists(process?.uid) ? String(process?.uid) : undefined], 182 ['Package name', process?.packageName], 183 [ 184 'Version code', 185 process?.versionCode !== undefined 186 ? String(process?.versionCode) 187 : undefined, 188 ], 189 ]); 190 } 191 192 private renderDetails(data: Data, threadInfo?: ThreadDesc): m.Children { 193 if (!threadInfo) { 194 return null; 195 } 196 197 const extras: m.Children = []; 198 199 for (const [key, value] of this.getProcessThreadDetails(data)) { 200 if (value !== undefined) { 201 extras.push(m(TreeNode, {left: key, right: value})); 202 } 203 } 204 205 const treeNodes = [ 206 m(TreeNode, { 207 left: 'Process', 208 right: `${threadInfo.procName} [${threadInfo.pid}]`, 209 }), 210 m(TreeNode, { 211 left: 'Thread', 212 right: m( 213 Anchor, 214 { 215 icon: 'call_made', 216 onclick: () => { 217 this.goToThread(data); 218 }, 219 }, 220 `${threadInfo.threadName} [${threadInfo.tid}]`, 221 ), 222 }), 223 m(TreeNode, { 224 left: 'Cmdline', 225 right: threadInfo.cmdline, 226 }), 227 m(TreeNode, { 228 left: 'Start time', 229 right: m(Timestamp, {ts: data.sched.ts}), 230 }), 231 m(TreeNode, { 232 left: 'Duration', 233 right: m(DurationWidget, {dur: data.sched.dur}), 234 }), 235 m(TreeNode, { 236 left: 'Priority', 237 right: this.renderPriorityText(data.sched.priority), 238 }), 239 m(TreeNode, { 240 left: 'End State', 241 right: translateState(data.sched.endState), 242 }), 243 m(TreeNode, { 244 left: 'SQL ID', 245 right: m(SqlRef, {table: 'sched', id: data.sched.id}), 246 }), 247 ...extras, 248 ]; 249 250 return m(Section, {title: 'Details'}, m(Tree, treeNodes)); 251 } 252 253 goToThread(data: Data) { 254 if (data.sched.threadStateId) { 255 this.trace.selection.selectSqlEvent( 256 'thread_state', 257 data.sched.threadStateId, 258 {scrollToSelection: true}, 259 ); 260 } 261 } 262 263 renderCanvas() {} 264} 265