Skip to content

Commit

Permalink
src: use V8 graph heap snapshot API
Browse files Browse the repository at this point in the history
Transition to a newer, more flexible API for
heap snapshot creation.

This addresses a currently pending deprecation in the V8 API.

Fixes: nodejs#21633
  • Loading branch information
addaleax committed Jul 14, 2018
1 parent f778d10 commit 8e5489f
Show file tree
Hide file tree
Showing 8 changed files with 160 additions and 116 deletions.
100 changes: 5 additions & 95 deletions src/async_wrap.cc
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ using v8::Function;
using v8::FunctionCallbackInfo;
using v8::FunctionTemplate;
using v8::HandleScope;
using v8::HeapProfiler;
using v8::Integer;
using v8::Isolate;
using v8::Local;
Expand All @@ -43,7 +42,6 @@ using v8::ObjectTemplate;
using v8::Promise;
using v8::PromiseHookType;
using v8::PropertyCallbackInfo;
using v8::RetainedObjectInfo;
using v8::String;
using v8::Uint32;
using v8::Undefined;
Expand All @@ -61,87 +59,6 @@ static const char* const provider_names[] = {
};


// Report correct information in a heapdump.

class RetainedAsyncInfo: public RetainedObjectInfo {
public:
explicit RetainedAsyncInfo(uint16_t class_id, AsyncWrap* wrap);

void Dispose() override;
bool IsEquivalent(RetainedObjectInfo* other) override;
intptr_t GetHash() override;
const char* GetLabel() override;
intptr_t GetSizeInBytes() override;

private:
const char* label_;
const AsyncWrap* wrap_;
const size_t length_;
};


static int OwnMemory(AsyncWrap* async_wrap) {
MemoryTracker tracker;
tracker.set_track_only_self(true);
tracker.Track(async_wrap);
return tracker.accumulated_size();
}


RetainedAsyncInfo::RetainedAsyncInfo(uint16_t class_id, AsyncWrap* wrap)
: label_(provider_names[class_id - NODE_ASYNC_ID_OFFSET]),
wrap_(wrap),
length_(OwnMemory(wrap)) {
}


void RetainedAsyncInfo::Dispose() {
delete this;
}


bool RetainedAsyncInfo::IsEquivalent(RetainedObjectInfo* other) {
return label_ == other->GetLabel() &&
wrap_ == static_cast<RetainedAsyncInfo*>(other)->wrap_;
}


intptr_t RetainedAsyncInfo::GetHash() {
return reinterpret_cast<intptr_t>(wrap_);
}


const char* RetainedAsyncInfo::GetLabel() {
return label_;
}


intptr_t RetainedAsyncInfo::GetSizeInBytes() {
return length_;
}


RetainedObjectInfo* WrapperInfo(uint16_t class_id, Local<Value> wrapper) {
// No class_id should be the provider type of NONE.
CHECK_GT(class_id, NODE_ASYNC_ID_OFFSET);
// And make sure the class_id doesn't extend past the last provider.
CHECK_LE(class_id - NODE_ASYNC_ID_OFFSET, AsyncWrap::PROVIDERS_LENGTH);
CHECK(wrapper->IsObject());
CHECK(!wrapper.IsEmpty());

Local<Object> object = wrapper.As<Object>();
CHECK_GT(object->InternalFieldCount(), 0);

AsyncWrap* wrap;
ASSIGN_OR_RETURN_UNWRAP(&wrap, object, nullptr);

return new RetainedAsyncInfo(class_id, wrap);
}


// end RetainedAsyncInfo


