@@ -492,200 +492,6 @@ exports.allocationTracker = function ({
492492 return ChromeUtils . saveHeapSnapshot ( { debugger : dbg } ) ;
493493 } ,
494494
495- /**
496- * Print information about why a list of objects are being held in memory.
497- *
498- * @param Array<NodeId> objects
499- * List of NodeId's of objects to debug. NodeIds can be retrieved
500- * via ChromeUtils.getObjectNodeId.
501- * @param String snapshotFile
502- * Absolute path to a Heap snapshot file retrieved via this.getSnapshotFile.
503- * This is used to trace content process objects. We have to record the snapshot
504- * from the content process, but can only read it from the parent process because
505- * of I/O restrictions in content processes.
506- */
507- traceObjects ( objects , snapshotFile ) {
508- // There is no API to get the heap snapshot at runtime,
509- // the only way is to save it to disk and then load it from disk
510- if ( ! snapshotFile ) {
511- snapshotFile = this . getSnapshotFile ( ) ;
512- }
513- const snapshot = ChromeUtils . readHeapSnapshot ( snapshotFile ) ;
514-
515- function getObjectDescription ( id , prefix = 0 ) {
516- prefix = " " . repeat ( prefix ) ;
517- if ( ! id ) {
518- return prefix + "<null>" ;
519- }
520- try {
521- let stack = [ ...snapshot . describeNode ( { by : "allocationStack" } , id ) ] ;
522- if ( stack ) {
523- stack = stack . find ( ( [ src ] ) => src != "noStack" ) ;
524- if ( stack ) {
525- const { line, column, source } = stack [ 0 ] ;
526- if ( source ) {
527- const lines = getFileContent ( source ) ;
528- const lineBefore = lines [ line - 2 ] ;
529- const lineText = lines [ line - 1 ] ;
530- const lineAfter = lines [ line ] ;
531- const filename = source . substr ( source . lastIndexOf ( "/" ) + 1 ) ;
532-
533- stack = "allocated at " + source + ":\n" ;
534- // Print one line before and after for context
535- if ( lineBefore . trim ( ) . length ) {
536- stack += prefix + ` ${ filename } @ ${ line - 1 } \u007C` ;
537- stack += "\x1b[2m" + lineBefore + "\n" ;
538- }
539- stack += prefix + ` ${ filename } @ ${ line } > \u007C` ;
540- // Grey out the beginning of the line, before frame's column,
541- // and display an arrow before displaying the rest of the line.
542- stack +=
543- "\x1b[2m" +
544- lineText . substr ( 0 , column - 1 ) +
545- "\x1b[0m" +
546- "\u21A6 " +
547- lineText . substr ( column - 1 ) +
548- "\n" ;
549- if ( lineAfter . trim ( ) . length ) {
550- stack += prefix + ` ${ filename } @ ${ line + 1 } \u007C` ;
551- stack += lineAfter ;
552- }
553- } else {
554- stack = "(missing source)" ;
555- }
556- } else {
557- stack = "(without allocation stack)" ;
558- }
559- } else {
560- stack = "(without description)" ;
561- }
562- let objectClass = Object . entries (
563- snapshot . describeNode ( { by : "objectClass" } , id )
564- ) [ 0 ] [ 0 ] ;
565- if ( objectClass == "other" ) {
566- objectClass = Object . entries (
567- snapshot . describeNode ( { by : "internalType" } , id )
568- ) [ 0 ] [ 0 ] ;
569- }
570- const arrow = prefix > 0 ? "\\--> " : "" ;
571- return prefix + arrow + objectClass + " " + stack ;
572- } catch ( e ) {
573- if ( e . name == "NS_ERROR_ILLEGAL_VALUE" ) {
574- return (
575- prefix + "<not-in-memory-snapshot:is-from-untracked-global?>"
576- ) ;
577- }
578- return prefix + "<invalid:" + id + ":" + e + ">" ;
579- }
580- }
581-
582- const fileContents = new Map ( ) ;
583-
584- function getFileContent ( url ) {
585- let content = fileContents . get ( url ) ;
586- if ( content ) {
587- return content ;
588- }
589- content = readURI ( url ) . split ( "\n" ) ;
590- fileContents . set ( url , content ) ;
591- return content ;
592- }
593-
594- function readURI ( uri ) {
595- const { NetUtil } = ChromeUtils . importESModule (
596- "resource://gre/modules/NetUtil.sys.mjs" ,
597- { global : "contextual" }
598- ) ;
599- const stream = NetUtil . newChannel ( {
600- uri : NetUtil . newURI ( uri , "UTF-8" ) ,
601- loadUsingSystemPrincipal : true ,
602- } ) . open ( ) ;
603- const count = stream . available ( ) ;
604- const data = NetUtil . readInputStreamToString ( stream , count , {
605- charset : "UTF-8" ,
606- } ) ;
607-
608- stream . close ( ) ;
609- return data ;
610- }
611-
612- function printPath ( src , dst ) {
613- let paths ;
614- try {
615- paths = snapshot . computeShortestPaths ( src , [ dst ] , 10 ) ;
616- } catch ( e ) { }
617- if ( paths && paths . has ( dst ) ) {
618- let pathLength = Infinity ;
619- let n = 0 ;
620- for ( const path of paths . get ( dst ) ) {
621- n ++ ;
622- // Only print the smaller paths.
623- // The longer ones will only repeat the smaller ones, with some extra edges.
624- if ( path . length > pathLength + 1 ) {
625- continue ;
626- }
627- pathLength = path . length ;
628- logTracker (
629- `Path #${ n } :\n` +
630- path
631- . map ( ( { predecessor, edge } , i ) => {
632- return (
633- getObjectDescription ( predecessor , i ) +
634- "\n" +
635- " " . repeat ( i ) +
636- "Holds the following object via '" +
637- edge +
638- "' attribute:\n"
639- ) ;
640- } )
641- . join ( "" ) +
642- getObjectDescription ( dst , path . length )
643- ) ;
644- }
645- } else {
646- logTracker ( "NO-PATH" ) ;
647- }
648- }
649-
650- const tree = snapshot . computeDominatorTree ( ) ;
651- for ( const objectNodeId of objects ) {
652- logTracker ( " # Tracing object #" + objectNodeId + "\n" ) ;
653-
654- // Print the path from the global object down to leaked object.
655- // This print the allocation site of each object which has a reference
656- // to another object, ultimately leading to our leaked object.
657- logTracker ( "### Path(s) from root:" ) ;
658- printPath ( tree . root , objectNodeId ) ;
659-
660- /**
661- * This happens to be redundant with printPath, but printed the other way around.
662- *
663- // Print the dominators.
664- // i.e. from the leaked object, print all parent objects whichs
665- // keeps a reference to the previous object, up to a global object.
666- logTracker("### Dominators:");
667- let node = objectNodeId,
668- logTracker(" " + getObjectDescription(node));
669- while ((node = tree.getImmediateDominator(node))) {
670- logTracker(" ^-- " + getObjectDescription(node));
671- }
672- */
673-
674- /**
675- * In case you are not able to figure out what the object is.
676- * This will print all what it keeps allocated,
677- * kinds of list of attributes
678- *
679- logTracker("### Dominateds:");
680- node = objectNodeId,
681- logTracker(" " + getObjectDescription(node));
682- for (const n of tree.getImmediatelyDominated(objectNodeId)) {
683- logTracker(" --> " + getObjectDescription(n));
684- }
685- */
686- }
687- } ,
688-
689495 stop ( ) {
690496 logTracker ( "Stop logging allocations" ) ;
691497 dbg . onNewGlobalObject = undefined ;
0 commit comments