Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

src: use V8 graph heap snapshot API #21741

Closed
wants to merge 6 commits into from
Closed
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
2 changes: 1 addition & 1 deletion common.gypi
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@

# Reset this number to 0 on major V8 upgrades.
# Increment by one for each non-official patch applied to deps/v8.
'v8_embedder_string': '-node.14',
'v8_embedder_string': '-node.15',

# Enable disassembler for `--print-code` v8 options
'v8_enable_disassembler': 1,
Expand Down
22 changes: 17 additions & 5 deletions deps/v8/include/v8-profiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -636,7 +636,7 @@ class V8_EXPORT AllocationProfile {
* Usage:
* 1) Define derived class of EmbedderGraph::Node for embedder objects.
* 2) Set the build embedder graph callback on the heap profiler using
* HeapProfiler::SetBuildEmbedderGraphCallback.
* HeapProfiler::AddBuildEmbedderGraphCallback.
* 3) In the callback use graph->AddEdge(node1, node2) to add an edge from
* node1 to node2.
* 4) To represent references from/to V8 object, construct V8 nodes using
Expand Down Expand Up @@ -736,7 +736,12 @@ class V8_EXPORT HeapProfiler {
* The callback must not trigger garbage collection in V8.
*/
typedef void (*BuildEmbedderGraphCallback)(v8::Isolate* isolate,
v8::EmbedderGraph* graph);
v8::EmbedderGraph* graph,
void* data);

/** TODO(addaleax): Remove */
typedef void (*LegacyBuildEmbedderGraphCallback)(v8::Isolate* isolate,
v8::EmbedderGraph* graph);

