Refactored Time class and added usage example
authorSasha Goldshtein <goldshtn@gmail.com>
Sun, 7 Feb 2016 20:03:54 +0000 (12:03 -0800)
committerSasha Goldshtein <goldshtn@gmail.com>
Sun, 7 Feb 2016 20:03:54 +0000 (12:03 -0800)
tools/memleak.py

index 03fd0c1..cc56a21 100755 (executable)
@@ -7,20 +7,48 @@ import subprocess
 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 @@ Supports both user-mode allocations made with malloc/free and kernel-mode
 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 @@ def decode_addr(code_ranges, addr):
        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 @@ def decode_stack(info):
        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: