Example #1
0
class ProfileGraphViewer( QWidget ):
    " Profiling results as a graph "

    escapePressed = pyqtSignal()

    def __init__( self, scriptName, params, reportTime,
                        dataFile, stats, parent = None ):
        QWidget.__init__( self, parent )

        self.__dataFile = dataFile
        self.__script = scriptName
        self.__reportTime = reportTime
        self.__params = params
        self.__stats = stats

        project = GlobalData().project
        if project.isLoaded():
            self.__projectPrefix = os.path.dirname( project.fileName )
        else:
            self.__projectPrefix = os.path.dirname( scriptName )
        if not self.__projectPrefix.endswith( os.path.sep ):
            self.__projectPrefix += os.path.sep

        self.__createLayout()
        self.__getDiagramLayout()

        self.__viewer.setScene( self.__scene )
        return

    def setFocus( self ):
        " Sets the focus properly "
        self.__viewer.setFocus()
        return

    def __isOutsideItem( self, fileName ):
        " Detects if the record should be shown as an outside one "
        return not fileName.startswith( self.__projectPrefix )

    def __createLayout( self ):
        " Creates the widget layout "
        totalCalls = self.__stats.total_calls
        totalPrimitiveCalls = self.__stats.prim_calls  # The calls were not induced via recursion
        totalTime = self.__stats.total_tt

        txt = "<b>Script:</b> " + self.__script + " " + self.__params.arguments + "<br>" \
              "<b>Run at:</b> " + self.__reportTime + "<br>" + \
              str( totalCalls ) + " function calls (" + \
              str( totalPrimitiveCalls ) + " primitive calls) in " + \
              FLOAT_FORMAT % totalTime + " CPU seconds"
        summary = QLabel( txt )
        summary.setToolTip( txt )
        summary.setSizePolicy( QSizePolicy.Ignored, QSizePolicy.Fixed )
        summary.setFrameStyle( QFrame.StyledPanel )
        summary.setAutoFillBackground( True )
        summaryPalette = summary.palette()
        summaryBackground = summaryPalette.color( QPalette.Background )
        summaryBackground.setRgb( min( summaryBackground.red() + 30, 255 ),
                                  min( summaryBackground.green() + 30, 255 ),
                                  min( summaryBackground.blue() + 30, 255 ) )
        summaryPalette.setColor( QPalette.Background, summaryBackground )
        summary.setPalette( summaryPalette )

        self.__scene = QGraphicsScene()
        self.__viewer = DiagramWidget()
        self.__viewer.escapePressed.connect( self.__onESC )

        vLayout = QVBoxLayout()
        vLayout.setContentsMargins( 0, 0, 0, 0 )
        vLayout.setSpacing( 0 )
        vLayout.addWidget( summary )
        vLayout.addWidget( self.__viewer )

        self.setLayout( vLayout )
        return


    def __getDiagramLayout( self ):
        " Runs external tools to get the diagram layout "

        # Preparation: build a map of func ID -> fileName + line
        funcMap = {}
        index = 0
        for func, props in self.__stats.stats.items():
            funcMap[ index ] = ( func[ 0 ], func[ 1 ] )
            index += 1

        # First step is to run grpof2dot
        gprof2dot = thirdpartyDir + "gprof2dot" + os.path.sep + "gprof2dot.py"
        outputFile = self.__dataFile + ".dot"
        nodeLimit = Settings().profileNodeLimit
        edgeLimit = Settings().profileEdgeLimit
        dotSpec = safeRun( [ gprof2dot, '-n', str( nodeLimit ),
                             '-e', str( edgeLimit ),
                             '-f', 'pstats', '-o', outputFile,
                             self.__dataFile ] )
        graphDescr = safeRun( [ "dot", "-Tplain", outputFile ] )
        graph = getGraphFromPlainDotData( graphDescr )
        graph.normalize( self.physicalDpiX(), self.physicalDpiY() )

        self.__scene.clear()
        self.__scene.setSceneRect( 0, 0, graph.width, graph.height )

        for edge in graph.edges:
            self.__scene.addItem( FuncConnection( edge ) )
            if edge.label != "":
                self.__scene.addItem( FuncConnectionLabel( edge ) )

        for node in graph.nodes:
            fileName = ""
            lineNumber = 0
            isOutside = True
            nodeID, newLabel = extractNodeID( node.label )
            if nodeID != -1:
                node.label = newLabel

                # Now, detect the file name/line number and
                # if it belongs to the project
                ( fileName, lineNumber ) = funcMap[ nodeID ]
            self.__scene.addItem( Function( node, fileName, lineNumber,
                                            self.__isOutsideItem( fileName ) ) )

        return

    def __onESC( self ):
        " Triggered when ESC is clicked "
        self.escapePressed.emit()
        return

    def onCopy( self ):
        " Copies the diagram to the exchange buffer "
        self.__viewer.onCopy()
        return

    def onSaveAs( self, fileName ):
        " Saves the diagram to a file "
        self.__viewer.onSaveAs( fileName )
        return

    def zoomIn( self ):
        " Triggered on the 'zoom in' button "
        self.__viewer.zoomIn()
        return

    def zoomOut( self ):
        " Triggered on the 'zoom out' button "
        self.__viewer.zoomOut()
        return

    def resetZoom( self ):
        " Triggered on the 'zoom reset' button "
        self.__viewer.resetZoom()
        return
