├── Makefile ├── README.md ├── bin ├── .gitignore └── VersionCheck.py ├── doc └── ProgressiveMemoryLeakDetection.txt ├── include ├── asmalloc.h ├── test-mallocations.h └── vm_stats.h ├── lib └── .gitignore ├── obj └── .gitignore └── src ├── asmalloc.c ├── test-asmalloc.c └── vm_stats.c /Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # Makefile 3 | # 4 | # This is the makefile for the ASMalloc memory allocation tracking tools. 5 | # 6 | 7 | # Define variables. 8 | 9 | INCDIR = include 10 | SRCDIR = src 11 | OBJDIR = obj 12 | LIBDIR = lib 13 | BINDIR = bin 14 | 15 | LIB_HEADERS = \ 16 | $(INCDIR)/asmalloc.h \ 17 | $(INCDIR)/vm_stats.h 18 | 19 | LIB_SOURCES = \ 20 | $(SRCDIR)/asmalloc.c \ 21 | $(SRCDIR)/vm_stats.c 22 | 23 | LIB_OBJECTS = $(LIB_SOURCES:$(SRCDIR)/%.c=$(OBJDIR)/%.o) 24 | 25 | LIBRARY = $(LIBDIR)/asmalloc$(EXT).so 26 | 27 | PRELOADS = $(LIBRARY) 28 | 29 | JEM_LIBRARY = /usr/lib64/libjemalloc.so 30 | 31 | PROGRAM_NAMES = test-asmalloc 32 | 33 | PROGRAMS = $(PROGRAM_NAMES:%=$(BINDIR)/%$(EXT)) 34 | 35 | PGM_HEADERS = $(INCDIR)/test-mallocations.h 36 | 37 | PGM_SOURCES = $(PROGRAM_NAMES:%=$(SRCDIR)/%.c) 38 | 39 | PGM_OBJECTS = $(PGM_SOURCES:$(SRCDIR)/%.c=$(OBJDIR)/%.o) 40 | 41 | HEADERS = $(LIB_HEADERS) $(PGM_HEADERS) 42 | 43 | SOURCES = $(LIB_SOURCES) $(PGM_SOURCES) 44 | 45 | OBJECTS = $(LIB_OBJECTS) $(PGM_OBJECTS) 46 | 47 | # If GCC v4.4.7 or later, use DWARF version 4, othewise use version 2: 48 | ifeq ($(shell bin/VersionCheck.py 'gcc -dumpversion' 4.4.7), 1) 49 | DWARF_VERSION=4 50 | else 51 | DWARF_VERSION=2 52 | endif 53 | 54 | CFLAGS = -gdwarf-$(DWARF_VERSION) -g3 -Wall -std=gnu99 -fPIC -I$(INCDIR) 55 | 56 | ifneq ($(FOR_JEMALLOC),) 57 | CFLAGS += -DFOR_JEMALLOC 58 | EXT = .jem 59 | PRELOADS = $(LIBRARY):$(JEM_LIBRARY) 60 | endif 61 | 62 | LIB_LDFLAGS = -shared 63 | 64 | PGM_LDFLAGS = -rdynamic 65 | 66 | LIBRARIES = -ldl -lpthread -lrt 67 | 68 | TARGETS = all clean cleaner cleanest test 69 | 70 | TARGETS.JEM = $(TARGETS:%=%.jem) 71 | 72 | # Define targets. 73 | 74 | all: clean $(LIBRARY) $(PROGRAMS) 75 | 76 | jem: all.jem 77 | 78 | clean: 79 | $(RM) $(OBJECTS) 80 | 81 | cleaner: clean 82 | $(RM) $(PROGRAMS) 83 | 84 | cleanest: cleaner 85 | $(RM) $(LIBRARY) 86 | 87 | test: all 88 | LD_PRELOAD=$(PRELOADS) $(PROGRAMS) 89 | 90 | $(TARGETS.JEM): 91 | $(MAKE) $(@:%.jem=%) FOR_JEMALLOC=1 92 | 93 | $(SOURCES): $(HEADERS) 94 | 95 | $(LIBRARY): $(LIB_OBJECTS) 96 | $(LINK.c) $(LIB_LDFLAGS) -o $@ $^ $(LIBRARIES) 97 | 98 | $(PROGRAMS): $(PGM_OBJECTS) 99 | $(LINK.c) $(PGM_LDFLAGS) -o $@ $^ $(LIBRARIES) 100 | 101 | $(OBJDIR)/%.o: $(SRCDIR)/%.c 102 | $(COMPILE.c) $< -o $@ 103 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ASMalloc: Memory Allocation Tracking 2 | 3 | This repository provides a safe and efficient means to track dynamic 4 | memory usage in programs using an implementation of the standard C 5 | library **malloc(3)** / **free(3)** interface and is useful for locating 6 | and eliminating memory leaks. 7 | 8 | ## Build 9 | 10 | To build for use with GLibC (PTMalloc2): 11 | 12 | prompt% make 13 | 14 | To build for use with JEMalloc: 15 | 16 | prompt% make jem 17 | 18 | ## Test 19 | 20 | To test with GLibC: 21 | 22 | prompt% make test 23 | 24 | To test with JEMalloc: 25 | 26 | prompt% make test.jem 27 | 28 | ## Clean 29 | 30 | To clean GLibC {JEMalloc} build products: 31 | 32 | prompt% make clean{.jem} -- Remove objects. 33 | 34 | prompt% make cleaner{.jem} -- Remove the above plus programs. 35 | 36 | prompt% make cleanest{.jem} -- Remove the above plus library. 37 | 38 | ## Application Integration 39 | 40 | ASMalloc can be used to a very basic level without any modifications to 41 | the application. Using more advanced features requires integration of 42 | the application with the ASMalloc API. 43 | 44 | The output of ASMalloc will be logged to **stderr**. 45 | 46 | To execute **my_executable** with ASMalloc, use the **LD_PRELOAD** 47 | environment variable: 48 | 49 | [*Note:* Because they have slightly different needs, there are separate 50 | builds for the GLibC-compatible (**lib/asmalloc.so**) and JEMalloc-compatible 51 | (**lib/asmalloc.jem.so**) versions of the ASMalloc shared library.] 52 | 53 | ### With GLibC (PTMalloc2): 54 | 55 | Under TCSH: 56 | 57 | prompt% env LD_PRELOAD=/path/to/asmalloc.so my_executable 58 | 59 | Under BASH: 60 | 61 | prompt$ export LD_PRELOAD=/path/to/asmalloc.so my_executable 62 | 63 | ### With JEMalloc: 64 | 65 | Under TCSH: 66 | 67 | prompt% env LD_PRELOAD=/path/to/asmalloc.jem.so:/path/to/libjemalloc.so my_executable 68 | 69 | Under BASH: 70 | 71 | prompt$ export LD_PRELOAD=/path/to/asmalloc.jem.so:/path/to/libjemalloc.so my_executable 72 | 73 | ## ASMalloc API: 74 | 75 | The ASMalloc API, declared in the file **include/asmalloc.h**, is as follows: 76 | 77 | /* 78 | * Library-exported version of the hook function. 79 | */ 80 | int asm_hook(void *arg, asm_stats_t **asm_stats, vm_stats_t **vm_stats); 81 | 82 | /* 83 | * Library-exported version of the command function. 84 | */ 85 | int asm_cmd(asm_cmd_t cmd, va_list args); 86 | 87 | /* 88 | * Store the type and location of a mallocation in thread-specific storage. 89 | */ 90 | void asm_mallocation_set(uint16_t type, uint16_t loc, ssize_t delta_size); 91 | 92 | /* 93 | * Return the type and location of a mallocation in thread-specific storage. 94 | */ 95 | void asm_mallocation_get(uint16_t *type, uint16_t loc, ssize_t *total_size, ssize_t *delta_size, struct timespec *last_time); 96 | 97 | The following commands are available to be sent via **as_cmd()**: 98 | 99 | ASM_CMD_SET_FEATURES -- Set the bit vector of ASMalloc features. 100 | ASM_CMD_SET_THRESHOLDS -- The the triplet of callback-triggering thresholds. 101 | ASM_CMD_SET_CALLBACK -- Set the user's callback. 102 | ASM_CMD_PRINT_STATS -- Print memory statistics. 103 | 104 | The following independent features are available to be combined using 105 | the bitwise OR operator ("|") and sent as the argument to the 106 | **ASM_CMD_SET_FEATURES** command: 107 | 108 | ASM_LOG_DATESTAMP -- Log the current date and time. 109 | ASM_LOG_THREAD_STATS -- Log thread statistics. 110 | ASM_LOG_MEM_COUNT_STATS -- Log memory count statistics. 111 | ASM_LOG_MALLOCATIONS -- Invoke callback on delta mallocations of sufficient size. 112 | ASM_LOG_BLOCKS -- Invoke callback on block allocations of sufficient size. 113 | ASM_LOG_MALLOC_INFO -- Log GLibC "malloc_info()" output. 114 | ASM_LOG_MALLOC_STATS -- Log GLibC "malloc_stats()" output. 115 | ASM_LOG_MALLINFO -- Log GLibC "mallinfo()" output. 116 | ASM_LOG_VM_STATS -- Log the process' Virtual Memory (VM) statistics. 117 | 118 | ## Sample Application Integration: 119 | 120 | Please see the **src/test-asmalloc.c** program for an example of how to 121 | integrate an application with ASMalloc. 122 | 123 | Note that the the file **include/test-mallocations.h** is sample database of 124 | (fictitious) memory allocation function call locations in the 125 | application. This file defines types and a statically-initialized table 126 | giving the info. about each memory allocation-related function call in 127 | the application source code. Ideally, the actual table would be 128 | automatically re-generated whenever the application source code is modified. 129 | -------------------------------------------------------------------------------- /bin/.gitignore: -------------------------------------------------------------------------------- 1 | [^.]* 2 | -------------------------------------------------------------------------------- /bin/VersionCheck.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # 4 | # VersionCheck.py: 5 | # Execute the given command, which must output a version of the form: 6 | # 7 | # {..}, where all three fields are non-negative integers and missing components default to 0 8 | # 9 | # and check against the supplied minimum version components. 10 | # 11 | # Returns 1 if the version is at least the minimum, 0 if not, or else -1 if an error occurs. 12 | # 13 | 14 | import os, sys 15 | 16 | def VersionCheck(command, minVersion): 17 | try: 18 | minVers = minVersion.split('.') 19 | while (len(minVers) < 3): 20 | minVers.append(0) 21 | minMajor, minMinor, minPatch = [int(c) for c in minVers] 22 | vers = os.popen(command).read().strip().split('.') 23 | while (len(vers) < 3): 24 | vers.append(0) 25 | major, minor, patch = [int(c) for c in vers] 26 | return 1 if (major > minMajor or 27 | (major == minMajor and (minor > minMinor or 28 | (minor == minMinor and patch >= minPatch)))) else 0 29 | except: 30 | return -1 31 | 32 | sys.stdout.write(str(VersionCheck(*sys.argv[1:3]))) 33 | -------------------------------------------------------------------------------- /doc/ProgressiveMemoryLeakDetection.txt: -------------------------------------------------------------------------------- 1 | Safe and Efficient Progressive Resource Leak Detection: ***PSI*** 29-Aug-2013 2 | ------------------------------------------------------- 3 | (For multi-threaded, Linux/C applications.) 4 | 5 | 0). Overview: 6 | - In general, the question of whether a program leaks memory is 7 | undecidable. (It can be reduced to the halting problem.) 8 | 9 | - In practice, however, it is both possible as well as extremely 10 | useful to be able to give even an approximate answer to the 11 | question, and specifically to point to a likely location 12 | where such a leak may be occurring for further investigation 13 | by the developers. 14 | 15 | - A program-agnostic, tools-based approach will be the most 16 | useful way to (approximately) answer this question. (Note 17 | that the details of the implementation are dependent upon the 18 | current Linux operating environment using the C language, 19 | GLibC, PThreads, and the GCC tool chain.) 20 | 21 | - Given these considerations, the approach proposed below is 22 | a Safe, Efficient, Progressive Resource Leak Detector that 23 | provides Positive and Negative Evidence for Resource Leaks. 24 | 25 | 1). Safe: 26 | - Thread-local memory accounting ==> No additional locking on 27 | most allocations / deallocations. 28 | 29 | - Tunable significant event detection (requires locking to 30 | periodically propagate local info. globally.) 31 | 32 | 2). Efficient: 33 | - (Essentially) Constant space requirement. 34 | 35 | - [Small overhead on allocated blocks, perhaps absorbed by 36 | reality of over-allocation ("malloc_usable_size()".)] 37 | 38 | - Light-weight in terms of both memory and CPU overhead. 39 | 40 | - Collection runs "on-line." 41 | 42 | - Detection can be enabled / disabled at any point. 43 | 44 | - Level of detection overhead tunable as a trade-off vs. 45 | latency / size of (supposed) detection. 46 | 47 | 3). Progressive Resource Leak Detection: 48 | - "Progressive" means leak keeps occurring in a long-lived process 49 | (e.g., servers.) 50 | 51 | - Method generally not applicable to bounded number of "one-time" 52 | leaks, except by incurring potentially intrusive impact on 53 | program efficiency / "correct" behavior by using a low 54 | significance threshold, which also implies the likelihood 55 | frequent of "false positive" reports (albeit offset by just 56 | as numerous "never mind"s.) 57 | 58 | - Prime example: GLibC dynamically-allocated memory leak detection. 59 | 60 | - Same ideas could be applied to FDs (FILE, socket, (named) 61 | pipe), threads (+ TLS/stack size), etc. 62 | 63 | 4). Positive / Negative Evidence for Leaks: 64 | - When a significant (according to the significance threshold for 65 | that resource) quantity of resource allocation changes 66 | (positive = allocation, negative = deallocation) occurs, if 67 | alerting is enabled (CB not NULL), then the thread detecting 68 | a significant event will invoke the significance callback function. 69 | 70 | - For memory, the thresholds are on delta #bytes allocated and 71 | delta #allocations. Other resources would only have the second. 72 | 73 | - Thus, each detecting thread needs to keep track of the current 74 | and last-reported resource usage to compute the delta. 75 | 76 | - The significance callback function will take a global lock protecting 77 | the global resource accounting data structure. <== Implies 78 | single-threaded, blocking the allocating / deallocating 79 | thread. (Alternative: MsgQ w/ separate reporting thread.) 80 | 81 | - The CB will aggregate the significant event report into the 82 | global resource account. 83 | 84 | - If the incoming report triggers a significant rise or fall in 85 | resource usage according to the appropriate threshold(s) [same 86 | threshold(s) as used by the detector??], then the appropriate 87 | message will be logged. 88 | 89 | - If the delta is > 0, the log message will be that a possible 90 | leak has been detected. 91 | 92 | - If the delta is < 0, the log message will be a "never mind" 93 | (i.e., that the previous report may have been a false positive. 94 | 95 | - In the case that a true progressive resource leak exists, it 96 | will eventually be detected by this method. 97 | 98 | - In the case that the presumed "leak" is actually legitimate 99 | (i.e., intended) a high-resource usage condition, a positive 100 | report will eventually be followed by a corresponding 101 | negative report. Thus, determining what's actually to be 102 | considered a leak is to be evaluated by the user. 103 | 104 | - In particular, if the significant event threshold(s) are low, 105 | numerous false positives followed by "never mind"s would be 106 | expected. Thus, tuning the threshold(s) to sufficiently high 107 | values to only report high-likelihood resource leaks is 108 | required to use this detection scheme effectively. 109 | 110 | 5). Implementation: 111 | - Use wrapper functions (or macros) for resource allocation / 112 | deallocation. 113 | 114 | - Use static program analysis to determine and construct a table 115 | of all locations where resources are allocated / deallocated 116 | via the wrapper functions. 117 | 118 | - Each program location is represented as a unique, small integer, 119 | corresponding to a function, file name, and line number within 120 | file. (Used as a compact, direct index into the allocation array.) 121 | 122 | - For human-readable logging, the program location table is 123 | automatically generated as a source file to be compiled into 124 | the program. 125 | 126 | - Hook system library functions: 127 | - Resource allocation / deallocation functions. 128 | - Thread creation / destruction. 129 | 130 | - Each thread is represented as a unique, small integer. 131 | (Used instead of thread ID for compaction and direct use as an 132 | index into array of thread ID x program location.) 133 | 134 | - Upon thread creation, create in TLS the following variables: 135 | - Thread ID. 136 | - Array indexed by location of accounting info., initialized 137 | to 0, and containing: 138 | - Net #allocations. 139 | - Net #bytes allocated (in the case of memory.) 140 | - Last reported net #allocations. 141 | - Last reported #bytes allocated (in the case of memory.) 142 | 143 | - Upon thread termination, all resource accounts must be summed into 144 | the global accounts. (This prevents positive / negative leaks 145 | from being missed through the creation of numerous short-lived 146 | threads.) 147 | 148 | - Upon resource allocation, increase allocation size by N bytes to store: 149 | - ID of allocating thread. 150 | - Allocating program location. 151 | - [Optional] Flag bits to differentiate the actual library 152 | function used for the allocation. 153 | - [Optional] Size of allocation. (Only for determining 154 | allocation overhead, which should generally be minimal if 155 | the heap allocator is any good.) 156 | 157 | - Threshold set / get config. parameter for each type of resource. 158 | 159 | - Command to enable / disable resource leak detection by setting 160 | the significance callback function variable to the actual 161 | function or NULL. 162 | 163 | - Upon receipt of the significance callback, grab the lock, 164 | update the global account, and log if threshold crossed. 165 | 166 | Initialization and Control: 167 | 168 | - Application looks up the command function and the hook 169 | function via "dlsym()". 170 | 171 | - The command function is used to: 172 | 1). Register a logging function used to output messages via 173 | the application's logging system. 174 | 2). Pass command requests to the resource accounting system, 175 | e.g., have all threads send there accounting info. up to 176 | the center upon the next resource allocation-related 177 | function call. 178 | 3). What else?? 179 | 180 | - The hook can be called periodically to allow the library to do 181 | work: 182 | 1). Log the current state of resource accounting. 183 | 2). Perhaps request threads to send up their local 184 | accounting data globally. 185 | 3). What else?? 186 | 187 | 6). Comparison with Other Approaches: 188 | - GLibC memory tracing / heap checking functions: 189 | Not multi-thread safe! 190 | Only useful on toy problems. 191 | No ability to filter allocation stream for only specific 192 | allocations, leading to data overload (in an external log 193 | file (could on be a RAM disk.)) 194 | 195 | - Original Aerospike "alloc.[ch]" Memory Accounting: 196 | Uses 2 hash tables for "precise" memory accounting 197 | (Ptr==>Loc & Loc==>AllocInfo.) 198 | Has the ability to query allocations database by time, 199 | space, net and total counts, and change. 200 | Has multi-thread issues that can become prohibitive at large 201 | scale. 202 | Has relatively high run-time and memory overhead costs. 203 | Can be enabled / disabled at run time with a loss of 204 | precision (and some apparent multi-thread hazard risk of crash.) 205 | Apparent multi-thread issues, likely due to allocating and 206 | freeing on different threads, perhaps in reverse order(!), 207 | which are mitigated by running "non-strict", which ignores 208 | unexpected frees (due to either double frees or out-of-order 209 | detection.) 210 | Never started up on server daemon with 99GB namespace. 211 | 212 | - "Light-Weight" Aerospike Memory Accounting: 213 | Similar, simpler, earlier version of this approach that 214 | simply wraps the allocation / deallocation functions and 215 | provides thread-safe, light-weight net memory allocation 216 | statistics, but there is no way to determine who allocated 217 | what, where, and when. 218 | Ran with no discernible performance degradation on 150GB+ 219 | process size server daemon with 99GB namespace. 220 | 221 | - Valgrind: 222 | Precise resource usage tracking, but extremely high memory 223 | and performance overhead that is totally unusable in any 224 | non-toy clustered server environment. 225 | 226 | 7). Implementation Fine Points: 227 | - The resource accounting feature is enabled using the 228 | "LD_PRELOAD" mechanism to load the resource accounting 229 | shared library which hooks the relevant GLibC functions. 230 | 231 | - The application may look up the command and hook functions in 232 | the library to communicate with the resource. (Not 233 | required, but generally necessary to be useful.) 234 | 235 | - In GLibC, "vfprintf()" in "stdio-common/vfprintf.c" calls "free 236 | (args_malloced)" where "args_malloced" = 0 frequently, and 237 | thus all "free(0)" calls are not counted. 238 | 239 | - In GLibC, "_dlerror_run()" in "dlfcn/dlerror.c" calls 240 | "calloc (1, sizeof (*result))", where the total request size 241 | is 32 when using PThreads (i.e., including "pthread.h".) 242 | Since this happens before the memory accounting functions 243 | are registered, there is a special case in the accounting 244 | version of "calloc()" that allocates any pre-init. requests 245 | from a statically-allocated (1KB) buffer. Note that if any 246 | other such allocation occurs, the buffer size may need to be 247 | increased. Also note that if such a request were ever to be 248 | freed, it would fail. In this case, it's used to allocate a 249 | thread specific "key", which is never freed. 250 | 251 | - To hook "pthread_create()", a wrapper start routine and an 252 | associated data type representing the thread to be created 253 | (thread ID, start routine, and argument) are used. The 254 | wrapper start routine sets values of the thread-specific 255 | keys (ID, resource accounting table location) and 256 | initializes the resource accounting table. 257 | 258 | - Upon thread termination, the thread-specific "my_key" 259 | destructor will be called, which will send up the thread's 260 | final resource accounting info. globally. 261 | 262 | - Since "pthread_self()" values are re-used, the resource 263 | accounting system uses its own thread IDs. (Could also use 264 | the result of "gettid" system call.) 265 | 266 | - To prevent GCC from using builtins for "strdup()" and 267 | "strndup()" (which happens whenever optimization is enabled 268 | (-O1 and greater)), the following construct is used: 269 | 270 | retval = (*(&(function)))(args...); 271 | 272 | Otherwise, the functions themselves will not be called in all 273 | cases that they are present in the source code (depending 274 | upon the special cases handled as builtins by the compiler.) 275 | For resource accounting, it would generally be better not to 276 | have these requests be optimized, unless there is a need to 277 | account for the actions taken by the results of the builtin 278 | transformations. 279 | -------------------------------------------------------------------------------- /include/asmalloc.h: -------------------------------------------------------------------------------- 1 | /* 2 | * include/asmalloc.h 3 | * 4 | * Copyright (C) 2013-2014 Aerospike, Inc. 5 | * 6 | * Portions may be licensed to Aerospike, Inc. under one or more contributor 7 | * license agreements. 8 | * 9 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 10 | * use this file except in compliance with the License. You may obtain a copy of 11 | * the License at http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 15 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 16 | * License for the specific language governing permissions and limitations under 17 | * the License. 18 | */ 19 | 20 | /* 21 | * SYNOPSIS 22 | * Declarations file for the ASMalloc memory allocation tracking tool. 23 | * This header file provides the external API to ASMalloc for use by 24 | * the program being instrumented. 25 | */ 26 | 27 | #pragma once 28 | 29 | #include 30 | #include 31 | #include 32 | 33 | #include "vm_stats.h" 34 | 35 | /* 36 | * Bit vector flags for enabling ASMalloc features. 37 | * (Logging happens in the hook function.) 38 | */ 39 | typedef enum asm_features_e { 40 | ASM_LOG_DATESTAMP = (1 << 0), // Log the current date and time. 41 | ASM_LOG_THREAD_STATS = (1 << 1), // Log thread statistics. 42 | ASM_LOG_MEM_COUNT_STATS = (1 << 2), // Log memory count statistics. 43 | ASM_LOG_MALLOCATIONS = (1 << 3), // Invoke callback on delta mallocations of sufficient size. 44 | ASM_LOG_BLOCKS = (1 << 4), // Invoke callback on block allocations of sufficient size. 45 | ASM_LOG_MALLOC_INFO = (1 << 5), // Log GLibC "malloc_info()" output. 46 | ASM_LOG_MALLOC_STATS = (1 << 6), // Log GLibC "malloc_stats()" output. 47 | ASM_LOG_MALLINFO = (1 << 7), // Log GLibC "mallinfo()" output. 48 | ASM_LOG_VM_STATS = (1 << 8) // Log the process' Virtual Memory (VM) statistics. 49 | } asm_features_t; 50 | 51 | /* 52 | * Type for memory accounting commands. 53 | */ 54 | typedef enum asm_cmd_e { 55 | ASM_CMD_SET_FEATURES, // Set the bit vector of ASMalloc features. 56 | ASM_CMD_SET_THRESHOLDS, // The the triplet of callback-triggering thresholds. 57 | ASM_CMD_SET_CALLBACK, // Set the user's callback. 58 | ASM_CMD_PRINT_STATS // Print memory statistics. 59 | } asm_cmd_t; 60 | 61 | /* 62 | * Type for the memory statistics, all in bytes. 63 | */ 64 | typedef struct asm_stats_s { 65 | size_t mem_count; // Net dynamic memory allocated by the process. 66 | size_t net_mmaps; // Net memory "mmap(2)"'d by the process. 67 | size_t net_shm; // Net Sys V shared memory held by the process. 68 | } asm_stats_t; 69 | 70 | /* 71 | * Library-exported version of the hook function. 72 | */ 73 | int asm_hook(void *arg, asm_stats_t **asm_stats, vm_stats_t **vm_stats); 74 | 75 | /* 76 | * Library-exported version of the command function. 77 | */ 78 | int asm_cmd(asm_cmd_t cmd, va_list args); 79 | 80 | /* 81 | * Store the type and location of a mallocation in thread-specific storage. 82 | */ 83 | void asm_mallocation_set(uint16_t type, uint16_t loc, ssize_t delta_size); 84 | 85 | /* 86 | * Return the type and location of a mallocation in thread-specific storage. 87 | */ 88 | void asm_mallocation_get(uint16_t *type, uint16_t loc, ssize_t *total_size, ssize_t *delta_size, struct timespec *last_time); 89 | -------------------------------------------------------------------------------- /include/test-mallocations.h: -------------------------------------------------------------------------------- 1 | /* 2 | * include/test-mallocations.h 3 | * 4 | * Copyright (C) 2013-2014 Aerospike, Inc. 5 | * 6 | * Portions may be licensed to Aerospike, Inc. under one or more contributor 7 | * license agreements. 8 | * 9 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 10 | * use this file except in compliance with the License. You may obtain a copy of 11 | * the License at http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 15 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 16 | * License for the specific language governing permissions and limitations under 17 | * the License. 18 | */ 19 | 20 | /* 21 | * SYNOPSIS 22 | * Sample database of (fictitious) memory allocation function call locations 23 | * in the application. This file defines types and a statically-initialized 24 | * table giving the info. about each memory allocation-related function call 25 | * in the application source code. Ideally, the actual table would be 26 | * automatically re-generated whenever the application source code is modified. 27 | * This is part of the ASMalloc memory allocation tracking tool package. 28 | */ 29 | 30 | #pragma once 31 | 32 | typedef enum mallocation_type_e { 33 | MALLOCATION_TYPE_NONE, 34 | MALLOCATION_TYPE_REALLOC, 35 | MALLOCATION_TYPE_FREE, 36 | MALLOCATION_TYPE_CALLOC, 37 | MALLOCATION_TYPE_VALLOC, 38 | MALLOCATION_TYPE_STRDUP, 39 | MALLOCATION_TYPE_STRNDUP, 40 | MALLOCATION_TYPE_MALLOC, 41 | } mallocation_type_t; 42 | 43 | char *mallocation_type_names[] = { 44 | "MALLOCATION_TYPE_NONE", 45 | "MALLOCATION_TYPE_REALLOC", 46 | "MALLOCATION_TYPE_FREE", 47 | "MALLOCATION_TYPE_CALLOC", 48 | "MALLOCATION_TYPE_VALLOC", 49 | "MALLOCATION_TYPE_STRDUP", 50 | "MALLOCATION_TYPE_STRNDUP", 51 | "MALLOCATION_TYPE_MALLOC", 52 | }; 53 | 54 | typedef struct mallocation_s { 55 | mallocation_type_t type; 56 | char *file; 57 | int line; 58 | int id; 59 | } mallocation_t; 60 | 61 | #define NUM_MALLOCATIONS (7) 62 | 63 | mallocation_t mallocations[NUM_MALLOCATIONS] = { 64 | { MALLOCATION_TYPE_NONE, "", 0, 0 }, /* Non-existent mallocation. */ 65 | { MALLOCATION_TYPE_CALLOC, "test1.c", 101, 1 }, 66 | { MALLOCATION_TYPE_FREE, "test2.c", 202, 2 }, 67 | { MALLOCATION_TYPE_MALLOC, "test3.c", 303, 3 }, 68 | { MALLOCATION_TYPE_REALLOC, "test4.c", 404, 4 }, 69 | { MALLOCATION_TYPE_STRDUP, "test5.c", 505, 5 }, 70 | { MALLOCATION_TYPE_VALLOC, "test6.c", 606, 6 } 71 | }; 72 | -------------------------------------------------------------------------------- /include/vm_stats.h: -------------------------------------------------------------------------------- 1 | /* 2 | * include/vm_stats.h 3 | * 4 | * Copyright (C) 2013-2014 Aerospike, Inc. 5 | * 6 | * Portions may be licensed to Aerospike, Inc. under one or more contributor 7 | * license agreements. 8 | * 9 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 10 | * use this file except in compliance with the License. You may obtain a copy of 11 | * the License at http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 15 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 16 | * License for the specific language governing permissions and limitations under 17 | * the License. 18 | */ 19 | 20 | /* 21 | * SYNOPSIS 22 | * Declarations file for the Virtual Memory (VM) statistics functions of 23 | * the ASMalloc memory allocation tracking tool. These types and functions 24 | * are used both internally by ASMalloc and by the program being instrumented. 25 | */ 26 | 27 | #pragma once 28 | 29 | #include 30 | 31 | /* 32 | * Type representing a key name string and its offset in the "vm_stats_t" object. 33 | */ 34 | typedef struct vm_stats_desc_s { 35 | char *name; 36 | size_t offset; 37 | } vm_stats_desc_t; 38 | 39 | /* 40 | * Array of VM-related key names and structure offsets. 41 | */ 42 | extern vm_stats_desc_t vm_stats_desc[]; 43 | 44 | /* 45 | * Type representing the Virtual Memory (VM)-related statistics about a process. 46 | * 47 | * Note: Not all of these will exist on all versions of the Linux kernel, so 48 | * UINT64_MAX is used to indicate a non-existent value. 49 | * 50 | * Also Note: All of these related types must be kept in sync.! 51 | */ 52 | typedef struct vm_stats_s { 53 | vm_stats_desc_t *desc; // Description of the VM stats structure. 54 | uint64_t 55 | vm_peak, // Peak Virtual Set Size in KB. 56 | vm_size, // Current Virtual Set Size in KB. 57 | vm_lck, // Current "mlock(2)"'d memory size in KB. 58 | vm_pin, // Current pinned (unswappable) memory size in KB. [In 3.2+ kernels.] 59 | vm_hwm, // Peak Resident Set Size in KB. 60 | vm_rss, // Current Resident Set Size in KB. 61 | vm_data, // Size of "data" segment in KB. 62 | vm_stk, // Size of stack in KB. 63 | vm_exe, // Size of "text" segment in KB. 64 | vm_lib, // Shared library size (all pages, not just used ones!) 65 | vm_pte, // Size of Page Table Entries in KB. 66 | vm_swap; // Swap space used size in KB. [In 2.6.34+ kernels.] 67 | } vm_stats_t; 68 | 69 | /* 70 | * Symbolic key names for the VM-related statistics, corresponding to the position in the "vm_stats_desc[]" array. 71 | */ 72 | typedef enum vm_stats_key_e { 73 | VM_PEAK, 74 | VM_SIZE, 75 | VM_LCK, 76 | VM_PIN, 77 | VM_HWM, 78 | VM_RSS, 79 | VM_DATA, 80 | VM_STK, 81 | VM_EXE, 82 | VM_LIB, 83 | VM_PTE, 84 | VM_SWAP, 85 | VM_NUM_KEYS 86 | } vm_stats_key_t; 87 | 88 | /* 89 | * Return the name for the given key. 90 | */ 91 | #define vm_stats_key_name(key) (vm_stats_desc[key].name) 92 | 93 | /* 94 | * Return the value for the VM statistic with the given key. 95 | */ 96 | #define vm_stats_get_key_value(vm_stats_tp, key) (* (uint64_t *)(((char *) vm_stats_tp) + vm_stats_desc[key].offset)) 97 | 98 | /* 99 | * Set the value for the VM statistic with the given key to the given value. 100 | */ 101 | #define vm_stats_set_key_value(vm_stats_tp, key, val) ((* (uint64_t *)(((char *) vm_stats_tp) + vm_stats_desc[key].offset)) = (val)) 102 | 103 | /* 104 | * Read the kernel's VM-related statistics for process PID into the given structure. 105 | */ 106 | int get_vm_stats(int pid, vm_stats_t *stats); 107 | 108 | /* 109 | * Print the current VM-related statistics for the given PID. 110 | */ 111 | vm_stats_t *log_vm_stats(int pid); 112 | -------------------------------------------------------------------------------- /lib/.gitignore: -------------------------------------------------------------------------------- 1 | [^.]* 2 | -------------------------------------------------------------------------------- /obj/.gitignore: -------------------------------------------------------------------------------- 1 | [^.]* 2 | -------------------------------------------------------------------------------- /src/asmalloc.c: -------------------------------------------------------------------------------- 1 | /* 2 | * src/asmalloc.c 3 | * 4 | * Copyright (C) 2013-2016 Aerospike, Inc. 5 | * 6 | * Portions may be licensed to Aerospike, Inc. under one or more contributor 7 | * license agreements. 8 | * 9 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 10 | * use this file except in compliance with the License. You may obtain a copy of 11 | * the License at http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 15 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 16 | * License for the specific language governing permissions and limitations under 17 | * the License. 18 | */ 19 | 20 | /* 21 | * SYNOPSIS 22 | * ASMalloc is a memory allocation tracking tool with extremely low overhead, 23 | * suitable for use on multi-threaded, high-performance, high-memory-use systems. 24 | * The tool is embodied in a dynamic shared library used via "LD_PRELOAD". It 25 | * provids wrapper functions for the standard C library memory allocation-related 26 | * function plus the control APIs necessary for performing memory allocation tracking. 27 | */ 28 | 29 | #define _GNU_SOURCE 30 | 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | 46 | #include "asmalloc.h" 47 | 48 | /* 49 | * Small buffer to handle "calloc()" calls in "libdl" at startup time. 50 | */ 51 | #define HAKBUF_SIZE (1024) 52 | static char hakbuf[HAKBUF_SIZE]; 53 | static char *hakbuf_ptr = hakbuf; 54 | static size_t haksize = sizeof(hakbuf); 55 | 56 | /* 57 | * Enable debug printouts. 58 | */ 59 | //#define DEBUG 60 | 61 | /* 62 | * Define macro to control printouts 63 | */ 64 | #ifdef DEBUG 65 | #define dfprintf fprintf 66 | #define dfputs fputs 67 | #else 68 | #define dfprintf if (false) fprintf 69 | #define dfputs if (false) fputs 70 | #endif 71 | 72 | /* 73 | * Log a single character. 74 | */ 75 | #define LOG(c) { putchar(c); putchar('\n'); fflush(stdout); } 76 | 77 | /* 78 | * Log two characters. 79 | */ 80 | #define LOG2(c, d) { putchar(c); putchar(d); putchar('\n'); fflush(stdout); } 81 | 82 | /* 83 | * Break into the debugger if the "BREAK" environment variable is set. 84 | */ 85 | #define BREAK() if (getenv("BREAK")) { __asm__("int3"); } 86 | 87 | /* 88 | * Macro to initialize us only once. 89 | */ 90 | #define INIT(x) if (!inited) { if (x <= 1) { init(x); } } 91 | 92 | /* 93 | * Are any of the given features enabled? 94 | */ 95 | #define FEATURE(x) (g_features & (x)) 96 | 97 | /* 98 | * Are all of (and only) the given features enabled? 99 | */ 100 | #define FEATURES(x) ((g_features & (x)) & ~(g_features & ~(x))) 101 | 102 | /* 103 | * Default features to enable. 104 | */ 105 | #define DEFAULT_FEATURES (ASM_LOG_DATESTAMP | ASM_LOG_THREAD_STATS | ASM_LOG_MEM_COUNT_STATS | ASM_LOG_MALLOCATIONS | ASM_LOG_BLOCKS | ASM_LOG_VM_STATS) 106 | 107 | /* 108 | * Features currently enabled. 109 | */ 110 | static uint64_t g_features = DEFAULT_FEATURES; 111 | 112 | /* 113 | * Maximum number of unique memory allocation program locations. 114 | * 115 | * [Note: This is used to size the allocation thread-specific data.] 116 | * 117 | * ***WARNING: Bad things will happen if this number is exceeded by the program!!*** 118 | */ 119 | #define MAX_TLS_MALLOCATIONS (1152) 120 | 121 | /* 122 | * Default minimum size of blocks triggering mallocation alerts. 123 | */ 124 | #define DEFAULT_THRESH_BLOCK_SIZE_BYTES (512 * 1024) 125 | 126 | /* 127 | * Default minimum delta size between mallocation alerts per thread. 128 | */ 129 | #define DEFAULT_THRESH_DELTA_SIZE_BYTES (1024 * 1024) 130 | 131 | /* 132 | * Default minimum time between mallocation alerts per thread. 133 | */ 134 | #define DEFAULT_THRESH_DELTA_TIME_SECONDS (60) 135 | 136 | /* 137 | * Threshold for triggering memory allocation callbacks. 138 | */ 139 | static size_t g_thresh_block_size = DEFAULT_THRESH_BLOCK_SIZE_BYTES; 140 | static size_t g_thresh_delta_size = DEFAULT_THRESH_DELTA_SIZE_BYTES; 141 | static time_t g_thresh_delta_time = DEFAULT_THRESH_DELTA_TIME_SECONDS; 142 | 143 | /* 144 | * Type encapsulating all of the necessary state for a thread to be created. 145 | * 146 | * This value is passed to "asm_start_routine()" to wrap the user's thread start 147 | * routine with the extra steps we need to perform on every thread (i.e., creating 148 | * the thread-specific data. 149 | */ 150 | typedef struct asm_thread_launcher_s { 151 | uint64_t id; // Our ID for this thread. 152 | void *(*start_routine)(void *); // User's thread start routine. 153 | void *arg; // User's thread start routine argument. 154 | const char *sname; // Name of start routine. 155 | } asm_thread_launcher_t; 156 | 157 | /* 158 | * Type for distinguishing allocations from frees. 159 | */ 160 | typedef enum alloc_or_free_e { 161 | ALLOC = 0, // Any memory-allocting operation. 162 | FREE = 1 // Any memory-freeing operation. 163 | } alloc_or_free_t; 164 | 165 | /* 166 | * Location where the mallocation "loc" of the memory allocation is located 167 | * (i.e., the last "int" in usable size of the memory block.) 168 | */ 169 | #define LOC_LOC(ptr) ((int *) (((char *) ptr) + malloc_usable_size(ptr) - sizeof(int))) 170 | // XXX -- An alternative way. 171 | //#define LOC_LOC(ptr) ((int *) (((char *) ptr) + actual_size - sizeof(int))) 172 | 173 | /* 174 | * Set the "loc" of the mallocation at the end of the memory block. 175 | */ 176 | #if 0 177 | #define SET_LOC_LOC(ptr) \ 178 | if (type && tls_mallocation_ptr) { \ 179 | ssize_t dds = actual_size - delta_size; \ 180 | tls_mallocation_ptr->total_size += (actual_size - delta_size); \ 181 | if (dds < 0) \ 182 | fprintf(stderr, "sll(): type = %d ; loc = %d ; as = %ld ; ds = %ld ; dds = %ld\n", type, loc, actual_size, delta_size, actual_size - delta_size); \ 183 | *LOC_LOC(ptr) = tls_mallocations[0].loc; \ 184 | } 185 | #elif 0 186 | #define SET_LOC_LOC(ptr) \ 187 | if (type && tls_mallocation_ptr) { \ 188 | ssize_t dds = actual_size - size; \ 189 | tls_mallocation_ptr->total_size += (actual_size - size); \ 190 | if (dds) \ 191 | fprintf(stderr, "sll(): type = %d ; loc = %d ; as = %ld ; s = %ld ; ds = %ld ; dds = %ld\n", type, loc, actual_size, size, delta_size, dds); \ 192 | *LOC_LOC(ptr) = tls_mallocations[0].loc; \ 193 | } 194 | #else 195 | #define SET_LOC_LOC(ptr, size) \ 196 | if (type && tls_mallocation_ptr) { \ 197 | ssize_t dds = actual_size - size; \ 198 | tls_mallocation_ptr->total_size += dds; \ 199 | *LOC_LOC(ptr) = tls_mallocations[0].loc; \ 200 | } 201 | #endif 202 | 203 | /* 204 | * Set the memory allocation info. for an allocating operation. 205 | */ 206 | #define SET_ALLOC_INFO() SET_MALLOCATION_INFO(ALLOC, 0) 207 | 208 | /* 209 | * Set the memory allocation info. for a freeing operation on the given pointer. 210 | */ 211 | #define SET_FREE_INFO(ptr) SET_MALLOCATION_INFO(FREE, ptr) 212 | 213 | /* 214 | * Set the memory allocation info. for this operation. 215 | */ 216 | #define SET_MALLOCATION_INFO(alloc_or_free, ptr) \ 217 | tls_mallocation_t *tls_mallocations = (tls_mallocation_t *) pthread_getspecific(tls_mallocations_key); \ 218 | int type = -1; \ 219 | if (!tls_mallocations) { \ 220 | tls_mallocations = g_tls_mallocations; \ 221 | type = tls_mallocations[0].type; \ 222 | } \ 223 | int loc = 0; \ 224 | tls_mallocation_t *tls_mallocation_ptr = NULL; \ 225 | ssize_t delta_size = 0; \ 226 | ssize_t total_size = 0; \ 227 | if (type == -1) { \ 228 | loc = ((ALLOC == (alloc_or_free)) ? tls_mallocations[0].loc : *LOC_LOC(ptr)); \ 229 | tls_mallocation_ptr = &tls_mallocations[loc]; \ 230 | delta_size = tls_mallocation_ptr->delta_size = tls_mallocations[0].delta_size; \ 231 | total_size = tls_mallocation_ptr->total_size += tls_mallocations[0].delta_size; \ 232 | type = tls_mallocation_ptr->type = tls_mallocations[0].type; \ 233 | } 234 | 235 | #if 1 236 | // XXX -- Send ds instead of delta_size. 237 | #define MAYBE_DO_CB() \ 238 | if (type && tls_mallocation_ptr) { \ 239 | ssize_t ds; \ 240 | if (FEATURE(ASM_LOG_MALLOCATIONS) && (labs(ds = (total_size - tls_mallocation_ptr->last_size)) > g_thresh_delta_size) && (labs(tls_mallocations[0].last_time.tv_sec - tls_mallocation_ptr->last_time.tv_sec) > g_thresh_delta_time)) { \ 241 | dfprintf(stderr, "smi(): Call type %d @ loc %d delta_size = %ld total_size = %ld!\n", type, loc, delta_size, total_size); \ 242 | if (g_cb) { \ 243 | asm_thread_launcher_t *asmtl = (asm_thread_launcher_t *) pthread_getspecific(asmtl_key); \ 244 | (g_cb)((asmtl ? asmtl->id : 0), type, loc, ds, total_size, &(tls_mallocations[0].last_time), g_cb_udata); \ 245 | } \ 246 | tls_mallocation_ptr->last_size = total_size; \ 247 | tls_mallocation_ptr->last_time.tv_sec = tls_mallocations[0].last_time.tv_sec; \ 248 | tls_mallocation_ptr->last_time.tv_nsec = tls_mallocations[0].last_time.tv_nsec; \ 249 | } \ 250 | } 251 | #else 252 | #define MAYBE_DO_CB() \ 253 | if (type && tls_mallocation_ptr) { \ 254 | if (FEATURE(ASM_LOG_MALLOCATIONS) && (labs(total_size - tls_mallocation_ptr->last_size) > g_thresh_delta_size) && (labs(tls_mallocations[0].last_time.tv_sec - tls_mallocation_ptr->last_time.tv_sec) > g_thresh_delta_time)) { \ 255 | dfprintf(stderr, "smi(): Call type %d @ loc %d delta_size = %ld total_size = %ld!\n", type, loc, delta_size, total_size); \ 256 | if (g_cb) { \ 257 | asm_thread_launcher_t *asmtl = (asm_thread_launcher_t *) pthread_getspecific(asmtl_key); \ 258 | (g_cb)((asmtl ? asmtl->id : 0), type, loc, delta_size, total_size, &(tls_mallocations[0].last_time), g_cb_udata); \ 259 | } \ 260 | tls_mallocation_ptr->last_size = total_size; \ 261 | tls_mallocation_ptr->last_time.tv_sec = tls_mallocations[0].last_time.tv_sec; \ 262 | tls_mallocation_ptr->last_time.tv_nsec = tls_mallocations[0].last_time.tv_nsec; \ 263 | } \ 264 | } 265 | #endif 266 | 267 | /* 268 | * Type representing the state of a memory allocation location in a program. 269 | */ 270 | typedef struct tls_mallocation_s { 271 | ssize_t total_size; // Cumulative net total size allocated by this thread. 272 | ssize_t delta_size; // Most recent change in size. 273 | ssize_t last_size; // Total size last reported change from this location. 274 | struct timespec last_time; // Time of last reported change from this location. 275 | uint16_t type; // Type of the last memory allocation-related operation. 276 | uint16_t loc; // Location of the allocation in the program. 277 | } __attribute__((__packed__)) tls_mallocation_t; 278 | 279 | /* 280 | * Memory allocation locations for the main process (i.e., not in any thread.) 281 | */ 282 | static tls_mallocation_t g_tls_mallocations[MAX_TLS_MALLOCATIONS]; 283 | 284 | /* 285 | * Thread-specific storage key holding a pointer to the ASM thread launcher. 286 | */ 287 | static pthread_key_t asmtl_key; 288 | 289 | /* 290 | * Thread-specific storage key holding the memory allocation location information. 291 | */ 292 | static pthread_key_t tls_mallocations_key; 293 | 294 | /* 295 | * Callback function registered by the program. 296 | */ 297 | static void (*g_cb)(uint64_t thread_id, uint16_t type, uint16_t loc, ssize_t delta_size, ssize_t total_size, struct timespec *last_time, void *udata) = NULL; 298 | 299 | /* 300 | * User-supplied private data to be passed to the callback function. 301 | */ 302 | static void *g_cb_udata = NULL; 303 | 304 | /* 305 | * Have we been initialized? 306 | */ 307 | static bool inited = false; 308 | 309 | /* 310 | * ASMalloc memory statistics (written on the hook thread.) 311 | */ 312 | static asm_stats_t g_asm_stats; 313 | 314 | /* 315 | * Lock to serialize memory counting. 316 | */ 317 | static pthread_mutex_t init_lock = PTHREAD_MUTEX_INITIALIZER; 318 | 319 | // Function pointer variables for the shadowed original functions. 320 | static int (*original_pthread_create)(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg); 321 | static void *(*original_calloc)(size_t nmemb, size_t size); 322 | static void *(*original_malloc)(size_t size); 323 | static void (*original_free)(void *ptr); 324 | static void *(*original_realloc)(void *ptr, size_t size); 325 | static char *(*original_strdup)(const char *s); 326 | static char *(*original_strndup)(const char *s, size_t n); 327 | static int (*original_posix_memalign)(void **memptr, size_t alignment, size_t size); 328 | 329 | static int (*original_brk)(void *addr); 330 | static void *(*original_sbrk)(intptr_t increment); 331 | static void *(*original___default_morecore)(ptrdiff_t __size); 332 | 333 | #ifdef FOR_JEMALLOC 334 | static void *(*original_mallocx)(size_t size, int flags); 335 | static void *(*original_rallocx)(void *ptr, size_t size, int flags); 336 | static void (*original_malloc_stats_print)(void (*write_cb)(void *, const char *), void *je_cbopaque, const char *opts); 337 | #else 338 | static void *(*original_mmap)(void *addr, size_t length, int prot, int flags, int fd, off_t offset); 339 | static void *(*original_mmap64)(void *addr, size_t length, int prot, int flags, int fd, off64_t offset); 340 | static int (*original_munmap)(void *addr, size_t length); 341 | #endif 342 | 343 | static void *(*original_shmat)(int shmid, const void *shmaddr, int shmflg); 344 | static int (*original_shmctl)(int shmid, int cmd, struct shmid_ds *buf); 345 | static int (*original_shmdt)(const void *shmaddr); 346 | static int (*original_shmget)(key_t key, size_t size, int shmflg); 347 | 348 | 349 | /*****************************************************************************************/ 350 | 351 | /* 352 | * Basic Atomics. 353 | */ 354 | 355 | typedef volatile uint64_t cf_atomic64; 356 | 357 | #define cf_atomic64_get(a) (a) 358 | #define cf_atomic64_set(a, b) (*(a) = (b)) 359 | 360 | static inline int64_t 361 | cf_atomic64_add(cf_atomic64 *a, int64_t b) 362 | { 363 | int64_t i = b; 364 | 365 | __asm__ __volatile__ ("lock; xaddq %0, %1" : "+r" (b), "+m" (*a) : : "memory"); 366 | 367 | return(b + i); 368 | } 369 | #define cf_atomic64_sub(a,b) (cf_atomic64_add((a), (0 - (b)))) 370 | #define cf_atomic64_incr(a) (cf_atomic64_add((a), 1)) 371 | #define cf_atomic64_decr(a) (cf_atomic64_add((a), -1)) 372 | 373 | /*****************************************************************************************/ 374 | 375 | // Counter for created pthreads. 376 | static cf_atomic64 thread_id = 0; 377 | 378 | // Number of currently alive pthreads. 379 | static cf_atomic64 live_threads = 0; 380 | 381 | // Counters for numbers of allocation function calls. 382 | cf_atomic64 mem_count = 0; 383 | cf_atomic64 mem_count_mallocs = 0; 384 | cf_atomic64 mem_count_frees = 0; 385 | cf_atomic64 mem_count_callocs = 0; 386 | cf_atomic64 mem_count_reallocs = 0; 387 | cf_atomic64 mem_count_strdups = 0; 388 | cf_atomic64 mem_count_strndups = 0; 389 | cf_atomic64 mem_count_vallocs = 0; 390 | 391 | cf_atomic64 mem_count_brks = 0; 392 | cf_atomic64 mem_count_sbrks = 0; 393 | cf_atomic64 mem_count_morecores = 0; 394 | 395 | #ifdef FOR_JEMALLOC 396 | cf_atomic64 mem_count_mallocxs = 0; 397 | cf_atomic64 mem_count_rallocxs = 0; 398 | 399 | cf_atomic64 mem_count_mallocx_total = 0; 400 | cf_atomic64 mem_count_rallocx_plus_total = 0; 401 | cf_atomic64 mem_count_rallocx_minus_total = 0; 402 | #endif 403 | 404 | cf_atomic64 mem_count_net_mmaps = 0; 405 | cf_atomic64 mem_count_mmaps = 0; 406 | cf_atomic64 mem_count_mmap64s = 0; 407 | cf_atomic64 mem_count_munmaps = 0; 408 | 409 | // Counters for numbers bytes allocated / released by allocation function calls. 410 | cf_atomic64 mem_count_malloc_total = 0; 411 | cf_atomic64 mem_count_free_total = 0; 412 | cf_atomic64 mem_count_calloc_total = 0; 413 | cf_atomic64 mem_count_realloc_plus_total = 0; 414 | cf_atomic64 mem_count_realloc_minus_total = 0; 415 | cf_atomic64 mem_count_strdup_total = 0; 416 | cf_atomic64 mem_count_strndup_total = 0; 417 | cf_atomic64 mem_count_valloc_total = 0; 418 | 419 | cf_atomic64 mem_count_sbrk_total = 0; 420 | cf_atomic64 mem_count_morecore_total = 0; 421 | 422 | cf_atomic64 mem_count_net_mmap_total = 0; 423 | cf_atomic64 mem_count_mmap_total = 0; 424 | cf_atomic64 mem_count_mmap64_total = 0; 425 | cf_atomic64 mem_count_munmap_total = 0; 426 | 427 | cf_atomic64 mem_count_shmats = 0; 428 | cf_atomic64 mem_count_shmctls = 0; 429 | cf_atomic64 mem_count_shmdts = 0; 430 | cf_atomic64 mem_count_shmgets = 0; 431 | cf_atomic64 mem_count_net_shm = 0; 432 | 433 | /* 434 | * Initialize the memory accounting statistics. 435 | */ 436 | static void init_counters(void) 437 | { 438 | memset(&g_asm_stats, sizeof(g_asm_stats), 0); 439 | 440 | cf_atomic64_set(&mem_count, 0); 441 | cf_atomic64_set(&mem_count_mallocs, 0); 442 | cf_atomic64_set(&mem_count_frees, 0); 443 | cf_atomic64_set(&mem_count_callocs, 0); 444 | cf_atomic64_set(&mem_count_reallocs, 0); 445 | cf_atomic64_set(&mem_count_strdups, 0); 446 | cf_atomic64_set(&mem_count_strndups, 0); 447 | cf_atomic64_set(&mem_count_vallocs, 0); 448 | 449 | cf_atomic64_set(&mem_count_brks, 0); 450 | cf_atomic64_set(&mem_count_sbrks, 0); 451 | cf_atomic64_set(&mem_count_morecores, 0); 452 | 453 | #ifdef FOR_JEMALLOC 454 | cf_atomic64_set(&mem_count_mallocxs, 0); 455 | cf_atomic64_set(&mem_count_rallocxs, 0); 456 | 457 | cf_atomic64_set(&mem_count_mallocx_total, 0); 458 | cf_atomic64_set(&mem_count_rallocx_plus_total, 0); 459 | cf_atomic64_set(&mem_count_rallocx_minus_total, 0); 460 | #endif 461 | 462 | cf_atomic64_set(&mem_count_net_mmaps, 0); 463 | cf_atomic64_set(&mem_count_mmaps, 0); 464 | cf_atomic64_set(&mem_count_mmap64s, 0); 465 | cf_atomic64_set(&mem_count_munmaps, 0); 466 | 467 | cf_atomic64_set(&mem_count_malloc_total, 0); 468 | cf_atomic64_set(&mem_count_free_total, 0); 469 | cf_atomic64_set(&mem_count_calloc_total, 0); 470 | cf_atomic64_set(&mem_count_realloc_plus_total, 0); 471 | cf_atomic64_set(&mem_count_realloc_minus_total, 0); 472 | cf_atomic64_set(&mem_count_strdup_total, 0); 473 | cf_atomic64_set(&mem_count_strndup_total, 0); 474 | cf_atomic64_set(&mem_count_valloc_total, 0); 475 | 476 | cf_atomic64_set(&mem_count_sbrk_total, 0); 477 | cf_atomic64_set(&mem_count_morecore_total, 0); 478 | 479 | cf_atomic64_set(&mem_count_net_mmap_total, 0); 480 | cf_atomic64_set(&mem_count_mmap_total, 0); 481 | cf_atomic64_set(&mem_count_mmap64_total, 0); 482 | cf_atomic64_set(&mem_count_munmap_total, 0); 483 | 484 | cf_atomic64_set(&mem_count_shmats, 0); 485 | cf_atomic64_set(&mem_count_shmctls, 0); 486 | cf_atomic64_set(&mem_count_shmdts, 0); 487 | cf_atomic64_set(&mem_count_shmgets, 0); 488 | cf_atomic64_set(&mem_count_net_shm, 0); 489 | 490 | cf_atomic64_set(&thread_id, 0); 491 | cf_atomic64_set(&live_threads, 0); 492 | } 493 | 494 | /*****************************************************************************************/ 495 | 496 | /* 497 | * Print the current GLibC heap statistics. 498 | */ 499 | static void asm_log_datestamp(void) 500 | { 501 | char datestamp[100]; 502 | struct tm nowtm; 503 | time_t now = time(NULL); 504 | gmtime_r(&now, &nowtm); 505 | strftime(datestamp, sizeof(datestamp), "\n%b %d %Y %T %Z:\n\n", &nowtm); 506 | 507 | fprintf(stderr, datestamp); 508 | } 509 | 510 | /* 511 | * Print the number of threads. 512 | */ 513 | static void asm_thread_stats(void) 514 | { 515 | fprintf(stderr, ">>>There are %lu live (out of %lu total) threads.<<<\n\n", live_threads, thread_id); 516 | fflush(stderr); 517 | } 518 | 519 | /* 520 | * Print the current GLibC heap info. in a self-describing format. 521 | */ 522 | static void asm_log_malloc_info(void) 523 | { 524 | malloc_info(0, stderr); 525 | fprintf(stderr, "\n"); 526 | } 527 | 528 | /* 529 | * Print the current GLibC heap statistics. 530 | * 531 | * [Note: This is only 32-bits due to the 532 | */ 533 | static void asm_log_malloc_stats(void) 534 | { 535 | malloc_stats(); 536 | fprintf(stderr, "\n"); 537 | } 538 | 539 | /* 540 | * Print GLibC heap usage statistics. 541 | * 542 | * [Note: This only describes the main arena.] 543 | */ 544 | static void asm_log_mallinfo(void) 545 | { 546 | struct mallinfo mi = mallinfo(); 547 | 548 | fprintf(stderr, "struct mallinfo = {\n"); 549 | fprintf(stderr, "\tarena = %d;\t\t/* non-mmapped space allocated from system */\n", mi.arena); 550 | fprintf(stderr, "\tordblks = %d;\t\t/* number of free chunks */\n", mi.ordblks); 551 | fprintf(stderr, "\tsmblks = %d;\t\t/* number of fastbin blocks */ *GLIBC UNUSED*\n", mi.smblks); 552 | fprintf(stderr, "\thblks = %d;\t\t/* number of mmapped regions */\n", mi.hblks); 553 | fprintf(stderr, "\thblkhd = %d;\t\t/* space in mmapped regions */\n", mi.hblkhd); 554 | fprintf(stderr, "\tusmblks = %d;\t\t/* maximum total allocated space */ *GLIBC UNUSED*\n", mi.usmblks); 555 | fprintf(stderr, "\tfsmblks = %d;\t\t/* space available in freed fastbin blocks */ *GLIBC UNUSED*\n", mi.fsmblks); 556 | fprintf(stderr, "\tuordblks = %d;\t\t/* total allocated space */\n", mi.uordblks); 557 | fprintf(stderr, "\tfordblks = %d;\t\t/* total free space */\n", mi.fordblks); 558 | fprintf(stderr, "\tkeepcost = %d;\t\t/* top-most, releasable (via malloc_trim) space */\n", mi.keepcost); 559 | fprintf(stderr, "}\n"); 560 | 561 | size_t total_used = mi.arena + mi.hblkhd - mi.fordblks; 562 | 563 | fprintf(stderr, "total_used: %zu ; diff: %ld\n", total_used, total_used - mem_count); 564 | } 565 | 566 | /* 567 | * Return human-readable value (in terms of floting point powers of 2 ^ 10) for a given memory size. 568 | */ 569 | static void get_human_readable_memory_size(ssize_t sz, double *quantity, char **scale) 570 | { 571 | size_t asz = labs(sz); 572 | 573 | if (asz >= (1 << 30)) { 574 | *scale = "GB"; 575 | *quantity = ((double) sz) / exp2(30.0); 576 | } else if (asz >= (1 << 20)) { 577 | *scale = "MB"; 578 | *quantity = ((double) sz) / exp2(20.0); 579 | } else if (asz >= (1 << 10)) { 580 | *scale = "KB"; 581 | *quantity = ((double) sz) / exp2(10.0); 582 | } else { 583 | *scale = "B"; 584 | *quantity = (double) sz; 585 | } 586 | } 587 | 588 | /* 589 | * Print the current memory allocation statistics. 590 | */ 591 | static void asm_mem_count_stats() 592 | { 593 | fprintf(stderr, "Mem Count Stats:\n"); 594 | fprintf(stderr, "=============================================\n"); 595 | 596 | size_t mc = cf_atomic64_get(mem_count); 597 | double quantity = 0.0; 598 | char *scale = "B"; 599 | get_human_readable_memory_size(mc, &quantity, &scale); 600 | 601 | size_t mcm = cf_atomic64_get(mem_count_mallocs); 602 | size_t mcf = cf_atomic64_get(mem_count_frees); 603 | size_t mcc = cf_atomic64_get(mem_count_callocs); 604 | size_t mcr = cf_atomic64_get(mem_count_reallocs); 605 | size_t mcs = cf_atomic64_get(mem_count_strdups); 606 | size_t mcsn = cf_atomic64_get(mem_count_strndups); 607 | size_t mcv = cf_atomic64_get(mem_count_vallocs); 608 | 609 | #ifdef FOR_JEMALLOC 610 | size_t mcmx = cf_atomic64_get(mem_count_mallocxs); 611 | size_t mcrx = cf_atomic64_get(mem_count_rallocxs); 612 | #endif 613 | 614 | fprintf(stderr, "mem_count: %ld (%.3f %s)\n", mc, quantity, scale); 615 | fprintf(stderr, "=============================================\n"); 616 | fprintf(stderr, "net mallocs: %ld\n", (mcm + mcc + mcv + mcs + mcsn - mcf)); 617 | fprintf(stderr, "=============================================\n"); 618 | fprintf(stderr, "mem_count_mallocs: %ld (%ld)\n", mcm, cf_atomic64_get(mem_count_malloc_total)); 619 | fprintf(stderr, "mem_count_frees: %ld (%ld)\n", mcf, cf_atomic64_get(mem_count_free_total)); 620 | fprintf(stderr, "mem_count_callocs: %ld (%ld)\n", mcc, cf_atomic64_get(mem_count_calloc_total)); 621 | fprintf(stderr, "mem_count_reallocs: %ld (%ld / %ld)\n", mcr, cf_atomic64_get(mem_count_realloc_plus_total), cf_atomic64_get(mem_count_realloc_minus_total)); 622 | fprintf(stderr, "mem_count_strdups: %ld (%ld)\n", mcs, cf_atomic64_get(mem_count_strdup_total)); 623 | fprintf(stderr, "mem_count_strndups: %ld (%ld)\n", mcsn, cf_atomic64_get(mem_count_strndup_total)); 624 | fprintf(stderr, "mem_count_vallocs: %ld (%ld)\n", mcv, cf_atomic64_get(mem_count_valloc_total)); 625 | fprintf(stderr, "=============================================\n"); 626 | #ifdef FOR_JEMALLOC 627 | fprintf(stderr, "mem_count_mallocxs: %ld (%ld)\n", mcmx, cf_atomic64_get(mem_count_mallocx_total)); 628 | fprintf(stderr, "mem_count_rallocxs: %ld (%ld / %ld)\n", mcrx, cf_atomic64_get(mem_count_rallocx_plus_total), cf_atomic64_get(mem_count_rallocx_minus_total)); 629 | fprintf(stderr, "=============================================\n"); 630 | #endif 631 | 632 | quantity = 0.0; 633 | scale = "B"; 634 | size_t sbt = cf_atomic64_get(mem_count_sbrk_total); 635 | get_human_readable_memory_size(sbt, &quantity, &scale); 636 | 637 | fprintf(stderr, "mem_count_brks: %ld\n", cf_atomic64_get(mem_count_brks)); 638 | fprintf(stderr, "mem_count_sbrks: %ld\n", cf_atomic64_get(mem_count_sbrks)); 639 | fprintf(stderr, "mem_count_sbrk_total: %ld (%.3f %s)\n", sbt, quantity, scale); 640 | 641 | quantity = 0.0; 642 | scale = "B"; 643 | size_t mct = cf_atomic64_get(mem_count_morecore_total); 644 | get_human_readable_memory_size(mct, &quantity, &scale); 645 | 646 | fprintf(stderr, "mem_count_morecores: %ld\n", cf_atomic64_get(mem_count_morecores)); 647 | fprintf(stderr, "mem_count_morecore_total: %ld (%.3f %s)\n", mct, quantity, scale); 648 | fprintf(stderr, "=============================================\n"); 649 | 650 | quantity = 0.0; 651 | scale = "B"; 652 | size_t nmt = cf_atomic64_get(mem_count_net_mmap_total); 653 | get_human_readable_memory_size(nmt, &quantity, &scale); 654 | 655 | fprintf(stderr, "net mmaps: %ld : %ld (%.3f %s)\n", cf_atomic64_get(mem_count_net_mmaps), nmt, quantity, scale); 656 | fprintf(stderr, "=============================================\n"); 657 | fprintf(stderr, "mem_count_mmaps: %ld (%ld)\n", cf_atomic64_get(mem_count_mmaps), cf_atomic64_get(mem_count_mmap_total)); 658 | fprintf(stderr, "mem_count_mmap64s: %ld (%ld)\n", cf_atomic64_get(mem_count_mmap64s), cf_atomic64_get(mem_count_mmap64_total)); 659 | fprintf(stderr, "mem_count_munmaps: %ld (%ld)\n", cf_atomic64_get(mem_count_munmaps), cf_atomic64_get(mem_count_munmap_total)); 660 | fprintf(stderr, "=============================================\n"); 661 | 662 | quantity = 0.0; 663 | scale = "B"; 664 | size_t nshm = cf_atomic64_get(mem_count_net_shm); 665 | get_human_readable_memory_size(nshm, &quantity, &scale); 666 | 667 | fprintf(stderr, "net shms: %ld (%.3f %s)\n", nshm, quantity, scale); 668 | fprintf(stderr, "=============================================\n"); 669 | fprintf(stderr, "mem_count_shmats: %ld\n", cf_atomic64_get(mem_count_shmats)); 670 | fprintf(stderr, "mem_count_shmctls: %ld\n", cf_atomic64_get(mem_count_shmctls)); 671 | fprintf(stderr, "mem_count_shmdts: %ld\n", cf_atomic64_get(mem_count_shmdts)); 672 | fprintf(stderr, "mem_count_shmgets: %ld\n", cf_atomic64_get(mem_count_shmgets)); 673 | fprintf(stderr, "=============================================\n\n"); 674 | 675 | // Snap-shot the current usage of various types of memory. 676 | g_asm_stats.mem_count = mc; 677 | g_asm_stats.net_mmaps = nmt; 678 | g_asm_stats.net_shm = nshm; 679 | } 680 | 681 | /* 682 | * ASM version of the hook function. 683 | */ 684 | int asm_hook(void *arg, asm_stats_t **asm_stats, vm_stats_t **vm_stats) 685 | { 686 | dfprintf(stderr, ">>>In asm_hook(%p)<<<\n", arg); 687 | 688 | if (asm_stats) { 689 | *asm_stats = &g_asm_stats; 690 | } 691 | 692 | if (FEATURE(ASM_LOG_DATESTAMP)) { 693 | asm_log_datestamp(); 694 | } 695 | 696 | if (FEATURE(ASM_LOG_THREAD_STATS)) { 697 | asm_thread_stats(); 698 | } 699 | 700 | if (FEATURE(ASM_LOG_MEM_COUNT_STATS)) { 701 | asm_mem_count_stats(); 702 | } 703 | 704 | if (FEATURE(ASM_LOG_MALLOC_INFO)) { 705 | asm_log_malloc_info(); 706 | } 707 | 708 | if (FEATURE(ASM_LOG_MALLOC_STATS)) { 709 | asm_log_malloc_stats(); 710 | } 711 | 712 | if (FEATURE(ASM_LOG_MALLINFO)) { 713 | // [Note: This only describes the main arena.] 714 | asm_log_mallinfo(); 715 | } 716 | 717 | if (FEATURE(ASM_LOG_VM_STATS)) { 718 | vm_stats_t *stats = log_vm_stats(getpid()); 719 | 720 | if (vm_stats) { 721 | *vm_stats = stats; 722 | } 723 | } 724 | 725 | return 0; 726 | } 727 | 728 | /* 729 | * ASM version of the command function. 730 | */ 731 | int asm_cmd(asm_cmd_t cmd, va_list args) 732 | { 733 | dfprintf(stderr, ">>>In asm_cmd(%d)<<<\n", cmd); 734 | 735 | // Handle variable arguments. 736 | switch (cmd) { 737 | case ASM_CMD_SET_FEATURES: 738 | { 739 | g_features = va_arg(args, uint64_t); 740 | dfprintf(stderr, ">>>ASM_CMD_SET_FEATURES: \"0x%lx\"<<<\n", g_features); 741 | break; 742 | } 743 | 744 | case ASM_CMD_SET_CALLBACK: 745 | g_cb = va_arg(args, void *); 746 | g_cb_udata = va_arg(args, void *); 747 | dfprintf(stderr, ">>>ASM_CMD_SET_CALLBACK: g_cb = %p ; g_cb_udata = %p<<<\n", g_cb, g_cb_udata); 748 | break; 749 | 750 | case ASM_CMD_SET_THRESHOLDS: 751 | g_thresh_block_size = va_arg(args, size_t); 752 | g_thresh_delta_size = va_arg(args, size_t); 753 | g_thresh_delta_time = va_arg(args, time_t); 754 | dfprintf(stderr, ">>>ASM_CMD_SET_THRESHOLDS: g_thresh_block_size = %lu ; g_thresh_delta_size = %lu ; g_thresh_delta_time = %lu<<<\n", 755 | g_thresh_block_size, g_thresh_delta_size, g_thresh_delta_time); 756 | break; 757 | 758 | case ASM_CMD_PRINT_STATS: 759 | #ifdef FOR_JEMALLOC 760 | if (original_malloc_stats_print) { 761 | original_malloc_stats_print(NULL, NULL, NULL); 762 | } else { 763 | fprintf(stderr, ">>>original_malloc_stats_print() is NULL!<<<\n"); 764 | } 765 | #endif 766 | break; 767 | 768 | default: 769 | break; 770 | } 771 | 772 | return 0; 773 | } 774 | 775 | /* 776 | * Initialize the memory wrappers. 777 | */ 778 | static bool init(int i) 779 | { 780 | dfprintf(stderr, ">>>In asm init(1)<<<\n"); 781 | 782 | pthread_mutex_lock(&init_lock); 783 | 784 | dfprintf(stderr, ">>>In asm init(2)<<<\n"); 785 | 786 | if (!inited) { 787 | init_counters(); 788 | 789 | if (!(original_calloc = dlsym(RTLD_NEXT, "calloc"))) { 790 | fprintf(stderr, "Could not find original calloc()!\n"); 791 | } 792 | 793 | if (!(original_malloc = dlsym(RTLD_NEXT, "malloc"))) { 794 | fprintf(stderr, "Could not find original malloc()!\n"); 795 | } 796 | 797 | if (!(original_free = dlsym(RTLD_NEXT, "free"))) { 798 | fprintf(stderr, "Could not find original free()!\n"); 799 | } 800 | 801 | if (!(original_realloc = dlsym(RTLD_NEXT, "realloc"))) { 802 | fprintf(stderr, "Could not find original realloc()!\n"); 803 | } 804 | 805 | if (!(original_strdup = dlsym(RTLD_NEXT, "strdup"))) { 806 | fprintf(stderr, "Could not find original strdup()!\n"); 807 | } 808 | 809 | if (!(original_strndup = dlsym(RTLD_NEXT, "strndup"))) { 810 | fprintf(stderr, "Could not find original strndup()!\n"); 811 | } 812 | 813 | if (!(original_posix_memalign = dlsym(RTLD_NEXT, "posix_memalign"))) { 814 | fprintf(stderr, "Could not find original posix_memalign()!\n"); 815 | } 816 | 817 | /********************************/ 818 | 819 | if (!(original_brk = dlsym(RTLD_NEXT, "brk"))) { 820 | fprintf(stderr, "Could not find original brk()!\n"); 821 | } 822 | 823 | if (!(original_sbrk = dlsym(RTLD_NEXT, "__sbrk"))) { 824 | fprintf(stderr, "Could not find original sbrk()!\n"); 825 | } 826 | 827 | if (!(original___default_morecore = dlsym(RTLD_NEXT, "__default_morecore"))) { 828 | fprintf(stderr, "Could not find original __default_morecore()!\n"); 829 | } 830 | __morecore = __default_morecore; 831 | 832 | /********************************/ 833 | 834 | #ifdef FOR_JEMALLOC 835 | if (!(original_mallocx = dlsym(RTLD_NEXT, "mallocx"))) { 836 | dfprintf(stderr, "Could not find \"mallocx\"!\n"); 837 | } 838 | 839 | if (!(original_rallocx = dlsym(RTLD_NEXT, "rallocx"))) { 840 | dfprintf(stderr, "Could not find \"rallocx\"!\n"); 841 | } 842 | 843 | if (!(original_malloc_stats_print = dlsym(RTLD_NEXT, "malloc_stats_print"))) { 844 | dfprintf(stderr, "Could not find \"malloc_stats_print\"!\n"); 845 | } 846 | #else 847 | 848 | if (!(original_mmap = dlsym(RTLD_NEXT, "mmap"))) { 849 | fprintf(stderr, "Could not find original mmap()!\n"); 850 | } 851 | 852 | if (!(original_mmap64 = dlsym(RTLD_NEXT, "mmap64"))) { 853 | fprintf(stderr, "Could not find original mmap64()!\n"); 854 | } 855 | 856 | if (!(original_munmap = dlsym(RTLD_NEXT, "munmap"))) { 857 | fprintf(stderr, "Could not find original munmap()!\n"); 858 | } 859 | #endif 860 | 861 | /********************************/ 862 | 863 | if (!(original_shmat = dlsym(RTLD_NEXT, "shmat"))) { 864 | fprintf(stderr, "Could not find original shmat()!\n"); 865 | } 866 | 867 | if (!(original_shmctl = dlsym(RTLD_NEXT, "shmctl"))) { 868 | fprintf(stderr, "Could not find original shmctl()!\n"); 869 | } 870 | 871 | if (!(original_shmdt = dlsym(RTLD_NEXT, "shmdt"))) { 872 | fprintf(stderr, "Could not find original shmdt()!\n"); 873 | } 874 | 875 | if (!(original_shmget = dlsym(RTLD_NEXT, "shmget"))) { 876 | fprintf(stderr, "Could not find original shmget()!\n"); 877 | } 878 | 879 | inited = true; 880 | 881 | pthread_mutex_unlock(&init_lock); 882 | } 883 | 884 | return inited; 885 | } 886 | 887 | /*****************************************************************************************/ 888 | 889 | /* 890 | * Thread-specific key destructor function for ASM thread launcher. 891 | */ 892 | static void asmtl_key_destructor(void *key) 893 | { 894 | asm_thread_launcher_t *asmtl = (asm_thread_launcher_t *) key; 895 | 896 | fprintf(stderr, "Calling asmtl key destructor on thread #%lu: \"%s\" (%p) ; tid %lu!\n", asmtl->id, asmtl->sname, (void *) pthread_self(), syscall(SYS_gettid)); 897 | 898 | cf_atomic64_decr(&live_threads); 899 | } 900 | 901 | /* 902 | * Initialize the shared library. 903 | */ 904 | static void __attribute__ ((constructor)) begin(void) 905 | { 906 | #ifdef DEBUG 907 | BREAK(); 908 | LOG('*'); 909 | BREAK(); 910 | #endif 911 | 912 | dfprintf(stderr, ">>>In begin()<<<\n"); 913 | 914 | // Zero-out the global statically-allocated array of memory allocation locations. 915 | memset(g_tls_mallocations, 0, MAX_TLS_MALLOCATIONS * sizeof(tls_mallocation_t)); 916 | 917 | int rv; 918 | if ((rv = pthread_key_create(&asmtl_key, asmtl_key_destructor))) { 919 | fprintf(stderr, "pthread_key_create(asmtl_key) failed with rv = %d!\n", rv); 920 | } 921 | if ((rv = pthread_key_create(&tls_mallocations_key, NULL))) { 922 | fprintf(stderr, "pthread_key_create(tls_mallocations_key) failed with rv = %d!\n", rv); 923 | } 924 | 925 | INIT(1); 926 | } 927 | 928 | /* 929 | * De-initialize the shared library. 930 | */ 931 | static void __attribute__ ((destructor)) end(void) 932 | { 933 | dfprintf(stderr, ">>>In end()<<<\n"); 934 | } 935 | 936 | /*****************************************************************************************/ 937 | 938 | /* 939 | * Wrapper for thread start routines that sets up the thread-specific data 940 | * needed for memory accounting. 941 | */ 942 | static void *asm_start_routine(void *arg) 943 | { 944 | asm_thread_launcher_t *asmtl = (asm_thread_launcher_t *) arg; 945 | tls_mallocation_t tls_mallocations[MAX_TLS_MALLOCATIONS]; 946 | tls_mallocation_t *tls_mallocations_ptr = tls_mallocations; 947 | 948 | // Zero-out the array of memory allocation locations allocated on the process' stack. 949 | memset(tls_mallocations_ptr, 0, MAX_TLS_MALLOCATIONS * sizeof(tls_mallocation_t)); 950 | 951 | int rv; 952 | if ((rv = pthread_setspecific(asmtl_key, (void *) asmtl))) { 953 | fprintf(stderr, "Thread #%ld failed to set asmtl_key with rv = %d!\n", asmtl->id, rv); 954 | } 955 | if ((rv = pthread_setspecific(tls_mallocations_key, (void *) tls_mallocations_ptr))) { 956 | fprintf(stderr, "Thread #%ld failed to set asmtl_key with rv = %d!\n", asmtl->id, rv); 957 | } 958 | 959 | fprintf(stderr, "I am thread #%lu: \"%s\" (%p) ; tid %lu!\n", asmtl->id, asmtl->sname, (void *) pthread_self(), syscall(SYS_gettid)); 960 | 961 | return asmtl->start_routine(asmtl->arg); 962 | } 963 | 964 | /* 965 | * ASM version of pthread_create(). 966 | */ 967 | int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg) 968 | { 969 | asm_thread_launcher_t *asmtl = (asm_thread_launcher_t *) calloc(1, sizeof(asm_thread_launcher_t)); 970 | asmtl->id = cf_atomic64_incr(&thread_id); 971 | asmtl->start_routine = start_routine; 972 | asmtl->arg = arg; 973 | asmtl->sname = NULL; 974 | 975 | Dl_info dl_info; 976 | if (dladdr(asmtl->start_routine, &dl_info)) { 977 | asmtl->sname = dl_info.dli_sname; 978 | } else { 979 | fprintf(stderr, "Failed to get Dl_info for start routine %p!\n", asmtl->start_routine); 980 | } 981 | 982 | fprintf(stderr, "Created thread #%lu: start_routine = %p ; arg = %p\n", asmtl->id, start_routine, arg); 983 | cf_atomic64_incr(&live_threads); 984 | 985 | if (!original_pthread_create) { 986 | if (!(original_pthread_create = dlsym(RTLD_NEXT, "pthread_create"))) { 987 | fprintf(stderr, "Could not find original pthread_create()!\n"); 988 | return EAGAIN; 989 | } 990 | } 991 | 992 | return original_pthread_create(thread, attr, asm_start_routine, asmtl); 993 | } 994 | 995 | /* 996 | * Store the type and location of a mallocation in thread-specific storage. 997 | */ 998 | void asm_mallocation_set(uint16_t type, uint16_t loc, ssize_t delta_size) 999 | { 1000 | tls_mallocation_t *tls_mallocations = (tls_mallocation_t *) pthread_getspecific(tls_mallocations_key); 1001 | 1002 | // Use the global array for any calls from the main process. 1003 | if (!tls_mallocations) { 1004 | tls_mallocations = g_tls_mallocations; 1005 | } 1006 | 1007 | // The 0th mallocation is used to pass information from the program to this module. 1008 | // [Note: Total size of the 0th mallocation is always 0.] 1009 | tls_mallocations[0].delta_size = delta_size; 1010 | tls_mallocations[0].type = type; 1011 | tls_mallocations[0].loc = loc; 1012 | 1013 | // Set the time of the event. 1014 | clock_gettime(CLOCK_MONOTONIC, &(tls_mallocations[0].last_time)); 1015 | 1016 | // Log large block (de)allocations. 1017 | if (FEATURE(ASM_LOG_BLOCKS) && (delta_size > g_thresh_block_size)) { 1018 | dfprintf(stderr, "ams(): Call type %d @ loc %d delta_size = %ld!\n", type, loc, delta_size); 1019 | if (g_cb) { 1020 | asm_thread_launcher_t *asmtl = (asm_thread_launcher_t *) pthread_getspecific(asmtl_key); 1021 | // [Note: Thread #0 is the main process / initial thread.] 1022 | (g_cb)((asmtl ? asmtl->id : 0), type, loc, delta_size, 0, &(tls_mallocations[0].last_time), g_cb_udata); 1023 | } 1024 | } 1025 | } 1026 | 1027 | /* 1028 | * Return the type and location of a mallocation in thread-specific storage. 1029 | */ 1030 | void asm_mallocation_get(uint16_t *type, uint16_t loc, ssize_t *total_size, ssize_t *delta_size, struct timespec *last_time) 1031 | { 1032 | tls_mallocation_t *tls_mallocations = (tls_mallocation_t *) pthread_getspecific(tls_mallocations_key); 1033 | 1034 | // Use the global array for any calls from the main process. 1035 | if (!tls_mallocations) { 1036 | tls_mallocations = g_tls_mallocations; 1037 | } 1038 | 1039 | if (type) { 1040 | *type = tls_mallocations[loc].type; 1041 | } 1042 | // [Note: The loc is only passed in.] 1043 | if (total_size) { 1044 | *total_size = tls_mallocations[loc].total_size; 1045 | } 1046 | if (delta_size) { 1047 | *delta_size = tls_mallocations[loc].delta_size; 1048 | } 1049 | 1050 | if (last_time) { 1051 | last_time->tv_sec = tls_mallocations[loc].last_time.tv_sec; 1052 | last_time->tv_nsec = tls_mallocations[loc].last_time.tv_nsec; 1053 | } 1054 | } 1055 | 1056 | /******************************** MALLOC/FREE and Friends ********************************/ 1057 | 1058 | /* 1059 | * ASM version of calloc(3). 1060 | */ 1061 | void *calloc(size_t nmemb, size_t size) 1062 | { 1063 | INIT(2); 1064 | 1065 | // Handle allocation request in "dlerror()" when multithreaded. 1066 | if (!inited) { 1067 | size_t requested = nmemb * size; 1068 | if (requested > haksize) { 1069 | // [Note: Only the lower 8 bits will be returned as the exit status.] 1070 | exit(requested); 1071 | } 1072 | void *result = (void *) hakbuf_ptr; 1073 | hakbuf_ptr += requested; 1074 | haksize -= requested; 1075 | 1076 | cf_atomic64_incr(&mem_count_callocs); 1077 | cf_atomic64_add(&mem_count, requested); 1078 | cf_atomic64_add(&mem_count_calloc_total, requested); 1079 | 1080 | return result; 1081 | } 1082 | 1083 | cf_atomic64_incr(&mem_count_callocs); 1084 | dfprintf(stderr, ">>>In asm calloc()<<<\n"); 1085 | 1086 | SET_ALLOC_INFO(); 1087 | 1088 | // Reserve space for the allocation location. 1089 | // XXX -- Overallocates by (4 * (nmemb - 1)) bytes. 1090 | size += sizeof(int); 1091 | 1092 | void *retval = original_calloc(nmemb, size); 1093 | 1094 | size_t actual_size = malloc_usable_size(retval); 1095 | cf_atomic64_add(&mem_count, actual_size); 1096 | cf_atomic64_add(&mem_count_calloc_total, actual_size); 1097 | 1098 | SET_LOC_LOC(retval, nmemb * size); 1099 | 1100 | MAYBE_DO_CB(); 1101 | 1102 | return retval; 1103 | } 1104 | 1105 | /* 1106 | * ASM version of malloc(3). 1107 | */ 1108 | void *malloc(size_t size) 1109 | { 1110 | INIT(3); 1111 | 1112 | SET_ALLOC_INFO(); 1113 | 1114 | // Reserve space for the allocation location. 1115 | size += sizeof(int); 1116 | 1117 | void *retval = original_malloc(size); 1118 | 1119 | size_t actual_size = malloc_usable_size(retval); 1120 | cf_atomic64_add(&mem_count, actual_size); 1121 | cf_atomic64_add(&mem_count_malloc_total, actual_size); 1122 | 1123 | cf_atomic64_incr(&mem_count_mallocs); 1124 | dfprintf(stderr, ">>>In asm malloc()<<<\n"); 1125 | 1126 | SET_LOC_LOC(retval, size); 1127 | 1128 | MAYBE_DO_CB(); 1129 | 1130 | return retval; 1131 | } 1132 | 1133 | /* 1134 | * ASM version of free(3). 1135 | */ 1136 | void free(void *ptr) 1137 | { 1138 | INIT(4); 1139 | 1140 | // Only count non-"free(0)"'s. 1141 | if (ptr) { 1142 | cf_atomic64_incr(&mem_count_frees); 1143 | dfprintf(stderr, ">>>In asm free(%p)<<<\n", ptr); 1144 | 1145 | size_t actual_size = malloc_usable_size(ptr); 1146 | 1147 | SET_FREE_INFO(ptr); 1148 | 1149 | cf_atomic64_sub(&mem_count, actual_size); 1150 | cf_atomic64_sub(&mem_count_free_total, actual_size); 1151 | 1152 | MAYBE_DO_CB(); 1153 | } 1154 | 1155 | original_free(ptr); 1156 | } 1157 | 1158 | /* 1159 | * ASM version of realloc(3). 1160 | */ 1161 | void *realloc(void *ptr, size_t size) 1162 | { 1163 | INIT(5); 1164 | 1165 | cf_atomic64_incr(&mem_count_reallocs); 1166 | dfprintf(stderr, ">>>In asm realloc()<<<\n"); 1167 | 1168 | int64_t orig_size = (ptr ? malloc_usable_size(ptr) : 0); 1169 | int64_t delta = 0; 1170 | 1171 | // Reserve space for the allocation location. 1172 | if (size) { 1173 | size += sizeof(int); 1174 | } 1175 | 1176 | void *retval = original_realloc(ptr, size); 1177 | 1178 | if (!size) { 1179 | delta = - orig_size; 1180 | } else { 1181 | // [Note: If realloc() fails, NULL is returned and the original block is left unchanged.] 1182 | if (retval) { 1183 | delta = malloc_usable_size(retval) - orig_size; 1184 | } 1185 | } 1186 | SET_MALLOCATION_INFO((!size ? FREE : ALLOC), ptr); 1187 | 1188 | cf_atomic64_add(&mem_count, delta); 1189 | if (delta > 0) { 1190 | cf_atomic64_add(&mem_count_realloc_plus_total, delta); 1191 | } else { 1192 | cf_atomic64_add(&mem_count_realloc_minus_total, delta); 1193 | } 1194 | 1195 | if (!ptr) { 1196 | cf_atomic64_incr(&mem_count_mallocs); 1197 | } else if (!size) { 1198 | cf_atomic64_incr(&mem_count_frees); 1199 | } else { 1200 | cf_atomic64_incr(&mem_count_frees); 1201 | cf_atomic64_incr(&mem_count_mallocs); 1202 | } 1203 | 1204 | // Only set the allocation location for non-free()-type realloc()'s. 1205 | if (size) { 1206 | size_t actual_size = malloc_usable_size(retval); 1207 | SET_LOC_LOC(retval, size); 1208 | } 1209 | 1210 | MAYBE_DO_CB(); 1211 | 1212 | return retval; 1213 | } 1214 | 1215 | /* 1216 | * ASM version of strdup(3). 1217 | */ 1218 | char *strdup(const char *s) 1219 | { 1220 | INIT(6); 1221 | 1222 | cf_atomic64_incr(&mem_count_strdups); 1223 | dfprintf(stderr, ">>>In asm strdup()<<<\n"); 1224 | 1225 | SET_ALLOC_INFO(); 1226 | 1227 | char *retval = original_strdup(s); 1228 | 1229 | cf_atomic64_add(&mem_count_strdup_total, malloc_usable_size(retval)); 1230 | 1231 | // XXX -- How do we set the loc? 1232 | 1233 | MAYBE_DO_CB(); 1234 | 1235 | return retval; 1236 | } 1237 | 1238 | /* 1239 | * ASM version of strndup(3). 1240 | */ 1241 | char *strndup(const char *s, size_t n) 1242 | { 1243 | INIT(7); 1244 | 1245 | cf_atomic64_incr(&mem_count_strndups); 1246 | dfprintf(stderr, ">>>In asm strndup()<<<\n"); 1247 | 1248 | SET_ALLOC_INFO(); 1249 | 1250 | char *retval = original_strndup(s, n); 1251 | 1252 | cf_atomic64_add(&mem_count_strndup_total, malloc_usable_size(retval)); 1253 | 1254 | // XXX -- How do we set the loc? 1255 | 1256 | MAYBE_DO_CB(); 1257 | 1258 | return retval; 1259 | } 1260 | 1261 | /* 1262 | * ASM version of posix_memalign(3). 1263 | */ 1264 | int posix_memalign(void **memptr, size_t alignment, size_t size) 1265 | { 1266 | INIT(8); 1267 | 1268 | cf_atomic64_incr(&mem_count_vallocs); 1269 | dfprintf(stderr, ">>>In asm posix_memalign()<<<\n"); 1270 | 1271 | // Reserve space for the allocation location. 1272 | size += sizeof(int); 1273 | 1274 | SET_ALLOC_INFO(); 1275 | 1276 | int retval = original_posix_memalign(memptr, alignment, size); 1277 | 1278 | size_t actual_size = (memptr ? malloc_usable_size(*memptr) : 0); 1279 | if (memptr) { 1280 | cf_atomic64_add(&mem_count, actual_size); 1281 | cf_atomic64_add(&mem_count_valloc_total, actual_size); 1282 | } 1283 | 1284 | SET_LOC_LOC(*memptr, size); 1285 | 1286 | MAYBE_DO_CB(); 1287 | 1288 | return retval; 1289 | } 1290 | 1291 | /******************************** BRK/SBRK/MORECORE ********************************/ 1292 | 1293 | /* 1294 | * ASM version of brk(2). 1295 | */ 1296 | int brk(void *addr) 1297 | { 1298 | INIT(9); 1299 | 1300 | cf_atomic64_incr(&mem_count_brks); 1301 | dfprintf(stderr, ">>>In asm brk()<<<\n"); 1302 | 1303 | int retval = original_brk(addr); 1304 | 1305 | return retval; 1306 | } 1307 | 1308 | /* 1309 | * ASM version of sbrk(2). 1310 | */ 1311 | void *__sbrk(intptr_t increment) 1312 | { 1313 | INIT(10); 1314 | 1315 | cf_atomic64_incr(&mem_count_sbrks); 1316 | dfprintf(stderr, ">>>In asm sbrk()<<<\n"); 1317 | 1318 | void *retval = original_sbrk(increment); 1319 | 1320 | cf_atomic64_add(&mem_count_sbrk_total, increment); 1321 | 1322 | return retval; 1323 | } 1324 | 1325 | /* 1326 | * ASM version of __default_morecore(). 1327 | */ 1328 | void *__default_morecore(ptrdiff_t __size) 1329 | { 1330 | INIT(11); 1331 | 1332 | cf_atomic64_incr(&mem_count_morecores); 1333 | dfprintf(stderr, ">>>In asm __default_morecore()<<<\n"); 1334 | 1335 | void *retval = original___default_morecore(__size); 1336 | 1337 | cf_atomic64_add(&mem_count_morecore_total, __size); 1338 | 1339 | return retval; 1340 | } 1341 | 1342 | #ifdef FOR_JEMALLOC 1343 | 1344 | /******************************** JEMALLOC NON-STANDARD API ******************************/ 1345 | 1346 | /* 1347 | * ASM version of mallocx(3). 1348 | * (Treat essentially like malloc(3).) 1349 | */ 1350 | void *mallocx(size_t size, int flags) 1351 | { 1352 | INIT(12); 1353 | 1354 | SET_ALLOC_INFO(); 1355 | 1356 | // Reserve space for the allocation location. 1357 | size += sizeof(int); 1358 | 1359 | void *retval = original_mallocx(size, flags); 1360 | 1361 | size_t actual_size = malloc_usable_size(retval); 1362 | cf_atomic64_add(&mem_count, actual_size); 1363 | cf_atomic64_add(&mem_count_malloc_total, actual_size); 1364 | cf_atomic64_add(&mem_count_mallocx_total, actual_size); 1365 | 1366 | cf_atomic64_incr(&mem_count_mallocs); 1367 | cf_atomic64_incr(&mem_count_mallocxs); 1368 | dfprintf(stderr, ">>>In asm mallocx()<<<\n"); 1369 | 1370 | SET_LOC_LOC(retval, size); 1371 | 1372 | MAYBE_DO_CB(); 1373 | 1374 | return retval; 1375 | } 1376 | 1377 | /* 1378 | * ASM version of rallocx(3). 1379 | * (Treat essentially like realloc(3).) 1380 | */ 1381 | void *rallocx(void *ptr, size_t size, int flags) 1382 | { 1383 | INIT(13); 1384 | 1385 | cf_atomic64_incr(&mem_count_reallocs); 1386 | cf_atomic64_incr(&mem_count_rallocxs); 1387 | dfprintf(stderr, ">>>In asm reallocx()<<<\n"); 1388 | 1389 | int64_t orig_size = (ptr ? malloc_usable_size(ptr) : 0); 1390 | int64_t delta = 0; 1391 | 1392 | // Reserve space for the allocation location. 1393 | if (size) { 1394 | size += sizeof(int); 1395 | } 1396 | 1397 | void *retval = original_rallocx(ptr, size, flags); 1398 | 1399 | if (!size) { 1400 | delta = - orig_size; 1401 | } else { 1402 | // [Note: If rallocx() fails, NULL is returned and the original block is left unchanged.] 1403 | if (retval) { 1404 | delta = malloc_usable_size(retval) - orig_size; 1405 | } 1406 | } 1407 | SET_MALLOCATION_INFO((!size ? FREE : ALLOC), ptr); 1408 | 1409 | cf_atomic64_add(&mem_count, delta); 1410 | if (delta > 0) { 1411 | cf_atomic64_add(&mem_count_realloc_plus_total, delta); 1412 | cf_atomic64_add(&mem_count_rallocx_plus_total, delta); 1413 | } else { 1414 | cf_atomic64_add(&mem_count_realloc_minus_total, delta); 1415 | cf_atomic64_add(&mem_count_rallocx_minus_total, delta); 1416 | } 1417 | 1418 | if (!ptr) { 1419 | cf_atomic64_incr(&mem_count_mallocs); 1420 | } else if (!size) { 1421 | cf_atomic64_incr(&mem_count_frees); 1422 | } else { 1423 | cf_atomic64_incr(&mem_count_frees); 1424 | cf_atomic64_incr(&mem_count_mallocs); 1425 | } 1426 | 1427 | // Only set the allocation location for non-free()-type rallocx()'s. 1428 | if (size) { 1429 | size_t actual_size = malloc_usable_size(retval); 1430 | SET_LOC_LOC(retval, size); 1431 | } 1432 | 1433 | MAYBE_DO_CB(); 1434 | 1435 | return retval; 1436 | } 1437 | 1438 | #else 1439 | 1440 | /****************************************** MMAP *****************************************/ 1441 | 1442 | /* 1443 | * ASM version of mmap(2). 1444 | */ 1445 | void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset) 1446 | { 1447 | INIT(12); 1448 | 1449 | cf_atomic64_incr(&mem_count_mmaps); 1450 | cf_atomic64_incr(&mem_count_net_mmaps); 1451 | dfprintf(stderr, ">>>In asm mmap()<<<\n"); 1452 | 1453 | void *retval = original_mmap(addr, length, prot, flags, fd, offset); 1454 | 1455 | cf_atomic64_add(&mem_count_mmap_total, length); 1456 | cf_atomic64_add(&mem_count_net_mmap_total, length); 1457 | 1458 | return retval; 1459 | } 1460 | 1461 | /* 1462 | * ASM version of mmap64(2). 1463 | */ 1464 | void *mmap64(void *addr, size_t length, int prot, int flags, int fd, off64_t offset) 1465 | { 1466 | INIT(13); 1467 | 1468 | cf_atomic64_incr(&mem_count_mmap64s); 1469 | cf_atomic64_incr(&mem_count_net_mmaps); 1470 | dfprintf(stderr, ">>>In asm mmap64()<<<\n"); 1471 | 1472 | void *retval = original_mmap64(addr, length, prot, flags, fd, offset); 1473 | 1474 | cf_atomic64_add(&mem_count_mmap64_total, length); 1475 | cf_atomic64_add(&mem_count_net_mmap_total, length); 1476 | 1477 | return retval; 1478 | } 1479 | 1480 | /* 1481 | * ASM version of munmap(2). 1482 | */ 1483 | int munmap(void *addr, size_t length) 1484 | { 1485 | INIT(14); 1486 | 1487 | cf_atomic64_incr(&mem_count_munmaps); 1488 | cf_atomic64_decr(&mem_count_net_mmaps); 1489 | dfprintf(stderr, ">>>In asm munmap()<<<\n"); 1490 | 1491 | int retval = original_munmap(addr, length); 1492 | 1493 | cf_atomic64_sub(&mem_count_munmap_total, length); 1494 | cf_atomic64_sub(&mem_count_net_mmap_total, length); 1495 | 1496 | return retval; 1497 | } 1498 | #endif 1499 | 1500 | /************************************ Sys V IPC - SHM ************************************/ 1501 | 1502 | /* 1503 | * ASM version of shmat(2). 1504 | */ 1505 | void *shmat(int shmid, const void *shmaddr, int shmflg) 1506 | { 1507 | INIT(15); 1508 | 1509 | cf_atomic64_incr(&mem_count_shmats); 1510 | dfprintf(stderr, ">>>In asm shmat()<<<\n"); 1511 | 1512 | void *retval = original_shmat(shmid, shmaddr, shmflg); 1513 | 1514 | // XXX -- Should correctly modify shm accounting! 1515 | 1516 | return retval; 1517 | } 1518 | 1519 | /* 1520 | * ASM version of shmctl(2). 1521 | */ 1522 | int shmctl(int shmid, int cmd, struct shmid_ds *buf) 1523 | { 1524 | INIT(16); 1525 | 1526 | cf_atomic64_incr(&mem_count_shmctls); 1527 | dfprintf(stderr, ">>>In asm shmctl()<<<\n"); 1528 | 1529 | int retval = original_shmctl(shmid, cmd, buf); 1530 | 1531 | // XXX -- Should correctly modify shm accounting! 1532 | 1533 | return retval; 1534 | } 1535 | 1536 | /* 1537 | * ASM version of shmdt(2). 1538 | */ 1539 | int shmdt(const void *shmaddr) 1540 | { 1541 | INIT(17); 1542 | 1543 | cf_atomic64_incr(&mem_count_shmdts); 1544 | dfprintf(stderr, ">>>In asm shmdt()<<<\n"); 1545 | 1546 | int retval = original_shmdt(shmaddr); 1547 | 1548 | // XXX -- Should correctly modify shm accounting! 1549 | 1550 | return retval; 1551 | } 1552 | 1553 | /* 1554 | * ASM version of shmget(2). 1555 | */ 1556 | int shmget(key_t key, size_t size, int shmflg) 1557 | { 1558 | INIT(18); 1559 | 1560 | cf_atomic64_incr(&mem_count_shmgets); 1561 | cf_atomic64_add(&mem_count_net_shm, size); 1562 | dfprintf(stderr, ">>>In asm shmget()<<<\n"); 1563 | 1564 | int retval = original_shmget(key, size, shmflg); 1565 | 1566 | return retval; 1567 | } 1568 | -------------------------------------------------------------------------------- /src/test-asmalloc.c: -------------------------------------------------------------------------------- 1 | /* 2 | * src/test-asmalloc.c 3 | * 4 | * Copyright (C) 2013-2014 Aerospike, Inc. 5 | * 6 | * Portions may be licensed to Aerospike, Inc. under one or more contributor 7 | * license agreements. 8 | * 9 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 10 | * use this file except in compliance with the License. You may obtain a copy of 11 | * the License at http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 15 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 16 | * License for the specific language governing permissions and limitations under 17 | * the License. 18 | */ 19 | 20 | /* 21 | * SYNOPSIS 22 | * Test program for instrumenting using the ASMalloc memory allocation tracking tool. 23 | * This program may be run either with or without the ASMalloc shared library preloaded 24 | * via "LD_PRELOAD" feature, and may be built for use with either the GNU C Libary (GLibC) 25 | * or JEMalloc supplying the the memory allocation functions. It uses the fictitious 26 | * database of memory allocation locations in the "test-mallocations.h" header file 27 | * to simulate reporting of the program locations where the memory allocation-related 28 | * functions are called. (To instrument a real application, the mallocations database 29 | * would be automatically generated from the application source code.) 30 | */ 31 | 32 | #define _GNU_SOURCE 33 | 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | 43 | #include "asmalloc.h" 44 | #include "test-mallocations.h" 45 | 46 | // Number of threads to spin up. 47 | #define NUM_THREADS (5) 48 | 49 | // Number of iterations of the main loop to perform. 50 | #define MAX_ITER (10) 51 | 52 | // Time to wait between hook invocations. 53 | #define SLEEP_TIME (1) 54 | 55 | // Maximum number of blocks of memory to allocate. 56 | #define MAX_BLOCKS (1024) 57 | 58 | // Maximum block size in bytes. 59 | #define MAX_SIZE (10 * 1000) 60 | 61 | /* 62 | * Default minimum size of blocks triggering mallocation alerts. 63 | */ 64 | #define DEFAULT_THRESH_BLOCK_SIZE_BYTES (99990) 65 | 66 | /* 67 | * Default minimum delta size between mallocation alerts per thread. 68 | */ 69 | #define DEFAULT_THRESH_DELTA_SIZE_BYTES (900 * 1024) 70 | 71 | /* 72 | * Default minimum time between mallocation alerts per thread. 73 | */ 74 | #define DEFAULT_THRESH_DELTA_TIME_SECONDS (5) 75 | 76 | /* 77 | * Threshold for triggering memory allocation callbacks. 78 | */ 79 | size_t g_thresh_block_size = DEFAULT_THRESH_BLOCK_SIZE_BYTES; 80 | size_t g_thresh_delta_size = DEFAULT_THRESH_DELTA_SIZE_BYTES; 81 | time_t g_thresh_delta_time = DEFAULT_THRESH_DELTA_TIME_SECONDS; 82 | 83 | // Is the program running? 84 | bool g_running = false; 85 | 86 | int original_hook(void *arg, asm_stats_t **asm_stats, vm_stats_t **vm_stats) 87 | { 88 | fprintf(stderr, "In original_hook(%ld)!\n", (long) arg); 89 | fflush(stderr); 90 | 91 | return 0; 92 | } 93 | 94 | int (*g_hook)(void *arg, asm_stats_t **asm_stats, vm_stats_t **vm_stats) = original_hook; 95 | 96 | int hook(void *arg, asm_stats_t **asm_stats, vm_stats_t **vm_stats) 97 | { 98 | return (g_hook)(arg, asm_stats, vm_stats); 99 | } 100 | 101 | static int original_cmd(asm_cmd_t cmd, ...) 102 | { 103 | fprintf(stderr, "In original_cmd(%d)!\n", cmd); 104 | fflush(stderr); 105 | 106 | return 0; 107 | } 108 | 109 | static int (*g_cmd)(asm_cmd_t cmd, ...) = original_cmd; 110 | 111 | /* 112 | * Invoke the command. 113 | */ 114 | int cmd(asm_cmd_t cmd, ...) 115 | { 116 | va_list args; 117 | va_start(args, cmd); 118 | int retval = (g_cmd)(cmd, args); 119 | va_end(args); 120 | 121 | return retval; 122 | } 123 | 124 | void (*g_mallocation_set)(uint16_t type, uint16_t loc, ssize_t delta_size) = NULL; 125 | void (*g_mallocation_get)(uint16_t *type, uint16_t loc, ssize_t *total_size, ssize_t *size) = NULL; 126 | 127 | #ifdef FOR_JEMALLOC 128 | void (*malloc_stats_print)(void (*write_cb)(void *, const char *), void *je_cbopaque, const char *opts) = NULL; 129 | #endif 130 | 131 | /* 132 | * Type representing a unique location in the program where a memory allocation-related function is called. 133 | */ 134 | typedef uint16_t malloc_loc_t; 135 | 136 | /* 137 | * Register an immediately-upcoming memory allocation-related function on this thread. 138 | * 139 | * Return 0 if successful, -1 otherwise. 140 | * 141 | * XXX -- Do we have to do anything to guarantee this happens before the library function call? 142 | */ 143 | int mallocation_register(mallocation_type_t type, malloc_loc_t loc, ssize_t delta_size) 144 | { 145 | int rv = -1; 146 | 147 | if (g_mallocation_set) { 148 | (g_mallocation_set)((uint16_t) type, (uint16_t) loc, delta_size); 149 | rv = 0; 150 | } 151 | 152 | return rv; 153 | } 154 | 155 | /* 156 | * Callback function to log messages from the library. 157 | */ 158 | static void my_cb(uint64_t thread_id, uint16_t type, uint16_t loc, ssize_t delta_size, ssize_t total_size, struct timespec *last_time, void *udata) 159 | { 160 | fprintf(stderr, "my_cb(): thread %lu ; type %d ; loc %d (%s:%d); delta_size %ld ; total_size %ld ; last_time %lu.%09lu\n", 161 | thread_id, type, loc, mallocations[loc].file, mallocations[loc].line, delta_size, total_size, last_time->tv_sec, last_time->tv_nsec); 162 | } 163 | 164 | void init(void) 165 | { 166 | fprintf(stderr, "In init()!\n"); 167 | fflush(stderr); 168 | 169 | if (!(g_hook = dlsym(RTLD_NEXT, "asm_hook"))) { 170 | g_hook = original_hook; 171 | } 172 | 173 | if (!(g_cmd = dlsym(RTLD_NEXT, "asm_cmd"))) { 174 | fprintf(stderr, "Could not find \"asm_cmd\" ~~ Using \"original_cmd\"!\n"); 175 | g_cmd = original_cmd; 176 | } 177 | 178 | if (!(g_mallocation_set = dlsym(RTLD_NEXT, "asm_mallocation_set"))) { 179 | fprintf(stderr, "Could not find \"asm_mallocation_set\"!\n"); 180 | } 181 | 182 | if (!(g_mallocation_get = dlsym(RTLD_NEXT, "asm_mallocation_get"))) { 183 | fprintf(stderr, "Could not find \"asm_mallocation_get\"!\n"); 184 | } 185 | 186 | #ifdef FOR_JEMALLOC 187 | if (!(malloc_stats_print = dlsym(RTLD_NEXT, "malloc_stats_print"))) { 188 | fprintf(stderr, "Could not find \"malloc_stats_print\"!\n"); 189 | } 190 | #endif 191 | 192 | cmd(ASM_CMD_SET_FEATURES, ASM_LOG_DATESTAMP | ASM_LOG_THREAD_STATS | ASM_LOG_MEM_COUNT_STATS | ASM_LOG_MALLOCATIONS | ASM_LOG_BLOCKS | ASM_LOG_MALLOC_INFO | ASM_LOG_MALLOC_STATS | ASM_LOG_MALLINFO | ASM_LOG_VM_STATS); 193 | cmd(ASM_CMD_SET_THRESHOLDS, g_thresh_block_size, g_thresh_delta_size, g_thresh_delta_time); 194 | cmd(ASM_CMD_SET_CALLBACK, my_cb, NULL); 195 | } 196 | 197 | void *thread_fn(void *arg) 198 | { 199 | long id = (long) arg; 200 | int *blk[MAX_BLOCKS], blk_num = 0; 201 | bool wrapped = false; 202 | 203 | while (g_running) { 204 | size_t size = rand() % MAX_SIZE; 205 | malloc_loc_t loc = 3; 206 | 207 | mallocation_register(MALLOCATION_TYPE_MALLOC, loc, size); 208 | 209 | blk[blk_num++] = malloc(size); 210 | usleep(100); 211 | 212 | if (!(blk_num % 1000)) { 213 | // fprintf(stderr, "Thread #%ld: blk_num = %d\n", id, blk_num); 214 | fprintf(stderr, "."); 215 | } 216 | 217 | if (blk_num >= MAX_BLOCKS) { 218 | blk_num = 0; 219 | wrapped = true; 220 | } 221 | 222 | if (wrapped) { 223 | free(blk[blk_num]); 224 | blk[blk_num] = 0; 225 | } 226 | } 227 | 228 | int i; 229 | size_t total_size = 0; 230 | for (i = 0; i < MAX_BLOCKS; i++) { 231 | total_size += malloc_usable_size(blk[i]); 232 | } 233 | fprintf(stderr, "Thread #%ld: Was using %ld bytes.\n", id, total_size); 234 | 235 | return 0; 236 | } 237 | 238 | int main(int argc, char *argv[]) 239 | { 240 | long i; 241 | int rv; 242 | pthread_t thread; 243 | 244 | if (((argc == 2) && !strcmp(argv[1], "--help")) || (argc > 4)) { 245 | fprintf(stderr, "Usage: %s [ \n", 246 | argv[0], DEFAULT_THRESH_BLOCK_SIZE_BYTES, DEFAULT_THRESH_DELTA_SIZE_BYTES, DEFAULT_THRESH_DELTA_TIME_SECONDS); 247 | fprintf(stderr, "\tPerform multi-threaded memory allocations test.\n"); 248 | return -1; 249 | } 250 | if (argc > 1) { 251 | g_thresh_block_size = atol(argv[1]); 252 | } 253 | if (argc > 2) { 254 | g_thresh_delta_size = atol(argv[2]); 255 | } 256 | if (argc > 3) { 257 | g_thresh_delta_time = atol(argv[3]); 258 | } 259 | fprintf(stderr, "Test parameters: g_thresh_block_size = %lu ; g_thresh_delta_size = %lu ; g_thresh_delta_time = %lu\n", g_thresh_block_size, g_thresh_delta_size, g_thresh_delta_time); 260 | 261 | init(); 262 | 263 | #ifdef FOR_JEMALLOC 264 | if (malloc_stats_print) { 265 | malloc_stats_print(NULL, NULL, NULL); 266 | } 267 | #endif 268 | 269 | g_running = true; 270 | 271 | struct timespec ts; 272 | clock_gettime(CLOCK_REALTIME, &ts); 273 | srand(ts.tv_nsec % (1LU << 32)); 274 | 275 | for (i = 0; i < NUM_THREADS; i++) { 276 | fprintf(stderr, "main(): Creating thread %ld\n", i); 277 | 278 | if ((rv = pthread_create(&thread, NULL, thread_fn, (void *) i))) { 279 | fprintf(stderr, "Failed to create thread #%ld!\n", i); 280 | } 281 | } 282 | 283 | long iter = 0; 284 | while (g_running) { 285 | asm_stats_t *asm_stats = NULL; 286 | vm_stats_t *vm_stats = NULL; 287 | 288 | hook((void *) iter, &asm_stats, &vm_stats); 289 | 290 | if (asm_stats) { 291 | fprintf(stderr, "***MAIN: asm: mem_count: %lu ; net_mmaps: %lu ; net_shm: %lu***\n", 292 | asm_stats->mem_count, asm_stats->net_mmaps, asm_stats->net_shm); 293 | } 294 | 295 | if (vm_stats) { 296 | vm_stats_desc_t *vm_stats_desc = vm_stats->desc; 297 | fprintf(stderr, "***MAIN: vm: %s: %lu KB; %s: %lu KB ; %s: %lu KB ; %s: %lu KB***\n", 298 | vm_stats_key_name(VM_PEAK), 299 | vm_stats_get_key_value(vm_stats, VM_PEAK), 300 | vm_stats_key_name(VM_SIZE), 301 | vm_stats_get_key_value(vm_stats, VM_SIZE), 302 | vm_stats_key_name(VM_RSS), 303 | vm_stats_get_key_value(vm_stats, VM_RSS), 304 | vm_stats_key_name(VM_DATA), 305 | vm_stats_get_key_value(vm_stats, VM_DATA)); 306 | } 307 | 308 | cmd(ASM_CMD_PRINT_STATS); 309 | 310 | if (++iter > MAX_ITER) { 311 | g_running = false; 312 | } 313 | sleep(SLEEP_TIME); 314 | } 315 | 316 | malloc_stats(); 317 | 318 | #ifdef FOR_JEMALLOC 319 | if (malloc_stats_print) { 320 | malloc_stats_print(NULL, NULL, NULL); 321 | } 322 | #endif 323 | 324 | if ((rv = pthread_join(thread, NULL))) { 325 | fprintf(stderr, "Main failed to join thread %p with rv = %d!\n", (void *) thread, rv); 326 | } 327 | 328 | return 0; 329 | } 330 | -------------------------------------------------------------------------------- /src/vm_stats.c: -------------------------------------------------------------------------------- 1 | /* 2 | * src/vm_stats.c 3 | * 4 | * Copyright (C) 2013-2014 Aerospike, Inc. 5 | * 6 | * Portions may be licensed to Aerospike, Inc. under one or more contributor 7 | * license agreements. 8 | * 9 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 10 | * use this file except in compliance with the License. You may obtain a copy of 11 | * the License at http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 15 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 16 | * License for the specific language governing permissions and limitations under 17 | * the License. 18 | */ 19 | 20 | /* 21 | * SYNOPSIS 22 | * Determine the Virtual Memory (VM) statistics for a given process 23 | * by reading the process' "/proc//status" file. This code has 24 | * been verified under Linux kernel versions from 2.6.X ==> 3.2.X. 25 | * Future kernel versions may provide additional VM statistics that 26 | * may be worthwhile to add to the set this module knows about. 27 | * This is part of the ASMalloc memory allocation tracking tool. 28 | */ 29 | 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | 39 | #include "vm_stats.h" 40 | 41 | /* 42 | * Array of VM-related key names and structure offsets. 43 | */ 44 | vm_stats_desc_t vm_stats_desc[] = 45 | { 46 | { "VmPeak", offsetof(vm_stats_t, vm_peak) }, 47 | { "VmSize", offsetof(vm_stats_t, vm_size) }, 48 | { "VmLck", offsetof(vm_stats_t, vm_lck) }, 49 | { "VmPin", offsetof(vm_stats_t, vm_pin) }, 50 | { "VmHWM", offsetof(vm_stats_t, vm_hwm) }, 51 | { "VmRSS", offsetof(vm_stats_t, vm_rss) }, 52 | { "VmData", offsetof(vm_stats_t, vm_data) }, 53 | { "VmStk", offsetof(vm_stats_t, vm_stk) }, 54 | { "VmExe", offsetof(vm_stats_t, vm_exe) }, 55 | { "VmLib", offsetof(vm_stats_t, vm_lib) }, 56 | { "VmPTE", offsetof(vm_stats_t, vm_pte) }, 57 | { "VmSwap", offsetof(vm_stats_t, vm_swap) } 58 | }; 59 | 60 | /* 61 | * Return a (statically-allocated) string representation of the current date and time. 62 | * 63 | * +++NOTE: This function must NOT allocate memory!+++ 64 | */ 65 | static char *datestamp() 66 | { 67 | static char datestamp[100]; 68 | 69 | time_t now = time(NULL); 70 | strftime(datestamp, sizeof(datestamp), "%d %B %Y @ %H:%M:%S", localtime(&now)); 71 | 72 | return datestamp; 73 | } 74 | 75 | /* 76 | * Read a single key from the given buffer and set the appropriate numeric value in stats. 77 | * 78 | * NOTE: This code assumes that the given key is at the current position in the buffer, 79 | * which is generally the case, or else the statistic is not provided by this version 80 | * of the kernel, since the ordering tends to remain consistent across kernel versions. 81 | * (A less efficient, but more robust, approach would be to scan the entire buffer each time.) 82 | * 83 | * +++NOTE: This function must NOT allocate memory!+++ 84 | */ 85 | static uint64_t read_key(int key, vm_stats_t *stats, char **buf) 86 | { 87 | uint64_t val = 0; 88 | bool found_val = false; 89 | 90 | char *kp = vm_stats_key_name(key); 91 | char *bp = *buf; 92 | while (*kp && *bp && (*kp++ == *bp++)) 93 | ; 94 | 95 | if (*kp) { 96 | // XXX -- This is not actually the correct error message for this case. 97 | // fprintf(stderr, "Failed to read expected \"%s\" ~~ found \"%s\"\n", vm_stats_key_name(key), *buf); 98 | return UINT64_MAX; 99 | } 100 | 101 | while (*bp && !isdigit(*bp)) 102 | bp++; 103 | 104 | while (*bp && isdigit(*bp)) { 105 | found_val = true; 106 | if (val) 107 | val *= 10; 108 | val += *bp++ - '0'; 109 | } 110 | 111 | while (*bp && '\n' != *bp++) 112 | ; 113 | 114 | *buf = bp; 115 | 116 | if (found_val) 117 | vm_stats_set_key_value(stats, key, val); 118 | 119 | return val; 120 | } 121 | 122 | /* 123 | * Read the kernel's VM-related statistics for process PID into the given structure. 124 | */ 125 | int get_vm_stats(int pid, vm_stats_t *stats) 126 | { 127 | static int fd = -1; 128 | static int saved_pid = -1; 129 | 130 | if ((-1 == fd) || (pid != saved_pid)) { 131 | char stat_filename[100]; 132 | sprintf(stat_filename, "/proc/%d/status", pid); 133 | 134 | if (-1 != fd) 135 | close(fd); 136 | 137 | if (0 > (fd = open(stat_filename, O_RDONLY))) { 138 | perror("open"); 139 | return -1; 140 | } 141 | 142 | saved_pid = pid; 143 | } else { 144 | lseek(fd, 0, SEEK_SET); 145 | } 146 | 147 | char buf[1024]; 148 | int num_read = read(fd, buf, sizeof(buf)); 149 | 150 | if (0 > num_read) { 151 | perror("read"); 152 | close(fd); 153 | return -1; 154 | } 155 | 156 | // Note: Assume certain ordering for the fields ~~ Kernel interface dependent! 157 | int pos = 0; 158 | bool line_start = true; 159 | while (pos < num_read) { 160 | if ('\n' == buf[pos]) 161 | line_start = true; 162 | else if (line_start && ('V' == buf[pos])) 163 | break; 164 | else 165 | line_start = false; 166 | pos++; 167 | } 168 | 169 | if (pos >= num_read) { 170 | fprintf(stderr, "Could not locate 'V' after reading %d characters!\n", pos); 171 | close(fd); 172 | return saved_pid = fd = -1; 173 | } 174 | 175 | char *bp = &buf[pos]; 176 | for (int key = 0; key < VM_NUM_KEYS; key++) 177 | read_key(key, stats, &bp); 178 | 179 | return 0; 180 | } 181 | 182 | /* 183 | * Print a VM-related statistic if it exists. 184 | * 185 | * +++NOTE: This function must NOT allocate memory!+++ 186 | */ 187 | static void print_if_found(int key, vm_stats_t *stats) 188 | { 189 | uint64_t val; 190 | 191 | if (UINT64_MAX != (val = vm_stats_get_key_value(stats, key))) 192 | fprintf(stderr, " %s = %lu\n", vm_stats_key_name(key), val); 193 | } 194 | 195 | /* 196 | * Print the current VM-related statistics for the given PID. 197 | * 198 | * +++NOTE: This function must NOT allocate memory!+++ 199 | */ 200 | vm_stats_t *log_vm_stats(int pid) 201 | { 202 | static int64_t init_size = 0; 203 | static int64_t last_size = 0; 204 | static vm_stats_t stats; 205 | 206 | // Initialize all statistics to not found the first time around. 207 | if (!init_size) { 208 | // Initialize the description of the VM stats structure. 209 | stats.desc = &vm_stats_desc[0]; 210 | 211 | for (int key = 0; key < VM_NUM_KEYS; key++) 212 | vm_stats_set_key_value(&stats, key, UINT64_MAX); 213 | } 214 | 215 | if (-1 == get_vm_stats(pid, &stats)) { 216 | fprintf(stderr, "Failed to get VM stats for PID %d!\n", pid); 217 | return 0; 218 | } 219 | 220 | if (0 >= init_size) { 221 | init_size = stats.vm_size; 222 | } 223 | 224 | int64_t delta = stats.vm_size - last_size; 225 | int64_t net_delta = stats.vm_size - init_size; 226 | 227 | if (delta) { 228 | fprintf(stderr, "\n%s: Proc %d %s by %ld KB (net change: %ld KB)\n", datestamp(), pid, (delta ? (delta > 0 ? "grew" : "shrunk") : "unchanged"), delta, net_delta); 229 | last_size = stats.vm_size; 230 | for (int key = 0; key < VM_NUM_KEYS; key++) 231 | print_if_found(key, &stats); 232 | } 233 | 234 | return &stats; 235 | } 236 | --------------------------------------------------------------------------------