#!/usr/bin/env python
#
# Copyright 2012-2016 Free Software Foundation, Inc.
#
# This file is part of GNU Radio
#
# GNU Radio is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3, or (at your option)
# any later version.
#
# GNU Radio is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with GNU Radio; see the file COPYING.  If not, write to
# the Free Software Foundation, Inc., 51 Franklin Street,
# Boston, MA 02110-1301, USA.
#

from __future__ import print_function

from argparse import ArgumentParser

import sys, time, re, signal
import random,math,operator

from gnuradio.ctrlport.GNURadioControlPortClient import GNURadioControlPortClient

try:
    import networkx as nx
    import matplotlib
    matplotlib.use("QT5Agg")
    import matplotlib.pyplot as plt
    from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas

    try:
        from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
    except ImportError as e:
        print(e)
        print(sys.argv[0], "could not load QTAgg backend.")
        sys.exit(1)


    from matplotlib.figure import Figure
except ImportError:
    print(sys.argv[0], "requires networkx and matplotlib.",
       "Please check that they are installed and try again.")
    sys.exit(1)

from PyQt5 import QtCore, Qt
import itertools

from gnuradio import gr, ctrlport
from gnuradio.ctrlport.GrDataPlotter import *

from networkx.drawing.nx_agraph import graphviz_layout

