ext/objspace/objspace_dump.c

Go to the documentation of this file.
00001 /**********************************************************************
00002 
00003   objspace_dump.c - Heap dumping ObjectSpace extender for MRI.
00004 
00005   $Author$
00006   created at: Sat Oct 11 10:11:00 2013
00007 
00008   NOTE: This extension library is not expected to exist except C Ruby.
00009 
00010   All the files in this distribution are covered under the Ruby's
00011   license (see the file COPYING).
00012 
00013 **********************************************************************/
00014 
00015 #include "ruby/ruby.h"
00016 #include "ruby/debug.h"
00017 #include "ruby/encoding.h"
00018 #include "ruby/io.h"
00019 #include "gc.h"
00020 #include "node.h"
00021 #include "vm_core.h"
00022 #include "objspace.h"
00023 #include "internal.h"
00024 
00025 static VALUE sym_output, sym_stdout, sym_string, sym_file;
00026 
00027 struct dump_config {
00028     VALUE type;
00029     FILE *stream;
00030     VALUE string;
00031     int roots;
00032     const char *root_category;
00033     VALUE cur_obj;
00034     VALUE cur_obj_klass;
00035     size_t cur_obj_references;
00036 };
00037 
00038 static void
00039 dump_append(struct dump_config *dc, const char *format, ...)
00040 {
00041     va_list vl;
00042     va_start(vl, format);
00043 
00044     if (dc->stream) {
00045         vfprintf(dc->stream, format, vl);
00046         fflush(dc->stream);
00047     }
00048     else if (dc->string)
00049         rb_str_vcatf(dc->string, format, vl);
00050 
00051     va_end(vl);
00052 }
00053 
00054 static void
00055 dump_append_string_value(struct dump_config *dc, VALUE obj)
00056 {
00057     int i;
00058     char c, *value;
00059 
00060     dump_append(dc, "\"");
00061     for (i = 0, value = RSTRING_PTR(obj); i < RSTRING_LEN(obj); i++) {
00062         switch ((c = value[i])) {
00063           case '\\':
00064           case '"':
00065             dump_append(dc, "\\%c", c);
00066             break;
00067           case '\0':
00068             dump_append(dc, "\\u0000");
00069             break;
00070           case '\b':
00071             dump_append(dc, "\\b");
00072             break;
00073           case '\t':
00074             dump_append(dc, "\\t");
00075             break;
00076           case '\f':
00077             dump_append(dc, "\\f");
00078             break;
00079           case '\n':
00080             dump_append(dc, "\\n");
00081             break;
00082           case '\r':
00083             dump_append(dc, "\\r");
00084             break;
00085           default:
00086             if (c <= 0x1f)
00087                 dump_append(dc, "\\u%04d", c);
00088             else
00089                 dump_append(dc, "%c", c);
00090         }
00091     }
00092     dump_append(dc, "\"");
00093 }
00094 
00095 static inline const char *
00096 obj_type(VALUE obj)
00097 {
00098     switch (BUILTIN_TYPE(obj)) {
00099 #define CASE_TYPE(type) case T_##type: return #type; break
00100         CASE_TYPE(NONE);
00101         CASE_TYPE(NIL);
00102         CASE_TYPE(OBJECT);
00103         CASE_TYPE(CLASS);
00104         CASE_TYPE(ICLASS);
00105         CASE_TYPE(MODULE);
00106         CASE_TYPE(FLOAT);
00107         CASE_TYPE(STRING);
00108         CASE_TYPE(REGEXP);
00109         CASE_TYPE(ARRAY);
00110         CASE_TYPE(HASH);
00111         CASE_TYPE(STRUCT);
00112         CASE_TYPE(BIGNUM);
00113         CASE_TYPE(FILE);
00114         CASE_TYPE(FIXNUM);
00115         CASE_TYPE(TRUE);
00116         CASE_TYPE(FALSE);
00117         CASE_TYPE(DATA);
00118         CASE_TYPE(MATCH);
00119         CASE_TYPE(SYMBOL);
00120         CASE_TYPE(RATIONAL);
00121         CASE_TYPE(COMPLEX);
00122         CASE_TYPE(UNDEF);
00123         CASE_TYPE(NODE);
00124         CASE_TYPE(ZOMBIE);
00125 #undef CASE_TYPE
00126     }
00127     return "UNKNOWN";
00128 }
00129 
00130 static void
00131 reachable_object_i(VALUE ref, void *data)
00132 {
00133     struct dump_config *dc = (struct dump_config *)data;
00134 
00135     if (dc->cur_obj_klass == ref)
00136         return;
00137 
00138     if (dc->cur_obj_references == 0)
00139         dump_append(dc, ", \"references\":[\"%p\"", (void *)ref);
00140     else
00141         dump_append(dc, ", \"%p\"", (void *)ref);
00142 
00143     dc->cur_obj_references++;
00144 }
00145 
00146 static void
00147 dump_object(VALUE obj, struct dump_config *dc)
00148 {
00149     size_t memsize;
00150     struct allocation_info *ainfo;
00151     rb_io_t *fptr;
00152     ID flags[RB_OBJ_GC_FLAGS_MAX];
00153     size_t n, i;
00154 
00155     dc->cur_obj = obj;
00156     dc->cur_obj_references = 0;
00157     dc->cur_obj_klass = BUILTIN_TYPE(obj) == T_NODE ? 0 : RBASIC_CLASS(obj);
00158 
00159     if (dc->cur_obj == dc->string)
00160         return;
00161 
00162     dump_append(dc, "{\"address\":\"%p\", \"type\":\"%s\"", (void *)obj, obj_type(obj));
00163 
00164     if (dc->cur_obj_klass)
00165         dump_append(dc, ", \"class\":\"%p\"", (void *)dc->cur_obj_klass);
00166     if (rb_obj_frozen_p(obj))
00167         dump_append(dc, ", \"frozen\":true");
00168 
00169     switch (BUILTIN_TYPE(obj)) {
00170       case T_NODE:
00171         dump_append(dc, ", \"node_type\":\"%s\"", ruby_node_name(nd_type(obj)));
00172         break;
00173 
00174       case T_STRING:
00175         if (STR_EMBED_P(obj))
00176             dump_append(dc, ", \"embedded\":true");
00177         if (STR_ASSOC_P(obj))
00178             dump_append(dc, ", \"associated\":true");
00179         if (is_broken_string(obj))
00180             dump_append(dc, ", \"broken\":true");
00181         if (FL_TEST(obj, RSTRING_FSTR))
00182             dump_append(dc, ", \"fstring\":true");
00183         if (STR_SHARED_P(obj))
00184             dump_append(dc, ", \"shared\":true");
00185         else {
00186             dump_append(dc, ", \"bytesize\":%ld", RSTRING_LEN(obj));
00187             if (!STR_EMBED_P(obj) && !STR_NOCAPA_P(obj) && (long)rb_str_capacity(obj) != RSTRING_LEN(obj))
00188                 dump_append(dc, ", \"capacity\":%ld", rb_str_capacity(obj));
00189 
00190             if (is_ascii_string(obj)) {
00191                 dump_append(dc, ", \"value\":");
00192                 dump_append_string_value(dc, obj);
00193             }
00194         }
00195 
00196         if (!ENCODING_IS_ASCII8BIT(obj))
00197             dump_append(dc, ", \"encoding\":\"%s\"", rb_enc_name(rb_enc_from_index(ENCODING_GET(obj))));
00198         break;
00199 
00200       case T_HASH:
00201         dump_append(dc, ", \"size\":%ld", RHASH_SIZE(obj));
00202         if (FL_TEST(obj, HASH_PROC_DEFAULT))
00203             dump_append(dc, ", \"default\":\"%p\"", (void *)RHASH_IFNONE(obj));
00204         break;
00205 
00206       case T_ARRAY:
00207         dump_append(dc, ", \"length\":%ld", RARRAY_LEN(obj));
00208         if (RARRAY_LEN(obj) > 0 && FL_TEST(obj, ELTS_SHARED))
00209             dump_append(dc, ", \"shared\":true");
00210         if (RARRAY_LEN(obj) > 0 && FL_TEST(obj, RARRAY_EMBED_FLAG))
00211             dump_append(dc, ", \"embedded\":true");
00212         break;
00213 
00214       case T_CLASS:
00215       case T_MODULE:
00216         if (dc->cur_obj_klass)
00217             dump_append(dc, ", \"name\":\"%s\"", rb_class2name(obj));
00218         break;
00219 
00220       case T_DATA:
00221         if (RTYPEDDATA_P(obj))
00222             dump_append(dc, ", \"struct\":\"%s\"", RTYPEDDATA_TYPE(obj)->wrap_struct_name);
00223         break;
00224 
00225       case T_FLOAT:
00226         dump_append(dc, ", \"value\":\"%g\"", RFLOAT_VALUE(obj));
00227         break;
00228 
00229       case T_OBJECT:
00230         dump_append(dc, ", \"ivars\":%ld", ROBJECT_NUMIV(obj));
00231         break;
00232 
00233       case T_FILE:
00234         fptr = RFILE(obj)->fptr;
00235         if (fptr)
00236             dump_append(dc, ", \"fd\":%d", fptr->fd);
00237         break;
00238 
00239       case T_ZOMBIE:
00240         dump_append(dc, "}\n");
00241         return;
00242     }
00243 
00244     rb_objspace_reachable_objects_from(obj, reachable_object_i, dc);
00245     if (dc->cur_obj_references > 0)
00246         dump_append(dc, "]");
00247 
00248     if ((ainfo = objspace_lookup_allocation_info(obj))) {
00249         dump_append(dc, ", \"file\":\"%s\", \"line\":%lu", ainfo->path, ainfo->line);
00250         if (RTEST(ainfo->mid))
00251             dump_append(dc, ", \"method\":\"%s\"", rb_id2name(SYM2ID(ainfo->mid)));
00252         dump_append(dc, ", \"generation\":%"PRIuSIZE, ainfo->generation);
00253     }
00254 
00255     if ((memsize = rb_obj_memsize_of(obj)) > 0)
00256         dump_append(dc, ", \"memsize\":%"PRIuSIZE, memsize);
00257 
00258     if ((n = rb_obj_gc_flags(obj, flags, sizeof(flags))) > 0) {
00259         dump_append(dc, ", \"flags\":{");
00260         for (i=0; i<n; i++) {
00261             dump_append(dc, "\"%s\":true", rb_id2name(flags[i]));
00262             if (i != n-1) dump_append(dc, ", ");
00263         }
00264         dump_append(dc, "}");
00265     }
00266 
00267     dump_append(dc, "}\n");
00268 }
00269 
00270 static int
00271 heap_i(void *vstart, void *vend, size_t stride, void *data)
00272 {
00273     VALUE v = (VALUE)vstart;
00274     for (; v != (VALUE)vend; v += stride) {
00275         if (RBASIC(v)->flags)
00276             dump_object(v, data);
00277     }
00278     return 0;
00279 }
00280 
00281 static void
00282 root_obj_i(const char *category, VALUE obj, void *data)
00283 {
00284     struct dump_config *dc = (struct dump_config *)data;
00285 
00286     if (dc->root_category != NULL && category != dc->root_category)
00287         dump_append(dc, "]}\n");
00288     if (dc->root_category == NULL || category != dc->root_category)
00289         dump_append(dc, "{\"type\":\"ROOT\", \"root\":\"%s\", \"references\":[\"%p\"", category, (void *)obj);
00290     else
00291         dump_append(dc, ", \"%p\"", (void *)obj);
00292 
00293     dc->root_category = category;
00294     dc->roots++;
00295 }
00296 
00297 static VALUE
00298 dump_output(struct dump_config *dc, VALUE opts, VALUE output, const char *filename)
00299 {
00300     VALUE tmp;
00301 
00302     if (RTEST(opts))
00303         output = rb_hash_aref(opts, sym_output);
00304 
00305     if (output == sym_stdout) {
00306         dc->stream = stdout;
00307         dc->string = Qnil;
00308     }
00309     else if (output == sym_file) {
00310         rb_io_t *fptr;
00311         rb_require("tempfile");
00312         tmp = rb_assoc_new(rb_str_new_cstr(filename), rb_str_new_cstr(".json"));
00313         tmp = rb_funcallv(rb_path2class("Tempfile"), rb_intern("create"), 1, &tmp);
00314       io:
00315         dc->string = rb_io_get_write_io(tmp);
00316         rb_io_flush(dc->string);
00317         GetOpenFile(dc->string, fptr);
00318         dc->stream = rb_io_stdio_file(fptr);
00319     }
00320     else if (output == sym_string) {
00321         dc->string = rb_str_new_cstr("");
00322     }
00323     else if (!NIL_P(tmp = rb_io_check_io(output))) {
00324         output = sym_file;
00325         goto io;
00326     }
00327     else {
00328         rb_raise(rb_eArgError, "wrong output option: %"PRIsVALUE, output);
00329     }
00330     return output;
00331 }
00332 
00333 static VALUE
00334 dump_result(struct dump_config *dc, VALUE output)
00335 {
00336     if (output == sym_string) {
00337         return dc->string;
00338     }
00339     else if (output == sym_file) {
00340         rb_io_flush(dc->string);
00341         return dc->string;
00342     }
00343     else {
00344         return Qnil;
00345     }
00346 }
00347 
00348 /*
00349  *  call-seq:
00350  *    ObjectSpace.dump(obj[, output: :string]) # => "{ ... }"
00351  *    ObjectSpace.dump(obj, output: :file)     # => #<File:/tmp/rubyobj20131125-88733-1xkfmpv.json>
00352  *    ObjectSpace.dump(obj, output: :stdout)   # => nil
00353  *
00354  *  Dump the contents of a ruby object as JSON.
00355  *
00356  *  This method is only expected to work with C Ruby.
00357  *  This is an experimental method and is subject to change.
00358  *  In particular, the function signature and output format are
00359  *  not guaranteed to be compatible in future versions of ruby.
00360  */
00361 
00362 static VALUE
00363 objspace_dump(int argc, VALUE *argv, VALUE os)
00364 {
00365     static const char filename[] = "rubyobj";
00366     VALUE obj = Qnil, opts = Qnil, output;
00367     struct dump_config dc = {0,};
00368 
00369     rb_scan_args(argc, argv, "1:", &obj, &opts);
00370 
00371     output = dump_output(&dc, opts, sym_string, filename);
00372 
00373     dump_object(obj, &dc);
00374 
00375     return dump_result(&dc, output);
00376 }
00377 
00378 /*
00379  *  call-seq:
00380  *    ObjectSpace.dump_all([output: :file]) # => #<File:/tmp/rubyheap20131125-88469-laoj3v.json>
00381  *    ObjectSpace.dump_all(output: :stdout) # => nil
00382  *    ObjectSpace.dump_all(output: :string) # => "{...}\n{...}\n..."
00383  *    ObjectSpace.dump_all(output:
00384  *      File.open('heap.json','w'))         # => #<File:heap.json>
00385  *
00386  *  Dump the contents of the ruby heap as JSON.
00387  *
00388  *  This method is only expected to work with C Ruby.
00389  *  This is an experimental method and is subject to change.
00390  *  In particular, the function signature and output format are
00391  *  not guaranteed to be compatible in future versions of ruby.
00392  */
00393 
00394 static VALUE
00395 objspace_dump_all(int argc, VALUE *argv, VALUE os)
00396 {
00397     static const char filename[] = "rubyheap";
00398     VALUE opts = Qnil, output;
00399     struct dump_config dc = {0,};
00400 
00401     rb_scan_args(argc, argv, "0:", &opts);
00402 
00403     output = dump_output(&dc, opts, sym_file, filename);
00404 
00405     /* dump roots */
00406     rb_objspace_reachable_objects_from_root(root_obj_i, &dc);
00407     if (dc.roots) dump_append(&dc, "]}\n");
00408 
00409     /* dump all objects */
00410     rb_objspace_each_objects(heap_i, &dc);
00411 
00412     return dump_result(&dc, output);
00413 }
00414 
00415 void
00416 Init_objspace_dump(VALUE rb_mObjSpace)
00417 {
00418 #if 0
00419     rb_mObjSpace = rb_define_module("ObjectSpace"); /* let rdoc know */
00420 #endif
00421 
00422     rb_define_module_function(rb_mObjSpace, "dump", objspace_dump, -1);
00423     rb_define_module_function(rb_mObjSpace, "dump_all", objspace_dump_all, -1);
00424 
00425     sym_output = ID2SYM(rb_intern("output"));
00426     sym_stdout = ID2SYM(rb_intern("stdout"));
00427     sym_string = ID2SYM(rb_intern("string"));
00428     sym_file   = ID2SYM(rb_intern("file"));
00429 
00430     /* force create static IDs */
00431     rb_obj_gc_flags(rb_mObjSpace, 0, 0);
00432 }
00433 

Generated on 19 Jul 2016 for Ruby by  doxygen 1.4.7