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