"""Main program and stuff""" #from pprint import pprint from sys import stdin import os.path import re from argparse import ArgumentParser import cPickle as pickle from collections import namedtuple from plotting import plotseries, disp_pic import smmu class TracelineParser(object): """Parse the needed information out of an ftrace line""" # <...>-6 [000] d..2 5.287079: dmadebug_iommu_map_page: device=sdhci-tegra.3, addr=0x01048000, size=4096 page=c13e7214 archdata=ed504640 def __init__(self): self.pattern = re.compile("device=(?P.*), addr=(?P.*), size=(?P.*) page=(?P.*) archdata=(?P.*)") def parse(self, args): args = self.pattern.match(args) return (args.group("dev"), int(args.group("addr"), 16), int(args.group("size")), int(args.group("page"), 16), int(args.group("archdata"), 16)) def biggest_indices(items, n): """Return list of indices of n biggest elements in items""" with_indices = [(x, i) for i, x in enumerate(items)] ordered = sorted(with_indices) return [i for x, i in ordered[-n:]] def by_indices(xs, ids): """Get elements from the list xs by their indices""" return [xs[i] for i in ids] """Event represents one input line""" Event = namedtuple("Event", ["time", "dev", "data", "delta"]) class Trace(object): def __init__(self, args): smmu.VERBOSITY = args.verbosity self._args = args self.devlist = [] self.events = [] self.metrics = { "max_peak": self._usage_peak, "activity_rate": self._usage_activity, "average_mem": self._usage_avg } self.traceliner = TracelineParser() @staticmethod def get_metrics(): """What filter metrics to get max users""" return ["max_peak", "activity_rate", "average_mem"] def show(self): """Shuffle events around, build plots, and show them""" if self._args.max_plots: evs = self.merge_events() else: evs = self.events series, devlist = self.unload(evs) if not self._args.no_plots: self.plot(series, devlist) def _get_usage(self, evs): """Return a metric of how active the events in evs are""" return self.metrics[self._args.max_metric](evs) def _usage_peak(self, evs): """Return the biggest peak""" return max(e.data for e in evs) def _usage_activity(self, evs): """Return the activity count: simply the length of the event list""" return len(evs) def _usage_avg(self, evs): """Return the average over all points""" # FIXME: the data points are not uniform in time, so this might be # somewhat off. return float(sum(e.data for e in evs)) / len(e) def merge_events(self): """Find out biggest users, keep them and flatten others to a single user""" sizes = [] dev_evs = [] for i, dev in enumerate(self.devlist): dev_evs.append([e for e in self.events if e.dev == dev]) sizes.append(self._get_usage(dev_evs[i])) # indices of the devices biggestix = biggest_indices(sizes, self._args.max_plots) print biggestix is_big = {} for i, dev in enumerate(self.devlist): is_big[dev] = i in biggestix evs = [] for e in self.events: if not is_big[e.dev]: e = Event(e.time, "others", e.data, e.delta) evs.append(e) self.devlist.append("others") return evs def unload(self, events): """Prepare the event list for plotting series ends up as [([time0], [data0]), ([time1], [data1]), ...] """ # ([x], [y]) for matplotlib series = [([], []) for x in self.devlist] devidx = dict([(d, i) for i, d in enumerate(self.devlist)]) for event in events: devid = devidx[event.dev] series[devid][0].append(event.time) series[devid][1].append(event.data) # self.dev_data(event.dev)) series_out = [] devlist_out = [] for ser, dev in zip(series, self.devlist): if len(ser[0]) > 0: series_out.append(ser) devlist_out.append(dev) return series_out, devlist_out def plot(self, series, devlist): """Display the plots""" #series, devlist = flatten_axes(self.series, self.devlist, # self._args.max_plots) devinfo = (series, map(str, devlist)) allocfreeinfo = (self.allocsfrees, ["allocd", "freed", "current"]) plotseries(devinfo, allocfreeinfo) #plotseries(devinfo) def dev_data(self, dev): """what data to plot against time""" return dev._cur_alloc def _cache_hash(self, filename): """The trace files are probably not of the same size""" return str(os.path.getsize(filename)) def load_cache(self): """Get the trace data from a database file, if one exists""" has = self._cache_hash(self._args.filename) try: cache = open("trace." + has) except IOError: pass else: self._load_cache(pickle.load(cache)) return True return False def save_cache(self): """Store the raw trace data to a database""" data = self._save_cache() fh = open("trace." + self._cache_hash(self._args.filename), "w") pickle.dump(data, fh) def _save_cache(self): """Return the internal data that is needed to be pickled""" return self.events, self.devlist, self.allocsfrees def _load_cache(self, data): """Get the data from an unpickled object""" self.events, self.devlist, self.allocsfrees = data def load_events(self): """Get the internal data from a trace file or cache""" if self._args.filename: if self._args.cache and self.load_cache(): return fh = open(self._args.filename) else: fh = stdin self.parse(fh) if self._args.cache and self._args.filename: self.save_cache() def parse(self, fh): """Parse the trace file in fh, store data to self""" mems = {} dev_by_name = {} devlist = [] buf_owners = {} events = [] allocsfrees = [([], []), ([], []), ([], [])] # allocs, frees, current allocs = 0 frees = 0 curbufs = 0 mem_bytes = 1024 * 1024 * 1024 npages = mem_bytes / 4096 ncols = 512 le_pic = [0] * npages lastupd = 0 for lineidx, line in enumerate(fh): # no comments if line.startswith("#"): continue taskpid, cpu, flags, timestamp, func, args = line.strip().split(None, 5) func = func[:-len(":")] # unneeded events may be there too if not func.startswith("dmadebug"): continue if self._args.verbosity >= 3: print line.rstrip() timestamp = float(timestamp[:-1]) if timestamp < self._args.start: continue if timestamp >= self._args.end: break devname, addr, size, page, archdata = self.traceliner.parse(args) if self._args.processes: devname = taskpid.split("-")[0] mapping = archdata try: memmap = mems[mapping] except KeyError: memmap = mem(mapping) mems[mapping] = memmap try: dev = dev_by_name[devname] except KeyError: dev = smmu.Device(devname, memmap) dev_by_name[devname] = dev devlist.append(dev) allocfuncs = ["dmadebug_map_page", "dmadebug_map_sg", "dmadebug_alloc_coherent"] freefuncs = ["dmadebug_unmap_page", "dmadebug_unmap_sg", "dmadebug_free_coherent"] ignfuncs = [] if timestamp-lastupd > 0.1: # just some debug prints for now lastupd = timestamp print lineidx,timestamp le_pic2 = [le_pic[i:i+ncols] for i in range(0, npages, ncols)] #disp_pic(le_pic2) # animating the bitmap would be cool #for row in le_pic: # for i, a in enumerate(row): # pass #row[i] = 0.09 * a if func in allocfuncs: pages = dev_by_name[devname].alloc(addr, size) for p in pages: le_pic[p] = 1 buf_owners[addr] = dev_by_name[devname] allocs += 1 curbufs += 1 allocsfrees[0][0].append(timestamp) allocsfrees[0][1].append(allocs) elif func in freefuncs: if addr not in buf_owners: if self._args.verbosity >= 1: print "warning: %s unmapping unmapped %s" % (dev, addr) buf_owners[addr] = dev # fixme: move this to bitmap handling # get to know the owners of bits # allocs/frees calls should be traced separately from maps? # map_pages is traced per page :( if buf_owners[addr] != dev and self._args.verbosity >= 2: print "note: %s unmapping [%d,%d) mapped by %s" % ( dev, addr, addr+size, buf_owners[addr]) pages = buf_owners[addr].free(addr, size) for p in pages: le_pic[p] = 0 frees -= 1 curbufs -= 1 allocsfrees[1][0].append(timestamp) allocsfrees[1][1].append(frees) elif func not in ignfuncs: raise ValueError("unhandled %s" % func) allocsfrees[2][0].append(timestamp) allocsfrees[2][1].append(curbufs) events.append(Event(timestamp, dev, self.dev_data(dev), size)) self.events = events self.devlist = devlist self.allocsfrees = allocsfrees le_pic2 = [le_pic[i:i+ncols] for i in range(0, npages, ncols)] # FIXME: not quite ready yet disp_pic(le_pic2) return def mem(asid): """Create a new memory object for the given asid space""" SZ_2G = 2 * 1024 * 1024 * 1024 SZ_1M = 1 * 1024 * 1024 # arch/arm/mach-tegra/include/mach/iomap.h TEGRA_SMMU_(BASE|SIZE) base = 0x80000000 size = SZ_2G - SZ_1M return smmu.Memory(base, size, asid) def get_args(): """Eat command line arguments, return argparse namespace for settings""" parser = ArgumentParser() parser.add_argument("filename", nargs="?", help="trace file dump, stdin if not given") parser.add_argument("-s", "--start", type=float, default=0, help="start timestamp") parser.add_argument("-e", "--end", type=float, default=1e9, help="end timestamp") parser.add_argument("-v", "--verbosity", action="count", default=0, help="amount of extra information: once for warns (dup addrs), " "twice for notices (different client in map/unmap), " "three for echoing all back") parser.add_argument("-p", "--processes", action="store_true", help="use processes as memory clients instead of devices") parser.add_argument("-n", "--no-plots", action="store_true", help="Don't draw the plots, only read the trace") parser.add_argument("-c", "--cache", action="store_true", help="Pickle the data and make a cache file for fast reloading") parser.add_argument("-m", "--max-plots", type=int, help="Maximum number of clients to show; show biggest and sum others") parser.add_argument("-M", "--max-metric", choices=Trace.get_metrics(), default=Trace.get_metrics()[0], help="Metric to use when choosing clients in --max-plots") return parser.parse_args() def main(): args = get_args() trace = Trace(args) trace.load_events() trace.show() if __name__ == "__main__": main()