Stats.c 17.8 KB
Newer Older
1
/* -----------------------------------------------------------------------------
2
 *
3
 * (c) The GHC Team, 1998-2005
4 5 6 7 8 9 10 11
 *
 * Statistics and timing-related functions.
 *
 * ---------------------------------------------------------------------------*/

#include "Rts.h"
#include "RtsFlags.h"
#include "RtsUtils.h"
12
#include "MBlock.h"
13
#include "Schedule.h"
14
#include "Stats.h"
sof's avatar
sof committed
15
#include "ParTicky.h"                       /* ToDo: move into Rts.h */
16
#include "Profiling.h"
17
#include "Storage.h"
18
#include "GetTime.h"
19

20 21 22
/* huh? */
#define BIG_STRING_LEN              512

23
#define TICK_TO_DBL(t) ((double)(t) / TICKS_PER_SECOND)
24

25
static Ticks ElapsedTimeStart = 0;
26

27 28 29
static Ticks InitUserTime     = 0;
static Ticks InitElapsedTime  = 0;
static Ticks InitElapsedStamp = 0;
30

31 32 33
static Ticks MutUserTime      = 0;
static Ticks MutElapsedTime   = 0;
static Ticks MutElapsedStamp  = 0;
34

35 36
static Ticks ExitUserTime     = 0;
static Ticks ExitElapsedTime  = 0;
37

38 39
static ullong GC_tot_alloc        = 0;
static ullong GC_tot_copied       = 0;
40
static ullong GC_tot_scavd_copied = 0;
41

42 43
static Ticks GC_start_time = 0,  GC_tot_time  = 0;  /* User GC Time */
static Ticks GCe_start_time = 0, GCe_tot_time = 0;  /* Elapsed GC time */
44

sof's avatar
sof committed
45
#ifdef PROFILING
46 47
static Ticks RP_start_time  = 0, RP_tot_time  = 0;  /* retainer prof user time */
static Ticks RPe_start_time = 0, RPe_tot_time = 0;  /* retainer prof elap time */
sof's avatar
sof committed
48

49 50
static Ticks HC_start_time, HC_tot_time = 0;     // heap census prof user time
static Ticks HCe_start_time, HCe_tot_time = 0;   // heap census prof elap time
sof's avatar
sof committed
51
#endif
52

sof's avatar
sof committed
53
#ifdef PROFILING
54
#define PROF_VAL(x)   (x)
sof's avatar
sof committed
55 56 57
#else
#define PROF_VAL(x)   0
#endif
58

59 60 61
static lnat MaxResidency = 0;     // in words; for stats only
static lnat AvgResidency = 0;
static lnat ResidencySamples = 0; // for stats only
62 63 64

static lnat GC_start_faults = 0, GC_end_faults = 0;

65
static Ticks *GC_coll_times;
66

67 68 69
static void statsPrintf( char *s, ... ) 
    GNUC3_ATTRIBUTE(format (printf, 1, 2));

70 71 72
static void statsFlush( void );
static void statsClose( void );

73
Ticks stat_getElapsedGCTime(void)
74
{
75
    return GCe_tot_time;
76
}
sof's avatar
sof committed
77

78 79
/* mut_user_time_during_GC() and mut_user_time()
 *
chak's avatar
chak committed
80 81 82 83 84 85 86 87 88
 * The former function can be used to get the current mutator time
 * *during* a GC, i.e. between stat_startGC and stat_endGC.  This is
 * used in the heap profiler for accurately time stamping the heap
 * sample.  
 *
 * ATTENTION: mut_user_time_during_GC() relies on GC_start_time being 
 *	      defined in stat_startGC() - to minimise system calls, 
 *	      GC_start_time is, however, only defined when really needed (check
 *	      stat_startGC() for details)
89 90
 */
double
91
mut_user_time_during_GC( void )
92
{
93
  return TICK_TO_DBL(GC_start_time - GC_tot_time - PROF_VAL(RP_tot_time + HC_tot_time));
94 95 96
}

double
97
mut_user_time( void )
98
{
99 100
    Ticks user;
    user = getProcessCPUTime();
101
    return TICK_TO_DBL(user - GC_tot_time - PROF_VAL(RP_tot_time + HC_tot_time));
102 103
}

104
#ifdef PROFILING
105 106 107
/*
  mut_user_time_during_RP() is similar to mut_user_time_during_GC();
  it returns the MUT time during retainer profiling.
108
  The same is for mut_user_time_during_HC();
109 110 111 112
 */
