Stats.c 18 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 "Storage.h"
14
#include "Schedule.h"
15
#include "Stats.h"
sof's avatar
sof committed
16
#include "ParTicky.h"                       /* ToDo: move into Rts.h */
17
#include "Profiling.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

Simon Marlow's avatar
Simon Marlow committed
78 79 80 81 82
Ticks stat_getElapsedTime(void)
{
    return getProcessElapsedTime() - ElapsedTimeStart;
}

83 84
/* mut_user_time_during_GC() and mut_user_time()
 *
chak's avatar
chak committed
85 86 87 88 89 90 91 92 93
 * 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)
94 95
 */
double
96
mut_user_time_during_GC( void )
97
{
98
  return TICK_TO_DBL(GC_start_time - GC_tot_time - PROF_VAL(RP_tot_time + HC_tot_time));
99 100 101
}

double
102
mut_user_time( void )
103
{
104 105
    Ticks user;
    user = getProcessCPUTime();
106
    return TICK_TO_DBL(user - GC_tot_time - PROF_VAL(RP_tot_time + HC_tot_time));
107 108
}

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

double
122
mut_user_time_during_heap_census( void )
123
{
124
  return TICK_TO_DBL(HC_start_time - GC_tot_time - RP_tot_time - HC_tot_time);
125
}
sof's avatar
sof committed
126
#endif /* PROFILING */
127

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

/* -----------------------------------------------------------------------------
   Initialisation time...
   -------------------------------------------------------------------------- */
149 150

void
151
stat_startInit(void)
152
{
153
    Ticks elapsed;
154

155
    elapsed = getProcessElapsedTime();
156
    ElapsedTimeStart = elapsed;
157 158 159
}

void 
160
stat_endInit(void)
161
{
162 163 164 165
    Ticks user, elapsed;

    getProcessTimes(&user, &elapsed);

166 167 168
    InitUserTime = user;
    InitElapsedStamp = elapsed; 
    if (ElapsedTimeStart > elapsed) {
169 170
	InitElapsedTime = 0;
    } else {
171
	InitElapsedTime = elapsed - ElapsedTimeStart;
172
    }
173 174
}

175 176 177 178 179 180
/* -----------------------------------------------------------------------------
   stat_startExit and stat_endExit
   
   These two measure the time taken in shutdownHaskell().
   -------------------------------------------------------------------------- */

181 182 183
void
stat_startExit(void)
{
184 185 186
    Ticks user, elapsed;

    getProcessTimes(&user, &elapsed);
187 188 189

    MutElapsedStamp = elapsed;
    MutElapsedTime = elapsed - GCe_tot_time -
190
	PROF_VAL(RPe_tot_time + HCe_tot_time) - InitElapsedStamp;
191
    if (MutElapsedTime < 0) { MutElapsedTime = 0; }	/* sometimes -0.00 */
192

193
    MutUserTime = user - GC_tot_time - PROF_VAL(RP_tot_time + HC_tot_time) - InitUserTime;
194
    if (MutUserTime < 0) { MutUserTime = 0; }
195 196 197 198 199
}

void
stat_endExit(void)
{
200 201 202
    Ticks user, elapsed;

    getProcessTimes(&user, &elapsed);
203 204 205

    ExitUserTime = user - MutUserTime - GC_tot_time - PROF_VAL(RP_tot_time + HC_tot_time) - InitUserTime;
    ExitElapsedTime = elapsed - MutElapsedStamp;
206 207 208 209 210 211
    if (ExitUserTime < 0) {
	ExitUserTime = 0;
    }
    if (ExitElapsedTime < 0) {
	ExitElapsedTime = 0;
    }
212 213
}

214 215 216 217 218 219
/* -----------------------------------------------------------------------------
   Called at the beginning of each GC
   -------------------------------------------------------------------------- */

static nat rub_bell = 0;

chak's avatar
chak committed
220 221 222 223 224
/*  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
 */