/** Returns the number of snapshots taken. */
int GetSnapshotCount();
Expand Down Expand Up @@ -878,15 +883,22 @@ class V8_EXPORT HeapProfiler {

/** Binds a callback to embedder's class ID. */
V8_DEPRECATED(
"Use SetBuildEmbedderGraphCallback to provide info about embedder nodes",
"Use AddBuildEmbedderGraphCallback to provide info about embedder nodes",
void SetWrapperClassInfoProvider(uint16_t class_id,
WrapperInfoCallback callback));

V8_DEPRECATED(
"Use SetBuildEmbedderGraphCallback to provide info about embedder nodes",
"Use AddBuildEmbedderGraphCallback to provide info about embedder nodes",
void SetGetRetainerInfosCallback(GetRetainerInfosCallback callback));

void SetBuildEmbedderGraphCallback(BuildEmbedderGraphCallback callback);
V8_DEPRECATE_SOON(
"Use AddBuildEmbedderGraphCallback to provide info about embedder nodes",
void SetBuildEmbedderGraphCallback(
LegacyBuildEmbedderGraphCallback callback));
void AddBuildEmbedderGraphCallback(BuildEmbedderGraphCallback callback,
void* data);
void RemoveBuildEmbedderGraphCallback(BuildEmbedderGraphCallback callback,
void* data);

/**
* Default value of persistent handle class ID. Must not be used to
Expand Down
22 changes: 19 additions & 3 deletions deps/v8/src/api.cc
Original file line number Diff line number Diff line change
Expand Up @@ -10558,9 +10558,25 @@ void HeapProfiler::SetGetRetainerInfosCallback(
}

void HeapProfiler::SetBuildEmbedderGraphCallback(
BuildEmbedderGraphCallback callback) {
reinterpret_cast<i::HeapProfiler*>(this)->SetBuildEmbedderGraphCallback(
callback);
LegacyBuildEmbedderGraphCallback callback) {
reinterpret_cast<i::HeapProfiler*>(this)->AddBuildEmbedderGraphCallback(
[](v8::Isolate* isolate, v8::EmbedderGraph* graph, void* data) {
reinterpret_cast<LegacyBuildEmbedderGraphCallback>(data)(isolate,
graph);
},
reinterpret_cast<void*>(callback));
}

void HeapProfiler::AddBuildEmbedderGraphCallback(
BuildEmbedderGraphCallback callback, void* data) {
reinterpret_cast<i::HeapProfiler*>(this)->AddBuildEmbedderGraphCallback(
callback, data);
}

void HeapProfiler::RemoveBuildEmbedderGraphCallback(
BuildEmbedderGraphCallback callback, void* data) {
reinterpret_cast<i::HeapProfiler*>(this)->RemoveBuildEmbedderGraphCallback(
callback, data);
}

v8::Testing::StressType internal::Testing::stress_type_ =
Expand Down
21 changes: 15 additions & 6 deletions deps/v8/src/profiler/heap-profiler.cc
Original file line number Diff line number Diff line change
Expand Up @@ -69,16 +69,25 @@ v8::HeapProfiler::RetainerInfos HeapProfiler::GetRetainerInfos(
return infos;
}

void HeapProfiler::SetBuildEmbedderGraphCallback(
v8::HeapProfiler::BuildEmbedderGraphCallback callback) {
build_embedder_graph_callback_ = callback;
void HeapProfiler::AddBuildEmbedderGraphCallback(
v8::HeapProfiler::BuildEmbedderGraphCallback callback, void* data) {
build_embedder_graph_callbacks_.push_back({callback, data});
}

void HeapProfiler::RemoveBuildEmbedderGraphCallback(
v8::HeapProfiler::BuildEmbedderGraphCallback callback, void* data) {
auto it = std::find(build_embedder_graph_callbacks_.begin(),
build_embedder_graph_callbacks_.end(),
std::make_pair(callback, data));
if (it != build_embedder_graph_callbacks_.end())
build_embedder_graph_callbacks_.erase(it);
}

void HeapProfiler::BuildEmbedderGraph(Isolate* isolate,
v8::EmbedderGraph* graph) {
if (build_embedder_graph_callback_ != nullptr)
build_embedder_graph_callback_(reinterpret_cast<v8::Isolate*>(isolate),
graph);
for (const auto& cb : build_embedder_graph_callbacks_) {
cb.first(reinterpret_cast<v8::Isolate*>(isolate), graph, cb.second);
}
}

HeapSnapshot* HeapProfiler::TakeSnapshot(
Expand Down
12 changes: 7 additions & 5 deletions deps/v8/src/profiler/heap-profiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,13 @@ class HeapProfiler : public HeapObjectAllocationTracker {
v8::HeapProfiler::GetRetainerInfosCallback callback);
v8::HeapProfiler::RetainerInfos GetRetainerInfos(Isolate* isolate);

void SetBuildEmbedderGraphCallback(
v8::HeapProfiler::BuildEmbedderGraphCallback callback);
void AddBuildEmbedderGraphCallback(
v8::HeapProfiler::BuildEmbedderGraphCallback callback, void* data);
void RemoveBuildEmbedderGraphCallback(
v8::HeapProfiler::BuildEmbedderGraphCallback callback, void* data);
void BuildEmbedderGraph(Isolate* isolate, v8::EmbedderGraph* graph);
bool HasBuildEmbedderGraphCallback() {
return build_embedder_graph_callback_ != nullptr;
return !build_embedder_graph_callbacks_.empty();
}

bool is_tracking_object_moves() const { return is_tracking_object_moves_; }
Expand Down Expand Up @@ -103,8 +105,8 @@ class HeapProfiler : public HeapObjectAllocationTracker {
std::unique_ptr<SamplingHeapProfiler> sampling_heap_profiler_;
v8::HeapProfiler::GetRetainerInfosCallback get_retainer_infos_callback_ =
nullptr;
v8::HeapProfiler::BuildEmbedderGraphCallback build_embedder_graph_callback_ =
nullptr;
std::vector<std::pair<v8::HeapProfiler::BuildEmbedderGraphCallback, void*>>
build_embedder_graph_callbacks_;

DISALLOW_COPY_AND_ASSIGN(HeapProfiler);
};
Expand Down
104 changes: 93 additions & 11 deletions deps/v8/test/cctest/test-heap-profiler.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1541,8 +1541,8 @@ class EmbedderGraphBuilder : public v8::PersistentHandleVisitor {
graph->AddNode(std::unique_ptr<Group>(new Group("ccc-group")));
}

static void BuildEmbedderGraph(v8::Isolate* isolate,
v8::EmbedderGraph* graph) {
static void BuildEmbedderGraph(v8::Isolate* isolate, v8::EmbedderGraph* graph,
void* data) {
EmbedderGraphBuilder builder(isolate, graph);
isolate->VisitHandlesWithClassIds(&builder);
}
Expand Down Expand Up @@ -1604,8 +1604,8 @@ TEST(HeapSnapshotRetainedObjectInfo) {
v8::HandleScope scope(isolate);
v8::HeapProfiler* heap_profiler = isolate->GetHeapProfiler();

heap_profiler->SetBuildEmbedderGraphCallback(
EmbedderGraphBuilder::BuildEmbedderGraph);
heap_profiler->AddBuildEmbedderGraphCallback(
EmbedderGraphBuilder::BuildEmbedderGraph, nullptr);
v8::Persistent<v8::String> p_AAA(isolate, v8_str("AAA"));
p_AAA.SetWrapperClassId(1);
v8::Persistent<v8::String> p_BBB(isolate, v8_str("BBB"));
Expand Down Expand Up @@ -2932,7 +2932,8 @@ class EmbedderRootNode : public EmbedderNode {
// global object.
v8::Local<v8::Value>* global_object_pointer;

void BuildEmbedderGraph(v8::Isolate* v8_isolate, v8::EmbedderGraph* graph) {
void BuildEmbedderGraph(v8::Isolate* v8_isolate, v8::EmbedderGraph* graph,
void* data) {
using Node = v8::EmbedderGraph::Node;
Node* global_node = graph->V8Node(*global_object_pointer);
Node* embedder_node_A = graph->AddNode(
Expand Down Expand Up @@ -2979,12 +2980,92 @@ TEST(EmbedderGraph) {
(isolate->context()->native_context()->global_object())));
global_object_pointer = &global_object;
v8::HeapProfiler* heap_profiler = env->GetIsolate()->GetHeapProfiler();
heap_profiler->SetBuildEmbedderGraphCallback(BuildEmbedderGraph);
heap_profiler->AddBuildEmbedderGraphCallback(BuildEmbedderGraph, nullptr);
const v8::HeapSnapshot* snapshot = heap_profiler->TakeHeapSnapshot();
CHECK(ValidateSnapshot(snapshot));
CheckEmbedderGraphSnapshot(env->GetIsolate(), snapshot);
}

struct GraphBuildingContext {
int counter = 0;
};

void CheckEmbedderGraphSnapshotWithContext(
v8::Isolate* isolate, const v8::HeapSnapshot* snapshot,
const GraphBuildingContext* context) {
const v8::HeapGraphNode* global = GetGlobalObject(snapshot);
CHECK_GE(context->counter, 1);
CHECK_LE(context->counter, 2);

const v8::HeapGraphNode* embedder_node_A =
GetChildByName(global, "EmbedderNodeA");
CHECK_EQ(10, GetSize(embedder_node_A));

const v8::HeapGraphNode* embedder_node_B =
GetChildByName(global, "EmbedderNodeB");
if (context->counter == 2) {
CHECK_NOT_NULL(embedder_node_B);
CHECK_EQ(20, GetSize(embedder_node_B));
} else {
CHECK_NULL(embedder_node_B);
}
}

void BuildEmbedderGraphWithContext(v8::Isolate* v8_isolate,
v8::EmbedderGraph* graph, void* data) {
using Node = v8::EmbedderGraph::Node;
GraphBuildingContext* context = static_cast<GraphBuildingContext*>(data);
Node* global_node = graph->V8Node(*global_object_pointer);

CHECK_GE(context->counter, 0);
CHECK_LE(context->counter, 1);
switch (context->counter++) {
case 0: {
Node* embedder_node_A = graph->AddNode(
std::unique_ptr<Node>(new EmbedderNode("EmbedderNodeA", 10)));
graph->AddEdge(global_node, embedder_node_A);
break;
}
case 1: {
Node* embedder_node_B = graph->AddNode(
std::unique_ptr<Node>(new EmbedderNode("EmbedderNodeB", 20)));
graph->AddEdge(global_node, embedder_node_B);
break;
}
}
}

TEST(EmbedderGraphMultipleCallbacks) {
i::FLAG_heap_profiler_use_embedder_graph = true;
LocalContext env;
v8::HandleScope scope(env->GetIsolate());
i::Isolate* isolate = reinterpret_cast<i::Isolate*>(env->GetIsolate());
v8::Local<v8::Value> global_object =
v8::Utils::ToLocal(i::Handle<i::JSObject>(
(isolate->context()->native_context()->global_object())));
global_object_pointer = &global_object;
v8::HeapProfiler* heap_profiler = env->GetIsolate()->GetHeapProfiler();
GraphBuildingContext context;

heap_profiler->AddBuildEmbedderGraphCallback(BuildEmbedderGraphWithContext,
&context);
heap_profiler->AddBuildEmbedderGraphCallback(BuildEmbedderGraphWithContext,
&context);
const v8::HeapSnapshot* snapshot = heap_profiler->TakeHeapSnapshot();
CHECK_EQ(context.counter, 2);
CHECK(ValidateSnapshot(snapshot));
CheckEmbedderGraphSnapshotWithContext(env->GetIsolate(), snapshot, &context);

heap_profiler->RemoveBuildEmbedderGraphCallback(BuildEmbedderGraphWithContext,
&context);
context.counter = 0;

snapshot = heap_profiler->TakeHeapSnapshot();
CHECK_EQ(context.counter, 1);
CHECK(ValidateSnapshot(snapshot));
CheckEmbedderGraphSnapshotWithContext(env->GetIsolate(), snapshot, &context);
}

TEST(StrongHandleAnnotation) {
LocalContext env;
v8::HandleScope scope(env->GetIsolate());
Expand All @@ -3010,7 +3091,7 @@ TEST(StrongHandleAnnotation) {
}

void BuildEmbedderGraphWithWrapperNode(v8::Isolate* v8_isolate,
v8::EmbedderGraph* graph) {
v8::EmbedderGraph* graph, void* data) {
using Node = v8::EmbedderGraph::Node;
Node* global_node = graph->V8Node(*global_object_pointer);
Node* wrapper_node = graph->AddNode(
Expand Down Expand Up @@ -3041,8 +3122,8 @@ TEST(EmbedderGraphWithWrapperNode) {
(isolate->context()->native_context()->global_object())));
global_object_pointer = &global_object;
v8::HeapProfiler* heap_profiler = env->GetIsolate()->GetHeapProfiler();
heap_profiler->SetBuildEmbedderGraphCallback(
BuildEmbedderGraphWithWrapperNode);
heap_profiler->AddBuildEmbedderGraphCallback(
BuildEmbedderGraphWithWrapperNode, nullptr);
const v8::HeapSnapshot* snapshot = heap_profiler->TakeHeapSnapshot();
CHECK(ValidateSnapshot(snapshot));
const v8::HeapGraphNode* global = GetGlobalObject(snapshot);
Expand Down Expand Up @@ -3080,7 +3161,7 @@ class EmbedderNodeWithPrefix : public v8::EmbedderGraph::Node {
};

void BuildEmbedderGraphWithPrefix(v8::Isolate* v8_isolate,
v8::EmbedderGraph* graph) {
v8::EmbedderGraph* graph, void* data) {
using Node = v8::EmbedderGraph::Node;
Node* global_node = graph->V8Node(*global_object_pointer);
Node* node = graph->AddNode(
Expand All @@ -3098,7 +3179,8 @@ TEST(EmbedderGraphWithPrefix) {
(isolate->context()->native_context()->global_object())));
global_object_pointer = &global_object;
v8::HeapProfiler* heap_profiler = env->GetIsolate()->GetHeapProfiler();
heap_profiler->SetBuildEmbedderGraphCallback(BuildEmbedderGraphWithPrefix);
heap_profiler->AddBuildEmbedderGraphCallback(BuildEmbedderGraphWithPrefix,
nullptr);
const v8::HeapSnapshot* snapshot = heap_profiler->TakeHeapSnapshot();
CHECK(ValidateSnapshot(snapshot));
const v8::HeapGraphNode* global = GetGlobalObject(snapshot);
Expand Down
87 changes: 87 additions & 0 deletions lib/internal/test/heap.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
'use strict';

process.emitWarning(
'These APIs are exposed only for testing and are not ' +
'tracked by any versioning system or deprecation process.',
'internal/test/heap');

const { internalBinding } = require('internal/bootstrap/loaders');
const { createHeapDump, buildEmbedderGraph } = internalBinding('heap_utils');
const assert = require('assert');

// This is not suitable for production code. It creates a full V8 heap dump,
// parses it as JSON, and then creates complex objects from it, leading
// to significantly increased memory usage.
function createJSHeapDump() {
const dump = createHeapDump();
const meta = dump.snapshot.meta;

const nodes =
readHeapInfo(dump.nodes, meta.node_fields, meta.node_types, dump.strings);
const edges =
readHeapInfo(dump.edges, meta.edge_fields, meta.edge_types, dump.strings);

for (const node of nodes) {
node.incomingEdges = [];
node.outgoingEdges = [];
}

let fromNodeIndex = 0;
let edgeIndex = 0;
for (const { type, name_or_index, to_node } of edges) {
while (edgeIndex === nodes[fromNodeIndex].edge_count) {
edgeIndex = 0;
fromNodeIndex++;
}
const toNode = nodes[to_node / meta.node_fields.length];
const fromNode = nodes[fromNodeIndex];
const edge = {
type,
toNode,
fromNode,
name: typeof name_or_index === 'string' ? name_or_index : null
};
toNode.incomingEdges.push(edge);
fromNode.outgoingEdges.push(edge);
edgeIndex++;
}

for (const node of nodes)
assert.strictEqual(node.edge_count, node.outgoingEdges.length);

return nodes;
}

function readHeapInfo(raw, fields, types, strings) {
const items = [];

for (var i = 0; i < raw.length; i += fields.length) {
const item = {};
for (var j = 0; j < fields.length; j++) {
const name = fields[j];
let type = types[j];
if (Array.isArray(type)) {
item[name] = type[raw[i + j]];
} else if (name === 'name_or_index') { // type === 'string_or_number'
if (item.type === 'element' || item.type === 'hidden')
type = 'number';
else
type = 'string';
}

if (type === 'string') {
item[name] = strings[raw[i + j]];
} else if (type === 'number' || type === 'node') {
item[name] = raw[i + j];
}
}
items.push(item);
}

return items;
}

module.exports = {
createJSHeapDump,
buildEmbedderGraph
};
Loading