scripts: tool for visualizing DMA mapping API ftrace dump
Konsta Holtta [Thu, 29 Aug 2013 18:37:38 +0000 (21:37 +0300)]
Add a tool for parsing and plotting data from dmadebug:* ftrace events.

Bug 1173494

Change-Id: Id7e175fda4c1ef42ef6a0403f1bece36b273cd99
Signed-off-by: Konsta Holtta <kholtta@nvidia.com>
Signed-off-by: Hiroshi Doyu <hdoyu@nvidia.com>
Reviewed-on: http://git-master/r/268051
Reviewed-by: Krishna Reddy <vdumpa@nvidia.com>

scripts/tracing/dma-api/.gitignore [new file with mode: 0644]
scripts/tracing/dma-api/README [new file with mode: 0644]
scripts/tracing/dma-api/plotting.py [new file with mode: 0644]
scripts/tracing/dma-api/smmu.py [new file with mode: 0644]
scripts/tracing/dma-api/trace.py [new file with mode: 0644]

diff --git a/scripts/tracing/dma-api/.gitignore b/scripts/tracing/dma-api/.gitignore
new file mode 100644 (file)
index 0000000..0d20b64
--- /dev/null
@@ -0,0 +1 @@
+*.pyc
diff --git a/scripts/tracing/dma-api/README b/scripts/tracing/dma-api/README
new file mode 100644 (file)
index 0000000..caeb88f
--- /dev/null
@@ -0,0 +1,36 @@
+
+        DMA API trace BASIC USAGE
+       ===========================
+
+
+Enable CONFIG_DMA_API_DEBUG
+----------------------------
+$TOP/kernel/scripts/config \
+       --file $OUT/obj/KERNEL/.config \
+       -e DMA_API_DEBUG
+make -C$TOP/kernel ARCH=arm O=$OUT/obj/KERNEL oldconfig
+grep DMA_API_DEBUG $OUT/obj/KERNEL/.config
+
+
+
+Via debugfs
+-----------
+adb shell cat /d/dma-api/dump_{mappings,allocs,allocs_detail}
+
+
+Via Ftrace
+----------
+# All ftraces from boot, add "trace_event=dmadebug:*" to kernel command line
+sed -i '/strlcpy(cmd_line, boot_command_line, /i \
+    \tstrcat(boot_command_line, " trace_event=dmadebug:*");' \
+    $TOP/kernel/arch/arm/kernel/setup.c
+
+Or
+
+adb shell "echo 'dmadebug:*' >> /d/tracing/set_event"
+
+adb shell <your_test_here>
+adb shell "echo '\!dmadebug:*' >> /d/tracing/set_event"
+adb pull /d/tracing/trace
+
+python $TOP/kernel/scripts/tracing/dma-api/trace.py trace
diff --git a/scripts/tracing/dma-api/plotting.py b/scripts/tracing/dma-api/plotting.py
new file mode 100644 (file)
index 0000000..f3d69b6
--- /dev/null
@@ -0,0 +1,100 @@
+"""Ugly graph drawing tools"""
+import matplotlib.pyplot as plt
+import matplotlib.cm as cmap
+#import numpy as np
+from matplotlib import cbook
+
+# http://stackoverflow.com/questions/4652439/is-there-a-matplotlib-equivalent-of-matlabs-datacursormode
+class DataCursor(object):
+    """A simple data cursor widget that displays the x,y location of a
+    matplotlib artist when it is selected."""
+    def __init__(self, artists, tolerance=5, offsets=(-20, 20),
+                 template='x: %0.2f\ny: %0.2f', display_all=False):
+        """Create the data cursor and connect it to the relevant figure.
+        "artists" is the matplotlib artist or sequence of artists that will be
+            selected.
+        "tolerance" is the radius (in points) that the mouse click must be
+            within to select the artist.
+        "offsets" is a tuple of (x,y) offsets in points from the selected
+            point to the displayed annotation box
+        "template" is the format string to be used. Note: For compatibility
+            with older versions of python, this uses the old-style (%)
+            formatting specification.
+        "display_all" controls whether more than one annotation box will
+            be shown if there are multiple axes.  Only one will be shown
+            per-axis, regardless.
+        """
+        self.template = template
+        self.offsets = offsets
+        self.display_all = display_all
+        if not cbook.iterable(artists):
+            artists = [artists]
+        self.artists = artists
+        self.axes = tuple(set(art.axes for art in self.artists))
+        self.figures = tuple(set(ax.figure for ax in self.axes))
+
+        self.annotations = {}
+        for ax in self.axes:
+            self.annotations[ax] = self.annotate(ax)
+
+        for artist in self.artists:
+            artist.set_picker(tolerance)
+        for fig in self.figures:
+            fig.canvas.mpl_connect('pick_event', self)
+
+    def annotate(self, ax):
+        """Draws and hides the annotation box for the given axis "ax"."""
+        annotation = ax.annotate(self.template, xy=(0, 0), ha='right',
+                xytext=self.offsets, textcoords='offset points', va='bottom',
+                bbox=dict(boxstyle='round,pad=0.5', fc='yellow', alpha=0.5),
+                arrowprops=dict(arrowstyle='->', connectionstyle='arc3,rad=0')
+                )
+        annotation.set_visible(False)
+        return annotation
+
+    def __call__(self, event):
+        """Intended to be called through "mpl_connect"."""
+        # Rather than trying to interpolate, just display the clicked coords
+        # This will only be called if it's within "tolerance", anyway.
+        x, y = event.mouseevent.xdata, event.mouseevent.ydata
+        try:
+            annotation = self.annotations[event.artist.axes]
+        except KeyError:
+            return
+        if x is not None:
+            if not self.display_all:
+                # Hide any other annotation boxes...
+                for ann in self.annotations.values():
+                    ann.set_visible(False)
+            # Update the annotation in the current axis..
+            annotation.xy = x, y
+            annotation.set_text(self.template % (x, y))
+            annotation.set_visible(True)
+            event.canvas.draw()
+
+def plotseries(*serieslabels):
+    """Plot lists of series in separate axes, tie time axis together"""
+    global fig
+    fig, axes = plt.subplots(nrows=len(serieslabels), sharex=True)
+
+    for subplot, ax in zip(serieslabels, axes):
+        for ser, lab in zip(*subplot): # subplot = ([x], [y])
+            ax.step(ser[0], ser[1], label=lab, where="post")
+        ax.grid(True)
+        ax.legend()
+
+        (DataCursor(ax.lines))
+        plt.grid(True)
+    plt.show()
+
+
+
+def disp_pic(bitmap):
+    """Display the allocation bitmap. TODO."""
+    fig=plt.figure()
+    a=fig.add_subplot(1,1,1)
+    fig.clf()
+    implt=plt.imshow(bitmap, extent=(0, len(bitmap[0]), 0, len(bitmap)),
+            interpolation="nearest", cmap=cmap.gist_heat)
+    fig.canvas.draw()
+    plt.show()
diff --git a/scripts/tracing/dma-api/smmu.py b/scripts/tracing/dma-api/smmu.py
new file mode 100644 (file)
index 0000000..0fbadff
--- /dev/null
@@ -0,0 +1,202 @@
+"""Low-level memory management tracking"""
+
+VERBOSITY = 0 # TODO: use logging
+
+class Bitmap(object):
+    """Just a raw bitmap for reserving the pages"""
+    def __init__(self, size, verbosity):
+        self._size = size
+        self._bits = 0
+        self._verbosity = verbosity
+        self._bits_allocd = 0
+
+    def _mask(self, offset, length):
+        """length amount of 1's, shifted left by offset"""
+        assert offset >= 0, "offset < 0"
+        assert offset < self._size, "offset 0x%d >= size %s" % (offset, self._size)
+        bitstring = (1 << length) - 1
+        return bitstring << offset
+
+    def _indices(self, offset, length):
+        """Bit numbers starting from offset"""
+        return range(offset, offset + length)
+
+    def alloc(self, offset, length):
+        """Reserve the bits, warn if verbose and some set already"""
+        mask = self._mask(offset, length)
+        if (self._bits & mask) != 0 and self._verbosity >= 1:
+            print "warning: duplicate allocation"
+        self._bits |= mask
+        self._bits_allocd += length
+        return self._indices(offset, length)
+
+    def free(self, offset, length):
+        """Free the bits, warn if verbose and some not set yet"""
+        mask = self._mask(offset, length)
+        if (self._bits & mask) != mask and self._verbosity >= 1:
+            print "warning: freeing freed memory, mapbits %x" % (self._bits & mask)
+        self._bits &= ~mask
+        self._bits_allocd -= length
+        return self._indices(offset, length)
+
+    def contains(self, offset, length):
+        """Are some bits in the given range set?"""
+        mask = self._mask(offset, length)
+        return self._bits & mask
+
+    def __repr__(self):
+        return "<bitmap, %d/%d allocd>" % (self._bits_allocd, self._size)
+
+class Memory(object):
+    """Store and handle raw bitmaps, check mapping collisions between devices"""
+    PAGESHIFT = 12
+    PAGESIZE = 1 << PAGESHIFT
+    PAGEMASK = PAGESIZE - 1
+
+    def __init__(self, addr, size, asid):
+        """addr: lowest possible address, size: bytes, asid: arbitrary id"""
+        assert (addr & self.PAGEMASK) == 0, addr
+        assert (size & self.PAGEMASK) == 0, size
+
+        self._addr = addr
+        self._size = size
+        self._end = addr + size
+        self._asid = asid
+        self._bitmap = Bitmap(size >> self.PAGESHIFT, VERBOSITY)
+        self._devmaps = {}
+
+        if VERBOSITY >= 1:
+            print "memory at %08x-%08x" % (addr, addr + size)
+
+    def to_bit(self, addr):
+        """Address to bitmap position"""
+        return addr >> self.PAGESHIFT
+
+    def alloc(self, dev, addr, size):
+        """Allocate (map) for the given device, verify things"""
+        if addr >= self._end:
+            if VERBOSITY >= 1:
+                print "warning: %s mapping beyond bitmap: %08x" % (dev, addr)
+            return []
+
+        if (addr & self.PAGEMASK) != 0:
+            if VERBOSITY >= 1:
+                print "warning: alloc not aligned at 0x%x, size %d (new addr 0x%x, size %d)" % (
+                        addr, size, addr & ~self.PAGEMASK, size + (addr & self.PAGEMASK))
+            addr &= ~self.PAGEMASK
+            size += addr & self.PAGEMASK
+
+        if size < self.PAGESIZE:
+            size = self.PAGESIZE
+
+        for user, bmp in self._devmaps.iteritems():
+            if bmp.contains(self.to_bit(addr - self._addr), self.to_bit(size)):
+                if VERBOSITY >= 1:
+                    print "warning: %s mapping [0x%x,0x%x) already used by %s" % (
+                            dev, addr, addr + size, user)
+
+        devmap = self._devmaps.setdefault(dev, Bitmap(self._bitmap._size, 0))
+
+        self._alloc(devmap, addr, size)
+        bits = self._alloc(self._bitmap, addr, size)
+        return bits
+
+    def _alloc(self, bitmap, addr, size):
+        """Allocate from an internal bitmap"""
+        return bitmap.alloc(self.to_bit(addr - self._addr), self.to_bit(size))
+
+    def free(self, dev, addr, size):
+        """Free (unmap) for the given device, verify things"""
+        if (addr & self.PAGEMASK) != 0:
+            if VERBOSITY >= 1:
+                print "warning: free not aligned at 0x%x, size %d (new addr 0x%x, size %d)" % (
+                        addr, size, addr & ~self.PAGEMASK, size + (addr & self.PAGEMASK))
+            addr &= ~self.PAGEMASK
+            size += addr & self.PAGEMASK
+
+        if size < self.PAGESIZE:
+            size = self.PAGESIZE
+
+        devmap = self._devmaps.setdefault(dev, Bitmap(self._bitmap._size, 0))
+
+        owners = []
+        for user, bmp in self._devmaps.iteritems():
+            if bmp.contains(self.to_bit(addr - self._addr), self.to_bit(size)):
+                owners.append((user, bmp))
+
+        if len(owners) == 0:
+            if VERBOSITY >= 1:
+                print "warning: %s freeing 0x%x that nobody owns" % (dev, addr)
+        elif len(owners) == 1:
+            if owners[0][0] != dev and VERBOSITY >= 2:
+                print "note: %s freeing 0x%x allocd by %s" % (
+                        dev, addr, owners[0][0])
+            devmap = owners[0][1]
+
+        self._free(devmap, addr, size)
+        bits = self._free(self._bitmap, addr, size)
+        return bits
+
+    def _free(self, bitmap, addr, size):
+        """Free from an internal bitmap"""
+        return bitmap.free(self.to_bit(addr - self._addr), self.to_bit(size))
+
+
+class Device(object):
+    """Keep track of allocations per device/process
+
+    This needs more tricky work for tracking inter-process maps/unmaps :(
+    """
+
+    def __init__(self, name, mem):
+        self._name = name
+        self._mem = mem
+        self._max_alloc = 0
+        self._cur_alloc = 0
+        self._alloc_history = []
+        self._addresses = []
+
+    def alloc(self, addr, size):
+        pages = self._mem.alloc(self, addr, size)
+        if pages is not False:
+            self._cur_alloc += size
+            self._max_alloc = max(self._max_alloc, self._cur_alloc)
+            self._alloc_history.append(self._cur_alloc)
+            if addr in self._addresses:
+                if VERBOSITY >= 1:
+                    print "warning: %s allocing dupe address %x %s" % (self._name, addr, len([x for x in self._addresses if x == addr]))
+            self._addresses.append(addr)
+        return pages
+
+    def free(self, addr, size):
+        pages = self._mem.free(self, addr, size)
+        self._cur_alloc -= size
+        if addr in self._addresses:
+            self._addresses.remove(addr)
+        else:
+            if VERBOSITY >= 1:
+                print "warning: %s freeing unallocated %x" % (self._name, addr)
+        return pages
+
+    def history_at(self, i):
+        return self._alloc_history[i]
+
+    @property
+    def name(self):
+        return self._name
+
+    def __str__(self):
+        return self.name
+
+    def __repr__(self):
+        return "<dev: %s>" % self.name
+
+    @property
+    def max_allocated(self):
+        return self._max_alloc
+
+
+class AsidSpace(object):
+    # TODO: don't pre-grep by archdata but put devices' mem maps here.
+    pass
+
diff --git a/scripts/tracing/dma-api/trace.py b/scripts/tracing/dma-api/trace.py
new file mode 100644 (file)
index 0000000..c4507b6
--- /dev/null
@@ -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()