diff options
author | Joyee Cheung <joyeec9h3@gmail.com> | 2018-09-23 17:52:09 -0400 |
---|---|---|
committer | Joyee Cheung <joyeec9h3@gmail.com> | 2018-10-04 15:32:30 +0200 |
commit | 92fa0fcdb76e2b6cb0040eede97fe3c167c31897 (patch) | |
tree | 5016ac6cbf7e5873421788387a5b2e011c31ac56 | |
parent | c0c58d5660aeea93c492877894f66dd55771be2e (diff) | |
download | node-new-92fa0fcdb76e2b6cb0040eede97fe3c167c31897.tar.gz |
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 <anna@addaleax.net>
50 files changed, 713 insertions, 503 deletions
diff --git a/lib/internal/test/heap.js b/lib/internal/test/heap.js index fae8326645..61fe5916fd 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 eb67433c39..2b163a5fa2 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> 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 64a2371433..e0f3f27950 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 3187b7d476..3fb00859d3 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> object) : AsyncWrap(env, object, PROVIDER_DNSCHANNEL), @@ -225,11 +221,9 @@ class GetAddrInfoReqWrap : public ReqWrap<uv_getaddrinfo_t> { Local<Object> 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<uv_getnameinfo_t> { public: GetNameInfoReqWrap(Environment* env, Local<Object> 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 2370157eaa..88221b7746 100644 --- a/src/connect_wrap.h +++ b/src/connect_wrap.h @@ -16,11 +16,9 @@ class ConnectWrap : public ReqWrap<uv_connect_t> { v8::Local<v8::Object> 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 aaf03dcb2b..739794665b 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<Value>& args); static void GetInitialized(const FunctionCallbackInfo<Value>& 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 668dff3451..72ad33c99a 100644 --- a/src/heap_utils.cc +++ b/src/heap_utils.cc @@ -103,15 +103,28 @@ class JSGraph : public EmbedderGraph { for (const std::unique_ptr<Node>& n : nodes_) { Local<Object> obj = info_objects[n.get()]; Local<Value> 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<Array>(); } 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<Object> to_object = info_objects[edge.second]; - Local<Object> edge_info = Object::New(isolate_); + Local<Object> edge_obj = Object::New(isolate_); Local<Value> 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<Array>(); + if (edge_name != nullptr) { + if (!String::NewFromUtf8( + isolate_, edge_name, v8::NewStringType::kNormal) + .ToLocal(&edge_name_value)) { + return MaybeLocal<Array>(); + } } 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<Array>()->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<Array>()->Set(context, i++, edge_obj).IsNothing()) { return MaybeLocal<Array>(); } } diff --git a/src/inspector_js_api.cc b/src/inspector_js_api.cc index 49e1dcc6e8..8ec6603e5b 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<InspectorSession> session_; diff --git a/src/js_stream.h b/src/js_stream.h index 05fb688f2f..6612e558ae 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<v8::Object> obj); diff --git a/src/memory_tracker-inl.h b/src/memory_tracker-inl.h index 568a4364f9..65bcbccdc0 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<v8::Object> 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<v8::Object> 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 <typename T> -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 <typename T> -void MemoryTracker::TrackField(const char* name, - const std::unique_ptr<T>& value) { - TrackField(name, value.get()); +void MemoryTracker::TrackField(const char* edge_name, + const std::unique_ptr<T>& value, + const char* node_name) { + if (value.get() == nullptr) { + return; + } + TrackField(edge_name, value.get(), node_name); } template <typename T, typename Iterator> -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 <typename T> -void MemoryTracker::TrackField(const char* name, const std::queue<T>& value) { +void MemoryTracker::TrackField(const char* edge_name, + const std::queue<T>& value, + const char* node_name, + const char* element_name) { struct ContainerGetter : public std::queue<T> { static const typename std::queue<T>::container_type& Get( const std::queue<T>& value) { @@ -97,61 +144,98 @@ void MemoryTracker::TrackField(const char* name, const std::queue<T>& value) { }; const auto& container = ContainerGetter::Get(value); - TrackField(name, container); + TrackField(edge_name, container, node_name, element_name); } template <typename T, typename test_for_number, typename dummy> -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 <typename T, typename U> -void MemoryTracker::TrackField(const char* name, const std::pair<T, U>& value) { - PushNode(name); +void MemoryTracker::TrackField(const char* edge_name, + const std::pair<T, U>& value, + const char* node_name) { + PushNode(node_name == nullptr ? "pair" : node_name, + sizeof(const std::pair<T, U>), + 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 <typename T> -void MemoryTracker::TrackField(const char* name, - const std::basic_string<T>& value) { - TrackFieldWithSize(name, value.size() * sizeof(T)); +void MemoryTracker::TrackField(const char* edge_name, + const std::basic_string<T>& value, + const char* node_name) { + TrackFieldWithSize(edge_name, value.size() * sizeof(T), "std::basic_string"); } template <typename T, typename Traits> -void MemoryTracker::TrackField(const char* name, - const v8::Persistent<T, Traits>& value) { - TrackField(name, value.Get(isolate_)); +void MemoryTracker::TrackField(const char* edge_name, + const v8::Persistent<T, Traits>& value, + const char* node_name) { + TrackField(edge_name, value.Get(isolate_)); } template <typename T> -void MemoryTracker::TrackField(const char* name, const v8::Local<T>& value) { +void MemoryTracker::TrackField(const char* edge_name, + const v8::Local<T>& value, + const char* node_name) { if (!value.IsEmpty()) - graph_->AddEdge(CurrentNode(), graph_->V8Node(value)); + graph_->AddEdge(CurrentNode(), graph_->V8Node(value), edge_name); } template <typename T> +void MemoryTracker::TrackField(const char* edge_name, + const MallocedBuffer<T>& 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<T>& 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 <class NativeT, class V8T> void MemoryTracker::TrackField(const char* name, - const AliasedBuffer<NativeT, V8T>& value) { - TrackField(name, value.GetJSArray()); + const AliasedBuffer<NativeT, V8T>& 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<v8::EmbedderGraph::Node>(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<v8::EmbedderGraph::Node>(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<v8::EmbedderGraph::Node>(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 d0f9e0dcad..1799279212 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<v8::Object> WrappedObject() const override { + * return node::PersistentToLocal(wrapped_); + * } + * private: + * AnotherRetainerClass another_retainer_; + * InternalClass internal_member_; + * std::vector<uv_async_t> vector_; + * node::Persistent<Object> target_; + * + * node::Persistent<Object> 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<v8::Object> WrappedObject() const { return v8::Local<v8::Object>(); } 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 <typename T> - 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 <typename T> - inline void TrackField(const char* name, const std::unique_ptr<T>& value); + inline void TrackField(const char* edge_name, + const std::unique_ptr<T>& 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 <typename T, typename Iterator = typename T::const_iterator> - 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 <typename T> - inline void TrackField(const char* name, const std::queue<T>& value); - template <typename T> - inline void TrackField(const char* name, const std::basic_string<T>& value); - template <typename T, typename test_for_number = - typename std::enable_if< - std::numeric_limits<T>::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<T>& value, + const char* node_name = nullptr, + const char* element_name = nullptr); template <typename T, typename U> - inline void TrackField(const char* name, const std::pair<T, U>& value); + inline void TrackField(const char* edge_name, + const std::pair<T, U>& 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 <typename T> + inline void TrackField(const char* edge_name, + const std::basic_string<T>& value, + const char* node_name = nullptr); + template <typename T, + typename test_for_number = typename std:: + enable_if<std::numeric_limits<T>::is_specialized, bool>::type, + typename dummy = bool> + inline void TrackField(const char* edge_name, + const T& value, + const char* node_name = nullptr); template <typename T, typename Traits> - inline void TrackField(const char* name, - const v8::Persistent<T, Traits>& value); + inline void TrackField(const char* edge_name, + const v8::Persistent<T, Traits>& value, + const char* node_name = nullptr); template <typename T> - inline void TrackField(const char* name, const v8::Local<T>& value); + inline void TrackField(const char* edge_name, + const v8::Local<T>& value, + const char* node_name = nullptr); template <typename T> - inline void TrackField(const char* name, const MallocedBuffer<T>& value); - inline void TrackField(const char* name, const uv_buf_t& value); + inline void TrackField(const char* edge_name, + const MallocedBuffer<T>& 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 <class NativeT, class V8T> - inline void TrackField(const char* name, - const AliasedBuffer<NativeT, V8T>& value); + inline void TrackField(const char* edge_name, + const AliasedBuffer<NativeT, V8T>& 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<MemoryRetainerNode*> node_stack_; diff --git a/src/module_wrap.h b/src/module_wrap.h index 3e19b6c9eb..d6593c4813 100644 --- a/src/module_wrap.h +++ b/src/module_wrap.h @@ -34,12 +34,12 @@ class ModuleWrap : public BaseObject { v8::Local<v8::Object> 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 bbbbd27292..4239f07f06 100644 --- a/src/node_contextify.cc +++ b/src/node_contextify.cc @@ -590,13 +590,11 @@ class ContextifyScript : public BaseObject { private: Persistent<UnboundScript> 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<Object> target) { HandleScope scope(env->isolate()); Local<String> class_name = diff --git a/src/node_crypto.h b/src/node_crypto.h index 714afd0d3b..acb61885f2 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<v8::Object> 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<v8::Object> 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<v8::Object> 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<v8::Object> 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<v8::Value>& 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<v8::Object> wrap, ECKeyPointer&& key) diff --git a/src/node_crypto_bio.h b/src/node_crypto_bio.h index 0c61f19d01..1c62fbbd35 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 f4f5b263a6..5b10226925 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<Object> obj) : ReqWrap(handle->env(), obj, AsyncWrap::PROVIDER_FSREQCALLBACK), diff --git a/src/node_file.h b/src/node_file.h index af62be0fec..31242e1a1b 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<Value>& 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<uv_fs_t> { return static_cast<FileHandleReadWrap*>(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 b95a81e385..44353e2d57 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 7fa230979a..2ab452bf02 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 dcaf3d211a..5d093b27c3 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 e87f8f5955..5966e3ff67 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 0a79d6f9d3..a8b95401fd 100644 --- a/src/node_messaging.cc +++ b/src/node_messaging.cc @@ -326,7 +326,6 @@ Maybe<bool> 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 b7fd392ccc..e4674885d2 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<char> 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 490d2c325a..bb7890a730 100644 --- a/src/node_serdes.cc +++ b/src/node_serdes.cc @@ -53,11 +53,9 @@ class SerializerContext : public BaseObject, static void WriteDouble(const FunctionCallbackInfo<Value>& args); static void WriteRawBytes(const FunctionCallbackInfo<Value>& 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<Value>& args); static void ReadRawBytes(const FunctionCallbackInfo<Value>& 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 33c90ad3cd..3d819b4578 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<v8::Value>& args); static void Start(const v8::FunctionCallbackInfo<v8::Value>& 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 f60c5cd9b5..6530bdb04c 100644 --- a/src/node_trace_events.cc +++ b/src/node_trace_events.cc @@ -27,11 +27,11 @@ class NodeCategorySet : public BaseObject { const std::set<std::string>& 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 8491ad221b..cbd4a86157 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 5def50d3b9..6e99f68108 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 7faf5145ab..05a5ba6e11 100644 --- a/src/pipe_wrap.h +++ b/src/pipe_wrap.h @@ -45,11 +45,9 @@ class PipeWrap : public ConnectionWrap<PipeWrap, uv_pipe_t> { v8::Local<v8::Value> unused, v8::Local<v8::Context> 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 af6cbfb4e5..edf79177d3 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<Value>& args) { diff --git a/src/sharedarraybuffer_metadata.cc b/src/sharedarraybuffer_metadata.cc index 3d5b96051e..b20d9f46a4 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 e1792a0267..afdf140d8b 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<Value>& args) { diff --git a/src/stream_base.h b/src/stream_base.h index 05c2a96236..d8e6df960f 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 <typename OtherBase> @@ -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 c76afac416..51a33b7ef6 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<v8::Value>& args); static void Unpipe(const v8::FunctionCallbackInfo<v8::Value>& 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 829c1b22bf..90c81bcae6 100644 --- a/src/tcp_wrap.h +++ b/src/tcp_wrap.h @@ -44,10 +44,8 @@ class TCPWrap : public ConnectionWrap<TCPWrap, uv_tcp_t> { v8::Local<v8::Value> unused, v8::Local<v8::Context> 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 3462dac8ae..3dbeabd891 100644 --- a/src/tls_wrap.cc +++ b/src/tls_wrap.cc @@ -858,7 +858,6 @@ void TLSWrap::GetWriteQueueSize(const FunctionCallbackInfo<Value>& 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 aea8568b11..0e26576482 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 45357cfa46..ad5f364134 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 0000000000..e69de29bb2 --- /dev/null +++ b/src/txt diff --git a/src/udp_wrap.cc b/src/udp_wrap.cc index 77139c2f93..92fc036778 100644 --- a/src/udp_wrap.cc +++ b/src/udp_wrap.cc @@ -56,11 +56,9 @@ class SendWrap : public ReqWrap<uv_udp_send_t> { 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 b5d2824896..030abdf74d 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 f0a93f3185..79b766939b 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<v8::Object> object, @@ -50,9 +50,9 @@ class TestHandleWrap : public node::HandleWrap { class TestReqWrap : public node::ReqWrap<uv_req_t> { 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<v8::Object> object) : node::ReqWrap<uv_req_t>(env, @@ -76,9 +76,9 @@ class DummyBaseObject : public node::BaseObject { DummyBaseObject(node::Environment* env, v8::Local<v8::Object> 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 382d1d3642..e23670b64c 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 00ca54917f..6fe79f7dd4 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 855b135f6a..ca170e13a6 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 b503951e65..caece96d01 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 08fc6703d8..3d031d87eb 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 90b2d8dc95..fee19bf676 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 44a50dd66b..02b989c6b3 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 936e3a1a50..f79e345821 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' } ] } ]); |