Bug 1226176 - Compute retained sizes in dominator trees and expose them to JavaScript; r=bz,sfink

This commit is contained in:
Nick Fitzgerald 2015-11-20 09:08:15 -08:00
parent f9e24efcb2
commit 11e1dd10a1
10 changed files with 190 additions and 9 deletions

View File

@ -4,14 +4,45 @@
* 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"
namespace mozilla {
namespace devtools {
dom::Nullable<uint64_t>
DominatorTree::GetRetainedSize(uint64_t aNodeId, ErrorResult& aRv)
{
JS::ubi::Node::Id id(aNodeId);
auto node = mHeapSnapshot->getNodeById(id);
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);
JS::ubi::Node::Size size = 0;
if (!mDominatorTree.getRetainedSize(*node, mallocSizeOf, size)) {
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
return dom::Nullable<uint64_t>();
}
MOZ_ASSERT(size != 0,
"The node should not have been unknown since we got it from the heap snapshot.");
return dom::Nullable<uint64_t>(size);
}
/*** Cycle Collection Boilerplate *****************************************************************/
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(DominatorTree, mParent)
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(DominatorTree, mParent, mHeapSnapshot)
NS_IMPL_CYCLE_COLLECTING_ADDREF(DominatorTree)
NS_IMPL_CYCLE_COLLECTING_RELEASE(DominatorTree)

View File

@ -6,6 +6,7 @@
#ifndef mozilla_devtools_DominatorTree__
#define mozilla_devtools_DominatorTree__
#include "mozilla/devtools/HeapSnapshot.h"
#include "mozilla/dom/BindingDeclarations.h"
#include "mozilla/ErrorResult.h"
#include "mozilla/RefCounted.h"
@ -25,13 +26,17 @@ protected:
private:
JS::ubi::DominatorTree mDominatorTree;
RefPtr<HeapSnapshot> mHeapSnapshot;
public:
explicit DominatorTree(JS::ubi::DominatorTree&& aDominatorTree, nsISupports* aParent)
explicit DominatorTree(JS::ubi::DominatorTree&& aDominatorTree, HeapSnapshot* aHeapSnapshot,
nsISupports* aParent)
: mParent(aParent)
, mDominatorTree(Move(aDominatorTree))
, mHeapSnapshot(aHeapSnapshot)
{
MOZ_ASSERT(aParent);
MOZ_ASSERT(aHeapSnapshot);
};
NS_DECL_CYCLE_COLLECTING_ISUPPORTS;
@ -42,7 +47,11 @@ public:
virtual JSObject* WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) override;
// readonly attribute NodeId root
uint64_t Root() const { return mDominatorTree.root().identifier(); }
// [Throws] NodeSize getRetainedSize(NodeId node)
dom::Nullable<uint64_t> GetRetainedSize(uint64_t aNodeId, ErrorResult& aRv);
};
} // namespace devtools

View File

@ -11,12 +11,14 @@
#include "js/Debug.h"
#include "js/TypeDecls.h"
#include "js/UbiNodeCensus.h"
#include "js/UbiNodeBreadthFirst.h"
#include "js/UbiNodeCensus.h"
#include "js/UbiNodeDominatorTree.h"
#include "mozilla/Attributes.h"
#include "mozilla/devtools/AutoMemMap.h"
#include "mozilla/devtools/CoreDump.pb.h"
#include "mozilla/devtools/DeserializedNode.h"
#include "mozilla/devtools/DominatorTree.h"
#include "mozilla/devtools/FileDescriptorOutputStream.h"
#include "mozilla/devtools/HeapSnapshotTempFileHelperChild.h"
#include "mozilla/devtools/ZeroCopyNSIOutputStream.h"
@ -538,7 +540,7 @@ HeapSnapshot::ComputeDominatorTree(ErrorResult& rv)
return nullptr;
}
return MakeAndAddRef<DominatorTree>(Move(*maybeTree), mParent);
return MakeAndAddRef<DominatorTree>(Move(*maybeTree), this, mParent);
}

View File

