Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion binding.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
"bindings/thread-cpu-clock.cc",
"bindings/translate-heap-profile.cc",
"bindings/translate-time-profile.cc",
"bindings/binding.cc"
"bindings/binding.cc",
"bindings/allocation-profile-node.cc"
],
"include_dirs": [
"bindings",
Expand All @@ -42,6 +43,7 @@
"bindings/translate-heap-profile.cc",
"bindings/translate-time-profile.cc",
"bindings/test/binding.cc",
"bindings/allocation-profile-node.cc"
],
"include_dirs": [
"bindings",
Expand Down
130 changes: 130 additions & 0 deletions bindings/allocation-profile-node.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
/*
* Copyright 2024 Datadog, Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#include "allocation-profile-node.hh"
#include "per-isolate-data.hh"

namespace dd {

NAN_MODULE_INIT(ExternalAllocationNode::Init) {
v8::Local<v8::FunctionTemplate> tpl = Nan::New<v8::FunctionTemplate>();
tpl->SetClassName(Nan::New("AllocationProfileNode").ToLocalChecked());
tpl->InstanceTemplate()->SetInternalFieldCount(1);

auto inst = tpl->InstanceTemplate();
Nan::SetAccessor(inst, Nan::New("name").ToLocalChecked(), GetName);
Nan::SetAccessor(
inst, Nan::New("scriptName").ToLocalChecked(), GetScriptName);
Nan::SetAccessor(inst, Nan::New("scriptId").ToLocalChecked(), GetScriptId);
Nan::SetAccessor(
inst, Nan::New("lineNumber").ToLocalChecked(), GetLineNumber);
Nan::SetAccessor(
inst, Nan::New("columnNumber").ToLocalChecked(), GetColumnNumber);
Nan::SetAccessor(
inst, Nan::New("allocations").ToLocalChecked(), GetAllocations);
Nan::SetAccessor(inst, Nan::New("children").ToLocalChecked(), GetChildren);

PerIsolateData::For(v8::Isolate::GetCurrent())
->AllocationNodeConstructor()
.Reset(Nan::GetFunction(tpl).ToLocalChecked());
}

v8::Local<v8::Object> ExternalAllocationNode::New(
std::shared_ptr<ExternalNode> node) {
auto* isolate = v8::Isolate::GetCurrent();

v8::Local<v8::Function> constructor =
Nan::New(PerIsolateData::For(isolate)->AllocationNodeConstructor());

v8::Local<v8::Object> obj = Nan::NewInstance(constructor).ToLocalChecked();

auto* wrapper = new ExternalAllocationNode(node);
wrapper->Wrap(obj);

return obj;
}

NAN_GETTER(ExternalAllocationNode::GetName) {
auto* wrapper =
Nan::ObjectWrap::Unwrap<ExternalAllocationNode>(info.Holder());
auto* isolate = v8::Isolate::GetCurrent();
info.GetReturnValue().Set(
v8::Local<v8::String>::New(isolate, wrapper->node_->name));
}

NAN_GETTER(ExternalAllocationNode::GetScriptName) {
auto* wrapper =
Nan::ObjectWrap::Unwrap<ExternalAllocationNode>(info.Holder());
auto* isolate = v8::Isolate::GetCurrent();
info.GetReturnValue().Set(
v8::Local<v8::String>::New(isolate, wrapper->node_->script_name));
}

NAN_GETTER(ExternalAllocationNode::GetScriptId) {
auto* wrapper =
Nan::ObjectWrap::Unwrap<ExternalAllocationNode>(info.Holder());
info.GetReturnValue().Set(Nan::New(wrapper->node_->script_id));
}

NAN_GETTER(ExternalAllocationNode::GetLineNumber) {
auto* wrapper =
Nan::ObjectWrap::Unwrap<ExternalAllocationNode>(info.Holder());
info.GetReturnValue().Set(Nan::New(wrapper->node_->line_number));
}

NAN_GETTER(ExternalAllocationNode::GetColumnNumber) {
auto* wrapper =
Nan::ObjectWrap::Unwrap<ExternalAllocationNode>(info.Holder());
info.GetReturnValue().Set(Nan::New(wrapper->node_->column_number));
}

NAN_GETTER(ExternalAllocationNode::GetAllocations) {
auto* wrapper =
Nan::ObjectWrap::Unwrap<ExternalAllocationNode>(info.Holder());
auto* isolate = v8::Isolate::GetCurrent();
auto context = isolate->GetCurrentContext();

const auto& allocations = wrapper->node_->allocations;
v8::Local<v8::Array> arr = v8::Array::New(isolate, allocations.size());
for (size_t i = 0; i < allocations.size(); i++) {
const auto& alloc = allocations[i];
v8::Local<v8::Object> alloc_obj = v8::Object::New(isolate);
Nan::Set(alloc_obj,
Nan::New("sizeBytes").ToLocalChecked(),
Nan::New<v8::Number>(static_cast<double>(alloc.size)));
Nan::Set(alloc_obj,
Nan::New("count").ToLocalChecked(),
Nan::New<v8::Number>(static_cast<double>(alloc.count)));
arr->Set(context, i, alloc_obj).Check();
}
info.GetReturnValue().Set(arr);
}

NAN_GETTER(ExternalAllocationNode::GetChildren) {
auto* wrapper =
Nan::ObjectWrap::Unwrap<ExternalAllocationNode>(info.Holder());
auto* isolate = v8::Isolate::GetCurrent();
auto context = isolate->GetCurrentContext();

const auto& children = wrapper->node_->children;
v8::Local<v8::Array> arr = v8::Array::New(isolate, children.size());
for (size_t i = 0; i < children.size(); i++) {
arr->Set(context, i, ExternalAllocationNode::New(children[i])).Check();
}
info.GetReturnValue().Set(arr);
}

} // namespace dd
46 changes: 46 additions & 0 deletions bindings/allocation-profile-node.hh
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* Copyright 2024 Datadog, Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#pragma once

#include <nan.h>
#include <memory>

#include "translate-heap-profile.hh"

namespace dd {

class ExternalAllocationNode : public Nan::ObjectWrap {
public:
static NAN_MODULE_INIT(Init);

static v8::Local<v8::Object> New(std::shared_ptr<ExternalNode> node);

private:
ExternalAllocationNode(std::shared_ptr<ExternalNode> node) : node_(node) {}

static NAN_GETTER(GetName);
static NAN_GETTER(GetScriptName);
static NAN_GETTER(GetScriptId);
static NAN_GETTER(GetLineNumber);
static NAN_GETTER(GetColumnNumber);
static NAN_GETTER(GetAllocations);
static NAN_GETTER(GetChildren);

std::shared_ptr<ExternalNode> node_;
};

} // namespace dd
2 changes: 2 additions & 0 deletions bindings/binding.cc
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include <node.h>
#include <v8.h>

#include "allocation-profile-node.hh"
#include "profilers/heap.hh"
#include "profilers/wall.hh"

Expand Down Expand Up @@ -47,6 +48,7 @@ NODE_MODULE_INIT(/* exports, module, context */) {
#pragma GCC diagnostic pop
#endif

dd::ExternalAllocationNode::Init(exports);
dd::HeapProfiler::Init(exports);
dd::WallProfiler::Init(exports);
Nan::SetMethod(exports, "getNativeThreadId", GetNativeThreadId);
Expand Down
4 changes: 4 additions & 0 deletions bindings/per-isolate-data.cc
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ Nan::Global<v8::Function>& PerIsolateData::WallProfilerConstructor() {
return wall_profiler_constructor;
}

Nan::Global<v8::Function>& PerIsolateData::AllocationNodeConstructor() {
return allocation_node_constructor;
}

std::shared_ptr<HeapProfilerState>& PerIsolateData::GetHeapProfilerState() {
return heap_profiler_state;
}
Expand Down
2 changes: 2 additions & 0 deletions bindings/per-isolate-data.hh
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ struct HeapProfilerState;
class PerIsolateData {
private:
Nan::Global<v8::Function> wall_profiler_constructor;
Nan::Global<v8::Function> allocation_node_constructor;
std::shared_ptr<HeapProfilerState> heap_profiler_state;

PerIsolateData() {}
Expand All @@ -36,6 +37,7 @@ class PerIsolateData {
static PerIsolateData* For(v8::Isolate* isolate);

Nan::Global<v8::Function>& WallProfilerConstructor();
Nan::Global<v8::Function>& AllocationNodeConstructor();
std::shared_ptr<HeapProfilerState>& GetHeapProfilerState();
};

Expand Down
104 changes: 26 additions & 78 deletions bindings/profilers/heap.cc
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@

#include <node.h>
#include <v8-profiler.h>
#include "allocation-profile-node.hh"

namespace dd {

Expand Down Expand Up @@ -55,17 +56,6 @@ static size_t NearHeapLimit(void* data,
static void InterruptCallback(v8::Isolate* isolate, void* data);
static void AsyncCallback(uv_async_t* handle);

struct Node {
using Allocation = v8::AllocationProfile::Allocation;
std::string name;
std::string script_name;
int line_number;
int column_number;
int script_id;
std::vector<std::shared_ptr<Node>> children;
std::vector<Allocation> allocations;
};

enum CallbackMode {
kNoCallback = 0,
kAsyncCallback = 1,
Expand Down Expand Up @@ -139,73 +129,6 @@ struct HeapProfilerState {
bool insideCallback = false;
};

std::shared_ptr<Node> TranslateAllocationProfileToCpp(
v8::AllocationProfile::Node* node) {
auto new_node = std::make_shared<Node>();
new_node->line_number = node->line_number;
new_node->column_number = node->column_number;
new_node->script_id = node->script_id;
Nan::Utf8String name(node->name);
new_node->name.assign(*name, name.length());
Nan::Utf8String script_name(node->script_name);
new_node->script_name.assign(*script_name, script_name.length());

new_node->children.reserve(node->children.size());
for (auto& child : node->children) {
new_node->children.push_back(TranslateAllocationProfileToCpp(child));
}

new_node->allocations.reserve(node->allocations.size());
for (auto& allocation : node->allocations) {
new_node->allocations.push_back(allocation);
}
return new_node;
}

v8::Local<v8::Value> TranslateAllocationProfile(Node* node) {
v8::Local<v8::Object> js_node = Nan::New<v8::Object>();

Nan::Set(js_node,
Nan::New<v8::String>("name").ToLocalChecked(),
Nan::New(node->name).ToLocalChecked());
Nan::Set(js_node,
Nan::New<v8::String>("scriptName").ToLocalChecked(),
Nan::New(node->script_name).ToLocalChecked());
Nan::Set(js_node,
Nan::New<v8::String>("scriptId").ToLocalChecked(),
Nan::New<v8::Integer>(node->script_id));
Nan::Set(js_node,
Nan::New<v8::String>("lineNumber").ToLocalChecked(),
Nan::New<v8::Integer>(node->line_number));
Nan::Set(js_node,
Nan::New<v8::String>("columnNumber").ToLocalChecked(),
Nan::New<v8::Integer>(node->column_number));

v8::Local<v8::Array> children = Nan::New<v8::Array>(node->children.size());
for (size_t i = 0; i < node->children.size(); i++) {
Nan::Set(children, i, TranslateAllocationProfile(node->children[i].get()));
}
Nan::Set(
js_node, Nan::New<v8::String>("children").ToLocalChecked(), children);
v8::Local<v8::Array> allocations =
Nan::New<v8::Array>(node->allocations.size());
for (size_t i = 0; i < node->allocations.size(); i++) {
v8::AllocationProfile::Allocation alloc = node->allocations[i];
v8::Local<v8::Object> js_alloc = Nan::New<v8::Object>();
Nan::Set(js_alloc,
Nan::New<v8::String>("sizeBytes").ToLocalChecked(),
Nan::New<v8::Number>(alloc.size));
Nan::Set(js_alloc,
Nan::New<v8::String>("count").ToLocalChecked(),
Nan::New<v8::Number>(alloc.count));
Nan::Set(allocations, i, js_alloc);
}
Nan::Set(js_node,
Nan::New<v8::String>("allocations").ToLocalChecked(),
allocations);
return js_node;
}

static void dumpAllocationProfile(FILE* file,
Node* node,
std::string& cur_stack) {
Expand Down Expand Up @@ -582,6 +505,29 @@ NAN_METHOD(HeapProfiler::GetAllocationProfile) {
info.GetReturnValue().Set(TranslateAllocationProfile(root));
}

// getAllocationProfileV2(): ExternalAllocationNode
NAN_METHOD(HeapProfiler::GetAllocationProfileV2) {
auto isolate = info.GetIsolate();

std::unique_ptr<v8::AllocationProfile> profile(
isolate->GetHeapProfiler()->GetAllocationProfile());

if (!profile) {
return Nan::ThrowError("Heap profiler is not enabled.");
}

auto root_node =
TranslateAllocationProfileToExternal(isolate, profile->GetRootNode());

auto state = PerIsolateData::For(isolate)->GetHeapProfilerState();
if (state) {
state->OnNewProfile();
}

auto root = ExternalAllocationNode::New(root_node);
info.GetReturnValue().Set(root);
}

NAN_METHOD(HeapProfiler::MonitorOutOfMemory) {
if (info.Length() != 7) {
return Nan::ThrowTypeError("MonitorOOMCondition must have 7 arguments.");
Expand Down Expand Up @@ -645,6 +591,8 @@ NAN_MODULE_INIT(HeapProfiler::Init) {
Nan::SetMethod(
heapProfiler, "stopSamplingHeapProfiler", StopSamplingHeapProfiler);
Nan::SetMethod(heapProfiler, "getAllocationProfile", GetAllocationProfile);
Nan::SetMethod(
heapProfiler, "getAllocationProfileV2", GetAllocationProfileV2);
Nan::SetMethod(heapProfiler, "monitorOutOfMemory", MonitorOutOfMemory);
Nan::Set(target,
Nan::New<v8::String>("heapProfiler").ToLocalChecked(),
Expand Down
4 changes: 4 additions & 0 deletions bindings/profilers/heap.hh
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ class HeapProfiler {
// getAllocationProfile(): AllocationProfileNode
static NAN_METHOD(GetAllocationProfile);

// Signature:
// getAllocationProfileV2(): ExternalAllocationNode
static NAN_METHOD(GetAllocationProfileV2);

static NAN_METHOD(MonitorOutOfMemory);

static NAN_MODULE_INIT(Init);
Expand Down
Loading