summaryrefslogtreecommitdiff
path: root/gnuradio-core
diff options
context:
space:
mode:
Diffstat (limited to 'gnuradio-core')
-rwxr-xr-xgnuradio-core/src/python/gnuradio/ctrlport/gr-perf-monitor588
1 files changed, 588 insertions, 0 deletions
diff --git a/gnuradio-core/src/python/gnuradio/ctrlport/gr-perf-monitor b/gnuradio-core/src/python/gnuradio/ctrlport/gr-perf-monitor
new file mode 100755
index 0000000000..18ccbd5c12
--- /dev/null
+++ b/gnuradio-core/src/python/gnuradio/ctrlport/gr-perf-monitor
@@ -0,0 +1,588 @@
+#!/usr/bin/env python
+#
+# Copyright 2012-2013 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 gnuradio import gr, ctrlport
+
+from PyQt4 import QtCore,Qt,Qwt5
+import PyQt4.QtGui as QtGui
+import sys, time, re, pprint
+import itertools
+import scipy
+
+import Ice
+from gnuradio.ctrlport.IceRadioClient import *
+from gnuradio.ctrlport.GrDataPlotter import *
+from gnuradio.ctrlport import GNURadio
+
+class MAINWindow(QtGui.QMainWindow):
+ def minimumSizeHint(self):
+ return QtGui.QSize(800,600)
+
+ def __init__(self, radio, port, interface):
+
+ super(MAINWindow, self).__init__()
+ self.conns = []
+ self.plots = []
+ self.knobprops = []
+ self.interface = interface
+
+ self.mdiArea = QtGui.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[QtGui.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(radio, port)
+ icon = QtGui.QIcon(ctrlport.__path__[0] + "/icon.png" )
+ self.setWindowIcon(icon)
+
+ def newCon(self, radio=None, port=None):
+ child = MForm(radio, port, len(self.conns), self)
+ if(child.radio is not None):
+ child.setWindowTitle(str(child.radio))
+ horizbar = QtGui.QScrollArea()
+ horizbar.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
+ horizbar.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
+ horizbar.setWidget(child)
+ self.mdiArea.addSubWindow(horizbar)
+ self.mdiArea.currentSubWindow().showMaximized()
+
+ self.conns.append(child)
+ self.plots.append([])
+
+ def newUpdater(self, key, radio):
+ updater = UpdaterWindow(key, radio, None)
+ updater.setWindowTitle("Updater: " + key)
+ updater.setModal(False)
+ updater.exec_()
+
+ def update(self, knobs, uid):
+ #sys.stderr.write("KNOB KEYS: {0}\n".format(knobs.keys()))
+ for plot in self.plots[uid]:
+ data = knobs[plot.name()].value
+ plot.update(data)
+ plot.stop()
+ plot.wait()
+ plot.start()
+
+ def setActiveSubWindow(self, window):
+ if window:
+ self.mdiArea.setActiveSubWindow(window)
+
+
+ def createActions(self):
+ self.newConAct = QtGui.QAction("&New Connection",
+ self, shortcut=QtGui.QKeySequence.New,
+ statusTip="Create a new file", triggered=self.newCon)
+
+ self.exitAct = QtGui.QAction("E&xit", self, shortcut="Ctrl+Q",
+ statusTip="Exit the application",
+ triggered=QtGui.qApp.closeAllWindows)
+
+ self.closeAct = QtGui.QAction("Cl&ose", self, shortcut="Ctrl+F4",
+ statusTip="Close the active window",
+ triggered=self.mdiArea.closeActiveSubWindow)
+
+ self.closeAllAct = QtGui.QAction("Close &All", self,
+ statusTip="Close all the windows",
+ triggered=self.mdiArea.closeAllSubWindows)
+
+
+ qks = QtGui.QKeySequence(QtCore.Qt.CTRL + QtCore.Qt.Key_T);
+ self.tileAct = QtGui.QAction("&Tile", self,
+ statusTip="Tile the windows",
+ triggered=self.mdiArea.tileSubWindows,
+ shortcut=qks)
+
+ qks = QtGui.QKeySequence(QtCore.Qt.CTRL + QtCore.Qt.Key_C);
+ self.cascadeAct = QtGui.QAction("&Cascade", self,
+ statusTip="Cascade the windows", shortcut=qks,
+ triggered=self.mdiArea.cascadeSubWindows)
+
+ self.nextAct = QtGui.QAction("Ne&xt", self,
+ shortcut=QtGui.QKeySequence.NextChild,
+ statusTip="Move the focus to the next window",
+ triggered=self.mdiArea.activateNextSubWindow)
+
+ self.previousAct = QtGui.QAction("Pre&vious", self,
+ shortcut=QtGui.QKeySequence.PreviousChild,
+ statusTip="Move the focus to the previous window",
+ triggered=self.mdiArea.activatePreviousSubWindow)
+
+ self.separatorAct = QtGui.QAction(self)
+ self.separatorAct.setSeparator(True)
+
+ self.aboutAct = QtGui.QAction("&About", self,
+ statusTip="Show the application's About box",
+ triggered=self.about)
+
+ self.aboutQtAct = QtGui.QAction("About &Qt", self,
+ statusTip="Show the Qt library's About box",
+ triggered=QtGui.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.'''
+
+ QtGui.QMessageBox.about(None, "gr-ctrlport-monitor", about_info)
+
+
+class ConInfoDialog(QtGui.QDialog):
+ def __init__(self, parent=None):
+ super(ConInfoDialog, self).__init__(parent)
+
+ self.gridLayout = QtGui.QGridLayout(self)
+
+
+ self.host = QtGui.QLineEdit(self);
+ self.port = QtGui.QLineEdit(self);
+ self.host.setText("localhost");
+ self.port.setText("43243");
+
+ self.buttonBox = QtGui.QDialogButtonBox(QtGui.QDialogButtonBox.Ok |
+ QtGui.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 UpdaterWindow(QtGui.QDialog):
+ def __init__(self, key, radio, parent):
+ QtGui.QDialog.__init__(self, parent)
+
+ self.key = key;
+ self.radio = radio
+
+ self.resize(300,200)
+ self.layout = QtGui.QVBoxLayout()
+
+ self.props = radio.properties([key])[key]
+ info = str(self.props)
+
+ self.infoLabel = QtGui.QLabel(info)
+ self.layout.addWidget(self.infoLabel)
+
+ self.cancelButton = QtGui.QPushButton("Ok")
+ self.cancelButton.connect(self.cancelButton, QtCore.SIGNAL('clicked()'), self.reject)
+
+ self.buttonlayout = QtGui.QHBoxLayout()
+ self.buttonlayout.addWidget(self.cancelButton)
+ self.layout.addLayout(self.buttonlayout)
+
+ # set layout and go...
+ self.setLayout(self.layout)
+
+ def _set_slider_value(self, val):
+ self.slider.setValue(self.steps*(val-self.props.min.value)/self.valspan)
+
+ def _slide(self):
+ val = (self.slider.value()*self.valspan + self.props.min.value)/float(self.steps)
+ self.textInput.setText(str(val))
+
+ def _apply(self):
+ if(type(self.sv.value) == str):
+ val = str(self.textInput.text())
+ elif(type(self.sv.value) == int):
+ val = int(round(float(self.textInput.text())))
+ elif(type(self.sv.value) == float):
+ val = float(self.textInput.text())
+ else:
+ sys.stderr.write("set type not supported! ({0})\n".format(type(self.sv.value)))
+ sys.exit(-1)
+
+ self.sv.value = val
+ km = {}
+ km[self.key] = self.sv
+ self.radio.set(km)
+ self._set_slider_value(self.sv.value)
+
+ def _set(self):
+ self._apply()
+ self.done(0)
+
+
+def build_edge_graph(sources, sinks, edges):
+ '''
+ Starting from the sources, walks through all of the edges to find
+ the next connected block. The output is stored in 'allblocks'
+ where each row starts with a source and follows one path down
+ until it terminates in either a sink or as an input to a block
+ that is part of another chain.
+ '''
+ def find_edge(src, sinks, edges, row, col):
+ #print "\n\nAll blocks: "
+ #printer.pprint(allblocks)
+ #print "\nLooking for: ", src
+
+ src0 = src.split(":")[0]
+ if(src0 in sinks):
+ if(len(allblocks) <= row):
+ allblocks.append(col*[""])
+ allblocks[row].append(src)
+ return row+1
+
+ for edge in edges:
+ if(re.match(src0, edge)):
+ s = edge.split("->")[0]
+ b = edge.split("->")[1]
+ if(len(allblocks) <= row):
+ allblocks.append(col*[""])
+ allblocks[row].append(s)
+ #print "Source: {0} Sink: {1}".format(s, b)
+ row = find_edge(b, sinks, edges, row, col+1)
+ return row
+
+ # Recursively get all edges as a matrix of source->sink
+ n = 0
+ allblocks = []
+ for src in sources:
+ n = find_edge(src, sinks, edges, n, 0)
+
+ # Sort by longest list
+ allblocks = sorted(allblocks, key=len)
+ allblocks.reverse()
+
+ # Make all rows same length by padding '' in front of sort rows
+ maxrowlen = len(allblocks[0])
+ for i,a in enumerate(allblocks):
+ rowlen = len(a)
+ allblocks[i] = (maxrowlen-rowlen)*[''] + a
+
+ # Dedup rows
+ allblocks = sorted(allblocks)
+ allblocks = list(k for k,_ in itertools.groupby(allblocks))
+ allblocks.reverse()
+
+ for a in allblocks:
+ print a
+ print "\n\n"
+ return allblocks
+
+
+class MForm(QtGui.QWidget):
+ def update(self):
+ try:
+ st = time.time()
+ knobs = self.radio.get([b[0] for b in self.block_dict])
+
+ ft = time.time()
+ latency = ft-st
+ self.parent.statusBar().showMessage("Current GNU Radio Control Port Query Latency: %f ms"%\
+ (latency*1000))
+
+ except Exception, e:
+ sys.stderr.write("ctrlport-monitor: radio.get threw exception ({0}).\n".format(e))
+ if(type(self.parent) is MAINWindow):
+ # Find window of connection
+ remove = []
+ for p in self.parent.mdiArea.subWindowList():
+ if self.parent.conns[self.uid] == p.widget():
+ remove.append(p)
+
+ # Find any subplot windows of connection
+ for p in self.parent.mdiArea.subWindowList():
+ for plot in self.parent.plots[self.uid]:
+ if plot.qwidget() == p.widget():
+ remove.append(p)
+
+ # Clean up local references to these
+ self.parent.conns.remove(self.parent.conns[self.uid])
+ self.parent.plots.remove(self.parent.plots[self.uid])
+
+ # Remove subwindows for connection and plots
+ for r in remove:
+ self.parent.mdiArea.removeSubWindow(r)
+
+ # Clean up self
+ self.close()
+ else:
+ sys.exit(1)
+ return
+
+ #UPDATE TABLE:
+ self.updateItems(knobs)
+
+ #UPDATE PLOTS
+ self.parent.update(knobs, self.uid)
+
+ def updateItems(self, knobs):
+ for b in self.block_dict:
+ if(knobs[b[0]].ice_id.im_class == GNURadio.KnobVecF):
+ b[1].setText("{0:.4f}".format(knobs[b[0]].value[b[2]]))
+ else:
+ b[1].setText("{0:.4f}".format(knobs[b[0]].value))
+
+ def __init__(self, radio=None, port=None, uid=0, parent=None):
+
+ super(MForm, self).__init__()
+
+ if(radio == None or port == None):
+ askinfo = ConInfoDialog(self);
+ if askinfo.exec_():
+ host = str(askinfo.host.text());
+ port = str(askinfo.port.text());
+ radio = parent.interface.getRadio(host, port)
+ else:
+ self.radio = None
+ return
+
+ self.uid = uid
+ self.parent = parent
+ self.layout = QtGui.QGridLayout(self)
+ self.layout.setSizeConstraint(QtGui.QLayout.SetFixedSize)
+
+ self.radio = radio
+ self.knobprops = self.radio.properties([])
+ self.parent.knobprops.append(self.knobprops)
+ self.resize(775,500)
+ 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.get([])
+ for k in knobs:
+ propname = k.split("::")
+ blockname = propname[0]
+ keyname = propname[1]
+ if(keyname == "edge list"):
+ edgelist = knobs[k].value
+ elif(blockname not in tmplist):
+ # only take gr_blocks (no hier_block2)
+ if(knobs.has_key(input_name(blockname))):
+ tmplist.append(blockname)
+
+ edges = edgelist.split("\n")[0:-1]
+ producers = []
+ consumers = []
+ for e in edges:
+ _e = e.split("->")
+ producers.append(_e[0])
+ consumers.append(_e[1])
+
+ # Get producers and consumers as sets while ignoring the
+ # ports.
+ prods = set(map(lambda x: x.split(":")[0], producers))
+ cons = set(map(lambda x: x.split(":")[0], consumers))
+
+ # Split out all blocks, sources, and sinks based on how they
+ # appear as consumers and/or producers.
+ blocks = prods.intersection(cons)
+ sources = prods.difference(blocks)
+ sinks = cons.difference(blocks)
+
+ nblocks = len(prods) + len(cons)
+
+ allblocks = build_edge_graph(sources, sinks, edges)
+ nrows = len(allblocks)
+ ncols = len(allblocks[0])
+
+ col_width = 120
+
+ self.block_dict = []
+
+ for row, blockrow in enumerate(allblocks):
+ for col, block in enumerate(blockrow):
+ print "row: {0} col: {1} block: {2}".format(row, col, block)
+ if(block == ''):
+ continue
+
+ bgroup = QtGui.QGroupBox(block)
+ playout = QtGui.QFormLayout()
+ bgroup.setLayout(playout)
+ self.layout.addWidget(bgroup, row, 2*col)
+
+ blockname = block.split(":")[0]
+
+ name = wtime_name(blockname)
+ wtime = knobs[name].value
+ newtime = QtGui.QLineEdit()
+ newtime.setMinimumWidth(col_width)
+ newtime.setText("{0:.4f}".format(wtime))
+ self.block_dict.append((name, newtime))
+
+ name = nout_name(blockname)
+ nout = knobs[name].value
+ newnout = QtGui.QLineEdit()
+ newnout.setText("{0:.4f}".format(nout))
+ newnout.setMinimumWidth(col_width)
+ self.block_dict.append((name, newnout))
+
+ name = nprod_name(blockname)
+ nprod = knobs[name].value
+ newnprod = QtGui.QLineEdit()
+ newnprod.setMinimumWidth(col_width)
+ newnprod.setText("{0:.4f}".format(nprod))
+ self.block_dict.append((name, newnprod))
+
+ playout.addRow("Work time", newtime)
+ playout.addRow("noutput_items", newnout)
+ playout.addRow("nproduced", newnprod)
+
+ if blockname in blocks or blockname in sources:
+ # Add a buffer between blocks
+ buffgroup = QtGui.QGroupBox("Buffer")
+ bufflayout = QtGui.QFormLayout()
+ buffgroup.setLayout(bufflayout)
+ self.layout.addWidget(buffgroup, row, 2*col+1)
+
+ i = int(block.split(":")[1])
+ name = output_name(blockname)
+ obuff = knobs[name].value
+ for i,o in enumerate(obuff):
+ newobuff = QtGui.QLineEdit()
+ newobuff.setMinimumWidth(col_width)
+ newobuff.setText("{0:.4f}".format(o))
+ self.block_dict.append((name, newobuff, i))
+ bufflayout.addRow("Out Buffer {0}".format(i),
+ newobuff)
+
+ if blockname in blocks or blockname in sinks:
+ item = self.layout.itemAtPosition(row, 2*col-1)
+ if(item):
+ buffgroup = item.widget()
+ bufflayout = buffgroup.layout()
+ else:
+ buffgroup = QtGui.QGroupBox("Buffer")
+ bufflayout = QtGui.QFormLayout()
+ buffgroup.setLayout(bufflayout)
+ self.layout.addWidget(buffgroup, row, 2*col-1)
+
+ i = int(block.split(":")[1])
+ name = input_name(blockname)
+ ibuff = knobs[name].value[i]
+ newibuff = QtGui.QLineEdit()
+ newibuff.setMinimumWidth(col_width)
+ newibuff.setText("{0:.4f}".format(ibuff))
+ self.block_dict.append((name, newibuff, i))
+ bufflayout.addRow("In Buffer {0}".format(i),
+ newibuff)
+
+ # set up timer
+ self.timer = QtCore.QTimer()
+ self.connect(self.timer, QtCore.SIGNAL('timeout()'), self.update)
+ self.timer.start(1000)
+
+ 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.radio, self.uid)
+
+
+class MyClient(IceRadioClient):
+ def __init__(self):
+ IceRadioClient.__init__(self, MAINWindow)
+
+sys.exit(MyClient().main(sys.argv))