class MAINWindow(Qt.QMainWindow):
    def minimumSizeHint(self):
        return Qt.QSize(800,600)

    def __init__(self, radioclient):

        super(MAINWindow, self).__init__()
        self.plots = []
        self.knobprops = []

        self.mdiArea = Qt.QMdiArea()
        self.mdiArea.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
        self.mdiArea.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
        self.setCentralWidget(self.mdiArea)

        self.mdiArea.subWindowActivated.connect(self.updateMenus)
        self.windowMapper = QtCore.QSignalMapper(self)
        self.windowMapper.mapped[Qt.QWidget].connect(self.setActiveSubWindow)

        self.createActions()
        self.createMenus()
        self.createToolBars()
        self.createStatusBar()
        self.updateMenus()

        self.setWindowTitle("GNU Radio Performance Monitor")
        self.setUnifiedTitleAndToolBarOnMac(True)

        self.newCon(radioclient)

        icon = Qt.QIcon(ctrlport.__path__[0] + "/icon.png" )
        self.setWindowIcon(icon)

    def newSubWindow(self, window, title):
        child = window
        child.setWindowTitle(title)
        self.mdiArea.addSubWindow(child)
        child.show()
        self.mdiArea.currentSubWindow().showMaximized()

    def newCon(self, radioclient=None):
        child = MForm(radioclient, self)
        if(child.radioclient is not None):
            child.setWindowTitle(str(child.radio))
            self.mdiArea.addSubWindow(child)
            child.show()
            self.mdiArea.currentSubWindow().showMaximized()

    def setActiveSubWindow(self, window):
        if window:
            self.mdiArea.setActiveSubWindow(window)


    def createActions(self):
        self.newConAct = Qt.QAction("&New Connection",
                self, shortcut=Qt.QKeySequence.New,
                statusTip="Create a new file", triggered=lambda x: self.newCon(None))

        self.exitAct = Qt.QAction("E&xit", self, shortcut="Ctrl+Q",
                statusTip="Exit the application",
                triggered=Qt.qApp.closeAllWindows)

        self.closeAct = Qt.QAction("Cl&ose", self, shortcut="Ctrl+F4",
                statusTip="Close the active window",
                triggered=self.mdiArea.closeActiveSubWindow)

        self.closeAllAct = Qt.QAction("Close &All", self,
                statusTip="Close all the windows",
                triggered=self.mdiArea.closeAllSubWindows)

        qks = Qt.QKeySequence(QtCore.Qt.CTRL + QtCore.Qt.Key_T)
        self.tileAct = Qt.QAction("&Tile", self,
                statusTip="Tile the windows",
                triggered=self.mdiArea.tileSubWindows,
                shortcut=qks)

        qks = Qt.QKeySequence(QtCore.Qt.CTRL + QtCore.Qt.Key_C)
        self.cascadeAct = Qt.QAction("&Cascade", self,
                statusTip="Cascade the windows", shortcut=qks,
                triggered=self.mdiArea.cascadeSubWindows)

        self.nextAct = Qt.QAction("Ne&xt", self,
                shortcut=Qt.QKeySequence.NextChild,
                statusTip="Move the focus to the next window",
                triggered=self.mdiArea.activateNextSubWindow)

        self.previousAct = Qt.QAction("Pre&vious", self,
                shortcut=Qt.QKeySequence.PreviousChild,
                statusTip="Move the focus to the previous window",
                triggered=self.mdiArea.activatePreviousSubWindow)

        self.separatorAct = Qt.QAction(self)
        self.separatorAct.setSeparator(True)

        self.aboutAct = Qt.QAction("&About", self,
                statusTip="Show the application's About box",
                triggered=self.about)

        self.aboutQtAct = Qt.QAction("About &Qt", self,
                statusTip="Show the Qt library's About box",
                triggered=Qt.qApp.aboutQt)

    def createMenus(self):
        self.fileMenu = self.menuBar().addMenu("&File")
        self.fileMenu.addAction(self.newConAct)
        self.fileMenu.addSeparator()
        self.fileMenu.addAction(self.exitAct)

        self.windowMenu = self.menuBar().addMenu("&Window")
        self.updateWindowMenu()
        self.windowMenu.aboutToShow.connect(self.updateWindowMenu)

        self.menuBar().addSeparator()

        self.helpMenu = self.menuBar().addMenu("&Help")
        self.helpMenu.addAction(self.aboutAct)
        self.helpMenu.addAction(self.aboutQtAct)

    def createToolBars(self):
        self.fileToolBar = self.addToolBar("File")
        self.fileToolBar.addAction(self.newConAct)

        self.fileToolBar = self.addToolBar("Window")
        self.fileToolBar.addAction(self.tileAct)
        self.fileToolBar.addAction(self.cascadeAct)

    def createStatusBar(self):
        self.statusBar().showMessage("Ready")


    def activeMdiChild(self):
        activeSubWindow = self.mdiArea.activeSubWindow()
        if activeSubWindow:
            return activeSubWindow.widget()
        return None

    def updateMenus(self):
        hasMdiChild = (self.activeMdiChild() is not None)
        self.closeAct.setEnabled(hasMdiChild)
        self.closeAllAct.setEnabled(hasMdiChild)
        self.tileAct.setEnabled(hasMdiChild)
        self.cascadeAct.setEnabled(hasMdiChild)
        self.nextAct.setEnabled(hasMdiChild)
        self.previousAct.setEnabled(hasMdiChild)
        self.separatorAct.setVisible(hasMdiChild)

    def updateWindowMenu(self):
        self.windowMenu.clear()
        self.windowMenu.addAction(self.closeAct)
        self.windowMenu.addAction(self.closeAllAct)
        self.windowMenu.addSeparator()
        self.windowMenu.addAction(self.tileAct)
        self.windowMenu.addAction(self.cascadeAct)
        self.windowMenu.addSeparator()
        self.windowMenu.addAction(self.nextAct)
        self.windowMenu.addAction(self.previousAct)
        self.windowMenu.addAction(self.separatorAct)

    def about(self):
        about_info = \
'''Copyright 2012 Free Software Foundation, Inc.\n
This program is part of GNU Radio.\n
GNU Radio is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version.\n
GNU Radio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.\n
You should have received a copy of the GNU General Public License along with GNU Radio; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Boston, MA 02110-1301, USA.'''

        Qt.QMessageBox.about(None, "gr-perf-monitorx", about_info)


class ConInfoDialog(Qt.QDialog):
    def __init__(self, parent=None):
        super(ConInfoDialog, self).__init__(parent)

        self.gridLayout = Qt.QGridLayout(self)


        self.host = Qt.QLineEdit(self)
        self.port = Qt.QLineEdit(self)
        self.host.setText("localhost")
        self.port.setText("43243")

        self.buttonBox = Qt.QDialogButtonBox(Qt.QDialogButtonBox.Ok |
                                                Qt.QDialogButtonBox.Cancel)

        self.gridLayout.addWidget(self.host)
        self.gridLayout.addWidget(self.port)
        self.gridLayout.addWidget(self.buttonBox)

        self.buttonBox.accepted.connect(self.accept)
        self.buttonBox.rejected.connect(self.reject)


    def accept(self):
        self.done(1)

    def reject(self):
        self.done(0)


