aboutsummaryrefslogtreecommitdiff
path: root/scripts/tracing/dma-api/trace.py
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/tracing/dma-api/trace.py')
-rw-r--r--scripts/tracing/dma-api/trace.py349
1 files changed, 349 insertions, 0 deletions
diff --git a/scripts/tracing/dma-api/trace.py b/scripts/tracing/dma-api/trace.py
new file mode 100644
index 00000000000..c4507b6c4d0
--- /dev/null
+++ b/scripts/tracing/dma-api/trace.py
@@ -0,0 +1,349 @@
+"""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<dev>.*), addr=(?P<addr>.*), size=(?P<size>.*) page=(?P<page>.*) archdata=(?P<archdata>.*)")
+ 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()