Bug 1201213 - Add a HeapAnalyses{Worker,Client} for running heap analyses; r=jsantell

This commit is contained in:
Nick Fitzgerald 2015-09-03 17:29:40 -07:00
parent 4ed9ec0501
commit 1e3a3a0f76
9 changed files with 254 additions and 1 deletions

View File

@ -0,0 +1,74 @@
/* 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, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const DevToolsUtils = require("devtools/toolkit/DevToolsUtils");
const { DevToolsWorker } = require("devtools/toolkit/shared/worker.js");
const WORKER_URL = "resource://gre/modules/devtools/heapsnapshot/HeapAnalysesWorker.js";
let workerCounter = 0;
/**
* A HeapAnalysesClient instance provides a developer-friendly interface for
* interacting with a HeapAnalysesWorker. This enables users to be ignorant of
* the message passing protocol used to communicate with the worker. The
* HeapAnalysesClient owns the worker, and terminating the worker is done by
* terminating the client (see the `destroy` method).
*/
const HeapAnalysesClient = module.exports = function () {
this._worker = new DevToolsWorker(WORKER_URL, {
name: `HeapAnalyses-${workerCounter++}`,
verbose: DevToolsUtils.dumpn.wantLogging
});
};
/**
* Destroy the worker, causing it to release its resources (such as heap
* snapshots it has deserialized and read into memory). The client is no longer
* usable after calling this method.
*/
HeapAnalysesClient.prototype.destroy = function () {
this._worker.destroy();
};
/**
* Tell the worker to read into memory the heap snapshot at the given file
* path. This is a prerequisite for asking the worker to perform various
* analyses on a heap snapshot.
*
* @param {String} snapshotFilePath
*
* @returns Promise
* The promise is fulfilled if the heap snapshot is successfully
* deserialized and read into memory. The promise is rejected if that
* does not happen, eg due to a bad file path or malformed heap
* snapshot file.
*/
HeapAnalysesClient.prototype.readHeapSnapshot = function (snapshotFilePath) {
return this._worker.performTask("readHeapSnapshot", { snapshotFilePath });
};
/**
* Ask the worker to perform a census analysis on the heap snapshot with the
* given path. The heap snapshot at the given path must have already been read
* into memory by the worker (see `readHeapSnapshot`).
*
* @param {String} snapshotFilePath
*
* @param {Object} censusOptions
* A structured-cloneable object specifying the requested census's
* breakdown. See the "takeCensus" section of
* `js/src/doc/Debugger/Debugger.Memory.md` for detailed documentation.
*
* @returns Promise<census report>
* The report generated by the given census breakdown.
*/
HeapAnalysesClient.prototype.takeCensus = function (snapshotFilePath,
censusOptions) {
return this._worker.performTask("takeCensus", {
snapshotFilePath,
censusOptions
});
};

View File

@ -0,0 +1,37 @@
/* 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, You can obtain one at http://mozilla.org/MPL/2.0/. */
/*global ThreadSafeChromeUtils*/
// This is a worker which reads offline heap snapshots into memory and performs
// heavyweight analyses on them without blocking the main thread. A
// HeapAnalysesWorker is owned and communicated with by a HeapAnalysesClient
// instance. See HeapAnalysesClient.js.
"use strict";
importScripts("resource://gre/modules/devtools/shared/worker-helper.js");
// The set of HeapSnapshot instances this worker has read into memory. Keyed by
// snapshot file path.
const snapshots = Object.create(null);
/**
* @see HeapAnalysesClient.prototype.readHeapSnapshot
*/
workerHelper.createTask(self, "readHeapSnapshot", ({ snapshotFilePath }) => {
snapshots[snapshotFilePath] =
ThreadSafeChromeUtils.readHeapSnapshot(snapshotFilePath);
return true;
});
/**
* @see HeapAnalysesClient.prototype.takeCensus
*/
workerHelper.createTask(self, "takeCensus", ({ snapshotFilePath, censusOptions }) => {
if (!snapshots[snapshotFilePath]) {
throw new Error(`No known heap snapshot for '${snapshotFilePath}'`);
}
return snapshots[snapshotFilePath].takeCensus(censusOptions);
});

View File

@ -32,5 +32,6 @@ DEFINES['GOOGLE_PROTOBUF_NO_RTTI'] = True
FINAL_LIBRARY = 'xul'
EXTRA_JS_MODULES.devtools.heapsnapshot += [
'HeapSnapshotFileUtils.js',
'HeapAnalysesClient.js',
'HeapAnalysesWorker.js',
]

View File

