1 /* -----------------------------------------------------------------------------
3 * (c) The GHC Team, 1998-2005
5 * Statistics and timing-related functions.
7 * ---------------------------------------------------------------------------*/
9 #include "PosixSource.h"
15 #include "Profiling.h"
17 #include "sm/Storage.h"
18 #include "sm/GC.h" // gc_alloc_block_sync, whitehole_spin
25 #define BIG_STRING_LEN 512
27 #define TICK_TO_DBL(t) ((double)(t) / TICKS_PER_SECOND)
29 static Ticks ElapsedTimeStart = 0;
31 static Ticks InitUserTime = 0;
32 static Ticks InitElapsedTime = 0;
33 static Ticks InitElapsedStamp = 0;
35 static Ticks MutUserTime = 0;
36 static Ticks MutElapsedTime = 0;
37 static Ticks MutElapsedStamp = 0;
39 static Ticks ExitUserTime = 0;
40 static Ticks ExitElapsedTime = 0;
42 static StgWord64 GC_tot_alloc = 0;
43 static StgWord64 GC_tot_copied = 0;
45 static StgWord64 GC_par_max_copied = 0;
46 static StgWord64 GC_par_avg_copied = 0;
48 static Ticks GC_start_time = 0, GC_tot_time = 0; /* User GC Time */
49 static Ticks GCe_start_time = 0, GCe_tot_time = 0; /* Elapsed GC time */
52 static Ticks RP_start_time = 0, RP_tot_time = 0; /* retainer prof user time */
53 static Ticks RPe_start_time = 0, RPe_tot_time = 0; /* retainer prof elap time */
55 static Ticks HC_start_time, HC_tot_time = 0; // heap census prof user time
56 static Ticks HCe_start_time, HCe_tot_time = 0; // heap census prof elap time
60 #define PROF_VAL(x) (x)
65 static lnat MaxResidency = 0; // in words; for stats only
66 static lnat AvgResidency = 0;
67 static lnat ResidencySamples = 0; // for stats only
68 static lnat MaxSlop = 0;
70 static lnat GC_start_faults = 0, GC_end_faults = 0;
72 static Ticks *GC_coll_times = NULL;
73 static Ticks *GC_coll_etimes = NULL;
75 static void statsFlush( void );
76 static void statsClose( void );
78 Ticks stat_getElapsedGCTime(void)
83 Ticks stat_getElapsedTime(void)
85 return getProcessElapsedTime() - ElapsedTimeStart;
88 /* mut_user_time_during_GC() and mut_user_time()
90 * The former function can be used to get the current mutator time
91 * *during* a GC, i.e. between stat_startGC and stat_endGC. This is
92 * used in the heap profiler for accurately time stamping the heap
95 * ATTENTION: mut_user_time_during_GC() relies on GC_start_time being
96 * defined in stat_startGC() - to minimise system calls,
97 * GC_start_time is, however, only defined when really needed (check
98 * stat_startGC() for details)
101 mut_user_time_during_GC( void )
103 return TICK_TO_DBL(GC_start_time - GC_tot_time - PROF_VAL(RP_tot_time + HC_tot_time));
107 mut_user_time( void )
110 user = getProcessCPUTime();
111 return TICK_TO_DBL(user - GC_tot_time - PROF_VAL(RP_tot_time + HC_tot_time));
116 mut_user_time_during_RP() is similar to mut_user_time_during_GC();
117 it returns the MUT time during retainer profiling.
118 The same is for mut_user_time_during_HC();
121 mut_user_time_during_RP( void )
123 return TICK_TO_DBL(RP_start_time - GC_tot_time - RP_tot_time - HC_tot_time);
127 mut_user_time_during_heap_census( void )
129 return TICK_TO_DBL(HC_start_time - GC_tot_time - RP_tot_time - HC_tot_time);
131 #endif /* PROFILING */
133 // initStats0() has no dependencies, it can be called right at the beginning
137 ElapsedTimeStart = 0;
141 InitElapsedStamp = 0;
152 GC_par_max_copied = 0;
153 GC_par_avg_copied = 0;
173 ResidencySamples = 0;
180 // initStats1() can be called after setupRtsFlags()
186 if (RtsFlags.GcFlags.giveStats >= VERBOSE_GC_STATS) {
187 statsPrintf(" Alloc Copied Live GC GC TOT TOT Page Flts\n");
188 statsPrintf(" bytes bytes bytes user elap user elap\n");
191 (Ticks *)stgMallocBytes(
192 sizeof(Ticks)*RtsFlags.GcFlags.generations,
195 (Ticks *)stgMallocBytes(
196 sizeof(Ticks)*RtsFlags.GcFlags.generations,
198 for (i = 0; i < RtsFlags.GcFlags.generations; i++) {
199 GC_coll_times[i] = 0;
200 GC_coll_etimes[i] = 0;
204 /* -----------------------------------------------------------------------------
205 Initialisation time...
206 -------------------------------------------------------------------------- */
213 elapsed = getProcessElapsedTime();
214 ElapsedTimeStart = elapsed;
222 getProcessTimes(&user, &elapsed);
225 InitElapsedStamp = elapsed;
226 if (ElapsedTimeStart > elapsed) {
229 InitElapsedTime = elapsed - ElapsedTimeStart;
232 /* We start counting events for the mutator
233 * when garbage collection starts
234 * we switch to the GC event set. */
235 papi_start_mutator_count();
237 /* This flag is needed to avoid counting the last GC */
238 papi_is_reporting = 1;
243 /* -----------------------------------------------------------------------------
244 stat_startExit and stat_endExit
246 These two measure the time taken in shutdownHaskell().
247 -------------------------------------------------------------------------- */
254 getProcessTimes(&user, &elapsed);
256 MutElapsedStamp = elapsed;
257 MutElapsedTime = elapsed - GCe_tot_time -
258 PROF_VAL(RPe_tot_time + HCe_tot_time) - InitElapsedStamp;
259 if (MutElapsedTime < 0) { MutElapsedTime = 0; } /* sometimes -0.00 */
261 MutUserTime = user - GC_tot_time - PROF_VAL(RP_tot_time + HC_tot_time) - InitUserTime;
262 if (MutUserTime < 0) { MutUserTime = 0; }
265 /* We stop counting mutator events
266 * GC events are not being counted at this point */
267 papi_stop_mutator_count();
269 /* This flag is needed, because GC is run once more after this function */
270 papi_is_reporting = 0;
280 getProcessTimes(&user, &elapsed);
282 ExitUserTime = user - MutUserTime - GC_tot_time - PROF_VAL(RP_tot_time + HC_tot_time) - InitUserTime;
283 ExitElapsedTime = elapsed - MutElapsedStamp;
284 if (ExitUserTime < 0) {
287 if (ExitElapsedTime < 0) {
292 /* -----------------------------------------------------------------------------
293 Called at the beginning of each GC
294 -------------------------------------------------------------------------- */
296 static nat rub_bell = 0;
298 /* initialise global variables needed during GC
300 * * GC_start_time is read in mut_user_time_during_GC(), which in turn is
301 * needed if either PROFILING or DEBUGing is enabled
306 nat bell = RtsFlags.GcFlags.ringBell;
317 #if defined(PROFILING) || defined(DEBUG)
318 GC_start_time = getProcessCPUTime(); // needed in mut_user_time_during_GC()
321 if (RtsFlags.GcFlags.giveStats != NO_GC_STATS) {
322 #if !defined(PROFILING) && !defined(DEBUG)
323 GC_start_time = getProcessCPUTime();
325 GCe_start_time = getProcessElapsedTime();
326 if (RtsFlags.GcFlags.giveStats) {
327 GC_start_faults = getPageFaults();
332 if(papi_is_reporting) {
333 /* Switch to counting GC events */
334 papi_stop_mutator_count();
335 papi_start_gc_count();
341 /* -----------------------------------------------------------------------------
342 Called at the end of each GC
343 -------------------------------------------------------------------------- */
346 stat_endGC (lnat alloc, lnat live, lnat copied, lnat gen,
347 lnat max_copied, lnat avg_copied, lnat slop)
349 if (RtsFlags.GcFlags.giveStats != NO_GC_STATS) {
350 Ticks time, etime, gc_time, gc_etime;
352 getProcessTimes(&time, &etime);
353 gc_time = time - GC_start_time;
354 gc_etime = etime - GCe_start_time;
356 if (RtsFlags.GcFlags.giveStats == VERBOSE_GC_STATS) {
357 nat faults = getPageFaults();
359 statsPrintf("%9ld %9ld %9ld",
360 alloc*sizeof(W_), copied*sizeof(W_),
362 statsPrintf(" %5.2f %5.2f %7.2f %7.2f %4ld %4ld (Gen: %2ld)\n",
363 TICK_TO_DBL(gc_time),
364 TICK_TO_DBL(gc_etime),
366 TICK_TO_DBL(etime - ElapsedTimeStart),
367 faults - GC_start_faults,
368 GC_start_faults - GC_end_faults,
371 GC_end_faults = faults;
375 GC_coll_times[gen] += gc_time;
376 GC_coll_etimes[gen] += gc_etime;
378 GC_tot_copied += (StgWord64) copied;
379 GC_tot_alloc += (StgWord64) alloc;
380 GC_par_max_copied += (StgWord64) max_copied;
381 GC_par_avg_copied += (StgWord64) avg_copied;
382 GC_tot_time += gc_time;
383 GCe_tot_time += gc_etime;
385 #if defined(THREADED_RTS)
388 if ((task = myTask()) != NULL) {
389 task->gc_time += gc_time;
390 task->gc_etime += gc_etime;
395 if (gen == RtsFlags.GcFlags.generations-1) { /* major GC? */
396 if (live > MaxResidency) {
400 AvgResidency += live;
403 if (slop > MaxSlop) MaxSlop = slop;
407 debugBelch("\b\b\b \b\b\b");
412 if(papi_is_reporting) {
413 /* Switch to counting mutator events */
415 papi_stop_gc0_count();
417 papi_stop_gc1_count();
419 papi_start_mutator_count();
424 /* -----------------------------------------------------------------------------
425 Called at the beginning of each Retainer Profiliing
426 -------------------------------------------------------------------------- */
432 getProcessTimes( &user, &elapsed );
434 RP_start_time = user;
435 RPe_start_time = elapsed;
437 #endif /* PROFILING */
439 /* -----------------------------------------------------------------------------
440 Called at the end of each Retainer Profiliing
441 -------------------------------------------------------------------------- */
446 nat retainerGeneration,
447 #ifdef DEBUG_RETAINER
451 double averageNumVisit)
454 getProcessTimes( &user, &elapsed );
456 RP_tot_time += user - RP_start_time;
457 RPe_tot_time += elapsed - RPe_start_time;
459 fprintf(prof_file, "Retainer Profiling: %d, at %f seconds\n",
460 retainerGeneration, mut_user_time_during_RP());
461 #ifdef DEBUG_RETAINER
462 fprintf(prof_file, "\tMax C stack size = %u\n", maxCStackSize);
463 fprintf(prof_file, "\tMax auxiliary stack size = %u\n", maxStackSize);
465 fprintf(prof_file, "\tAverage number of visits per object = %f\n", averageNumVisit);
467 #endif /* PROFILING */
469 /* -----------------------------------------------------------------------------
470 Called at the beginning of each heap census
471 -------------------------------------------------------------------------- */
474 stat_startHeapCensus(void)
477 getProcessTimes( &user, &elapsed );
479 HC_start_time = user;
480 HCe_start_time = elapsed;
482 #endif /* PROFILING */
484 /* -----------------------------------------------------------------------------
485 Called at the end of each heap census
486 -------------------------------------------------------------------------- */
489 stat_endHeapCensus(void)
492 getProcessTimes( &user, &elapsed );
494 HC_tot_time += user - HC_start_time;
495 HCe_tot_time += elapsed - HCe_start_time;
497 #endif /* PROFILING */
499 /* -----------------------------------------------------------------------------
500 Called at the end of execution
502 NOTE: number of allocations is not entirely accurate: it doesn't
503 take into account the few bytes at the end of the heap that
504 were left unused when the heap-check failed.
505 -------------------------------------------------------------------------- */
508 #define TICK_VAR_INI(arity) \
509 StgInt SLOW_CALLS_##arity = 1; \
510 StgInt RIGHT_ARITY_##arity = 1; \
511 StgInt TAGGED_PTR_##arity = 0;
516 StgInt TOTAL_CALLS=1;
519 /* Report the value of a counter */
520 #define REPORT(counter) \
522 showStgWord64(counter,temp,rtsTrue/*commas*/); \
523 statsPrintf(" (" #counter ") : %s\n",temp); \
526 /* Report the value of a counter as a percentage of another counter */
527 #define REPORT_PCT(counter,countertot) \
528 statsPrintf(" (" #counter ") %% of (" #countertot ") : %.1f%%\n", \
529 counter*100.0/countertot)
531 #define TICK_PRINT(arity) \
532 REPORT(SLOW_CALLS_##arity); \
533 REPORT_PCT(RIGHT_ARITY_##arity,SLOW_CALLS_##arity); \
534 REPORT_PCT(TAGGED_PTR_##arity,RIGHT_ARITY_##arity); \
535 REPORT(RIGHT_ARITY_##arity); \
536 REPORT(TAGGED_PTR_##arity)
538 #define TICK_PRINT_TOT(arity) \
539 statsPrintf(" (SLOW_CALLS_" #arity ") %% of (TOTAL_CALLS) : %.1f%%\n", \
540 SLOW_CALLS_##arity * 100.0/TOTAL_CALLS)
542 extern lnat hw_alloc_blocks;
547 if (RtsFlags.GcFlags.giveStats != NO_GC_STATS) {
549 char temp[BIG_STRING_LEN];
552 nat g, total_collections = 0;
554 getProcessTimes( &time, &etime );
555 etime -= ElapsedTimeStart;
557 GC_tot_alloc += alloc;
559 /* Count total garbage collections */
560 for (g = 0; g < RtsFlags.GcFlags.generations; g++)
561 total_collections += generations[g].collections;
563 /* avoid divide by zero if time is measured as 0.00 seconds -- SDM */
564 if (time == 0.0) time = 1;
565 if (etime == 0.0) etime = 1;
567 if (RtsFlags.GcFlags.giveStats >= VERBOSE_GC_STATS) {
568 statsPrintf("%9ld %9.9s %9.9s", (lnat)alloc*sizeof(W_), "", "");
569 statsPrintf(" %5.2f %5.2f\n\n", 0.0, 0.0);
572 if (RtsFlags.GcFlags.giveStats >= SUMMARY_GC_STATS) {
573 showStgWord64(GC_tot_alloc*sizeof(W_),
574 temp, rtsTrue/*commas*/);
575 statsPrintf("%16s bytes allocated in the heap\n", temp);
577 showStgWord64(GC_tot_copied*sizeof(W_),
578 temp, rtsTrue/*commas*/);
579 statsPrintf("%16s bytes copied during GC\n", temp);
581 if ( ResidencySamples > 0 ) {
582 showStgWord64(MaxResidency*sizeof(W_),
583 temp, rtsTrue/*commas*/);
584 statsPrintf("%16s bytes maximum residency (%ld sample(s))\n",
585 temp, ResidencySamples);
588 showStgWord64(MaxSlop*sizeof(W_), temp, rtsTrue/*commas*/);
589 statsPrintf("%16s bytes maximum slop\n", temp);
591 statsPrintf("%16ld MB total memory in use (%ld MB lost due to fragmentation)\n\n",
592 mblocks_allocated * MBLOCK_SIZE_W / (1024 * 1024 / sizeof(W_)),
593 (mblocks_allocated * MBLOCK_SIZE_W - hw_alloc_blocks * BLOCK_SIZE_W) / (1024 * 1024 / sizeof(W_)));
595 /* Print garbage collections in each gen */
596 for (g = 0; g < RtsFlags.GcFlags.generations; g++) {
597 statsPrintf(" Generation %d: %5d collections, %5d parallel, %5.2fs, %5.2fs elapsed\n",
598 g, generations[g].collections,
599 generations[g].par_collections,
600 TICK_TO_DBL(GC_coll_times[g]),
601 TICK_TO_DBL(GC_coll_etimes[g]));
604 #if defined(THREADED_RTS)
605 if (RtsFlags.ParFlags.parGcEnabled) {
606 statsPrintf("\n Parallel GC work balance: %.2f (%ld / %ld, ideal %d)\n",
607 (double)GC_par_avg_copied / (double)GC_par_max_copied,
608 (lnat)GC_par_avg_copied, (lnat)GC_par_max_copied,
609 RtsFlags.ParFlags.nNodes
616 #if defined(THREADED_RTS)
620 statsPrintf(" MUT time (elapsed) GC time (elapsed)\n");
621 for (i = 0, task = all_tasks;
623 i++, task = task->all_link) {
624 statsPrintf(" Task %2d %-8s : %6.2fs (%6.2fs) %6.2fs (%6.2fs)\n",
626 (task->worker) ? "(worker)" : "(bound)",
627 TICK_TO_DBL(task->mut_time),
628 TICK_TO_DBL(task->mut_etime),
629 TICK_TO_DBL(task->gc_time),
630 TICK_TO_DBL(task->gc_etime));
638 lnat sparks_created = 0;
639 lnat sparks_converted = 0;
640 lnat sparks_pruned = 0;
641 for (i = 0; i < n_capabilities; i++) {
642 sparks_created += capabilities[i].sparks_created;
643 sparks_converted += capabilities[i].sparks_converted;
644 sparks_pruned += capabilities[i].sparks_pruned;
647 statsPrintf(" SPARKS: %ld (%ld converted, %ld pruned)\n\n",
648 sparks_created, sparks_converted, sparks_pruned);
652 statsPrintf(" INIT time %6.2fs (%6.2fs elapsed)\n",
653 TICK_TO_DBL(InitUserTime), TICK_TO_DBL(InitElapsedTime));
654 statsPrintf(" MUT time %6.2fs (%6.2fs elapsed)\n",
655 TICK_TO_DBL(MutUserTime), TICK_TO_DBL(MutElapsedTime));
656 statsPrintf(" GC time %6.2fs (%6.2fs elapsed)\n",
657 TICK_TO_DBL(GC_tot_time), TICK_TO_DBL(GCe_tot_time));
659 statsPrintf(" RP time %6.2fs (%6.2fs elapsed)\n",
660 TICK_TO_DBL(RP_tot_time), TICK_TO_DBL(RPe_tot_time));
661 statsPrintf(" PROF time %6.2fs (%6.2fs elapsed)\n",
662 TICK_TO_DBL(HC_tot_time), TICK_TO_DBL(HCe_tot_time));
664 statsPrintf(" EXIT time %6.2fs (%6.2fs elapsed)\n",
665 TICK_TO_DBL(ExitUserTime), TICK_TO_DBL(ExitElapsedTime));
666 statsPrintf(" Total time %6.2fs (%6.2fs elapsed)\n\n",
667 TICK_TO_DBL(time), TICK_TO_DBL(etime));
668 statsPrintf(" %%GC time %5.1f%% (%.1f%% elapsed)\n\n",
669 TICK_TO_DBL(GC_tot_time)*100/TICK_TO_DBL(time),
670 TICK_TO_DBL(GCe_tot_time)*100/TICK_TO_DBL(etime));
672 if (time - GC_tot_time - PROF_VAL(RP_tot_time + HC_tot_time) == 0)
673 showStgWord64(0, temp, rtsTrue/*commas*/);
676 (StgWord64)((GC_tot_alloc*sizeof(W_))/
677 TICK_TO_DBL(time - GC_tot_time -
678 PROF_VAL(RP_tot_time + HC_tot_time))),
679 temp, rtsTrue/*commas*/);
681 statsPrintf(" Alloc rate %s bytes per MUT second\n\n", temp);
683 statsPrintf(" Productivity %5.1f%% of total user, %.1f%% of total elapsed\n\n",
684 TICK_TO_DBL(time - GC_tot_time -
685 PROF_VAL(RP_tot_time + HC_tot_time) - InitUserTime) * 100
687 TICK_TO_DBL(time - GC_tot_time -
688 PROF_VAL(RP_tot_time + HC_tot_time) - InitUserTime) * 100
689 / TICK_TO_DBL(etime));
702 #if defined(THREADED_RTS) && defined(PROF_SPIN)
706 statsPrintf("gc_alloc_block_sync: %"FMT_Word64"\n", gc_alloc_block_sync.spin);
707 statsPrintf("whitehole_spin: %"FMT_Word64"\n", whitehole_spin);
708 for (g = 0; g < RtsFlags.GcFlags.generations; g++) {
709 statsPrintf("gen[%d].sync_large_objects: %"FMT_Word64"\n", g, generations[g].sync_large_objects.spin);
715 if (RtsFlags.GcFlags.giveStats == ONELINE_GC_STATS) {
717 if (RtsFlags.MiscFlags.machineReadable) {
718 fmt1 = " [(\"bytes allocated\", \"%llu\")\n";
719 fmt2 = " ,(\"num_GCs\", \"%d\")\n"
720 " ,(\"average_bytes_used\", \"%ld\")\n"
721 " ,(\"max_bytes_used\", \"%ld\")\n"
722 " ,(\"num_byte_usage_samples\", \"%ld\")\n"
723 " ,(\"peak_megabytes_allocated\", \"%lu\")\n"
724 " ,(\"init_cpu_seconds\", \"%.2f\")\n"
725 " ,(\"init_wall_seconds\", \"%.2f\")\n"
726 " ,(\"mutator_cpu_seconds\", \"%.2f\")\n"
727 " ,(\"mutator_wall_seconds\", \"%.2f\")\n"
728 " ,(\"GC_cpu_seconds\", \"%.2f\")\n"
729 " ,(\"GC_wall_seconds\", \"%.2f\")\n"
733 fmt1 = "<<ghc: %llu bytes, ";
734 fmt2 = "%d GCs, %ld/%ld avg/max bytes residency (%ld samples), %luM in use, %.2f INIT (%.2f elapsed), %.2f MUT (%.2f elapsed), %.2f GC (%.2f elapsed) :ghc>>\n";
736 /* print the long long separately to avoid bugginess on mingwin (2001-07-02, mingw-0.5) */
737 statsPrintf(fmt1, GC_tot_alloc*(StgWord64)sizeof(W_));
740 ResidencySamples == 0 ? 0 :
741 AvgResidency*sizeof(W_)/ResidencySamples,
742 MaxResidency*sizeof(W_),
744 (unsigned long)(peak_mblocks_allocated * MBLOCK_SIZE / (1024L * 1024L)),
745 TICK_TO_DBL(InitUserTime), TICK_TO_DBL(InitElapsedTime),
746 TICK_TO_DBL(MutUserTime), TICK_TO_DBL(MutElapsedTime),
747 TICK_TO_DBL(GC_tot_time), TICK_TO_DBL(GCe_tot_time));
755 stgFree(GC_coll_times);
756 GC_coll_times = NULL;
758 stgFree(GC_coll_etimes);
759 GC_coll_etimes = NULL;
762 /* -----------------------------------------------------------------------------
765 Produce some detailed info on the state of the generational GC.
766 -------------------------------------------------------------------------- */
768 statDescribeGens(void)
772 lnat tot_live, tot_slop;
777 "----------------------------------------------------------\n"
778 " Gen Max Mut-list Blocks Large Live Slop\n"
779 " Blocks Bytes Objects \n"
780 "----------------------------------------------------------\n");
784 for (g = 0; g < RtsFlags.GcFlags.generations; g++) {
786 for (bd = generations[g].mut_list; bd != NULL; bd = bd->link) {
787 mut += (bd->free - bd->start) * sizeof(W_);
790 gen = &generations[g];
792 debugBelch("%5d %7d %9d", g, gen->max_blocks, mut);
794 for (bd = gen->large_objects, lge = 0; bd; bd = bd->link) {
797 live = gen->n_words + countOccupied(gen->large_objects);
798 slop = (gen->n_blocks + gen->n_large_blocks) * BLOCK_SIZE_W - live;
799 debugBelch("%8d %8d %8ld %8ld\n", gen->n_blocks, lge,
800 live*sizeof(W_), slop*sizeof(W_));
804 debugBelch("----------------------------------------------------------\n");
805 debugBelch("%41s%8ld %8ld\n","",tot_live*sizeof(W_),tot_slop*sizeof(W_));
806 debugBelch("----------------------------------------------------------\n");
810 /* -----------------------------------------------------------------------------
811 Stats available via a programmatic interface, so eg. GHCi can time
812 each compilation and expression evaluation.
813 -------------------------------------------------------------------------- */
815 extern HsInt64 getAllocations( void )
816 { return (HsInt64)GC_tot_alloc * sizeof(W_); }
818 /* -----------------------------------------------------------------------------
819 Dumping stuff in the stats file, or via the debug message interface
820 -------------------------------------------------------------------------- */
823 statsPrintf( char *s, ... )
825 FILE *sf = RtsFlags.GcFlags.statsFile;
840 FILE *sf = RtsFlags.GcFlags.statsFile;
849 FILE *sf = RtsFlags.GcFlags.statsFile;