ext/objspace/object_tracing.c

Go to the documentation of this file.
00001 /**********************************************************************
00002 
00003   object_tracing.c - Object Tracing mechanism/ObjectSpace extender for MRI.
00004 
00005   $Author$
00006   created at: Mon May 27 16:27:44 2013
00007 
00008   NOTE: This extension library is not expected to exist except C Ruby.
00009   NOTE: This feature is an example usage of internal event tracing APIs.
00010 
00011   All the files in this distribution are covered under the Ruby's
00012   license (see the file COPYING).
00013 
00014 **********************************************************************/
00015 
00016 #include "ruby/ruby.h"
00017 #include "ruby/debug.h"
00018 #include "objspace.h"
00019 #include "internal.h"
00020 
00021 struct traceobj_arg {
00022     int running;
00023     int keep_remains;
00024     VALUE newobj_trace;
00025     VALUE freeobj_trace;
00026     st_table *object_table; /* obj (VALUE) -> allocation_info */
00027     st_table *str_table;    /* cstr -> refcount */
00028     struct traceobj_arg *prev_traceobj_arg;
00029 };
00030 
00031 static const char *
00032 make_unique_str(st_table *tbl, const char *str, long len)
00033 {
00034     if (!str) {
00035         return NULL;
00036     }
00037     else {
00038         st_data_t n;
00039         char *result;
00040 
00041         if (st_lookup(tbl, (st_data_t)str, &n)) {
00042             st_insert(tbl, (st_data_t)str, n+1);
00043             st_get_key(tbl, (st_data_t)str, (st_data_t *)&result);
00044         }
00045         else {
00046             result = (char *)ruby_xmalloc(len+1);
00047             strncpy(result, str, len);
00048             result[len] = 0;
00049             st_add_direct(tbl, (st_data_t)result, 1);
00050         }
00051         return result;
00052     }
00053 }
00054 
00055 static void
00056 delete_unique_str(st_table *tbl, const char *str)
00057 {
00058     if (str) {
00059         st_data_t n;
00060 
00061         st_lookup(tbl, (st_data_t)str, &n);
00062         if (n == 1) {
00063             st_delete(tbl, (st_data_t *)&str, 0);
00064             ruby_xfree((char *)str);
00065         }
00066         else {
00067             st_insert(tbl, (st_data_t)str, n-1);
00068         }
00069     }
00070 }
00071 
00072 static void
00073 newobj_i(VALUE tpval, void *data)
00074 {
00075     struct traceobj_arg *arg = (struct traceobj_arg *)data;
00076     rb_trace_arg_t *tparg = rb_tracearg_from_tracepoint(tpval);
00077     VALUE obj = rb_tracearg_object(tparg);
00078     VALUE path = rb_tracearg_path(tparg);
00079     VALUE line = rb_tracearg_lineno(tparg);
00080     VALUE mid = rb_tracearg_method_id(tparg);
00081     VALUE klass = rb_tracearg_defined_class(tparg);
00082     struct allocation_info *info;
00083     const char *path_cstr = RTEST(path) ? make_unique_str(arg->str_table, RSTRING_PTR(path), RSTRING_LEN(path)) : 0;
00084     VALUE class_path = (RTEST(klass) && !OBJ_FROZEN(klass)) ? rb_class_path_cached(klass) : Qnil;
00085     const char *class_path_cstr = RTEST(class_path) ? make_unique_str(arg->str_table, RSTRING_PTR(class_path), RSTRING_LEN(class_path)) : 0;
00086 
00087     if (st_lookup(arg->object_table, (st_data_t)obj, (st_data_t *)&info)) {
00088         if (arg->keep_remains) {
00089             if (info->living) {
00090                 /* do nothing. there is possibility to keep living if FREEOBJ events while suppressing tracing */
00091             }
00092         }
00093         /* reuse info */
00094         delete_unique_str(arg->str_table, info->path);
00095         delete_unique_str(arg->str_table, info->class_path);
00096     }
00097     else {
00098         info = (struct allocation_info *)ruby_xmalloc(sizeof(struct allocation_info));
00099     }
00100     info->living = 1;
00101     info->flags = RBASIC(obj)->flags;
00102     info->klass = RBASIC_CLASS(obj);
00103 
00104     info->path = path_cstr;
00105     info->line = NUM2INT(line);
00106     info->mid = mid;
00107     info->class_path = class_path_cstr;
00108     info->generation = rb_gc_count();
00109     st_insert(arg->object_table, (st_data_t)obj, (st_data_t)info);
00110 }
00111 
00112 static void
00113 freeobj_i(VALUE tpval, void *data)
00114 {
00115     struct traceobj_arg *arg = (struct traceobj_arg *)data;
00116     rb_trace_arg_t *tparg = rb_tracearg_from_tracepoint(tpval);
00117     VALUE obj = rb_tracearg_object(tparg);
00118     struct allocation_info *info;
00119 
00120     if (st_lookup(arg->object_table, (st_data_t)obj, (st_data_t *)&info)) {
00121         if (arg->keep_remains) {
00122             info->living = 0;
00123         }
00124         else {
00125             st_delete(arg->object_table, (st_data_t *)&obj, (st_data_t *)&info);
00126             delete_unique_str(arg->str_table, info->path);
00127             delete_unique_str(arg->str_table, info->class_path);
00128             ruby_xfree(info);
00129         }
00130     }
00131 }
00132 
00133 static int
00134 free_keys_i(st_data_t key, st_data_t value, void *data)
00135 {
00136     ruby_xfree((void *)key);
00137     return ST_CONTINUE;
00138 }
00139 
00140 static int
00141 free_values_i(st_data_t key, st_data_t value, void *data)
00142 {
00143     ruby_xfree((void *)value);
00144     return ST_CONTINUE;
00145 }
00146 
00147 static struct traceobj_arg *tmp_trace_arg; /* TODO: Do not use global variables */
00148 static int tmp_keep_remains;               /* TODO: Do not use global variables */
00149 
00150 static struct traceobj_arg *
00151 get_traceobj_arg(void)
00152 {
00153     if (tmp_trace_arg == 0) {
00154         tmp_trace_arg = ALLOC_N(struct traceobj_arg, 1);
00155         tmp_trace_arg->running = 0;
00156         tmp_trace_arg->keep_remains = tmp_keep_remains;
00157         tmp_trace_arg->newobj_trace = 0;
00158         tmp_trace_arg->freeobj_trace = 0;
00159         tmp_trace_arg->object_table = st_init_numtable();
00160         tmp_trace_arg->str_table = st_init_strtable();
00161     }
00162     return tmp_trace_arg;
00163 }
00164 
00165 /*
00166  * call-seq: trace_object_allocations_start
00167  *
00168  * Starts tracing object allocations.
00169  *
00170  */
00171 static VALUE
00172 trace_object_allocations_start(VALUE self)
00173 {
00174     struct traceobj_arg *arg = get_traceobj_arg();
00175 
00176     if (arg->running++ > 0) {
00177         /* do nothing */
00178     }
00179     else {
00180         if (arg->newobj_trace == 0) {
00181             arg->newobj_trace = rb_tracepoint_new(0, RUBY_INTERNAL_EVENT_NEWOBJ, newobj_i, arg);
00182             arg->freeobj_trace = rb_tracepoint_new(0, RUBY_INTERNAL_EVENT_FREEOBJ, freeobj_i, arg);
00183         }
00184         rb_tracepoint_enable(arg->newobj_trace);
00185         rb_tracepoint_enable(arg->freeobj_trace);
00186     }
00187 
00188     return Qnil;
00189 }
00190 
00191 /*
00192  * call-seq: trace_object_allocations_stop
00193  *
00194  * Stop tracing object allocations.
00195  *
00196  * Note that if ::trace_object_allocations_start is called n-times, then
00197  * tracing will stop after calling ::trace_object_allocations_stop n-times.
00198  *
00199  */
00200 static VALUE
00201 trace_object_allocations_stop(VALUE self)
00202 {
00203     struct traceobj_arg *arg = get_traceobj_arg();
00204 
00205     if (arg->running > 0) {
00206         arg->running--;
00207     }
00208 
00209     if (arg->running == 0) {
00210         rb_tracepoint_disable(arg->newobj_trace);
00211         rb_tracepoint_disable(arg->freeobj_trace);
00212         arg->newobj_trace = 0;
00213         arg->freeobj_trace = 0;
00214     }
00215 
00216     return Qnil;
00217 }
00218 
00219 /*
00220  * call-seq: trace_object_allocations_clear
00221  *
00222  * Clear recorded tracing information.
00223  *
00224  */
00225 static VALUE
00226 trace_object_allocations_clear(VALUE self)
00227 {
00228     struct traceobj_arg *arg = get_traceobj_arg();
00229 
00230     /* clear tables */
00231     st_foreach(arg->object_table, free_values_i, 0);
00232     st_clear(arg->object_table);
00233     st_foreach(arg->str_table, free_keys_i, 0);
00234     st_clear(arg->str_table);
00235 
00236     /* do not touch TracePoints */
00237 
00238     return Qnil;
00239 }
00240 
00241 /*
00242  * call-seq: trace_object_allocations { block }
00243  *
00244  * Starts tracing object allocations from the ObjectSpace extension module.
00245  *
00246  * For example:
00247  *
00248  *      require 'objspace'
00249  *
00250  *      class C
00251  *        include ObjectSpace
00252  *
00253  *        def foo
00254  *          trace_object_allocations do
00255  *            obj = Object.new
00256  *            p "#{allocation_sourcefile(obj)}:#{allocation_sourceline(obj)}"
00257  *          end
00258  *        end
00259  *      end
00260  *
00261  *      C.new.foo #=> "objtrace.rb:8"
00262  *
00263  * This example has included the ObjectSpace module to make it easier to read,
00264  * but you can also use the ::trace_object_allocations notation (recommended).
00265  *
00266  * Note that this feature introduces a huge performance decrease and huge
00267  * memory consumption.
00268  */
00269 static VALUE
00270 trace_object_allocations(VALUE self)
00271 {
00272     trace_object_allocations_start(self);
00273     return rb_ensure(rb_yield, Qnil, trace_object_allocations_stop, self);
00274 }
00275 
00276 int rb_bug_reporter_add(void (*func)(FILE *, void *), void *data);
00277 static int object_allocations_reporter_registered = 0;
00278 
00279 static int
00280 object_allocations_reporter_i(st_data_t key, st_data_t val, st_data_t ptr)
00281 {
00282     FILE *out = (FILE *)ptr;
00283     VALUE obj = (VALUE)key;
00284     struct allocation_info *info = (struct allocation_info *)val;
00285 
00286     fprintf(out, "-- %p (%s F: %p, ", (void *)obj, info->living ? "live" : "dead", (void *)info->flags);
00287     if (info->class_path) fprintf(out, "C: %s", info->class_path);
00288     else                  fprintf(out, "C: %p", (void *)info->klass);
00289     fprintf(out, "@%s:%lu", info->path ? info->path : "", info->line);
00290     if (!NIL_P(info->mid)) fprintf(out, " (%s)", rb_id2name(SYM2ID(info->mid)));
00291     fprintf(out, ")\n");
00292 
00293     return ST_CONTINUE;
00294 }
00295 
00296 static void
00297 object_allocations_reporter(FILE *out, void *ptr)
00298 {
00299     fprintf(out, "== object_allocations_reporter: START\n");
00300     if (tmp_trace_arg) {
00301         st_foreach(tmp_trace_arg->object_table, object_allocations_reporter_i, (st_data_t)out);
00302     }
00303     fprintf(out, "== object_allocations_reporter: END\n");
00304 }
00305 
00306 static VALUE
00307 trace_object_allocations_debug_start(VALUE self)
00308 {
00309     tmp_keep_remains = 1;
00310     if (object_allocations_reporter_registered == 0) {
00311         object_allocations_reporter_registered = 1;
00312         rb_bug_reporter_add(object_allocations_reporter, 0);
00313     }
00314 
00315     return trace_object_allocations_start(self);
00316 }
00317 
00318 static struct allocation_info *
00319 lookup_allocation_info(VALUE obj)
00320 {
00321     if (tmp_trace_arg) {
00322         struct allocation_info *info;
00323         if (st_lookup(tmp_trace_arg->object_table, obj, (st_data_t *)&info)) {
00324             return info;
00325         }
00326     }
00327     return NULL;
00328 }
00329 
00330 struct allocation_info *
00331 objspace_lookup_allocation_info(VALUE obj)
00332 {
00333     return lookup_allocation_info(obj);
00334 }
00335 
00336 /*
00337  * call-seq: allocation_sourcefile(object) -> string
00338  *
00339  * Returns the source file origin from the given +object+.
00340  *
00341  * See ::trace_object_allocations for more information and examples.
00342  */
00343 static VALUE
00344 allocation_sourcefile(VALUE self, VALUE obj)
00345 {
00346     struct allocation_info *info = lookup_allocation_info(obj);
00347 
00348     if (info && info->path) {
00349         return rb_str_new2(info->path);
00350     }
00351     else {
00352         return Qnil;
00353     }
00354 }
00355 
00356 /*
00357  * call-seq: allocation_sourceline(object) -> string
00358  *
00359  * Returns the original line from source for from the given +object+.
00360  *
00361  * See ::trace_object_allocations for more information and examples.
00362  */
00363 static VALUE
00364 allocation_sourceline(VALUE self, VALUE obj)
00365 {
00366     struct allocation_info *info = lookup_allocation_info(obj);
00367 
00368     if (info) {
00369         return INT2FIX(info->line);
00370     }
00371     else {
00372         return Qnil;
00373     }
00374 }
00375 
00376 /*
00377  * call-seq: allocation_class_path(object) -> string
00378  *
00379  * Returns the class for the given +object+.
00380  *
00381  *      class A
00382  *        def foo
00383  *          ObjectSpace::trace_object_allocations do
00384  *            obj = Object.new
00385  *            p "#{ObjectSpace::allocation_class_path(obj)}"
00386  *          end
00387  *        end
00388  *      end
00389  *
00390  *      A.new.foo #=> "Class"
00391  *
00392  * See ::trace_object_allocations for more information and examples.
00393  */
00394 static VALUE
00395 allocation_class_path(VALUE self, VALUE obj)
00396 {
00397     struct allocation_info *info = lookup_allocation_info(obj);
00398 
00399     if (info && info->class_path) {
00400         return rb_str_new2(info->class_path);
00401     }
00402     else {
00403         return Qnil;
00404     }
00405 }
00406 
00407 /*
00408  * call-seq: allocation_method_id(object) -> string
00409  *
00410  * Returns the method identifier for the given +object+.
00411  *
00412  *      class A
00413  *        include ObjectSpace
00414  *
00415  *        def foo
00416  *          trace_object_allocations do
00417  *            obj = Object.new
00418  *            p "#{allocation_class_path(obj)}##{allocation_method_id(obj)}"
00419  *          end
00420  *        end
00421  *      end
00422  *
00423  *      A.new.foo #=> "Class#new"
00424  *
00425  * See ::trace_object_allocations for more information and examples.
00426  */
00427 static VALUE
00428 allocation_method_id(VALUE self, VALUE obj)
00429 {
00430     struct allocation_info *info = lookup_allocation_info(obj);
00431     if (info) {
00432         return info->mid;
00433     }
00434     else {
00435         return Qnil;
00436     }
00437 }
00438 
00439 /*
00440  * call-seq: allocation_generation(object) -> Fixnum
00441  *
00442  * Returns garbage collector generation for the given +object+.
00443  *
00444  *      class B
00445  *        include ObjectSpace
00446  *
00447  *        def foo
00448  *          trace_object_allocations do
00449  *            obj = Object.new
00450  *            p "Generation is #{allocation_generation(obj)}"
00451  *          end
00452  *        end
00453  *      end
00454  *
00455  *      B.new.foo #=> "Generation is 3"
00456  *
00457  * See ::trace_object_allocations for more information and examples.
00458  */
00459 static VALUE
00460 allocation_generation(VALUE self, VALUE obj)
00461 {
00462     struct allocation_info *info = lookup_allocation_info(obj);
00463     if (info) {
00464         return SIZET2NUM(info->generation);
00465     }
00466     else {
00467         return Qnil;
00468     }
00469 }
00470 
00471 void
00472 Init_object_tracing(VALUE rb_mObjSpace)
00473 {
00474 #if 0
00475     rb_mObjSpace = rb_define_module("ObjectSpace"); /* let rdoc know */
00476 #endif
00477 
00478     rb_define_module_function(rb_mObjSpace, "trace_object_allocations", trace_object_allocations, 0);
00479     rb_define_module_function(rb_mObjSpace, "trace_object_allocations_start", trace_object_allocations_start, 0);
00480     rb_define_module_function(rb_mObjSpace, "trace_object_allocations_stop", trace_object_allocations_stop, 0);
00481     rb_define_module_function(rb_mObjSpace, "trace_object_allocations_clear", trace_object_allocations_clear, 0);
00482 
00483     rb_define_module_function(rb_mObjSpace, "trace_object_allocations_debug_start", trace_object_allocations_debug_start, 0);
00484 
00485     rb_define_module_function(rb_mObjSpace, "allocation_sourcefile", allocation_sourcefile, 1);
00486     rb_define_module_function(rb_mObjSpace, "allocation_sourceline", allocation_sourceline, 1);
00487     rb_define_module_function(rb_mObjSpace, "allocation_class_path", allocation_class_path, 1);
00488     rb_define_module_function(rb_mObjSpace, "allocation_method_id", allocation_method_id, 1);
00489     rb_define_module_function(rb_mObjSpace, "allocation_generation", allocation_generation, 1);
00490 }
00491 

Generated on 19 Jul 2016 for Ruby by  doxygen 1.4.7