class DataTable(Qt.QWidget):
    def update(self):
        print("update")

    def closeEvent(self, event):
        self.timer = None

    def __init__(self, radioclient, G):
        Qt.QWidget.__init__(self)

        self.layout = Qt.QVBoxLayout(self)
        self.hlayout = Qt.QHBoxLayout()
        self.layout.addLayout(self.hlayout)

        self.G = G
        self.radioclient = radioclient

        self._keymap = None

        self.disp = None

        # Create a combobox to set the type of statistic we want.
        self._statistic = "Instantaneous"
        self._statistics_table = {"Instantaneous": "",
                                  "Average": "avg ",
                                  "Variance": "var "}
        self.stattype = Qt.QComboBox()
        self.stattype.addItem("Instantaneous")
        self.stattype.addItem("Average")
        self.stattype.addItem("Variance")
        self.stattype.setMaximumWidth(200)
        self.hlayout.addWidget(self.stattype)
        self.stattype.currentIndexChanged.connect(self.stat_changed)

        # Create a checkbox to toggle sorting of graphs
        self._sort = False
        self.checksort = Qt.QCheckBox("Sort")
        self.checksort.setCheckState(self._sort)
        self.hlayout.addWidget(self.checksort)
        self.checksort.stateChanged.connect(self.checksort_changed)

        # set up table
        self.perfTable = Qt.QTableWidget()
        self.perfTable.setColumnCount(2)
        self.perfTable.verticalHeader().hide()
        self.perfTable.setHorizontalHeaderLabels( ["Block Name", "Percent Runtime"] )
        self.perfTable.horizontalHeader().setStretchLastSection(True)
        self.perfTable.setSortingEnabled(True)
        nodes = self.G.nodes(data=True)

        # set up plot
        self.f = plt.figure(figsize=(10,8), dpi=90)
        self.sp = self.f.add_subplot(111)
        self.sp.autoscale_view(True,True,True)
        self.sp.set_autoscale_on(True)

        # set up tabs
        self.tabber = Qt.QTabWidget()
        self.layout.addWidget(self.tabber)
        self.tabber.addTab(self.perfTable,"Table View")
        self.tabber.addTab(self.f.canvas, "Graph View")

        # set up timer
        self.timer = QtCore.QTimer()
        self.timer.timeout.connect(self.update)
        self.timer.start(500)

        for i, node in enumerate(nodes):
            self.perfTable.setItem(i, 0,
                Qt.QTableWidgetItem(node[0]))

    def table_update(self,data):
        for k in data.keys():
            weight = data[k]
            existing = self.perfTable.findItems(str(k),QtCore.Qt.MatchFixedString)
            if(len(existing) == 0):
                i = self.perfTable.rowCount()
                self.perfTable.setRowCount( i+1)
                self.perfTable.setItem( i,0, Qt.QTableWidgetItem(str(k)))
                self.perfTable.setItem( i,1, Qt.QTableWidgetItem(str(weight)))
            else:
                self.perfTable.setItem( self.perfTable.row(existing[0]),1, Qt.QTableWidgetItem(str(weight)))

    def stat_changed(self, index):
        self._statistic = str(self.stattype.currentText())

    def checksort_changed(self, state):
        self._sort = state > 0

