1 /**********************************************************************
2 
3   object_tracing.c - Object Tracing mechanism/ObjectSpace extender for MRI.
4 
5   $Author$
6   created at: Mon May 27 16:27:44 2013
7 
8   NOTE: This extension library is not expected to exist except C Ruby.
9   NOTE: This feature is an example usage of internal event tracing APIs.
10 
11   All the files in this distribution are covered under the Ruby's
12   license (see the file COPYING).
13 
14 **********************************************************************/
15 
16 #include "internal.h"
17 #include "ruby/debug.h"
18 #include "objspace.h"
19 
20 struct traceobj_arg {
21     int running;
22     int keep_remains;
23     VALUE newobj_trace;
24     VALUE freeobj_trace;
25     st_table *object_table; /* obj (VALUE) -> allocation_info */
26     st_table *str_table;    /* cstr -> refcount */
27     struct traceobj_arg *prev_traceobj_arg;
28 };
29 
30 static const char *
make_unique_str(st_table * tbl,const char * str,long len)31 make_unique_str(st_table *tbl, const char *str, long len)
32 {
33     if (!str) {
34 	return NULL;
35     }
36     else {
37 	st_data_t n;
38 	char *result;
39 
40 	if (st_lookup(tbl, (st_data_t)str, &n)) {
41 	    st_insert(tbl, (st_data_t)str, n+1);
42 	    st_get_key(tbl, (st_data_t)str, &n);
43 	    result = (char *)n;
44 	}
45 	else {
46 	    result = (char *)ruby_xmalloc(len+1);
47 	    strncpy(result, str, len);
48 	    result[len] = 0;
49 	    st_add_direct(tbl, (st_data_t)result, 1);
50 	}
51 	return result;
52     }
53 }
54 
55 static void
delete_unique_str(st_table * tbl,const char * str)56 delete_unique_str(st_table *tbl, const char *str)
57 {
58     if (str) {
59 	st_data_t n;
60 
61 	st_lookup(tbl, (st_data_t)str, &n);
62 	if (n == 1) {
63 	    n = (st_data_t)str;
64 	    st_delete(tbl, &n, 0);
65 	    ruby_xfree((char *)n);
66 	}
67 	else {
68 	    st_insert(tbl, (st_data_t)str, n-1);
69 	}
70     }
71 }
72 
73 static void
newobj_i(VALUE tpval,void * data)74 newobj_i(VALUE tpval, void *data)
75 {
76     struct traceobj_arg *arg = (struct traceobj_arg *)data;
77     rb_trace_arg_t *tparg = rb_tracearg_from_tracepoint(tpval);
78     VALUE obj = rb_tracearg_object(tparg);
79     VALUE path = rb_tracearg_path(tparg);
80     VALUE line = rb_tracearg_lineno(tparg);
81     VALUE mid = rb_tracearg_method_id(tparg);
82     VALUE klass = rb_tracearg_defined_class(tparg);
83     struct allocation_info *info;
84     const char *path_cstr = RTEST(path) ? make_unique_str(arg->str_table, RSTRING_PTR(path), RSTRING_LEN(path)) : 0;
85     VALUE class_path = (RTEST(klass) && !OBJ_FROZEN(klass)) ? rb_class_path_cached(klass) : Qnil;
86     const char *class_path_cstr = RTEST(class_path) ? make_unique_str(arg->str_table, RSTRING_PTR(class_path), RSTRING_LEN(class_path)) : 0;
87     st_data_t v;
88 
89     if (st_lookup(arg->object_table, (st_data_t)obj, &v)) {
90 	info = (struct allocation_info *)v;
91 	if (arg->keep_remains) {
92 	    if (info->living) {
93 		/* do nothing. there is possibility to keep living if FREEOBJ events while suppressing tracing */
94 	    }
95 	}
96 	/* reuse info */
97 	delete_unique_str(arg->str_table, info->path);
98 	delete_unique_str(arg->str_table, info->class_path);
99     }
100     else {
101 	info = (struct allocation_info *)ruby_xmalloc(sizeof(struct allocation_info));
102     }
103     info->living = 1;
104     info->flags = RBASIC(obj)->flags;
105     info->klass = RBASIC_CLASS(obj);
106 
107     info->path = path_cstr;
108     info->line = NUM2INT(line);
109     info->mid = mid;
110     info->class_path = class_path_cstr;
111     info->generation = rb_gc_count();
112     st_insert(arg->object_table, (st_data_t)obj, (st_data_t)info);
113 }
114 
115 static void
freeobj_i(VALUE tpval,void * data)116 freeobj_i(VALUE tpval, void *data)
117 {
118     struct traceobj_arg *arg = (struct traceobj_arg *)data;
119     rb_trace_arg_t *tparg = rb_tracearg_from_tracepoint(tpval);
120     st_data_t obj = (st_data_t)rb_tracearg_object(tparg);
121     st_data_t v;
122     struct allocation_info *info;
123 
124     if (arg->keep_remains) {
125 	if (st_lookup(arg->object_table, obj, &v)) {
126 	    info = (struct allocation_info *)v;
127 	    info->living = 0;
128 	}
129     }
130     else {
131 	if (st_delete(arg->object_table, &obj, &v)) {
132 	    info = (struct allocation_info *)v;
133 	    delete_unique_str(arg->str_table, info->path);
134 	    delete_unique_str(arg->str_table, info->class_path);
135 	    ruby_xfree(info);
136 	}
137     }
138 }
139 
140 static int
free_keys_i(st_data_t key,st_data_t value,void * data)141 free_keys_i(st_data_t key, st_data_t value, void *data)
142 {
143     ruby_xfree((void *)key);
144     return ST_CONTINUE;
145 }
146 
147 static int
free_values_i(st_data_t key,st_data_t value,void * data)148 free_values_i(st_data_t key, st_data_t value, void *data)
149 {
150     ruby_xfree((void *)value);
151     return ST_CONTINUE;
152 }
153 
154 static struct traceobj_arg *tmp_trace_arg; /* TODO: Do not use global variables */
155 static int tmp_keep_remains;               /* TODO: Do not use global variables */
156 
157 static struct traceobj_arg *
get_traceobj_arg(void)158 get_traceobj_arg(void)
159 {
160     if (tmp_trace_arg == 0) {
161 	tmp_trace_arg = ALLOC_N(struct traceobj_arg, 1);
162 	tmp_trace_arg->running = 0;
163 	tmp_trace_arg->keep_remains = tmp_keep_remains;
164 	tmp_trace_arg->newobj_trace = 0;
165 	tmp_trace_arg->freeobj_trace = 0;
166 	tmp_trace_arg->object_table = st_init_numtable();
167 	tmp_trace_arg->str_table = st_init_strtable();
168     }
169     return tmp_trace_arg;
170 }
171 
172 /*
173  * call-seq: trace_object_allocations_start
174  *
175  * Starts tracing object allocations.
176  *
177  */
178 static VALUE
trace_object_allocations_start(VALUE self)179 trace_object_allocations_start(VALUE self)
180 {
181     struct traceobj_arg *arg = get_traceobj_arg();
182 
183     if (arg->running++ > 0) {
184 	/* do nothing */
185     }
186     else {
187 	if (arg->newobj_trace == 0) {
188 	    arg->newobj_trace = rb_tracepoint_new(0, RUBY_INTERNAL_EVENT_NEWOBJ, newobj_i, arg);
189 	    rb_gc_register_mark_object(arg->newobj_trace);
190 	    arg->freeobj_trace = rb_tracepoint_new(0, RUBY_INTERNAL_EVENT_FREEOBJ, freeobj_i, arg);
191 	    rb_gc_register_mark_object(arg->freeobj_trace);
192 	}
193 	rb_tracepoint_enable(arg->newobj_trace);
194 	rb_tracepoint_enable(arg->freeobj_trace);
195     }
196 
197     return Qnil;
198 }
199 
200 /*
201  * call-seq: trace_object_allocations_stop
202  *
203  * Stop tracing object allocations.
204  *
205  * Note that if ::trace_object_allocations_start is called n-times, then
206  * tracing will stop after calling ::trace_object_allocations_stop n-times.
207  *
208  */
209 static VALUE
trace_object_allocations_stop(VALUE self)210 trace_object_allocations_stop(VALUE self)
211 {
212     struct traceobj_arg *arg = get_traceobj_arg();
213 
214     if (arg->running > 0) {
215 	arg->running--;
216     }
217 
218     if (arg->running == 0) {
219 	rb_tracepoint_disable(arg->newobj_trace);
220 	rb_tracepoint_disable(arg->freeobj_trace);
221     }
222 
223     return Qnil;
224 }
225 
226 /*
227  * call-seq: trace_object_allocations_clear
228  *
229  * Clear recorded tracing information.
230  *
231  */
232 static VALUE
trace_object_allocations_clear(VALUE self)233 trace_object_allocations_clear(VALUE self)
234 {
235     struct traceobj_arg *arg = get_traceobj_arg();
236 
237     /* clear tables */
238     st_foreach(arg->object_table, free_values_i, 0);
239     st_clear(arg->object_table);
240     st_foreach(arg->str_table, free_keys_i, 0);
241     st_clear(arg->str_table);
242 
243     /* do not touch TracePoints */
244 
245     return Qnil;
246 }
247 
248 /*
249  * call-seq: trace_object_allocations { block }
250  *
251  * Starts tracing object allocations from the ObjectSpace extension module.
252  *
253  * For example:
254  *
255  *	require 'objspace'
256  *
257  *	class C
258  *	  include ObjectSpace
259  *
260  *	  def foo
261  *	    trace_object_allocations do
262  *	      obj = Object.new
263  *	      p "#{allocation_sourcefile(obj)}:#{allocation_sourceline(obj)}"
264  *	    end
265  *	  end
266  *	end
267  *
268  *	C.new.foo #=> "objtrace.rb:8"
269  *
270  * This example has included the ObjectSpace module to make it easier to read,
271  * but you can also use the ::trace_object_allocations notation (recommended).
272  *
273  * Note that this feature introduces a huge performance decrease and huge
274  * memory consumption.
275  */
276 static VALUE
trace_object_allocations(VALUE self)277 trace_object_allocations(VALUE self)
278 {
279     trace_object_allocations_start(self);
280     return rb_ensure(rb_yield, Qnil, trace_object_allocations_stop, self);
281 }
282 
283 int rb_bug_reporter_add(void (*func)(FILE *, void *), void *data);
284 static int object_allocations_reporter_registered = 0;
285 
286 static int
object_allocations_reporter_i(st_data_t key,st_data_t val,st_data_t ptr)287 object_allocations_reporter_i(st_data_t key, st_data_t val, st_data_t ptr)
288 {
289     FILE *out = (FILE *)ptr;
290     VALUE obj = (VALUE)key;
291     struct allocation_info *info = (struct allocation_info *)val;
292 
293     fprintf(out, "-- %p (%s F: %p, ", (void *)obj, info->living ? "live" : "dead", (void *)info->flags);
294     if (info->class_path) fprintf(out, "C: %s", info->class_path);
295     else                  fprintf(out, "C: %p", (void *)info->klass);
296     fprintf(out, "@%s:%lu", info->path ? info->path : "", info->line);
297     if (!NIL_P(info->mid)) {
298 	VALUE m = rb_sym2str(info->mid);
299 	fprintf(out, " (%s)", RSTRING_PTR(m));
300     }
301     fprintf(out, ")\n");
302 
303     return ST_CONTINUE;
304 }
305 
306 static void
object_allocations_reporter(FILE * out,void * ptr)307 object_allocations_reporter(FILE *out, void *ptr)
308 {
309     fprintf(out, "== object_allocations_reporter: START\n");
310     if (tmp_trace_arg) {
311 	st_foreach(tmp_trace_arg->object_table, object_allocations_reporter_i, (st_data_t)out);
312     }
313     fprintf(out, "== object_allocations_reporter: END\n");
314 }
315 
316 static VALUE
trace_object_allocations_debug_start(VALUE self)317 trace_object_allocations_debug_start(VALUE self)
318 {
319     tmp_keep_remains = 1;
320     if (object_allocations_reporter_registered == 0) {
321 	object_allocations_reporter_registered = 1;
322 	rb_bug_reporter_add(object_allocations_reporter, 0);
323     }
324 
325     return trace_object_allocations_start(self);
326 }
327 
328 static struct allocation_info *
lookup_allocation_info(VALUE obj)329 lookup_allocation_info(VALUE obj)
330 {
331     if (tmp_trace_arg) {
332 	st_data_t info;
333 	if (st_lookup(tmp_trace_arg->object_table, obj, &info)) {
334 	    return (struct allocation_info *)info;
335 	}
336     }
337     return NULL;
338 }
339 
340 struct allocation_info *
objspace_lookup_allocation_info(VALUE obj)341 objspace_lookup_allocation_info(VALUE obj)
342 {
343     return lookup_allocation_info(obj);
344 }
345 
346 /*
347  * call-seq: allocation_sourcefile(object) -> string
348  *
349  * Returns the source file origin from the given +object+.
350  *
351  * See ::trace_object_allocations for more information and examples.
352  */
353 static VALUE
allocation_sourcefile(VALUE self,VALUE obj)354 allocation_sourcefile(VALUE self, VALUE obj)
355 {
356     struct allocation_info *info = lookup_allocation_info(obj);
357 
358     if (info && info->path) {
359 	return rb_str_new2(info->path);
360     }
361     else {
362 	return Qnil;
363     }
364 }
365 
366 /*
367  * call-seq: allocation_sourceline(object) -> integer
368  *
369  * Returns the original line from source for from the given +object+.
370  *
371  * See ::trace_object_allocations for more information and examples.
372  */
373 static VALUE
allocation_sourceline(VALUE self,VALUE obj)374 allocation_sourceline(VALUE self, VALUE obj)
375 {
376     struct allocation_info *info = lookup_allocation_info(obj);
377 
378     if (info) {
379 	return INT2FIX(info->line);
380     }
381     else {
382 	return Qnil;
383     }
384 }
385 
386 /*
387  * call-seq: allocation_class_path(object) -> string
388  *
389  * Returns the class for the given +object+.
390  *
391  *	class A
392  *	  def foo
393  *	    ObjectSpace::trace_object_allocations do
394  *	      obj = Object.new
395  *	      p "#{ObjectSpace::allocation_class_path(obj)}"
396  *	    end
397  *	  end
398  *	end
399  *
400  *	A.new.foo #=> "Class"
401  *
402  * See ::trace_object_allocations for more information and examples.
403  */
404 static VALUE
allocation_class_path(VALUE self,VALUE obj)405 allocation_class_path(VALUE self, VALUE obj)
406 {
407     struct allocation_info *info = lookup_allocation_info(obj);
408 
409     if (info && info->class_path) {
410 	return rb_str_new2(info->class_path);
411     }
412     else {
413 	return Qnil;
414     }
415 }
416 
417 /*
418  * call-seq: allocation_method_id(object) -> string
419  *
420  * Returns the method identifier for the given +object+.
421  *
422  *	class A
423  *	  include ObjectSpace
424  *
425  *	  def foo
426  *	    trace_object_allocations do
427  *	      obj = Object.new
428  *	      p "#{allocation_class_path(obj)}##{allocation_method_id(obj)}"
429  *	    end
430  *	  end
431  *	end
432  *
433  *	A.new.foo #=> "Class#new"
434  *
435  * See ::trace_object_allocations for more information and examples.
436  */
437 static VALUE
allocation_method_id(VALUE self,VALUE obj)438 allocation_method_id(VALUE self, VALUE obj)
439 {
440     struct allocation_info *info = lookup_allocation_info(obj);
441     if (info) {
442 	return info->mid;
443     }
444     else {
445 	return Qnil;
446     }
447 }
448 
449 /*
450  * call-seq: allocation_generation(object) -> integer or nil
451  *
452  * Returns garbage collector generation for the given +object+.
453  *
454  *	class B
455  *	  include ObjectSpace
456  *
457  *	  def foo
458  *	    trace_object_allocations do
459  *	      obj = Object.new
460  *	      p "Generation is #{allocation_generation(obj)}"
461  *	    end
462  *	  end
463  *	end
464  *
465  *	B.new.foo #=> "Generation is 3"
466  *
467  * See ::trace_object_allocations for more information and examples.
468  */
469 static VALUE
allocation_generation(VALUE self,VALUE obj)470 allocation_generation(VALUE self, VALUE obj)
471 {
472     struct allocation_info *info = lookup_allocation_info(obj);
473     if (info) {
474 	return SIZET2NUM(info->generation);
475     }
476     else {
477 	return Qnil;
478     }
479 }
480 
481 void
Init_object_tracing(VALUE rb_mObjSpace)482 Init_object_tracing(VALUE rb_mObjSpace)
483 {
484 #if 0
485     rb_mObjSpace = rb_define_module("ObjectSpace"); /* let rdoc know */
486 #endif
487 
488     rb_define_module_function(rb_mObjSpace, "trace_object_allocations", trace_object_allocations, 0);
489     rb_define_module_function(rb_mObjSpace, "trace_object_allocations_start", trace_object_allocations_start, 0);
490     rb_define_module_function(rb_mObjSpace, "trace_object_allocations_stop", trace_object_allocations_stop, 0);
491     rb_define_module_function(rb_mObjSpace, "trace_object_allocations_clear", trace_object_allocations_clear, 0);
492 
493     rb_define_module_function(rb_mObjSpace, "trace_object_allocations_debug_start", trace_object_allocations_debug_start, 0);
494 
495     rb_define_module_function(rb_mObjSpace, "allocation_sourcefile", allocation_sourcefile, 1);
496     rb_define_module_function(rb_mObjSpace, "allocation_sourceline", allocation_sourceline, 1);
497     rb_define_module_function(rb_mObjSpace, "allocation_class_path", allocation_class_path, 1);
498     rb_define_module_function(rb_mObjSpace, "allocation_method_id", allocation_method_id, 1);
499     rb_define_module_function(rb_mObjSpace, "allocation_generation", allocation_generation, 1);
500 }
501