#!/usr/bin/env python2 import gzip import sys def commas(v): return "{:12,}".format(v) class Event: def __init__(self): self.alloc = False self.free = False self.ts = None self.size = None self.addr = None self.trace_name = None class Analyzer: def __init__(self): self.traces = {} self.events = [] self.all_allocations = {} self.new_allocations = {} self.all_mem = 0 self.old_mem = 0 def read(self, path): with gzip.open(path, "rb") as f: try: for line in f: line = line.strip() if line.startswith("t"): self.add_trace(line) elif line.startswith("["): self.add_call(line) except: pass def add_trace(self, line): t, name, content = line.split(" ", 2) name = int(name) assert(name > 0) assert(name not in self.traces) self.traces[name] = content.split("|") def add_call(self, line): first, second = line.split(":", 1) first = first.strip() ts, t, tname = first.split(" ") ts = float(ts.replace("[", "").replace("]", "")) assert(t == "t") tname = int(tname) assert(tname in self.traces) second = second.strip() result, eq, func, params = second.split(" ", 3) assert(eq == "=") params = params.split(" ") e = Event() e.ts = ts e.trace_name = tname if func == "m" and result != "(nil)": e.alloc = True e.addr = int(result, 16) e.size = int(params[0]) elif func == "c" and result != "(nil)": e.alloc = True e.addr = int(result, 16) e.size = int(params[0]) * int(params[1]) elif func == "r": old_addr = params[0] size = int(params[1]) if old_addr == "(nil)": if result != "(nil)": e.alloc = True e.addr = int(result, 16) e.size = size elif old_addr != "(nil)": if result != "(nil)": e.free = True e.addr = int(old_addr, 16) self.events.append(e) e = Event() e.ts = ts e.trace_name = tname e.alloc = True e.addr = int(result, 16) e.size = size elif func == "f": addr = params[0] if addr != "(nil)": e.free = True e.addr = int(addr, 16) if e.alloc or e.free: self.events.append(e) def analyze(self): self.all_allocations = {} self.new_allocations = {} self.all_mem = 0 self.old_mem = 0 self.peak_mem = 0 period = 1 next_ts_print = self.events[0].ts for e in self.events: if e.ts >= next_ts_print: self.print_allocations() next_ts_print += period self.process_event(e) self.print_allocations() self.print_leaks() def print_allocations(self): print "Memory usage: current:", commas(self.all_mem), " | delta:", commas(self.all_mem - self.old_mem), " | peak:", commas(self.peak_mem) self.old_mem = self.all_mem self.new_alllocations = {} def print_leaks(self): if not self.all_allocations: return print "Memory leaked:", commas(self.all_mem) leaks = [e for addr, e in self.all_allocations.iteritems()] leaks.sort(key=lambda e: e.size) for e in leaks: print "Leak:", commas(e.size) print "Allocated in:\n ", "\n ".join(self.traces[e.trace_name]) def process_event(self, e): if e.alloc: assert(e.addr not in self.all_allocations) self.all_allocations[e.addr] = e assert(e.addr not in self.new_allocations) self.new_allocations[e.addr] = e self.all_mem += e.size self.peak_mem = max(self.peak_mem, self.all_mem) else: assert(e.addr in self.all_allocations) e_alloc = self.all_allocations[e.addr] if e.ts - e_alloc.ts > 1. and e_alloc.size > 1e3 and False: print "Long-lived alloc:", commas(e_alloc.size), "freed after", commas(e.ts - e_alloc.ts) + "s" print "Allocated in:\n ", "\n ".join(self.traces[e_alloc.trace_name]) print "Freed in:\n ", "\n ".join(self.traces[e.trace_name]) if e_alloc.addr in self.new_allocations: if e_alloc.size > 1e7 and False: print "Large alloc:", commas(e_alloc.size), "freed after", commas(e.ts - e_alloc.ts) + "s" print "Allocated in:\n ", "\n ".join(self.traces[e_alloc.trace_name]) print "Freed in:\n ", "\n ".join(self.traces[e.trace_name]) del self.new_allocations[e_alloc.addr] del self.all_allocations[e_alloc.addr] self.all_mem -= e_alloc.size def main(): a = Analyzer() a.read(sys.argv[1]) a.analyze() if __name__ == "__main__": main()