@@ -288,6 +288,277 @@ describeWithEnvironment('SamplesHandler', function() {
288288 } ) ;
289289 } ) ;
290290
291+ describe ( 'profile source selection' , ( ) => {
292+ const pid = Trace . Types . Events . ProcessID ( 42 ) ;
293+ const tid = Trace . Types . Events . ThreadID ( 7 ) ;
294+
295+ type ProfileStreamEvent = Trace . Types . Events . Profile | Trace . Types . Events . ProfileChunk ;
296+
297+ function makeProfileEvent ( id : Trace . Types . Events . ProfileID , source : string ) : Trace . Types . Events . Profile {
298+ return {
299+ cat : '' ,
300+ name : Trace . Types . Events . Name . PROFILE ,
301+ ph : Trace . Types . Events . Phase . SAMPLE ,
302+ pid,
303+ tid,
304+ ts : Trace . Types . Timing . Micro ( 0 ) ,
305+ id,
306+ args : { data : { startTime : Trace . Types . Timing . Micro ( 0 ) , source : source as Trace . Types . Events . ProfileSource } } ,
307+ } ;
308+ }
309+
310+ function makeProfileChunkEvent (
311+ id : Trace . Types . Events . ProfileID ,
312+ source : string ,
313+ samples : number [ ] ,
314+ timeDeltas : number [ ] ,
315+ nodes : Array < { id : number , children : number [ ] } > =
316+ [
317+ { id : 0 , children : [ 1 ] } ,
318+ { id : 1 , children : [ ] } ,
319+ ] ,
320+ ) : Trace . Types . Events . ProfileChunk {
321+ return {
322+ cat : '' ,
323+ name : Trace . Types . Events . Name . PROFILE_CHUNK ,
324+ ph : Trace . Types . Events . Phase . SAMPLE ,
325+ pid,
326+ tid,
327+ ts : Trace . Types . Timing . Micro ( 1 ) ,
328+ id,
329+ args : {
330+ data : {
331+ source : source as Trace . Types . Events . ProfileSource ,
332+ cpuProfile : {
333+ samples : samples . map ( Trace . Types . Events . CallFrameID ) ,
334+ nodes : nodes . map ( n => ( {
335+ id : Trace . Types . Events . CallFrameID ( n . id ) ,
336+ children : n . children ,
337+ callFrame : { functionName : '' , scriptId : 0 , columnNumber : 0 , lineNumber : 0 , url : '' } ,
338+ } ) ) ,
339+ } ,
340+ timeDeltas : timeDeltas . map ( Trace . Types . Timing . Micro ) ,
341+ } ,
342+ } ,
343+ } ;
344+ }
345+
346+ function makeProfileStream ( {
347+ id,
348+ source,
349+ samples = [ 1 ] ,
350+ timeDeltas = [ 10 ] ,
351+ nodes,
352+ } : {
353+ id : Trace . Types . Events . ProfileID ,
354+ source : string ,
355+ samples ?: number [ ] ,
356+ timeDeltas ?: number [ ] ,
357+ nodes ?: Array < { id : number , children : number [ ] } > ,
358+ } ) : ProfileStreamEvent [ ] {
359+ const events : ProfileStreamEvent [ ] = [ ] ;
360+ events . push ( makeProfileEvent ( id , source ) ) ;
361+ events . push ( makeProfileChunkEvent ( id , source , samples , timeDeltas , nodes ?? [
362+ { id : 0 , children : [ 1 ] } ,
363+ { id : 1 , children : [ ] } ,
364+ ] ) ) ;
365+ return events ;
366+ }
367+
368+ async function runSelectionScenario ( events : ProfileStreamEvent [ ] , isCPUProfile = false ) {
369+ Trace . Handlers . ModelHandlers . Samples . reset ( ) ;
370+ Trace . Handlers . ModelHandlers . Meta . reset ( ) ;
371+
372+ for ( const event of events ) {
373+ Trace . Handlers . ModelHandlers . Samples . handleEvent ( event ) ;
374+ }
375+
376+ await Trace . Handlers . ModelHandlers . Meta . finalize ( ) ;
377+ await Trace . Handlers . ModelHandlers . Samples . finalize ( { isCPUProfile} ) ;
378+
379+ const profileByThread = Trace . Handlers . ModelHandlers . Samples . data ( ) . profilesInProcess . get ( pid ) ;
380+ assert . exists ( profileByThread ) ;
381+ const selected = profileByThread ?. get ( tid ) ;
382+ assert . exists ( selected ) ;
383+ return selected ! ;
384+ }
385+
386+ it ( 'selects Internal stream over SelfProfiling in performance trace' , async ( ) => {
387+ const internalId = Trace . Types . Events . ProfileID ( '0xE' ) ;
388+ const selfProfilingId = Trace . Types . Events . ProfileID ( '0xJ' ) ;
389+
390+ const selected = await runSelectionScenario ( [
391+ ...makeProfileStream ( {
392+ id : internalId ,
393+ source : 'Internal' ,
394+ } ) ,
395+ ...makeProfileStream ( {
396+ id : selfProfilingId ,
397+ source : 'SelfProfiling' ,
398+ } ) ,
399+ ] ) ;
400+
401+ assert . strictEqual ( selected . profileId , internalId ) ;
402+ } ) ;
403+
404+ it ( 'prefers Inspector stream over Internal in CPU profile mode' , async ( ) => {
405+ const inspectorId = Trace . Types . Events . ProfileID ( '0xD' ) ;
406+ const internalId = Trace . Types . Events . ProfileID ( '0xE' ) ;
407+
408+ const selected = await runSelectionScenario (
409+ [
410+ ...makeProfileStream ( {
411+ id : inspectorId ,
412+ source : 'Inspector' ,
413+ } ) ,
414+ ...makeProfileStream ( {
415+ id : internalId ,
416+ source : 'Internal' ,
417+ } ) ,
418+ ] ,
419+ true ) ;
420+
421+ assert . strictEqual ( selected . profileId , inspectorId ) ;
422+ } ) ;
423+
424+ it ( 'prefers Internal stream over Inspector in performance trace mode' , async ( ) => {
425+ const internalId = Trace . Types . Events . ProfileID ( '0xI' ) ;
426+ const inspectorId = Trace . Types . Events . ProfileID ( '0xD' ) ;
427+
428+ const selected = await runSelectionScenario ( [
429+ ...makeProfileStream ( {
430+ id : internalId ,
431+ source : 'Internal' ,
432+ } ) ,
433+ ...makeProfileStream ( {
434+ id : inspectorId ,
435+ source : 'Inspector' ,
436+ } ) ,
437+ ] ) ;
438+
439+ assert . strictEqual ( selected . profileId , internalId ) ;
440+ } ) ;
441+
442+ it ( 'selects complete priority order in CPU profile mode' , async ( ) => {
443+ const inspectorId = Trace . Types . Events . ProfileID ( '0xD' ) ;
444+ const internalId = Trace . Types . Events . ProfileID ( '0xE' ) ;
445+ const selfProfilingId = Trace . Types . Events . ProfileID ( '0xJ' ) ;
446+
447+ const selected = await runSelectionScenario (
448+ [
449+ ...makeProfileStream ( {
450+ id : selfProfilingId ,
451+ source : 'SelfProfiling' ,
452+ } ) ,
453+ ...makeProfileStream ( {
454+ id : internalId ,
455+ source : 'Internal' ,
456+ } ) ,
457+ ...makeProfileStream ( {
458+ id : inspectorId ,
459+ source : 'Inspector' ,
460+ } ) ,
461+ ] ,
462+ true ) ;
463+
464+ assert . strictEqual ( selected . profileId , inspectorId ) ;
465+ } ) ;
466+
467+ it ( 'selects complete priority order in performance trace mode' , async ( ) => {
468+ const inspectorId = Trace . Types . Events . ProfileID ( '0xD' ) ;
469+ const internalId = Trace . Types . Events . ProfileID ( '0xE' ) ;
470+ const selfProfilingId = Trace . Types . Events . ProfileID ( '0xJ' ) ;
471+
472+ const selected = await runSelectionScenario ( [
473+ ...makeProfileStream ( {
474+ id : selfProfilingId ,
475+ source : 'SelfProfiling' ,
476+ } ) ,
477+ ...makeProfileStream ( {
478+ id : inspectorId ,
479+ source : 'Inspector' ,
480+ } ) ,
481+ ...makeProfileStream ( {
482+ id : internalId ,
483+ source : 'Internal' ,
484+ } ) ,
485+ ] ) ;
486+
487+ assert . strictEqual ( selected . profileId , internalId ) ;
488+ } ) ;
489+
490+ it ( 'falls back to first candidate when no recognized sources exist' , async ( ) => {
491+ const firstUnknownId = Trace . Types . Events . ProfileID ( '0xU1' ) ;
492+ const secondUnknownId = Trace . Types . Events . ProfileID ( '0xU2' ) ;
493+
494+ const events : ProfileStreamEvent [ ] = [
495+ ...makeProfileStream ( {
496+ id : firstUnknownId ,
497+ source : 'Unspecified' ,
498+ } ) ,
499+ ...makeProfileStream ( {
500+ id : secondUnknownId ,
501+ source : 'CustomSource' ,
502+ } ) ,
503+ ] ;
504+
505+ const selected = await runSelectionScenario ( events ) ;
506+
507+ // Falls back to candidates[0] when no priority matches
508+ assert . strictEqual ( selected . profileId , firstUnknownId ) ;
509+ } ) ;
510+
511+ it ( 'ignores profiles with unknown sources in priority matching' , async ( ) => {
512+ const inspectorId = Trace . Types . Events . ProfileID ( '0xD' ) ;
513+ const unknownId = Trace . Types . Events . ProfileID ( '0xU' ) ;
514+
515+ const events : ProfileStreamEvent [ ] = [
516+ ...makeProfileStream ( {
517+ id : unknownId ,
518+ source : 'Unspecified' ,
519+ } ) ,
520+ ...makeProfileStream ( {
521+ id : inspectorId ,
522+ source : 'Inspector' ,
523+ } ) ,
524+ ] ;
525+
526+ const selected = await runSelectionScenario ( events , true ) ;
527+
528+ assert . strictEqual ( selected . profileId , inspectorId ) ;
529+ } ) ;
530+
531+ it ( 'falls back to SelfProfiling when higher priority sources absent' , async ( ) => {
532+ const selfProfilingId = Trace . Types . Events . ProfileID ( '0xJ' ) ;
533+
534+ const selected = await runSelectionScenario (
535+ [
536+ ...makeProfileStream ( {
537+ id : selfProfilingId ,
538+ source : 'SelfProfiling' ,
539+ } ) ,
540+ ] ,
541+ true ) ;
542+
543+ assert . strictEqual ( selected . profileId , selfProfilingId ) ;
544+ } ) ;
545+
546+ it ( 'selects available source even when not in priority list for CPU profile mode' , async ( ) => {
547+ const internalId = Trace . Types . Events . ProfileID ( '0xE' ) ;
548+
549+ const selected = await runSelectionScenario (
550+ [
551+ ...makeProfileStream ( {
552+ id : internalId ,
553+ source : 'Internal' ,
554+ } ) ,
555+ ] ,
556+ true ) ;
557+
558+ assert . strictEqual ( selected . profileId , internalId ) ;
559+ } ) ;
560+ } ) ;
561+
291562 describe ( 'getProfileCallFunctionName' , ( ) => {
292563 /**
293564 * Find an event from the trace that represents some work. The use of
0 commit comments