Skip to content

Commit 13d0550

Browse files
jackfranklinDevtools-frontend LUCI CQ
authored andcommitted
Trace: remove need to clone handler data
Because handlers did not recreate instance variables like maps and arrays, we had to clone the data after parsing to ensure if you then import another trace, the handlers don't mutate previous trace data. This CL updates each handler to create new instances of data, meaning the worry about subsequent trace parsing mutating previous ones is removed, and we can remove the requirement to clone any data after parsing. On a 400mb trace of Figma, this knocks ~200ms off the total time. Not only do we not have to clone, but we also avoid the nested clones in the RendererHandler `data()` call, which saves us ~50ms of work every time it is called (which is 4 times as other handlers depend on it). R=cjamcl@chromium.org Fixed: 41484172 Bug: 436491188 Change-Id: Ie989c6d2c8d66e1b091b4d93e387dadb1b0b1be8 Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/6897737 Auto-Submit: Jack Franklin <jacktfranklin@chromium.org> Reviewed-by: Connor Clark <cjamcl@chromium.org> Commit-Queue: Connor Clark <cjamcl@chromium.org>
1 parent dab75e1 commit 13d0550

31 files changed

+274
-305
lines changed

front_end/models/trace/Processor.ts

Lines changed: 3 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -234,39 +234,12 @@ export class TraceProcessor extends EventTarget {
234234
this.dispatchEvent(new TraceParseProgressEvent({percent}));
235235
}
236236

