diff options
-rw-r--r-- | src/bm/bmrequest.cpp | 447 | ||||
-rw-r--r-- | src/bm/bmrequest.h | 29 | ||||
-rw-r--r-- | src/bm/plotter.cpp | 71 | ||||
-rw-r--r-- | src/bm/plotter.h | 6 | ||||
-rw-r--r-- | src/bmclient/main.cpp | 113 |
5 files changed, 587 insertions, 79 deletions
diff --git a/src/bm/bmrequest.cpp b/src/bm/bmrequest.cpp index 33769a5..52bacdb 100644 --- a/src/bm/bmrequest.cpp +++ b/src/bm/bmrequest.cpp @@ -110,6 +110,8 @@ BMRequest * BMRequest::create(const QByteArray &data, const QString &msgType) request = new BMRequest_GetIXHistories(doc); else if (type == "ASFStatsGetValues") request = new BMRequest_ASFStatsGetValues(doc); + else if (type == "ASFStatsGetValues2") + request = new BMRequest_ASFStatsGetValues2(doc); #ifdef BMDEBUG else qDebug() << "invalid request type:" << type; @@ -918,7 +920,7 @@ static bool lastValueIsStable( } // ### 2 B DOCUMENTED! -bool BMRequest::appendBranchHistoryToReply( +bool BMRequest::appendResultHistoryToReply( QString *reply, int bmcontextId, const QString &suffix, const QString ×tamp1, const QString ×tamp2, qreal diffTolerance, int stabTolerance, int maxSize) const { @@ -1811,10 +1813,10 @@ QByteArray BMRequest_GetHistory::toRequestBuffer(QString *) "testCase=\"%2\" testFunction=\"%3\" dataTag=\"%4\" metric=\"%5\" platform=\"%6\" " "host=\"%7\" gitRepo=\"%8\" gitBranch=\"%9\" timestamp1=\"%10\" " "timestamp2=\"%11\" diffTolerance=\"%12\" stabTolerance=\"%13\" " - "maxSize=\"%14\" explicitBMContextId=\"%15\" /></request>") + "maxSize=\"%14\" /></request>") .arg(name()).arg(testCase).arg(testFunction).arg(dataTag).arg(metric).arg(platform) .arg(host).arg(gitRepo).arg(gitBranch).arg(timestamp1).arg(timestamp2) - .arg(diffTolerance).arg(stabTolerance).arg(maxSize).arg(explicitBMContextId); + .arg(diffTolerance).arg(stabTolerance).arg(maxSize); return xmlConvert(request); } @@ -1838,34 +1840,18 @@ QByteArray BMRequest_GetHistory::toReplyBuffer() Q_ASSERT(ok); maxSize = contextElem.attributeNode("maxSize").value().toInt(&ok); Q_ASSERT(ok); - explicitBMContextId = contextElem.attributeNode("explicitBMContextId").value().toInt(&ok); - Q_ASSERT(ok); - - if (explicitBMContextId >= 0) { - - QString error; - if (!extractBMAndContextFromRH( - explicitBMContextId, name(), database, &testCase, &testFunction, &dataTag, - &metric, &platform, &host, &gitRepo, &gitBranch, &error)) { - return xmlConvert(error); - } - } QString reply = QString("<reply type=\"%1\">").arg(name()); + int benchmarkId; + if (!getBenchmarkId(&reply, &benchmarkId, testCase, testFunction, dataTag)) + return xmlConvert(reply); int bmcontextId; - if (explicitBMContextId >= 0) { - bmcontextId = explicitBMContextId; - } else { - int benchmarkId; - if (!getBenchmarkId(&reply, &benchmarkId, testCase, testFunction, dataTag)) - return xmlConvert(reply); - if (!getBMContextId( - &reply, &bmcontextId, benchmarkId, metric, platform, host, gitRepo, gitBranch)) - return xmlConvert(reply); - } + if (!getBMContextId( + &reply, &bmcontextId, benchmarkId, metric, platform, host, gitRepo, gitBranch)) + return xmlConvert(reply); - if (!appendBranchHistoryToReply( + if (!appendResultHistoryToReply( &reply, bmcontextId, "", timestamp1, timestamp2, diffTolerance, stabTolerance, maxSize)) return xmlConvert(reply); @@ -2664,7 +2650,7 @@ QByteArray BMRequest_GetHistory2::toReplyBuffer() if (!getBMContextId( &reply, &bmcontextId1, benchmarkId, metric, platform, host, gitRepo1, gitBranch1)) return xmlConvert(reply); - if (!appendBranchHistoryToReply( + if (!appendResultHistoryToReply( &reply, bmcontextId1, "1", timestamp1, timestamp2, diffTolerance, stabTolerance, maxSize)) return xmlConvert(reply); @@ -2673,7 +2659,7 @@ QByteArray BMRequest_GetHistory2::toReplyBuffer() if (!getBMContextId( &reply, &bmcontextId2, benchmarkId, metric, platform, host, gitRepo2, gitBranch2)) return xmlConvert(reply); - if (!appendBranchHistoryToReply( + if (!appendResultHistoryToReply( &reply, bmcontextId2, "2", timestamp1, timestamp2, diffTolerance, stabTolerance, maxSize)) return xmlConvert(reply); @@ -7223,7 +7209,7 @@ void BMRequest_GetIXHistories::handleReply_Image(const QStringList &args) const QString error_; Plotter *plotter = new HistoriesPlotter( - rhInfos, true, evalTimestamp, &basePos, &extraPos, &descr, false, true); + rhInfos, true, evalTimestamp, &basePos, &extraPos, 0, &descr, false, true); const QImage image = plotter->createImage(&error_); // Free dynamic memory ... @@ -7495,20 +7481,20 @@ struct RHStats { // ### 2 B DOCUMENTED! static QString createRHDetailsLink( - const QString &webServer, const QString &server, const QString &styleSheet, int bmcontextId) + const QString &webServer, const QString &server, const QString &styleSheet, int bmcontextId, + int medianWinSize, int fromTime, int toTime) { QString url; url = QString("%1/cgi-bin/bmclientwrapper").arg(webServer); url += QString("?command=-server %1").arg(server); - url += - " get detailspage dummyTestCase dummyTestFunction dummyDataTag dummyMetric dummyPlatform" - " dummyHost dummyGitRepo dummyGitBranch"; // ### this is a bit silly, so fix it by making - // the bmclient independent of argument positions - QDateTime dateTime = QDateTime::currentDateTime(); - url += QString(" -timerange first last 0.00 0 -1 %1 %2 -bmcontextid %3") - .arg(styleSheet) - .arg(dateTime.toTime_t()) - .arg(bmcontextId); + url += QString( + " asfstats get detailspage2 -bmcontextid %1 -medianwinsize %2 -fromtime %3 -totime %4" + " -stylesheet %5") + .arg(bmcontextId) + .arg(medianWinSize) + .arg(fromTime) + .arg(toTime) + .arg(styleSheet); return QString("<a href=\"%1\">details</a>").arg(url); } @@ -7561,8 +7547,7 @@ void BMRequest_ASFStatsGetValues::handleReply_HTML(const QStringList &args) cons reply += "<br /><br /><table><tr><td style=\"background-color:#eeeeee\">"; - reply += "This page shows statistics suitable for presentation on the ASF Scorecard "; - reply += "<span style=\"color:red\"> (More documentation to follow soon!)</span>."; + reply += "This page shows statistics suitable for presentation on the ASF Scorecard."; reply += "</td></tr></table>\n"; @@ -7790,7 +7775,9 @@ void BMRequest_ASFStatsGetValues::handleReply_HTML(const QStringList &args) cons "<td>%10</td>" "</tr>\n") .arg(i) - .arg(createRHDetailsLink(webServer, server, styleSheet, rhStats.at(i)->id)) + .arg(createRHDetailsLink( + webServer, server, styleSheet, rhStats.at(i)->id, medianWinSize, + fromTimestamp, toTimestamp)) .arg(rhStats.at(i)->rg ? "RG" : "") .arg(rhStats.at(i)->uc ? "UC" : "") .arg(rhStats.at(i)->im ? "IM" : "") @@ -7808,3 +7795,379 @@ void BMRequest_ASFStatsGetValues::handleReply_HTML(const QStringList &args) cons BMMisc::printHTMLOutput(reply); } + + +// --- ASFStatsGetValues2 --- +QByteArray BMRequest_ASFStatsGetValues2::toRequestBuffer(QString *) +{ + const QString request = + QString("<request type=\"%1\"><args bmcontextId=\"%2\" cacheKey=\"%3\" /></request>") + .arg(name()) + .arg(bmcontextId) + .arg(cacheKey); + + return xmlConvert(request); +} + +QByteArray BMRequest_ASFStatsGetValues2::toReplyBuffer() +{ + QString error; + QString reply; + bool ok; + + QDomElement argsElem = doc.elementsByTagName("args").at(0).toElement(); + + // Check if a cached reply can be returned ... + int cacheKey_ = argsElem.attributeNode("cacheKey").value().toInt(&ok); + if (ok) { + reply = Cache::instance()->get(cacheKey_); + if (!reply.isEmpty()) + return xmlConvert(reply); + } + + // Compute from scratch ... + + bmcontextId = argsElem.attributeNode("bmcontextId").value().toInt(&ok); + Q_ASSERT(ok); + + QString testCase; + QString testFunction; + QString dataTag; + QString metric; + QString platform; + QString host; + QString gitRepo; + QString gitBranch; + + if (!extractBMAndContextFromRH( + bmcontextId, name(), database, &testCase, &testFunction, &dataTag, + &metric, &platform, &host, &gitRepo, &gitBranch, &error)) { + return xmlConvert(error); + } + + reply = QString("<reply type=\"%1\">").arg(name()); + + const qreal diffTolerance = 0.0; // 4 NOW + const qreal stabTolerance = 0; // 4 NOW + if (!appendResultHistoryToReply( + &reply, bmcontextId, "", "first", "last", diffTolerance, stabTolerance, -1)) + return xmlConvert(reply); + + reply += QString( + "<bmcontext testCase=\"%1\" testFunction=\"%2\" dataTag=\"%3\" metric=\"%4\" " + "platform=\"%5\" host=\"%6\" gitRepo=\"%7\" gitBranch=\"%8\" />") + .arg(testCase).arg(testFunction).arg(dataTag).arg(metric).arg(platform) + .arg(host).arg(gitRepo).arg(gitBranch); + + // Finalize the reply and cache it ... + cacheKey_ = Cache::instance()->nextId(); + reply += QString("<args cacheKey=\"%1\" /></reply>").arg(cacheKey_); + Cache::instance()->put(cacheKey_, reply); + + return xmlConvert(reply); +} + +void BMRequest_ASFStatsGetValues2::handleReply_HTML(const QStringList &args) const +{ + QString error; + + error = doc.elementsByTagName("reply").at(0).toElement().attributeNode("error").value(); + if (!error.isEmpty()) { + BMMisc::printHTMLErrorPage( + QString( + "failed to create details page for RH contributing to ASF stats: %1").arg(error)); + return; + } + + QStringList optValues; + + // Get style sheet ... + QString styleSheet; + if (BMMisc::getOption(args, "-stylesheet", &optValues, 1, 0)) { + styleSheet = optValues.first().trimmed(); + } else { + BMMisc::printHTMLErrorPage("-stylesheet option not found"); + return; + } + + // Get median window size ... + int medianWinSize = -1; + if (BMMisc::getOption(args, "-medianwinsize", &optValues, 1, 0)) { + bool ok; + medianWinSize = optValues.first().toInt(&ok); + if ((!ok) || (medianWinSize < 1)) { + BMMisc::printHTMLErrorPage("-medianwinsize not a positive integer"); + return; + } + } else { + BMMisc::printHTMLErrorPage("-medianwinsize option not found"); + return; + } + + // Get 'from' timestamp ... + int fromTimestamp = -1; + if (BMMisc::getOption(args, "-fromtime", &optValues, 1, 0)) { + bool ok; + fromTimestamp = optValues.first().toInt(&ok); + if ((!ok) || (fromTimestamp < 0)) { + BMMisc::printHTMLErrorPage("-fromtime not a non-negative integer"); + return; + } + } else { + BMMisc::printHTMLErrorPage("-fromtime option not found"); + return; + } + + // Get 'to' timestamp ... + int toTimestamp = -1; + if (BMMisc::getOption(args, "-totime", &optValues, 1, 0)) { + bool ok; + toTimestamp = optValues.first().toInt(&ok); + if ((!ok) || (toTimestamp < fromTimestamp)) { + BMMisc::printHTMLErrorPage(QString("-totime not >= %1").arg(fromTimestamp)); + return; + } + } else { + BMMisc::printHTMLErrorPage("-totime option not found"); + return; + } + + // *** Header *** + QString reply = QString("<html>\n<head>\n"); + reply += QString("<link rel=\"stylesheet\" type=\"text/css\" href=\"%1\" />\n").arg(styleSheet); + reply += QString("</head>\n<body>\n"); + + reply += "<span style=\"font-size:18\">ASF stats contributor</span>"; + + reply += "<br /><br /><table><tr><td style=\"background-color:#eeeeee\">"; + + reply += "This page shows a result history that contributed to the ASF stats."; + + reply += "<br /><br />"; + + reply += QString( + "Normal data points are indicated in black, whereas potential outliers " + "(filtered out by median of %1 smoothing) are indicated in red.") + .arg(medianWinSize); + + reply += "<br /><br />"; + + reply += + "The 'from' and 'to' times are indicated by dashed and solid brown vertical lines " + "respectively. The data points to be used for sampling the result history at " + "these times are indicated in the graph as an orange square and an orange circle " + "respectively. If the result history is classified as <i>stable</i>, the two samples " + "serve to further classify it as <i>regressed</i>, <i>unchanged</i>, or <i>improved</i>. " + "Otherwise the two samples are just ignored."; + + reply += "</td></tr></table>\n"; + + + // *** Plot *** + + // reply += "<br />args:<br />"; + // for (int i = 0; i < args.size(); ++i) + // reply += QString("arg %1: >%2<<br />").arg(i).arg(args.at(i)); + // reply += "<br />"; + + QDomElement argsElem = doc.elementsByTagName("args").at(0).toElement(); + + reply += "<br />\n<img src=\"bmclientwrapper?command="; + for (int i = args.indexOf("-server"); i < args.size(); ++i) + reply += QString("%1 ") + .arg((args.at(i) == "detailspage2") ? QLatin1String("plot2") : args.at(i)); + reply += QString("-cachekey %1 ").arg(argsElem.attributeNode("cacheKey").value()); + reply += "-httpheader "; + reply += "\" />\n"; + + + // *** Table of values *** + + reply += "<br />\n<br />\n"; + + reply += "<table style=\"text-align:right\">"; + + reply += QString( + "<tr>" + "<th class=\"details_table_head\">n</th>" + "<th class=\"details_table_head\">Timestamp</th>" + "<th class=\"details_table_head\">SHA-1</th>" + "<th class=\"details_table_head\">Value</th>" + "</tr>\n"); + + // Get the time series ... + QDomNodeList resultNodes = doc.elementsByTagName("result"); + QList<int> timestamps; + QList<qreal> values; + QStringList sha1s; + for (int i = 0; i < resultNodes.size(); ++i) { + bool ok; + QDomElement resultElem = resultNodes.at(i).toElement(); + timestamps += resultElem.attributeNode("timestamp").value().toInt(&ok); + Q_ASSERT(ok); + values += resultElem.attributeNode("value").value().toDouble(&ok); + Q_ASSERT(ok); + sha1s += resultElem.attributeNode("sha1").value(); + } + + ResultHistoryInfo rhInfo(-1, timestamps, values, medianWinSize, ""); + + // Get the actual data positions used for sampling at 'from' and 'to' timestamps ... + int fromPos = -1; + rhInfo.findSmoothPos(fromTimestamp, &fromPos); + int toPos = -1; + rhInfo.findSmoothPos(toTimestamp, &toPos); + + for (int i = 0; i < timestamps.size(); ++i) { + + const int i_ = resultNodes.size() - 1 - i; // reverse chronological order + reply += QString( + "<tr>" + "<td>%1</td>" + "<td>%2</td>" + "<td>%3</td>" + "<td%4>%5</td>" + "</tr>\n" + ) + .arg(i_ + 1) + .arg(timestamps.at(i_)) + .arg(sha1s.at(i_)) + .arg(rhInfo.isOutlier(i_) + ? " style=\"color:red\"" + : (((i_ == fromPos) || (i_ == toPos)) + ? " style=\"background-color:#ff944d\"" + : "" + )) + .arg(values.at(i_)); + } + + reply += "</table>\n"; + + reply += "</body></html>"; + + BMMisc::printHTMLOutput(reply); +} + +void BMRequest_ASFStatsGetValues2::handleReply_Image(const QStringList &args) const +{ + const QString error = + doc.elementsByTagName("reply").at(0).toElement().attributeNode("error").value(); + + if (!error.isEmpty()) { + BMMisc::printHTMLErrorPage( + QString( + "failed to create plot for ASF stats contributor: %1").arg(error)); + return; + } + + bool ok; + QStringList optValues; + + + // Get median window size ... + int medianWinSize = -1; + if (BMMisc::getOption(args, "-medianwinsize", &optValues, 1, 0)) { + medianWinSize = optValues.first().toInt(&ok); + if ((!ok) || (medianWinSize < 1)) { + BMMisc::printHTMLErrorPage("-medianwinsize not a positive integer"); + return; + } + } else { + BMMisc::printHTMLErrorPage("-medianwinsize option not found"); + return; + } + + // Get 'from' timestamp ... + int fromTimestamp = -1; + if (BMMisc::getOption(args, "-fromtime", &optValues, 1, 0)) { + fromTimestamp = optValues.first().toInt(&ok); + if ((!ok) || (fromTimestamp < 0)) { + BMMisc::printHTMLErrorPage("-fromtime not a non-negative integer"); + return; + } + } else { + BMMisc::printHTMLErrorPage("-fromtime option not found"); + return; + } + + // Get 'to' timestamp ... + int toTimestamp = -1; + if (BMMisc::getOption(args, "-totime", &optValues, 1, 0)) { + toTimestamp = optValues.first().toInt(&ok); + if ((!ok) || (toTimestamp < fromTimestamp)) { + BMMisc::printHTMLErrorPage(QString("-totime not >= %1").arg(fromTimestamp)); + return; + } + } else { + BMMisc::printHTMLErrorPage("-totime option not found"); + return; + } + + + QDomElement argsElem = doc.elementsByTagName("args").at(0).toElement(); + + + // Get the BM context ... + QDomNodeList bmcontextNodes = doc.elementsByTagName("bmcontext"); + QDomElement bmcontextElem = bmcontextNodes.at(0).toElement(); + const QString testCase = bmcontextElem.attributeNode("testCase").value(); + const QString testFunction = bmcontextElem.attributeNode("testFunction").value(); + const QString dataTag = bmcontextElem.attributeNode("dataTag").value(); + const QString metric = bmcontextElem.attributeNode("metric").value(); + const QString platform = bmcontextElem.attributeNode("platform").value(); + const QString host = bmcontextElem.attributeNode("host").value(); + const QString gitRepo = bmcontextElem.attributeNode("gitRepo").value(); + const QString gitBranch = bmcontextElem.attributeNode("gitBranch").value(); + + // Get the time series ... + QDomNodeList resultNodes = doc.elementsByTagName("result"); + QList<int> timestamps; + QList<qreal> values; + for (int i = 0; i < resultNodes.size(); ++i) { + bool ok; + QDomElement resultElem = resultNodes.at(i).toElement(); + timestamps += resultElem.attributeNode("timestamp").value().toInt(&ok); + Q_ASSERT(ok); + values += resultElem.attributeNode("value").value().toDouble(&ok); + Q_ASSERT(ok); + } + + QList<ResultHistoryInfo *> rhInfos; + ResultHistoryInfo *rhInfo = new ResultHistoryInfo( + -1, timestamps, values, medianWinSize, metric, platform, host, gitRepo, gitBranch, + testCase, testFunction, dataTag); + rhInfos.append(rhInfo); + + // Get the actual data positions used for sampling at 'from' and 'to' timestamps ... + int fromPos = -1; + rhInfo->findSmoothPos(fromTimestamp, &fromPos); + int toPos = -1; + rhInfo->findSmoothPos(toTimestamp, &toPos); + QList<QList<int> > extraPos; + extraPos.append(QList<int>() << fromPos << toPos); + const QList<int> extraTimestamps = QList<int>() << fromTimestamp << toTimestamp; + + const QStringList descr = QStringList() << "<no description>"; + + + // Create image ... + QString error_; + Plotter *plotter = + new HistoriesPlotter( + rhInfos, true, -1, 0, &extraPos, &extraTimestamps, &descr, false, true); + const QImage image = plotter->createImage(&error_); + + // Free dynamic memory ... + for (int i = 0; i < rhInfos.size(); ++i) + delete rhInfos.at(i); + + // Output image ... + if (!image.isNull()) { + BMMisc::printImageOutput( + image, "png", QString(), BMMisc::hasOption(args, "-httpheader")); + } else { + BMMisc::printHTMLErrorPage( + QString( + "failed to create plot for main index contributors: %1").arg(error_)); + } +} diff --git a/src/bm/bmrequest.h b/src/bm/bmrequest.h index 12d61a1..f7a5a2b 100644 --- a/src/bm/bmrequest.h +++ b/src/bm/bmrequest.h @@ -113,7 +113,7 @@ protected: const QString &testCaseFilter, const QString &testFunctionFilter, const QString &dataTagFilter, const bool leadingAnd, const QString &prefix = QString()) const; - bool appendBranchHistoryToReply( + bool appendResultHistoryToReply( QString *reply, int bmcontextId, const QString &suffix, const QString ×tamp1, const QString ×tamp2, qreal diffTolerance, int stabTolerance, int maxSize) const; @@ -308,13 +308,13 @@ public: const QString &gitRepo, const QString &gitBranch, const QString ×tamp1, const QString ×tamp2, const qreal diffTolerance, const int stabTolerance, const int maxSize, - const QString &styleSheet, const int currTimestamp, const int explicitBMContextId = -1) + const QString &styleSheet, const int currTimestamp) : testCase(testCase), testFunction(testFunction), dataTag(dataTag), metric(metric) , platform(platform), host(host), gitRepo(gitRepo), gitBranch(gitBranch) , timestamp1(timestamp1), timestamp2(timestamp2) , diffTolerance(diffTolerance), stabTolerance(stabTolerance) , maxSize(maxSize), styleSheet(styleSheet), currTimestamp(currTimestamp) - , explicitBMContextId(explicitBMContextId) {} + {} BMRequest_GetHistory(const QDomDocument &doc) : BMRequest(doc) {} private: mutable QString testCase; @@ -332,7 +332,6 @@ private: mutable int maxSize; mutable QString styleSheet; mutable int currTimestamp; - mutable int explicitBMContextId; QString name() const { return "GetHistory"; } QByteArray toRequestBuffer(QString *error); @@ -787,4 +786,26 @@ private: void handleReply_HTML(const QStringList &args) const; }; +// ### 2 B DOCUMENTED (in bmclient.html and bmproto.html) +class BMRequest_ASFStatsGetValues2 : public BMRequest +{ +public: + BMRequest_ASFStatsGetValues2( + const int bmcontextId, const QString &cacheKey) + : bmcontextId(bmcontextId), cacheKey(cacheKey) {} + BMRequest_ASFStatsGetValues2(const QDomDocument &doc) : BMRequest(doc) {} + +private: + int bmcontextId; + QString cacheKey; + + QString name() const { return "ASFStatsGetValues2"; } + QByteArray toRequestBuffer(QString *error); + QByteArray toReplyBuffer(); + void handleReply_Raw(const QStringList &args) const { Q_UNUSED(args); /* 2 B DONE! */ }; + void handleReply_JSON(const QStringList &args) const { Q_UNUSED(args); /* 2 B DONE! */ } + void handleReply_HTML(const QStringList &args) const; + void handleReply_Image(const QStringList &args) const; +}; + #endif // BMREQUEST_H diff --git a/src/bm/plotter.cpp b/src/bm/plotter.cpp index 8d3592e..8488772 100644 --- a/src/bm/plotter.cpp +++ b/src/bm/plotter.cpp @@ -696,13 +696,15 @@ bool IndexPlotter::drawScenes( HistoriesPlotter::HistoriesPlotter( const QList<ResultHistoryInfo *> &rhInfos, const bool showBenchmark, const int baseTimestamp, - const QList<int> *basePos, const QList<QList<int> > *extraPos, const QStringList *extraDescr, + const QList<int> *basePos, const QList<QList<int> > *extraPos, + const QList<int> *extraTimestamps, const QStringList *extraDescr, const bool commonValueRange, const bool showOutliers) : rhInfos(rhInfos) , showBenchmark(showBenchmark) , baseTimestamp(baseTimestamp) , basePos(basePos) , extraPos(extraPos) + , extraTimestamps(extraTimestamps) , extraDescr(extraDescr) , commonValueRange(commonValueRange) , showOutliers(showOutliers) @@ -912,6 +914,25 @@ bool HistoriesPlotter::drawScenes( scene_far->addLine(btx, ymin, btx, ymax, QPen(color, 2)); } + if (extraTimestamps) { + // Draw lines indicating extra timestamps ... + + for (int i = 0; i < extraTimestamps->size(); ++i) { + + const int t = extraTimestamps->at(i); + + if ((t < loTimestamp) || (t > hiTimestamp)) + continue; + + QColor color("#803300"); + color.setAlpha(200); + const qreal tx = xmin + (t - loTimestamp) * xfact; + scene_far->addLine( + tx, ymin, tx, ymax, + QPen(color, 2, (i == 0) ? Qt::DashLine : Qt::SolidLine)); + } + } + const qreal dpSize = 4; // Draw history curve between data points ... @@ -974,17 +995,22 @@ bool HistoriesPlotter::drawScenes( const qreal dpSize_large_2 = dpSize_large / 2; for (int i = 0; i < exPos.size(); ++i) { + + const int pos = exPos.at(i); + if ((pos < 0) || (pos >= x.size())) + continue; + QPainterPath path; if (i == 0) { path.addRect( - x.at(exPos.at(i)) - dpSize_large_2, - y.at(exPos.at(i)) - dpSize_large_2, + x.at(pos) - dpSize_large_2, + y.at(pos) - dpSize_large_2, dpSize_large, dpSize_large); } else { path.addEllipse( - x.at(exPos.at(i)) - dpSize_large_2, - y.at(exPos.at(i)) - dpSize_large_2, + x.at(pos) - dpSize_large_2, + y.at(pos) - dpSize_large_2, dpSize_large, dpSize_large); } @@ -1123,5 +1149,40 @@ bool HistoriesPlotter::drawScenes( } + if (extraTimestamps) { + + for (int i = 0; i < extraTimestamps->size(); ++i) { + + const int t = extraTimestamps->at(i); + + if ((t < loTimestamp) || (t > hiTimestamp)) + continue; + + const QString xLabel = BMMisc::compactDateString(t); + font.setPointSize(12); + + const qreal xpos = xmin + (t - loTimestamp) * xfact; + const QColor color("#803300"); + + // ... top x axis label ... + { + QGraphicsSimpleTextItem *text = scene_near->addSimpleText(xLabel, font); + text->setPos( + xpos - text->boundingRect().width() / 2, + ymin - text->boundingRect().height() + labelPad / 2); + text->setBrush(color); + } + + // ... bottom x axis label ... + { + QGraphicsSimpleTextItem *text = scene_near->addSimpleText(xLabel, font); + text->setPos( + xpos - text->boundingRect().width() / 2, + ymax + labelPad / 4); + text->setBrush(color); + } + } + } + return true; } diff --git a/src/bm/plotter.h b/src/bm/plotter.h index 8bf5056..32b194a 100644 --- a/src/bm/plotter.h +++ b/src/bm/plotter.h @@ -102,8 +102,9 @@ public: HistoriesPlotter( const QList<ResultHistoryInfo *> &rhInfos, const bool showBenchmark = false, const int baseTimestamp = -1, const QList<int> *basePos = 0, - const QList<QList<int> > *extraPos = 0, const QStringList *extraDescr = 0, - const bool commonValueRange = true, const bool showOutliers = false); + const QList<QList<int> > *extraPos = 0, const QList<int> *extraTimestamps = 0, + const QStringList *extraDescr = 0, const bool commonValueRange = true, + const bool showOutliers = false); private: QList<ResultHistoryInfo *> rhInfos; @@ -111,6 +112,7 @@ private: int baseTimestamp; const QList<int> *basePos; const QList<QList<int> > *extraPos; + const QList<int> *extraTimestamps; const QStringList *extraDescr; bool commonValueRange; bool showOutliers; diff --git a/src/bmclient/main.cpp b/src/bmclient/main.cpp index 0823d20..8c36a86 100644 --- a/src/bmclient/main.cpp +++ b/src/bmclient/main.cpp @@ -119,6 +119,9 @@ private: BMRequest * createASFStatsGetValuesRequest( const QStringList &args, QString *error, const QString &command = "asfstats get values") const; + BMRequest * createASFStatsGetValues2Request( + const QStringList &args, QString *error, + const QString &command = "asfstats get values2") const; mutable BMRequest::OutputFormat explicitOutputFormat; mutable bool useExplicitOutputFormat; BMRequest::OutputFormat outputFormat() const; @@ -798,15 +801,6 @@ BMRequest * Executor::createRequest(const QStringList &args, QString *error) con return createASFStatsGetValuesRequest(args, error); - // } else if ( - // (args.size() >= 3) && (args.at(0) == "asfstats") && (args.at(1) == "get") - // && (args.at(2) == "plot")) { - // // --- 'asfstats get plot' command --- - - // BMRequest *request = createASFStatsGetValuesRequest(args, error, "asfstats get plot"); - // setOutputFormat(BMRequest::Image); - // return request; - } else if ( (args.size() >= 3) && (args.at(0) == "asfstats") && (args.at(1) == "get") && (args.at(2) == "detailspage")) { @@ -817,6 +811,33 @@ BMRequest * Executor::createRequest(const QStringList &args, QString *error) con setOutputFormat(BMRequest::HTML); return request; + } else if ( + (args.size() >= 3) && (args.at(0) == "asfstats") && (args.at(1) == "get") + && (args.at(2) == "values2")) { + // --- 'asfstats get values2' command --- + + return createASFStatsGetValues2Request(args, error); + + } else if ( + (args.size() >= 3) && (args.at(0) == "asfstats") && (args.at(1) == "get") + && (args.at(2) == "detailspage2")) { + // --- 'asfstats get detailspage2' command --- + + BMRequest *request = + createASFStatsGetValues2Request(args, error, "asfstats get detailspage2"); + setOutputFormat(BMRequest::HTML); + return request; + + } else if ( + (args.size() >= 3) && (args.at(0) == "asfstats") && (args.at(1) == "get") + && (args.at(2) == "plot2")) { + // --- 'asfstats get plot2' command --- + + BMRequest *request = + createASFStatsGetValues2Request(args, error, "asfstats get plot2"); + setOutputFormat(BMRequest::Image); + return request; + } else if ((args.size() >= 2) && (args.at(0) == "get") && (args.at(1) == "detailspage")) { // --- 'get detailspage' command --- @@ -915,21 +936,10 @@ BMRequest * Executor::createGetHistoryRequest(const QStringList &args, QString * currTimestamp = -1; } - // Extract BM context ID (which overrides other values if specified) ... - int explicitBMContextId = -1; - if (BMMisc::getOption(args, "-bmcontextid", &values, 1, 0, error)) { - bool ok; - explicitBMContextId = values.at(0).toInt(&ok); - if ((!ok) || (explicitBMContextId < 0)) { - *error = "failed to extract stability tolerance as non-negative integer"; - return 0; - } - } - return new BMRequest_GetHistory( args.at(2), args.at(3), args.at(4), args.at(5), args.at(6), args.at(7), args.at(8), args.at(9), timeRange.first, timeRange.second, diffTolerance, - stabTolerance, maxSize, args.at(16), currTimestamp, explicitBMContextId); + stabTolerance, maxSize, args.at(16), currTimestamp); } BMRequest * Executor::createGetHistory2Request(const QStringList &args, QString *error) const @@ -1425,6 +1435,45 @@ BMRequest * Executor::createASFStatsGetValuesRequest( branchFilter, fromTimestamp, toTimestamp, diffTol, stabTol, sfTol, lfTol, maxLDTol); } +BMRequest * Executor::createASFStatsGetValues2Request( + const QStringList &args, QString *error, const QString &command) const +{ + Q_UNUSED(command); + + QStringList values; + + // Extract BM context ID ... + int bmcontextId = -1; + if (BMMisc::getOption(args, "-bmcontextid", &values, 1, 0, error)) { + bool ok; + bmcontextId = values.at(0).toInt(&ok); + if ((!ok) || (bmcontextId < 0)) { + *error = "failed to extract BM context ID as a non-negative integer"; + return 0; + } + } else { + if (error->isEmpty()) + *error = "-bmcontextid option not found"; + return 0; + } + + // Get cache key ... + QString cacheKey; + if (BMMisc::getOption(args, "-cachekey", &values, 1, 0, error)) { + cacheKey = values.at(0).trimmed(); + bool ok; + cacheKey.toInt(&ok); + if (!ok) { + *error = "failed to extract cache key as an integer"; + return 0; + } + } else if (!error->isEmpty()) { + return 0; + } + + return new BMRequest_ASFStatsGetValues2(bmcontextId, cacheKey); +} + // ### 2 B DOCUMENTED! static void splitQuotedArgs(const QString &arg_s, QStringList *args) { @@ -1730,7 +1779,7 @@ class DirectExecutor : public Executor << "get history <test case> <test function> <data tag> <metric> " - "<platform> <host> <git repo> <git branch> \\\n" + " <platform> <host> <git repo> <git branch> \\\n" " -timerange <from> <to> \\\n" " <difference tolerance> <stability tolerance> <max size> \\\n" " <style sheet> <current Unix timestamp>\n" @@ -1795,7 +1844,7 @@ class DirectExecutor : public Executor << "index get plot <SAME AS 'index get values' except that the optional \\\n" - " -httpHeader and -cachekey options are recognized>\n" + " -httpheader and -cachekey options are recognized>\n" << "index get detailspage <SAME AS 'index get plot'> except that the mandatory \\\n" @@ -1822,7 +1871,7 @@ class DirectExecutor : public Executor << "get histories plot <SAME AS 'get histories' except that the optional \\\n" - " -httpHeader and -cachekey options are recognized>\n" + " -httpheader and -cachekey options are recognized>\n" << "get histories detailspage <SAME AS 'get histories'> except that the mandatory \\\n" @@ -1838,7 +1887,7 @@ class DirectExecutor : public Executor << "get ixhistories plot <SAME AS 'get ixhistories' except that the optional \\\n" - " -httpHeader and -cachekey options are recognized>\n" + " -httpheader and -cachekey options are recognized>\n" << "get ixhistories detailspage <SAME AS 'get ixhistories'> except that the mandatory \\\n" @@ -1854,9 +1903,21 @@ class DirectExecutor : public Executor << "asfstats get detailspage <SAME AS 'asfstats get values'> except that the \\\n" - " optional -httpHeader and -cachekey options, and the mandatory \\\n" + " optional -httpheader and -cachekey options, and the mandatory \\\n" " -stylesheet option are recognized\n" + << + "asfstats get values2 -bmcontextid <...>\n" + + << + "asfstats get plot2 <SAME AS 'asfstats get detailspage2' except that the optional \\\n" + " -httpheader and -cachekey options are recognized>\n" + + << + "asfstats get detailspage2 <SAME AS 'asfstats get values2'> except that the \\\n" + " mandatory -medianwinsize, -fromtime, -totime, and -stylesheet options are \\\n" + " recognized\n" + ; qDebug() << "\nNote: the -server option may be replaced by a BMSERVER=<host>:<port> " |