Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Add ObjectSpace.heap_dump to objspace.so #423

Closed
wants to merge 14 commits into from

2 participants

@tmm1

In large ruby applications it is quite tricky to track down the cause of increased memory usage, especially when a reference leak is present (a global/root object holding references to an increasing number of objects over time).

This patch adds a ObjectSpace.heap_dump to objspace.so. This new method dumps out the contents of the ruby heap as json, either to stdout or an optional file.

ObjectSpace.trace_object_allocations_start
GC.start
ObjectSpace.heap_dump

root objects

Objects referenced by roots are listed in ROOT entries:

{"type":"ROOT", "root":"vm", "references":["0x7f82f28d0640"]}
{"type":"ROOT", "root":"global_list", "references":["0x7f82f2833638", "0x7f82f282bf78"]}
{"type":"ROOT", "root":"global_tbl", "references":["0x7f82f28db428", "0x7f82f28db428", "0x7f82f28dac08", "0x7f82f28dab90", "0x7f82f28dab18", "0x7f82f28dab90", "0x7f82f28da898", "0x7f82f28da898", "0x7f82f28da898", "0x7f82f28da898", "0x7f82f28da898", "0x7f82f28d0640", "0x7f82f28d0640", "0x7f82f28d0640", "0x7f82f293b5f8", "0x7f82f293b5f8"]}
strings

STRING entries contain details about the strings size, class, shared/frozen/embedded status and outbound references:

{"address":"0x7f82f2828008", "type":"STRING", "class":"0x7f82f2829660", "frozen":true, "embedded":true, "bytesize":9, "value":"LoadError", "encoding":"US-ASCII"}
{"address":"0x7f82f2833e80", "type":"STRING", "class":"0x7f82f2829660", "embedded":true, "bytesize":5, "value":"Class", "encoding":"US-ASCII", "file":"-e", "line":1, "generation":6}
{"address":"0x7f82f2833778", "type":"STRING", "class":"0x7f82f2829660", "shared":true, "references":["0x7f82f2832f58"]}
{"address":"0x7f82f28399e8", "type":"STRING", "class":"0x7f82f2829660", "frozen":true, "bytesize":19, "capacity":120, "value":"block in initialize", "memsize":120}
hashes
{"address":"0x7f82f2859b58", "type":"HASH", "class":"0x7f82f28e1940", "size":0, "default":"0x7f82f2859b08", "references":["0x7f82f2859b08"], "memsize":192}
{"address":"0x7f82f285adf0", "type":"HASH", "class":"0x7f82f28e1940", "size":2, "references":["0x7f82f285adc8", "0x7f82f285acd8"], "memsize":192}
arrays
{"address":"0x7f82f2872068", "type":"ARRAY", "length":1, "embedded":true, "references":["0x7f82f2901c40"]}
{"address":"0x7f82f2890568", "type":"ARRAY", "length":56, "references":["0x7f82f2890dd8", "0x7f82f2890338", "0x7f82f288ba18", "0x7f82f288b2c0", "0x7f82f288ac58", "0x7f82f288a000", "0x7f82f28891a0", "0x7f82f2888598", "0x7f82f287af10", "0x7f82f2878530", "0x7f82f28780a8", "0x7f82f2873c10", "0x7f82f28737d8", "0x7f82f28734b8", "0x7f82f2873120", "0x7f82f2872e50", "0x7f82f29310a8", "0x7f82f2930388", "0x7f82f2930220", "0x7f82f292bd60", "0x7f82f292bc70", "0x7f82f292a528", "0x7f82f292a208", "0x7f82f2929e48", "0x7f82f290bdd0", "0x7f82f290bce0", "0x7f82f290bb28", "0x7f82f290b6f0", "0x7f82f290b5b0", "0x7f82f290b268", "0x7f82f290ae08", "0x7f82f290ad18", "0x7f82f28782b0", "0x7f82f29094e0", "0x7f82f2907258", "0x7f82f2906f10", "0x7f82f2906e20", "0x7f82f2906d58", "0x7f82f2906b50", "0x7f82f2906920", "0x7f82f2906678", "0x7f82f2906308", "0x7f82f2906060", "0x7f82f2905de0", "0x7f82f2905b10", "0x7f82f2905840", "0x7f82f2905430", "0x7f82f29051b0", "0x7f82f29050c0", "0x7f82f2904eb8", "0x7f82f2904e40", "0x7f82f2904dc8", "0x7f82f2904d50", "0x7f82f2904cd8", "0x7f82f29045a8", "0x7f82f2904288"], "memsize":448}
data
{"address":"0x7f82f2890dd8", "type":"DATA", "class":"0x7f82f28d02f8", "struct":"iseq", "references":["0x7f82f28906f8", "0x7f82f2890ec8", "0x7f82f28917b0", "0x7f82f292ac08", "0x7f82f292ac08", "0x7f82f28913f0"], "memsize":976}
{"address":"0x7f82f2963b20", "type":"DATA", "class":"0x7f82f28d9128", "struct":"time", "memsize":88}
nodes
{"address":"0x7f82f2960b50", "type":"NODE", "node_type":"NODE_CREF", "references":["0x7f82f282b7a8"]}
{"address":"0x7f82f293b648", "type":"NODE", "node_type":"NODE_BLOCK", "references":["0x7f82f293b670"]}
{"address":"0x7f82f293b670", "type":"NODE", "node_type":"NODE_CALL", "references":["0x7f82f293b698"]}
{"address":"0x7f82f293b698", "type":"NODE", "node_type":"NODE_CONST"}
classes and modules
{"address":"0x7f82f28280d0", "type":"CLASS", "class":"0x7f82f28280a8", "name":"ScriptError", "references":["0x7f82f2832e40", "0x7f82f28286e8"], "memsize":656}
{"address":"0x7f82f2829e58", "type":"MODULE", "class":"0x7f82f282b758", "name":"Comparable", "references":["0x7f82f2829e58", "0x7f82f2829e58", "0x7f82f2829e58", "0x7f82f2829e58", "0x7f82f2829e58", "0x7f82f2829e58", "0x7f82f28324b8"], "memsize":848}
{"address":"0x7f82f28504e0", "type":"ICLASS", "class":"0x7f82f2829e58", "references":["0x7f82f28324b8", "0x7f82f282b7a8"]}
other objects
{"address":"0x7f82f28328c8", "type":"OBJECT", "class":"0x7f82f2838188", "ivars":3, "references":["0x7f82f285b638"]}
ext/objspace/object_tracing.c
@@ -256,7 +248,7 @@ struct allocation_info {
return rb_ensure(rb_yield, Qnil, trace_object_allocations_stop, self);
}
-static struct allocation_info *
+struct allocation_info *
lookup_allocation_info(VALUE obj)
@charliesome Collaborator

