diff --git a/lib/internal/test/heap.js b/lib/internal/test/heap.js index a9260f651b9c1a..0a5bf7059713a5 100644 --- a/lib/internal/test/heap.js +++ b/lib/internal/test/heap.js @@ -37,8 +37,8 @@ function createJSHeapDump() { const fromNode = nodes[fromNodeIndex]; const edge = { type, - toNode, - fromNode, + to: toNode, + from: fromNode, name: typeof name_or_index === 'string' ? name_or_index : null }; toNode.incomingEdges.push(edge); diff --git a/src/async_wrap.cc b/src/async_wrap.cc index 00c71b47e4ad40..0cfe0202ccf675 100644 --- a/src/async_wrap.cc +++ b/src/async_wrap.cc @@ -72,9 +72,9 @@ struct AsyncWrapObject : public AsyncWrap { inline AsyncWrapObject(Environment* env, Local object, ProviderType type) : AsyncWrap(env, object, type) {} - void MemoryInfo(MemoryTracker* tracker) const override { - tracker->TrackThis(this); - } + SET_NO_MEMORY_INFO() + SET_MEMORY_INFO_NAME(AsyncWrapObject) + SET_SELF_SIZE(AsyncWrapObject) }; @@ -180,9 +180,9 @@ class PromiseWrap : public AsyncWrap { MakeWeak(); } - void MemoryInfo(MemoryTracker* tracker) const override { - tracker->TrackThis(this); - } + SET_NO_MEMORY_INFO() + SET_MEMORY_INFO_NAME(PromiseWrap) + SET_SELF_SIZE(PromiseWrap) static constexpr int kPromiseField = 1; static constexpr int kIsChainedPromiseField = 2; diff --git a/src/base_object.h b/src/base_object.h index 64a237143386f2..e0f3f27950e7d0 100644 --- a/src/base_object.h +++ b/src/base_object.h @@ -33,11 +33,6 @@ namespace node { class Environment; -#define ADD_MEMORY_INFO_NAME(name) \ - std::string MemoryInfoName() const override { \ - return #name; \ - } - class BaseObject : public MemoryRetainer { public: // Associates this object with `object`. It uses the 0th internal field for diff --git a/src/cares_wrap.cc b/src/cares_wrap.cc index 5970ec387fa516..1941fa1b4e3a1d 100644 --- a/src/cares_wrap.cc +++ b/src/cares_wrap.cc @@ -127,8 +127,9 @@ struct node_ares_task : public MemoryRetainer { ares_socket_t sock; uv_poll_t poll_watcher; - void MemoryInfo(MemoryTracker* tracker) const override; - ADD_MEMORY_INFO_NAME(node_ares_task) + inline void MemoryInfo(MemoryTracker* tracker) const override; + SET_MEMORY_INFO_NAME(node_ares_task) + SET_SELF_SIZE(node_ares_task) }; struct TaskHash { @@ -172,13 +173,13 @@ class ChannelWrap : public AsyncWrap { inline node_ares_task_list* task_list() { return &task_list_; } void MemoryInfo(MemoryTracker* tracker) const override { - tracker->TrackThis(this); if (timer_handle_ != nullptr) - tracker->TrackFieldWithSize("timer handle", sizeof(*timer_handle_)); - tracker->TrackField("node_ares_task_list", task_list_); + tracker->TrackField("timer_handle", *timer_handle_); + tracker->TrackField("task_list", task_list_, "node_ares_task_list"); } - ADD_MEMORY_INFO_NAME(ChannelWrap) + SET_MEMORY_INFO_NAME(ChannelWrap) + SET_SELF_SIZE(ChannelWrap) static void AresTimeout(uv_timer_t* handle); @@ -192,11 +193,6 @@ class ChannelWrap : public AsyncWrap { node_ares_task_list task_list_; }; -void node_ares_task::MemoryInfo(MemoryTracker* tracker) const { - tracker->TrackThis(this); - tracker->TrackField("channel", channel); -} - ChannelWrap::ChannelWrap(Environment* env, Local object) : AsyncWrap(env, object, PROVIDER_DNSCHANNEL), @@ -225,11 +221,9 @@ class GetAddrInfoReqWrap : public ReqWrap { Local req_wrap_obj, bool verbatim); - void MemoryInfo(MemoryTracker* tracker) const override { - tracker->TrackThis(this); - } - - ADD_MEMORY_INFO_NAME(GetAddrInfoReqWrap) + SET_NO_MEMORY_INFO() + SET_MEMORY_INFO_NAME(GetAddrInfoReqWrap) + SET_SELF_SIZE(GetAddrInfoReqWrap) bool verbatim() const { return verbatim_; } @@ -249,11 +243,9 @@ class GetNameInfoReqWrap : public ReqWrap { public: GetNameInfoReqWrap(Environment* env, Local req_wrap_obj); - void MemoryInfo(MemoryTracker* tracker) const override { - tracker->TrackThis(this); - } - - ADD_MEMORY_INFO_NAME(GetNameInfoReqWrap) + SET_NO_MEMORY_INFO() + SET_MEMORY_INFO_NAME(GetNameInfoReqWrap) + SET_SELF_SIZE(GetNameInfoReqWrap) }; GetNameInfoReqWrap::GetNameInfoReqWrap(Environment* env, @@ -298,6 +290,9 @@ void ares_poll_close_cb(uv_poll_t* watcher) { delete task; } +void node_ares_task::MemoryInfo(MemoryTracker* tracker) const { + tracker->TrackField("channel", channel); +} /* Allocates and returns a new node_ares_task */ node_ares_task* ares_task_create(ChannelWrap* channel, ares_socket_t sock) { @@ -1195,11 +1190,9 @@ class QueryAnyWrap: public QueryWrap { return 0; } - void MemoryInfo(MemoryTracker* tracker) const override { - tracker->TrackThis(this); - } - - ADD_MEMORY_INFO_NAME(QueryAnyWrap) + SET_NO_MEMORY_INFO() + SET_MEMORY_INFO_NAME(QueryAnyWrap) + SET_SELF_SIZE(QueryAnyWrap) protected: void Parse(unsigned char* buf, int len) override { @@ -1376,11 +1369,9 @@ class QueryAWrap: public QueryWrap { return 0; } - void MemoryInfo(MemoryTracker* tracker) const override { - tracker->TrackThis(this); - } - - ADD_MEMORY_INFO_NAME(QueryAWrap) + SET_NO_MEMORY_INFO() + SET_MEMORY_INFO_NAME(QueryAWrap) + SET_SELF_SIZE(QueryAWrap) protected: void Parse(unsigned char* buf, int len) override { @@ -1424,11 +1415,9 @@ class QueryAaaaWrap: public QueryWrap { return 0; } - void MemoryInfo(MemoryTracker* tracker) const override { - tracker->TrackThis(this); - } - - ADD_MEMORY_INFO_NAME(QueryAaaaWrap) + SET_NO_MEMORY_INFO() + SET_MEMORY_INFO_NAME(QueryAaaaWrap) + SET_SELF_SIZE(QueryAaaaWrap) protected: void Parse(unsigned char* buf, int len) override { @@ -1472,11 +1461,9 @@ class QueryCnameWrap: public QueryWrap { return 0; } - void MemoryInfo(MemoryTracker* tracker) const override { - tracker->TrackThis(this); - } - - ADD_MEMORY_INFO_NAME(QueryCnameWrap) + SET_NO_MEMORY_INFO() + SET_MEMORY_INFO_NAME(QueryCnameWrap) + SET_SELF_SIZE(QueryCnameWrap) protected: void Parse(unsigned char* buf, int len) override { @@ -1507,11 +1494,9 @@ class QueryMxWrap: public QueryWrap { return 0; } - void MemoryInfo(MemoryTracker* tracker) const override { - tracker->TrackThis(this); - } - - ADD_MEMORY_INFO_NAME(QueryMxWrap) + SET_NO_MEMORY_INFO() + SET_MEMORY_INFO_NAME(QueryMxWrap) + SET_SELF_SIZE(QueryMxWrap) protected: void Parse(unsigned char* buf, int len) override { @@ -1542,11 +1527,9 @@ class QueryNsWrap: public QueryWrap { return 0; } - void MemoryInfo(MemoryTracker* tracker) const override { - tracker->TrackThis(this); - } - - ADD_MEMORY_INFO_NAME(QueryNsWrap) + SET_NO_MEMORY_INFO() + SET_MEMORY_INFO_NAME(QueryNsWrap) + SET_SELF_SIZE(QueryNsWrap) protected: void Parse(unsigned char* buf, int len) override { @@ -1577,11 +1560,9 @@ class QueryTxtWrap: public QueryWrap { return 0; } - void MemoryInfo(MemoryTracker* tracker) const override { - tracker->TrackThis(this); - } - - ADD_MEMORY_INFO_NAME(QueryTxtWrap) + SET_NO_MEMORY_INFO() + SET_MEMORY_INFO_NAME(QueryTxtWrap) + SET_SELF_SIZE(QueryTxtWrap) protected: void Parse(unsigned char* buf, int len) override { @@ -1611,11 +1592,9 @@ class QuerySrvWrap: public QueryWrap { return 0; } - void MemoryInfo(MemoryTracker* tracker) const override { - tracker->TrackThis(this); - } - - ADD_MEMORY_INFO_NAME(QuerySrvWrap) + SET_NO_MEMORY_INFO() + SET_MEMORY_INFO_NAME(QuerySrvWrap) + SET_SELF_SIZE(QuerySrvWrap) protected: void Parse(unsigned char* buf, int len) override { @@ -1644,11 +1623,9 @@ class QueryPtrWrap: public QueryWrap { return 0; } - void MemoryInfo(MemoryTracker* tracker) const override { - tracker->TrackThis(this); - } - - ADD_MEMORY_INFO_NAME(QueryPtrWrap) + SET_NO_MEMORY_INFO() + SET_MEMORY_INFO_NAME(QueryPtrWrap) + SET_SELF_SIZE(QueryPtrWrap) protected: void Parse(unsigned char* buf, int len) override { @@ -1679,11 +1656,9 @@ class QueryNaptrWrap: public QueryWrap { return 0; } - void MemoryInfo(MemoryTracker* tracker) const override { - tracker->TrackThis(this); - } - - ADD_MEMORY_INFO_NAME(QueryNaptrWrap) + SET_NO_MEMORY_INFO() + SET_MEMORY_INFO_NAME(QueryNaptrWrap) + SET_SELF_SIZE(QueryNaptrWrap) protected: void Parse(unsigned char* buf, int len) override { @@ -1713,11 +1688,9 @@ class QuerySoaWrap: public QueryWrap { return 0; } - void MemoryInfo(MemoryTracker* tracker) const override { - tracker->TrackThis(this); - } - - ADD_MEMORY_INFO_NAME(QuerySoaWrap) + SET_NO_MEMORY_INFO() + SET_MEMORY_INFO_NAME(QuerySoaWrap) + SET_SELF_SIZE(QuerySoaWrap) protected: void Parse(unsigned char* buf, int len) override { @@ -1801,11 +1774,9 @@ class GetHostByAddrWrap: public QueryWrap { return 0; } - void MemoryInfo(MemoryTracker* tracker) const override { - tracker->TrackThis(this); - } - - ADD_MEMORY_INFO_NAME(GetHostByAddrWrap) + SET_NO_MEMORY_INFO() + SET_MEMORY_INFO_NAME(GetHostByAddrWrap) + SET_SELF_SIZE(GetHostByAddrWrap) protected: void Parse(struct hostent* host) override { diff --git a/src/connect_wrap.h b/src/connect_wrap.h index 2370157eaa2a11..88221b77468631 100644 --- a/src/connect_wrap.h +++ b/src/connect_wrap.h @@ -16,11 +16,9 @@ class ConnectWrap : public ReqWrap { v8::Local req_wrap_obj, AsyncWrap::ProviderType provider); - void MemoryInfo(MemoryTracker* tracker) const override { - tracker->TrackThis(this); - } - - ADD_MEMORY_INFO_NAME(ConnectWrap) + SET_NO_MEMORY_INFO() + SET_MEMORY_INFO_NAME(ConnectWrap) + SET_SELF_SIZE(ConnectWrap) }; } // namespace node diff --git a/src/fs_event_wrap.cc b/src/fs_event_wrap.cc index 1444937e7a853e..130996a6b7fcdd 100644 --- a/src/fs_event_wrap.cc +++ b/src/fs_event_wrap.cc @@ -57,11 +57,9 @@ class FSEventWrap: public HandleWrap { static void Start(const FunctionCallbackInfo& args); static void GetInitialized(const FunctionCallbackInfo& args); - void MemoryInfo(MemoryTracker* tracker) const override { - tracker->TrackThis(this); - } - - ADD_MEMORY_INFO_NAME(FSEventWrap) + SET_NO_MEMORY_INFO() + SET_MEMORY_INFO_NAME(FSEventWrap) + SET_SELF_SIZE(FSEventWrap) private: static const encoding kDefaultEncoding = UTF8; diff --git a/src/heap_utils.cc b/src/heap_utils.cc index 5ed5119bdb0ca0..c4c42bb3a17fd4 100644 --- a/src/heap_utils.cc +++ b/src/heap_utils.cc @@ -110,15 +110,28 @@ class JSGraph : public EmbedderGraph { for (const std::unique_ptr& n : nodes_) { Local obj = info_objects[n.get()]; Local value; - if (!String::NewFromUtf8(isolate_, n->Name(), - v8::NewStringType::kNormal).ToLocal(&value) || + std::string name_str; + const char* prefix = n->NamePrefix(); + if (prefix == nullptr) { + name_str = n->Name(); + } else { + name_str = n->NamePrefix(); + name_str += " "; + name_str += n->Name(); + } + if (!String::NewFromUtf8( + isolate_, name_str.c_str(), v8::NewStringType::kNormal) + .ToLocal(&value) || obj->Set(context, name_string, value).IsNothing() || - obj->Set(context, is_root_string, - Boolean::New(isolate_, n->IsRootNode())).IsNothing() || - obj->Set(context, size_string, - Number::New(isolate_, n->SizeInBytes())).IsNothing() || - obj->Set(context, edges_string, - Array::New(isolate_)).IsNothing()) { + obj->Set(context, + is_root_string, + Boolean::New(isolate_, n->IsRootNode())) + .IsNothing() || + obj->Set(context, + size_string, + Number::New(isolate_, n->SizeInBytes())) + .IsNothing() || + obj->Set(context, edges_string, Array::New(isolate_)).IsNothing()) { return MaybeLocal(); } if (nodes->Set(context, i++, obj).IsNothing()) @@ -152,20 +165,21 @@ class JSGraph : public EmbedderGraph { size_t j = 0; for (const auto& edge : edge_info.second) { Local to_object = info_objects[edge.second]; - Local edge_info = Object::New(isolate_); + Local edge_obj = Object::New(isolate_); Local edge_name_value; const char* edge_name = edge.first; - if (edge_name != nullptr && - !String::NewFromUtf8( - isolate_, edge_name, v8::NewStringType::kNormal) - .ToLocal(&edge_name_value)) { - return MaybeLocal(); + if (edge_name != nullptr) { + if (!String::NewFromUtf8( + isolate_, edge_name, v8::NewStringType::kNormal) + .ToLocal(&edge_name_value)) { + return MaybeLocal(); + } } else { edge_name_value = Number::New(isolate_, j++); } - if (edge_info->Set(context, name_string, edge_name_value).IsNothing() || - edge_info->Set(context, to_string, to_object).IsNothing() || - edges.As()->Set(context, i++, edge_info).IsNothing()) { + if (edge_obj->Set(context, name_string, edge_name_value).IsNothing() || + edge_obj->Set(context, to_string, to_object).IsNothing() || + edges.As()->Set(context, i++, edge_obj).IsNothing()) { return MaybeLocal(); } } diff --git a/src/inspector_js_api.cc b/src/inspector_js_api.cc index 49e1dcc6e8a30c..8ec6603e5b4bc3 100644 --- a/src/inspector_js_api.cc +++ b/src/inspector_js_api.cc @@ -105,12 +105,13 @@ class JSBindingsConnection : public AsyncWrap { } void MemoryInfo(MemoryTracker* tracker) const override { - tracker->TrackThis(this); tracker->TrackField("callback", callback_); - tracker->TrackFieldWithSize("session", sizeof(*session_)); + tracker->TrackFieldWithSize( + "session", sizeof(*session_), "InspectorSession"); } - ADD_MEMORY_INFO_NAME(JSBindingsConnection) + SET_MEMORY_INFO_NAME(JSBindingsConnection) + SET_SELF_SIZE(JSBindingsConnection) private: std::unique_ptr session_; diff --git a/src/js_stream.h b/src/js_stream.h index 05fb688f2f4115..6612e558aea1d7 100644 --- a/src/js_stream.h +++ b/src/js_stream.h @@ -27,11 +27,9 @@ class JSStream : public AsyncWrap, public StreamBase { size_t count, uv_stream_t* send_handle) override; - void MemoryInfo(MemoryTracker* tracker) const override { - tracker->TrackThis(this); - } - - ADD_MEMORY_INFO_NAME(JSStream) + SET_NO_MEMORY_INFO() + SET_MEMORY_INFO_NAME(JSStream) + SET_SELF_SIZE(JSStream) protected: JSStream(Environment* env, v8::Local obj); diff --git a/src/memory_tracker-inl.h b/src/memory_tracker-inl.h index 568a4364f9c64d..20300297043ea7 100644 --- a/src/memory_tracker-inl.h +++ b/src/memory_tracker-inl.h @@ -7,23 +7,40 @@ namespace node { +// Fallback edge_name if node_name is not available, or "" if edge_name +// is not available either. +inline const char* GetNodeName(const char* node_name, const char* edge_name) { + if (node_name != nullptr) { + return node_name; + } + if (edge_name != nullptr) { + return edge_name; + } + return ""; +} + 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); + inline MemoryRetainerNode(MemoryTracker* tracker, + const MemoryRetainer* retainer) + : retainer_(retainer) { + CHECK_NOT_NULL(retainer_); + v8::HandleScope handle_scope(tracker->isolate()); + v8::Local obj = retainer_->WrappedObject(); + if (!obj.IsEmpty()) wrapper_node_ = tracker->graph()->V8Node(obj); + + name_ = retainer_->MemoryInfoName(); + size_ = retainer_->SelfSize(); + } - name_ = retainer_->MemoryInfoName(); - } - if (name_.empty() && name != nullptr) { - name_ = name; - } + inline MemoryRetainerNode(MemoryTracker* tracker, + const char* name, + size_t size, + bool is_root_node = false) + : retainer_(nullptr) { + name_ = name; + size_ = size; + is_root_node_ = is_root_node; } const char* Name() override { return name_.c_str(); } @@ -35,60 +52,92 @@ class MemoryRetainerNode : public v8::EmbedderGraph::Node { Node* JSWrapperNode() { return wrapper_node_; } bool IsRootNode() override { - return retainer_ != nullptr && retainer_->IsRootNode(); + if (retainer_ != nullptr) { + return retainer_->IsRootNode(); + } + return is_root_node_; } private: friend class MemoryTracker; - Node* wrapper_node_ = nullptr; + // If retainer_ is not nullptr, then it must have a wrapper_node_, + // and we have + // name_ == retainer_->MemoryInfoName() + // size_ == retainer_->SelfSize() + // is_root_node_ == retainer_->IsRootNode() const MemoryRetainer* retainer_; + Node* wrapper_node_ = nullptr; + + // Otherwise (retainer == nullptr), we set these fields in an ad-hoc way + bool is_root_node_ = false; std::string name_; size_t size_ = 0; }; -template -void MemoryTracker::TrackThis(const T* obj) { - CurrentNode()->size_ = sizeof(T); -} - -void MemoryTracker::TrackFieldWithSize(const char* name, size_t size) { - if (size > 0) - AddNode(name)->size_ = size; +void MemoryTracker::TrackFieldWithSize(const char* edge_name, + size_t size, + const char* node_name) { + if (size > 0) AddNode(GetNodeName(node_name, edge_name), size, edge_name); } -void MemoryTracker::TrackField(const char* name, const MemoryRetainer& value) { - TrackField(name, &value); +void MemoryTracker::TrackField(const char* edge_name, + const MemoryRetainer& value, + const char* node_name) { + TrackField(edge_name, &value); } -void MemoryTracker::TrackField(const char* name, const MemoryRetainer* value) { - if (track_only_self_ || value == nullptr) return; +void MemoryTracker::TrackField(const char* edge_name, + const MemoryRetainer* value, + const char* node_name) { + if (value == nullptr) return; auto it = seen_.find(value); if (it != seen_.end()) { - graph_->AddEdge(CurrentNode(), it->second); + // For ABI compatibility, we did not backport the virtual function + // AddEdge() with a name as last argument back to v10.x. + graph_->AddEdge(CurrentNode(), it->second/*, edge_name */); } else { - Track(value, name); + Track(value, edge_name); } } template -void MemoryTracker::TrackField(const char* name, - const std::unique_ptr& value) { - TrackField(name, value.get()); +void MemoryTracker::TrackField(const char* edge_name, + const std::unique_ptr& value, + const char* node_name) { + if (value.get() == nullptr) { + return; + } + TrackField(edge_name, value.get(), node_name); } template -void MemoryTracker::TrackField(const char* name, const T& value) { +void MemoryTracker::TrackField(const char* edge_name, + const T& value, + const char* node_name, + const char* element_name, + bool subtract_from_self) { + // If the container is empty, the size has been accounted into the parent's + // self size 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); + // Fall back to edge name if node names are not provided + if (CurrentNode() != nullptr && subtract_from_self) { + // Shift the self size of this container out to a separate node + CurrentNode()->size_ -= sizeof(T); + } + PushNode(GetNodeName(node_name, edge_name), sizeof(T), edge_name); + for (Iterator it = value.begin(); it != value.end(); ++it) { + // Use nullptr as edge names so the elements appear as indexed properties + TrackField(nullptr, *it, element_name); + } PopNode(); } template -void MemoryTracker::TrackField(const char* name, const std::queue& value) { +void MemoryTracker::TrackField(const char* edge_name, + const std::queue& value, + const char* node_name, + const char* element_name) { struct ContainerGetter : public std::queue { static const typename std::queue::container_type& Get( const std::queue& value) { @@ -97,61 +146,103 @@ void MemoryTracker::TrackField(const char* name, const std::queue& value) { }; const auto& container = ContainerGetter::Get(value); - TrackField(name, container); + TrackField(edge_name, container, node_name, element_name); } template -void MemoryTracker::TrackField(const char* name, const T& value) { +void MemoryTracker::TrackField(const char* edge_name, + const T& value, + const char* node_name) { // For numbers, creating new nodes is not worth the overhead. CurrentNode()->size_ += sizeof(T); } template -void MemoryTracker::TrackField(const char* name, const std::pair& value) { - PushNode(name); +void MemoryTracker::TrackField(const char* edge_name, + const std::pair& value, + const char* node_name) { + PushNode(node_name == nullptr ? "pair" : node_name, + sizeof(const std::pair), + edge_name); + // TODO(joyeecheung): special case if one of these is a number type + // that meets the test_for_number trait so that their sizes don't get + // merged into the pair node TrackField("first", value.first); TrackField("second", value.second); PopNode(); } template -void MemoryTracker::TrackField(const char* name, - const std::basic_string& value) { - TrackFieldWithSize(name, value.size() * sizeof(T)); +void MemoryTracker::TrackField(const char* edge_name, + const std::basic_string& value, + const char* node_name) { + TrackFieldWithSize(edge_name, value.size() * sizeof(T), "std::basic_string"); } template -void MemoryTracker::TrackField(const char* name, - const v8::Persistent& value) { - TrackField(name, value.Get(isolate_)); +void MemoryTracker::TrackField(const char* edge_name, + const v8::Persistent& value, + const char* node_name) { + TrackField(edge_name, value.Get(isolate_)); } template -void MemoryTracker::TrackField(const char* name, const v8::Local& value) { - if (!value.IsEmpty()) - graph_->AddEdge(CurrentNode(), graph_->V8Node(value)); +void MemoryTracker::TrackField(const char* edge_name, + const v8::Local& value, + const char* node_name) { + if (!value.IsEmpty()) { + // For ABI compatibility, we did not backport the virtual function + // AddEdge() with a name as last argument back to v10.x. + graph_->AddEdge(CurrentNode(), graph_->V8Node(value)/*, edge_name */); + } } template +void MemoryTracker::TrackField(const char* edge_name, + const MallocedBuffer& value, + const char* node_name) { + TrackFieldWithSize(edge_name, value.size, "MallocedBuffer"); +} + +void MemoryTracker::TrackField(const char* name, + const uv_buf_t& value, + const char* node_name) { + TrackFieldWithSize(name, value.len, "uv_buf_t"); +} + void MemoryTracker::TrackField(const char* name, - const MallocedBuffer& value) { - TrackFieldWithSize(name, value.size); + const uv_timer_t& value, + const char* node_name) { + TrackFieldWithSize(name, sizeof(value), "uv_timer_t"); } -void MemoryTracker::TrackField(const char* name, const uv_buf_t& value) { - TrackFieldWithSize(name, value.len); +void MemoryTracker::TrackField(const char* name, + const uv_async_t& value, + const char* node_name) { + TrackFieldWithSize(name, sizeof(value), "uv_async_t"); } template void MemoryTracker::TrackField(const char* name, - const AliasedBuffer& value) { - TrackField(name, value.GetJSArray()); + const AliasedBuffer& value, + const char* node_name) { + TrackField(name, value.GetJSArray(), "AliasedBuffer"); } -void MemoryTracker::Track(const MemoryRetainer* value, const char* name) { +void MemoryTracker::Track(const MemoryRetainer* retainer, + const char* edge_name) { v8::HandleScope handle_scope(isolate_); - MemoryRetainerNode* n = PushNode(name, value); - value->MemoryInfo(this); + auto it = seen_.find(retainer); + if (it != seen_.end()) { + if (CurrentNode() != nullptr) { + // For ABI compatibility, we did not backport the virtual function + // AddEdge() with a name as last argument back to v10.x. + graph_->AddEdge(CurrentNode(), it->second/*, edge_name */); + } + return; // It has already been tracked, no need to call MemoryInfo again + } + MemoryRetainerNode* n = PushNode(retainer, edge_name); + retainer->MemoryInfo(this); CHECK_EQ(CurrentNode(), n); CHECK_NE(n->size_, 0); PopNode(); @@ -162,27 +253,56 @@ MemoryRetainerNode* MemoryTracker::CurrentNode() const { 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; +MemoryRetainerNode* MemoryTracker::AddNode(const MemoryRetainer* retainer, + const char* edge_name) { + auto it = seen_.find(retainer); + if (it != seen_.end()) { + return it->second; + } - if (CurrentNode() != nullptr) - graph_->AddEdge(CurrentNode(), n); + MemoryRetainerNode* n = new MemoryRetainerNode(this, retainer); + graph_->AddNode(std::unique_ptr(n)); + seen_[retainer] = n; + if (CurrentNode() != nullptr) { + // For ABI compatibility, we did not backport the virtual function + // AddEdge() with a name as last argument back to v10.x. + graph_->AddEdge(CurrentNode(), n/*, edge_name */); + } if (n->JSWrapperNode() != nullptr) { - graph_->AddEdge(n, n->JSWrapperNode()); - graph_->AddEdge(n->JSWrapperNode(), n); + graph_->AddEdge(n, n->JSWrapperNode()/*, "wrapped" */); + graph_->AddEdge(n->JSWrapperNode(), n/*, "wrapper" */); + } + + return n; +} + +MemoryRetainerNode* MemoryTracker::AddNode(const char* node_name, + size_t size, + const char* edge_name) { + MemoryRetainerNode* n = new MemoryRetainerNode(this, node_name, size); + graph_->AddNode(std::unique_ptr(n)); + + if (CurrentNode() != nullptr) { + // For ABI compatibility, we did not backport the virtual function + // AddEdge() with a name as last argument back to v10.x. + graph_->AddEdge(CurrentNode(), n/*, edge_name*/); } return n; } -MemoryRetainerNode* MemoryTracker::PushNode( - const char* name, const MemoryRetainer* retainer) { - MemoryRetainerNode* n = AddNode(name, retainer); +MemoryRetainerNode* MemoryTracker::PushNode(const MemoryRetainer* retainer, + const char* edge_name) { + MemoryRetainerNode* n = AddNode(retainer, edge_name); + node_stack_.push(n); + return n; +} + +MemoryRetainerNode* MemoryTracker::PushNode(const char* node_name, + size_t size, + const char* edge_name) { + MemoryRetainerNode* n = AddNode(node_name, size, edge_name); node_stack_.push(n); return n; } diff --git a/src/memory_tracker.h b/src/memory_tracker.h index d0f9e0dcad8f1e..17992792128809 100644 --- a/src/memory_tracker.h +++ b/src/memory_tracker.h @@ -14,6 +14,19 @@ namespace node { +// Set the node name of a MemoryRetainer to klass +#define SET_MEMORY_INFO_NAME(Klass) \ + inline std::string MemoryInfoName() const override { return #Klass; } + +// Set the self size of a MemoryRetainer to the stack-allocated size of a +// certain class +#define SET_SELF_SIZE(Klass) \ + inline size_t SelfSize() const override { return sizeof(Klass); } + +// Used when there is no additional fields to track +#define SET_NO_MEMORY_INFO() \ + inline void MemoryInfo(node::MemoryTracker* tracker) const override {} + class MemoryTracker; class MemoryRetainerNode; @@ -21,61 +34,169 @@ namespace crypto { class NodeBIO; } +/* Example: + * + * class ExampleRetainer : public MemoryRetainer { + * public: + * // Or use SET_NO_MEMORY_INFO() when there is no additional fields + * // to track. + * void MemoryInfo(MemoryTracker* tracker) const override { + * // Node name and size comes from the MemoryInfoName and SelfSize of + * // AnotherRetainerClass + * tracker->TrackField("another_retainer", another_retainer_); + * // Specify node name and size explicitly + * tracker->TrackFieldWithSize("internal_member", + * internal_member_.size(), + * "InternalClass"); + * // Node name falls back to the edge name, + * // elements in the container appear as grandchildren nodes + * tracker->TrackField("vector", vector_); + * // Node name and size come from the JS object + * tracker->TrackField("target", target_); + * } + * + * // Or use SET_MEMORY_INFO_NAME(ExampleRetainer) + * std::string MemoryInfoName() const override { + * return "ExampleRetainer"; + * } + * + * // Or use SET_SELF_SIZE(ExampleRetainer) + * size_t SelfSize() const override { + * return sizeof(ExampleRetainer); + * } + * + * // Note: no need to implement these two methods when implementing + * // a BaseObject or an AsyncWrap class + * bool IsRootNode() const override { return !wrapped_.IsWeak(); } + * v8::Local WrappedObject() const override { + * return node::PersistentToLocal(wrapped_); + * } + * private: + * AnotherRetainerClass another_retainer_; + * InternalClass internal_member_; + * std::vector vector_; + * node::Persistent target_; + * + * node::Persistent wrapped_; + * } + * + * This creates the following graph: + * Node / ExampleRetainer + * |> another_retainer :: Node / AnotherRetainerClass + * |> internal_member :: Node / InternalClass + * |> vector :: Node / vector (elements will be grandchildren) + * |> [1] :: Node / uv_async_t (uv_async_t has predefined names) + * |> [2] :: Node / uv_async_t + * |> ... + * |> target :: TargetClass (JS class name of the target object) + * |> wrapped :: WrappedClass (JS class name of the wrapped object) + * |> wrapper :: Node / ExampleRetainer (back reference) + */ class MemoryRetainer { public: virtual ~MemoryRetainer() {} - // Subclasses should implement this to provide information for heap snapshots. + // Subclasses should implement these methods to provide information + // for the V8 heap snapshot generator. + // The MemoryInfo() method is assumed to be called within a context + // where all the edges start from the node of the current retainer, + // and point to the nodes as specified by tracker->Track* calls. virtual void MemoryInfo(MemoryTracker* tracker) const = 0; + virtual std::string MemoryInfoName() const = 0; + virtual size_t SelfSize() const = 0; virtual v8::Local WrappedObject() const { return v8::Local(); } virtual bool IsRootNode() const { return false; } - - virtual std::string MemoryInfoName() const { return std::string(); } }; class MemoryTracker { public: + // Used to specify node name and size explicitly + inline void TrackFieldWithSize(const char* edge_name, + size_t size, + const char* node_name = nullptr); + // Shortcut to extract the underlying object out of the smart pointer template - inline void TrackThis(const T* obj); - - inline void TrackFieldWithSize(const char* name, size_t size); - - inline void TrackField(const char* name, const MemoryRetainer& value); - inline void TrackField(const char* name, const MemoryRetainer* value); - template - inline void TrackField(const char* name, const std::unique_ptr& value); + inline void TrackField(const char* edge_name, + const std::unique_ptr& value, + const char* node_name = nullptr); + + // For containers, the elements will be graphed as grandchildren nodes + // if the container is not empty. + // By default, we assume the parent count the stack size of the container + // into its SelfSize so that will be subtracted from the parent size when we + // spin off a new node for the container. + // TODO(joyeecheung): use RTTI to retrieve the class name at runtime? template - inline void TrackField(const char* name, const T& value); + inline void TrackField(const char* edge_name, + const T& value, + const char* node_name = nullptr, + const char* element_name = nullptr, + bool subtract_from_self = true); template - inline void TrackField(const char* name, const std::queue& value); - template - inline void TrackField(const char* name, const std::basic_string& value); - template ::is_specialized, bool>::type, - typename dummy = bool> - inline void TrackField(const char* name, const T& value); + inline void TrackField(const char* edge_name, + const std::queue& value, + const char* node_name = nullptr, + const char* element_name = nullptr); template - inline void TrackField(const char* name, const std::pair& value); + inline void TrackField(const char* edge_name, + const std::pair& value, + const char* node_name = nullptr); + + // For the following types, node_name will be ignored and predefined names + // will be used instead. They are only in the signature for template + // expansion. + inline void TrackField(const char* edge_name, + const MemoryRetainer& value, + const char* node_name = nullptr); + inline void TrackField(const char* edge_name, + const MemoryRetainer* value, + const char* node_name = nullptr); + template + inline void TrackField(const char* edge_name, + const std::basic_string& value, + const char* node_name = nullptr); + template ::is_specialized, bool>::type, + typename dummy = bool> + inline void TrackField(const char* edge_name, + const T& value, + const char* node_name = nullptr); template - inline void TrackField(const char* name, - const v8::Persistent& value); + inline void TrackField(const char* edge_name, + const v8::Persistent& value, + const char* node_name = nullptr); template - inline void TrackField(const char* name, const v8::Local& value); + inline void TrackField(const char* edge_name, + const v8::Local& value, + const char* node_name = nullptr); template - inline void TrackField(const char* name, const MallocedBuffer& value); - inline void TrackField(const char* name, const uv_buf_t& value); + inline void TrackField(const char* edge_name, + const MallocedBuffer& value, + const char* node_name = nullptr); + inline void TrackField(const char* edge_name, + const uv_buf_t& value, + const char* node_name = nullptr); + inline void TrackField(const char* edge_name, + const uv_timer_t& value, + const char* node_name = nullptr); + inline void TrackField(const char* edge_name, + const uv_async_t& value, + const char* node_name = nullptr); template - inline void TrackField(const char* name, - const AliasedBuffer& value); + inline void TrackField(const char* edge_name, + const AliasedBuffer& value, + const char* node_name = nullptr); - inline void Track(const MemoryRetainer* value, const char* name = nullptr); + // Put a memory container into the graph, create an edge from + // the current node if there is one on the stack. + inline void Track(const MemoryRetainer* retainer, + const char* edge_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_; } @@ -88,13 +209,18 @@ class MemoryTracker { 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 MemoryRetainerNode* AddNode(const MemoryRetainer* retainer, + const char* edge_name = nullptr); + inline MemoryRetainerNode* PushNode(const MemoryRetainer* retainer, + const char* edge_name = nullptr); + inline MemoryRetainerNode* AddNode(const char* node_name, + size_t size, + const char* edge_name = nullptr); + inline MemoryRetainerNode* PushNode(const char* node_name, + size_t size, + const char* edge_name = nullptr); inline void PopNode(); - bool track_only_self_ = false; v8::Isolate* isolate_; v8::EmbedderGraph* graph_; std::stack node_stack_; diff --git a/src/module_wrap.h b/src/module_wrap.h index 3e19b6c9eb3ebe..d6593c48135d18 100644 --- a/src/module_wrap.h +++ b/src/module_wrap.h @@ -34,12 +34,12 @@ class ModuleWrap : public BaseObject { v8::Local meta); void MemoryInfo(MemoryTracker* tracker) const override { - tracker->TrackThis(this); tracker->TrackField("url", url_); tracker->TrackField("resolve_cache", resolve_cache_); } - ADD_MEMORY_INFO_NAME(ModuleWrap) + SET_MEMORY_INFO_NAME(ModuleWrap) + SET_SELF_SIZE(ModuleWrap) private: ModuleWrap(Environment* env, diff --git a/src/node_contextify.cc b/src/node_contextify.cc index e850b94e71cba9..f46193d77510b7 100644 --- a/src/node_contextify.cc +++ b/src/node_contextify.cc @@ -589,13 +589,11 @@ class ContextifyScript : public BaseObject { private: Persistent script_; - void MemoryInfo(MemoryTracker* tracker) const override { - tracker->TrackThis(this); - } - - ADD_MEMORY_INFO_NAME(ContextifyScript) - public: + SET_NO_MEMORY_INFO() + SET_MEMORY_INFO_NAME(ContextifyScript) + SET_SELF_SIZE(ContextifyScript) + static void Init(Environment* env, Local target) { HandleScope scope(env->isolate()); Local class_name = diff --git a/src/node_crypto.h b/src/node_crypto.h index 714afd0d3bb868..acb61885f28213 100644 --- a/src/node_crypto.h +++ b/src/node_crypto.h @@ -107,11 +107,10 @@ class SecureContext : public BaseObject { static void Initialize(Environment* env, v8::Local target); - void MemoryInfo(MemoryTracker* tracker) const override { - tracker->TrackThis(this); - } - - ADD_MEMORY_INFO_NAME(SecureContext) + // TODO(joyeecheung): track the memory used by OpenSSL types + SET_NO_MEMORY_INFO() + SET_MEMORY_INFO_NAME(SecureContext) + SET_SELF_SIZE(SecureContext) SSLCtxPointer ctx_; X509Pointer cert_; @@ -347,11 +346,10 @@ class CipherBase : public BaseObject { public: static void Initialize(Environment* env, v8::Local target); - void MemoryInfo(MemoryTracker* tracker) const override { - tracker->TrackThis(this); - } - - ADD_MEMORY_INFO_NAME(CipherBase) + // TODO(joyeecheung): track the memory used by OpenSSL types + SET_NO_MEMORY_INFO() + SET_MEMORY_INFO_NAME(CipherBase) + SET_SELF_SIZE(CipherBase) protected: enum CipherKind { @@ -436,11 +434,10 @@ class Hmac : public BaseObject { public: static void Initialize(Environment* env, v8::Local target); - void MemoryInfo(MemoryTracker* tracker) const override { - tracker->TrackThis(this); - } - - ADD_MEMORY_INFO_NAME(Hmac) + // TODO(joyeecheung): track the memory used by OpenSSL types + SET_NO_MEMORY_INFO() + SET_MEMORY_INFO_NAME(Hmac) + SET_SELF_SIZE(Hmac) protected: void HmacInit(const char* hash_type, const char* key, int key_len); @@ -465,11 +462,10 @@ class Hash : public BaseObject { public: static void Initialize(Environment* env, v8::Local target); - void MemoryInfo(MemoryTracker* tracker) const override { - tracker->TrackThis(this); - } - - ADD_MEMORY_INFO_NAME(Hash) + // TODO(joyeecheung): track the memory used by OpenSSL types + SET_NO_MEMORY_INFO() + SET_MEMORY_INFO_NAME(Hash) + SET_SELF_SIZE(Hash) bool HashInit(const char* hash_type); bool HashUpdate(const char* data, int len); @@ -510,11 +506,10 @@ class SignBase : public BaseObject { Error Init(const char* sign_type); Error Update(const char* data, int len); - void MemoryInfo(MemoryTracker* tracker) const override { - tracker->TrackThis(this); - } - - ADD_MEMORY_INFO_NAME(SignBase) + // TODO(joyeecheung): track the memory used by OpenSSL types + SET_NO_MEMORY_INFO() + SET_MEMORY_INFO_NAME(SignBase) + SET_SELF_SIZE(SignBase) protected: void CheckThrow(Error error); @@ -628,11 +623,10 @@ class DiffieHellman : public BaseObject { MakeWeak(); } - void MemoryInfo(MemoryTracker* tracker) const override { - tracker->TrackThis(this); - } - - ADD_MEMORY_INFO_NAME(DiffieHellman) + // TODO(joyeecheung): track the memory used by OpenSSL types + SET_NO_MEMORY_INFO() + SET_MEMORY_INFO_NAME(DiffieHellman) + SET_SELF_SIZE(DiffieHellman) private: static void GetField(const v8::FunctionCallbackInfo& args, @@ -659,11 +653,10 @@ class ECDH : public BaseObject { char* data, size_t len); - void MemoryInfo(MemoryTracker* tracker) const override { - tracker->TrackThis(this); - } - - ADD_MEMORY_INFO_NAME(ECDH) + // TODO(joyeecheung): track the memory used by OpenSSL types + SET_NO_MEMORY_INFO() + SET_MEMORY_INFO_NAME(ECDH) + SET_SELF_SIZE(ECDH) protected: ECDH(Environment* env, v8::Local wrap, ECKeyPointer&& key) diff --git a/src/node_crypto_bio.h b/src/node_crypto_bio.h index 0c61f19d0189d2..1c62fbbd359405 100644 --- a/src/node_crypto_bio.h +++ b/src/node_crypto_bio.h @@ -108,11 +108,11 @@ class NodeBIO : public MemoryRetainer { static NodeBIO* FromBIO(BIO* bio); void MemoryInfo(MemoryTracker* tracker) const override { - tracker->TrackThis(this); - tracker->TrackFieldWithSize("buffer", length_); + tracker->TrackFieldWithSize("buffer", length_, "NodeBIO::Buffer"); } - ADD_MEMORY_INFO_NAME(NodeBIO) + SET_MEMORY_INFO_NAME(NodeBIO) + SET_SELF_SIZE(NodeBIO) private: static int New(BIO* bio); diff --git a/src/node_file.cc b/src/node_file.cc index 3d40c8e9bf4dc1..6a2f2107d6df27 100644 --- a/src/node_file.cc +++ b/src/node_file.cc @@ -277,6 +277,10 @@ void FileHandle::AfterClose() { EmitRead(UV_EOF); } +void FileHandleReadWrap::MemoryInfo(MemoryTracker* tracker) const { + tracker->TrackField("buffer", buffer_); + tracker->TrackField("file_handle", this->file_handle_); +} FileHandleReadWrap::FileHandleReadWrap(FileHandle* handle, Local obj) : ReqWrap(handle->env(), obj, AsyncWrap::PROVIDER_FSREQWRAP), diff --git a/src/node_file.h b/src/node_file.h index 457e72c33a7321..cbbb8b037d8e0a 100644 --- a/src/node_file.h +++ b/src/node_file.h @@ -53,10 +53,12 @@ class FSContinuationData : public MemoryRetainer { } void MemoryInfo(MemoryTracker* tracker) const override { - tracker->TrackThis(this); tracker->TrackField("paths", paths); } + SET_MEMORY_INFO_NAME(FSContinuationData) + SET_SELF_SIZE(FSContinuationData) + private: uv_fs_cb done_cb; }; @@ -136,11 +138,11 @@ class FSReqWrap : public FSReqBase { void SetReturnValue(const FunctionCallbackInfo& args) override; void MemoryInfo(MemoryTracker* tracker) const override { - tracker->TrackThis(this); tracker->TrackField("continuation_data", continuation_data); } - ADD_MEMORY_INFO_NAME(FSReqWrap) + SET_MEMORY_INFO_NAME(FSReqWrap) + SET_SELF_SIZE(FSReqWrap) private: DISALLOW_COPY_AND_ASSIGN(FSReqWrap); @@ -201,12 +203,12 @@ class FSReqPromise : public FSReqBase { } void MemoryInfo(MemoryTracker* tracker) const override { - tracker->TrackThis(this); tracker->TrackField("stats_field_array", stats_field_array_); tracker->TrackField("continuation_data", continuation_data); } - ADD_MEMORY_INFO_NAME(FSReqPromise) + SET_MEMORY_INFO_NAME(FSReqPromise) + SET_SELF_SIZE(FSReqPromise) private: bool finished_ = false; @@ -242,12 +244,9 @@ class FileHandleReadWrap : public ReqWrap { return static_cast(ReqWrap::from_req(req)); } - void MemoryInfo(MemoryTracker* tracker) const override { - tracker->TrackThis(this); - tracker->TrackField("buffer", buffer_); - } - - ADD_MEMORY_INFO_NAME(FileHandleReadWrap) + void MemoryInfo(MemoryTracker* tracker) const override; + SET_MEMORY_INFO_NAME(FileHandleReadWrap) + SET_SELF_SIZE(FileHandleReadWrap) private: FileHandle* file_handle_; @@ -296,11 +295,11 @@ class FileHandle : public AsyncWrap, public StreamBase { } void MemoryInfo(MemoryTracker* tracker) const override { - tracker->TrackThis(this); tracker->TrackField("current_read", current_read_); } - ADD_MEMORY_INFO_NAME(FileHandle) + SET_MEMORY_INFO_NAME(FileHandle) + SET_SELF_SIZE(FileHandle) private: // Synchronous close that emits a warning @@ -329,12 +328,12 @@ class FileHandle : public AsyncWrap, public StreamBase { FileHandle* file_handle(); void MemoryInfo(MemoryTracker* tracker) const override { - tracker->TrackThis(this); tracker->TrackField("promise", promise_); tracker->TrackField("ref", ref_); } - ADD_MEMORY_INFO_NAME(CloseReq) + SET_MEMORY_INFO_NAME(CloseReq) + SET_SELF_SIZE(CloseReq) void Resolve(); diff --git a/src/node_http2.cc b/src/node_http2.cc index fcc83f4acc53ef..bde0ef79e95dc2 100644 --- a/src/node_http2.cc +++ b/src/node_http2.cc @@ -2897,7 +2897,6 @@ void Http2Session::Http2Ping::Done(bool ack, const uint8_t* payload) { void nghttp2_stream_write::MemoryInfo(MemoryTracker* tracker) const { - tracker->TrackThis(this); if (req_wrap != nullptr) tracker->TrackField("req_wrap", req_wrap->GetAsyncWrap()); tracker->TrackField("buf", buf); @@ -2905,7 +2904,6 @@ void nghttp2_stream_write::MemoryInfo(MemoryTracker* tracker) const { void nghttp2_header::MemoryInfo(MemoryTracker* tracker) const { - tracker->TrackThis(this); tracker->TrackFieldWithSize("name", nghttp2_rcbuf_get_buf(name).len); tracker->TrackFieldWithSize("value", nghttp2_rcbuf_get_buf(value).len); } diff --git a/src/node_http2.h b/src/node_http2.h index 7fa230979a87cb..2ab452bf02aaa8 100644 --- a/src/node_http2.h +++ b/src/node_http2.h @@ -92,6 +92,8 @@ struct nghttp2_stream_write : public MemoryRetainer { req_wrap(req), buf(buf_) {} void MemoryInfo(MemoryTracker* tracker) const override; + SET_MEMORY_INFO_NAME(nghttp2_stream_write) + SET_SELF_SIZE(nghttp2_stream_write) }; struct nghttp2_header : public MemoryRetainer { @@ -100,6 +102,8 @@ struct nghttp2_header : public MemoryRetainer { uint8_t flags = 0; void MemoryInfo(MemoryTracker* tracker) const override; + SET_MEMORY_INFO_NAME(nghttp2_header) + SET_SELF_SIZE(nghttp2_header) }; @@ -570,12 +574,12 @@ class Http2Stream : public AsyncWrap, uv_stream_t* send_handle) override; void MemoryInfo(MemoryTracker* tracker) const override { - tracker->TrackThis(this); tracker->TrackField("current_headers", current_headers_); tracker->TrackField("queue", queue_); } - ADD_MEMORY_INFO_NAME(Http2Stream) + SET_MEMORY_INFO_NAME(Http2Stream) + SET_SELF_SIZE(Http2Stream) std::string diagnostic_name() const override; @@ -755,7 +759,6 @@ class Http2Session : public AsyncWrap, public StreamListener { ssize_t Write(const uv_buf_t* bufs, size_t nbufs); void MemoryInfo(MemoryTracker* tracker) const override { - tracker->TrackThis(this); tracker->TrackField("streams", streams_); tracker->TrackField("outstanding_pings", outstanding_pings_); tracker->TrackField("outstanding_settings", outstanding_settings_); @@ -765,7 +768,8 @@ class Http2Session : public AsyncWrap, public StreamListener { pending_rst_streams_.size() * sizeof(int32_t)); } - ADD_MEMORY_INFO_NAME(Http2Session) + SET_MEMORY_INFO_NAME(Http2Session) + SET_SELF_SIZE(Http2Session) std::string diagnostic_name() const override; @@ -1085,11 +1089,11 @@ class Http2Session::Http2Ping : public AsyncWrap { explicit Http2Ping(Http2Session* session); void MemoryInfo(MemoryTracker* tracker) const override { - tracker->TrackThis(this); tracker->TrackField("session", session_); } - ADD_MEMORY_INFO_NAME(Http2Ping) + SET_MEMORY_INFO_NAME(Http2Ping) + SET_SELF_SIZE(Http2Ping) void Send(uint8_t* payload); void Done(bool ack, const uint8_t* payload = nullptr); @@ -1110,11 +1114,11 @@ class Http2Session::Http2Settings : public AsyncWrap { explicit Http2Settings(Http2Session* session); void MemoryInfo(MemoryTracker* tracker) const override { - tracker->TrackThis(this); tracker->TrackField("session", session_); } - ADD_MEMORY_INFO_NAME(Http2Settings) + SET_MEMORY_INFO_NAME(Http2Settings) + SET_SELF_SIZE(Http2Settings) void Send(); void Done(bool ack); diff --git a/src/node_http_parser.cc b/src/node_http_parser.cc index eb7a69ce752f37..0907f0a4329e88 100644 --- a/src/node_http_parser.cc +++ b/src/node_http_parser.cc @@ -157,11 +157,11 @@ class Parser : public AsyncWrap, public StreamListener { void MemoryInfo(MemoryTracker* tracker) const override { - tracker->TrackThis(this); tracker->TrackField("current_buffer", current_buffer_); } - ADD_MEMORY_INFO_NAME(Parser) + SET_MEMORY_INFO_NAME(Parser) + SET_SELF_SIZE(Parser) int on_message_begin() { num_fields_ = num_values_ = 0; diff --git a/src/node_i18n.cc b/src/node_i18n.cc index e87f8f59550e19..5966e3ff678e34 100644 --- a/src/node_i18n.cc +++ b/src/node_i18n.cc @@ -251,11 +251,9 @@ class ConverterObject : public BaseObject, Converter { args.GetReturnValue().Set(status); } - void MemoryInfo(MemoryTracker* tracker) const override { - tracker->TrackThis(this); - } - - ADD_MEMORY_INFO_NAME(ConverterObject) + SET_NO_MEMORY_INFO() + SET_MEMORY_INFO_NAME(ConverterObject) + SET_SELF_SIZE(ConverterObject) protected: ConverterObject(Environment* env, diff --git a/src/node_messaging.cc b/src/node_messaging.cc index 0a79d6f9d3d36a..a8b95401fdf06c 100644 --- a/src/node_messaging.cc +++ b/src/node_messaging.cc @@ -326,7 +326,6 @@ Maybe Message::Serialize(Environment* env, } void Message::MemoryInfo(MemoryTracker* tracker) const { - tracker->TrackThis(this); tracker->TrackField("array_buffer_contents", array_buffer_contents_); tracker->TrackFieldWithSize("shared_array_buffers", shared_array_buffers_.size() * sizeof(shared_array_buffers_[0])); @@ -342,7 +341,6 @@ MessagePortData::~MessagePortData() { void MessagePortData::MemoryInfo(MemoryTracker* tracker) const { Mutex::ScopedLock lock(mutex_); - tracker->TrackThis(this); tracker->TrackField("incoming_messages", incoming_messages_); } diff --git a/src/node_messaging.h b/src/node_messaging.h index b7fd392ccc6fab..e4674885d2b89e 100644 --- a/src/node_messaging.h +++ b/src/node_messaging.h @@ -57,7 +57,8 @@ class Message : public MemoryRetainer { void MemoryInfo(MemoryTracker* tracker) const override; - ADD_MEMORY_INFO_NAME(Message) + SET_MEMORY_INFO_NAME(Message) + SET_SELF_SIZE(Message) private: MallocedBuffer main_message_buf_; @@ -100,7 +101,8 @@ class MessagePortData : public MemoryRetainer { void MemoryInfo(MemoryTracker* tracker) const override; - ADD_MEMORY_INFO_NAME(MessagePortData) + SET_MEMORY_INFO_NAME(MessagePortData) + SET_SELF_SIZE(MessagePortData) private: // After disentangling this message port, the owner handle (if any) @@ -187,11 +189,11 @@ class MessagePort : public HandleWrap { inline bool IsDetached() const; void MemoryInfo(MemoryTracker* tracker) const override { - tracker->TrackThis(this); tracker->TrackField("data", data_); } - ADD_MEMORY_INFO_NAME(MessagePort) + SET_MEMORY_INFO_NAME(MessagePort) + SET_SELF_SIZE(MessagePort) private: void OnClose() override; diff --git a/src/node_serdes.cc b/src/node_serdes.cc index 5de0ddd81909b0..a6f91d56c2b2bb 100644 --- a/src/node_serdes.cc +++ b/src/node_serdes.cc @@ -53,11 +53,9 @@ class SerializerContext : public BaseObject, static void WriteDouble(const FunctionCallbackInfo& args); static void WriteRawBytes(const FunctionCallbackInfo& args); - void MemoryInfo(MemoryTracker* tracker) const override { - tracker->TrackThis(this); - } - - ADD_MEMORY_INFO_NAME(SerializerContext) + SET_NO_MEMORY_INFO() + SET_MEMORY_INFO_NAME(SerializerContext) + SET_SELF_SIZE(SerializerContext) private: ValueSerializer serializer_; @@ -84,11 +82,9 @@ class DeserializerContext : public BaseObject, static void ReadDouble(const FunctionCallbackInfo& args); static void ReadRawBytes(const FunctionCallbackInfo& args); - void MemoryInfo(MemoryTracker* tracker) const override { - tracker->TrackThis(this); - } - - ADD_MEMORY_INFO_NAME(DeserializerContext) + SET_NO_MEMORY_INFO() + SET_MEMORY_INFO_NAME(DeserializerContext) + SET_SELF_SIZE(DeserializerContext) private: const uint8_t* data_; diff --git a/src/node_stat_watcher.h b/src/node_stat_watcher.h index 33c90ad3cde737..3d819b45787e98 100644 --- a/src/node_stat_watcher.h +++ b/src/node_stat_watcher.h @@ -44,11 +44,9 @@ class StatWatcher : public HandleWrap { static void New(const v8::FunctionCallbackInfo& args); static void Start(const v8::FunctionCallbackInfo& args); - void MemoryInfo(MemoryTracker* tracker) const override { - tracker->TrackThis(this); - } - - ADD_MEMORY_INFO_NAME(StatWatcher) + SET_NO_MEMORY_INFO() + SET_MEMORY_INFO_NAME(StatWatcher) + SET_SELF_SIZE(StatWatcher) private: static void Callback(uv_fs_poll_t* handle, diff --git a/src/node_trace_events.cc b/src/node_trace_events.cc index 3c18b0d45b11c9..f06648b24492fd 100644 --- a/src/node_trace_events.cc +++ b/src/node_trace_events.cc @@ -28,11 +28,11 @@ class NodeCategorySet : public BaseObject { const std::set& GetCategories() const { return categories_; } void MemoryInfo(MemoryTracker* tracker) const override { - tracker->TrackThis(this); tracker->TrackField("categories", categories_); } - ADD_MEMORY_INFO_NAME(NodeCategorySet) + SET_MEMORY_INFO_NAME(NodeCategorySet) + SET_SELF_SIZE(NodeCategorySet) private: NodeCategorySet(Environment* env, diff --git a/src/node_worker.h b/src/node_worker.h index 8491ad221b6dde..cbd4a861570b27 100644 --- a/src/node_worker.h +++ b/src/node_worker.h @@ -26,15 +26,15 @@ class Worker : public AsyncWrap { void JoinThread(); void MemoryInfo(MemoryTracker* tracker) const override { - tracker->TrackThis(this); - tracker->TrackFieldWithSize("isolate_data", sizeof(IsolateData)); - tracker->TrackFieldWithSize("env", sizeof(Environment)); - tracker->TrackFieldWithSize("thread_exit_async", sizeof(uv_async_t)); + tracker->TrackFieldWithSize( + "isolate_data", sizeof(IsolateData), "IsolateData"); + tracker->TrackFieldWithSize("env", sizeof(Environment), "Environment"); + tracker->TrackField("thread_exit_async", *thread_exit_async_); tracker->TrackField("parent_port", parent_port_); } - - ADD_MEMORY_INFO_NAME(Worker) + SET_MEMORY_INFO_NAME(Worker) + SET_SELF_SIZE(Worker) bool is_stopped() const; diff --git a/src/node_zlib.cc b/src/node_zlib.cc index 5def50d3b9dceb..6e99f68108730f 100644 --- a/src/node_zlib.cc +++ b/src/node_zlib.cc @@ -661,14 +661,13 @@ class ZCtx : public AsyncWrap, public ThreadPoolWork { } void MemoryInfo(MemoryTracker* tracker) const override { - tracker->TrackThis(this); tracker->TrackField("dictionary", dictionary_); - tracker->TrackFieldWithSize("zlib memory", - zlib_memory_ + unreported_allocations_); + tracker->TrackFieldWithSize("zlib_memory", + zlib_memory_ + unreported_allocations_); } - - ADD_MEMORY_INFO_NAME(ZCtx) + SET_MEMORY_INFO_NAME(ZCtx) + SET_SELF_SIZE(ZCtx) private: void Ref() { diff --git a/src/pipe_wrap.h b/src/pipe_wrap.h index 7faf5145abdcfe..05a5ba6e113a18 100644 --- a/src/pipe_wrap.h +++ b/src/pipe_wrap.h @@ -45,11 +45,9 @@ class PipeWrap : public ConnectionWrap { v8::Local unused, v8::Local context); - void MemoryInfo(MemoryTracker* tracker) const override { - tracker->TrackThis(this); - } - - ADD_MEMORY_INFO_NAME(PipeWrap) + SET_NO_MEMORY_INFO() + SET_MEMORY_INFO_NAME(PipeWrap) + SET_SELF_SIZE(PipeWrap) private: PipeWrap(Environment* env, diff --git a/src/process_wrap.cc b/src/process_wrap.cc index 03628c856b68b4..cd443b34aa972c 100644 --- a/src/process_wrap.cc +++ b/src/process_wrap.cc @@ -66,11 +66,9 @@ class ProcessWrap : public HandleWrap { constructor->GetFunction(context).ToLocalChecked()); } - void MemoryInfo(MemoryTracker* tracker) const override { - tracker->TrackThis(this); - } - - ADD_MEMORY_INFO_NAME(ProcessWrap) + SET_NO_MEMORY_INFO() + SET_MEMORY_INFO_NAME(ProcessWrap) + SET_SELF_SIZE(ProcessWrap) private: static void New(const FunctionCallbackInfo& args) { diff --git a/src/sharedarraybuffer_metadata.cc b/src/sharedarraybuffer_metadata.cc index 3d5b96051ead6e..b20d9f46a44d5e 100644 --- a/src/sharedarraybuffer_metadata.cc +++ b/src/sharedarraybuffer_metadata.cc @@ -47,11 +47,9 @@ class SABLifetimePartner : public BaseObject { MakeWeak(); } - void MemoryInfo(MemoryTracker* tracker) const override { - tracker->TrackThis(this); - } - - ADD_MEMORY_INFO_NAME(SABLifetimePartner) + SET_NO_MEMORY_INFO() + SET_MEMORY_INFO_NAME(SABLifetimePartner) + SET_SELF_SIZE(SABLifetimePartner) SharedArrayBufferMetadataReference reference; }; diff --git a/src/signal_wrap.cc b/src/signal_wrap.cc index d7f21db8b3e8dd..b390066140a739 100644 --- a/src/signal_wrap.cc +++ b/src/signal_wrap.cc @@ -59,11 +59,9 @@ class SignalWrap : public HandleWrap { constructor->GetFunction(env->context()).ToLocalChecked()); } - void MemoryInfo(MemoryTracker* tracker) const override { - tracker->TrackThis(this); - } - - ADD_MEMORY_INFO_NAME(SignalWrap) + SET_NO_MEMORY_INFO() + SET_MEMORY_INFO_NAME(SignalWrap) + SET_SELF_SIZE(SignalWrap) private: static void New(const FunctionCallbackInfo& args) { diff --git a/src/stream_base.h b/src/stream_base.h index 05c2a9623625d6..d8e6df960f4f54 100644 --- a/src/stream_base.h +++ b/src/stream_base.h @@ -347,11 +347,9 @@ class SimpleShutdownWrap : public ShutdownWrap, public OtherBase { AsyncWrap* GetAsyncWrap() override { return this; } - void MemoryInfo(MemoryTracker* tracker) const override { - tracker->TrackThis(this); - } - - ADD_MEMORY_INFO_NAME(SimpleShutdownWrap) + SET_NO_MEMORY_INFO() + SET_MEMORY_INFO_NAME(SimpleShutdownWrap) + SET_SELF_SIZE(SimpleShutdownWrap) }; template @@ -362,13 +360,9 @@ class SimpleWriteWrap : public WriteWrap, public OtherBase { AsyncWrap* GetAsyncWrap() override { return this; } - void MemoryInfo(MemoryTracker* tracker) const override { - tracker->TrackThis(this); - tracker->TrackFieldWithSize("storage", StorageSize()); - } - - - ADD_MEMORY_INFO_NAME(SimpleWriteWrap) + SET_NO_MEMORY_INFO() + SET_MEMORY_INFO_NAME(SimpleWriteWrap) + SET_SELF_SIZE(SimpleWriteWrap) }; } // namespace node diff --git a/src/stream_pipe.h b/src/stream_pipe.h index c76afac41689a6..51a33b7ef69776 100644 --- a/src/stream_pipe.h +++ b/src/stream_pipe.h @@ -18,11 +18,9 @@ class StreamPipe : public AsyncWrap { static void Start(const v8::FunctionCallbackInfo& args); static void Unpipe(const v8::FunctionCallbackInfo& args); - void MemoryInfo(MemoryTracker* tracker) const override { - tracker->TrackThis(this); - } - - ADD_MEMORY_INFO_NAME(StreamPipe) + SET_NO_MEMORY_INFO() + SET_MEMORY_INFO_NAME(StreamPipe) + SET_SELF_SIZE(StreamPipe) private: inline StreamBase* source(); diff --git a/src/tcp_wrap.h b/src/tcp_wrap.h index 829c1b22bf3aec..90c81bcae6fd6f 100644 --- a/src/tcp_wrap.h +++ b/src/tcp_wrap.h @@ -44,10 +44,8 @@ class TCPWrap : public ConnectionWrap { v8::Local unused, v8::Local context); - void MemoryInfo(MemoryTracker* tracker) const override { - tracker->TrackThis(this); - } - + SET_NO_MEMORY_INFO() + SET_SELF_SIZE(TCPWrap) std::string MemoryInfoName() const override { switch (provider_type()) { case ProviderType::PROVIDER_TCPWRAP: diff --git a/src/timer_wrap.cc b/src/timer_wrap.cc index 436e3def174328..4e5a9b4e83ea86 100644 --- a/src/timer_wrap.cc +++ b/src/timer_wrap.cc @@ -66,9 +66,9 @@ class TimerWrap : public HandleWrap { ->GetFunction(env->context()).ToLocalChecked()).FromJust(); } - void MemoryInfo(MemoryTracker* tracker) const override { - tracker->TrackThis(this); - } + SET_NO_MEMORY_INFO() + SET_MEMORY_INFO_NAME(TimerWrap) + SET_SELF_SIZE(TimerWrap) private: static void SetupTimers(const FunctionCallbackInfo& args) { diff --git a/src/tls_wrap.cc b/src/tls_wrap.cc index b392ab6016a5ca..6577ffd3ec2080 100644 --- a/src/tls_wrap.cc +++ b/src/tls_wrap.cc @@ -861,7 +861,6 @@ void TLSWrap::GetWriteQueueSize(const FunctionCallbackInfo& info) { void TLSWrap::MemoryInfo(MemoryTracker* tracker) const { - tracker->TrackThis(this); tracker->TrackField("error", error_); tracker->TrackField("pending_cleartext_input", pending_cleartext_input_); if (enc_in_ != nullptr) diff --git a/src/tls_wrap.h b/src/tls_wrap.h index aea8568b11b51c..0e265764822f29 100644 --- a/src/tls_wrap.h +++ b/src/tls_wrap.h @@ -78,7 +78,8 @@ class TLSWrap : public AsyncWrap, void MemoryInfo(MemoryTracker* tracker) const override; - ADD_MEMORY_INFO_NAME(TLSWrap) + SET_MEMORY_INFO_NAME(TLSWrap) + SET_SELF_SIZE(TLSWrap) protected: inline StreamBase* underlying_stream() { diff --git a/src/tty_wrap.h b/src/tty_wrap.h index 45357cfa4637cf..ad5f364134e3e2 100644 --- a/src/tty_wrap.h +++ b/src/tty_wrap.h @@ -38,11 +38,9 @@ class TTYWrap : public LibuvStreamWrap { uv_tty_t* UVHandle(); - void MemoryInfo(MemoryTracker* tracker) const override { - tracker->TrackThis(this); - } - - ADD_MEMORY_INFO_NAME(TTYWrap) + SET_NO_MEMORY_INFO() + SET_MEMORY_INFO_NAME(TTYWrap) + SET_SELF_SIZE(TTYWrap) private: TTYWrap(Environment* env, diff --git a/src/udp_wrap.cc b/src/udp_wrap.cc index d1802bc4809726..28fa51f1c639d2 100644 --- a/src/udp_wrap.cc +++ b/src/udp_wrap.cc @@ -56,11 +56,9 @@ class SendWrap : public ReqWrap { inline bool have_callback() const; size_t msg_size; - void MemoryInfo(MemoryTracker* tracker) const override { - tracker->TrackThis(this); - } - - ADD_MEMORY_INFO_NAME(SendWrap) + SET_NO_MEMORY_INFO() + SET_MEMORY_INFO_NAME(SendWrap) + SET_SELF_SIZE(SendWrap) private: const bool have_callback_; diff --git a/src/udp_wrap.h b/src/udp_wrap.h index ca048f5aef98af..c1261fdb3945e4 100644 --- a/src/udp_wrap.h +++ b/src/udp_wrap.h @@ -64,11 +64,9 @@ class UDPWrap: public HandleWrap { SocketType type); uv_udp_t* UVHandle(); - void MemoryInfo(MemoryTracker* tracker) const override { - tracker->TrackThis(this); - } - - ADD_MEMORY_INFO_NAME(UDPWrap) + SET_NO_MEMORY_INFO() + SET_MEMORY_INFO_NAME(UDPWrap) + SET_SELF_SIZE(UDPWrap) private: typedef uv_udp_t HandleType; diff --git a/test/cctest/test_node_postmortem_metadata.cc b/test/cctest/test_node_postmortem_metadata.cc index 743b4ccc88ae0e..bb56fbc2047f43 100644 --- a/test/cctest/test_node_postmortem_metadata.cc +++ b/test/cctest/test_node_postmortem_metadata.cc @@ -34,9 +34,9 @@ class DebugSymbolsTest : public EnvironmentTestFixture {}; class TestHandleWrap : public node::HandleWrap { public: - void MemoryInfo(node::MemoryTracker* tracker) const override { - tracker->TrackThis(this); - } + SET_NO_MEMORY_INFO() + SET_MEMORY_INFO_NAME(TestHandleWrap) + SET_SELF_SIZE(TestHandleWrap) TestHandleWrap(node::Environment* env, v8::Local object, @@ -50,9 +50,9 @@ class TestHandleWrap : public node::HandleWrap { class TestReqWrap : public node::ReqWrap { public: - void MemoryInfo(node::MemoryTracker* tracker) const override { - tracker->TrackThis(this); - } + SET_NO_MEMORY_INFO() + SET_MEMORY_INFO_NAME(TestReqWrap) + SET_SELF_SIZE(TestReqWrap) TestReqWrap(node::Environment* env, v8::Local object) : node::ReqWrap(env, @@ -76,9 +76,9 @@ class DummyBaseObject : public node::BaseObject { DummyBaseObject(node::Environment* env, v8::Local obj) : BaseObject(env, obj) {} - void MemoryInfo(node::MemoryTracker* tracker) const override { - tracker->TrackThis(this); - } + SET_NO_MEMORY_INFO() + SET_MEMORY_INFO_NAME(DummyBaseObject) + SET_SELF_SIZE(DummyBaseObject) }; TEST_F(DebugSymbolsTest, BaseObjectPersistentHandle) { diff --git a/test/common/heap.js b/test/common/heap.js index 382d1d3642c959..2fe99bf67eb643 100644 --- a/test/common/heap.js +++ b/test/common/heap.js @@ -12,75 +12,110 @@ try { } const { createJSHeapDump, buildEmbedderGraph } = internalTestHeap; +function inspectNode(snapshot) { + return util.inspect(snapshot, { depth: 4 }); +} + +function isEdge(edge, { node_name, edge_name }) { + // For ABI compatibility, we did not backport the virtual function + // AddEdge() with a name as last argument back to v10.x, so edge_name. + // is ignored. + // if (edge.name !== edge_name) { + // return false; + // } + // From our internal embedded graph + if (edge.to.value) { + if (edge.to.value.constructor.name !== node_name) { + return false; + } + } else if (edge.to.name !== node_name) { + return false; + } + return true; +} + class State { constructor() { this.snapshot = createJSHeapDump(); this.embedderGraph = buildEmbedderGraph(); } - validateSnapshotNodes(name, expected, { loose = false } = {}) { - const snapshot = this.snapshot.filter( - (node) => node.name === 'Node / ' + name && node.type !== 'string'); - if (loose) - assert(snapshot.length >= expected.length); - else - assert.strictEqual(snapshot.length, expected.length); - for (const expectedNode of expected) { - if (expectedNode.children) { - for (const expectedChild of expectedNode.children) { - const check = typeof expectedChild === 'function' ? - expectedChild : - (node) => [expectedChild.name, 'Node / ' + expectedChild.name] - .includes(node.name); + // Validate the v8 heap snapshot + validateSnapshot(rootName, expected, { loose = false } = {}) { + const rootNodes = this.snapshot.filter( + (node) => node.name === rootName && node.type !== 'string'); + if (loose) { + assert(rootNodes.length >= expected.length, + `Expect to find at least ${expected.length} '${rootName}', ` + + `found ${rootNodes.length}`); + } else { + assert.strictEqual( + rootNodes.length, expected.length, + `Expect to find ${expected.length} '${rootName}', ` + + `found ${rootNodes.length}`); + } - const hasChild = snapshot.some((node) => { - return node.outgoingEdges.map((edge) => edge.toNode).some(check); - }); + for (const expectation of expected) { + if (expectation.children) { + for (const expectedEdge of expectation.children) { + const check = typeof expectedEdge === 'function' ? expectedEdge : + (edge) => (isEdge(edge, expectedEdge)); + const hasChild = rootNodes.some( + (node) => node.outgoingEdges.some(check) + ); // Don't use assert with a custom message here. Otherwise the // inspection in the message is done eagerly and wastes a lot of CPU // time. if (!hasChild) { throw new Error( 'expected to find child ' + - `${util.inspect(expectedChild)} in ${util.inspect(snapshot)}`); + `${util.inspect(expectedEdge)} in ${inspectNode(rootNodes)}`); } } } } + } - const graph = this.embedderGraph.filter((node) => node.name === name); - if (loose) - assert(graph.length >= expected.length); - else - assert.strictEqual(graph.length, expected.length); - for (const expectedNode of expected) { - if (expectedNode.children) { - for (const expectedChild of expectedNode.children) { - const check = (edge) => { - // TODO(joyeecheung): check the edge names - const node = edge.to; - if (typeof expectedChild === 'function') { - return expectedChild(node); - } - return node.name === expectedChild.name || - (node.value && - node.value.constructor && - node.value.constructor.name === expectedChild.name); - }; - + // Validate our internal embedded graph representation + validateGraph(rootName, expected, { loose = false } = {}) { + const rootNodes = this.embedderGraph.filter( + (node) => node.name === rootName + ); + if (loose) { + assert(rootNodes.length >= expected.length, + `Expect to find at least ${expected.length} '${rootName}', ` + + `found ${rootNodes.length}`); + } else { + assert.strictEqual( + rootNodes.length, expected.length, + `Expect to find ${expected.length} '${rootName}', ` + + `found ${rootNodes.length}`); + } + for (const expectation of expected) { + if (expectation.children) { + for (const expectedEdge of expectation.children) { + const check = typeof expectedEdge === 'function' ? expectedEdge : + (edge) => (isEdge(edge, expectedEdge)); // Don't use assert with a custom message here. Otherwise the // inspection in the message is done eagerly and wastes a lot of CPU // time. - const hasChild = graph.some((node) => node.edges.some(check)); + const hasChild = rootNodes.some( + (node) => node.edges.some(check) + ); if (!hasChild) { throw new Error( 'expected to find child ' + - `${util.inspect(expectedChild)} in ${util.inspect(snapshot)}`); + `${util.inspect(expectedEdge)} in ${inspectNode(rootNodes)}`); } } } } } + + validateSnapshotNodes(rootName, expected, { loose = false } = {}) { + this.validateSnapshot(rootName, expected, { loose }); + this.validateGraph(rootName, expected, { loose }); + } } function recordState() { diff --git a/test/parallel/test-heapdump-dns.js b/test/parallel/test-heapdump-dns.js index 00ca54917f5df5..6fe79f7dd4ec5a 100644 --- a/test/parallel/test-heapdump-dns.js +++ b/test/parallel/test-heapdump-dns.js @@ -3,16 +3,16 @@ require('../common'); const { validateSnapshotNodes } = require('../common/heap'); -validateSnapshotNodes('ChannelWrap', []); +validateSnapshotNodes('Node / ChannelWrap', []); const dns = require('dns'); -validateSnapshotNodes('ChannelWrap', [{}]); +validateSnapshotNodes('Node / ChannelWrap', [{}]); dns.resolve('localhost', () => {}); -validateSnapshotNodes('ChannelWrap', [ +validateSnapshotNodes('Node / ChannelWrap', [ { children: [ - { name: 'node_ares_task_list' }, + { node_name: 'Node / node_ares_task_list', edge_name: 'task_list' }, // `Node / ChannelWrap` (C++) -> `ChannelWrap` (JS) - { name: 'ChannelWrap' } + { node_name: 'ChannelWrap', edge_name: 'wrapped' } ] } ]); diff --git a/test/parallel/test-heapdump-fs-promise.js b/test/parallel/test-heapdump-fs-promise.js index 855b135f6ae31b..ca170e13a61770 100644 --- a/test/parallel/test-heapdump-fs-promise.js +++ b/test/parallel/test-heapdump-fs-promise.js @@ -4,13 +4,13 @@ require('../common'); const { validateSnapshotNodes } = require('../common/heap'); const fs = require('fs').promises; -validateSnapshotNodes('FSReqPromise', []); +validateSnapshotNodes('Node / FSReqPromise', []); fs.stat(__filename); -validateSnapshotNodes('FSReqPromise', [ +validateSnapshotNodes('Node / FSReqPromise', [ { children: [ - { name: 'FSReqPromise' }, - { name: 'Float64Array' } // Stat array + { node_name: 'FSReqPromise', edge_name: 'wrapped' }, + { node_name: 'Float64Array', edge_name: 'stats_field_array' } ] } ]); diff --git a/test/parallel/test-heapdump-http2.js b/test/parallel/test-heapdump-http2.js index b503951e65851b..caece96d01cc72 100644 --- a/test/parallel/test-heapdump-http2.js +++ b/test/parallel/test-heapdump-http2.js @@ -8,8 +8,8 @@ const http2 = require('http2'); { const state = recordState(); - state.validateSnapshotNodes('Http2Session', []); - state.validateSnapshotNodes('Http2Stream', []); + state.validateSnapshotNodes('Node / Http2Session', []); + state.validateSnapshotNodes('Node / Http2Stream', []); } const server = http2.createServer(); @@ -24,50 +24,56 @@ server.listen(0, () => { const state = recordState(); // `Node / Http2Stream` (C++) -> Http2Stream (JS) - state.validateSnapshotNodes('Http2Stream', [ + state.validateSnapshotNodes('Node / Http2Stream', [ { children: [ - { name: 'Http2Stream' } + // current_headers and/or queue could be empty + { node_name: 'Http2Stream', edge_name: 'wrapped' } ] }, ], { loose: true }); // `Node / FileHandle` (C++) -> FileHandle (JS) - state.validateSnapshotNodes('FileHandle', [ + state.validateSnapshotNodes('Node / FileHandle', [ { children: [ - { name: 'FileHandle' } + { node_name: 'FileHandle', edge_name: 'wrapped' } + // current_headers could be empty ] } - ]); - state.validateSnapshotNodes('TCPSocketWrap', [ + ], { loose: true }); + state.validateSnapshotNodes('Node / TCPSocketWrap', [ { children: [ - { name: 'TCP' } + { node_name: 'TCP', edge_name: 'wrapped' } ] } ], { loose: true }); - state.validateSnapshotNodes('TCPServerWrap', [ + state.validateSnapshotNodes('Node / TCPServerWrap', [ { children: [ - { name: 'TCP' } + { node_name: 'TCP', edge_name: 'wrapped' } ] } ], { loose: true }); // `Node / StreamPipe` (C++) -> StreamPipe (JS) - state.validateSnapshotNodes('StreamPipe', [ + state.validateSnapshotNodes('Node / StreamPipe', [ { children: [ - { name: 'StreamPipe' } + { node_name: 'StreamPipe', edge_name: 'wrapped' } ] } ]); // `Node / Http2Session` (C++) -> Http2Session (JS) - state.validateSnapshotNodes('Http2Session', [ + state.validateSnapshotNodes('Node / Http2Session', [ { children: [ - { name: 'Http2Session' }, - { name: 'streams' } + { node_name: 'Http2Session', edge_name: 'wrapped' }, + { + node_name: 'Node / streams', edge_name: 'streams' + } + // outstanding_pings, outgoing_buffers, outgoing_storage, + // pending_rst_streams could be empty ] } ], { loose: true }); diff --git a/test/parallel/test-heapdump-inspector.js b/test/parallel/test-heapdump-inspector.js index 08fc6703d87c5c..4ac78edf6b928d 100644 --- a/test/parallel/test-heapdump-inspector.js +++ b/test/parallel/test-heapdump-inspector.js @@ -8,14 +8,15 @@ const { validateSnapshotNodes } = require('../common/heap'); const inspector = require('inspector'); const session = new inspector.Session(); -validateSnapshotNodes('JSBindingsConnection', []); +validateSnapshotNodes('Node / JSBindingsConnection', []); session.connect(); -validateSnapshotNodes('JSBindingsConnection', [ +validateSnapshotNodes('Node / JSBindingsConnection', [ { children: [ - { name: 'session' }, - { name: 'Connection' }, - (node) => node.type === 'closure' || typeof node.value === 'function' + { node_name: 'Node / InspectorSession', edge_name: 'session' }, + { node_name: 'Connection', edge_name: 'wrapped' }, + (edge) => (edge.to.type === undefined || // embedded graph + edge.to.type === 'closure') // snapshot ] } ]); diff --git a/test/parallel/test-heapdump-tls.js b/test/parallel/test-heapdump-tls.js index 90b2d8dc952f56..fee19bf67625b2 100644 --- a/test/parallel/test-heapdump-tls.js +++ b/test/parallel/test-heapdump-tls.js @@ -9,7 +9,7 @@ const { validateSnapshotNodes } = require('../common/heap'); const net = require('net'); const tls = require('tls'); -validateSnapshotNodes('TLSWrap', []); +validateSnapshotNodes('Node / TLSWrap', []); const server = net.createServer(common.mustCall((c) => { c.end(); @@ -21,13 +21,14 @@ const server = net.createServer(common.mustCall((c) => { })); c.write('hello'); - validateSnapshotNodes('TLSWrap', [ + validateSnapshotNodes('Node / TLSWrap', [ { children: [ - { name: 'NodeBIO' }, - { name: 'NodeBIO' }, + { node_name: 'Node / NodeBIO', edge_name: 'enc_out' }, + { node_name: 'Node / NodeBIO', edge_name: 'enc_in' }, // `Node / TLSWrap` (C++) -> `TLSWrap` (JS) - { name: 'TLSWrap' } + { node_name: 'TLSWrap', edge_name: 'wrapped' } + // pending_cleartext_input could be empty ] } ]); diff --git a/test/parallel/test-heapdump-worker.js b/test/parallel/test-heapdump-worker.js index 44a50dd66b5fca..02b989c6b324dd 100644 --- a/test/parallel/test-heapdump-worker.js +++ b/test/parallel/test-heapdump-worker.js @@ -4,22 +4,22 @@ require('../common'); const { validateSnapshotNodes } = require('../common/heap'); const { Worker } = require('worker_threads'); -validateSnapshotNodes('Worker', []); +validateSnapshotNodes('Node / Worker', []); const worker = new Worker('setInterval(() => {}, 100);', { eval: true }); -validateSnapshotNodes('Worker', [ +validateSnapshotNodes('Node / Worker', [ { children: [ - { name: 'thread_exit_async' }, - { name: 'env' }, - { name: 'MessagePort' }, - { name: 'Worker' } + { node_name: 'Node / uv_async_t', edge_name: 'thread_exit_async' }, + { node_name: 'Node / Environment', edge_name: 'env' }, + { node_name: 'Node / MessagePort', edge_name: 'parent_port' }, + { node_name: 'Worker', edge_name: 'wrapped' } ] } ]); -validateSnapshotNodes('MessagePort', [ +validateSnapshotNodes('Node / MessagePort', [ { children: [ - { name: 'MessagePortData' } + { node_name: 'Node / MessagePortData', edge_name: 'data' } ] } ], { loose: true }); diff --git a/test/parallel/test-heapdump-zlib.js b/test/parallel/test-heapdump-zlib.js index 936e3a1a500b2d..f79e345821ea50 100644 --- a/test/parallel/test-heapdump-zlib.js +++ b/test/parallel/test-heapdump-zlib.js @@ -4,14 +4,14 @@ require('../common'); const { validateSnapshotNodes } = require('../common/heap'); const zlib = require('zlib'); -validateSnapshotNodes('ZCtx', []); +validateSnapshotNodes('Node / ZCtx', []); // eslint-disable-next-line no-unused-vars const gunzip = zlib.createGunzip(); -validateSnapshotNodes('ZCtx', [ +validateSnapshotNodes('Node / ZCtx', [ { children: [ - { name: 'Zlib' }, - { name: 'zlib memory' } + { node_name: 'Zlib', edge_name: 'wrapped' }, + { node_name: 'Node / zlib_memory', edge_name: 'zlib_memory' } ] } ]);