Bug 1226416 - Expose a method to get a node's set of immediately dominated nodes in the dominator tree; r=bz,sfink

This adds the `getImmediatelyDominated` method to `DominatorTree` which takes a
node id and returns the set of each node ids for every node that is immediately
dominated by the node with the given id. The results are sorted by greatest to
least retained size. In conjunction with the `root` attribute, this can be used
to traverse the whole dominator tree.
This commit is contained in:
Nick Fitzgerald 2015-11-30 17:38:06 -08:00
parent de384d56c2
commit 2aace9ce67
6 changed files with 172 additions and 8 deletions

View File

@ -4,7 +4,6 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/devtools/DominatorTree.h"
#include "js/Debug.h"
#include "mozilla/CycleCollectedJSRuntime.h"
#include "mozilla/dom/DominatorTreeBinding.h"
@ -12,6 +11,18 @@
namespace mozilla {
namespace devtools {
static MallocSizeOf
getCurrentThreadDebuggerMallocSizeOf()
{
auto ccrt = CycleCollectedJSRuntime::Get();
MOZ_ASSERT(ccrt);
auto rt = ccrt->Runtime();
MOZ_ASSERT(rt);
auto mallocSizeOf = JS::dbg::GetDebuggerMallocSizeOf(rt);
MOZ_ASSERT(mallocSizeOf);
return mallocSizeOf;
}
dom::Nullable<uint64_t>
DominatorTree::GetRetainedSize(uint64_t aNodeId, ErrorResult& aRv)
{
@ -20,13 +31,7 @@ DominatorTree::GetRetainedSize(uint64_t aNodeId, ErrorResult& aRv)
if (node.isNothing())
return dom::Nullable<uint64_t>();
auto ccrt = CycleCollectedJSRuntime::Get();
MOZ_ASSERT(ccrt);
auto rt = ccrt->Runtime();
MOZ_ASSERT(rt);
auto mallocSizeOf = JS::dbg::GetDebuggerMallocSizeOf(rt);
MOZ_ASSERT(mallocSizeOf);
auto mallocSizeOf = getCurrentThreadDebuggerMallocSizeOf();
JS::ubi::Node::Size size = 0;
if (!mDominatorTree.getRetainedSize(*node, mallocSizeOf, size)) {
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
@ -38,6 +43,79 @@ DominatorTree::GetRetainedSize(uint64_t aNodeId, ErrorResult& aRv)
return dom::Nullable<uint64_t>(size);
}
struct NodeAndRetainedSize
{
JS::ubi::Node mNode;
JS::ubi::Node::Size mSize;
NodeAndRetainedSize(const JS::ubi::Node& aNode, JS::ubi::Node::Size aSize)
: mNode(aNode)
, mSize(aSize)
{ }
struct Comparator
{
static bool
Equals(const NodeAndRetainedSize& aLhs, const NodeAndRetainedSize& aRhs)
{
return aLhs.mSize == aRhs.mSize;
}
static bool
LessThan(const NodeAndRetainedSize& aLhs, const NodeAndRetainedSize& aRhs)
{
// Use > because we want to sort from greatest to least retained size.
return aLhs.mSize > aRhs.mSize;
}
};
};
void
DominatorTree::GetImmediatelyDominated(uint64_t aNodeId,
dom::Nullable<nsTArray<uint64_t>>& aOutResult,
ErrorResult& aRv)
{
MOZ_ASSERT(aOutResult.IsNull());
JS::ubi::Node::Id id(aNodeId);
Maybe<JS::ubi::Node> node = mHeapSnapshot->getNodeById(id);
if (node.isNothing())
return;
// Get all immediately dominated nodes and their retained sizes.
MallocSizeOf mallocSizeOf = getCurrentThreadDebuggerMallocSizeOf();
Maybe<JS::ubi::DominatorTree::DominatedSetRange> range = mDominatorTree.getDominatedSet(*node);
MOZ_ASSERT(range.isSome(), "The node should be known, since we got it from the heap snapshot.");
size_t length = range->length();
nsTArray<NodeAndRetainedSize> dominatedNodes(length);
for (const JS::ubi::Node& dominatedNode : *range) {
JS::ubi::Node::Size retainedSize = 0;
if (NS_WARN_IF(!mDominatorTree.getRetainedSize(dominatedNode, mallocSizeOf, retainedSize))) {
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
return;
}
MOZ_ASSERT(retainedSize != 0,
"retainedSize should not be zero since we know the node is in the dominator tree.");
dominatedNodes.AppendElement(NodeAndRetainedSize(dominatedNode, retainedSize));
}
// Sort them by retained size.
NodeAndRetainedSize::Comparator comparator;
dominatedNodes.Sort(comparator);
// Fill the result with the nodes' ids.
JS::ubi::Node root = mDominatorTree.root();
aOutResult.SetValue(nsTArray<uint64_t>(length));
for (const NodeAndRetainedSize& entry : dominatedNodes) {
// The root dominates itself, but we don't want to expose that to JS.
if (entry.mNode == root)
continue;
aOutResult.Value().AppendElement(entry.mNode.identifier());
}
}
/*** Cycle Collection Boilerplate *****************************************************************/

View File

@ -52,6 +52,10 @@ public:
// [Throws] NodeSize getRetainedSize(NodeId node)
dom::Nullable<uint64_t> GetRetainedSize(uint64_t aNodeId, ErrorResult& aRv);
// [Throws] sequence<NodeId>? getImmediatelyDominated(NodeId node);
void GetImmediatelyDominated(uint64_t aNodeId, dom::Nullable<nsTArray<uint64_t>>& aOutDominated,
ErrorResult& aRv);
};
} // namespace devtools

View File

@ -0,0 +1,68 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Test that we can get the set of immediately dominated nodes for any given
// node and that this forms a tree.
function run_test() {
var dominatorTree = saveHeapSnapshotAndComputeDominatorTree();
equal(typeof dominatorTree.getImmediatelyDominated, "function",
"getImmediatelyDominated should be a function");
// Do a traversal of the dominator tree.
//
// Note that we don't assert directly, only if we get an unexpected
// value. There are just way too many nodes in the heap graph to assert for
// every one. This test would constantly time out and assertion messages would
// overflow the log size.
var root = dominatorTree.root;
var seen = new Set();
var stack = [root];
while (stack.length > 0) {
var top = stack.pop();
if (seen.has(top)) {
ok(false,
"This is a tree, not a graph: we shouldn't have multiple edges to the same node");
}
seen.add(top);
if (seen.size % 1000 === 0) {
dumpn("Progress update: seen size = " + seen.size);
}
var newNodes = dominatorTree.getImmediatelyDominated(top);
if (Object.prototype.toString.call(newNodes) !== "[object Array]") {
ok(false, "getImmediatelyDominated should return an array for known node ids");
}
var topSize = dominatorTree.getRetainedSize(top);
var lastSize = Infinity;
for (var i = 0; i < newNodes.length; i++) {
if (typeof newNodes[i] !== "number") {
ok(false, "Every dominated id should be a number");
}
var thisSize = dominatorTree.getRetainedSize(newNodes[i]);
if (thisSize >= topSize) {
ok(false, "the size of children in the dominator tree should always be less than that of their parent");
}
if (thisSize > lastSize) {
ok(false,
"children should be sorted by greatest to least retained size, "
+ "lastSize = " + lastSize + ", thisSize = " + thisSize);
}
lastSize = thisSize;
stack.push(newNodes[i]);
}
}
ok(true, "Successfully walked the tree");
dumpn("Walked " + seen.size + " nodes");
do_test_finished();
}

View File

@ -32,6 +32,7 @@ support-files =
[test_DominatorTree_02.js]
[test_DominatorTree_03.js]
[test_DominatorTree_04.js]
[test_DominatorTree_05.js]
[test_HeapAnalyses_getCreationTime_01.js]
[test_HeapAnalyses_readHeapSnapshot_01.js]
[test_HeapAnalyses_takeCensusDiff_01.js]

View File

@ -53,4 +53,12 @@ interface DominatorTree {
*/
[Throws]
NodeSize? getRetainedSize(NodeId node);
/**
* Get the set of ids of nodes immediately dominated by the node with the
* given id. The resulting array is sorted by greatest to least retained
* size. If given an invalid id, null is returned. Throws an error on OOM.
*/
[Throws]
sequence<NodeId>? getImmediatelyDominated(NodeId node);
};

View File

@ -141,6 +141,11 @@ class JS_PUBLIC_API(DominatorTree)
return DominatedNodePtr(postOrder, endPtr);
}
size_t length() const {
MOZ_ASSERT(beginPtr <= endPtr);
return endPtr - beginPtr;
}
/**
* Safely skip ahead `n` dominators in the range, in O(1) time.
*