├── sample.png ├── .gitmodules ├── .gitignore ├── .editorconfig ├── .github └── workflows │ └── build.yml ├── memtrail.version ├── memtrail.h ├── Makefile ├── benchmark.cpp ├── list.h ├── README.md ├── sample.cpp ├── memtrail.cpp └── memtrail /sample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jrfonseca/memtrail/HEAD/sample.png -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "libunwind"] 2 | path = libunwind 3 | url = https://github.com/libunwind/libunwind 4 | shallow = true 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .*.sw? 2 | *.data 3 | *.dot 4 | *.json 5 | *.o 6 | *.out 7 | *.pstats 8 | *.pyc 9 | *.so 10 | benchmark 11 | gprof2dot.py 12 | local 13 | sample 14 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*.{c,h,cpp,hpp}] 6 | indent_style = space 7 | indent_size = 3 8 | 9 | [*.{markdown,md}] 10 | indent_style = space 11 | trim_trailing_whitespace = false 12 | 13 | [{*.py,memtrail}] 14 | indent_style = space 15 | indent_size = 4 16 | 17 | [*.yml] 18 | indent_style = space 19 | indent_size = 2 20 | 21 | [Makefile] 22 | indent_style = tab 23 | indent_size = 8 24 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v3 10 | with: 11 | fetch-depth: 1 12 | submodules: recursive 13 | - run: sudo apt-get update -qq 14 | - run: sudo apt-get install -qq -y --no-install-recommends autoconf automake libtool 15 | - run: make -j $(nproc) test 16 | -------------------------------------------------------------------------------- /memtrail.version: -------------------------------------------------------------------------------- 1 | { 2 | global: 3 | _init; 4 | _fini; 5 | aligned_alloc; 6 | asprintf; 7 | calloc; 8 | cfree; 9 | free; 10 | malloc; 11 | memalign; 12 | memtrail_snapshot; 13 | posix_memalign; 14 | pvalloc; 15 | realloc; 16 | reallocarray; 17 | strdup; 18 | strndup; 19 | valloc; 20 | vasprintf; 21 | _ZdaPv; 22 | _ZdaPvRKSt9nothrow_t; 23 | _ZdlPv; 24 | _ZdlPvRKSt9nothrow_t; 25 | _Znam; 26 | _ZnamRKSt9nothrow_t; 27 | _Znwm; 28 | _ZnwmRKSt9nothrow_t; 29 | _ZdaPvmSt11align_val_t; 30 | _ZdaPvSt11align_val_t; 31 | _ZdaPvSt11align_val_tRKSt9nothrow_t; 32 | _ZdlPvmSt11align_val_t; 33 | _ZdlPvSt11align_val_t; 34 | _ZdlPvSt11align_val_tRKSt9nothrow_t; 35 | _ZnamSt11align_val_t; 36 | _ZnamSt11align_val_tRKSt9nothrow_t; 37 | _ZnwmSt11align_val_t; 38 | _ZnwmSt11align_val_tRKSt9nothrow_t; 39 | local: 40 | *; 41 | }; 42 | -------------------------------------------------------------------------------- /memtrail.h: -------------------------------------------------------------------------------- 1 | /************************************************************************** 2 | * 3 | * Copyright 2011-2014 Jose Fonseca 4 | * All Rights Reserved. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | * 24 | **************************************************************************/ 25 | 26 | 27 | #ifndef _MEMTRAIL_H_ 28 | #define _MEMTRAIL_H_ 29 | 30 | 31 | #ifdef __linux__ 32 | 33 | 34 | #include 35 | #include 36 | 37 | 38 | static void 39 | _memtrail_snapshot_init(void); 40 | 41 | typedef void (*_memtrail_snapshot_ptr)(void); 42 | 43 | static _memtrail_snapshot_ptr 44 | memtrail_snapshot = &_memtrail_snapshot_init; 45 | 46 | static void 47 | _memtrail_snapshot_noop(void) { 48 | } 49 | 50 | static inline void 51 | _memtrail_snapshot_init(void) { 52 | _memtrail_snapshot_ptr ptr = (_memtrail_snapshot_ptr)(uintptr_t)dlsym(RTLD_DEFAULT, "memtrail_snapshot"); 53 | memtrail_snapshot = ptr ? ptr : &_memtrail_snapshot_noop; 54 | memtrail_snapshot(); 55 | } 56 | 57 | 58 | #else /* !__linux__ */ 59 | 60 | 61 | static void 62 | memtrail_snapshot(void) { 63 | } 64 | 65 | 66 | #endif /* !__linux__ */ 67 | 68 | 69 | #endif /* _MEMTRAIL_H_ */ 70 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | VERBOSITY ?= 0 2 | 3 | CXX ?= g++ 4 | CXXFLAGS = -Wall -fno-omit-frame-pointer -fvisibility=hidden -std=gnu++17 $(UNWIND_INCLUDES) -DVERBOSITY=$(VERBOSITY) 5 | 6 | PYTHON ?= python3 7 | 8 | all: libmemtrail.so sample benchmark 9 | 10 | libmemtrail.so: memtrail.cpp memtrail.version 11 | 12 | ifeq ($(shell test -d libunwind && echo true),true) 13 | 14 | $(info info: using local libunwind) 15 | 16 | libunwind/configure: libunwind/configure.ac 17 | autoreconf -i libunwind 18 | 19 | libunwind/Makefile: libunwind/configure 20 | cd libunwind && ./configure --disable-cxx-exceptions --disable-debug-frame --disable-block-signals --disable-shared --enable-static --with-pic --prefix=$(abspath local) 21 | 22 | local/lib/pkgconfig/libunwind.pc: libunwind/Makefile 23 | $(MAKE) -C libunwind install 24 | 25 | libmemtrail.so: local/lib/pkgconfig/libunwind.pc 26 | 27 | export PKG_CONFIG_PATH := $(abspath local)/lib/pkgconfig:$(PKG_CONFIG_PATH) 28 | 29 | else 30 | 31 | $(info info: using system's libunwind) 32 | 33 | endif 34 | 35 | libmemtrail.so: Makefile 36 | pkg-config --cflags --libs --static libunwind 37 | $(CXX) -O2 -g2 $(CXXFLAGS) -shared -fPIC -Wl,--version-script,memtrail.version -o $@ memtrail.cpp $$(pkg-config --cflags --libs --static libunwind) -ldl 38 | 39 | %: %.cpp 40 | $(CXX) -O0 -g2 -Wno-unused-result -o $@ $< -ldl 41 | 42 | gprof2dot.py: 43 | wget --quiet --timestamping https://raw.githubusercontent.com/jrfonseca/gprof2dot/main/gprof2dot.py 44 | chmod +x gprof2dot.py 45 | 46 | sample: sample.cpp memtrail.h 47 | 48 | test: libmemtrail.so sample gprof2dot.py 49 | $(RM) memtrail.data $(wildcard memtrail.*.json) $(wildcard memtrail.*.dot) 50 | $(PYTHON) memtrail record ./sample 51 | $(PYTHON) memtrail dump 52 | $(PYTHON) memtrail report --show-snapshots --show-snapshot-deltas --show-cumulative-snapshot-delta --show-maximum --show-leaks --output-graphs 53 | $(foreach LABEL, snapshot-0 snapshot-1 snapshot-1-delta maximum leaked, ./gprof2dot.py -f json memtrail.$(LABEL).json > memtrail.$(LABEL).dot ;) 54 | 55 | test-debug: libmemtrail.so sample 56 | $(RM) memtrail.data $(wildcard memtrail.*.json) $(wildcard memtrail.*.dot) 57 | $(PYTHON) memtrail record --debug ./sample 58 | 59 | bench: libmemtrail.so benchmark 60 | $(RM) memtrail.data 61 | $(PYTHON) memtrail record ./benchmark 62 | time -p $(PYTHON) memtrail report --show-maximum 63 | 64 | profile: benchmark gprof2dot.py 65 | $(PYTHON) memtrail record ./benchmark 66 | $(PYTHON) -m cProfile -o memtrail.pstats -- memtrail report --show-maximum 67 | ./gprof2dot.py -f pstats memtrail.pstats > memtrail.dot 68 | 69 | clean: 70 | $(RM) libmemtrail.so gprof2dot.py sample benchmark 71 | 72 | 73 | .PHONY: all test test-debug bench profile clean 74 | -------------------------------------------------------------------------------- /benchmark.cpp: -------------------------------------------------------------------------------- 1 | /************************************************************************** 2 | * 3 | * Copyright 2014 Jose Fonseca 4 | * All Rights Reserved. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | * 24 | **************************************************************************/ 25 | 26 | 27 | #include 28 | #include 29 | #include 30 | 31 | 32 | static size_t numAllocations = 256*1024; 33 | static size_t allocationSize = 4; 34 | static size_t leaked = 0; 35 | 36 | #define NUM_FUNCTIONS 4 37 | 38 | typedef void (*FunctionPointer)(const unsigned *i, unsigned n, bool b); 39 | 40 | extern const FunctionPointer functionPointerss[NUM_FUNCTIONS]; 41 | 42 | #define FUNCTION(_index) \ 43 | static void \ 44 | fn##_index(const unsigned *indices, unsigned depth, bool leak) { \ 45 | if (depth == 0) { \ 46 | void * ptr = malloc(allocationSize); \ 47 | if (!leak) { \ 48 | free(ptr); \ 49 | } else { \ 50 | leaked += allocationSize; \ 51 | } \ 52 | } else { \ 53 | --depth; \ 54 | functionPointerss[indices[depth]](indices, depth, leak); \ 55 | } \ 56 | } 57 | 58 | FUNCTION(0) 59 | FUNCTION(1) 60 | FUNCTION(2) 61 | FUNCTION(3) 62 | 63 | 64 | const FunctionPointer functionPointerss[NUM_FUNCTIONS] = { 65 | fn0, 66 | fn1, 67 | fn2, 68 | fn3 69 | }; 70 | 71 | 72 | #define MAX_DEPTH 8 73 | 74 | 75 | int 76 | main(int argc, char *argv[]) 77 | { 78 | if (argc > 1) { 79 | numAllocations = atol(argv[1]); 80 | if (argc > 2) { 81 | allocationSize = atol(argv[2]); 82 | } 83 | } 84 | 85 | bool leak = false; 86 | for (unsigned i = 0; i < numAllocations; ++i) { 87 | unsigned indices[MAX_DEPTH]; 88 | for (unsigned depth = 0; depth < MAX_DEPTH; ++depth) { 89 | // Random number, with non-uniform distribution 90 | unsigned index = rand() & 0xffff; 91 | index = (index * index) >> 16; 92 | index = (index * NUM_FUNCTIONS) >> 16; 93 | assert(index >= 0); 94 | assert(index < NUM_FUNCTIONS); 95 | 96 | indices[depth] = index; 97 | } 98 | 99 | leak = !leak; 100 | functionPointerss[indices[MAX_DEPTH - 1]](indices, MAX_DEPTH - 1, leak); 101 | } 102 | 103 | printf("Should leak %zu bytes...\n", leaked); 104 | 105 | return 0; 106 | } 107 | 108 | 109 | // vim:set sw=3 ts=3 et: 110 | -------------------------------------------------------------------------------- /list.h: -------------------------------------------------------------------------------- 1 | /************************************************************************** 2 | * 3 | * Copyright 2006 Tungsten Graphics, Inc., Bismarck, ND. USA. 4 | * All Rights Reserved. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a 7 | * copy of this software and associated documentation files (the 8 | * "Software"), to deal in the Software without restriction, including 9 | * without limitation the rights to use, copy, modify, merge, publish, 10 | * distribute, sub license, and/or sell copies of the Software, and to 11 | * permit persons to whom the Software is furnished to do so, subject to 12 | * the following conditions: 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL 17 | * THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, 18 | * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 19 | * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 20 | * USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | * 22 | * The above copyright notice and this permission notice (including the 23 | * next paragraph) shall be included in all copies or substantial portions 24 | * of the Software. 25 | * 26 | **************************************************************************/ 27 | 28 | /** 29 | * \file 30 | * List macros heavily inspired by the Linux kernel 31 | * list handling. No list looping yet. 32 | * 33 | * Is not threadsafe, so common operations need to 34 | * be protected using an external mutex. 35 | */ 36 | 37 | #ifndef _LIST_H_ 38 | #define _LIST_H_ 39 | 40 | 41 | #include 42 | 43 | 44 | struct list_head 45 | { 46 | struct list_head *prev; 47 | struct list_head *next; 48 | }; 49 | 50 | static inline void 51 | list_inithead(struct list_head *item) 52 | { 53 | item->prev = item; 54 | item->next = item; 55 | } 56 | 57 | static inline void 58 | list_add(struct list_head *item, struct list_head *list) 59 | { 60 | item->prev = list; 61 | item->next = list->next; 62 | list->next->prev = item; 63 | list->next = item; 64 | } 65 | 66 | static inline void 67 | list_addtail(struct list_head *item, struct list_head *list) 68 | { 69 | item->next = list; 70 | item->prev = list->prev; 71 | list->prev->next = item; 72 | list->prev = item; 73 | } 74 | 75 | static inline void 76 | list_replace(struct list_head *from, struct list_head *to) 77 | { 78 | to->prev = from->prev; 79 | to->next = from->next; 80 | from->next->prev = to; 81 | from->prev->next = to; 82 | } 83 | 84 | static inline void 85 | list_del(struct list_head *item) 86 | { 87 | item->prev->next = item->next; 88 | item->next->prev = item->prev; 89 | } 90 | 91 | static inline void 92 | list_delinit(struct list_head *item) 93 | { 94 | item->prev->next = item->next; 95 | item->next->prev = item->prev; 96 | item->next = item; 97 | item->prev = item; 98 | } 99 | 100 | #define LIST_ENTRY(__type, __item, __field) \ 101 | ((__type *)(((char *)(__item)) - offsetof(__type, __field))) 102 | 103 | #define LIST_IS_EMPTY(__list) \ 104 | ((__list)->next == (__list)) 105 | 106 | #ifndef container_of 107 | #define container_of(ptr, sample, member) \ 108 | (void *)((char *)(ptr) \ 109 | - ((char *)&(sample)->member - (char *)(sample))) 110 | #endif 111 | 112 | #define LIST_FOR_EACH_ENTRY(pos, head, member) \ 113 | for (pos = container_of((head)->next, pos, member); \ 114 | &pos->member != (head); \ 115 | pos = container_of(pos->member.next, pos, member)) 116 | 117 | #define LIST_FOR_EACH_ENTRY_SAFE(pos, storage, head, member) \ 118 | for (pos = container_of((head)->next, pos, member), \ 119 | storage = container_of(pos->member.next, pos, member); \ 120 | &pos->member != (head); \ 121 | pos = storage, storage = container_of(storage->member.next, storage, member)) 122 | 123 | #define LIST_FOR_EACH_ENTRY_SAFE_REV(pos, storage, head, member) \ 124 | for (pos = container_of((head)->prev, pos, member), \ 125 | storage = container_of(pos->member.prev, pos, member); \ 126 | &pos->member != (head); \ 127 | pos = storage, storage = container_of(storage->member.prev, storage, member)) 128 | 129 | #define LIST_FOR_EACH_ENTRY_FROM(pos, start, head, member) \ 130 | for (pos = container_of((start), pos, member); \ 131 | &pos->member != (head); \ 132 | pos = container_of(pos->member.next, pos, member)) 133 | 134 | #define LIST_FOR_EACH_ENTRY_FROM_REV(pos, start, head, member) \ 135 | for (pos = container_of((start), pos, member); \ 136 | &pos->member != (head); \ 137 | pos = container_of(pos->member.prev, pos, member)) 138 | 139 | #endif /*_LIST_H_*/ 140 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | About 2 | ===== 3 | 4 | **memtrail** is a `LD_PRELOAD` based memory profiler and leak detector for Linux. 5 | 6 | There are already many other open-source memory debugging/profiling tools for 7 | Linux, several of which are listed on the [Links section below](#links), and 8 | the most powerful arguably being Valgrind. However, I needed a tool that could 9 | quantify leaks and identify memory hogs, for long-running and CPU intensive 10 | workloads, which simply run too slow under Valgrind's dynamic binary 11 | instrumentation, hence this project. 12 | 13 | [![build](https://github.com/jrfonseca/memtrail/actions/workflows/build.yml/badge.svg)](https://github.com/jrfonseca/memtrail/actions/workflows/build.yml) 14 | 15 | Features 16 | -------- 17 | 18 | * Very little runtime overhead 19 | 20 | * Will immediately output maxmimum memory allocated and total leaked memory at 21 | the end of program execution. 22 | 23 | * Text trees show callstacks for memory consuption or leaks 24 | 25 | * Can produce graphs showing flow of memory consumption or leaks 26 | 27 | 28 | Requirements 29 | ============ 30 | 31 | * Linux 32 | 33 | * Python 3 34 | 35 | * gzip 36 | 37 | * binutils' addr2line 38 | 39 | * [libunwind](http://www.nongnu.org/libunwind/) (bundled as a git submodule) 40 | 41 | * [gprof2dot](https://github.com/jrfonseca/gprof2dot) for graph output 42 | 43 | 44 | Build 45 | ===== 46 | 47 | make 48 | 49 | 50 | Usage 51 | ===== 52 | 53 | Run the application you want to debug as 54 | 55 | memtrail record /path/to/application [args...] 56 | 57 | and it will generate a record `memtrail.data` in the current 58 | directory. 59 | 60 | View results with 61 | 62 | memtrail report --show-maximum 63 | 64 | It will produce something like 65 | 66 | 67 | maximum: 8,960B 68 | -> 45.71% (4,096B, 2x): calloc 69 | | -> 45.71% (4,096B, 2x): test_calloc() [sample.cpp:82] 70 | | -> 45.71% (4,096B, 2x): main [sample.cpp:242] 71 | | -> 45.71% (4,096B, 2x): __libc_start_call_main [sysdeps/nptl/libc_start_call_main.h:58] 72 | | -> 45.71% (4,096B, 2x): call_init [csu/libc-start.c:128] 73 | | -> 45.71% (4,096B, 2x): _start 74 | | 75 | -> 31.43% (2,816B, 4x): malloc 76 | | -> 11.43% (1,024B, 1x): test_malloc() [sample.cpp:57] 77 | | | -> 11.43% (1,024B, 1x): main [sample.cpp:241] 78 | | | -> 11.43% (1,024B, 1x): __libc_start_call_main [sysdeps/nptl/libc_start_call_main.h:58] 79 | | | -> 11.43% (1,024B, 1x): call_init [csu/libc-start.c:128] 80 | | | -> 11.43% (1,024B, 1x): _start 81 | | | 82 | | -> 11.43% (1,024B, 1x): test_realloc() [sample.cpp:103] 83 | | | -> 11.43% (1,024B, 1x): main [sample.cpp:243] 84 | | | -> 11.43% (1,024B, 1x): __libc_start_call_main [sysdeps/nptl/libc_start_call_main.h:58] 85 | | | -> 11.43% (1,024B, 1x): call_init [csu/libc-start.c:128] 86 | | | -> 11.43% (1,024B, 1x): _start 87 | | | 88 | | -> 8.57% (768B, 2x): TestGlobal::TestGlobal() [sample.cpp:212] 89 | | -> 8.57% (768B, 2x): __static_initialization_and_destruction_0(int, int) [sample.cpp:225] 90 | | -> 8.57% (768B, 2x): _GLOBAL__sub_I_leaked [sample.cpp:252] 91 | | -> 8.57% (768B, 2x): call_init [csu/libc-start.c:144] 92 | | -> 8.57% (768B, 2x): _start 93 | | 94 | -> 22.86% (2,048B, 1x): realloc 95 | -> 22.86% (2,048B, 1x): test_realloc() [sample.cpp:106] 96 | -> 22.86% (2,048B, 1x): main [sample.cpp:243] 97 | -> 22.86% (2,048B, 1x): __libc_start_call_main [sysdeps/nptl/libc_start_call_main.h:58] 98 | -> 22.86% (2,048B, 1x): call_init [csu/libc-start.c:128] 99 | -> 22.86% (2,048B, 1x): _start 100 | memtrail.maximum.json written 101 | 102 | You can then use `gprof2dot.py` to obtain graphs highlighting memory leaks or 103 | consumption: 104 | 105 | gprof2dot.py -f json memtrail.maximum.json | dot -Tpng -o memtrail.maximum.png 106 | 107 | ![Sample](sample.png) 108 | 109 | 110 | It is also possible to trigger memtrail to take snapshots at specific points by 111 | calling `memtrail_snapshot` from your code: 112 | 113 | #include "memtrail.h" 114 | 115 | ... 116 | 117 | memtrail_snapshot(); 118 | 119 | 120 | Links 121 | ===== 122 | 123 | Memory debugging: 124 | 125 | * [Valgrind's Memcheck](http://valgrind.org/docs/manual/mc-manual.html) 126 | 127 | * [bcc memleak](https://github.com/iovisor/bcc) 128 | 129 | * [Google Sanitizers](https://github.com/google/sanitizers) 130 | 131 | * [duma](http://duma.sourceforge.net/) 132 | 133 | * [LeakTracer](http://www.andreasen.org/LeakTracer/) 134 | 135 | * [glibc mtrace](http://www.gnu.org/s/hello/manual/libc/Allocation-Debugging.html) 136 | 137 | * [Hans-Boehm garbage collector](http://www.hpl.hp.com/personal/Hans_Boehm/gc/leak.html) 138 | 139 | * [Leaky](http://mxr.mozilla.org/mozilla/source/tools/leaky/leaky.html) 140 | 141 | * [failmalloc](http://www.nongnu.org/failmalloc/) 142 | 143 | * [dmalloc](http://dmalloc.com/) 144 | 145 | Memory profiling: 146 | 147 | * [Valgrind's Massif](http://valgrind.org/docs/manual/ms-manual.html) 148 | 149 | * [Memory Frame Graphs](https://www.brendangregg.com/FlameGraphs/memoryflamegraphs.html) 150 | 151 | * [Heaptrack](https://github.com/KDE/heaptrack) 152 | 153 | * [Google Performance Tools' HEAPPROFILE](https://github.com/gperftools/gperftools) 154 | 155 | * [MemProf](http://www.secretlabs.de/projects/memprof/) 156 | 157 | Further links: 158 | 159 | * [cpp links](https://github.com/MattPD/cpplinks/blob/master/performance.tools.md#memory) 160 | -------------------------------------------------------------------------------- /sample.cpp: -------------------------------------------------------------------------------- 1 | /************************************************************************** 2 | * 3 | * Copyright 2011 Jose Fonseca 4 | * All Rights Reserved. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | * 24 | **************************************************************************/ 25 | 26 | 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | #include 34 | 35 | #include "memtrail.h" 36 | 37 | 38 | size_t leaked = 0; 39 | 40 | 41 | static void 42 | test_dlsym(void) 43 | { 44 | dlsym(RTLD_NEXT, "printf"); 45 | } 46 | 47 | 48 | static void 49 | test_malloc(void) 50 | { 51 | void *p; 52 | 53 | // allocate some 54 | p = malloc(1024); 55 | 56 | // leak some 57 | malloc(1024); 58 | leaked += 1024; 59 | 60 | // free some 61 | free(p); 62 | 63 | // allocate 0 bytes 64 | p = malloc(0); 65 | assert(p); 66 | free(p); 67 | 68 | // free nothing 69 | free(NULL); 70 | } 71 | 72 | 73 | static void 74 | test_calloc(void) 75 | { 76 | void *p; 77 | 78 | // allocate some 79 | p = calloc(2, 1024); 80 | 81 | // leak some 82 | calloc(2, 1024); 83 | leaked += 2 * 1024; 84 | 85 | // free some 86 | free(p); 87 | 88 | // allocate 0 bytes 89 | p = calloc(0, 1); 90 | assert(p); 91 | free(p); 92 | p = calloc(1, 0); 93 | assert(p); 94 | free(p); 95 | } 96 | 97 | 98 | static void 99 | test_realloc(void) 100 | { 101 | void *p; 102 | 103 | // allocate some 104 | p = realloc(NULL, 1024); 105 | 106 | // grow some 107 | p = realloc(p, 2048); 108 | 109 | // free some 110 | p = realloc(p, 0); 111 | assert(!p); 112 | 113 | // allocate 0 bytes 114 | p = realloc(NULL, 0); 115 | assert(p); 116 | p = realloc(p, 0); 117 | assert(!p); 118 | } 119 | 120 | 121 | size_t large = PTRDIFF_MAX; 122 | 123 | 124 | static void 125 | test_reallocarray(void) 126 | { 127 | size_t s = 128; 128 | void *p; 129 | 130 | // allocate some 131 | p = reallocarray(NULL, 1024, s); 132 | 133 | // grow some 134 | p = reallocarray(p, 2048, s); 135 | 136 | // free some 137 | p = reallocarray(p, 0, s); 138 | assert(!p); 139 | 140 | // allocate 0 bytes 141 | p = reallocarray(NULL, 0, s); 142 | assert(p); 143 | p = reallocarray(p, 0, s); 144 | assert(!p); 145 | 146 | // integer overflow 147 | p = reallocarray(p, large, large); 148 | assert(!p); 149 | } 150 | 151 | 152 | static void 153 | test_memalign(void) 154 | { 155 | void *p; 156 | void *q; 157 | 158 | // allocate some 159 | posix_memalign(&p, 16, 1024); 160 | assert(((size_t)p & 15) == 0); 161 | 162 | // leak some 163 | posix_memalign(&q, 4096, 1024); 164 | assert(((size_t)q & 4095) == 0); 165 | leaked += 1024; 166 | 167 | // free some 168 | free(p); 169 | 170 | // allocate 0 bytes 171 | posix_memalign(&p, sizeof (void*), 0); 172 | assert(p); 173 | free(p); 174 | } 175 | 176 | 177 | static void 178 | test_aligned_alloc(void) 179 | { 180 | void *p; 181 | void *q; 182 | 183 | // allocate some 184 | p = aligned_alloc(16, 1024); 185 | assert(((size_t)p & 15) == 0); 186 | 187 | // leak some 188 | q = aligned_alloc(4096, 1024); 189 | assert(((size_t)q & 4095) == 0); 190 | leaked += 1024; 191 | 192 | // free some 193 | free(p); 194 | 195 | // allocate 0 bytes 196 | p = aligned_alloc(sizeof (void*), 0); 197 | assert(p); 198 | free(p); 199 | } 200 | 201 | 202 | static void 203 | test_valloc(void) 204 | { 205 | void *p = valloc(1); 206 | assert(((size_t)p & 4095) == 0); 207 | free(p); 208 | 209 | void *q = pvalloc(1); 210 | assert(((size_t)q & 4095) == 0); 211 | free(q); 212 | } 213 | 214 | 215 | static void 216 | test_cxx(void) 217 | { 218 | char *p; 219 | char *q; 220 | 221 | // allocate some 222 | p = new char; 223 | q = new char[512]; 224 | 225 | // leak some 226 | new char; 227 | new char[512]; 228 | leaked += 1 + 512; 229 | 230 | // free some 231 | delete p; 232 | delete [] q; 233 | } 234 | 235 | 236 | struct Aligned 237 | { 238 | int dummy; 239 | } __attribute__((aligned(64))); 240 | 241 | 242 | static void 243 | test_cxx_17(void) 244 | { 245 | // allocate some 246 | auto p = new Aligned; 247 | auto q = new Aligned[512]; 248 | 249 | // leak some 250 | new Aligned; 251 | new Aligned[512]; 252 | leaked += 1 + 512; 253 | 254 | // free some 255 | delete p; 256 | delete [] q; 257 | } 258 | 259 | 260 | static void 261 | test_string(void) 262 | { 263 | char *p; 264 | int n; 265 | 266 | p = strdup("foo"); 267 | free(p); 268 | 269 | p = NULL; 270 | n = asprintf(&p, "%u", 12345); 271 | assert(n == 5); 272 | 273 | free(p); 274 | } 275 | 276 | 277 | static void 278 | test_subprocess(void) 279 | { 280 | const char *ld_preload = getenv("LD_PRELOAD"); 281 | assert(ld_preload == NULL || strstr(ld_preload, "memtrail.so") == NULL); 282 | 283 | system("env | grep LD_PRELOAD"); 284 | } 285 | 286 | 287 | static void 288 | test_snapshot(void) 289 | { 290 | memtrail_snapshot(); 291 | malloc(64); 292 | leaked += 64; 293 | memtrail_snapshot(); 294 | } 295 | 296 | 297 | class TestGlobal 298 | { 299 | public: 300 | void *p; 301 | 302 | TestGlobal() { 303 | malloc(512); 304 | leaked += 512; 305 | 306 | p = malloc(256); 307 | } 308 | 309 | ~TestGlobal() { 310 | free(p); 311 | 312 | malloc(64); 313 | leaked += 64; 314 | 315 | printf("Should leak %zu bytes...\n", leaked); 316 | } 317 | }; 318 | 319 | static TestGlobal test_global; 320 | 321 | 322 | static void 323 | test_atexit(void) 324 | { 325 | malloc(32); 326 | leaked += 32; 327 | } 328 | 329 | 330 | int 331 | main(int argc, char *argv[]) 332 | { 333 | test_dlsym(); 334 | test_malloc(); 335 | test_calloc(); 336 | test_realloc(); 337 | test_reallocarray(); 338 | test_memalign(); 339 | test_aligned_alloc(); 340 | test_valloc(); 341 | test_cxx(); 342 | test_cxx_17(); 343 | test_string(); 344 | test_subprocess(); 345 | test_snapshot(); 346 | 347 | atexit(test_atexit); 348 | 349 | return 0; 350 | } 351 | 352 | 353 | // vim:set sw=3 ts=3 et: 354 | -------------------------------------------------------------------------------- /memtrail.cpp: -------------------------------------------------------------------------------- 1 | /************************************************************************** 2 | * 3 | * Copyright 2011-2014 Jose Fonseca 4 | * All Rights Reserved. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | * 24 | **************************************************************************/ 25 | 26 | 27 | #ifndef _GNU_SOURCE 28 | #define _GNU_SOURCE 29 | #endif 30 | 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include // PIPE_BUF 45 | #include // _r_debug, link_map 46 | 47 | #define UNW_LOCAL_ONLY 48 | #include 49 | 50 | #include 51 | #include 52 | 53 | #include "list.h" 54 | 55 | 56 | #define PUBLIC __attribute__ ((visibility("default"))) 57 | #define PRIVATE __attribute__ ((visibility("hidden"))) 58 | 59 | #define ARRAY_SIZE(x) (sizeof (x) / sizeof ((x)[0])) 60 | 61 | 62 | #define RECORD 1 63 | 64 | #define MAX_STACK 32 65 | #define MAX_MODULES 128 66 | #define MAX_SYMBOLS 131071 67 | 68 | 69 | /* Minimum alignment for this platform */ 70 | #ifdef __x86_64__ 71 | #define MIN_ALIGN 16 72 | #else 73 | #define MIN_ALIGN (sizeof(double)) 74 | #endif 75 | 76 | 77 | extern "C" void *__libc_malloc(size_t size); 78 | extern "C" void __libc_free(void *ptr); 79 | 80 | 81 | static void 82 | _assert_fail(const char *expr, 83 | const char *file, 84 | unsigned line, 85 | const char *function) 86 | { 87 | fprintf(stderr, "%s:%u:%s: Assertion `%s' failed.\n", file, line, function, expr); 88 | abort(); 89 | } 90 | 91 | 92 | /** 93 | * glibc's assert macro invokes malloc, so roll our own to avoid recursion. 94 | */ 95 | #ifndef NDEBUG 96 | #define assert(expr) ((expr) ? (void)0 : _assert_fail(#expr, __FILE__, __LINE__, __FUNCTION__)) 97 | #else 98 | #define assert(expr) while (0) { (void)(expr) } 99 | #endif 100 | 101 | 102 | /** 103 | * Unlike glibc backtrace, libunwind will not invoke malloc. 104 | */ 105 | static int 106 | libunwind_backtrace(unw_context_t *uc, void **buffer, int size) 107 | { 108 | int count = 0; 109 | int ret; 110 | 111 | assert(uc != NULL); 112 | 113 | unw_cursor_t cursor; 114 | ret = unw_init_local(&cursor, uc); 115 | if (ret != 0) { 116 | return count; 117 | } 118 | 119 | while (count < size) { 120 | unw_word_t ip; 121 | ret = unw_get_reg(&cursor, UNW_REG_IP, &ip); 122 | if (ret != 0 || ip == 0) { 123 | break; 124 | } 125 | 126 | buffer[count++] = (void *)ip; 127 | 128 | ret = unw_step(&cursor); 129 | if (ret <= 0) { 130 | break; 131 | } 132 | } 133 | 134 | return count; 135 | } 136 | 137 | 138 | static char progname[PATH_MAX] = {0}; 139 | 140 | 141 | /** 142 | * Just like dladdr() but without holding lock. 143 | * 144 | * Calling dladdr() will dead-lock when another thread is doing dlopen() and 145 | * the newly loaded shared-object's global constructors call malloc. 146 | * 147 | * See also glibc/elf/rtld-debugger-interface.txt 148 | */ 149 | static int 150 | _dladdr (const void *address, Dl_info *info) { 151 | struct link_map * lm = _r_debug.r_map; 152 | ElfW(Addr) addr = (ElfW(Addr)) address; 153 | 154 | /* XXX: we're effectively replacing odds of deadlocking with the odds of a 155 | * race condition when a new shared library is opened. We should keep a 156 | * cache of this info to improve our odds. 157 | * 158 | * Another alternative would be to use /self/proc/maps 159 | */ 160 | if (_r_debug.r_state != r_debug::RT_CONSISTENT) { 161 | fprintf(stderr, "memtrail: warning: inconsistent r_debug state\n"); 162 | } 163 | 164 | if (0) fprintf(stderr, "0x%lx:\n", addr); 165 | 166 | assert(lm->l_prev == 0); 167 | while (lm->l_prev) { 168 | lm = lm->l_prev; 169 | } 170 | 171 | while (lm) { 172 | 173 | ElfW(Addr) l_addr; 174 | const char *l_name; 175 | if (lm->l_addr) { 176 | // Shared-object 177 | l_addr = lm->l_addr; 178 | l_name = lm->l_name; 179 | } else { 180 | // Main program 181 | #if defined(__i386__) 182 | l_addr = 0x08048000; 183 | #elif defined(__x86_64__) 184 | l_addr = 0x400000; 185 | #elif defined(__aarch64__) 186 | l_addr = 0x400000; 187 | #else 188 | #error 189 | #endif 190 | l_name = lm->l_name; 191 | } 192 | 193 | assert(l_name != nullptr); 194 | if (l_name[0] == 0 && lm == _r_debug.r_map) { 195 | // Determine the absolute path to progname 196 | if (progname[0] == 0) { 197 | size_t len = readlink("/proc/self/exe", progname, sizeof progname - 1); 198 | if (len <= 0) { 199 | strncpy(progname, program_invocation_name, PATH_MAX - 1); 200 | len = PATH_MAX - 1; 201 | } 202 | progname[len] = 0; 203 | } 204 | l_name = progname; 205 | } 206 | 207 | if (0) fprintf(stderr, " 0x%p, 0x%lx, %s\n", lm, l_addr, l_name); 208 | const ElfW(Ehdr) *l_ehdr = (const ElfW(Ehdr) *)l_addr; 209 | const ElfW(Phdr) *l_phdr = (const ElfW(Phdr) *)(l_addr + l_ehdr->e_phoff); 210 | for (int i = 0; i < l_ehdr->e_phnum; ++i) { 211 | if (l_phdr[i].p_type == PT_LOAD) { 212 | ElfW(Addr) start = lm->l_addr + l_phdr[i].p_vaddr; 213 | ElfW(Addr) stop = start + l_phdr[i].p_memsz; 214 | 215 | if (0) fprintf(stderr, " 0x%lx-0x%lx \n", start, stop); 216 | if (start <= addr && addr < stop) { 217 | info->dli_fname = l_name; 218 | info->dli_fbase = (void *)l_addr; 219 | info->dli_sname = NULL; 220 | info->dli_saddr = NULL; 221 | return 1; 222 | } 223 | } 224 | } 225 | 226 | lm = lm->l_next; 227 | } 228 | 229 | if (0) { 230 | int fd = open("/proc/self/maps", O_RDONLY); 231 | do { 232 | char buf[512]; 233 | size_t nread = read(fd, buf, sizeof buf); 234 | if (!nread) { 235 | break; 236 | } 237 | ssize_t nwritten; 238 | nwritten = write(STDERR_FILENO, buf, nread); 239 | (void)nwritten; 240 | } while (true); 241 | close(fd); 242 | } 243 | 244 | return 0; 245 | } 246 | 247 | 248 | struct header_t { 249 | struct list_head list_head; 250 | 251 | // Real pointer 252 | void *ptr; 253 | 254 | // Size 255 | size_t size; 256 | 257 | unsigned allocated:1; 258 | unsigned pending:1; 259 | unsigned internal:1; 260 | 261 | unsigned char addr_count; 262 | void *addrs[MAX_STACK]; 263 | }; 264 | 265 | 266 | static pthread_mutex_t 267 | mutex = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP; 268 | 269 | static ssize_t 270 | total_size = 0; 271 | 272 | static ssize_t 273 | max_size = 0; 274 | 275 | static ssize_t 276 | limit_size = SSIZE_MAX; 277 | 278 | struct list_head 279 | hdr_list = { &hdr_list, &hdr_list }; 280 | 281 | static int fd = -1; 282 | 283 | 284 | 285 | struct Module { 286 | const char *dli_fname; 287 | void *dli_fbase; 288 | }; 289 | 290 | static Module modules[MAX_MODULES]; 291 | static unsigned numModules = 0; 292 | 293 | struct Symbol { 294 | void *addr; 295 | Module *module; 296 | }; 297 | 298 | static Symbol symbols[MAX_SYMBOLS]; 299 | 300 | 301 | 302 | class PipeBuf 303 | { 304 | protected: 305 | int _fd; 306 | char _buf[PIPE_BUF]; 307 | size_t _written; 308 | 309 | public: 310 | inline 311 | PipeBuf(int fd) : 312 | _fd(fd), 313 | _written(0) 314 | { 315 | assert(fd >= 0); 316 | } 317 | 318 | inline void 319 | write(const void *buf, size_t nbytes) { 320 | if (!RECORD) { 321 | return; 322 | } 323 | 324 | if (nbytes) { 325 | assert(_written + nbytes <= PIPE_BUF); 326 | memcpy(_buf + _written, buf, nbytes); 327 | _written += nbytes; 328 | } 329 | } 330 | 331 | inline void 332 | flush(void) { 333 | if (!RECORD) { 334 | return; 335 | } 336 | 337 | if (_written) { 338 | ssize_t ret; 339 | ret = ::write(_fd, _buf, _written); 340 | assert(ret >= 0); 341 | assert((size_t)ret == _written); 342 | _written = 0; 343 | } 344 | } 345 | 346 | 347 | inline 348 | ~PipeBuf(void) { 349 | flush(); 350 | } 351 | }; 352 | 353 | 354 | static void 355 | _lookup(PipeBuf &buf, void *addr) { 356 | unsigned key = (size_t)addr % MAX_SYMBOLS; 357 | 358 | Symbol *sym = &symbols[key]; 359 | 360 | bool newModule = false; 361 | 362 | if (sym->addr != addr) { 363 | Dl_info info; 364 | if (_dladdr(addr, &info)) { 365 | Module *module = NULL; 366 | for (unsigned i = 0; i < numModules; ++i) { 367 | if (strcmp(modules[i].dli_fname, info.dli_fname) == 0) { 368 | module = &modules[i]; 369 | break; 370 | } 371 | } 372 | if (!module && numModules < ARRAY_SIZE(modules)) { 373 | module = &modules[numModules++]; 374 | module->dli_fname = info.dli_fname; 375 | module->dli_fbase = info.dli_fbase; 376 | newModule = true; 377 | } 378 | sym->module = module; 379 | } else { 380 | sym->module = NULL; 381 | } 382 | 383 | sym->addr = addr; 384 | } 385 | 386 | size_t offset; 387 | const char * name; 388 | unsigned char moduleNo; 389 | 390 | if (sym->module) { 391 | name = sym->module->dli_fname; 392 | offset = ((size_t)addr - (size_t)sym->module->dli_fbase); 393 | moduleNo = 1 + sym->module - modules; 394 | } else { 395 | name = ""; 396 | offset = (size_t)addr; 397 | moduleNo = 0; 398 | } 399 | 400 | buf.write(&addr, sizeof addr); 401 | buf.write(&offset, sizeof offset); 402 | buf.write(&moduleNo, sizeof moduleNo); 403 | if (newModule) { 404 | size_t len = strlen(name); 405 | buf.write(&len, sizeof len); 406 | buf.write(name, len); 407 | } 408 | } 409 | 410 | 411 | enum 412 | { 413 | READ_FD = 0, 414 | WRITE_FD = 1 415 | }; 416 | 417 | 418 | /** 419 | * Open a compressed stream for writing by forking a gzip process. 420 | */ 421 | static int 422 | _gzopen(const char *name, int oflag, mode_t mode) 423 | { 424 | int parentToChild[2]; 425 | pid_t pid; 426 | int out; 427 | int ret; 428 | 429 | ret = pipe(parentToChild); 430 | assert(ret == 0); 431 | 432 | pid = fork(); 433 | switch (pid) { 434 | case -1: 435 | fprintf(stderr, "memtrail: warning: could not fork\n"); 436 | close(parentToChild[READ_FD]); 437 | close(parentToChild[WRITE_FD]); 438 | return open(name, oflag, mode); 439 | 440 | case 0: 441 | // child 442 | out = open(name, oflag, mode); 443 | 444 | ret = dup2(parentToChild[READ_FD], STDIN_FILENO); 445 | assert(ret != -1); 446 | ret = dup2(out, STDOUT_FILENO); 447 | assert(ret != -1); 448 | ret = close(parentToChild[WRITE_FD]); 449 | assert(ret == 0); 450 | 451 | // Don't want to track gzip 452 | unsetenv("LD_PRELOAD"); 453 | 454 | execlp("gzip", "gzip", "--fast", NULL); 455 | 456 | // This line should never be reached 457 | abort(); 458 | 459 | default: 460 | // parent 461 | ret = close(parentToChild[READ_FD]); 462 | assert(ret == 0); 463 | 464 | return parentToChild[WRITE_FD]; 465 | } 466 | 467 | return -1; 468 | } 469 | 470 | 471 | static void 472 | _open(void) { 473 | if (fd < 0) { 474 | mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; 475 | fd = _gzopen("memtrail.data", O_WRONLY | O_CREAT | O_TRUNC, mode); 476 | 477 | if (fd < 0) { 478 | fprintf(stderr, "memtrail: error: could not open memtrail.data\n"); 479 | abort(); 480 | } 481 | 482 | unsigned char c = sizeof(void *); 483 | ssize_t ret; 484 | ret = ::write(fd, &c, sizeof c); 485 | assert(ret >= 0); 486 | assert((size_t)ret == sizeof c); 487 | } 488 | } 489 | 490 | 491 | static inline void 492 | _log(struct header_t *hdr) { 493 | const void *ptr = hdr->ptr; 494 | ssize_t ssize = hdr->allocated ? (ssize_t)hdr->size : -(ssize_t)hdr->size; 495 | 496 | assert(ptr); 497 | assert(ssize); 498 | 499 | _open(); 500 | 501 | PipeBuf buf(fd); 502 | buf.write(&ptr, sizeof ptr); 503 | buf.write(&ssize, sizeof ssize); 504 | 505 | if (hdr->allocated) { 506 | unsigned char c = (unsigned char) hdr->addr_count; 507 | buf.write(&c, 1); 508 | 509 | for (size_t i = 0; i < hdr->addr_count; ++i) { 510 | void *addr = hdr->addrs[i]; 511 | _lookup(buf, addr); 512 | } 513 | } 514 | } 515 | 516 | static void 517 | _flush(void) { 518 | struct header_t *it; 519 | struct header_t *tmp; 520 | for (it = (struct header_t *)hdr_list.next, 521 | tmp = (struct header_t *)it->list_head.next; 522 | &it->list_head != &hdr_list; 523 | it = tmp, tmp = (struct header_t *)tmp->list_head.next) { 524 | assert(it->pending); 525 | if (VERBOSITY >= 2) fprintf(stderr, "flush %p %zu\n", &it[1], it->size); 526 | if (!it->internal) { 527 | _log(it); 528 | } 529 | list_del(&it->list_head); 530 | if (!it->allocated) { 531 | __libc_free(it->ptr); 532 | it = nullptr; 533 | } else { 534 | it->pending = false; 535 | } 536 | } 537 | } 538 | 539 | static inline void 540 | init(struct header_t *hdr, 541 | size_t size, 542 | void *ptr, 543 | unw_context_t *uc) 544 | { 545 | hdr->ptr = ptr; 546 | hdr->size = size; 547 | hdr->allocated = true; 548 | hdr->pending = false; 549 | 550 | // Presume allocations created by libstdc++ before we initialized are 551 | // internal. This is necessary to ignore its emergency_pool global. 552 | hdr->internal = fd == -1; 553 | 554 | if (RECORD) { 555 | hdr->addr_count = libunwind_backtrace(uc, hdr->addrs, ARRAY_SIZE(hdr->addrs)); 556 | } 557 | } 558 | 559 | 560 | /** 561 | * Update/log changes to memory allocations. 562 | */ 563 | static inline void 564 | _update(struct header_t *hdr, 565 | bool allocating = true) 566 | { 567 | pthread_mutex_lock(&mutex); 568 | 569 | static int recursion = 0; 570 | 571 | if (recursion++ <= 0) { 572 | if (!allocating && max_size == total_size) { 573 | _flush(); 574 | } 575 | 576 | hdr->allocated = allocating; 577 | ssize_t size = allocating ? (ssize_t)hdr->size : -(ssize_t)hdr->size; 578 | 579 | bool internal = hdr->internal; 580 | if (hdr->pending) { 581 | assert(!allocating); 582 | hdr->pending = false; 583 | list_del(&hdr->list_head); 584 | __libc_free(hdr->ptr); 585 | hdr = nullptr; 586 | } else { 587 | hdr->pending = true; 588 | list_add(&hdr->list_head, &hdr_list); 589 | } 590 | 591 | if (!internal) { 592 | if (size > 0 && 593 | (total_size + size < total_size || // overflow 594 | total_size + size > limit_size)) { 595 | fprintf(stderr, "memtrail: warning: out of memory\n"); 596 | _flush(); 597 | _exit(1); 598 | } 599 | 600 | total_size += size; 601 | assert(total_size >= 0); 602 | 603 | if (total_size >= max_size) { 604 | max_size = total_size; 605 | } 606 | } 607 | } else { 608 | fprintf(stderr, "memtrail: warning: recursion\n"); 609 | hdr->internal = true; 610 | 611 | assert(!hdr->pending); 612 | if (!hdr->pending) { 613 | if (!allocating) { 614 | __libc_free(hdr->ptr); 615 | hdr = nullptr; 616 | } 617 | } 618 | } 619 | --recursion; 620 | 621 | pthread_mutex_unlock(&mutex); 622 | } 623 | 624 | 625 | static void * 626 | _memalign(size_t alignment, size_t size, unw_context_t *uc) 627 | { 628 | void *ptr; 629 | struct header_t *hdr; 630 | void *res; 631 | 632 | if ((alignment & (alignment - 1)) != 0 || 633 | (alignment & (sizeof(void*) - 1)) != 0) { 634 | return NULL; 635 | } 636 | 637 | if (size == 0) { 638 | // Honour malloc(0), but allocate one byte for accounting purposes. 639 | ++size; 640 | } 641 | 642 | ptr = __libc_malloc(alignment + sizeof *hdr + size); 643 | if (!ptr) { 644 | return NULL; 645 | } 646 | 647 | hdr = (struct header_t *)((((size_t)ptr + sizeof *hdr + alignment - 1) & ~(alignment - 1)) - sizeof *hdr); 648 | 649 | init(hdr, size, ptr, uc); 650 | res = &hdr[1]; 651 | assert(((size_t)res & (alignment - 1)) == 0); 652 | if (VERBOSITY >= 1) fprintf(stderr, "alloc %p %zu\n", res, size); 653 | 654 | _update(hdr); 655 | 656 | return res; 657 | } 658 | 659 | 660 | static inline void * 661 | _malloc(size_t size, unw_context_t *uc) 662 | { 663 | return _memalign(MIN_ALIGN, size, uc); 664 | } 665 | 666 | static void 667 | _free(void *ptr) 668 | { 669 | struct header_t *hdr; 670 | 671 | if (!ptr) { 672 | return; 673 | } 674 | 675 | hdr = (struct header_t *)ptr - 1; 676 | 677 | if (VERBOSITY >= 1) fprintf(stderr, "free %p %zu\n", ptr, hdr->size); 678 | 679 | _update(hdr, false); 680 | } 681 | 682 | 683 | /* 684 | * C 685 | */ 686 | 687 | extern "C" 688 | PUBLIC int 689 | posix_memalign(void **memptr, size_t alignment, size_t size) 690 | { 691 | *memptr = NULL; 692 | 693 | if ((alignment & (alignment - 1)) != 0 || 694 | (alignment & (sizeof(void*) - 1)) != 0) { 695 | return EINVAL; 696 | } 697 | 698 | unw_context_t uc; 699 | unw_getcontext(&uc); 700 | *memptr = _memalign(alignment, size, &uc); 701 | if (!*memptr) { 702 | return -ENOMEM; 703 | } 704 | 705 | return 0; 706 | } 707 | 708 | extern "C" 709 | PUBLIC void * 710 | memalign(size_t alignment, size_t size) 711 | { 712 | unw_context_t uc; 713 | unw_getcontext(&uc); 714 | return _memalign(alignment, size, &uc); 715 | } 716 | 717 | extern "C" 718 | PUBLIC void * 719 | aligned_alloc(size_t alignment, size_t size) 720 | { 721 | unw_context_t uc; 722 | unw_getcontext(&uc); 723 | return _memalign(alignment, size, &uc); 724 | } 725 | 726 | extern "C" 727 | PUBLIC void * 728 | valloc(size_t size) 729 | { 730 | unw_context_t uc; 731 | unw_getcontext(&uc); 732 | return _memalign(sysconf(_SC_PAGESIZE), size, &uc); 733 | } 734 | 735 | extern "C" 736 | PUBLIC void * 737 | pvalloc(size_t size) 738 | { 739 | unw_context_t uc; 740 | unw_getcontext(&uc); 741 | size_t pagesize = sysconf(_SC_PAGESIZE); 742 | return _memalign(pagesize, (size + pagesize - 1) & ~(pagesize - 1), &uc); 743 | } 744 | 745 | extern "C" 746 | PUBLIC void * 747 | malloc(size_t size) 748 | { 749 | unw_context_t uc; 750 | unw_getcontext(&uc); 751 | return _malloc(size, &uc); 752 | } 753 | 754 | extern "C" 755 | PUBLIC void 756 | free(void *ptr) 757 | { 758 | _free(ptr); 759 | } 760 | 761 | 762 | extern "C" 763 | PUBLIC void * 764 | calloc(size_t nmemb, size_t size) 765 | { 766 | void *ptr; 767 | unw_context_t uc; 768 | unw_getcontext(&uc); 769 | ptr = _malloc(nmemb * size, &uc); 770 | if (ptr) { 771 | memset(ptr, 0, nmemb * size); 772 | } 773 | return ptr; 774 | } 775 | 776 | 777 | extern "C" 778 | PUBLIC void 779 | cfree(void *ptr) 780 | { 781 | _free(ptr); 782 | } 783 | 784 | 785 | extern "C" 786 | PUBLIC void * 787 | realloc(void *ptr, size_t size) 788 | { 789 | struct header_t *hdr; 790 | void *new_ptr; 791 | 792 | unw_context_t uc; 793 | unw_getcontext(&uc); 794 | 795 | if (!ptr) { 796 | return _malloc(size, &uc); 797 | } 798 | 799 | if (!size) { 800 | _free(ptr); 801 | return NULL; 802 | } 803 | 804 | hdr = (struct header_t *)ptr - 1; 805 | 806 | new_ptr = _malloc(size, &uc); 807 | if (new_ptr) { 808 | size_t min_size = hdr->size >= size ? size : hdr->size; 809 | memcpy(new_ptr, ptr, min_size); 810 | _free(ptr); 811 | } 812 | 813 | return new_ptr; 814 | } 815 | 816 | 817 | extern "C" 818 | PUBLIC void * 819 | reallocarray(void *ptr, size_t nmemb, size_t size) 820 | { 821 | struct header_t *hdr; 822 | void *new_ptr; 823 | 824 | unw_context_t uc; 825 | unw_getcontext(&uc); 826 | 827 | if (nmemb && size) { 828 | size_t _size = nmemb * size; 829 | if (_size < size) { 830 | return NULL; 831 | } 832 | size = _size; 833 | } else { 834 | size = 0; 835 | } 836 | 837 | if (!ptr) { 838 | return _malloc(size, &uc); 839 | } 840 | 841 | if (!size) { 842 | _free(ptr); 843 | return NULL; 844 | } 845 | 846 | hdr = (struct header_t *)ptr - 1; 847 | 848 | new_ptr = _malloc(size, &uc); 849 | if (new_ptr) { 850 | size_t min_size = hdr->size >= size ? size : hdr->size; 851 | memcpy(new_ptr, ptr, min_size); 852 | _free(ptr); 853 | } 854 | 855 | return new_ptr; 856 | } 857 | 858 | 859 | extern "C" 860 | PUBLIC char * 861 | strdup(const char *s) 862 | { 863 | size_t size = strlen(s) + 1; 864 | unw_context_t uc; 865 | unw_getcontext(&uc); 866 | char *ptr = (char *)_malloc(size, &uc); 867 | if (ptr) { 868 | memcpy(ptr, s, size); 869 | } 870 | return ptr; 871 | } 872 | 873 | 874 | extern "C" 875 | PUBLIC char * 876 | strndup(const char *s, size_t n) 877 | { 878 | size_t len = 0; 879 | while (n && s[len]) { 880 | ++len; 881 | --n; 882 | } 883 | 884 | unw_context_t uc; 885 | unw_getcontext(&uc); 886 | char *ptr = (char *)_malloc(len + 1, &uc); 887 | if (ptr) { 888 | memcpy(ptr, s, len); 889 | ptr[len] = 0; 890 | } 891 | return ptr; 892 | } 893 | 894 | 895 | static int 896 | _vasprintf(char **strp, const char *fmt, va_list ap, unw_context_t *uc) 897 | { 898 | size_t size; 899 | 900 | { 901 | va_list ap_copy; 902 | va_copy(ap_copy, ap); 903 | 904 | char junk; 905 | size = vsnprintf(&junk, 1, fmt, ap_copy); 906 | assert(size >= 0); 907 | 908 | va_end(ap_copy); 909 | } 910 | 911 | *strp = (char *)_malloc(size, uc); 912 | if (!*strp) { 913 | return -1; 914 | } 915 | 916 | return vsnprintf(*strp, size, fmt, ap); 917 | } 918 | 919 | extern "C" 920 | PUBLIC int 921 | vasprintf(char **strp, const char *fmt, va_list ap) 922 | { 923 | unw_context_t uc; 924 | unw_getcontext(&uc); 925 | return _vasprintf(strp, fmt, ap, &uc); 926 | } 927 | 928 | extern "C" 929 | PUBLIC int 930 | asprintf(char **strp, const char *format, ...) 931 | { 932 | unw_context_t uc; 933 | unw_getcontext(&uc); 934 | int res; 935 | va_list ap; 936 | va_start(ap, format); 937 | res = _vasprintf(strp, format, ap, &uc); 938 | va_end(ap); 939 | return res; 940 | } 941 | 942 | 943 | /* 944 | * C++ 945 | * 946 | * See also the output of 947 | * 948 | * nm -D --defined-only /lib/x86_64-linux-gnu/libstdc++.so.6 | grep '\<_Z[dn]' | c++filt 949 | */ 950 | 951 | PUBLIC void * 952 | operator new(size_t size) noexcept(false) { 953 | unw_context_t uc; 954 | unw_getcontext(&uc); 955 | return _malloc(size, &uc); 956 | } 957 | 958 | 959 | PUBLIC void * 960 | operator new[] (size_t size) noexcept(false) { 961 | unw_context_t uc; 962 | unw_getcontext(&uc); 963 | return _malloc(size, &uc); 964 | } 965 | 966 | 967 | PUBLIC void 968 | operator delete (void *ptr) noexcept { 969 | _free(ptr); 970 | } 971 | 972 | 973 | PUBLIC void 974 | operator delete[] (void *ptr) noexcept { 975 | _free(ptr); 976 | } 977 | 978 | 979 | PUBLIC void * 980 | operator new(size_t size, const std::nothrow_t&) noexcept { 981 | unw_context_t uc; 982 | unw_getcontext(&uc); 983 | return _malloc(size, &uc); 984 | } 985 | 986 | 987 | PUBLIC void * 988 | operator new[] (size_t size, const std::nothrow_t&) noexcept { 989 | unw_context_t uc; 990 | unw_getcontext(&uc); 991 | return _malloc(size, &uc); 992 | } 993 | 994 | 995 | PUBLIC void 996 | operator delete (void *ptr, const std::nothrow_t&) noexcept { 997 | _free(ptr); 998 | } 999 | 1000 | 1001 | PUBLIC void 1002 | operator delete[] (void *ptr, const std::nothrow_t&) noexcept { 1003 | _free(ptr); 1004 | } 1005 | 1006 | 1007 | PUBLIC void * 1008 | operator new(size_t size, std::align_val_t al) noexcept(false) { 1009 | unw_context_t uc; 1010 | unw_getcontext(&uc); 1011 | return _memalign(static_cast(al), size, &uc); 1012 | } 1013 | 1014 | 1015 | PUBLIC void * 1016 | operator new[] (size_t size, std::align_val_t al) noexcept(false) { 1017 | unw_context_t uc; 1018 | unw_getcontext(&uc); 1019 | return _memalign(static_cast(al), size, &uc); 1020 | } 1021 | 1022 | 1023 | PUBLIC void 1024 | operator delete (void *ptr, std::align_val_t al) noexcept { 1025 | _free(ptr); 1026 | } 1027 | 1028 | 1029 | PUBLIC void 1030 | operator delete[] (void *ptr, std::align_val_t al) noexcept { 1031 | _free(ptr); 1032 | } 1033 | 1034 | 1035 | PUBLIC void * 1036 | operator new(size_t size, std::align_val_t al, const std::nothrow_t&) noexcept { 1037 | unw_context_t uc; 1038 | unw_getcontext(&uc); 1039 | return _memalign(static_cast(al), size, &uc); 1040 | } 1041 | 1042 | 1043 | PUBLIC void * 1044 | operator new[] (size_t size, std::align_val_t al, const std::nothrow_t&) noexcept { 1045 | unw_context_t uc; 1046 | unw_getcontext(&uc); 1047 | return _memalign(static_cast(al), size, &uc); 1048 | } 1049 | 1050 | 1051 | PUBLIC void 1052 | operator delete (void *ptr, std::align_val_t al, const std::nothrow_t&) noexcept { 1053 | _free(ptr); 1054 | } 1055 | 1056 | 1057 | PUBLIC void 1058 | operator delete[] (void *ptr, std::align_val_t al, const std::nothrow_t&) noexcept { 1059 | _free(ptr); 1060 | } 1061 | 1062 | 1063 | /* 1064 | * Snapshot. 1065 | */ 1066 | 1067 | 1068 | static size_t last_snapshot_size = 0; 1069 | static unsigned snapshot_no = 0; 1070 | 1071 | extern "C" 1072 | PUBLIC void 1073 | memtrail_snapshot(void) { 1074 | pthread_mutex_lock(&mutex); 1075 | 1076 | _flush(); 1077 | 1078 | _open(); 1079 | 1080 | static const void *ptr = NULL; 1081 | static const ssize_t size = 0; 1082 | PipeBuf buf(fd); 1083 | buf.write(&ptr, sizeof ptr); 1084 | buf.write(&size, sizeof size); 1085 | 1086 | size_t current_total_size = total_size; 1087 | size_t current_delta_size; 1088 | if (snapshot_no) 1089 | current_delta_size = current_total_size - last_snapshot_size; 1090 | else 1091 | current_delta_size = 0; 1092 | last_snapshot_size = current_total_size; 1093 | 1094 | ++snapshot_no; 1095 | 1096 | pthread_mutex_unlock(&mutex); 1097 | 1098 | fprintf(stderr, "memtrail: snapshot %zi bytes (%+zi bytes)\n", current_total_size, current_delta_size); 1099 | } 1100 | 1101 | 1102 | extern "C" void _IO_doallocbuf(FILE *ptr); 1103 | 1104 | 1105 | /* 1106 | * Constructor/destructor 1107 | */ 1108 | 1109 | __attribute__ ((constructor(101))) 1110 | static void 1111 | on_start(void) 1112 | { 1113 | // Only trace the current process. 1114 | unsetenv("LD_PRELOAD"); 1115 | 1116 | _IO_doallocbuf(stdin); 1117 | _IO_doallocbuf(stdout); 1118 | _IO_doallocbuf(stderr); 1119 | 1120 | dlsym(RTLD_NEXT, "printf"); 1121 | 1122 | _open(); 1123 | 1124 | // Abort when the application allocates half of the physical memory, to 1125 | // prevent the system from slowing down to a halt due to swapping 1126 | long pagesize = sysconf(_SC_PAGESIZE); 1127 | long phys_pages = sysconf(_SC_PHYS_PAGES); 1128 | limit_size = (ssize_t) std::min((intmax_t) phys_pages / 2, SSIZE_MAX / pagesize) * pagesize; 1129 | fprintf(stderr, "memtrail: limiting to %zi bytes\n", limit_size); 1130 | } 1131 | 1132 | 1133 | __attribute__ ((destructor(101))) 1134 | static void 1135 | on_exit(void) 1136 | { 1137 | pthread_mutex_lock(&mutex); 1138 | _flush(); 1139 | size_t current_max_size = max_size; 1140 | size_t current_total_size = total_size; 1141 | pthread_mutex_unlock(&mutex); 1142 | 1143 | fprintf(stderr, "memtrail: maximum %zi bytes\n", current_max_size); 1144 | fprintf(stderr, "memtrail: leaked %zi bytes\n", current_total_size); 1145 | 1146 | // We don't close the fd here, just in case another destructor that deals 1147 | // with memory gets called after us. 1148 | } 1149 | 1150 | 1151 | // vim:set sw=3 ts=3 et: 1152 | -------------------------------------------------------------------------------- /memtrail: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ########################################################################## 3 | # 4 | # Copyright 2011-2019 Jose Fonseca 5 | # All Rights Reserved. 6 | # 7 | # Permission is hereby granted, free of charge, to any person obtaining a copy 8 | # of this software and associated documentation files (the "Software"), to deal 9 | # in the Software without restriction, including without limitation the rights 10 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | # copies of the Software, and to permit persons to whom the Software is 12 | # furnished to do so, subject to the following conditions: 13 | # 14 | # The above copyright notice and this permission notice shall be included in 15 | # all copies or substantial portions of the Software. 16 | # 17 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | # THE SOFTWARE. 24 | # 25 | ##########################################################################/ 26 | 27 | 28 | import json 29 | import optparse 30 | import os.path 31 | import re 32 | import signal 33 | import struct 34 | import subprocess 35 | import sys 36 | import time 37 | 38 | from operator import attrgetter 39 | 40 | 41 | 42 | class OptionParser(optparse.OptionParser): 43 | 44 | def parse_args(self, args): 45 | # Skip option parsing when there are no options. This prevents optparse 46 | # from complaining about "-foo" when parsing "bla ble -foo" 47 | if args and args[0].startswith('-'): 48 | return optparse.OptionParser.parse_args(self, args) 49 | else: 50 | options, _ = optparse.OptionParser.parse_args(self, []) 51 | return options, args 52 | 53 | 54 | ########################################################################## 55 | # record 56 | 57 | 58 | signal_names = dict( 59 | (getattr(signal, name), name) 60 | for name in dir(signal) 61 | if name.startswith('SIG') and '_' not in name 62 | ) 63 | 64 | 65 | def record(args): 66 | '''Run a command and record its allocations into memtrail.data''' 67 | 68 | optparser = OptionParser( 69 | usage="\n\t%prog record [options] -- program [args] ...") 70 | optparser.add_option( 71 | '-d', '--debug', 72 | action="store_true", 73 | dest="debug", default=False, 74 | help="debug with gdb") 75 | optparser.add_option( 76 | '-p', '--profile', 77 | action="store_true", 78 | dest="profile", default=False, 79 | help="profile with perf") 80 | (options, args) = optparser.parse_args(args) 81 | 82 | if not args: 83 | optparser.error('insufficient number of arguments') 84 | 85 | ld_preload = os.path.abspath(os.path.join(os.path.dirname(__file__), 'libmemtrail.so')) 86 | if not os.path.exists(ld_preload): 87 | sys.error.write('memtrail: error: %s not found\n' % ld_preload) 88 | sys.exit(1) 89 | 90 | if options.debug: 91 | # http://stackoverflow.com/questions/4703763/how-to-run-gdb-with-ld-preload 92 | cmd = [ 93 | 'gdb', 94 | '--ex', 'set exec-wrapper env LD_PRELOAD=%s' % ld_preload, 95 | '--args' 96 | ] + args 97 | env = None 98 | elif options.profile: 99 | cmd = [ 100 | 'perf', 'record', '-g', 101 | 'env', 'LD_PRELOAD=%s' % ld_preload, 102 | ] + args 103 | env = None 104 | else: 105 | cmd = args 106 | env = os.environ.copy() 107 | env['LD_PRELOAD'] = ld_preload 108 | 109 | p = subprocess.Popen(cmd, env=env) 110 | try: 111 | retcode = p.wait() 112 | except KeyboardInterrupt: 113 | p.send_signal(signal.SIGINT) 114 | retcode = p.wait() 115 | 116 | if retcode < 0: 117 | try: 118 | signal_name = signal_names[-retcode] 119 | except KeyError: 120 | pass 121 | else: 122 | sys.stderr.write('%s\n' % signal_name) 123 | 124 | sys.exit(retcode) 125 | 126 | 127 | 128 | ########################################################################## 129 | # report 130 | 131 | 132 | NO_FUNCTION = '??' 133 | NO_LINE = '??:?' 134 | 135 | 136 | def _ignore_sigint(): 137 | # Prevent subprocesses from receiving ctrl-c 138 | # http://stackoverflow.com/questions/5045771/python-how-to-prevent-subprocesses-from-receiving-ctrl-c-control-c-sigint 139 | signal.signal(signal.SIGINT, signal.SIG_IGN) 140 | 141 | 142 | class Module: 143 | 144 | def __init__(self, path): 145 | self.path = path 146 | self._path = os.path.abspath(path) 147 | 148 | self.mime_type = subprocess.check_output(['file', '--dereference', '--brief', '--mime-type', self._path]).strip() 149 | if self.mime_type not in (b'application/x-executable', b'application/x-pie-executable', b'application/x-sharedlib'): 150 | sys.stderr.write('memtrail: warning: unexpected mime-type %s for %s\n' % (self.mime_type, path)) 151 | 152 | self.addr2line = None 153 | 154 | def lookup(self, addr, offset): 155 | 156 | if self.mime_type == b'application/x-executable': 157 | # use absolute addresses for executables 158 | _addr = addr 159 | elif self.mime_type in (b'application/x-sharedlib', b'application/x-pie-executable'): 160 | # use relative offset for shared objects 161 | _addr = offset 162 | else: 163 | return NO_FUNCTION, NO_LINE 164 | 165 | if self.addr2line is None: 166 | cmd = [ 167 | 'addr2line', 168 | '-e', self._path, 169 | '-f', 170 | '-C', 171 | ] 172 | self.addr2line = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, preexec_fn=_ignore_sigint) 173 | p = self.addr2line 174 | 175 | if p.returncode is not None: 176 | return NO_FUNCTION, NO_LINE 177 | 178 | self.addr2line.stdin.write(b'0x%x\n' % _addr) 179 | if False: 180 | sys.stderr.write('%s 0x%x\n' % (self.path, _addr)) 181 | self.addr2line.stdin.flush() 182 | function = self.addr2line.stdout.readline().decode().strip() 183 | line = self.addr2line.stdout.readline().decode().strip() 184 | 185 | return function, line 186 | 187 | 188 | class Symbol(object): 189 | 190 | _moduleCache = { 191 | None: None, 192 | } 193 | 194 | _cwd = os.getcwd() + os.path.sep 195 | 196 | __slots__ = [ 197 | 'addr', 198 | 'modulePath', 199 | 'offset', 200 | '_function', 201 | '_line', 202 | ] 203 | 204 | def __init__(self, addr, modulePath, offset): 205 | self.addr = addr 206 | self.modulePath = modulePath 207 | 208 | self.offset = offset 209 | self._function = None 210 | self._line = NO_LINE 211 | 212 | def _resolve(self): 213 | if self._function is None: 214 | self._function = NO_FUNCTION 215 | 216 | try: 217 | module = self._moduleCache[self.modulePath] 218 | except KeyError: 219 | module = Module(self.modulePath) 220 | self._moduleCache[self.modulePath] = module 221 | 222 | if module: 223 | self._function, self._line = module.lookup(self.addr, self.offset) 224 | assert self._function is not None 225 | 226 | def function(self): 227 | self._resolve() 228 | return self._function 229 | 230 | def id(self): 231 | self._resolve() 232 | if self._function != NO_FUNCTION: 233 | return '%s!%s' % (self.modulePath, self._function) 234 | return self.addr 235 | 236 | def __str__(self): 237 | self._resolve() 238 | if self.modulePath is None: 239 | return '0x%x' % self.addr 240 | 241 | if self._function != NO_FUNCTION: 242 | s = self._function 243 | moduleName = os.path.basename(self.modulePath) 244 | if moduleName == 'libmemtrail.so': 245 | return s 246 | else: 247 | s = '%s+0x%x' % (self.modulePath, self.offset) 248 | 249 | if self._line != NO_LINE: 250 | filename, lineNo = self._line.split(':', 1) 251 | filename = os.path.normpath(filename) 252 | if filename.startswith(self._cwd): 253 | filename = filename[len(self._cwd):] 254 | s += ' [%s:%s]' % (filename, lineNo) 255 | 256 | return s 257 | 258 | class SymbolTable: 259 | 260 | def __init__(self): 261 | self.symbols = {} 262 | 263 | def addSymbol(self, address, modulePath, offset): 264 | try: 265 | symbol = self.symbols[address] 266 | except KeyError: 267 | self.symbols[address] = Symbol(address, modulePath, offset) 268 | else: 269 | assert modulePath == symbol.modulePath 270 | assert offset == symbol.offset 271 | 272 | def getSymbol(self, address): 273 | return self.symbols[address] 274 | 275 | 276 | class Allocation(object): 277 | 278 | __slots__ = [ 279 | 'address', 280 | 'size', 281 | 'frames', 282 | ] 283 | 284 | def __init__(self, address, size, frames): 285 | self.address = address 286 | self.size = size 287 | self.frames = frames 288 | 289 | def __str__(self): 290 | return '0x%X+%%u' % (self.address, self.size) 291 | 292 | 293 | default_threshold = 0.01 294 | 295 | 296 | def format_size(n): 297 | return '{0:,}B'.format(n) 298 | 299 | 300 | class TreeNode: 301 | 302 | def __init__(self, label=''): 303 | self.label = label 304 | self.count = 0 305 | self.cost = 0 306 | self.children = {} 307 | 308 | def write(self, stream, threshold = default_threshold, indent = ''): 309 | assert not self.label 310 | self._write(stream, indent, threshold, self.cost) 311 | 312 | def _write(self, stream, indent, threshold, total_cost): 313 | children = self.children.values() 314 | children = [child for child in children if child.cost] 315 | children.sort(key = attrgetter("cost"), reverse = True) 316 | 317 | it = iter(children) 318 | for child in it: 319 | if abs(child.cost) < threshold * abs(total_cost): 320 | nr_pruned = 1 321 | pruned_count = child.count 322 | pruned_cost = child.cost 323 | for child in it: 324 | absolute_cost = child.cost 325 | relative_cost = float(pruned_cost) / float(total_cost) 326 | stream.write('%s-> %.2f%% (%s, %ux) in %u places, all below the %.2f%% threshold\n' % (indent, 100.0 * relative_cost, format_size(pruned_cost), pruned_count, nr_pruned, 100.0 * threshold)) 327 | break 328 | 329 | relative_cost = float(child.cost) / float(total_cost) 330 | stream.write('%s-> %.2f%% (%s, %ux): %s\n' % (indent, 100.0 * relative_cost, format_size(child.cost), child.count, child.label)) 331 | if child is children[-1]: 332 | child_indent = indent + ' ' 333 | else: 334 | child_indent = indent + '| ' 335 | 336 | child._write(stream, child_indent, threshold, total_cost) 337 | 338 | if child is not children[-1]: 339 | stream.write('%s\n' % (child_indent,)) 340 | 341 | class Heap: 342 | 343 | def __init__(self): 344 | self.framesStats = {} 345 | self.count = 0 346 | self.size = 0 347 | 348 | def add(self, alloc): 349 | self._update(1, alloc.size, alloc.frames) 350 | 351 | def pop(self, alloc): 352 | self._update(-1, -alloc.size, alloc.frames) 353 | 354 | def _update(self, count, ssize, frames): 355 | assert frames 356 | 357 | framesCount, framesSize = self.framesStats.get(frames, (0, 0)) 358 | framesCount += count 359 | framesSize += ssize 360 | self.framesStats[frames] = framesCount, framesSize 361 | 362 | self.count += count 363 | self.size += ssize 364 | 365 | def copy(self): 366 | other = Heap() 367 | other.framesStats.update(self.framesStats) 368 | other.count += self.count 369 | other.size += self.size 370 | return other 371 | 372 | def add_heap(self, other): 373 | for frames, stats in other.framesStats.items(): 374 | count, size = stats 375 | self._update(count, size, frames) 376 | 377 | def sub_heap(self, other): 378 | for frames, stats in other.framesStats.items(): 379 | count, size = stats 380 | self._update(-count, -size, frames) 381 | 382 | def tree(self, symbolTable): 383 | root = TreeNode() 384 | for frames, stats in self.framesStats.items(): 385 | count, size = stats 386 | if size == 0: 387 | assert count == 0 388 | continue 389 | root.count += count 390 | root.cost += size 391 | parent = root 392 | for address in frames: 393 | symbol = symbolTable.getSymbol(address) 394 | function_id = symbol.id() 395 | try: 396 | child = parent.children[function_id] 397 | except KeyError: 398 | child = TreeNode(str(symbol)) 399 | parent.children[function_id] = child 400 | child.count += count 401 | child.cost += size 402 | parent = child 403 | return root 404 | 405 | def write_profile(self, symbolTable, filename): 406 | costs = [ 407 | { 408 | 'description': 'Memory', 409 | 'unit': 'bytes', 410 | }, 411 | ] 412 | 413 | functions = [] 414 | functionIndices = {} 415 | 416 | callchains = {} 417 | 418 | for frames, stats in self.framesStats.items(): 419 | count, size = stats 420 | if size == 0: 421 | continue 422 | 423 | callchain = [] 424 | 425 | for address in frames: 426 | symbol = symbolTable.getSymbol(address) 427 | function_id = symbol.id() 428 | try: 429 | functionIndex = functionIndices[function_id] 430 | except KeyError: 431 | function_name = symbol.function() 432 | function = { 433 | 'name': function_name, 434 | } 435 | if symbol.modulePath is not None: 436 | moduleName = os.path.basename(symbol.modulePath) 437 | if moduleName != 'libmemtrail.so': 438 | function['module'] = moduleName 439 | functionIndex = len(functions) 440 | functionIndices[function_id] = functionIndex 441 | functions.append(function) 442 | callchain.append(functionIndex) 443 | 444 | callchain = tuple(callchain) 445 | 446 | try: 447 | callchains[callchain] += size 448 | except KeyError: 449 | callchains[callchain] = size 450 | 451 | events = [] 452 | for callchain, size in callchains.items(): 453 | if size == 0: 454 | continue 455 | if size < 0: 456 | if False: 457 | sys.stderr.write('warning: ignoring %s bytes\n' % (size,)) 458 | for functionIndex in callchain: 459 | sys.stderr.write('\t%s\n' % functions[functionIndex]['name']) 460 | continue 461 | 462 | event = { 463 | 'callchain': list(callchain), 464 | 'cost': [size], 465 | } 466 | events.append(event) 467 | 468 | profile = { 469 | 'version': 0, 470 | 'costs': costs, 471 | 'functions': functions, 472 | 'events': events, 473 | } 474 | 475 | stream = open(filename, 'wt') 476 | if True: 477 | json.dump(profile, stream, indent=2, sort_keys=True) 478 | else: 479 | json.dump(profile, stream) 480 | stream.write('\n') 481 | 482 | sys.stdout.write('%s written\n' % filename) 483 | 484 | 485 | 486 | class BaseFilter: 487 | 488 | def __call__(self, alloc, symbolTable): 489 | raise NotImplementedError 490 | 491 | 492 | class NoFilter(BaseFilter): 493 | 494 | def __call__(self, alloc, symbolTable): 495 | return True 496 | 497 | 498 | class Filter(BaseFilter): 499 | 500 | def __init__( 501 | self, 502 | include_functions = (), 503 | exclude_functions = (), 504 | include_modules = (), 505 | exclude_modules = (), 506 | default=None 507 | ): 508 | 509 | self.include_function_re = self._compile(include_functions) 510 | self.exclude_function_re = self._compile(exclude_functions) 511 | self.include_module_re = self._compile(include_modules) 512 | self.exclude_module_re = self._compile(exclude_modules) 513 | 514 | includes = include_functions or include_modules 515 | excludes = exclude_functions or exclude_modules 516 | 517 | if default is None: 518 | if excludes: 519 | if includes: 520 | self.default = True 521 | else: 522 | self.default = True 523 | else: 524 | if includes: 525 | self.default = False 526 | else: 527 | self.default = True 528 | else: 529 | self.default = default 530 | self.default = not includes 531 | 532 | def _compile(self, patterns): 533 | if patterns: 534 | return re.compile('|'.join(patterns)) 535 | else: 536 | return None 537 | 538 | def __call__(self, alloc, symbolTable): 539 | for addr in alloc.frames: 540 | symbol = symbolTable.getSymbol(addr) 541 | if self.include_function_re is not None or self.exclude_function_re is not None: 542 | function = symbol.function() 543 | if self.include_function_re is not None: 544 | mo = self.include_function_re.search(function) 545 | if mo: 546 | return True 547 | if self.exclude_function_re is not None: 548 | mo = self.exclude_function_re.search(function) 549 | if mo: 550 | return False 551 | if self.include_module_re is not None or self.exclude_module_re is not None: 552 | module = symbol.module 553 | if self.include_module_re is not None: 554 | mo = self.include_module_re.search(module.path) 555 | if mo: 556 | return True 557 | if self.exclude_module_re is not None: 558 | mo = self.exclude_module_re.search(module.path) 559 | if mo: 560 | return False 561 | return self.default 562 | 563 | 564 | def ReadMethod(fmt): 565 | # Generate a read_xxx method, precomputing the format size 566 | size = struct.calcsize(fmt) 567 | def read_fmt(self): 568 | return struct.unpack(fmt, self.read(size)) 569 | return read_fmt 570 | 571 | 572 | class Parser: 573 | 574 | def __init__(self, log): 575 | self.log = open(log, 'rb') 576 | magic = self.log.read(2) 577 | if magic == b'\037\213': 578 | # gzip file 579 | self.log.seek(-4, os.SEEK_END) 580 | self.log_size, = struct.unpack('I', self.log.read(4)) 581 | del self.log 582 | gzip = subprocess.Popen(['gzip', '-dc', log], stdout=subprocess.PIPE) 583 | self.log = gzip.stdout 584 | else: 585 | # raw data 586 | self.log.seek(0, os.SEEK_END) 587 | self.log_size = self.log.tell() 588 | assert self.log_size == os.path.getsize(log) 589 | self.log.seek(0, os.SEEK_SET) 590 | self.log_pos = 0 591 | 592 | self.stamp = 0 593 | 594 | self.modulePaths = {0: None} 595 | self.symbolTable = SymbolTable() 596 | 597 | def parse(self): 598 | # TODO 599 | addrsize = self.read_byte() 600 | 601 | try: 602 | while True: 603 | self.parse_event() 604 | except struct.error: 605 | pass 606 | except KeyboardInterrupt: 607 | sys.stdout.write('\n') 608 | 609 | def parse_event(self): 610 | self.stamp += 1 611 | stamp = self.stamp 612 | 613 | addr, ssize = self.read_event() 614 | 615 | if ssize > 0: 616 | frames = self.parse_frames() 617 | else: 618 | frames = () 619 | 620 | self.handle_event(stamp, addr, ssize, frames) 621 | 622 | return True 623 | 624 | def parse_frames(self): 625 | count, = self.read_byte() 626 | 627 | frames = [] 628 | for i in range(count): 629 | addr, offset, moduleNo = self.read_frame() 630 | 631 | try: 632 | modulePath = self.modulePaths[moduleNo] 633 | except KeyError: 634 | length, = self.read_pointer() 635 | modulePath = self.read(length).decode() 636 | self.modulePaths[moduleNo] = modulePath 637 | 638 | self.symbolTable.addSymbol(addr, modulePath, offset) 639 | frames.append(addr) 640 | 641 | assert frames 642 | return tuple(frames) 643 | 644 | def handle_event(self, stamp, addr, ssize, frames): 645 | pass 646 | 647 | def progress(self): 648 | return self.log_pos*100/self.log_size 649 | 650 | def read(self, size): 651 | data = self.log.read(size) 652 | self.log_pos += len(data) 653 | return data 654 | 655 | read_byte = ReadMethod('B') 656 | read_event = ReadMethod('Pl') 657 | read_pointer = ReadMethod('P') 658 | read_frame = ReadMethod('PPB') 659 | 660 | 661 | class Reporter(Parser): 662 | 663 | def __init__(self, log, filter, options): 664 | Parser.__init__(self, log) 665 | self.filter = filter 666 | self.threshold = options.threshold * 0.01 667 | self.show_progress = sys.stdout.isatty() 668 | self.show_snapshots = options.show_snapshots 669 | self.show_snapshot_deltas = options.show_snapshot_deltas 670 | self.show_cum_snapshot_delta = options.show_cum_snapshot_delta 671 | self.show_maximum = options.show_maximum 672 | self.show_leaks = options.show_leaks 673 | self.output_json = options.output_json 674 | 675 | self.allocs = {} 676 | self.size = 0 677 | self.max_heap = Heap() 678 | self.delta_heap = Heap() 679 | self.last_snapshot_heap = None 680 | self.cum_snapshot_delta_heap = Heap() 681 | 682 | def parse(self): 683 | Parser.parse(self) 684 | self.on_finish() 685 | 686 | def handle_event(self, stamp, addr, ssize, frames): 687 | if addr == 0: 688 | # Snapshot 689 | assert ssize == 0 690 | self.on_snapshot() 691 | elif ssize >= 0: 692 | # Allocation 693 | alloc = Allocation(addr, ssize, frames) 694 | if self.filter(alloc, self.symbolTable): 695 | assert alloc.address not in self.allocs 696 | self.allocs[alloc.address] = alloc 697 | self.size += alloc.size 698 | self.delta_heap.add(alloc) 699 | else: 700 | return True 701 | else: 702 | # Free 703 | try: 704 | alloc = self.allocs.pop(addr) 705 | except KeyError: 706 | return 707 | 708 | if self.show_maximum and self.size > self.max_heap.size: 709 | self.max_heap.add_heap(self.delta_heap) 710 | self.delta_heap = Heap() 711 | 712 | assert alloc.size == -ssize 713 | self.delta_heap.pop(alloc) 714 | self.size -= alloc.size 715 | 716 | self.on_update(stamp) 717 | 718 | interval = 1000 719 | 720 | last_stamp = 0 721 | 722 | def on_update(self, stamp): 723 | if self.show_progress: 724 | assert stamp >= self.last_stamp 725 | if stamp >= self.last_stamp + self.interval: 726 | progress = self.progress() 727 | kb = (self.size + 1024 - 1)/1024 728 | sys.stdout.write('%3d%% %8d KB\n' % (progress, kb)) 729 | sys.stdout.flush() 730 | self.last_stamp = stamp 731 | 732 | snapshot_no = 0 733 | 734 | def on_snapshot(self): 735 | if self.show_snapshots or self.show_snapshot_deltas or self.show_cum_snapshot_delta: 736 | heap = self.max_heap.copy() 737 | heap.add_heap(self.delta_heap) 738 | 739 | label = 'snapshot-%u' % self.snapshot_no 740 | 741 | if self.show_snapshots: 742 | self.report_heap(label, heap) 743 | 744 | if self.show_snapshot_deltas or self.show_cum_snapshot_delta: 745 | last_snapshot_heap = self.last_snapshot_heap 746 | self.last_snapshot_heap = heap 747 | 748 | if last_snapshot_heap is not None: 749 | delta_heap = heap.copy() 750 | delta_heap.sub_heap(last_snapshot_heap) 751 | if self.show_snapshot_deltas: 752 | self.report_heap(label + '-delta', delta_heap) 753 | if self.show_cum_snapshot_delta: 754 | self.cum_snapshot_delta_heap.add_heap(delta_heap) 755 | 756 | self.snapshot_no += 1 757 | 758 | def on_finish(self): 759 | if self.show_progress: 760 | sys.stdout.write('\n') 761 | if self.show_cum_snapshot_delta: 762 | self.report_heap('cum-snapshot-delta', self.cum_snapshot_delta_heap) 763 | if self.show_maximum: 764 | self.report_heap('maximum', self.max_heap) 765 | if self.show_leaks: 766 | heap = self.max_heap 767 | heap.add_heap(self.delta_heap) 768 | self.report_heap('leaked', heap) 769 | 770 | def report_heap(self, label, heap): 771 | if self.show_progress: 772 | sys.stdout.write('\n') 773 | sys.stdout.write('%s: %s\n' % (label, format_size(heap.size))) 774 | if heap.size: 775 | tree = heap.tree(self.symbolTable) 776 | tree.write(sys.stdout, threshold = self.threshold, indent=' ') 777 | if self.output_json: 778 | sys.stdout.flush() 779 | heap.write_profile(self.symbolTable, 'memtrail.%s.json' % label) 780 | sys.stdout.write('\n') 781 | sys.stdout.flush() 782 | 783 | 784 | def report(args): 785 | '''Read memtrail.data (created by memtrail record) and report the allocations''' 786 | 787 | optparser = OptionParser( 788 | usage="\n\t%prog report [options]") 789 | optparser.add_option( 790 | '-i', '--include-function', metavar='PATTERN', 791 | type="string", 792 | action='append', 793 | dest="include_functions", default=[], 794 | help="include functions matching the regular expression") 795 | optparser.add_option( 796 | '-x', '--exclude-function', metavar='PATTERN', 797 | type="string", 798 | action='append', 799 | dest="exclude_functions", default=[], 800 | help="exclude functions matching the regular expression") 801 | optparser.add_option( 802 | '--include-module', metavar='PATTERN', 803 | type="string", 804 | action='append', 805 | dest="include_modules", default=[], 806 | help="include modules matching the regular expression") 807 | optparser.add_option( 808 | '--exclude-module', metavar='PATTERN', 809 | type="string", 810 | action='append', 811 | dest="exclude_modules", default=[], 812 | help="exclude modules matching the regular expression") 813 | optparser.add_option( 814 | '-t', '--threshold', metavar='PERCENTAGE', 815 | type="float", dest="threshold", default = default_threshold*100.0, 816 | help="eliminate nodes below this threshold [default: %default]") 817 | optparser.add_option( 818 | '--show-snapshots', 819 | action="store_true", 820 | dest="show_snapshots", default=False, 821 | help="show deltas between heap snapshots") 822 | optparser.add_option( 823 | '--show-snapshot-deltas', 824 | action="store_true", 825 | dest="show_snapshot_deltas", default=False, 826 | help="show deltas between heap snapshots") 827 | optparser.add_option( 828 | '--show-cumulative-snapshot-delta', 829 | action="store_true", 830 | dest="show_cum_snapshot_delta", default=False, 831 | help="show cumulative delta between heap snapshots") 832 | optparser.add_option( 833 | '--show-maximum', 834 | action="store_true", 835 | dest="show_maximum", default=False, 836 | help="show maximum allocation") 837 | optparser.add_option( 838 | '--show-leaks', 839 | action="store_true", 840 | dest="show_leaks", default=False, 841 | help="show leaked allocations") 842 | optparser.add_option( 843 | '--output-graphs', 844 | action="store_true", 845 | dest="output_json", default=False, 846 | help="output gprof2dot json graphs") 847 | (options, args) = optparser.parse_args(args) 848 | 849 | # Default to showing leaks if nothing else was requested. 850 | if not options.show_maximum and \ 851 | not options.show_snapshots and \ 852 | not options.show_snapshot_deltas and \ 853 | not options.show_cum_snapshot_delta: 854 | options.show_leaks = True 855 | 856 | if args: 857 | optparser.error('wrong number of arguments') 858 | 859 | if options.include_functions or options.exclude_functions or options.include_modules or options.exclude_modules: 860 | filter = Filter( 861 | options.include_functions, 862 | options.exclude_functions, 863 | options.include_modules, 864 | options.exclude_modules, 865 | ) 866 | else: 867 | filter = NoFilter() 868 | 869 | reporter = Reporter( 870 | 'memtrail.data', 871 | filter, 872 | options 873 | ) 874 | reporter.parse() 875 | 876 | 877 | ########################################################################## 878 | # dump 879 | 880 | 881 | class Dumper(Parser): 882 | 883 | def handle_event(self, stamp, addr, ssize, frames): 884 | sys.stdout.write('%u: 0x%08x %+i\n' % (stamp, addr, ssize)) 885 | for address in frames: 886 | symbol = self.symbolTable.getSymbol(address) 887 | sys.stdout.write('\t%s\n' % symbol) 888 | sys.stdout.write('\n') 889 | 890 | 891 | def dump(args): 892 | '''Read memtrail.data (created by memtrail record) and dump the allocations''' 893 | 894 | optparser = OptionParser( 895 | usage="\n\t%prog dump [options]") 896 | (options, args) = optparser.parse_args(args) 897 | 898 | if args: 899 | optparser.error('wrong number of arguments') 900 | 901 | input = 'memtrail.data' 902 | 903 | dumper = Dumper(input) 904 | dumper.parse() 905 | 906 | 907 | ########################################################################## 908 | # help 909 | 910 | 911 | def synopsis(): 912 | command_names = sorted(commands.keys()) 913 | for command_name in command_names: 914 | yield command_name, commands[command_name].__doc__ 915 | 916 | 917 | def help(args): 918 | '''Display help information about memtrail''' 919 | 920 | optparser = OptionParser( 921 | usage="\n\t%prog help [options] -- command [args] ...") 922 | (options, args) = optparser.parse_args(args) 923 | 924 | if not args: 925 | optparser.error('insufficient number of arguments') 926 | 927 | command = args.pop(0) 928 | 929 | try: 930 | function = commands[command] 931 | except KeyError: 932 | optparser.error('unknown command %s\n' % command) 933 | else: 934 | function(['--help']) 935 | 936 | 937 | 938 | ########################################################################## 939 | # main 940 | 941 | 942 | commands = { 943 | 'record': record, 944 | 'report': report, 945 | 'dump': dump, 946 | 'help': help, 947 | } 948 | 949 | 950 | def main(): 951 | usage = [ 952 | '\t%prog [options] COMMAND [ARGS ...]', 953 | '', 954 | 'Commands:', 955 | ] 956 | for name, desc in synopsis(): 957 | usage.append(' %-10s%s' % (name, desc)) 958 | 959 | optparser = OptionParser(usage = '\n' + '\n'.join(usage)) 960 | 961 | (options, args) = optparser.parse_args(sys.argv[1:]) 962 | 963 | if not args: 964 | optparser.error('insufficient number of arguments') 965 | 966 | command = args.pop(0) 967 | 968 | try: 969 | function = commands[command] 970 | except KeyError: 971 | optparser.error('unknown command %s\n' % command) 972 | else: 973 | function(args) 974 | 975 | 976 | if __name__ == '__main__': 977 | main() 978 | 979 | 980 | # vim:set sw=4 ts=4 et: 981 | --------------------------------------------------------------------------------