You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
206 lines
6.1 KiB
206 lines
6.1 KiB
#!/usr/bin/env bcc-lua
|
|
--[[
|
|
Copyright 2016 GitHub, Inc
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
]]
|
|
|
|
local bpf_source = [[
|
|
#include <uapi/linux/ptrace.h>
|
|
|
|
struct alloc_info_t {
|
|
u64 size;
|
|
u64 timestamp_ns;
|
|
int stack_id;
|
|
};
|
|
|
|
BPF_HASH(sizes, u64);
|
|
BPF_HASH(allocs, u64, struct alloc_info_t);
|
|
BPF_STACK_TRACE(stack_traces, 10240);
|
|
|
|
int alloc_enter(struct pt_regs *ctx, size_t size)
|
|
{
|
|
SIZE_FILTER
|
|
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);
|
|
|
|
if (SHOULD_PRINT)
|
|
bpf_trace_printk("alloc entered, size = %u\n", size);
|
|
return 0;
|
|
}
|
|
|
|
int alloc_exit(struct pt_regs *ctx)
|
|
{
|
|
u64 address = PT_REGS_RC(ctx);
|
|
u64 pid = bpf_get_current_pid_tgid();
|
|
u64* size64 = sizes.lookup(&pid);
|
|
struct alloc_info_t info = {0};
|
|
|
|
if (size64 == 0)
|
|
return 0; // missed alloc entry
|
|
|
|
info.size = *size64;
|
|
sizes.delete(&pid);
|
|
|
|
info.timestamp_ns = bpf_ktime_get_ns();
|
|
info.stack_id = stack_traces.get_stackid(ctx, STACK_FLAGS);
|
|
|
|
allocs.update(&address, &info);
|
|
|
|
if (SHOULD_PRINT) {
|
|
bpf_trace_printk("alloc exited, size = %lu, result = %lx\n",
|
|
info.size, address);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int free_enter(struct pt_regs *ctx, void *address)
|
|
{
|
|
u64 addr = (u64)address;
|
|
struct alloc_info_t *info = allocs.lookup(&addr);
|
|
if (info == 0)
|
|
return 0;
|
|
|
|
allocs.delete(&addr);
|
|
|
|
if (SHOULD_PRINT) {
|
|
bpf_trace_printk("free entered, address = %lx, size = %lu\n",
|
|
address, info->size);
|
|
}
|
|
return 0;
|
|
}
|
|
]]
|
|
|
|
return function(BPF, utils)
|
|
local parser = utils.argparse("memleak", "Catch memory leaks")
|
|
parser:flag("-t --trace")
|
|
parser:flag("-a --show-allocs")
|
|
parser:option("-p --pid"):convert(tonumber)
|
|
|
|
parser:option("-i --interval", "", 5):convert(tonumber)
|
|
parser:option("-o --older", "", 500):convert(tonumber)
|
|
parser:option("-s --sample-rate", "", 1):convert(tonumber)
|
|
|
|
parser:option("-z --min-size", ""):convert(tonumber)
|
|
parser:option("-Z --max-size", ""):convert(tonumber)
|
|
parser:option("-T --top", "", 10):convert(tonumber)
|
|
|
|
local args = parser:parse()
|
|
|
|
local size_filter = ""
|
|
if args.min_size and args.max_size then
|
|
size_filter = "if (size < %d || size > %d) return 0;" % {args.min_size, args.max_size}
|
|
elseif args.min_size then
|
|
size_filter = "if (size < %d) return 0;" % args.min_size
|
|
elseif args.max_size then
|
|
size_filter = "if (size > %d) return 0;" % args.max_size
|
|
end
|
|
|
|
local stack_flags = "BPF_F_REUSE_STACKID"
|
|
if args.pid then
|
|
stack_flags = stack_flags .. "|BPF_F_USER_STACK"
|
|
end
|
|
|
|
local text = bpf_source
|
|
text = text:gsub("SIZE_FILTER", size_filter)
|
|
text = text:gsub("STACK_FLAGS", stack_flags)
|
|
text = text:gsub("SHOULD_PRINT", args.trace and "1" or "0")
|
|
text = text:gsub("SAMPLE_EVERY_N", tostring(args.sample_rate))
|
|
|
|
local bpf = BPF:new{text=text, debug=0}
|
|
local syms = nil
|
|
local min_age_ns = args.older * 1e6
|
|
|
|
if args.pid then
|
|
print("Attaching to malloc and free in pid %d, Ctrl+C to quit." % args.pid)
|
|
bpf:attach_uprobe{name="c", sym="malloc", fn_name="alloc_enter", pid=args.pid}
|
|
bpf:attach_uprobe{name="c", sym="malloc", fn_name="alloc_exit", pid=args.pid, retprobe=true}
|
|
bpf:attach_uprobe{name="c", sym="free", fn_name="free_enter", pid=args.pid}
|
|
else
|
|
print("Attaching to kmalloc and kfree, Ctrl+C to quit.")
|
|
bpf:attach_kprobe{event="__kmalloc", fn_name="alloc_enter"}
|
|
bpf:attach_kprobe{event="__kmalloc", fn_name="alloc_exit", retprobe=true} -- TODO
|
|
bpf:attach_kprobe{event="kfree", fn_name="free_enter"}
|
|
end
|
|
|
|
local syms = BPF.SymbolCache(args.pid)
|
|
local allocs = bpf:get_table("allocs")
|
|
local stack_traces = bpf:get_table("stack_traces")
|
|
|
|
local function resolve(addr)
|
|
local sym = syms:resolve(addr)
|
|
if args.pid == nil then
|
|
sym = sym .. " [kernel]"
|
|
end
|
|
return string.format("%s (%p)", sym, addr)
|
|
end
|
|
|
|
local function print_outstanding()
|
|
local alloc_info = {}
|
|
local now = utils.posix.time_ns()
|
|
|
|
print("[%s] Top %d stacks with outstanding allocations:" %
|
|
{os.date("%H:%M:%S"), args.top})
|
|
|
|
for address, info in allocs:items() do
|
|
if now - min_age_ns >= tonumber(info.timestamp_ns) then
|
|
local stack_id = tonumber(info.stack_id)
|
|
|
|
if stack_id >= 0 then
|
|
if alloc_info[stack_id] then
|
|
local s = alloc_info[stack_id]
|
|
s.count = s.count + 1
|
|
s.size = s.size + tonumber(info.size)
|
|
else
|
|
local stack = stack_traces:get(stack_id, resolve)
|
|
alloc_info[stack_id] = { stack=stack, count=1, size=tonumber(info.size) }
|
|
end
|
|
end
|
|
|
|
if args.show_allocs then
|
|
print("\taddr = %p size = %s" % {address, tonumber(info.size)})
|
|
end
|
|
end
|
|
end
|
|
|
|
local top = table.values(alloc_info)
|
|
table.sort(top, function(a, b) return a.size > b.size end)
|
|
|
|
for n, alloc in ipairs(top) do
|
|
print("\t%d bytes in %d allocations from stack\n\t\t%s" %
|
|
{alloc.size, alloc.count, table.concat(alloc.stack, "\n\t\t")})
|
|
if n == args.top then break end
|
|
end
|
|
end
|
|
|
|
if args.trace then
|
|
local pipe = bpf:pipe()
|
|
while true do
|
|
print(pipe:trace_fields())
|
|
end
|
|
else
|
|
while true do
|
|
utils.posix.sleep(args.interval)
|
|
syms:refresh()
|
|
print_outstanding()
|
|
end
|
|
end
|
|
end
|