Example #2
0
class ProfileGraphViewer(QWidget):
    """Profiling results as a graph"""

    sigEscapePressed = pyqtSignal()

    def __init__(self,
                 scriptName,
                 params,
                 reportTime,
                 dataFile,
                 stats,
                 parent=None):
        QWidget.__init__(self, parent)

        self.__dataFile = dataFile
        self.__script = scriptName
        self.__reportTime = reportTime
        self.__params = params
        self.__stats = stats

        project = GlobalData().project
        if project.isLoaded():
            self.__projectPrefix = os.path.dirname(project.fileName)
        else:
            self.__projectPrefix = os.path.dirname(scriptName)
        if not self.__projectPrefix.endswith(os.path.sep):
            self.__projectPrefix += os.path.sep

        self.__createLayout()
        self.__getDiagramLayout()

        self.__viewer.setScene(self.__scene)

    def setFocus(self):
        """Sets the focus properly"""
        self.__viewer.setFocus()

    def __isOutsideItem(self, fileName):
        """Detects if the record should be shown as an outside one"""
        return not fileName.startswith(self.__projectPrefix)

    def __createLayout(self):
        """Creates the widget layout"""
        totalCalls = self.__stats.total_calls
        # The calls were not induced via recursion
        totalPrimitiveCalls = self.__stats.prim_calls
        totalTime = self.__stats.total_tt

        txt = "<b>Script:</b> " + self.__script + " " + \
              self.__params['arguments'] + "<br/>" \
              "<b>Run at:</b> " + self.__reportTime + "<br/>" + \
              str(totalCalls) + " function calls (" + \
              str(totalPrimitiveCalls) + " primitive calls) in " + \
              FLOAT_FORMAT % totalTime + " CPU seconds"
        summary = HeaderFitLabel(self)
        summary.setText(txt)
        summary.setToolTip(txt)
        summary.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Fixed)
        summary.setMinimumWidth(10)

        self.__scene = QGraphicsScene()
        self.__viewer = DiagramWidget()
        self.__viewer.sigEscapePressed.connect(self.__onESC)

        vLayout = QVBoxLayout()
        vLayout.setContentsMargins(0, 0, 0, 0)
        vLayout.setSpacing(0)
        vLayout.addWidget(summary)
        vLayout.addWidget(self.__viewer)

        self.setLayout(vLayout)

    @staticmethod
    def __getDotFont(parts):
        """Provides a QFont object if a font spec is found"""
        for part in parts:
            if 'fontname=' in part:
                fontName = part.replace('fontname=', '')
                fontName = fontName.replace('[', '')
                fontName = fontName.replace(']', '')
                fontName = fontName.replace(',', '')
                return QFont(fontName)
        return None

    def __postprocessFullDotSpec(self, dotSpec):
        """Removes the arrow size, extracts tooltips, extracts font info"""
        nodeFont = None
        edgeFont = None
        tooltips = {}
        processed = []

        for line in dotSpec.splitlines():
            parts = line.split()
            lineModified = False
            if parts:
                if parts[0] == 'node':
                    # need to extract the fontname
                    nodeFont = self.__getDotFont(parts)
                elif parts[0] == 'edge':
                    # need to extract the fontname
                    edgeFont = self.__getDotFont(parts)
                elif parts[0].isdigit():
                    if parts[1] == '->':
                        # certain edge spec: replace arrowsize and font size
                        for index, part in enumerate(parts):
                            if part.startswith('[arrowsize='):
                                modified = parts[:]
                                modified[index] = '[arrowsize="0.0",'
                                processed.append(' '.join(modified))
                            elif part.startswith('fontsize='):
                                size = float(part.split('"')[1])
                                if edgeFont:
                                    edgeFont.setPointSize(size)
                        lineModified = True
                    elif parts[1].startswith('['):
                        # certain node spec: pick the tooltip and font size
                        lineno = None
                        for part in parts:
                            if part.startswith('tooltip='):
                                nodePath = part.split('"')[1]
                                pathLine = nodePath + ':' + str(lineno)
                                tooltips[int(parts[0])] = pathLine
                            elif part.startswith('fontsize='):
                                size = float(part.split('"')[1])
                                if nodeFont:
                                    nodeFont.setPointSize(size)
                            elif part.startswith('label='):
                                try:
                                    lineno = int(part.split(':')[1])
                                except:
                                    pass
            if not lineModified:
                processed.append(line)

        return '\n'.join(processed), tooltips, nodeFont, edgeFont

    def __rungprof2dot(self):
        """Runs gprof2dot which produces a full dot spec"""
        nodeLimit = Settings().getProfilerSettings().nodeLimit
        edgeLimit = Settings().getProfilerSettings().edgeLimit
        with io.StringIO() as buf:
            gprofParser = gprof2dot.PstatsParser(self.__dataFile)
            profileData = gprofParser.parse()
            profileData.prune(nodeLimit / 100.0, edgeLimit / 100.0, False,
                              False)

            dot = gprof2dot.DotWriter(buf)
            dot.strip = False
            dot.wrap = False
            dot.graph(profileData, gprof2dot.TEMPERATURE_COLORMAP)

            output = buf.getvalue()
        return self.__postprocessFullDotSpec(output)

    def __getDiagramLayout(self):
        """Runs external tools to get the diagram layout"""
        fullDotSpec, tooltips, nodeFont, edgeFont = self.__rungprof2dot()

        dotProc = Popen(["dot", "-Tplain"], stdin=PIPE, stdout=PIPE, bufsize=1)
        graphDescr = dotProc.communicate(
            fullDotSpec.encode('utf-8'))[0].decode('utf-8')

        graph = getGraphFromPlainDotData(graphDescr)
        graph.normalize(self.physicalDpiX(), self.physicalDpiY())

        self.__scene.clear()
        self.__scene.setSceneRect(0, 0, graph.width, graph.height)

        for edge in graph.edges:
            self.__scene.addItem(FuncConnection(edge))
            if edge.label != "":
                self.__scene.addItem(FuncConnectionLabel(edge, edgeFont))

        for node in graph.nodes:
            fileName = ""
            lineNumber = 0

            try:
                nodeNameAsInt = int(node.name)
                if nodeNameAsInt in tooltips:
                    parts = tooltips[nodeNameAsInt].rsplit(':', 1)
                    fileName = parts[0]
                    if parts[1].isdigit():
                        lineNumber = int(parts[1])
            except:
                pass

            self.__scene.addItem(
                Function(node, fileName, lineNumber,
                         self.__isOutsideItem(fileName), nodeFont))

    def __onESC(self):
        """Triggered when ESC is clicked"""
        self.sigEscapePressed.emit()

    def onCopy(self):
        """Copies the diagram to the exchange buffer"""
        self.__viewer.onCopy()

    def onSaveAs(self, fileName):
        """Saves the diagram to a file"""
        self.__viewer.onSaveAs(fileName)

    def zoomIn(self):
        """Triggered on the 'zoom in' button"""
        self.__viewer.zoomIn()

    def zoomOut(self):
        """Triggered on the 'zoom out' button"""
        self.__viewer.zoomOut()

    def resetZoom(self):
        """Triggered on the 'zoom reset' button"""
        self.__viewer.resetZoom()
