Skip to content

Commit 64c8e48

Browse files
and-oliDevtools-frontend LUCI CQ
authored andcommitted
Parse console.timeStamp payload
The payload now contains the new params the API was called with, according to go/cpq:console-timestamp. This CL constructs the track data with this data. Timestamps for which a duration can be computed (because they have a start and an end) but that do not have an associated track name are stored separately. These events can be added to the timings track, I'd do that in a follow up CL. Bug: 390155241 Change-Id: Icc5103e3e22849aabbd0dc5643c871c193084c77 Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/6172724 Commit-Queue: Andres Olivares <[email protected]> Reviewed-by: Jack Franklin <[email protected]>
1 parent da8d124 commit 64c8e48

File tree

8 files changed

+927
-396
lines changed

8 files changed

+927
-396
lines changed

front_end/models/trace/extras/StackTraceForEvent.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,11 @@ function getForExtensionEntry(event: Types.Extensions.SyntheticExtensionEntry, p
120120
}
121121
const eventCallTime = Types.Events.isPerformanceMeasureBegin(event.rawSourceEvent) ?
122122
event.rawSourceEvent.args.callTime :
123-
event.rawSourceEvent.args.data?.callTime;
123+
Types.Events.isPerformanceMark(event.rawSourceEvent) ?
124+
event.rawSourceEvent.args.data?.callTime :
125+
// event added with console.timeStamp: take the original event's
126+
// ts.
127+
event.rawSourceEvent.ts;
124128
if (eventCallTime === undefined) {
125129
return null;
126130
}

front_end/models/trace/handlers/ExtensionTraceDataHandler.test.ts

Lines changed: 734 additions & 374 deletions
Large diffs are not rendered by default.

front_end/models/trace/handlers/ExtensionTraceDataHandler.ts

Lines changed: 169 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,26 +8,32 @@ 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 extensionFlameChartEntries: Types.Extensions.SyntheticExtensionTrackEntry[] = [];
11+
const extensionTrackEntries: Types.Extensions.SyntheticExtensionTrackEntry[] = [];
1212
const extensionTrackData: Types.Extensions.ExtensionTrackData[] = [];
1313
const extensionMarkers: Types.Extensions.SyntheticExtensionMarker[] = [];
1414
const entryToNode: Map<Types.Events.Event, Helpers.TreeHelpers.TraceEntryNode> = new Map();
15+
const timeStampByName: Map<string, Types.Events.ConsoleTimeStamp> = new Map();
16+
17+
const syntheticConsoleEntriesForTimingsTrack: Types.Events.SyntheticConsoleTimeStamp[] = [];
1518

1619
export interface ExtensionTraceData {
1720
extensionTrackData: readonly Types.Extensions.ExtensionTrackData[];
1821
extensionMarkers: readonly Types.Extensions.SyntheticExtensionMarker[];
1922
entryToNode: Map<Types.Events.Event, Helpers.TreeHelpers.TraceEntryNode>;
23+
syntheticConsoleEntriesForTimingsTrack: Types.Events.SyntheticConsoleTimeStamp[];
2024
}
2125

2226
export function handleEvent(_event: Types.Events.Event): void {
2327
// Implementation not needed because data is sourced from UserTimingsHandler
2428
}
2529

2630
export function reset(): void {
27-
extensionFlameChartEntries.length = 0;
31+
extensionTrackEntries.length = 0;
32+
syntheticConsoleEntriesForTimingsTrack.length = 0;
2833
extensionTrackData.length = 0;
2934
extensionMarkers.length = 0;
3035
entryToNode.clear();
36+
timeStampByName.clear();
3137
}
3238

