| // Copyright (C) 2019 The Android Open Source Project |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use size file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| import m from 'mithril'; |
| import {Anchor} from '../../widgets/anchor'; |
| import {DetailsShell} from '../../widgets/details_shell'; |
| import {GridLayout} from '../../widgets/grid_layout'; |
| import {Section} from '../../widgets/section'; |
| import {SqlRef} from '../../widgets/sql_ref'; |
| import {Tree, TreeNode} from '../../widgets/tree'; |
| import {DurationWidget} from '../../frontend/widgets/duration'; |
| import {Timestamp} from '../../frontend/widgets/timestamp'; |
| import {asSchedSqlId} from '../../trace_processor/sql_utils/core_types'; |
| import { |
| getSched, |
| getSchedWakeupInfo, |
| Sched, |
| SchedWakeupInfo, |
| } from '../../trace_processor/sql_utils/sched'; |
| import {exists} from '../../base/utils'; |
| import {translateState} from '../../trace_processor/sql_utils/thread_state'; |
| import {Trace} from '../../public/trace'; |
| import {TrackEventDetailsPanel} from '../../public/details_panel'; |
| import {TrackEventSelection} from '../../public/selection'; |
| import {ThreadDesc, ThreadMap} from '../dev.perfetto.Thread/threads'; |
| import {assetSrc} from '../../base/assets'; |
| |
| const MIN_NORMAL_SCHED_PRIORITY = 100; |
| |
| function getDisplayName( |
| name: string | undefined, |
| id: number | undefined, |
| ): string | undefined { |
| if (name === undefined) { |
| return id === undefined ? undefined : `${id}`; |
| } else { |
| return id === undefined ? name : `${name} ${id}`; |
| } |
| } |
| |
| interface Data { |
| sched: Sched; |
| wakeup?: SchedWakeupInfo; |
| } |
| |
| export class SchedSliceDetailsPanel implements TrackEventDetailsPanel { |
| private details?: Data; |
| |
| constructor( |
| private readonly trace: Trace, |
| private readonly threads: ThreadMap, |
| ) {} |
| |
| async load({eventId}: TrackEventSelection) { |
| const sched = await getSched(this.trace.engine, asSchedSqlId(eventId)); |
| if (sched === undefined) { |
| return; |
| } |
| const wakeup = await getSchedWakeupInfo(this.trace.engine, sched); |
| this.details = {sched, wakeup}; |
| this.trace.scheduleFullRedraw(); |
| } |
| |
| render() { |
| if (this.details === undefined) { |
| return m(DetailsShell, {title: 'Sched', description: 'Loading...'}); |
| } |
| const threadInfo = this.threads.get(this.details.sched.thread.utid); |
| |
| return m( |
| DetailsShell, |
| { |
| title: 'CPU Sched Slice', |
| description: this.renderTitle(this.details), |
| }, |
| m( |
| GridLayout, |
| this.renderDetails(this.details, threadInfo), |
| this.renderSchedLatencyInfo(this.details), |
| ), |
| ); |
| } |
| |
| private renderTitle(data: Data) { |
| const threadInfo = this.threads.get(data.sched.thread.utid); |
| if (!threadInfo) { |
| return null; |
| } |
| return `${threadInfo.procName} [${threadInfo.pid}]`; |
| } |
| |
| private renderSchedLatencyInfo(data: Data): m.Children { |
| if ( |
| data.wakeup?.wakeupTs === undefined || |
| data.wakeup?.wakerUtid === undefined |
| ) { |
| return null; |
| } |
| return m( |
| Section, |
| {title: 'Scheduling Latency'}, |
| m( |
| '.slice-details-latency-panel', |
| m('img.slice-details-image', { |
| src: assetSrc('assets/scheduling_latency.png'), |
| }), |
| this.renderWakeupText(data), |
| this.renderDisplayLatencyText(data), |
| ), |
| ); |
| } |
| |
| private renderWakeupText(data: Data): m.Children { |
| if ( |
| data.wakeup?.wakerUtid === undefined || |
| data.wakeup?.wakeupTs === undefined || |
| data.wakeup?.wakerCpu === undefined |
| ) { |
| return null; |
| } |
| const threadInfo = this.threads.get(data.wakeup.wakerUtid); |
| if (!threadInfo) { |
| return null; |
| } |
| return m( |
| '.slice-details-wakeup-text', |
| m( |
| '', |
| `Wakeup @ `, |
| m(Timestamp, {ts: data.wakeup?.wakeupTs}), |
| ` on CPU ${data.wakeup.wakerCpu} by`, |
| ), |
| m('', `P: ${threadInfo.procName} [${threadInfo.pid}]`), |
| m('', `T: ${threadInfo.threadName} [${threadInfo.tid}]`), |
| ); |
| } |
| |
| private renderDisplayLatencyText(data: Data): m.Children { |
| if (data.wakeup?.wakeupTs === undefined) { |
| return null; |
| } |
| |
| const latency = data.sched.ts - data.wakeup?.wakeupTs; |
| return m( |
| '.slice-details-latency-text', |
| m('', `Scheduling latency: `, m(DurationWidget, {dur: latency})), |
| m( |
| '.text-detail', |
| `This is the interval from when the task became eligible to run |
| (e.g. because of notifying a wait queue it was suspended on) to |
| when it started running.`, |
| ), |
| ); |
| } |
| |
| private renderPriorityText(priority?: number) { |
| if (priority === undefined) { |
| return undefined; |
| } |
| return priority < MIN_NORMAL_SCHED_PRIORITY |
| ? `${priority} (real-time)` |
| : `${priority}`; |
| } |
| |
| protected getProcessThreadDetails(data: Data) { |
| const process = data.sched.thread.process; |
| return new Map<string, string | undefined>([ |
| ['Thread', getDisplayName(data.sched.thread.name, data.sched.thread.tid)], |
| ['Process', getDisplayName(process?.name, process?.pid)], |
| ['User ID', exists(process?.uid) ? String(process?.uid) : undefined], |
| ['Package name', process?.packageName], |
| [ |
| 'Version code', |
| process?.versionCode !== undefined |
| ? String(process?.versionCode) |
| : undefined, |
| ], |
| ]); |
| } |
| |
| private renderDetails(data: Data, threadInfo?: ThreadDesc): m.Children { |
| if (!threadInfo) { |
| return null; |
| } |
| |
| const extras: m.Children = []; |
| |
| for (const [key, value] of this.getProcessThreadDetails(data)) { |
| if (value !== undefined) { |
| extras.push(m(TreeNode, {left: key, right: value})); |
| } |
| } |
| |
| const treeNodes = [ |
| m(TreeNode, { |
| left: 'Process', |
| right: `${threadInfo.procName} [${threadInfo.pid}]`, |
| }), |
| m(TreeNode, { |
| left: 'Thread', |
| right: m( |
| Anchor, |
| { |
| icon: 'call_made', |
| onclick: () => { |
| this.goToThread(data); |
| }, |
| }, |
| `${threadInfo.threadName} [${threadInfo.tid}]`, |
| ), |
| }), |
| m(TreeNode, { |
| left: 'Cmdline', |
| right: threadInfo.cmdline, |
| }), |
| m(TreeNode, { |
| left: 'Start time', |
| right: m(Timestamp, {ts: data.sched.ts}), |
| }), |
| m(TreeNode, { |
| left: 'Duration', |
| right: m(DurationWidget, {dur: data.sched.dur}), |
| }), |
| m(TreeNode, { |
| left: 'Priority', |
| right: this.renderPriorityText(data.sched.priority), |
| }), |
| m(TreeNode, { |
| left: 'End State', |
| right: translateState(data.sched.endState), |
| }), |
| m(TreeNode, { |
| left: 'SQL ID', |
| right: m(SqlRef, {table: 'sched', id: data.sched.id}), |
| }), |
| ...extras, |
| ]; |
| |
| return m(Section, {title: 'Details'}, m(Tree, treeNodes)); |
| } |
| |
| goToThread(data: Data) { |
| if (data.sched.threadStateId) { |
| this.trace.selection.selectSqlEvent( |
| 'thread_state', |
| data.sched.threadStateId, |
| {scrollToSelection: true}, |
| ); |
| } |
| } |
| |
| renderCanvas() {} |
| } |