double
mut_user_time_during_RP( void )
{
113
  return TICK_TO_DBL(RP_start_time - GC_tot_time - RP_tot_time - HC_tot_time);
114 115 116
}

double
117
mut_user_time_during_heap_census( void )
118
{
119
  return TICK_TO_DBL(HC_start_time - GC_tot_time - RP_tot_time - HC_tot_time);
120
}
sof's avatar
sof committed
121
#endif /* PROFILING */
122

123 124 125 126 127 128
void
initStats(void)
{
    nat i;
  
    if (RtsFlags.GcFlags.giveStats >= VERBOSE_GC_STATS) {
129
	statsPrintf("    Alloc    Copied     Live    GC    GC     TOT     TOT  Page Flts\n");
130
	statsPrintf("    bytes     bytes     bytes  user  elap    user    elap\n");
131 132
    }
    GC_coll_times = 
133 134
	(Ticks *)stgMallocBytes(
	    sizeof(Ticks)*RtsFlags.GcFlags.generations,
135 136 137 138 139 140 141 142 143
	    "initStats");
    for (i = 0; i < RtsFlags.GcFlags.generations; i++) {
	GC_coll_times[i] = 0;
    }
}    

/* -----------------------------------------------------------------------------
   Initialisation time...
   -------------------------------------------------------------------------- */
144 145

void
146
stat_startInit(void)
147
{
148
    Ticks elapsed;
149

150
    elapsed = getProcessElapsedTime();
151
    ElapsedTimeStart = elapsed;
152 153 154
}

void 
155
stat_endInit(void)
156
{
157 158 159 160
    Ticks user, elapsed;

    getProcessTimes(&user, &elapsed);

161 162 163
    InitUserTime = user;
    InitElapsedStamp = elapsed; 
    if (ElapsedTimeStart > elapsed) {
164 165
	InitElapsedTime = 0;
    } else {
166
	InitElapsedTime = elapsed - ElapsedTimeStart;
167
    }
168 169
}

170 171 172 173 174 175
/* -----------------------------------------------------------------------------
   stat_startExit and stat_endExit
   
   These two measure the time taken in shutdownHaskell().
   -------------------------------------------------------------------------- */

176 177 178
void
stat_startExit(void)
{
179 180 181
    Ticks user, elapsed;

    getProcessTimes(&user, &elapsed);
182 183 184

    MutElapsedStamp = elapsed;
    MutElapsedTime = elapsed - GCe_tot_time -
185
	PROF_VAL(RPe_tot_time + HCe_tot_time) - InitElapsedStamp;
186
    if (MutElapsedTime < 0) { MutElapsedTime = 0; }	/* sometimes -0.00 */
187

188
    MutUserTime = user - GC_tot_time - PROF_VAL(RP_tot_time + HC_tot_time) - InitUserTime;
189
    if (MutUserTime < 0) { MutUserTime = 0; }
190 191 192 193 194
}

void
stat_endExit(void)
{
195 196 197
    Ticks user, elapsed;

    getProcessTimes(&user, &elapsed);
198 199 200

    ExitUserTime = user - MutUserTime - GC_tot_time - PROF_VAL(RP_tot_time + HC_tot_time) - InitUserTime;
    ExitElapsedTime = elapsed - MutElapsedStamp;
201 202 203 204 205 206
    if (ExitUserTime < 0) {
	ExitUserTime = 0;
    }
    if (ExitElapsedTime < 0) {
	ExitElapsedTime = 0;
    }
207 208
}

209 210 211 212 213 214
/* -----------------------------------------------------------------------------
   Called at the beginning of each GC
   -------------------------------------------------------------------------- */

static nat rub_bell = 0;

chak's avatar
chak committed
215 216 217 218 219
/*  initialise global variables needed during GC
 *
 *  * GC_start_time is read in mut_user_time_during_GC(), which in turn is 
 *    needed if either PROFILING or DEBUGing is enabled
 */