237-
// Handlers that depend on other handlers do so via .data(), which used to always
238-
// return a shallow clone of its internal data structures. However, that pattern
239-
// easily results in egregious amounts of allocation. Now .data() does not do any
240-
// cloning, and it happens here instead so that users of the trace processor may
241-
// still assume that the parsed data is theirs.
242-
// See: crbug/41484172
243-
const shallowClone = (value: unknown, recurse = true): unknown => {
244-
if (value instanceof Map) {
245-
return new Map(value);
246-
}
247-
if (value instanceof Set) {
248-
return new Set(value);
249-
}
250-
if (Array.isArray(value)) {
251-
return [...value];
252-
}
253-
if (typeof value === 'object' && value && recurse) {
254-
const obj: Record<string, unknown> = {};
255-
for (const [key, v] of Object.entries(value)) {
256-
obj[key] = shallowClone(v, false);
257-
}
258-
return obj;
259-
}
260-
return value;
261-
};
262-
263-
options.logger?.start('parse:clone');
237+
options.logger?.start('parse:handler.data()');
264238
const parsedTrace = {};
265239
for (const [name, handler] of Object.entries(this.#traceHandlers)) {
266-
const data = shallowClone(handler.data());
267-
Object.assign(parsedTrace, {[name]: data});
240+
Object.assign(parsedTrace, {[name]: handler.data()});
268241
}
269-
options.logger?.end('parse:clone');
242+
options.logger?.end('parse:handler.data()');
270243

271244
this.dispatchEvent(new TraceParseProgressEvent({percent: ProgressPhase.CLONE}));
272245

front_end/models/trace/handlers/AnimationFramesHandler.ts

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,24 +16,23 @@ function threadKey(data: Types.Events.Event): string {
1616
}
1717
// Track all the start + end events. We key them by the PID+TID so we don't
1818
// accidentally pair across different threads.
19-
const animationFrameStarts = new Map<string, Types.Events.AnimationFrameAsyncStart[]>();
20-
const animationFrameEnds = new Map<string, Types.Events.AnimationFrameAsyncEnd[]>();
19+
let animationFrameStarts = new Map<string, Types.Events.AnimationFrameAsyncStart[]>();
20+
let animationFrameEnds = new Map<string, Types.Events.AnimationFrameAsyncEnd[]>();
2121
// Store all the AnimationFrame::Presentation events. Key them by their ID for
2222
// easy look-up later on when we associate one to the AnimationFrame event.
23-
const animationFramePresentations = new Map<string, Types.Events.AnimationFramePresentation>();
23+
let animationFramePresentations = new Map<string, Types.Events.AnimationFramePresentation>();
2424

2525
// The final list of animation frames that we return.
26-
const animationFrames: Types.Events.SyntheticAnimationFramePair[] = [];
26+
let animationFrames: Types.Events.SyntheticAnimationFramePair[] = [];
2727

28-
const presentationForFrame =
29-
new Map<Types.Events.SyntheticAnimationFramePair, Types.Events.AnimationFramePresentation>();
28+
let presentationForFrame = new Map<Types.Events.SyntheticAnimationFramePair, Types.Events.AnimationFramePresentation>();
3029

3130
export function reset(): void {
32-
animationFrameStarts.clear();
33-
animationFrameEnds.clear();
34-
animationFrames.length = 0;
35-
presentationForFrame.clear();
36-
animationFramePresentations.clear();
31+
animationFrameStarts = new Map();
32+
animationFrameEnds = new Map();
33+
animationFrames = [];
34+
presentationForFrame = new Map();
35+
animationFramePresentations = new Map();
3736
isEnabled = false;
3837
}
3938

front_end/models/trace/handlers/AnimationHandler.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,16 @@
55
import * as Helpers from '../helpers/helpers.js';
66
import * as Types from '../types/types.js';
77

8-
const animations: Types.Events.Animation[] = [];
9-
const animationsSyntheticEvents: Types.Events.SyntheticAnimationPair[] = [];
8+
let animations: Types.Events.Animation[] = [];
9+
let animationsSyntheticEvents: Types.Events.SyntheticAnimationPair[] = [];
1010

1111
export interface AnimationData {
1212
animations: readonly Types.Events.SyntheticAnimationPair[];
1313
}
1414

1515
export function reset(): void {
16-
animations.length = 0;
17-
animationsSyntheticEvents.length = 0;
16+
animations = [];
17+
animationsSyntheticEvents = [];
1818
}
1919

2020
export function handleEvent(event: Types.Events.Event): void {

front_end/models/trace/handlers/AsyncJSCallsHandler.ts

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,19 @@ import * as Types from '../types/types.js';
88
import {data as flowsHandlerData} from './FlowsHandler.js';
99
import {data as rendererHandlerData} from './RendererHandler.js';
1010

11-
const schedulerToRunEntryPoints = new Map<Types.Events.Event, Types.Events.Event[]>();
11+
let schedulerToRunEntryPoints = new Map<Types.Events.Event, Types.Events.Event[]>();
1212

13-
const taskScheduleForTaskRunEvent =
14-
new Map<Types.Events.DebuggerAsyncTaskRun, Types.Events.DebuggerAsyncTaskScheduled>();
15-
const asyncCallToScheduler =
13+
let taskScheduleForTaskRunEvent = new Map<Types.Events.DebuggerAsyncTaskRun, Types.Events.DebuggerAsyncTaskScheduled>();
14+
let asyncCallToScheduler =
1615
new Map<Types.Events.SyntheticProfileCall, {taskName: string, scheduler: Types.Events.Event}>();
1716

18-
const runEntryPointToScheduler = new Map<Types.Events.Event, {taskName: string, scheduler: Types.Events.Event}>();
17+
let runEntryPointToScheduler = new Map<Types.Events.Event, {taskName: string, scheduler: Types.Events.Event}>();
1918

2019
export function reset(): void {
21-
schedulerToRunEntryPoints.clear();
22-
asyncCallToScheduler.clear();
23-
taskScheduleForTaskRunEvent.clear();
24-
runEntryPointToScheduler.clear();
20+
schedulerToRunEntryPoints = new Map();
21+
asyncCallToScheduler = new Map();
22+
taskScheduleForTaskRunEvent = new Map();
23+
runEntryPointToScheduler = new Map();
2524
}
2625

2726
export function handleEvent(_: Types.Events.Event): void {

front_end/models/trace/handlers/AuctionWorkletsHandler.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,11 @@ import * as Types from '../types/types.js';
3131
* args.data.target property, which is a string ID shared by both
3232
* events.
3333
*/
34-
const runningInProcessEvents = new Map<Types.Events.ProcessID, Types.Events.AuctionWorkletRunningInProcess>();
35-
const doneWithProcessEvents = new Map<Types.Events.ProcessID, Types.Events.AuctionWorkletDoneWithProcess>();
34+
let runningInProcessEvents = new Map<Types.Events.ProcessID, Types.Events.AuctionWorkletRunningInProcess>();
35+
let doneWithProcessEvents = new Map<Types.Events.ProcessID, Types.Events.AuctionWorkletDoneWithProcess>();
3636

3737
// Keyed by the PID defined in `args.data.pid` on AuctionWorklet trace events..
38-
const createdSyntheticEvents = new Map<Types.Events.ProcessID, Types.Events.SyntheticAuctionWorklet>();
38+
let createdSyntheticEvents = new Map<Types.Events.ProcessID, Types.Events.SyntheticAuctionWorklet>();
3939

4040
// Each AuctonWorklet takes over a process and has 2 threads (that we care
4141
// about and want to show as tracks):
@@ -44,15 +44,15 @@ const createdSyntheticEvents = new Map<Types.Events.ProcessID, Types.Events.Synt
4444
// either a "Seller" or a "Bidder"
4545
// To detect these we look for the metadata thread_name events. We key these by
4646
// PID so that we can easily look them up later without having to loop through.
47-
const utilityThreads = new Map<Types.Events.ProcessID, Types.Events.ThreadName>();
48-
const v8HelperThreads = new Map<Types.Events.ProcessID, Types.Events.ThreadName>();
47+
let utilityThreads = new Map<Types.Events.ProcessID, Types.Events.ThreadName>();
48+
let v8HelperThreads = new Map<Types.Events.ProcessID, Types.Events.ThreadName>();
4949

5050
export function reset(): void {
51-
runningInProcessEvents.clear();
52-
doneWithProcessEvents.clear();
53-
createdSyntheticEvents.clear();
54-
utilityThreads.clear();
55-
v8HelperThreads.clear();
51+
runningInProcessEvents = new Map();
52+
doneWithProcessEvents = new Map();
53+
createdSyntheticEvents = new Map();
54+
utilityThreads = new Map();
55+
v8HelperThreads = new Map();
5656
}
5757

5858
export function handleEvent(event: Types.Events.Event): void {

front_end/models/trace/handlers/DOMStatsHandler.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@ export interface DOMStatsData {
99
domStatsByFrameId: Map<string, Types.Events.DOMStats[]>;
1010
}
1111

12-
const domStatsByFrameId: DOMStatsData['domStatsByFrameId'] = new Map();
12+
let domStatsByFrameId: DOMStatsData['domStatsByFrameId'] = new Map();
1313

1414
export function reset(): void {
15-
domStatsByFrameId.clear();
15+
domStatsByFrameId = new Map();
1616
}
1717

1818
export function handleEvent(event: Types.Events.Event): void {

front_end/models/trace/handlers/ExtensionTraceDataHandler.ts

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,13 @@ import * as Types from '../types/types.js';
88
import type {HandlerName} from './types.js';
99
import {data as userTimingsData} from './UserTimingsHandler.js';
1010

11-
const extensionTrackEntries: Types.Extensions.SyntheticExtensionTrackEntry[] = [];
12-
const extensionTrackData: Types.Extensions.ExtensionTrackData[] = [];
13-
const extensionMarkers: Types.Extensions.SyntheticExtensionMarker[] = [];
14-
const entryToNode = new Map<Types.Events.Event, Helpers.TreeHelpers.TraceEntryNode>();
15-
const timeStampByName = new Map<string, Types.Events.ConsoleTimeStamp>();
11+
let extensionTrackEntries: Types.Extensions.SyntheticExtensionTrackEntry[] = [];
12+
let extensionTrackData: Types.Extensions.ExtensionTrackData[] = [];
13+
let extensionMarkers: Types.Extensions.SyntheticExtensionMarker[] = [];
14+
let entryToNode = new Map<Types.Events.Event, Helpers.TreeHelpers.TraceEntryNode>();
15+
let timeStampByName = new Map<string, Types.Events.ConsoleTimeStamp>();
1616

17-
const syntheticConsoleEntriesForTimingsTrack: Types.Events.SyntheticConsoleTimeStamp[] = [];
17+
let syntheticConsoleEntriesForTimingsTrack: Types.Events.SyntheticConsoleTimeStamp[] = [];
1818

1919
export interface ExtensionTraceData {
2020
extensionTrackData: readonly Types.Extensions.ExtensionTrackData[];
@@ -29,12 +29,12 @@ export function handleEvent(_event: Types.Events.Event): void {
2929
}
3030

3131
export function reset(): void {
32-
extensionTrackEntries.length = 0;
33-
syntheticConsoleEntriesForTimingsTrack.length = 0;
34-
extensionTrackData.length = 0;
35-
extensionMarkers.length = 0;
36-
entryToNode.clear();
37-
timeStampByName.clear();
32+
extensionTrackEntries = [];
33+
syntheticConsoleEntriesForTimingsTrack = [];
34+
extensionTrackData = [];
35+
extensionMarkers = [];
36+
entryToNode = new Map();
37+
timeStampByName = new Map();
3838
}
3939

4040
export async function finalize(): Promise<void> {

front_end/models/trace/handlers/FlowsHandler.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import * as Types from '../types/types.js';
2424
// flows with the timestamps of each phase. Then, we place trace events
2525
// in the flows where their corresponding phase events were placed (if
2626
// there are any corresponding flow phase events at all).
27-
const flowDataByGroupToken = new Map<string, number>();
27+
let flowDataByGroupToken = new Map<string, number>();
2828

2929
interface EventFlowData {
3030
flows: Set<number>;
@@ -43,20 +43,20 @@ type FlowBindingTuple =
4343
// every event in a trace, resulting in a lot of memory overhead and
4444
// major GC triggering. So we are trading off readability for
4545
// performance.
46-
const boundFlowData: FlowBindingTuple = new Map();
46+
let boundFlowData: FlowBindingTuple = new Map();
4747

48-
const flowsById = new Map<number, Map<Types.Timing.Micro, Types.Events.Event>>();
49-
const flowEvents: Types.Events.FlowEvent[] = [];
50-
const nonFlowEvents: Types.Events.Event[] = [];
48+
let flowsById = new Map<number, Map<Types.Timing.Micro, Types.Events.Event>>();
49+
let flowEvents: Types.Events.FlowEvent[] = [];
50+
let nonFlowEvents: Types.Events.Event[] = [];
5151
let flows: Types.Events.Event[][] = [];
5252
const ID_COMPONENT_SEPARATOR = '-$-';
5353
export function reset(): void {
5454
flows = [];
55-
flowEvents.length = 0;
56-
nonFlowEvents.length = 0;
57-
flowDataByGroupToken.clear();
58-
boundFlowData.clear();
59-
flowsById.clear();
55+
flowEvents = [];
56+
nonFlowEvents = [];
57+
flowDataByGroupToken = new Map();
58+
boundFlowData = new Map();
59+
flowsById = new Map();
6060
}
6161

6262
export function handleEvent(event: Types.Events.Event): void {

front_end/models/trace/handlers/FramesHandler.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import type {HandlerName} from './types.js';
2525
*/
2626

2727
let model: TimelineFrameModel|null = null;
28-
const relevantFrameEvents: Types.Events.Event[] = [];
28+
let relevantFrameEvents: Types.Events.Event[] = [];
2929

3030
type FrameEvent = Types.Events.BeginFrame|Types.Events.DroppedFrame|Types.Events.RequestMainThreadFrame|
3131
Types.Events.BeginMainThreadFrame|Types.Events.Commit|Types.Events.CompositeLayers|
@@ -58,7 +58,7 @@ const MAIN_FRAME_MARKERS = new Set<Types.Events.Name>([
5858

5959
export function reset(): void {
6060
model = null;
61-
relevantFrameEvents.length = 0;
61+
relevantFrameEvents = [];
6262
}
6363
export function handleEvent(event: Types.Events.Event): void {
6464
// This might seem like a wide set of events to filter for, but these are all

front_end/models/trace/handlers/GPUHandler.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,12 @@ import type {HandlerName} from './types.js';
1010

1111
// Each thread contains events. Events indicate the thread and process IDs, which are
1212
// used to store the event in the correct process thread entry below.
13-
const eventsInProcessThread = new Map<Types.Events.ProcessID, Map<Types.Events.ThreadID, Types.Events.GPUTask[]>>();
13+
let eventsInProcessThread = new Map<Types.Events.ProcessID, Map<Types.Events.ThreadID, Types.Events.GPUTask[]>>();
1414

1515
let mainGPUThreadTasks: Types.Events.GPUTask[] = [];
1616

1717
export function reset(): void {
18-
eventsInProcessThread.clear();
18+
eventsInProcessThread = new Map();
1919
mainGPUThreadTasks = [];
2020
}
2121

0 commit comments

Comments
 (0)