Commit 50459640 authored by Sasha Goldshtein's avatar Sasha Goldshtein

Added -z and -Z switches for filtering by size, added copyright notices

parent 828edb50
...@@ -2,7 +2,8 @@ ...@@ -2,7 +2,8 @@
.SH NAME .SH NAME
memleak \- Print a summary of outstanding allocations and their call stacks to detect memory leaks. Uses Linux eBPF/bcc. memleak \- Print a summary of outstanding allocations and their call stacks to detect memory leaks. Uses Linux eBPF/bcc.
.SH SYNOPSIS .SH SYNOPSIS
.B memleak [-h] [-p PID] [-t] [-a] [-o OLDER] [-c COMMAND] [-s SAMPLE_RATE] [-d STACK_DEPTH] [-T TOP] [INTERVAL] [COUNT] .B memleak [-h] [-p PID] [-t] [-a] [-o OLDER] [-c COMMAND] [-s SAMPLE_RATE]
[-d STACK_DEPTH] [-T TOP] [-z MIN_SIZE] [-Z MAX_SIZE] [INTERVAL] [COUNT]
.SH DESCRIPTION .SH DESCRIPTION
memleak traces and matches memory allocation and deallocation requests, and memleak traces and matches memory allocation and deallocation requests, and
collects call stacks for each allocation. memleak can then print a summary collects call stacks for each allocation. memleak can then print a summary
...@@ -52,6 +53,12 @@ The default value is 10. ...@@ -52,6 +53,12 @@ The default value is 10.
Print only the top TOP stacks (sorted by size). Print only the top TOP stacks (sorted by size).
The default value is 10. The default value is 10.
.TP .TP
\-z MIN_SIZE
Capture only allocations that are larger than or equal to MIN_SIZE bytes.
.TP
\-Z MAX_SIZE
Capture only allocations that are smaller than or equal to MAX_SIZE bytes.
.TP
INTERVAL INTERVAL
Print a summary of oustanding allocations and their call stacks every INTERVAL seconds. Print a summary of oustanding allocations and their call stacks every INTERVAL seconds.
The default interval is 5 seconds. The default interval is 5 seconds.
...@@ -76,12 +83,17 @@ stacks 10 times before quitting. ...@@ -76,12 +83,17 @@ stacks 10 times before quitting.
Run ./allocs and print outstanding allocation stacks for that process: Run ./allocs and print outstanding allocation stacks for that process:
# #
.B memleak -c "./allocs" .B memleak -c "./allocs"
.TP
Capture only allocations between 16 and 32 bytes in size:
#
.B memleak -z 16 -Z 32
.SH OVERHEAD .SH OVERHEAD
memleak can have significant overhead if the target process or kernel performs 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 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 degradation in running time. Most of the time, however, memleak shouldn't cause
a significant slowdown. You can also use the \-s switch to reduce the overhead a significant slowdown. You can use the \-s switch to reduce the overhead
further by capturing only every N-th allocation. further by capturing only every N-th allocation. The \-z and \-Z switches can
also reduce overhead by capturing only allocations of specific sizes.
To determine the rate at which your application is calling malloc/free, or the 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 rate at which your kernel is calling kmalloc/kfree, place a probe with perf and
......
/*
* memleak.c Trace and display outstanding allocations to detect
* memory leaks in user-mode processes and the kernel.
*
* Copyright (C) 2016 Sasha Goldshtein.
*/
#include <uapi/linux/ptrace.h> #include <uapi/linux/ptrace.h>
struct alloc_info_t { struct alloc_info_t {
...@@ -33,9 +40,7 @@ static int grab_stack(struct pt_regs *ctx, struct alloc_info_t *info) ...@@ -33,9 +40,7 @@ static int grab_stack(struct pt_regs *ctx, struct alloc_info_t *info)
int alloc_enter(struct pt_regs *ctx, size_t size) int alloc_enter(struct pt_regs *ctx, size_t size)
{ {
// Ideally, this should use a random number source, such as SIZE_FILTER
// BPF_FUNC_get_prandom_u32, but that's currently not supported
// by the bcc front-end.
if (SAMPLE_EVERY_N > 1) { if (SAMPLE_EVERY_N > 1) {
u64 ts = bpf_ktime_get_ns(); u64 ts = bpf_ktime_get_ns();
if (ts % SAMPLE_EVERY_N != 0) if (ts % SAMPLE_EVERY_N != 0)
......
#!/usr/bin/env python #!/usr/bin/env python
#
# memleak.py Trace and display outstanding allocations to detect
# memory leaks in user-mode processes and the kernel.
#
# USAGE: memleak.py [-h] [-p PID] [-t] [-a] [-o OLDER] [-c COMMAND]
# [-s SAMPLE_RATE] [-d STACK_DEPTH] [-T TOP] [-z MIN_SIZE]
# [-Z MAX_SIZE]
# [interval] [count]
#
# Copyright (C) 2016 Sasha Goldshtein.
from bcc import BPF from bcc import BPF
from time import sleep from time import sleep
...@@ -195,6 +205,10 @@ parser.add_argument("-d", "--stack-depth", default=10, type=int, ...@@ -195,6 +205,10 @@ parser.add_argument("-d", "--stack-depth", default=10, type=int,
help="maximum stack depth to capture") help="maximum stack depth to capture")
parser.add_argument("-T", "--top", type=int, default=10, parser.add_argument("-T", "--top", type=int, default=10,
help="display only this many top allocating stacks (by size)") help="display only this many top allocating stacks (by size)")
parser.add_argument("-z", "--min-size", type=int,
help="capture only allocations larger than this size")
parser.add_argument("-Z", "--max-size", type=int,
help="capture only allocations smaller than this size")
args = parser.parse_args() args = parser.parse_args()
...@@ -208,6 +222,12 @@ sample_every_n = args.sample_rate ...@@ -208,6 +222,12 @@ sample_every_n = args.sample_rate
num_prints = args.count num_prints = args.count
max_stack_size = args.stack_depth + 2 max_stack_size = args.stack_depth + 2
top_stacks = args.top top_stacks = args.top
min_size = args.min_size
max_size = args.max_size
if min_size is not None and max_size is not None and min_size > max_size:
print("min_size (-z) can't be greater than max_size (-Z)")
exit(1)
if command is not None: if command is not None:
print("Executing '%s' and tracing the resulting process." % command) print("Executing '%s' and tracing the resulting process." % command)
...@@ -219,6 +239,17 @@ bpf_source = bpf_source.replace("SAMPLE_EVERY_N", str(sample_every_n)) ...@@ -219,6 +239,17 @@ bpf_source = bpf_source.replace("SAMPLE_EVERY_N", str(sample_every_n))
bpf_source = bpf_source.replace("GRAB_ONE_FRAME", max_stack_size * bpf_source = bpf_source.replace("GRAB_ONE_FRAME", max_stack_size *
"\tif (!(info->callstack[depth++] = get_frame(&bp))) return depth;\n") "\tif (!(info->callstack[depth++] = get_frame(&bp))) return depth;\n")
bpf_source = bpf_source.replace("MAX_STACK_SIZE", str(max_stack_size)) bpf_source = bpf_source.replace("MAX_STACK_SIZE", str(max_stack_size))
size_filter = ""
if min_size is not None and max_size is not None:
size_filter = "if (size < %d || size > %d) return 0;" % \
(min_size, max_size)
elif min_size is not None:
size_filter = "if (size < %d) return 0;" % min_size
elif max_size is not None:
size_filter = "if (size > %d) return 0;" % max_size
bpf_source = bpf_source.replace("SIZE_FILTER", size_filter)
bpf_program = BPF(text=bpf_source) bpf_program = BPF(text=bpf_source)
if not kernel_trace: if not kernel_trace:
......
...@@ -178,6 +178,10 @@ optional arguments: ...@@ -178,6 +178,10 @@ optional arguments:
-d STACK_DEPTH, --stack_depth STACK_DEPTH -d STACK_DEPTH, --stack_depth STACK_DEPTH
maximum stack depth to capture maximum stack depth to capture
-T TOP, --top TOP display only this many top allocating stacks (by size) -T TOP, --top TOP display only this many top allocating stacks (by size)
-z MIN_SIZE, --min-size MIN_SIZE
capture only allocations larger than this size
-Z MAX_SIZE, --max-size MAX_SIZE
capture only allocations smaller than this size
EXAMPLES: EXAMPLES:
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment