From: smbaker Date: Wed, 21 Sep 2011 00:54:08 +0000 (-0700) Subject: update to xmlrpc tracker, work in progress X-Git-Tag: sface-0.1-20~29 X-Git-Url: http://git.onelab.eu/?p=sface.git;a=commitdiff_plain;h=3f712fe77f9809d493ecd7c0aec9ec1005254c3c update to xmlrpc tracker, work in progress --- diff --git a/sface/mainwindow.py b/sface/mainwindow.py index a8e7d33..719fd07 100644 --- a/sface/mainwindow.py +++ b/sface/mainwindow.py @@ -11,6 +11,7 @@ from sface.config import config from sface.logwindow import LogWindow from sface.rspecwindow import RSpecWindow from sface.screens.sfascreen import SfaScreen +from sface.xmlrpcwindow import get_tracker, init_tracker # depending on the platform.. # could probably use Qt's resource system but looks overkill for just one file... @@ -126,6 +127,7 @@ class MainWindow(QWidget): # to our parent. Otherwise, getting a segfault on exit in Ubuntu. self.logWindow = LogWindow(parent) self.rspecWindow = RSpecWindow(parent) + self.trackerWindow = init_tracker(parent) self.pix = QLabel(self) @@ -153,12 +155,14 @@ class MainWindow(QWidget): self.nav.setTitle(self.screenWidgets[0].getTitleText()) self.status = Status(self) + self.tracker = QLabel("Show Xmlrpc", self) self.log = QLabel("Show Log", self) self.rspec = QLabel("Show RSpec", self) hlayout = QHBoxLayout() hlayout.addWidget(self.status) hlayout.addStretch() + hlayout.addWidget(self.tracker) hlayout.addWidget(self.rspec) hlayout.addWidget(self.log) @@ -173,6 +177,8 @@ class MainWindow(QWidget): self.connect(link, SIGNAL('linkActivated(QString)'), self.animateToScreen) + self.connect(self.tracker, SIGNAL('linkActivated(QString)'), + self.showTrackerWindow) self.connect(self.log, SIGNAL('linkActivated(QString)'), self.showLogWindow) self.connect(self.rspec, SIGNAL('linkActivated(QString)'), @@ -181,6 +187,13 @@ class MainWindow(QWidget): def redirectOutputToLog(self): self.logWindow.redirectOutput() + def showTrackerWindow(self): + tracker = get_tracker() + tracker.show() + tracker.resize(500, 640) + tracker.raise_() + tracker.activateWindow() + def showLogWindow(self, link): self.logWindow.show() self.logWindow.resize(800, 200) @@ -193,7 +206,6 @@ class MainWindow(QWidget): self.rspecWindow.raise_() self.rspecWindow.activateWindow() - def animatePixmap(self, y): self.pix.move(0, y) @@ -222,7 +234,6 @@ class MainWindow(QWidget): self.connect(timeLine, SIGNAL('finished()'), self.toNextScreen) timeLine.start() - def toNextScreen(self): self.screens.setCurrentWidget(self.next_screen) self.nav.setTitle(self.next_screen.getTitleText()) diff --git a/sface/sfiprocess.py b/sface/sfiprocess.py index 108530c..6950554 100644 --- a/sface/sfiprocess.py +++ b/sface/sfiprocess.py @@ -5,7 +5,7 @@ import time from PyQt4.QtCore import * from sface.config import config -from sface.xmlrpcwindow import XmlrpcTracker, XmlrpcReader +from sface.xmlrpcwindow import get_tracker, XmlrpcReader def find_executable(exec_name): """find the given executable in $PATH""" @@ -29,7 +29,6 @@ class SfiProcess(QObject): self.processFinished) self.xmlrpcreader = XmlrpcReader() # this one is for parsing XMLRPC responses - self.xmlrpctracker = XmlrpcTracker() # this one is for the debug window # holds aggregate output from processStandardOutput(); used by xmlrpc # tracker. @@ -212,5 +211,5 @@ class SfiProcess(QObject): # command = "%s %s" % (self.exe, self.args.join(" ")) print time.strftime('%H:%M:%S'),"Done [%.3f s]"%(time.time()-self._trace) if config.debug: - self.xmlrpctracker.getAndPrint(self.output) + get_tracker().getAndPrint(self.output) diff --git a/sface/xmlrpcwindow.py b/sface/xmlrpcwindow.py index 81007c0..72b0f68 100644 --- a/sface/xmlrpcwindow.py +++ b/sface/xmlrpcwindow.py @@ -1,24 +1,83 @@ import re from lxml import etree from PyQt4.QtXml import QDomDocument -from sface.xmlwidget import XmlWindow, DomModel +from sface.xmlwidget import XmlWindow, DomModel, XmlView, XmlDelegate, ElemNodeDelegate, TextNodeDelegate, CommentNodeDelegate + +from PyQt4.QtCore import * +from PyQt4.QtGui import * class XmlrpcReader(): def __init__(self): self.rawOutput = None self.responses = [] - - def getAndPrint(self, rawOutput): - self.store(rawOutput) - self.extractXml() - self.xmlrpcWindow.setData(self.xml) - if self.xml != "": - # only popup the window if we have something to show - self.showXmlrpc() + self.calls = [] def store(self, rawOutput): self.rawOutput = rawOutput + def paramString(self, item): + """ + Convert an xmlrpc into a human-readable string + Is there an easier way? + """ + if item.tag == "string": + return item.text + elif item.tag == "array": + arrayValues = item.xpath("data/value") + if arrayValues: + children = list(arrayValues[0]) + if children: + return self.paramString(children[0]) + ", ..." + else: + return "[]" + else: + return "[]" + elif item.tag == "struct": + dict = {} + members = item.xpath("member") + for member in members: + names = member.xpath("name") + values = member.xpath("value") + if (names) and (values): + name = names[0] + value = values[0] + if len(list(value))>0: + data = list(value)[0] + dict[name.text] = self.paramString(list(value)[0]) + return str(dict) + else: + return "unknown" + + def parseMethodCall(self, mc): + mc = str(mc) + + request = {} + + try: + tree = etree.fromstring(mc) + except etree.XMLSyntaxError, e: + return request + + if tree.tag != "methodCall" or (len(list(tree))==0): + return request + + methodNames = tree.xpath("//methodCall/methodName") + + if len(methodNames) == 0: + return request + + request["args"] = [] + + params = tree.xpath("//methodCall/params/param/value") + for param in params: + children = list(param) + if children: + request["args"].append(self.paramString(children[0])) + + request["methodName"] = methodNames[0].text + + return request + def parseMethodResponse(self, mr): mr = str(mr) # PyQT supplies a QByteArray; make it a string @@ -58,31 +117,45 @@ class XmlrpcReader(): return response # whatever didn't fault must have succeeded? - response["kind"] = "success" + # "success" really isn't quite the right word. The best we can say is + # that the call was executed. The actual op may or may not have + # succeeded. + response["kind"] = "executed" return response def extractXml(self): pttrnAsk = '.*?' pttrnAns = '.*?' - answers = re.compile(pttrnAsk, re.DOTALL).findall(self.rawOutput) + requests = re.compile(pttrnAsk, re.DOTALL).findall(self.rawOutput) replies = re.compile(pttrnAns, re.DOTALL).findall(self.rawOutput) # cleaning - answers = [ x.replace('\\n','\n') for x in answers ] + requests = [ x.replace('\\n','\n') for x in requests ] replies = [ x.replace('\\n','\n').replace("'\nbody: '", '').replace("\"\nbody: '", '') for x in replies ] - replies.reverse() # so that I use pop() as popleft # A well-formed XML document must have one, and only one, top-level element self.responses = [] self.xml = "" - for ans in answers: - self.xml += ans + for requestXml in requests: + self.xml += requestXml + callDict = {"request": requestXml} # we could have less responses than calls, so guard the pop if replies: - replyXml = replies.pop() + replyXml = replies.pop(0) self.xml += replyXml - self.responses.append(self.parseMethodResponse(replyXml)) + + # parse the response to extract fault information + responseDict = self.parseMethodResponse(replyXml) + self.responses.append(responseDict) + + requestDict = self.parseMethodCall(requestXml) + + callDict["reply"] = replyXml + callDict.update(responseDict) + callDict.update(requestDict) + + self.calls.append(callDict) # just in case somehow we ended up with more responses than calls while replies: @@ -96,39 +169,140 @@ class XmlrpcReader(): # statistics: round-trip time, size of the com pass -class XmlrpcTracker(XmlrpcReader): - def __init__(self): - XmlrpcReader.__init__(self) - self.xmlrpcWindow = XmlrpcWindow() +class XmlrpcTracker(QWidget): + def __init__(self, parent=None): + QWidget.__init__(self, parent=parent) + + self.reader = XmlrpcReader() + + labelCalls = QLabel("Calls: (from most recent to least recent)") + + self.callTable = QTableWidget(self) + self.callTable.setSelectionBehavior(QAbstractItemView.SelectRows) + self.callTable.setSelectionMode(QAbstractItemView.SingleSelection) + self.callTable.setMaximumHeight(140) + + labelArgs = QLabel("Params:") + + self.argsTable = QTableWidget(self) + self.argsTable.setMaximumHeight(140) + + labelXml = QLabel("XMLRPC Contents") + self.xmlView = XmlView(self) + + self.delegate = XmlDelegate(self) + self.delegate.insertNodeDelegate('element', ElemNodeDelegate()) + self.delegate.insertNodeDelegate('text', TextNodeDelegate()) + self.delegate.insertNodeDelegate('comment', CommentNodeDelegate()) + self.xmlView.setItemDelegate(self.delegate) + + self.layout = QVBoxLayout() + self.layout.addWidget(labelCalls) + self.layout.addWidget(self.callTable) + self.layout.addWidget(labelArgs) + self.layout.addWidget(self.argsTable) + self.layout.addWidget(labelXml) + self.layout.addWidget(self.xmlView) + + self.setLayout(self.layout) + + self.connect(self.callTable, SIGNAL("itemSelectionChanged ()"), self.onCallSelect) def getAndPrint(self, rawOutput): - self.store(rawOutput) - self.extractXml() - self.xmlrpcWindow.setData(self.xml) - if self.xml != "": - # only popup the window if we have something to show - self.showXmlrpc() - - def showXmlrpc(self): - self.xmlrpcWindow.show() - self.xmlrpcWindow.resize(500, 640) - self.xmlrpcWindow.raise_() - self.xmlrpcWindow.activateWindow() + self.reader.store(rawOutput) + self.reader.extractXml() + self.updateCallTable() - def extractXml(self): - self.xml = "" + XmlrpcReader.extractXml(self) + "" + def updateCallTable(self): + self.callTable.clear() + self.callTable.setColumnCount(2) + self.callTable.setHorizontalHeaderLabels(["name", "status"]) -class XmlrpcWindow(XmlWindow): - def __init__(self, parent=None): - # super __init__() calls updateView, - # which assumes you have some data - self.data = '' - XmlWindow.__init__(self, parent, 'XMLRPC window') + calls = self.reader.calls + self.callTable.setRowCount(len(calls)) + + row = 0 + reverse_calls = list(enumerate(calls)) + reverse_calls.reverse() + for (index,call) in reverse_calls: + item = QTableWidgetItem(call.get("methodName", "unknown")) + item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled) + item.setData(Qt.UserRole, index) + self.callTable.setItem(row, 0, item) + + item = QTableWidgetItem(call.get("kind", "unknown")) + item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled) + item.setData(Qt.UserRole, index) + self.callTable.setItem(row, 1, item) + + row = row + 1 + + self.callTable.resizeColumnsToContents() + + def updateArgsTable(self, args): + self.argsTable.clear() + self.argsTable.setColumnCount(1) + self.argsTable.setHorizontalHeaderLabels(["param"]) + self.argsTable.horizontalHeader().hide() + self.argsTable.setHorizontalScrollMode(QAbstractItemView.ScrollPerPixel) + + self.argsTable.setRowCount(len(args)) + + row = 0 + for arg in args: + if len(arg)>255: + arg = arg[:255] + "..." + + item = QTableWidgetItem(arg) + item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled) + self.argsTable.setItem(row, 0, item) + + row = row + 1 + + self.argsTable.resizeColumnsToContents() + + def onCallSelect(self): + selItems = self.callTable.selectedItems() + if (len(selItems) <= 0): + return + + row = selItems[0].data(Qt.UserRole).toInt()[0] + + calls = self.reader.calls + if len(calls)<=row: + return + + call = calls[row] + + xml = "" + call.get("request","") + call.get("reply", "") + "" + #xml = call.get("request","") + call.get("reply", "") + + self.displayXml(xml) + + self.updateArgsTable(call.get("args",[])) + + def displayXml(self, xml): + self.document = QDomDocument("XMLRPC Tracker") + self.document.setContent(xml) + self.model = DomModel(self.document, self) + self.xmlView.setModel(self.model) + + +# There's one global tracker for XMLRPCs. Use init_tracker() to create it, and +# get_tracker() to get it. + +glo_tracker = None + +def init_tracker(parent): + global glo_tracker + + glo_tracker = XmlrpcTracker(parent) + + return glo_tracker + +def get_tracker(): + global glo_tracker - def setData(self, XmlrpcCom): - self.data = XmlrpcCom + return glo_tracker - def updateView(self): - XmlWindow.updateView(self) - self.document.setContent(self.data)