might want to put this under a namespace if you're exporting the symbol

@tmm1
tmm1 added a note

These are exported within objspace.so only, not the ruby binary itself.

@charliesome Collaborator

Right, but they still become part of the global namespace when the so is loaded yeah?

@tmm1
tmm1 added a note

Yea, true. I can keep them static and add some public objspace_lookup_allocation_info and objspace_memsize_of wrappers.

What do you think @ko1?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
ext/objspace/objspace.c
@@ -27,7 +27,7 @@
size_t rb_generic_ivar_memsize(VALUE);
size_t rb_objspace_data_type_memsize(VALUE obj);
-static size_t
+size_t
memsize_of(VALUE obj)
@charliesome Collaborator

ditto

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@tmm1

Based on feedback from @ko1, I have modified the ruby signatures as follows:

 *  call-seq:
 *    ObjectSpace.dump(obj[, output: :string]) # => "{ ... }"
 *    ObjectSpace.dump(obj, output: :file) # => "/tmp/rubyobj000000"
 *    ObjectSpace.dump(obj, output: :stdout) # => nil

 *  call-seq:
 *    ObjectSpace.dump_all([output: :file]) # => "/tmp/rubyheap000000"
 *    ObjectSpace.dump_all(output: :stdout) # => nil
 *    ObjectSpace.dump_all(output: :string) # => "{...}\n{...}\n..."

/cc https://bugs.ruby-lang.org/issues/9026

