#!/usr/bin/env python3

import getopt
from gobject import *
import gtk
from tracecmd import *
import time

app = None
data_func_cnt = 0

# In a "real" app these width should be determined at runtime testing max length
# strings in the current font.
TS_COL_W    = 150
CPU_COL_W   = 35
EVENT_COL_W = 150
PID_COL_W   = 75
COMM_COL_W  = 250


def timing(func):
  def wrapper(*arg):
      start = time.time()
      ret = func(*arg)
      end = time.time()
      print('@%s took %0.3f s' % (func.func_name, (end-start)))
      return ret
  return wrapper


class EventStore(gtk.GenericTreeModel):
    class EventRef(object):
        '''Inner class to build the trace event index'''
        def __init__(self, index, timestamp, offset, cpu):
            self.index = index
            self.offset = offset
            self.ts = timestamp
            self.cpu = cpu

        def __cmp__(self, other):
            if self.ts < other.ts:
                return -1
            if self.ts > other.ts:
                return 1
            if self.offset < other.offset:
                return -1
            if self.offset > other.offset:
                return 1
            return 0

    # The store only returns the record offset into the trace
    # The view is responsible for looking up the Event with the offset
    column_types = (long,)

    @timing
    def __init__(self, trace):
        gtk.GenericTreeModel.__init__(self)
        self.trace = trace
        self.refs = []
        self._load_trace()
        self._sort()
        self._reindex()

    @timing
    def _load_trace(self):
        print("Building trace index...")
        index = 0
        for cpu in range(0, trace.cpus):
            rec = tracecmd_read_data(self.trace._handle, cpu)
            while rec:
                offset = tep_record_offset_get(rec)
                ts = tep_record_ts_get(rec)
                self.refs.append(self.EventRef(index, ts, offset, cpu))
                index = index + 1
                rec = tracecmd_read_data(self.trace._handle, cpu)
        print("Loaded %d events from trace" % (index))

    @timing
    def _sort(self):
        self.refs.sort()

    @timing
    def _reindex(self):
        for i in range(0, len(self.refs)):
            self.refs[i].index = i

    def on_get_flags(self):
        return gtk.TREE_MODEL_LIST_ONLY | gtk.TREE_MODEL_ITERS_PERSIST

    def on_get_n_columns(self):
        return len(self.column_types)

    def on_get_column_type(self, col):
        return self.column_types[col]

    def on_get_iter(self, path):
        return self.refs[path[0]]

    def on_get_path(self, ref):
        return ref.index

    def on_get_value(self, ref, col):
        '''
        The Event record was getting deleted when passed back via this
        method, now it just returns the ref itself. Use get_event() instead.
        '''
        if col == 0:
            #return self.trace.read_event_at(ref.offset)
            return ref
        return None

    def on_iter_next(self, ref):
        try:
            return self.refs[ref.index+1]
        except IndexError:
            return None

    def on_iter_children(self, ref):
        if ref:
            return None
        return self.refs[0]

    def on_iter_has_child(self, ref):
        return False

    def on_iter_n_children(self, ref):
        if ref:
            return 0
        return len(self.refs)

    def on_iter_nth_child(self, ref, n):
        if ref:
            return None
        try:
            return self.refs[n]
        except IndexError:
            return None

    def on_iter_parent(self, child):
        return None

    def get_event(self, iter):
        '''This allocates a record which must be freed by the caller'''
        try:
            ref = self.refs[self.get_path(iter)[0]]
            ev = self.trace.read_event_at(ref.offset)
            return ev
        except IndexError:
            return None


class EventView(gtk.TreeView):
    def __init__(self, model):
        gtk.TreeView.__init__(self, model)
        self.set_fixed_height_mode(True)

        ts_col = gtk.TreeViewColumn("Time (s)")
        ts_col.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
        ts_col.set_fixed_width(TS_COL_W)
        ts_cell = gtk.CellRendererText()
        ts_col.pack_start(ts_cell, False)
        ts_col.set_cell_data_func(ts_cell, self.data_func, "ts")
        self.append_column(ts_col)

        cpu_col = gtk.TreeViewColumn("CPU")
        cpu_col.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
        cpu_col.set_fixed_width(CPU_COL_W)
        cpu_cell = gtk.CellRendererText()
        cpu_col.pack_start(cpu_cell, False)
        cpu_col.set_cell_data_func(cpu_cell, self.data_func, "cpu")
        self.append_column(cpu_col)

        event_col = gtk.TreeViewColumn("Event")
        event_col.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
        event_col.set_fixed_width(EVENT_COL_W)
        event_cell = gtk.CellRendererText()
        event_col.pack_start(event_cell, False)
        event_col.set_cell_data_func(event_cell, self.data_func, "event")
        self.append_column(event_col)

        pid_col = gtk.TreeViewColumn("PID")
        pid_col.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
        pid_col.set_fixed_width(PID_COL_W)
        pid_cell = gtk.CellRendererText()
        pid_col.pack_start(pid_cell, False)
        pid_col.set_cell_data_func(pid_cell, self.data_func, "pid")
        self.append_column(pid_col)

        comm_col = gtk.TreeViewColumn("Comm")
        comm_col.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
        comm_col.set_fixed_width(COMM_COL_W)
        comm_cell = gtk.CellRendererText()
        comm_col.pack_start(comm_cell, False)
        comm_col.set_cell_data_func(comm_cell, self.data_func, "comm")
        self.append_column(comm_col)

    def data_func(self, col, cell, model, iter, data):
        global app, data_func_cnt

        ev = model.get_event(iter)
        #ev = model.get_value(iter, 0)
        if not ev:
            return False

        if data == "ts":
            cell.set_property("markup", "%d.%09d" % (ev.ts/1000000000,
                                                     ev.ts%1000000000))
            data_func_cnt = data_func_cnt + 1
            if app:
                app.inc_data_func()
        elif data == "cpu":
            cell.set_property("markup", ev.cpu)
        elif data == "event":
            cell.set_property("markup", ev.name)
        elif data == "pid":
            cell.set_property("markup", ev.pid)
        elif data == "comm":
            cell.set_property("markup", ev.comm)
        else:
            print("Unknown Column:", data)
            return False

        return True


class EventViewerApp(gtk.Window):
    def __init__(self, trace):
        gtk.Window.__init__(self)

        self.set_size_request(650, 400)
        self.set_position(gtk.WIN_POS_CENTER)

        self.connect("destroy", gtk.main_quit)
        self.set_title("Event Viewer")

        store = EventStore(trace)
        view = EventView(store)

        sw = gtk.ScrolledWindow()
        sw.set_policy(gtk.POLICY_NEVER, gtk.POLICY_ALWAYS)
        sw.add(view)

        # track how often the treeview data_func is called
        self.data_func_label = gtk.Label("0")
        hbox = gtk.HBox()
        hbox.pack_start(gtk.Label("TS Data Func Calls:"), False, False)
        hbox.pack_start(self.data_func_label, False, False)

        vbox = gtk.VBox()
        vbox.pack_start(hbox, False)
        vbox.pack_end(sw)

        self.add(vbox)
        self.show_all()

    def inc_data_func(self):
        global data_func_cnt
        self.data_func_label.set_text(str(data_func_cnt))


if __name__ == "__main__":
    if len(sys.argv) >=2:
        filename = sys.argv[1]
    else:
        filename = "trace.dat"

    print("Initializing trace...")
    trace = Trace(filename)
    print("Initializing app...")
    app = EventViewerApp(trace)
    print("Go!")
    gtk.main()