225 226 227 228 229 230 231
void
stat_startGC(void)
{
    nat bell = RtsFlags.GcFlags.ringBell;

    if (bell) {
	if (bell > 1) {
232
	    debugBelch(" GC ");
233 234
	    rub_bell = 1;
	} else {
235
	    debugBelch("\007");
236 237 238
	}
    }

chak's avatar
chak committed
239
#if defined(PROFILING) || defined(DEBUG)
240
    GC_start_time = getProcessCPUTime();  // needed in mut_user_time_during_GC()
chak's avatar
chak committed
241 242
#endif

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

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

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

	    GC_end_faults = faults;
285
	    statsFlush();
286 287
	}

chak's avatar
chak committed
288
	GC_coll_times[gen] += gc_time;
289

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

306
	if (gen == RtsFlags.GcFlags.generations-1) { /* major GC? */
307 308 309 310 311
	    if (live > MaxResidency) {
		MaxResidency = live;
	    }
	    ResidencySamples++;
	    AvgResidency += live;
312
	}
313 314 315
    }

    if (rub_bell) {
316
	debugBelch("\b\b\b  \b\b\b");
317 318 319 320
	rub_bell = 0;
    }
}

321 322 323
/* -----------------------------------------------------------------------------
   Called at the beginning of each Retainer Profiliing
   -------------------------------------------------------------------------- */
324 325 326
#ifdef PROFILING
void
stat_startRP(void)
327
{
328 329
    Ticks user, elapsed;
    getProcessTimes( &user, &elapsed );
330 331 332

    RP_start_time = user;
    RPe_start_time = elapsed;
333
}
sof's avatar
sof committed
334
#endif /* PROFILING */
335 336 337 338

/* -----------------------------------------------------------------------------
   Called at the end of each Retainer Profiliing
   -------------------------------------------------------------------------- */
339 340 341 342

#ifdef PROFILING
void
stat_endRP(
343 344 345 346 347
  nat retainerGeneration,
#ifdef DEBUG_RETAINER
  nat maxCStackSize,
  int maxStackSize,
#endif
348
  double averageNumVisit)
349
{
350 351
    Ticks user, elapsed;
    getProcessTimes( &user, &elapsed );
352 353 354

    RP_tot_time += user - RP_start_time;
    RPe_tot_time += elapsed - RPe_start_time;
355 356 357 358 359 360 361 362 363

  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
364
#endif /* PROFILING */
365 366

/* -----------------------------------------------------------------------------
367
   Called at the beginning of each heap census
368
   -------------------------------------------------------------------------- */
369 370
#ifdef PROFILING
void
371
stat_startHeapCensus(void)
372
{
373 374
    Ticks user, elapsed;
    getProcessTimes( &user, &elapsed );
375 376 377

    HC_start_time = user;
    HCe_start_time = elapsed;
378
}
sof's avatar
sof committed
379
#endif /* PROFILING */
380 381

/* -----------------------------------------------------------------------------
382
   Called at the end of each heap census
383
   -------------------------------------------------------------------------- */
384 385
#ifdef PROFILING
void
386
stat_endHeapCensus(void) 
387
{
388 389
    Ticks user, elapsed;
    getProcessTimes( &user, &elapsed );
390 391 392

    HC_tot_time += user - HC_start_time;
    HCe_tot_time += elapsed - HCe_start_time;
393
}
sof's avatar
sof committed
394
#endif /* PROFILING */
395

396 397 398 399 400 401 402 403 404 405 406
/* -----------------------------------------------------------------------------
   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)
{
407
    if (RtsFlags.GcFlags.giveStats != NO_GC_STATS) {
408 409

	char temp[BIG_STRING_LEN];
410 411
	Ticks time;
	Ticks etime;
412
	nat g, total_collections = 0;
413

414 415
	getProcessTimes( &time, &etime );
	etime -= ElapsedTimeStart;
416 417

	GC_tot_alloc += alloc;
418

419 420 421
	/* Count total garbage collections */
	for (g = 0; g < RtsFlags.GcFlags.generations; g++)
	    total_collections += generations[g].collections;
422

423 424 425 426
	/* 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;
	
427 428 429
	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);
430 431
	}

432
	if (RtsFlags.GcFlags.giveStats >= SUMMARY_GC_STATS) {
433 434
	    ullong_format_string(GC_tot_alloc*sizeof(W_), 
				 temp, rtsTrue/*commas*/);
