enclose-io/compiler

View on GitHub
lts/src/heap_utils.cc

Summary

Maintainability
Test Coverage
#include "diagnosticfilename-inl.h"
#include "env-inl.h"
#include "memory_tracker-inl.h"
#include "stream_base-inl.h"
#include "util-inl.h"

using v8::Array;
using v8::Boolean;
using v8::Context;
using v8::EmbedderGraph;
using v8::EscapableHandleScope;
using v8::FunctionCallbackInfo;
using v8::FunctionTemplate;
using v8::Global;
using v8::HandleScope;
using v8::HeapSnapshot;
using v8::Isolate;
using v8::Local;
using v8::MaybeLocal;
using v8::Number;
using v8::Object;
using v8::ObjectTemplate;
using v8::String;
using v8::Value;

namespace node {
namespace heap {

class JSGraphJSNode : public EmbedderGraph::Node {
 public:
  const char* Name() override { return "<JS Node>"; }
  size_t SizeInBytes() override { return 0; }
  bool IsEmbedderNode() override { return false; }
  Local<Value> JSValue() { return PersistentToLocal::Strong(persistent_); }

  int IdentityHash() {
    Local<Value> v = JSValue();
    if (v->IsObject()) return v.As<Object>()->GetIdentityHash();
    if (v->IsName()) return v.As<v8::Name>()->GetIdentityHash();
    if (v->IsInt32()) return v.As<v8::Int32>()->Value();
    return 0;
  }

  JSGraphJSNode(Isolate* isolate, Local<Value> val)
      : persistent_(isolate, val) {
    CHECK(!val.IsEmpty());
  }

  struct Hash {
    inline size_t operator()(JSGraphJSNode* n) const {
      return static_cast<size_t>(n->IdentityHash());
    }
  };

  struct Equal {
    inline bool operator()(JSGraphJSNode* a, JSGraphJSNode* b) const {
      return a->JSValue()->SameValue(b->JSValue());
    }
  };

 private:
  Global<Value> persistent_;
};

class JSGraph : public EmbedderGraph {
 public:
  explicit JSGraph(Isolate* isolate) : isolate_(isolate) {}

  Node* V8Node(const Local<Value>& value) override {
    std::unique_ptr<JSGraphJSNode> n { new JSGraphJSNode(isolate_, value) };
    auto it = engine_nodes_.find(n.get());
    if (it != engine_nodes_.end())
      return *it;
    engine_nodes_.insert(n.get());
    return AddNode(std::unique_ptr<Node>(n.release()));
  }

  Node* AddNode(std::unique_ptr<Node> node) override {
    Node* n = node.get();
    nodes_.emplace(std::move(node));
    return n;
  }

  void AddEdge(Node* from, Node* to, const char* name = nullptr) override {
    edges_[from].insert(std::make_pair(name, to));
  }

