1 Only have cost centres etc if @USE_COST_CENTRES@ defined
5 Some of the code in here is pretty hairy for the compiler to deal
6 with after we've swiped all of the useful registers. I don't believe
7 any STG registers are live here, but I'm not completely certain.
9 Any specific routines that require the preservation of caller-saves
10 STG registers should be pulled out into another file and compiled
11 with the the appropriate register map. (Presumably one of the GC
12 register mappings?) --JSM
16 #include "../storage/SMinternal.h" /* for xmalloc */
18 #if defined (USE_COST_CENTRES)
21 %************************************************************************
23 \subsection[heap-profiling]{Heap Profiling}
25 %************************************************************************
27 The heap profiling reports the amount of heap space occupied by live
28 closures pressent in the heap during a garbage collection. This
29 profile may be broken down in a number of ways:
31 \item {\bf Cost Centre:} The cost centres responsible for building the
32 various closures in the heap.
33 \item {\bf Module:} Aggregation of all the cost centres declared in a module.
34 \item {\bf Group:} Aggregation of all the cost centres declared in a group.
35 \item {\bf Closure Description:} The heap occupied by closures with a particular description (normally the data constructor).
36 \item {\bf Type Description:} The heap occupied by closures with a particular type (normally the type constructor).
37 \item {\bf Production time stamp:} The heap occupied by closures of produced during a particular time interval.
40 Relevant closures may be selected by the Cost Centre (label, module
41 and group), by Closure Category (description, type, and kind) and/or
42 by age. A cost centre will be selected if its label, module or group
43 is selected (default is all). A closure category will be selected if
44 its description, type or kind is selected (default is all). A closure
45 will be selected if both its cost centre, closure category and age are
48 When recording the size of the heap objects the additional profiling
49 etc words are disregarded. The profiling itself is considered an
50 idealised process which should not affect the statistics gathered.
57 = HEAP_NO_PROFILING; /* type of heap profiling */
59 static char heap_profiling_char[] /* indexed by heap_profiling_req */
60 = {'?', CCchar, MODchar, GRPchar, DESCRchar, TYPEchar, TIMEchar};
62 static I_ cc_select = 0; /* are we selecting on Cost Centre */
63 static I_ clcat_select = 0; /* are we selecting on Closure Category*/
65 static I_ cc_select_no = 0;
66 static char *cc_select_strs[MAX_SELECT];
67 static char *ccmod_select_strs[MAX_SELECT];
69 static I_ mod_select_no = 0;
70 static char *mod_select_strs[MAX_SELECT];
71 static I_ grp_select_no = 0;
72 static char *grp_select_strs[MAX_SELECT];
74 static I_ descr_select_no = 0;
75 static char *descr_select_strs[MAX_SELECT];
76 static I_ type_select_no = 0;
77 static char *type_select_strs[MAX_SELECT];
78 static I_ kind_select_no = 0;
79 static I_ kind_selected[] = {0, 0, 0, 0, 0, 0};
80 static char *kind_select_strs[] = {"","CON","FN","PAP","THK","BH",0};
82 static I_ age_select = 0; /* select ages greater than this */
83 /* 0 indicates survived to the end of alloced interval */
85 I_ *resid = 0; /* residencies indexed by hashed feature */
87 /* For production times we have a resid table of time_intervals */
88 /* And a seperate resid counter stuff produced earlier & later */
92 I_ resid_max = 0; /* Max residency -- used for aux file */
94 I_ earlier_ticks = 0; /* No of earlier ticks grouped together */
95 hash_t time_intervals = 18; /* No of time_intervals, also earlier & later */
97 static hash_t earlier_intervals; /* No of earlier intervals grouped together + 1*/
99 hash_t dummy_index_time()
101 return time_intervals;
104 hash_t (* init_index_fns[])() = {
114 static char heap_filename[STATS_FILENAME_MAXLEN]; /* heap log file name = <program>.hp */
115 static FILE *heap_file = NULL;
117 extern I_ SM_force_gc; /* Set here if we force 2-space GC */
120 heap_profile_init(prof, cc_select_str, mod_select_str, grp_select_str,
121 descr_select_str, type_select_str, kind_select_str,
125 char *mod_select_str;
126 char *grp_select_str;
127 char *descr_select_str;
128 char *type_select_str;
129 char *kind_select_str;
133 hash_t count, max, first;
135 heap_profiling_req = prof;
137 if (heap_profiling_req == HEAP_NO_PROFILING)
140 /* for now, if using a generational collector and trying
141 to heap-profile, just force the GC to be used in two-space mode.
144 #if defined(GCap) || defined(GCgn)
145 SM_force_gc = USE_2s;
148 #if ! defined(HEAP_PROF_WITH_AGE)
149 if (heap_profiling_req == HEAP_BY_TIME || select_age) {
150 fprintf(stderr, "heap_profile_init: Heap Profiling not built with AGE field in closures\n");
153 #endif /* ! HEAP_PROF_WITH_AGE */
155 /* process select strings -- will break them into bits */
159 while (cc_select_str && cc_select_no < MAX_SELECT) {
160 if ((comma = strchr(cc_select_str, ',')) != 0) {
163 if ((colon = strchr(cc_select_str, ':')) != 0) {
165 ccmod_select_strs[cc_select_no] = cc_select_str;
166 cc_select_strs[cc_select_no++] = colon + 1;
168 ccmod_select_strs[cc_select_no] = (char *)0;
169 cc_select_strs[cc_select_no++] = cc_select_str;
172 cc_select_str = comma + 1;
174 cc_select_str = (char *)0;
177 if (cc_select_str && cc_select_no >= MAX_SELECT) {
178 fprintf(stderr, "heap_profile_init: Too many Cost Centres selected\n %ld used %s remaining\n",
179 cc_select_no, cc_select_str);
182 cc_select |= cc_select_no > 0;
184 if (mod_select_str) {
186 while ((comma = strchr(mod_select_str, ',')) && mod_select_no < MAX_SELECT) {
187 mod_select_strs[mod_select_no++] = mod_select_str;
189 mod_select_str = comma + 1;
191 if (mod_select_no < MAX_SELECT) {
192 mod_select_strs[mod_select_no++] = mod_select_str;
194 fprintf(stderr, "heap_profile_init: Too many Modules selected\n %ld used %s remaining\n",
195 mod_select_no, mod_select_str);
198 cc_select |= mod_select_no > 0;
200 if (grp_select_str) {
202 while ((comma = strchr(grp_select_str, ',')) && grp_select_no < MAX_SELECT) {
203 grp_select_strs[grp_select_no++] = grp_select_str;
205 grp_select_str = comma + 1;
207 if (grp_select_no < MAX_SELECT) {
208 grp_select_strs[grp_select_no++] = grp_select_str;
210 fprintf(stderr, "heap_profile_init: Too many Groups selected\n %ld used %s remaining\n",
211 grp_select_no, grp_select_str);
214 cc_select |= grp_select_no > 0;
217 if (descr_select_str) {
219 while ((comma = strchr(descr_select_str, ',')) && descr_select_no < MAX_SELECT) {
220 descr_select_strs[descr_select_no++] = descr_select_str;
222 descr_select_str = comma + 1;
224 if (descr_select_no < MAX_SELECT) {
225 descr_select_strs[descr_select_no++] = descr_select_str;
227 fprintf(stderr, "heap_profile_init: Too many Closure Descriptions selected\n %ld used %s remaining\n",
228 descr_select_no, descr_select_str);
231 clcat_select |= descr_select_no > 0;
233 if (type_select_str) {
235 while ((comma = strchr(type_select_str, ',')) && type_select_no < MAX_SELECT) {
236 type_select_strs[type_select_no++] = type_select_str;
238 type_select_str = comma + 1;
240 if (type_select_no < MAX_SELECT) {
241 type_select_strs[type_select_no++] = type_select_str;
243 fprintf(stderr, "heap_profile_init: Too many Closure Types selected\n %ld used %s remaining\n",
244 type_select_no, type_select_str);
247 clcat_select |= type_select_no > 0;
249 if (kind_select_str) {
251 while ((comma = strchr(kind_select_str, ',')) != 0) {
253 for (count = 1; kind_select_strs[count]; count++) {
254 if (strcmp(kind_select_strs[count],kind_select_str) == 0) {
255 kind_selected[count] = 1;
260 if (! kind_select_strs[count]) {
261 fprintf(stderr, "heap_profile_init: Invalid Kind: %s\n", kind_select_str);
264 kind_select_str = comma + 1;
266 for (count = 1; kind_select_strs[count]; count++) {
267 if (strcmp(kind_select_strs[count],kind_select_str) == 0) {
268 kind_selected[count] = 1;
273 if (! kind_select_strs[count]) {
274 fprintf(stderr, "heap_profile_init: Invalid Kind: %s\n", kind_select_str);
277 clcat_select |= kind_select_no > 0;
279 age_select = select_age;
282 /* open heap profiling log file */
284 sprintf(heap_filename, HP_FILENAME_FMT, argv[0]);
285 if ( (heap_file = fopen(heap_filename,"w")) == NULL ) {
286 fprintf(stderr, "Can't open heap log file %s\n", heap_filename);
290 /* write start of log file */
292 fprintf(heap_file, "JOB \"%s", argv[0]);
293 fprintf(heap_file, " +RTS -h%c", heap_profiling_char[heap_profiling_req]);
294 if (heap_profiling_req == HEAP_BY_TIME) {
295 fprintf(heap_file, "%ld", time_intervals);
297 fprintf(heap_file, ",%3.1f",
298 earlier_ticks / (StgFloat)TICK_FREQUENCY);
302 fprintf(heap_file, " -c{%s:%s",
303 ccmod_select_strs[0],
305 for (count = 1; count < cc_select_no; count++) {
306 fprintf(heap_file, ",%s:%s",
307 ccmod_select_strs[count],
308 cc_select_strs[count]);
310 fprintf(heap_file, "}");
313 fprintf(heap_file, " -m{%s", mod_select_strs[0]);
314 for (count = 1; count < mod_select_no; count++)
315 fprintf(heap_file, ",%s", mod_select_strs[count]);
316 fprintf(heap_file, "}");
319 fprintf(heap_file, " -g{%s", grp_select_strs[0]);
320 for (count = 1; count < grp_select_no; count++)
321 fprintf(heap_file, ",%s", grp_select_strs[count]);
322 fprintf(heap_file, "}");
324 if (descr_select_no) {
325 fprintf(heap_file, " -d{%s", descr_select_strs[0]);
326 for (count = 1; count < descr_select_no; count++)
327 fprintf(heap_file, ",%s", descr_select_strs[count]);
328 fprintf(heap_file, "}");
330 if (type_select_no) {
331 fprintf(heap_file, " -t{%s", type_select_strs[0]);
332 for (count = 1; count < type_select_no; count++)
333 fprintf(heap_file, ",%s", type_select_strs[count]);
334 fprintf(heap_file, "}");
336 if (kind_select_no) {
337 fprintf(heap_file, " -k{");
338 for (count = 1, first = 1; kind_select_strs[count]; count++)
339 if (kind_selected[count]) {
340 fprintf(heap_file, "%s%s", first?"":",", kind_select_strs[count]);
343 fprintf(heap_file, "}");
346 fprintf(heap_file, " -a%ld", age_select);
348 fprintf(heap_file, " -i%4.2f -RTS", interval_ticks/(StgFloat)TICK_FREQUENCY);
349 for(count = 1; argv[count]; count++)
350 fprintf(heap_file, " %s", argv[count]);
351 fprintf(heap_file, "\"\n");
353 fprintf(heap_file, "DATE \"%s\"\n", time_str());
355 fprintf(heap_file, "SAMPLE_UNIT \"seconds\"\n");
356 fprintf(heap_file, "VALUE_UNIT \"bytes\"\n");
358 fprintf(heap_file, "BEGIN_SAMPLE 0.00\n");
359 fprintf(heap_file, "END_SAMPLE 0.00\n");
362 /* initialise required heap profiling data structures & hashing */
364 earlier_intervals = (earlier_ticks / interval_ticks) + 1;
365 max = (* init_index_fns[heap_profiling_req])();
366 resid = (I_ *) xmalloc(max * sizeof(I_));
367 for (count = 0; count < max; count++) resid[count] = 0;
373 Cost centre selection is set up before a heap profile by running
374 through the list of registered cost centres and memoising the
375 selection in the cost centre record. It is only necessary to memoise
376 the cost centre selection if a selection profiling function is
379 Category selection is determined when each closure is encountered. It
380 is memoised within the category record. We always have to check that
381 the memoisation has been done as we do not have a list of categories
382 we can process before hand.
384 Age selection is done for every closure -- not memoised.
388 set_selected_ccs() /* set selection before we profile heap */
394 for (cc = Registered_CC; cc != REGISTERED_END; cc = cc->registered) {
395 for (x = 0; ! cc->selected && x < cc_select_no; x++)
396 cc->selected = (strcmp(cc->label, cc_select_strs[x]) == 0) &&
397 (strcmp(cc->module, ccmod_select_strs[x]) == 0);
398 for (x = 0; ! cc->selected && x < mod_select_no; x++)
399 cc->selected = (strcmp(cc->module, mod_select_strs[x]) == 0);
400 for (x = 0; ! cc->selected && x < grp_select_no; x++)
401 cc->selected = (strcmp(cc->group, grp_select_strs[x]) == 0);
404 for (cc = Registered_CC; cc != REGISTERED_END; cc = cc->registered)
405 cc->selected = 1; /* true if ! cc_select */
411 selected_clcat(clcat)
416 if (clcat->selected == -1) { /* if not memoised check selection */
419 for (x = 0; ! clcat->selected && x < descr_select_no; x++)
420 clcat->selected = (strcmp(clcat->descr, descr_select_strs[x]) == 0);
421 for (x = 0; ! clcat->selected && x < type_select_no; x++)
422 clcat->selected = (strcmp(clcat->type, type_select_strs[x]) == 0);
423 if (kind_select_no) clcat->selected |= kind_selected[clcat->kind];
428 return clcat->selected; /* return memoised selection */
433 Profiling functions called for each closure. The appropriate function
434 is stored in @heap_profile_fn@ by @heap_profile_setup@.
435 @heap_profile_fn@ is called for each live closure by the macros
436 embedded in the garbage collector. They increment the appropriate
437 resident space counter by the size of the closure (less any profiling
441 #define NON_PROF_HS (FIXED_HS - PROF_FIXED_HDR - AGE_FIXED_HDR)
444 profile_closure_none(closure,size)
452 profile_closure_cc(closure,size)
456 CostCentre cc = (CostCentre) CC_HDR(closure);
457 resid[index_cc(cc)] += size + NON_PROF_HS;
462 profile_closure_cc_select(closure,size)
466 CostCentre cc; ClCategory clcat;
468 cc = (CostCentre) CC_HDR(closure);
469 if (! cc->selected) /* selection determined before profile */
470 return; /* all selected if ! cc_select */
472 clcat = (ClCategory) INFO_CAT(INFO_PTR(closure));
473 if (clcat_select && ! selected_clcat(clcat)) /* selection memoised during profile */
476 #if defined(HEAP_PROF_WITH_AGE)
478 I_ age, ts = AGE_HDR(closure);
480 if (ts == 0) { /* Set to 0 when alloced -- now set to current interval */
481 AGE_HDR(closure) = (W_)current_interval;
485 age = current_interval - ts - age_select;
489 #endif /* HEAP_PROF_WITH_AGE */
491 resid[index_cc(cc)] += size + NON_PROF_HS;
496 profile_closure_mod(closure,size)
500 CostCentre cc = (CostCentre) CC_HDR(closure);
501 resid[index_mod(cc)] += size + NON_PROF_HS;
506 profile_closure_mod_select(closure,size)
510 CostCentre cc; ClCategory clcat;
512 cc = (CostCentre) CC_HDR(closure);
513 if (! cc->selected) /* selection determined before profile */
516 clcat = (ClCategory) INFO_CAT(INFO_PTR(closure));
517 if (clcat_select && ! selected_clcat(clcat)) /* selection memoised during profile */
520 #if defined(HEAP_PROF_WITH_AGE)
522 I_ age, ts = AGE_HDR(closure);
524 if (ts == 0) { /* Set to 0 when alloced -- now set to current interval */
525 AGE_HDR(closure) = (W_)current_interval;
529 age = current_interval - ts - age_select;
533 #endif /* HEAP_PROF_WITH_AGE */
535 resid[index_mod(cc)] += size + NON_PROF_HS;
540 profile_closure_grp(closure,size)
544 CostCentre cc = (CostCentre) CC_HDR(closure);
545 resid[index_grp(cc)] += size + NON_PROF_HS;
549 profile_closure_grp_select(closure,size)
553 CostCentre cc; ClCategory clcat;
555 cc = (CostCentre) CC_HDR(closure);
556 if (! cc->selected) /* selection determined before profile */
559 clcat = (ClCategory) INFO_CAT(INFO_PTR(closure));
560 if (clcat_select && ! selected_clcat(clcat)) /* selection memoised during profile */
563 #if defined(HEAP_PROF_WITH_AGE)
565 I_ age, ts = AGE_HDR(closure);
567 if (ts == 0) { /* Set to 0 when alloced -- now set to current interval */
568 AGE_HDR(closure) = (W_)current_interval;
572 age = current_interval - ts - age_select;
576 #endif /* HEAP_PROF_WITH_AGE */
578 resid[index_grp(cc)] += size + NON_PROF_HS;
583 profile_closure_descr(closure,size)
587 ClCategory clcat = (ClCategory) INFO_CAT(INFO_PTR(closure));
588 resid[index_descr(clcat)] += size + NON_PROF_HS;
593 profile_closure_descr_select(closure,size)
597 CostCentre cc; ClCategory clcat;
599 cc = (CostCentre) CC_HDR(closure);
600 if (! cc->selected) /* selection determined before profile */
601 return; /* all selected if ! cc_select */
603 clcat = (ClCategory) INFO_CAT(INFO_PTR(closure));
604 if (clcat_select && ! selected_clcat(clcat)) /* selection memoised during profile */
607 #if defined(HEAP_PROF_WITH_AGE)
609 I_ age, ts = AGE_HDR(closure);
611 if (ts == 0) { /* Set to 0 when alloced -- now set to current interval */
612 AGE_HDR(closure) = (W_)current_interval;
616 age = current_interval - ts - age_select;
620 #endif /* HEAP_PROF_WITH_AGE */
622 resid[index_descr(clcat)] += size + NON_PROF_HS;
627 profile_closure_type(closure,size)
631 ClCategory clcat = (ClCategory) INFO_CAT(INFO_PTR(closure));
632 resid[index_type(clcat)] += size + NON_PROF_HS;
637 profile_closure_type_select(closure,size)
641 CostCentre cc; ClCategory clcat;
643 cc = (CostCentre) CC_HDR(closure);
644 if (! cc->selected) /* selection determined before profile */
645 return; /* all selected if ! cc_select */
647 clcat = (ClCategory) INFO_CAT(INFO_PTR(closure));
648 if (clcat_select && ! selected_clcat(clcat)) /* selection memoised during profile */
651 #if defined(HEAP_PROF_WITH_AGE)
653 I_ age, ts = AGE_HDR(closure);
655 if (ts == 0) { /* Set to 0 when alloced -- now set to current interval */
656 AGE_HDR(closure) = (W_)current_interval;
660 age = current_interval - ts - age_select;
664 #endif /* HEAP_PROF_WITH_AGE */
666 resid[index_type(clcat)] += size + NON_PROF_HS;
671 profile_closure_time(closure,size)
675 #if defined(HEAP_PROF_WITH_AGE)
676 I_ ts = AGE_HDR(closure);
678 if (ts == 0) { /* Set to 0 when alloced -- now set to current interval */
679 AGE_HDR(closure) = (W_)current_interval;
680 ts = current_interval;
683 ts -= earlier_intervals;
686 resid_earlier += size + NON_PROF_HS;
688 else if (ts < time_intervals) {
689 resid[ts] += size + NON_PROF_HS;
692 resid_later += size + NON_PROF_HS;
694 #endif /* HEAP_PROF_WITH_AGE */
700 profile_closure_time_select(closure,size)
704 #if defined(HEAP_PROF_WITH_AGE)
705 CostCentre cc; ClCategory clcat; I_ age, ts;
707 cc = (CostCentre) CC_HDR(closure);
708 if (! cc->selected) /* selection determined before profile */
709 return; /* all selected if ! cc_select */
711 clcat = (ClCategory) INFO_CAT(INFO_PTR(closure));
712 if (clcat_select && ! selected_clcat(clcat)) /* selection memoised during profile */
715 ts = AGE_HDR(closure);
716 if (ts == 0) { /* Set to 0 when alloced -- now set to current interval */
717 AGE_HDR(closure) = (W_)current_interval;
718 ts = current_interval;
722 age = current_interval - ts - age_select;
727 ts -= earlier_intervals;
730 resid_earlier += size + NON_PROF_HS;
732 else if (ts < time_intervals) {
733 resid[ts] += size + NON_PROF_HS;
736 resid_later += size + NON_PROF_HS;
738 #endif /* HEAP_PROF_WITH_AGE */
744 @heap_profile_setup@ is called before garbage collection to initialise
745 for the profile. It assigns the appropriate closure profiling function
746 to @heap_profile_fn@ and memoises any cost centre selection. If no
747 profile is required @profile_closure_none@ is assigned.
749 On completion of garbage collection @heap_profile_done@ is called. It
750 produces a heap profile report and resets the residency counts to 0.
754 void (* heap_profile_fn) PROTO((P_,I_)) = profile_closure_none;
756 void (* profiling_fns_select[]) PROTO((P_,I_)) = {
757 profile_closure_none,
758 profile_closure_cc_select,
759 profile_closure_mod_select,
760 profile_closure_grp_select,
761 profile_closure_descr_select,
762 profile_closure_type_select,
763 profile_closure_time_select
766 void (* profiling_fns[]) PROTO((P_,I_)) = {
767 profile_closure_none,
771 profile_closure_descr,
772 profile_closure_type,
777 heap_profile_setup(STG_NO_ARGS) /* called at start of heap profile */
779 if (heap_profiling_req == HEAP_NO_PROFILING)
782 if (cc_select || clcat_select || age_select) {
783 set_selected_ccs(); /* memoise cc selection */
784 heap_profile_fn = profiling_fns_select[heap_profiling_req];
786 heap_profile_fn = profiling_fns[heap_profiling_req];
791 heap_profile_done(STG_NO_ARGS) /* called at end of heap profile */
793 CostCentre cc; ClCategory clcat; hash_t ind, max;
796 if (heap_profiling_req == HEAP_NO_PROFILING)
799 heap_profile_fn = profile_closure_none;
801 seconds = (previous_ticks + current_ticks) / (StgFloat)TICK_FREQUENCY;
802 fprintf(heap_file, "BEGIN_SAMPLE %0.2f\n", seconds);
804 max = (* init_index_fns[heap_profiling_req])();
806 switch (heap_profiling_req) {
808 for (ind = 0; ind < max; ind++) {
809 if ((cc = index_cc_table[ind]) != 0) {
810 fprintf(heap_file, " %0.11s:%0.16s %ld\n", cc->module, cc->label, resid[ind] * sizeof(W_));
817 for (ind = 0; ind < max; ind++) {
818 if ((cc = index_mod_table[ind]) != 0) {
819 fprintf(heap_file, " %0.11s %ld\n", cc->module, resid[ind] * sizeof(W_));
826 for (ind = 0; ind < max; ind++) {
827 if ((cc = index_grp_table[ind]) != 0) {
828 fprintf(heap_file, " %0.11s %ld\n", cc->group, resid[ind] * sizeof(W_));
835 for (ind = 0; ind < max; ind++) {
836 if ((clcat = index_descr_table[ind]) != 0) {
837 fprintf(heap_file, " %0.28s %ld\n", clcat->descr, resid[ind] * sizeof(W_));
844 for (ind = 0; ind < max; ind++) {
845 if ((clcat = index_type_table[ind]) != 0) {
846 fprintf(heap_file, " %0.28s %ld\n", clcat->type, resid[ind] * sizeof(W_));
852 #if defined(HEAP_PROF_WITH_AGE)
856 resid_tot += resid_earlier;
857 fprintf(heap_file, " before_%4.2fs %ld\n",
858 (earlier_intervals-1)*interval_ticks/(StgFloat)TICK_FREQUENCY,
859 resid_earlier * sizeof(StgWord));
862 for (ind = 0; ind < max; ind++) {
864 resid_tot += resid[ind];
865 fprintf(heap_file, " before_%4.2fs %ld\n",
866 (ind+earlier_intervals)*interval_ticks/(StgFloat)TICK_FREQUENCY,
867 resid[ind] * sizeof(StgWord));
872 resid_tot += resid_later;
873 fprintf(heap_file, " later %ld\n", resid_later * sizeof(StgWord));
877 if (resid_max < resid_tot) resid_max = resid_tot;
880 #endif /* HEAP_PROF_WITH_AGE */
883 fprintf(heap_file, "END_SAMPLE %0.2f\n", seconds);
888 heap_profile_finish(STG_NO_ARGS) /* called at end of execution */
892 if (heap_profiling_req == HEAP_NO_PROFILING)
895 seconds = (previous_ticks + current_ticks) / (StgFloat)TICK_FREQUENCY;
896 fprintf(heap_file, "BEGIN_SAMPLE %0.2f\n", seconds);
897 fprintf(heap_file, "END_SAMPLE %0.2f\n", seconds);
905 #endif /* USE_COST_CENTRES */