220 221 222 223 224 225 226
void
stat_startGC(void)
{
    nat bell = RtsFlags.GcFlags.ringBell;

    if (bell) {
	if (bell > 1) {
227
	    debugBelch(" GC ");
228 229
	    rub_bell = 1;
	} else {
230
	    debugBelch("\007");
231 232 233
	}
    }

chak's avatar
chak committed
234
#if defined(PROFILING) || defined(DEBUG)
235
    GC_start_time = getProcessCPUTime();  // needed in mut_user_time_during_GC()
chak's avatar
chak committed
236 237
#endif

238
    if (RtsFlags.GcFlags.giveStats != NO_GC_STATS) {
chak's avatar
chak committed
239
#if !defined(PROFILING) && !defined(DEBUG)
240
        GC_start_time = getProcessCPUTime();
chak's avatar
chak committed
241
#endif
242
	GCe_start_time = getProcessElapsedTime();
243
	if (RtsFlags.GcFlags.giveStats) {
244
	    GC_start_faults = getPageFaults();
245 246 247 248 249 250 251 252 253
	}
    }
}

/* -----------------------------------------------------------------------------
   Called at the end of each GC
   -------------------------------------------------------------------------- */

void
254
stat_endGC (lnat alloc, lnat live, lnat copied, 
255
	    lnat scavd_copied, lnat gen)
256
{
257
    if (RtsFlags.GcFlags.giveStats != NO_GC_STATS) {
258
	Ticks time, etime, gc_time, gc_etime;
259
	
260
	getProcessTimes(&time, &etime);
261 262 263
	gc_time  = time - GC_start_time;
	gc_etime = etime - GCe_start_time;
	
264
	if (RtsFlags.GcFlags.giveStats == VERBOSE_GC_STATS) {
265
	    nat faults = getPageFaults();
266
	    
267
	    statsPrintf("%9ld %9ld %9ld",
268 269
		    alloc*sizeof(W_), (copied+scavd_copied)*sizeof(W_), 
			live*sizeof(W_));
270
	    statsPrintf(" %5.2f %5.2f %7.2f %7.2f %4ld %4ld  (Gen: %2ld)\n", 
271 272 273 274
		    TICK_TO_DBL(gc_time),
		    TICK_TO_DBL(gc_etime),
		    TICK_TO_DBL(time),
		    TICK_TO_DBL(etime - ElapsedTimeStart),
275 276
		    faults - GC_start_faults,
		    GC_start_faults - GC_end_faults,
277
		    gen);
278 279

	    GC_end_faults = faults;
280
	    statsFlush();
281 282
	}

chak's avatar
chak committed
283
	GC_coll_times[gen] += gc_time;
284

285
	GC_tot_copied += (ullong) copied;
286
	GC_tot_scavd_copied += (ullong) scavd_copied;
287
	GC_tot_alloc  += (ullong) alloc;
288 289
	GC_tot_time   += gc_time;
	GCe_tot_time  += gc_etime;
290
	
291
#if defined(THREADED_RTS)
292
	{
293 294 295 296
	    Task *task;
	    if ((task = myTask()) != NULL) {
		task->gc_time += gc_time;
		task->gc_etime += gc_etime;
297 298 299 300
	    }
	}
#endif

301
	if (gen == RtsFlags.GcFlags.generations-1) { /* major GC? */
302 303 304 305 306
	    if (live > MaxResidency) {
		MaxResidency = live;
	    }
	    ResidencySamples++;
	    AvgResidency += live;
307
	}
308 309 310
    }

    if (rub_bell) {
311
	debugBelch("\b\b\b  \b\b\b");
312 313 314 315
	rub_bell = 0;
    }
}

316 317 318
/* -----------------------------------------------------------------------------
   Called at the beginning of each Retainer Profiliing
   -------------------------------------------------------------------------- */
319 320 321
#ifdef PROFILING
void
stat_startRP(void)
322
{
323 324
    Ticks user, elapsed;
    getProcessTimes( &user, &elapsed );
325 326 327

    RP_start_time = user;
    RPe_start_time = elapsed;
328
}
sof's avatar
sof committed
329
#endif /* PROFILING */
330 331 332 333

/* -----------------------------------------------------------------------------
   Called at the end of each Retainer Profiliing
   -------------------------------------------------------------------------- */
334 335 336 337

#ifdef PROFILING
void
stat_endRP(
338 339 340 341 342
  nat retainerGeneration,
#ifdef DEBUG_RETAINER
  nat maxCStackSize,
  int maxStackSize,
#endif
343
  double averageNumVisit)