Example #3
0
class ProfileGraphViewer(QWidget):
    " Profiling results as a graph "

    escapePressed = pyqtSignal()

    def __init__(self,
                 scriptName,
                 params,
                 reportTime,
                 dataFile,
                 stats,
                 parent=None):
        QWidget.__init__(self, parent)

        self.__dataFile = dataFile
        self.__script = scriptName
        self.__reportTime = reportTime
        self.__params = params
        self.__stats = stats

        project = GlobalData().project
        if project.isLoaded():
            self.__projectPrefix = os.path.dirname(project.fileName)
        else:
            self.__projectPrefix = os.path.dirname(scriptName)
        if not self.__projectPrefix.endswith(os.path.sep):
            self.__projectPrefix += os.path.sep

        self.__createLayout()
        self.__getDiagramLayout()

        self.__viewer.setScene(self.__scene)
        return

    def setFocus(self):
        " Sets the focus properly "
        self.__viewer.setFocus()
        return

    def __isOutsideItem(self, fileName):
        " Detects if the record should be shown as an outside one "
        return not fileName.startswith(self.__projectPrefix)

    def __createLayout(self):
        " Creates the widget layout "
        totalCalls = self.__stats.total_calls
        totalPrimitiveCalls = self.__stats.prim_calls  # The calls were not induced via recursion
        totalTime = self.__stats.total_tt

        txt = "<b>Script:</b> " + self.__script + " " + self.__params.arguments + "<br>" \
              "<b>Run at:</b> " + self.__reportTime + "<br>" + \
              str( totalCalls ) + " function calls (" + \
              str( totalPrimitiveCalls ) + " primitive calls) in " + \
              FLOAT_FORMAT % totalTime + " CPU seconds"
        summary = QLabel(txt)
        summary.setToolTip(txt)
        summary.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Fixed)
        summary.setFrameStyle(QFrame.StyledPanel)
        summary.setAutoFillBackground(True)
        summaryPalette = summary.palette()
        summaryBackground = summaryPalette.color(QPalette.Background)
        summaryBackground.setRgb(min(summaryBackground.red() + 30, 255),
                                 min(summaryBackground.green() + 30, 255),
                                 min(summaryBackground.blue() + 30, 255))
        summaryPalette.setColor(QPalette.Background, summaryBackground)
        summary.setPalette(summaryPalette)

        self.__scene = QGraphicsScene()
        self.__viewer = DiagramWidget()
        self.__viewer.escapePressed.connect(self.__onESC)

        vLayout = QVBoxLayout()
        vLayout.setContentsMargins(0, 0, 0, 0)
        vLayout.setSpacing(0)
        vLayout.addWidget(summary)
        vLayout.addWidget(self.__viewer)

        self.setLayout(vLayout)
        return

    def __getDiagramLayout(self):
        " Runs external tools to get the diagram layout "

        # Preparation: build a map of func ID -> fileName + line
        funcMap = {}
        index = 0
        for func, props in self.__stats.stats.items():
            funcMap[index] = (func[0], func[1])
            index += 1

        # First step is to run grpof2dot
        gprof2dot = thirdpartyDir + "gprof2dot" + os.path.sep + "gprof2dot.py"
        outputFile = self.__dataFile + ".dot"
        nodeLimit = Settings().profileNodeLimit
        edgeLimit = Settings().profileEdgeLimit
        dotSpec = safeRun([
            gprof2dot, '-n',
            str(nodeLimit), '-e',
            str(edgeLimit), '-f', 'pstats', '-o', outputFile, self.__dataFile
        ])
        graphDescr = safeRun(["dot", "-Tplain", outputFile])
        graph = getGraphFromPlainDotData(graphDescr)
        graph.normalize(self.physicalDpiX(), self.physicalDpiY())

        self.__scene.clear()
        self.__scene.setSceneRect(0, 0, graph.width, graph.height)

        for edge in graph.edges:
            self.__scene.addItem(FuncConnection(edge))
            if edge.label != "":
                self.__scene.addItem(FuncConnectionLabel(edge))

        for node in graph.nodes:
            fileName = ""
            lineNumber = 0
            isOutside = True
            nodeID, newLabel = extractNodeID(node.label)
            if nodeID != -1:
                node.label = newLabel

                # Now, detect the file name/line number and
                # if it belongs to the project
                (fileName, lineNumber) = funcMap[nodeID]
            self.__scene.addItem(
                Function(node, fileName, lineNumber,
                         self.__isOutsideItem(fileName)))

        return

    def __onESC(self):
        " Triggered when ESC is clicked "
        self.escapePressed.emit()
        return

    def onCopy(self):
        " Copies the diagram to the exchange buffer "
        self.__viewer.onCopy()
        return

    def onSaveAs(self, fileName):
        " Saves the diagram to a file "
        self.__viewer.onSaveAs(fileName)
        return

    def zoomIn(self):
        " Triggered on the 'zoom in' button "
        self.__viewer.zoomIn()
        return

    def zoomOut(self):
        " Triggered on the 'zoom out' button "
        self.__viewer.zoomOut()
        return

    def resetZoom(self):
        " Triggered on the 'zoom reset' button "
        self.__viewer.resetZoom()
        return