Ruby 3.0.5p211 (2022-11-24 revision ba5cf0f7c52d4d35cc6a173c89eda98ceffa2dcf)
object_tracing.c
Go to the documentation of this file.
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
25 st_table *object_table; /* obj (VALUE) -> allocation_info */
26 st_table *str_table; /* cstr -> refcount */
28};
29
30static const char *
31make_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
55static void
56delete_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
73static void
74newobj_i(VALUE tpval, void *data)
75{
76 struct traceobj_arg *arg = (struct traceobj_arg *)data;
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;
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
115static void
116freeobj_i(VALUE tpval, void *data)
117{
118 struct traceobj_arg *arg = (struct traceobj_arg *)data;
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
140static int
141free_keys_i(st_data_t key, st_data_t value, st_data_t data)
142{
143 ruby_xfree((void *)key);
144 return ST_CONTINUE;
145}
146
147static int
148free_values_i(st_data_t key, st_data_t value, st_data_t data)
149{
150 ruby_xfree((void *)value);
151 return ST_CONTINUE;
152}
153
154static void
155allocation_info_tracer_mark(void *ptr)
156{
157 struct traceobj_arg *trace_arg = (struct traceobj_arg *)ptr;
158 rb_gc_mark(trace_arg->newobj_trace);
159 rb_gc_mark(trace_arg->freeobj_trace);
160}
161
162static void
163allocation_info_tracer_free(void *ptr)
164{
165 struct traceobj_arg *arg = (struct traceobj_arg *)ptr;
166 /* clear tables */
167 st_foreach(arg->object_table, free_values_i, 0);
169 st_foreach(arg->str_table, free_keys_i, 0);
171 xfree(arg);
172}
173
174static size_t
175allocation_info_tracer_memsize(const void *ptr)
176{
177 size_t size;
178 struct traceobj_arg *trace_arg = (struct traceobj_arg *)ptr;
179 size = sizeof(*trace_arg);
180 size += st_memsize(trace_arg->object_table);
181 size += st_memsize(trace_arg->str_table);
182 return size;
183}
184
185static int
186hash_foreach_should_replace_key(st_data_t key, st_data_t value, st_data_t argp, int error)
187{
188 VALUE allocated_object;
189
190 allocated_object = (VALUE)value;
191 if (allocated_object != rb_gc_location(allocated_object)) {
192 return ST_REPLACE;
193 }
194
195 return ST_CONTINUE;
196}
197
198static int
199hash_replace_key(st_data_t *key, st_data_t *value, st_data_t argp, int existing)
200{
202
203 return ST_CONTINUE;
204}
205
206static void
207allocation_info_tracer_compact(void *ptr)
208{
209 struct traceobj_arg *trace_arg = (struct traceobj_arg *)ptr;
210
211 if (trace_arg->object_table &&
212 st_foreach_with_replace(trace_arg->object_table, hash_foreach_should_replace_key, hash_replace_key, 0)) {
213 rb_raise(rb_eRuntimeError, "hash modified during iteration");
214 }
215}
216
217static const rb_data_type_t allocation_info_tracer_type = {
218 "ObjectTracing/allocation_info_tracer",
219 {
220 allocation_info_tracer_mark,
221 allocation_info_tracer_free, /* Never called because global */
222 allocation_info_tracer_memsize,
223 allocation_info_tracer_compact,
224 },
226};
227
228static VALUE traceobj_arg;
229static struct traceobj_arg *tmp_trace_arg; /* TODO: Do not use global variables */
230static int tmp_keep_remains; /* TODO: Do not use global variables */
231
232static struct traceobj_arg *
233get_traceobj_arg(void)
234{
235 if (tmp_trace_arg == 0) {
236 VALUE obj = TypedData_Make_Struct(rb_cObject, struct traceobj_arg, &allocation_info_tracer_type, tmp_trace_arg);
237 traceobj_arg = obj;
239 tmp_trace_arg->running = 0;
240 tmp_trace_arg->keep_remains = tmp_keep_remains;
241 tmp_trace_arg->newobj_trace = 0;
242 tmp_trace_arg->freeobj_trace = 0;
243 tmp_trace_arg->object_table = st_init_numtable();
244 tmp_trace_arg->str_table = st_init_strtable();
245 }
246 return tmp_trace_arg;
247}
248
249/*
250 * call-seq: trace_object_allocations_start
251 *
252 * Starts tracing object allocations.
253 *
254 */
255static VALUE
256trace_object_allocations_start(VALUE self)
257{
258 struct traceobj_arg *arg = get_traceobj_arg();
259
260 if (arg->running++ > 0) {
261 /* do nothing */
262 }
263 else {
264 if (arg->newobj_trace == 0) {
267 }
270 }
271
272 return Qnil;
273}
274
275/*
276 * call-seq: trace_object_allocations_stop
277 *
278 * Stop tracing object allocations.
279 *
280 * Note that if ::trace_object_allocations_start is called n-times, then
281 * tracing will stop after calling ::trace_object_allocations_stop n-times.
282 *
283 */
284static VALUE
285trace_object_allocations_stop(VALUE self)
286{
287 struct traceobj_arg *arg = get_traceobj_arg();
288
289 if (arg->running > 0) {
290 arg->running--;
291 }
292
293 if (arg->running == 0) {
294 if (arg->newobj_trace != 0) {
296 }
297 if (arg->freeobj_trace != 0) {
299 }
300 }
301
302 return Qnil;
303}
304
305/*
306 * call-seq: trace_object_allocations_clear
307 *
308 * Clear recorded tracing information.
309 *
310 */
311static VALUE
312trace_object_allocations_clear(VALUE self)
313{
314 struct traceobj_arg *arg = get_traceobj_arg();
315
316 /* clear tables */
317 st_foreach(arg->object_table, free_values_i, 0);
319 st_foreach(arg->str_table, free_keys_i, 0);
320 st_clear(arg->str_table);
321
322 /* do not touch TracePoints */
323
324 return Qnil;
325}
326
327/*
328 * call-seq: trace_object_allocations { block }
329 *
330 * Starts tracing object allocations from the ObjectSpace extension module.
331 *
332 * For example:
333 *
334 * require 'objspace'
335 *
336 * class C
337 * include ObjectSpace
338 *
339 * def foo
340 * trace_object_allocations do
341 * obj = Object.new
342 * p "#{allocation_sourcefile(obj)}:#{allocation_sourceline(obj)}"
343 * end
344 * end
345 * end
346 *
347 * C.new.foo #=> "objtrace.rb:8"
348 *
349 * This example has included the ObjectSpace module to make it easier to read,
350 * but you can also use the ::trace_object_allocations notation (recommended).
351 *
352 * Note that this feature introduces a huge performance decrease and huge
353 * memory consumption.
354 */
355static VALUE
356trace_object_allocations(VALUE self)
357{
358 trace_object_allocations_start(self);
359 return rb_ensure(rb_yield, Qnil, trace_object_allocations_stop, self);
360}
361
362int rb_bug_reporter_add(void (*func)(FILE *, void *), void *data);
363static int object_allocations_reporter_registered = 0;
364
365static int
366object_allocations_reporter_i(st_data_t key, st_data_t val, st_data_t ptr)
367{
368 FILE *out = (FILE *)ptr;
369 VALUE obj = (VALUE)key;
370 struct allocation_info *info = (struct allocation_info *)val;
371
372 fprintf(out, "-- %p (%s F: %p, ", (void *)obj, info->living ? "live" : "dead", (void *)info->flags);
373 if (info->class_path) fprintf(out, "C: %s", info->class_path);
374 else fprintf(out, "C: %p", (void *)info->klass);
375 fprintf(out, "@%s:%lu", info->path ? info->path : "", info->line);
376 if (!NIL_P(info->mid)) {
377 VALUE m = rb_sym2str(info->mid);
378 fprintf(out, " (%s)", RSTRING_PTR(m));
379 }
380 fprintf(out, ")\n");
381
382 return ST_CONTINUE;
383}
384
385static void
386object_allocations_reporter(FILE *out, void *ptr)
387{
388 fprintf(out, "== object_allocations_reporter: START\n");
389 if (tmp_trace_arg) {
390 st_foreach(tmp_trace_arg->object_table, object_allocations_reporter_i, (st_data_t)out);
391 }
392 fprintf(out, "== object_allocations_reporter: END\n");
393}
394
395static VALUE
396trace_object_allocations_debug_start(VALUE self)
397{
398 tmp_keep_remains = 1;
399 if (object_allocations_reporter_registered == 0) {
400 object_allocations_reporter_registered = 1;
401 rb_bug_reporter_add(object_allocations_reporter, 0);
402 }
403
404 return trace_object_allocations_start(self);
405}
406
407static struct allocation_info *
408lookup_allocation_info(VALUE obj)
409{
410 if (tmp_trace_arg) {
411 st_data_t info;
412 if (st_lookup(tmp_trace_arg->object_table, obj, &info)) {
413 return (struct allocation_info *)info;
414 }
415 }
416 return NULL;
417}
418
419struct allocation_info *
421{
422 return lookup_allocation_info(obj);
423}
424
425/*
426 * call-seq: allocation_sourcefile(object) -> string
427 *
428 * Returns the source file origin from the given +object+.
429 *
430 * See ::trace_object_allocations for more information and examples.
431 */
432static VALUE
433allocation_sourcefile(VALUE self, VALUE obj)
434{
435 struct allocation_info *info = lookup_allocation_info(obj);
436
437 if (info && info->path) {
438 return rb_str_new2(info->path);
439 }
440 else {
441 return Qnil;
442 }
443}
444
445/*
446 * call-seq: allocation_sourceline(object) -> integer
447 *
448 * Returns the original line from source for from the given +object+.
449 *
450 * See ::trace_object_allocations for more information and examples.
451 */
452static VALUE
453allocation_sourceline(VALUE self, VALUE obj)
454{
455 struct allocation_info *info = lookup_allocation_info(obj);
456
457 if (info) {
458 return INT2FIX(info->line);
459 }
460 else {
461 return Qnil;
462 }
463}
464
465/*
466 * call-seq: allocation_class_path(object) -> string
467 *
468 * Returns the class for the given +object+.
469 *
470 * class A
471 * def foo
472 * ObjectSpace::trace_object_allocations do
473 * obj = Object.new
474 * p "#{ObjectSpace::allocation_class_path(obj)}"
475 * end
476 * end
477 * end
478 *
479 * A.new.foo #=> "Class"
480 *
481 * See ::trace_object_allocations for more information and examples.
482 */
483static VALUE
484allocation_class_path(VALUE self, VALUE obj)
485{
486 struct allocation_info *info = lookup_allocation_info(obj);
487
488 if (info && info->class_path) {
489 return rb_str_new2(info->class_path);
490 }
491 else {
492 return Qnil;
493 }
494}
495
496/*
497 * call-seq: allocation_method_id(object) -> string
498 *
499 * Returns the method identifier for the given +object+.
500 *
501 * class A
502 * include ObjectSpace
503 *
504 * def foo
505 * trace_object_allocations do
506 * obj = Object.new
507 * p "#{allocation_class_path(obj)}##{allocation_method_id(obj)}"
508 * end
509 * end
510 * end
511 *
512 * A.new.foo #=> "Class#new"
513 *
514 * See ::trace_object_allocations for more information and examples.
515 */
516static VALUE
517allocation_method_id(VALUE self, VALUE obj)
518{
519 struct allocation_info *info = lookup_allocation_info(obj);
520 if (info) {
521 return info->mid;
522 }
523 else {
524 return Qnil;
525 }
526}
527
528/*
529 * call-seq: allocation_generation(object) -> integer or nil
530 *
531 * Returns garbage collector generation for the given +object+.
532 *
533 * class B
534 * include ObjectSpace
535 *
536 * def foo
537 * trace_object_allocations do
538 * obj = Object.new
539 * p "Generation is #{allocation_generation(obj)}"
540 * end
541 * end
542 * end
543 *
544 * B.new.foo #=> "Generation is 3"
545 *
546 * See ::trace_object_allocations for more information and examples.
547 */
548static VALUE
549allocation_generation(VALUE self, VALUE obj)
550{
551 struct allocation_info *info = lookup_allocation_info(obj);
552 if (info) {
553 return SIZET2NUM(info->generation);
554 }
555 else {
556 return Qnil;
557 }
558}
559
560void
562{
563#if 0
564 rb_mObjSpace = rb_define_module("ObjectSpace"); /* let rdoc know */
565#endif
566
567 rb_define_module_function(rb_mObjSpace, "trace_object_allocations", trace_object_allocations, 0);
568 rb_define_module_function(rb_mObjSpace, "trace_object_allocations_start", trace_object_allocations_start, 0);
569 rb_define_module_function(rb_mObjSpace, "trace_object_allocations_stop", trace_object_allocations_stop, 0);
570 rb_define_module_function(rb_mObjSpace, "trace_object_allocations_clear", trace_object_allocations_clear, 0);
571
572 rb_define_module_function(rb_mObjSpace, "trace_object_allocations_debug_start", trace_object_allocations_debug_start, 0);
573
574 rb_define_module_function(rb_mObjSpace, "allocation_sourcefile", allocation_sourcefile, 1);
575 rb_define_module_function(rb_mObjSpace, "allocation_sourceline", allocation_sourceline, 1);
576 rb_define_module_function(rb_mObjSpace, "allocation_class_path", allocation_class_path, 1);
577 rb_define_module_function(rb_mObjSpace, "allocation_method_id", allocation_method_id, 1);
578 rb_define_module_function(rb_mObjSpace, "allocation_generation", allocation_generation, 1);
579}
#define rb_define_module_function(klass, mid, func, arity)
Defines klass#mid and makes it a module function.
Definition: cxxanyargs.hpp:672
struct RIMemo * ptr
Definition: debug.c:88
VALUE rb_tracearg_object(rb_trace_arg_t *trace_arg)
Definition: vm_trace.c:1018
VALUE rb_tracearg_defined_class(rb_trace_arg_t *trace_arg)
Definition: vm_trace.c:910
VALUE rb_tracepoint_new(VALUE target_thread_not_supported_yet, rb_event_flag_t events, void(*func)(VALUE, void *), void *data)
Definition: vm_trace.c:1407
VALUE rb_tracepoint_disable(VALUE tpval)
Definition: vm_trace.c:1233
VALUE rb_tracearg_path(rb_trace_arg_t *trace_arg)
Definition: vm_trace.c:828
VALUE rb_tracepoint_enable(VALUE tpval)
Definition: vm_trace.c:1125
VALUE rb_tracearg_method_id(rb_trace_arg_t *trace_arg)
Definition: vm_trace.c:896
rb_trace_arg_t * rb_tracearg_from_tracepoint(VALUE tpval)
Definition: vm_trace.c:796
VALUE rb_tracearg_lineno(rb_trace_arg_t *trace_arg)
Definition: vm_trace.c:822
string_t out
Definition: enough.c:230
uint8_t len
Definition: escape.c:17
char str[HTML_ESCAPE_MAX_LEN+1]
Definition: escape.c:18
#define RUBY_INTERNAL_EVENT_FREEOBJ
Definition: event.h:57
#define RUBY_INTERNAL_EVENT_NEWOBJ
Definition: event.h:56
#define RSTRING_LEN(string)
Definition: fbuffer.h:22
#define RSTRING_PTR(string)
Definition: fbuffer.h:19
void ruby_xfree(void *x)
Deallocates a storage instance.
Definition: gc.c:10914
VALUE rb_gc_location(VALUE value)
Definition: gc.c:9003
void rb_gc_mark(VALUE ptr)
Definition: gc.c:6112
void * ruby_xmalloc(size_t size)
Allocates a storage instance.
Definition: gc.c:12795
void rb_gc_register_mark_object(VALUE obj)
Inform the garbage collector that object is a live Ruby object that should not be moved.
Definition: gc.c:8022
size_t rb_gc_count(void)
Definition: gc.c:9529
VALUE rb_define_module(const char *name)
Definition: class.c:871
#define OBJ_FROZEN
Definition: fl_type.h:136
int rb_bug_reporter_add(void(*func)(FILE *, void *), void *data)
Definition: error.c:587
void rb_raise(VALUE exc, const char *fmt,...)
Definition: error.c:2917
VALUE rb_eRuntimeError
Definition: error.c:1055
VALUE rb_ensure(VALUE(*b_proc)(VALUE), VALUE data1, VALUE(*e_proc)(VALUE), VALUE data2)
An equivalent to ensure clause.
Definition: eval.c:1148
VALUE rb_cObject
Object class.
Definition: object.c:49
#define rb_str_new2
Definition: string.h:276
VALUE rb_class_path_cached(VALUE)
Definition: variable.c:178
VALUE rb_sym2str(VALUE)
Definition: symbol.c:927
#define NUM2INT
Definition: int.h:44
voidpf void uLong size
Definition: ioapi.h:138
VALUE rb_yield(VALUE)
Definition: vm_eval.c:1341
#define INT2FIX
Definition: long.h:48
void Init_object_tracing(VALUE rb_mObjSpace)
struct allocation_info * objspace_lookup_allocation_info(VALUE obj)
#define RBASIC(obj)
Definition: rbasic.h:34
#define RBASIC_CLASS
Definition: rbasic.h:35
#define NULL
Definition: regenc.h:69
@ RUBY_TYPED_FREE_IMMEDIATELY
Definition: rtypeddata.h:62
#define TypedData_Make_Struct(klass, type, data_type, sval)
Definition: rtypeddata.h:122
#define SIZET2NUM
Definition: size_t.h:52
#define RTEST
#define Qnil
#define NIL_P
@ ST_REPLACE
Definition: st.h:99
@ ST_CONTINUE
Definition: st.h:99
unsigned long st_data_t
Definition: st.h:22
#define st_get_key
Definition: st.h:130
#define st_foreach
Definition: st.h:142
#define st_init_numtable
Definition: st.h:106
#define st_lookup
Definition: st.h:128
#define st_clear
Definition: st.h:160
#define st_add_direct
Definition: st.h:154
#define st_delete
Definition: st.h:118
#define st_memsize
Definition: st.h:174
#define st_insert
Definition: st.h:124
#define st_foreach_with_replace
Definition: st.h:140
#define st_free_table
Definition: st.h:156
#define st_init_strtable
Definition: st.h:110
const char * class_path
Definition: objspace.h:14
VALUE flags
Definition: objspace.h:8
VALUE klass
Definition: objspace.h:9
unsigned long line
Definition: objspace.h:13
const char * path
Definition: objspace.h:12
size_t generation
Definition: objspace.h:16
Definition: st.h:79
st_table * str_table
struct traceobj_arg * prev_traceobj_arg
st_table * object_table
VALUE freeobj_trace
VALUE newobj_trace
void error(const char *msg)
Definition: untgz.c:593
unsigned long VALUE
Definition: value.h:38
#define xfree
Definition: xmalloc.h:49