[project @ 2000-12-19 14:30:17 by simonmar]
[ghc-hetmet.git] / ghc / rts / Stats.c
1 /* -----------------------------------------------------------------------------
2  * $Id: Stats.c,v 1.23 2000/12/19 14:30:17 simonmar Exp $
3  *
4  * (c) The GHC Team, 1998-1999
5  *
6  * Statistics and timing-related functions.
7  *
8  * ---------------------------------------------------------------------------*/
9
10 #define NON_POSIX_SOURCE
11
12 #include "Rts.h"
13 #include "RtsFlags.h"
14 #include "RtsUtils.h"
15 #include "StoragePriv.h"
16 #include "MBlock.h"
17 #include "Schedule.h"
18 #include "Stats.h"
19
20 #ifdef HAVE_UNISTD_H
21 #include <unistd.h>
22 #endif
23
24 #ifndef mingw32_TARGET_OS
25 # ifdef HAVE_SYS_TIMES_H
26 #  include <sys/times.h>
27 # endif
28 #endif
29
30 #ifdef HAVE_SYS_TIME_H
31 #include <sys/time.h>
32 #endif
33
34 #ifdef __CYGWIN32__
35 # ifdef HAVE_TIME_H
36 #  include <time.h>
37 # endif
38 #endif
39
40 #if ! irix_TARGET_OS && ! defined(mingw32_TARGET_OS)
41 # if defined(HAVE_SYS_RESOURCE_H)
42 #  include <sys/resource.h>
43 # endif
44 #endif
45
46 #ifdef HAVE_SYS_TIMEB_H
47 #include <sys/timeb.h>
48 #endif
49
50 #if HAVE_STDLIB_H
51 #include <stdlib.h>
52 #endif
53
54 #if HAVE_WINDOWS_H
55 #include <windows.h>
56 #endif
57
58 /* huh? */
59 #define BIG_STRING_LEN              512
60
61 /* We're not trying to be terribly accurate here, using the 
62  * basic times() function to get a resolution of about 100ths of a 
63  * second, depending on the OS.  A long int will do fine for holding
64  * these values.
65  */
66 #define TICK_TYPE long int
67 #define TICK_TO_DBL(t) ((double)(t) / TicksPerSecond)
68
69 static int TicksPerSecond = 0;
70
71 static TICK_TYPE ElapsedTimeStart = 0;
72 static TICK_TYPE CurrentElapsedTime = 0;
73 static TICK_TYPE CurrentUserTime    = 0;
74
75 static TICK_TYPE InitUserTime     = 0;
76 static TICK_TYPE InitElapsedTime  = 0;
77 static TICK_TYPE InitElapsedStamp = 0;
78
79 static TICK_TYPE MutUserTime      = 0;
80 static TICK_TYPE MutElapsedTime   = 0;
81 static TICK_TYPE MutElapsedStamp  = 0;
82
83 static TICK_TYPE ExitUserTime     = 0;
84 static TICK_TYPE ExitElapsedTime  = 0;
85
86 static ullong GC_tot_alloc        = 0;
87 static ullong GC_tot_copied       = 0;
88
89 static TICK_TYPE GC_start_time,  GC_tot_time = 0;  /* User GC Time */
90 static TICK_TYPE GCe_start_time, GCe_tot_time = 0; /* Elapsed GC time */
91
92 lnat MaxResidency = 0;     /* in words; for stats only */
93 lnat AvgResidency = 0;
94 lnat ResidencySamples = 0; /* for stats only */
95
96 static lnat GC_start_faults = 0, GC_end_faults = 0;
97
98 static TICK_TYPE *GC_coll_times;
99
100 static void  getTimes(void);
101 static nat   pageFaults(void);
102
103 /* elapsedtime() -- The current elapsed time in seconds */
104
105 #ifdef _WIN32
106 #define NS_PER_SEC 10000000LL
107 /* Convert FILETIMEs into secs since the Epoch (Jan1-1970) */
108 #define FT2longlong(ll,ft)    \
109     (ll)=(ft).dwHighDateTime; \
110     (ll) <<= 32;              \
111     (ll) |= (ft).dwLowDateTime; \
112     (ll) /= (unsigned long long) (NS_PER_SEC / CLOCKS_PER_SEC)
113 #endif
114
115 #ifdef _WIN32
116 /* cygwin32 or mingw32 version */
117 static void
118 getTimes(void)
119 {
120     FILETIME creationTime, exitTime, kernelTime, userTime;
121     long long int kT, uT;
122  
123     /* ToDo: pin down elapsed times to just the OS thread(s) that
124        are evaluating/managing Haskell code.
125     */
126     if (!GetProcessTimes (GetCurrentProcess(), &creationTime,
127                           &exitTime, &kernelTime, &userTime)) {
128         /* Probably on a Win95 box..*/
129         return 0;
130     }
131
132     FT2longlong(kT,kernelTime);
133     FT2longlong(uT,userTime);
134     CurrentElapsedTime = uT + kT;
135     CurrentUserTime = uT;
136 }
137
138 #else /* !_WIN32 */
139
140 static void
141 getTimes(void)
142 {
143
144 # if !defined(HAVE_TIMES)
145     /* We will #ifdef around the fprintf for machines
146        we *know* are unsupported. (WDP 94/05)
147     */
148     fprintf(stderr, "NOTE: `getTimes' does nothing!\n");
149     return 0.0;
150
151 # else /* not stumped */
152     struct tms t;
153     clock_t r = times(&t);
154
155     CurrentElapsedTime = r;
156     CurrentUserTime = t.tms_utime;
157 #endif
158
159 }
160 #endif /* !_WIN32 */
161
162 /* mut_user_time_during_GC() and mut_user_time()
163  *
164  * The former function can be used to get the current mutator time
165  * *during* a GC, i.e. between stat_startGC and stat_endGC.  This is
166  * used in the heap profiler for accurately time stamping the heap
167  * sample.  
168  *
169  * ATTENTION: mut_user_time_during_GC() relies on GC_start_time being 
170  *            defined in stat_startGC() - to minimise system calls, 
171  *            GC_start_time is, however, only defined when really needed (check
172  *            stat_startGC() for details)
173  */
174 double
175 mut_user_time_during_GC(void)
176 {
177     return ((double)GC_start_time - (double)GC_tot_time);
178 }
179
180 double
181 mut_user_time(void)
182 {
183     getTimes();
184     return ((double)CurrentUserTime - (double)GC_tot_time);
185 }
186
187 static nat
188 pageFaults(void)
189 {
190   /* ToDo (on NT): better, get this via the performance data
191      that's stored in the registry. */
192 # if !defined(HAVE_GETRUSAGE) || irix_TARGET_OS || defined(_WIN32)
193     return 0;
194 # else
195     struct rusage t;
196
197     getrusage(RUSAGE_SELF, &t);
198     return(t.ru_majflt);
199 # endif
200 }
201
202 void
203 initStats(void)
204 {
205     nat i;
206     FILE *sf = RtsFlags.GcFlags.statsFile;
207   
208     if (RtsFlags.GcFlags.giveStats >= VERBOSE_GC_STATS) {
209         fprintf(sf, "    Alloc    Collect    Live    GC    GC     TOT     TOT  Page Flts\n");
210         fprintf(sf, "    bytes     bytes     bytes  user  elap    user    elap\n");
211     }
212     GC_coll_times = 
213         (TICK_TYPE *)stgMallocBytes(
214             sizeof(TICK_TYPE)*RtsFlags.GcFlags.generations,
215             "initStats");
216     for (i = 0; i < RtsFlags.GcFlags.generations; i++) {
217         GC_coll_times[i] = 0;
218     }
219 }    
220
221 /* -----------------------------------------------------------------------------
222    Initialisation time...
223    -------------------------------------------------------------------------- */
224
225 void
226 stat_startInit(void)
227 {
228     /* Determine TicksPerSecond ... */
229 #if defined(CLK_TCK)            /* defined by POSIX */
230     TicksPerSecond = CLK_TCK;
231
232 #elif defined(HAVE_SYSCONF)
233     long ticks;
234
235     ticks = sysconf(_SC_CLK_TCK);
236     if ( ticks == -1 ) {
237         fprintf(stderr, "stat_init: bad call to 'sysconf'!\n");
238         stg_exit(EXIT_FAILURE);
239     }
240     TicksPerSecond = (double) ticks;
241
242 /* no "sysconf" or CLK_TCK; had better guess */
243 #elif defined(HZ)
244     TicksPerSecond = (StgDouble) (HZ);
245
246 #elif defined(CLOCKS_PER_SEC)
247     TicksPerSecond = (StgDouble) (CLOCKS_PER_SEC);
248 #else /* had better guess wildly */
249     /* We will #ifdef around the fprintf for machines
250        we *know* are unsupported. (WDP 94/05)
251     */
252     fprintf(stderr, "NOTE: Guessing `TicksPerSecond = 60'!\n");
253     TicksPerSecond = 60.0;
254 #endif
255
256     getTimes();
257     ElapsedTimeStart = CurrentElapsedTime;
258 }
259
260 void 
261 stat_endInit(void)
262 {
263     getTimes();
264     InitUserTime = CurrentUserTime;
265     InitElapsedStamp = CurrentElapsedTime; 
266     if (ElapsedTimeStart > CurrentElapsedTime) {
267         InitElapsedTime = 0;
268     } else {
269         InitElapsedTime = CurrentElapsedTime - ElapsedTimeStart;
270     }
271 }
272
273 /* -----------------------------------------------------------------------------
274    stat_startExit and stat_endExit
275    
276    These two measure the time taken in shutdownHaskell().
277    -------------------------------------------------------------------------- */
278
279 void
280 stat_startExit(void)
281 {
282     getTimes();
283     MutElapsedStamp = CurrentElapsedTime;
284     MutElapsedTime = CurrentElapsedTime - GCe_tot_time - InitElapsedStamp;
285     if (MutElapsedTime < 0) { MutElapsedTime = 0; }     /* sometimes -0.00 */
286     
287     /* for SMP, we don't know the mutator time yet, we have to inspect
288      * all the running threads to find out, and they haven't stopped
289      * yet.  So we just timestamp MutUserTime at this point so we can
290      * calculate the EXIT time.  The real MutUserTime is calculated
291      * in stat_exit below.
292      */
293 #ifdef SMP
294     MutUserTime = CurrentUserTime;
295 #else
296     MutUserTime = CurrentUserTime - GC_tot_time - InitUserTime;
297     if (MutUserTime < 0) { MutUserTime = 0; }
298 #endif
299 }
300
301 void
302 stat_endExit(void)
303 {
304     getTimes();
305 #ifdef SMP
306     ExitUserTime = CurrentUserTime - MutUserTime;
307 #else
308     ExitUserTime = CurrentUserTime - MutUserTime - GC_tot_time - InitUserTime;
309 #endif
310     ExitElapsedTime = CurrentElapsedTime - MutElapsedStamp;
311     if (ExitUserTime < 0) {
312         ExitUserTime = 0;
313     }
314     if (ExitElapsedTime < 0) {
315         ExitElapsedTime = 0;
316     }
317 }
318
319 /* -----------------------------------------------------------------------------
320    Called at the beginning of each GC
321    -------------------------------------------------------------------------- */
322
323 static nat rub_bell = 0;
324
325 /*  initialise global variables needed during GC
326  *
327  *  * GC_start_time is read in mut_user_time_during_GC(), which in turn is 
328  *    needed if either PROFILING or DEBUGing is enabled
329  */
330 void
331 stat_startGC(void)
332 {
333     nat bell = RtsFlags.GcFlags.ringBell;
334
335     if (bell) {
336         if (bell > 1) {
337             fprintf(stderr, " GC ");
338             rub_bell = 1;
339         } else {
340             fprintf(stderr, "\007");
341         }
342     }
343
344 #if defined(PROFILING) || defined(DEBUG)
345     getTimes();
346     GC_start_time = CurrentUserTime;  /* needed in mut_user_time_during_GC() */
347 #endif
348
349     if (RtsFlags.GcFlags.giveStats != NO_GC_STATS) {
350 #if !defined(PROFILING) && !defined(DEBUG)
351         getTimes();
352         GC_start_time = CurrentUserTime;
353 #endif
354         GCe_start_time = CurrentElapsedTime;
355         if (RtsFlags.GcFlags.giveStats) {
356             GC_start_faults = pageFaults();
357         }
358     }
359 }
360
361 /* -----------------------------------------------------------------------------
362    Called at the end of each GC
363    -------------------------------------------------------------------------- */
364
365 void
366 stat_endGC(lnat alloc, lnat collect, lnat live, lnat copied, lnat gen)
367 {
368     FILE *sf = RtsFlags.GcFlags.statsFile;
369
370     if (RtsFlags.GcFlags.giveStats != NO_GC_STATS) {
371         TICK_TYPE time, etime, gc_time, gc_etime;
372         
373         getTimes();
374         time     = CurrentUserTime;
375         etime    = CurrentElapsedTime;
376         gc_time  = time - GC_start_time;
377         gc_etime = etime - GCe_start_time;
378         
379         if (RtsFlags.GcFlags.giveStats == VERBOSE_GC_STATS && sf != NULL) {
380             nat faults = pageFaults();
381             
382             fprintf(sf, "%9ld %9ld %9ld",
383                     alloc*sizeof(W_), collect*sizeof(W_), live*sizeof(W_));
384             fprintf(sf, " %5.2f %5.2f %7.2f %7.2f %4ld %4ld  (Gen: %2ld)\n", 
385                     TICK_TO_DBL(gc_time),
386                     TICK_TO_DBL(gc_etime),
387                     TICK_TO_DBL(time),
388                     TICK_TO_DBL(etime - ElapsedTimeStart),
389                     faults - GC_start_faults,
390                     GC_start_faults - GC_end_faults,
391                     gen);
392
393             GC_end_faults = faults;
394             fflush(sf);
395         }
396
397         GC_coll_times[gen] += gc_time;
398
399         GC_tot_copied += (ullong) copied;
400         GC_tot_alloc  += (ullong) alloc;
401         GC_tot_time   += gc_time;
402         GCe_tot_time  += gc_etime;
403         
404 #ifdef SMP
405         {
406             nat i;
407             pthread_t me = pthread_self();
408
409             for (i = 0; i < RtsFlags.ParFlags.nNodes; i++) {
410                 if (me == task_ids[i].id) {
411                     task_ids[i].gc_time += gc_time;
412                     task_ids[i].gc_etime += gc_etime;
413                     break;
414                 }
415             }
416         }
417 #endif
418
419         if (gen == RtsFlags.GcFlags.generations-1) { /* major GC? */
420             if (live > MaxResidency) {
421                 MaxResidency = live;
422             }
423             ResidencySamples++;
424             AvgResidency += live;
425         }
426     }
427
428     if (rub_bell) {
429         fprintf(stderr, "\b\b\b  \b\b\b");
430         rub_bell = 0;
431     }
432 }
433
434 /* -----------------------------------------------------------------------------
435    stat_workerStop
436
437    Called under SMP when a worker thread finishes.  We drop the timing
438    stats for this thread into the task_ids struct for that thread.
439    -------------------------------------------------------------------------- */
440
441 #ifdef SMP
442 void
443 stat_workerStop(void)
444 {
445     nat i;
446     pthread_t me = pthread_self();
447
448     for (i = 0; i < RtsFlags.ParFlags.nNodes; i++) {
449         if (task_ids[i].id == me) {
450             task_ids[i].mut_time = usertime() - task_ids[i].gc_time;
451             task_ids[i].mut_etime = elapsedtime()
452                 - GCe_tot_time
453                 - task_ids[i].elapsedtimestart;
454             if (task_ids[i].mut_time < 0.0)  { task_ids[i].mut_time = 0.0;  }
455             if (task_ids[i].mut_etime < 0.0) { task_ids[i].mut_etime = 0.0; }
456         }
457     }
458 }
459 #endif
460
461 /* -----------------------------------------------------------------------------
462    Called at the end of execution
463
464    NOTE: number of allocations is not entirely accurate: it doesn't
465    take into account the few bytes at the end of the heap that
466    were left unused when the heap-check failed.
467    -------------------------------------------------------------------------- */
468
469 void
470 stat_exit(int alloc)
471 {
472     FILE *sf = RtsFlags.GcFlags.statsFile;
473     
474     if (RtsFlags.GcFlags.giveStats != NO_GC_STATS) {
475
476         char temp[BIG_STRING_LEN];
477         TICK_TYPE time;
478         TICK_TYPE etime;
479         nat g, total_collections = 0;
480
481         getTimes();
482         time = CurrentUserTime;
483         etime = CurrentElapsedTime - ElapsedTimeStart;
484
485         GC_tot_alloc += alloc;
486
487         /* avoid divide by zero if time is measured as 0.00 seconds -- SDM */
488         if (time  == 0.0)  time = 1;
489         if (etime == 0.0) etime = 1;
490         
491         /* Count total garbage collections */
492         for (g = 0; g < RtsFlags.GcFlags.generations; g++)
493             total_collections += generations[g].collections;
494
495         /* For SMP, we have to get the user time from each thread
496          * and try to work out the total time.
497          */
498 #ifdef SMP
499         {   nat i;
500             MutUserTime = 0.0;
501             for (i = 0; i < RtsFlags.ParFlags.nNodes; i++) {
502                 MutUserTime += task_ids[i].mut_time;
503             }
504         }
505         time = MutUserTime + GC_tot_time + InitUserTime + ExitUserTime;
506         if (MutUserTime < 0) { MutUserTime = 0; }
507 #endif
508
509         if (RtsFlags.GcFlags.giveStats >= VERBOSE_GC_STATS && sf != NULL) {
510             fprintf(sf, "%9ld %9.9s %9.9s", (lnat)alloc*sizeof(W_), "", "");
511             fprintf(sf, " %5.2f %5.2f\n\n", 0.0, 0.0);
512         }
513
514         if (RtsFlags.GcFlags.giveStats >= SUMMARY_GC_STATS && sf != NULL) {
515             ullong_format_string(GC_tot_alloc*sizeof(W_), 
516                                  temp, rtsTrue/*commas*/);
517             fprintf(sf, "%11s bytes allocated in the heap\n", temp);
518
519             ullong_format_string(GC_tot_copied*sizeof(W_), 
520                                  temp, rtsTrue/*commas*/);
521             fprintf(sf, "%11s bytes copied during GC\n", temp);
522
523             if ( ResidencySamples > 0 ) {
524                 ullong_format_string(MaxResidency*sizeof(W_), 
525                                      temp, rtsTrue/*commas*/);
526                 fprintf(sf, "%11s bytes maximum residency (%ld sample(s))\n",
527                         temp, ResidencySamples);
528             }
529             fprintf(sf,"\n");
530
531             /* Print garbage collections in each gen */
532             for (g = 0; g < RtsFlags.GcFlags.generations; g++) {
533                 fprintf(sf, "%11d collections in generation %d (%6.2fs)\n", 
534                         generations[g].collections, g, 
535                         TICK_TO_DBL(GC_coll_times[g]));
536             }
537
538             fprintf(sf,"\n%11ld Mb total memory in use\n\n", 
539                     mblocks_allocated * MBLOCK_SIZE / (1024 * 1024));
540
541 #ifdef SMP
542             {
543                 nat i;
544                 for (i = 0; i < RtsFlags.ParFlags.nNodes; i++) {
545                     fprintf(sf, "  Task %2d:  MUT time: %6.2fs  (%6.2fs elapsed)\n"
546                             "            GC  time: %6.2fs  (%6.2fs elapsed)\n\n", 
547                             i, 
548                             TICK_TO_DBL(task_ids[i].mut_time),
549                             TICK_TO_DBL(task_ids[i].mut_etime),
550                             TICK_TO_DBL(task_ids[i].gc_time),
551                             TICK_TO_DBL(task_ids[i].gc_etime));
552                 }
553             }
554 #endif
555
556             fprintf(sf, "  INIT  time  %6.2fs  (%6.2fs elapsed)\n",
557                     TICK_TO_DBL(InitUserTime), TICK_TO_DBL(InitElapsedTime));
558             fprintf(sf, "  MUT   time  %6.2fs  (%6.2fs elapsed)\n",
559                     TICK_TO_DBL(MutUserTime), TICK_TO_DBL(MutElapsedTime));
560             fprintf(sf, "  GC    time  %6.2fs  (%6.2fs elapsed)\n",
561                     TICK_TO_DBL(GC_tot_time), TICK_TO_DBL(GCe_tot_time));
562             fprintf(sf, "  EXIT  time  %6.2fs  (%6.2fs elapsed)\n",
563                     TICK_TO_DBL(ExitUserTime), TICK_TO_DBL(ExitElapsedTime));
564             fprintf(sf, "  Total time  %6.2fs  (%6.2fs elapsed)\n\n",
565                     TICK_TO_DBL(time), TICK_TO_DBL(etime));
566             fprintf(sf, "  %%GC time     %5.1f%%  (%.1f%% elapsed)\n\n",
567                     TICK_TO_DBL(GC_tot_time)*100/time, 
568                     TICK_TO_DBL(GCe_tot_time)*100/etime);
569
570             if (time - GC_tot_time == 0)
571                 ullong_format_string(0, temp, rtsTrue/*commas*/);
572             else
573                 ullong_format_string(
574                     (ullong)((GC_tot_alloc*sizeof(W_))/
575                              TICK_TO_DBL(time - GC_tot_time)),
576                     temp, rtsTrue/*commas*/);
577             
578             fprintf(sf, "  Alloc rate    %s bytes per MUT second\n\n", temp);
579         
580             fprintf(sf, "  Productivity %5.1f%% of total user, %.1f%% of total elapsed\n\n",
581                     TICK_TO_DBL(time - GC_tot_time - InitUserTime) * 100 
582                     / TICK_TO_DBL(time), 
583                     TICK_TO_DBL(time - GC_tot_time - InitUserTime) * 100 
584                     / TICK_TO_DBL(etime));
585         }
586
587         if (RtsFlags.GcFlags.giveStats == ONELINE_GC_STATS && sf != NULL) {
588             fprintf(sf, "<<ghc: %lld bytes, %d GCs, %ld/%ld avg/max bytes residency (%ld samples), %ldM in use, %.2f INIT (%.2f elapsed), %.2f MUT (%.2f elapsed), %.2f GC (%.2f elapsed) :ghc>>\n",
589                     GC_tot_alloc*sizeof(W_), total_collections,
590                     AvgResidency*sizeof(W_)/ResidencySamples, 
591                     MaxResidency*sizeof(W_), 
592                     ResidencySamples, 
593                     mblocks_allocated * MBLOCK_SIZE / (1024 * 1024),
594                     TICK_TO_DBL(InitUserTime), TICK_TO_DBL(InitElapsedTime),
595                     TICK_TO_DBL(MutUserTime), TICK_TO_DBL(MutElapsedTime),
596                     TICK_TO_DBL(GC_tot_time), TICK_TO_DBL(GCe_tot_time));
597         }
598
599         fflush(sf);
600         fclose(sf);
601     }
602 }
603
604 /* -----------------------------------------------------------------------------
605    stat_describe_gens
606
607    Produce some detailed info on the state of the generational GC.
608    -------------------------------------------------------------------------- */
609 void
610 stat_describe_gens(void)
611 {
612   nat g, s, mut, mut_once, lge, live;
613   StgMutClosure *m;
614   bdescr *bd;
615   step *step;
616
617   fprintf(stderr, "     Gen    Steps      Max   Mutable  Mut-Once  Step   Blocks     Live    Large\n                    Blocks  Closures  Closures                         Objects\n");
618
619   for (g = 0; g < RtsFlags.GcFlags.generations; g++) {
620     for (m = generations[g].mut_list, mut = 0; m != END_MUT_LIST; 
621          m = m->mut_link) 
622       mut++;
623     for (m = generations[g].mut_once_list, mut_once = 0; m != END_MUT_LIST; 
624          m = m->mut_link) 
625       mut_once++;
626     fprintf(stderr, "%8d %8d %8d %9d %9d", g, generations[g].n_steps,
627             generations[g].max_blocks, mut, mut_once);
628
629     for (s = 0; s < generations[g].n_steps; s++) {
630       step = &generations[g].steps[s];
631       for (bd = step->large_objects, lge = 0; bd; bd = bd->link)
632         lge++;
633       live = 0;
634       if (RtsFlags.GcFlags.generations == 1) {
635         bd = step->to_space;
636       } else {
637         bd = step->blocks;
638       }
639       for (; bd; bd = bd->link) {
640         live += (bd->free - bd->start) * sizeof(W_);
641       }
642       if (s != 0) {
643         fprintf(stderr,"%46s","");
644       }
645       fprintf(stderr,"%6d %8d %8d %8d\n", s, step->n_blocks,
646               live, lge);
647     }
648   }
649   fprintf(stderr,"\n");
650 }
651
652 /* -----------------------------------------------------------------------------
653    Stats available via a programmatic interface, so eg. GHCi can time
654    each compilation and expression evaluation.
655    -------------------------------------------------------------------------- */
656
657 extern HsInt getAllocations( void ) 
658 { return (HsInt)(total_allocated * sizeof(W_)); }