Bug 1225588 - Expose DominatorTree to JavaScript; r=sfink,bz

This commit adds the DominatorTree.webidl interface, which is only exposed to
chrome JS. The interface is implemented by mozilla::devtools::DominatorTree,
which is a thin wrapper around JS::ubi::DominatorTree. This does not expose any
methods on the DominatorTree interface, those will come as follow up changesets.
This commit is contained in:
Nick Fitzgerald 2015-11-18 14:12:23 -08:00
parent 23ad260321
commit 1bcd4d95d4
16 changed files with 316 additions and 3 deletions

View File

@ -0,0 +1,30 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
/* 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/. */
#include "mozilla/devtools/DominatorTree.h"
#include "mozilla/dom/DominatorTreeBinding.h"
namespace mozilla {
namespace devtools {
/*** Cycle Collection Boilerplate *****************************************************************/
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(DominatorTree, mParent)
NS_IMPL_CYCLE_COLLECTING_ADDREF(DominatorTree)
NS_IMPL_CYCLE_COLLECTING_RELEASE(DominatorTree)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DominatorTree)
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
/* virtual */ JSObject*
DominatorTree::WrapObject(JSContext* aCx, JS::HandleObject aGivenProto)
{
return dom::DominatorTreeBinding::Wrap(aCx, this, aGivenProto);
}
} // namespace devtools
} // namespace mozilla

View File

@ -0,0 +1,50 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
/* 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/. */
#ifndef mozilla_devtools_DominatorTree__
#define mozilla_devtools_DominatorTree__
#include "mozilla/dom/BindingDeclarations.h"
#include "mozilla/ErrorResult.h"
#include "mozilla/RefCounted.h"
#include "js/UbiNodeDominatorTree.h"
#include "nsWrapperCache.h"
namespace mozilla {
namespace devtools {
class DominatorTree final : public nsISupports
, public nsWrapperCache
{
protected:
nsCOMPtr<nsISupports> mParent;
virtual ~DominatorTree() { }
private:
JS::ubi::DominatorTree mDominatorTree;
public:
explicit DominatorTree(JS::ubi::DominatorTree&& aDominatorTree, nsISupports* aParent)
: mParent(aParent)
, mDominatorTree(Move(aDominatorTree))
{
MOZ_ASSERT(aParent);
};
NS_DECL_CYCLE_COLLECTING_ISUPPORTS;
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(DominatorTree);
nsISupports* GetParentObject() const { return mParent; }
virtual JSObject* WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) override;
};
} // namespace devtools
} // namespace mozilla
#endif // mozilla_devtools_DominatorTree__

View File

@ -52,6 +52,8 @@ using ::google::protobuf::io::ZeroCopyInputStream;
using JS::ubi::AtomOrTwoByteChars;
/*** Cycle Collection Boilerplate *****************************************************************/
NS_IMPL_CYCLE_COLLECTION_CLASS(HeapSnapshot)
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(HeapSnapshot)
@ -381,6 +383,9 @@ HeapSnapshot::saveStackFrame(const protobuf::StackFrame& frame,
return true;
}
#undef GET_STRING_OR_REF_WITH_PROP_NAMES
#undef GET_STRING_OR_REF
static inline bool
StreamHasData(GzipInputStream& stream)
{
@ -515,8 +520,26 @@ HeapSnapshot::TakeCensus(JSContext* cx, JS::HandleObject options,
}
}
#undef GET_STRING_OR_REF_WITH_PROP_NAMES
#undef GET_STRING_OR_REF
already_AddRefed<DominatorTree>
HeapSnapshot::ComputeDominatorTree(ErrorResult& rv)
{
Maybe<JS::ubi::DominatorTree> maybeTree;
{
auto ccrt = CycleCollectedJSRuntime::Get();
MOZ_ASSERT(ccrt);
auto rt = ccrt->Runtime();
MOZ_ASSERT(rt);
JS::AutoCheckCannotGC nogc(rt);
maybeTree = JS::ubi::DominatorTree::Create(rt, nogc, getRoot());
}
if (maybeTree.isNothing()) {
rv.Throw(NS_ERROR_OUT_OF_MEMORY);
return nullptr;
}
return MakeAndAddRef<DominatorTree>(Move(*maybeTree), mParent);
}
/*** Saving Heap Snapshots ************************************************************************/

View File

