Skip to content

Commit

Permalink
profiler: Allow querying SnapshotObjectId for native objects
Browse files Browse the repository at this point in the history
- Adds regular native heap entries to the HeapObjectsMap.
- Adds a side map for keeping a mapping of native objects to their canonical
  heap entry that they have been merged into.

Change-Id: Ida00628126ded1948ceb2a0cbe14da817af7f361
Bug: chromium:988350
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1720810
Commit-Queue: Michael Lippautz <mlippautz@chromium.org>
Reviewed-by: Alexei Filippov <alph@chromium.org>
Reviewed-by: Ulan Degenbaev <ulan@chromium.org>
Cr-Commit-Position: refs/heads/master@{#63140}
  • Loading branch information
mlippautz authored and Commit Bot committed Aug 9, 2019
1 parent ac8acab commit 2ac8bb7
Show file tree
Hide file tree
Showing 7 changed files with 242 additions and 8 deletions.
16 changes: 14 additions & 2 deletions include/v8-profiler.h
Expand Up @@ -18,8 +18,8 @@ namespace v8 {
class HeapGraphNode;
struct HeapStatsUpdate;

typedef uint32_t SnapshotObjectId;

using NativeObject = void*;
using SnapshotObjectId = uint32_t;

struct CpuProfileDeoptFrame {
int script_id;
Expand Down Expand Up @@ -808,6 +808,12 @@ class V8_EXPORT EmbedderGraph {
*/
virtual const char* NamePrefix() { return nullptr; }

/**
* Returns the NativeObject that can be used for querying the
* |HeapSnapshot|.
*/
virtual NativeObject GetNativeObject() { return nullptr; }

Node(const Node&) = delete;
Node& operator=(const Node&) = delete;
};
Expand Down Expand Up @@ -870,6 +876,12 @@ class V8_EXPORT HeapProfiler {
*/
SnapshotObjectId GetObjectId(Local<Value> value);

/**
* Returns SnapshotObjectId for a native object referenced by |value| if it
* has been seen by the heap profiler, kUnknownObjectId otherwise.
*/
SnapshotObjectId GetObjectId(NativeObject value);

/**
* Returns heap object with given SnapshotObjectId if the object is alive,
* otherwise empty handle is returned.
Expand Down
4 changes: 4 additions & 0 deletions src/api/api.cc
Expand Up @@ -10103,6 +10103,10 @@ SnapshotObjectId HeapProfiler::GetObjectId(Local<Value> value) {
return reinterpret_cast<i::HeapProfiler*>(this)->GetSnapshotObjectId(obj);
}

SnapshotObjectId HeapProfiler::GetObjectId(NativeObject value) {
return reinterpret_cast<i::HeapProfiler*>(this)->GetSnapshotObjectId(value);
}

Local<Value> HeapProfiler::FindObjectById(SnapshotObjectId id) {
i::Handle<i::Object> obj =
reinterpret_cast<i::HeapProfiler*>(this)->FindHeapObjectById(id);
Expand Down
11 changes: 11 additions & 0 deletions src/profiler/heap-profiler.cc
Expand Up @@ -151,6 +151,17 @@ SnapshotObjectId HeapProfiler::GetSnapshotObjectId(Handle<Object> obj) {
return ids_->FindEntry(HeapObject::cast(*obj).address());
}

SnapshotObjectId HeapProfiler::GetSnapshotObjectId(NativeObject obj) {
// Try to find id of regular native node first.
SnapshotObjectId id = ids_->FindEntry(reinterpret_cast<Address>(obj));
// In case no id has been found, check whether there exists an entry where the
// native objects has been merged into a V8 entry.
if (id == v8::HeapProfiler::kUnknownObjectId) {
id = ids_->FindMergedNativeEntry(obj);
}
return id;
}

void HeapProfiler::ObjectMoveEvent(Address from, Address to, int size) {
base::MutexGuard guard(&profiler_mutex_);
bool known_object = ids_->MoveObject(from, to, size);
Expand Down
1 change: 1 addition & 0 deletions src/profiler/heap-profiler.h
Expand Up @@ -52,6 +52,7 @@ class HeapProfiler : public HeapObjectAllocationTracker {
int GetSnapshotsCount();
HeapSnapshot* GetSnapshot(int index);
SnapshotObjectId GetSnapshotObjectId(Handle<Object> obj);
SnapshotObjectId GetSnapshotObjectId(NativeObject obj);
void DeleteAllSnapshots();
void RemoveSnapshot(HeapSnapshot* snapshot);

Expand Down
64 changes: 58 additions & 6 deletions src/profiler/heap-snapshot-generator.cc
Expand Up @@ -352,7 +352,7 @@ void HeapObjectsMap::UpdateObjectSize(Address addr, int size) {
SnapshotObjectId HeapObjectsMap::FindEntry(Address addr) {
base::HashMap::Entry* entry = entries_map_.Lookup(
reinterpret_cast<void*>(addr), ComputeAddressHash(addr));
if (entry == nullptr) return 0;
if (entry == nullptr) return v8::HeapProfiler::kUnknownObjectId;
int entry_index = static_cast<int>(reinterpret_cast<intptr_t>(entry->value));
EntryInfo& entry_info = entries_.at(entry_index);
DCHECK(static_cast<uint32_t>(entries_.size()) > entries_map_.occupancy());
Expand Down Expand Up @@ -386,6 +386,25 @@ SnapshotObjectId HeapObjectsMap::FindOrAddEntry(Address addr,
return id;
}

SnapshotObjectId HeapObjectsMap::FindMergedNativeEntry(NativeObject addr) {
auto it = merged_native_entries_map_.find(addr);
if (it == merged_native_entries_map_.end())
return v8::HeapProfiler::kUnknownObjectId;
return entries_[it->second].id;
}

void HeapObjectsMap::AddMergedNativeEntry(NativeObject addr,
Address canonical_addr) {
base::HashMap::Entry* entry =
entries_map_.Lookup(reinterpret_cast<void*>(canonical_addr),
ComputeAddressHash(canonical_addr));
auto result = merged_native_entries_map_.insert(
{addr, reinterpret_cast<size_t>(entry->value)});
if (!result.second) {
result.first->second = reinterpret_cast<size_t>(entry->value);
}
}

void HeapObjectsMap::StopHeapObjectsTracking() { time_intervals_.clear(); }

void HeapObjectsMap::UpdateHeapObjectsMap() {
Expand Down Expand Up @@ -465,9 +484,20 @@ SnapshotObjectId HeapObjectsMap::PushHeapObjectsStats(OutputStream* stream,
void HeapObjectsMap::RemoveDeadEntries() {
DCHECK(entries_.size() > 0 && entries_.at(0).id == 0 &&
entries_.at(0).addr == kNullAddress);

// Build up temporary reverse map.
std::unordered_map<size_t, NativeObject> reverse_merged_native_entries_map;
for (const auto& it : merged_native_entries_map_) {
auto result =
reverse_merged_native_entries_map.emplace(it.second, it.first);
DCHECK(result.second);
USE(result);
}

size_t first_free_entry = 1;
for (size_t i = 1; i < entries_.size(); ++i) {
EntryInfo& entry_info = entries_.at(i);
auto merged_reverse_it = reverse_merged_native_entries_map.find(i);
if (entry_info.accessed) {
if (first_free_entry != i) {
entries_.at(first_free_entry) = entry_info;
Expand All @@ -478,11 +508,19 @@ void HeapObjectsMap::RemoveDeadEntries() {
ComputeAddressHash(entry_info.addr));
DCHECK(entry);
entry->value = reinterpret_cast<void*>(first_free_entry);
if (merged_reverse_it != reverse_merged_native_entries_map.end()) {
auto it = merged_native_entries_map_.find(merged_reverse_it->second);
DCHECK_NE(merged_native_entries_map_.end(), it);
it->second = first_free_entry;
}
++first_free_entry;
} else {
if (entry_info.addr) {
entries_map_.Remove(reinterpret_cast<void*>(entry_info.addr),
ComputeAddressHash(entry_info.addr));
if (merged_reverse_it != reverse_merged_native_entries_map.end()) {
merged_native_entries_map_.erase(merged_reverse_it->second);
}
}
}
}
Expand Down Expand Up @@ -1853,10 +1891,14 @@ HeapEntry* EmbedderGraphEntriesAllocator::AllocateEntry(HeapThing ptr) {
reinterpret_cast<EmbedderGraphImpl::Node*>(ptr);
DCHECK(node->IsEmbedderNode());
size_t size = node->SizeInBytes();
return snapshot_->AddEntry(
EmbedderGraphNodeType(node), EmbedderGraphNodeName(names_, node),
static_cast<SnapshotObjectId>(reinterpret_cast<uintptr_t>(node) << 1),
static_cast<int>(size), 0);
Address lookup_address = reinterpret_cast<Address>(node->GetNativeObject());
SnapshotObjectId id =
(lookup_address) ? heap_object_map_->FindOrAddEntry(lookup_address, 0)
: static_cast<SnapshotObjectId>(
reinterpret_cast<uintptr_t>(node) << 1);
return snapshot_->AddEntry(EmbedderGraphNodeType(node),
EmbedderGraphNodeName(names_, node), id,
static_cast<int>(size), 0);
}

NativeObjectsExplorer::NativeObjectsExplorer(
Expand All @@ -1865,12 +1907,14 @@ NativeObjectsExplorer::NativeObjectsExplorer(
Isolate::FromHeap(snapshot->profiler()->heap_object_map()->heap())),
snapshot_(snapshot),
names_(snapshot_->profiler()->names()),
heap_object_map_(snapshot_->profiler()->heap_object_map()),
embedder_graph_entries_allocator_(
new EmbedderGraphEntriesAllocator(snapshot)) {}

HeapEntry* NativeObjectsExplorer::EntryForEmbedderGraphNode(
EmbedderGraphImpl::Node* node) {
EmbedderGraphImpl::Node* wrapper = node->WrapperNode();
NativeObject native_object = node->GetNativeObject();
if (wrapper) {
node = wrapper;
}
Expand All @@ -1882,8 +1926,16 @@ HeapEntry* NativeObjectsExplorer::EntryForEmbedderGraphNode(
static_cast<EmbedderGraphImpl::V8NodeImpl*>(node);
Object object = v8_node->GetObject();
if (object.IsSmi()) return nullptr;
return generator_->FindEntry(
HeapEntry* entry = generator_->FindEntry(
reinterpret_cast<void*>(Object::cast(object).ptr()));
if (native_object) {
HeapObject heap_object = HeapObject::cast(object);
heap_object_map_->AddMergedNativeEntry(native_object,
heap_object.address());
DCHECK_EQ(entry->id(),
heap_object_map_->FindMergedNativeEntry(native_object));
}
return entry;
}
}

Expand Down
5 changes: 5 additions & 0 deletions src/profiler/heap-snapshot-generator.h
Expand Up @@ -249,6 +249,8 @@ class HeapObjectsMap {
SnapshotObjectId FindOrAddEntry(Address addr,
unsigned int size,
bool accessed = true);
SnapshotObjectId FindMergedNativeEntry(NativeObject addr);
void AddMergedNativeEntry(NativeObject addr, Address canonical_addr);
bool MoveObject(Address from, Address to, int size);
void UpdateObjectSize(Address addr, int size);
SnapshotObjectId last_assigned_id() const {
Expand Down Expand Up @@ -285,6 +287,8 @@ class HeapObjectsMap {
base::HashMap entries_map_;
std::vector<EntryInfo> entries_;
std::vector<TimeInterval> time_intervals_;
// Map from NativeObject to EntryInfo index in entries_.
std::unordered_map<NativeObject, size_t> merged_native_entries_map_;
Heap* heap_;

DISALLOW_COPY_AND_ASSIGN(HeapObjectsMap);
Expand Down Expand Up @@ -453,6 +457,7 @@ class NativeObjectsExplorer {
Isolate* isolate_;
HeapSnapshot* snapshot_;
StringsStorage* names_;
HeapObjectsMap* heap_object_map_;
std::unique_ptr<HeapEntriesAllocator> embedder_graph_entries_allocator_;
// Used during references extraction.
HeapSnapshotGenerator* generator_ = nullptr;
Expand Down
149 changes: 149 additions & 0 deletions test/cctest/test-heap-profiler.cc
Expand Up @@ -46,6 +46,7 @@
#include "src/profiler/heap-snapshot-generator-inl.h"
#include "test/cctest/cctest.h"
#include "test/cctest/collector.h"
#include "test/cctest/heap/heap-utils.h"

using i::AllocationTraceNode;
using i::AllocationTraceTree;
Expand Down Expand Up @@ -1693,6 +1694,154 @@ TEST(HeapSnapshotRetainedObjectInfo) {
CHECK_EQ(native_group_ccc, GetChildByName(n_CCC, "ccc-group"));
}

namespace {

class EmbedderGraphBuilderForNativeSnapshotObjectId final {
public:
class RegularNode : public v8::EmbedderGraph::Node {
public:
RegularNode(v8::NativeObject native_object, const char* name, size_t size,
Node* wrapper_node)
: name_(name),
size_(size),
native_object_(native_object),
wrapper_node_(wrapper_node) {}
// v8::EmbedderGraph::Node
const char* Name() override { return name_; }
size_t SizeInBytes() override { return size_; }
Node* WrapperNode() override { return wrapper_node_; }
v8::NativeObject GetNativeObject() override {
return native_object_ ? native_object_ : this;
}

private:
const char* name_;
size_t size_;
v8::NativeObject native_object_;
Node* wrapper_node_;
};

class RootNode : public RegularNode {
public:
explicit RootNode(const char* name)
: RegularNode(nullptr, name, 0, nullptr) {}
// v8::EmbedderGraph::EmbedderNode
bool IsRootNode() override { return true; }
};

struct BuildParameter {
v8::Persistent<v8::String>* wrapper;
void* native1;
void* native2;
};

static void BuildEmbedderGraph(v8::Isolate* isolate, v8::EmbedderGraph* graph,
void* data) {
BuildParameter* parameter = reinterpret_cast<BuildParameter*>(data);
v8::Local<v8::String> local_str =
v8::Local<v8::String>::New(isolate, *(parameter->wrapper));
auto* v8_node = graph->V8Node(local_str);
CHECK(!v8_node->IsEmbedderNode());
auto* root_node =
graph->AddNode(std::unique_ptr<RootNode>(new RootNode("root")));
auto* non_merged_node = graph->AddNode(std::unique_ptr<RegularNode>(
new RegularNode(parameter->native1, "non-merged", 0, nullptr)));
auto* merged_node = graph->AddNode(std::unique_ptr<RegularNode>(
new RegularNode(parameter->native2, "merged", 0, v8_node)));
graph->AddEdge(root_node, non_merged_node);
graph->AddEdge(root_node, merged_node);
}
};

} // namespace

TEST(NativeSnapshotObjectId) {
LocalContext env;
v8::Isolate* isolate = env->GetIsolate();
v8::HandleScope scope(isolate);
v8::HeapProfiler* heap_profiler = isolate->GetHeapProfiler();

v8::Persistent<v8::String> wrapper(isolate, v8_str("wrapper"));
int native1;
int native2;

EmbedderGraphBuilderForNativeSnapshotObjectId::BuildParameter parameter{
&wrapper, &native1, &native2};
heap_profiler->AddBuildEmbedderGraphCallback(
EmbedderGraphBuilderForNativeSnapshotObjectId::BuildEmbedderGraph,
&parameter);
const v8::HeapSnapshot* snapshot = heap_profiler->TakeHeapSnapshot();
CHECK(ValidateSnapshot(snapshot));

v8::SnapshotObjectId non_merged_id = heap_profiler->GetObjectId(&native1);
CHECK_NE(v8::HeapProfiler::kUnknownObjectId, non_merged_id);
v8::SnapshotObjectId merged_id = heap_profiler->GetObjectId(&native2);
CHECK_NE(v8::HeapProfiler::kUnknownObjectId, merged_id);
CHECK_NE(non_merged_id, merged_id);
const v8::HeapGraphNode* non_merged_node =
snapshot->GetNodeById(non_merged_id);
CHECK_NOT_NULL(non_merged_node);
const v8::HeapGraphNode* merged_node = snapshot->GetNodeById(merged_id);
CHECK_NOT_NULL(merged_node);

heap_profiler->ClearObjectIds();
CHECK_EQ(v8::HeapProfiler::kUnknownObjectId,
heap_profiler->GetObjectId(&native1));
CHECK_EQ(v8::HeapProfiler::kUnknownObjectId,
heap_profiler->GetObjectId(&native2));
}

TEST(NativeSnapshotObjectIdMoving) {
// Required to allow moving specific objects.
i::FLAG_manual_evacuation_candidates_selection = true;

LocalContext env;
v8::Isolate* isolate = env->GetIsolate();
v8::HandleScope scope(isolate);
v8::HeapProfiler* heap_profiler = isolate->GetHeapProfiler();
heap_profiler->StartTrackingHeapObjects(true);

v8::Persistent<v8::String> wrapper(isolate, v8_str("wrapper"));
int native1;
int native2;

EmbedderGraphBuilderForNativeSnapshotObjectId::BuildParameter parameter{
&wrapper, &native1, &native2};
heap_profiler->AddBuildEmbedderGraphCallback(
EmbedderGraphBuilderForNativeSnapshotObjectId::BuildEmbedderGraph,
&parameter);
const v8::HeapSnapshot* snapshot = heap_profiler->TakeHeapSnapshot();
CHECK(ValidateSnapshot(snapshot));

v8::SnapshotObjectId non_merged_id = heap_profiler->GetObjectId(&native1);
CHECK_NE(v8::HeapProfiler::kUnknownObjectId, non_merged_id);
v8::SnapshotObjectId merged_id = heap_profiler->GetObjectId(&native2);
CHECK_NE(v8::HeapProfiler::kUnknownObjectId, merged_id);
CHECK_NE(non_merged_id, merged_id);
const v8::HeapGraphNode* non_merged_node =
snapshot->GetNodeById(non_merged_id);
CHECK_NOT_NULL(non_merged_node);
const v8::HeapGraphNode* merged_node = snapshot->GetNodeById(merged_id);
CHECK_NOT_NULL(merged_node);

{
v8::HandleScope scope(isolate);
auto local = v8::Local<v8::String>::New(isolate, wrapper);
i::Handle<i::String> internal = i::Handle<i::String>::cast(
v8::Utils::OpenHandle(*v8::Local<v8::String>::Cast(local)));
i::heap::ForceEvacuationCandidate(i::Page::FromHeapObject(*internal));
}
CcTest::CollectAllGarbage();

non_merged_id = heap_profiler->GetObjectId(&native1);
CHECK_NE(v8::HeapProfiler::kUnknownObjectId, non_merged_id);
merged_id = heap_profiler->GetObjectId(&native2);
CHECK_NE(v8::HeapProfiler::kUnknownObjectId, merged_id);
CHECK_NE(non_merged_id, merged_id);

heap_profiler->StopTrackingHeapObjects();
}

TEST(DeleteAllHeapSnapshots) {
LocalContext env;
v8::HandleScope scope(env->GetIsolate());
Expand Down

0 comments on commit 2ac8bb7

Please sign in to comment.