├── test-memprofile ├── memprofile.pdf ├── memprofile.gnuplot ├── Makefile └── test.cc ├── test-malloc_count ├── Makefile └── test.c ├── stack_count.h ├── stack_count.c ├── malloc_count.h ├── README.md ├── memprofile.h └── malloc_count.c /test-memprofile/memprofile.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bingmann/malloc_count/HEAD/test-memprofile/memprofile.pdf -------------------------------------------------------------------------------- /test-malloc_count/Makefile: -------------------------------------------------------------------------------- 1 | # Simplistic Makefile for malloc_count example 2 | 3 | CC = gcc 4 | CFLAGS = -g -W -Wall -ansi -I.. 5 | LDFLAGS = 6 | LIBS = -ldl 7 | OBJS = test.o ../malloc_count.o ../stack_count.o 8 | 9 | all: test 10 | 11 | %.o: %.c 12 | $(CC) $(CFLAGS) -c -o $@ $< 13 | 14 | test: $(OBJS) 15 | $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(OBJS) $(LIBS) 16 | 17 | clean: 18 | rm -f *.o test 19 | -------------------------------------------------------------------------------- /test-memprofile/memprofile.gnuplot: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env gnuplot 2 | 3 | set terminal pdf size 28cm,18cm linewidth 2.0 4 | set output "memprofile.pdf" 5 | 6 | set key top right 7 | set grid xtics ytics 8 | 9 | set title 'Memory Profile of Test Program' 10 | set xlabel 'Time [s]' 11 | set ylabel 'Memory Usage [MiB]' 12 | 13 | plot \ 14 | 'memprofile.txt' using 1:($2 / 1024/1024) title 'memprofile' with lines 15 | -------------------------------------------------------------------------------- /test-memprofile/Makefile: -------------------------------------------------------------------------------- 1 | # Simplistic Makefile for malloc_count example 2 | 3 | CC = gcc 4 | CXX = g++ 5 | CFLAGS = -g -W -Wall -ansi -I.. 6 | CXXFLAGS = -g -W -Wall -ansi -I.. 7 | LDFLAGS = 8 | LIBS = -ldl 9 | OBJS = test.o ../malloc_count.o 10 | 11 | all: test 12 | 13 | %.o: %.c 14 | $(CC) $(CFLAGS) -c -o $@ $< 15 | 16 | %.o: %.cc 17 | $(CXX) $(CXXFLAGS) -c -o $@ $< 18 | 19 | test: $(OBJS) 20 | $(CXX) $(CXXFLAGS) $(LDFLAGS) -o $@ $(OBJS) $(LIBS) 21 | 22 | clean: 23 | rm -f *.o test 24 | -------------------------------------------------------------------------------- /test-memprofile/test.cc: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * test-memprofile/test.cc 3 | * 4 | * Example to write a memory profile. 5 | * 6 | ****************************************************************************** 7 | * Copyright (C) 2013 Timo Bingmann 8 | * 9 | * This program is free software: you can redistribute it and/or modify it 10 | * under the terms of the GNU General Public License as published by the Free 11 | * Software Foundation, either version 3 of the License, or (at your option) 12 | * any later version. 13 | * 14 | * This program is distributed in the hope that it will be useful, but WITHOUT 15 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 16 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 17 | * more details. 18 | * 19 | * You should have received a copy of the GNU General Public License along with 20 | * this program. If not, see . 21 | *****************************************************************************/ 22 | 23 | #include "memprofile.h" 24 | 25 | #include 26 | #include 27 | 28 | int main() 29 | { 30 | MemProfile mp("memprofile.txt", 0.1, 1024); 31 | 32 | { 33 | std::vector v; 34 | for (size_t i = 0; i < 10000000; ++i) 35 | v.push_back(i); 36 | } 37 | 38 | { 39 | std::set v; 40 | for (size_t i = 0; i < 200000; ++i) 41 | v.insert(i); 42 | } 43 | 44 | return 0; 45 | } 46 | -------------------------------------------------------------------------------- /stack_count.h: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * stack_count.h 3 | * 4 | * Header containing two functions to monitor stack usage of a program. 5 | * 6 | ****************************************************************************** 7 | * Copyright (C) 2013 Timo Bingmann 8 | * 9 | * Permission is hereby granted, free of charge, to any person obtaining a copy 10 | * of this software and associated documentation files (the "Software"), to 11 | * deal in the Software without restriction, including without limitation the 12 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 13 | * sell copies of the Software, and to permit persons to whom the Software is 14 | * furnished to do so, subject to the following conditions: 15 | * 16 | * The above copyright notice and this permission notice shall be included in 17 | * all copies or substantial portions of the Software. 18 | * 19 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 24 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 25 | * IN THE SOFTWARE. 26 | *****************************************************************************/ 27 | 28 | #ifndef _STACK_COUNT_H_ 29 | #define _STACK_COUNT_H_ 30 | 31 | #include 32 | 33 | #ifdef __cplusplus 34 | extern "C" { /* for inclusion from C++ */ 35 | #endif 36 | 37 | /* "clear" the stack by writing a sentinel value into it. */ 38 | extern void* stack_count_clear(void); 39 | 40 | /* checks the maximum usage of the stack since the last clear call. */ 41 | extern size_t stack_count_usage(void* lastbase); 42 | 43 | #ifdef __cplusplus 44 | } /* extern "C" */ 45 | #endif 46 | 47 | #endif /* _STACK_COUNT_H_ */ 48 | 49 | /*****************************************************************************/ 50 | -------------------------------------------------------------------------------- /stack_count.c: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * stack_count.c 3 | * 4 | * Header containing two functions to monitor stack usage of a program. 5 | * 6 | ****************************************************************************** 7 | * Copyright (C) 2013 Timo Bingmann 8 | * 9 | * Permission is hereby granted, free of charge, to any person obtaining a copy 10 | * of this software and associated documentation files (the "Software"), to 11 | * deal in the Software without restriction, including without limitation the 12 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 13 | * sell copies of the Software, and to permit persons to whom the Software is 14 | * furnished to do so, subject to the following conditions: 15 | * 16 | * The above copyright notice and this permission notice shall be included in 17 | * all copies or substantial portions of the Software. 18 | * 19 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 24 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 25 | * IN THE SOFTWARE. 26 | *****************************************************************************/ 27 | 28 | #include "stack_count.h" 29 | 30 | #include 31 | 32 | /* default stack size on Linux is 8 MiB, so fill 75% of it. */ 33 | static const size_t stacksize = 6*1024*1024; 34 | 35 | /* "clear" the stack by writing a sentinel value into it. */ 36 | void* stack_count_clear(void) 37 | { 38 | const size_t asize = stacksize / sizeof(uint32_t); 39 | uint32_t stack[asize]; /* allocated on stack */ 40 | uint32_t* p = stack; 41 | while ( p < stack + asize ) *p++ = 0xDEADC0DEu; 42 | return p; 43 | } 44 | 45 | /* checks the maximum usage of the stack since the last clear call. */ 46 | size_t stack_count_usage(void* lastbase) 47 | { 48 | const size_t asize = stacksize / sizeof(uint32_t); 49 | uint32_t* p = (uint32_t*)lastbase - asize; /* calculate top of last clear */ 50 | while ( *p == 0xDEADC0DEu ) ++p; 51 | return ((uint32_t*)lastbase - p) * sizeof(uint32_t); 52 | } 53 | 54 | /*****************************************************************************/ 55 | -------------------------------------------------------------------------------- /test-malloc_count/test.c: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * test-malloc_count/test.c 3 | * 4 | * Small program to test malloc_count hooks and user functions. 5 | * 6 | ****************************************************************************** 7 | * Copyright (C) 2013 Timo Bingmann 8 | * 9 | * Permission is hereby granted, free of charge, to any person obtaining a copy 10 | * of this software and associated documentation files (the "Software"), to 11 | * deal in the Software without restriction, including without limitation the 12 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 13 | * sell copies of the Software, and to permit persons to whom the Software is 14 | * furnished to do so, subject to the following conditions: 15 | * 16 | * The above copyright notice and this permission notice shall be included in 17 | * all copies or substantial portions of the Software. 18 | * 19 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 24 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 25 | * IN THE SOFTWARE. 26 | *****************************************************************************/ 27 | 28 | #include "malloc_count.h" 29 | #include "stack_count.h" 30 | 31 | #include 32 | #include 33 | #include 34 | 35 | void function_use_stack() 36 | { 37 | char data[64*1024]; 38 | memset(data, 1, sizeof(data)); 39 | } 40 | 41 | int main() 42 | { 43 | /* allocate and free some memory */ 44 | void* a = malloc(2*1024*1024); 45 | free(a); 46 | 47 | /* query malloc_count for information */ 48 | printf("our peak memory allocation: %lld\n", 49 | (long long)malloc_count_peak()); 50 | 51 | /* use realloc() */ 52 | void* b = malloc(3*1024*1024); 53 | malloc_count_print_status(); 54 | 55 | b = realloc(b, 2*1024*1024); 56 | malloc_count_print_status(); 57 | 58 | b = realloc(b, 4*1024*1024); 59 | malloc_count_print_status(); 60 | 61 | free(b); 62 | 63 | /* some unusual realloc calls */ 64 | void* c = realloc(NULL, 1*1024*1024); 65 | c = realloc(c, 0); 66 | 67 | /* show how stack_count works */ 68 | { 69 | void* base = stack_count_clear(); 70 | function_use_stack(); 71 | printf("maximum stack usage: %lld\n", 72 | (long long)stack_count_usage(base)); 73 | } 74 | 75 | return 0; 76 | } 77 | 78 | /*****************************************************************************/ 79 | -------------------------------------------------------------------------------- /malloc_count.h: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * malloc_count.h 3 | * 4 | * Header containing prototypes of user-callable functions to retrieve run-time 5 | * information about malloc()/free() allocation. 6 | * 7 | ****************************************************************************** 8 | * Copyright (C) 2013 Timo Bingmann 9 | * 10 | * Permission is hereby granted, free of charge, to any person obtaining a copy 11 | * of this software and associated documentation files (the "Software"), to 12 | * deal in the Software without restriction, including without limitation the 13 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 14 | * sell copies of the Software, and to permit persons to whom the Software is 15 | * furnished to do so, subject to the following conditions: 16 | * 17 | * The above copyright notice and this permission notice shall be included in 18 | * all copies or substantial portions of the Software. 19 | * 20 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 25 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 26 | * IN THE SOFTWARE. 27 | *****************************************************************************/ 28 | 29 | #ifndef _MALLOC_COUNT_H_ 30 | #define _MALLOC_COUNT_H_ 31 | 32 | #include 33 | 34 | #ifdef __cplusplus 35 | extern "C" { /* for inclusion from C++ */ 36 | #endif 37 | 38 | /* returns the currently allocated amount of memory */ 39 | extern size_t malloc_count_current(void); 40 | 41 | /* returns the current peak memory allocation */ 42 | extern size_t malloc_count_peak(void); 43 | 44 | /* resets the peak memory allocation to current */ 45 | extern void malloc_count_reset_peak(void); 46 | 47 | /* returns the total number of allocations */ 48 | extern size_t malloc_count_num_allocs(void); 49 | 50 | /* typedef of callback function */ 51 | typedef void (*malloc_count_callback_type)(void* cookie, size_t current); 52 | 53 | /* supply malloc_count with a callback function that is invoked on each change 54 | * of the current allocation. The callback function must not use 55 | * malloc()/realloc()/free() or it will go into an endless recursive loop! */ 56 | extern void malloc_count_set_callback(malloc_count_callback_type cb, 57 | void* cookie); 58 | 59 | /* user function which prints current and peak allocation to stderr */ 60 | extern void malloc_count_print_status(void); 61 | 62 | #ifdef __cplusplus 63 | } /* extern "C" */ 64 | #endif 65 | 66 | #endif /* _MALLOC_COUNT_H_ */ 67 | 68 | /*****************************************************************************/ 69 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # README for malloc_count # 2 | 3 | `malloc_count` provides a set of source code tools to **measure the amount of 4 | allocated memory of a program at run-time**. The code library provides 5 | facilities to 6 | 7 | * measure the **current and peak** heap memory allocation, and 8 | * write a **memory profile** for plotting, see the figure on the right. 9 | * Furthermore, separate `stack_count` function can measure **stack usage**. 10 | 11 | The code tool works by intercepting the standard `malloc()`, `free()`, etc 12 | functions. Thus **no changes** are necessary to the inspected source code. 13 | 14 | See for the current verison. 15 | 16 | ## Intercepting Heap Allocation Functions ## 17 | 18 | The source code of `malloc_count.[ch]` intercepts the standard heap allocation 19 | functions `malloc()`, `free()`, `realloc()` and `calloc()` and adds simple 20 | counting statistics to each call. Thus the program must be relinked for 21 | `malloc_count` to work. Each call to `malloc()` and others is passed on to 22 | lower levels, and the regular `malloc()` is used for heap allocation. 23 | 24 | Of course, `malloc_count` can also be used with C++ programs and maybe even 25 | script languages like Python and Perl, because the `new` operator and most 26 | script interpreters allocations all are based on `malloc`. 27 | 28 | The tools are usable under Linux and probably also with Cygwin and MinGW, as 29 | they too support the standard Linux dynamic link loading mechanisms. 30 | 31 | ## Memory Profile and `stack_count` 32 | 33 | The `malloc_count` source is accompanied by two further memory analysis tools: 34 | `stack_count` and a C++ header called `memprofile.h`. 35 | 36 | In `stack_count.[ch]` two simple functions are provided that can measure the 37 | **maximum stack usage** between two points in a program. 38 | 39 | Maybe the most useful application of `malloc_count` is to create a 40 | **memory/heap profile** of a program (while it is running). This profile can 41 | also be created using the well-known 42 | [valgrind tool "massif"](http://valgrind.org/docs/manual/ms-manual.html), 43 | however, massif is really slow. The overhead of `malloc_count` is much smaller, 44 | and using `memprofile.h` a statistic file can be produced, which is directly 45 | usable with Gnuplot. 46 | 47 | The source code archive contains two example applications: one which queries 48 | `malloc_count` for current heap usage, and a second which creates the memory 49 | profile in the figure on the right. See the STX B+ Tree library for another, 50 | more complex example of a memory profile. 51 | 52 | ## Downloads ## 53 | 54 | See for the current verison. 55 | 56 | The source code is published under the 57 | [MIT License (MIT)](http://opensource.org/licenses/MIT), which is also found in 58 | the header of all source files. 59 | 60 | ## Short Usage Guide ## 61 | 62 | Compile `malloc_count.c` and link it with your program. The source file 63 | `malloc_count.o` should be located towards the end of the `.o` file 64 | sequence. You must also add "`-ldl`" to the list of libraries. 65 | 66 | Run your program and observe that when terminating, it outputs a line like 67 | 68 | malloc_count ### exiting, total: 12,582,912, peak: 4,194,304, current: 0 69 | 70 | If desired, increase verbosity 71 | 72 | 1. by setting `log_operations = 1` at the top of `malloc_count.c` and adapting 73 | `log_operations_threshold` to output only large allocations, or 74 | 2. by including `malloc_count.h` in your program and using the user-functions 75 | define therein to output memory usage at specific checkpoints. See the 76 | directory `test-malloc_count/` in the source code for an example. 77 | 78 | Tip: Set the locale environment variable `LC_NUMERIC=en_GB` or similar to get 79 | comma-separation of thousands in the printed numbers. 80 | 81 | The directory `test-memprofile/` contains a simple program, which fills a 82 | `std::vector` and `std::set` with integers. The memory usage of these 83 | containers is profiled using the facilities of `memprofile.h`, which are 84 | described verbosely in the source. 85 | 86 | ## Thread Safety ## 87 | 88 | The current statistic methods in `malloc_count.c` are **not thread-safe**. 89 | However, the general mechanism (as described below) is per-se thread-safe. The 90 | only non-safe parts are adding and subtracting from the counters in 91 | `inc_count()` and `dec_count()`. 92 | 93 | The `malloc_count.c` code contains a `#define THREAD_SAFE_GCC_INTRINSICS`, 94 | which enables use of gcc's intrinsics for atomic counting operations. If you 95 | use gcc, enable this option to make the `malloc_count` tool thread-safe. 96 | 97 | The functions in `memprofile.h` are not thread-safe. `stack_count` can also be 98 | used on local thread stacks. 99 | 100 | ## Technicalities of Intercepting `libc` Function Calls ## 101 | 102 | The method used in `malloc_count` to hook the standard heap allocation calls is 103 | to provide a source file exporting the symbols "`malloc`", "`free`", etc. These 104 | override the libc symbols and thus the functions in `malloc_count` are used 105 | instead. 106 | 107 | However, `malloc_count` does not implement a heap allocator. It loads the symbols 108 | "`malloc`", "`free`", etc. directly using the dynamic link loader "`dl`" from the 109 | chain of shared libraries. Calls to the overriding "`malloc`" functions are 110 | forwarded to the usual libc allocator. 111 | 112 | To keep track of the size of each allocated memory area, `malloc_count` uses a 113 | trick: it prepends each allocation pointer with two additional bookkeeping 114 | variables: the allocation size and a sentinel value. Thus when allocating *n* 115 | bytes, in truth *n + c* bytes are requested from the libc `malloc()` to save the 116 | size (*c* is by default 16, but can be adapted to fix alignment problems). The 117 | sentinel only serves as a check that your program has not overwritten the size 118 | information. 119 | 120 | ## Closing Credits ## 121 | 122 | The idea for this augmenting interception method is not my own, it was borrowed 123 | from Jeremy Kerr . 124 | 125 | Written 2013-01-21, 2013-03-16, and 2014-09-10 by Timo Bingmann 126 | -------------------------------------------------------------------------------- /memprofile.h: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * memprofile.h 3 | * 4 | * Class to write the datafile for a memory profile plot using malloc_count. 5 | * 6 | ****************************************************************************** 7 | * Copyright (C) 2013 Timo Bingmann 8 | * 9 | * This program is free software: you can redistribute it and/or modify it 10 | * under the terms of the GNU General Public License as published by the Free 11 | * Software Foundation, either version 3 of the License, or (at your option) 12 | * any later version. 13 | * 14 | * This program is distributed in the hope that it will be useful, but WITHOUT 15 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 16 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 17 | * more details. 18 | * 19 | * You should have received a copy of the GNU General Public License along with 20 | * this program. If not, see . 21 | *****************************************************************************/ 22 | 23 | #ifndef _MEM_PROFILE_H_ 24 | #define _MEM_PROFILE_H_ 25 | 26 | #include 27 | #include 28 | 29 | #include "malloc_count.h" 30 | 31 | /** 32 | * MemProfile is a class which hooks into malloc_count's callback and writes a 33 | * heap usage profile at run-time. 34 | * 35 | * A usual application will have many heap allocations and deallocations, 36 | * therefore these must be aggregated to create a useful plot. This is the main 37 | * purposes of MemProfile. However, the "resolution" of discrete aggregation 38 | * intervals must be configured manually, as they highly depend on the profiled 39 | * application. 40 | */ 41 | class MemProfile 42 | { 43 | protected: 44 | 45 | /// output time resolution 46 | double m_time_resolution; 47 | /// output memory resolution 48 | size_t m_size_resolution; 49 | 50 | /// function marker for multi-output 51 | const char* m_funcname; 52 | /// output file 53 | FILE* m_file; 54 | 55 | /// start of current memprofile 56 | double m_base_ts; 57 | /// start memory usage of current memprofile 58 | size_t m_base_mem; 59 | /// start stack pointer of memprofile 60 | char* m_stack_base; 61 | 62 | /// timestamp of previous log output 63 | double m_prev_ts; 64 | /// memory usage of previous log output 65 | size_t m_prev_mem; 66 | /// maximum memory usage to previous log output 67 | size_t m_max; 68 | 69 | protected: 70 | 71 | /// template function missing in cmath, absolute difference 72 | template 73 | static inline Type absdiff(const Type& a, const Type& b) 74 | { 75 | return (a < b) ? (b - a) : (a - b); 76 | } 77 | 78 | /// time is measured using gettimeofday() or omp_get_wtime() 79 | static inline double timestamp() 80 | { 81 | #ifdef _OPENMP 82 | return omp_get_wtime(); 83 | #else 84 | struct timeval tv; 85 | gettimeofday(&tv, NULL); 86 | return tv.tv_sec + tv.tv_usec / 1e6; 87 | #endif 88 | } 89 | 90 | /// output a data pair (ts,mem) to log file 91 | inline void output(double ts, unsigned long long mem) 92 | { 93 | if (m_funcname) { // more verbose output format 94 | fprintf(m_file, "func=%s ts=%g mem=%llu\n", 95 | m_funcname, ts - m_base_ts, mem); 96 | } 97 | else { // simple gnuplot output 98 | fprintf(m_file, "%g %llu\n", 99 | ts - m_base_ts, mem); 100 | } 101 | } 102 | 103 | /// callback invoked by malloc_count when heap usage changes. 104 | inline void callback(size_t memcurr) 105 | { 106 | size_t mem = (memcurr > m_base_mem) ? (memcurr - m_base_mem) : 0; 107 | 108 | if ((char*)&mem < m_stack_base) // add stack usage 109 | mem += m_stack_base - (char*)&mem; 110 | 111 | double ts = timestamp(); 112 | if (m_max < mem) m_max = mem; // keep max usage to last output 113 | 114 | // check to output a pair 115 | if (ts - m_prev_ts > m_time_resolution || 116 | absdiff(mem, m_prev_mem) > m_size_resolution ) 117 | { 118 | output(ts, m_max); 119 | m_max = 0; 120 | m_prev_ts = ts; 121 | m_prev_mem = mem; 122 | } 123 | } 124 | 125 | /// static callback for malloc_count, forwards to class method. 126 | static void static_callback(void* cookie, size_t memcurr) 127 | { 128 | return static_cast(cookie)->callback(memcurr); 129 | } 130 | 131 | public: 132 | 133 | /** Constructor for MemProfile. 134 | * @param filepath file to write memprofile log entries to. 135 | * @param time_resolution resolution when a log entry is always written. 136 | * @param size_resolution resolution when a log entry is always written. 137 | * @param funcname enables multi-function output, appends to file. 138 | */ 139 | MemProfile(const char* filepath, 140 | double time_resolution = 0.1, size_t size_resolution = 1024, 141 | const char* funcname = NULL) 142 | : m_time_resolution( time_resolution ), 143 | m_size_resolution( size_resolution ), 144 | m_funcname( funcname ), 145 | m_base_ts( timestamp() ), 146 | m_base_mem( malloc_count_current() ), 147 | m_prev_ts( 0 ), 148 | m_prev_mem( 0 ), 149 | m_max( 0 ) 150 | { 151 | char stack; 152 | m_stack_base = &stack; 153 | m_file = fopen(filepath, funcname ? "a" : "w"); 154 | malloc_count_set_callback(MemProfile::static_callback, this); 155 | } 156 | 157 | /// Destructor flushes currently aggregated values and closes the file. 158 | ~MemProfile() 159 | { 160 | m_prev_ts = 0; // force flush 161 | m_prev_mem = 0; 162 | callback( malloc_count_current() ); 163 | malloc_count_set_callback(NULL, NULL); 164 | fclose(m_file); 165 | } 166 | }; 167 | 168 | #endif // _MEM_PROFILE_H_ 169 | 170 | /*****************************************************************************/ 171 | -------------------------------------------------------------------------------- /malloc_count.c: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * malloc_count.c 3 | * 4 | * malloc() allocation counter based on http://ozlabs.org/~jk/code/ and other 5 | * code preparing LD_PRELOAD shared objects. 6 | * 7 | ****************************************************************************** 8 | * Copyright (C) 2013-2014 Timo Bingmann 9 | * 10 | * Permission is hereby granted, free of charge, to any person obtaining a copy 11 | * of this software and associated documentation files (the "Software"), to 12 | * deal in the Software without restriction, including without limitation the 13 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 14 | * sell copies of the Software, and to permit persons to whom the Software is 15 | * furnished to do so, subject to the following conditions: 16 | * 17 | * The above copyright notice and this permission notice shall be included in 18 | * all copies or substantial portions of the Software. 19 | * 20 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 25 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 26 | * IN THE SOFTWARE. 27 | *****************************************************************************/ 28 | 29 | #define _GNU_SOURCE 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | #include "malloc_count.h" 37 | 38 | /* user-defined options for output malloc()/free() operations to stderr */ 39 | 40 | static const int log_operations = 0; /* <-- set this to 1 for log output */ 41 | static const size_t log_operations_threshold = 1024*1024; 42 | 43 | /* option to use gcc's intrinsics to do thread-safe statistics operations */ 44 | #define THREAD_SAFE_GCC_INTRINSICS 0 45 | 46 | /* to each allocation additional data is added for bookkeeping. due to 47 | * alignment requirements, we can optionally add more than just one integer. */ 48 | static const size_t alignment = 16; /* bytes (>= 2*sizeof(size_t)) */ 49 | 50 | /* function pointer to the real procedures, loaded using dlsym */ 51 | typedef void* (*malloc_type)(size_t); 52 | typedef void (*free_type)(void*); 53 | typedef void* (*realloc_type)(void*, size_t); 54 | 55 | static malloc_type real_malloc = NULL; 56 | static free_type real_free = NULL; 57 | static realloc_type real_realloc = NULL; 58 | 59 | /* a sentinel value prefixed to each allocation */ 60 | static const size_t sentinel = 0xDEADC0DE; 61 | 62 | /* a simple memory heap for allocations prior to dlsym loading */ 63 | #define INIT_HEAP_SIZE 1024*1024 64 | static char init_heap[INIT_HEAP_SIZE]; 65 | static size_t init_heap_use = 0; 66 | static const int log_operations_init_heap = 0; 67 | 68 | /* output */ 69 | #define PPREFIX "malloc_count ### " 70 | 71 | /*****************************************/ 72 | /* run-time memory allocation statistics */ 73 | /*****************************************/ 74 | 75 | static long long peak = 0, curr = 0, total = 0, num_allocs = 0; 76 | 77 | static malloc_count_callback_type callback = NULL; 78 | static void* callback_cookie = NULL; 79 | 80 | /* add allocation to statistics */ 81 | static void inc_count(size_t inc) 82 | { 83 | #if THREAD_SAFE_GCC_INTRINSICS 84 | long long mycurr = __sync_add_and_fetch(&curr, inc); 85 | if (mycurr > peak) peak = mycurr; 86 | total += inc; 87 | if (callback) callback(callback_cookie, mycurr); 88 | #else 89 | if ((curr += inc) > peak) peak = curr; 90 | total += inc; 91 | if (callback) callback(callback_cookie, curr); 92 | #endif 93 | ++num_allocs; 94 | } 95 | 96 | /* decrement allocation to statistics */ 97 | static void dec_count(size_t dec) 98 | { 99 | #if THREAD_SAFE_GCC_INTRINSICS 100 | long long mycurr = __sync_sub_and_fetch(&curr, dec); 101 | if (callback) callback(callback_cookie, mycurr); 102 | #else 103 | curr -= dec; 104 | if (callback) callback(callback_cookie, curr); 105 | #endif 106 | } 107 | 108 | /* user function to return the currently allocated amount of memory */ 109 | extern size_t malloc_count_current(void) 110 | { 111 | return curr; 112 | } 113 | 114 | /* user function to return the peak allocation */ 115 | extern size_t malloc_count_peak(void) 116 | { 117 | return peak; 118 | } 119 | 120 | /* user function to reset the peak allocation to current */ 121 | extern void malloc_count_reset_peak(void) 122 | { 123 | peak = curr; 124 | } 125 | 126 | /* user function to return total number of allocations */ 127 | extern size_t malloc_count_num_allocs(void) 128 | { 129 | return num_allocs; 130 | } 131 | 132 | /* user function which prints current and peak allocation to stderr */ 133 | extern void malloc_count_print_status(void) 134 | { 135 | fprintf(stderr, PPREFIX "current %'lld, peak %'lld\n", 136 | curr, peak); 137 | } 138 | 139 | /* user function to supply a memory profile callback */ 140 | void malloc_count_set_callback(malloc_count_callback_type cb, void* cookie) 141 | { 142 | callback = cb; 143 | callback_cookie = cookie; 144 | } 145 | 146 | /****************************************************/ 147 | /* exported symbols that overlay the libc functions */ 148 | /****************************************************/ 149 | 150 | /* exported malloc symbol that overrides loading from libc */ 151 | extern void* malloc(size_t size) 152 | { 153 | void* ret; 154 | 155 | if (size == 0) return NULL; 156 | 157 | if (real_malloc) 158 | { 159 | /* call read malloc procedure in libc */ 160 | ret = (*real_malloc)(alignment + size); 161 | 162 | inc_count(size); 163 | if (log_operations && size >= log_operations_threshold) { 164 | fprintf(stderr, PPREFIX "malloc(%'lld) = %p (current %'lld)\n", 165 | (long long)size, (char*)ret + alignment, curr); 166 | } 167 | 168 | /* prepend allocation size and check sentinel */ 169 | *(size_t*)ret = size; 170 | *(size_t*)((char*)ret + alignment - sizeof(size_t)) = sentinel; 171 | 172 | return (char*)ret + alignment; 173 | } 174 | else 175 | { 176 | if (init_heap_use + alignment + size > INIT_HEAP_SIZE) { 177 | fprintf(stderr, PPREFIX "init heap full !!!\n"); 178 | exit(EXIT_FAILURE); 179 | } 180 | 181 | ret = init_heap + init_heap_use; 182 | init_heap_use += alignment + size; 183 | 184 | /* prepend allocation size and check sentinel */ 185 | *(size_t*)ret = size; 186 | *(size_t*)((char*)ret + alignment - sizeof(size_t)) = sentinel; 187 | 188 | if (log_operations_init_heap) { 189 | fprintf(stderr, PPREFIX "malloc(%'lld) = %p on init heap\n", 190 | (long long)size, (char*)ret + alignment); 191 | } 192 | 193 | return (char*)ret + alignment; 194 | } 195 | } 196 | 197 | /* exported free symbol that overrides loading from libc */ 198 | extern void free(void* ptr) 199 | { 200 | size_t size; 201 | 202 | if (!ptr) return; /* free(NULL) is no operation */ 203 | 204 | if ((char*)ptr >= init_heap && 205 | (char*)ptr <= init_heap + init_heap_use) 206 | { 207 | if (log_operations_init_heap) { 208 | fprintf(stderr, PPREFIX "free(%p) on init heap\n", ptr); 209 | } 210 | return; 211 | } 212 | 213 | if (!real_free) { 214 | fprintf(stderr, PPREFIX 215 | "free(%p) outside init heap and without real_free !!!\n", ptr); 216 | return; 217 | } 218 | 219 | ptr = (char*)ptr - alignment; 220 | 221 | if (*(size_t*)((char*)ptr + alignment - sizeof(size_t)) != sentinel) { 222 | fprintf(stderr, PPREFIX 223 | "free(%p) has no sentinel !!! memory corruption?\n", ptr); 224 | } 225 | 226 | size = *(size_t*)ptr; 227 | dec_count(size); 228 | 229 | if (log_operations && size >= log_operations_threshold) { 230 | fprintf(stderr, PPREFIX "free(%p) -> %'lld (current %'lld)\n", 231 | ptr, (long long)size, curr); 232 | } 233 | 234 | (*real_free)(ptr); 235 | } 236 | 237 | /* exported calloc() symbol that overrides loading from libc, implemented using 238 | * our malloc */ 239 | extern void* calloc(size_t nmemb, size_t size) 240 | { 241 | void* ret; 242 | size *= nmemb; 243 | if (!size) return NULL; 244 | ret = malloc(size); 245 | memset(ret, 0, size); 246 | return ret; 247 | } 248 | 249 | /* exported realloc() symbol that overrides loading from libc */ 250 | extern void* realloc(void* ptr, size_t size) 251 | { 252 | void* newptr; 253 | size_t oldsize; 254 | 255 | if ((char*)ptr >= (char*)init_heap && 256 | (char*)ptr <= (char*)init_heap + init_heap_use) 257 | { 258 | if (log_operations_init_heap) { 259 | fprintf(stderr, PPREFIX "realloc(%p) = on init heap\n", ptr); 260 | } 261 | 262 | ptr = (char*)ptr - alignment; 263 | 264 | if (*(size_t*)((char*)ptr + alignment - sizeof(size_t)) != sentinel) { 265 | fprintf(stderr, PPREFIX 266 | "realloc(%p) has no sentinel !!! memory corruption?\n", 267 | ptr); 268 | } 269 | 270 | oldsize = *(size_t*)ptr; 271 | 272 | if (oldsize >= size) { 273 | /* keep old area, just reduce the size */ 274 | *(size_t*)ptr = size; 275 | return (char*)ptr + alignment; 276 | } 277 | else { 278 | /* allocate new area and copy data */ 279 | ptr = (char*)ptr + alignment; 280 | newptr = malloc(size); 281 | memcpy(newptr, ptr, oldsize); 282 | free(ptr); 283 | return newptr; 284 | } 285 | } 286 | 287 | if (size == 0) { /* special case size == 0 -> free() */ 288 | free(ptr); 289 | return NULL; 290 | } 291 | 292 | if (ptr == NULL) { /* special case ptr == 0 -> malloc() */ 293 | return malloc(size); 294 | } 295 | 296 | ptr = (char*)ptr - alignment; 297 | 298 | if (*(size_t*)((char*)ptr + alignment - sizeof(size_t)) != sentinel) { 299 | fprintf(stderr, PPREFIX 300 | "free(%p) has no sentinel !!! memory corruption?\n", ptr); 301 | } 302 | 303 | oldsize = *(size_t*)ptr; 304 | 305 | dec_count(oldsize); 306 | inc_count(size); 307 | 308 | newptr = (*real_realloc)(ptr, alignment + size); 309 | 310 | if (log_operations && size >= log_operations_threshold) 311 | { 312 | if (newptr == ptr) 313 | fprintf(stderr, PPREFIX 314 | "realloc(%'lld -> %'lld) = %p (current %'lld)\n", 315 | (long long)oldsize, (long long)size, newptr, curr); 316 | else 317 | fprintf(stderr, PPREFIX 318 | "realloc(%'lld -> %'lld) = %p -> %p (current %'lld)\n", 319 | (long long)oldsize, (long long)size, ptr, newptr, curr); 320 | } 321 | 322 | *(size_t*)newptr = size; 323 | 324 | return (char*)newptr + alignment; 325 | } 326 | 327 | static __attribute__((constructor)) void init(void) 328 | { 329 | char *error; 330 | 331 | setlocale(LC_NUMERIC, ""); /* for better readable numbers */ 332 | 333 | dlerror(); 334 | 335 | real_malloc = (malloc_type)dlsym(RTLD_NEXT, "malloc"); 336 | if ((error = dlerror()) != NULL) { 337 | fprintf(stderr, PPREFIX "error %s\n", error); 338 | exit(EXIT_FAILURE); 339 | } 340 | 341 | real_realloc = (realloc_type)dlsym(RTLD_NEXT, "realloc"); 342 | if ((error = dlerror()) != NULL) { 343 | fprintf(stderr, PPREFIX "error %s\n", error); 344 | exit(EXIT_FAILURE); 345 | } 346 | 347 | real_free = (free_type)dlsym(RTLD_NEXT, "free"); 348 | if ((error = dlerror()) != NULL) { 349 | fprintf(stderr, PPREFIX "error %s\n", error); 350 | exit(EXIT_FAILURE); 351 | } 352 | } 353 | 354 | static __attribute__((destructor)) void finish(void) 355 | { 356 | fprintf(stderr, PPREFIX 357 | "exiting, total: %'lld, peak: %'lld, current: %'lld\n", 358 | total, peak, curr); 359 | } 360 | 361 | /*****************************************************************************/ 362 | --------------------------------------------------------------------------------