00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
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
00350
00351
00352
00353
00354
00355
00356
00357
00358
00359
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
00380
00381
00382
00383
00384
00385
00386
00387
00388
00389
00390
00391
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
00406 rb_objspace_reachable_objects_from_root(root_obj_i, &dc);
00407 if (dc.roots) dump_append(&dc, "]}\n");
00408
00409
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");
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
00431 rb_obj_gc_flags(rb_mObjSpace, 0, 0);
00432 }
00433