FEATURE: add support for heap dumps

FEATURE: add support for heap dumps

This new feature allows you to dump memory inside v8, it can help find memory leaks etc

Usage is:

context.write_heap_snapshot(path)
diff --git a/ext/mini_racer_extension/mini_racer_extension.cc b/ext/mini_racer_extension/mini_racer_extension.cc
index dad8f20..688bce0 100644
--- a/ext/mini_racer_extension/mini_racer_extension.cc
+++ b/ext/mini_racer_extension/mini_racer_extension.cc
@@ -1,7 +1,9 @@
 #include <stdio.h>
 #include <ruby.h>
 #include <ruby/thread.h>
+#include <ruby/io.h>
 #include <v8.h>
+#include <v8-profiler.h>
 #include <libplatform/libplatform.h>
 #include <ruby/encoding.h>
 #include <pthread.h>
@@ -1315,6 +1317,69 @@ rb_heap_stats(VALUE self) {
     return rval;
 }
 
+// https://github.com/bnoordhuis/node-heapdump/blob/master/src/heapdump.cc
+class FileOutputStream : public OutputStream {
+ public:
+  FileOutputStream(FILE* stream) : stream_(stream) {}
+
+  virtual int GetChunkSize() {
+    return 65536;
+  }
+
+  virtual void EndOfStream() {}
+
+  virtual WriteResult WriteAsciiChunk(char* data, int size) {
+    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_;
+};
+
+
+static VALUE
+rb_heap_snapshot(VALUE self, VALUE file) {
+
+    rb_io_t *fptr;
+
+    fptr = RFILE(file)->fptr;
+
+    if (!fptr) return Qfalse;
+
+    FILE* fp;
+    fp = fdopen(fptr->fd, "w");
+    if (fp == NULL) return Qfalse;
+
+
+    ContextInfo* context_info;
+    Data_Get_Struct(self, ContextInfo, context_info);
+    Isolate* isolate;
+    isolate = context_info->isolate_info ? context_info->isolate_info->isolate : NULL;
+
+    if (!isolate) return Qfalse;
+
+    Locker lock(isolate);
+    Isolate::Scope isolate_scope(isolate);
+    HandleScope handle_scope(isolate);
+
+    HeapProfiler* heap_profiler = isolate->GetHeapProfiler();
+
+    const HeapSnapshot* const snap = heap_profiler->TakeHeapSnapshot();
+
+    FileOutputStream stream(fp);
+    snap->Serialize(&stream, HeapSnapshot::kJSON);
+
+    const_cast<HeapSnapshot*>(snap)->Delete();
+
+    return Qtrue;
+}
+
 static VALUE
 rb_context_stop(VALUE self) {
 
@@ -1500,6 +1565,8 @@ extern "C" {
         rb_define_method(rb_cContext, "stop", (VALUE(*)(...))&rb_context_stop, 0);
         rb_define_method(rb_cContext, "dispose_unsafe", (VALUE(*)(...))&rb_context_dispose, 0);
         rb_define_method(rb_cContext, "heap_stats", (VALUE(*)(...))&rb_heap_stats, 0);
+	rb_define_method(rb_cContext, "write_heap_snapshot_unsafe", (VALUE(*)(...))&rb_heap_snapshot, 1);
+
         rb_define_private_method(rb_cContext, "create_isolate_value",(VALUE(*)(...))&rb_context_create_isolate_value, 0);
         rb_define_private_method(rb_cContext, "eval_unsafe",(VALUE(*)(...))&rb_context_eval_unsafe, 2);
         rb_define_private_method(rb_cContext, "call_unsafe", (VALUE(*)(...))&rb_context_call_unsafe, -1);
diff --git a/lib/mini_racer.rb b/lib/mini_racer.rb
index 4524a5a..4f15b65 100644
--- a/lib/mini_racer.rb
+++ b/lib/mini_racer.rb
@@ -167,6 +167,28 @@ module MiniRacer
       eval(File.read(filename))
     end
 
+    def write_heap_snapshot(file_or_io)
+      f = nil
+      implicit = false
+
+
+      if String === file_or_io
+        f = File.open(file_or_io, "w")
+        implicit = true
+      else
+        f = file_or_io
+      end
+
+      if !(File === f)
+        raise ArgumentError("file_or_io")
+      end
+
+      write_heap_snapshot_unsafe(f)
+
+    ensure
+      f.close if implicit
+    end
+
     def eval(str, options=nil)
       raise(ContextDisposedError, 'attempted to call eval on a disposed context!') if @disposed
 
diff --git a/test/mini_racer_test.rb b/test/mini_racer_test.rb
index 0655810..7a57511 100644
--- a/test/mini_racer_test.rb
+++ b/test/mini_racer_test.rb
@@ -755,4 +755,22 @@ raise FooError, "I like foos"
     context.dispose
     assert_nil context.isolate
   end
+
+  def test_heap_dump
+
+    f = Tempfile.new("heap")
+    path = f.path
+    f.unlink
+
+    context = MiniRacer::Context.new
+    context.eval('let x = 1000;')
+    context.write_heap_snapshot(path)
+
+    dump = File.read(path)
+
+    assert dump.length > 0
+
+    FileUtils.rm(path)
+
+  end
 end

GitHub sha: 7b836f79