diff --git a/gc.c b/gc.c index cc37327..9a87bac 100644 --- a/gc.c +++ b/gc.c @@ -450,6 +450,8 @@ typedef struct rb_objspace { int dont_lazy_sweep; int during_gc; rb_atomic_t finalizing; + int collect_gc_stats; + int verbose_gc_stats; } flags; st_table *finalizer_table; mark_stack_t mark_stack; @@ -495,13 +497,18 @@ typedef struct rb_objspace { /* temporary profiling space */ double gc_sweep_start_time; + double gc_mark_start_time; size_t total_allocated_object_num_at_gc_start; size_t heap_used_at_gc_start; /* basic statistics */ size_t count; + double time; size_t total_allocated_object_num; size_t total_freed_object_num; + size_t total_mallocs; + size_t total_malloced_bytes; + size_t live_after_last_sweep; int latest_gc_info; } profile; struct gc_list *global_list; @@ -622,6 +629,7 @@ VALUE *ruby_initial_gc_stress_ptr = &rb_objspace.gc_stress; #define heap_tomb (&objspace->tomb_heap) #define dont_gc objspace->flags.dont_gc #define during_gc objspace->flags.during_gc +#define collect_gc_stats objspace->flags.collect_gc_stats #define finalizing objspace->flags.finalizing #define finalizer_table objspace->finalizer_table #define global_List objspace->global_list @@ -5646,6 +5654,155 @@ rb_gc_disable(void) return old ? Qtrue : Qfalse; } +/* + * call-seq: + * GC.enable_stats => true or false + * + * Enables garbage collection statistics, returning true if garbage + * collection statistics was already enabled. + * + * GC.enable_stats #=> false or true + * GC.enable_stats #=> true + * + */ + +VALUE +rb_gc_enable_stats() +{ + rb_objspace_t *objspace = &rb_objspace; + int old = collect_gc_stats; + collect_gc_stats = 1; + return old ? Qtrue : Qfalse; +} +/* + * call-seq: + * GC.disable_stats => true or false + * + * Disables garbage collection statistics, returning true if garbage + * collection statistics was already disabled. + * + * GC.disable_stats #=> false or true + * GC.disable_stats #=> true + * + */ + +VALUE +rb_gc_disable_stats() +{ + rb_objspace_t *objspace = &rb_objspace; + int old = collect_gc_stats; + collect_gc_stats = 0; + return old ? Qtrue : Qfalse; +} + +/* + * call-seq: + * GC.stats_enabled? => true or false + * + * Check whether GC stats have been enabled. + * + * GC.stats_enabled? #=> false or true + * + */ + +VALUE +rb_gc_stats_enabled() +{ + rb_objspace_t *objspace = &rb_objspace; + return collect_gc_stats ? Qtrue : Qfalse; +} + + +double rb_gc_total_time(void) +{ + return rb_objspace.profile.time; +} + +/* + * call-seq: + * GC.time => Integer + * + * Returns the time spent during garbage collection while GC statistics collection + * was enabled (in micro seconds). + * + * GC.time #=> 20000 + * + */ + +static VALUE +gc_time() +{ + return DBL2NUM(1000000*rb_objspace.profile.time); +} + +/* + * call-seq: + * GC.heap_slots => Integer + * + * Returns the number of heap slots available for object allocations. + * + * GC.heap_slots #=> 10000 + * + */ +VALUE +rb_gc_heap_slots() +{ + rb_objspace_t *objspace = &rb_objspace; + return SIZET2NUM(heap_pages_used * HEAP_OBJ_LIMIT); +} + +/* + * call-seq: + * GC.heap_slots_live_after_last_gc => Integer + * + * Returns the number of heap slots which were live after the last garbage collection. + * + * GC.heap_slots_live_after_last_gc #=> 231223 + * + */ +VALUE +rb_gc_heap_slots_live_after_last_gc() +{ + rb_objspace_t *objspace = &rb_objspace; + return SIZET2NUM(objspace->profile.live_after_last_sweep); +} + +/* + * call-seq: + * GC.total_mallocs => Integer + * + * Returns the number malloc calls. Might wrap around. + * + * GC.total_mallocs #=> 324234323246 + * + */ +static VALUE +gc_total_mallocs() +{ + return SIZET2NUM(rb_objspace.profile.total_mallocs); +} +size_t rb_gc_total_mallocs(void) { + return rb_objspace.profile.total_mallocs; +} + +/* + * call-seq: + * GC.total_malloced_bytes => Integer + * + * Returns the number of bytes allocated. Might wrap around. + * + * GC.total_malloced_bytes #=> 354656256432446 + * + */ +static VALUE +gc_total_malloced_bytes() +{ + return SIZET2NUM(rb_objspace.profile.total_malloced_bytes); +} +size_t rb_gc_total_malloced_bytes(void) { + return rb_objspace.profile.total_malloced_bytes; +} + static int get_envparam_int(const char *name, unsigned int *default_value, int lower_bound) { @@ -5979,6 +6136,10 @@ objspace_malloc_increase(rb_objspace_t *objspace, void *mem, size_t new_size, si { if (new_size > old_size) { ATOMIC_SIZE_ADD(malloc_increase, new_size - old_size); + if (collect_gc_stats) { + ATOMIC_SIZE_ADD(objspace->profile.total_mallocs, 1); + ATOMIC_SIZE_ADD(objspace->profile.total_malloced_bytes, new_size - old_size); + } #if RGENGC_ESTIMATE_OLDMALLOC ATOMIC_SIZE_ADD(objspace->rgengc.oldmalloc_increase, new_size - old_size); #endif @@ -6012,14 +6173,14 @@ objspace_malloc_increase(rb_objspace_t *objspace, void *mem, size_t new_size, si } else { size_t dec_size = old_size - new_size; - size_t allocated_size = objspace->malloc_params.allocated_size; #if MALLOC_ALLOCATED_SIZE_CHECK + size_t allocated_size = objspace->malloc_params.allocated_size; if (allocated_size < dec_size) { rb_bug("objspace_malloc_increase: underflow malloc_params.allocated_size."); } #endif - atomic_sub_nounderflow(objspace->malloc_params.allocated_size, dec_size); + atomic_sub_nounderflow(&objspace->malloc_params.allocated_size, dec_size); } if (0) fprintf(stderr, "incraese - ptr: %p, type: %s, new_size: %d, old_size: %d\n", @@ -6037,7 +6198,7 @@ objspace_malloc_increase(rb_objspace_t *objspace, void *mem, size_t new_size, si { size_t allocations = objspace->malloc_params.allocations; if (allocations > 0) { - atomic_sub_nounderflow(objspace->malloc_params.allocations, 1); + atomic_sub_nounderflow(&objspace->malloc_params.allocations, 1); } #if MALLOC_ALLOCATED_SIZE_CHECK else { @@ -6289,6 +6450,12 @@ gc_malloc_allocated_size(VALUE self) return UINT2NUM(rb_objspace.malloc_params.allocated_size); } +size_t +rb_gc_malloc_allocated_size(void) +{ + return rb_objspace.malloc_params.allocated_size; +} + /* * call-seq: * GC.malloc_allocations -> Integer @@ -6303,6 +6470,12 @@ gc_malloc_allocations(VALUE self) { return UINT2NUM(rb_objspace.malloc_params.allocations); } + +size_t +rb_gc_malloc_allocations(void) +{ + return rb_objspace.malloc_params.allocations; +} #endif /* @@ -6844,6 +7017,14 @@ gc_prof_mark_timer_start(rb_objspace_t *objspace) #if GC_PROFILE_MORE_DETAIL if (gc_prof_enabled(objspace)) { gc_prof_record(objspace)->gc_mark_time = getrusage_time(); + } else { + if (collect_gc_stats) { + objspace->profile.gc_mark_start_time = getrusage_time(); + } + } +#else + if (collect_gc_stats) { + objspace->profile.gc_mark_start_time = getrusage_time(); } #endif } @@ -6858,6 +7039,17 @@ gc_prof_mark_timer_stop(rb_objspace_t *objspace) if (gc_prof_enabled(objspace)) { gc_profile_record *record = gc_prof_record(objspace); record->gc_mark_time = elapsed_time_from(record->gc_mark_time); + if (collect_gc_stats) { + objspace->profile.time += record->gc_mark_time; + } + } else { + if (collect_gc_stats) { + objspace->profile.time += elapsed_time_from(objspace->profile.gc_mark_start_time); + } + } +#else + if (collect_gc_stats) { + objspace->profile.time += elapsed_time_from(objspace->profile.gc_mark_start_time); } #endif } @@ -6871,9 +7063,13 @@ gc_prof_sweep_timer_start(rb_objspace_t *objspace) if (gc_prof_enabled(objspace)) { gc_profile_record *record = gc_prof_record(objspace); - if (record->gc_time > 0 || GC_PROFILE_MORE_DETAIL) { + if (record->gc_time > 0 || GC_PROFILE_MORE_DETAIL || collect_gc_stats) { objspace->profile.gc_sweep_start_time = getrusage_time(); - } + } + } else { + if (collect_gc_stats) { + objspace->profile.gc_sweep_start_time = getrusage_time(); + } } } @@ -6892,16 +7088,30 @@ gc_prof_sweep_timer_stop(rb_objspace_t *objspace) sweep_time = elapsed_time_from(objspace->profile.gc_sweep_start_time); /* need to accumulate GC time for lazy sweep after gc() */ record->gc_time += sweep_time; + if (collect_gc_stats) { + objspace->profile.time += sweep_time; + } } else if (GC_PROFILE_MORE_DETAIL) { sweep_time = elapsed_time_from(objspace->profile.gc_sweep_start_time); - } + if (collect_gc_stats) { + objspace->profile.time += sweep_time; + } + } else { + if (collect_gc_stats) { + objspace->profile.time += elapsed_time_from(objspace->profile.gc_sweep_start_time); + } + } #if GC_PROFILE_MORE_DETAIL record->gc_sweep_time += sweep_time; if (heap_pages_deferred_final) record->flags |= GPR_FLAG_HAVE_FINALIZE; #endif if (heap_pages_deferred_final) objspace->profile.latest_gc_info |= GPR_FLAG_HAVE_FINALIZE; + } else { + if (collect_gc_stats) { + objspace->profile.time += elapsed_time_from(objspace->profile.gc_sweep_start_time); + } } } @@ -6920,9 +7130,12 @@ gc_prof_set_malloc_info(rb_objspace_t *objspace) static inline void gc_prof_set_heap_info(rb_objspace_t *objspace) { + objspace->profile.live_after_last_sweep = + objspace->profile.total_allocated_object_num_at_gc_start - objspace->profile.total_freed_object_num; + if (gc_prof_enabled(objspace)) { gc_profile_record *record = gc_prof_record(objspace); - size_t live = objspace->profile.total_allocated_object_num_at_gc_start - objspace->profile.total_freed_object_num; + size_t live = objspace->profile.live_after_last_sweep; size_t total = objspace->profile.heap_used_at_gc_start * HEAP_OBJ_LIMIT; #if GC_PROFILE_MORE_DETAIL @@ -7479,11 +7692,19 @@ Init_GC(void) rb_define_singleton_method(rb_mGC, "start", gc_start_internal, -1); rb_define_singleton_method(rb_mGC, "enable", rb_gc_enable, 0); rb_define_singleton_method(rb_mGC, "disable", rb_gc_disable, 0); + rb_define_singleton_method(rb_mGC, "enable_stats", rb_gc_enable_stats, 0); + rb_define_singleton_method(rb_mGC, "disable_stats", rb_gc_disable_stats, 0); + rb_define_singleton_method(rb_mGC, "stats_enabled?", rb_gc_stats_enabled, 0); rb_define_singleton_method(rb_mGC, "stress", gc_stress_get, 0); rb_define_singleton_method(rb_mGC, "stress=", gc_stress_set, 1); rb_define_singleton_method(rb_mGC, "count", gc_count, 0); + rb_define_singleton_method(rb_mGC, "time", gc_time, 0); + rb_define_singleton_method(rb_mGC, "total_mallocs", gc_total_mallocs, 0); + rb_define_singleton_method(rb_mGC, "total_malloced_bytes", gc_total_malloced_bytes, 0); + rb_define_singleton_method(rb_mGC, "heap_slots", rb_gc_heap_slots, 0); rb_define_singleton_method(rb_mGC, "stat", gc_stat, -1); rb_define_singleton_method(rb_mGC, "latest_gc_info", gc_latest_gc_info, -1); + rb_define_singleton_method(rb_mGC, "heap_slots_live_after_last_gc", rb_gc_heap_slots_live_after_last_gc, 0); rb_define_method(rb_mGC, "garbage_collect", gc_start_internal, -1); gc_constants = rb_hash_new(); diff --git a/include/ruby/intern.h b/include/ruby/intern.h index 46af270..4bbf0e8 100644 --- a/include/ruby/intern.h +++ b/include/ruby/intern.h @@ -481,6 +481,9 @@ void rb_gc_finalize_deferred(void); void rb_gc_call_finalizer_at_exit(void); VALUE rb_gc_enable(void); VALUE rb_gc_disable(void); +VALUE rb_gc_enable_stats(void); +VALUE rb_gc_disable_stats(void); +VALUE rb_gc_stats_enabled(void); VALUE rb_gc_start(void); DEPRECATED(void rb_gc_set_params(void)); VALUE rb_define_finalizer(VALUE, VALUE); @@ -488,6 +491,11 @@ VALUE rb_undefine_finalizer(VALUE); size_t rb_gc_count(void); size_t rb_gc_stat(VALUE); VALUE rb_gc_latest_gc_info(VALUE); +double rb_gc_total_time(void); +VALUE rb_gc_heap_slots(void); +VALUE rb_gc_heap_slots_live_after_last_gc(void); +size_t rb_gc_total_mallocs(void); +size_t rb_gc_total_malloced_bytes(void); /* hash.c */ void st_foreach_safe(struct st_table *, int (*)(ANYARGS), st_data_t); VALUE rb_check_hash_type(VALUE);