| // 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 {Actions} from '../common/actions'; |
| import {translateState} from '../common/thread_state'; |
| import {THREAD_STATE_TRACK_KIND} from '../tracks/thread_state'; |
| 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 {globals, SliceDetails, ThreadDesc} from './globals'; |
| import {scrollToTrackAndTs} from './scroll_helper'; |
| import {SlicePanel} from './slice_panel'; |
| import {DurationWidget} from './widgets/duration'; |
| import {Timestamp} from './widgets/timestamp'; |
| |
| const MIN_NORMAL_SCHED_PRIORITY = 100; |
| |
| export class SliceDetailsPanel extends SlicePanel { |
| view() { |
| const sliceInfo = globals.sliceDetails; |
| if (sliceInfo.utid === undefined) return; |
| const threadInfo = globals.threads.get(sliceInfo.utid); |
| |
| return m( |
| DetailsShell, |
| { |
| title: 'CPU Sched Slice', |
| description: this.renderDescription(sliceInfo), |
| }, |
| m( |
| GridLayout, |
| this.renderDetails(sliceInfo, threadInfo), |
| this.renderSchedLatencyInfo(sliceInfo), |
| ), |
| ); |
| } |
| |
| private renderDescription(sliceInfo: SliceDetails) { |
| const threadInfo = globals.threads.get(sliceInfo.wakerUtid!); |
| if (!threadInfo) { |
| return null; |
| } |
| return `${threadInfo.procName} [${threadInfo.pid}]`; |
| } |
| |
| private renderSchedLatencyInfo(sliceInfo: SliceDetails): m.Children { |
| if (!this.hasSchedLatencyInfo(sliceInfo)) { |
| return null; |
| } |
| return m( |
| Section, |
| {title: 'Scheduling Latency'}, |
| m( |
| '.slice-details-latency-panel', |
| m('img.slice-details-image', { |
| src: `${globals.root}assets/scheduling_latency.png`, |
| }), |
| this.renderWakeupText(sliceInfo), |
| this.renderDisplayLatencyText(sliceInfo), |
| ), |
| ); |
| } |
| |
| private renderWakeupText(sliceInfo: SliceDetails): m.Children { |
| if (sliceInfo.wakerUtid === undefined) { |
| return null; |
| } |
| const threadInfo = globals.threads.get(sliceInfo.wakerUtid!); |
| if (!threadInfo) { |
| return null; |
| } |
| return m( |
| '.slice-details-wakeup-text', |
| m( |
| '', |
| `Wakeup @ `, |
| m(Timestamp, {ts: sliceInfo.wakeupTs!}), |
| ` on CPU ${sliceInfo.wakerCpu} by`, |
| ), |
| m('', `P: ${threadInfo.procName} [${threadInfo.pid}]`), |
| m('', `T: ${threadInfo.threadName} [${threadInfo.tid}]`), |
| ); |
| } |
| |
| private renderDisplayLatencyText(sliceInfo: SliceDetails): m.Children { |
| if (sliceInfo.ts === undefined || sliceInfo.wakeupTs === undefined) { |
| return null; |
| } |
| |
| const latency = sliceInfo.ts - sliceInfo.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 hasSchedLatencyInfo({wakeupTs, wakerUtid}: SliceDetails): boolean { |
| return wakeupTs !== undefined && wakerUtid !== undefined; |
| } |
| |
| private renderThreadDuration(sliceInfo: SliceDetails) { |
| if (sliceInfo.threadDur !== undefined && sliceInfo.threadTs !== undefined) { |
| return m(TreeNode, { |
| icon: 'timer', |
| left: 'Thread Duration', |
| right: m(DurationWidget, {dur: sliceInfo.threadDur}), |
| }); |
| } else { |
| return null; |
| } |
| } |
| |
| private renderPriorityText(priority?: number) { |
| if (priority === undefined) { |
| return undefined; |
| } |
| return priority < MIN_NORMAL_SCHED_PRIORITY |
| ? `${priority} (real-time)` |
| : `${priority}`; |
| } |
| |
| private renderDetails( |
| sliceInfo: SliceDetails, |
| threadInfo?: ThreadDesc, |
| ): m.Children { |
| if ( |
| !threadInfo || |
| sliceInfo.ts === undefined || |
| sliceInfo.dur === undefined |
| ) { |
| return null; |
| } else { |
| const extras: m.Children = []; |
| |
| for (const [key, value] of this.getProcessThreadDetails(sliceInfo)) { |
| 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(); |
| }, |
| }, |
| `${threadInfo.threadName} [${threadInfo.tid}]`, |
| ), |
| }), |
| m(TreeNode, { |
| left: 'Cmdline', |
| right: threadInfo.cmdline, |
| }), |
| m(TreeNode, { |
| left: 'Start time', |
| right: m(Timestamp, {ts: sliceInfo.ts}), |
| }), |
| m(TreeNode, { |
| left: 'Duration', |
| right: m(DurationWidget, {dur: sliceInfo.dur}), |
| }), |
| this.renderThreadDuration(sliceInfo), |
| m(TreeNode, { |
| left: 'Priority', |
| right: this.renderPriorityText(sliceInfo.priority), |
| }), |
| m(TreeNode, { |
| left: 'End State', |
| right: translateState(sliceInfo.endState), |
| }), |
| m(TreeNode, { |
| left: 'SQL ID', |
| right: m(SqlRef, {table: 'sched', id: sliceInfo.id}), |
| }), |
| ...extras, |
| ]; |
| |
| return m(Section, {title: 'Details'}, m(Tree, treeNodes)); |
| } |
| } |
| |
| goToThread() { |
| const sliceInfo = globals.sliceDetails; |
| if (sliceInfo.utid === undefined) return; |
| const threadInfo = globals.threads.get(sliceInfo.utid); |
| |
| if ( |
| sliceInfo.id === undefined || |
| sliceInfo.ts === undefined || |
| sliceInfo.dur === undefined || |
| sliceInfo.cpu === undefined || |
| threadInfo === undefined |
| ) { |
| return; |
| } |
| |
| let trackKey: string | number | undefined; |
| for (const track of Object.values(globals.state.tracks)) { |
| const trackDesc = globals.trackManager.resolveTrackInfo(track.uri); |
| // TODO(stevegolton): Handle v2. |
| if ( |
| trackDesc && |
| trackDesc.kind === THREAD_STATE_TRACK_KIND && |
| trackDesc.utid === threadInfo.utid |
| ) { |
| trackKey = track.key; |
| } |
| } |
| |
| // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions |
| if (trackKey && sliceInfo.threadStateId) { |
| globals.makeSelection( |
| Actions.selectThreadState({ |
| id: sliceInfo.threadStateId, |
| trackKey: trackKey.toString(), |
| }), |
| ); |
| |
| scrollToTrackAndTs(trackKey, sliceInfo.ts, true); |
| } |
| } |
| |
| renderCanvas() {} |
| } |