Skip to content

Commit 5121278

Browse files
addaleaxtargos
authored andcommitted
src: use V8 graph heap snapshot API
Transition to a newer, more flexible API for heap snapshot creation. This addresses a currently pending deprecation in the V8 API. PR-URL: #21741 Fixes: #21633 Reviewed-By: James M Snell <[email protected]> Reviewed-By: Joyee Cheung <[email protected]> Reviewed-By: Refael Ackermann <[email protected]>
1 parent 355c5e3 commit 5121278

8 files changed

+160
-116
lines changed

src/async_wrap.cc

+5-95
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ using v8::Function;
3232
using v8::FunctionCallbackInfo;
3333
using v8::FunctionTemplate;
3434
using v8::HandleScope;
35-
using v8::HeapProfiler;
3635
using v8::Integer;
3736
using v8::Isolate;
3837
using v8::Local;
@@ -43,7 +42,6 @@ using v8::ObjectTemplate;
4342
using v8::Promise;
4443
using v8::PromiseHookType;
4544
using v8::PropertyCallbackInfo;
46-
using v8::RetainedObjectInfo;
4745
using v8::String;
4846
using v8::Uint32;
4947
using v8::Undefined;
@@ -61,87 +59,6 @@ static const char* const provider_names[] = {
6159
};
6260

6361

