diff --git a/src/async_wrap.cc b/src/async_wrap.cc index e98dca3c56651b..7ef3dafdf992c5 100644 --- a/src/async_wrap.cc +++ b/src/async_wrap.cc @@ -32,7 +32,6 @@ using v8::Function; using v8::FunctionCallbackInfo; using v8::FunctionTemplate; using v8::HandleScope; -using v8::HeapProfiler; using v8::Integer; using v8::Isolate; using v8::Local; @@ -43,7 +42,6 @@ using v8::ObjectTemplate; using v8::Promise; using v8::PromiseHookType; using v8::PropertyCallbackInfo; -using v8::RetainedObjectInfo; using v8::String; using v8::Uint32; using v8::Undefined; @@ -61,87 +59,6 @@ static const char* const provider_names[] = { }; -// Report correct information in a heapdump. - -class RetainedAsyncInfo: public RetainedObjectInfo { - public: - explicit RetainedAsyncInfo(uint16_t class_id, AsyncWrap* wrap); - - void Dispose() override; - bool IsEquivalent(RetainedObjectInfo* other) override; - intptr_t GetHash() override; - const char* GetLabel() override; - intptr_t GetSizeInBytes() override; - - private: - const char* label_; - const AsyncWrap* wrap_; - const size_t length_; -}; - - -static int OwnMemory(AsyncWrap* async_wrap) { - MemoryTracker tracker; - tracker.set_track_only_self(true); - tracker.Track(async_wrap); - return tracker.accumulated_size(); -} - - -RetainedAsyncInfo::RetainedAsyncInfo(uint16_t class_id, AsyncWrap* wrap) - : label_(provider_names[class_id - NODE_ASYNC_ID_OFFSET]), - wrap_(wrap), - length_(OwnMemory(wrap)) { -} - - -void RetainedAsyncInfo::Dispose() { - delete this; -} - - -bool RetainedAsyncInfo::IsEquivalent(RetainedObjectInfo* other) { - return label_ == other->GetLabel() && - wrap_ == static_cast(other)->wrap_; -} - - -intptr_t RetainedAsyncInfo::GetHash() { - return reinterpret_cast(wrap_); -} - - -const char* RetainedAsyncInfo::GetLabel() { - return label_; -} - - -intptr_t RetainedAsyncInfo::GetSizeInBytes() { - return length_; -} - - -RetainedObjectInfo* WrapperInfo(uint16_t class_id, Local wrapper) { - // No class_id should be the provider type of NONE. - CHECK_GT(class_id, NODE_ASYNC_ID_OFFSET); - // And make sure the class_id doesn't extend past the last provider. - CHECK_LE(class_id - NODE_ASYNC_ID_OFFSET, AsyncWrap::PROVIDERS_LENGTH); - CHECK(wrapper->IsObject()); - CHECK(!wrapper.IsEmpty()); - - Local object = wrapper.As(); - CHECK_GT(object->InternalFieldCount(), 0); - - AsyncWrap* wrap; - ASSIGN_OR_RETURN_UNWRAP(&wrap, object, nullptr); - - return new RetainedAsyncInfo(class_id, wrap); -} - - -// end RetainedAsyncInfo - - struct AsyncWrapObject : public AsyncWrap { static inline void New(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); @@ -616,16 +533,6 @@ void AsyncWrap::Initialize(Local target, } -void LoadAsyncWrapperInfo(Environment* env) { - HeapProfiler* heap_profiler = env->isolate()->GetHeapProfiler(); -#define V(PROVIDER) \ - heap_profiler->SetWrapperClassInfoProvider( \ - (NODE_ASYNC_ID_OFFSET + AsyncWrap::PROVIDER_ ## PROVIDER), WrapperInfo); - NODE_ASYNC_PROVIDER_TYPES(V) -#undef V -} - - AsyncWrap::AsyncWrap(Environment* env, Local object, ProviderType provider, @@ -814,9 +721,12 @@ void EmitAsyncDestroy(Isolate* isolate, async_context asyncContext) { Environment::GetCurrent(isolate), asyncContext.async_id); } +std::string AsyncWrap::MemoryInfoName() const { + return provider_names[provider_type()]; +} + std::string AsyncWrap::diagnostic_name() const { - return std::string(provider_names[provider_type()]) + - " (" + std::to_string(env()->thread_id()) + ":" + + return MemoryInfoName() + " (" + std::to_string(env()->thread_id()) + ":" + std::to_string(static_cast(async_id_)) + ")"; } diff --git a/src/async_wrap.h b/src/async_wrap.h index ef3a5934893d7f..f748dc801dab77 100644 --- a/src/async_wrap.h +++ b/src/async_wrap.h @@ -174,6 +174,7 @@ class AsyncWrap : public BaseObject { v8::Local* argv); virtual std::string diagnostic_name() const; + std::string MemoryInfoName() const override; static void WeakCallback(const v8::WeakCallbackInfo &info); @@ -204,8 +205,6 @@ class AsyncWrap : public BaseObject { double trigger_async_id_; }; -void LoadAsyncWrapperInfo(Environment* env); - } // namespace node #endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS diff --git a/src/env.cc b/src/env.cc index 30bc85559a9a0a..5a6e765681f24b 100644 --- a/src/env.cc +++ b/src/env.cc @@ -144,9 +144,15 @@ Environment::Environment(IsolateData* isolate_data, std::string debug_cats; SafeGetenv("NODE_DEBUG_NATIVE", &debug_cats); set_debug_categories(debug_cats, true); + + isolate()->GetHeapProfiler()->AddBuildEmbedderGraphCallback( + BuildEmbedderGraph, this); } Environment::~Environment() { + isolate()->GetHeapProfiler()->RemoveBuildEmbedderGraphCallback( + BuildEmbedderGraph, this); + // Make sure there are no re-used libuv wrapper objects. // CleanupHandles() should have removed all of them. CHECK(file_handle_read_wrap_freelist_.empty()); @@ -217,7 +223,6 @@ void Environment::Start(int argc, set_process_object(process_object); SetupProcessObject(this, argc, argv, exec_argc, exec_argv); - LoadAsyncWrapperInfo(this); static uv_once_t init_once = UV_ONCE_INIT; uv_once(&init_once, InitThreadLocalOnce); @@ -734,6 +739,16 @@ void Environment::stop_sub_worker_contexts() { } } +void Environment::BuildEmbedderGraph(v8::Isolate* isolate, + v8::EmbedderGraph* graph, + void* data) { + MemoryTracker tracker(isolate, graph); + static_cast(data)->ForEachBaseObject([&](BaseObject* obj) { + tracker.Track(obj); + }); +} + + // Not really any better place than env.cc at this moment. void BaseObject::DeleteMe(void* data) { BaseObject* self = static_cast(data); diff --git a/src/env.h b/src/env.h index 9bdf33bce0b88c..120048fe009b03 100644 --- a/src/env.h +++ b/src/env.h @@ -861,6 +861,10 @@ class Environment { inline void RemoveCleanupHook(void (*fn)(void*), void* arg); void RunCleanup(); + static void BuildEmbedderGraph(v8::Isolate* isolate, + v8::EmbedderGraph* graph, + void* data); + private: inline void CreateImmediate(native_immediate_callback cb, void* data, diff --git a/src/memory_tracker-inl.h b/src/memory_tracker-inl.h index 758223492f6e71..568a4364f9c64d 100644 --- a/src/memory_tracker-inl.h +++ b/src/memory_tracker-inl.h @@ -7,13 +7,54 @@ namespace node { +class MemoryRetainerNode : public v8::EmbedderGraph::Node { + public: + explicit inline MemoryRetainerNode(MemoryTracker* tracker, + const MemoryRetainer* retainer, + const char* name) + : retainer_(retainer) { + if (retainer_ != nullptr) { + v8::HandleScope handle_scope(tracker->isolate()); + v8::Local obj = retainer_->WrappedObject(); + if (!obj.IsEmpty()) + wrapper_node_ = tracker->graph()->V8Node(obj); + + name_ = retainer_->MemoryInfoName(); + } + if (name_.empty() && name != nullptr) { + name_ = name; + } + } + + const char* Name() override { return name_.c_str(); } + const char* NamePrefix() override { return "Node /"; } + size_t SizeInBytes() override { return size_; } + // TODO(addaleax): Merging this with the "official" WrapperNode() method + // seems to lose accuracy, e.g. SizeInBytes() is disregarded. + // Figure out whether to do anything about that. + Node* JSWrapperNode() { return wrapper_node_; } + + bool IsRootNode() override { + return retainer_ != nullptr && retainer_->IsRootNode(); + } + + private: + friend class MemoryTracker; + + Node* wrapper_node_ = nullptr; + const MemoryRetainer* retainer_; + std::string name_; + size_t size_ = 0; +}; + template void MemoryTracker::TrackThis(const T* obj) { - accumulated_size_ += sizeof(T); + CurrentNode()->size_ = sizeof(T); } void MemoryTracker::TrackFieldWithSize(const char* name, size_t size) { - accumulated_size_ += size; + if (size > 0) + AddNode(name)->size_ = size; } void MemoryTracker::TrackField(const char* name, const MemoryRetainer& value) { @@ -21,9 +62,13 @@ void MemoryTracker::TrackField(const char* name, const MemoryRetainer& value) { } void MemoryTracker::TrackField(const char* name, const MemoryRetainer* value) { - if (track_only_self_ || value == nullptr || seen_.count(value) > 0) return; - seen_.insert(value); - Track(value); + if (track_only_self_ || value == nullptr) return; + auto it = seen_.find(value); + if (it != seen_.end()) { + graph_->AddEdge(CurrentNode(), it->second); + } else { + Track(value, name); + } } template @@ -36,8 +81,10 @@ template void MemoryTracker::TrackField(const char* name, const T& value) { if (value.begin() == value.end()) return; size_t index = 0; + PushNode(name); for (Iterator it = value.begin(); it != value.end(); ++it) TrackField(std::to_string(index++).c_str(), *it); + PopNode(); } template @@ -56,13 +103,15 @@ void MemoryTracker::TrackField(const char* name, const std::queue& value) { template void MemoryTracker::TrackField(const char* name, const T& value) { // For numbers, creating new nodes is not worth the overhead. - TrackFieldWithSize(name, sizeof(T)); + CurrentNode()->size_ += sizeof(T); } template void MemoryTracker::TrackField(const char* name, const std::pair& value) { + PushNode(name); TrackField("first", value.first); TrackField("second", value.second); + PopNode(); } template @@ -74,10 +123,13 @@ void MemoryTracker::TrackField(const char* name, template void MemoryTracker::TrackField(const char* name, const v8::Persistent& value) { + TrackField(name, value.Get(isolate_)); } template void MemoryTracker::TrackField(const char* name, const v8::Local& value) { + if (!value.IsEmpty()) + graph_->AddEdge(CurrentNode(), graph_->V8Node(value)); } template @@ -96,8 +148,47 @@ void MemoryTracker::TrackField(const char* name, TrackField(name, value.GetJSArray()); } -void MemoryTracker::Track(const MemoryRetainer* value) { +void MemoryTracker::Track(const MemoryRetainer* value, const char* name) { + v8::HandleScope handle_scope(isolate_); + MemoryRetainerNode* n = PushNode(name, value); value->MemoryInfo(this); + CHECK_EQ(CurrentNode(), n); + CHECK_NE(n->size_, 0); + PopNode(); +} + +MemoryRetainerNode* MemoryTracker::CurrentNode() const { + if (node_stack_.empty()) return nullptr; + return node_stack_.top(); +} + +MemoryRetainerNode* MemoryTracker::AddNode( + const char* name, const MemoryRetainer* retainer) { + MemoryRetainerNode* n = new MemoryRetainerNode(this, retainer, name); + graph_->AddNode(std::unique_ptr(n)); + if (retainer != nullptr) + seen_[retainer] = n; + + if (CurrentNode() != nullptr) + graph_->AddEdge(CurrentNode(), n); + + if (n->JSWrapperNode() != nullptr) { + graph_->AddEdge(n, n->JSWrapperNode()); + graph_->AddEdge(n->JSWrapperNode(), n); + } + + return n; +} + +MemoryRetainerNode* MemoryTracker::PushNode( + const char* name, const MemoryRetainer* retainer) { + MemoryRetainerNode* n = AddNode(name, retainer); + node_stack_.push(n); + return n; +} + +void MemoryTracker::PopNode() { + node_stack_.pop(); } } // namespace node diff --git a/src/memory_tracker.h b/src/memory_tracker.h index 18822651f67873..d0f9e0dcad8f1e 100644 --- a/src/memory_tracker.h +++ b/src/memory_tracker.h @@ -3,15 +3,19 @@ #if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS -#include +#include #include +#include +#include #include #include -#include +#include "aliased_buffer.h" +#include "v8-profiler.h" namespace node { class MemoryTracker; +class MemoryRetainerNode; namespace crypto { class NodeBIO; @@ -29,6 +33,8 @@ class MemoryRetainer { } virtual bool IsRootNode() const { return false; } + + virtual std::string MemoryInfoName() const { return std::string(); } }; class MemoryTracker { @@ -67,17 +73,32 @@ class MemoryTracker { inline void TrackField(const char* name, const AliasedBuffer& value); - inline void Track(const MemoryRetainer* value); - inline size_t accumulated_size() const { return accumulated_size_; } + inline void Track(const MemoryRetainer* value, const char* name = nullptr); inline void set_track_only_self(bool value) { track_only_self_ = value; } + inline v8::EmbedderGraph* graph() { return graph_; } + inline v8::Isolate* isolate() { return isolate_; } - inline MemoryTracker() {} + inline explicit MemoryTracker(v8::Isolate* isolate, + v8::EmbedderGraph* graph) + : isolate_(isolate), graph_(graph) {} private: + typedef std::unordered_map + NodeMap; + + inline MemoryRetainerNode* CurrentNode() const; + inline MemoryRetainerNode* AddNode(const char* name, + const MemoryRetainer* retainer = nullptr); + inline MemoryRetainerNode* PushNode(const char* name, + const MemoryRetainer* retainer = nullptr); + inline void PopNode(); + bool track_only_self_ = false; - size_t accumulated_size_ = 0; - std::unordered_set seen_; + v8::Isolate* isolate_; + v8::EmbedderGraph* graph_; + std::stack node_stack_; + NodeMap seen_; }; } // namespace node diff --git a/src/tls_wrap.cc b/src/tls_wrap.cc index 0d0791b710c860..3efa6adb4edb0e 100644 --- a/src/tls_wrap.cc +++ b/src/tls_wrap.cc @@ -758,6 +758,8 @@ void TLSWrap::DestroySSL(const FunctionCallbackInfo& args) { // Destroy the SSL structure and friends wrap->SSLWrap::DestroySSL(); + wrap->enc_in_ = nullptr; + wrap->enc_out_ = nullptr; if (wrap->stream_ != nullptr) wrap->stream_->RemoveStreamListener(wrap); @@ -868,8 +870,10 @@ void TLSWrap::MemoryInfo(MemoryTracker* tracker) const { tracker->TrackThis(this); tracker->TrackField("error", error_); tracker->TrackField("pending_cleartext_input", pending_cleartext_input_); - tracker->TrackField("enc_in", crypto::NodeBIO::FromBIO(enc_in_)); - tracker->TrackField("enc_out", crypto::NodeBIO::FromBIO(enc_out_)); + if (enc_in_ != nullptr) + tracker->TrackField("enc_in", crypto::NodeBIO::FromBIO(enc_in_)); + if (enc_out_ != nullptr) + tracker->TrackField("enc_out", crypto::NodeBIO::FromBIO(enc_out_)); } diff --git a/src/tls_wrap.h b/src/tls_wrap.h index b45e379ca3f61c..5f4fd3f7073305 100644 --- a/src/tls_wrap.h +++ b/src/tls_wrap.h @@ -143,8 +143,8 @@ class TLSWrap : public AsyncWrap, static int SelectSNIContextCallback(SSL* s, int* ad, void* arg); crypto::SecureContext* sc_; - BIO* enc_in_; - BIO* enc_out_; + BIO* enc_in_ = nullptr; + BIO* enc_out_ = nullptr; std::vector pending_cleartext_input_; size_t write_size_; WriteWrap* current_write_ = nullptr;