VirtualBox

Changeset 61254 in vbox for trunk/src/VBox/ValidationKit


Ignore:
Timestamp:
May 28, 2016 1:38:58 AM (9 years ago)
Author:
vboxsync
svn:sync-xref-src-repo-rev:
107546
Message:

testmanager: Added report on faillure reasons, listing first and last build with each.

Location:
trunk/src/VBox/ValidationKit/testmanager
Files:
3 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/VBox/ValidationKit/testmanager/core/db.py

    r61217 r61254  
    333333
    334334        aasExplain = None;
    335         if self._oExplainCursor is not None:
     335        if self._oExplainCursor is not None and not sBound.startswith('DROP'):
    336336            try:
    337337                if config.g_kfWebUiSqlTraceExplainTiming:
  • trunk/src/VBox/ValidationKit/testmanager/core/report.py

    r56763 r61254  
    3434from testmanager.core.build         import BuildCategoryData;
    3535from testmanager.core.dbobjcache    import DatabaseObjCache;
     36from testmanager.core.failurereason import FailureReasonLogic;
    3637from testmanager.core.testbox       import TestBoxData;
    3738from common                         import constants;
     
    126127        return sWhere;
    127128
     129    def getPeriodStart(self, iPeriod):
     130        """ Gets the python timestamp for the start of the given period. """
     131        from datetime import timedelta;
     132        tsNow = self.tsNow if self.tsNow is not None else self._oDb.getCurrentTimestamp();
     133        cHoursStart = (self.cPeriods - iPeriod    ) * self.cHoursPerPeriod;
     134        return tsNow - timedelta(hours = cHoursStart);
     135
     136    def getPeriodEnd(self, iPeriod):
     137        """ Gets the python timestamp for the end of the given period. """
     138        from datetime import timedelta;
     139        tsNow = self.tsNow if self.tsNow is not None else self._oDb.getCurrentTimestamp();
     140        cHoursEnd   = (self.cPeriods - iPeriod - 1) * self.cHoursPerPeriod;
     141        return tsNow - timedelta(hours = cHoursEnd);
     142
    128143    def getExtraWhereExprForPeriod(self, iPeriod):
    129144        """
     
    157172        return '%dh ago' % (iPeriod * self.cHoursPerPeriod, );
    158173
     174    def getStraightPeriodDesc(self, iPeriod):
     175        """
     176        Returns the period description, usually for graph data.
     177        """
     178        iWickedPeriod = self.cPeriods - iPeriod - 1;
     179        return self.getPeriodDesc(iWickedPeriod);
     180
     181
     182class ReportFailureReasonRow(object):
     183    """ Simpler to return this than muck about with stupid arrays. """
     184    def __init__(self, aoRow, oReason):
     185        self.idFailureReason = aoRow[0];
     186        self.cHits           = aoRow[1];
     187        self.tsMin           = aoRow[2];
     188        self.tsMax           = aoRow[3];
     189        self.oReason         = oReason; # FailureReasonDataEx
     190
     191class ReportFailureReasonTransient(object):
     192    """ Details the first or last occurence of a reason.  """
     193    def __init__(self, idBuild, iRevision, sRepository, idTestSet, idTestResult, tsDone, oReason, iPeriod, fEnter):
     194        self.idBuild      = idBuild;        # Build ID.
     195        self.iRevision    = iRevision;      # SVN revision for build.
     196        self.sRepository  = sRepository;    # SVN repository for build.
     197        self.idTestSet    = idTestSet;      # Test set.
     198        self.idTestResult = idTestResult;   # Test result.
     199        self.tsDone       = tsDone;         # When the test set was done.
     200        self.oReason      = oReason;        # FailureReasonDataEx
     201        self.iPeriod      = iPeriod;        # Data set period.
     202        self.fEnter       = fEnter;         # True if enter event, False if leave event.
     203
     204class ReportFailureReasonPeriod(object):
     205    """ A period in ReportFailureReasonSet. """
     206    def __init__(self, oSet, iPeriod, sDesc, tsFrom, tsTo):
     207        self.oSet      = oSet   # Reference to the parent ReportFailureReasonSet.
     208        self.iPeriod   = iPeriod;
     209        self.sDesc     = sDesc;
     210        self.aoRows    = [];    # Rows in order the database returned them.
     211        self.dById     = {};    # Same as aoRows but indexed by idFailureReason.
     212        self.cHits     = 0;     # Total number of hits.
     213        self.dFirst    = {};    # The reasons seen for the first time (idFailureReason key).
     214        self.dLast     = {};    # The reasons seen for the last time (idFailureReason key).
     215        self.tsStart   = tsFrom;
     216        self.tsEnd     = tsTo;
     217        self.tsMin     = tsTo;
     218        self.tsMax     = tsFrom;
     219
     220class ReportFailureReasonSet(object):
     221    """ What ReportLazyModel.getFailureReasons returns. """
     222    def __init__(self):
     223        self.aoPeriods   = [];  # Periods in ascending order (time wise).
     224        self.dReasons    = {};  # FailureReasonDataEx objected indexted by idFailureReason
     225        self.dTotals     = {};  # Totals per reason, indexed by idFailureReason.
     226        self.cHits       = 0;   # Total number of hits in all periods and all reasons.
     227        self.cMaxRowHits = 0;   # Max hits in a row.
     228        self.diFirst     = {};  # The period number a reason was first seen (idFailureReason key).
     229        self.diLast      = {};  # The period number a reason was last seen (idFailureReason key).
     230        self.aoEnterInfo = [];  # Array of ReportFailureReasonTransient order by iRevision. Excludes the first period.
     231        self.aoLeaveInfo = [];  # Array of ReportFailureReasonTransient order in descending order by iRevision.  Excludes last.
    159232
    160233
     
    191264
    192265        adPeriods = [];
    193         for iPeriod in range(self.cPeriods):
     266        for iPeriod in xrange(self.cPeriods):
    194267            self._oDb.execute('SELECT   enmStatus, COUNT(TestSets.idTestSet)\n'
    195268                              'FROM     TestSets' + self.getExtraSubjectTables() +'\n'
     
    219292
    220293        return adPeriods;
     294
     295
     296    def getFailureReasons(self):
     297        """
     298        Gets the failure reasons of the subject in the specified period.
     299
     300        Returns an array of data per period (0 is the oldes, self.cPeriods-1 is
     301        the latest) where each entry is a dicationary using failure reason ID as
     302        key.  The dictionary contains a tuple where the first element is the
     303        number of occurences and the second is the corresponding
     304        FailureReasonDataEx object from the cache.
     305
     306        Note that reason IDs may not be present in every period, we only return
     307        those with actual occurences.
     308        """
     309
     310        # Create a temporary table
     311        sTsNow   = 'CURRENT_TIMESTAMP' if self.tsNow is None else self._oDb.formatBindArgs('%s::TIMESTAMP', (self.tsNow,));
     312        sTsFirst = '(%s - interval \'%s hours\')' \
     313                 % (sTsNow, self.cHoursPerPeriod * self.cPeriods,);
     314        self._oDb.execute('CREATE TEMPORARY TABLE TmpReasons ON COMMIT DROP AS\n'
     315                          'SELECT   TestResultFailures.idFailureReason AS idFailureReason,\n'
     316                          '         TestResultFailures.idTestResult    AS idTestResult,\n'
     317                          '         TestSets.idTestSet                 AS idTestSet,\n'
     318                          '         TestSets.tsDone                    AS tsDone,\n'
     319                          '         TestSets.tsCreated                 AS tsCreated,\n'
     320                          '         TestSets.idBuild                   AS idBuild\n'
     321                          'FROM     TestResultFailures,\n'
     322                          '         TestResults,\n'
     323                          '         TestSets' + self.getExtraSubjectTables() + '\n'
     324                          'WHERE    TestResultFailures.idTestResult = TestResults.idTestResult\n'
     325                          '     AND TestResultFailures.tsExpire     = \'infinity\'::TIMESTAMP\n'
     326                          '     AND TestResultFailures.tsEffective >= ' + sTsFirst + '\n'
     327                          '     AND TestResults.enmStatus          <> \'running\'\n'
     328                          '     AND TestResults.enmStatus          <> \'success\'\n'
     329                          '     AND TestResults.tsCreated          >= ' + sTsFirst + '\n'
     330                          '     AND TestResults.tsCreated          <  ' + sTsNow + '\n'
     331                          '     AND TestResults.idTestSet           = TestSets.idTestSet\n'
     332                          '     AND TestSets.tsDone                >= ' + sTsFirst + '\n'
     333                          '     AND TestSets.tsDone                <  ' + sTsNow + '\n'
     334                        + self.getExtraSubjectWhereExpr());
     335        self._oDb.execute('SELECT idFailureReason FROM TmpReasons;');
     336
     337
     338        # Retrieve the period results.
     339        oFailureReasonLogic = FailureReasonLogic(self._oDb);
     340        oSet = ReportFailureReasonSet();
     341        for iPeriod in xrange(self.cPeriods):
     342            #cHoursStarted = (self.cPeriods - iPeriod) * self.cHoursPerPeriod;
     343            #if self.tsNow is None:
     344            #    sTsFirst = '(CURRENT_TIMESTAMP - interval \'%u hours\')' % (cHoursStarted,);
     345            #
     346            #else:
     347            #    sTsFirst = '(%s - interval \'%u hours\')' \
     348            #             % ( self._oDb.formatBindArgs('%s::TIMESTAMP', (self.tsNow,)), cHoursStarted,) ;
     349            #
     350            #self._oDb.execute('SELECT   TestResultFailures.idFailureReason,\n'
     351            #                  '         COUNT(TestResultFailures.idTestResult),\n'
     352            #                  '         MIN(TestSets.tsDone),\n'
     353            #                  '         MAX(TestSets.tsDone)\n'
     354            #                  'FROM     TestResultFailures,\n'
     355            #                  '         TestResults,\n'
     356            #                  '         TestSets' + self.getExtraSubjectTables() + '\n'
     357            #                  'WHERE    TestResultFailures.idTestResult = TestResults.idTestResult\n'
     358            #                  '     AND TestResultFailures.tsExpire = \'infinity\'::TIMESTAMP\n'
     359            #                  '     AND TestResultFailures.tsEffective >= ' + sTsFirst + '\n'
     360            #                  '     AND TestResults.enmStatus <> \'running\'\n'
     361            #                  '     AND TestResults.enmStatus <> \'success\'\n'
     362            #                  '     AND TestResults.tsCreated >= ' + sTsFirst + '\n'
     363            #                  '     AND TestResults.idTestSet = TestSets.idTestSet\n'
     364            #                + self.getExtraSubjectWhereExpr()
     365            #                + self.getExtraWhereExprForPeriod(iPeriod)
     366            #                + 'GROUP BY TestResultFailures.idFailureReason\n');
     367
     368            self._oDb.execute('SELECT   idFailureReason,\n'
     369                              '         COUNT(idTestResult),\n'
     370                              '         MIN(tsDone),\n'
     371                              '         MAX(tsDone)\n'
     372                              'FROM     TmpReasons\n'
     373                              'WHERE    TRUE\n'
     374                            + self.getExtraWhereExprForPeriod(iPeriod).replace('TestSets.', '')
     375                            + 'GROUP BY idFailureReason\n');
     376
     377            aaoRows = self._oDb.fetchAll()
     378
     379            oPeriod = ReportFailureReasonPeriod(oSet, iPeriod, self.getStraightPeriodDesc(iPeriod),
     380                                                self.getPeriodStart(iPeriod), self.getPeriodEnd(iPeriod));
     381            oSet.aoPeriods.append(oPeriod);
     382            for aoRow in aaoRows:
     383                oReason = oFailureReasonLogic.cachedLookup(aoRow[0]);
     384                oPeriodRow = ReportFailureReasonRow(aoRow, oReason);
     385                oPeriod.aoRows.append(oPeriodRow);
     386                oPeriod.dById[oPeriodRow.idFailureReason] = oPeriodRow;
     387                oPeriod.cHits += oPeriodRow.cHits;
     388                if oPeriodRow.idFailureReason in oSet.dReasons:
     389                    oSet.dTotals[oPeriodRow.idFailureReason]  += oPeriodRow.cHits;
     390                else:
     391                    oSet.dTotals[oPeriodRow.idFailureReason]   = oPeriodRow.cHits;
     392                    oSet.dReasons[oPeriodRow.idFailureReason]  = oReason;
     393                    oSet.diFirst[oPeriodRow.idFailureReason]   = iPeriod;
     394                    oPeriod.dFirst[oPeriodRow.idFailureReason] = oReason;
     395                if oPeriodRow.cHits > oSet.cMaxRowHits:
     396                    oSet.cMaxRowHits = oPeriodRow.cHits;
     397                if oPeriodRow.tsMin < oPeriod.tsMin:
     398                    oPeriod.tsMin = oPeriodRow.tsMin;
     399                if oPeriodRow.tsMax > oPeriod.tsMax:
     400                    oPeriod.tsMax = oPeriodRow.tsMax;
     401            oSet.cHits += oPeriod.cHits;
     402
     403        #
     404        # construct the diLast and dLast bits.
     405        #
     406        for iPeriod in xrange(self.cPeriods - 1, 0, -1):
     407            oPeriod = oSet.aoPeriods[iPeriod];
     408            for oRow in oPeriod.aoRows:
     409                if oRow.idFailureReason not in oSet.diLast:
     410                    oSet.diLast[oRow.idFailureReason]   = iPeriod;
     411                    oPeriod.dLast[oRow.idFailureReason] = oRow.oReason;
     412
     413        #
     414        # For reasons entering after the first period, look up the build and
     415        # test set it first occured with.
     416        #
     417        for iPeriod in xrange(1, self.cPeriods):
     418            oPeriod = oSet.aoPeriods[iPeriod];
     419            for oReason in oPeriod.dFirst.values():
     420                oSet.aoEnterInfo.append(self._getEdgeFailureReasonOccurence(oReason, iPeriod, fEnter = True));
     421        oSet.aoEnterInfo = sorted(oSet.aoEnterInfo, key = lambda oTrans: oTrans.iRevision);
     422
     423        # Ditto for reasons leaving before the last.
     424        for iPeriod in xrange(self.cPeriods - 1):
     425            oPeriod = oSet.aoPeriods[iPeriod];
     426            for oReason in oPeriod.dLast.values():
     427                oSet.aoLeaveInfo.append(self._getEdgeFailureReasonOccurence(oReason, iPeriod, fEnter = False));
     428        oSet.aoLeaveInfo = sorted(oSet.aoLeaveInfo, key = lambda oTrans: oTrans.iRevision, reverse = True);
     429
     430        self._oDb.execute('DROP TABLE TmpReasons\n');
     431        return oSet;
     432
     433
     434    def _getEdgeFailureReasonOccurence(self, oReason, iPeriod, fEnter = True):
     435        """
     436        Helper for the failure reason report that finds the oldest or newest build
     437        (SVN rev) and test set (start time) it occured with.
     438
     439        If fEnter is set the oldest occurence is return, if fEnter clear the newest
     440        is is returned.
     441
     442        Returns ReportFailureReasonTransient instant.
     443
     444        """
     445        sSorting = 'ASC' if fEnter else 'DESC';
     446        self._oDb.execute('SELECT   TmpReasons.idTestResult,\n'
     447                          '         TmpReasons.idTestSet,\n'
     448                          '         TmpReasons.tsDone,\n'
     449                          '         TmpReasons.idBuild,\n'
     450                          '         Builds.iRevision,\n'
     451                          '         BuildCategories.sRepository\n'
     452                          'FROM     TmpReasons,\n'
     453                          '         Builds,\n'
     454                          '         BuildCategories\n'
     455                          'WHERE    TmpReasons.idFailureReason  = %s\n'
     456                          '     AND TmpReasons.idBuild          = Builds.idBuild\n'
     457                          '     AND Builds.tsExpire             > TmpReasons.tsCreated\n'
     458                          '     AND Builds.tsEffective         <= TmpReasons.tsCreated\n'
     459                          '     AND Builds.idBuildCategory      = BuildCategories.idBuildCategory\n'
     460                          'ORDER BY Builds.iRevision ' + sSorting + ',\n'
     461                          '         TmpReasons.tsCreated ' + sSorting + '\n'
     462                          'LIMIT 1\n'
     463                          , ( oReason.idFailureReason, ));
     464        aoRow = self._oDb.fetchOne();
     465        if aoRow is None:
     466            return ReportFailureReasonTransient(-1, -1, 'internal-error', -1, -1,
     467                                                self._oDb.getCurrentTimestamp(), oReason, iPeriod);
     468        return ReportFailureReasonTransient(idBuild = aoRow[3], iRevision = aoRow[4], sRepository = aoRow[5],
     469                                            idTestSet = aoRow[1], idTestResult = aoRow[0], tsDone = aoRow[2],
     470                                            oReason = oReason, iPeriod = iPeriod, fEnter = fEnter);
     471
     472
     473
    221474
    222475class ReportGraphModel(ReportModelBase): # pylint: disable=R0903
  • trunk/src/VBox/ValidationKit/testmanager/webui/wuireport.py

    r61220 r61254  
    3131
    3232# Validation Kit imports.
    33 from testmanager.webui.wuicontentbase   import WuiContentBase;
    34 from testmanager.webui.wuihlpgraph       import WuiHlpGraphDataTable, WuiHlpBarGraph;
     33from common                             import webutils;
     34from testmanager.webui.wuicontentbase   import WuiContentBase, WuiSvnLinkWithTooltip;
     35from testmanager.webui.wuihlpgraph      import WuiHlpGraphDataTable, WuiHlpBarGraph;
    3536from testmanager.core.report            import ReportModelBase;
    3637
     
    132133    """
    133134
    134     def generateReportBody(self):
    135         # Mockup.
    136         self._sTitle = 'Success rate';
    137         return '<p>Graph showing COUNT(idFailureReason) grouped by time period.</p>' \
    138                '<p>New reasons per period, tracked down to build revision.</p>' \
    139                '<p>Show graph content in table form.</p>';
     135    def _formatEdgeOccurence(self, oTransient):
     136        """
     137        Helper for formatting the transients.
     138        oTransient is of type ReportFailureReasonTransient.
     139        """
     140        sHtml = u'<li>';
     141        if oTransient.fEnter:   sHtml += 'Since ';
     142        else:                   sHtml += 'Till ';
     143        sHtml += WuiSvnLinkWithTooltip(oTransient.iRevision, oTransient.sRepository, fBracketed = 'False').toHtml();
     144        sHtml += u', %s: ' % (self.formatTsShort(oTransient.tsDone),);
     145        sHtml += u'%s / %s' % (webutils.escapeElem(oTransient.oReason.oCategory.sShort),
     146                               webutils.escapeElem(oTransient.oReason.sShort),);
     147        sHtml += u'</li>\n';
     148
     149        return sHtml;
     150
     151
     152    def generateReportBody(self):
     153        self._sTitle = 'Failure reasons';
     154
     155        sHtml = u'';
     156
     157        #
     158        # The array of periods we get have the oldest period first [0].
     159        #
     160        oSet = self._oModel.getFailureReasons();
     161
     162        #
     163        # List failure reasons starting or stopping to appear within the data set.
     164        #
     165        dtFirstLast = {};
     166        for iPeriod, oPeriod in enumerate(oSet.aoPeriods):
     167            for oRow in oPeriod.aoRows:
     168                tIt = dtFirstLast.get(oRow.idFailureReason, (iPeriod, iPeriod));
     169                #sHtml += u'<!-- %d: %d,%d -- %d -->\n' % (oRow.idFailureReason, tIt[0], tIt[1], iPeriod);
     170                dtFirstLast[oRow.idFailureReason] = (tIt[0], iPeriod);
     171
     172        sHtml += '<!-- \n';
     173        for iPeriod, oPeriod in enumerate(oSet.aoPeriods):
     174            sHtml += ' iPeriod=%d tsStart=%s tsEnd=%s\n' % (iPeriod, oPeriod.tsStart, oPeriod.tsEnd,);
     175            sHtml += '             tsMin=%s tsMax=%s\n' % (oPeriod.tsMin, oPeriod.tsMax,);
     176            sHtml += '              %d / %s\n' % (oPeriod.iPeriod, oPeriod.sDesc,)
     177        sHtml += '-->\n';
     178
     179        sHtml += u'<h4>Changes:</h4>\n' \
     180                 u'<ul>\n';
     181        if len(oSet.aoEnterInfo) == 0 and len(oSet.aoLeaveInfo) == 0:
     182            sHtml += u'<li> No changes</li>\n';
     183        else:
     184            for oTransient in oSet.aoEnterInfo:
     185                sHtml += self._formatEdgeOccurence(oTransient);
     186            for oTransient in oSet.aoLeaveInfo:
     187                sHtml += self._formatEdgeOccurence(oTransient);
     188        sHtml += u'</ul>\n';
     189
     190        #
     191        # Graph.
     192        #
     193        if True: # pylint: disable=W0125
     194            aidSorted = sorted(oSet.dReasons, key = lambda idReason: oSet.dTotals[idReason], reverse = True);
     195        else:
     196            aidSorted = sorted(oSet.dReasons,
     197                               key = lambda idReason: '%s / %s' % (oSet.dReasons[idReason].oCategory.sShort,
     198                                                                   oSet.dReasons[idReason].sShort,));
     199
     200        asNames = [];
     201        for idReason in aidSorted:
     202            oReason = oSet.dReasons[idReason];
     203            asNames.append('%s / %s' % (oReason.oCategory.sShort, oReason.sShort,) )
     204        oTable = WuiHlpGraphDataTable('Period', asNames);
     205
     206        for iPeriod, oPeriod in enumerate(reversed(oSet.aoPeriods)):
     207            aiValues = [];
     208            for idReason in aidSorted:
     209                oRow = oPeriod.dById.get(idReason, None);
     210                iValue = oRow.cHits if oRow is not None else 0;
     211                aiValues.append(iValue);
     212            oTable.addRow(oPeriod.sDesc, aiValues);
     213
     214        oGraph = WuiHlpBarGraph('failure-reason', oTable, self._oDisp);
     215        oGraph.setRangeMax(max(oSet.cMaxRowHits + 1, 3));
     216        sHtml += oGraph.renderGraph();
     217
     218        #
     219        # Table form necessary?
     220        #
     221        #sHtml += u'<p>TODO: Show graph content in table form.</p>';
     222
     223        return sHtml;
    140224
    141225
     
    152236        oSuccessRate = WuiReportSuccessRate(self._oModel, self._dParams, fSubReport = True,
    153237                                            fnDPrint = self._fnDPrint, oDisp = self._oDisp);
    154         sHtml += oSuccessRate.show()[1];
     238
     239
     240
     241        oFailureReasons = WuiReportFailureReasons(self._oModel, self._dParams, fSubReport = True,
     242                                                  fnDPrint = self._fnDPrint, oDisp = self._oDisp);
     243        for oReport in [oSuccessRate, oFailureReasons, ]:
     244            (sTitle, sContent) = oReport.show();
     245            sHtml += '<br>'; # drop this layout hack
     246            sHtml += '<div>';
     247            sHtml += '<h3>%s</h3>\n' % (webutils.escapeElem(sTitle),);
     248            sHtml += sContent;
     249            sHtml += '</div>';
    155250
    156251        return sHtml;
Note: See TracChangeset for help on using the changeset viewer.

© 2025 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette