#include "env-inl.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 ""; } size_t SizeInBytes() override { return 0; } bool IsEmbedderNode() override { return false; } Local JSValue() { return PersistentToLocal::Strong(persistent_); } int IdentityHash() { Local v = JSValue(); if (v->IsObject()) return v.As()->GetIdentityHash(); if (v->IsName()) return v.As()->GetIdentityHash(); if (v->IsInt32()) return v.As()->Value(); return 0; } JSGraphJSNode(Isolate* isolate, Local val) : persistent_(isolate, val) { CHECK(!val.IsEmpty()); } struct Hash { inline size_t operator()(JSGraphJSNode* n) const { return static_cast(n->IdentityHash()); } }; struct Equal { inline bool operator()(JSGraphJSNode* a, JSGraphJSNode* b) const { return a->JSValue()->SameValue(b->JSValue()); } }; private: Persistent persistent_; }; class JSGraph : public EmbedderGraph { public: explicit JSGraph(Isolate* isolate) : isolate_(isolate) {} Node* V8Node(const Local& value) override { std::unique_ptr 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(n.release())); } Node* AddNode(std::unique_ptr node) override { Node* n = node.get(); nodes_.emplace(std::move(node)); return n; } void AddEdge(Node* from, Node* to, const char* name = nullptr) override { edges_[from].insert(std::make_pair(name, to)); } MaybeLocal CreateObject() const { EscapableHandleScope handle_scope(isolate_); Local context = isolate_->GetCurrentContext(); std::unordered_map> info_objects; Local nodes = Array::New(isolate_, nodes_.size()); Local edges_string = FIXED_ONE_BYTE_STRING(isolate_, "edges"); Local is_root_string = FIXED_ONE_BYTE_STRING(isolate_, "isRoot"); Local name_string = FIXED_ONE_BYTE_STRING(isolate_, "name"); Local size_string = FIXED_ONE_BYTE_STRING(isolate_, "size"); Local value_string = FIXED_ONE_BYTE_STRING(isolate_, "value"); Local wraps_string = FIXED_ONE_BYTE_STRING(isolate_, "wraps"); Local to_string = FIXED_ONE_BYTE_STRING(isolate_, "to"); for (const std::unique_ptr& n : nodes_) info_objects[n.get()] = Object::New(isolate_); { HandleScope handle_scope(isolate_); size_t i = 0; for (const std::unique_ptr& n : nodes_) { Local obj = info_objects[n.get()]; Local value; std::string name_str; const char* prefix = n->NamePrefix(); if (prefix == nullptr) { name_str = n->Name(); } else { name_str = n->NamePrefix(); name_str += " "; name_str += n->Name(); } if (!String::NewFromUtf8( isolate_, name_str.c_str(), 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(); } if (nodes->Set(context, i++, obj).IsNothing()) return MaybeLocal(); if (!n->IsEmbedderNode()) { value = static_cast(n.get())->JSValue(); if (obj->Set(context, value_string, value).IsNothing()) return MaybeLocal(); } } } for (const std::unique_ptr& n : nodes_) { Node* wraps = n->WrapperNode(); if (wraps == nullptr) continue; Local from = info_objects[n.get()]; Local to = info_objects[wraps]; if (from->Set(context, wraps_string, to).IsNothing()) return MaybeLocal(); } for (const auto& edge_info : edges_) { Node* source = edge_info.first; Local edges; if (!info_objects[source]->Get(context, edges_string).ToLocal(&edges) || !edges->IsArray()) { return MaybeLocal(); } size_t i = 0; size_t j = 0; for (const auto& edge : edge_info.second) { Local to_object = info_objects[edge.second]; Local edge_obj = Object::New(isolate_); Local edge_name_value; const char* edge_name = edge.first; if (edge_name != nullptr) { if (!String::NewFromUtf8( isolate_, edge_name, v8::NewStringType::kNormal) .ToLocal(&edge_name_value)) { return MaybeLocal(); } } else { edge_name_value = Number::New(isolate_, j++); } if (edge_obj->Set(context, name_string, edge_name_value).IsNothing() || edge_obj->Set(context, to_string, to_object).IsNothing() || edges.As()->Set(context, i++, edge_obj).IsNothing()) { return MaybeLocal(); } } } return handle_scope.Escape(nodes); } private: Isolate* isolate_; std::unordered_set> nodes_; std::unordered_set engine_nodes_; std::unordered_map>> edges_; }; void BuildEmbedderGraph(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); JSGraph graph(env->isolate()); Environment::BuildEmbedderGraph(env->isolate(), &graph, env); Local 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 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 buffer_; }; void CreateHeapDump(const FunctionCallbackInfo& args) { Isolate* isolate = args.GetIsolate(); const HeapSnapshot* snapshot = isolate->GetHeapProfiler()->TakeHeapSnapshot(); BufferOutputStream out; snapshot->Serialize(&out, HeapSnapshot::kJSON); const_cast(snapshot)->Delete(); Local ret; if (JSON::Parse(isolate->GetCurrentContext(), out.ToString(isolate)).ToLocal(&ret)) { args.GetReturnValue().Set(ret); } } void Initialize(Local target, Local unused, Local context, void* priv) { 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)