cc-analyzer.sys.mjs (3813B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this file, 3 * You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 import { setTimeout } from "resource://gre/modules/Timer.sys.mjs"; 6 7 /** 8 * Helper class to retrieve CC/GC Logs via nsICycleCollectorListener interface. 9 */ 10 export class CCAnalyzer { 11 clear() { 12 this.processingCount = 0; 13 this.graph = {}; 14 this.roots = []; 15 this.garbage = []; 16 this.edges = []; 17 this.listener = null; 18 this.count = 0; 19 } 20 21 /** 22 * Run the analyzer by running the CC/GC, which would allow use 23 * to call nsICycleCollectorListener.processNext() 24 * which would call nsICycleCollectorListener.{noteRefCountedObject,noteGCedObject,noteEdge}. 25 * 26 * @param {boolean} wantAllTraces 27 * See nsICycleCollectorListener.allTraces() jsdoc. 28 */ 29 async run(wantAllTraces = false) { 30 this.clear(); 31 32 // Instantiate and configure the CC logger 33 this.listener = Cu.createCCLogger(); 34 if (wantAllTraces) { 35 dump("CC Analyzer >> all traces!\n"); 36 this.listener = this.listener.allTraces(); 37 } 38 39 this.listener.disableLog = true; 40 this.listener.wantAfterProcessing = true; 41 42 // Register the CC logger 43 Cu.forceCC(this.listener); 44 45 // Process the entire heap step by step in 10K chunks 46 let done = false; 47 while (!done) { 48 for (let i = 0; i < 10000; i++) { 49 if (!this.listener.processNext(this)) { 50 done = true; 51 break; 52 } 53 } 54 dump("Process CC/GC logs " + this.count + "\n"); 55 // Process next chunk after an event loop to avoid freezing the process 56 await new Promise(resolve => setTimeout(resolve, 0)); 57 } 58 await new Promise(resolve => setTimeout(resolve, 0)); 59 dump("Done!\n"); 60 } 61 62 noteRefCountedObject(address, refCount, objectDescription) { 63 const o = this.ensureObject(address); 64 o.address = address; 65 o.refcount = refCount; 66 o.name = objectDescription; 67 } 68 69 noteGCedObject(address, marked, objectDescription, compartmentAddr) { 70 const o = this.ensureObject(address); 71 o.address = address; 72 o.gcmarked = marked; 73 o.name = objectDescription; 74 o.compartment = compartmentAddr; 75 } 76 77 noteEdge(fromAddress, toAddress, edgeName) { 78 const fromObject = this.ensureObject(fromAddress); 79 const toObject = this.ensureObject(toAddress); 80 fromObject.edges.push({ name: edgeName, to: toObject }); 81 toObject.owners.push({ name: edgeName, from: fromObject }); 82 83 this.edges.push({ 84 name: edgeName, 85 from: fromObject, 86 to: toObject, 87 }); 88 } 89 90 describeRoot(address, knownEdges) { 91 const o = this.ensureObject(address); 92 o.root = true; 93 o.knownEdges = knownEdges; 94 this.roots.push(o); 95 } 96 97 describeGarbage(address) { 98 const o = this.ensureObject(address); 99 o.garbage = true; 100 this.garbage.push(o); 101 } 102 103 ensureObject(address) { 104 if (!this.graph[address]) { 105 this.count++; 106 this.graph[address] = new CCObject(); 107 } 108 109 return this.graph[address]; 110 } 111 112 find(text) { 113 const result = []; 114 for (const address in this.graph) { 115 const o = this.graph[address]; 116 if (!o.garbage && o.name.includes(text)) { 117 result.push(o); 118 } 119 } 120 return result; 121 } 122 123 findNotJS() { 124 const result = []; 125 for (const address in this.graph) { 126 const o = this.graph[address]; 127 if (!o.garbage && o.name.indexOf("JS") != 0) { 128 result.push(o); 129 } 130 } 131 return result; 132 } 133 } 134 135 class CCObject { 136 constructor() { 137 this.name = ""; 138 this.address = null; 139 this.refcount = 0; 140 this.gcmarked = false; 141 this.root = false; 142 this.garbage = false; 143 this.knownEdges = 0; 144 this.edges = []; 145 this.owners = []; 146 } 147 }