From b082adc701f055ed2f81763f25229e44d9833665 Mon Sep 17 00:00:00 2001 From: Joyee Cheung Date: Sun, 23 Sep 2018 17:52:09 -0400 Subject: [PATCH] src: name EmbededderGraph edges and use class names for nodes This patch: - Refactors the `MemoryRetainer` API so that the impementer no longer calls `TrackThis()` that sets the size of node on the top of the stack, which may be hard to understand. Instead now they implements `SelfSize()` to provide their self sizes. Also documents the API in the header. - Refactors `MemoryTracker` so it calls `MemoryInfoName()` and `SelfSize()` of `MemoryRetainer` to retrieve info about them, and separate `node_names` and `edge_names` so the edges can be properly named with reference names and the nodes can be named with class names. (Previously the nodes are named with reference names while the edges are all indexed and appear as array elements). - Adds `SET_MEMORY_INFO_NAME()`, `SET_SELF_SIZE()` and `SET_NO_MEMORY_INFO()` convenience macros - Fixes a few `MemoryInfo` calls in some `MemoryRetainers` to track their references properly. - Refactors the heapdump tests to check both node names and edge names, distinguishing between wrapped JS nodes (without prefixes) and embedder wrappers (prefixed with `Node / `). PR-URL: https://github.com/nodejs/node/pull/23072 Reviewed-By: Anna Henningsen --- lib/internal/test/heap.js | 4 +- src/async_wrap.cc | 12 +- src/base_object.h | 5 - src/cares_wrap.cc | 133 ++++------ src/connect_wrap.h | 8 +- src/fs_event_wrap.cc | 8 +- src/heap_utils.cc | 48 ++-- src/inspector_js_api.cc | 7 +- src/js_stream.h | 8 +- src/memory_tracker-inl.h | 251 +++++++++++++------ src/memory_tracker.h | 196 ++++++++++++--- src/module_wrap.h | 4 +- src/node_contextify.cc | 10 +- src/node_crypto.h | 63 +++-- src/node_crypto_bio.h | 6 +- src/node_file.cc | 4 + src/node_file.h | 29 ++- src/node_http2.cc | 2 - src/node_http2.h | 20 +- src/node_http_parser.cc | 4 +- src/node_i18n.cc | 8 +- src/node_messaging.cc | 2 - src/node_messaging.h | 10 +- src/node_serdes.cc | 16 +- src/node_stat_watcher.h | 8 +- src/node_trace_events.cc | 4 +- src/node_worker.h | 12 +- src/node_zlib.cc | 9 +- src/pipe_wrap.h | 8 +- src/process_wrap.cc | 8 +- src/sharedarraybuffer_metadata.cc | 8 +- src/signal_wrap.cc | 8 +- src/stream_base.h | 18 +- src/stream_pipe.h | 8 +- src/tcp_wrap.h | 6 +- src/tls_wrap.cc | 1 - src/tls_wrap.h | 3 +- src/tty_wrap.h | 8 +- src/txt | 0 src/udp_wrap.cc | 8 +- src/udp_wrap.h | 8 +- test/cctest/test_node_postmortem_metadata.cc | 18 +- test/common/heap.js | 112 ++++++--- test/parallel/test-heapdump-dns.js | 10 +- test/parallel/test-heapdump-fs-promise.js | 8 +- test/parallel/test-heapdump-http2.js | 38 +-- test/parallel/test-heapdump-inspector.js | 12 +- test/parallel/test-heapdump-tls.js | 11 +- test/parallel/test-heapdump-worker.js | 16 +- test/parallel/test-heapdump-zlib.js | 8 +- 50 files changed, 713 insertions(+), 503 deletions(-) create mode 100644 src/txt diff --git a/lib/internal/test/heap.js b/lib/internal/test/heap.js index fae83266455250..61fe5916fd7584 100644 --- a/lib/internal/test/heap.js +++ b/lib/internal/test/heap.js @@ -36,8 +36,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 eb67433c39f6e5..2b163a5fa283d1 100644 --- a/src/async_wrap.cc +++ b/src/async_wrap.cc @@ -73,9 +73,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) }; @@ -181,9 +181,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 3187b7d4760b1d..3fb00859d300c7 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 aaf03dcb2b5588..739794665b2cfe 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 668dff3451686f..72ad33c99a1e80 100644 --- a/src/heap_utils.cc +++ b/src/heap_utils.cc @@ -103,15 +103,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()) @@ -145,20 +158,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..65bcbccdc02aa4 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,90 @@ 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); + 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 +144,98 @@ 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) { +void MemoryTracker::TrackField(const char* edge_name, + const v8::Local& value, + const char* node_name) { if (!value.IsEmpty()) - graph_->AddEdge(CurrentNode(), graph_->V8Node(value)); + 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) { + 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 +246,48 @@ 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) 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::PushNode( - const char* name, const MemoryRetainer* retainer) { - MemoryRetainerNode* n = AddNode(name, retainer); +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) graph_->AddEdge(CurrentNode(), n, edge_name); + + return n; +} + +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 bbbbd272921f98..4239f07f061cc9 100644 --- a/src/node_contextify.cc +++ b/src/node_contextify.cc @@ -590,13 +590,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 f4f5b263a6567b..5b102269258e0b 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_FSREQCALLBACK), diff --git a/src/node_file.h b/src/node_file.h index af62be0feca0d4..31242e1a1b1055 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 FSReqCallback : 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(FSReqCallback) + SET_MEMORY_INFO_NAME(FSReqCallback) + SET_SELF_SIZE(FSReqCallback) private: DISALLOW_COPY_AND_ASSIGN(FSReqCallback); @@ -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 b95a81e385c239..44353e2d57414c 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 dcaf3d211aee4e..5d093b27c39d3c 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 490d2c325ace97..bb7890a7300e16 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 f60c5cd9b5075e..6530bdb04c44e8 100644 --- a/src/node_trace_events.cc +++ b/src/node_trace_events.cc @@ -27,11 +27,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 af6cbfb4e5991b..edf79177d32502 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 e1792a0267af74..afdf140d8b07c3 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/tls_wrap.cc b/src/tls_wrap.cc index 3462dac8ae0d64..3dbeabd891ad20 100644 --- a/src/tls_wrap.cc +++ b/src/tls_wrap.cc @@ -858,7 +858,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/txt b/src/txt new file mode 100644 index 00000000000000..e69de29bb2d1d6 diff --git a/src/udp_wrap.cc b/src/udp_wrap.cc index 77139c2f935bf3..92fc036778f998 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 b5d282489685ed..030abdf74d958d 100644 --- a/src/udp_wrap.h +++ b/src/udp_wrap.h @@ -65,11 +65,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 f0a93f3185464c..79b766939b9d0d 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..e23670b64c8a2d 100644 --- a/test/common/heap.js +++ b/test/common/heap.js @@ -12,75 +12,107 @@ try { } const { createJSHeapDump, buildEmbedderGraph } = internalTestHeap; +function inspectNode(snapshot) { + return util.inspect(snapshot, { depth: 4 }); +} + +function isEdge(edge, { node_name, edge_name }) { + 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..3d031d87eb1a99 100644 --- a/test/parallel/test-heapdump-inspector.js +++ b/test/parallel/test-heapdump-inspector.js @@ -8,14 +8,16 @@ 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.name === 'callback' && + (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' } ] } ]);