166 lines
4.5 KiB
Python
Executable File
166 lines
4.5 KiB
Python
Executable File
#!/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()
|