class DataTableBuffers(DataTable):
    def __init__(self, radioclient, G):
        super(DataTableBuffers, self).__init__(radioclient, G)
        self.perfTable.setHorizontalHeaderLabels( ["Block Name", "Percent Buffer Full"] )

    def update(self):
        nodes = self.G.nodes()

        # get buffer fullness for all blocks
        kl = list(map(lambda x: "%s::%soutput %% full" % \
                 (x, self._statistics_table[self._statistic]),
                 nodes))
        try:
            buf_knobs = self.radioclient.getKnobs(kl)
        except Exception as e:
            sys.stderr.write("gr-perf-monitorx: lost connection ({0}).\n".format(e))
            self.parentWidget().mdiArea().removeSubWindow(self.parentWidget())
            self.close()
            return

        # strip values out of ctrlport response
        buffer_fullness = dict(zip(
            map(lambda x: x.split("::")[0], buf_knobs.keys()),
            map(lambda x: x.value, buf_knobs.values())))

        blockport_fullness = {}
        for blk in buffer_fullness:
            bdata = buffer_fullness[blk]
            if bdata:
                for port in range(0,len(bdata)):
                    blockport_fullness["%s:%d"%(blk,port)] =  bdata[port]

        if(self.perfTable.isVisible()):
            self.table_update(blockport_fullness)

        else:
            if(self._sort):
                sorted_fullness = sorted(blockport_fullness.items(),
                                         key=operator.itemgetter(1))
                self._keymap = list(map(operator.itemgetter(0), sorted_fullness))
            else:
                if self._keymap:
                    sorted_fullness = len(self._keymap)*['',]
                    for b in blockport_fullness:
                        sorted_fullness[self._keymap.index(b)] = (b, blockport_fullness[b])
                else:
                    sorted_fullness = blockport_fullness.items()

            if(not self.disp):
                self.disp = self.sp.bar(range(0,len(sorted_fullness)),
                                        list(map(lambda x: x[1], sorted_fullness)),
                                        alpha=0.5, align='edge')
                self.sp.set_ylabel("% Buffers Full")
                self.sp.set_xticks(list(map(lambda x: x+0.5, range(0,len(sorted_fullness)))))
                self.sp.set_xticklabels(list(map(lambda x: "  " + x, map(lambda x: x[0], sorted_fullness))),
                                        rotation="vertical", verticalalignment="bottom")
            else:
                self.sp.set_xticklabels(list(map(lambda x: "  " + x, map(lambda x: x[0], sorted_fullness))),
                                        rotation="vertical", verticalalignment="bottom")
                for r,w in zip(self.disp, sorted_fullness):
                    r.set_height(w[1])

            self.f.canvas.draw()

class DataTableRuntimes(DataTable):
    def __init__(self, radioclient, G):
        super(DataTableRuntimes, self).__init__( radioclient, G)

    def update(self):
        nodes = self.G.nodes()

        # get work time for all blocks
        kl = list(map(lambda x: "%s::%swork time" % \
                 (x, self._statistics_table[self._statistic]),
                 nodes))

        try:
            wrk_knobs = self.radioclient.getKnobs(kl)

        except Exception as e:
            sys.stderr.write("gr-perf-monitorx: lost connection ({0}).\n".format(e))
            self.parentWidget().mdiArea().removeSubWindow(self.parentWidget())
            self.close()
            return

        # strip values out of ctrlport response
        total_work = sum(map(lambda x: x.value, wrk_knobs.values()))
        if(total_work == 0):
            total_work = 1
        work_times = dict(zip(
            map(lambda x: x.split("::")[0], wrk_knobs.keys()),
            map(lambda x: 1e-10 + x.value/total_work, wrk_knobs.values())))

        # update table view
        if(self.perfTable.isVisible()):
            self.table_update(work_times)

        else:
            if(self._sort):
                sorted_work = sorted(work_times.items(), key=operator.itemgetter(1))
                self._keymap = list(map(operator.itemgetter(0), sorted_work))
            else:
                if self._keymap:
                    sorted_work = len(list(self._keymap))*['',]
                    for b in work_times:
                        sorted_work[self._keymap.index(b)] = (b, work_times[b])
                else:
                    sorted_work = work_times.items()

            if(not self.disp):
                self.disp = self.sp.bar(range(0,len(sorted_work)),
                                        list(map(lambda x: x[1], sorted_work)),
                                        alpha=0.5, align='edge')
                self.sp.set_ylabel("% Runtime")
                self.sp.set_xticks(list(map(lambda x: x+0.5, range(0,len(sorted_work)))))
                self.sp.set_xticklabels(list(map(lambda x: "  " + x[0], sorted_work)),
                                         rotation="vertical", verticalalignment="bottom" )
            else:
                self.sp.set_xticklabels(list(map(lambda x: "  " + x[0], sorted_work)),
                                         rotation="vertical", verticalalignment="bottom" )
                for r,w in zip(self.disp, sorted_work):
                    r.set_height(w[1])

            self.f.canvas.draw()

