perfetto-ui: Go to thread state
There is a button in the details panel of the CPU slice that
will scroll you to the thread and select the running state.
Change-Id: Ia56b0b065f79962977fa6d7c09c8fea90f27ca31
diff --git a/ui/src/frontend/frontend_local_state.ts b/ui/src/frontend/frontend_local_state.ts
index 25f7991..85d1eff 100644
--- a/ui/src/frontend/frontend_local_state.ts
+++ b/ui/src/frontend/frontend_local_state.ts
@@ -95,6 +95,7 @@
visibleTracks = new Set<string>();
prevVisibleTracks = new Set<string>();
searchIndex = -1;
+ scrollToTrackId: undefined|string|number = undefined;
private scrollBarWidth: undefined|number = undefined;
private _omniboxState: OmniboxState = {
diff --git a/ui/src/frontend/globals.ts b/ui/src/frontend/globals.ts
index b2e7f97..032cdb5 100644
--- a/ui/src/frontend/globals.ts
+++ b/ui/src/frontend/globals.ts
@@ -28,6 +28,7 @@
dur?: number;
priority?: number;
endState?: string;
+ cpu?: number;
id?: number;
utid?: number;
wakeupTs?: number;
diff --git a/ui/src/frontend/slice_panel.ts b/ui/src/frontend/slice_panel.ts
index 80f5c7b..99191fe 100644
--- a/ui/src/frontend/slice_panel.ts
+++ b/ui/src/frontend/slice_panel.ts
@@ -14,6 +14,7 @@
import * as m from 'mithril';
+import {Actions} from '../common/actions';
import {drawDoubleHeadedArrow} from '../common/canvas_utils';
import {translateState} from '../common/thread_state';
import {timeToCode} from '../common/time';
@@ -21,37 +22,46 @@
import {globals} from './globals';
import {Panel, PanelSize} from './panel';
-
export class SliceDetailsPanel extends Panel {
view() {
const sliceInfo = globals.sliceDetails;
- if (!sliceInfo.utid) return;
+ if (sliceInfo.utid === undefined) return;
const threadInfo = globals.threads.get(sliceInfo.utid);
- if (threadInfo && sliceInfo.ts && sliceInfo.dur) {
+ if (threadInfo && sliceInfo.ts !== undefined &&
+ sliceInfo.dur !== undefined) {
return m(
'.details-panel',
m('.details-panel-heading', `Slice Details:`),
- m('.details-table', [m('table', [
- m('tr', m('th', `PID`), m('td', `${threadInfo.pid}`)),
- m('tr',
- m('th', `Process name`),
- m('td', `${threadInfo.procName}`)),
- m('tr', m('th', `TID`), m('td', `${threadInfo.tid}`)),
- m('tr',
- m('th', `Thread name`),
- m('td', `${threadInfo.threadName}`)),
- m('tr',
- m('th', `Start time`),
- m('td', `${timeToCode(sliceInfo.ts)}`)),
- m('tr',
- m('th', `Duration`),
- m('td', `${timeToCode(sliceInfo.dur)}`)),
- m('tr', m('th', `Prio`), m('td', `${sliceInfo.priority}`)),
- m('tr',
- m('th', `End State`),
- m('td', `${translateState(sliceInfo.endState)}`))
- ])], ));
+ m(
+ '.details-table',
+ [m('table',
+ [
+ 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',
+ {
+ onclick: () => this.goToThread(),
+ title: 'Go to thread'
+ },
+ 'call_made'))),
+ m('tr',
+ m('th', `Start time`),
+ m('td', `${timeToCode(sliceInfo.ts)}`)),
+ m('tr',
+ m('th', `Duration`),
+ m('td', `${timeToCode(sliceInfo.dur)}`)),
+ m('tr', m('th', `Prio`), m('td', `${sliceInfo.priority}`)),
+ m('tr',
+ m('th', `End State`),
+ m('td', `${translateState(sliceInfo.endState)}`))
+ ])],
+ ));
} else {
return m(
'.details-panel',
@@ -62,6 +72,41 @@
}
}
+ 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;
+ }
+ globals.makeSelection(Actions.selectThreadState({
+ utid: threadInfo.utid,
+ ts: sliceInfo.ts + globals.state.traceTime.startSec,
+ dur: sliceInfo.dur,
+ state: 'Running',
+ cpu: sliceInfo.cpu,
+ }));
+ let trackId: string|number|undefined;
+ let trackGroupId;
+ for (const track of Object.values(globals.state.tracks)) {
+ if (track.kind === 'ThreadStateTrack' &&
+ (track.config as {utid: number}).utid === threadInfo.utid) {
+ trackGroupId = track.trackGroup;
+ trackId = track.id;
+ }
+ }
+ // After the track exists in the dom, it will be scrolled to.
+ globals.frontendLocalState.scrollToTrackId = trackId;
+
+ if (trackGroupId && globals.state.trackGroups[trackGroupId].collapsed) {
+ globals.dispatch(Actions.toggleTrackGroupCollapsed({trackGroupId}));
+ }
+ }
+
+
renderCanvas(ctx: CanvasRenderingContext2D, size: PanelSize) {
const details = globals.sliceDetails;
// Show expanded details on the scheduling of the currently selected slice.
diff --git a/ui/src/frontend/track_panel.ts b/ui/src/frontend/track_panel.ts
index d60f4b4..1af0ef0 100644
--- a/ui/src/frontend/track_panel.ts
+++ b/ui/src/frontend/track_panel.ts
@@ -20,6 +20,7 @@
import {globals} from './globals';
import {drawGridLines} from './gridline_helper';
import {Panel, PanelSize} from './panel';
+import {verticalScrollToTrack} from './scroll_helper';
import {Track} from './track';
import {TRACK_SHELL_WIDTH} from './track_constants';
import {trackRegistry} from './track_registry';
@@ -187,6 +188,13 @@
m(TrackContent, {track: attrs.track})
]);
}
+
+ onupdate({attrs}: m.CVnode<TrackComponentAttrs>) {
+ if (globals.frontendLocalState.scrollToTrackId === attrs.trackState.id) {
+ verticalScrollToTrack(attrs.trackState.id);
+ globals.frontendLocalState.scrollToTrackId = undefined;
+ }
+ }
}
export interface TrackButtonAttrs {