64-
// Report correct information in a heapdump.
65-
66-
class RetainedAsyncInfo: public RetainedObjectInfo {
67-
public:
68-
explicit RetainedAsyncInfo(uint16_t class_id, AsyncWrap* wrap);
69-
70-
void Dispose() override;
71-
bool IsEquivalent(RetainedObjectInfo* other) override;
72-
intptr_t GetHash() override;
73-
const char* GetLabel() override;
74-
intptr_t GetSizeInBytes() override;
75-
76-
private:
77-
const char* label_;
78-
const AsyncWrap* wrap_;
79-
const size_t length_;
80-
};
81-
82-
83-
static int OwnMemory(AsyncWrap* async_wrap) {
84-
MemoryTracker tracker;
85-
tracker.set_track_only_self(true);
86-
tracker.Track(async_wrap);
87-
return tracker.accumulated_size();
88-
}
89-
90-
91-
RetainedAsyncInfo::RetainedAsyncInfo(uint16_t class_id, AsyncWrap* wrap)
92-
: label_(provider_names[class_id - NODE_ASYNC_ID_OFFSET]),
93-
wrap_(wrap),
94-
length_(OwnMemory(wrap)) {
95-
}
96-
97-
98-
void RetainedAsyncInfo::Dispose() {
99-
delete this;
100-
}
101-
102-
103-
bool RetainedAsyncInfo::IsEquivalent(RetainedObjectInfo* other) {
104-
return label_ == other->GetLabel() &&
105-
wrap_ == static_cast<RetainedAsyncInfo*>(other)->wrap_;
106-
}
107-
108-
109-
intptr_t RetainedAsyncInfo::GetHash() {
110-
return reinterpret_cast<intptr_t>(wrap_);
111-
}
112-
113-
114-
const char* RetainedAsyncInfo::GetLabel() {
115-
return label_;
116-
}
117-
118-
119-
intptr_t RetainedAsyncInfo::GetSizeInBytes() {
120-
return length_;
121-
}
122-
123-
124-
RetainedObjectInfo* WrapperInfo(uint16_t class_id, Local<Value> wrapper) {
125-
// No class_id should be the provider type of NONE.
126-
CHECK_GT(class_id, NODE_ASYNC_ID_OFFSET);
127-
// And make sure the class_id doesn't extend past the last provider.
128-
CHECK_LE(class_id - NODE_ASYNC_ID_OFFSET, AsyncWrap::PROVIDERS_LENGTH);
129-
CHECK(wrapper->IsObject());
130-
CHECK(!wrapper.IsEmpty());
131-
132-
Local<Object> object = wrapper.As<Object>();
133-
CHECK_GT(object->InternalFieldCount(), 0);
134-
135-
AsyncWrap* wrap;
136-
ASSIGN_OR_RETURN_UNWRAP(&wrap, object, nullptr);
137-
138-
return new RetainedAsyncInfo(class_id, wrap);
139-
}
140-
141-
142-
// end RetainedAsyncInfo
143-
144-
14562
struct AsyncWrapObject : public AsyncWrap {
14663
static inline void New(const FunctionCallbackInfo<Value>& args) {
14764
Environment* env = Environment::GetCurrent(args);
@@ -616,16 +533,6 @@ void AsyncWrap::Initialize(Local<Object> target,
616533
}
617534

618535

619-
void LoadAsyncWrapperInfo(Environment* env) {
620-
HeapProfiler* heap_profiler = env->isolate()->GetHeapProfiler();
621-
#define V(PROVIDER) \
622-
heap_profiler->SetWrapperClassInfoProvider( \
623-
(NODE_ASYNC_ID_OFFSET + AsyncWrap::PROVIDER_ ## PROVIDER), WrapperInfo);
624-
NODE_ASYNC_PROVIDER_TYPES(V)
625-
#undef V
626-
}
627-
628-
629536
AsyncWrap::AsyncWrap(Environment* env,
630537
Local<Object> object,
631538
ProviderType provider,
@@ -814,9 +721,12 @@ void EmitAsyncDestroy(Isolate* isolate, async_context asyncContext) {
814721
Environment::GetCurrent(isolate), asyncContext.async_id);
815722
}
816723

724+
std::string AsyncWrap::MemoryInfoName() const {
725+
return provider_names[provider_type()];
726+
}
727+
817728
std::string AsyncWrap::diagnostic_name() const {
818-
return std::string(provider_names[provider_type()]) +
819-
" (" + std::to_string(env()->thread_id()) + ":" +
729+
return MemoryInfoName() + " (" + std::to_string(env()->thread_id()) + ":" +
820730
std::to_string(static_cast<int64_t>(async_id_)) + ")";
821731
}
822732

src/async_wrap.h

+1-2
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,7 @@ class AsyncWrap : public BaseObject {
175175
v8::Local<v8::Value>* argv);
176176

177177
virtual std::string diagnostic_name() const;
178+
std::string MemoryInfoName() const override;
178179

179180
static void WeakCallback(const v8::WeakCallbackInfo<DestroyParam> &info);
180181

@@ -205,8 +206,6 @@ class AsyncWrap : public BaseObject {
205206
double trigger_async_id_;
206207
};
207208

208-
void LoadAsyncWrapperInfo(Environment* env);
209-
210209
} // namespace node
211210

212211
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS

src/env.cc

+16-1
Original file line numberDiff line numberDiff line change
@@ -142,9 +142,15 @@ Environment::Environment(IsolateData* isolate_data,
142142
std::string debug_cats;
143143
SafeGetenv("NODE_DEBUG_NATIVE", &debug_cats);
144144
set_debug_categories(debug_cats, true);
145+
146+
isolate()->GetHeapProfiler()->AddBuildEmbedderGraphCallback(
147+
BuildEmbedderGraph, this);
145148
}
146149

147150
Environment::~Environment() {
151+
isolate()->GetHeapProfiler()->RemoveBuildEmbedderGraphCallback(
152+
BuildEmbedderGraph, this);
153+
148154
// Make sure there are no re-used libuv wrapper objects.
149155
// CleanupHandles() should have removed all of them.
150156
CHECK(file_handle_read_wrap_freelist_.empty());
@@ -212,7 +218,6 @@ void Environment::Start(int argc,
212218
set_process_object(process_object);
213219

214220
SetupProcessObject(this, argc, argv, exec_argc, exec_argv);
215-
LoadAsyncWrapperInfo(this);
216221

217222
static uv_once_t init_once = UV_ONCE_INIT;
218223
uv_once(&init_once, InitThreadLocalOnce);
@@ -653,6 +658,16 @@ void Environment::stop_sub_worker_contexts() {
653658
}
654659
}
655660

661+
void Environment::BuildEmbedderGraph(v8::Isolate* isolate,
662+
v8::EmbedderGraph* graph,
663+
void* data) {
664+
MemoryTracker tracker(isolate, graph);
665+
static_cast<Environment*>(data)->ForEachBaseObject([&](BaseObject* obj) {
666+
tracker.Track(obj);
667+
});
668+
}
669+
670+
656671
// Not really any better place than env.cc at this moment.
657672
void BaseObject::DeleteMe(void* data) {
658673
BaseObject* self = static_cast<BaseObject*>(data);

src/env.h

+4
Original file line numberDiff line numberDiff line change
@@ -859,6 +859,10 @@ class Environment {
859859
inline void RemoveCleanupHook(void (*fn)(void*), void* arg);
860860
void RunCleanup();
861861

862+
static void BuildEmbedderGraph(v8::Isolate* isolate,
863+
v8::EmbedderGraph* graph,
864+
void* data);
865+
862866
private:
863867
inline void CreateImmediate(native_immediate_callback cb,
864868
void* data,

src/memory_tracker-inl.h

+98-7
Original file line numberDiff line numberDiff line change
@@ -7,23 +7,68 @@
77

88
namespace node {
99

10+
class MemoryRetainerNode : public v8::EmbedderGraph::Node {
11+
public:
12+
explicit inline MemoryRetainerNode(MemoryTracker* tracker,
13+
const MemoryRetainer* retainer,
14+
const char* name)
15+
: retainer_(retainer) {
16+
if (retainer_ != nullptr) {
17+
v8::HandleScope handle_scope(tracker->isolate());
18+
v8::Local<v8::Object> obj = retainer_->WrappedObject();
19+
if (!obj.IsEmpty())
20+
wrapper_node_ = tracker->graph()->V8Node(obj);
21+
22+
name_ = retainer_->MemoryInfoName();
23+
}
24+
if (name_.empty() && name != nullptr) {
25+
name_ = name;
26+
}
27+
}
28+
29+
const char* Name() override { return name_.c_str(); }
30+
const char* NamePrefix() override { return "Node /"; }
31+
size_t SizeInBytes() override { return size_; }
32+
// TODO(addaleax): Merging this with the "official" WrapperNode() method
33+
// seems to lose accuracy, e.g. SizeInBytes() is disregarded.
34+
// Figure out whether to do anything about that.
35+
Node* JSWrapperNode() { return wrapper_node_; }
36+
37+
bool IsRootNode() override {
38+
return retainer_ != nullptr && retainer_->IsRootNode();
39+
}
40+
41+
private:
42+
friend class MemoryTracker;
43+
44+
Node* wrapper_node_ = nullptr;
45+
const MemoryRetainer* retainer_;
46+
std::string name_;
47+
size_t size_ = 0;
48+
};
49+
1050
template <typename T>
1151
void MemoryTracker::TrackThis(const T* obj) {
12-
accumulated_size_ += sizeof(T);
52+
CurrentNode()->size_ = sizeof(T);
1353
}
1454

1555
void MemoryTracker::TrackFieldWithSize(const char* name, size_t size) {
16-
accumulated_size_ += size;
56+
if (size > 0)
57+
AddNode(name)->size_ = size;
1758
}
1859

1960
void MemoryTracker::TrackField(const char* name, const MemoryRetainer& value) {
2061
TrackField(name, &value);
2162
}
2263

2364
void MemoryTracker::TrackField(const char* name, const MemoryRetainer* value) {
24-
if (track_only_self_ || value == nullptr || seen_.count(value) > 0) return;
25-
seen_.insert(value);
26-
Track(value);
65+
if (track_only_self_ || value == nullptr) return;
66+
auto it = seen_.find(value);
67+
if (it != seen_.end()) {
68+
graph_->AddEdge(CurrentNode(), it->second);
69+
} else {
70+
Track(value, name);
71+
}
2772
}
2873

2974
template <typename T>
@@ -36,8 +81,10 @@ template <typename T, typename Iterator>
3681
void MemoryTracker::TrackField(const char* name, const T& value) {
3782
if (value.begin() == value.end()) return;
3883
size_t index = 0;
84+
PushNode(name);
3985
for (Iterator it = value.begin(); it != value.end(); ++it)
4086
TrackField(std::to_string(index++).c_str(), *it);
87+
PopNode();
4188
}
4289

4390
template <typename T>
@@ -56,13 +103,15 @@ void MemoryTracker::TrackField(const char* name, const std::queue<T>& value) {
56103
template <typename T, typename test_for_number, typename dummy>
57104
void MemoryTracker::TrackField(const char* name, const T& value) {
58105
// For numbers, creating new nodes is not worth the overhead.
59-
TrackFieldWithSize(name, sizeof(T));
106+
CurrentNode()->size_ += sizeof(T);
60107
}
61108

62109
template <typename T, typename U>
63110
void MemoryTracker::TrackField(const char* name, const std::pair<T, U>& value) {
111+
PushNode(name);
64112
TrackField("first", value.first);
65113
TrackField("second", value.second);
114+
PopNode();
66115
}
67116

68117
template <typename T>
@@ -74,10 +123,13 @@ void MemoryTracker::TrackField(const char* name,
74123
template <typename T, typename Traits>
75124
void MemoryTracker::TrackField(const char* name,
76125
const v8::Persistent<T, Traits>& value) {
126+
TrackField(name, value.Get(isolate_));
77127
}
78128

79129
template <typename T>
80130
void MemoryTracker::TrackField(const char* name, const v8::Local<T>& value) {
131+
if (!value.IsEmpty())
132+
graph_->AddEdge(CurrentNode(), graph_->V8Node(value));
81133
}
82134

83135
template <typename T>
@@ -96,8 +148,47 @@ void MemoryTracker::TrackField(const char* name,
96148
TrackField(name, value.GetJSArray());
97149
}
98150

99-
void MemoryTracker::Track(const MemoryRetainer* value) {
151+
void MemoryTracker::Track(const MemoryRetainer* value, const char* name) {
152+
v8::HandleScope handle_scope(isolate_);
153+
MemoryRetainerNode* n = PushNode(name, value);
100154
value->MemoryInfo(this);
155+
CHECK_EQ(CurrentNode(), n);
156+
CHECK_NE(n->size_, 0);
157+
PopNode();
158+
}
159+
160+
MemoryRetainerNode* MemoryTracker::CurrentNode() const {
161+
if (node_stack_.empty()) return nullptr;
162+
return node_stack_.top();
163+
}
164+
165+
MemoryRetainerNode* MemoryTracker::AddNode(
166+
const char* name, const MemoryRetainer* retainer) {
167+
MemoryRetainerNode* n = new MemoryRetainerNode(this, retainer, name);
168+
graph_->AddNode(std::unique_ptr<v8::EmbedderGraph::Node>(n));
169+
if (retainer != nullptr)
170+
seen_[retainer] = n;
171+
172+
if (CurrentNode() != nullptr)
173+
graph_->AddEdge(CurrentNode(), n);
174+
175+
if (n->JSWrapperNode() != nullptr) {
176+
graph_->AddEdge(n, n->JSWrapperNode());
177+
graph_->AddEdge(n->JSWrapperNode(), n);
178+
}
179+
180+
return n;
181+
}
182+
183+
MemoryRetainerNode* MemoryTracker::PushNode(
184+
const char* name, const MemoryRetainer* retainer) {
185+
MemoryRetainerNode* n = AddNode(name, retainer);
186+
node_stack_.push(n);
187+
return n;
188+
}
189+
190+
void MemoryTracker::PopNode() {
191+
node_stack_.pop();
101192
}
102193

103194
} // namespace node

0 commit comments

Comments
 (0)