2005-02-09 Jakub Jelinek <jakub@redhat.com>
[glibc/history.git] / malloc / memusage.c
blobb552ec37b08ef1fbe5308a96546b4f3751e141b7
1 /* Profile heap and stack memory usage of running program.
2 Copyright (C) 1998-2002, 2004 Free Software Foundation, Inc.
3 This file is part of the GNU C Library.
4 Contributed by Ulrich Drepper <drepper@cygnus.com>, 1998.
6 The GNU C Library is free software; you can redistribute it and/or
7 modify it under the terms of the GNU Lesser General Public
8 License as published by the Free Software Foundation; either
9 version 2.1 of the License, or (at your option) any later version.
11 The GNU C Library is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 Lesser General Public License for more details.
16 You should have received a copy of the GNU Lesser General Public
17 License along with the GNU C Library; if not, write to the Free
18 Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
19 02111-1307 USA. */
21 #include <dlfcn.h>
22 #include <errno.h>
23 #include <fcntl.h>
24 #include <inttypes.h>
25 #include <signal.h>
26 #include <stdbool.h>
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <unistd.h>
31 #include <sys/mman.h>
32 #include <sys/time.h>
34 #include <memusage.h>
36 /* Pointer to the real functions. These are determined used `dlsym'
37 when really needed. */
38 static void *(*mallocp) (size_t);
39 static void *(*reallocp) (void *, size_t);
40 static void *(*callocp) (size_t, size_t);
41 static void (*freep) (void *);
43 static void *(*mmapp) (void *, size_t, int, int, int, off_t);
44 static void *(*mmap64p) (void *, size_t, int, int, int, off64_t);
45 static int (*munmapp) (void *, size_t);
46 static void *(*mremapp) (void *, size_t, size_t, int);
48 enum
50 idx_malloc = 0,
51 idx_realloc,
52 idx_calloc,
53 idx_free,
54 idx_mmap_r,
55 idx_mmap_w,
56 idx_mmap_a,
57 idx_mremap,
58 idx_munmap,
59 idx_last
63 struct header
65 size_t length;
66 size_t magic;
69 #define MAGIC 0xfeedbeaf
72 static unsigned long int calls[idx_last];
73 static unsigned long int failed[idx_last];
74 static unsigned long long int total[idx_last];
75 static unsigned long long int grand_total;
76 static unsigned long int histogram[65536 / 16];
77 static unsigned long int large;
78 static unsigned long int calls_total;
79 static unsigned long int inplace;
80 static unsigned long int decreasing;
81 static unsigned long int inplace_mremap;
82 static unsigned long int decreasing_mremap;
83 static long int current_use[2];
84 static long int peak_use[3];
85 static uintptr_t start_sp;
87 /* A few macros to make the source more readable. */
88 #define current_heap current_use[0]
89 #define current_stack current_use[1]
90 #define peak_heap peak_use[0]
91 #define peak_stack peak_use[1]
92 #define peak_total peak_use[2]
94 #define DEFAULT_BUFFER_SIZE 1024
95 static size_t buffer_size;
97 static int fd = -1;
99 static bool not_me;
100 static int initialized;
101 static bool trace_mmap;
102 extern const char *__progname;
104 struct entry
106 size_t heap;
107 size_t stack;
108 uint32_t time_low;
109 uint32_t time_high;
112 static struct entry buffer[DEFAULT_BUFFER_SIZE];
113 static size_t buffer_cnt;
114 static struct entry first;
117 /* Update the global data after a successful function call. */
118 static void
119 update_data (struct header *result, size_t len, size_t old_len)
121 long int total_use;
123 if (result != NULL)
125 /* Record the information we need and mark the block using a
126 magic number. */
127 result->length = len;
128 result->magic = MAGIC;
131 /* Compute current heap usage and compare it with the maximum value. */
132 current_heap += len - old_len;
133 if (current_heap > peak_heap)
134 peak_heap = current_heap;
136 /* Compute current stack usage and compare it with the maximum value. */
137 #ifdef STACK_GROWS_UPWARD
138 current_stack = GETSP () - start_sp;
139 #else
140 current_stack = start_sp - GETSP ();
141 #endif
142 if (current_stack > peak_stack)
143 peak_stack = current_stack;
145 /* Add up heap and stack usage and compare it with the maximum value. */
146 total_use = current_heap + current_stack;
147 if (total_use > peak_total)
148 peak_total = total_use;
150 /* Store the value only if we are writing to a file. */
151 if (fd != -1)
153 buffer[buffer_cnt].heap = current_heap;
154 buffer[buffer_cnt].stack = current_stack;
155 GETTIME (buffer[buffer_cnt].time_low, buffer[buffer_cnt].time_high);
156 ++buffer_cnt;
158 /* Write out buffer if it is full. */
159 if (buffer_cnt == buffer_size)
161 write (fd, buffer, buffer_cnt * sizeof (struct entry));
162 buffer_cnt = 0;
168 /* Interrupt handler. */
169 static void
170 int_handler (int signo)
172 /* Nothing gets allocated. Just record the stack pointer position. */
173 update_data (NULL, 0, 0);
177 /* Find out whether this is the program we are supposed to profile.
178 For this the name in the variable `__progname' must match the one
179 given in the environment variable MEMUSAGE_PROG_NAME. If the variable
180 is not present every program assumes it should be profiling.
182 If this is the program open a file descriptor to the output file.
183 We will write to it whenever the buffer overflows. The name of the
184 output file is determined by the environment variable MEMUSAGE_OUTPUT.
186 If the environment variable MEMUSAGE_BUFFER_SIZE is set its numerical
187 value determines the size of the internal buffer. The number gives
188 the number of elements in the buffer. By setting the number to one
189 one effectively selects unbuffered operation.
191 If MEMUSAGE_NO_TIMER is not present an alarm handler is installed
192 which at the highest possible frequency records the stack pointer. */
193 static void
194 me (void)
196 const char *env = getenv ("MEMUSAGE_PROG_NAME");
197 size_t prog_len = strlen (__progname);
199 initialized = -1;
200 mallocp = (void *(*) (size_t)) dlsym (RTLD_NEXT, "malloc");
201 reallocp = (void *(*) (void *, size_t)) dlsym (RTLD_NEXT, "realloc");
202 callocp = (void *(*) (size_t, size_t)) dlsym (RTLD_NEXT, "calloc");
203 freep = (void (*) (void *)) dlsym (RTLD_NEXT, "free");
205 mmapp = (void *(*) (void *, size_t, int, int, int, off_t)) dlsym (RTLD_NEXT,
206 "mmap");
207 mmap64p =
208 (void *(*) (void *, size_t, int, int, int, off64_t)) dlsym (RTLD_NEXT,
209 "mmap64");
210 mremapp = (void *(*) (void *, size_t, size_t, int)) dlsym (RTLD_NEXT,
211 "mremap");
212 munmapp = (int (*) (void *, size_t)) dlsym (RTLD_NEXT, "munmap");
213 initialized = 1;
215 if (env != NULL)
217 /* Check for program name. */
218 size_t len = strlen (env);
219 if (len > prog_len || strcmp (env, &__progname[prog_len - len]) != 0
220 || (prog_len != len && __progname[prog_len - len - 1] != '/'))
221 not_me = true;
224 /* Only open the file if it's really us. */
225 if (!not_me && fd == -1)
227 const char *outname;
229 if (!start_sp)
230 start_sp = GETSP ();
232 outname = getenv ("MEMUSAGE_OUTPUT");
233 if (outname != NULL && outname[0] != '\0'
234 && (access (outname, R_OK | W_OK) == 0 || errno == ENOENT))
236 fd = creat64 (outname, 0666);
238 if (fd == -1)
239 /* Don't do anything in future calls if we cannot write to
240 the output file. */
241 not_me = true;
242 else
244 /* Write the first entry. */
245 first.heap = 0;
246 first.stack = 0;
247 GETTIME (first.time_low, first.time_high);
248 /* Write it two times since we need the starting and end time. */
249 write (fd, &first, sizeof (first));
251 /* Determine the buffer size. We use the default if the
252 environment variable is not present. */
253 buffer_size = DEFAULT_BUFFER_SIZE;
254 if (getenv ("MEMUSAGE_BUFFER_SIZE") != NULL)
256 buffer_size = atoi (getenv ("MEMUSAGE_BUFFER_SIZE"));
257 if (buffer_size == 0 || buffer_size > DEFAULT_BUFFER_SIZE)
258 buffer_size = DEFAULT_BUFFER_SIZE;
261 /* Possibly enable timer-based stack pointer retrieval. */
262 if (getenv ("MEMUSAGE_NO_TIMER") == NULL)
264 struct sigaction act;
266 act.sa_handler = (sighandler_t) &int_handler;
267 act.sa_flags = SA_RESTART;
268 sigfillset (&act.sa_mask);
270 if (sigaction (SIGPROF, &act, NULL) >= 0)
272 struct itimerval timer;
274 timer.it_value.tv_sec = 0;
275 timer.it_value.tv_usec = 1;
276 timer.it_interval = timer.it_value;
277 setitimer (ITIMER_PROF, &timer, NULL);
283 if (!not_me && getenv ("MEMUSAGE_TRACE_MMAP") != NULL)
284 trace_mmap = true;
289 /* Record the initial stack position. */
290 static void
291 __attribute__ ((constructor))
292 init (void)
294 start_sp = GETSP ();
295 if (! initialized)
296 me ();
300 /* `malloc' replacement. We keep track of the memory usage if this is the
301 correct program. */
302 void *
303 malloc (size_t len)
305 struct header *result = NULL;
307 /* Determine real implementation if not already happened. */
308 if (__builtin_expect (initialized <= 0, 0))
310 if (initialized == -1)
311 return NULL;
312 me ();
315 /* If this is not the correct program just use the normal function. */
316 if (not_me)
317 return (*mallocp) (len);
319 /* Keep track of number of calls. */
320 ++calls[idx_malloc];
321 /* Keep track of total memory consumption for `malloc'. */
322 total[idx_malloc] += len;
323 /* Keep track of total memory requirement. */
324 grand_total += len;
325 /* Remember the size of the request. */
326 if (len < 65536)
327 ++histogram[len / 16];
328 else
329 ++large;
330 /* Total number of calls of any of the functions. */
331 ++calls_total;
333 /* Do the real work. */
334 result = (struct header *) (*mallocp) (len + sizeof (struct header));
335 if (result == NULL)
337 ++failed[idx_malloc];
338 return NULL;
341 /* Update the allocation data and write out the records if necessary. */
342 update_data (result, len, 0);
344 /* Return the pointer to the user buffer. */
345 return (void *) (result + 1);
349 /* `realloc' replacement. We keep track of the memory usage if this is the
350 correct program. */
351 void *
352 realloc (void *old, size_t len)
354 struct header *result = NULL;
355 struct header *real;
356 size_t old_len;
358 /* Determine real implementation if not already happened. */
359 if (__builtin_expect (initialized <= 0, 0))
361 if (initialized == -1)
362 return NULL;
363 me ();
366 /* If this is not the correct program just use the normal function. */
367 if (not_me)
368 return (*reallocp) (old, len);
370 if (old == NULL)
372 /* This is really a `malloc' call. */
373 real = NULL;
374 old_len = 0;
376 else
378 real = ((struct header *) old) - 1;
379 if (real->magic != MAGIC)
380 /* This is no memory allocated here. */
381 return (*reallocp) (old, len);
382 old_len = real->length;
385 /* Keep track of number of calls. */
386 ++calls[idx_realloc];
387 if (len > old_len)
389 /* Keep track of total memory consumption for `realloc'. */
390 total[idx_realloc] += len - old_len;
391 /* Keep track of total memory requirement. */
392 grand_total += len - old_len;
394 /* Remember the size of the request. */
395 if (len < 65536)
396 ++histogram[len / 16];
397 else
398 ++large;
399 /* Total number of calls of any of the functions. */
400 ++calls_total;
402 /* Do the real work. */
403 result = (struct header *) (*reallocp) (real, len + sizeof (struct header));
404 if (result == NULL)
406 ++failed[idx_realloc];
407 return NULL;
410 /* Record whether the reduction/increase happened in place. */
411 if (real == result)
412 ++inplace;
413 /* Was the buffer increased? */
414 if (old_len > len)
415 ++decreasing;
417 /* Update the allocation data and write out the records if necessary. */
418 update_data (result, len, old_len);
420 /* Return the pointer to the user buffer. */
421 return (void *) (result + 1);
425 /* `calloc' replacement. We keep track of the memory usage if this is the
426 correct program. */
427 void *
428 calloc (size_t n, size_t len)
430 struct header *result;
431 size_t size = n * len;
433 /* Determine real implementation if not already happened. */
434 if (__builtin_expect (initialized <= 0, 0))
436 if (initialized == -1)
437 return NULL;
438 me ();
441 /* If this is not the correct program just use the normal function. */
442 if (not_me)
443 return (*callocp) (n, len);
445 /* Keep track of number of calls. */
446 ++calls[idx_calloc];
447 /* Keep track of total memory consumption for `calloc'. */
448 total[idx_calloc] += size;
449 /* Keep track of total memory requirement. */
450 grand_total += size;
451 /* Remember the size of the request. */
452 if (size < 65536)
453 ++histogram[size / 16];
454 else
455 ++large;
456 /* Total number of calls of any of the functions. */
457 ++calls_total;
459 /* Do the real work. */
460 result = (struct header *) (*mallocp) (size + sizeof (struct header));
461 if (result == NULL)
463 ++failed[idx_calloc];
464 return NULL;
467 /* Update the allocation data and write out the records if necessary. */
468 update_data (result, size, 0);
470 /* Do what `calloc' would have done and return the buffer to the caller. */
471 return memset (result + 1, '\0', size);
475 /* `free' replacement. We keep track of the memory usage if this is the
476 correct program. */
477 void
478 free (void *ptr)
480 struct header *real;
482 /* Determine real implementation if not already happened. */
483 if (__builtin_expect (initialized <= 0, 0))
485 if (initialized == -1)
486 return;
487 me ();
490 /* If this is not the correct program just use the normal function. */
491 if (not_me)
493 (*freep) (ptr);
494 return;
497 /* `free (NULL)' has no effect. */
498 if (ptr == NULL)
500 ++calls[idx_free];
501 return;
504 /* Determine the pointer to the header. */
505 real = ((struct header *) ptr) - 1;
506 if (real->magic != MAGIC)
508 /* This block wasn't allocated here. */
509 (*freep) (ptr);
510 return;
513 /* Keep track of number of calls. */
514 ++calls[idx_free];
515 /* Keep track of total memory freed using `free'. */
516 total[idx_free] += real->length;
518 /* Update the allocation data and write out the records if necessary. */
519 update_data (NULL, 0, real->length);
521 /* Do the real work. */
522 (*freep) (real);
526 /* `mmap' replacement. We do not have to keep track of the sizesince
527 `munmap' will get it as a parameter. */
528 void *
529 mmap (void *start, size_t len, int prot, int flags, int fd, off_t offset)
531 void *result = NULL;
533 /* Determine real implementation if not already happened. */
534 if (__builtin_expect (initialized <= 0, 0))
536 if (initialized == -1)
537 return NULL;
538 me ();
541 /* Always get a block. We don't need extra memory. */
542 result = (*mmapp) (start, len, prot, flags, fd, offset);
544 if (!not_me && trace_mmap)
546 int idx = (flags & MAP_ANON
547 ? idx_mmap_a : prot & PROT_WRITE ? idx_mmap_w : idx_mmap_r);
549 /* Keep track of number of calls. */
550 ++calls[idx];
551 /* Keep track of total memory consumption for `malloc'. */
552 total[idx] += len;
553 /* Keep track of total memory requirement. */
554 grand_total += len;
555 /* Remember the size of the request. */
556 if (len < 65536)
557 ++histogram[len / 16];
558 else
559 ++large;
560 /* Total number of calls of any of the functions. */
561 ++calls_total;
563 /* Check for failures. */
564 if (result == NULL)
565 ++failed[idx];
566 else if (idx == idx_mmap_w)
567 /* Update the allocation data and write out the records if
568 necessary. Note the first parameter is NULL which means
569 the size is not tracked. */
570 update_data (NULL, len, 0);
573 /* Return the pointer to the user buffer. */
574 return result;
578 /* `mmap' replacement. We do not have to keep track of the sizesince
579 `munmap' will get it as a parameter. */
580 void *
581 mmap64 (void *start, size_t len, int prot, int flags, int fd, off64_t offset)
583 void *result = NULL;
585 /* Determine real implementation if not already happened. */
586 if (__builtin_expect (initialized <= 0, 0))
588 if (initialized == -1)
589 return NULL;
590 me ();
593 /* Always get a block. We don't need extra memory. */
594 result = (*mmap64p) (start, len, prot, flags, fd, offset);
596 if (!not_me && trace_mmap)
598 int idx = (flags & MAP_ANON
599 ? idx_mmap_a : prot & PROT_WRITE ? idx_mmap_w : idx_mmap_r);
601 /* Keep track of number of calls. */
602 ++calls[idx];
603 /* Keep track of total memory consumption for `malloc'. */
604 total[idx] += len;
605 /* Keep track of total memory requirement. */
606 grand_total += len;
607 /* Remember the size of the request. */
608 if (len < 65536)
609 ++histogram[len / 16];
610 else
611 ++large;
612 /* Total number of calls of any of the functions. */
613 ++calls_total;
615 /* Check for failures. */
616 if (result == NULL)
617 ++failed[idx];
618 else if (idx == idx_mmap_w)
619 /* Update the allocation data and write out the records if
620 necessary. Note the first parameter is NULL which means
621 the size is not tracked. */
622 update_data (NULL, len, 0);
625 /* Return the pointer to the user buffer. */
626 return result;
630 /* `mmap' replacement. We do not have to keep track of the sizesince
631 `munmap' will get it as a parameter. */
632 void *
633 mremap (void *start, size_t old_len, size_t len, int flags)
635 void *result = NULL;
637 /* Determine real implementation if not already happened. */
638 if (__builtin_expect (initialized <= 0, 0))
640 if (initialized == -1)
641 return NULL;
642 me ();
645 /* Always get a block. We don't need extra memory. */
646 result = (*mremapp) (start, old_len, len, flags);
648 if (!not_me && trace_mmap)
650 /* Keep track of number of calls. */
651 ++calls[idx_mremap];
652 if (len > old_len)
654 /* Keep track of total memory consumption for `malloc'. */
655 total[idx_mremap] += len - old_len;
656 /* Keep track of total memory requirement. */
657 grand_total += len - old_len;
659 /* Remember the size of the request. */
660 if (len < 65536)
661 ++histogram[len / 16];
662 else
663 ++large;
664 /* Total number of calls of any of the functions. */
665 ++calls_total;
667 /* Check for failures. */
668 if (result == NULL)
669 ++failed[idx_mremap];
670 else
672 /* Record whether the reduction/increase happened in place. */
673 if (start == result)
674 ++inplace_mremap;
675 /* Was the buffer increased? */
676 if (old_len > len)
677 ++decreasing_mremap;
679 /* Update the allocation data and write out the records if
680 necessary. Note the first parameter is NULL which means
681 the size is not tracked. */
682 update_data (NULL, len, old_len);
686 /* Return the pointer to the user buffer. */
687 return result;
691 /* `munmap' replacement. */
693 munmap (void *start, size_t len)
695 int result;
697 /* Determine real implementation if not already happened. */
698 if (__builtin_expect (initialized <= 0, 0))
700 if (initialized == -1)
701 return -1;
702 me ();
705 /* Do the real work. */
706 result = (*munmapp) (start, len);
708 if (!not_me && trace_mmap)
710 /* Keep track of number of calls. */
711 ++calls[idx_munmap];
713 if (__builtin_expect (result == 0, 1))
715 /* Keep track of total memory freed using `free'. */
716 total[idx_munmap] += len;
718 /* Update the allocation data and write out the records if
719 necessary. */
720 update_data (NULL, 0, len);
722 else
723 ++failed[idx_munmap];
726 return result;
730 /* Write some statistics to standard error. */
731 static void
732 __attribute__ ((destructor))
733 dest (void)
735 int percent, cnt;
736 unsigned long int maxcalls;
738 /* If we haven't done anything here just return. */
739 if (not_me)
740 return;
741 /* If we should call any of the memory functions don't do any profiling. */
742 not_me = true;
744 /* Finish the output file. */
745 if (fd != -1)
747 /* Write the partially filled buffer. */
748 write (fd, buffer, buffer_cnt * sizeof (struct entry));
749 /* Go back to the beginning of the file. We allocated two records
750 here when we opened the file. */
751 lseek (fd, 0, SEEK_SET);
752 /* Write out a record containing the total size. */
753 first.stack = peak_total;
754 write (fd, &first, sizeof (struct entry));
755 /* Write out another record containing the maximum for heap and
756 stack. */
757 first.heap = peak_heap;
758 first.stack = peak_stack;
759 GETTIME (first.time_low, first.time_high);
760 write (fd, &first, sizeof (struct entry));
762 /* Close the file. */
763 close (fd);
764 fd = -1;
767 /* Write a colorful statistic. */
768 fprintf (stderr, "\n\
769 \e[01;32mMemory usage summary:\e[0;0m heap total: %llu, heap peak: %lu, stack peak: %lu\n\
770 \e[04;34m total calls total memory failed calls\e[0m\n\
771 \e[00;34m malloc|\e[0m %10lu %12llu %s%12lu\e[00;00m\n\
772 \e[00;34mrealloc|\e[0m %10lu %12llu %s%12lu\e[00;00m (in place: %ld, dec: %ld)\n\
773 \e[00;34m calloc|\e[0m %10lu %12llu %s%12lu\e[00;00m\n\
774 \e[00;34m free|\e[0m %10lu %12llu\n",
775 grand_total, (unsigned long int) peak_heap,
776 (unsigned long int) peak_stack,
777 calls[idx_malloc], total[idx_malloc],
778 failed[idx_malloc] ? "\e[01;41m" : "", failed[idx_malloc],
779 calls[idx_realloc], total[idx_realloc],
780 failed[idx_realloc] ? "\e[01;41m" : "", failed[idx_realloc],
781 inplace, decreasing,
782 calls[idx_calloc], total[idx_calloc],
783 failed[idx_calloc] ? "\e[01;41m" : "", failed[idx_calloc],
784 calls[idx_free], total[idx_free]);
786 if (trace_mmap)
787 fprintf (stderr, "\
788 \e[00;34mmmap(r)|\e[0m %10lu %12llu %s%12lu\e[00;00m\n\
789 \e[00;34mmmap(w)|\e[0m %10lu %12llu %s%12lu\e[00;00m\n\
790 \e[00;34mmmap(a)|\e[0m %10lu %12llu %s%12lu\e[00;00m\n\
791 \e[00;34m mremap|\e[0m %10lu %12llu %s%12lu\e[00;00m (in place: %ld, dec: %ld)\n\
792 \e[00;34m munmap|\e[0m %10lu %12llu %s%12lu\e[00;00m\n",
793 calls[idx_mmap_r], total[idx_mmap_r],
794 failed[idx_mmap_r] ? "\e[01;41m" : "", failed[idx_mmap_r],
795 calls[idx_mmap_w], total[idx_mmap_w],
796 failed[idx_mmap_w] ? "\e[01;41m" : "", failed[idx_mmap_w],
797 calls[idx_mmap_a], total[idx_mmap_a],
798 failed[idx_mmap_a] ? "\e[01;41m" : "", failed[idx_mmap_a],
799 calls[idx_mremap], total[idx_mremap],
800 failed[idx_mremap] ? "\e[01;41m" : "", failed[idx_mremap],
801 inplace_mremap, decreasing_mremap,
802 calls[idx_munmap], total[idx_munmap],
803 failed[idx_munmap] ? "\e[01;41m" : "", failed[idx_munmap]);
805 /* Write out a histoogram of the sizes of the allocations. */
806 fprintf (stderr, "\e[01;32mHistogram for block sizes:\e[0;0m\n");
808 /* Determine the maximum of all calls for each size range. */
809 maxcalls = large;
810 for (cnt = 0; cnt < 65536; cnt += 16)
811 if (histogram[cnt / 16] > maxcalls)
812 maxcalls = histogram[cnt / 16];
814 for (cnt = 0; cnt < 65536; cnt += 16)
815 /* Only write out the nonzero entries. */
816 if (histogram[cnt / 16] != 0)
818 percent = (histogram[cnt / 16] * 100) / calls_total;
819 fprintf (stderr, "%5d-%-5d%12lu ", cnt, cnt + 15,
820 histogram[cnt / 16]);
821 if (percent == 0)
822 fputs (" <1% \e[41;37m", stderr);
823 else
824 fprintf (stderr, "%3d%% \e[41;37m", percent);
826 /* Draw a bar with a length corresponding to the current
827 percentage. */
828 percent = (histogram[cnt / 16] * 50) / maxcalls;
829 while (percent-- > 0)
830 fputc ('=', stderr);
831 fputs ("\e[0;0m\n", stderr);
834 if (large != 0)
836 percent = (large * 100) / calls_total;
837 fprintf (stderr, " large %12lu ", large);
838 if (percent == 0)
839 fputs (" <1% \e[41;37m", stderr);
840 else
841 fprintf (stderr, "%3d%% \e[41;37m", percent);
842 percent = (large * 50) / maxcalls;
843 while (percent-- > 0)
844 fputc ('=', stderr);
845 fputs ("\e[0;0m\n", stderr);