@@ -65,6 +65,7 @@ const SHORT_KEY_TO_TRANSFORM: { [string]: TransformType } = {};
65
65
'focus-category' ,
66
66
'merge-call-node' ,
67
67
'merge-function' ,
68
+ 'merge-unaccounted-native-functions' ,
68
69
'drop-function' ,
69
70
'collapse-resource' ,
70
71
'collapse-direct-recursion' ,
@@ -91,6 +92,9 @@ const SHORT_KEY_TO_TRANSFORM: { [string]: TransformType } = {};
91
92
case 'merge-function' :
92
93
shortKey = 'mf' ;
93
94
break ;
95
+ case 'merge-unaccounted-native-functions' :
96
+ shortKey = 'mu' ;
97
+ break ;
94
98
case 'drop-function' :
95
99
shortKey = 'df' ;
96
100
break ;
@@ -235,6 +239,12 @@ export function parseTransforms(transformString: string): TransformStack {
235
239
}
236
240
break ;
237
241
}
242
+ case 'merge-unaccounted-native-functions' : {
243
+ transforms . push ( {
244
+ type : 'merge-unaccounted-native-functions' ,
245
+ } ) ;
246
+ break ;
247
+ }
238
248
case 'focus-category' : {
239
249
// e.g. "fg-3"
240
250
const [ , categoryRaw ] = tuple ;
@@ -348,6 +358,8 @@ export function stringifyTransforms(transformStack: TransformStack): string {
348
358
case 'collapse-function-subtree' :
349
359
case 'focus-function' :
350
360
return `${ shortKey } -${ transform . funcIndex } ` ;
361
+ case 'merge-unaccounted-native-functions' :
362
+ return shortKey ;
351
363
case 'focus-category' :
352
364
return `${ shortKey } -${ transform . category } ` ;
353
365
case 'collapse-resource' :
@@ -432,6 +444,13 @@ export function getTransformLabelL10nIds(
432
444
}
433
445
}
434
446
447
+ if ( transform . type === 'merge-unaccounted-native-functions' ) {
448
+ return {
449
+ l10nId : 'TransformNavigator--merge-unaccounted-native-functions' ,
450
+ item : '' ,
451
+ } ;
452
+ }
453
+
435
454
// Lookup function name.
436
455
let funcIndex ;
437
456
switch ( transform . type ) {
@@ -517,6 +536,12 @@ export function applyTransformToCallNodePath(
517
536
return _mergeNodeInCallNodePath ( transform . callNodePath , callNodePath ) ;
518
537
case 'merge-function' :
519
538
return _mergeFunctionInCallNodePath ( transform . funcIndex , callNodePath ) ;
539
+ case 'merge-unaccounted-native-functions' :
540
+ return _mergeUnaccountedNativeFunctionsInCallNodePath (
541
+ callNodePath ,
542
+ transformedThread . funcTable ,
543
+ transformedThread . stringTable
544
+ ) ;
520
545
case 'drop-function' :
521
546
return _dropFunctionInCallNodePath ( transform . funcIndex , callNodePath ) ;
522
547
case 'collapse-resource' :
@@ -588,6 +613,38 @@ function _mergeFunctionInCallNodePath(
588
613
return callNodePath . filter ( ( nodeFunc ) => nodeFunc !== funcIndex ) ;
589
614
}
590
615
616
+ function _mergeUnaccountedNativeFunctionsInCallNodePath (
617
+ callNodePath : CallNodePath ,
618
+ funcTable : FuncTable ,
619
+ stringTable : UniqueStringArray
620
+ ) : CallNodePath {
621
+ return callNodePath . filter ( ( nodeFunc ) =>
622
+ isUnaccountedNativeFunction ( nodeFunc , funcTable , stringTable )
623
+ ) ;
624
+ }
625
+
626
+ // Returns true if funcIndex is probably a JIT frame outside of any known JIT
627
+ // address mappings.
628
+ export function isUnaccountedNativeFunction (
629
+ funcIndex : IndexIntoFuncTable ,
630
+ funcTable : FuncTable ,
631
+ stringTable : UniqueStringArray
632
+ ) : boolean {
633
+ if ( funcTable . resource [ funcIndex ] !== - 1 ) {
634
+ // This function has a resource. That means it's not "unaccounted".
635
+ return false ;
636
+ }
637
+ if ( funcTable . isJS [ funcIndex ] ) {
638
+ // This function is a JS function. That means it's not a "native" function.
639
+ return false ;
640
+ }
641
+ // Ok, so now we either have a native function without a library (otherwise it
642
+ // would have a "lib" resource), or we have a label frame. Assume that label
643
+ // frames don't start with "0x".
644
+ const locationString = stringTable . getString ( funcTable . name [ funcIndex ] ) ;
645
+ return locationString . startsWith ( '0x' ) ;
646
+ }
647
+
591
648
function _dropFunctionInCallNodePath (
592
649
funcIndex : IndexIntoFuncTable ,
593
650
callNodePath : CallNodePath
@@ -859,13 +916,92 @@ export function mergeFunction(
859
916
} ) ;
860
917
}
861
918
919
+ /**
920
+ * Returns a Uint8Array filled with zeros and ones.
921
+ * result[funcIndex] === 1 if and only if isUnaccountedNativeFunction(funcIndex)
922
+ */
923
+ function getUnaccountedNativeFunctions ( thread : Thread ) : Uint8Array {
924
+ const { funcTable, stringTable } = thread ;
925
+ const funcCount = funcTable . length ;
926
+ const isUnaccountedNativeFunctionArr = new Uint8Array ( funcCount ) ;
927
+ for ( let i = 0 ; i < funcCount ; i ++ ) {
928
+ if ( isUnaccountedNativeFunction ( i , funcTable , stringTable ) ) {
929
+ isUnaccountedNativeFunctionArr [ i ] = 1 ;
930
+ }
931
+ }
932
+ return isUnaccountedNativeFunctionArr ;
933
+ }
934
+
935
+ export function mergeUnaccountedNativeFunctions ( thread : Thread ) : Thread {
936
+ const isUnaccountedNativeFunctionArr = getUnaccountedNativeFunctions ( thread ) ;
937
+ return mergeFunctions ( thread , isUnaccountedNativeFunctionArr ) ;
938
+ }
939
+
940
+ export function mergeFunctions (
941
+ thread : Thread ,
942
+ shouldMergeFunction : Uint8Array
943
+ ) : Thread {
944
+ const { stackTable, frameTable } = thread ;
945
+
946
+ // A map oldStack -> newStack+1, implemented as a Uint32Array for performance.
947
+ // If newStack+1 is zero it means "null", i.e. this stack was filtered out.
948
+ // Typed arrays are initialized to zero, which we interpret as null.
949
+ //
950
+ // For each old stack, the new stack is computed as follows (with `oldStackFun`
951
+ // being the old stack's frame's function):
952
+ // - If shouldMergeFunction[oldStackFun] === 0, then the new stack is the same
953
+ // as the old stack.
954
+ // - If shouldMergeFunction[oldStackFun] === 1, then the new stack is the closest
955
+ // ancestor for which shouldMergeFunction[ancestorFunc] is 0, or null if no
956
+ // such ancestor exists.
957
+ //
958
+ // We only compute a new prefix column; the other columns are copied from the
959
+ // old stack table. The skipped stacks are "orphaned"; they'll still be present
960
+ // in the new stack table but not referenced by samples or other stacks.
961
+ const oldStackToNewStackPlusOne = new Uint32Array ( stackTable . length ) ;
962
+
963
+ const stackTableFrameCol = stackTable . frame ;
964
+ const frameTableFuncCol = frameTable . func ;
965
+ const oldPrefixCol = stackTable . prefix ;
966
+ const newPrefixCol = new Array ( stackTable . length ) ;
967
+
968
+ for ( let stackIndex = 0 ; stackIndex < stackTable . length ; stackIndex ++ ) {
969
+ const oldPrefix = oldPrefixCol [ stackIndex ] ;
970
+ const newPrefixPlusOne =
971
+ oldPrefix === null ? 0 : oldStackToNewStackPlusOne [ oldPrefix ] ;
972
+
973
+ const frameIndex = stackTableFrameCol [ stackIndex ] ;
974
+ const funcIndex = frameTableFuncCol [ frameIndex ] ;
975
+ if ( shouldMergeFunction [ funcIndex ] === 1 ) {
976
+ oldStackToNewStackPlusOne [ stackIndex ] = newPrefixPlusOne ;
977
+ } else {
978
+ oldStackToNewStackPlusOne [ stackIndex ] = stackIndex + 1 ;
979
+ }
980
+ const newPrefix = newPrefixPlusOne === 0 ? null : newPrefixPlusOne - 1 ;
981
+ newPrefixCol [ stackIndex ] = newPrefix ;
982
+ }
983
+
984
+ const newStackTable = {
985
+ ...stackTable ,
986
+ prefix : newPrefixCol ,
987
+ } ;
988
+
989
+ return updateThreadStacks ( thread , newStackTable , ( oldStack ) => {
990
+ if ( oldStack === null ) {
991
+ return null ;
992
+ }
993
+ const newStackPlusOne = oldStackToNewStackPlusOne [ oldStack ] ;
994
+ return newStackPlusOne === 0 ? null : newStackPlusOne - 1 ;
995
+ } ) ;
996
+ }
997
+
862
998
/**
863
999
* Drop any samples that contain the given function.
864
1000
*/
865
1001
export function dropFunction (
866
1002
thread : Thread ,
867
1003
funcIndexToDrop : IndexIntoFuncTable
868
- ) {
1004
+ ) : Thread {
869
1005
const { stackTable, frameTable } = thread ;
870
1006
871
1007
// Go through each stack, and label it as containing the function or not.
@@ -1774,6 +1910,8 @@ export function applyTransform(
1774
1910
) ;
1775
1911
case 'merge-function' :
1776
1912
return mergeFunction ( thread , transform . funcIndex ) ;
1913
+ case 'merge-unaccounted-native-functions' :
1914
+ return mergeUnaccountedNativeFunctions ( thread ) ;
1777
1915
case 'drop-function' :
1778
1916
return dropFunction ( thread , transform . funcIndex ) ;
1779
1917
case 'focus-function' :
0 commit comments