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