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 } 73 74 render() { 75 if (this.details === undefined) { 76 return m(DetailsShell, {title: 'Sched', description: 'Loading...'}); 77 } 78 const threadInfo = this.threads.get(this.details.sched.thread.utid); 79 80 return m( 81 DetailsShell, 82 { 83 title: 'CPU Sched Slice', 84 description: this.renderTitle(this.details), 85 }, 86 m( 87 GridLayout, 88 this.renderDetails(this.details, threadInfo), 89 this.renderSchedLatencyInfo(this.details), 90 ), 91 ); 92 } 93 94 private renderTitle(data: Data) { 95 const threadInfo = this.threads.get(data.sched.thread.utid); 96 if (!threadInfo) { 97 return null; 98 } 99 return `${threadInfo.procName} [${threadInfo.pid}]`; 100 } 101 102 private renderSchedLatencyInfo(data: Data): m.Children { 103 if ( 104 data.wakeup?.wakeupTs === undefined || 105 data.wakeup?.wakerUtid === undefined 106 ) { 107 return null; 108 } 109 return m( 110 Section, 111 {title: 'Scheduling Latency'}, 112 m( 113 '.slice-details-latency-panel', 114 m('img.slice-details-image', { 115 src: assetSrc('assets/scheduling_latency.png'), 116 }), 117 this.renderWakeupText(data), 118 this.renderDisplayLatencyText(data), 119 ), 120 ); 121 } 122 123 private renderWakeupText(data: Data): m.Children { 124 if ( 125 data.wakeup?.wakerUtid === undefined || 126 data.wakeup?.wakeupTs === undefined || 127 data.wakeup?.wakerCpu === undefined 128 ) { 129 return null; 130 } 131 const threadInfo = this.threads.get(data.wakeup.wakerUtid); 132 if (!threadInfo) { 133 return null; 134 } 135 return m( 136 '.slice-details-wakeup-text', 137 m( 138 '', 139 `Wakeup @ `, 140 m(Timestamp, {ts: data.wakeup?.wakeupTs}), 141 ` on CPU ${data.wakeup.wakerCpu} by`, 142 ), 143 m('', `P: ${threadInfo.procName} [${threadInfo.pid}]`), 144 m('', `T: ${threadInfo.threadName} [${threadInfo.tid}]`), 145 ); 146 } 147 148 private renderDisplayLatencyText(data: Data): m.Children { 149 if (data.wakeup?.wakeupTs === undefined) { 150 return null; 151 } 152 153 const latency = data.sched.ts - data.wakeup?.wakeupTs; 154 return m( 155 '.slice-details-latency-text', 156 m('', `Scheduling latency: `, m(DurationWidget, {dur: latency})), 157 m( 158 '.text-detail', 159 `This is the interval from when the task became eligible to run 160 (e.g. because of notifying a wait queue it was suspended on) to 161 when it started running.`, 162 ), 163 ); 164 } 165 166 private renderPriorityText(priority?: number) { 167 if (priority === undefined) { 168 return undefined; 169 } 170 return priority < MIN_NORMAL_SCHED_PRIORITY 171 ? `${priority} (real-time)` 172 : `${priority}`; 173 } 174 175 protected getProcessThreadDetails(data: Data) { 176 const process = data.sched.thread.process; 177 return new Map<string, string | undefined>([ 178 ['Thread', getDisplayName(data.sched.thread.name, data.sched.thread.tid)], 179 ['Process', getDisplayName(process?.name, process?.pid)], 180 ['User ID', exists(process?.uid) ? String(process?.uid) : undefined], 181 ['Package name', process?.packageName], 182 [ 183 'Version code', 184 process?.versionCode !== undefined 185 ? String(process?.versionCode) 186 : undefined, 187 ], 188 ]); 189 } 190 191 private renderDetails(data: Data, threadInfo?: ThreadDesc): m.Children { 192 if (!threadInfo) { 193 return null; 194 } 195 196 const extras: m.Children = []; 197 198 for (const [key, value] of this.getProcessThreadDetails(data)) { 199 if (value !== undefined) { 200 extras.push(m(TreeNode, {left: key, right: value})); 201 } 202 } 203 204 const treeNodes = [ 205 m(TreeNode, { 206 left: 'Process', 207 right: `${threadInfo.procName} [${threadInfo.pid}]`, 208 }), 209 m(TreeNode, { 210 left: 'Thread', 211 right: m( 212 Anchor, 213 { 214 icon: 'call_made', 215 onclick: () => { 216 this.goToThread(data); 217 }, 218 }, 219 `${threadInfo.threadName} [${threadInfo.tid}]`, 220 ), 221 }), 222 m(TreeNode, { 223 left: 'Cmdline', 224 right: threadInfo.cmdline, 225 }), 226 m(TreeNode, { 227 left: 'Start time', 228 right: m(Timestamp, {ts: data.sched.ts}), 229 }), 230 m(TreeNode, { 231 left: 'Duration', 232 right: m(DurationWidget, {dur: data.sched.dur}), 233 }), 234 m(TreeNode, { 235 left: 'Priority', 236 right: this.renderPriorityText(data.sched.priority), 237 }), 238 m(TreeNode, { 239 left: 'End State', 240 right: translateState(data.sched.endState), 241 }), 242 m(TreeNode, { 243 left: 'SQL ID', 244 right: m(SqlRef, {table: 'sched', id: data.sched.id}), 245 }), 246 ...extras, 247 ]; 248 249 return m(Section, {title: 'Details'}, m(Tree, treeNodes)); 250 } 251 252 goToThread(data: Data) { 253 if (data.sched.threadStateId) { 254 this.trace.selection.selectSqlEvent( 255 'thread_state', 256 data.sched.threadStateId, 257 {scrollToSelection: true}, 258 ); 259 } 260 } 261 262 renderCanvas() {} 263} 264