  MaybeLocal<Array> CreateObject() const {
    EscapableHandleScope handle_scope(isolate_);
    Local<Context> context = isolate_->GetCurrentContext();
    Environment* env = Environment::GetCurrent(context);

    std::unordered_map<Node*, Local<Object>> info_objects;
    Local<Array> nodes = Array::New(isolate_, nodes_.size());
    Local<String> edges_string = FIXED_ONE_BYTE_STRING(isolate_, "edges");
    Local<String> is_root_string = FIXED_ONE_BYTE_STRING(isolate_, "isRoot");
    Local<String> name_string = env->name_string();
    Local<String> size_string = env->size_string();
    Local<String> value_string = env->value_string();
    Local<String> wraps_string = FIXED_ONE_BYTE_STRING(isolate_, "wraps");
    Local<String> to_string = FIXED_ONE_BYTE_STRING(isolate_, "to");

    for (const std::unique_ptr<Node>& n : nodes_)
      info_objects[n.get()] = Object::New(isolate_);

    {
      HandleScope handle_scope(isolate_);
      size_t i = 0;
      for (const std::unique_ptr<Node>& n : nodes_) {
        Local<Object> obj = info_objects[n.get()];
        Local<Value> value;
        std::string name_str;
        const char* prefix = n->NamePrefix();
        if (prefix == nullptr) {
          name_str = n->Name();
        } else {
          name_str = n->NamePrefix();
          name_str += " ";
          name_str += n->Name();
        }
        if (!String::NewFromUtf8(
                 isolate_, name_str.c_str(), v8::NewStringType::kNormal)
                 .ToLocal(&value) ||
            obj->Set(context, name_string, value).IsNothing() ||
            obj->Set(context,
                     is_root_string,
                     Boolean::New(isolate_, n->IsRootNode()))
                .IsNothing() ||
            obj->Set(context,
                     size_string,
                     Number::New(isolate_, n->SizeInBytes()))
                .IsNothing() ||
            obj->Set(context, edges_string, Array::New(isolate_)).IsNothing()) {
          return MaybeLocal<Array>();
        }
        if (nodes->Set(context, i++, obj).IsNothing())
          return MaybeLocal<Array>();
        if (!n->IsEmbedderNode()) {
          value = static_cast<JSGraphJSNode*>(n.get())->JSValue();
          if (obj->Set(context, value_string, value).IsNothing())
            return MaybeLocal<Array>();
        }
      }
    }

    for (const std::unique_ptr<Node>& n : nodes_) {
      Node* wraps = n->WrapperNode();
      if (wraps == nullptr) continue;
      Local<Object> from = info_objects[n.get()];
      Local<Object> to = info_objects[wraps];
      if (from->Set(context, wraps_string, to).IsNothing())
        return MaybeLocal<Array>();
    }

    for (const auto& edge_info : edges_) {
      Node* source = edge_info.first;
      Local<Value> edges;
      if (!info_objects[source]->Get(context, edges_string).ToLocal(&edges) ||
          !edges->IsArray()) {
        return MaybeLocal<Array>();
      }

      size_t i = 0;
      size_t j = 0;
      for (const auto& edge : edge_info.second) {
        Local<Object> to_object = info_objects[edge.second];
        Local<Object> edge_obj = Object::New(isolate_);
        Local<Value> edge_name_value;
        const char* edge_name = edge.first;
        if (edge_name != nullptr) {
          if (!String::NewFromUtf8(
                  isolate_, edge_name, v8::NewStringType::kNormal)
                  .ToLocal(&edge_name_value)) {
            return MaybeLocal<Array>();
          }
        } else {
          edge_name_value = Number::New(isolate_, j++);
        }
        if (edge_obj->Set(context, name_string, edge_name_value).IsNothing() ||
            edge_obj->Set(context, to_string, to_object).IsNothing() ||
            edges.As<Array>()->Set(context, i++, edge_obj).IsNothing()) {
          return MaybeLocal<Array>();
        }
      }
    }

    return handle_scope.Escape(nodes);
  }

 private:
  Isolate* isolate_;
  std::unordered_set<std::unique_ptr<Node>> nodes_;
  std::unordered_set<JSGraphJSNode*, JSGraphJSNode::Hash, JSGraphJSNode::Equal>
      engine_nodes_;
  std::unordered_map<Node*, std::set<std::pair<const char*, Node*>>> edges_;
};

void BuildEmbedderGraph(const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);
  JSGraph graph(env->isolate());
  Environment::BuildEmbedderGraph(env->isolate(), &graph, env);
  Local<Array> ret;
  if (graph.CreateObject().ToLocal(&ret))
    args.GetReturnValue().Set(ret);
}

namespace {
class FileOutputStream : public v8::OutputStream {
 public:
  explicit FileOutputStream(FILE* stream) : stream_(stream) {}

  int GetChunkSize() override {
    return 65536;  // big chunks == faster
  }

  void EndOfStream() override {}

  WriteResult WriteAsciiChunk(char* data, int size) override {
    const size_t len = static_cast<size_t>(size);
    size_t off = 0;

    while (off < len && !feof(stream_) && !ferror(stream_))
      off += fwrite(data + off, 1, len - off, stream_);

    return off == len ? kContinue : kAbort;
  }

