Skip to content

Commit

Permalink
Merge "Add links to Scroll Jank V3 track." into main
Browse files Browse the repository at this point in the history
  • Loading branch information
Harkiran Bolaria authored and Gerrit Code Review committed Aug 30, 2023
2 parents 5ccf587 + ef45a17 commit 17b4035
Show file tree
Hide file tree
Showing 5 changed files with 222 additions and 48 deletions.
130 changes: 108 additions & 22 deletions ui/src/tracks/chrome_scroll_jank/event_latency_details_panel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import m from 'mithril';

import {exists} from '../../base/utils';
import {NUM} from '../../common/query_result';
import {raf} from '../../core/raf_scheduler';
import {
BottomTab,
Expand All @@ -33,11 +34,21 @@ import {GridLayout, GridLayoutColumn} from '../../frontend/widgets/grid_layout';
import {Section} from '../../frontend/widgets/section';
import {Tree, TreeNode} from '../../frontend/widgets/tree';

import {
getScrollJankSlices,
getSliceForTrack,
ScrollJankSlice,
} from './scroll_jank_slice';
import {ScrollJankV3Track} from './scroll_jank_v3_track';

export class EventLatencySliceDetailsPanel extends
BottomTab<GenericSliceDetailsTabConfig> {
static readonly kind = 'dev.perfetto.EventLatencySliceDetailsPanel';

private loaded = false;

private sliceDetails?: SliceDetails;
private jankySlice?: ScrollJankSlice;

static create(args: NewBottomTabArgs): EventLatencySliceDetailsPanel {
return new EventLatencySliceDetailsPanel(args);
Expand All @@ -46,8 +57,13 @@ export class EventLatencySliceDetailsPanel extends
constructor(args: NewBottomTabArgs) {
super(args);

// Start loading the slice details
this.loadSlice();
this.loadData();
}

async loadData() {
await this.loadSlice();
await this.loadJankSlice();
this.loaded = true;
}

async loadSlice() {
Expand All @@ -56,6 +72,88 @@ export class EventLatencySliceDetailsPanel extends
raf.scheduleRedraw();
}

async loadJankSlice() {
if (exists(this.sliceDetails)) {
// Get the id for the top-level EventLatency slice (this or parent), as
// this id is used in the ScrollJankV3 track to identify the corresponding
// janky interval.
let eventLatencyId = -1;
if (this.sliceDetails.name == 'EventLatency') {
eventLatencyId = this.sliceDetails.id;
} else {
const queryResult = await this.engine.query(`
SELECT
id
FROM ancestor_slice(${this.sliceDetails.id})
WHERE name = 'EventLatency'
`);
const it = queryResult.iter({
id: NUM,
});
for (; it.valid(); it.next()) {
eventLatencyId = it.id;
break;
}
}

const possibleSlices =
await getScrollJankSlices(this.engine, eventLatencyId);
// We may not get any slices if the EventLatency doesn't indicate any
// jank occurred.
if (possibleSlices.length > 0) {
this.jankySlice = possibleSlices[0];
}
}
}

private getLinksSection(): m.Child {
return m(
Section,
{title: 'Quick links'},
m(
Tree,
m(TreeNode, {
left: exists(this.sliceDetails) ?
sliceRef(
this.sliceDetails,
'EventLatency in context of other Input events') :
'EventLatency in context of other Input events',
right: exists(this.sliceDetails) ? '' : 'N/A',
}),
m(TreeNode, {
left: exists(this.jankySlice) ? getSliceForTrack(
this.jankySlice,
ScrollJankV3Track.kind,
'Jank Interval') :
'Jank Interval',
right: exists(this.jankySlice) ? '' : 'N/A',
}),
),
);
}

private getDescriptionText(): m.Child {
return m(
`div[style='white-space:pre-wrap']`,
`EventLatency tracks the latency of handling a given input event
(Scrolls, Touchs, Taps, etc). Ideally from when the input was read by
the hardware to when it was reflected on the screen.{new_lines}
Note however the concept of coalescing or terminating early. This occurs
when we receive multiple events or handle them quickly by converting
the into a different event. Such as a TOUCH_MOVE being converted into a
GESTURE_SCROLL_UPDATE type, or a multiple GESTURE_SCROLL_UPDATE events
being formed into a single frame at the end of the
RendererCompositorQueuingDelay.{new_lines}
*Important:* On some platforms (MacOS) we do not get feedback on when
something is presented on the screen so the timings are only accurate
for what we know on a given platform.`.replace(/\s\s+/g, ' ')
.replace(/{new_lines}/g, '\n\n')
.replace(/ Note:/g, 'Note:'),
);
}

viewTab() {
if (exists(this.sliceDetails)) {
const slice = this.sliceDetails;
Expand All @@ -66,34 +164,22 @@ export class EventLatencySliceDetailsPanel extends
description: slice.name,
},
m(GridLayout,
m(
GridLayoutColumn,
renderDetails(slice),
),
m(GridLayoutColumn,
renderArguments(this.engine, slice),
m(
Section,
{title: 'Quick links'},
// TODO(hbolaria): add a link to the jank interval if this
// slice is a janky latency.
m(
Tree,
m(TreeNode, {
left: sliceRef(
this.sliceDetails, 'Original EventLatency'),
right: '',
}),
),
))),
renderDetails(slice),
renderArguments(this.engine, slice)),
m(GridLayoutColumn,
m(Section,
{title: 'Description'},
m('.div', this.getDescriptionText())),
this.getLinksSection())),
);
} else {
return m(DetailsShell, {title: 'Slice', description: 'Loading...'});
}
}