435
	    statsPrintf("%11s bytes allocated in the heap\n", temp);
436 437 438

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

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

	    /* Print garbage collections in each gen */
	    for (g = 0; g < RtsFlags.GcFlags.generations; g++) {
455
		statsPrintf("%11d collections in generation %d (%6.2fs)\n", 
456 457 458 459
			generations[g].collections, g, 
			TICK_TO_DBL(GC_coll_times[g]));
	    }

460
	    statsPrintf("\n%11ld Mb total memory in use\n\n", 
461
		    mblocks_allocated * MBLOCK_SIZE / (1024 * 1024));
462

463
#if defined(THREADED_RTS)
464 465
	    {
		nat i;
466 467 468 469
		Task *task;
		for (i = 0, task = all_tasks; 
		     task != NULL; 
		     i++, task = task->all_link) {
470 471 472
		    statsPrintf("  Task %2d %-8s :  MUT time: %6.2fs  (%6.2fs elapsed)\n"
			    "                      GC  time: %6.2fs  (%6.2fs elapsed)\n\n", 
				i,
473 474 475 476 477
				(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));
478 479 480 481
		}
	    }
#endif

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

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

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

537 538
	statsFlush();
	statsClose();
539
    }
540 541 542
    if (GC_coll_times)
      stgFree(GC_coll_times);
    GC_coll_times = NULL;
543
}
544 545 546 547 548 549

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

   Produce some detailed info on the state of the generational GC.
   -------------------------------------------------------------------------- */
550
#ifdef DEBUG
551
void
552
statDescribeGens(void)
553
{
554 555
  nat g, s, mut, lge;
  lnat live;
556 557 558
  bdescr *bd;
  step *step;

559
  debugBelch(
Simon Marlow's avatar
Simon Marlow committed
560 561
"     Gen    Steps      Max  Mut-list  Step   Blocks     Live    Large\n"
"                    Blocks     Bytes                          Objects\n");
562

563
  mut = 0;
564
  for (g = 0; g < RtsFlags.GcFlags.generations; g++) {
565
      for (bd = generations[g].mut_list; bd != NULL; bd = bd->link) {
Simon Marlow's avatar
Simon Marlow committed
566
	  mut += (bd->free - bd->start) * sizeof(W_);
567 568 569 570
      }

    debugBelch("%8d %8d %8d %9d", g, generations[g].n_steps,
	    generations[g].max_blocks, mut);
571 572 573 574

    for (s = 0; s < generations[g].n_steps; s++) {
      step = &generations[g].steps[s];
      live = 0;
575 576 577 578
      for (bd = step->large_objects, lge = 0; bd; bd = bd->link) {
	lge++;
      }
      live = step->n_large_blocks * BLOCK_SIZE;
579
      bd = step->blocks;
580 581 582
      // 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.
583
      for (; bd; bd = bd->link) {
584 585 586
	live += (bd->free - bd->start) * sizeof(W_);
      }
      if (s != 0) {
587
	debugBelch("%36s","");
588
      }
Simon Marlow's avatar
Simon Marlow committed
589
      debugBelch("%6d %8d %8ld %8d\n", s, step->n_blocks,
590 591 592
	      live, lge);
    }
  }
593
  debugBelch("\n");
594
}
595
#endif
596 597 598 599 600 601

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

602 603
extern HsInt64 getAllocations( void ) 
{ return (HsInt64)total_allocated * sizeof(W_); }
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 633 634 635 636 637 638 639 640

/* -----------------------------------------------------------------------------
   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);
    }
}