#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::JSON; 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 ""; } 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(), 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); } 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); 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, const HeapSnapshot* snapshot, v8::Local obj) : AsyncWrap(env, obj, AsyncWrap::PROVIDER_HEAPSNAPSHOT), StreamBase(env), snapshot_(snapshot) { MakeWeak(); StreamBase::AttachToObject(GetObject()); } ~HeapSnapshotStream() override { Cleanup(); } int GetChunkSize() override { return 65536; // big chunks == faster } void EndOfStream() override { EmitRead(UV_EOF); Cleanup(); } 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 -= 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: void Cleanup() { if (snapshot_ != nullptr) { const_cast(snapshot_)->Delete(); snapshot_ = nullptr; } } const HeapSnapshot* snapshot_; }; inline void TakeSnapshot(Isolate* isolate, v8::OutputStream* out) { const HeapSnapshot* const snapshot = isolate->GetHeapProfiler()->TakeHeapSnapshot(); snapshot->Serialize(out, HeapSnapshot::kJSON); const_cast(snapshot)->Delete(); } 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 CreateHeapSnapshotStream(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); HandleScope scope(env->isolate()); const HeapSnapshot* const snapshot = env->isolate()->GetHeapProfiler()->TakeHeapSnapshot(); CHECK_NOT_NULL(snapshot); Local obj; if (!env->streambaseoutputstream_constructor_template() ->NewInstance(env->context()) .ToLocal(&obj)) { return; } HeapSnapshotStream* out = new HeapSnapshotStream(env, snapshot, obj); args.GetReturnValue().Set(out->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(isolate, *name)) return; if (String::NewFromUtf8(isolate, *name, v8::NewStringType::kNormal) .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 target, Local unused, Local context, void* priv) { Environment* env = Environment::GetCurrent(context); env->SetMethodNoSideEffect(target, "buildEmbedderGraph", BuildEmbedderGraph); env->SetMethodNoSideEffect(target, "triggerHeapSnapshot", TriggerHeapSnapshot); env->SetMethodNoSideEffect(target, "createHeapSnapshotStream", CreateHeapSnapshotStream); // Create FunctionTemplate for HeapSnapshotStream Local os = FunctionTemplate::New(env->isolate()); os->Inherit(AsyncWrap::GetConstructorTemplate(env)); Local ost = os->InstanceTemplate(); ost->SetInternalFieldCount(StreamBase::kStreamBaseFieldCount); os->SetClassName(FIXED_ONE_BYTE_STRING(env->isolate(), "HeapSnapshotStream")); StreamBase::AddMethods(env, os); env->set_streambaseoutputstream_constructor_template(ost); } } // namespace heap } // namespace node NODE_MODULE_CONTEXT_AWARE_INTERNAL(heap_utils, node::heap::Initialize)