1 /* Profile heap and stack memory usage of running program.
2 Copyright (C) 1998-2002, 2004, 2005 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
38 /* Pointer to the real functions. These are determined used `dlsym'
39 when really needed. */
40 static void *(*mallocp
) (size_t);
41 static void *(*reallocp
) (void *, size_t);
42 static void *(*callocp
) (size_t, size_t);
43 static void (*freep
) (void *);
45 static void *(*mmapp
) (void *, size_t, int, int, int, off_t
);
46 static void *(*mmap64p
) (void *, size_t, int, int, int, off64_t
);
47 static int (*munmapp
) (void *, size_t);
48 static void *(*mremapp
) (void *, size_t, size_t, int, void *);
71 #define MAGIC 0xfeedbeaf
74 static memusage_cntr_t calls
[idx_last
];
75 static memusage_cntr_t failed
[idx_last
];
76 static memusage_size_t total
[idx_last
];
77 static memusage_size_t grand_total
;
78 static memusage_cntr_t histogram
[65536 / 16];
79 static memusage_cntr_t large
;
80 static memusage_cntr_t calls_total
;
81 static memusage_cntr_t inplace
;
82 static memusage_cntr_t decreasing
;
83 static memusage_cntr_t inplace_mremap
;
84 static memusage_cntr_t decreasing_mremap
;
85 static memusage_size_t current_heap
;
86 static memusage_size_t peak_use
[3];
87 static __thread
uintptr_t start_sp
;
89 /* A few macros to make the source more readable. */
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
;
100 static int initialized
;
101 static bool trace_mmap
;
102 extern const char *__progname
;
112 static struct entry buffer
[2 * DEFAULT_BUFFER_SIZE
];
113 static uatomic32_t buffer_cnt
;
114 static struct entry first
;
117 /* Update the global data after a successful function call. */
119 update_data (struct header
*result
, size_t len
, size_t old_len
)
123 /* Record the information we need and mark the block using a
125 result
->length
= len
;
126 result
->magic
= MAGIC
;
129 /* Compute current heap usage and compare it with the maximum value. */
131 = atomic_exchange_and_add (¤t_heap
, len
- old_len
) + len
- old_len
;
132 atomic_max (&peak_heap
, heap
);
134 /* Compute current stack usage and compare it with the maximum
135 value. The base stack pointer might not be set if this is not
136 the main thread and it is the first call to any of these
138 if (__builtin_expect (!start_sp
, 0))
141 uintptr_t sp
= GETSP ();
142 #ifdef STACK_GROWS_UPWARD
143 /* This can happen in threads where we didn't catch the thread's
144 stack early enough. */
145 if (__builtin_expect (sp
< start_sp
, 0))
147 size_t current_stack
= sp
- start_sp
;
149 /* This can happen in threads where we didn't catch the thread's
150 stack early enough. */
151 if (__builtin_expect (sp
> start_sp
, 0))
153 size_t current_stack
= start_sp
- sp
;
155 atomic_max (&peak_stack
, current_stack
);
157 /* Add up heap and stack usage and compare it with the maximum value. */
158 atomic_max (&peak_total
, heap
+ current_stack
);
160 /* Store the value only if we are writing to a file. */
163 uatomic32_t idx
= atomic_exchange_and_add (&buffer_cnt
, 1);
164 if (idx
>= 2 * buffer_size
)
166 /* We try to reset the counter to the correct range. If
167 this fails because of another thread increasing the
168 counter it does not matter since that thread will take
169 care of the correction. */
170 unsigned int reset
= idx
- 2 * buffer_size
;
171 atomic_compare_and_exchange_val_acq (&buffer_size
, reset
, idx
);
175 buffer
[idx
].heap
= current_heap
;
176 buffer
[idx
].stack
= current_stack
;
177 GETTIME (buffer
[idx
].time_low
, buffer
[idx
].time_high
);
179 /* Write out buffer if it is full. */
180 if (idx
+ 1 == buffer_size
)
181 write (fd
, buffer
, buffer_size
* sizeof (struct entry
));
182 else if (idx
+ 1 == 2 * buffer_size
)
183 write (fd
, &buffer
[buffer_size
], buffer_size
* sizeof (struct entry
));
188 /* Interrupt handler. */
190 int_handler (int signo
)
192 /* Nothing gets allocated. Just record the stack pointer position. */
193 update_data (NULL
, 0, 0);
197 /* Find out whether this is the program we are supposed to profile.
198 For this the name in the variable `__progname' must match the one
199 given in the environment variable MEMUSAGE_PROG_NAME. If the variable
200 is not present every program assumes it should be profiling.
202 If this is the program open a file descriptor to the output file.
203 We will write to it whenever the buffer overflows. The name of the
204 output file is determined by the environment variable MEMUSAGE_OUTPUT.
206 If the environment variable MEMUSAGE_BUFFER_SIZE is set its numerical
207 value determines the size of the internal buffer. The number gives
208 the number of elements in the buffer. By setting the number to one
209 one effectively selects unbuffered operation.
211 If MEMUSAGE_NO_TIMER is not present an alarm handler is installed
212 which at the highest possible frequency records the stack pointer. */
216 const char *env
= getenv ("MEMUSAGE_PROG_NAME");
217 size_t prog_len
= strlen (__progname
);
220 mallocp
= (void *(*) (size_t)) dlsym (RTLD_NEXT
, "malloc");
221 reallocp
= (void *(*) (void *, size_t)) dlsym (RTLD_NEXT
, "realloc");
222 callocp
= (void *(*) (size_t, size_t)) dlsym (RTLD_NEXT
, "calloc");
223 freep
= (void (*) (void *)) dlsym (RTLD_NEXT
, "free");
225 mmapp
= (void *(*) (void *, size_t, int, int, int, off_t
)) dlsym (RTLD_NEXT
,
228 (void *(*) (void *, size_t, int, int, int, off64_t
)) dlsym (RTLD_NEXT
,
230 mremapp
= (void *(*) (void *, size_t, size_t, int, void *)) dlsym (RTLD_NEXT
,
232 munmapp
= (int (*) (void *, size_t)) dlsym (RTLD_NEXT
, "munmap");
237 /* Check for program name. */
238 size_t len
= strlen (env
);
239 if (len
> prog_len
|| strcmp (env
, &__progname
[prog_len
- len
]) != 0
240 || (prog_len
!= len
&& __progname
[prog_len
- len
- 1] != '/'))
244 /* Only open the file if it's really us. */
245 if (!not_me
&& fd
== -1)
252 outname
= getenv ("MEMUSAGE_OUTPUT");
253 if (outname
!= NULL
&& outname
[0] != '\0'
254 && (access (outname
, R_OK
| W_OK
) == 0 || errno
== ENOENT
))
256 fd
= creat64 (outname
, 0666);
259 /* Don't do anything in future calls if we cannot write to
264 /* Write the first entry. */
267 GETTIME (first
.time_low
, first
.time_high
);
268 /* Write it two times since we need the starting and end time. */
269 write (fd
, &first
, sizeof (first
));
271 /* Determine the buffer size. We use the default if the
272 environment variable is not present. */
273 buffer_size
= DEFAULT_BUFFER_SIZE
;
274 if (getenv ("MEMUSAGE_BUFFER_SIZE") != NULL
)
276 buffer_size
= atoi (getenv ("MEMUSAGE_BUFFER_SIZE"));
277 if (buffer_size
== 0 || buffer_size
> DEFAULT_BUFFER_SIZE
)
278 buffer_size
= DEFAULT_BUFFER_SIZE
;
281 /* Possibly enable timer-based stack pointer retrieval. */
282 if (getenv ("MEMUSAGE_NO_TIMER") == NULL
)
284 struct sigaction act
;
286 act
.sa_handler
= (sighandler_t
) &int_handler
;
287 act
.sa_flags
= SA_RESTART
;
288 sigfillset (&act
.sa_mask
);
290 if (sigaction (SIGPROF
, &act
, NULL
) >= 0)
292 struct itimerval timer
;
294 timer
.it_value
.tv_sec
= 0;
295 timer
.it_value
.tv_usec
= 1;
296 timer
.it_interval
= timer
.it_value
;
297 setitimer (ITIMER_PROF
, &timer
, NULL
);
303 if (!not_me
&& getenv ("MEMUSAGE_TRACE_MMAP") != NULL
)
309 /* Record the initial stack position. */
311 __attribute__ ((constructor
))
320 /* `malloc' replacement. We keep track of the memory usage if this is the
325 struct header
*result
= NULL
;
327 /* Determine real implementation if not already happened. */
328 if (__builtin_expect (initialized
<= 0, 0))
330 if (initialized
== -1)
335 /* If this is not the correct program just use the normal function. */
337 return (*mallocp
) (len
);
339 /* Keep track of number of calls. */
340 atomic_increment (&calls
[idx_malloc
]);
341 /* Keep track of total memory consumption for `malloc'. */
342 atomic_add (&total
[idx_malloc
], len
);
343 /* Keep track of total memory requirement. */
344 atomic_add (&grand_total
, len
);
345 /* Remember the size of the request. */
347 atomic_increment (&histogram
[len
/ 16]);
349 atomic_increment (&large
);
350 /* Total number of calls of any of the functions. */
351 atomic_increment (&calls_total
);
353 /* Do the real work. */
354 result
= (struct header
*) (*mallocp
) (len
+ sizeof (struct header
));
357 atomic_increment (&failed
[idx_malloc
]);
361 /* Update the allocation data and write out the records if necessary. */
362 update_data (result
, len
, 0);
364 /* Return the pointer to the user buffer. */
365 return (void *) (result
+ 1);
369 /* `realloc' replacement. We keep track of the memory usage if this is the
372 realloc (void *old
, size_t len
)
374 struct header
*result
= NULL
;
378 /* Determine real implementation if not already happened. */
379 if (__builtin_expect (initialized
<= 0, 0))
381 if (initialized
== -1)
386 /* If this is not the correct program just use the normal function. */
388 return (*reallocp
) (old
, len
);
392 /* This is really a `malloc' call. */
398 real
= ((struct header
*) old
) - 1;
399 if (real
->magic
!= MAGIC
)
400 /* This is no memory allocated here. */
401 return (*reallocp
) (old
, len
);
402 old_len
= real
->length
;
405 /* Keep track of number of calls. */
406 atomic_increment (&calls
[idx_realloc
]);
409 /* Keep track of total memory consumption for `realloc'. */
410 atomic_add (&total
[idx_realloc
], len
- old_len
);
411 /* Keep track of total memory requirement. */
412 atomic_add (&grand_total
, len
- old_len
);
414 /* Remember the size of the request. */
416 atomic_increment (&histogram
[len
/ 16]);
418 atomic_increment (&large
);
419 /* Total number of calls of any of the functions. */
420 atomic_increment (&calls_total
);
422 /* Do the real work. */
423 result
= (struct header
*) (*reallocp
) (real
, len
+ sizeof (struct header
));
426 atomic_increment (&failed
[idx_realloc
]);
430 /* Record whether the reduction/increase happened in place. */
432 atomic_increment (&inplace
);
433 /* Was the buffer increased? */
435 atomic_increment (&decreasing
);
437 /* Update the allocation data and write out the records if necessary. */
438 update_data (result
, len
, old_len
);
440 /* Return the pointer to the user buffer. */
441 return (void *) (result
+ 1);
445 /* `calloc' replacement. We keep track of the memory usage if this is the
448 calloc (size_t n
, size_t len
)
450 struct header
*result
;
451 size_t size
= n
* len
;
453 /* Determine real implementation if not already happened. */
454 if (__builtin_expect (initialized
<= 0, 0))
456 if (initialized
== -1)
461 /* If this is not the correct program just use the normal function. */
463 return (*callocp
) (n
, len
);
465 /* Keep track of number of calls. */
466 atomic_increment (&calls
[idx_calloc
]);
467 /* Keep track of total memory consumption for `calloc'. */
468 atomic_add (&total
[idx_calloc
], size
);
469 /* Keep track of total memory requirement. */
470 atomic_add (&grand_total
, size
);
471 /* Remember the size of the request. */
473 atomic_increment (&histogram
[size
/ 16]);
475 atomic_increment (&large
);
476 /* Total number of calls of any of the functions. */
479 /* Do the real work. */
480 result
= (struct header
*) (*mallocp
) (size
+ sizeof (struct header
));
483 atomic_increment (&failed
[idx_calloc
]);
487 /* Update the allocation data and write out the records if necessary. */
488 update_data (result
, size
, 0);
490 /* Do what `calloc' would have done and return the buffer to the caller. */
491 return memset (result
+ 1, '\0', size
);
495 /* `free' replacement. We keep track of the memory usage if this is the
502 /* Determine real implementation if not already happened. */
503 if (__builtin_expect (initialized
<= 0, 0))
505 if (initialized
== -1)
510 /* If this is not the correct program just use the normal function. */
517 /* `free (NULL)' has no effect. */
520 atomic_increment (&calls
[idx_free
]);
524 /* Determine the pointer to the header. */
525 real
= ((struct header
*) ptr
) - 1;
526 if (real
->magic
!= MAGIC
)
528 /* This block wasn't allocated here. */
533 /* Keep track of number of calls. */
534 atomic_increment (&calls
[idx_free
]);
535 /* Keep track of total memory freed using `free'. */
536 atomic_add (&total
[idx_free
], real
->length
);
538 /* Update the allocation data and write out the records if necessary. */
539 update_data (NULL
, 0, real
->length
);
541 /* Do the real work. */
546 /* `mmap' replacement. We do not have to keep track of the sizesince
547 `munmap' will get it as a parameter. */
549 mmap (void *start
, size_t len
, int prot
, int flags
, int fd
, off_t offset
)
553 /* Determine real implementation if not already happened. */
554 if (__builtin_expect (initialized
<= 0, 0))
556 if (initialized
== -1)
561 /* Always get a block. We don't need extra memory. */
562 result
= (*mmapp
) (start
, len
, prot
, flags
, fd
, offset
);
564 if (!not_me
&& trace_mmap
)
566 int idx
= (flags
& MAP_ANON
567 ? idx_mmap_a
: prot
& PROT_WRITE
? idx_mmap_w
: idx_mmap_r
);
569 /* Keep track of number of calls. */
570 atomic_increment (&calls
[idx
]);
571 /* Keep track of total memory consumption for `malloc'. */
572 atomic_add (&total
[idx
], len
);
573 /* Keep track of total memory requirement. */
574 atomic_add (&grand_total
, len
);
575 /* Remember the size of the request. */
577 atomic_increment (&histogram
[len
/ 16]);
579 atomic_increment (&large
);
580 /* Total number of calls of any of the functions. */
581 atomic_increment (&calls_total
);
583 /* Check for failures. */
585 atomic_increment (&failed
[idx
]);
586 else if (idx
== idx_mmap_w
)
587 /* Update the allocation data and write out the records if
588 necessary. Note the first parameter is NULL which means
589 the size is not tracked. */
590 update_data (NULL
, len
, 0);
593 /* Return the pointer to the user buffer. */
598 /* `mmap' replacement. We do not have to keep track of the sizesince
599 `munmap' will get it as a parameter. */
601 mmap64 (void *start
, size_t len
, int prot
, int flags
, int fd
, off64_t offset
)
605 /* Determine real implementation if not already happened. */
606 if (__builtin_expect (initialized
<= 0, 0))
608 if (initialized
== -1)
613 /* Always get a block. We don't need extra memory. */
614 result
= (*mmap64p
) (start
, len
, prot
, flags
, fd
, offset
);
616 if (!not_me
&& trace_mmap
)
618 int idx
= (flags
& MAP_ANON
619 ? idx_mmap_a
: prot
& PROT_WRITE
? idx_mmap_w
: idx_mmap_r
);
621 /* Keep track of number of calls. */
622 atomic_increment (&calls
[idx
]);
623 /* Keep track of total memory consumption for `malloc'. */
624 atomic_add (&total
[idx
], len
);
625 /* Keep track of total memory requirement. */
626 atomic_add (&grand_total
, len
);
627 /* Remember the size of the request. */
629 atomic_increment (&histogram
[len
/ 16]);
631 atomic_increment (&large
);
632 /* Total number of calls of any of the functions. */
633 atomic_increment (&calls_total
);
635 /* Check for failures. */
637 atomic_increment (&failed
[idx
]);
638 else if (idx
== idx_mmap_w
)
639 /* Update the allocation data and write out the records if
640 necessary. Note the first parameter is NULL which means
641 the size is not tracked. */
642 update_data (NULL
, len
, 0);
645 /* Return the pointer to the user buffer. */
650 /* `mmap' replacement. We do not have to keep track of the sizesince
651 `munmap' will get it as a parameter. */
653 mremap (void *start
, size_t old_len
, size_t len
, int flags
, ...)
658 va_start (ap
, flags
);
659 void *newaddr
= (flags
& MREMAP_FIXED
) ? va_arg (ap
, void *) : NULL
;
662 /* Determine real implementation if not already happened. */
663 if (__builtin_expect (initialized
<= 0, 0))
665 if (initialized
== -1)
670 /* Always get a block. We don't need extra memory. */
671 result
= (*mremapp
) (start
, old_len
, len
, flags
, newaddr
);
673 if (!not_me
&& trace_mmap
)
675 /* Keep track of number of calls. */
676 atomic_increment (&calls
[idx_mremap
]);
679 /* Keep track of total memory consumption for `malloc'. */
680 atomic_add (&total
[idx_mremap
], len
- old_len
);
681 /* Keep track of total memory requirement. */
682 atomic_add (&grand_total
, len
- old_len
);
684 /* Remember the size of the request. */
686 atomic_increment (&histogram
[len
/ 16]);
688 atomic_increment (&large
);
689 /* Total number of calls of any of the functions. */
690 atomic_increment (&calls_total
);
692 /* Check for failures. */
694 atomic_increment (&failed
[idx_mremap
]);
697 /* Record whether the reduction/increase happened in place. */
699 atomic_increment (&inplace_mremap
);
700 /* Was the buffer increased? */
702 atomic_increment (&decreasing_mremap
);
704 /* Update the allocation data and write out the records if
705 necessary. Note the first parameter is NULL which means
706 the size is not tracked. */
707 update_data (NULL
, len
, old_len
);
711 /* Return the pointer to the user buffer. */
716 /* `munmap' replacement. */
718 munmap (void *start
, size_t len
)
722 /* Determine real implementation if not already happened. */
723 if (__builtin_expect (initialized
<= 0, 0))
725 if (initialized
== -1)
730 /* Do the real work. */
731 result
= (*munmapp
) (start
, len
);
733 if (!not_me
&& trace_mmap
)
735 /* Keep track of number of calls. */
736 atomic_increment (&calls
[idx_munmap
]);
738 if (__builtin_expect (result
== 0, 1))
740 /* Keep track of total memory freed using `free'. */
741 atomic_add (&total
[idx_munmap
], len
);
743 /* Update the allocation data and write out the records if
745 update_data (NULL
, 0, len
);
748 atomic_increment (&failed
[idx_munmap
]);
755 /* Write some statistics to standard error. */
757 __attribute__ ((destructor
))
761 unsigned long int maxcalls
;
763 /* If we haven't done anything here just return. */
766 /* If we should call any of the memory functions don't do any profiling. */
769 /* Finish the output file. */
772 /* Write the partially filled buffer. */
773 write (fd
, buffer
, buffer_cnt
* sizeof (struct entry
));
774 /* Go back to the beginning of the file. We allocated two records
775 here when we opened the file. */
776 lseek (fd
, 0, SEEK_SET
);
777 /* Write out a record containing the total size. */
778 first
.stack
= peak_total
;
779 write (fd
, &first
, sizeof (struct entry
));
780 /* Write out another record containing the maximum for heap and
782 first
.heap
= peak_heap
;
783 first
.stack
= peak_stack
;
784 GETTIME (first
.time_low
, first
.time_high
);
785 write (fd
, &first
, sizeof (struct entry
));
787 /* Close the file. */
792 /* Write a colorful statistic. */
793 fprintf (stderr
, "\n\
794 \e[01;32mMemory usage summary:\e[0;0m heap total: %llu, heap peak: %lu, stack peak: %lu\n\
795 \e[04;34m total calls total memory failed calls\e[0m\n\
796 \e[00;34m malloc|\e[0m %10lu %12llu %s%12lu\e[00;00m\n\
797 \e[00;34mrealloc|\e[0m %10lu %12llu %s%12lu\e[00;00m (in place: %ld, dec: %ld)\n\
798 \e[00;34m calloc|\e[0m %10lu %12llu %s%12lu\e[00;00m\n\
799 \e[00;34m free|\e[0m %10lu %12llu\n",
800 (unsigned long long int) grand_total
, (unsigned long int) peak_heap
,
801 (unsigned long int) peak_stack
,
802 (unsigned long int) calls
[idx_malloc
],
803 (unsigned long long int) total
[idx_malloc
],
804 failed
[idx_malloc
] ? "\e[01;41m" : "",
805 (unsigned long int) failed
[idx_malloc
],
806 (unsigned long int) calls
[idx_realloc
],
807 (unsigned long long int) total
[idx_realloc
],
808 failed
[idx_realloc
] ? "\e[01;41m" : "",
809 (unsigned long int) failed
[idx_realloc
],
810 (unsigned long int) inplace
, (unsigned long int) decreasing
,
811 (unsigned long int) calls
[idx_calloc
],
812 (unsigned long long int) total
[idx_calloc
],
813 failed
[idx_calloc
] ? "\e[01;41m" : "",
814 (unsigned long int) failed
[idx_calloc
],
815 (unsigned long int) calls
[idx_free
],
816 (unsigned long long int) total
[idx_free
]);
820 \e[00;34mmmap(r)|\e[0m %10lu %12llu %s%12lu\e[00;00m\n\
821 \e[00;34mmmap(w)|\e[0m %10lu %12llu %s%12lu\e[00;00m\n\
822 \e[00;34mmmap(a)|\e[0m %10lu %12llu %s%12lu\e[00;00m\n\
823 \e[00;34m mremap|\e[0m %10lu %12llu %s%12lu\e[00;00m (in place: %ld, dec: %ld)\n\
824 \e[00;34m munmap|\e[0m %10lu %12llu %s%12lu\e[00;00m\n",
825 (unsigned long int) calls
[idx_mmap_r
],
826 (unsigned long long int) total
[idx_mmap_r
],
827 failed
[idx_mmap_r
] ? "\e[01;41m" : "",
828 (unsigned long int) failed
[idx_mmap_r
],
829 (unsigned long int) calls
[idx_mmap_w
],
830 (unsigned long long int) total
[idx_mmap_w
],
831 failed
[idx_mmap_w
] ? "\e[01;41m" : "",
832 (unsigned long int) failed
[idx_mmap_w
],
833 (unsigned long int) calls
[idx_mmap_a
],
834 (unsigned long long int) total
[idx_mmap_a
],
835 failed
[idx_mmap_a
] ? "\e[01;41m" : "",
836 (unsigned long int) failed
[idx_mmap_a
],
837 (unsigned long int) calls
[idx_mremap
],
838 (unsigned long long int) total
[idx_mremap
],
839 failed
[idx_mremap
] ? "\e[01;41m" : "",
840 (unsigned long int) failed
[idx_mremap
],
841 (unsigned long int) inplace_mremap
,
842 (unsigned long int) decreasing_mremap
,
843 (unsigned long int) calls
[idx_munmap
],
844 (unsigned long long int) total
[idx_munmap
],
845 failed
[idx_munmap
] ? "\e[01;41m" : "",
846 (unsigned long int) failed
[idx_munmap
]);
848 /* Write out a histoogram of the sizes of the allocations. */
849 fprintf (stderr
, "\e[01;32mHistogram for block sizes:\e[0;0m\n");
851 /* Determine the maximum of all calls for each size range. */
853 for (cnt
= 0; cnt
< 65536; cnt
+= 16)
854 if (histogram
[cnt
/ 16] > maxcalls
)
855 maxcalls
= histogram
[cnt
/ 16];
857 for (cnt
= 0; cnt
< 65536; cnt
+= 16)
858 /* Only write out the nonzero entries. */
859 if (histogram
[cnt
/ 16] != 0)
861 percent
= (histogram
[cnt
/ 16] * 100) / calls_total
;
862 fprintf (stderr
, "%5d-%-5d%12lu ", cnt
, cnt
+ 15,
863 (unsigned long int) histogram
[cnt
/ 16]);
865 fputs (" <1% \e[41;37m", stderr
);
867 fprintf (stderr
, "%3d%% \e[41;37m", percent
);
869 /* Draw a bar with a length corresponding to the current
871 percent
= (histogram
[cnt
/ 16] * 50) / maxcalls
;
872 while (percent
-- > 0)
874 fputs ("\e[0;0m\n", stderr
);
879 percent
= (large
* 100) / calls_total
;
880 fprintf (stderr
, " large %12lu ", (unsigned long int) large
);
882 fputs (" <1% \e[41;37m", stderr
);
884 fprintf (stderr
, "%3d%% \e[41;37m", percent
);
885 percent
= (large
* 50) / maxcalls
;
886 while (percent
-- > 0)
888 fputs ("\e[0;0m\n", stderr
);