summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAnna Henningsen <anna@addaleax.net>2018-07-01 01:27:09 +0200
committerAnna Henningsen <anna@addaleax.net>2018-07-15 20:35:41 +0200
commit45ad8df318390b54ea6cb54b4b4b320875f9c88f (patch)
treedf48dc0d6595cf726fe604a1133284ad79aad931
parent266a7e62585b975f2ea2d25473b21395f5ca5a3f (diff)
downloadandroid-node-v8-45ad8df318390b54ea6cb54b4b4b320875f9c88f.tar.gz
android-node-v8-45ad8df318390b54ea6cb54b4b4b320875f9c88f.tar.bz2
android-node-v8-45ad8df318390b54ea6cb54b4b4b320875f9c88f.zip
src: make heap snapshot & embedder graph accessible for tests
Add methods that allow inspection of heap snapshots and a JS version of our own embedder graph. These can be used in tests and might also prove useful for ad-hoc debugging. Usage requires `--expose-internals` and prints a warning similar to our other modules whose primary purpose is test support. PR-URL: https://github.com/nodejs/node/pull/21741 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com> Reviewed-By: Refael Ackermann <refack@gmail.com>
-rw-r--r--lib/internal/test/heap.js87
-rw-r--r--node.gyp2
-rw-r--r--src/heap_utils.cc232
-rw-r--r--src/node_internals.h1
4 files changed, 322 insertions, 0 deletions
diff --git a/lib/internal/test/heap.js b/lib/internal/test/heap.js
new file mode 100644
index 0000000000..a9260f651b
--- /dev/null
+++ b/lib/internal/test/heap.js
@@ -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
+};
diff --git a/node.gyp b/node.gyp
index d3bebba8ce..75288114a8 100644
--- a/node.gyp
+++ b/node.gyp
@@ -148,6 +148,7 @@
'lib/internal/repl/await.js',
'lib/internal/socket_list.js',
'lib/internal/test/binding.js',
+ 'lib/internal/test/heap.js',
'lib/internal/test/unicode.js',
'lib/internal/timers.js',
'lib/internal/tls.js',
@@ -330,6 +331,7 @@
'src/exceptions.cc',
'src/fs_event_wrap.cc',
'src/handle_wrap.cc',
+ 'src/heap_utils.cc',
'src/js_stream.cc',
'src/module_wrap.cc',
'src/node.cc',
diff --git a/src/heap_utils.cc b/src/heap_utils.cc
new file mode 100644
index 0000000000..2d339c580f
--- /dev/null
+++ b/src/heap_utils.cc
@@ -0,0 +1,232 @@
+#include "node_internals.h"
+#include "env.h"
+
+using v8::Array;
+using v8::Boolean;
+using v8::Context;
+using v8::EmbedderGraph;
+using v8::EscapableHandleScope;
+using v8::FunctionCallbackInfo;
+using v8::HandleScope;
+using v8::HeapSnapshot;
+using v8::Isolate;
+using v8::JSON;
+using v8::Local;
+using v8::MaybeLocal;
+using v8::Number;
+using v8::Object;
+using v8::String;
+using v8::Value;
+
+namespace node {
+namespace heap {
+
+class JSGraphJSNode : public EmbedderGraph::Node {
+ public:
+ const char* Name() override { return "<JS Node>"; }
+ size_t SizeInBytes() override { return 0; }
+ bool IsEmbedderNode() override { return false; }
+ Local<Value> JSValue() { return StrongPersistentToLocal(persistent_); }
+
+ int IdentityHash() {
+ Local<Value> v = JSValue();
+ if (v->IsObject()) return v.As<Object>()->GetIdentityHash();
+ if (v->IsName()) return v.As<v8::Name>()->GetIdentityHash();
+ if (v->IsInt32()) return v.As<v8::Int32>()->Value();
+ return 0;
+ }
+
+ JSGraphJSNode(Isolate* isolate, Local<Value> val)
+ : persistent_(isolate, val) {
+ CHECK(!val.IsEmpty());
+ }
+
+ struct Hash {
+ inline size_t operator()(JSGraphJSNode* n) const {
+ return n->IdentityHash();
+ }
+ };
+
+ struct Equal {
+ inline bool operator()(JSGraphJSNode* a, JSGraphJSNode* b) const {
+ return a->JSValue()->SameValue(b->JSValue());
+ }
+ };
+
+ private:
+ Persistent<Value> persistent_;
+};
+
+class JSGraph : public EmbedderGraph {
+ public:
+ explicit JSGraph(Isolate* isolate) : isolate_(isolate) {}
+
+ Node* V8Node(const Local<Value>& value) override {
+ std::unique_ptr<JSGraphJSNode> n { new JSGraphJSNode(isolate_, value) };
+ auto it = engine_nodes_.find(n.get());
+ if (it != engine_nodes_.end())
+ return *it;
+ engine_nodes_.insert(n.get());
+ return AddNode(std::unique_ptr<Node>(n.release()));
+ }
+
+ Node* AddNode(std::unique_ptr<Node> node) override {
+ Node* n = node.get();
+ nodes_.emplace(std::move(node));
+ return n;
+ }
+
+ void AddEdge(Node* from, Node* to) override {
+ edges_[from].insert(to);
+ }
+
+ MaybeLocal<Array> CreateObject() const {
+ EscapableHandleScope handle_scope(isolate_);
+ Local<Context> context = isolate_->GetCurrentContext();
+
+ std::unordered_map<Node*, Local<Object>> info_objects;
+ Local<Array> nodes = Array::New(isolate_, nodes_.size());
+ Local<String> edges_string = FIXED_ONE_BYTE_STRING(isolate_, "edges");
+ Local<String> is_root_string = FIXED_ONE_BYTE_STRING(isolate_, "isRoot");
+ Local<String> name_string = FIXED_ONE_BYTE_STRING(isolate_, "name");
+ Local<String> size_string = FIXED_ONE_BYTE_STRING(isolate_, "size");
+ Local<String> value_string = FIXED_ONE_BYTE_STRING(isolate_, "value");
+ Local<String> wraps_string = FIXED_ONE_BYTE_STRING(isolate_, "wraps");
+
+ for (const std::unique_ptr<Node>& n : nodes_)
+ info_objects[n.get()] = Object::New(isolate_);
+
+ {
+ HandleScope handle_scope(isolate_);
+ size_t i = 0;
+ 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) ||
+ 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()) {
+ return MaybeLocal<Array>();
+ }
+ if (nodes->Set(context, i++, obj).IsNothing())
+ return MaybeLocal<Array>();
+ if (!n->IsEmbedderNode()) {
+ value = static_cast<JSGraphJSNode*>(n.get())->JSValue();
+ if (obj->Set(context, value_string, value).IsNothing())
+ return MaybeLocal<Array>();
+ }
+ }
+ }
+
+ for (const std::unique_ptr<Node>& n : nodes_) {
+ Node* wraps = n->WrapperNode();
+ if (wraps == nullptr) continue;
+ Local<Object> from = info_objects[n.get()];
+ Local<Object> to = info_objects[wraps];
+ if (from->Set(context, wraps_string, to).IsNothing())
+ return MaybeLocal<Array>();
+ }
+
+ for (const auto& edge_info : edges_) {
+ Node* source = edge_info.first;
+ Local<Value> edges;
+ if (!info_objects[source]->Get(context, edges_string).ToLocal(&edges) ||
+ !edges->IsArray()) {
+ return MaybeLocal<Array>();
+ }
+
+ size_t i = 0;
+ for (Node* target : edge_info.second) {
+ if (edges.As<Array>()->Set(context,
+ i++,
+ info_objects[target]).IsNothing()) {
+ return MaybeLocal<Array>();
+ }
+ }
+ }
+
+ return handle_scope.Escape(nodes);
+ }
+
+ private:
+ Isolate* isolate_;
+ std::unordered_set<std::unique_ptr<Node>> nodes_;
+ std::unordered_set<JSGraphJSNode*, JSGraphJSNode::Hash, JSGraphJSNode::Equal>
+ engine_nodes_;
+ std::unordered_map<Node*, std::unordered_set<Node*>> edges_;
+};
+
+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);
+}
+
+
+class BufferOutputStream : public v8::OutputStream {
+ public:
+ BufferOutputStream() : buffer_(new JSString()) {}
+
+ void EndOfStream() override {}
+ int GetChunkSize() override { return 1024 * 1024; }
+ WriteResult WriteAsciiChunk(char* data, int size) override {
+ buffer_->Append(data, size);
+ return kContinue;
+ }
+
+ Local<String> ToString(Isolate* isolate) {
+ return String::NewExternalOneByte(isolate,
+ buffer_.release()).ToLocalChecked();
+ }
+
+ private:
+ class JSString : public String::ExternalOneByteStringResource {
+ public:
+ void Append(char* data, size_t count) {
+ store_.append(data, count);
+ }
+
+ const char* data() const override { return store_.data(); }
+ size_t length() const override { return store_.size(); }
+
+ private:
+ std::string store_;
+ };
+
+ std::unique_ptr<JSString> buffer_;
+};
+
+void CreateHeapDump(const FunctionCallbackInfo<Value>& args) {
+ Isolate* isolate = args.GetIsolate();
+ const HeapSnapshot* snapshot = isolate->GetHeapProfiler()->TakeHeapSnapshot();
+ BufferOutputStream out;
+ snapshot->Serialize(&out, HeapSnapshot::kJSON);
+ const_cast<HeapSnapshot*>(snapshot)->Delete();
+ Local<Value> ret;
+ if (JSON::Parse(isolate->GetCurrentContext(),
+ out.ToString(isolate)).ToLocal(&ret)) {
+ args.GetReturnValue().Set(ret);
+ }
+}
+
+void Initialize(Local<Object> target,
+ Local<Value> unused,
+ Local<Context> context) {
+ Environment* env = Environment::GetCurrent(context);
+
+ env->SetMethodNoSideEffect(target, "buildEmbedderGraph", BuildEmbedderGraph);
+ env->SetMethodNoSideEffect(target, "createHeapDump", CreateHeapDump);
+}
+
+} // namespace heap
+} // namespace node
+
+NODE_MODULE_CONTEXT_AWARE_INTERNAL(heap_utils, node::heap::Initialize)
diff --git a/src/node_internals.h b/src/node_internals.h
index 860566e314..f29812e26e 100644
--- a/src/node_internals.h
+++ b/src/node_internals.h
@@ -111,6 +111,7 @@ struct sockaddr;
V(domain) \
V(fs) \
V(fs_event_wrap) \
+ V(heap_utils) \
V(http2) \
V(http_parser) \
V(inspector) \