diff options
Diffstat (limited to 'gnuradio-core')
-rwxr-xr-x | gnuradio-core/src/python/gnuradio/ctrlport/gr-perf-monitor | 588 |
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)) |