| // 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 {Anchor} from '../widgets/anchor'; |
| import {DetailsShell} from '../widgets/details_shell'; |
| import {DurationWidget} from '../widgets/duration'; |
| 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 {Timestamp} from './widgets/timestamp'; |
| |
| 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: this.computeDuration(sliceInfo.threadTs, sliceInfo.threadDur), |
| }); |
| } else { |
| return null; |
| } |
| } |
| |
| 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: this.computeDuration(sliceInfo.ts, sliceInfo.dur), |
| }), |
| this.renderThreadDuration(sliceInfo), |
| m(TreeNode, { |
| left: 'Prio', |
| right: 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 trackId: string|number|undefined; |
| for (const track of Object.values(globals.state.tracks)) { |
| if (track.kind === 'ThreadStateTrack' && |
| (track.config as {utid: number}).utid === threadInfo.utid) { |
| trackId = track.id; |
| } |
| } |
| |
| if (trackId && sliceInfo.threadStateId) { |
| globals.makeSelection(Actions.selectThreadState({ |
| id: sliceInfo.threadStateId, |
| trackId: trackId.toString(), |
| })); |
| |
| scrollToTrackAndTs(trackId, sliceInfo.ts, true); |
| } |
| } |
| |
| renderCanvas() {} |
| } |