Skip to content

Commit 266a7e6

Browse files
committed
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 d2a1408 commit 266a7e6

File tree

8 files changed

+160
-116
lines changed

8 files changed

+160
-116
lines changed

src/async_wrap.cc

Lines changed: 5 additions & 95 deletions
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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,7 @@ class AsyncWrap : public BaseObject {
174174
v8::Local<v8::Value>* argv);
175175

176176
virtual std::string diagnostic_name() const;
177+
std::string MemoryInfoName() const override;
177178

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

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

207-
void LoadAsyncWrapperInfo(Environment* env);
208-
209208
} // namespace node
210209

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

src/env.cc

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,9 +144,15 @@ Environment::Environment(IsolateData* isolate_data,
144144
std::string debug_cats;
145145
SafeGetenv("NODE_DEBUG_NATIVE", &debug_cats);
146146
set_debug_categories(debug_cats, true);
147+
148+
isolate()->GetHeapProfiler()->AddBuildEmbedderGraphCallback(
149+
BuildEmbedderGraph, this);
147150
}
148151

149152
Environment::~Environment() {
153+
isolate()->GetHeapProfiler()->RemoveBuildEmbedderGraphCallback(
154+
BuildEmbedderGraph, this);
155+
150156
// Make sure there are no re-used libuv wrapper objects.
151157
// CleanupHandles() should have removed all of them.
152158
CHECK(file_handle_read_wrap_freelist_.empty());
@@ -217,7 +223,6 @@ void Environment::Start(int argc,
217223
set_process_object(process_object);
218224

219225
SetupProcessObject(this, argc, argv, exec_argc, exec_argv);
220-
LoadAsyncWrapperInfo(this);
221226

222227
static uv_once_t init_once = UV_ONCE_INIT;
223228
uv_once(&init_once, InitThreadLocalOnce);
@@ -734,6 +739,16 @@ void Environment::stop_sub_worker_contexts() {
734739
}
735740
}
736741

742+
void Environment::BuildEmbedderGraph(v8::Isolate* isolate,
743+
v8::EmbedderGraph* graph,
744+
void* data) {
745+
MemoryTracker tracker(isolate, graph);
746+
static_cast<Environment*>(data)->ForEachBaseObject([&](BaseObject* obj) {
747+
tracker.Track(obj);
748+
});
749+
}
750+
751+
737752
// Not really any better place than env.cc at this moment.
738753
void BaseObject::DeleteMe(void* data) {
739754
BaseObject* self = static_cast<BaseObject*>(data);

src/env.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -861,6 +861,10 @@ class Environment {
861861
inline void RemoveCleanupHook(void (*fn)(void*), void* arg);
862862
void RunCleanup();
863863

864+
static void BuildEmbedderGraph(v8::Isolate* isolate,
865+
v8::EmbedderGraph* graph,
866+
void* data);
867+
864868
private:
865869
inline void CreateImmediate(native_immediate_callback cb,
866870
void* data,

src/memory_tracker-inl.h

Lines changed: 98 additions & 7 deletions
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)