diff --git a/lib/internal/test/heap.js b/lib/internal/test/heap.js
index a9260f651b9c1a..0a5bf7059713a5 100644
--- a/lib/internal/test/heap.js
+++ b/lib/internal/test/heap.js
@@ -37,8 +37,8 @@ function createJSHeapDump() {
     const fromNode = nodes[fromNodeIndex];
     const edge = {
       type,
-      toNode,
-      fromNode,
+      to: toNode,
+      from: fromNode,
       name: typeof name_or_index === 'string' ? name_or_index : null
     };
     toNode.incomingEdges.push(edge);
diff --git a/src/async_wrap.cc b/src/async_wrap.cc
index b04ab6808704b1..5f04317960d201 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(AsyncWrapObject);
+  SET_SELF_SIZE(AsyncWrapObject)
 
   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 a967414aecdec2..b372cfb34a3adc 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 2370157eaa2a11..88221b77468631 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 c51054819f7c01..b84d5b01c2c452 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 668dff3451686f..fee5f874f59d49 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>();
         }
       }
@@ -179,9 +193,8 @@ void BuildEmbedderGraph(const FunctionCallbackInfo<Value>& args) {
   Environment* env = Environment::GetCurrent(args);
   JSGraph graph(env->isolate());
   Environment::BuildEmbedderGraph(env->isolate(), &graph, env);
-  Local<Array> ret;
-  if (graph.CreateObject().ToLocal(&ret))
-    args.GetReturnValue().Set(ret);
+  // Crash if we cannot build a proper graph
+  args.GetReturnValue().Set(graph.CreateObject().ToLocalChecked());
 }
 
 
diff --git a/src/inspector_js_api.cc b/src/inspector_js_api.cc
index 52184111f5527a..90825670cd9ea2 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 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<v8::Object> obj);
diff --git a/src/memory_tracker-inl.h b/src/memory_tracker-inl.h
index 568a4364f9c64d..359388a9525fea 100644
--- a/src/memory_tracker-inl.h
+++ b/src/memory_tracker-inl.h
@@ -5,25 +5,33 @@
 
 #include "memory_tracker.h"
 
+#define DEFAULT_NODE_NAME                                                      \
+  (node_name == nullptr ? (edge_name == nullptr ? "" : edge_name) : node_name)
+
 namespace node {
 
 class MemoryRetainerNode : public v8::EmbedderGraph::Node {
  public:
   explicit inline MemoryRetainerNode(MemoryTracker* tracker,
-                                     const MemoryRetainer* retainer,
-                                     const char* name)
-     : retainer_(retainer) {
-    if (retainer_ != nullptr) {
-      v8::HandleScope handle_scope(tracker->isolate());
-      v8::Local<v8::Object> obj = retainer_->WrappedObject();
-      if (!obj.IsEmpty())
-        wrapper_node_ = tracker->graph()->V8Node(obj);
+                                     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;
-    }
+  explicit 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 +43,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) {
+void MemoryTracker::TrackFieldWithSize(const char* edge_name,
+                                       size_t size,
+                                       const char* node_name) {
   if (size > 0)
-    AddNode(name)->size_ = size;
+    AddNode(DEFAULT_NODE_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) {
+  // 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) {
+    // Shift the self size of this container out to a separate node
+    CurrentNode()->size_ -= sizeof(T);
+  }
+  PushNode(DEFAULT_NODE_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 +135,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 MallocedBuffer<T>& value) {
-  TrackFieldWithSize(name, value.size);
+                               const uv_buf_t& value,
+                               const char* node_name) {
+  TrackFieldWithSize(name, value.len, "uv_buf_t");
 }
 
-void MemoryTracker::TrackField(const char* name, const uv_buf_t& value) {
-  TrackFieldWithSize(name, value.len);
+void MemoryTracker::TrackField(const char* name,
+                               const uv_timer_t& value,
+                               const char* node_name) {
+  TrackFieldWithSize(name, sizeof(value), "uv_timer_t");
+}
+
+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 +237,50 @@ 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) {
+  MemoryRetainerNode* n;
+  auto it = seen_.find(retainer);
+  if (it != seen_.end()) {
+    n = it->second;
+    return n;
+  }
 
-  if (CurrentNode() != nullptr)
-    graph_->AddEdge(CurrentNode(), n);
+  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 d0f9e0dcad8f1e..0cc2def22e9af7 100644
--- a/src/memory_tracker.h
+++ b/src/memory_tracker.h
@@ -12,6 +12,19 @@
 #include "aliased_buffer.h"
 #include "v8-profiler.h"
 
+// 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 {}
+
 namespace node {
 
 class MemoryTracker;
@@ -21,61 +34,162 @@ 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", this->another_retainer);
+ *       // Specify node name and size explicitly
+ *       tracker->TrackFieldWithSize("internal_member",
+ *                                   this->internal_member.size(),
+ *                                   "InternalClass");
+ *       // Node name falls back to the edge name,
+ *       // elements in the container appear as grandchildren nodes
+ *       tracker->TrackField("vector", this->vector_field);
+ *       // Node name and size come from the JS object
+ *       tracker->TrackField("target", this->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(this->wrapped);
+ *     }
+ *   private:
+ *     AnotherRetainerClass another_retainer;
+ *     InternalClass internal_member;
+ *     std::vector<int> 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
+ *    |> 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 TrackField(const char* edge_name,
+                         const std::unique_ptr<T>& value,
+                         const char* node_name = nullptr);
 
-  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);
+  // For containers, the elements will be graphed as grandchildren nodes
+  // if the container is not empty.
+  // 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);
   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 +202,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 3e19b6c9eb3ebe..d6593c48135d18 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 bbbbd272921f98..4239f07f061cc9 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 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<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 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 6a71ef11240689..2425cf01471d96 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 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<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 9a0cc4ae191ed0..328be42d1bdc4d 100644
--- a/src/node_http2.cc
+++ b/src/node_http2.cc
@@ -2893,7 +2893,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);
@@ -2901,7 +2900,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 3236dd4f9f8b1e..7c0e9c8fe83067 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 6dd66f243e8a20..071b06a90a21b5 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 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<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 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<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 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<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 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<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 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 d0cc54c9436582..9affcef0c358ca 100644
--- a/src/node_zlib.cc
+++ b/src/node_zlib.cc
@@ -657,14 +657,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<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 1daa437b296b39..f350a2e42bf096 100644
--- a/src/process_wrap.cc
+++ b/src/process_wrap.cc
@@ -68,11 +68,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 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 b9ec037b453d91..7747699f90be54 100644
--- a/src/signal_wrap.cc
+++ b/src/signal_wrap.cc
@@ -61,11 +61,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 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 <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 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<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 829c1b22bf3aec..90c81bcae6fd6f 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 27bedd08cec974..cd49cdd38b54b8 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 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 510926abde2584..8c0e25981fef70 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 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<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 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' }
     ]
   }
 ]);