1/* Profile heap and stack memory usage of running program.
2 Copyright (C) 1998-2017 Free Software Foundation, Inc.
3 This file is part of the GNU C Library.
4 Contributed by Ulrich Drepper <drepper@cygnus.com>, 1998.
5
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.
10
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.
15
16 You should have received a copy of the GNU Lesser General Public
17 License along with the GNU C Library; if not, see
18 <http://www.gnu.org/licenses/>. */
19
20#include <assert.h>
21#include <atomic.h>
22#include <dlfcn.h>
23#include <errno.h>
24#include <fcntl.h>
25#include <inttypes.h>
26#include <signal.h>
27#include <stdarg.h>
28#include <stdbool.h>
29#include <stdio.h>
30#include <stdlib.h>
31#include <string.h>
32#include <unistd.h>
33#include <stdint.h>
34#include <sys/mman.h>
35#include <sys/time.h>
36
37#include <memusage.h>
38
39/* Pointer to the real functions. These are determined used `dlsym'
40 when really needed. */
41static void *(*mallocp)(size_t);
42static void *(*reallocp) (void *, size_t);
43static void *(*callocp) (size_t, size_t);
44static void (*freep) (void *);
45
46static void *(*mmapp) (void *, size_t, int, int, int, off_t);
47static void *(*mmap64p) (void *, size_t, int, int, int, off64_t);
48static int (*munmapp) (void *, size_t);
49static void *(*mremapp) (void *, size_t, size_t, int, void *);
50
51enum
52{
53 idx_malloc = 0,
54 idx_realloc,
55 idx_calloc,
56 idx_free,
57 idx_mmap_r,
58 idx_mmap_w,
59 idx_mmap_a,
60 idx_mremap,
61 idx_munmap,
62 idx_last
63};
64
65
66struct header
67{
68 size_t length;
69 size_t magic;
70};
71
72#define MAGIC 0xfeedbeaf
73
74
75static memusage_cntr_t calls[idx_last];
76static memusage_cntr_t failed[idx_last];
77static memusage_size_t total[idx_last];
78static memusage_size_t grand_total;
79static memusage_cntr_t histogram[65536 / 16];
80static memusage_cntr_t large;
81static memusage_cntr_t calls_total;
82static memusage_cntr_t inplace;
83static memusage_cntr_t decreasing;
84static memusage_cntr_t realloc_free;
85static memusage_cntr_t inplace_mremap;
86static memusage_cntr_t decreasing_mremap;
87static memusage_size_t current_heap;
88static memusage_size_t peak_use[3];
89static __thread uintptr_t start_sp;
90
91/* A few macros to make the source more readable. */
92#define peak_heap peak_use[0]
93#define peak_stack peak_use[1]
94#define peak_total peak_use[2]
95
96#define DEFAULT_BUFFER_SIZE 32768
97static size_t buffer_size;
98
99static int fd = -1;
100
101static bool not_me;
102static int initialized;
103static bool trace_mmap;
104extern const char *__progname;
105
106struct entry
107{
108 uint64_t heap;
109 uint64_t stack;
110 uint32_t time_low;
111 uint32_t time_high;
112};
113
114static struct entry buffer[2 * DEFAULT_BUFFER_SIZE];
115static uatomic32_t buffer_cnt;
116static struct entry first;
117
118
119/* Update the global data after a successful function call. */
120static void
121update_data (struct header *result, size_t len, size_t old_len)
122{
123 if (result != NULL)
124 {
125 /* Record the information we need and mark the block using a
126 magic number. */
127 result->length = len;
128 result->magic = MAGIC;
129 }
130
131 /* Compute current heap usage and compare it with the maximum value. */
132 memusage_size_t heap
133 = catomic_exchange_and_add (&current_heap, len - old_len) + len - old_len;
134 catomic_max (&peak_heap, heap);
135
136 /* Compute current stack usage and compare it with the maximum
137 value. The base stack pointer might not be set if this is not
138 the main thread and it is the first call to any of these
139 functions. */
140 if (__glibc_unlikely (!start_sp))
141 start_sp = GETSP ();
142
143 uintptr_t sp = GETSP ();
144#ifdef STACK_GROWS_UPWARD
145 /* This can happen in threads where we didn't catch the thread's
146 stack early enough. */
147 if (__glibc_unlikely (sp < start_sp))
148 start_sp = sp;
149 size_t current_stack = sp - start_sp;
150#else
151 /* This can happen in threads where we didn't catch the thread's
152 stack early enough. */
153 if (__glibc_unlikely (sp > start_sp))
154 start_sp = sp;
155 size_t current_stack = start_sp - sp;
156#endif
157 catomic_max (&peak_stack, current_stack);
158
159 /* Add up heap and stack usage and compare it with the maximum value. */
160 catomic_max (&peak_total, heap + current_stack);
161
162 /* Store the value only if we are writing to a file. */
163 if (fd != -1)
164 {
165 uatomic32_t idx = catomic_exchange_and_add (&buffer_cnt, 1);
166 if (idx + 1 >= 2 * buffer_size)
167 {
168 /* We try to reset the counter to the correct range. If
169 this fails because of another thread increasing the
170 counter it does not matter since that thread will take
171 care of the correction. */
172 uatomic32_t reset = (idx + 1) % (2 * buffer_size);
173 catomic_compare_and_exchange_val_acq (&buffer_cnt, reset, idx + 1);
174 if (idx >= 2 * buffer_size)
175 idx = reset - 1;
176 }
177 assert (idx < 2 * DEFAULT_BUFFER_SIZE);
178
179 buffer[idx].heap = current_heap;
180 buffer[idx].stack = current_stack;
181 GETTIME (buffer[idx].time_low, buffer[idx].time_high);
182
183 /* Write out buffer if it is full. */
184 if (idx + 1 == buffer_size)
185 write (fd, buffer, buffer_size * sizeof (struct entry));
186 else if (idx + 1 == 2 * buffer_size)
187 write (fd, &buffer[buffer_size], buffer_size * sizeof (struct entry));
188 }
189}
190
191
192/* Interrupt handler. */
193static void
194int_handler (int signo)
195{
196 /* Nothing gets allocated. Just record the stack pointer position. */
197 update_data (NULL, 0, 0);
198}
199
200
201/* Find out whether this is the program we are supposed to profile.
202 For this the name in the variable `__progname' must match the one
203 given in the environment variable MEMUSAGE_PROG_NAME. If the variable
204 is not present every program assumes it should be profiling.
205
206 If this is the program open a file descriptor to the output file.
207 We will write to it whenever the buffer overflows. The name of the
208 output file is determined by the environment variable MEMUSAGE_OUTPUT.
209
210 If the environment variable MEMUSAGE_BUFFER_SIZE is set its numerical
211 value determines the size of the internal buffer. The number gives
212 the number of elements in the buffer. By setting the number to one
213 one effectively selects unbuffered operation.
214
215 If MEMUSAGE_NO_TIMER is not present an alarm handler is installed
216 which at the highest possible frequency records the stack pointer. */
217static void
218me (void)
219{
220 const char *env = getenv ("MEMUSAGE_PROG_NAME");
221 size_t prog_len = strlen (__progname);
222
223 initialized = -1;
224 mallocp = (void *(*)(size_t))dlsym (RTLD_NEXT, "malloc");
225 reallocp = (void *(*)(void *, size_t))dlsym (RTLD_NEXT, "realloc");
226 callocp = (void *(*)(size_t, size_t))dlsym (RTLD_NEXT, "calloc");
227 freep = (void (*)(void *))dlsym (RTLD_NEXT, "free");
228
229 mmapp = (void *(*)(void *, size_t, int, int, int, off_t))dlsym (RTLD_NEXT,
230 "mmap");
231 mmap64p =
232 (void *(*)(void *, size_t, int, int, int, off64_t))dlsym (RTLD_NEXT,
233 "mmap64");
234 mremapp = (void *(*)(void *, size_t, size_t, int, void *))dlsym (RTLD_NEXT,
235 "mremap");
236 munmapp = (int (*)(void *, size_t))dlsym (RTLD_NEXT, "munmap");
237 initialized = 1;
238
239 if (env != NULL)
240 {
241 /* Check for program name. */
242 size_t len = strlen (env);
243 if (len > prog_len || strcmp (env, &__progname[prog_len - len]) != 0
244 || (prog_len != len && __progname[prog_len - len - 1] != '/'))
245 not_me = true;
246 }
247
248 /* Only open the file if it's really us. */
249 if (!not_me && fd == -1)
250 {
251 const char *outname;
252
253 if (!start_sp)
254 start_sp = GETSP ();
255
256 outname = getenv ("MEMUSAGE_OUTPUT");
257 if (outname != NULL && outname[0] != '\0'
258 && (access (outname, R_OK | W_OK) == 0 || errno == ENOENT))
259 {
260 fd = creat64 (outname, 0666);
261
262 if (fd == -1)
263 /* Don't do anything in future calls if we cannot write to
264 the output file. */
265 not_me = true;
266 else
267 {
268 /* Write the first entry. */
269 first.heap = 0;
270 first.stack = 0;
271 GETTIME (first.time_low, first.time_high);
272 /* Write it two times since we need the starting and end time. */
273 write (fd, &first, sizeof (first));
274 write (fd, &first, sizeof (first));
275
276 /* Determine the buffer size. We use the default if the
277 environment variable is not present. */
278 buffer_size = DEFAULT_BUFFER_SIZE;
279 const char *str_buffer_size = getenv ("MEMUSAGE_BUFFER_SIZE");
280 if (str_buffer_size != NULL)
281 {
282 buffer_size = atoi (str_buffer_size);
283 if (buffer_size == 0 || buffer_size > DEFAULT_BUFFER_SIZE)
284 buffer_size = DEFAULT_BUFFER_SIZE;
285 }
286
287 /* Possibly enable timer-based stack pointer retrieval. */
288 if (getenv ("MEMUSAGE_NO_TIMER") == NULL)
289 {
290 struct sigaction act;
291
292 act.sa_handler = (sighandler_t) &int_handler;
293 act.sa_flags = SA_RESTART;
294 sigfillset (&act.sa_mask);
295
296 if (sigaction (SIGPROF, &act, NULL) >= 0)
297 {
298 struct itimerval timer;
299
300 timer.it_value.tv_sec = 0;
301 timer.it_value.tv_usec = 1;
302 timer.it_interval = timer.it_value;
303 setitimer (ITIMER_PROF, &timer, NULL);
304 }
305 }
306 }
307 }
308
309 if (!not_me && getenv ("MEMUSAGE_TRACE_MMAP") != NULL)
310 trace_mmap = true;
311 }
312}
313
314
315/* Record the initial stack position. */
316static void
317__attribute__ ((constructor))
318init (void)
319{
320 start_sp = GETSP ();
321 if (!initialized)
322 me ();
323}
324
325
326/* `malloc' replacement. We keep track of the memory usage if this is the
327 correct program. */
328void *
329malloc (size_t len)
330{
331 struct header *result = NULL;
332
333 /* Determine real implementation if not already happened. */
334 if (__glibc_unlikely (initialized <= 0))
335 {
336 if (initialized == -1)
337 return NULL;
338
339 me ();
340 }
341
342 /* If this is not the correct program just use the normal function. */
343 if (not_me)
344 return (*mallocp)(len);
345
346 /* Keep track of number of calls. */
347 catomic_increment (&calls[idx_malloc]);
348 /* Keep track of total memory consumption for `malloc'. */
349 catomic_add (&total[idx_malloc], len);
350 /* Keep track of total memory requirement. */
351 catomic_add (&grand_total, len);
352 /* Remember the size of the request. */
353 if (len < 65536)
354 catomic_increment (&histogram[len / 16]);
355 else
356 catomic_increment (&large);
357 /* Total number of calls of any of the functions. */
358 catomic_increment (&calls_total);
359
360 /* Do the real work. */
361 result = (struct header *) (*mallocp)(len + sizeof (struct header));
362 if (result == NULL)
363 {
364 catomic_increment (&failed[idx_malloc]);
365 return NULL;
366 }
367
368 /* Update the allocation data and write out the records if necessary. */
369 update_data (result, len, 0);
370
371 /* Return the pointer to the user buffer. */
372 return (void *) (result + 1);
373}
374
375
376/* `realloc' replacement. We keep track of the memory usage if this is the
377 correct program. */
378void *
379realloc (void *old, size_t len)
380{
381 struct header *result = NULL;
382 struct header *real;
383 size_t old_len;
384
385 /* Determine real implementation if not already happened. */
386 if (__glibc_unlikely (initialized <= 0))
387 {
388 if (initialized == -1)
389 return NULL;
390
391 me ();
392 }
393
394 /* If this is not the correct program just use the normal function. */
395 if (not_me)
396 return (*reallocp)(old, len);
397
398 if (old == NULL)
399 {
400 /* This is really a `malloc' call. */
401 real = NULL;
402 old_len = 0;
403 }
404 else
405 {
406 real = ((struct header *) old) - 1;
407 if (real->magic != MAGIC)
408 /* This is no memory allocated here. */
409 return (*reallocp)(old, len);
410
411 old_len = real->length;
412 }
413
414 /* Keep track of number of calls. */
415 catomic_increment (&calls[idx_realloc]);
416 if (len > old_len)
417 {
418 /* Keep track of total memory consumption for `realloc'. */
419 catomic_add (&total[idx_realloc], len - old_len);
420 /* Keep track of total memory requirement. */
421 catomic_add (&grand_total, len - old_len);
422 }
423
424 if (len == 0 && old != NULL)
425 {
426 /* Special case. */
427 catomic_increment (&realloc_free);
428 /* Keep track of total memory freed using `free'. */
429 catomic_add (&total[idx_free], real->length);
430
431 /* Update the allocation data and write out the records if necessary. */
432 update_data (NULL, 0, old_len);
433
434 /* Do the real work. */
435 (*freep) (real);
436
437 return NULL;
438 }
439
440 /* Remember the size of the request. */
441 if (len < 65536)
442 catomic_increment (&histogram[len / 16]);
443 else
444 catomic_increment (&large);
445 /* Total number of calls of any of the functions. */
446 catomic_increment (&calls_total);
447
448 /* Do the real work. */
449 result = (struct header *) (*reallocp)(real, len + sizeof (struct header));
450 if (result == NULL)
451 {
452 catomic_increment (&failed[idx_realloc]);
453 return NULL;
454 }
455
456 /* Record whether the reduction/increase happened in place. */
457 if (real == result)
458 catomic_increment (&inplace);
459 /* Was the buffer increased? */
460 if (old_len > len)
461 catomic_increment (&decreasing);
462
463 /* Update the allocation data and write out the records if necessary. */
464 update_data (result, len, old_len);
465
466 /* Return the pointer to the user buffer. */
467 return (void *) (result + 1);
468}
469
470
471/* `calloc' replacement. We keep track of the memory usage if this is the
472 correct program. */
473void *
474calloc (size_t n, size_t len)
475{
476 struct header *result;
477 size_t size = n * len;
478
479 /* Determine real implementation if not already happened. */
480 if (__glibc_unlikely (initialized <= 0))
481 {
482 if (initialized == -1)
483 return NULL;
484
485 me ();
486 }
487
488 /* If this is not the correct program just use the normal function. */
489 if (not_me)
490 return (*callocp)(n, len);
491
492 /* Keep track of number of calls. */
493 catomic_increment (&calls[idx_calloc]);
494 /* Keep track of total memory consumption for `calloc'. */
495 catomic_add (&total[idx_calloc], size);
496 /* Keep track of total memory requirement. */
497 catomic_add (&grand_total, size);
498 /* Remember the size of the request. */
499 if (size < 65536)
500 catomic_increment (&histogram[size / 16]);
501 else
502 catomic_increment (&large);
503 /* Total number of calls of any of the functions. */
504 ++calls_total;
505
506 /* Do the real work. */
507 result = (struct header *) (*mallocp)(size + sizeof (struct header));
508 if (result == NULL)
509 {
510 catomic_increment (&failed[idx_calloc]);
511 return NULL;
512 }
513
514 /* Update the allocation data and write out the records if necessary. */
515 update_data (result, size, 0);
516
517 /* Do what `calloc' would have done and return the buffer to the caller. */
518 return memset (result + 1, '\0', size);
519}
520
521
522/* `free' replacement. We keep track of the memory usage if this is the
523 correct program. */
524void
525free (void *ptr)
526{
527 struct header *real;
528
529 /* Determine real implementation if not already happened. */
530 if (__glibc_unlikely (initialized <= 0))
531 {
532 if (initialized == -1)
533 return;
534
535 me ();
536 }
537
538 /* If this is not the correct program just use the normal function. */
539 if (not_me)
540 {
541 (*freep) (ptr);
542 return;
543 }
544
545 /* `free (NULL)' has no effect. */
546 if (ptr == NULL)
547 {
548 catomic_increment (&calls[idx_free]);
549 return;
550 }
551
552 /* Determine the pointer to the header. */
553 real = ((struct header *) ptr) - 1;
554 if (real->magic != MAGIC)
555 {
556 /* This block wasn't allocated here. */
557 (*freep) (ptr);
558 return;
559 }
560
561 /* Keep track of number of calls. */
562 catomic_increment (&calls[idx_free]);
563 /* Keep track of total memory freed using `free'. */
564 catomic_add (&total[idx_free], real->length);
565
566 /* Update the allocation data and write out the records if necessary. */
567 update_data (NULL, 0, real->length);
568
569 /* Do the real work. */
570 (*freep) (real);
571}
572
573
574/* `mmap' replacement. We do not have to keep track of the size since
575 `munmap' will get it as a parameter. */
576void *
577mmap (void *start, size_t len, int prot, int flags, int fd, off_t offset)
578{
579 void *result = NULL;
580
581 /* Determine real implementation if not already happened. */
582 if (__glibc_unlikely (initialized <= 0))
583 {
584 if (initialized == -1)
585 return NULL;
586
587 me ();
588 }
589
590 /* Always get a block. We don't need extra memory. */
591 result = (*mmapp)(start, len, prot, flags, fd, offset);
592
593 if (!not_me && trace_mmap)
594 {
595 int idx = (flags & MAP_ANON
596 ? idx_mmap_a : prot & PROT_WRITE ? idx_mmap_w : idx_mmap_r);
597
598 /* Keep track of number of calls. */
599 catomic_increment (&calls[idx]);
600 /* Keep track of total memory consumption for `malloc'. */
601 catomic_add (&total[idx], len);
602 /* Keep track of total memory requirement. */
603 catomic_add (&grand_total, len);
604 /* Remember the size of the request. */
605 if (len < 65536)
606 catomic_increment (&histogram[len / 16]);
607 else
608 catomic_increment (&large);
609 /* Total number of calls of any of the functions. */
610 catomic_increment (&calls_total);
611
612 /* Check for failures. */
613 if (result == NULL)
614 catomic_increment (&failed[idx]);
615 else if (idx == idx_mmap_w)
616 /* Update the allocation data and write out the records if
617 necessary. Note the first parameter is NULL which means
618 the size is not tracked. */
619 update_data (NULL, len, 0);
620 }
621
622 /* Return the pointer to the user buffer. */
623 return result;
624}
625
626
627/* `mmap64' replacement. We do not have to keep track of the size since
628 `munmap' will get it as a parameter. */
629void *
630mmap64 (void *start, size_t len, int prot, int flags, int fd, off64_t offset)
631{
632 void *result = NULL;
633
634 /* Determine real implementation if not already happened. */
635 if (__glibc_unlikely (initialized <= 0))
636 {
637 if (initialized == -1)
638 return NULL;
639
640 me ();
641 }
642
643 /* Always get a block. We don't need extra memory. */
644 result = (*mmap64p)(start, len, prot, flags, fd, offset);
645
646 if (!not_me && trace_mmap)
647 {
648 int idx = (flags & MAP_ANON
649 ? idx_mmap_a : prot & PROT_WRITE ? idx_mmap_w : idx_mmap_r);
650
651 /* Keep track of number of calls. */
652 catomic_increment (&calls[idx]);
653 /* Keep track of total memory consumption for `malloc'. */
654 catomic_add (&total[idx], len);
655 /* Keep track of total memory requirement. */
656 catomic_add (&grand_total, len);
657 /* Remember the size of the request. */
658 if (len < 65536)
659 catomic_increment (&histogram[len / 16]);
660 else
661 catomic_increment (&large);
662 /* Total number of calls of any of the functions. */
663 catomic_increment (&calls_total);
664
665 /* Check for failures. */
666 if (result == NULL)
667 catomic_increment (&failed[idx]);
668 else if (idx == idx_mmap_w)
669 /* Update the allocation data and write out the records if
670 necessary. Note the first parameter is NULL which means
671 the size is not tracked. */
672 update_data (NULL, len, 0);
673 }
674
675 /* Return the pointer to the user buffer. */
676 return result;
677}
678
679
680/* `mremap' replacement. We do not have to keep track of the size since
681 `munmap' will get it as a parameter. */
682void *
683mremap (void *start, size_t old_len, size_t len, int flags, ...)
684{
685 void *result = NULL;
686 va_list ap;
687
688 va_start (ap, flags);
689 void *newaddr = (flags & MREMAP_FIXED) ? va_arg (ap, void *) : NULL;
690 va_end (ap);
691
692 /* Determine real implementation if not already happened. */
693 if (__glibc_unlikely (initialized <= 0))
694 {
695 if (initialized == -1)
696 return NULL;
697
698 me ();
699 }
700
701 /* Always get a block. We don't need extra memory. */
702 result = (*mremapp)(start, old_len, len, flags, newaddr);
703
704 if (!not_me && trace_mmap)
705 {
706 /* Keep track of number of calls. */
707 catomic_increment (&calls[idx_mremap]);
708 if (len > old_len)
709 {
710 /* Keep track of total memory consumption for `malloc'. */
711 catomic_add (&total[idx_mremap], len - old_len);
712 /* Keep track of total memory requirement. */
713 catomic_add (&grand_total, len - old_len);
714 }
715 /* Remember the size of the request. */
716 if (len < 65536)
717 catomic_increment (&histogram[len / 16]);
718 else
719 catomic_increment (&large);
720 /* Total number of calls of any of the functions. */
721 catomic_increment (&calls_total);
722
723 /* Check for failures. */
724 if (result == NULL)
725 catomic_increment (&failed[idx_mremap]);
726 else
727 {
728 /* Record whether the reduction/increase happened in place. */
729 if (start == result)
730 catomic_increment (&inplace_mremap);
731 /* Was the buffer increased? */
732 if (old_len > len)
733 catomic_increment (&decreasing_mremap);
734
735 /* Update the allocation data and write out the records if
736 necessary. Note the first parameter is NULL which means
737 the size is not tracked. */
738 update_data (NULL, len, old_len);
739 }
740 }
741
742 /* Return the pointer to the user buffer. */
743 return result;
744}
745
746
747/* `munmap' replacement. */
748int
749munmap (void *start, size_t len)
750{
751 int result;
752
753 /* Determine real implementation if not already happened. */
754 if (__glibc_unlikely (initialized <= 0))
755 {
756 if (initialized == -1)
757 return -1;
758
759 me ();
760 }
761
762 /* Do the real work. */
763 result = (*munmapp)(start, len);
764
765 if (!not_me && trace_mmap)
766 {
767 /* Keep track of number of calls. */
768 catomic_increment (&calls[idx_munmap]);
769
770 if (__glibc_likely (result == 0))
771 {
772 /* Keep track of total memory freed using `free'. */
773 catomic_add (&total[idx_munmap], len);
774
775 /* Update the allocation data and write out the records if
776 necessary. */
777 update_data (NULL, 0, len);
778 }
779 else
780 catomic_increment (&failed[idx_munmap]);
781 }
782
783 return result;
784}
785
786
787/* Write some statistics to standard error. */
788static void
789__attribute__ ((destructor))
790dest (void)
791{
792 int percent, cnt;
793 unsigned long int maxcalls;
794
795 /* If we haven't done anything here just return. */
796 if (not_me)
797 return;
798
799 /* If we should call any of the memory functions don't do any profiling. */
800 not_me = true;
801
802 /* Finish the output file. */
803 if (fd != -1)
804 {
805 /* Write the partially filled buffer. */
806 if (buffer_cnt > buffer_size)
807 write (fd, buffer + buffer_size,
808 (buffer_cnt - buffer_size) * sizeof (struct entry));
809 else
810 write (fd, buffer, buffer_cnt * sizeof (struct entry));
811
812 /* Go back to the beginning of the file. We allocated two records
813 here when we opened the file. */
814 lseek (fd, 0, SEEK_SET);
815 /* Write out a record containing the total size. */
816 first.stack = peak_total;
817 write (fd, &first, sizeof (struct entry));
818 /* Write out another record containing the maximum for heap and
819 stack. */
820 first.heap = peak_heap;
821 first.stack = peak_stack;
822 GETTIME (first.time_low, first.time_high);
823 write (fd, &first, sizeof (struct entry));
824
825 /* Close the file. */
826 close (fd);
827 fd = -1;
828 }
829
830 /* Write a colorful statistic. */
831 fprintf (stderr, "\n\
832\e[01;32mMemory usage summary:\e[0;0m heap total: %llu, heap peak: %lu, stack peak: %lu\n\
833\e[04;34m total calls total memory failed calls\e[0m\n\
834\e[00;34m malloc|\e[0m %10lu %12llu %s%12lu\e[00;00m\n\
835\e[00;34mrealloc|\e[0m %10lu %12llu %s%12lu\e[00;00m (nomove:%ld, dec:%ld, free:%ld)\n\
836\e[00;34m calloc|\e[0m %10lu %12llu %s%12lu\e[00;00m\n\
837\e[00;34m free|\e[0m %10lu %12llu\n",
838 (unsigned long long int) grand_total, (unsigned long int) peak_heap,
839 (unsigned long int) peak_stack,
840 (unsigned long int) calls[idx_malloc],
841 (unsigned long long int) total[idx_malloc],
842 failed[idx_malloc] ? "\e[01;41m" : "",
843 (unsigned long int) failed[idx_malloc],
844 (unsigned long int) calls[idx_realloc],
845 (unsigned long long int) total[idx_realloc],
846 failed[idx_realloc] ? "\e[01;41m" : "",
847 (unsigned long int) failed[idx_realloc],
848 (unsigned long int) inplace,
849 (unsigned long int) decreasing,
850 (unsigned long int) realloc_free,
851 (unsigned long int) calls[idx_calloc],
852 (unsigned long long int) total[idx_calloc],
853 failed[idx_calloc] ? "\e[01;41m" : "",
854 (unsigned long int) failed[idx_calloc],
855 (unsigned long int) calls[idx_free],
856 (unsigned long long int) total[idx_free]);
857
858 if (trace_mmap)
859 fprintf (stderr, "\
860\e[00;34mmmap(r)|\e[0m %10lu %12llu %s%12lu\e[00;00m\n\
861\e[00;34mmmap(w)|\e[0m %10lu %12llu %s%12lu\e[00;00m\n\
862\e[00;34mmmap(a)|\e[0m %10lu %12llu %s%12lu\e[00;00m\n\
863\e[00;34m mremap|\e[0m %10lu %12llu %s%12lu\e[00;00m (nomove: %ld, dec:%ld)\n\
864\e[00;34m munmap|\e[0m %10lu %12llu %s%12lu\e[00;00m\n",
865 (unsigned long int) calls[idx_mmap_r],
866 (unsigned long long int) total[idx_mmap_r],
867 failed[idx_mmap_r] ? "\e[01;41m" : "",
868 (unsigned long int) failed[idx_mmap_r],
869 (unsigned long int) calls[idx_mmap_w],
870 (unsigned long long int) total[idx_mmap_w],
871 failed[idx_mmap_w] ? "\e[01;41m" : "",
872 (unsigned long int) failed[idx_mmap_w],
873 (unsigned long int) calls[idx_mmap_a],
874 (unsigned long long int) total[idx_mmap_a],
875 failed[idx_mmap_a] ? "\e[01;41m" : "",
876 (unsigned long int) failed[idx_mmap_a],
877 (unsigned long int) calls[idx_mremap],
878 (unsigned long long int) total[idx_mremap],
879 failed[idx_mremap] ? "\e[01;41m" : "",
880 (unsigned long int) failed[idx_mremap],
881 (unsigned long int) inplace_mremap,
882 (unsigned long int) decreasing_mremap,
883 (unsigned long int) calls[idx_munmap],
884 (unsigned long long int) total[idx_munmap],
885 failed[idx_munmap] ? "\e[01;41m" : "",
886 (unsigned long int) failed[idx_munmap]);
887
888 /* Write out a histoogram of the sizes of the allocations. */
889 fprintf (stderr, "\e[01;32mHistogram for block sizes:\e[0;0m\n");
890
891 /* Determine the maximum of all calls for each size range. */
892 maxcalls = large;
893 for (cnt = 0; cnt < 65536; cnt += 16)
894 if (histogram[cnt / 16] > maxcalls)
895 maxcalls = histogram[cnt / 16];
896
897 for (cnt = 0; cnt < 65536; cnt += 16)
898 /* Only write out the nonzero entries. */
899 if (histogram[cnt / 16] != 0)
900 {
901 percent = (histogram[cnt / 16] * 100) / calls_total;
902 fprintf (stderr, "%5d-%-5d%12lu ", cnt, cnt + 15,
903 (unsigned long int) histogram[cnt / 16]);
904 if (percent == 0)
905 fputs (" <1% \e[41;37m", stderr);
906 else
907 fprintf (stderr, "%3d%% \e[41;37m", percent);
908
909 /* Draw a bar with a length corresponding to the current
910 percentage. */
911 percent = (histogram[cnt / 16] * 50) / maxcalls;
912 while (percent-- > 0)
913 fputc ('=', stderr);
914 fputs ("\e[0;0m\n", stderr);
915 }
916
917 if (large != 0)
918 {
919 percent = (large * 100) / calls_total;
920 fprintf (stderr, " large %12lu ", (unsigned long int) large);
921 if (percent == 0)
922 fputs (" <1% \e[41;37m", stderr);
923 else
924 fprintf (stderr, "%3d%% \e[41;37m", percent);
925 percent = (large * 50) / maxcalls;
926 while (percent-- > 0)
927 fputc ('=', stderr);
928 fputs ("\e[0;0m\n", stderr);
929 }
930
931 /* Any following malloc/free etc. calls should generate statistics again,
932 because otherwise freeing something that has been malloced before
933 this destructor (including struct header in front of it) wouldn't
934 be properly freed. */
935 not_me = false;
936}
937