| #include "diagnosticfilename-inl.h" |
| #include "env-inl.h" |
| #include "memory_tracker-inl.h" |
| #include "stream_base-inl.h" |
| #include "util-inl.h" |
| |
| using v8::Array; |
| using v8::Boolean; |
| using v8::Context; |
| using v8::EmbedderGraph; |
| using v8::EscapableHandleScope; |
| using v8::FunctionCallbackInfo; |
| using v8::FunctionTemplate; |
| using v8::Global; |
| using v8::HandleScope; |
| using v8::HeapSnapshot; |
| using v8::Isolate; |
| using v8::Local; |
| using v8::MaybeLocal; |
| using v8::Number; |
| using v8::Object; |
| using v8::ObjectTemplate; |
| 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 PersistentToLocal::Strong(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 static_cast<size_t>(n->IdentityHash()); |
| } |
| }; |
| |
| struct Equal { |
| inline bool operator()(JSGraphJSNode* a, JSGraphJSNode* b) const { |
| return a->JSValue()->SameValue(b->JSValue()); |
| } |
| }; |
| |
| private: |
| Global<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, const char* name = nullptr) override { |
| edges_[from].insert(std::make_pair(name, to)); |
| } |
| |
| MaybeLocal<Array> CreateObject() const { |
| EscapableHandleScope handle_scope(isolate_); |
| Local<Context> context = isolate_->GetCurrentContext(); |
| Environment* env = Environment::GetCurrent(context); |
| |
| 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 = env->name_string(); |
| Local<String> size_string = env->size_string(); |
| Local<String> value_string = env->value_string(); |
| Local<String> wraps_string = FIXED_ONE_BYTE_STRING(isolate_, "wraps"); |
| Local<String> to_string = FIXED_ONE_BYTE_STRING(isolate_, "to"); |
| |
| 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; |
| 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()) |
| .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; |
| size_t j = 0; |
| for (const auto& edge : edge_info.second) { |
| Local<Object> to_object = info_objects[edge.second]; |
| Local<Object> edge_obj = Object::New(isolate_); |
| Local<Value> edge_name_value; |
| const char* edge_name = edge.first; |
| if (edge_name != nullptr) { |
| if (!String::NewFromUtf8(isolate_, edge_name) |
| .ToLocal(&edge_name_value)) { |
| return MaybeLocal<Array>(); |
| } |
| } 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<Array>()->Set(context, i++, edge_obj).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::set<std::pair<const char*, 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); |
| } |
| |
| namespace { |
| class FileOutputStream : public v8::OutputStream { |
| public: |
| explicit FileOutputStream(FILE* stream) : stream_(stream) {} |
| |
| int GetChunkSize() override { |
| return 65536; // big chunks == faster |
| } |
| |
| void EndOfStream() override {} |
| |
| WriteResult WriteAsciiChunk(char* data, int size) override { |
| const size_t len = static_cast<size_t>(size); |
| size_t off = 0; |
| |
| while (off < len && !feof(stream_) && !ferror(stream_)) |
| off += fwrite(data + off, 1, len - off, stream_); |
| |
| return off == len ? kContinue : kAbort; |
| } |
| |
| private: |
| FILE* stream_; |
| }; |
| |
| class HeapSnapshotStream : public AsyncWrap, |
| public StreamBase, |
| public v8::OutputStream { |
| public: |
| HeapSnapshotStream( |
| Environment* env, |
| HeapSnapshotPointer&& snapshot, |
| Local<Object> obj) : |
| AsyncWrap(env, obj, AsyncWrap::PROVIDER_HEAPSNAPSHOT), |
| StreamBase(env), |
| snapshot_(std::move(snapshot)) { |
| MakeWeak(); |
| StreamBase::AttachToObject(GetObject()); |
| } |
| |
| ~HeapSnapshotStream() override {} |
| |
| int GetChunkSize() override { |
| return 65536; // big chunks == faster |
| } |
| |
| void EndOfStream() override { |
| EmitRead(UV_EOF); |
| snapshot_.reset(); |
| } |
| |
| WriteResult WriteAsciiChunk(char* data, int size) override { |
| int len = size; |
| while (len != 0) { |
| uv_buf_t buf = EmitAlloc(size); |
| ssize_t avail = len; |
| if (static_cast<ssize_t>(buf.len) < avail) |
| avail = buf.len; |
| memcpy(buf.base, data, avail); |
| data += avail; |
| len -= avail; |
| EmitRead(size, buf); |
| } |
| return kContinue; |
| } |
| |
| int ReadStart() override { |
| CHECK_NE(snapshot_, nullptr); |
| snapshot_->Serialize(this, HeapSnapshot::kJSON); |
| return 0; |
| } |
| |
| int ReadStop() override { |
| return 0; |
| } |
| |
| int DoShutdown(ShutdownWrap* req_wrap) override { |
| UNREACHABLE(); |
| } |
| |
| int DoWrite(WriteWrap* w, |
| uv_buf_t* bufs, |
| size_t count, |
| uv_stream_t* send_handle) override { |
| UNREACHABLE(); |
| } |
| |
| bool IsAlive() override { return snapshot_ != nullptr; } |
| bool IsClosing() override { return snapshot_ == nullptr; } |
| AsyncWrap* GetAsyncWrap() override { return this; } |
| |
| void MemoryInfo(MemoryTracker* tracker) const override { |
| if (snapshot_ != nullptr) { |
| tracker->TrackFieldWithSize( |
| "snapshot", sizeof(*snapshot_), "HeapSnapshot"); |
| } |
| } |
| |
| SET_MEMORY_INFO_NAME(HeapSnapshotStream) |
| SET_SELF_SIZE(HeapSnapshotStream) |
| |
| private: |
| HeapSnapshotPointer snapshot_; |
| }; |
| |
| inline void TakeSnapshot(Isolate* isolate, v8::OutputStream* out) { |
| HeapSnapshotPointer snapshot { |
| isolate->GetHeapProfiler()->TakeHeapSnapshot() }; |
| snapshot->Serialize(out, HeapSnapshot::kJSON); |
| } |
| |
| inline bool WriteSnapshot(Isolate* isolate, const char* filename) { |
| FILE* fp = fopen(filename, "w"); |
| if (fp == nullptr) |
| return false; |
| FileOutputStream stream(fp); |
| TakeSnapshot(isolate, &stream); |
| fclose(fp); |
| return true; |
| } |
| |
| } // namespace |
| |
| void DeleteHeapSnapshot(const HeapSnapshot* snapshot) { |
| const_cast<HeapSnapshot*>(snapshot)->Delete(); |
| } |
| |
| BaseObjectPtr<AsyncWrap> CreateHeapSnapshotStream( |
| Environment* env, HeapSnapshotPointer&& snapshot) { |
| HandleScope scope(env->isolate()); |
| |
| if (env->streambaseoutputstream_constructor_template().IsEmpty()) { |
| // Create FunctionTemplate for HeapSnapshotStream |
| Local<FunctionTemplate> os = FunctionTemplate::New(env->isolate()); |
| os->Inherit(AsyncWrap::GetConstructorTemplate(env)); |
| Local<ObjectTemplate> ost = os->InstanceTemplate(); |
| ost->SetInternalFieldCount(StreamBase::kInternalFieldCount); |
| os->SetClassName( |
| FIXED_ONE_BYTE_STRING(env->isolate(), "HeapSnapshotStream")); |
| StreamBase::AddMethods(env, os); |
| env->set_streambaseoutputstream_constructor_template(ost); |
| } |
| |
| Local<Object> obj; |
| if (!env->streambaseoutputstream_constructor_template() |
| ->NewInstance(env->context()) |
| .ToLocal(&obj)) { |
| return {}; |
| } |
| return MakeBaseObject<HeapSnapshotStream>(env, std::move(snapshot), obj); |
| } |
| |
| void CreateHeapSnapshotStream(const FunctionCallbackInfo<Value>& args) { |
| Environment* env = Environment::GetCurrent(args); |
| HeapSnapshotPointer snapshot { |
| env->isolate()->GetHeapProfiler()->TakeHeapSnapshot() }; |
| CHECK(snapshot); |
| BaseObjectPtr<AsyncWrap> stream = |
| CreateHeapSnapshotStream(env, std::move(snapshot)); |
| if (stream) |
| args.GetReturnValue().Set(stream->object()); |
| } |
| |
| void TriggerHeapSnapshot(const FunctionCallbackInfo<Value>& args) { |
| Environment* env = Environment::GetCurrent(args); |
| Isolate* isolate = args.GetIsolate(); |
| |
| Local<Value> filename_v = args[0]; |
| |
| if (filename_v->IsUndefined()) { |
| DiagnosticFilename name(env, "Heap", "heapsnapshot"); |
| if (!WriteSnapshot(isolate, *name)) |
| return; |
| if (String::NewFromUtf8(isolate, *name).ToLocal(&filename_v)) { |
| args.GetReturnValue().Set(filename_v); |
| } |
| return; |
| } |
| |
| BufferValue path(isolate, filename_v); |
| CHECK_NOT_NULL(*path); |
| if (!WriteSnapshot(isolate, *path)) |
| return; |
| return args.GetReturnValue().Set(filename_v); |
| } |
| |
| void Initialize(Local<Object> target, |
| Local<Value> unused, |
| Local<Context> context, |
| void* priv) { |
| Environment* env = Environment::GetCurrent(context); |
| |
| env->SetMethod(target, "buildEmbedderGraph", BuildEmbedderGraph); |
| env->SetMethod(target, "triggerHeapSnapshot", TriggerHeapSnapshot); |
| env->SetMethod(target, "createHeapSnapshotStream", CreateHeapSnapshotStream); |
| } |
| |
| } // namespace heap |
| } // namespace node |
| |
| NODE_MODULE_CONTEXT_AWARE_INTERNAL(heap_utils, node::heap::Initialize) |