1 *********************************************************************
3 Stuff for printing out GC statistics
5 usertime() -- The current user time in seconds
6 elapsedtime() -- The current elapsed time in seconds
13 *********************************************************************
17 #define NON_POSIX_SOURCE /*needed for solaris2 only?*/
21 #include "SMinternal.h"
25 #define _INCLUDE_HPUX_SOURCE
28 #ifdef solaris2_TARGET_OS
29 #define __EXTENSIONS__
32 #ifdef HAVE_SYS_TYPES_H
33 #include <sys/types.h>
40 #ifdef HAVE_SYS_TIMES_H
41 #include <sys/times.h>
44 #ifdef HAVE_SYS_TIME_H
48 #if defined(HAVE_SYS_RESOURCE_H) && ! irix_TARGET_OS
49 #include <sys/resource.h>
52 #ifdef HAVE_SYS_TIMEB_H
53 #include <sys/timeb.h>
57 #include <sys/syscall.h>
58 #define getrusage(a, b) syscall(SYS_GETRUSAGE, a, b)
59 #define HAVE_GETRUSAGE
63 getrusage() is not the preferred way of getting at process-specific
64 info under Solaris...at least it wasn't. It was supported via a BSD
65 compatibility library in 2.4, whereas 2.5 has it in libc.
67 The upshot of this change of heart is that we cannot rely on getrusage()
68 being available via libc, i.e., 2.5 binaries will not run under 2.4
69 without some extra work. Could use libucb under 2.5 as well, but
70 a simpler solution is simply to avoid the problem and stay away
71 from getrusage() for Solaris -- SOF
73 #if solaris2_TARGET_OS
74 #include <sys/fcntl.h>
75 #include <sys/signal.h>
76 #include <sys/procfs.h>
79 static StgDouble GC_start_time, GC_tot_time = 0; /* User GC Time */
80 static StgDouble GCe_start_time, GCe_tot_time = 0; /* Elapsed GC time */
82 #if defined(GCap) || defined(GCgn)
83 static StgDouble GC_min_time = 0;
84 static StgDouble GCe_min_time = 0;
86 static I_ GC_min_no = 0;
87 static I_ GC_min_since_maj = 0;
88 static I_ GC_live_maj = 0; /* Heap live at last major collection */
89 static I_ GC_alloc_since_maj = 0; /* Heap alloc since collection major */
92 static I_ GC_maj_no = 0;
93 static ullong GC_tot_alloc = 0; /* Total heap allocated -- 64 bits? */
95 static I_ GC_start_faults = 0, GC_end_faults = 0;
98 ullong_format_string(ullong x, char *s, rtsBool with_commas)
100 if (x < (ullong)1000)
101 sprintf(s, "%ld", (I_)x);
102 else if (x < (ullong)1000000)
103 sprintf(s, (with_commas) ? "%ld,%3.3ld" : "%ld%3.3ld",
104 (I_)((x)/(ullong)1000),
105 (I_)((x)%(ullong)1000));
106 else if (x < (ullong)1000000000)
107 sprintf(s, (with_commas) ? "%ld,%3.3ld,%3.3ld" : "%ld%3.3ld%3.3ld",
108 (I_)((x)/(ullong)1000000),
109 (I_)((x)/(ullong)1000%(ullong)1000),
110 (I_)((x)%(ullong)1000));
112 sprintf(s, (with_commas) ? "%ld,%3.3ld,%3.3ld,%3.3ld" : "%ld%3.3ld%3.3ld%3.3ld",
113 (I_)((x)/(ullong)1000000000),
114 (I_)((x)/(ullong)1000000%(ullong)1000),
115 (I_)((x)/(ullong)1000%(ullong)1000),
116 (I_)((x)%(ullong)1000));
120 /* "constants" for "usertime" and "elapsedtime" */
122 static StgDouble ElapsedTimeStart = 0.0; /* setup when beginning things */
123 static StgDouble TicksPerSecond = 0.0; /* ditto */
125 /* usertime() -- The current user time in seconds */
130 #if ! (defined(HAVE_GETRUSAGE) || defined(HAVE_TIMES))
131 /* We will #ifdef around the fprintf for machines
132 we *know* are unsupported. (WDP 94/05)
134 fprintf(stderr, "NOTE: `usertime' does nothing!\n");
137 #else /* not stumped */
139 # if defined(HAVE_TIMES)
143 return(((StgDouble)(t.tms_utime))/TicksPerSecond);
145 #else /* HAVE_GETRUSAGE */
148 getrusage(RUSAGE_SELF, &t);
149 return(t.ru_utime.tv_sec + 1e-6*t.ru_utime.tv_usec);
151 # endif /* HAVE_GETRUSAGE */
152 #endif /* not stumped */
156 /* elapsedtime() -- The current elapsed time in seconds */
161 #if ! (defined(HAVE_TIMES) || defined(HAVE_FTIME))
162 /* We will #ifdef around the fprintf for machines
163 we *know* are unsupported. (WDP 94/05)
165 fprintf(stderr, "NOTE: `elapsedtime' does nothing!\n");
168 #else /* not stumped */
170 /* "ftime" may be nicer, but "times" is more standard;
171 but, on a Sun, if you do not get the SysV one, you are *hosed*...
174 # if defined(HAVE_TIMES) && ! sunos4_TARGET_OS
177 return (((StgDouble) times(&t))/TicksPerSecond - ElapsedTimeStart);
179 # else /* HAVE_FTIME */
183 return (fabs(t.time + 1e-3*t.millitm - ElapsedTimeStart));
185 # endif /* HAVE_FTIME */
186 #endif /* not stumped */
190 start_time(STG_NO_ARGS)
194 /* Determine TicksPerSecond ... */
196 ticks = sysconf(_SC_CLK_TCK);
198 fprintf(stderr, "stat_init: bad call to 'sysconf'!\n");
201 TicksPerSecond = (StgDouble) ticks;
203 #else /* no "sysconf"; had better guess */
205 TicksPerSecond = (StgDouble) (HZ);
207 # else /* had better guess wildly */
208 /* We will #ifdef around the fprintf for machines
209 we *know* are unsupported. (WDP 94/05)
211 fprintf(stderr, "NOTE: Guessing `TicksPerSecond = 60'!\n");
212 TicksPerSecond = 60.0;
216 ElapsedTimeStart = elapsedtime();
219 static StgDouble InitUserTime = 0.0; /* user time taken for initialization */
220 static StgDouble InitElapsedTime = 0.0; /* elapsed time taken for initialization */
222 void end_init(STG_NO_ARGS)
224 InitUserTime = usertime();
225 InitElapsedTime = elapsedtime();
228 #if defined(solaris2_TARGET_OS)
230 pagefaults(STG_NO_ARGS)
233 char proc[30]; /* Will break when PIDs are repr. by more than 64bits */
236 /* Under Solaris, we get at the number of major page faults
237 via the process file descriptor and ioctl()ing with
238 PIOCUSAGE to get the prusage_t structure.
239 (as per proc(4) man page and Solaris porting FAQ).
241 sprintf(proc,"/proc/%d", getpid()); /* ToDo: this string is static
242 per process, optimise? */
244 while ((fd = open(proc, O_RDONLY)) == -1 ) {
245 if ( errno != EINTR ) {
247 fprintf(stderr,"pagefaults: open() failed\n");
251 while (ioctl(fd, PIOCUSAGE, &prusage) == -1 ) {
252 if (errno != EINTR) {
254 fprintf(stderr,"pagefaults: ioctl() failed\n");
258 while ((close(fd)) == -1 ) {
259 if (errno != EINTR) {
261 fprintf(stderr, "pagefaults: close() failed\n");
265 return prusage.pr_majf;
270 pagefaults(STG_NO_ARGS)
272 # if !defined(HAVE_GETRUSAGE) || irix_TARGET_OS
277 getrusage(RUSAGE_SELF, &t);
278 /* cygwin32 note: Last time I looked (b18), the ru_majflt field
279 was always filled in with a 0. -- SOF (ToDo: Win32ify?)
286 /* Called at the beginning of execution of the program */
287 /* Writes the command line and inits stats header */
290 stat_init(char *collector, char *comment1, char *comment2)
292 FILE *sf = RTSflags.GcFlags.statsFile;
295 char temp[BIG_STRING_LEN];
296 ullong_format_string( (ullong)RTSflags.GcFlags.heapSize*sizeof(W_), temp, rtsTrue/*commas*/);
297 fprintf(sf, "\nCollector: %s HeapSize: %s (bytes)\n\n", collector, temp);
298 if (RTSflags.GcFlags.giveStats) {
299 #if !defined(HAVE_GETRUSAGE) || irix_TARGET_OS
300 fprintf(sf, "NOTE: `pagefaults' does nothing!\n");
303 /*######## ####### ####### ##.# ##.## ##.## ####.## ####.## #### ####*/
304 " Alloc Collect Live Resid GC GC TOT TOT Page Flts %s\n",
307 " bytes bytes bytes ency user elap user elap GC MUT %s\n",
311 #if defined(GCap) || defined(GCgn)
314 /*######## ####### ##.# ####### ##.# ### ##.## ##.## ##.## ##.## ####.## ####.## #### ####*/
315 " Alloc Promote Promo Live Resid Minor Minor Minor Major Major TOT TOT Page Flts\n");
317 " bytes bytes ted bytes ency No user elap user elap user elap MUT Major\n");
319 #endif /* generational */
325 /* Called at the beginning of each GC */
326 static I_ rub_bell = 0;
329 stat_startGC(I_ alloc)
331 FILE *sf = RTSflags.GcFlags.statsFile;
333 #if defined(GCap) || defined(GCgn)
334 I_ bell = alloc == 0 ? RTSflags.GcFlags.ringBell : 0;
335 #else /* ! generational */
336 I_ bell = RTSflags.GcFlags.ringBell;
337 #endif /* ! generational */
341 fprintf(stderr, " GC ");
344 fprintf(stderr, "\007");
349 GC_start_time = usertime();
350 GCe_start_time = elapsedtime();
352 #if defined(GCap) || defined(GCgn)
353 if (RTSflags.GcFlags.giveStats || alloc == 0) {
354 GC_start_faults = pagefaults();
356 #else /* ! generational */
357 if (RTSflags.GcFlags.giveStats) {
358 GC_start_faults = pagefaults();
360 #endif /* ! generational */
365 /* Called at the end of each GC */
368 stat_endGC(I_ alloc, I_ collect, I_ live, char *comment)
370 FILE *sf = RTSflags.GcFlags.statsFile;
373 StgDouble time = usertime();
374 StgDouble etime = elapsedtime();
376 if (RTSflags.GcFlags.giveStats) {
377 I_ faults = pagefaults();
379 fprintf(sf, "%8ld %7ld %7ld %5.1f%%",
380 alloc*sizeof(W_), collect*sizeof(W_), live*sizeof(W_), collect == 0 ? 0.0 : (live / (StgDouble) collect * 100));
381 fprintf(sf, " %5.2f %5.2f %7.2f %7.2f %4ld %4ld %s\n",
382 (time-GC_start_time),
383 (etime-GCe_start_time),
386 faults - GC_start_faults,
387 GC_start_faults - GC_end_faults,
390 GC_end_faults = faults;
394 #if defined(GCap) || defined(GCgn)
395 else if(alloc == 0 && collect != 0) {
396 I_ faults = pagefaults();
398 fprintf(sf, "%8ld %7ld %5.1f%% %7ld %5.1f%%",
399 GC_alloc_since_maj*sizeof(W_), (collect - GC_live_maj)*sizeof(W_),
400 (collect - GC_live_maj) / (StgDouble) GC_alloc_since_maj * 100,
401 live*sizeof(W_), live / (StgDouble) RTSflags.GcFlags.heapSize * 100);
402 fprintf(sf, " %3ld %5.2f %5.2f %5.2f %5.2f %7.2f %7.2f %4ld %4ld\n",
403 GC_min_since_maj, GC_min_time, GCe_min_time,
404 (time-GC_start_time),
405 (etime-GCe_start_time),
408 faults - GC_start_faults,
409 GC_start_faults - GC_end_faults
412 GC_end_faults = faults;
415 #endif /* generational */
417 #if defined(GCap) || defined(GCgn)
418 if (alloc == 0 && collect != 0) {
421 GC_min_no += GC_min_since_maj;
422 GC_min_since_maj = 0;
423 GC_tot_alloc += (ullong) GC_alloc_since_maj;
424 GC_alloc_since_maj = 0;
425 GC_tot_time += time-GC_start_time + GC_min_time;
427 GCe_tot_time += etime-GCe_start_time + GCe_min_time;
431 GC_alloc_since_maj += alloc;
432 GC_min_time += time-GC_start_time;
433 GCe_min_time += etime-GCe_start_time;
435 #else /* ! generational */
437 GC_tot_alloc += (ullong) alloc;
438 GC_tot_time += time-GC_start_time;
439 GCe_tot_time += etime-GCe_start_time;
440 #endif /* ! generational */
445 fprintf(stderr, "\b\b\b \b\b\b");
450 /* Called at the end of execution -- to print a summary of statistics */
455 FILE *sf = RTSflags.GcFlags.statsFile;
458 char temp[BIG_STRING_LEN];
459 StgDouble time = usertime();
460 StgDouble etime = elapsedtime();
462 /* avoid divide by zero if time is measured as 0.00 seconds -- SDM */
463 if (time == 0.0) time = 0.0001;
464 if (etime == 0.0) etime = 0.0001;
467 if (RTSflags.GcFlags.giveStats) {
468 fprintf(sf, "%8ld\n\n", alloc*sizeof(W_));
471 #if defined(GCap) || defined (GCgn)
473 fprintf(sf, "%8ld %7.7s %6.6s %7.7s %6.6s",
474 (GC_alloc_since_maj + alloc)*sizeof(W_), "", "", "", "");
475 fprintf(sf, " %3ld %5.2f %5.2f\n\n",
476 GC_min_since_maj, GC_min_time, GCe_min_time);
478 GC_min_no += GC_min_since_maj;
479 GC_tot_time += GC_min_time;
480 GCe_tot_time += GCe_min_time;
481 GC_tot_alloc += (ullong) (GC_alloc_since_maj + alloc);
482 ullong_format_string(GC_tot_alloc*sizeof(W_), temp, rtsTrue/*commas*/);
483 fprintf(sf, "%11s bytes allocated in the heap\n", temp);
484 if ( ResidencySamples > 0 ) {
485 ullong_format_string(MaxResidency*sizeof(W_), temp, rtsTrue/*commas*/);
486 fprintf(sf, "%11s bytes maximum residency (%.1f%%, %ld sample(s))\n",
488 MaxResidency / (StgDouble) RTSflags.GcFlags.heapSize * 100,
491 fprintf(sf, "%11ld garbage collections performed (%ld major, %ld minor)\n\n",
492 GC_maj_no + GC_min_no, GC_maj_no, GC_min_no);
494 #else /* ! generational */
496 GC_tot_alloc += (ullong) alloc;
497 ullong_format_string(GC_tot_alloc*sizeof(W_), temp, rtsTrue/*commas*/);
498 fprintf(sf, "%11s bytes allocated in the heap\n", temp);
499 if ( ResidencySamples > 0 ) {
500 ullong_format_string(MaxResidency*sizeof(W_), temp, rtsTrue/*commas*/);
501 fprintf(sf, "%11s bytes maximum residency (%.1f%%, %ld sample(s))\n",
503 MaxResidency / (StgDouble) RTSflags.GcFlags.heapSize * 100,
506 fprintf(sf, "%11ld garbage collections performed\n\n", GC_maj_no);
508 #endif /* ! generational */
510 fprintf(sf, " INIT time %6.2fs (%6.2fs elapsed)\n",
511 InitUserTime, InitElapsedTime);
512 fprintf(sf, " MUT time %6.2fs (%6.2fs elapsed)\n",
513 time - GC_tot_time - InitUserTime,
514 etime - GCe_tot_time - InitElapsedTime);
515 fprintf(sf, " GC time %6.2fs (%6.2fs elapsed)\n",
516 GC_tot_time, GCe_tot_time);
517 fprintf(sf, " Total time %6.2fs (%6.2fs elapsed)\n\n",
520 fprintf(sf, " %%GC time %5.1f%% (%.1f%% elapsed)\n\n",
521 GC_tot_time*100./time, GCe_tot_time*100./etime);
523 if (time - GC_tot_time == 0.0)
524 ullong_format_string((ullong)0, temp, rtsTrue/*commas*/);
526 ullong_format_string((ullong)(GC_tot_alloc*sizeof(W_)/(time - GC_tot_time)),
527 temp, rtsTrue/*commas*/);
529 fprintf(sf, " Alloc rate %s bytes per MUT second\n\n", temp);
531 fprintf(sf, " Productivity %5.1f%% of total user, %.1f%% of total elapsed\n\n",
532 (time - GC_tot_time - InitUserTime) * 100. / time,
533 (time - GC_tot_time - InitUserTime) * 100. / etime);