3339
export async function finalize(): Promise<void> {
@@ -39,14 +45,127 @@ function createExtensionFlameChartEntries(): void {
3945
const marks: readonly Types.Events.PerformanceMark[] = userTimingsData().performanceMarks;
4046
const mergedRawExtensionEvents = Helpers.Trace.mergeEventsInOrder(pairedMeasures, marks);
4147

42-
extractExtensionEntries(mergedRawExtensionEvents);
43-
Helpers.Extensions.buildTrackDataFromExtensionEntries(extensionFlameChartEntries, extensionTrackData, entryToNode);
48+
extractPerformanceAPIExtensionEntries(mergedRawExtensionEvents);
49+
extractConsoleAPIExtensionEntries();
50+
// extensionTrackEntries is filled by the above two calls.
51+
Helpers.Trace.sortTraceEventsInPlace(extensionTrackEntries);
52+
Helpers.Extensions.buildTrackDataFromExtensionEntries(extensionTrackEntries, extensionTrackData, entryToNode);
53+
}
54+
55+
/**
56+
* Extracts extension entries from console.timeStamp events.
57+
*
58+
* Entries are built by pairing `console.timeStamp` events based on
59+
* their names. When a `console.timeStamp` event includes a `start`
60+
* argument (and optionally an `end` argument), it attempts to find
61+
* previously recorded `console.timeStamp` events with names matching
62+
* the `start` and `end` values. These matching events are then used to
63+
* determine the start and end times of the new entry.
64+
*
65+
* If a `console.timeStamp` event includes data for a custom track
66+
* (specified by the `track` argument), an extension track entry is
67+
* created and added to the `extensionTrackEntries` array. These entries
68+
* are used to visualize custom tracks in the Performance panel.
69+
*
70+
* If a `console.timeStamp` event includes data for a custom track
71+
* (specified by the `track` argument), an extension track entry is
72+
* created and added to the `extensionTrackEntries` array. These entries
73+
* are used to visualize custom tracks in the Performance panel.
74+
*
75+
* If a `console.timeStamp` event does not specify a custom track but
76+
* includes a start and/or end time (referencing other
77+
* `console.timeStamp` names), a synthetic console time stamp entry is
78+
* created and added to the `syntheticConsoleEntriesForTimingsTrack`
79+
* array. These entries are displayed in the "Timings" track.
80+
*/
81+
export function extractConsoleAPIExtensionEntries(): void {
82+
const consoleTimeStamps: readonly Types.Events.ConsoleTimeStamp[] = userTimingsData().timestampEvents;
83+
for (const currentTimeStamp of consoleTimeStamps) {
84+
const timeStampName = String(currentTimeStamp.args.data.name);
85+
timeStampByName.set(timeStampName, currentTimeStamp);
86+
const extensionData = extensionDataInConsoleTimeStamp(currentTimeStamp);
87+
const startName = currentTimeStamp.args.data.start;
88+
const endName = currentTimeStamp.args.data.end;
89+
if (!extensionData && !startName && !endName) {
90+
continue;
91+
}
92+
const startTimeStamp = startName ? timeStampByName.get(String(startName)) : undefined;
93+
const endTimeStamp = endName ? timeStampByName.get(String(endName)) : undefined;
94+
if (endTimeStamp && !startTimeStamp) {
95+
// Invalid data
96+
continue;
97+
}
98+
const entryStartTime = startTimeStamp?.ts ?? currentTimeStamp.ts;
99+
const entryEndTime = endTimeStamp?.ts ?? currentTimeStamp.ts;
100+
if (extensionData) {
101+
const unregisteredExtensionEntry: Omit<Types.Extensions.SyntheticExtensionTrackEntry, '_tag'> = {
102+
...currentTimeStamp,
103+
name: timeStampName,
104+
cat: 'devtools.extension',
105+
args: extensionData,
106+
rawSourceEvent: currentTimeStamp,
107+
dur: Types.Timing.MicroSeconds(entryEndTime - entryStartTime),
108+
ts: entryStartTime,
109+
};
110+
const extensionEntry =
111+
Helpers.SyntheticEvents.SyntheticEventsManager.getActiveManager()
112+
.registerSyntheticEvent<Types.Extensions.SyntheticExtensionTrackEntry>(unregisteredExtensionEntry);
113+
extensionTrackEntries.push(extensionEntry);
114+
continue;
115+
}
116+
// If no extension data is found in the entry (no custom track name
117+
// was passed), but the entry has a duration. we still save it here
118+
// to be added in the timings track. Note that timings w/o duration
119+
// and extension data are already handled by the UserTimingsHandler.
120+
const unregisteredSyntheticTimeStamp: Omit<Types.Events.SyntheticConsoleTimeStamp, '_tag'> = {
121+
...currentTimeStamp,
122+
name: timeStampName,
123+
cat: 'disabled-by-default-v8.inspector',
124+
ph: Types.Events.Phase.COMPLETE,
125+
ts: entryStartTime,
126+
dur: Types.Timing.MicroSeconds(entryEndTime - entryStartTime),
127+
rawSourceEvent: currentTimeStamp
128+
};
129+
const syntheticTimeStamp =
130+
Helpers.SyntheticEvents.SyntheticEventsManager.getActiveManager()
131+
.registerSyntheticEvent<Types.Events.SyntheticConsoleTimeStamp>(unregisteredSyntheticTimeStamp);
132+
syntheticConsoleEntriesForTimingsTrack.push(syntheticTimeStamp);
133+
}
44134
}
45135

46-
export function extractExtensionEntries(timings: (Types.Events.SyntheticUserTimingPair|Types.Events.PerformanceMark)[]):
47-
void {
136+
/**
137+
* Extracts extension entries from Performance API events (marks and
138+
* measures).
139+
* It specifically looks for events that contain extension-specific data
140+
* within their `detail` property.
141+
*
142+
* If an event's `detail` property can be parsed as a JSON object and
143+
* contains a `devtools` field with a valid extension payload, a
144+
* synthetic extension entry is created. The type of extension entry
145+
* created depends on the payload:
146+
*
147+
* - If the payload conforms to `ExtensionPayloadMarker`, a
148+
* `SyntheticExtensionMarker` is created and added to the
149+
* `extensionMarkers` array. These markers represent single points in
150+
* time.
151+
* - If the payload conforms to `ExtensionPayloadTrackEntry`, a
152+
* `SyntheticExtensionTrackEntry` is created and added to the
153+
* `extensionTrackEntries` array. These entries represent events with
154+
* a duration and are displayed on custom tracks in the Performance
155+
* panel.
156+
*
157+
* **Note:** Only events with a `detail` property that contains valid
158+
* extension data are processed. Other `performance.mark` and
159+
* `performance.measure` events are ignored.
160+
*
161+
* @param timings An array of `SyntheticUserTimingPair` or
162+
* `PerformanceMark` events, typically obtained from the
163+
* `UserTimingsHandler`.
164+
*/
165+
export function extractPerformanceAPIExtensionEntries(
166+
timings: (Types.Events.SyntheticUserTimingPair|Types.Events.PerformanceMark)[]): void {
48167
for (const timing of timings) {
49-
const extensionPayload = extensionDataInTiming(timing);
168+
const extensionPayload = extensionDataInPerformanceTiming(timing);
50169
if (!extensionPayload) {
51170
// Not an extension user timing.
52171
continue;
@@ -78,14 +197,15 @@ export function extractExtensionEntries(timings: (Types.Events.SyntheticUserTimi
78197
Helpers.SyntheticEvents.SyntheticEventsManager.getActiveManager()
79198
.registerSyntheticEvent<Types.Extensions.SyntheticExtensionTrackEntry>(
80199
extensionSyntheticEntry as Omit<Types.Extensions.SyntheticExtensionTrackEntry, '_tag'>);
81-
extensionFlameChartEntries.push(extensionTrackEntry);
200+
extensionTrackEntries.push(extensionTrackEntry);
82201
continue;
83202
}
84203
}
85204
}
86205

87-
export function extensionDataInTiming(timing: Types.Events.SyntheticUserTimingPair|
88-
Types.Events.PerformanceMark): Types.Extensions.ExtensionDataPayload|null {
206+
export function extensionDataInPerformanceTiming(timing: Types.Events.SyntheticUserTimingPair|
207+
Types.Events.PerformanceMark): Types.Extensions.ExtensionDataPayload|
208+
null {
89209
const timingDetail =
90210
Types.Events.isPerformanceMark(timing) ? timing.args.data?.detail : timing.args.data.beginEvent.args.detail;
91211
if (!timingDetail) {
@@ -112,12 +232,49 @@ export function extensionDataInTiming(timing: Types.Events.SyntheticUserTimingPa
112232
return null;
113233
}
114234
}
235+
/**
236+
* Extracts extension data from a `console.timeStamp` event.
237+
*
238+
* Checks if a `console.timeStamp` event contains data intended for
239+
* creating a custom track entry in the DevTools Performance panel. It
240+
* specifically looks for a `track` argument within the event's data.
241+
*
242+
* If a `track` argument is present (and not an empty string), the
243+
* function constructs an `ExtensionTrackEntryPayload` object containing
244+
* the track name, an optional color, an optional track group. This
245+
* payload is then used to create a `SyntheticExtensionTrackEntry`.
246+
*
247+
* **Note:** The `color` argument is optional and its type is validated
248+
* against a predefined palette (see
249+
* `ExtensionUI::extensionEntryColor`).
250+
*
251+
* @param timeStamp The `ConsoleTimeStamp` event to extract data from.
252+
* @return An `ExtensionTrackEntryPayload` object if the event contains
253+
* valid extension data for a track entry, or `null` otherwise.
254+
*/
255+
export function extensionDataInConsoleTimeStamp(timeStamp: Types.Events.ConsoleTimeStamp):
256+
Types.Extensions.ExtensionTrackEntryPayload|null {
257+
const trackName = timeStamp.args.data.track;
258+
if (trackName === '' || trackName === undefined) {
259+
return null;
260+
}
261+
return {
262+
// the color is defaulted to primary if it's value isn't one from
263+
// the defined palette (see ExtensionUI::extensionEntryColor) so
264+
// we don't need to check the value is valid here.
265+
color: String(timeStamp.args.data.color) as Types.Extensions.ExtensionTrackEntryPayload['color'],
266+
track: String(trackName),
267+
dataType: 'track-entry',
268+
trackGroup: timeStamp.args.data.trackGroup !== undefined ? String(timeStamp.args.data.trackGroup) : undefined
269+
};
270+
}
115271

116272
export function data(): ExtensionTraceData {
117273
return {
118274
entryToNode,
119-
extensionTrackData: [...extensionTrackData],
120-
extensionMarkers: [...extensionMarkers],
275+
extensionTrackData,
276+
extensionMarkers,
277+
syntheticConsoleEntriesForTimingsTrack,
121278
};
122279
}
123280

front_end/models/trace/types/Extensions.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5-
import type {Args, Event, PerformanceMark, PerformanceMeasureBegin, Phase, SyntheticBased} from './TraceEvents.js';
5+
import type {
6+
Args, ConsoleTimeStamp, Event, PerformanceMark, PerformanceMeasureBegin, Phase, SyntheticBased} from
7+
'./TraceEvents.js';
68

79
export type ExtensionEntryType = 'track-entry'|'marker';
810

@@ -58,7 +60,7 @@ export interface ExtensionMarkerPayload extends ExtensionDataPayloadBase {
5860
* Synthetic events created for extension tracks.
5961
*/
6062
export interface SyntheticExtensionTrackEntry extends
61-
SyntheticBased<Phase.COMPLETE, PerformanceMeasureBegin|PerformanceMark> {
63+
SyntheticBased<Phase.COMPLETE, PerformanceMeasureBegin|PerformanceMark|ConsoleTimeStamp> {
6264
args: Args&ExtensionTrackEntryPayload;
6365
}
6466

front_end/models/trace/types/TraceEvents.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1394,16 +1394,23 @@ export interface ConsoleTimeStamp extends Event {
13941394
ph: Phase.COMPLETE;
13951395
args: Args&{
13961396
data: ArgsData & {
1397+
// The console.timeStamp allows to pass integers as values as well
1398+
// as strings
13971399
name: string | number,
13981400
start?: string|number,
13991401
end?: string|number,
1400-
trackName?: string|number,
1402+
track?: string|number,
14011403
trackGroup?: string|number,
14021404
color?: string|number,
14031405
},
14041406
};
14051407
}
14061408

1409+
export interface SyntheticConsoleTimeStamp extends Event, SyntheticBased {
1410+
cat: 'disabled-by-default-v8.inspector';
1411+
ph: Phase.COMPLETE;
1412+
}
1413+
14071414
/** ChromeFrameReporter args for PipelineReporter event.
14081415
Matching proto: https://source.chromium.org/chromium/chromium/src/+/main:third_party/perfetto/protos/perfetto/trace/track_event/chrome_frame_reporter.proto
14091416
*/

front_end/panels/timeline/TimingsTrackAppender.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,9 @@ export class TimingsTrackAppender implements TrackAppender {
7272
appendTrackAtLevel(trackStartLevel: number, expanded?: boolean): number {
7373
const extensionMarkersAreEmpty = this.#extensionMarkers.length === 0;
7474
const performanceMarks = this.#parsedTrace.UserTimings.performanceMarks.filter(
75-
m => !Trace.Handlers.ModelHandlers.ExtensionTraceData.extensionDataInTiming(m));
75+
m => !Trace.Handlers.ModelHandlers.ExtensionTraceData.extensionDataInPerformanceTiming(m));
7676
const performanceMeasures = this.#parsedTrace.UserTimings.performanceMeasures.filter(
77-
m => !Trace.Handlers.ModelHandlers.ExtensionTraceData.extensionDataInTiming(m));
77+
m => !Trace.Handlers.ModelHandlers.ExtensionTraceData.extensionDataInPerformanceTiming(m));
7878
const timestampEvents = this.#parsedTrace.UserTimings.timestampEvents;
7979
const consoleTimings = this.#parsedTrace.UserTimings.consoleTimings;
8080

front_end/panels/timeline/track_appenders/ExtensionTrackAppender.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
// found in the LICENSE file.
44

55
import { // eslint-disable-line rulesdir/es-modules-import
6-
createTraceExtensionDataFromTestInput,
7-
type ExtensionTestData,
6+
createTraceExtensionDataFromPerformanceAPITestInput,
7+
type PerformanceAPIExtensionTestData,
88
} from '../../../models/trace/handlers/ExtensionTraceDataHandler.test.js';
99
import * as Trace from '../../../models/trace/trace.js';
1010
import {describeWithEnvironment} from '../../../testing/EnvironmentHelpers.js';
@@ -112,8 +112,8 @@ describeWithEnvironment('ExtensionTrackAppender', function() {
112112
ts: 100,
113113
dur: 100,
114114
},
115-
] as ExtensionTestData[];
116-
const traceExtensionData = await createTraceExtensionDataFromTestInput(extensionData);
115+
] as PerformanceAPIExtensionTestData[];
116+
const traceExtensionData = await createTraceExtensionDataFromPerformanceAPITestInput(extensionData);
117117
const testParsedTrace = getBaseTraceParseModelData({ExtensionTraceData: traceExtensionData});
118118
entryData = [];
119119
flameChartData = PerfUI.FlameChart.FlameChartTimelineData.createEmpty();

front_end/testing/TraceHelpers.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -707,6 +707,7 @@ export function getBaseTraceParseModelData(overrides: Partial<ParsedTrace> = {})
707707
entryToNode: new Map(),
708708
extensionMarkers: [],
709709
extensionTrackData: [],
710+
syntheticConsoleEntriesForTimingsTrack: [],
710711
},
711712
Frames: {
712713
frames: [],

0 commit comments

Comments
 (0)