Skip to content

Commit 52ddffb

Browse files
jackfranklinDevtools-frontend LUCI CQ
authored andcommitted
RPP: refactor initiator code
When reviewing a bug fix in this code this week I found that the code was quite hard to follow and with no tests I got confused a few times. This CL is my attempt to refactor the code slightly. I haven't done anything too wild but instead have just renamed some things and changed how we cache + track the last selected index vs the current one. Bug: none Change-Id: I119cf6a50839e19c6004e34b55984789358662d9 Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/6432453 Reviewed-by: Andres Olivares <[email protected]> Commit-Queue: Jack Franklin <[email protected]>
1 parent 716b39b commit 52ddffb

File tree

4 files changed

+95
-31
lines changed

4 files changed

+95
-31
lines changed

front_end/panels/timeline/TimelineFlameChartDataProvider.test.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,46 @@ describeWithEnvironment('TimelineFlameChartDataProvider', function() {
6363
assert.lengthOf(timelineData3.initiatorsData, 0);
6464
});
6565

66+
it('caches initiator arrows for the same event', async function() {
67+
const dataProvider = new Timeline.TimelineFlameChartDataProvider.TimelineFlameChartDataProvider();
68+
const {parsedTrace} = await TraceLoader.traceEngine(this, 'scheduler-post-task.json.gz');
69+
const entityMapper = new Timeline.Utils.EntityMapper.EntityMapper(parsedTrace);
70+
dataProvider.setModel(parsedTrace, entityMapper);
71+
dataProvider.timelineData();
72+
// a postTask scheduled event - picked as it has an initiator
73+
const event = parsedTrace.Renderer.allTraceEntries.find(event => {
74+
return event.name === Trace.Types.Events.Name.RUN_POST_TASK_CALLBACK && event.ts === 512724961655;
75+
});
76+
assert.exists(event);
77+
const index = dataProvider.indexForEvent(event);
78+
assert.isNotNull(index);
79+
dataProvider.buildFlowForInitiator(index);
80+
const initiatorDataBefore = dataProvider.timelineData().initiatorsData;
81+
dataProvider.buildFlowForInitiator(-1);
82+
dataProvider.buildFlowForInitiator(index);
83+
const initiatorDataAfter = dataProvider.timelineData().initiatorsData;
84+
assert.strictEqual(initiatorDataBefore, initiatorDataAfter);
85+
});
86+
87+
it('does not trigger a redraw if there are no initiators for the old and new selection', async function() {
88+
const dataProvider = new Timeline.TimelineFlameChartDataProvider.TimelineFlameChartDataProvider();
89+
const {parsedTrace} = await TraceLoader.traceEngine(this, 'scheduler-post-task.json.gz');
90+
const entityMapper = new Timeline.Utils.EntityMapper.EntityMapper(parsedTrace);
91+
dataProvider.setModel(parsedTrace, entityMapper);
92+
dataProvider.timelineData();
93+
// a RunTask event with no initiators
94+
const event = parsedTrace.Renderer.allTraceEntries.find(event => {
95+
return event.name === Trace.Types.Events.Name.RUN_TASK && event.ts === 512724754996;
96+
});
97+
assert.exists(event);
98+
const index = dataProvider.indexForEvent(event);
99+
assert.isNotNull(index);
100+
const shouldRedraw = dataProvider.buildFlowForInitiator(index);
101+
assert.isFalse(shouldRedraw); // this event has no initiators
102+
const shouldRedrawAgain = dataProvider.buildFlowForInitiator(-1);
103+
assert.isFalse(shouldRedrawAgain); // previous event has no initiators & user has selected no event
104+
});
105+
66106
describe('groupTreeEvents', function() {
67107
it('returns the correct events for tree views given a flame chart group', async function() {
68108
const dataProvider = new Timeline.TimelineFlameChartDataProvider.TimelineFlameChartDataProvider();

front_end/panels/timeline/TimelineFlameChartDataProvider.ts

Lines changed: 50 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -132,14 +132,20 @@ export class TimelineFlameChartDataProvider extends Common.ObjectWrapper.ObjectW
132132

133133
private entryTypeByLevel: EntryType[] = [];
134134
private entryIndexToTitle: string[] = [];
135-
private lastInitiatorEntry = -1;
136-
private lastInitiatorsData: PerfUI.FlameChart.FlameChartInitiatorData[] = [];
135+
#lastInitiatorEntryIndex = -1;
137136

138137
private lastSelection: Selection|null = null;
139138
readonly #font = `${PerfUI.Font.DEFAULT_FONT_SIZE} ${PerfUI.Font.getFontFamilyForCanvas()}`;
140139
#eventIndexByEvent = new WeakMap<Trace.Types.Events.Event, number|null>();
141140
#entityMapper: Utils.EntityMapper.EntityMapper|null = null;
142141

142+
/**
143+
* When we create initiator chains for a selected event, we store those
144+
* chains in this map so that if the user reselects the same event we do not
145+
* have to recalculate. This is reset when the trace changes.
146+
*/
147+
#initiatorsCache = new Map<number, PerfUI.FlameChart.FlameChartInitiatorData[]>();
148+
143149
constructor() {
144150
super();
145151
this.reset();
@@ -543,6 +549,8 @@ export class TimelineFlameChartDataProvider extends Common.ObjectWrapper.ObjectW
543549
this.timelineDataInternal = null;
544550
this.parsedTrace = null;
545551
this.#entityMapper = null;
552+
this.#lastInitiatorEntryIndex = -1;
553+
this.#initiatorsCache.clear();
546554
}
547555

548556
maxStackDepth(): number {
@@ -1243,45 +1251,52 @@ export class TimelineFlameChartDataProvider extends Common.ObjectWrapper.ObjectW
12431251
* @returns if we should re-render the flame chart (canvas)
12441252
*/
12451253
buildFlowForInitiator(entryIndex: number): boolean {
1246-
if (!this.parsedTrace) {
1254+
if (!this.parsedTrace || !this.compatibilityTracksAppender || !this.timelineDataInternal) {
12471255
return false;
12481256
}
1249-
if (!this.timelineDataInternal) {
1257+
1258+
if (this.#lastInitiatorEntryIndex === entryIndex) {
1259+
// If the user clicks on an entry twice by mistake, this can fire. But if
1260+
// the entry matches the selected entry, then there is nothing more for
1261+
// us to do.
12501262
return false;
12511263
}
1252-
// Remove all previously assigned decorations indicating that the flow event entries are hidden
1264+
1265+
this.#lastInitiatorEntryIndex = entryIndex;
1266+
12531267
const previousInitiatorsDataLength = this.timelineDataInternal.initiatorsData.length;
1254-
// |entryIndex| equals -1 means there is no entry selected, just clear the
1255-
// initiator cache if there is any previous arrow and return true to
1256-
// re-render.
1268+
12571269
if (entryIndex === -1) {
1258-
this.lastInitiatorEntry = entryIndex;
1259-
if (previousInitiatorsDataLength === 0) {
1260-
// This means there is no arrow before, so we don't need to re-render.
1270+
// User has deselected an event, so if it had any initiators we need to clear them.
1271+
if (this.timelineDataInternal.initiatorsData.length === 0) {
1272+
// The previous selected entry had no initiators, so we can early exit and not redraw anything.
12611273
return false;
12621274
}
1263-
// Reset to clear any previous arrows from the last event.
1264-
this.timelineDataInternal.resetFlowData();
1275+
// Clear initiator data and trigger a re-render.
1276+
this.timelineDataInternal.emptyInitiators();
12651277
return true;
12661278
}
1267-
if (this.lastInitiatorEntry === entryIndex) {
1268-
if (this.lastInitiatorsData) {
1269-
this.timelineDataInternal.initiatorsData = this.lastInitiatorsData;
1270-
}
1271-
return false;
1272-
}
1273-
if (!this.compatibilityTracksAppender) {
1274-
return false;
1275-
}
12761279

1280+
// If the user hasn't clicked on an event, bail, as there are no initiators
1281+
// for screenshots or frames.
12771282
const entryType = this.#entryTypeForIndex(entryIndex);
12781283
if (entryType !== EntryType.TRACK_APPENDER) {
12791284
return false;
12801285
}
1286+
1287+
// Avoid re-building the initiators if we already did it previously.
1288+
const cached = this.#initiatorsCache.get(entryIndex);
1289+
if (cached) {
1290+
this.timelineDataInternal.initiatorsData = cached;
1291+
return true;
1292+
}
1293+
1294+
// At this point, we know we:
1295+
// 1. Have an event to build initiators for.
1296+
// 2. Know that it's not an event with initiators that are cached.
12811297
const event = this.entryData[entryIndex];
12821298
// Reset to clear any previous arrows from the last event.
1283-
this.timelineDataInternal.resetFlowData();
1284-
this.lastInitiatorEntry = entryIndex;
1299+
this.timelineDataInternal.emptyInitiators();
12851300

12861301
const hiddenEvents: Trace.Types.Events.Event[] =
12871302
ModificationsManager.activeManager()?.getEntriesFilter().invisibleEntries() ?? [];
@@ -1294,10 +1309,19 @@ export class TimelineFlameChartDataProvider extends Common.ObjectWrapper.ObjectW
12941309
hiddenEvents,
12951310
expandableEntries,
12961311
);
1297-
// This means there is no change for arrows.
1312+
1313+
if (initiatorsData.length === 0) {
1314+
// Small optimization: cache if this entry has 0 initiators, meaning if
1315+
// it gets reselected we don't redo the work to find out it has 0
1316+
// initiators.
1317+
this.#initiatorsCache.set(entryIndex, []);
1318+
}
1319+
1320+
// Previous event had 0 initiators, new event has 0, therefore exit early and don't render.
12981321
if (previousInitiatorsDataLength === 0 && initiatorsData.length === 0) {
12991322
return false;
13001323
}
1324+
13011325
for (const initiatorData of initiatorsData) {
13021326
const eventIndex = this.indexForEvent(initiatorData.event);
13031327
const initiatorIndex = this.indexForEvent(initiatorData.initiator);
@@ -1311,7 +1335,7 @@ export class TimelineFlameChartDataProvider extends Common.ObjectWrapper.ObjectW
13111335
isEntryHidden: initiatorData.isEntryHidden,
13121336
});
13131337
}
1314-
this.lastInitiatorsData = this.timelineDataInternal.initiatorsData;
1338+
this.#initiatorsCache.set(entryIndex, this.timelineDataInternal.initiatorsData);
13151339
return true;
13161340
}
13171341

front_end/panels/timeline/TimelineFlameChartNetworkDataProvider.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -554,13 +554,13 @@ export class TimelineFlameChartNetworkDataProvider implements PerfUI.FlameChart.
554554
return false;
555555
}
556556
// Reset to clear any previous arrows from the last event.
557-
this.#timelineDataInternal.resetFlowData();
557+
this.#timelineDataInternal.emptyInitiators();
558558
return true;
559559
}
560560

561561
const event = this.#events[entryIndex];
562562
// Reset to clear any previous arrows from the last event.
563-
this.#timelineDataInternal.resetFlowData();
563+
this.#timelineDataInternal.emptyInitiators();
564564
this.#lastInitiatorEntry = entryIndex;
565565

566566
const initiatorsData = initiatorsDataToDrawForNetwork(this.#parsedTrace, event);

front_end/ui/legacy/components/perf_ui/FlameChart.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1130,7 +1130,7 @@ export class FlameChart extends Common.ObjectWrapper.eventMixin<EventTypes, type
11301130

11311131
private deselectAllEntries(): void {
11321132
this.selectedEntryIndex = -1;
1133-
this.rawTimelineData?.resetFlowData();
1133+
this.rawTimelineData?.emptyInitiators();
11341134
this.draw();
11351135
}
11361136

@@ -1218,7 +1218,7 @@ export class FlameChart extends Common.ObjectWrapper.eventMixin<EventTypes, type
12181218
(groupIndex >= groups.length - 1 || groups[groupIndex + 1].startLevel > level)) {
12191219
this.selectedEntryIndex = -1;
12201220
// Reset all flow arrows when we deselect the entry.
1221-
this.rawTimelineData.resetFlowData();
1221+
this.rawTimelineData.emptyInitiators();
12221222
}
12231223
}
12241224
}
@@ -4150,7 +4150,7 @@ export class FlameChartTimelineData {
41504150
);
41514151
}
41524152

4153-
resetFlowData(): void {
4153+
emptyInitiators(): void {
41544154
this.initiatorsData = [];
41554155
}
41564156
}

0 commit comments

Comments
 (0)