Ruby 2.7.7p221 (2022-11-24 revision 168ec2b1e5ad0e4688e963d9de019557c78feed9)
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 {
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) {
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;
80 VALUE line = rb_tracearg_lineno(tparg);
81 VALUE mid = rb_tracearg_method_id(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;
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 struct traceobj_arg *tmp_trace_arg; /* TODO: Do not use global variables */
155static int tmp_keep_remains; /* TODO: Do not use global variables */
156
157static struct traceobj_arg *
158get_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 */
178static VALUE
179trace_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 */
209static VALUE
210trace_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 */
232static VALUE
233trace_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 */
276static VALUE
277trace_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
283int rb_bug_reporter_add(void (*func)(FILE *, void *), void *data);
284static int object_allocations_reporter_registered = 0;
285
286static int
287object_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
306static void
307object_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
316static VALUE
317trace_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
328static struct allocation_info *
329lookup_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
340struct allocation_info *
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 */
353static VALUE
354allocation_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 */
373static VALUE
374allocation_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 */
404static VALUE
405allocation_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 */
437static VALUE
438allocation_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 */
469static VALUE
470allocation_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
481void
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}
struct RIMemo * ptr
Definition: debug.c:65
VALUE rb_tracearg_object(rb_trace_arg_t *trace_arg)
Definition: vm_trace.c:1014
VALUE rb_tracearg_defined_class(rb_trace_arg_t *trace_arg)
Definition: vm_trace.c:906
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:1400
VALUE rb_tracepoint_disable(VALUE tpval)
Definition: vm_trace.c:1227
VALUE rb_tracearg_path(rb_trace_arg_t *trace_arg)
Definition: vm_trace.c:824
VALUE rb_tracepoint_enable(VALUE tpval)
Definition: vm_trace.c:1119
VALUE rb_tracearg_method_id(rb_trace_arg_t *trace_arg)
Definition: vm_trace.c:892
rb_trace_arg_t * rb_tracearg_from_tracepoint(VALUE tpval)
Definition: vm_trace.c:792
VALUE rb_tracearg_lineno(rb_trace_arg_t *trace_arg)
Definition: vm_trace.c:818
char str[HTML_ESCAPE_MAX_LEN+1]
Definition: escape.c:18
VALUE rb_define_module(const char *)
Definition: class.c:785
int rb_bug_reporter_add(void(*func)(FILE *, void *), void *data)
Definition: error.c:457
VALUE rb_ensure(VALUE(*)(VALUE), VALUE, VALUE(*)(VALUE), VALUE)
An equivalent to ensure clause.
Definition: eval.c:1115
void Init_object_tracing(VALUE rb_mObjSpace)
struct allocation_info * objspace_lookup_allocation_info(VALUE obj)
#define rb_str_new2
#define NULL
#define RSTRING_LEN(str)
VALUE rb_class_path_cached(VALUE)
Definition: variable.c:162
#define RTEST(v)
unsigned long st_data_t
#define RBASIC(obj)
const VALUE VALUE obj
#define RSTRING_PTR(str)
void rb_gc_register_mark_object(VALUE)
Definition: gc.c:7079
char * strncpy(char *__restrict__, const char *__restrict__, size_t)
#define NIL_P(v)
int fprintf(FILE *__restrict__, const char *__restrict__,...) __attribute__((__format__(__printf__
const char size_t n
const char const char *typedef unsigned long VALUE
VALUE rb_sym2str(VALUE)
Definition: symbol.c:784
__inline__ const void *__restrict__ size_t len
#define OBJ_FROZEN(x)
#define ALLOC_N(type, n)
void rb_define_module_function(VALUE, const char *, VALUE(*)(), int)
#define NUM2INT(x)
int VALUE v
#define RUBY_INTERNAL_EVENT_FREEOBJ
#define Qnil
#define INT2FIX(i)
void void ruby_xfree(void *)
Definition: gc.c:10183
#define RUBY_INTERNAL_EVENT_NEWOBJ
#define RBASIC_CLASS(obj)
VALUE rb_yield(VALUE)
Definition: vm_eval.c:1237
size_t rb_gc_count(void)
Definition: gc.c:8727
#define SIZET2NUM(v)
void * ruby_xmalloc(size_t) __attribute__((__malloc__)) __attribute__((__returns_nonnull__)) __attribute__((alloc_size(1)))
Definition: gc.c:12008
unsigned long VALUE
Definition: ruby.h:102
int st_delete(st_table *tab, st_data_t *key, st_data_t *value)
Definition: st.c:1418
void st_add_direct(st_table *tab, st_data_t key, st_data_t value)
Definition: st.c:1251
st_table * st_init_numtable(void)
Definition: st.c:653
st_table * st_init_strtable(void)
Definition: st.c:668
int st_insert(st_table *tab, st_data_t key, st_data_t value)
Definition: st.c:1171
void st_clear(st_table *tab)
Definition: st.c:698
int st_lookup(st_table *tab, st_data_t key, st_data_t *value)
Definition: st.c:1101
int st_foreach(st_table *tab, st_foreach_callback_func *func, st_data_t arg)
Definition: st.c:1717
int st_get_key(st_table *tab, st_data_t key, st_data_t *result)
Definition: st.c:1130
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
st_table * str_table
struct traceobj_arg * prev_traceobj_arg
st_table * object_table
VALUE freeobj_trace
VALUE newobj_trace