Refactored Time class and added usage example
diff --git a/tools/memleak.py b/tools/memleak.py
index 03fd0c1..cc56a21 100755
--- a/tools/memleak.py
+++ b/tools/memleak.py
@@ -7,20 +7,48 @@
import ctypes
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:
-memleak.py -p $(pidof allocs)
+./memleak.py -p $(pidof allocs)
Trace allocations and display a summary of "leaked" (outstanding)
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
-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
every 10 seconds for outstanding allocations
-memleak.py
+./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
"""
description = """
@@ -29,12 +57,20 @@
allocations made with kmalloc/kfree.
"""
-parser = argparse.ArgumentParser(description=description, formatter_class=argparse.RawDescriptionHelpFormatter, epilog=examples)
-parser.add_argument("-p", "--pid", help="the PID to trace; if not specified, trace kernel allocs")
-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")
+parser = argparse.ArgumentParser(description=description,
+ formatter_class=argparse.RawDescriptionHelpFormatter,
+ epilog=examples)
+parser.add_argument("-p", "--pid",
+ help="the PID to trace; if not specified, trace kernel allocs")
+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()
@@ -106,7 +142,7 @@
for binary, (start, end) in code_ranges.items():
if addr >= start and addr <= end:
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
def decode_stack(info):
@@ -116,45 +152,25 @@
for i in range(0, info.num_frames):
addr = info.callstack[i]
if kernel_trace:
- stack += " %s (%x) ;" % (bpf_program.ksym(addr), addr)
+ stack += " %s [kernel] (%x) ;" % (bpf_program.ksym(addr), addr)
else:
stack += " %s (%x) ;" % (decode_addr(code_ranges, addr), addr)
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():
stacks = {}
print("*** Outstanding allocations:")
allocs = bpf_program.get_table("allocs")
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
stack = decode_stack(info)
- if stack in stacks: stacks[stack] += info.size
- else: stacks[stack] = info.size
+ if stack in stacks: stacks[stack] = (stacks[stack][0] + 1, stacks[stack][1] + info.size)
+ else: stacks[stack] = (1, info.size)
if args.show_allocs:
print("\taddr = %x size = %s" % (address.value, info.size))
- for stack, size in sorted(stacks.items(), key=lambda s: -s[1]):
- print("\t%d bytes allocated from stack\n\t\t%s" % (size, stack.replace(";", "\n\t\t")))
+ for stack, (count, size) in sorted(stacks.items(), key=lambda s: -s[1][1]):
+ print("\t%d bytes in %d allocations from stack\n\t\t%s" % (size, count, stack.replace(";", "\n\t\t")))
while True:
if trace_all: