Files
tint2/test/memtrace-analyze.py
2017-12-19 12:35:35 +01:00

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()