class MForm(Qt.QWidget):
    def update(self):
        try:
            try:
                # update current clock type
                self.prevent_clock_change = True
                kl1 = None
                if(self.clockKey == None):
                    kl1 = self.radioclient.getRe([".*perfcounter_clock"])
                else:
                    kl1 = self.radioclient.getKnobs([self.clockKey])
                self.clockKey = list(kl1.keys())[0]
                self.currClock = kl1[self.clockKey].value
                self.clockSelIdx = list(self.clocks.values()).index(self.currClock)
                self.clockSel.setCurrentIndex(self.clockSelIdx)
                self.prevent_clock_change = False
            except Exception as e:
                print(e)
                print("WARNING: Failed to get current clock setting!")

            nodes_stream = self.G_stream.nodes()
            nodes_msg = self.G_msg.nodes()

            # get current buffer depths of all output buffers
            kl = list(map(lambda x: "%s::%soutput %% full" % \
                     (x, self._statistics_table[self._statistic]),
                     nodes_stream))

            st = time.time()
            buf_knobs = self.radioclient.getKnobs(kl)
            td1 = time.time() - st

            # strip values out of ctrlport response
            buf_vals = dict(zip(
                map(lambda x: x.split("::")[0], buf_knobs.keys()),
                map(lambda x: x.value, buf_knobs.values())))

            # get work time for all blocks
            kl = list(map(lambda x: "%s::%swork time" % \
                         (x, self._statistics_table[self._statistic]),
                     nodes_stream))
            st = time.time()
            wrk_knobs = self.radioclient.getKnobs(kl)
            td2 = time.time() - st

            # strip values out of ctrlport response
            total_work = sum(map(lambda x: x.value, wrk_knobs.values()))
            if(total_work == 0):
                total_work = 1
            work_times = dict(zip(
                        map(lambda x: x.split("::")[0], wrk_knobs.keys()),
                        map(lambda x: x.value/total_work, wrk_knobs.values())))
            work_times_padded = dict(zip(
                        self.G.nodes(),
                        [0.1]*len(self.G.nodes())))
            work_times_padded.update(work_times)

            for n in nodes_stream:
                # ne is the list of edges away from this node!
                ne = self.G.edges([n],True)
                #for e in ne: # iterate over edges from this block
                for e in ne: # iterate over edges from this block
                    # get the right output buffer/port weight for each edge
                    sourceport = e[2]["sourceport"]
                    if(e[2]["type"] == "stream"):
                        newweight = buf_vals[n][sourceport]
                        e[2]["weight"] = newweight

            for n in nodes_msg:
                ne = self.G.edges([n],True)
                for e in ne: # iterate over edges from this block
                    sourceport = e[2]["sourceport"]
                    if(e[2]["type"] == "msg"):
                        newweight = 0.01
                        e[2]["weight"] = newweight

            # set updated weights
            #self.node_weights = map(lambda x: 20+2000*work_times[x], nodes_stream)
            self.node_weights = list(map(lambda x: 20+2000*work_times_padded[x], self.G.nodes()))
            self.edge_weights = list(map(lambda x: 100.0*x[2]["weight"], self.G.edges(data=True)))

            # draw graph updates
            if(self.do_update):
                self.drawGraph()
            else:
                self.updateGraph()

            latency = td1 + td2
            self.parent.statusBar().showMessage("Current GNU Radio Control Port Query Latency: %f ms"%\
                                                    (latency*1000))

        except Exception as e:
            sys.stderr.write("gr-perf-monitorx: lost connection ({0}).\n".format(e))
            if(type(self.parent) is MAINWindow):
                # Find window of connection
                for p in self.parent.mdiArea.subWindowList():
                    self.parent.mdiArea.removeSubWindow(p)
                    break

                self.close()
            else:
                sys.exit(1)
            return

    def rtt(self):
        self.parent.newSubWindow(DataTableRuntimes(self.radioclient, self.G_stream), "Runtime Table")

    def bpt(self):
        self.parent.newSubWindow(DataTableBuffers(self.radioclient, self.G_stream), "Buffers Table")

    def resetPCs(self):
        knobs = []
        for b in self.blocks_list:
            knobs += [self.radioclient.Knob(b + "::reset_perf_counters"),]
        k = self.radioclient.setKnobs(knobs)

    def toggleFlowgraph(self):
        if self.pauseFGAct.isChecked():
            self.pauseFlowgraph()
        else:
            self.unpauseFlowgraph()

    def pauseFlowgraph(self):
        knobs = [self.radioclient.Knob(self.top_block + "::lock"),
                 self.radioclient.Knob(self.top_block + "::stop")]
        k = self.radioclient.setKnobs(knobs)

    def unpauseFlowgraph(self):
        knobs = [self.radioclient.Knob(self.top_block + "::unlock")]
        k = self.radioclient.setKnobs(knobs)

    def stat_changed(self, index):
        self._statistic = str(self.stattype.currentText())

    def update_clock(self, clkidx):
        if(self.prevent_clock_change):
            return
        idx = self.clockSel.currentIndex()
        clk = list(self.clocks.values())[idx]
        # print("UPDATE CLOCK!!! %d -> %d"%(idx,clk);)
        k = self.radioclient.getKnobs([self.clockKey])
        k[self.clockKey].value = clk
        km = {}
        km[self.clockKey] = k[self.clockKey]
        self.radioclient.setKnobs(km)

    def __init__(self, radioclient=None, parent=None):

        super(MForm, self).__init__()

        if radioclient is None:
            askinfo = ConInfoDialog(self)
            self.radioclient = None
            if askinfo.exec_():
                host = str(askinfo.host.text())
                port = str(askinfo.port.text())
                try:
                    self.radioclient = GNURadioControlPortClient(host, port, 'thrift').client
                    print("Connected to %s:%s" % (host, port))
                except:
                    print("Error connecting to %s:%s" % (host, port))
        else:
            self.radioclient = radioclient
                    

        if self.radioclient is None:
            return

        self.parent = parent

        self.layoutTop = Qt.QVBoxLayout(self)
        self.ctlBox = Qt.QHBoxLayout()
        self.layout = Qt.QHBoxLayout()

        self.layoutTop.addLayout(self.ctlBox)
        self.layoutTop.addLayout(self.layout)

        self.rttAct = Qt.QAction("Runtime Table",
                self, statusTip="Runtime Table", triggered=self.rtt)
        self.rttBut = Qt.QToolButton()
        self.rttBut.setDefaultAction(self.rttAct)
        self.ctlBox.addWidget(self.rttBut)

        self.bptAct = Qt.QAction("Buffer Table",
                self, statusTip="Buffer Table", triggered=self.bpt)
        self.bptBut = Qt.QToolButton()
        self.bptBut.setDefaultAction(self.bptAct)
        self.ctlBox.addWidget(self.bptBut)

        self.resetPCsAct = Qt.QAction("Reset", self,
                statusTip="Reset all Performance Counters",
                triggered=self.resetPCs)
        self.resetPCsBut = Qt.QToolButton()
        self.resetPCsBut.setDefaultAction(self.resetPCsAct)
        self.ctlBox.addWidget(self.resetPCsBut)

        self.pauseFGAct = Qt.QAction("Pause", self,
                statusTip="Pause the Flowgraph",
                triggered=self.toggleFlowgraph)
        self.pauseFGAct.setCheckable(True)
        self.pauseFGBut = Qt.QToolButton()
        self.pauseFGBut.setDefaultAction(self.pauseFGAct)
        self.ctlBox.addWidget(self.pauseFGBut)

        self.prevent_clock_change = True
        self.clockKey = None
        self.clocks = {"MONOTONIC":1, "THREAD":3}
        self.clockSel = Qt.QComboBox(self)
        list(map(lambda x: self.clockSel.addItem(x), self.clocks.keys()))
        self.ctlBox.addWidget(self.clockSel)
        self.clockSel.currentIndexChanged.connect(self.update_clock)
        self.prevent_clock_change = False

        self._statistic = "Instantaneous"
        self._statistics_table = {"Instantaneous": "",
                                  "Average": "avg ",
                                  "Variance": "var "}
        self.stattype = Qt.QComboBox()
        self.stattype.addItem("Instantaneous")
        self.stattype.addItem("Average")
        self.stattype.addItem("Variance")
        self.stattype.setMaximumWidth(200)
        self.ctlBox.addWidget(self.stattype)
        self.stattype.currentIndexChanged.connect(self.stat_changed)