@evanphx evanphx closed this pull request from a commit
@tmm1 tmm1 * ext/objspace/object_tracing.c: Add experimental methods to dump
objectspace as json: ObjectSpace.dump_all and ObjectSpace.dump(obj).
These methods are useful for debugging reference leaks and memory growth
in large ruby applications. [Bug #9026] [ruby-core:57893] [Fixes GH-423]

git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@43585 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
d0d6e2e
@evanphx evanphx closed this in d0d6e2e
@mmasaki mmasaki referenced this pull request from a commit in mmasaki/ruby
@tmm1 tmm1 * ext/objspace/object_tracing.c: Add experimental methods to dump
objectspace as json: ObjectSpace.dump_all and ObjectSpace.dump(obj).
These methods are useful for debugging reference leaks and memory growth
in large ruby applications. [Bug #9026] [ruby-core:57893] [Fixes GH-423]

git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@43585 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
33363f5
@tenderlove tenderlove referenced this pull request from a commit in tenderlove/ruby
@tmm1 tmm1 * ext/objspace/object_tracing.c: Add experimental methods to dump
objectspace as json: ObjectSpace.dump_all and ObjectSpace.dump(obj).
These methods are useful for debugging reference leaks and memory growth
in large ruby applications. [Bug #9026] [ruby-core:57893] [Fixes GH-423]

git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@43585 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
946eb22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Oct 16, 2013
  1. @tmm1

    add WIP ObjectSpace.heap_dump

    tmm1 authored
  2. @tmm1

    namespace exported functions

    tmm1 authored
  3. @tmm1
  4. @tmm1

    rename to objspace_dump.c

    tmm1 authored
  5. @tmm1

    flesh out ruby api

    tmm1 authored
Commits on Oct 19, 2013
  1. @tmm1

    escape null byte

    tmm1 authored
  2. @tmm1

    include file descriptors

    tmm1 authored
  3. @tmm1

    Merge branch 'trunk' into heapdump

    tmm1 authored
    Conflicts:
    	ext/objspace/object_tracing.c
  4. @tmm1

    shuffle comment

    tmm1 authored
Commits on Nov 8, 2013
  1. @tmm1
  2. @tmm1
  3. @tmm1

    update depend file

    tmm1 authored
  4. @tmm1

    fix warnings

    tmm1 authored
  5. @tmm1

    catch up to upstream changes

    tmm1 authored
This page is out of date. Refresh to see the latest.
View
3  ext/objspace/depend
@@ -9,3 +9,6 @@ objspace.o: $(HDRS) $(ruby_headers) \
$(top_srcdir)/regint.h $(top_srcdir)/internal.h
gc_hook.o: $(HDRS) $(ruby_headers) $(hdrdir)/ruby/debug.h
object_tracing.o: $(HDRS) $(ruby_headers) $(hdrdir)/ruby/debug.h
+objspace_dump.o: $(HDRS) $(ruby_headers) $(hdrdir)/ruby/debug.h \
+ $(hdrdir)/ruby/encoding.h $(hdrdir)/ruby/io.h \
+ $(top_srcdir)/node.h $(top_srcdir)/vm_core.h $(top_srcdir)/gc.h
View
21 ext/objspace/object_tracing.c
@@ -15,6 +15,7 @@
#include "ruby/ruby.h"
#include "ruby/debug.h"
+#include "objspace.h"
size_t rb_gc_count(void); /* from gc.c */
@@ -28,20 +29,6 @@ struct traceobj_arg {
struct traceobj_arg *prev_traceobj_arg;
};
-/* all of information don't need marking. */
-struct allocation_info {
- int living;
- VALUE flags;
- VALUE klass;
-
- /* allocation info */
- const char *path;
- unsigned long line;
- const char *class_path;
- VALUE mid;
- size_t generation;
-};
-
static const char *
make_unique_str(st_table *tbl, const char *str, long len)
{
@@ -341,6 +328,12 @@ lookup_allocation_info(VALUE obj)
return NULL;
}
+struct allocation_info *
+objspace_lookup_allocation_info(VALUE obj)
+{
+ return lookup_allocation_info(obj);
+}
+
/*
* call-seq: allocation_sourcefile(object) -> string
*
View
2  ext/objspace/objspace.c
@@ -719,6 +719,7 @@ reachable_objects_from_root(VALUE self)
void Init_object_tracing(VALUE rb_mObjSpace);
void Init_gc_hook(VALUE rb_mObjSpace);
+void Init_objspace_dump(VALUE rb_mObjSpace);
/*
* Document-module: ObjectSpace
@@ -770,4 +771,5 @@ Init_objspace(void)
Init_object_tracing(rb_mObjSpace);
Init_gc_hook(rb_mObjSpace);
+ Init_objspace_dump(rb_mObjSpace);
}
View
20 ext/objspace/objspace.h
@@ -0,0 +1,20 @@
+#ifndef OBJSPACE_H
+#define OBJSPACE_H 1
+
+/* object_tracing.c */
+struct allocation_info {
+ /* all of information don't need marking. */
+ int living;
+ VALUE flags;
+ VALUE klass;
+
+ /* allocation info */
+ const char *path;
+ unsigned long line;
+ const char *class_path;
+ VALUE mid;
+ size_t generation;
+};
+struct allocation_info *objspace_lookup_allocation_info(VALUE obj);
+
+#endif
View
415 ext/objspace/objspace_dump.c
@@ -0,0 +1,415 @@
+/**********************************************************************
+
+ objspace_dump.c - Heap dumping ObjectSpace extender for MRI.
+
+ $Author$
+ created at: Sat Oct 11 10:11:00 2013
+
+ NOTE: This extension library is not expected to exist except C Ruby.
+
+ All the files in this distribution are covered under the Ruby's
+ license (see the file COPYING).
+
+**********************************************************************/
+
+#include "ruby/ruby.h"
+#include "ruby/debug.h"
+#include "ruby/encoding.h"
+#include "ruby/io.h"
+#include "gc.h"
+#include "node.h"
+#include "vm_core.h"
+#include "objspace.h"
+
+/* from string.c */
+#define STR_NOEMBED FL_USER1
+#define STR_SHARED FL_USER2 /* = ELTS_SHARED */
+#define STR_ASSOC FL_USER3
+#define STR_SHARED_P(s) FL_ALL((s), STR_NOEMBED|ELTS_SHARED)
+#define STR_ASSOC_P(s) FL_ALL((s), STR_NOEMBED|STR_ASSOC)
+#define STR_NOCAPA (STR_NOEMBED|ELTS_SHARED|STR_ASSOC)
+#define STR_NOCAPA_P(s) (FL_TEST((s),STR_NOEMBED) && FL_ANY((s),ELTS_SHARED|STR_ASSOC))
+#define STR_EMBED_P(str) (!FL_TEST((str), STR_NOEMBED))
+#define is_ascii_string(str) (rb_enc_str_coderange(str) == ENC_CODERANGE_7BIT)
+#define is_broken_string(str) (rb_enc_str_coderange(str) == ENC_CODERANGE_BROKEN)
+/* from hash.c */
+#define HASH_PROC_DEFAULT FL_USER2
+
+static VALUE sym_output, sym_stdout, sym_string, sym_file;
+
+struct dump_config {
+ VALUE type;
+ FILE *stream;
+ VALUE string;
+ int roots;
+ const char *root_category;
+ VALUE cur_obj;
+ VALUE cur_obj_klass;
+ size_t cur_obj_references;
+};
+
+static void
+dump_append(struct dump_config *dc, const char *format, ...)
+{
+ va_list vl;
+ va_start(vl, format);
+
+ if (dc->stream) {
+ vfprintf(dc->stream, format, vl);
+ fflush(dc->stream);
+ }
+ else if (dc->string)
+ rb_str_vcatf(dc->string, format, vl);
+
+ va_end(vl);
+}
+
+static void
+dump_append_string_value(struct dump_config *dc, VALUE obj)
+{
+ int i;
+ char c, *value;
+
+ dump_append(dc, "\"");
+ for (i = 0, value = RSTRING_PTR(obj); i < RSTRING_LEN(obj); i++) {
+ switch ((c = value[i])) {
+ case '\\':
+ case '"':
+ dump_append(dc, "\\%c", c);
+ break;
+ case '\0':
+ dump_append(dc, "\\u0000");
+ break;
+ case '\b':
+ dump_append(dc, "\\b");
+ break;
+ case '\t':
+ dump_append(dc, "\\t");
+ break;
+ case '\f':
+ dump_append(dc, "\\f");
+ break;
+ case '\n':
+ dump_append(dc, "\\n");
+ break;
+ case '\r':
+ dump_append(dc, "\\r");
+ break;
+ default:
+ dump_append(dc, "%c", c);
+ }
+ }
+ dump_append(dc, "\"");
+}
+
+static inline const char *
+obj_type(VALUE obj)
+{
+ switch (BUILTIN_TYPE(obj)) {
+#define CASE_TYPE(type) case T_##type: return #type; break
+ CASE_TYPE(NONE);
+ CASE_TYPE(NIL);
+ CASE_TYPE(OBJECT);
+ CASE_TYPE(CLASS);
+ CASE_TYPE(ICLASS);
+ CASE_TYPE(MODULE);
+ CASE_TYPE(FLOAT);
+ CASE_TYPE(STRING);
+ CASE_TYPE(REGEXP);
+ CASE_TYPE(ARRAY);
+ CASE_TYPE(HASH);
+ CASE_TYPE(STRUCT);
+ CASE_TYPE(BIGNUM);
+ CASE_TYPE(FILE);
+ CASE_TYPE(FIXNUM);
+ CASE_TYPE(TRUE);
+ CASE_TYPE(FALSE);
+ CASE_TYPE(DATA);
+ CASE_TYPE(MATCH);
+ CASE_TYPE(SYMBOL);
+ CASE_TYPE(RATIONAL);
+ CASE_TYPE(COMPLEX);
+ CASE_TYPE(UNDEF);
+ CASE_TYPE(NODE);
+ CASE_TYPE(ZOMBIE);
+#undef CASE_TYPE
+ }
+ return "UNKNOWN";
+}
+
+static void
+reachable_object_i(VALUE ref, void *data)
+{
+ struct dump_config *dc = (struct dump_config *)data;
+
+ if (dc->cur_obj_klass == ref)
+ return;
+
+ if (dc->cur_obj_references == 0)
+ dump_append(dc, ", \"references\":[\"%p\"", (void *)ref);
+ else
+ dump_append(dc, ", \"%p\"", (void *)ref);
+
+ dc->cur_obj_references++;
+}
+
+static void
+dump_object(VALUE obj, struct dump_config *dc)
+{
+ int enc;
+ long length;
+ size_t memsize;
+ struct allocation_info *ainfo;
+ rb_io_t *fptr;
+
+ dc->cur_obj = obj;
+ dc->cur_obj_references = 0;
+ dc->cur_obj_klass = BUILTIN_TYPE(obj) == T_NODE ? 0 : RBASIC_CLASS(obj);
+
+ if (dc->cur_obj == dc->string)
+ return;
+
+ dump_append(dc, "{\"address\":\"%p\", \"type\":\"%s\"", (void *)obj, obj_type(obj));
+
+ if (dc->cur_obj_klass)
+ dump_append(dc, ", \"class\":\"%p\"", (void *)dc->cur_obj_klass);
+ if (rb_obj_frozen_p(obj))
+ dump_append(dc, ", \"frozen\":true");
+
+ switch (BUILTIN_TYPE(obj)) {
+ case T_NODE:
+ dump_append(dc, ", \"node_type\":\"%s\"", ruby_node_name(nd_type(obj)));
+ break;
+
+ case T_STRING:
+ if (STR_EMBED_P(obj))
+ dump_append(dc, ", \"embedded\":true");
+ if (STR_ASSOC_P(obj))
+ dump_append(dc, ", \"associated\":true");
+ if (is_broken_string(obj))
+ dump_append(dc, ", \"broken\":true");
+ if (STR_SHARED_P(obj))
+ dump_append(dc, ", \"shared\":true");
+ else {
+ dump_append(dc, ", \"bytesize\":%ld", RSTRING_LEN(obj));
+ if (!STR_EMBED_P(obj) && !STR_NOCAPA_P(obj) && rb_str_capacity(obj) != RSTRING_LEN(obj))
+ dump_append(dc, ", \"capacity\":%ld", rb_str_capacity(obj));
+
+ if (is_ascii_string(obj)) {
+ dump_append(dc, ", \"value\":");
+ dump_append_string_value(dc, obj);
+ }
+ }
+
+ if (!ENCODING_IS_ASCII8BIT(obj))
+ dump_append(dc, ", \"encoding\":\"%s\"", rb_enc_name(rb_enc_from_index(ENCODING_GET(obj))));
+ break;
+
+ case T_HASH:
+ dump_append(dc, ", \"size\":%ld", RHASH_SIZE(obj));
+ if (FL_TEST(obj, HASH_PROC_DEFAULT))
+ dump_append(dc, ", \"default\":\"%p\"", (void *)RHASH_IFNONE(obj));
+ break;
+
+ case T_ARRAY:
+ dump_append(dc, ", \"length\":%ld", RARRAY_LEN(obj));
+ if (RARRAY_LEN(obj) > 0 && FL_TEST(obj, ELTS_SHARED))
+ dump_append(dc, ", \"shared\":true");
+ if (RARRAY_LEN(obj) > 0 && FL_TEST(obj, RARRAY_EMBED_FLAG))
+ dump_append(dc, ", \"embedded\":true");
+ break;
+
+ case T_CLASS:
+ case T_MODULE:
+ if (dc->cur_obj_klass)
+ dump_append(dc, ", \"name\":\"%s\"", rb_class2name(obj));
+ break;
+
+ case T_DATA:
+ if (RTYPEDDATA_P(obj))
+ dump_append(dc, ", \"struct\":\"%s\"", RTYPEDDATA_TYPE(obj)->wrap_struct_name);
+ break;
+
+ case T_FLOAT:
+ dump_append(dc, ", \"value\":\"%g\"", RFLOAT_VALUE(obj));
+ break;
+
+ case T_OBJECT:
+ dump_append(dc, ", \"ivars\":%ld", ROBJECT_NUMIV(obj));
+ break;
+
+ case T_FILE:
+ fptr = RFILE(obj)->fptr;
+ dump_append(dc, ", \"fd\":%d", fptr->fd);
+ break;
+
+ case T_ZOMBIE:
+ dump_append(dc, "}\n");
+ return;
+ }
+
+ rb_objspace_reachable_objects_from(obj, reachable_object_i, dc);
+ if (dc->cur_obj_references > 0)
+ dump_append(dc, "]");
+
+ if ((ainfo = objspace_lookup_allocation_info(obj))) {
+ dump_append(dc, ", \"file\":\"%s\", \"line\":%lu", ainfo->path, ainfo->line);
+ if (RTEST(ainfo->mid))
+ dump_append(dc, ", \"method\":\"%s\"", rb_id2name(SYM2ID(ainfo->mid)));
+ dump_append(dc, ", \"generation\":%zu", ainfo->generation);
+ }
+
+ if ((memsize = rb_obj_memsize_of(obj)) > 0)
+ dump_append(dc, ", \"memsize\":%zu", memsize);
+
+ dump_append(dc, "}\n");
+}
+
+static int
+heap_i(void *vstart, void *vend, size_t stride, void *data)
+{
+ VALUE v = (VALUE)vstart;
+ for (; v != (VALUE)vend; v += stride) {
+ if (RBASIC(v)->flags)
+ dump_object(v, data);
+ }
+ return 0;
+}
+
+static void
+root_obj_i(const char *category, VALUE obj, void *data)
+{
+ struct dump_config *dc = (struct dump_config *)data;
+
+ if (dc->root_category != NULL && category != dc->root_category)
+ dump_append(dc, "]}\n");
+ if (dc->root_category == NULL || category != dc->root_category)
+ dump_append(dc, "{\"type\":\"ROOT\", \"root\":\"%s\", \"references\":[\"%p\"", category, (void *)obj);
+ else
+ dump_append(dc, ", \"%p\"", (void *)obj);
+
+ dc->root_category = category;
+ dc->roots++;
+}
+
+/*
+ * call-seq:
+ * ObjectSpace.dump(obj[, output: :string]) # => "{ ... }"
+ * ObjectSpace.dump(obj, output: :file) # => "/tmp/rubyobj000000"
+ * ObjectSpace.dump(obj, output: :stdout) # => nil
+ *
+ * Dump the contents of a ruby object as JSON.
+ *
+ * This method is only expected to work with C Ruby.
+ * This is an experimental method and is subject to change.
+ * In particular, the function signature and output format are
+ * not guaranteed to be compatible in future versions of ruby.
+ */
+
+static VALUE
+objspace_dump(int argc, VALUE *argv, VALUE os)
+{
+ int fd;
+ char filename[] = "/tmp/rubyobjXXXXXX";
+ VALUE obj = Qnil, opts = Qnil, output;
+ struct dump_config dc = {0,};
+
+ rb_scan_args(argc, argv, "1:", &obj, &opts);
+
+ if (RTEST(opts))
+ output = rb_hash_aref(opts, sym_output);
+
+ if (output == sym_stdout)
+ dc.stream = stdout;
+ else if (output == sym_file) {
+ fd = mkstemp(filename);
+ if (fd == -1) rb_sys_fail_path(rb_str_new_cstr(filename));
+ dc.stream = fdopen(fd, "w");
+ }
+ else {
+ output = sym_string;
+ dc.string = rb_str_new2("");
+ }
+
+ dump_object(obj, &dc);
+
+ if (output == sym_string)
+ return dc.string;
+ else if (output == sym_file) {
+ fclose(dc.stream);
+ return rb_str_new2(filename);
+ }
+ else
+ return Qnil;
+}
+
+/*
+ * call-seq:
+ * ObjectSpace.dump_all([output: :file]) # => "/tmp/rubyheap000000"
+ * ObjectSpace.dump_all(output: :stdout) # => nil
+ * ObjectSpace.dump_all(output: :string) # => "{...}\n{...}\n..."
+ *
+ * Dump the contents of the ruby heap as JSON.
+ *
+ * This method is only expected to work with C Ruby.
+ * This is an experimental method and is subject to change.
+ * In particular, the function signature and output format are
+ * not guaranteed to be compatible in future versions of ruby.
+ */
+
+static VALUE
+objspace_dump_all(int argc, VALUE *argv, VALUE os)
+{
+ int fd;
+ char filename[] = "/tmp/rubyheapXXXXXX";
+ VALUE opts = Qnil, output;
+ struct dump_config dc = {0,};
+
+ rb_scan_args(argc, argv, "0:", &opts);
+
+ if (RTEST(opts))
+ output = rb_hash_aref(opts, sym_output);
+
+ if (output == sym_string)
+ dc.string = rb_str_new2("");
+ else if (output == sym_stdout)
+ dc.stream = stdout;
+ else {
+ output = sym_file;
+ fd = mkstemp(filename);
+ if (fd == -1) rb_sys_fail_path(rb_str_new_cstr(filename));
+ dc.stream = fdopen(fd, "w");
+ }
+
+ /* dump roots */
+ rb_objspace_reachable_objects_from_root(root_obj_i, &dc);
+ if (dc.roots) dump_append(&dc, "]}\n");
+
+ /* dump all objects */
+ rb_objspace_each_objects(heap_i, &dc);
+
+ if (output == sym_string)
+ return dc.string;
+ else if (output == sym_file) {
+ fclose(dc.stream);
+ return rb_str_new2(filename);
+ }
+ else
+ return Qnil;
+}
+
+void
+Init_objspace_dump(VALUE rb_mObjSpace)
+{
+#if 0
+ rb_mObjSpace = rb_define_module("ObjectSpace"); /* let rdoc know */
+#endif
+
+ rb_define_module_function(rb_mObjSpace, "dump", objspace_dump, -1);
+ rb_define_module_function(rb_mObjSpace, "dump_all", objspace_dump_all, -1);
+
+ sym_output = ID2SYM(rb_intern("output"));
+ sym_stdout = ID2SYM(rb_intern("stdout"));
+ sym_string = ID2SYM(rb_intern("string"));
+ sym_file = ID2SYM(rb_intern("file"));
+}
View
27 test/objspace/test_objspace.rb
@@ -203,4 +203,31 @@ def test_after_gc_start_hook_with_GC_stress
end;
end
end
+
+ def test_dump
+ info = nil
+ ObjectSpace.trace_object_allocations do
+ str = "hello world"
+ info = ObjectSpace.dump(str)
+ end
+
+ assert_match /"type":"STRING"/, info
+ assert_match /"embedded":true, "bytesize":11, "value":"hello world", "encoding":"UTF-8"/, info
+ assert_match /"file":"#{Regexp.escape __FILE__}", "line":#{__LINE__-6}/, info
+ assert_match /"method":"test_dump"/, info
+ end
+
+ def test_dump_all
+ entry = /"value":"this is a test string", "encoding":"UTF-8", "file":"-", "line":4, "method":"dump_my_heap_please"/
+ assert_in_out_err(%w[-robjspace], <<-'end;', entry)
+ def dump_my_heap_please
+ ObjectSpace.trace_object_allocations_start
+ GC.start
+ "this is a test string".force_encoding("UTF-8")
+ ObjectSpace.dump_all(output: :stdout)
+ end
+
+ dump_my_heap_please
+ end;
+ end
end
Something went wrong with that request. Please try again.