• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
19 using v8::Array;
20 using v8::Boolean;
21 using v8::Context;
22 using v8::EmbedderGraph;
23 using v8::EscapableHandleScope;
24 using v8::FunctionCallbackInfo;
25 using v8::FunctionTemplate;
26 using v8::Global;
27 using v8::HandleScope;
28 using v8::HeapSnapshot;
29 using v8::Isolate;
30 using v8::JustVoid;
31 using v8::Local;
32 using v8::Maybe;
33 using v8::MaybeLocal;
34 using v8::Nothing;
35 using v8::Number;
36 using v8::Object;
37 using v8::ObjectTemplate;
38 using v8::String;
39 using v8::Value;
40 
41 namespace node {
42 namespace heap {
43 
44 class JSGraphJSNode : public EmbedderGraph::Node {
45  public:
Name()46   const char* Name() override { return "<JS Node>"; }
SizeInBytes()47   size_t SizeInBytes() override { return 0; }
IsEmbedderNode()48   bool IsEmbedderNode() override { return false; }
JSValue()49   Local<Value> JSValue() { return PersistentToLocal::Strong(persistent_); }
50 
IdentityHash()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 
JSGraphJSNode(Isolate * isolate,Local<Value> val)59   JSGraphJSNode(Isolate* isolate, Local<Value> val)
60       : persistent_(isolate, val) {
61     CHECK(!val.IsEmpty());
62   }
63 
64   struct Hash {
operator ()node::heap::JSGraphJSNode::Hash65     inline size_t operator()(JSGraphJSNode* n) const {
66       return static_cast<size_t>(n->IdentityHash());
67     }
68   };
69 
70   struct Equal {
operator ()node::heap::JSGraphJSNode::Equal71     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 
80 class JSGraph : public EmbedderGraph {
81  public:
JSGraph(Isolate * isolate)82   explicit JSGraph(Isolate* isolate) : isolate_(isolate) {}
83 
V8Node(const Local<Value> & value)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 
AddNode(std::unique_ptr<Node> node)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 
AddEdge(Node * from,Node * to,const char * name=nullptr)99   void AddEdge(Node* from, Node* to, const char* name = nullptr) override {
100     edges_[from].insert(std::make_pair(name, to));
101   }
102 
CreateObject() const103   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 
BuildEmbedderGraph(const FunctionCallbackInfo<Value> & args)211 void 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 
220 namespace {
221 class FileOutputStream : public v8::OutputStream {
222  public:
FileOutputStream(const int fd,uv_fs_t * req)223   FileOutputStream(const int fd, uv_fs_t* req) : fd_(fd), req_(req) {}
224 
GetChunkSize()225   int GetChunkSize() override {
226     return 65536;  // big chunks == faster
227   }
228 
EndOfStream()229   void EndOfStream() override {}
230 
WriteAsciiChunk(char * data,const int size)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 
status() const255   int status() const { return status_; }
256 
257  private:
258   const int fd_;
259   uv_fs_t* req_;
260   int status_ = 0;
261 };
262 
263 class HeapSnapshotStream : public AsyncWrap,
264                            public StreamBase,
265                            public v8::OutputStream {
266  public:
HeapSnapshotStream(Environment * env,HeapSnapshotPointer && snapshot,Local<Object> obj)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 
~HeapSnapshotStream()278   ~HeapSnapshotStream() override {}
279 
GetChunkSize()280   int GetChunkSize() override {
281     return 65536;  // big chunks == faster
282   }
283 
EndOfStream()284   void EndOfStream() override {
285     EmitRead(UV_EOF);
286     snapshot_.reset();
287   }
288 
WriteAsciiChunk(char * data,int size)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 
ReadStart()304   int ReadStart() override {
305     CHECK_NE(snapshot_, nullptr);
306     snapshot_->Serialize(this, HeapSnapshot::kJSON);
307     return 0;
308   }
309 
ReadStop()310   int ReadStop() override {
311     return 0;
312   }
313 
DoShutdown(ShutdownWrap * req_wrap)314   int DoShutdown(ShutdownWrap* req_wrap) override {
315     UNREACHABLE();
316   }
317 
DoWrite(WriteWrap * w,uv_buf_t * bufs,size_t count,uv_stream_t * send_handle)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 
IsAlive()325   bool IsAlive() override { return snapshot_ != nullptr; }
IsClosing()326   bool IsClosing() override { return snapshot_ == nullptr; }
GetAsyncWrap()327   AsyncWrap* GetAsyncWrap() override { return this; }
328 
MemoryInfo(MemoryTracker * tracker) const329   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 
TakeSnapshot(Environment * env,v8::OutputStream * out)343 inline 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 
WriteSnapshot(Environment * env,const char * filename)351 Maybe<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 
DeleteHeapSnapshot(const HeapSnapshot * snapshot)384 void DeleteHeapSnapshot(const HeapSnapshot* snapshot) {
385   const_cast<HeapSnapshot*>(snapshot)->Delete();
386 }
387 
CreateHeapSnapshotStream(Environment * env,HeapSnapshotPointer && snapshot)388 BaseObjectPtr<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 
CreateHeapSnapshotStream(const FunctionCallbackInfo<Value> & args)413 void 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 
TriggerHeapSnapshot(const FunctionCallbackInfo<Value> & args)424 void 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 
Initialize(Local<Object> target,Local<Value> unused,Local<Context> context,void * priv)447 void 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 
RegisterExternalReferences(ExternalReferenceRegistry * registry)457 void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
458   registry->Register(BuildEmbedderGraph);
459   registry->Register(TriggerHeapSnapshot);
460   registry->Register(CreateHeapSnapshotStream);
461 }
462 
463 }  // namespace heap
464 }  // namespace node
465 
466 NODE_BINDING_CONTEXT_AWARE_INTERNAL(heap_utils, node::heap::Initialize)
467 NODE_BINDING_EXTERNAL_REFERENCE(heap_utils,
468                                 node::heap::RegisterExternalReferences)
469