Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New tool: memleak #355

Merged
merged 13 commits into from
Feb 8, 2016
Prev Previous commit
Added -s switch to perform allocation sampling
  • Loading branch information
goldshtn committed Feb 8, 2016
commit 521ab4f137aa75f651f50b47c54950c091d1b529
8 changes: 6 additions & 2 deletions man/man8/memleak.8
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
.SH NAME
memleak \- Print a summary of outstanding allocations and their call stacks to detect memory leaks. Uses Linux eBPF/bcc.
.SH SYNOPSIS
.B memleak [-h] [-p PID] [-t] [-i INTERVAL] [-a] [-o OLDER] [-c COMMAND]
.B memleak [-h] [-p PID] [-t] [-i INTERVAL] [-a] [-o OLDER] [-c COMMAND] [-s SAMPLE_RATE]
.SH DESCRIPTION
memleak traces and matches memory allocation and deallocation requests, and
collects call stacks for each allocation. memleak can then print a summary
Expand Down Expand Up @@ -40,6 +40,9 @@ The default value is 500 milliseconds.
.TP
\-c COMMAND
Run the specified command and trace its allocations only. This traces malloc and free from libc.
.TP
\-s SAMPLE_RATE
Record roughly every SAMPLE_RATE-th allocation to reduce overhead.
.SH EXAMPLES
.TP
Print outstanding kernel allocation stacks every 3 seconds:
Expand All @@ -57,7 +60,8 @@ Run ./allocs and print outstanding allocation stacks for that process:
memleak can have significant overhead if the target process or kernel performs
allocations at a very high rate. Pathological cases may exhibit up to 100x
degradation in running time. Most of the time, however, memleak shouldn't cause
a significant slowdown.
a significant slowdown. You can also use the \-s switch to reduce the overhead
further by capturing only every N-th allocation.

To determine the rate at which your application is calling malloc/free, or the
rate at which your kernel is calling kmalloc/kfree, place a probe with perf and
Expand Down
9 changes: 9 additions & 0 deletions tools/memleak.c
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,15 @@ static int grab_stack(struct pt_regs *ctx, struct alloc_info_t *info)

int alloc_enter(struct pt_regs *ctx, size_t size)
{
// Ideally, this should use a random number source, such as
// BPF_FUNC_get_prandom_u32, but that's currently not supported
// by the bcc front-end.
if (SAMPLE_EVERY_N > 1) {
u64 ts = bpf_ktime_get_ns();
if (ts % SAMPLE_EVERY_N != 0)
return 0;
}

u64 pid = bpf_get_current_pid_tgid();
u64 size64 = size;
sizes.update(&pid, &size64);
Expand Down
8 changes: 7 additions & 1 deletion tools/memleak.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ def refresh_code_ranges(self):
@staticmethod
def _is_binary_segment(parts):
return len(parts) == 6 and \
parts[5][0] == '[' and 'x' in parts[1]
parts[5][0] != '[' and 'x' in parts[1]

def _get_code_ranges(self):
ranges = {}
Expand Down Expand Up @@ -161,6 +161,8 @@ def run_command_get_pid(command):
./memleak.py -o 60000
Trace allocations in kernel mode and display a summary of outstanding
allocations that are at least one minute (60 seconds) old
./memleak.py -s 5
Trace roughly every 5th allocation, to reduce overhead
"""

description = """
Expand All @@ -184,6 +186,8 @@ def run_command_get_pid(command):
help="prune allocations younger than this age in milliseconds")
parser.add_argument("-c", "--command",
help="execute and trace the specified command")
parser.add_argument("-s", "--sample-rate", default=1,
help="sample every N-th allocation to decrease the overhead")

args = parser.parse_args()

Expand All @@ -193,13 +197,15 @@ def run_command_get_pid(command):
trace_all = args.trace
interval = int(args.interval)
min_age_ns = 1e6 * int(args.older)
sample_every_n = args.sample_rate

if command is not None:
print("Executing '%s' and tracing the resulting process." % command)
pid = run_command_get_pid(command)

bpf_source = open("memleak.c").read()
bpf_source = bpf_source.replace("SHOULD_PRINT", "1" if trace_all else "0")
bpf_source = bpf_source.replace("SAMPLE_EVERY_N", str(sample_every_n))

bpf_program = BPF(text=bpf_source)

Expand Down
6 changes: 5 additions & 1 deletion tools/memleak_examples.txt
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ USAGE message:

# ./memleak.py -h
usage: memleak.py [-h] [-p PID] [-t] [-i INTERVAL] [-a] [-o OLDER]
[-c COMMAND]
[-c COMMAND] [-s SAMPLE_RATE]

Trace outstanding memory allocations that weren't freed.
Supports both user-mode allocations made with malloc/free and kernel-mode
Expand All @@ -137,6 +137,8 @@ optional arguments:
milliseconds
-c COMMAND, --command COMMAND
execute and trace the specified command
-s SAMPLE_RATE, --sample-rate SAMPLE_RATE
sample every N-th allocation to decrease the overhead

EXAMPLES:

Expand All @@ -146,4 +148,6 @@ EXAMPLES:
./memleak.py -p $(pidof allocs) -t
Trace allocations and display each individual call to malloc/free
./memleak.py -p $(pidof allocs) -a -i 10 Trace allocations and display allocated addresses, sizes, and stacks every 10 seconds for outstanding allocations ./memleak.py -c "./allocs" Run the specified command and trace its allocations ./memleak.py Trace allocations in kernel mode and display a summary of outstanding allocations every 5 seconds ./memleak.py -o 60000 Trace allocations in kernel mode and display a summary of outstanding allocations that are at least one minute (60 seconds) old
./memleak.py -s 5
Trace roughly every 5th allocation, to reduce overhead