blob: bc59b406916969621c43a6294ebdb047df9e194c [file] [log] [blame]
Sasha Goldshtein4f1ea672016-02-07 01:57:42 -08001#!/usr/bin/env python
Sasha Goldshtein50459642016-02-10 08:35:20 -08002#
Sasha Goldshtein0e856f42016-03-21 07:26:52 -07003# memleak Trace and display outstanding allocations to detect
4# memory leaks in user-mode processes and the kernel.
Sasha Goldshtein50459642016-02-10 08:35:20 -08005#
Sasha Goldshtein29e37d92016-02-14 06:56:07 -08006# USAGE: memleak [-h] [-p PID] [-t] [-a] [-o OLDER] [-c COMMAND]
Sasha Goldshtein0e856f42016-03-21 07:26:52 -07007# [-s SAMPLE_RATE] [-d STACK_DEPTH] [-T TOP] [-z MIN_SIZE]
8# [-Z MAX_SIZE]
9# [interval] [count]
Sasha Goldshtein50459642016-02-10 08:35:20 -080010#
Sasha Goldshtein43fa0412016-02-10 22:17:26 -080011# Licensed under the Apache License, Version 2.0 (the "License")
Sasha Goldshtein50459642016-02-10 08:35:20 -080012# Copyright (C) 2016 Sasha Goldshtein.
Sasha Goldshtein4f1ea672016-02-07 01:57:42 -080013
Sasha Goldshtein8737c6e2016-03-21 10:05:29 -070014from bcc import BPF, ProcessSymbols
Sasha Goldshtein4f1ea672016-02-07 01:57:42 -080015from time import sleep
Sasha Goldshteinc8148c82016-02-09 11:15:41 -080016from datetime import datetime
Sasha Goldshtein4f1ea672016-02-07 01:57:42 -080017import argparse
18import subprocess
Sasha Goldshteincfce3112016-02-07 11:09:36 -080019import ctypes
20import os
Sasha Goldshtein4f1ea672016-02-07 01:57:42 -080021
Sasha Goldshteina7cc6c22016-02-07 12:03:54 -080022class Time(object):
Sasha Goldshtein33522d72016-02-08 03:39:44 -080023 # BPF timestamps come from the monotonic clock. To be able to filter
24 # and compare them from Python, we need to invoke clock_gettime.
25 # Adapted from http://stackoverflow.com/a/1205762
26 CLOCK_MONOTONIC_RAW = 4 # see <linux/time.h>
Sasha Goldshteina7cc6c22016-02-07 12:03:54 -080027
Sasha Goldshtein33522d72016-02-08 03:39:44 -080028 class timespec(ctypes.Structure):
29 _fields_ = [
30 ('tv_sec', ctypes.c_long),
31 ('tv_nsec', ctypes.c_long)
32 ]
Sasha Goldshteina7cc6c22016-02-07 12:03:54 -080033
Sasha Goldshtein33522d72016-02-08 03:39:44 -080034 librt = ctypes.CDLL('librt.so.1', use_errno=True)
35 clock_gettime = librt.clock_gettime
36 clock_gettime.argtypes = [ctypes.c_int, ctypes.POINTER(timespec)]
Sasha Goldshteina7cc6c22016-02-07 12:03:54 -080037
Sasha Goldshtein33522d72016-02-08 03:39:44 -080038 @staticmethod
39 def monotonic_time():
40 t = Time.timespec()
41 if Time.clock_gettime(
42 Time.CLOCK_MONOTONIC_RAW, ctypes.pointer(t)) != 0:
43 errno_ = ctypes.get_errno()
44 raise OSError(errno_, os.strerror(errno_))
45 return t.tv_sec * 1e9 + t.tv_nsec
Sasha Goldshteina7cc6c22016-02-07 12:03:54 -080046
Vicent Martie25ae032016-03-25 17:14:34 +010047class KStackDecoder(object):
48 def refresh(self):
49 pass
50
51 def __call__(self, addr):
52 return "%s [kernel] (%x)" % (BPF.ksym(addr), addr)
53
54class UStackDecoder(object):
Sasha Goldshtein0e856f42016-03-21 07:26:52 -070055 def __init__(self, pid):
Sasha Goldshtein33522d72016-02-08 03:39:44 -080056 self.pid = pid
Vicent Martie25ae032016-03-25 17:14:34 +010057 self.proc_sym = ProcessSymbols(pid)
Sasha Goldshtein29228612016-02-07 12:20:19 -080058
Sasha Goldshtein0e856f42016-03-21 07:26:52 -070059 def refresh(self):
Vicent Martie25ae032016-03-25 17:14:34 +010060 self.proc_sym.refresh_code_ranges()
Sasha Goldshtein33522d72016-02-08 03:39:44 -080061
Vicent Martie25ae032016-03-25 17:14:34 +010062 def __call__(self, addr):
63 return "%s (%x)" % (self.proc_sym.decode_addr(addr), addr)
64
65class Allocation(object):
66 def __init__(self, stack, size):
67 self.stack = stack
68 self.count = 1
69 self.size = size
70
71 def update(self, size):
72 self.count += 1
73 self.size += size
Sasha Goldshtein29228612016-02-07 12:20:19 -080074
Sasha Goldshtein751fce52016-02-08 02:57:02 -080075def run_command_get_output(command):
Sasha Goldshtein33522d72016-02-08 03:39:44 -080076 p = subprocess.Popen(command.split(),
77 stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
78 return iter(p.stdout.readline, b'')
Sasha Goldshtein29228612016-02-07 12:20:19 -080079
Sasha Goldshtein751fce52016-02-08 02:57:02 -080080def run_command_get_pid(command):
Sasha Goldshtein33522d72016-02-08 03:39:44 -080081 p = subprocess.Popen(command.split())
82 return p.pid
Sasha Goldshtein751fce52016-02-08 02:57:02 -080083
Sasha Goldshtein4f1ea672016-02-07 01:57:42 -080084examples = """
85EXAMPLES:
86
Sasha Goldshtein29e37d92016-02-14 06:56:07 -080087./memleak -p $(pidof allocs)
Sasha Goldshtein33522d72016-02-08 03:39:44 -080088 Trace allocations and display a summary of "leaked" (outstanding)
89 allocations every 5 seconds
Sasha Goldshtein29e37d92016-02-14 06:56:07 -080090./memleak -p $(pidof allocs) -t
Sasha Goldshtein33522d72016-02-08 03:39:44 -080091 Trace allocations and display each individual call to malloc/free
Sasha Goldshtein29e37d92016-02-14 06:56:07 -080092./memleak -ap $(pidof allocs) 10
Sasha Goldshtein33522d72016-02-08 03:39:44 -080093 Trace allocations and display allocated addresses, sizes, and stacks
94 every 10 seconds for outstanding allocations
Sasha Goldshtein29e37d92016-02-14 06:56:07 -080095./memleak -c "./allocs"
Sasha Goldshtein33522d72016-02-08 03:39:44 -080096 Run the specified command and trace its allocations
Sasha Goldshtein29e37d92016-02-14 06:56:07 -080097./memleak
Sasha Goldshtein33522d72016-02-08 03:39:44 -080098 Trace allocations in kernel mode and display a summary of outstanding
99 allocations every 5 seconds
Sasha Goldshtein29e37d92016-02-14 06:56:07 -0800100./memleak -o 60000
Sasha Goldshtein33522d72016-02-08 03:39:44 -0800101 Trace allocations in kernel mode and display a summary of outstanding
102 allocations that are at least one minute (60 seconds) old
Sasha Goldshtein29e37d92016-02-14 06:56:07 -0800103./memleak -s 5
Sasha Goldshtein521ab4f2016-02-08 05:48:31 -0800104 Trace roughly every 5th allocation, to reduce overhead
Sasha Goldshtein4f1ea672016-02-07 01:57:42 -0800105"""
106
107description = """
108Trace outstanding memory allocations that weren't freed.
109Supports both user-mode allocations made with malloc/free and kernel-mode
110allocations made with kmalloc/kfree.
111"""
112
Sasha Goldshteina7cc6c22016-02-07 12:03:54 -0800113parser = argparse.ArgumentParser(description=description,
Sasha Goldshtein33522d72016-02-08 03:39:44 -0800114 formatter_class=argparse.RawDescriptionHelpFormatter,
115 epilog=examples)
Sasha Goldshteind2241f42016-02-09 06:23:10 -0800116parser.add_argument("-p", "--pid", type=int, default=-1,
Sasha Goldshtein33522d72016-02-08 03:39:44 -0800117 help="the PID to trace; if not specified, trace kernel allocs")
Sasha Goldshteina7cc6c22016-02-07 12:03:54 -0800118parser.add_argument("-t", "--trace", action="store_true",
Sasha Goldshtein33522d72016-02-08 03:39:44 -0800119 help="print trace messages for each alloc/free call")
Sasha Goldshtein40e55ba2016-02-09 05:53:48 -0800120parser.add_argument("interval", nargs="?", default=5, type=int,
Sasha Goldshtein33522d72016-02-08 03:39:44 -0800121 help="interval in seconds to print outstanding allocations")
Sasha Goldshtein40e55ba2016-02-09 05:53:48 -0800122parser.add_argument("count", nargs="?", type=int,
123 help="number of times to print the report before exiting")
Sasha Goldshteina7cc6c22016-02-07 12:03:54 -0800124parser.add_argument("-a", "--show-allocs", default=False, action="store_true",
Sasha Goldshtein33522d72016-02-08 03:39:44 -0800125 help="show allocation addresses and sizes as well as call stacks")
Sasha Goldshtein40e55ba2016-02-09 05:53:48 -0800126parser.add_argument("-o", "--older", default=500, type=int,
Sasha Goldshtein33522d72016-02-08 03:39:44 -0800127 help="prune allocations younger than this age in milliseconds")
Sasha Goldshtein29228612016-02-07 12:20:19 -0800128parser.add_argument("-c", "--command",
Sasha Goldshtein33522d72016-02-08 03:39:44 -0800129 help="execute and trace the specified command")
Sasha Goldshtein40e55ba2016-02-09 05:53:48 -0800130parser.add_argument("-s", "--sample-rate", default=1, type=int,
Sasha Goldshtein521ab4f2016-02-08 05:48:31 -0800131 help="sample every N-th allocation to decrease the overhead")
Sasha Goldshteinc8148c82016-02-09 11:15:41 -0800132parser.add_argument("-T", "--top", type=int, default=10,
133 help="display only this many top allocating stacks (by size)")
Sasha Goldshtein50459642016-02-10 08:35:20 -0800134parser.add_argument("-z", "--min-size", type=int,
135 help="capture only allocations larger than this size")
136parser.add_argument("-Z", "--max-size", type=int,
137 help="capture only allocations smaller than this size")
Sasha Goldshtein4f1ea672016-02-07 01:57:42 -0800138
139args = parser.parse_args()
140
Sasha Goldshteind2241f42016-02-09 06:23:10 -0800141pid = args.pid
Sasha Goldshtein29228612016-02-07 12:20:19 -0800142command = args.command
143kernel_trace = (pid == -1 and command is None)
Sasha Goldshtein4f1ea672016-02-07 01:57:42 -0800144trace_all = args.trace
Sasha Goldshtein40e55ba2016-02-09 05:53:48 -0800145interval = args.interval
146min_age_ns = 1e6 * args.older
Sasha Goldshtein521ab4f2016-02-08 05:48:31 -0800147sample_every_n = args.sample_rate
Sasha Goldshtein40e55ba2016-02-09 05:53:48 -0800148num_prints = args.count
Sasha Goldshteinc8148c82016-02-09 11:15:41 -0800149top_stacks = args.top
Sasha Goldshtein50459642016-02-10 08:35:20 -0800150min_size = args.min_size
151max_size = args.max_size
152
153if min_size is not None and max_size is not None and min_size > max_size:
154 print("min_size (-z) can't be greater than max_size (-Z)")
155 exit(1)
Sasha Goldshtein4f1ea672016-02-07 01:57:42 -0800156
Sasha Goldshtein33522d72016-02-08 03:39:44 -0800157if command is not None:
158 print("Executing '%s' and tracing the resulting process." % command)
159 pid = run_command_get_pid(command)
Sasha Goldshtein29228612016-02-07 12:20:19 -0800160
Sasha Goldshtein43fa0412016-02-10 22:17:26 -0800161bpf_source = """
162#include <uapi/linux/ptrace.h>
163
164struct alloc_info_t {
165 u64 size;
166 u64 timestamp_ns;
Vicent Martie25ae032016-03-25 17:14:34 +0100167 int stack_id;
Sasha Goldshtein43fa0412016-02-10 22:17:26 -0800168};
169
170BPF_HASH(sizes, u64);
171BPF_HASH(allocs, u64, struct alloc_info_t);
Vicent Martie25ae032016-03-25 17:14:34 +0100172BPF_STACK_TRACE(stack_traces, 1024)
Sasha Goldshtein43fa0412016-02-10 22:17:26 -0800173
174int alloc_enter(struct pt_regs *ctx, size_t size)
175{
176 SIZE_FILTER
177 if (SAMPLE_EVERY_N > 1) {
178 u64 ts = bpf_ktime_get_ns();
179 if (ts % SAMPLE_EVERY_N != 0)
180 return 0;
181 }
182
183 u64 pid = bpf_get_current_pid_tgid();
184 u64 size64 = size;
185 sizes.update(&pid, &size64);
186
187 if (SHOULD_PRINT)
188 bpf_trace_printk("alloc entered, size = %u\\n", size);
189 return 0;
190}
191
192int alloc_exit(struct pt_regs *ctx)
193{
194 u64 address = ctx->ax;
195 u64 pid = bpf_get_current_pid_tgid();
196 u64* size64 = sizes.lookup(&pid);
197 struct alloc_info_t info = {0};
198
199 if (size64 == 0)
200 return 0; // missed alloc entry
201
202 info.size = *size64;
203 sizes.delete(&pid);
204
205 info.timestamp_ns = bpf_ktime_get_ns();
Vicent Martie25ae032016-03-25 17:14:34 +0100206 info.stack_id = stack_traces.get_stackid(ctx, STACK_FLAGS);
Sasha Goldshtein43fa0412016-02-10 22:17:26 -0800207 allocs.update(&address, &info);
Sasha Goldshtein0e856f42016-03-21 07:26:52 -0700208
Sasha Goldshtein43fa0412016-02-10 22:17:26 -0800209 if (SHOULD_PRINT) {
Vicent Martie25ae032016-03-25 17:14:34 +0100210 bpf_trace_printk("alloc exited, size = %lu, result = %lx\\n",
211 info.size, address);
Sasha Goldshtein43fa0412016-02-10 22:17:26 -0800212 }
213 return 0;
214}
215
216int free_enter(struct pt_regs *ctx, void *address)
217{
218 u64 addr = (u64)address;
219 struct alloc_info_t *info = allocs.lookup(&addr);
220 if (info == 0)
221 return 0;
222
223 allocs.delete(&addr);
224
225 if (SHOULD_PRINT) {
226 bpf_trace_printk("free entered, address = %lx, size = %lu\\n",
227 address, info->size);
228 }
229 return 0;
230}
Sasha Goldshtein0e856f42016-03-21 07:26:52 -0700231"""
Sasha Goldshtein4f1ea672016-02-07 01:57:42 -0800232bpf_source = bpf_source.replace("SHOULD_PRINT", "1" if trace_all else "0")
Sasha Goldshtein521ab4f2016-02-08 05:48:31 -0800233bpf_source = bpf_source.replace("SAMPLE_EVERY_N", str(sample_every_n))
Sasha Goldshtein50459642016-02-10 08:35:20 -0800234
235size_filter = ""
236if min_size is not None and max_size is not None:
237 size_filter = "if (size < %d || size > %d) return 0;" % \
238 (min_size, max_size)
239elif min_size is not None:
240 size_filter = "if (size < %d) return 0;" % min_size
241elif max_size is not None:
242 size_filter = "if (size > %d) return 0;" % max_size
243bpf_source = bpf_source.replace("SIZE_FILTER", size_filter)
244
Vicent Martie25ae032016-03-25 17:14:34 +0100245stack_flags = "BPF_F_REUSE_STACKID"
246if not kernel_trace:
247 stack_flags += "|BPF_F_USER_STACK"
248bpf_source = bpf_source.replace("STACK_FLAGS", stack_flags)
249
Sasha Goldshtein4f1ea672016-02-07 01:57:42 -0800250bpf_program = BPF(text=bpf_source)
251
252if not kernel_trace:
Sasha Goldshtein33522d72016-02-08 03:39:44 -0800253 print("Attaching to malloc and free in pid %d, Ctrl+C to quit." % pid)
254 bpf_program.attach_uprobe(name="c", sym="malloc",
255 fn_name="alloc_enter", pid=pid)
256 bpf_program.attach_uretprobe(name="c", sym="malloc",
257 fn_name="alloc_exit", pid=pid)
258 bpf_program.attach_uprobe(name="c", sym="free",
259 fn_name="free_enter", pid=pid)
Sasha Goldshtein4f1ea672016-02-07 01:57:42 -0800260else:
Sasha Goldshtein33522d72016-02-08 03:39:44 -0800261 print("Attaching to kmalloc and kfree, Ctrl+C to quit.")
262 bpf_program.attach_kprobe(event="__kmalloc", fn_name="alloc_enter")
263 bpf_program.attach_kretprobe(event="__kmalloc", fn_name="alloc_exit")
264 bpf_program.attach_kprobe(event="kfree", fn_name="free_enter")
Sasha Goldshtein4f1ea672016-02-07 01:57:42 -0800265
Vicent Martie25ae032016-03-25 17:14:34 +0100266decoder = KStackDecoder() if kernel_trace else UStackDecoder(pid)
Sasha Goldshtein4f1ea672016-02-07 01:57:42 -0800267
268def print_outstanding():
Sasha Goldshteinc8148c82016-02-09 11:15:41 -0800269 print("[%s] Top %d stacks with outstanding allocations:" %
270 (datetime.now().strftime("%H:%M:%S"), top_stacks))
Vicent Martie25ae032016-03-25 17:14:34 +0100271 alloc_info = {}
272 allocs = bpf_program["allocs"]
273 stack_traces = bpf_program["stack_traces"]
Sasha Goldshtein33522d72016-02-08 03:39:44 -0800274 for address, info in sorted(allocs.items(), key=lambda a: a[1].size):
275 if Time.monotonic_time() - min_age_ns < info.timestamp_ns:
276 continue
Vicent Martie25ae032016-03-25 17:14:34 +0100277 if info.stack_id < 0:
278 continue
279 if info.stack_id in alloc_info:
280 alloc_info[info.stack_id].update(info.size)
Sasha Goldshtein33522d72016-02-08 03:39:44 -0800281 else:
Vicent Martie25ae032016-03-25 17:14:34 +0100282 stack = list(stack_traces.walk(info.stack_id, decoder))
283 alloc_info[info.stack_id] = Allocation(stack, info.size)
Sasha Goldshtein33522d72016-02-08 03:39:44 -0800284 if args.show_allocs:
285 print("\taddr = %x size = %s" %
286 (address.value, info.size))
Vicent Martie25ae032016-03-25 17:14:34 +0100287 to_show = sorted(alloc_info.values(), key=lambda a: a.size)[-top_stacks:]
288 for alloc in to_show:
Sasha Goldshtein33522d72016-02-08 03:39:44 -0800289 print("\t%d bytes in %d allocations from stack\n\t\t%s" %
Vicent Martie25ae032016-03-25 17:14:34 +0100290 (alloc.size, alloc.count, "\n\t\t".join(alloc.stack)))
Sasha Goldshtein4f1ea672016-02-07 01:57:42 -0800291
Sasha Goldshtein40e55ba2016-02-09 05:53:48 -0800292count_so_far = 0
Sasha Goldshtein4f1ea672016-02-07 01:57:42 -0800293while True:
Sasha Goldshtein33522d72016-02-08 03:39:44 -0800294 if trace_all:
Brenden Blancoc94ab7a2016-03-11 15:34:29 -0800295 print(bpf_program.trace_fields())
Sasha Goldshtein33522d72016-02-08 03:39:44 -0800296 else:
297 try:
298 sleep(interval)
299 except KeyboardInterrupt:
300 exit()
Sasha Goldshtein0e856f42016-03-21 07:26:52 -0700301 decoder.refresh()
Sasha Goldshtein33522d72016-02-08 03:39:44 -0800302 print_outstanding()
Sasha Goldshtein40e55ba2016-02-09 05:53:48 -0800303 count_so_far += 1
304 if num_prints is not None and count_so_far >= num_prints:
305 exit()