344
{
345 346
    Ticks user, elapsed;
    getProcessTimes( &user, &elapsed );
347 348 349

    RP_tot_time += user - RP_start_time;
    RPe_tot_time += elapsed - RPe_start_time;
350 351 352 353 354 355 356 357 358

  fprintf(prof_file, "Retainer Profiling: %d, at %f seconds\n", 
    retainerGeneration, mut_user_time_during_RP());
#ifdef DEBUG_RETAINER
  fprintf(prof_file, "\tMax C stack size = %u\n", maxCStackSize);
  fprintf(prof_file, "\tMax auxiliary stack size = %u\n", maxStackSize);
#endif
  fprintf(prof_file, "\tAverage number of visits per object = %f\n", averageNumVisit);
}
sof's avatar
sof committed
359
#endif /* PROFILING */
360 361

/* -----------------------------------------------------------------------------
362
   Called at the beginning of each heap census
363
   -------------------------------------------------------------------------- */
364 365
#ifdef PROFILING
void
366
stat_startHeapCensus(void)
367
{
368 369
    Ticks user, elapsed;
    getProcessTimes( &user, &elapsed );
370 371 372

    HC_start_time = user;
    HCe_start_time = elapsed;
373
}
sof's avatar
sof committed
374
#endif /* PROFILING */
375 376

/* -----------------------------------------------------------------------------
377
   Called at the end of each heap census
378
   -------------------------------------------------------------------------- */
379 380
#ifdef PROFILING
void
381
stat_endHeapCensus(void) 
382
{
383 384
    Ticks user, elapsed;
    getProcessTimes( &user, &elapsed );
385 386 387

    HC_tot_time += user - HC_start_time;
    HCe_tot_time += elapsed - HCe_start_time;
388
}
sof's avatar
sof committed
389
#endif /* PROFILING */
390

391 392 393 394 395 396 397 398 399 400 401
/* -----------------------------------------------------------------------------
   Called at the end of execution

   NOTE: number of allocations is not entirely accurate: it doesn't
   take into account the few bytes at the end of the heap that
   were left unused when the heap-check failed.
   -------------------------------------------------------------------------- */

