content: """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()
  