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);