@@ -7,7 +7,7 @@ import type * as CPUProfile from '../../cpu_profile/cpu_profile.js';
77import * as Types from '../types/types.js' ;
88
99import { milliToMicro } from './Timing.js' ;
10- import { makeProfileCall , mergeEventsInOrder } from './Trace.js' ;
10+ import { extractSampleTraceId , makeProfileCall , mergeEventsInOrder } from './Trace.js' ;
1111
1212/**
1313 * This is a helper that integrates CPU profiling data coming in the
@@ -70,6 +70,12 @@ export class SamplesIntegrator {
7070 * in the stack before the event came.
7171 */
7272 #lockedJsStackDepth: number [ ] = [ ] ;
73+ /**
74+ * For samples with a trace id, creates a profile call and keeps it
75+ * in a record keyed by that id. The value is typed as an union with
76+ * undefined to avoid nullish accesses when a key is not present.
77+ */
78+ #callsByTraceIds: Record < number , Types . Events . SyntheticProfileCall | undefined > = { } ;
7379 /**
7480 * Used to keep track when samples should be integrated even if they
7581 * are not children of invocation trace events. This is useful in
@@ -243,11 +249,18 @@ export class SamplesIntegrator {
243249 if ( ! node ) {
244250 continue ;
245251 }
252+ const maybeTraceId = this . #profileModel. traceIds ?. [ i ] ;
246253 const call = makeProfileCall ( node , this . #profileId, i , timestamp , this . #processId, this . #threadId) ;
247- calls . push ( call ) ;
248-
254+ // Separate samples with trace ids so that they are only used when
255+ // processing the owner event.
256+ if ( maybeTraceId === undefined ) {
257+ calls . push ( call ) ;
258+ } else {
259+ this . #callsByTraceIds[ maybeTraceId ] = call ;
260+ }
249261 if ( debugModeEnabled ) {
250- this . jsSampleEvents . push ( this . #makeJSSampleEvent( call , timestamp ) ) ;
262+ const traceId = this . #profileModel. traceIds ?. [ i ] ;
263+ this . jsSampleEvents . push ( this . #makeJSSampleEvent( call , timestamp , traceId ) ) ;
251264 }
252265 if ( node . id === this . #profileModel. gcNode ?. id && prevNode ) {
253266 // GC samples have no stack, so we just put GC node on top of the
@@ -261,7 +274,23 @@ export class SamplesIntegrator {
261274 return calls ;
262275 }
263276
264- #makeProfileCallsForStack( profileCall : Types . Events . SyntheticProfileCall ) : Types . Events . SyntheticProfileCall [ ] {
277+ /**
278+ * Given a synthetic profile call, returns an array of profile calls
279+ * representing the stack trace that profile call belongs to based on
280+ * its nodeId. The input profile call will be at the top of the
281+ * returned stack (last position), meaning that any other frames that
282+ * were effectively above it are omitted.
283+ * @param profileCall
284+ * @param overrideTimeStamp a custom timestamp to use for the returned
285+ * profile calls. If not defined, the timestamp of the input
286+ * profileCall is used instead. This param is useful for example when
287+ * creating the profile calls for a sample with a trace id, since the
288+ * timestamp of the corresponding trace event should be used instead
289+ * of the sample's.
290+ */
291+
292+ #makeProfileCallsForStack( profileCall : Types . Events . SyntheticProfileCall , overrideTimeStamp ?: Types . Timing . Micro ) :
293+ Types . Events . SyntheticProfileCall [ ] {
265294 let node = this . #profileModel. nodeById ( profileCall . nodeId ) ;
266295 const isGarbageCollection = node ?. id === this . #profileModel. gcNode ?. id ;
267296 if ( isGarbageCollection ) {
@@ -286,7 +315,8 @@ export class SamplesIntegrator {
286315 // durations
287316 while ( node ) {
288317 callFrames [ i -- ] = makeProfileCall (
289- node , profileCall . profileId , profileCall . sampleIndex , profileCall . ts , this . #processId, this . #threadId) ;
318+ node , profileCall . profileId , profileCall . sampleIndex , overrideTimeStamp ?? profileCall . ts , this . #processId,
319+ this . #threadId) ;
290320 node = node . parent ;
291321 }
292322 return callFrames ;
@@ -296,7 +326,18 @@ export class SamplesIntegrator {
296326 * Update tracked stack using this event's call stack.
297327 */
298328 #extractStackTrace( event : Types . Events . Event ) : void {
299- const stackTrace = Types . Events . isProfileCall ( event ) ? this . #makeProfileCallsForStack( event ) : this . #currentJSStack;
329+ let stackTrace = this . #currentJSStack;
330+ if ( Types . Events . isProfileCall ( event ) ) {
331+ stackTrace = this . #makeProfileCallsForStack( event ) ;
332+ }
333+ const traceId = extractSampleTraceId ( event ) ;
334+ if ( traceId !== null ) {
335+ const maybeCallForTraceId = this . #callsByTraceIds[ traceId ] ;
336+ if ( maybeCallForTraceId ) {
337+ stackTrace = this . #makeProfileCallsForStack( maybeCallForTraceId , event . ts ) ;
338+ }
339+ }
340+
300341 SamplesIntegrator . filterStackFrames ( stackTrace , this . #engineConfig) ;
301342
302343 const endTime = event . ts + ( event . dur || 0 ) ;
@@ -391,13 +432,13 @@ export class SamplesIntegrator {
391432 this . #currentJSStack. length = depth ;
392433 }
393434
394- #makeJSSampleEvent( call : Types . Events . SyntheticProfileCall , timestamp : Types . Timing . Micro ) :
435+ #makeJSSampleEvent( call : Types . Events . SyntheticProfileCall , timestamp : Types . Timing . Micro , traceId ?: number ) :
395436 Types . Events . SyntheticJSSample {
396437 const JSSampleEvent : Types . Events . SyntheticJSSample = {
397438 name : Types . Events . Name . JS_SAMPLE ,
398439 cat : 'devtools.timeline' ,
399440 args : {
400- data : { stackTrace : this . #makeProfileCallsForStack( call ) . map ( e => e . callFrame ) } ,
441+ data : { traceId , stackTrace : this . #makeProfileCallsForStack( call ) . map ( e => e . callFrame ) } ,
401442 } ,
402443 ph : Types . Events . Phase . INSTANT ,
403444 ts : timestamp ,
0 commit comments