| // 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 {timeToCode, toNs} from '../common/time'; |
| import {globals, SliceDetails, ThreadDesc} from './globals'; |
| import {scrollToTrackAndTs} from './scroll_helper'; |
| import {SlicePanel} from './slice_panel'; |
| |
| export class SliceDetailsPanel extends SlicePanel { |
| view() { |
| const sliceInfo = globals.sliceDetails; |
| if (sliceInfo.utid === undefined) return; |
| const threadInfo = globals.threads.get(sliceInfo.utid); |
| |
| return m( |
| '.details-panel', |
| m( |
| '.details-panel-heading', |
| m('h2.split', `Slice Details`), |
| this.hasSchedLatencyInfo(sliceInfo) && |
| m('h2.split', 'Scheduling Latency'), |
| ), |
| this.renderDetails(sliceInfo, threadInfo)); |
| } |
| |
| private renderSchedLatencyInfo(sliceInfo: SliceDetails): m.Children { |
| if (!this.hasSchedLatencyInfo(sliceInfo)) { |
| return null; |
| } |
| return m( |
| '.half-width-panel.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; |
| } |
| const timestamp = timeToCode( |
| sliceInfo.wakeupTs! - globals.state.traceTime.startSec, |
| ); |
| return m( |
| '.slice-details-wakeup-text', |
| m('', `Wakeup @ ${timestamp} 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 = timeToCode( |
| sliceInfo.ts - (sliceInfo.wakeupTs - globals.state.traceTime.startSec), |
| ); |
| return m( |
| '.slice-details-latency-text', |
| m('', `Scheduling latency: ${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 renderDetails(sliceInfo: SliceDetails, threadInfo?: ThreadDesc): |
| m.Children { |
| if (!threadInfo || sliceInfo.ts === undefined || |
| sliceInfo.dur === undefined) { |
| return null; |
| } else { |
| const tableRows = [ |
| m('tr', |
| m('th', `Process`), |
| m('td', `${threadInfo.procName} [${threadInfo.pid}]`)), |
| m('tr', |
| m('th', `Thread`), |
| m('td', |
| `${threadInfo.threadName} [${threadInfo.tid}]`, |
| m('i.material-icons.grey', |
| {onclick: () => this.goToThread(), title: 'Go to thread'}, |
| 'call_made'))), |
| m('tr', m('th', `Cmdline`), m('td', threadInfo.cmdline)), |
| m('tr', m('th', `Start time`), m('td', `${timeToCode(sliceInfo.ts)}`)), |
| m('tr', |
| m('th', `Duration`), |
| m('td', this.computeDuration(sliceInfo.ts, sliceInfo.dur))), |
| (sliceInfo.threadDur === undefined || |
| sliceInfo.threadTs === undefined) ? |
| '' : |
| m('tr', |
| m('th', 'Thread duration'), |
| m('td', |
| this.computeDuration(sliceInfo.threadTs, sliceInfo.threadDur))), |
| m('tr', m('th', `Prio`), m('td', `${sliceInfo.priority}`)), |
| m('tr', |
| m('th', `End State`), |
| m('td', translateState(sliceInfo.endState))), |
| m('tr', |
| m('th', `Slice ID`), |
| m('td', |
| (sliceInfo.id !== undefined) ? sliceInfo.id.toString() : |
| 'Unknown')), |
| ]; |
| |
| for (const [key, value] of this.getProcessThreadDetails(sliceInfo)) { |
| if (value !== undefined) { |
| tableRows.push(m('tr', m('th', key), m('td', value))); |
| } |
| } |
| |
| return m( |
| '.details-table-multicolumn', |
| m('table.half-width-panel', tableRows), |
| this.renderSchedLatencyInfo(sliceInfo), |
| ); |
| } |
| } |
| |
| 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, toNs(sliceInfo.ts + globals.state.traceTime.startSec), true); |
| } |
| } |
| |
| renderCanvas() {} |
| } |