xref: /third_party/node/src/heap_utils.cc (revision 1cb0ef41)
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