1#include "diagnosticfilename-inl.h" 2#include "env-inl.h" 3#include "memory_tracker-inl.h" 4#include "node_external_reference.h" 5#include "stream_base-inl.h" 6#include "util-inl.h" 7 8// Copied from https://github.com/nodejs/node/blob/b07dc4d19fdbc15b4f76557dc45b3ce3a43ad0c3/src/util.cc#L36-L41. 9#ifdef _WIN32 10#include <io.h> // _S_IREAD _S_IWRITE 11#ifndef S_IRUSR 12#define S_IRUSR _S_IREAD 13#endif // S_IRUSR 14#ifndef S_IWUSR 15#define S_IWUSR _S_IWRITE 16#endif // S_IWUSR 17#endif 18 19using v8::Array; 20using v8::Boolean; 21using v8::Context; 22using v8::EmbedderGraph; 23using v8::EscapableHandleScope; 24using v8::FunctionCallbackInfo; 25using v8::FunctionTemplate; 26using v8::Global; 27using v8::HandleScope; 28using v8::HeapSnapshot; 29using v8::Isolate; 30using v8::JustVoid; 31using v8::Local; 32using v8::Maybe; 33using v8::MaybeLocal; 34using v8::Nothing; 35using v8::Number; 36using v8::Object; 37using v8::ObjectTemplate; 38using v8::String; 39using v8::Value; 40 41namespace node { 42namespace heap { 43 44class JSGraphJSNode : public EmbedderGraph::Node { 45 public: 46 const char* Name() override { return "<JS Node>"; } 47 size_t SizeInBytes() override { return 0; } 48 bool IsEmbedderNode() override { return false; } 49 Local<Value> JSValue() { return PersistentToLocal::Strong(persistent_); } 50 51 int IdentityHash() { 52 Local<Value> v = JSValue(); 53 if (v->IsObject()) return v.As<Object>()->GetIdentityHash(); 54 if (v->IsName()) return v.As<v8::Name>()->GetIdentityHash(); 55 if (v->IsInt32()) return v.As<v8::Int32>()->Value(); 56 return 0; 57 } 58 59 JSGraphJSNode(Isolate* isolate, Local<Value> val) 60 : persistent_(isolate, val) { 61 CHECK(!val.IsEmpty()); 62 } 63 64 struct Hash { 65 inline size_t operator()(JSGraphJSNode* n) const { 66 return static_cast<size_t>(n->IdentityHash()); 67 } 68 }; 69 70 struct Equal { 71 inline bool operator()(JSGraphJSNode* a, JSGraphJSNode* b) const { 72 return a->JSValue()->SameValue(b->JSValue()); 73 } 74 }; 75 76 private: 77 Global<Value> persistent_; 78}; 79 80class JSGraph : public EmbedderGraph { 81 public: 82 explicit JSGraph(Isolate* isolate) : isolate_(isolate) {} 83 84 Node* V8Node(const Local<Value>& value) override { 85 std::unique_ptr<JSGraphJSNode> n { new JSGraphJSNode(isolate_, value) }; 86 auto it = engine_nodes_.find(n.get()); 87 if (it != engine_nodes_.end()) 88 return *it; 89 engine_nodes_.insert(n.get()); 90 return AddNode(std::unique_ptr<Node>(n.release())); 91 } 92 93 Node* AddNode(std::unique_ptr<Node> node) override { 94 Node* n = node.get(); 95 nodes_.emplace(std::move(node)); 96 return n; 97 } 98 99 void AddEdge(Node* from, Node* to, const char* name = nullptr) override { 100 edges_[from].insert(std::make_pair(name, to)); 101 } 102 103 MaybeLocal<Array> CreateObject() const { 104 EscapableHandleScope handle_scope(isolate_); 105 Local<Context> context = isolate_->GetCurrentContext(); 106 Environment* env = Environment::GetCurrent(context); 107 108 std::unordered_map<Node*, Local<Object>> info_objects; 109 Local<Array> nodes = Array::New(isolate_, nodes_.size()); 110 Local<String> edges_string = FIXED_ONE_BYTE_STRING(isolate_, "edges"); 111 Local<String> is_root_string = FIXED_ONE_BYTE_STRING(isolate_, "isRoot"); 112 Local<String> name_string = env->name_string(); 113 Local<String> size_string = env->size_string(); 114 Local<String> value_string = env->value_string(); 115 Local<String> wraps_string = FIXED_ONE_BYTE_STRING(isolate_, "wraps"); 116 Local<String> to_string = FIXED_ONE_BYTE_STRING(isolate_, "to"); 117 118 for (const std::unique_ptr<Node>& n : nodes_) 119 info_objects[n.get()] = Object::New(isolate_); 120 121 { 122 HandleScope handle_scope(isolate_); 123 size_t i = 0; 124 for (const std::unique_ptr<Node>& n : nodes_) { 125 Local<Object> obj = info_objects[n.get()]; 126 Local<Value> value; 127 std::string name_str; 128 const char* prefix = n->NamePrefix(); 129 if (prefix == nullptr) { 130 name_str = n->Name(); 131 } else { 132 name_str = n->NamePrefix(); 133 name_str += " "; 134 name_str += n->Name(); 135 } 136 if (!String::NewFromUtf8(isolate_, name_str.c_str()).ToLocal(&value) || 137 obj->Set(context, name_string, value).IsNothing() || 138 obj->Set(context, 139 is_root_string, 140 Boolean::New(isolate_, n->IsRootNode())) 141 .IsNothing() || 142 obj->Set( 143 context, 144 size_string, 145 Number::New(isolate_, static_cast<double>(n->SizeInBytes()))) 146 .IsNothing() || 147 obj->Set(context, edges_string, Array::New(isolate_)).IsNothing()) { 148 return MaybeLocal<Array>(); 149 } 150 if (nodes->Set(context, i++, obj).IsNothing()) 151 return MaybeLocal<Array>(); 152 if (!n->IsEmbedderNode()) { 153 value = static_cast<JSGraphJSNode*>(n.get())->JSValue(); 154 if (obj->Set(context, value_string, value).IsNothing()) 155 return MaybeLocal<Array>(); 156 } 157 } 158 } 159 160 for (const std::unique_ptr<Node>& n : nodes_) { 161 Node* wraps = n->WrapperNode(); 162 if (wraps == nullptr) continue; 163 Local<Object> from = info_objects[n.get()]; 164 Local<Object> to = info_objects[wraps]; 165 if (from->Set(context, wraps_string, to).IsNothing()) 166 return MaybeLocal<Array>(); 167 } 168 169 for (const auto& edge_info : edges_) { 170 Node* source = edge_info.first; 171 Local<Value> edges; 172 if (!info_objects[source]->Get(context, edges_string).ToLocal(&edges) || 173 !edges->IsArray()) { 174 return MaybeLocal<Array>(); 175 } 176 177 size_t i = 0; 178 size_t j = 0; 179 for (const auto& edge : edge_info.second) { 180 Local<Object> to_object = info_objects[edge.second]; 181 Local<Object> edge_obj = Object::New(isolate_); 182 Local<Value> edge_name_value; 183 const char* edge_name = edge.first; 184 if (edge_name != nullptr) { 185 if (!String::NewFromUtf8(isolate_, edge_name) 186 .ToLocal(&edge_name_value)) { 187 return MaybeLocal<Array>(); 188 } 189 } else { 190 edge_name_value = Number::New(isolate_, static_cast<double>(j++)); 191 } 192 if (edge_obj->Set(context, name_string, edge_name_value).IsNothing() || 193 edge_obj->Set(context, to_string, to_object).IsNothing() || 194 edges.As<Array>()->Set(context, i++, edge_obj).IsNothing()) { 195 return MaybeLocal<Array>(); 196 } 197 } 198 } 199 200 return handle_scope.Escape(nodes); 201 } 202 203 private: 204 Isolate* isolate_; 205 std::unordered_set<std::unique_ptr<Node>> nodes_; 206 std::unordered_set<JSGraphJSNode*, JSGraphJSNode::Hash, JSGraphJSNode::Equal> 207 engine_nodes_; 208 std::unordered_map<Node*, std::set<std::pair<const char*, Node*>>> edges_; 209}; 210 211void BuildEmbedderGraph(const FunctionCallbackInfo<Value>& args) { 212 Environment* env = Environment::GetCurrent(args); 213 JSGraph graph(env->isolate()); 214 Environment::BuildEmbedderGraph(env->isolate(), &graph, env); 215 Local<Array> ret; 216 if (graph.CreateObject().ToLocal(&ret)) 217 args.GetReturnValue().Set(ret); 218} 219 220namespace { 221class FileOutputStream : public v8::OutputStream { 222 public: 223 FileOutputStream(const int fd, uv_fs_t* req) : fd_(fd), req_(req) {} 224 225 int GetChunkSize() override { 226 return 65536; // big chunks == faster 227 } 228 229 void EndOfStream() override {} 230 231 WriteResult WriteAsciiChunk(char* data, const int size) override { 232 DCHECK_EQ(status_, 0); 233 int offset = 0; 234 while (offset < size) { 235 const uv_buf_t buf = uv_buf_init(data + offset, size - offset); 236 const int num_bytes_written = uv_fs_write(nullptr, 237 req_, 238 fd_, 239 &buf, 240 1, 241 -1, 242 nullptr); 243 uv_fs_req_cleanup(req_); 244 if (num_bytes_written < 0) { 245 status_ = num_bytes_written; 246 return kAbort; 247 } 248 DCHECK_LE(static_cast<size_t>(num_bytes_written), buf.len); 249 offset += num_bytes_written; 250 } 251 DCHECK_EQ(offset, size); 252 return kContinue; 253 } 254 255 int status() const { return status_; } 256 257 private: 258 const int fd_; 259 uv_fs_t* req_; 260 int status_ = 0; 261}; 262 263class HeapSnapshotStream : public AsyncWrap, 264 public StreamBase, 265 public v8::OutputStream { 266 public: 267 HeapSnapshotStream( 268 Environment* env, 269 HeapSnapshotPointer&& snapshot, 270 Local<Object> obj) : 271 AsyncWrap(env, obj, AsyncWrap::PROVIDER_HEAPSNAPSHOT), 272 StreamBase(env), 273 snapshot_(std::move(snapshot)) { 274 MakeWeak(); 275 StreamBase::AttachToObject(GetObject()); 276 } 277 278 ~HeapSnapshotStream() override {} 279 280 int GetChunkSize() override { 281 return 65536; // big chunks == faster 282 } 283 284 void EndOfStream() override { 285 EmitRead(UV_EOF); 286 snapshot_.reset(); 287 } 288 289 WriteResult WriteAsciiChunk(char* data, int size) override { 290 int len = size; 291 while (len != 0) { 292 uv_buf_t buf = EmitAlloc(size); 293 ssize_t avail = len; 294 if (static_cast<ssize_t>(buf.len) < avail) 295 avail = buf.len; 296 memcpy(buf.base, data, avail); 297 data += avail; 298 len -= static_cast<int>(avail); 299 EmitRead(size, buf); 300 } 301 return kContinue; 302 } 303 304 int ReadStart() override { 305 CHECK_NE(snapshot_, nullptr); 306 snapshot_->Serialize(this, HeapSnapshot::kJSON); 307 return 0; 308 } 309 310 int ReadStop() override { 311 return 0; 312 } 313 314 int DoShutdown(ShutdownWrap* req_wrap) override { 315 UNREACHABLE(); 316 } 317 318 int DoWrite(WriteWrap* w, 319 uv_buf_t* bufs, 320 size_t count, 321 uv_stream_t* send_handle) override { 322 UNREACHABLE(); 323 } 324 325 bool IsAlive() override { return snapshot_ != nullptr; } 326 bool IsClosing() override { return snapshot_ == nullptr; } 327 AsyncWrap* GetAsyncWrap() override { return this; } 328 329 void MemoryInfo(MemoryTracker* tracker) const override { 330 if (snapshot_ != nullptr) { 331 tracker->TrackFieldWithSize( 332 "snapshot", sizeof(*snapshot_), "HeapSnapshot"); 333 } 334 } 335 336 SET_MEMORY_INFO_NAME(HeapSnapshotStream) 337 SET_SELF_SIZE(HeapSnapshotStream) 338 339 private: 340 HeapSnapshotPointer snapshot_; 341}; 342 343inline void TakeSnapshot(Environment* env, v8::OutputStream* out) { 344 HeapSnapshotPointer snapshot { 345 env->isolate()->GetHeapProfiler()->TakeHeapSnapshot() }; 346 snapshot->Serialize(out, HeapSnapshot::kJSON); 347} 348 349} // namespace 350 351Maybe<void> WriteSnapshot(Environment* env, const char* filename) { 352 uv_fs_t req; 353 int err; 354 355 const int fd = uv_fs_open(nullptr, 356 &req, 357 filename, 358 O_WRONLY | O_CREAT | O_TRUNC, 359 S_IWUSR | S_IRUSR, 360 nullptr); 361 uv_fs_req_cleanup(&req); 362 if ((err = fd) < 0) { 363 env->ThrowUVException(err, "open", nullptr, filename); 364 return Nothing<void>(); 365 } 366 367 FileOutputStream stream(fd, &req); 368 TakeSnapshot(env, &stream); 369 if ((err = stream.status()) < 0) { 370 env->ThrowUVException(err, "write", nullptr, filename); 371 return Nothing<void>(); 372 } 373 374 err = uv_fs_close(nullptr, &req, fd, nullptr); 375 uv_fs_req_cleanup(&req); 376 if (err < 0) { 377 env->ThrowUVException(err, "close", nullptr, filename); 378 return Nothing<void>(); 379 } 380 381 return JustVoid(); 382} 383 384void DeleteHeapSnapshot(const HeapSnapshot* snapshot) { 385 const_cast<HeapSnapshot*>(snapshot)->Delete(); 386} 387 388BaseObjectPtr<AsyncWrap> CreateHeapSnapshotStream( 389 Environment* env, HeapSnapshotPointer&& snapshot) { 390 HandleScope scope(env->isolate()); 391 392 if (env->streambaseoutputstream_constructor_template().IsEmpty()) { 393 // Create FunctionTemplate for HeapSnapshotStream 394 Local<FunctionTemplate> os = FunctionTemplate::New(env->isolate()); 395 os->Inherit(AsyncWrap::GetConstructorTemplate(env)); 396 Local<ObjectTemplate> ost = os->InstanceTemplate(); 397 ost->SetInternalFieldCount(StreamBase::kInternalFieldCount); 398 os->SetClassName( 399 FIXED_ONE_BYTE_STRING(env->isolate(), "HeapSnapshotStream")); 400 StreamBase::AddMethods(env, os); 401 env->set_streambaseoutputstream_constructor_template(ost); 402 } 403 404 Local<Object> obj; 405 if (!env->streambaseoutputstream_constructor_template() 406 ->NewInstance(env->context()) 407 .ToLocal(&obj)) { 408 return {}; 409 } 410 return MakeBaseObject<HeapSnapshotStream>(env, std::move(snapshot), obj); 411} 412 413void CreateHeapSnapshotStream(const FunctionCallbackInfo<Value>& args) { 414 Environment* env = Environment::GetCurrent(args); 415 HeapSnapshotPointer snapshot { 416 env->isolate()->GetHeapProfiler()->TakeHeapSnapshot() }; 417 CHECK(snapshot); 418 BaseObjectPtr<AsyncWrap> stream = 419 CreateHeapSnapshotStream(env, std::move(snapshot)); 420 if (stream) 421 args.GetReturnValue().Set(stream->object()); 422} 423 424void TriggerHeapSnapshot(const FunctionCallbackInfo<Value>& args) { 425 Environment* env = Environment::GetCurrent(args); 426 Isolate* isolate = args.GetIsolate(); 427 428 Local<Value> filename_v = args[0]; 429 430 if (filename_v->IsUndefined()) { 431 DiagnosticFilename name(env, "Heap", "heapsnapshot"); 432 if (WriteSnapshot(env, *name).IsNothing()) 433 return; 434 if (String::NewFromUtf8(isolate, *name).ToLocal(&filename_v)) { 435 args.GetReturnValue().Set(filename_v); 436 } 437 return; 438 } 439 440 BufferValue path(isolate, filename_v); 441 CHECK_NOT_NULL(*path); 442 if (WriteSnapshot(env, *path).IsNothing()) 443 return; 444 return args.GetReturnValue().Set(filename_v); 445} 446 447void Initialize(Local<Object> target, 448 Local<Value> unused, 449 Local<Context> context, 450 void* priv) { 451 SetMethod(context, target, "buildEmbedderGraph", BuildEmbedderGraph); 452 SetMethod(context, target, "triggerHeapSnapshot", TriggerHeapSnapshot); 453 SetMethod( 454 context, target, "createHeapSnapshotStream", CreateHeapSnapshotStream); 455} 456 457void RegisterExternalReferences(ExternalReferenceRegistry* registry) { 458 registry->Register(BuildEmbedderGraph); 459 registry->Register(TriggerHeapSnapshot); 460 registry->Register(CreateHeapSnapshotStream); 461} 462 463} // namespace heap 464} // namespace node 465 466NODE_BINDING_CONTEXT_AWARE_INTERNAL(heap_utils, node::heap::Initialize) 467NODE_BINDING_EXTERNAL_REFERENCE(heap_utils, 468 node::heap::RegisterExternalReferences) 469