#include "diagnosticfilename-inl.h" #include "env-inl.h" #include "memory_tracker-inl.h" #include "node_external_reference.h" #include "stream_base-inl.h" #include "util-inl.h" // Copied from https://github.com/nodejs/node/blob/b07dc4d19fdbc15b4f76557dc45b3ce3a43ad0c3/src/util.cc#L36-L41. #ifdef _WIN32 #include // _S_IREAD _S_IWRITE #ifndef S_IRUSR #define S_IRUSR _S_IREAD #endif // S_IRUSR #ifndef S_IWUSR #define S_IWUSR _S_IWRITE #endif // S_IWUSR #endif 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::JustVoid; using v8::Local; using v8::Maybe; using v8::MaybeLocal; using v8::Nothing; 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 ""; } 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: Global 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(); Environment* env = Environment::GetCurrent(context); 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 = env->name_string(); Local size_string = env->size_string(); Local value_string = env->value_string(); 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()).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_, static_cast(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) .ToLocal(&edge_name_value)) { return MaybeLocal(); } } else { edge_name_value = Number::New(isolate_, static_cast(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); } namespace { class FileOutputStream : public v8::OutputStream { public: FileOutputStream(const int fd, uv_fs_t* req) : fd_(fd), req_(req) {} int GetChunkSize() override { return 65536; // big chunks == faster } void EndOfStream() override {} WriteResult WriteAsciiChunk(char* data, const int size) override { DCHECK_EQ(status_, 0); int offset = 0; while (offset < size) { const uv_buf_t buf = uv_buf_init(data + offset, size - offset); const int num_bytes_written = uv_fs_write(nullptr, req_, fd_, &buf, 1, -1, nullptr); uv_fs_req_cleanup(req_); if (num_bytes_written < 0) { status_ = num_bytes_written; return kAbort; } DCHECK_LE(static_cast(num_bytes_written), buf.len); offset += num_bytes_written; } DCHECK_EQ(offset, size); return kContinue; } int status() const { return status_; } private: const int fd_; uv_fs_t* req_; int status_ = 0; }; class HeapSnapshotStream : public AsyncWrap, public StreamBase, public v8::OutputStream { public: HeapSnapshotStream( Environment* env, HeapSnapshotPointer&& snapshot, Local 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(buf.len) < avail) avail = buf.len; memcpy(buf.base, data, avail); data += avail; len -= static_cast(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(Environment* env, v8::OutputStream* out) { HeapSnapshotPointer snapshot { env->isolate()->GetHeapProfiler()->TakeHeapSnapshot() }; snapshot->Serialize(out, HeapSnapshot::kJSON); } } // namespace Maybe WriteSnapshot(Environment* env, const char* filename) { uv_fs_t req; int err; const int fd = uv_fs_open(nullptr, &req, filename, O_WRONLY | O_CREAT | O_TRUNC, S_IWUSR | S_IRUSR, nullptr); uv_fs_req_cleanup(&req); if ((err = fd) < 0) { env->ThrowUVException(err, "open", nullptr, filename); return Nothing(); } FileOutputStream stream(fd, &req); TakeSnapshot(env, &stream); if ((err = stream.status()) < 0) { env->ThrowUVException(err, "write", nullptr, filename); return Nothing(); } err = uv_fs_close(nullptr, &req, fd, nullptr); uv_fs_req_cleanup(&req); if (err < 0) { env->ThrowUVException(err, "close", nullptr, filename); return Nothing(); } return JustVoid(); } void DeleteHeapSnapshot(const HeapSnapshot* snapshot) { const_cast(snapshot)->Delete(); } BaseObjectPtr CreateHeapSnapshotStream( Environment* env, HeapSnapshotPointer&& snapshot) { HandleScope scope(env->isolate()); if (env->streambaseoutputstream_constructor_template().IsEmpty()) { // Create FunctionTemplate for HeapSnapshotStream Local os = FunctionTemplate::New(env->isolate()); os->Inherit(AsyncWrap::GetConstructorTemplate(env)); Local 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 obj; if (!env->streambaseoutputstream_constructor_template() ->NewInstance(env->context()) .ToLocal(&obj)) { return {}; } return MakeBaseObject(env, std::move(snapshot), obj); } void CreateHeapSnapshotStream(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); HeapSnapshotPointer snapshot { env->isolate()->GetHeapProfiler()->TakeHeapSnapshot() }; CHECK(snapshot); BaseObjectPtr stream = CreateHeapSnapshotStream(env, std::move(snapshot)); if (stream) args.GetReturnValue().Set(stream->object()); } void TriggerHeapSnapshot(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); Isolate* isolate = args.GetIsolate(); Local filename_v = args[0]; if (filename_v->IsUndefined()) { DiagnosticFilename name(env, "Heap", "heapsnapshot"); if (WriteSnapshot(env, *name).IsNothing()) 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(env, *path).IsNothing()) return; return args.GetReturnValue().Set(filename_v); } void Initialize(Local target, Local unused, Local context, void* priv) { Environment* env = Environment::GetCurrent(context); env->SetMethod(target, "buildEmbedderGraph", BuildEmbedderGraph); env->SetMethod(target, "triggerHeapSnapshot", TriggerHeapSnapshot); env->SetMethod(target, "createHeapSnapshotStream", CreateHeapSnapshotStream); } void RegisterExternalReferences(ExternalReferenceRegistry* registry) { registry->Register(BuildEmbedderGraph); registry->Register(TriggerHeapSnapshot); registry->Register(CreateHeapSnapshotStream); } } // namespace heap } // namespace node NODE_MODULE_CONTEXT_AWARE_INTERNAL(heap_utils, node::heap::Initialize) NODE_MODULE_EXTERNAL_REFERENCE(heap_utils, node::heap::RegisterExternalReferences)