struct AsyncWrapObject : public AsyncWrap {
static inline void New(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
Expand Down Expand Up @@ -616,16 +533,6 @@ void AsyncWrap::Initialize(Local<Object> target,
}


void LoadAsyncWrapperInfo(Environment* env) {
HeapProfiler* heap_profiler = env->isolate()->GetHeapProfiler();
#define V(PROVIDER) \
heap_profiler->SetWrapperClassInfoProvider( \
(NODE_ASYNC_ID_OFFSET + AsyncWrap::PROVIDER_ ## PROVIDER), WrapperInfo);
NODE_ASYNC_PROVIDER_TYPES(V)
#undef V
}


AsyncWrap::AsyncWrap(Environment* env,
Local<Object> object,
ProviderType provider,
Expand Down Expand Up @@ -814,9 +721,12 @@ void EmitAsyncDestroy(Isolate* isolate, async_context asyncContext) {
Environment::GetCurrent(isolate), asyncContext.async_id);
}

std::string AsyncWrap::MemoryInfoName() const {
return provider_names[provider_type()];
}

std::string AsyncWrap::diagnostic_name() const {
return std::string(provider_names[provider_type()]) +
" (" + std::to_string(env()->thread_id()) + ":" +
return MemoryInfoName() + " (" + std::to_string(env()->thread_id()) + ":" +
std::to_string(static_cast<int64_t>(async_id_)) + ")";
}

Expand Down
3 changes: 1 addition & 2 deletions src/async_wrap.h
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ class AsyncWrap : public BaseObject {
v8::Local<v8::Value>* argv);

virtual std::string diagnostic_name() const;
std::string MemoryInfoName() const override;

static void WeakCallback(const v8::WeakCallbackInfo<DestroyParam> &info);

Expand Down Expand Up @@ -204,8 +205,6 @@ class AsyncWrap : public BaseObject {
double trigger_async_id_;
};

void LoadAsyncWrapperInfo(Environment* env);

} // namespace node

#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
Expand Down
17 changes: 16 additions & 1 deletion src/env.cc
Original file line number Diff line number Diff line change
Expand Up @@ -144,9 +144,15 @@ Environment::Environment(IsolateData* isolate_data,
std::string debug_cats;
SafeGetenv("NODE_DEBUG_NATIVE", &debug_cats);
set_debug_categories(debug_cats, true);

isolate()->GetHeapProfiler()->AddBuildEmbedderGraphCallback(
BuildEmbedderGraph, this);
}

Environment::~Environment() {
isolate()->GetHeapProfiler()->RemoveBuildEmbedderGraphCallback(
BuildEmbedderGraph, this);

// Make sure there are no re-used libuv wrapper objects.
// CleanupHandles() should have removed all of them.
CHECK(file_handle_read_wrap_freelist_.empty());
Expand Down Expand Up @@ -217,7 +223,6 @@ void Environment::Start(int argc,
set_process_object(process_object);

SetupProcessObject(this, argc, argv, exec_argc, exec_argv);
LoadAsyncWrapperInfo(this);

static uv_once_t init_once = UV_ONCE_INIT;
uv_once(&init_once, InitThreadLocalOnce);
Expand Down Expand Up @@ -734,6 +739,16 @@ void Environment::stop_sub_worker_contexts() {
}
}

void Environment::BuildEmbedderGraph(v8::Isolate* isolate,
v8::EmbedderGraph* graph,
void* data) {
MemoryTracker tracker(isolate, graph);
static_cast<Environment*>(data)->ForEachBaseObject([&](BaseObject* obj) {
tracker.Track(obj);
});
}


// Not really any better place than env.cc at this moment.
void BaseObject::DeleteMe(void* data) {
BaseObject* self = static_cast<BaseObject*>(data);
Expand Down
4 changes: 4 additions & 0 deletions src/env.h
Original file line number Diff line number Diff line change
Expand Up @@ -861,6 +861,10 @@ class Environment {
inline void RemoveCleanupHook(void (*fn)(void*), void* arg);
void RunCleanup();

static void BuildEmbedderGraph(v8::Isolate* isolate,
v8::EmbedderGraph* graph,
void* data);

private:
inline void CreateImmediate(native_immediate_callback cb,
void* data,
Expand Down
105 changes: 98 additions & 7 deletions src/memory_tracker-inl.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,68 @@

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);

name_ = retainer_->MemoryInfoName();
}
if (name_.empty() && name != nullptr) {
name_ = name;
}
}

const char* Name() override { return name_.c_str(); }
const char* NamePrefix() override { return "Node /"; }
size_t SizeInBytes() override { return size_; }
// TODO(addaleax): Merging this with the "official" WrapperNode() method
// seems to lose accuracy, e.g. SizeInBytes() is disregarded.
// Figure out whether to do anything about that.
Node* JSWrapperNode() { return wrapper_node_; }

bool IsRootNode() override {
return retainer_ != nullptr && retainer_->IsRootNode();
}

private:
friend class MemoryTracker;

Node* wrapper_node_ = nullptr;
const MemoryRetainer* retainer_;
std::string name_;
size_t size_ = 0;
};

template <typename T>
void MemoryTracker::TrackThis(const T* obj) {
accumulated_size_ += sizeof(T);
CurrentNode()->size_ = sizeof(T);
}

void MemoryTracker::TrackFieldWithSize(const char* name, size_t size) {
accumulated_size_ += size;
if (size > 0)
AddNode(name)->size_ = size;
}

void MemoryTracker::TrackField(const char* name, const MemoryRetainer& value) {
TrackField(name, &value);
}

void MemoryTracker::TrackField(const char* name, const MemoryRetainer* value) {
if (track_only_self_ || value == nullptr || seen_.count(value) > 0) return;
seen_.insert(value);
Track(value);
if (track_only_self_ || value == nullptr) return;
auto it = seen_.find(value);
if (it != seen_.end()) {
graph_->AddEdge(CurrentNode(), it->second);
} else {
Track(value, name);
}
}

template <typename T>
Expand All @@ -36,8 +81,10 @@ template <typename T, typename Iterator>
void MemoryTracker::TrackField(const char* name, const T& value) {
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);
PopNode();
}

template <typename T>
Expand All @@ -56,13 +103,15 @@ void MemoryTracker::TrackField(const char* name, const std::queue<T>& value) {
template <typename T, typename test_for_number, typename dummy>
void MemoryTracker::TrackField(const char* name, const T& value) {
// For numbers, creating new nodes is not worth the overhead.
TrackFieldWithSize(name, sizeof(T));
CurrentNode()->size_ += sizeof(T);
}

template <typename T, typename U>
void MemoryTracker::TrackField(const char* name, const std::pair<T, U>& value) {
PushNode(name);
TrackField("first", value.first);
TrackField("second", value.second);
PopNode();
}

template <typename T>
Expand All @@ -74,10 +123,13 @@ void MemoryTracker::TrackField(const char* name,
template <typename T, typename Traits>
void MemoryTracker::TrackField(const char* name,
const v8::Persistent<T, Traits>& value) {
TrackField(name, value.Get(isolate_));
}

template <typename T>
void MemoryTracker::TrackField(const char* name, const v8::Local<T>& value) {
if (!value.IsEmpty())
graph_->AddEdge(CurrentNode(), graph_->V8Node(value));
}

template <typename T>
Expand All @@ -96,8 +148,47 @@ void MemoryTracker::TrackField(const char* name,
TrackField(name, value.GetJSArray());
}

void MemoryTracker::Track(const MemoryRetainer* value) {
void MemoryTracker::Track(const MemoryRetainer* value, const char* name) {
v8::HandleScope handle_scope(isolate_);
MemoryRetainerNode* n = PushNode(name, value);
value->MemoryInfo(this);
CHECK_EQ(CurrentNode(), n);
CHECK_NE(n->size_, 0);
PopNode();
}

MemoryRetainerNode* MemoryTracker::CurrentNode() const {
if (node_stack_.empty()) return nullptr;
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;

if (CurrentNode() != nullptr)
graph_->AddEdge(CurrentNode(), n);

if (n->JSWrapperNode() != nullptr) {
graph_->AddEdge(n, n->JSWrapperNode());
graph_->AddEdge(n->JSWrapperNode(), n);
}

return n;
}

MemoryRetainerNode* MemoryTracker::PushNode(
const char* name, const MemoryRetainer* retainer) {
MemoryRetainerNode* n = AddNode(name, retainer);
node_stack_.push(n);
return n;
}

void MemoryTracker::PopNode() {
node_stack_.pop();
}

} // namespace node
Expand Down
Loading

0 comments on commit 8e5489f

Please sign in to comment.