2010-02-20 13:23:25 -08:00
|
|
|
/* -*- Mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil; -*- */
|
|
|
|
/* ***** BEGIN LICENSE BLOCK *****
|
|
|
|
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
|
|
|
*
|
|
|
|
* The contents of this file are subject to the Mozilla Public License Version
|
|
|
|
* 1.1 (the "License"); you may not use this file except in compliance with
|
|
|
|
* the License. You may obtain a copy of the License at
|
|
|
|
* http://www.mozilla.org/MPL/
|
|
|
|
*
|
|
|
|
* Software distributed under the License is distributed on an "AS IS" basis,
|
|
|
|
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
|
|
|
* for the specific language governing rights and limitations under the
|
|
|
|
* License.
|
|
|
|
*
|
|
|
|
* The Original Code is about:memory
|
|
|
|
*
|
2011-05-03 17:12:58 -07:00
|
|
|
* The Initial Developer of the Original Code is the Mozilla Foundation.
|
2010-02-20 13:23:25 -08:00
|
|
|
* Portions created by the Initial Developer are Copyright (C) 2009
|
|
|
|
* the Initial Developer. All Rights Reserved.
|
|
|
|
*
|
|
|
|
* Contributor(s):
|
|
|
|
* Vladimir Vukicevic <vladimir@pobox.com>
|
2011-05-03 17:12:58 -07:00
|
|
|
* Nicholas Nethercote <nnethercote@mozilla.com>
|
2010-02-20 13:23:25 -08:00
|
|
|
*
|
|
|
|
* Alternatively, the contents of this file may be used under the terms of
|
|
|
|
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
|
|
|
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
|
|
|
* in which case the provisions of the GPL or the LGPL are applicable instead
|
|
|
|
* of those above. If you wish to allow use of your version of this file only
|
|
|
|
* under the terms of either the GPL or the LGPL, and not to allow others to
|
|
|
|
* use your version of this file under the terms of the MPL, indicate your
|
|
|
|
* decision by deleting the provisions above and replace them with the notice
|
|
|
|
* and other provisions required by the GPL or the LGPL. If you do not delete
|
|
|
|
* the provisions above, a recipient may use your version of this file under
|
|
|
|
* the terms of any one of the MPL, the GPL or the LGPL.
|
|
|
|
*
|
|
|
|
* ***** END LICENSE BLOCK ***** */
|
|
|
|
|
2011-05-03 17:12:58 -07:00
|
|
|
"use strict";
|
|
|
|
|
2011-03-31 10:19:30 -07:00
|
|
|
const Cc = Components.classes;
|
|
|
|
const Ci = Components.interfaces;
|
2011-05-11 16:09:50 -07:00
|
|
|
const Cu = Components.utils;
|
2011-03-31 10:19:30 -07:00
|
|
|
|
2011-05-03 17:12:58 -07:00
|
|
|
// Must use .href here instead of .search because "about:memory" is a
|
|
|
|
// non-standard URL.
|
|
|
|
var gVerbose = (location.href.split(/[\?,]/).indexOf("verbose") !== -1);
|
2010-02-20 13:23:25 -08:00
|
|
|
|
2011-05-11 16:09:50 -07:00
|
|
|
var gAddedObserver = false;
|
|
|
|
|
2011-07-24 20:56:50 -07:00
|
|
|
const KIND_NONHEAP = Ci.nsIMemoryReporter.KIND_NONHEAP;
|
|
|
|
const KIND_HEAP = Ci.nsIMemoryReporter.KIND_HEAP;
|
|
|
|
const KIND_OTHER = Ci.nsIMemoryReporter.KIND_OTHER;
|
|
|
|
const UNITS_BYTES = Ci.nsIMemoryReporter.UNITS_BYTES;
|
|
|
|
const UNITS_COUNT = Ci.nsIMemoryReporter.UNITS_COUNT;
|
|
|
|
const UNITS_COUNT_CUMULATIVE = Ci.nsIMemoryReporter.UNITS_COUNT_CUMULATIVE;
|
2011-07-20 21:08:24 -07:00
|
|
|
const UNITS_PERCENTAGE = Ci.nsIMemoryReporter.UNITS_PERCENTAGE;
|
2011-05-22 19:49:56 -07:00
|
|
|
|
2011-06-16 11:34:09 -07:00
|
|
|
const kUnknown = -1; // used for _amount if a memory reporter failed
|
2011-05-22 19:49:56 -07:00
|
|
|
|
2011-08-05 15:22:11 -07:00
|
|
|
const kTreeDescriptions = {
|
|
|
|
'explicit' :
|
|
|
|
"This tree covers explicit memory allocations by the application, " +
|
|
|
|
"both at the operating system level (via calls to functions such as " +
|
|
|
|
"VirtualAlloc, vm_allocate, and mmap), and at the heap allocation level " +
|
|
|
|
"(via functions such as malloc, calloc, realloc, memalign, operator " +
|
|
|
|
"new, and operator new[]). It excludes memory that is mapped implicitly " +
|
|
|
|
"such as code and data segments, and thread stacks. It also excludes " +
|
|
|
|
"heap memory that has been freed by the application but is still being " +
|
|
|
|
"held onto by the heap allocator. It is not guaranteed to cover every " +
|
|
|
|
"explicit allocation, but it does cover most (including the entire " +
|
|
|
|
"heap), and therefore it is the single best number to focus on when " +
|
|
|
|
"trying to reduce memory usage.",
|
|
|
|
|
|
|
|
'resident':
|
|
|
|
"This tree shows how much space in physical memory each of the " +
|
|
|
|
"process's mappings is currently using (the mapping's 'resident set size', " +
|
|
|
|
"or 'RSS'). This is a good measure of the 'cost' of the mapping, although " +
|
|
|
|
"it does not take into account the fact that shared libraries may be mapped " +
|
|
|
|
"by multiple processes but appear only once in physical memory. " +
|
|
|
|
"Note that the 'resident' value here might not equal the value for " +
|
|
|
|
"'resident' under 'Other Measurements' because the two measurements are not " +
|
|
|
|
"taken at exactly the same time.",
|
|
|
|
|
2011-09-15 10:53:04 -07:00
|
|
|
'pss':
|
|
|
|
"This tree shows how much space in physical memory can be 'blamed' on this " +
|
|
|
|
"process. For each mapping, its 'proportional set size' (PSS) is the " +
|
|
|
|
"mapping's resident size divided by the number of processes which use the " +
|
|
|
|
"mapping. So if a mapping is private to this process, its PSS should equal " +
|
|
|
|
"its RSS. But if a mapping is shared between three processes, its PSS in " +
|
|
|
|
"each of the processes would be 1/3 its RSS.",
|
|
|
|
|
2011-08-05 15:22:11 -07:00
|
|
|
'vsize':
|
|
|
|
"This tree shows how much virtual addres space each of the process's " +
|
|
|
|
"mappings takes up (the mapping's 'vsize'). A mapping may have a large " +
|
|
|
|
"vsize but use only a small amount of physical memory; the resident set size " +
|
|
|
|
"of a mapping is a better measure of the mapping's 'cost'. Note that the " +
|
|
|
|
"'vsize' value here might not equal the value for 'vsize' under 'Other " +
|
|
|
|
"Measurements' because the two measurements are not taken at exactly the " +
|
|
|
|
"same time.",
|
|
|
|
|
|
|
|
'swap':
|
|
|
|
"This tree shows how much space in the swap file each of the process's " +
|
|
|
|
"mappings is currently using. Mappings which are not in the swap file " +
|
|
|
|
"(i.e., nodes which would have a value of 0 in this tree) are omitted."
|
|
|
|
};
|
|
|
|
|
|
|
|
const kTreeNames = {
|
|
|
|
'explicit': 'Explicit Allocations',
|
|
|
|
'resident': 'Resident Set Size (RSS) Breakdown',
|
2011-09-15 10:53:04 -07:00
|
|
|
'pss': 'Proportional Set Size (PSS) Breakdown',
|
2011-08-05 15:22:11 -07:00
|
|
|
'vsize': 'Virtual Size Breakdown',
|
|
|
|
'swap': 'Swap Usage Breakdown',
|
|
|
|
'other': 'Other Measurements'
|
|
|
|
};
|
|
|
|
|
2011-09-15 10:53:04 -07:00
|
|
|
const kMapTreePaths = ['map/resident', 'map/pss', 'map/vsize', 'map/swap'];
|
2011-08-05 15:22:11 -07:00
|
|
|
|
2011-05-03 17:12:58 -07:00
|
|
|
function onLoad()
|
|
|
|
{
|
|
|
|
var os = Cc["@mozilla.org/observer-service;1"].
|
|
|
|
getService(Ci.nsIObserverService);
|
|
|
|
os.notifyObservers(null, "child-memory-reporter-request", null);
|
2010-02-20 13:23:25 -08:00
|
|
|
|
2011-05-03 17:12:58 -07:00
|
|
|
os.addObserver(ChildMemoryListener, "child-memory-reporter-update", false);
|
2011-05-11 16:09:50 -07:00
|
|
|
gAddedObserver = true;
|
2010-02-20 13:23:25 -08:00
|
|
|
|
2011-05-03 17:12:58 -07:00
|
|
|
update();
|
2010-02-20 13:23:25 -08:00
|
|
|
}
|
|
|
|
|
2011-05-03 17:12:58 -07:00
|
|
|
function onUnload()
|
|
|
|
{
|
2011-05-11 16:09:50 -07:00
|
|
|
// We need to check if the observer has been added before removing; in some
|
|
|
|
// circumstances (eg. reloading the page quickly) it might not have because
|
|
|
|
// onLoad might not fire.
|
|
|
|
if (gAddedObserver) {
|
|
|
|
var os = Cc["@mozilla.org/observer-service;1"].
|
|
|
|
getService(Ci.nsIObserverService);
|
|
|
|
os.removeObserver(ChildMemoryListener, "child-memory-reporter-update");
|
|
|
|
}
|
2010-02-20 13:23:25 -08:00
|
|
|
}
|
|
|
|
|
2011-05-03 17:12:58 -07:00
|
|
|
function ChildMemoryListener(aSubject, aTopic, aData)
|
|
|
|
{
|
|
|
|
update();
|
2010-02-20 13:23:25 -08:00
|
|
|
}
|
|
|
|
|
2011-05-03 17:12:58 -07:00
|
|
|
function $(n)
|
|
|
|
{
|
|
|
|
return document.getElementById(n);
|
2010-02-20 13:23:25 -08:00
|
|
|
}
|
|
|
|
|
2011-05-11 16:09:50 -07:00
|
|
|
function doGlobalGC()
|
|
|
|
{
|
|
|
|
Cu.forceGC();
|
2011-09-13 10:53:51 -07:00
|
|
|
var os = Cc["@mozilla.org/observer-service;1"]
|
|
|
|
.getService(Ci.nsIObserverService);
|
|
|
|
os.notifyObservers(null, "child-gc-request", null);
|
2011-05-11 16:09:50 -07:00
|
|
|
update();
|
|
|
|
}
|
|
|
|
|
2011-08-12 11:48:18 -07:00
|
|
|
function doCC()
|
2011-05-11 16:09:50 -07:00
|
|
|
{
|
|
|
|
window.QueryInterface(Ci.nsIInterfaceRequestor)
|
|
|
|
.getInterface(Ci.nsIDOMWindowUtils)
|
2011-08-12 11:48:18 -07:00
|
|
|
.cycleCollect();
|
2011-09-13 10:53:51 -07:00
|
|
|
var os = Cc["@mozilla.org/observer-service;1"]
|
|
|
|
.getService(Ci.nsIObserverService);
|
|
|
|
os.notifyObservers(null, "child-cc-request", null);
|
2011-05-11 16:09:50 -07:00
|
|
|
update();
|
|
|
|
}
|
|
|
|
|
|
|
|
// For maximum effect, this returns to the event loop between each
|
|
|
|
// notification. See bug 610166 comment 12 for an explanation.
|
|
|
|
// Ideally a single notification would be enough.
|
|
|
|
function sendHeapMinNotifications()
|
|
|
|
{
|
|
|
|
function runSoon(f)
|
|
|
|
{
|
|
|
|
var tm = Cc["@mozilla.org/thread-manager;1"]
|
|
|
|
.getService(Ci.nsIThreadManager);
|
|
|
|
|
|
|
|
tm.mainThread.dispatch({ run: f }, Ci.nsIThread.DISPATCH_NORMAL);
|
|
|
|
}
|
|
|
|
|
|
|
|
function sendHeapMinNotificationsInner()
|
|
|
|
{
|
|
|
|
var os = Cc["@mozilla.org/observer-service;1"]
|
|
|
|
.getService(Ci.nsIObserverService);
|
|
|
|
os.notifyObservers(null, "memory-pressure", "heap-minimize");
|
|
|
|
|
|
|
|
if (++j < 3)
|
|
|
|
runSoon(sendHeapMinNotificationsInner);
|
|
|
|
else
|
|
|
|
runSoon(update);
|
|
|
|
}
|
|
|
|
|
|
|
|
var j = 0;
|
|
|
|
sendHeapMinNotificationsInner();
|
|
|
|
}
|
|
|
|
|
2011-08-11 00:04:44 -07:00
|
|
|
function Reporter(aPath, aKind, aUnits, aAmount, aDescription)
|
|
|
|
{
|
|
|
|
this._path = aPath;
|
|
|
|
this._kind = aKind;
|
|
|
|
this._units = aUnits;
|
|
|
|
this._amount = aAmount;
|
|
|
|
this._description = aDescription;
|
|
|
|
// this._nMerged is only defined if > 1
|
|
|
|
// this._done is defined when getBytes is called
|
|
|
|
}
|
|
|
|
|
|
|
|
Reporter.prototype = {
|
|
|
|
// Sum the values (accounting for possible kUnknown amounts), and mark |this|
|
|
|
|
// as a dup. We mark dups because it's useful to know when a reporter is
|
|
|
|
// duplicated; it might be worth investigating and splitting up to have
|
|
|
|
// non-duplicated names.
|
|
|
|
merge: function(r) {
|
|
|
|
if (this._amount !== kUnknown && r._amount !== kUnknown) {
|
|
|
|
this._amount += r._amount;
|
|
|
|
} else if (this._amount === kUnknown && r._amount !== kUnknown) {
|
|
|
|
this._amount = r._amount;
|
|
|
|
}
|
|
|
|
this._nMerged = this._nMerged ? this._nMerged + 1 : 2;
|
|
|
|
},
|
|
|
|
|
|
|
|
treeNameMatches: function(aTreeName) {
|
2011-12-12 19:04:12 -08:00
|
|
|
// Nb: the '/' must be present, because we have a KIND_OTHER reporter
|
|
|
|
// called "explicit" which is not part of the "explicit" tree.
|
|
|
|
aTreeName += "/";
|
2011-08-11 00:04:44 -07:00
|
|
|
return this._path.slice(0, aTreeName.length) === aTreeName;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2012-01-17 20:23:55 -08:00
|
|
|
function getReportersByProcess(aMgr)
|
2011-05-03 17:12:58 -07:00
|
|
|
{
|
|
|
|
// Process each memory reporter:
|
|
|
|
// - Make a copy of it into a sub-table indexed by its process. Each copy
|
2011-08-11 00:04:44 -07:00
|
|
|
// is a Reporter object. After this point we never use the original memory
|
|
|
|
// reporter again.
|
2011-06-05 18:22:45 -07:00
|
|
|
//
|
2011-06-16 11:34:09 -07:00
|
|
|
// - Note that copying rOrig.amount (which calls a C++ function under the
|
|
|
|
// IDL covers) to r._amount for every reporter now means that the
|
2011-05-03 17:12:58 -07:00
|
|
|
// results as consistent as possible -- measurements are made all at
|
|
|
|
// once before most of the memory required to generate this page is
|
|
|
|
// allocated.
|
2011-06-05 18:22:45 -07:00
|
|
|
var reportersByProcess = {};
|
2011-06-29 16:39:32 -07:00
|
|
|
|
|
|
|
function addReporter(aProcess, aPath, aKind, aUnits, aAmount, aDescription)
|
|
|
|
{
|
|
|
|
var process = aProcess === "" ? "Main" : aProcess;
|
2011-08-11 00:04:44 -07:00
|
|
|
var r = new Reporter(aPath, aKind, aUnits, aAmount, aDescription);
|
2011-06-05 18:22:45 -07:00
|
|
|
if (!reportersByProcess[process]) {
|
|
|
|
reportersByProcess[process] = {};
|
2011-05-03 17:12:58 -07:00
|
|
|
}
|
2011-06-05 18:22:45 -07:00
|
|
|
var reporters = reportersByProcess[process];
|
2011-07-19 16:59:17 -07:00
|
|
|
var reporter = reporters[r._path];
|
|
|
|
if (reporter) {
|
2011-08-11 00:04:44 -07:00
|
|
|
// Already an entry; must be a duplicated reporter. This can happen
|
|
|
|
// legitimately. Merge them.
|
|
|
|
reporter.merge(r);
|
2011-05-03 17:12:58 -07:00
|
|
|
} else {
|
2011-06-05 18:22:45 -07:00
|
|
|
reporters[r._path] = r;
|
2011-05-03 17:12:58 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-06-29 16:39:32 -07:00
|
|
|
// Process vanilla reporters first, then multi-reporters.
|
2012-01-17 20:23:55 -08:00
|
|
|
var e = aMgr.enumerateReporters();
|
2011-06-29 16:39:32 -07:00
|
|
|
while (e.hasMoreElements()) {
|
|
|
|
var rOrig = e.getNext().QueryInterface(Ci.nsIMemoryReporter);
|
2011-09-26 11:53:55 -07:00
|
|
|
try {
|
|
|
|
addReporter(rOrig.process, rOrig.path, rOrig.kind, rOrig.units,
|
|
|
|
rOrig.amount, rOrig.description);
|
|
|
|
}
|
|
|
|
catch(e) {
|
|
|
|
debug("An error occurred when collecting results from the memory reporter " +
|
|
|
|
rOrig.path + ": " + e);
|
|
|
|
}
|
2011-06-29 16:39:32 -07:00
|
|
|
}
|
2012-01-17 20:23:55 -08:00
|
|
|
var e = aMgr.enumerateMultiReporters();
|
2011-06-29 16:39:32 -07:00
|
|
|
while (e.hasMoreElements()) {
|
2011-08-11 00:04:44 -07:00
|
|
|
var mrOrig = e.getNext().QueryInterface(Ci.nsIMemoryMultiReporter);
|
2011-09-26 11:53:55 -07:00
|
|
|
try {
|
|
|
|
mrOrig.collectReports(addReporter, null);
|
|
|
|
}
|
|
|
|
catch(e) {
|
|
|
|
debug("An error occurred when collecting a multi-reporter's results: " + e);
|
|
|
|
}
|
2011-06-29 16:39:32 -07:00
|
|
|
}
|
|
|
|
|
2011-07-25 18:51:38 -07:00
|
|
|
return reportersByProcess;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Top-level function that does the work of generating the page.
|
|
|
|
*/
|
|
|
|
function update()
|
|
|
|
{
|
|
|
|
// First, clear the page contents. Necessary because update() might be
|
|
|
|
// called more than once due to ChildMemoryListener.
|
|
|
|
var content = $("content");
|
|
|
|
content.parentNode.replaceChild(content.cloneNode(false), content);
|
|
|
|
content = $("content");
|
|
|
|
|
2011-09-07 17:14:47 -07:00
|
|
|
if (gVerbose)
|
|
|
|
content.parentNode.classList.add('verbose');
|
|
|
|
else
|
|
|
|
content.parentNode.classList.add('non-verbose');
|
|
|
|
|
2012-01-17 20:23:55 -08:00
|
|
|
var mgr = Cc["@mozilla.org/memory-reporter-manager;1"].
|
|
|
|
getService(Ci.nsIMemoryReporterManager);
|
|
|
|
|
|
|
|
var text = "";
|
|
|
|
|
2011-05-03 17:12:58 -07:00
|
|
|
// Generate output for one process at a time. Always start with the
|
|
|
|
// Main process.
|
2012-01-17 20:23:55 -08:00
|
|
|
var reportersByProcess = getReportersByProcess(mgr);
|
|
|
|
var hasMozMallocUsableSize = mgr.hasMozMallocUsableSize;
|
|
|
|
text += genProcessText("Main", reportersByProcess["Main"],
|
|
|
|
hasMozMallocUsableSize);
|
2011-06-05 18:22:45 -07:00
|
|
|
for (var process in reportersByProcess) {
|
2011-05-03 17:12:58 -07:00
|
|
|
if (process !== "Main") {
|
2012-01-17 20:23:55 -08:00
|
|
|
text += genProcessText(process, reportersByProcess[process],
|
|
|
|
hasMozMallocUsableSize);
|
2011-05-03 17:12:58 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-05-11 16:09:50 -07:00
|
|
|
// Memory-related actions.
|
2012-01-26 14:02:49 -08:00
|
|
|
const UpDesc = "Re-measure.";
|
2011-05-11 16:09:50 -07:00
|
|
|
const GCDesc = "Do a global garbage collection.";
|
2011-08-12 11:48:18 -07:00
|
|
|
const CCDesc = "Do a cycle collection.";
|
2011-05-22 19:49:56 -07:00
|
|
|
const MPDesc = "Send three \"heap-minimize\" notifications in a " +
|
|
|
|
"row. Each notification triggers a global garbage " +
|
|
|
|
"collection followed by a cycle collection, and causes the " +
|
|
|
|
"process to reduce memory usage in other ways, e.g. by " +
|
|
|
|
"flushing various caches.";
|
2011-05-11 16:09:50 -07:00
|
|
|
|
2012-01-26 14:02:49 -08:00
|
|
|
// The "Update" button has an id so it can be clicked in a test.
|
2011-05-11 16:09:50 -07:00
|
|
|
text += "<div>" +
|
2012-01-26 14:02:49 -08:00
|
|
|
"<button title='" + UpDesc + "' onclick='update()' id='updateButton'>Update</button>" +
|
2011-05-11 16:09:50 -07:00
|
|
|
"<button title='" + GCDesc + "' onclick='doGlobalGC()'>GC</button>" +
|
2011-08-12 11:48:18 -07:00
|
|
|
"<button title='" + CCDesc + "' onclick='doCC()'>CC</button>" +
|
2011-05-11 16:09:50 -07:00
|
|
|
"<button title='" + MPDesc + "' onclick='sendHeapMinNotifications()'>" + "Minimize memory usage</button>" +
|
|
|
|
"</div>";
|
|
|
|
|
2011-05-03 17:12:58 -07:00
|
|
|
// Generate verbosity option link at the bottom.
|
2011-05-22 19:49:59 -07:00
|
|
|
text += "<div>";
|
2011-05-03 17:12:58 -07:00
|
|
|
text += gVerbose
|
|
|
|
? "<span class='option'><a href='about:memory'>Less verbose</a></span>"
|
|
|
|
: "<span class='option'><a href='about:memory?verbose'>More verbose</a></span>";
|
2011-05-22 19:49:59 -07:00
|
|
|
text += "</div>";
|
|
|
|
|
2011-09-07 08:26:15 -07:00
|
|
|
text += "<div>" +
|
|
|
|
"<span class='option'><a href='about:support'>Troubleshooting information</a></span>" +
|
|
|
|
"</div>";
|
|
|
|
|
2011-05-22 19:49:59 -07:00
|
|
|
text += "<div>" +
|
2012-01-26 14:02:49 -08:00
|
|
|
"<span class='legend'>Click on a non-leaf node in a tree to expand ('++') " +
|
|
|
|
"or collapse ('--') its children.</span>" +
|
2011-05-22 19:49:59 -07:00
|
|
|
"</div>";
|
2012-01-26 14:02:49 -08:00
|
|
|
text += "<div>" +
|
|
|
|
"<span class='legend'>Hover the pointer over the name of a memory " +
|
|
|
|
"reporter to see a description of what it measures.</span>";
|
2011-05-22 19:49:59 -07:00
|
|
|
|
2011-05-03 17:12:58 -07:00
|
|
|
var div = document.createElement("div");
|
|
|
|
div.innerHTML = text;
|
|
|
|
content.appendChild(div);
|
2010-02-20 13:23:25 -08:00
|
|
|
}
|
|
|
|
|
2012-01-07 02:45:35 -08:00
|
|
|
// There are two kinds of TreeNode.
|
2012-01-26 14:02:49 -08:00
|
|
|
// - Leaf TreeNodes correspond to Reporters and have more properties.
|
2012-01-07 02:45:35 -08:00
|
|
|
// - Non-leaf TreeNodes are just scaffolding nodes for the tree; their values
|
|
|
|
// are derived from their children.
|
2011-08-11 00:04:44 -07:00
|
|
|
function TreeNode(aName)
|
2011-07-20 21:08:24 -07:00
|
|
|
{
|
2011-08-11 00:04:44 -07:00
|
|
|
// Nb: _units is not needed, it's always UNITS_BYTES.
|
|
|
|
this._name = aName;
|
|
|
|
this._kids = [];
|
|
|
|
// All TreeNodes have these properties added later:
|
|
|
|
// - _amount (which is never |kUnknown|)
|
|
|
|
// - _description
|
|
|
|
//
|
2012-01-07 02:45:35 -08:00
|
|
|
// Leaf TreeNodes have these properties added later:
|
2011-08-11 00:04:44 -07:00
|
|
|
// - _kind
|
|
|
|
// - _nMerged (if > 1)
|
|
|
|
// - _hasProblem (only defined if true)
|
2012-01-26 14:02:49 -08:00
|
|
|
//
|
|
|
|
// Non-leaf TreeNodes have these properties added later:
|
|
|
|
// - _hideKids (only defined if true)
|
2011-08-11 00:04:44 -07:00
|
|
|
}
|
2011-07-20 21:08:24 -07:00
|
|
|
|
2011-08-11 00:04:44 -07:00
|
|
|
TreeNode.prototype = {
|
|
|
|
findKid: function(aName) {
|
|
|
|
for (var i = 0; i < this._kids.length; i++) {
|
|
|
|
if (this._kids[i]._name === aName) {
|
|
|
|
return this._kids[i];
|
|
|
|
}
|
2011-07-25 18:51:38 -07:00
|
|
|
}
|
2011-08-11 00:04:44 -07:00
|
|
|
return undefined;
|
|
|
|
},
|
|
|
|
|
|
|
|
toString: function() {
|
|
|
|
return formatBytes(this._amount);
|
2011-07-25 18:51:38 -07:00
|
|
|
}
|
2011-08-11 00:04:44 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
TreeNode.compare = function(a, b) {
|
|
|
|
return b._amount - a._amount;
|
|
|
|
};
|
2011-07-25 18:51:38 -07:00
|
|
|
|
2011-03-31 10:19:30 -07:00
|
|
|
/**
|
2011-07-25 18:51:38 -07:00
|
|
|
* From a list of memory reporters, builds a tree that mirrors the tree
|
|
|
|
* structure that will be shown as output.
|
2011-05-03 17:12:58 -07:00
|
|
|
*
|
2011-06-05 18:22:45 -07:00
|
|
|
* @param aReporters
|
2011-08-11 00:04:44 -07:00
|
|
|
* The table of Reporters, indexed by path.
|
2011-08-05 15:22:11 -07:00
|
|
|
* @param aTreeName
|
|
|
|
* The name of the tree being built.
|
2011-08-11 00:04:44 -07:00
|
|
|
* @return The built tree.
|
2011-03-31 10:19:30 -07:00
|
|
|
*/
|
2011-08-05 15:22:11 -07:00
|
|
|
function buildTree(aReporters, aTreeName)
|
2010-02-20 13:23:25 -08:00
|
|
|
{
|
2011-08-05 15:22:11 -07:00
|
|
|
// We want to process all reporters that begin with |aTreeName|. First we
|
2011-08-11 00:04:44 -07:00
|
|
|
// build the tree but only fill the properties that we can with a top-down
|
|
|
|
// traversal.
|
2011-08-05 15:22:11 -07:00
|
|
|
|
2012-01-07 02:45:35 -08:00
|
|
|
// There should always be at least one matching reporter when |aTreeName| is
|
|
|
|
// "explicit". But there may be zero for "map" trees; if that happens,
|
|
|
|
// bail.
|
2011-08-05 15:22:11 -07:00
|
|
|
var foundReporter = false;
|
|
|
|
for (var path in aReporters) {
|
|
|
|
if (aReporters[path].treeNameMatches(aTreeName)) {
|
|
|
|
foundReporter = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!foundReporter) {
|
2012-01-07 02:45:35 -08:00
|
|
|
assert(aTreeName !== 'explicit');
|
2011-09-14 17:37:45 -07:00
|
|
|
return null;
|
2011-08-05 15:22:11 -07:00
|
|
|
}
|
|
|
|
|
2011-08-11 00:04:44 -07:00
|
|
|
var t = new TreeNode("falseRoot");
|
2011-07-25 18:51:38 -07:00
|
|
|
for (var path in aReporters) {
|
2011-08-11 00:04:44 -07:00
|
|
|
// Add any missing nodes in the tree implied by the path.
|
2011-07-25 18:51:38 -07:00
|
|
|
var r = aReporters[path];
|
2011-08-05 15:22:11 -07:00
|
|
|
if (r.treeNameMatches(aTreeName)) {
|
2011-07-25 18:51:38 -07:00
|
|
|
assert(r._kind === KIND_HEAP || r._kind === KIND_NONHEAP,
|
|
|
|
"reporters in the tree must have KIND_HEAP or KIND_NONHEAP");
|
2011-08-05 15:22:11 -07:00
|
|
|
assert(r._units === UNITS_BYTES, "r._units === UNITS_BYTES");
|
2011-07-25 18:51:38 -07:00
|
|
|
var names = r._path.split('/');
|
|
|
|
var u = t;
|
|
|
|
for (var i = 0; i < names.length; i++) {
|
|
|
|
var name = names[i];
|
2011-08-11 00:04:44 -07:00
|
|
|
var uMatch = u.findKid(name);
|
2011-07-25 18:51:38 -07:00
|
|
|
if (uMatch) {
|
|
|
|
u = uMatch;
|
|
|
|
} else {
|
2011-08-11 00:04:44 -07:00
|
|
|
var v = new TreeNode(name);
|
2011-07-25 18:51:38 -07:00
|
|
|
u._kids.push(v);
|
|
|
|
u = v;
|
2011-05-03 17:12:58 -07:00
|
|
|
}
|
|
|
|
}
|
2011-08-11 00:04:44 -07:00
|
|
|
// Fill in extra details from the Reporter.
|
2011-07-25 18:51:38 -07:00
|
|
|
u._kind = r._kind;
|
|
|
|
if (r._nMerged) {
|
|
|
|
u._nMerged = r._nMerged;
|
2011-05-03 17:12:58 -07:00
|
|
|
}
|
|
|
|
}
|
2011-07-25 18:51:38 -07:00
|
|
|
}
|
2011-08-05 15:22:11 -07:00
|
|
|
|
2011-07-25 18:51:38 -07:00
|
|
|
// Using falseRoot makes the above code simpler. Now discard it, leaving
|
2011-08-05 15:22:11 -07:00
|
|
|
// aTreeName at the root.
|
2011-07-25 18:51:38 -07:00
|
|
|
t = t._kids[0];
|
|
|
|
|
2011-08-11 00:04:44 -07:00
|
|
|
// Next, fill in the remaining properties bottom-up.
|
|
|
|
// Note that this function never returns kUnknown.
|
2011-07-25 18:51:38 -07:00
|
|
|
function fillInTree(aT, aPrepath)
|
|
|
|
{
|
|
|
|
var path = aPrepath ? aPrepath + '/' + aT._name : aT._name;
|
|
|
|
if (aT._kids.length === 0) {
|
|
|
|
// Leaf node. Must have a reporter.
|
2012-01-07 02:45:35 -08:00
|
|
|
assert(aT._kind !== undefined, "aT._kind is undefined for leaf node");
|
2011-07-25 18:51:38 -07:00
|
|
|
aT._description = getDescription(aReporters, path);
|
|
|
|
var amount = getBytes(aReporters, path);
|
|
|
|
if (amount !== kUnknown) {
|
|
|
|
aT._amount = amount;
|
|
|
|
} else {
|
|
|
|
aT._amount = 0;
|
|
|
|
aT._hasProblem = true;
|
|
|
|
}
|
|
|
|
} else {
|
2012-01-07 02:45:35 -08:00
|
|
|
// Non-leaf node. Derive its size and description entirely from its
|
|
|
|
// children.
|
|
|
|
assert(aT._kind === undefined, "aT._kind is defined for non-leaf node");
|
2011-07-25 18:51:38 -07:00
|
|
|
var childrenBytes = 0;
|
|
|
|
for (var i = 0; i < aT._kids.length; i++) {
|
2011-08-11 00:04:44 -07:00
|
|
|
childrenBytes += fillInTree(aT._kids[i], path);
|
2011-07-25 18:51:38 -07:00
|
|
|
}
|
2012-01-07 02:45:35 -08:00
|
|
|
aT._amount = childrenBytes;
|
|
|
|
aT._description = "The sum of all entries below '" + aT._name + "'.";
|
2011-05-03 17:12:58 -07:00
|
|
|
}
|
2011-08-05 15:22:11 -07:00
|
|
|
assert(aT._amount !== kUnknown, "aT._amount !== kUnknown");
|
2011-07-25 18:51:38 -07:00
|
|
|
return aT._amount;
|
|
|
|
}
|
2011-08-05 15:22:11 -07:00
|
|
|
|
2011-07-25 18:51:38 -07:00
|
|
|
fillInTree(t, "");
|
|
|
|
|
2011-08-05 15:22:11 -07:00
|
|
|
// Reduce the depth of the tree by the number of occurrences of '/' in
|
|
|
|
// aTreeName. (Thus the tree named 'foo/bar/baz' will be rooted at 'baz'.)
|
|
|
|
var slashCount = 0;
|
|
|
|
for (var i = 0; i < aTreeName.length; i++) {
|
|
|
|
if (aTreeName[i] == '/') {
|
|
|
|
assert(t._kids.length == 1, "Not expecting multiple kids here.");
|
|
|
|
t = t._kids[0];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set the description on the root node.
|
|
|
|
t._description = kTreeDescriptions[t._name];
|
|
|
|
|
|
|
|
return t;
|
|
|
|
}
|
|
|
|
|
2012-01-26 14:02:49 -08:00
|
|
|
/**
|
|
|
|
* Ignore all the memory reporters that belong to a tree; this involves
|
|
|
|
* explicitly marking them as done.
|
|
|
|
*
|
|
|
|
* @param aReporters
|
|
|
|
* The table of Reporters, indexed by path.
|
|
|
|
* @param aTreeName
|
|
|
|
* The name of the tree being built.
|
|
|
|
*/
|
|
|
|
function ignoreTree(aReporters, aTreeName)
|
|
|
|
{
|
|
|
|
for (var path in aReporters) {
|
|
|
|
var r = aReporters[path];
|
|
|
|
if (r.treeNameMatches(aTreeName)) {
|
|
|
|
var dummy = getBytes(aReporters, path);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-08-05 15:22:11 -07:00
|
|
|
/**
|
|
|
|
* Do some work which only makes sense for the 'explicit' tree.
|
2012-01-17 20:23:55 -08:00
|
|
|
*
|
|
|
|
* @param aT
|
|
|
|
* The tree.
|
|
|
|
* @param aReporters
|
|
|
|
* Table of Reporters for this process, indexed by _path.
|
|
|
|
* @return A boolean indicating if "heap-allocated" is known for the process.
|
2011-08-05 15:22:11 -07:00
|
|
|
*/
|
2012-01-17 20:23:55 -08:00
|
|
|
function fixUpExplicitTree(aT, aReporters)
|
|
|
|
{
|
2012-01-07 02:45:35 -08:00
|
|
|
// Determine how many bytes are reported by heap reporters.
|
2011-07-25 18:51:38 -07:00
|
|
|
var s = "";
|
|
|
|
function getKnownHeapUsedBytes(aT)
|
|
|
|
{
|
2012-01-07 02:45:35 -08:00
|
|
|
var n = 0;
|
|
|
|
if (aT._kids.length === 0) {
|
|
|
|
// Leaf node.
|
|
|
|
assert(aT._kind !== undefined, "aT._kind is undefined for leaf node");
|
|
|
|
n = aT._kind === KIND_HEAP ? aT._amount : 0;
|
2011-07-25 18:51:38 -07:00
|
|
|
} else {
|
|
|
|
for (var i = 0; i < aT._kids.length; i++) {
|
|
|
|
n += getKnownHeapUsedBytes(aT._kids[i]);
|
2011-05-22 19:49:56 -07:00
|
|
|
}
|
|
|
|
}
|
2012-01-07 02:45:35 -08:00
|
|
|
return n;
|
2011-07-25 18:51:38 -07:00
|
|
|
}
|
2011-05-22 19:49:56 -07:00
|
|
|
|
2011-07-25 18:51:38 -07:00
|
|
|
// A special case: compute the derived "heap-unclassified" value. Don't
|
|
|
|
// mark "heap-allocated" when we get its size because we want it to appear
|
|
|
|
// in the "Other Measurements" list.
|
2012-01-07 02:45:35 -08:00
|
|
|
var heapAllocatedBytes = getBytes(aReporters, "heap-allocated", true);
|
|
|
|
var heapUnclassifiedT = new TreeNode("heap-unclassified");
|
2012-01-17 20:23:55 -08:00
|
|
|
var hasKnownHeapAllocated = heapAllocatedBytes !== kUnknown;
|
|
|
|
if (hasKnownHeapAllocated) {
|
2012-01-07 02:45:35 -08:00
|
|
|
heapUnclassifiedT._amount =
|
|
|
|
heapAllocatedBytes - getKnownHeapUsedBytes(aT);
|
|
|
|
} else {
|
|
|
|
heapUnclassifiedT._amount = 0;
|
|
|
|
heapUnclassifiedT._hasProblem = true;
|
2011-07-25 18:51:38 -07:00
|
|
|
}
|
2011-08-11 00:04:44 -07:00
|
|
|
// This kindToString() ensures the "(Heap)" prefix is set without having to
|
|
|
|
// set the _kind property, which would mean that there is a corresponding
|
|
|
|
// Reporter for this TreeNode (which isn't true).
|
2012-01-07 02:45:35 -08:00
|
|
|
heapUnclassifiedT._description =
|
2011-08-11 00:04:44 -07:00
|
|
|
kindToString(KIND_HEAP) +
|
2011-07-25 18:51:38 -07:00
|
|
|
"Memory not classified by a more specific reporter. This includes " +
|
2012-01-07 02:45:35 -08:00
|
|
|
"slop bytes due to internal fragmentation in the heap allocator "
|
|
|
|
"(caused when the allocator rounds up request sizes).";
|
2011-08-11 00:04:44 -07:00
|
|
|
|
2012-01-07 02:45:35 -08:00
|
|
|
aT._kids.push(heapUnclassifiedT);
|
|
|
|
aT._amount += heapUnclassifiedT._amount;
|
2012-01-17 20:23:55 -08:00
|
|
|
|
|
|
|
return hasKnownHeapAllocated;
|
2011-07-25 18:51:38 -07:00
|
|
|
}
|
2011-05-03 17:12:58 -07:00
|
|
|
|
2011-07-25 18:51:38 -07:00
|
|
|
/**
|
2012-01-26 14:02:49 -08:00
|
|
|
* Sort all kid nodes from largest to smallest, and insert aggregate nodes
|
|
|
|
* where appropriate.
|
2011-07-25 18:51:38 -07:00
|
|
|
*
|
|
|
|
* @param aTotalBytes
|
2011-08-11 00:04:44 -07:00
|
|
|
* The size of the tree's root node.
|
2011-07-25 18:51:38 -07:00
|
|
|
* @param aT
|
2011-08-11 00:04:44 -07:00
|
|
|
* The tree.
|
2011-07-25 18:51:38 -07:00
|
|
|
*/
|
2012-01-26 14:02:49 -08:00
|
|
|
function sortTreeAndInsertAggregateNodes(aTotalBytes, aT)
|
2011-07-25 18:51:38 -07:00
|
|
|
{
|
2012-01-26 14:02:49 -08:00
|
|
|
const kSignificanceThresholdPerc = 1;
|
2011-07-25 18:51:38 -07:00
|
|
|
|
2012-01-26 14:02:49 -08:00
|
|
|
function isInsignificant(aT)
|
2011-07-25 18:51:38 -07:00
|
|
|
{
|
|
|
|
return !gVerbose &&
|
|
|
|
aTotalBytes !== kUnknown &&
|
2012-01-26 14:02:49 -08:00
|
|
|
(100 * aT._amount / aTotalBytes) < kSignificanceThresholdPerc;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (aT._kids.length === 0) {
|
|
|
|
return;
|
2011-07-25 18:51:38 -07:00
|
|
|
}
|
|
|
|
|
2011-08-11 00:04:44 -07:00
|
|
|
aT._kids.sort(TreeNode.compare);
|
2011-07-25 18:51:38 -07:00
|
|
|
|
2012-01-26 14:02:49 -08:00
|
|
|
// If the first child is insignificant, they all are, and there's no point
|
|
|
|
// creating an aggregate node that lacks siblings. Just set the parent's
|
|
|
|
// _hideKids property and process all children.
|
|
|
|
if (isInsignificant(aT._kids[0])) {
|
|
|
|
aT._hideKids = true;
|
|
|
|
for (var i = 0; i < aT._kids.length; i++) {
|
|
|
|
sortTreeAndInsertAggregateNodes(aTotalBytes, aT._kids[i]);
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Look at all children except the last one.
|
|
|
|
for (var i = 0; i < aT._kids.length - 1; i++) {
|
|
|
|
if (isInsignificant(aT._kids[i])) {
|
|
|
|
// This child is below the significance threshold. If there are other
|
|
|
|
// (smaller) children remaining, move them under an aggregate node.
|
2011-07-25 18:51:38 -07:00
|
|
|
var i0 = i;
|
2012-01-26 14:02:49 -08:00
|
|
|
var nAgg = aT._kids.length - i0;
|
|
|
|
// Create an aggregate node.
|
|
|
|
var aggT = new TreeNode("(" + nAgg + " tiny)");
|
2011-07-25 18:51:38 -07:00
|
|
|
var aggBytes = 0;
|
|
|
|
for ( ; i < aT._kids.length; i++) {
|
|
|
|
aggBytes += aT._kids[i]._amount;
|
2012-01-26 14:02:49 -08:00
|
|
|
aggT._kids.push(aT._kids[i]);
|
2011-05-03 17:12:58 -07:00
|
|
|
}
|
2012-01-26 14:02:49 -08:00
|
|
|
aggT._hideKids = true;
|
|
|
|
aggT._amount = aggBytes;
|
|
|
|
aggT._description =
|
|
|
|
nAgg + " sub-trees that are below the " + kSignificanceThresholdPerc +
|
|
|
|
"% significance threshold.";
|
|
|
|
aT._kids.splice(i0, nAgg, aggT);
|
2011-08-11 00:04:44 -07:00
|
|
|
aT._kids.sort(TreeNode.compare);
|
2012-01-26 14:02:49 -08:00
|
|
|
|
|
|
|
// Process the moved children.
|
|
|
|
for (i = 0; i < aggT._kids.length; i++) {
|
|
|
|
sortTreeAndInsertAggregateNodes(aTotalBytes, aggT._kids[i]);
|
|
|
|
}
|
|
|
|
return;
|
2010-02-20 13:23:25 -08:00
|
|
|
}
|
2012-01-26 14:02:49 -08:00
|
|
|
|
|
|
|
sortTreeAndInsertAggregateNodes(aTotalBytes, aT._kids[i]);
|
2011-05-03 17:12:58 -07:00
|
|
|
}
|
2012-01-26 14:02:49 -08:00
|
|
|
|
|
|
|
// The first n-1 children were significant. Don't consider if the last child
|
|
|
|
// is significant; there's no point creating an aggregate node that only has
|
|
|
|
// one child. Just process it.
|
|
|
|
sortTreeAndInsertAggregateNodes(aTotalBytes, aT._kids[i]);
|
2011-07-25 18:51:38 -07:00
|
|
|
}
|
|
|
|
|
2012-01-26 14:02:49 -08:00
|
|
|
function genWarningText(aHasKnownHeapAllocated, aHasMozMallocUsableSize)
|
2012-01-17 20:23:55 -08:00
|
|
|
{
|
|
|
|
var warningText = "";
|
|
|
|
|
|
|
|
if (!aHasKnownHeapAllocated && !aHasMozMallocUsableSize) {
|
|
|
|
warningText =
|
|
|
|
"<p class='accuracyWarning'>WARNING: the 'heap-allocated' memory " +
|
|
|
|
"reporter and the moz_malloc_usable_size() function do not work for " +
|
|
|
|
"this platform and/or configuration. This means that " +
|
|
|
|
"'heap-unclassified' is zero and the 'explicit' tree shows " +
|
|
|
|
"much less memory than it should.</p>\n\n";
|
|
|
|
|
|
|
|
} else if (!aHasKnownHeapAllocated) {
|
|
|
|
warningText =
|
|
|
|
"<p class='accuracyWarning'>WARNING: the 'heap-allocated' memory " +
|
|
|
|
"reporter does not work for this platform and/or configuration. " +
|
|
|
|
"This means that 'heap-unclassified' is zero and the 'explicit' tree " +
|
|
|
|
"shows less memory than it should.</p>\n\n";
|
|
|
|
|
|
|
|
} else if (!aHasMozMallocUsableSize) {
|
|
|
|
warningText =
|
|
|
|
"<p class='accuracyWarning'>WARNING: the moz_malloc_usable_size() " +
|
|
|
|
"function does not work for this platform and/or configuration. " +
|
|
|
|
"This means that much of the heap-allocated memory is not measured " +
|
|
|
|
"by individual memory reporters and so will fall under " +
|
|
|
|
"'heap-unclassified'.</p>\n\n";
|
|
|
|
}
|
|
|
|
return warningText;
|
|
|
|
}
|
|
|
|
|
2011-07-25 18:51:38 -07:00
|
|
|
/**
|
|
|
|
* Generates the text for a single process.
|
|
|
|
*
|
|
|
|
* @param aProcess
|
2011-08-11 00:04:44 -07:00
|
|
|
* The name of the process.
|
2011-07-25 18:51:38 -07:00
|
|
|
* @param aReporters
|
2011-08-11 00:04:44 -07:00
|
|
|
* Table of Reporters for this process, indexed by _path.
|
2012-01-17 20:23:55 -08:00
|
|
|
* @param aHasMozMallocUsableSize
|
|
|
|
* Boolean indicating if moz_malloc_usable_size works.
|
2011-08-11 00:04:44 -07:00
|
|
|
* @return The generated text.
|
2011-07-25 18:51:38 -07:00
|
|
|
*/
|
2012-01-17 20:23:55 -08:00
|
|
|
function genProcessText(aProcess, aReporters, aHasMozMallocUsableSize)
|
2011-07-25 18:51:38 -07:00
|
|
|
{
|
2011-08-05 15:22:11 -07:00
|
|
|
var explicitTree = buildTree(aReporters, 'explicit');
|
2012-01-17 20:23:55 -08:00
|
|
|
var hasKnownHeapAllocated = fixUpExplicitTree(explicitTree, aReporters);
|
2012-01-26 14:02:49 -08:00
|
|
|
sortTreeAndInsertAggregateNodes(explicitTree._amount, explicitTree);
|
2011-08-05 15:22:11 -07:00
|
|
|
var explicitText = genTreeText(explicitTree, aProcess);
|
|
|
|
|
2012-01-17 20:23:55 -08:00
|
|
|
// Generate any warnings about inaccuracies due to platform limitations.
|
|
|
|
// The newlines give nice spacing if we cut+paste into a text buffer.
|
|
|
|
var warningText = "";
|
|
|
|
var accuracyTagText = "<p class='accuracyWarning'>";
|
|
|
|
var warningText =
|
|
|
|
genWarningText(hasKnownHeapAllocated, aHasMozMallocUsableSize);
|
|
|
|
|
2012-01-26 14:02:49 -08:00
|
|
|
// We only show these breakdown trees in verbose mode.
|
|
|
|
var mapTreeText = "";
|
2011-08-05 15:22:11 -07:00
|
|
|
kMapTreePaths.forEach(function(t) {
|
2012-01-26 14:02:49 -08:00
|
|
|
if (gVerbose) {
|
|
|
|
var tree = buildTree(aReporters, t);
|
|
|
|
|
|
|
|
// |tree| will be null if we don't have any reporters for the given path.
|
|
|
|
if (tree) {
|
|
|
|
sortTreeAndInsertAggregateNodes(tree._amount, tree);
|
|
|
|
tree._hideKids = true; // map trees are always initially collapsed
|
|
|
|
mapTreeText += genTreeText(tree, aProcess);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
ignoreTree(aReporters, t);
|
2011-09-14 17:37:45 -07:00
|
|
|
}
|
2011-08-05 15:22:11 -07:00
|
|
|
});
|
|
|
|
|
|
|
|
// We have to call genOtherText after we process all the trees, because it
|
|
|
|
// looks at all the reporters which aren't part of a tree.
|
|
|
|
var otherText = genOtherText(aReporters, aProcess);
|
|
|
|
|
|
|
|
// The newlines give nice spacing if we cut+paste into a text buffer.
|
|
|
|
return "<h1>" + aProcess + " Process</h1>\n\n" +
|
2012-01-17 20:23:55 -08:00
|
|
|
warningText + explicitText + mapTreeText + otherText +
|
2011-08-05 15:22:11 -07:00
|
|
|
"<hr></hr>";
|
2010-02-20 13:23:25 -08:00
|
|
|
}
|
|
|
|
|
2011-06-16 11:34:09 -07:00
|
|
|
/**
|
|
|
|
* Formats an int as a human-readable string.
|
|
|
|
*
|
|
|
|
* @param aN
|
2011-08-11 00:04:44 -07:00
|
|
|
* The integer to format.
|
|
|
|
* @return A human-readable string representing the int.
|
2011-06-16 11:34:09 -07:00
|
|
|
*/
|
|
|
|
function formatInt(aN)
|
|
|
|
{
|
|
|
|
var neg = false;
|
|
|
|
if (aN < 0) {
|
|
|
|
neg = true;
|
|
|
|
aN = -aN;
|
|
|
|
}
|
|
|
|
var s = "";
|
|
|
|
while (true) {
|
|
|
|
var k = aN % 1000;
|
|
|
|
aN = Math.floor(aN / 1000);
|
|
|
|
if (aN > 0) {
|
|
|
|
if (k < 10) {
|
|
|
|
s = ",00" + k + s;
|
|
|
|
} else if (k < 100) {
|
|
|
|
s = ",0" + k + s;
|
|
|
|
} else {
|
|
|
|
s = "," + k + s;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
s = k + s;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return neg ? "-" + s : s;
|
|
|
|
}
|
|
|
|
|
2011-03-31 10:19:30 -07:00
|
|
|
/**
|
2011-05-03 17:12:58 -07:00
|
|
|
* Converts a byte count to an appropriate string representation.
|
|
|
|
*
|
|
|
|
* @param aBytes
|
2011-08-11 00:04:44 -07:00
|
|
|
* The byte count.
|
|
|
|
* @return The string representation.
|
2011-03-31 10:19:30 -07:00
|
|
|
*/
|
2011-05-03 17:12:58 -07:00
|
|
|
function formatBytes(aBytes)
|
2010-02-20 13:23:25 -08:00
|
|
|
{
|
2011-05-03 17:12:58 -07:00
|
|
|
var unit = gVerbose ? "B" : "MB";
|
2011-02-16 10:43:23 -08:00
|
|
|
|
2011-05-03 17:12:58 -07:00
|
|
|
var s;
|
|
|
|
if (gVerbose) {
|
|
|
|
s = formatInt(aBytes) + " " + unit;
|
|
|
|
} else {
|
|
|
|
var mbytes = (aBytes / (1024 * 1024)).toFixed(2);
|
|
|
|
var a = String(mbytes).split(".");
|
|
|
|
s = formatInt(a[0]) + "." + a[1] + " " + unit;
|
|
|
|
}
|
|
|
|
return s;
|
2011-02-16 10:43:23 -08:00
|
|
|
}
|
|
|
|
|
2011-07-20 21:08:24 -07:00
|
|
|
/**
|
|
|
|
* Converts a percentage to an appropriate string representation.
|
|
|
|
*
|
|
|
|
* @param aPerc100x
|
2011-08-11 00:04:44 -07:00
|
|
|
* The percentage, multiplied by 100 (see nsIMemoryReporter).
|
2011-07-20 21:08:24 -07:00
|
|
|
* @return The string representation
|
|
|
|
*/
|
|
|
|
function formatPercentage(aPerc100x)
|
|
|
|
{
|
|
|
|
return (aPerc100x / 100).toFixed(2) + "%";
|
|
|
|
}
|
|
|
|
|
2011-05-03 17:12:58 -07:00
|
|
|
/**
|
2011-08-11 00:04:44 -07:00
|
|
|
* Right-justifies a string in a field of a given width, padding as necessary.
|
2011-05-03 17:12:58 -07:00
|
|
|
*
|
|
|
|
* @param aS
|
2011-08-11 00:04:44 -07:00
|
|
|
* The string.
|
2011-05-03 17:12:58 -07:00
|
|
|
* @param aN
|
2011-08-11 00:04:44 -07:00
|
|
|
* The field width.
|
2011-05-03 17:12:58 -07:00
|
|
|
* @param aC
|
2011-08-11 00:04:44 -07:00
|
|
|
* The char used to pad.
|
|
|
|
* @return The string representation.
|
2011-05-03 17:12:58 -07:00
|
|
|
*/
|
|
|
|
function pad(aS, aN, aC)
|
|
|
|
{
|
|
|
|
var padding = "";
|
|
|
|
var n2 = aN - aS.length;
|
|
|
|
for (var i = 0; i < n2; i++) {
|
|
|
|
padding += aC;
|
|
|
|
}
|
|
|
|
return padding + aS;
|
2011-02-16 10:43:23 -08:00
|
|
|
}
|
|
|
|
|
2011-05-03 17:12:58 -07:00
|
|
|
/**
|
2011-08-11 00:04:44 -07:00
|
|
|
* Gets the byte count for a particular Reporter and sets its _done
|
2011-05-22 19:49:56 -07:00
|
|
|
* property.
|
2011-05-03 17:12:58 -07:00
|
|
|
*
|
2011-06-05 18:22:45 -07:00
|
|
|
* @param aReporters
|
2011-08-11 00:04:44 -07:00
|
|
|
* Table of Reporters for this process, indexed by _path.
|
2011-06-05 18:22:45 -07:00
|
|
|
* @param aPath
|
2011-08-11 00:04:44 -07:00
|
|
|
* The path of the R.
|
2011-05-22 19:49:56 -07:00
|
|
|
* @param aDoNotMark
|
2011-08-11 00:04:44 -07:00
|
|
|
* If true, the _done property is not set.
|
|
|
|
* @return The byte count.
|
2011-05-03 17:12:58 -07:00
|
|
|
*/
|
2011-06-05 18:22:45 -07:00
|
|
|
function getBytes(aReporters, aPath, aDoNotMark)
|
2011-05-03 17:12:58 -07:00
|
|
|
{
|
2011-06-05 18:22:45 -07:00
|
|
|
var r = aReporters[aPath];
|
2011-08-11 00:04:44 -07:00
|
|
|
assert(r, "getBytes: no such Reporter: " + aPath);
|
|
|
|
if (!aDoNotMark) {
|
|
|
|
r._done = true;
|
2011-05-03 17:12:58 -07:00
|
|
|
}
|
2011-08-11 00:04:44 -07:00
|
|
|
return r._amount;
|
2011-05-03 17:12:58 -07:00
|
|
|
}
|
2010-02-20 13:23:25 -08:00
|
|
|
|
2011-05-03 17:12:58 -07:00
|
|
|
/**
|
2011-08-11 00:04:44 -07:00
|
|
|
* Gets the description for a particular Reporter.
|
2011-05-03 17:12:58 -07:00
|
|
|
*
|
2011-06-05 18:22:45 -07:00
|
|
|
* @param aReporters
|
2011-08-11 00:04:44 -07:00
|
|
|
* Table of Reporters for this process, indexed by _path.
|
2011-06-05 18:22:45 -07:00
|
|
|
* @param aPath
|
2011-08-11 00:04:44 -07:00
|
|
|
* The path of the Reporter.
|
|
|
|
* @return The description.
|
2011-05-03 17:12:58 -07:00
|
|
|
*/
|
2011-06-05 18:22:45 -07:00
|
|
|
function getDescription(aReporters, aPath)
|
2011-02-16 10:43:23 -08:00
|
|
|
{
|
2011-06-05 18:22:45 -07:00
|
|
|
var r = aReporters[aPath];
|
2011-08-11 00:04:44 -07:00
|
|
|
assert(r, "getDescription: no such Reporter: " + aPath);
|
|
|
|
return r._description;
|
2011-05-03 17:12:58 -07:00
|
|
|
}
|
2011-02-16 10:43:23 -08:00
|
|
|
|
2012-01-26 14:02:49 -08:00
|
|
|
// There's a subset of the Unicode "light" box-drawing chars that are widely
|
|
|
|
// implemented in terminals, and this code sticks to that subset to maximize
|
|
|
|
// the chance that cutting and pasting about:memory output to a terminal will
|
|
|
|
// work correctly:
|
|
|
|
const kHorizontal = "\u2500",
|
|
|
|
kVertical = "\u2502",
|
|
|
|
kUpAndRight = "\u2514",
|
|
|
|
kVerticalAndRight = "\u251c";
|
|
|
|
|
2011-05-03 17:12:58 -07:00
|
|
|
function genMrValueText(aValue)
|
|
|
|
{
|
2012-01-26 14:02:49 -08:00
|
|
|
return "<span class='mrValue'>" + aValue + " </span>";
|
2011-05-03 17:12:58 -07:00
|
|
|
}
|
2011-02-16 10:43:23 -08:00
|
|
|
|
2011-05-22 19:49:56 -07:00
|
|
|
function kindToString(aKind)
|
2011-05-03 17:12:58 -07:00
|
|
|
{
|
2011-05-22 19:49:56 -07:00
|
|
|
switch (aKind) {
|
2011-07-19 07:33:49 -07:00
|
|
|
case KIND_NONHEAP: return "(Non-heap) ";
|
|
|
|
case KIND_HEAP: return "(Heap) ";
|
2011-08-11 00:04:44 -07:00
|
|
|
case KIND_OTHER:
|
|
|
|
case undefined: return "";
|
|
|
|
default: assert(false, "bad kind in kindToString");
|
2011-05-22 19:49:56 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-06-29 22:44:17 -07:00
|
|
|
// For user-controlled strings.
|
|
|
|
function escapeAll(aStr)
|
|
|
|
{
|
|
|
|
return aStr.replace(/\&/g, '&').replace(/'/g, ''').
|
|
|
|
replace(/\</g, '<').replace(/>/g, '>').
|
|
|
|
replace(/\"/g, '"');
|
|
|
|
}
|
|
|
|
|
|
|
|
// Compartment reporter names are URLs and so can include forward slashes. But
|
|
|
|
// forward slash is the memory reporter path separator. So the memory
|
2012-01-26 14:02:49 -08:00
|
|
|
// reporters change them to backslashes. Undo that here.
|
2011-06-29 22:44:17 -07:00
|
|
|
function flipBackslashes(aStr)
|
|
|
|
{
|
|
|
|
return aStr.replace(/\\/g, '/');
|
|
|
|
}
|
|
|
|
|
|
|
|
function prepName(aStr)
|
|
|
|
{
|
2011-09-07 17:14:47 -07:00
|
|
|
return escapeAll(flipBackslashes(aStr));
|
2011-06-29 22:44:17 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
function prepDesc(aStr)
|
|
|
|
{
|
2011-10-03 12:11:14 -07:00
|
|
|
return escapeAll(flipBackslashes(aStr));
|
2011-05-22 19:49:56 -07:00
|
|
|
}
|
|
|
|
|
2012-01-26 14:02:49 -08:00
|
|
|
function genMrNameText(aKind, aShowSubtrees, aHasKids, aDesc, aName,
|
|
|
|
aHasProblem, aNMerged)
|
2011-05-22 19:49:56 -07:00
|
|
|
{
|
2012-01-26 14:02:49 -08:00
|
|
|
var text = "";
|
|
|
|
if (aHasKids) {
|
|
|
|
if (aShowSubtrees) {
|
|
|
|
text += "<span class='mrSep hidden'>++ </span>";
|
|
|
|
text += "<span class='mrSep'>-- </span>";
|
|
|
|
} else {
|
|
|
|
text += "<span class='mrSep'>++ </span>";
|
|
|
|
text += "<span class='mrSep hidden'>-- </span>";
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
text += "<span class='mrSep'>" + kHorizontal + kHorizontal + " </span>";
|
|
|
|
}
|
|
|
|
text += "<span class='mrName' title='" +
|
|
|
|
kindToString(aKind) + prepDesc(aDesc) + "'>" +
|
|
|
|
prepName(aName) + "</span>";
|
2011-07-19 16:59:17 -07:00
|
|
|
if (aHasProblem) {
|
|
|
|
const problemDesc =
|
2012-01-17 20:23:55 -08:00
|
|
|
"Warning: this memory reporter was unable to compute a useful value. ";
|
2012-01-26 14:02:49 -08:00
|
|
|
text += "<span class='mrStar' title=\"" + problemDesc + "\"> [*]</span>";
|
2011-07-19 16:59:17 -07:00
|
|
|
}
|
|
|
|
if (aNMerged) {
|
|
|
|
const dupDesc = "This value is the sum of " + aNMerged +
|
|
|
|
" memory reporters that all have the same path.";
|
2012-01-26 14:02:49 -08:00
|
|
|
text += "<span class='mrStar' title=\"" + dupDesc + "\"> [" +
|
2011-07-19 16:59:17 -07:00
|
|
|
aNMerged + "]</span>";
|
|
|
|
}
|
|
|
|
return text + '\n';
|
2010-02-20 13:23:25 -08:00
|
|
|
}
|
|
|
|
|
2012-01-26 14:02:49 -08:00
|
|
|
// This is used to record which sub-trees have been toggled, so the
|
|
|
|
// collapsed/expanded state can be replicated when the page is regenerated.
|
|
|
|
// It can end up holding IDs of nodes that no longer exist, e.g. for
|
|
|
|
// compartments that have been closed. This doesn't seem like a big deal,
|
|
|
|
// because the number is limited by the number of entries the user has changed
|
|
|
|
// from their original state.
|
|
|
|
var gToggles = {};
|
|
|
|
|
|
|
|
function toggle(aEvent)
|
|
|
|
{
|
|
|
|
// This relies on each line being a span that contains at least five spans:
|
|
|
|
// mrValue, mrPerc, mrSep ('++'), mrSep ('--'), mrName, and then zero or more
|
|
|
|
// mrStars. All whitespace must be within one of these spans for this
|
|
|
|
// function to find the right nodes. And the span containing the children of
|
|
|
|
// this line must immediately follow. Assertions check this.
|
|
|
|
|
|
|
|
function assertClassName(span, className) {
|
|
|
|
assert(span, "undefined " + className);
|
|
|
|
assert(span.nodeName === "span", "non-span " + className);
|
|
|
|
assert(span.classList.contains(className), "bad " + className);
|
|
|
|
}
|
|
|
|
|
|
|
|
// |aEvent.target| will be one of the five spans. Get the outer span.
|
|
|
|
var outerSpan = aEvent.target.parentNode;
|
|
|
|
assertClassName(outerSpan, "hasKids");
|
|
|
|
|
|
|
|
// Toggle visibility of the '++' and '--' separators.
|
|
|
|
var plusSpan = outerSpan.childNodes[2];
|
|
|
|
var minusSpan = outerSpan.childNodes[3];
|
|
|
|
assertClassName(plusSpan, "mrSep");
|
|
|
|
assertClassName(minusSpan, "mrSep");
|
|
|
|
plusSpan .classList.toggle("hidden");
|
|
|
|
minusSpan.classList.toggle("hidden");
|
|
|
|
|
|
|
|
// Toggle visibility of the span containing this node's children.
|
|
|
|
var subTreeSpan = outerSpan.nextSibling;
|
|
|
|
assertClassName(subTreeSpan, "kids");
|
|
|
|
subTreeSpan.classList.toggle("hidden");
|
|
|
|
|
|
|
|
// Record/unrecord that this sub-tree was toggled.
|
|
|
|
var treeId = outerSpan.id;
|
|
|
|
if (gToggles[treeId]) {
|
|
|
|
delete gToggles[treeId];
|
|
|
|
} else {
|
|
|
|
gToggles[treeId] = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-05-03 17:12:58 -07:00
|
|
|
/**
|
2011-05-22 19:49:56 -07:00
|
|
|
* Generates the text for the tree, including its heading.
|
2011-05-03 17:12:58 -07:00
|
|
|
*
|
|
|
|
* @param aT
|
2011-08-11 00:04:44 -07:00
|
|
|
* The tree.
|
2011-08-05 15:22:11 -07:00
|
|
|
* @param aProcess
|
|
|
|
* The process the tree corresponds to.
|
2011-08-11 00:04:44 -07:00
|
|
|
* @return The generated text.
|
2011-05-03 17:12:58 -07:00
|
|
|
*/
|
2011-08-05 15:22:11 -07:00
|
|
|
function genTreeText(aT, aProcess)
|
2011-02-16 10:43:23 -08:00
|
|
|
{
|
2011-06-16 11:34:09 -07:00
|
|
|
var treeBytes = aT._amount;
|
2011-08-11 00:04:44 -07:00
|
|
|
var rootStringLength = aT.toString().length;
|
2011-08-05 15:22:11 -07:00
|
|
|
var isExplicitTree = aT._name == 'explicit';
|
2011-05-03 17:12:58 -07:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Generates the text for a particular tree, without a heading.
|
|
|
|
*
|
2012-01-26 14:02:49 -08:00
|
|
|
* @param aPrePath
|
|
|
|
* The partial path leading up to this node.
|
2011-05-03 17:12:58 -07:00
|
|
|
* @param aT
|
2011-08-11 00:04:44 -07:00
|
|
|
* The tree.
|
2011-05-03 17:12:58 -07:00
|
|
|
* @param aIndentGuide
|
|
|
|
* Records what indentation is required for this tree. It has one
|
|
|
|
* entry per level of indentation. For each entry, ._isLastKid
|
|
|
|
* records whether the node in question is the last child, and
|
|
|
|
* ._depth records how many chars of indentation are required.
|
2011-08-11 00:04:44 -07:00
|
|
|
* @param aParentStringLength
|
|
|
|
* The length of the formatted byte count of the top node in the tree.
|
|
|
|
* @return The generated text.
|
2011-05-03 17:12:58 -07:00
|
|
|
*/
|
2012-01-26 14:02:49 -08:00
|
|
|
function genTreeText2(aPrePath, aT, aIndentGuide, aParentStringLength)
|
2011-05-03 17:12:58 -07:00
|
|
|
{
|
|
|
|
function repeatStr(aC, aN)
|
|
|
|
{
|
|
|
|
var s = "";
|
|
|
|
for (var i = 0; i < aN; i++) {
|
|
|
|
s += aC;
|
|
|
|
}
|
|
|
|
return s;
|
|
|
|
}
|
|
|
|
|
2012-01-26 14:02:49 -08:00
|
|
|
// Generate the indent.
|
2011-05-03 17:12:58 -07:00
|
|
|
var indent = "<span class='treeLine'>";
|
|
|
|
if (aIndentGuide.length > 0) {
|
|
|
|
for (var i = 0; i < aIndentGuide.length - 1; i++) {
|
|
|
|
indent += aIndentGuide[i]._isLastKid ? " " : kVertical;
|
|
|
|
indent += repeatStr(" ", aIndentGuide[i]._depth - 1);
|
|
|
|
}
|
|
|
|
indent += aIndentGuide[i]._isLastKid ? kUpAndRight : kVerticalAndRight;
|
|
|
|
indent += repeatStr(kHorizontal, aIndentGuide[i]._depth - 1);
|
|
|
|
}
|
|
|
|
// Indent more if this entry is narrower than its parent, and update
|
|
|
|
// aIndentGuide accordingly.
|
2011-08-11 00:04:44 -07:00
|
|
|
var tString = aT.toString();
|
|
|
|
var extraIndentLength = Math.max(aParentStringLength - tString.length, 0);
|
2011-05-03 17:12:58 -07:00
|
|
|
if (extraIndentLength > 0) {
|
|
|
|
for (var i = 0; i < extraIndentLength; i++) {
|
|
|
|
indent += kHorizontal;
|
|
|
|
}
|
|
|
|
aIndentGuide[aIndentGuide.length - 1]._depth += extraIndentLength;
|
|
|
|
}
|
|
|
|
indent += "</span>";
|
|
|
|
|
2012-01-26 14:02:49 -08:00
|
|
|
// Generate the percentage, and determine if we should show subtrees.
|
|
|
|
var percText = "";
|
|
|
|
var showSubtrees = !aT._hideKids;
|
2011-06-16 11:34:09 -07:00
|
|
|
if (aT._amount === treeBytes) {
|
2012-01-26 14:02:49 -08:00
|
|
|
percText = "100.0";
|
2011-05-22 19:49:56 -07:00
|
|
|
} else {
|
2012-01-26 14:02:49 -08:00
|
|
|
var perc = (100 * aT._amount / treeBytes);
|
|
|
|
percText = (100 * aT._amount / treeBytes).toFixed(2);
|
|
|
|
percText = pad(percText, 5, '0');
|
|
|
|
}
|
|
|
|
percText = "<span class='mrPerc'>(" + percText + "%) </span>";
|
|
|
|
|
|
|
|
// Reinstate any previous toggling of this sub-tree.
|
|
|
|
var path = aPrePath + aT._name;
|
|
|
|
var treeId = escapeAll(aProcess + ":" + path);
|
|
|
|
if (gToggles[treeId]) {
|
|
|
|
showSubtrees = !showSubtrees;
|
2011-05-03 17:12:58 -07:00
|
|
|
}
|
|
|
|
|
2011-08-05 15:22:11 -07:00
|
|
|
// We don't want to show '(nonheap)' on a tree like 'map/vsize', since the
|
|
|
|
// whole tree is non-heap.
|
|
|
|
var kind = isExplicitTree ? aT._kind : undefined;
|
2012-01-26 14:02:49 -08:00
|
|
|
|
|
|
|
// For non-leaf nodes, the entire sub-tree is put within a span so it can
|
|
|
|
// be collapsed if the node is clicked on.
|
|
|
|
var hasKids = aT._kids.length > 0;
|
|
|
|
if (!hasKids) {
|
|
|
|
assert(!aT._hideKids, "leaf node with _hideKids set")
|
|
|
|
}
|
|
|
|
var text = indent;
|
|
|
|
if (hasKids) {
|
|
|
|
text +=
|
|
|
|
"<span onclick='toggle(event)' class='hasKids' id='" + treeId + "'>";
|
|
|
|
}
|
|
|
|
text += genMrValueText(tString) + percText;
|
|
|
|
text += genMrNameText(kind, showSubtrees, hasKids, aT._description,
|
|
|
|
aT._name, aT._hasProblem, aT._nMerged);
|
|
|
|
if (hasKids) {
|
|
|
|
var hiddenText = showSubtrees ? "" : " hidden";
|
|
|
|
// The 'kids' class is just used for sanity checking in toggle().
|
|
|
|
text += "</span><span class='kids" + hiddenText + "'>";
|
|
|
|
}
|
2011-05-03 17:12:58 -07:00
|
|
|
|
|
|
|
for (var i = 0; i < aT._kids.length; i++) {
|
|
|
|
// 3 is the standard depth, the callee adjusts it if necessary.
|
2011-05-11 16:09:50 -07:00
|
|
|
aIndentGuide.push({ _isLastKid: (i === aT._kids.length - 1), _depth: 3 });
|
2012-01-26 14:02:49 -08:00
|
|
|
text += genTreeText2(path + "/", aT._kids[i], aIndentGuide,
|
|
|
|
tString.length);
|
2011-05-03 17:12:58 -07:00
|
|
|
aIndentGuide.pop();
|
|
|
|
}
|
2012-01-26 14:02:49 -08:00
|
|
|
text += hasKids ? "</span>" : "";
|
2011-05-03 17:12:58 -07:00
|
|
|
return text;
|
|
|
|
}
|
|
|
|
|
2012-01-26 14:02:49 -08:00
|
|
|
var text = genTreeText2(/* prePath = */"", aT, [], rootStringLength);
|
2011-08-05 15:22:11 -07:00
|
|
|
|
2012-01-26 14:02:49 -08:00
|
|
|
return genSectionMarkup(aT._name, text);
|
2011-02-16 10:43:23 -08:00
|
|
|
}
|
2011-05-03 17:12:58 -07:00
|
|
|
|
2012-01-26 14:02:49 -08:00
|
|
|
function OtherReporter(aPath, aUnits, aAmount, aDescription,
|
2011-08-11 00:04:44 -07:00
|
|
|
aNMerged)
|
|
|
|
{
|
|
|
|
// Nb: _kind is not needed, it's always KIND_OTHER.
|
|
|
|
this._path = aPath;
|
|
|
|
this._units = aUnits;
|
|
|
|
if (aAmount === kUnknown) {
|
|
|
|
this._amount = 0;
|
|
|
|
this._hasProblem = true;
|
|
|
|
} else {
|
|
|
|
this._amount = aAmount;
|
|
|
|
}
|
|
|
|
this._description = aDescription;
|
|
|
|
this.asString = this.toString();
|
|
|
|
}
|
|
|
|
|
|
|
|
OtherReporter.prototype = {
|
|
|
|
toString: function() {
|
|
|
|
switch(this._units) {
|
|
|
|
case UNITS_BYTES: return formatBytes(this._amount);
|
|
|
|
case UNITS_COUNT:
|
|
|
|
case UNITS_COUNT_CUMULATIVE: return formatInt(this._amount);
|
|
|
|
case UNITS_PERCENTAGE: return formatPercentage(this._amount);
|
|
|
|
default:
|
|
|
|
assert(false, "bad units in OtherReporter.toString");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
OtherReporter.compare = function(a, b) {
|
|
|
|
return a._path < b._path ? -1 :
|
|
|
|
a._path > b._path ? 1 :
|
|
|
|
0;
|
|
|
|
};
|
|
|
|
|
2011-05-03 17:12:58 -07:00
|
|
|
/**
|
|
|
|
* Generates the text for the "Other Measurements" section.
|
|
|
|
*
|
2011-08-11 00:04:44 -07:00
|
|
|
* @param aReportersByProcess
|
|
|
|
* Table of Reporters for this process, indexed by _path.
|
2011-08-05 15:22:11 -07:00
|
|
|
* @param aProcess
|
|
|
|
* The process these reporters correspond to.
|
2011-08-11 00:04:44 -07:00
|
|
|
* @return The generated text.
|
2011-05-03 17:12:58 -07:00
|
|
|
*/
|
2011-08-05 15:22:11 -07:00
|
|
|
function genOtherText(aReportersByProcess, aProcess)
|
2011-05-03 17:12:58 -07:00
|
|
|
{
|
2011-06-05 18:22:45 -07:00
|
|
|
// Generate an array of Reporter-like elements, stripping out all the
|
2011-08-11 00:04:44 -07:00
|
|
|
// Reporters that have already been handled. Also find the width of the
|
2011-06-05 18:22:45 -07:00
|
|
|
// widest element, so we can format things nicely.
|
2011-08-11 00:04:44 -07:00
|
|
|
var maxStringLength = 0;
|
|
|
|
var otherReporters = [];
|
|
|
|
for (var path in aReportersByProcess) {
|
|
|
|
var r = aReportersByProcess[path];
|
2011-06-05 18:22:45 -07:00
|
|
|
if (!r._done) {
|
2011-08-11 00:04:44 -07:00
|
|
|
assert(r._kind === KIND_OTHER, "_kind !== KIND_OTHER for " + r._path);
|
2012-01-26 14:02:49 -08:00
|
|
|
assert(r.nMerged === undefined); // we don't allow dup'd OTHER reporters
|
2011-05-22 19:49:56 -07:00
|
|
|
var hasProblem = false;
|
2011-06-16 11:34:09 -07:00
|
|
|
if (r._amount === kUnknown) {
|
2011-05-22 19:49:56 -07:00
|
|
|
hasProblem = true;
|
|
|
|
}
|
2011-08-11 00:04:44 -07:00
|
|
|
var o = new OtherReporter(r._path, r._units, r._amount, r._description);
|
|
|
|
otherReporters.push(o);
|
|
|
|
if (o.asString.length > maxStringLength) {
|
|
|
|
maxStringLength = o.asString.length;
|
2011-05-22 19:49:56 -07:00
|
|
|
}
|
2011-05-03 17:12:58 -07:00
|
|
|
}
|
|
|
|
}
|
2011-08-11 00:04:44 -07:00
|
|
|
otherReporters.sort(OtherReporter.compare);
|
2011-05-03 17:12:58 -07:00
|
|
|
|
2011-05-22 19:49:56 -07:00
|
|
|
// Generate text for the not-yet-printed values.
|
2011-05-03 17:12:58 -07:00
|
|
|
var text = "";
|
2011-08-11 00:04:44 -07:00
|
|
|
for (var i = 0; i < otherReporters.length; i++) {
|
|
|
|
var o = otherReporters[i];
|
2012-01-26 14:02:49 -08:00
|
|
|
text += genMrValueText(pad(o.asString, maxStringLength, ' '));
|
|
|
|
text += genMrNameText(KIND_OTHER, /* showSubtrees = */true,
|
|
|
|
/* hasKids = */false, o._description, o._path,
|
|
|
|
o._hasProblem);
|
2011-05-03 17:12:58 -07:00
|
|
|
}
|
|
|
|
|
2012-01-26 14:02:49 -08:00
|
|
|
return genSectionMarkup('other', text);
|
2011-08-05 15:22:11 -07:00
|
|
|
}
|
|
|
|
|
2012-01-26 14:02:49 -08:00
|
|
|
function genSectionMarkup(aName, aText)
|
2011-08-05 15:22:11 -07:00
|
|
|
{
|
2012-01-26 14:02:49 -08:00
|
|
|
return "<h2 class='sectionHeader'>" + kTreeNames[aName] + "</h2>\n" +
|
|
|
|
"<pre class='tree'>" + aText + "</pre>\n";
|
2011-05-03 17:12:58 -07:00
|
|
|
}
|
|
|
|
|
2011-07-20 21:08:24 -07:00
|
|
|
function assert(aCond, aMsg)
|
|
|
|
{
|
|
|
|
if (!aCond) {
|
|
|
|
throw("assertion failed: " + aMsg);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-06-05 18:22:45 -07:00
|
|
|
function debug(x)
|
|
|
|
{
|
|
|
|
var content = $("content");
|
|
|
|
var div = document.createElement("div");
|
|
|
|
div.innerHTML = JSON.stringify(x);
|
|
|
|
content.appendChild(div);
|
|
|
|
}
|