New API for capturing embedder object graph in heap snapshot.

That patch introduces EmbedderGraph interface that embedders can use to
represent C++ objects that retain or are retained by V8 JS objects.

The heap snapshot generator adds nodes and edges of the EmbedderGraph to
the heap snapshot, allowing arbitrarily complex retaining paths that
cross V8/Embedder boundary.

The new functionality is enabled only if the embedder sets the
BuildEmbedderGraph callback.


Bug: chromium:749490

Cq-Include-Trybots: master.tryserver.chromium.linux:linux_chromium_rel_ng
Change-Id: I10a1fa000d6d4ba47fc19d84c7cfc2c619d496fc
Reviewed-on: https://chromium-review.googlesource.com/890521
Reviewed-by: Alexei Filippov <alph@chromium.org>
Reviewed-by: Michael Lippautz <mlippautz@chromium.org>
Commit-Queue: Ulan Degenbaev <ulan@chromium.org>
Cr-Commit-Position: refs/heads/master@{#51016}
This commit is contained in:
Ulan Degenbaev 2018-01-31 20:55:31 +01:00 committed by Commit Bot
parent ca1d44e35f
commit 239dd8124b
7 changed files with 349 additions and 36 deletions

View File

@ -626,6 +626,58 @@ class V8_EXPORT AllocationProfile {
static const int kNoColumnNumberInfo = Message::kNoColumnInfo;
};
/**
* An object graph consisting of embedder objects and V8 objects.
* Edges of the graph are strong references between the objects.
* The embedder can build this graph during heap snapshot generation
* to include the embedder objects in the heap snapshot.
* Usage:
* 1) Define derived class of EmbedderGraph::Node for embedder objects.
* 2) Set the build embedder graph callback on the heap profiler using
* HeapProfiler::SetBuildEmbedderGraphCallback.
* 3) In the callback use graph->AddEdge(node1, node2) to add an edge from
* node1 to node2.
* 4) To represent references from/to V8 object, construct V8 nodes using
* graph->V8Node(value).
*/
class V8_EXPORT EmbedderGraph {
public:
class Node {
public:
Node() = default;
virtual ~Node() = default;
virtual const char* Name() = 0;
virtual size_t SizeInBytes() = 0;
virtual bool IsRootNode() { return false; }
/** Must return true for non-V8 nodes. */
virtual bool IsEmbedderNode() { return true; }
private:
Node(const Node&) = delete;
Node& operator=(const Node&) = delete;
};
/**
* Returns a node corresponding to the given V8 value. Ownership is not
* transferred. The result pointer is valid while the graph is alive.
*/
virtual Node* V8Node(const v8::Local<v8::Value>& value) = 0;
/**
* Adds the given node to the graph and takes ownership of the node.
* Returns a raw pointer to the node that is valid while the graph is alive.
*/
virtual Node* AddNode(std::unique_ptr<Node> node) = 0;
/**
* Adds an edge that represents a strong reference from the given node
* |from| to the given node |to|. The nodes must be added to the graph
* before calling this function.
*/
virtual void AddEdge(Node* from, Node* to) = 0;
virtual ~EmbedderGraph() = default;
};
/**
* Interface for controlling heap profiling. Instance of the
@ -665,6 +717,15 @@ class V8_EXPORT HeapProfiler {
typedef RetainedObjectInfo* (*WrapperInfoCallback)(uint16_t class_id,
Local<Value> wrapper);
/**
* Callback function invoked during heap snapshot generation to retrieve
* the embedder object graph. The callback should use graph->AddEdge(..) to
* add references between the objects.
* The callback must not trigger garbage collection in V8.
*/
typedef void (*BuildEmbedderGraphCallback)(v8::Isolate* isolate,
v8::EmbedderGraph* graph);
/** Returns the number of snapshots taken. */
int GetSnapshotCount();
@ -809,6 +870,7 @@ class V8_EXPORT HeapProfiler {
WrapperInfoCallback callback);
void SetGetRetainerInfosCallback(GetRetainerInfosCallback callback);
void SetBuildEmbedderGraphCallback(BuildEmbedderGraphCallback callback);
/**
* Default value of persistent handle class ID. Must not be used to

View File

@ -10481,6 +10481,12 @@ void HeapProfiler::SetGetRetainerInfosCallback(
callback);
}
void HeapProfiler::SetBuildEmbedderGraphCallback(
BuildEmbedderGraphCallback callback) {
reinterpret_cast<i::HeapProfiler*>(this)->SetBuildEmbedderGraphCallback(
callback);
}
v8::Testing::StressType internal::Testing::stress_type_ =
v8::Testing::kStressTypeOpt;

View File

@ -17,8 +17,7 @@ namespace internal {
HeapProfiler::HeapProfiler(Heap* heap)
: ids_(new HeapObjectsMap(heap)),
names_(new StringsStorage(heap)),
is_tracking_object_moves_(false),
get_retainer_infos_callback_(nullptr) {}
is_tracking_object_moves_(false) {}
static void DeleteHeapSnapshot(HeapSnapshot* snapshot_ptr) {
delete snapshot_ptr;
@ -75,6 +74,18 @@ v8::HeapProfiler::RetainerInfos HeapProfiler::GetRetainerInfos(
return infos;
}
void HeapProfiler::SetBuildEmbedderGraphCallback(
v8::HeapProfiler::BuildEmbedderGraphCallback callback) {
build_embedder_graph_callback_ = callback;
}
void HeapProfiler::BuildEmbedderGraph(Isolate* isolate,
v8::EmbedderGraph* graph) {
if (build_embedder_graph_callback_ != nullptr)
build_embedder_graph_callback_(reinterpret_cast<v8::Isolate*>(isolate),
graph);
}
HeapSnapshot* HeapProfiler::TakeSnapshot(
v8::ActivityControl* control,
v8::HeapProfiler::ObjectNameResolver* resolver) {

View File

@ -65,9 +65,15 @@ class HeapProfiler {
void SetGetRetainerInfosCallback(
v8::HeapProfiler::GetRetainerInfosCallback callback);
v8::HeapProfiler::RetainerInfos GetRetainerInfos(Isolate* isolate);
void SetBuildEmbedderGraphCallback(
v8::HeapProfiler::BuildEmbedderGraphCallback callback);
void BuildEmbedderGraph(Isolate* isolate, v8::EmbedderGraph* graph);
bool HasBuildEmbedderGraphCallback() {
return build_embedder_graph_callback_ != nullptr;
}
bool is_tracking_object_moves() const { return is_tracking_object_moves_; }
bool is_tracking_allocations() const { return !!allocation_tracker_; }
@ -92,7 +98,10 @@ class HeapProfiler {
bool is_tracking_object_moves_;
base::Mutex profiler_mutex_;
std::unique_ptr<SamplingHeapProfiler> sampling_heap_profiler_;
v8::HeapProfiler::GetRetainerInfosCallback get_retainer_infos_callback_;
v8::HeapProfiler::GetRetainerInfosCallback get_retainer_infos_callback_ =
nullptr;
v8::HeapProfiler::BuildEmbedderGraphCallback build_embedder_graph_callback_ =
nullptr;
DISALLOW_COPY_AND_ASSIGN(HeapProfiler);
};

View File

@ -1969,6 +1969,57 @@ void V8HeapExplorer::TagGlobalObjects() {
}
}
class EmbedderGraphImpl : public EmbedderGraph {
public:
struct Edge {
Node* from;
Node* to;
};
class V8NodeImpl : public Node {
public:
explicit V8NodeImpl(Object* object) : object_(object) {}
Object* GetObject() { return object_; }
// Node overrides.
bool IsEmbedderNode() override { return false; }
const char* Name() override {
// The name should be retrieved via GetObject().
UNREACHABLE();
return "";
}
size_t SizeInBytes() override {
// The size should be retrieved via GetObject().
UNREACHABLE();
return 0;
}
private:
Object* object_;
};
Node* V8Node(const v8::Local<v8::Value>& value) final {
Handle<Object> object = v8::Utils::OpenHandle(*value);
DCHECK(!object.is_null());
return AddNode(std::unique_ptr<Node>(new V8NodeImpl(*object)));
}
Node* AddNode(std::unique_ptr<Node> node) final {
Node* result = node.get();
nodes_.push_back(std::move(node));
return result;
}
void AddEdge(Node* from, Node* to) final { edges_.push_back({from, to}); }
const std::vector<std::unique_ptr<Node>>& nodes() { return nodes_; }
const std::vector<Edge>& edges() { return edges_; }
private:
std::vector<std::unique_ptr<Node>> nodes_;
std::vector<Edge> edges_;
};
class GlobalHandlesExtractor : public PersistentHandleVisitor {
public:
explicit GlobalHandlesExtractor(NativeObjectsExplorer* explorer)
@ -2020,6 +2071,35 @@ HeapEntry* BasicHeapEntriesAllocator::AllocateEntry(HeapThing ptr) {
0);
}
class EmbedderGraphEntriesAllocator : public HeapEntriesAllocator {
public:
EmbedderGraphEntriesAllocator(HeapSnapshot* snapshot,
HeapEntry::Type entries_type)
: snapshot_(snapshot),
names_(snapshot_->profiler()->names()),
heap_object_map_(snapshot_->profiler()->heap_object_map()),
entries_type_(entries_type) {}
virtual HeapEntry* AllocateEntry(HeapThing ptr);
private:
HeapSnapshot* snapshot_;
StringsStorage* names_;
HeapObjectsMap* heap_object_map_;
HeapEntry::Type entries_type_;
};
HeapEntry* EmbedderGraphEntriesAllocator::AllocateEntry(HeapThing ptr) {
EmbedderGraphImpl::Node* node =
reinterpret_cast<EmbedderGraphImpl::Node*>(ptr);
DCHECK(node->IsEmbedderNode());
const char* name = names_->GetCopy(node->Name());
size_t size = node->SizeInBytes();
return snapshot_->AddEntry(
entries_type_, name,
static_cast<SnapshotObjectId>(reinterpret_cast<uintptr_t>(node) << 1),
static_cast<int>(size), 0);
}
NativeObjectsExplorer::NativeObjectsExplorer(
HeapSnapshot* snapshot, SnapshottingProgressReportingInterface* progress)
: isolate_(snapshot->profiler()->heap_object_map()->heap()->isolate()),
@ -2028,13 +2108,13 @@ NativeObjectsExplorer::NativeObjectsExplorer(
embedder_queried_(false),
objects_by_info_(RetainedInfosMatch),
native_groups_(StringsMatch),
filler_(nullptr) {
synthetic_entries_allocator_ =
new BasicHeapEntriesAllocator(snapshot, HeapEntry::kSynthetic);
native_entries_allocator_ =
new BasicHeapEntriesAllocator(snapshot, HeapEntry::kNative);
}
synthetic_entries_allocator_(
new BasicHeapEntriesAllocator(snapshot, HeapEntry::kSynthetic)),
native_entries_allocator_(
new BasicHeapEntriesAllocator(snapshot, HeapEntry::kNative)),
embedder_graph_entries_allocator_(
new EmbedderGraphEntriesAllocator(snapshot, HeapEntry::kNative)),
filler_(nullptr) {}
NativeObjectsExplorer::~NativeObjectsExplorer() {
for (base::HashMap::Entry* p = objects_by_info_.Start(); p != nullptr;
@ -2052,8 +2132,6 @@ NativeObjectsExplorer::~NativeObjectsExplorer() {
reinterpret_cast<v8::RetainedObjectInfo*>(p->value);
info->Dispose();
}
delete synthetic_entries_allocator_;
delete native_entries_allocator_;
}
@ -2100,13 +2178,14 @@ void NativeObjectsExplorer::FillEdges() {
*pair.first->Get(reinterpret_cast<v8::Isolate*>(isolate_)));
HeapObject* parent = HeapObject::cast(*parent_object);
int parent_entry =
filler_->FindOrAddEntry(parent, native_entries_allocator_)->index();
filler_->FindOrAddEntry(parent, native_entries_allocator_.get())
->index();
DCHECK_NE(parent_entry, HeapEntry::kNoEntry);
Handle<Object> child_object = v8::Utils::OpenHandle(
*pair.second->Get(reinterpret_cast<v8::Isolate*>(isolate_)));
HeapObject* child = HeapObject::cast(*child_object);
HeapEntry* child_entry =
filler_->FindOrAddEntry(child, native_entries_allocator_);
filler_->FindOrAddEntry(child, native_entries_allocator_.get());
filler_->SetNamedReference(HeapGraphEdge::kInternal, parent_entry, "native",
child_entry);
}
@ -2125,25 +2204,66 @@ std::vector<HeapObject*>* NativeObjectsExplorer::GetVectorMaybeDisposeInfo(
return reinterpret_cast<std::vector<HeapObject*>*>(entry->value);
}
HeapEntry* NativeObjectsExplorer::EntryForEmbedderGraphNode(
EmbedderGraphImpl::Node* node) {
if (node->IsEmbedderNode()) {
return filler_->FindOrAddEntry(node,
embedder_graph_entries_allocator_.get());
} else {
EmbedderGraphImpl::V8NodeImpl* v8_node =
static_cast<EmbedderGraphImpl::V8NodeImpl*>(node);
Object* object = v8_node->GetObject();
if (object->IsSmi()) return nullptr;
HeapEntry* entry = filler_->FindEntry(HeapObject::cast(object));
return entry;
}
}
bool NativeObjectsExplorer::IterateAndExtractReferences(
SnapshotFiller* filler) {
filler_ = filler;
FillRetainedObjects();
FillEdges();
if (EstimateObjectsCount() > 0) {
for (base::HashMap::Entry* p = objects_by_info_.Start(); p != nullptr;
p = objects_by_info_.Next(p)) {
v8::RetainedObjectInfo* info =
reinterpret_cast<v8::RetainedObjectInfo*>(p->key);
SetNativeRootReference(info);
std::vector<HeapObject*>* objects =
reinterpret_cast<std::vector<HeapObject*>*>(p->value);
for (HeapObject* object : *objects) {
SetWrapperNativeReferences(object, info);
if (snapshot_->profiler()->HasBuildEmbedderGraphCallback()) {
v8::HandleScope scope(reinterpret_cast<v8::Isolate*>(isolate_));
DisallowHeapAllocation no_allocation;
EmbedderGraphImpl graph;
snapshot_->profiler()->BuildEmbedderGraph(isolate_, &graph);
// Fill root nodes of the graph.
for (const auto& node : graph.nodes()) {
if (node->IsRootNode()) {
filler_->SetIndexedAutoIndexReference(
HeapGraphEdge::kElement, snapshot_->root()->index(),
EntryForEmbedderGraphNode(node.get()));
}
}
SetRootNativeRootsReference();
// Fill edges of the graph.
for (const auto& edge : graph.edges()) {
HeapEntry* from = EntryForEmbedderGraphNode(edge.from);
HeapEntry* to = EntryForEmbedderGraphNode(edge.to);
// The |from| and |to| can nullptr if the corrsponding node is a V8 node
// pointing to a Smi.
if (from && to) {
filler_->SetIndexedAutoIndexReference(HeapGraphEdge::kElement,
from->index(), to);
}
}
} else {
FillRetainedObjects();
FillEdges();
if (EstimateObjectsCount() > 0) {
for (base::HashMap::Entry* p = objects_by_info_.Start(); p != nullptr;
p = objects_by_info_.Next(p)) {
v8::RetainedObjectInfo* info =
reinterpret_cast<v8::RetainedObjectInfo*>(p->key);
SetNativeRootReference(info);
std::vector<HeapObject*>* objects =
reinterpret_cast<std::vector<HeapObject*>*>(p->value);
for (HeapObject* object : *objects) {
SetWrapperNativeReferences(object, info);
}
}
SetRootNativeRootsReference();
}
}
filler_ = nullptr;
return true;
@ -2196,12 +2316,12 @@ NativeGroupRetainedObjectInfo* NativeObjectsExplorer::FindOrAddGroupInfo(
void NativeObjectsExplorer::SetNativeRootReference(
v8::RetainedObjectInfo* info) {
HeapEntry* child_entry =
filler_->FindOrAddEntry(info, native_entries_allocator_);
filler_->FindOrAddEntry(info, native_entries_allocator_.get());
DCHECK_NOT_NULL(child_entry);
NativeGroupRetainedObjectInfo* group_info =
FindOrAddGroupInfo(info->GetGroupLabel());
HeapEntry* group_entry =
filler_->FindOrAddEntry(group_info, synthetic_entries_allocator_);
filler_->FindOrAddEntry(group_info, synthetic_entries_allocator_.get());
// |FindOrAddEntry| can move and resize the entries backing store. Reload
// potentially-stale pointer.
child_entry = filler_->FindEntry(info);
@ -2217,7 +2337,7 @@ void NativeObjectsExplorer::SetWrapperNativeReferences(
HeapEntry* wrapper_entry = filler_->FindEntry(wrapper);
DCHECK_NOT_NULL(wrapper_entry);
HeapEntry* info_entry =
filler_->FindOrAddEntry(info, native_entries_allocator_);
filler_->FindOrAddEntry(info, native_entries_allocator_.get());
DCHECK_NOT_NULL(info_entry);
filler_->SetNamedReference(HeapGraphEdge::kInternal,
wrapper_entry->index(),
@ -2235,7 +2355,7 @@ void NativeObjectsExplorer::SetRootNativeRootsReference() {
NativeGroupRetainedObjectInfo* group_info =
static_cast<NativeGroupRetainedObjectInfo*>(entry->value);
HeapEntry* group_entry =
filler_->FindOrAddEntry(group_info, native_entries_allocator_);
filler_->FindOrAddEntry(group_info, native_entries_allocator_.get());
DCHECK_NOT_NULL(group_entry);
filler_->SetIndexedAutoIndexReference(
HeapGraphEdge::kElement,

View File

@ -514,6 +514,8 @@ class NativeObjectsExplorer {
NativeGroupRetainedObjectInfo* FindOrAddGroupInfo(const char* label);
HeapEntry* EntryForEmbedderGraphNode(EmbedderGraph::Node* node);
Isolate* isolate_;
HeapSnapshot* snapshot_;
StringsStorage* names_;
@ -522,8 +524,9 @@ class NativeObjectsExplorer {
// RetainedObjectInfo* -> std::vector<HeapObject*>*
base::CustomMatcherHashMap objects_by_info_;
base::CustomMatcherHashMap native_groups_;
HeapEntriesAllocator* synthetic_entries_allocator_;
HeapEntriesAllocator* native_entries_allocator_;
std::unique_ptr<HeapEntriesAllocator> synthetic_entries_allocator_;
std::unique_ptr<HeapEntriesAllocator> native_entries_allocator_;
std::unique_ptr<HeapEntriesAllocator> embedder_graph_entries_allocator_;
// Used during references extraction.
SnapshotFiller* filler_;
v8::HeapProfiler::RetainerEdges edges_;

View File

@ -98,7 +98,6 @@ class NamedEntriesDetector {
static const v8::HeapGraphNode* GetGlobalObject(
const v8::HeapSnapshot* snapshot) {
CHECK_EQ(2, snapshot->GetRoot()->GetChildrenCount());
// The 0th-child is (GC Roots), 1st is the user root.
const v8::HeapGraphNode* global_obj =
snapshot->GetRoot()->GetChild(1)->GetToNode();
@ -107,6 +106,32 @@ static const v8::HeapGraphNode* GetGlobalObject(
return global_obj;
}
static const char* GetName(const v8::HeapGraphNode* node) {
return const_cast<i::HeapEntry*>(reinterpret_cast<const i::HeapEntry*>(node))
->name();
}
static size_t GetSize(const v8::HeapGraphNode* node) {
return const_cast<i::HeapEntry*>(reinterpret_cast<const i::HeapEntry*>(node))
->self_size();
}
static const v8::HeapGraphNode* GetChildByName(const v8::HeapGraphNode* node,
const char* name) {
for (int i = 0, count = node->GetChildrenCount(); i < count; ++i) {
const v8::HeapGraphNode* child = node->GetChild(i)->GetToNode();
if (!strcmp(name, GetName(child))) {
return child;
}
}
return nullptr;
}
static const v8::HeapGraphNode* GetRootChild(const v8::HeapSnapshot* snapshot,
const char* name) {
return GetChildByName(snapshot->GetRoot(), name);
}
static const v8::HeapGraphNode* GetProperty(v8::Isolate* isolate,
const v8::HeapGraphNode* node,
v8::HeapGraphEdge::Type type,
@ -2789,11 +2814,88 @@ TEST(JSPromise) {
}
}
class EmbedderNode : public v8::EmbedderGraph::Node {
public:
explicit EmbedderNode(const char* name, size_t size)
: name_(name), size_(size) {}
// Graph::Node overrides.
const char* Name() override { return name_; }
size_t SizeInBytes() override { return size_; }
private:
const char* name_;
size_t size_;
};
class EmbedderRootNode : public EmbedderNode {
public:
explicit EmbedderRootNode(const char* name) : EmbedderNode(name, 0) {}
// Graph::Node override.
bool IsRootNode() { return true; }
};
// Used to pass the global object to the BuildEmbedderGraph callback.
// Otherwise, the callback has to iterate the global handles to find the
// global object.
v8::Local<v8::Value>* global_object_pointer;
void BuildEmbedderGraph(v8::Isolate* v8_isolate, v8::EmbedderGraph* graph) {
using Node = v8::EmbedderGraph::Node;
Node* global_node = graph->V8Node(*global_object_pointer);
Node* embedder_node_A = graph->AddNode(
std::unique_ptr<Node>(new EmbedderNode("EmbedderNodeA", 10)));
Node* embedder_node_B = graph->AddNode(
std::unique_ptr<Node>(new EmbedderNode("EmbedderNodeB", 20)));
Node* embedder_node_C = graph->AddNode(
std::unique_ptr<Node>(new EmbedderNode("EmbedderNodeC", 30)));
Node* embedder_root = graph->AddNode(
std::unique_ptr<Node>(new EmbedderRootNode("EmbedderRoot")));
graph->AddEdge(global_node, embedder_node_A);
graph->AddEdge(embedder_node_A, embedder_node_B);
graph->AddEdge(embedder_root, embedder_node_C);
graph->AddEdge(embedder_node_C, global_node);
}
void CheckEmbedderGraphSnapshot(v8::Isolate* isolate,
const v8::HeapSnapshot* snapshot) {
const v8::HeapGraphNode* global = GetGlobalObject(snapshot);
const v8::HeapGraphNode* embedder_node_A =
GetChildByName(global, "EmbedderNodeA");
CHECK_EQ(10, GetSize(embedder_node_A));
const v8::HeapGraphNode* embedder_node_B =
GetChildByName(embedder_node_A, "EmbedderNodeB");
CHECK_EQ(20, GetSize(embedder_node_B));
const v8::HeapGraphNode* embedder_root =
GetRootChild(snapshot, "EmbedderRoot");
CHECK(embedder_root);
const v8::HeapGraphNode* embedder_node_C =
GetChildByName(embedder_root, "EmbedderNodeC");
CHECK_EQ(30, GetSize(embedder_node_C));
const v8::HeapGraphNode* global_reference =
GetChildByName(embedder_node_C, "Object");
CHECK(global_reference);
}
TEST(EmbedderGraph) {
LocalContext env;
v8::HandleScope scope(env->GetIsolate());
i::Isolate* isolate = reinterpret_cast<i::Isolate*>(env->GetIsolate());
v8::Local<v8::Value> global_object =
v8::Utils::ToLocal(i::Handle<i::JSObject>(
(isolate->context()->native_context()->global_object())));
global_object_pointer = &global_object;
v8::HeapProfiler* heap_profiler = env->GetIsolate()->GetHeapProfiler();
heap_profiler->SetBuildEmbedderGraphCallback(BuildEmbedderGraph);
const v8::HeapSnapshot* snapshot = heap_profiler->TakeHeapSnapshot();
CHECK(ValidateSnapshot(snapshot));
CheckEmbedderGraphSnapshot(env->GetIsolate(), snapshot);
}
static inline i::Address ToAddress(int n) {
return reinterpret_cast<i::Address>(n);
}
TEST(AddressToTraceMap) {
i::AddressToTraceMap map;