@ -9,6 +9,7 @@
#include "js/HashTable.h"
#include "mozilla/ErrorResult.h"
#include "mozilla/devtools/DeserializedNode.h"
#include "mozilla/devtools/DominatorTree.h"
#include "mozilla/dom/BindingDeclarations.h"
#include "mozilla/dom/Nullable.h"
#include "mozilla/HashFunctions.h"
@ -150,6 +151,8 @@ public:
void TakeCensus(JSContext* cx, JS::HandleObject options,
JS::MutableHandleValue rval, ErrorResult& rv);
already_AddRefed<DominatorTree> ComputeDominatorTree(ErrorResult& rv);
dom::Nullable<uint64_t> GetCreationTime() {
static const uint64_t maxTime = uint64_t(1) << 53;
if (timestamp.isSome() && timestamp.ref() <= maxTime) {

View File

@ -15,6 +15,7 @@ EXPORTS.mozilla.devtools += [
'AutoMemMap.h',
'CoreDump.pb.h',
'DeserializedNode.h',
'DominatorTree.h',
'FileDescriptorOutputStream.h',
'HeapSnapshot.h',
'HeapSnapshotTempFileHelperChild.h',
@ -32,6 +33,7 @@ SOURCES += [
'AutoMemMap.cpp',
'CoreDump.pb.cc',
'DeserializedNode.cpp',
'DominatorTree.cpp',
'FileDescriptorOutputStream.cpp',
'HeapSnapshot.cpp',
'HeapSnapshotTempFileHelperParent.cpp',

View File

@ -3,5 +3,6 @@ tags = devtools devtools-memory
skip-if = buildapp == 'b2g' || os == 'android'
support-files =
[test_DominatorTree_01.html]
[test_SaveHeapSnapshot.html]

View File

@ -0,0 +1,37 @@
<!DOCTYPE HTML>
<html>
<!--
Sanity test that we can compute dominator trees from a heap snapshot in a web window.
-->
<head>
<meta charset="utf-8">
<title>ChromeUtils.saveHeapSnapshot test</title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
</head>
<body>
<pre id="test">
<script>
SimpleTest.waitForExplicitFinish();
window.onload = function() {
const path = ChromeUtils.saveHeapSnapshot({ runtime: true });
const snapshot = ChromeUtils.readHeapSnapshot(path);
const dominatorTree = snapshot.computeDominatorTree();
ok(dominatorTree);
ok(dominatorTree instanceof DominatorTree);
let threw = false;
try {
new DominatorTree();
} catch (e) {
threw = true;
}
ok(threw, "Constructor shouldn't be usable");
SimpleTest.finish();
};
</script>
</pre>
</body>
</html>

View File

@ -0,0 +1,47 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
console.log("Initializing worker.");
self.onmessage = e => {
console.log("Starting test.");
try {
const path = ThreadSafeChromeUtils.saveHeapSnapshot({ runtime: true });
const snapshot = ThreadSafeChromeUtils.readHeapSnapshot(path);
const dominatorTree = snapshot.computeDominatorTree();
ok(dominatorTree);
ok(dominatorTree instanceof DominatorTree);
let threw = false;
try {
new DominatorTree();
} catch (e) {
threw = true;
}
ok(threw, "Constructor shouldn't be usable");
} catch (e) {
ok(false, "Unexpected error inside worker:\n" + e.toString() + "\n" + e.stack);
} finally {
done();
}
};
// Proxy assertions to the main thread.
function ok(val, msg) {
console.log("ok(" + !!val + ", \"" + msg + "\")");
self.postMessage({
type: "assertion",
passed: !!val,
msg,
stack: Error().stack
});
}
// Tell the main thread we are done with the tests.
function done() {
console.log("done()");
self.postMessage({
type: "done"
});
}

View File

@ -0,0 +1,23 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Sanity test that we can compute dominator trees.
function run_test() {
const path = ChromeUtils.saveHeapSnapshot({ runtime: true });
const snapshot = ChromeUtils.readHeapSnapshot(path);
const dominatorTree = snapshot.computeDominatorTree();
ok(dominatorTree);
ok(dominatorTree instanceof DominatorTree);
let threw = false;
try {
new DominatorTree();
} catch (e) {
threw = true;
}
ok(threw, "Constructor shouldn't be usable");
do_test_finished();
}

View File

@ -0,0 +1,40 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Test that we can compute dominator trees from a snapshot in a worker.
add_task(function* () {
const worker = new ChromeWorker("resource://test/dominator-tree-worker.js");
worker.postMessage({});
let assertionCount = 0;
worker.onmessage = e => {
if (e.data.type !== "assertion") {
return;
}
ok(e.data.passed, e.data.msg + "\n" + e.data.stack);
assertionCount++;
};
yield waitForDone(worker);
ok(assertionCount > 0);
worker.terminate();
});
function waitForDone(w) {
return new Promise((resolve, reject) => {
w.onerror = e => {
reject();
ok(false, "Error in worker: " + e);
};
w.addEventListener("message", function listener(e) {
if (e.data.type === "done") {
w.removeEventListener("message", listener, false);
resolve();
}
}, false);
});
}

View File

@ -7,6 +7,7 @@ skip-if = toolkit == 'android' || toolkit == 'gonk'
support-files =
Census.jsm
dominator-tree-worker.js
heap-snapshot-worker.js
Match.jsm
@ -27,6 +28,8 @@ support-files =
[test_census-tree-node-06.js]
[test_census-tree-node-07.js]
[test_census-tree-node-08.js]
[test_DominatorTree_01.js]
[test_DominatorTree_02.js]
[test_HeapAnalyses_getCreationTime_01.js]
[test_HeapAnalyses_readHeapSnapshot_01.js]
[test_HeapAnalyses_takeCensusDiff_01.js]

View File

@ -409,6 +409,10 @@ DOMInterfaces = {
}
},
'DominatorTree': {
'nativeType': 'mozilla::devtools::DominatorTree'
},
'DOMException': {
'binaryNames': {
'message': 'messageMoz',

View File

@ -0,0 +1,41 @@
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/.
*/
/**
* In a directed graph with a root node `R`, a node `A` is said to "dominate" a
* node `B` iff every path from `R` to `B` contains `A`. A node `A` is said to
* be the "immediate dominator" of a node `B` iff it dominates `B`, is not `B`
* itself, and does not dominate any other nodes which also dominate `B` in
* turn.
*
* If we take every node from a graph `G` and create a new graph `T` with edges
* to each node from its immediate dominator, then `T` is a tree (each node has
* only one immediate dominator, or none if it is the root). This tree is called
* a "dominator tree".
*
* This interface represents a dominator tree constructed from a HeapSnapshot's
* heap graph. The domination relationship and dominator trees are useful tools
* for analyzing heap graphs because they tell you:
*
* - Exactly what could be reclaimed by the GC if some node `A` became
* unreachable: those nodes which are dominated by `A`,
*
* - The "retained size" of a node in the heap graph, in contrast to its
* "shallow size". The "shallow size" is the space taken by a node itself,
* not counting anything it references. The "retained size" of a node is its
* shallow size plus the size of all the things that would be collected if
* the original node wasn't (directly or indirectly) referencing them. In
* other words, the retained size is the shallow size of a node plus the
* shallow sizes of every other node it dominates. For example, the root
* node in a binary tree might have a small shallow size that does not take
* up much space itself, but it dominates the rest of the binary tree and
* its retained size is therefore significant (assuming no external
* references into the tree).
*/
[ChromeOnly, Exposed=(Window,System,Worker)]
interface DominatorTree {
};

View File

@ -56,4 +56,12 @@ interface HeapSnapshot {
*/
[Throws]
any takeCensus(object? options);
/**
* Compute the dominator tree for this heap snapshot.
*
* @see DominatorTree.webidl
*/
[Throws]
DominatorTree computeDominatorTree();
};

View File

@ -123,6 +123,7 @@ WEBIDL_FILES = [
'DOMError.webidl',
'DOMException.webidl',
'DOMImplementation.webidl',
'DominatorTree.webidl',
'DOMMatrix.webidl',
'DOMMobileMessageError.webidl',
'DOMParser.webidl',

View File

@ -121,7 +121,7 @@ class JS_PUBLIC_API(DominatorTree)
auto onEdge = [&](const Node& origin, const Edge& edge) {
auto p = predecessorSets.lookupForAdd(edge.referent);
if (!p) {
auto set = rt->make_unique<NodeSet>();
mozilla::UniquePtr<NodeSet, DeletePolicy<NodeSet>> set(js_new<NodeSet>());
if (!set ||
!set->init() ||
!predecessorSets.add(p, edge.referent, mozilla::Move(set)))