isLoading() {
return !exists(this.sliceDetails);
return !this.loaded;
}

getTitle(): string {
Expand Down
120 changes: 95 additions & 25 deletions ui/src/tracks/chrome_scroll_jank/scroll_details_panel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
import m from 'mithril';

import {exists} from '../../base/utils';
import {LONG, NUM} from '../../common/query_result';
import {LONG, NUM, STR} from '../../common/query_result';
import {duration, Time, time} from '../../common/time';
import {raf} from '../../core/raf_scheduler';
import {
Expand All @@ -27,13 +27,31 @@ import {
GenericSliceDetailsTabConfig,
} from '../../frontend/generic_slice_details_tab';
import {sqlValueToString} from '../../frontend/sql_utils';
import {
ColumnDescriptor,
numberColumn,
Table,
TableData,
} from '../../frontend/tables/table';
import {DetailsShell} from '../../frontend/widgets/details_shell';
import {DurationWidget} from '../../frontend/widgets/duration';
import {GridLayout, GridLayoutColumn} from '../../frontend/widgets/grid_layout';
import {Section} from '../../frontend/widgets/section';
import {SqlRef} from '../../frontend/widgets/sql_ref';
import {Timestamp} from '../../frontend/widgets/timestamp';
import {dictToTreeNodes, Tree, TreeNode} from '../../frontend/widgets/tree';
import {dictToTreeNodes, Tree} from '../../frontend/widgets/tree';

import {
getScrollJankSlices,
getSliceForTrack,
ScrollJankSlice,
} from './scroll_jank_slice';
import {ScrollJankV3Track} from './scroll_jank_v3_track';

function widgetColumn<T>(
name: string, getter: (t: T) => m.Child): ColumnDescriptor<T> {
return new ColumnDescriptor<T>(name, getter);
}

interface Data {
// Scroll ID.
Expand All @@ -51,17 +69,23 @@ interface Metrics {
jankyFrameCount?: number;
jankyFramePercent?: number;
missedVsyncs?: number;
maxDelayDur?: duration;
maxDelayVsync?: number;
// TODO(b/279581028): add pixels scrolled.
}

interface JankSliceDetails {
cause: string;
jankSlice: ScrollJankSlice;
delayDur: duration;
delayVsync: number;
}

export class ScrollDetailsPanel extends
BottomTab<GenericSliceDetailsTabConfig> {
static readonly kind = 'org.perfetto.ScrollDetailsPanel';
loaded = false;
data: Data|undefined;
metrics: Metrics = {};
maxJankSlices: JankSliceDetails[] = [];

static create(args: NewBottomTabArgs): ScrollDetailsPanel {
return new ScrollDetailsPanel(args);
Expand Down Expand Up @@ -166,22 +190,45 @@ export class ScrollDetailsPanel extends
private async loadMaxDelay() {
if (exists(this.data)) {
const queryResult = await this.engine.query(`
WITH max_delay_tbl AS (
SELECT
MAX(dur) AS max_dur
FROM chrome_janky_frame_presentation_intervals s
WHERE s.ts >= ${this.data.ts}
AND s.ts + s.dur <= ${this.data.ts + this.data.dur}
)
SELECT
IFNULL(sub_cause_of_jank, IFNULL(cause_of_jank, 'Unknown')) AS cause,
IFNULL(event_latency_id, 0) AS eventLatencyId,
IFNULL(MAX(dur), 0) AS maxDelayDur,
IFNULL(delayed_frame_count, 0) AS maxDelayVsync
FROM chrome_janky_frame_presentation_intervals s
WHERE s.ts >= ${this.data.ts}
AND s.ts + s.dur <= ${this.data.ts + this.data.dur}
AND dur IN (SELECT max_dur FROM max_delay_tbl)
GROUP BY eventLatencyId, cause;
`);

const iter = queryResult.firstRow({
const iter = queryResult.iter({
cause: STR,
eventLatencyId: NUM,
maxDelayDur: LONG,
maxDelayVsync: NUM,
});

if (iter.maxDelayDur > 0) {
this.metrics.maxDelayDur = iter.maxDelayDur;
this.metrics.maxDelayVsync = iter.maxDelayVsync;
for (; iter.valid(); iter.next()) {
if (iter.maxDelayDur <= 0) {
break;
}
const jankSlices =
await getScrollJankSlices(this.engine, iter.eventLatencyId);

this.maxJankSlices.push({
cause: iter.cause,
jankSlice: jankSlices[0],
delayDur: iter.maxDelayDur,
delayVsync: iter.maxDelayVsync,
});
}
}
}
Expand All @@ -200,24 +247,42 @@ export class ScrollDetailsPanel extends
sqlValueToString(`${this.metrics.jankyFramePercent}%`);
}

if (this.metrics.maxDelayDur !== undefined &&
this.metrics.maxDelayVsync !== undefined) {
// TODO(b/278844325): replace this with a link to the actual scroll slice.
metrics['Max Frame Presentation Delay'] =
m(Tree,
m(TreeNode, {
left: 'Duration',
right: m(DurationWidget, {dur: this.metrics.maxDelayDur}),
}),
m(TreeNode, {
left: 'Vsyncs Missed',
right: this.metrics.maxDelayVsync,
}));
return dictToTreeNodes(metrics);
}

private getMaxDelayTable(): m.Child {
if (this.maxJankSlices.length > 0) {
interface DelayData {
jankLink: m.Child;
dur: m.Child;
delayedVSyncs: number;
}
;

const columns: ColumnDescriptor<DelayData>[] = [
widgetColumn<DelayData>('Cause', (x) => x.jankLink),
widgetColumn<DelayData>('Duration', (x) => x.dur),
numberColumn<DelayData>('Delayed Vsyncs', (x) => x.delayedVSyncs),
];
const data: DelayData[] = [];
for (const jankSlice of this.maxJankSlices) {
data.push({
jankLink: getSliceForTrack(
jankSlice.jankSlice, ScrollJankV3Track.kind, jankSlice.cause),
dur: m(DurationWidget, {dur: jankSlice.delayDur}),
delayedVSyncs: jankSlice.delayVsync,
});
}

const tableData = new TableData(data);

return m(Table, {
data: tableData,
columns: columns,
});
} else {
metrics['Max Frame Presentation Delay'] = sqlValueToString('None');
return sqlValueToString('None');
}

return dictToTreeNodes(metrics);
}

private getDescriptionText(): m.Child {
Expand Down Expand Up @@ -267,7 +332,12 @@ export class ScrollDetailsPanel extends
),
m(Section,
{title: 'Slice Metrics'},
m(Tree, this.renderMetricsDictionary()))),
m(Tree, this.renderMetricsDictionary())),
m(
Section,
{title: 'Max Frame Presentation Delay'},
this.getMaxDelayTable(),
)),
m(
GridLayoutColumn,
m(
Expand Down
Loading

0 comments on commit 17b4035

Please sign in to comment.