Commit a7cc6c21 authored by Sasha Goldshtein's avatar Sasha Goldshtein

Refactored Time class and added usage example

parent 5cd2e0bc
...@@ -7,20 +7,48 @@ import subprocess ...@@ -7,20 +7,48 @@ import subprocess
import ctypes import ctypes
import os import os
class Time(object):
# Adapted from http://stackoverflow.com/a/1205762
CLOCK_MONOTONIC_RAW = 4 # see <linux/time.h>
class timespec(ctypes.Structure):
_fields_ = [
('tv_sec', ctypes.c_long),
('tv_nsec', ctypes.c_long)
]
librt = ctypes.CDLL('librt.so.1', use_errno=True)
clock_gettime = librt.clock_gettime
clock_gettime.argtypes = [ctypes.c_int, ctypes.POINTER(timespec)]
@staticmethod
def monotonic_time():
"""monotonic_time()
Returns the reading of the monotonic clock, in nanoseconds.
"""
t = Time.timespec()
if Time.clock_gettime(Time.CLOCK_MONOTONIC_RAW , ctypes.pointer(t)) != 0:
errno_ = ctypes.get_errno()
raise OSError(errno_, os.strerror(errno_))
return t.tv_sec*1e9 + t.tv_nsec
examples = """ examples = """
EXAMPLES: EXAMPLES:
memleak.py -p $(pidof allocs) ./memleak.py -p $(pidof allocs)
Trace allocations and display a summary of "leaked" (outstanding) Trace allocations and display a summary of "leaked" (outstanding)
allocations every 5 seconds allocations every 5 seconds
memleak.py -p $(pidof allocs) -t ./memleak.py -p $(pidof allocs) -t
Trace allocations and display each individual call to malloc/free Trace allocations and display each individual call to malloc/free
memleak.py -p $(pidof allocs) -a -i 10 ./memleak.py -p $(pidof allocs) -a -i 10
Trace allocations and display allocated addresses, sizes, and stacks Trace allocations and display allocated addresses, sizes, and stacks
every 10 seconds for outstanding allocations every 10 seconds for outstanding allocations
memleak.py ./memleak.py
Trace allocations in kernel mode and display a summary of outstanding Trace allocations in kernel mode and display a summary of outstanding
allocations every 5 seconds 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
""" """
description = """ description = """
...@@ -29,12 +57,20 @@ Supports both user-mode allocations made with malloc/free and kernel-mode ...@@ -29,12 +57,20 @@ Supports both user-mode allocations made with malloc/free and kernel-mode
allocations made with kmalloc/kfree. allocations made with kmalloc/kfree.
""" """
parser = argparse.ArgumentParser(description=description, formatter_class=argparse.RawDescriptionHelpFormatter, epilog=examples) parser = argparse.ArgumentParser(description=description,
parser.add_argument("-p", "--pid", help="the PID to trace; if not specified, trace kernel allocs") formatter_class=argparse.RawDescriptionHelpFormatter,
parser.add_argument("-t", "--trace", action="store_true", help="print trace messages for each alloc/free call") epilog=examples)
parser.add_argument("-i", "--interval", default=5, help="interval in seconds to print outstanding allocations") parser.add_argument("-p", "--pid",
parser.add_argument("-a", "--show-allocs", default=False, action="store_true", help="show allocation addresses and sizes as well as call stacks") help="the PID to trace; if not specified, trace kernel allocs")
parser.add_argument("-o", "--older", default=500, help="prune allocations younger than this age in milliseconds") parser.add_argument("-t", "--trace", action="store_true",
help="print trace messages for each alloc/free call")
parser.add_argument("-i", "--interval", default=5,
help="interval in seconds to print outstanding allocations")
parser.add_argument("-a", "--show-allocs", default=False, action="store_true",
help="show allocation addresses and sizes as well as call stacks")
parser.add_argument("-o", "--older", default=500,
help="prune allocations younger than this age in milliseconds")
# TODO Run a command and trace that command (-c)
args = parser.parse_args() args = parser.parse_args()
...@@ -106,7 +142,7 @@ def decode_addr(code_ranges, addr): ...@@ -106,7 +142,7 @@ def decode_addr(code_ranges, addr):
for binary, (start, end) in code_ranges.items(): for binary, (start, end) in code_ranges.items():
if addr >= start and addr <= end: if addr >= start and addr <= end:
offset = addr - start if binary.endswith(".so") else addr offset = addr - start if binary.endswith(".so") else addr
return "%s %s" % (binary, decode_sym(binary, offset)) return "%s [%s]" % (decode_sym(binary, offset), binary)
return "%x" % addr return "%x" % addr
def decode_stack(info): def decode_stack(info):
...@@ -116,45 +152,25 @@ def decode_stack(info): ...@@ -116,45 +152,25 @@ def decode_stack(info):
for i in range(0, info.num_frames): for i in range(0, info.num_frames):
addr = info.callstack[i] addr = info.callstack[i]
if kernel_trace: if kernel_trace:
stack += " %s (%x) ;" % (bpf_program.ksym(addr), addr) stack += " %s [kernel] (%x) ;" % (bpf_program.ksym(addr), addr)
else: else:
stack += " %s (%x) ;" % (decode_addr(code_ranges, addr), addr) stack += " %s (%x) ;" % (decode_addr(code_ranges, addr), addr)
return stack return stack
# Adapted from http://stackoverflow.com/a/1205762
CLOCK_MONOTONIC_RAW = 4 # see <linux/time.h>
class timespec(ctypes.Structure):
_fields_ = [
('tv_sec', ctypes.c_long),
('tv_nsec', ctypes.c_long)
]
librt = ctypes.CDLL('librt.so.1', use_errno=True)
clock_gettime = librt.clock_gettime
clock_gettime.argtypes = [ctypes.c_int, ctypes.POINTER(timespec)]
def monotonic_time():
t = timespec()
if clock_gettime(CLOCK_MONOTONIC_RAW , ctypes.pointer(t)) != 0:
errno_ = ctypes.get_errno()
raise OSError(errno_, os.strerror(errno_))
return t.tv_sec*1e9 + t.tv_nsec
def print_outstanding(): def print_outstanding():
stacks = {} stacks = {}
print("*** Outstanding allocations:") print("*** Outstanding allocations:")
allocs = bpf_program.get_table("allocs") allocs = bpf_program.get_table("allocs")
for address, info in sorted(allocs.items(), key=lambda a: -a[1].size): for address, info in sorted(allocs.items(), key=lambda a: -a[1].size):
if monotonic_time() - min_age_ns < info.timestamp_ns: if Time.monotonic_time() - min_age_ns < info.timestamp_ns:
continue continue
stack = decode_stack(info) stack = decode_stack(info)
if stack in stacks: stacks[stack] += info.size if stack in stacks: stacks[stack] = (stacks[stack][0] + 1, stacks[stack][1] + info.size)
else: stacks[stack] = info.size else: stacks[stack] = (1, info.size)
if args.show_allocs: if args.show_allocs:
print("\taddr = %x size = %s" % (address.value, info.size)) print("\taddr = %x size = %s" % (address.value, info.size))
for stack, size in sorted(stacks.items(), key=lambda s: -s[1]): for stack, (count, size) in sorted(stacks.items(), key=lambda s: -s[1][1]):
print("\t%d bytes allocated from stack\n\t\t%s" % (size, stack.replace(";", "\n\t\t"))) print("\t%d bytes in %d allocations from stack\n\t\t%s" % (size, count, stack.replace(";", "\n\t\t")))
while True: while True:
if trace_all: if trace_all:
......
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