 private:
  FILE* stream_;
};

class HeapSnapshotStream : public AsyncWrap,
                           public StreamBase,
                           public v8::OutputStream {
 public:
  HeapSnapshotStream(
      Environment* env,
      HeapSnapshotPointer&& snapshot,
      Local<Object> obj) :
      AsyncWrap(env, obj, AsyncWrap::PROVIDER_HEAPSNAPSHOT),
      StreamBase(env),
      snapshot_(std::move(snapshot)) {
    MakeWeak();
    StreamBase::AttachToObject(GetObject());
  }

  ~HeapSnapshotStream() override {}

  int GetChunkSize() override {
    return 65536;  // big chunks == faster
  }

  void EndOfStream() override {
    EmitRead(UV_EOF);
    snapshot_.reset();
  }

  WriteResult WriteAsciiChunk(char* data, int size) override {
    int len = size;
    while (len != 0) {
      uv_buf_t buf = EmitAlloc(size);
      ssize_t avail = len;
      if (static_cast<ssize_t>(buf.len) < avail)
        avail = buf.len;
      memcpy(buf.base, data, avail);
      data += avail;
      len -= avail;
      EmitRead(size, buf);
    }
    return kContinue;
  }

  int ReadStart() override {
    CHECK_NE(snapshot_, nullptr);
    snapshot_->Serialize(this, HeapSnapshot::kJSON);
    return 0;
  }

  int ReadStop() override {
    return 0;
  }

  int DoShutdown(ShutdownWrap* req_wrap) override {
    UNREACHABLE();
  }

  int DoWrite(WriteWrap* w,
              uv_buf_t* bufs,
              size_t count,
              uv_stream_t* send_handle) override {
    UNREACHABLE();
  }

  bool IsAlive() override { return snapshot_ != nullptr; }
  bool IsClosing() override { return snapshot_ == nullptr; }
  AsyncWrap* GetAsyncWrap() override { return this; }

  void MemoryInfo(MemoryTracker* tracker) const override {
    if (snapshot_ != nullptr) {
      tracker->TrackFieldWithSize(
          "snapshot", sizeof(*snapshot_), "HeapSnapshot");
    }
  }

  SET_MEMORY_INFO_NAME(HeapSnapshotStream)
  SET_SELF_SIZE(HeapSnapshotStream)

 private:
  HeapSnapshotPointer snapshot_;
};

inline void TakeSnapshot(Isolate* isolate, v8::OutputStream* out) {
  HeapSnapshotPointer snapshot {
      isolate->GetHeapProfiler()->TakeHeapSnapshot() };
  snapshot->Serialize(out, HeapSnapshot::kJSON);
}

inline bool WriteSnapshot(Isolate* isolate, const char* filename) {
  FILE* fp = fopen(filename, "w");
  if (fp == nullptr)
    return false;
  FileOutputStream stream(fp);
  TakeSnapshot(isolate, &stream);
  fclose(fp);
  return true;
}

}  // namespace

void DeleteHeapSnapshot(const HeapSnapshot* snapshot) {
  const_cast<HeapSnapshot*>(snapshot)->Delete();
}

BaseObjectPtr<AsyncWrap> CreateHeapSnapshotStream(
    Environment* env, HeapSnapshotPointer&& snapshot) {
  HandleScope scope(env->isolate());

  if (env->streambaseoutputstream_constructor_template().IsEmpty()) {
    // Create FunctionTemplate for HeapSnapshotStream
    Local<FunctionTemplate> os = FunctionTemplate::New(env->isolate());
    os->Inherit(AsyncWrap::GetConstructorTemplate(env));
    Local<ObjectTemplate> ost = os->InstanceTemplate();
    ost->SetInternalFieldCount(StreamBase::kInternalFieldCount);
    os->SetClassName(
        FIXED_ONE_BYTE_STRING(env->isolate(), "HeapSnapshotStream"));
    StreamBase::AddMethods(env, os);
    env->set_streambaseoutputstream_constructor_template(ost);
  }

  Local<Object> obj;
  if (!env->streambaseoutputstream_constructor_template()
           ->NewInstance(env->context())
           .ToLocal(&obj)) {
    return {};
  }
  return MakeBaseObject<HeapSnapshotStream>(env, std::move(snapshot), obj);
}

void CreateHeapSnapshotStream(const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);
  HeapSnapshotPointer snapshot {
      env->isolate()->GetHeapProfiler()->TakeHeapSnapshot() };
  CHECK(snapshot);
  BaseObjectPtr<AsyncWrap> stream =
      CreateHeapSnapshotStream(env, std::move(snapshot));
  if (stream)
    args.GetReturnValue().Set(stream->object());
}

void TriggerHeapSnapshot(const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);
  Isolate* isolate = args.GetIsolate();

  Local<Value> filename_v = args[0];

  if (filename_v->IsUndefined()) {
    DiagnosticFilename name(env, "Heap", "heapsnapshot");
    if (!WriteSnapshot(isolate, *name))
      return;
    if (String::NewFromUtf8(isolate, *name, v8::NewStringType::kNormal)
            .ToLocal(&filename_v)) {
      args.GetReturnValue().Set(filename_v);
    }
    return;
  }

  BufferValue path(isolate, filename_v);
  CHECK_NOT_NULL(*path);
  if (!WriteSnapshot(isolate, *path))
    return;
  return args.GetReturnValue().Set(filename_v);
}

void Initialize(Local<Object> target,
                Local<Value> unused,
                Local<Context> context,
                void* priv) {
  Environment* env = Environment::GetCurrent(context);

  env->SetMethod(target, "buildEmbedderGraph", BuildEmbedderGraph);
  env->SetMethod(target, "triggerHeapSnapshot", TriggerHeapSnapshot);
  env->SetMethod(target, "createHeapSnapshotStream", CreateHeapSnapshotStream);
}

}  // namespace heap
}  // namespace node

NODE_MODULE_CONTEXT_AWARE_INTERNAL(heap_utils, node::heap::Initialize)