<?xml version="1.0" encoding="UTF-8"?>
<commit>
  <added type="array"/>
  <modified type="array">
    <modified>
      <diff>@@ -172,8 +172,17 @@ ruby_xfree(x)
 	RUBY_CRITICAL(free(x));
 }
 
+#if HAVE_LONG_LONG
+    #define GC_TIME_TYPE LONG_LONG
+#else
+    #define GC_TIME_TYPE long
+#endif
+
 extern int ruby_in_compile;
 static int dont_gc;
+static int gc_statistics = 0;
+static GC_TIME_TYPE gc_time = 0;
+static int gc_collections = 0;
 static int during_gc;
 static int need_call_final = 0;
 static st_table *finalizer_table = 0;
@@ -368,6 +377,104 @@ rb_gc_disable()
     return old;
 }
 
+/*
+ *  call-seq:
+ *     GC.enable_stats    =&gt; true or false
+ *
+ *  Enables garbage collection statistics, returning &lt;code&gt;true&lt;/code&gt; if garbage
+ *  collection statistics was already enabled.
+ *
+ *     GC.enable_stats   #=&gt; false or true
+ *     GC.enable_stats   #=&gt; true
+ *
+ */
+
+VALUE
+rb_gc_enable_stats()
+{
+    int old = gc_statistics;
+    gc_statistics = Qtrue;
+    return old;
+}
+
+/*
+ *  call-seq:
+ *     GC.disable_stats    =&gt; true or false
+ *
+ *  Disables garbage collection statistics, returning &lt;code&gt;true&lt;/code&gt; if garbage
+ *  collection statistics was already disabled.
+ *
+ *     GC.disable_stats   #=&gt; false or true
+ *     GC.disable_stats   #=&gt; true
+ *
+ */
+
+VALUE
+rb_gc_disable_stats()
+{
+    int old = gc_statistics;
+    gc_statistics = Qfalse;
+    return old;
+}
+
+/*
+ *  call-seq:
+ *     GC.clear_stats    =&gt; nil
+ *
+ *  Clears garbage collection statistics, returning nil. This resets the number
+ *  of collections (GC.collections) and the time used (GC.time) to 0.
+ *
+ *     GC.clear_stats    #=&gt; nil
+ *
+ */
+
+VALUE
+rb_gc_clear_stats()
+{
+    gc_collections = 0;
+    gc_time = 0;
+    return Qnil; 
+}
+
+/*
+ *  call-seq:
+ *     GC.collections    =&gt; Integer
+ *
+ *  Returns the number of garbage collections performed while GC statistics collection
+ *  was enabled.
+ *
+ *     GC.collections    #=&gt; 35
+ *
+ */
+
+VALUE
+rb_gc_collections()
+{
+    return INT2NUM(gc_collections);
+}
+
+/*
+ *  call-seq:
+ *     GC.time    =&gt; Integer
+ *
+ *  Returns the time spent during garbage collection while GC statistics collection
+ *  was enabled (in micro seconds).
+ *
+ *     GC.time    #=&gt; 20000
+ *
+ */
+
+VALUE
+rb_gc_time()
+{
+#if HAVE_LONG_LONG
+    return LL2NUM(gc_time);
+#else
+    return LONG2NUM(gc_time);
+#endif
+}
+
+
 VALUE rb_mGC;
 
 static struct gc_list {
@@ -459,7 +566,7 @@ typedef struct RVALUE {
 static RVALUE *freelist = 0;
 static RVALUE *deferred_final_list = 0;
 
-#define HEAPS_INCREMENT 10
+static int heaps_increment = 10;
 static struct heaps_slot {
     void *membase;
     RVALUE *slot;
@@ -471,10 +578,18 @@ static struct heaps_slot {
 static int heaps_length = 0;
 static int heaps_used   = 0;
 
-#define HEAP_MIN_SLOTS 10000
-static int heap_slots = HEAP_MIN_SLOTS;
+static int heap_min_slots = 10000;
+static int heap_slots = 10000;
+
+static int heap_free_min = 4096;
+static int heap_slots_increment = 10000;
+static double heap_slots_growth_factor = 1.8;
+
+static long initial_malloc_limit = GC_MALLOC_LIMIT;
 
-#define FREE_MIN  4096
+static int verbose_gc_stats = Qfalse;
+
+static FILE* gc_data_file = NULL;
 
 static RVALUE *himem, *lomem;
 
@@ -484,6 +599,149 @@ static RVALUE *himem, *lomem;
 
 static int gc_cycles = 0;
 
+static void set_gc_parameters()
+{
+    char *gc_stats_ptr, *min_slots_ptr, *free_min_ptr, *heap_slots_incr_ptr,
+      *heap_incr_ptr, *malloc_limit_ptr, *gc_heap_file_ptr, *heap_slots_growth_factor_ptr;
+
+    gc_data_file = stderr;
+
+    gc_stats_ptr = getenv(&quot;RUBY_GC_STATS&quot;);
+    if (gc_stats_ptr != NULL) {
+	int gc_stats_i = atoi(gc_stats_ptr);
+	if (gc_stats_i &gt; 0) {
+	    verbose_gc_stats = Qtrue;
+	}
+    }
+
+    gc_heap_file_ptr = getenv(&quot;RUBY_GC_DATA_FILE&quot;);
+    if (gc_heap_file_ptr != NULL) {
+	FILE* data_file = fopen(gc_heap_file_ptr, &quot;w&quot;);
+	if (data_file != NULL) {
+	    gc_data_file = data_file;
+	}
+	else {
+	    fprintf(stderr,
+		    &quot;can't open gc log file %s for writing, using default\n&quot;, gc_heap_file_ptr);
+	}
+    }
+
+    min_slots_ptr = getenv(&quot;RUBY_HEAP_MIN_SLOTS&quot;);
+    if (min_slots_ptr != NULL) {
+	int min_slots_i = atoi(min_slots_ptr);
+        if (verbose_gc_stats) {
+	    fprintf(gc_data_file, &quot;RUBY_HEAP_MIN_SLOTS=%s\n&quot;, min_slots_ptr);
+        }
+	if (min_slots_i &gt; 0) {
+	    heap_slots = min_slots_i;
+	    heap_min_slots = min_slots_i;
+	}
+    }
+
+    free_min_ptr = getenv(&quot;RUBY_HEAP_FREE_MIN&quot;);
+    if (free_min_ptr != NULL) {
+	int free_min_i = atoi(free_min_ptr);
+        if (verbose_gc_stats) {
+	    fprintf(gc_data_file, &quot;RUBY_HEAP_FREE_MIN=%s\n&quot;, free_min_ptr);
+	}
+	if (free_min_i &gt; 0) {
+	    heap_free_min = free_min_i;
+	}
+    }
+
+    heap_incr_ptr = getenv(&quot;RUBY_HEAP_INCREMENT&quot;);
+    if (heap_incr_ptr != NULL) {
+	int heap_incr_i = atoi(heap_incr_ptr);
+        if (verbose_gc_stats) {
+	    fprintf(gc_data_file, &quot;RUBY_HEAP_INCREMENT=%s\n&quot;, heap_incr_ptr);
+	}
+	if (heap_incr_i &gt; 0) {
+	    heaps_increment = heap_incr_i;
+	}
+    }
+
+    heap_slots_incr_ptr = getenv(&quot;RUBY_HEAP_SLOTS_INCREMENT&quot;);
+    if (heap_slots_incr_ptr != NULL) {
+	int heap_slots_incr_i = atoi(heap_slots_incr_ptr);
+        if (verbose_gc_stats) {
+	    fprintf(gc_data_file, &quot;RUBY_HEAP_SLOTS_INCREMENT=%s\n&quot;, heap_slots_incr_ptr);
+	}
+	if (heap_slots_incr_i &gt; 0) {
+	    heap_slots_increment = heap_slots_incr_i;
+	}
+    }
+    heap_slots_growth_factor_ptr = getenv(&quot;RUBY_HEAP_SLOTS_GROWTH_FACTOR&quot;);
+    if (heap_slots_growth_factor_ptr != NULL) {
+	double heap_slots_growth_factor_d = atoi(heap_slots_growth_factor_ptr);
+        if (verbose_gc_stats) {
+	    fprintf(gc_data_file, &quot;RUBY_HEAP_SLOTS_GROWTH_FACTOR=%s\n&quot;, heap_slots_growth_factor_ptr);
+	}
+	if (heap_slots_growth_factor_d &gt; 0) {
+	    heap_slots_growth_factor = heap_slots_growth_factor_d;
+	}
+    }
+
+    malloc_limit_ptr = getenv(&quot;RUBY_GC_MALLOC_LIMIT&quot;);
+    if (malloc_limit_ptr != NULL) {
+	int malloc_limit_i = atol(malloc_limit_ptr);
+        if (verbose_gc_stats) {
+	    fprintf(gc_data_file, &quot;RUBY_GC_MALLOC_LIMIT=%s\n&quot;, malloc_limit_ptr);
+	}
+	if (malloc_limit_i &gt; 0) {
+	    initial_malloc_limit = malloc_limit_i;
+	}
+    }
+}
+
+/*
+ *  call-seq:
+ *     GC.dump    =&gt; nil
+ *
+ *  dumps information about the current GC data structures to the GC log file
+ *
+ *     GC.dump    #=&gt; nil
+ *
+ */
+
+VALUE
+rb_gc_dump()
+{
+    int i;
+
+    for (i = 0; i &lt; heaps_used; i++) {
+	int heap_size = heaps[i].limit;
+	fprintf(gc_data_file, &quot;HEAP[%2d]: size=%7d\n&quot;, i, heap_size);
+    }
+
+    return Qnil;
+}
+
+/*
+ *  call-seq:
+ *     GC.log String  =&gt; String
+ *
+ *  Logs string to the GC data file and returns it.
+ *
+ *     GC.log &quot;manual GC call&quot;    #=&gt; &quot;manual GC call&quot;
+ *
+ */
+
+VALUE
+rb_gc_log(self, original_str)
+     VALUE self, original_str;
+{
+    if (original_str == Qnil) {
+        fprintf(gc_data_file, &quot;\n&quot;);
+    }
+    else {
+        VALUE str = StringValue(original_str);
+        char *p = RSTRING(str)-&gt;ptr;
+        fprintf(gc_data_file, &quot;%s\n&quot;, p);
+    }
+    return original_str;
+}
+
+
 static void
 add_heap()
 {
@@ -494,7 +752,7 @@ add_heap()
 	struct heaps_slot *p;
 	int length;
 
-	heaps_length += HEAPS_INCREMENT;
+	heaps_length += heaps_increment;
 	length = heaps_length*sizeof(struct heaps_slot);
 	RUBY_CRITICAL(
 	    if (heaps_used &gt; 0) {
@@ -510,10 +768,10 @@ add_heap()
     for (;;) {
 	RUBY_CRITICAL(p = (RVALUE*)alloc_ruby_heap(sizeof(RVALUE)*(heap_slots+1)));
 	if (p == 0) {
-	    if (heap_slots == HEAP_MIN_SLOTS) {
+	    if (heap_slots == heap_min_slots) {
 		rb_memerror();
 	    }
-	    heap_slots = HEAP_MIN_SLOTS;
+	    heap_slots = heap_min_slots;
 	    continue;
 	}
         heaps[heaps_used].membase = p;
@@ -532,8 +790,9 @@ add_heap()
     if (lomem == 0 || lomem &gt; p) lomem = p;
     if (himem &lt; pend) himem = pend;
     heaps_used++;
-    heap_slots *= 1.8;
-    if (heap_slots &lt;= 0) heap_slots = HEAP_MIN_SLOTS;
+    heap_slots += heap_slots_increment;
+    heap_slots_increment *= heap_slots_growth_factor;
+    if (heap_slots &lt;= 0) heap_slots = heap_min_slots;
 
     while (p &lt; pend) {
 	p-&gt;as.free.flags = 0;
@@ -1204,6 +1463,39 @@ finalize_list(p)
     }
 }
 
+static char* obj_type(int tp)
+{
+    switch (tp) {
+	case T_NIL    : return &quot;NIL&quot;;   
+	case T_OBJECT : return &quot;OBJECT&quot;;
+	case T_CLASS  : return &quot;CLASS&quot;;
+	case T_ICLASS : return &quot;ICLASS&quot;;
+	case T_MODULE : return &quot;MODULE&quot;;
+	case T_FLOAT  : return &quot;FLOAT&quot;;
+	case T_STRING : return &quot;STRING&quot;;
+	case T_REGEXP : return &quot;REGEXP&quot;;
+	case T_ARRAY  : return &quot;ARRAY&quot;;
+	case T_FIXNUM : return &quot;FIXNUM&quot;;
+	case T_HASH   : return &quot;HASH&quot;;
+	case T_STRUCT : return &quot;STRUCT&quot;;
+	case T_BIGNUM : return &quot;BIGNUM&quot;;
+	case T_FILE   : return &quot;FILE&quot;;
+	    
+	case T_TRUE   : return &quot;TRUE&quot;;
+	case T_FALSE  : return &quot;FALSE&quot;;
+	case T_DATA   : return &quot;DATA&quot;;
+	case T_MATCH  : return &quot;MATCH&quot;;
+	case T_SYMBOL : return &quot;SYMBOL&quot;;
+	    
+	case T_BLKTAG : return &quot;BLKTAG&quot;;
+	case T_UNDEF  : return &quot;UNDEF&quot;;
+	case T_VARMAP : return &quot;VARMAP&quot;;
+	case T_SCOPE  : return &quot;SCOPE&quot;;
+	case T_NODE   : return &quot;NODE&quot;;
+	default: return &quot;____&quot;;
+    }
+}
+
 static void
 free_unused_heaps()
 {
@@ -1235,13 +1527,24 @@ gc_sweep()
     unsigned long live = 0;
     unsigned long free_min = 0;
     struct heaps_slot *heap;
+    
+    unsigned long really_freed = 0;
+    int free_counts[256];
+    int live_counts[256];
+    int do_gc_stats = gc_statistics &amp; verbose_gc_stats;
 
     for (i = 0; i &lt; heaps_used; i++) {
         free_min += heaps[i].limit;
     }
     free_min = free_min * 0.2;
-    if (free_min &lt; FREE_MIN)
-        free_min = FREE_MIN;
+    if (free_min &lt; heap_free_min)
+        free_min = heap_free_min;
+
+    if (do_gc_stats) {
+	for (i = 0 ; i&lt; 256; i++) {
+	    free_counts[i] = live_counts[i] = 0;
+	}
+    }
 
     if (ruby_in_compile &amp;&amp; ruby_parser_stack_on_heap()) {
 	/* should not reclaim nodes during compilation
@@ -1280,6 +1583,9 @@ gc_sweep()
 			debug_print(&quot;Sweeped object: %p\n&quot;, (void *) p);
 		    }
 		    obj_free((VALUE)p);
+		    if (do_gc_stats) {
+			really_freed++;
+		    }
 		}
 		if (need_call_final &amp;&amp; FL_TEST(p, FL_FINALIZE)) {
 		    /* This object has a finalizer, so don't free it right now, but do it later. */
@@ -1288,6 +1594,12 @@ gc_sweep()
 		    final_list = p;
 		}
 		else {
+		    if (do_gc_stats) {
+			int obt = p-&gt;as.basic.flags &amp; T_MASK;
+			if (obt) {
+			    free_counts[obt]++;
+			}
+		    }
 		    /* Do not touch the fields if they don't have to be modified.
 		     * This is in order to preserve copy-on-write semantics.
 		     */
@@ -1306,6 +1618,9 @@ gc_sweep()
 	    else {
 		rb_mark_table_heap_remove(heap, p);
 		live++;
+		if (do_gc_stats) {
+		    live_counts[RANY((VALUE)p)-&gt;as.basic.flags &amp; T_MASK]++;
+		}
 	    }
 	    p++;
 	}
@@ -1324,13 +1639,28 @@ gc_sweep()
     }
     if (malloc_increase &gt; malloc_limit) {
 	malloc_limit += (malloc_increase - malloc_limit) * (double)live / (live + freed);
-	if (malloc_limit &lt; GC_MALLOC_LIMIT) malloc_limit = GC_MALLOC_LIMIT;
+	if (malloc_limit &lt; initial_malloc_limit) malloc_limit = initial_malloc_limit;
     }
     malloc_increase = 0;
     if (freed &lt; free_min) {
 	add_heap();
     }
     during_gc = 0;
+    
+    if (do_gc_stats) {
+	fprintf(gc_data_file, &quot;objects processed: %.7d\n&quot;, live+freed);
+	fprintf(gc_data_file, &quot;live objects	: %.7d\n&quot;, live);
+	fprintf(gc_data_file, &quot;freelist objects : %.7d\n&quot;, freed - really_freed);
+	fprintf(gc_data_file, &quot;freed objects	: %.7d\n&quot;, really_freed);
+	for(i = 0; i &lt; 256; i++) {
+	    if (free_counts[i] &gt; 0) {
+		fprintf(gc_data_file,
+			&quot;kept %.7d / freed %.7d objects of type %s\n&quot;,
+			live_counts[i], free_counts[i], obj_type(i));
+	    }
+	}
+    }
+
 
     /* clear finalization list */
     if (final_list) {
@@ -1527,6 +1857,7 @@ garbage_collect()
     struct gc_list *list;
     struct FRAME * volatile frame; /* gcc 2.7.2.3 -O2 bug??  */
     jmp_buf save_regs_gc_mark;
+    struct timeval gctv1, gctv2;
     SET_STACK_END;
 
 #ifdef HAVE_NATIVETHREAD
@@ -1547,6 +1878,15 @@ garbage_collect()
 	debug_prompt(&quot;Press Enter to initiate garbage collection.\n&quot;);
     }
     rb_mark_table_prepare();
+    
+    if (gc_statistics) {
+        gc_collections++;
+	gettimeofday(&amp;gctv1, NULL);
+        if (verbose_gc_stats) {
+	    fprintf(gc_data_file, &quot;Garbage collection started\n&quot;);
+	}
+    }
+    
     init_mark_stack();
 
     gc_mark((VALUE)ruby_current_node, 0);
@@ -1631,6 +1971,17 @@ garbage_collect()
     }
     rb_mark_table_finalize();
     gc_cycles++;
+    
+    if (gc_statistics) {
+        GC_TIME_TYPE musecs_used;
+	gettimeofday(&amp;gctv2, NULL);
+	musecs_used = ((GC_TIME_TYPE)(gctv2.tv_sec - gctv1.tv_sec) * 1000000) + (gctv2.tv_usec - gctv1.tv_usec);
+	gc_time += musecs_used;
+
+	if (verbose_gc_stats) {
+	    fprintf(gc_data_file, &quot;GC time: %d msec\n&quot;, musecs_used / 1000);
+	}
+    }
 }
 
 void
@@ -1826,6 +2177,7 @@ Init_heap()
     if (!rb_gc_stack_start) {
 	Init_stack(0);
     }
+    set_gc_parameters();
     add_heap();
 }
 
@@ -2453,6 +2805,14 @@ Init_GC()
     rb_define_singleton_method(rb_mGC, &quot;initialize_debugging&quot;, rb_gc_init_debugging, 0);
     rb_define_singleton_method(rb_mGC, &quot;copy_on_write_friendly?&quot;, rb_gc_copy_on_write_friendly, 0);
     rb_define_singleton_method(rb_mGC, &quot;copy_on_write_friendly=&quot;, rb_gc_set_copy_on_write_friendly, 1);
+    
+    rb_define_singleton_method(rb_mGC, &quot;enable_stats&quot;, rb_gc_enable_stats, 0);
+    rb_define_singleton_method(rb_mGC, &quot;disable_stats&quot;, rb_gc_disable_stats, 0);
+    rb_define_singleton_method(rb_mGC, &quot;clear_stats&quot;, rb_gc_clear_stats, 0);
+    rb_define_singleton_method(rb_mGC, &quot;collections&quot;, rb_gc_collections, 0);
+    rb_define_singleton_method(rb_mGC, &quot;time&quot;, rb_gc_time, 0);
+    rb_define_singleton_method(rb_mGC, &quot;dump&quot;, rb_gc_dump, 0);
+    rb_define_singleton_method(rb_mGC, &quot;log&quot;, rb_gc_log, 1);
 
     rb_mObSpace = rb_define_module(&quot;ObjectSpace&quot;);
     rb_define_module_function(rb_mObSpace, &quot;each_object&quot;, os_each_obj, -1);</diff>
      <filename>gc.c</filename>
    </modified>
  </modified>
  <removed type="array"/>
  <parents type="array">
    <parent>
      <id>bdecfad1a6feb3eb198b1abde0dde2ea617a7efb</id>
    </parent>
  </parents>
  <author>
    <name>Hongli Lai (Phusion)</name>
    <email>hongli@phusion.nl</email>
  </author>
  <url>http://github.com/FooBarWidget/rubyenterpriseedition/commit/0e48f5bcbf48f8aa6bc9d02bb020f6e6925513f1</url>
  <id>0e48f5bcbf48f8aa6bc9d02bb020f6e6925513f1</id>
  <committed-date>2008-11-19T04:08:28-08:00</committed-date>
  <authored-date>2008-11-17T05:39:40-08:00</authored-date>
  <message>Port the RailsBench GC patch.</message>
  <tree>cc26d4f59f82ef58b76977aea0e36203b3b66157</tree>
  <committer>
    <name>Hongli Lai (Phusion)</name>
    <email>hongli@phusion.nl</email>
  </committer>
</commit>