#        self.setLayout(self.layout)

        self.radio = self.radioclient
        self.knobprops = self.radio.properties([])
        self.parent.knobprops.append(self.knobprops)

        self.timer = QtCore.QTimer()
        self.constupdatediv = 0
        self.tableupdatediv = 0
        plotsize=250


        # Set up the graph of blocks
        input_name = lambda x: x+"::avg input % full"
        output_name = lambda x: x+"::avg output % full"
        wtime_name = lambda x: x+"::avg work time"
        nout_name = lambda x: x+"::avg noutput_items"
        nprod_name = lambda x: x+"::avg nproduced"

        tmplist = []
        knobs = self.radio.getKnobs([])
        edgelist = None
        msgedgelist = None
        for k in knobs:
            propname = k.split("::")
            blockname = propname[0]
            keyname = propname[1]
            if(keyname == "edge list"):
                edgelist = knobs[k].value
                self.top_block = blockname
            elif(keyname == "msg edges list"):
                msgedgelist = knobs[k].value
            elif(blockname not in tmplist):
                # only take gr_blocks (no hier_block2)
                if(input_name(blockname) in knobs):
                    tmplist.append(blockname)


        if not edgelist:
            sys.stderr.write("Could not find list of edges from flowgraph. " + \
                                 "Make sure the option 'edges_list' is enabled " + \
                                 "in the ControlPort configuration.\n\n")
            sys.exit(1)

        self.blocks_list = tmplist
        edges = edgelist.split("\n")[0:-1]
        msgedges = msgedgelist.split("\n")[0:-1]

        edgepairs_stream = []
        edgepairs_msg = []

        # add stream connections
        for e in edges:
            _e = e.split("->")
            edgepairs_stream.append( (_e[0].split(":")[0], _e[1].split(":")[0], {"type":"stream", "sourceport":int(_e[0].split(":")[1])}) )

        # add msg connections
        for e in msgedges:
            _e = e.split("->")
            edgepairs_msg.append( (_e[0].split(":")[0], _e[1].split(":")[0], {"type":"msg", "sourceport":_e[0].split(":")[1]}) )

        self.G = nx.MultiDiGraph()
        self.G_stream = nx.MultiDiGraph()
        self.G_msg = nx.MultiDiGraph()

        self.G.add_edges_from(edgepairs_stream)
        self.G.add_edges_from(edgepairs_msg)

        self.G_stream.add_edges_from(edgepairs_stream)
        self.G_msg.add_edges_from(edgepairs_msg)

        n_edges = self.G.edges(data=True)
        for e in n_edges:
            e[2]["weight"] = 5+random.random()*10

        self.G = nx.MultiDiGraph()
        self.G.add_edges_from(n_edges)

        self.f = plt.figure(figsize=(10,8), dpi=90)
        self.sp = self.f.add_subplot(111)
        self.sp.autoscale_view(True,True,True)
        self.sp.set_autoscale_on(True)

        self.layout.addWidget(self.f.canvas)

        self.pos = graphviz_layout(self.G)
        #self.pos = pygraphviz_layout(self.G)
        #self.pos = nx.spectral_layout(self.G)
        #self.pos = nx.circular_layout(self.G)
        #self.pos = nx.shell_layout(self.G)
        #self.pos = nx.spring_layout(self.G)

        # Indicate to self.update to initialize the graph
        self.do_update = False

        # generate weights and plot
        self.update()
        # set up timer
        self.timer = QtCore.QTimer()
        self.timer.timeout.connect(self.update)
        self.timer.start(1000)

        # Set up mouse callback functions to move blocks around.
        self._grabbed = False
        self._current_block = ''
        self.f.canvas.mpl_connect('button_press_event',
                                  self.button_press)
        self.f.canvas.mpl_connect('motion_notify_event',
                                  self.mouse_move)
        self.f.canvas.mpl_connect('button_release_event',
                                  self.button_release)

    def button_press(self, event):
        x, y = event.xdata, event.ydata
        thrsh = 100

        if(x is not None and y is not None):
            nearby = list(map(lambda z: math.sqrt( math.pow(x-z[0],2) + math.pow(y-z[1],2)), self.pos.values()))
            i = nearby.index(min(nearby))
            if(abs(list(self.pos.values())[i][0] - x) < thrsh and
               abs(list(self.pos.values())[i][1]-y) < thrsh):
                self._current_block = list(self.pos.keys())[i]
                #print "MOVING BLOCK: ", self._current_block
                #print "CUR POS: ", self.pos.values()[i]
                self._grabbed = True

    def mouse_move(self, event):
        if self._grabbed:
            x, y = event.xdata, event.ydata
            if(x is not None and y is not None):
                self.pos[self._current_block] = (x,y)
                self.updateGraph()

    def button_release(self, event):
        self._grabbed = False


    def openMenu(self, pos):
        index = self.table.treeWidget.selectedIndexes()
        item = self.table.treeWidget.itemFromIndex(index[0])
        itemname = str(item.text(0))
        self.parent.propertiesMenu(itemname, self.radioclient)

    def drawGraph(self):

        self.do_update = True
        self.f.canvas.updateGeometry()
        self.sp.clear()
        plt.figure(self.f.number)
        plt.subplot(111)
        nx.draw(self.G, self.pos,
                edge_color=self.edge_weights,
                node_color='#A0CBE2',
                width=list(map(lambda x: 3+math.log(x+1e-20), self.edge_weights)),
                node_shape="s",
                node_size=self.node_weights,
                edge_cmap=plt.cm.Reds,
                ax=self.sp,
                arrows=False
        )
        nx.draw_networkx_labels(self.G, self.pos,
                                font_size=12)

        self.f.canvas.show()

    def updateGraph(self):

        self.sp.clear()
        nx.draw_networkx_nodes(self.G, self.pos,
                               node_color='#A0CBE2',
                               node_shape="s",
                               node_size=self.node_weights,
                               ax=self.sp,
                               arrows=False)

        nx.draw_networkx_edges(self.G, self.pos,
                               edge_color=self.edge_weights,
                               width=list(map(lambda x: 3+math.log(x+1e-20), self.edge_weights)),
                               edge_cmap=plt.cm.Reds,
                               ax=self.sp,
                               arrows=False)

        nx.draw_networkx_labels(self.G, self.pos,
                                ax=self.sp, font_size=12)

        self.f.canvas.draw()


class MyApp(object):
    def __init__(self, args):

        parser = ArgumentParser(description="GNU Radio Performance Monitor")
        parser.add_argument("host", nargs="?", default="localhost", help="host name or IP")
        parser.add_argument("port", help="port")
        args = parser.parse_args()

        signal.signal(signal.SIGINT, signal.SIG_DFL)
        signal.signal(signal.SIGTERM, signal.SIG_DFL)

        try:
            GNURadioControlPortClient(args.host, args.port, 'thrift', self.run, Qt.QApplication(sys.argv).exec_)
        except:
            print("ControlPort failed to connect. Check the config of your endpoint.")
            print("\t[ControlPort] on = True")
            print("\t[ControlPort] edges_list = True")
            print("\t[PerfCounters] on = True")
            print("\t[PerfCounters] export = True")
            sys.exit(1)

    def run(self, client):
        MAINWindow(client).show()

MyApp(sys.argv)