void
stat_exit(int alloc)
{
402
    if (RtsFlags.GcFlags.giveStats != NO_GC_STATS) {
403 404

	char temp[BIG_STRING_LEN];
405 406
	Ticks time;
	Ticks etime;
407
	nat g, total_collections = 0;
408

409 410
	getProcessTimes( &time, &etime );
	etime -= ElapsedTimeStart;
411 412

	GC_tot_alloc += alloc;
413

414 415 416
	/* Count total garbage collections */
	for (g = 0; g < RtsFlags.GcFlags.generations; g++)
	    total_collections += generations[g].collections;
417

418 419 420 421
	/* avoid divide by zero if time is measured as 0.00 seconds -- SDM */
	if (time  == 0.0)  time = 1;
	if (etime == 0.0) etime = 1;
	
422 423 424
	if (RtsFlags.GcFlags.giveStats >= VERBOSE_GC_STATS) {
	    statsPrintf("%9ld %9.9s %9.9s", (lnat)alloc*sizeof(W_), "", "");
	    statsPrintf(" %5.2f %5.2f\n\n", 0.0, 0.0);
425 426
	}

427
	if (RtsFlags.GcFlags.giveStats >= SUMMARY_GC_STATS) {
428 429
	    ullong_format_string(GC_tot_alloc*sizeof(W_), 
				 temp, rtsTrue/*commas*/);
430
	    statsPrintf("%11s bytes allocated in the heap\n", temp);
431 432 433

	    ullong_format_string(GC_tot_copied*sizeof(W_), 
				 temp, rtsTrue/*commas*/);
434
	    statsPrintf("%11s bytes copied during GC (scavenged)\n", temp);
435

436 437 438 439
	    ullong_format_string(GC_tot_scavd_copied*sizeof(W_), 
				 temp, rtsTrue/*commas*/);
	    statsPrintf("%11s bytes copied during GC (not scavenged)\n", temp);
  
440 441
	    if ( ResidencySamples > 0 ) {
		ullong_format_string(MaxResidency*sizeof(W_), 
442
				     temp, rtsTrue/*commas*/);
443
		statsPrintf("%11s bytes maximum residency (%ld sample(s))\n",
444 445
			temp, ResidencySamples);
	    }
446
	    statsPrintf("\n");
447 448 449

	    /* Print garbage collections in each gen */
	    for (g = 0; g < RtsFlags.GcFlags.generations; g++) {
450
		statsPrintf("%11d collections in generation %d (%6.2fs)\n", 
451 452 453 454
			generations[g].collections, g, 
			TICK_TO_DBL(GC_coll_times[g]));
	    }

455
	    statsPrintf("\n%11ld Mb total memory in use\n\n", 
456
		    mblocks_allocated * MBLOCK_SIZE / (1024 * 1024));
457

458
#if defined(THREADED_RTS)
459 460
	    {
		nat i;
461 462 463 464
		Task *task;
		for (i = 0, task = all_tasks; 
		     task != NULL; 
		     i++, task = task->all_link) {
465 466 467
		    statsPrintf("  Task %2d %-8s :  MUT time: %6.2fs  (%6.2fs elapsed)\n"
			    "                      GC  time: %6.2fs  (%6.2fs elapsed)\n\n", 
				i,
468 469 470 471 472
				(task->tso == NULL) ? "(worker)" : "(bound)",
				TICK_TO_DBL(task->mut_time),
				TICK_TO_DBL(task->mut_etime),
				TICK_TO_DBL(task->gc_time),
				TICK_TO_DBL(task->gc_etime));
473 474 475 476
		}
	    }
#endif

477
	    statsPrintf("  INIT  time  %6.2fs  (%6.2fs elapsed)\n",
478
		    TICK_TO_DBL(InitUserTime), TICK_TO_DBL(InitElapsedTime));
479
	    statsPrintf("  MUT   time  %6.2fs  (%6.2fs elapsed)\n",
480
		    TICK_TO_DBL(MutUserTime), TICK_TO_DBL(MutElapsedTime));
481
	    statsPrintf("  GC    time  %6.2fs  (%6.2fs elapsed)\n",
482
		    TICK_TO_DBL(GC_tot_time), TICK_TO_DBL(GCe_tot_time));
483
#ifdef PROFILING
484
	    statsPrintf("  RP    time  %6.2fs  (%6.2fs elapsed)\n",
485
		    TICK_TO_DBL(RP_tot_time), TICK_TO_DBL(RPe_tot_time));
486
	    statsPrintf("  PROF  time  %6.2fs  (%6.2fs elapsed)\n",
487
		    TICK_TO_DBL(HC_tot_time), TICK_TO_DBL(HCe_tot_time));
488
#endif 
489
	    statsPrintf("  EXIT  time  %6.2fs  (%6.2fs elapsed)\n",
490
		    TICK_TO_DBL(ExitUserTime), TICK_TO_DBL(ExitElapsedTime));
491
	    statsPrintf("  Total time  %6.2fs  (%6.2fs elapsed)\n\n",
492
		    TICK_TO_DBL(time), TICK_TO_DBL(etime));
493
	    statsPrintf("  %%GC time     %5.1f%%  (%.1f%% elapsed)\n\n",
494
		    TICK_TO_DBL(GC_tot_time)*100/TICK_TO_DBL(time),
sof's avatar
sof committed
495
		    TICK_TO_DBL(GCe_tot_time)*100/TICK_TO_DBL(etime));
496

497
	    if (time - GC_tot_time - PROF_VAL(RP_tot_time + HC_tot_time) == 0)
498 499 500 501
		ullong_format_string(0, temp, rtsTrue/*commas*/);
	    else
		ullong_format_string(
		    (ullong)((GC_tot_alloc*sizeof(W_))/
sof's avatar
sof committed
502
			     TICK_TO_DBL(time - GC_tot_time - 
503
					 PROF_VAL(RP_tot_time + HC_tot_time))),
504 505
		    temp, rtsTrue/*commas*/);
	    
506
	    statsPrintf("  Alloc rate    %s bytes per MUT second\n\n", temp);
507
	
508
	    statsPrintf("  Productivity %5.1f%% of total user, %.1f%% of total elapsed\n\n",
sof's avatar
sof committed
509
		    TICK_TO_DBL(time - GC_tot_time - 
510
				PROF_VAL(RP_tot_time + HC_tot_time) - InitUserTime) * 100 
511
		    / TICK_TO_DBL(time), 
sof's avatar
sof committed
512
		    TICK_TO_DBL(time - GC_tot_time - 
513
				PROF_VAL(RP_tot_time + HC_tot_time) - InitUserTime) * 100 
514 515 516
		    / TICK_TO_DBL(etime));
	}

517
	if (RtsFlags.GcFlags.giveStats == ONELINE_GC_STATS) {
rrt's avatar
rrt committed
518
	  /* print the long long separately to avoid bugginess on mingwin (2001-07-02, mingw-0.5) */
519
	  statsPrintf("<<ghc: %llu bytes, ", GC_tot_alloc*(ullong)sizeof(W_));
520
	  statsPrintf("%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",
rrt's avatar
rrt committed
521
		    total_collections,
522 523
		    ResidencySamples == 0 ? 0 : 
		        AvgResidency*sizeof(W_)/ResidencySamples, 
524
		    MaxResidency*sizeof(W_), 
rrt's avatar
rrt committed
525 526
		    ResidencySamples,
		    (unsigned long)(mblocks_allocated * MBLOCK_SIZE / (1024L * 1024L)),
527 528 529 530
		    TICK_TO_DBL(InitUserTime), TICK_TO_DBL(InitElapsedTime),
		    TICK_TO_DBL(MutUserTime), TICK_TO_DBL(MutElapsedTime),
		    TICK_TO_DBL(GC_tot_time), TICK_TO_DBL(GCe_tot_time));
	}
531

532 533
	statsFlush();
	statsClose();
534 535
    }
}
536 537 538 539 540 541

