@@ -20,7 +20,6 @@ import type {
2020} from 'firefox-profiler/types' ;
2121
2222import {
23- defaultThreadOrder ,
2423 getFriendlyThreadName ,
2524 computeStackTableFromRawStackTable ,
2625} from './profile-data' ;
@@ -167,14 +166,60 @@ function _getDefaultLocalTrackOrder(tracks: LocalTrack[], profile: ?Profile) {
167166 return trackOrder ;
168167}
169168
170- function _getDefaultGlobalTrackOrder ( tracks : GlobalTrack [ ] ) {
169+ function _getDefaultGlobalTrackOrder (
170+ tracks : GlobalTrack [ ] ,
171+ threadActivityScores : Array < ThreadActivityScore >
172+ ) {
171173 const trackOrder = tracks . map ( ( _ , index ) => index ) ;
174+
172175 // In place sort!
173- trackOrder . sort (
174- ( a , b ) =>
175- GLOBAL_TRACK_DISPLAY_ORDER [ tracks [ a ] . type ] -
176- GLOBAL_TRACK_DISPLAY_ORDER [ tracks [ b ] . type ]
177- ) ;
176+ trackOrder . sort ( ( a , b ) => {
177+ const trackA = tracks [ a ] ;
178+ const trackB = tracks [ b ] ;
179+
180+ // First, sort by track type priority (visual progress, screenshots, then process).
181+ const typeOrderA = GLOBAL_TRACK_DISPLAY_ORDER [ trackA . type ] ;
182+ const typeOrderB = GLOBAL_TRACK_DISPLAY_ORDER [ trackB . type ] ;
183+
184+ if ( typeOrderA !== typeOrderB ) {
185+ return typeOrderA - typeOrderB ;
186+ }
187+
188+ if ( trackA . type !== 'process' || trackB . type !== 'process' ) {
189+ // For all the cases where both of them are not the process type, return zero.
190+ return 0 ;
191+ }
192+
193+ // This is the case where both of the tracks are processes. Let's sort them
194+ // by activity while keeping the parent process at the top.
195+ // mainThreadIndex might be null in case the GeckoMain thread is not
196+ // profiled in a profile.
197+ const activityA =
198+ trackA . mainThreadIndex !== null
199+ ? threadActivityScores [ trackA . mainThreadIndex ]
200+ : null ;
201+ const activityB =
202+ trackB . mainThreadIndex !== null
203+ ? threadActivityScores [ trackB . mainThreadIndex ]
204+ : null ;
205+
206+ // Keep the parent process at the top.
207+ if ( activityA ?. isInParentProcess && ! activityB ?. isInParentProcess ) {
208+ return - 1 ;
209+ }
210+ if ( ! activityA ?. isInParentProcess && activityB ?. isInParentProcess ) {
211+ return 1 ;
212+ }
213+
214+ // For non-parent processes, sort by activity score.
215+ if ( activityA && activityB ) {
216+ return activityB . boostedSampleScore - activityA . boostedSampleScore ;
217+ }
218+
219+ // For all other cases, maintain original order.
220+ return 0 ;
221+ } ) ;
222+
178223 return trackOrder ;
179224}
180225
@@ -646,7 +691,8 @@ export function initializeGlobalTrackOrder(
646691 urlGlobalTrackOrder : TrackIndex [ ] | null ,
647692 // If viewing an old profile URL, there were not tracks, only thread indexes. Turn
648693 // the legacy ordering into track ordering.
649- legacyThreadOrder : ThreadIndex [ ] | null
694+ legacyThreadOrder : ThreadIndex [ ] | null ,
695+ threadActivityScores : Array < ThreadActivityScore >
650696) : TrackIndex [ ] {
651697 if ( legacyThreadOrder !== null ) {
652698 // Upgrade an older URL value based on the thread index to the track index based
@@ -692,18 +738,23 @@ export function initializeGlobalTrackOrder(
692738 return urlGlobalTrackOrder !== null &&
693739 _indexesAreValid ( globalTracks . length , urlGlobalTrackOrder )
694740 ? urlGlobalTrackOrder
695- : _getDefaultGlobalTrackOrder ( globalTracks ) ;
741+ : _getDefaultGlobalTrackOrder ( globalTracks , threadActivityScores ) ;
696742}
697743
698744// Returns the selected thread (set), intersected with the set of visible threads.
699745// Falls back to the default thread selection.
700746export function initializeSelectedThreadIndex (
701747 selectedThreadIndexes : Set < ThreadIndex > | null ,
702748 visibleThreadIndexes : ThreadIndex [ ] ,
703- profile : Profile
749+ profile : Profile ,
750+ threadActivityScores : Array < ThreadActivityScore >
704751) : Set < ThreadIndex > {
705752 if ( selectedThreadIndexes === null ) {
706- return getDefaultSelectedThreadIndexes ( visibleThreadIndexes , profile ) ;
753+ return getDefaultSelectedThreadIndexes (
754+ visibleThreadIndexes ,
755+ profile ,
756+ threadActivityScores
757+ ) ;
707758 }
708759
709760 // Filter out hidden threads from the set of selected threads.
@@ -713,16 +764,24 @@ export function initializeSelectedThreadIndex(
713764 ) ;
714765 if ( visibleSelectedThreadIndexes . size === 0 ) {
715766 // No selected threads were visible. Fall back to default selection.
716- return getDefaultSelectedThreadIndexes ( visibleThreadIndexes , profile ) ;
767+ return getDefaultSelectedThreadIndexes (
768+ visibleThreadIndexes ,
769+ profile ,
770+ threadActivityScores
771+ ) ;
717772 }
718773 return visibleSelectedThreadIndexes ;
719774}
720775
721- // Select either the GeckoMain [tab] thread, or the first thread in the thread
722- // order.
776+ // Select either the most active GeckoMain [tab] thread, or the most active
777+ // thread sorted by the thread activity scores.
778+ // It always selects global tracks when there is a GeckoMain [tab], but when
779+ // there is no GeckoMain [tab], it might select local tracks too depending
780+ // on the activity score.
723781function getDefaultSelectedThreadIndexes (
724782 visibleThreadIndexes : ThreadIndex [ ] ,
725- profile : Profile
783+ profile : Profile ,
784+ threadActivityScores : Array < ThreadActivityScore >
726785) : Set < ThreadIndex > {
727786 if ( profile . meta . initialSelectedThreads !== undefined ) {
728787 return new Set (
@@ -740,17 +799,83 @@ function getDefaultSelectedThreadIndexes(
740799 } )
741800 ) ;
742801 }
743- const visibleThreads = visibleThreadIndexes . map (
744- ( threadIndex ) => profile . threads [ threadIndex ]
745- ) ;
746- const defaultThread = _findDefaultThread ( visibleThreads ) ;
747- const defaultThreadIndex = profile . threads . indexOf ( defaultThread ) ;
748- if ( defaultThreadIndex === - 1 ) {
802+
803+ const { threads } = profile ;
804+ if ( threads . length === 0 ) {
749805 throw new Error ( 'Expected to find a thread index to select.' ) ;
750806 }
807+
808+ const threadOrder = _defaultThreadOrder (
809+ visibleThreadIndexes ,
810+ threads ,
811+ threadActivityScores
812+ ) ;
813+
814+ // Try to find a tab process with the highest activity score. If it can't
815+ // find one, select the first thread with the highest one.
816+ const defaultThreadIndex =
817+ threadOrder . find (
818+ ( threadIndex ) =>
819+ threads [ threadIndex ] . name === 'GeckoMain' &&
820+ threads [ threadIndex ] . processType === 'tab'
821+ ) ?? threadOrder [ 0 ] ;
822+
751823 return new Set ( [ defaultThreadIndex ] ) ;
752824}
753825
826+ function _defaultThreadOrder (
827+ visibleThreadIndexes : ThreadIndex [ ] ,
828+ threads : RawThread [ ] ,
829+ threadActivityScores : Array < ThreadActivityScore >
830+ ) : ThreadIndex [ ] {
831+ const threadOrder = [ ...visibleThreadIndexes ] ;
832+
833+ // Note: to have a consistent behavior independant of the sorting algorithm,
834+ // we need to be careful that the comparator function is consistent:
835+ // comparator(a, b) === - comparator(b, a)
836+ // and
837+ // comparator(a, b) === 0 if and only if a === b
838+ threadOrder . sort ( ( a , b ) => {
839+ const nameA = threads [ a ] . name ;
840+ const nameB = threads [ b ] . name ;
841+
842+ if ( nameA === nameB ) {
843+ // Sort by the activity, but keep the original order if the activity
844+ // scores are the equal.
845+ return (
846+ threadActivityScores [ b ] . boostedSampleScore -
847+ threadActivityScores [ a ] . boostedSampleScore || a - b
848+ ) ;
849+ }
850+
851+ // Put the compositor/renderer thread last.
852+ // Compositor will always be before Renderer, if both are present.
853+ if ( nameA === 'Compositor' ) {
854+ return 1 ;
855+ }
856+
857+ if ( nameB === 'Compositor' ) {
858+ return - 1 ;
859+ }
860+
861+ if ( nameA === 'Renderer' ) {
862+ return 1 ;
863+ }
864+
865+ if ( nameB === 'Renderer' ) {
866+ return - 1 ;
867+ }
868+
869+ // Otherwise keep the existing order. We don't return 0 to guarantee that
870+ // the sort is stable even if the sort algorithm isn't.
871+ return (
872+ threadActivityScores [ b ] . boostedSampleScore -
873+ threadActivityScores [ a ] . boostedSampleScore || a - b
874+ ) ;
875+ } ) ;
876+ return threadOrder ;
877+ }
878+
754879// Returns either a configuration of hidden tracks that has at least one
755880// visible thread, or null.
756881export function tryInitializeHiddenTracksLegacy (
@@ -1264,20 +1389,6 @@ function _computeThreadSampleScore(
12641389 return nonIdleSampleCount * referenceCPUDeltaPerInterval ;
12651390}
12661391
1267- function _findDefaultThread ( threads : RawThread [ ] ) : RawThread | null {
1268- if ( threads . length === 0 ) {
1269- // Tests may have no threads.
1270- return null ;
1271- }
1272- const contentThreadId = threads . findIndex (
1273- ( thread ) => thread . name === 'GeckoMain' && thread . processType === 'tab'
1274- ) ;
1275- const defaultThreadIndex =
1276- contentThreadId !== - 1 ? contentThreadId : defaultThreadOrder ( threads ) [ 0 ] ;
1277-
1278- return threads [ defaultThreadIndex ] ;
1279- }
1280-
12811392function _indexesAreValid ( listLength : number , indexes : number [ ] ) {
12821393 return (
12831394 // The item length is valid.
0 commit comments