blob: fb5d3a25dc22ef956ff7c17a583e2dfe75326dc3 [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 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() {}
}