/* -----------------------------------------------------------------------------
   stat_describe_gens

   Produce some detailed info on the state of the generational GC.
   -------------------------------------------------------------------------- */
542
#ifdef DEBUG
543
void
544
statDescribeGens(void)
545
{
546 547
  nat g, s, mut, lge;
  lnat live;
548 549 550
  bdescr *bd;
  step *step;

551 552 553
  debugBelch(
"     Gen    Steps      Max   Mutable  Step   Blocks     Live    Large\n"
"                     Blocks Closures                          Objects\n");
554

555
  mut = 0;
556
  for (g = 0; g < RtsFlags.GcFlags.generations; g++) {
557 558 559 560 561 562
      for (bd = generations[g].mut_list; bd != NULL; bd = bd->link) {
	  mut += bd->free - bd->start;
      }

    debugBelch("%8d %8d %8d %9d", g, generations[g].n_steps,
	    generations[g].max_blocks, mut);
563 564 565 566

    for (s = 0; s < generations[g].n_steps; s++) {
      step = &generations[g].steps[s];
      live = 0;
567 568 569 570
      for (bd = step->large_objects, lge = 0; bd; bd = bd->link) {
	lge++;
      }
      live = step->n_large_blocks * BLOCK_SIZE;
571
      bd = step->blocks;
572 573 574
      // This live figure will be slightly less that the "live" figure
      // given by +RTS -Sstderr, because we take don't count the
      // slop at the end of each block.
575
      for (; bd; bd = bd->link) {
576 577 578
	live += (bd->free - bd->start) * sizeof(W_);
      }
      if (s != 0) {
579
	debugBelch("%36s","");
580
      }
Simon Marlow's avatar
Simon Marlow committed
581
      debugBelch("%6d %8d %8ld %8d\n", s, step->n_blocks,
582 583 584
	      live, lge);
    }
  }
585
  debugBelch("\n");
586
}
587
#endif
588 589 590 591 592 593

/* -----------------------------------------------------------------------------
   Stats available via a programmatic interface, so eg. GHCi can time
   each compilation and expression evaluation.
   -------------------------------------------------------------------------- */

594 595
extern HsInt64 getAllocations( void ) 
{ return (HsInt64)total_allocated * sizeof(W_); }
596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632

/* -----------------------------------------------------------------------------
   Dumping stuff in the stats file, or via the debug message interface
   -------------------------------------------------------------------------- */

static void
statsPrintf( char *s, ... )
{
    FILE *sf = RtsFlags.GcFlags.statsFile;
    va_list ap;
    
    va_start(ap,s);
    if (sf == NULL) {
	vdebugBelch(s,ap);
    } else {
	vfprintf(sf, s, ap);
    }
    va_end(ap);
}

static void
statsFlush( void )
{
    FILE *sf = RtsFlags.GcFlags.statsFile;
    if (sf != NULL) {
	fflush(sf);
    }
}

static void
statsClose( void )
{
    FILE *sf = RtsFlags.GcFlags.statsFile;
    if (sf != NULL) {
	fclose(sf);
    }
}