lts/src/memory_tracker.h
#pragma once
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include "aliased_buffer.h"
#include "v8-profiler.h"
#include <uv.h>
#include <limits>
#include <queue>
#include <stack>
#include <string>
#include <unordered_map>
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;
template <typename T, bool kIsWeak>
class BaseObjectPtrImpl;
namespace crypto {
class NodeBIO;
}
class CleanupHookCallback;
/* 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_);
*
* // Add non_pointer_retainer as a separate node into the graph
* // and track its memory information recursively.
* // Note that we need to make sure its size is not accounted in
* // ExampleRetainer::SelfSize().
* tracker->TrackField("non_pointer_retainer", &non_pointer_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";
* }
*
* // Classes that only want to return its sizeof() value can use the
* // SET_SELF_SIZE(Class) macro instead.
* size_t SelfSize() const override {
* // We need to exclude the size of non_pointer_retainer so that
* // we can track it separately in ExampleRetainer::MemoryInfo().
* return sizeof(ExampleRetainer) - sizeof(NonPointerRetainerClass);
* }
*
* // 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::Default(wrapped_);
* }
*
* private:
* AnotherRetainerClass* another_retainer_;
* NonPointerRetainerClass non_pointer_retainer;
* InternalClass internal_member_;
* std::vector<uv_async_t> vector_;
* v8::Global<Object> target_;
*
* v8::Global<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() = default;
// 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; }
};
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);
inline void TrackInlineFieldWithSize(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, typename D>
inline void TrackField(const char* edge_name,
const std::unique_ptr<T, D>& value,
const char* node_name = nullptr);
template <typename T, bool kIsWeak>
void TrackField(const char* edge_name,
const BaseObjectPtrImpl<T, kIsWeak>& 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* 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* 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* 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>
void TrackField(const char* edge_name,
const v8::Eternal<T>& value,
const char* node_name);
template <typename T>
inline void TrackField(const char* edge_name,
const v8::PersistentBase<T>& value,
const char* node_name = nullptr);
template <typename T>
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* 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);
inline void TrackInlineField(const char* edge_name,
const uv_async_t& value,
const char* node_name = nullptr);
template <class NativeT, class V8T>
inline void TrackField(const char* edge_name,
const AliasedBufferBase<NativeT, V8T>& value,
const char* node_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);
// Useful for parents that do not wish to perform manual
// adjustments to its `SelfSize()` when embedding retainer
// objects inline.
// Put a memory container into the graph, create an edge from
// the current node if there is one on the stack - there should
// be one, of the container object which the current field is part of.
// Reduce the size of memory from the container so as to avoid
// duplication in accounting.
inline void TrackInlineField(const MemoryRetainer* retainer,
const char* edge_name = nullptr);
inline v8::EmbedderGraph* graph() { return graph_; }
inline v8::Isolate* isolate() { return isolate_; }
inline explicit MemoryTracker(v8::Isolate* isolate,
v8::EmbedderGraph* graph)
: isolate_(isolate), graph_(graph) {}
private:
typedef std::unordered_map<const MemoryRetainer*, MemoryRetainerNode*>
NodeMap;
inline MemoryRetainerNode* CurrentNode() const;
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();
v8::Isolate* isolate_;
v8::EmbedderGraph* graph_;
std::stack<MemoryRetainerNode*> node_stack_;
NodeMap seen_;
};
} // namespace node
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS