blob: 48a4e4351c30adada77f257502f7beac221b6cb0 [file] [log] [blame]
// 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 * as m from 'mithril';
import {Actions} from '../common/actions';
import {drawDoubleHeadedArrow} from '../common/canvas_utils';
import {translateState} from '../common/thread_state';
import {timeToCode, toNs} from '../common/time';
import {globals, SliceDetails, ThreadDesc} from './globals';
import {PanelSize} from './panel';
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`),
(sliceInfo.wakeupTs && sliceInfo.wakerUtid) ?
m('h2.split', 'Scheduling Latency') :
''),
this.getDetails(sliceInfo, threadInfo));
}
getDetails(sliceInfo: SliceDetails, threadInfo: ThreadDesc|undefined) {
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 ? 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',
m('table.half-width', tableRows),
);
}
}
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(ctx: CanvasRenderingContext2D, size: PanelSize) {
const details = globals.sliceDetails;
// Show expanded details on the scheduling of the currently selected slice.
if (details.wakeupTs && details.wakerUtid !== undefined) {
const threadInfo = globals.threads.get(details.wakerUtid);
// Draw diamond and vertical line.
const startDraw = {x: size.width / 2 + 20, y: 52};
ctx.beginPath();
ctx.moveTo(startDraw.x, startDraw.y + 28);
ctx.fillStyle = 'black';
ctx.lineTo(startDraw.x + 6, startDraw.y + 20);
ctx.lineTo(startDraw.x, startDraw.y + 12);
ctx.lineTo(startDraw.x - 6, startDraw.y + 20);
ctx.fill();
ctx.closePath();
ctx.fillRect(startDraw.x - 1, startDraw.y, 2, 100);
// Wakeup explanation text.
ctx.font = '13px Roboto Condensed';
ctx.fillStyle = '#3c4b5d';
if (threadInfo) {
const displayText = `Wakeup @ ${
timeToCode(
details.wakeupTs - globals.state.traceTime.startSec)} on CPU ${
details.wakerCpu} by`;
const processText = `P: ${threadInfo.procName} [${threadInfo.pid}]`;
const threadText = `T: ${threadInfo.threadName} [${threadInfo.tid}]`;
ctx.fillText(displayText, startDraw.x + 20, startDraw.y + 20);
ctx.fillText(processText, startDraw.x + 20, startDraw.y + 37);
ctx.fillText(threadText, startDraw.x + 20, startDraw.y + 55);
}
// Draw latency arrow and explanation text.
drawDoubleHeadedArrow(ctx, startDraw.x, startDraw.y + 80, 60, true);
if (details.ts) {
const displayLatency = `Scheduling latency: ${
timeToCode(
details.ts -
(details.wakeupTs - globals.state.traceTime.startSec))}`;
ctx.fillText(displayLatency, startDraw.x + 70, startDraw.y + 86);
const explain1 =
'This is the interval from when the task became eligible to run';
const explain2 =
'(e.g. because of notifying a wait queue it was suspended on) to';
const explain3 = 'when it started running.';
ctx.font = '10px Roboto Condensed';
ctx.fillText(explain1, startDraw.x + 70, startDraw.y + 86 + 16);
ctx.fillText(explain2, startDraw.x + 70, startDraw.y + 86 + 16 + 12);
ctx.fillText(explain3, startDraw.x + 70, startDraw.y + 86 + 16 + 24);
}
}
}
}