@ -14,12 +14,17 @@ const { Match } = Cu.import("resource://test/Match.jsm", {});
const { Census } = Cu.import("resource://test/Census.jsm", {});
const { addDebuggerToGlobal } =
Cu.import("resource://gre/modules/jsdebugger.jsm", {});
const { Task } = Cu.import("resource://gre/modules/Task.jsm", {});
const DevToolsUtils = require("devtools/toolkit/DevToolsUtils");
const HeapAnalysesClient =
require("devtools/toolkit/heapsnapshot/HeapAnalysesClient");
const Services = require("Services");
// Always log packets when running tests. runxpcshelltests.py will throw
// the output away anyway, unless you give it the --verbose flag.
Services.prefs.setBoolPref("devtools.debugger.log", true);
DevToolsUtils.dumpn.wantLogging = true;
const SYSTEM_PRINCIPAL = Cc["@mozilla.org/systemprincipal;1"]
.createInstance(Ci.nsIPrincipal);
@ -100,6 +105,16 @@ function getFilePath(aName, aAllowMissing=false, aUsePlatformPathSeparator=false
return path;
}
function saveNewHeapSnapshot(fileName=`core-dump-${Math.random()}.tmp`) {
const filePath = getFilePath(fileName, true, true);
ok(filePath, "Should get a file path to save the core dump to.");
ChromeUtils.saveHeapSnapshot(filePath, { runtime: true });
ok(true, "Saved a heap snapshot to " + filePath);
return filePath;
}
/**
* Save a heap snapshot to the file with the given name in the current
* directory, read it back as a HeapSnapshot instance, and then take a census of

View File

@ -0,0 +1,18 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Test that the HeapAnalyses{Client,Worker} can read heap snapshots.
function run_test() {
run_next_test();
}
add_task(function* () {
const client = new HeapAnalysesClient();
const snapshotFilePath = saveNewHeapSnapshot();
yield client.readHeapSnapshot(snapshotFilePath);
ok(true, "Should have read the heap snapshot");
client.destroy();
});

View File

@ -0,0 +1,27 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Test that the HeapAnalyses{Client,Worker} can take censuses.
function run_test() {
run_next_test();
}
add_task(function* () {
const client = new HeapAnalysesClient();
const snapshotFilePath = saveNewHeapSnapshot();
yield client.readHeapSnapshot(snapshotFilePath);
ok(true, "Should have read the heap snapshot");
const report = yield client.takeCensus(snapshotFilePath);
ok(report, "Should get a report");
equal(typeof report, "object", "report should be an object");
ok(report.objects);
ok(report.scripts);
ok(report.strings);
ok(report.other);
client.destroy();
});

View File

@ -0,0 +1,29 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Test that the HeapAnalyses{Client,Worker} can take censuses with breakdown
// options.
function run_test() {
run_next_test();
}
add_task(function* () {
const client = new HeapAnalysesClient();
const snapshotFilePath = saveNewHeapSnapshot();
yield client.readHeapSnapshot(snapshotFilePath);
ok(true, "Should have read the heap snapshot");
const report = yield client.takeCensus(snapshotFilePath, {
breakdown: { by: "count", count: true, bytes: true }
});
ok(report, "Should get a report");
equal(typeof report, "object", "report should be an object");
ok(report.count);
ok(report.bytes);
client.destroy();
});

View File

@ -0,0 +1,48 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Test that the HeapAnalyses{Client,Worker} bubbles errors properly when things
// go wrong.
function run_test() {
run_next_test();
}
add_task(function* () {
const client = new HeapAnalysesClient();
// Snapshot file path to a file that doesn't exist.
let failed = false;
try {
yield client.readHeapSnapshot(getFilePath("foo-bar-baz" + Math.random(), true));
} catch (e) {
failed = true;
}
ok(failed, "should not read heap snapshots that do not exist");
// Snapshot file path to a file that is not a heap snapshot.
failed = false;
try {
yield client.readHeapSnapshot(getFilePath("test_HeapAnalyses_takeCensus_03.js"));
} catch (e) {
failed = true;
}
ok(failed, "should not be able to read a file that is not a heap snapshot as a heap snapshot");
const snapshotFilePath = saveNewHeapSnapshot();
yield client.readHeapSnapshot(snapshotFilePath);
ok(true, "Should have read the heap snapshot");
// Bad census breakdown options.
failed = false;
try {
yield client.takeCensus(snapshotFilePath, {
breakdown: { by: "some classification that we do not have" }
});
} catch (e) {
failed = true;
}
ok(failed, "should not be able to breakdown by an unknown classification");
client.destroy();
});

View File

@ -9,6 +9,10 @@ support-files =
heap-snapshot-worker.js
Match.jsm
[test_HeapAnalyses_readHeapSnapshot_01.js]
[test_HeapAnalyses_takeCensus_01.js]
[test_HeapAnalyses_takeCensus_02.js]
[test_HeapAnalyses_takeCensus_03.js]
[test_HeapSnapshot_takeCensus_01.js]
[test_HeapSnapshot_takeCensus_02.js]
[test_HeapSnapshot_takeCensus_03.js]