@ -9,7 +9,6 @@
#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"
@ -30,6 +29,8 @@
namespace mozilla {
namespace devtools {
class DominatorTree;
struct NSFreePolicy {
void operator()(void* ptr) {
NS_Free(ptr);
@ -148,6 +149,13 @@ public:
return JS::ubi::Node(const_cast<DeserializedNode*>(&node));
}
Maybe<JS::ubi::Node> getNodeById(JS::ubi::Node::Id nodeId) {
auto p = nodes.lookup(nodeId);
if (!p)
return Nothing();
return Some(JS::ubi::Node(const_cast<DeserializedNode*>(&*p)));
}
void TakeCensus(JSContext* cx, JS::HandleObject options,
JS::MutableHandleValue rval, ErrorResult& rv);

View File

@ -0,0 +1,22 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Test that we can get the retained sizes of dominator trees.
function run_test() {
const dominatorTree = saveHeapSnapshotAndComputeDominatorTree();
equal(typeof dominatorTree.getRetainedSize, "function",
"getRetainedSize should be a function");
const size = dominatorTree.getRetainedSize(dominatorTree.root);
ok(size, "should get a size for the root");
equal(typeof size, "number", "retained sizes should be a number");
equal(Math.floor(size), size, "size should be an integer");
ok(size > 0, "size should be positive");
ok(size <= Math.pow(2, 64), "size should be less than or equal to 2^64");
const bad = dominatorTree.getRetainedSize(1);
equal(bad, null, "null is returned for unknown node ids");
do_test_finished();
}

View File

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

View File

@ -5,6 +5,7 @@
*/
typedef unsigned long long NodeId;
typedef unsigned long long NodeSize;
/**
* In a directed graph with a root node `R`, a node `A` is said to "dominate" a
@ -45,4 +46,11 @@ interface DominatorTree {
* tree was created from.
*/
readonly attribute NodeId root;
/**
* Get the retained size of the node with the given id. If given an invalid
* id, null is returned. Throws an error on OOM.
*/
[Throws]
NodeSize? getRetainedSize(NodeId node);
};

View File

@ -789,7 +789,11 @@ class Node {
using Size = Base::Size;
Size size(mozilla::MallocSizeOf mallocSizeof) const {
return base()->size(mallocSizeof);
auto size = base()->size(mallocSizeof);
MOZ_ASSERT(size > 0,
"C++ does not have zero-sized types! Choose 1 if you just need a "
"conservative default.");
return size;
}
UniquePtr<EdgeRange> edges(JSRuntime* rt, bool wantNames = true) const {

View File

@ -290,6 +290,7 @@ class JS_PUBLIC_API(DominatorTree)
NodeToIndexMap nodeToPostOrderIndex;
mozilla::Vector<uint32_t> doms;
DominatedSets dominatedSets;
mozilla::Maybe<mozilla::Vector<JS::ubi::Node::Size>> retainedSizes;
private:
// We use `UNDEFINED` as a sentinel value in the `doms` vector to signal
@ -303,6 +304,7 @@ class JS_PUBLIC_API(DominatorTree)
, nodeToPostOrderIndex(mozilla::Move(nodeToPostOrderIndex))
, doms(mozilla::Move(doms))
, dominatedSets(mozilla::Move(dominatedSets))
, retainedSizes(mozilla::Nothing())
{ }
static uint32_t intersect(mozilla::Vector<uint32_t>& doms, uint32_t finger1, uint32_t finger2) {
@ -415,6 +417,47 @@ class JS_PUBLIC_API(DominatorTree)
void assertSanity() const {
MOZ_ASSERT(postOrder.length() == doms.length());
MOZ_ASSERT(postOrder.length() == nodeToPostOrderIndex.count());
MOZ_ASSERT_IF(retainedSizes.isSome(), postOrder.length() == retainedSizes->length());
}
bool computeRetainedSizes(mozilla::MallocSizeOf mallocSizeOf) {
MOZ_ASSERT(retainedSizes.isNothing());
auto length = postOrder.length();
retainedSizes.emplace();
if (!retainedSizes->growBy(length)) {
retainedSizes = mozilla::Nothing();
return false;
}
// Iterate in forward order so that we know all of a node's children in
// the dominator tree have already had their retained size
// computed. Then we can simply say that the retained size of a node is
// its shallow size (JS::ubi::Node::size) plus the retained sizes of its
// immediate children in the tree.
for (uint32_t i = 0; i < length; i++) {
auto size = postOrder[i].size(mallocSizeOf);
for (const auto& dominated : dominatedSets.dominatedSet(postOrder, i)) {
// The root node dominates itself, but shouldn't contribute to
// its own retained size.
if (dominated == postOrder[length - 1]) {
MOZ_ASSERT(i == length - 1);
continue;
}
auto ptr = nodeToPostOrderIndex.lookup(dominated);
MOZ_ASSERT(ptr);
auto idxOfDominated = ptr->value();
MOZ_ASSERT(idxOfDominated < i);
size += retainedSizes.ref()[idxOfDominated];
}
retainedSizes.ref()[i] = size;
}
return true;
}
public:
@ -428,6 +471,7 @@ class JS_PUBLIC_API(DominatorTree)
, nodeToPostOrderIndex(mozilla::Move(rhs.nodeToPostOrderIndex))
, doms(mozilla::Move(rhs.doms))
, dominatedSets(mozilla::Move(rhs.dominatedSets))
, retainedSizes(mozilla::Move(rhs.retainedSizes))
{
MOZ_ASSERT(this != &rhs, "self-move is not allowed");
}
@ -594,7 +638,30 @@ class JS_PUBLIC_API(DominatorTree)
auto idx = ptr->value();
MOZ_ASSERT(idx < postOrder.length());
return mozilla::Some(dominatedSets.dominatedSet(postOrder, idx));
};
}
/**
* Get the retained size of the given `node`. The size is placed in
* `outSize`, or 0 if `node` is not a member of the dominator tree. Returns
* false on OOM failure, leaving `outSize` unchanged.
*/
bool getRetainedSize(const Node& node, mozilla::MallocSizeOf mallocSizeOf,
Node::Size& outSize) {
assertSanity();
auto ptr = nodeToPostOrderIndex.lookup(node);
if (!ptr) {
outSize = 0;
return true;
}
if (retainedSizes.isNothing() && !computeRetainedSizes(mallocSizeOf))
return false;
auto idx = ptr->value();
MOZ_ASSERT(idx < postOrder.length());
outSize = retainedSizes.ref()[idx];
return true;
}
};
} // namespace ubi

View File

@ -36,12 +36,16 @@ template<>
struct Concrete<FakeNode> : public Base
{
static const char16_t concreteTypeName[];
const char16_t* typeName() const { return concreteTypeName; }
const char16_t* typeName() const override { return concreteTypeName; }
UniquePtr<EdgeRange> edges(JSRuntime* rt, bool wantNames) const {
UniquePtr<EdgeRange> edges(JSRuntime* rt, bool wantNames) const override {
return UniquePtr<EdgeRange>(js_new<PreComputedEdgeRange>(get().edges));
}
Node::Size size(mozilla::MallocSizeOf) const override {
return 1;
}
static void construct(void* storage, FakeNode* ptr) { new (storage) Concrete(ptr); }
protected:
@ -582,6 +586,31 @@ BEGIN_TEST(test_JS_ubi_DominatorTree)
fprintf(stderr, "Done checking %c's dominated set.\n\n", node.name);
}
struct {
FakeNode& node;
JS::ubi::Node::Size retainedSize;
} sizes[] = {
{r, 13},
{a, 1},
{b, 1},
{c, 4},
{d, 2},
{e, 1},
{f, 1},
{g, 2},
{h, 1},
{i, 1},
{j, 1},
{k, 1},
{l, 1},
};
for (auto& expected : sizes) {
JS::ubi::Node::Size actual = 0;
CHECK(tree.getRetainedSize(&expected.node, nullptr, actual));
CHECK(actual == expected.retainedSize);
}
return true;
}
END_TEST(test_JS_ubi_DominatorTree)