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