Source code
Revision control
Copy as Markdown
Other Tools
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
import { setTimeout } from "resource://gre/modules/Timer.sys.mjs";
/**
* Helper class to retrieve CC/GC Logs via nsICycleCollectorListener interface.
*/
export class CCAnalyzer {
clear() {
this.processingCount = 0;
this.graph = {};
this.roots = [];
this.garbage = [];
this.edges = [];
this.listener = null;
this.count = 0;
}
/**
* Run the analyzer by running the CC/GC, which would allow use
* to call nsICycleCollectorListener.processNext()
* which would call nsICycleCollectorListener.{noteRefCountedObject,noteGCedObject,noteEdge}.
*
* @param {Boolean} wantAllTraces
* See nsICycleCollectorListener.allTraces() jsdoc.
*/
async run(wantAllTraces = false) {
this.clear();
// Instantiate and configure the CC logger
this.listener = Cu.createCCLogger();
if (wantAllTraces) {
dump("CC Analyzer >> all traces!\n");
this.listener = this.listener.allTraces();
}
this.listener.disableLog = true;
this.listener.wantAfterProcessing = true;
// Register the CC logger
Cu.forceCC(this.listener);
// Process the entire heap step by step in 10K chunks
let done = false;
while (!done) {
for (let i = 0; i < 10000; i++) {
if (!this.listener.processNext(this)) {
done = true;
break;
}
}
dump("Process CC/GC logs " + this.count + "\n");
// Process next chunk after an event loop to avoid freezing the process
await new Promise(resolve => setTimeout(resolve, 0));
}
await new Promise(resolve => setTimeout(resolve, 0));
dump("Done!\n");
}
noteRefCountedObject(address, refCount, objectDescription) {
const o = this.ensureObject(address);
o.address = address;
o.refcount = refCount;
o.name = objectDescription;
}
noteGCedObject(address, marked, objectDescription, compartmentAddr) {
const o = this.ensureObject(address);
o.address = address;
o.gcmarked = marked;
o.name = objectDescription;
o.compartment = compartmentAddr;
}
noteEdge(fromAddress, toAddress, edgeName) {
const fromObject = this.ensureObject(fromAddress);
const toObject = this.ensureObject(toAddress);
fromObject.edges.push({ name: edgeName, to: toObject });
toObject.owners.push({ name: edgeName, from: fromObject });
this.edges.push({
name: edgeName,
from: fromObject,
to: toObject,
});
}
describeRoot(address, knownEdges) {
const o = this.ensureObject(address);
o.root = true;
o.knownEdges = knownEdges;
this.roots.push(o);
}
describeGarbage(address) {
const o = this.ensureObject(address);
o.garbage = true;
this.garbage.push(o);
}
ensureObject(address) {
if (!this.graph[address]) {
this.count++;
this.graph[address] = new CCObject();
}
return this.graph[address];
}
find(text) {
const result = [];
for (const address in this.graph) {
const o = this.graph[address];
if (!o.garbage && o.name.includes(text)) {
result.push(o);
}
}
return result;
}
findNotJS() {
const result = [];
for (const address in this.graph) {
const o = this.graph[address];
if (!o.garbage && o.name.indexOf("JS") != 0) {
result.push(o);
}
}
return result;
}
}
class CCObject {
constructor() {
this.name = "";
this.address = null;
this.refcount = 0;
this.gcmarked = false;
this.root = false;
this.garbage = false;
this.knownEdges = 0;
this.edges = [];
this.owners = [];
}
}