diff options
Diffstat (limited to 'src/bm/bmrequest.cpp')
-rw-r--r-- | src/bm/bmrequest.cpp | 702 |
1 files changed, 581 insertions, 121 deletions
diff --git a/src/bm/bmrequest.cpp b/src/bm/bmrequest.cpp index 4da76f8..46d1bf1 100644 --- a/src/bm/bmrequest.cpp +++ b/src/bm/bmrequest.cpp @@ -104,6 +104,8 @@ BMRequest * BMRequest::create(const QByteArray &data, const QString &msgType) request = new BMRequest_GetHistories(doc); else if (type == "GetBMTree") request = new BMRequest_GetBMTree(doc); + else if (type == "GetIXHistories") + request = new BMRequest_GetIXHistories(doc); #ifdef BMDEBUG else qDebug() << "invalid request type:" << type; @@ -4873,6 +4875,7 @@ QByteArray BMRequest_IndexGetValues::toRequestBuffer(QString *) return xmlConvert(request); } +// ### OBSOLETE! void BMRequest_IndexGetValues::computeIndexValues( const QList<ResultHistoryInfo *> &rhInfos, int baseTimestamp, const QList<int> &evalTimestamps, int medianWinSize, const QList<qreal> &baseDistSums, qreal minBaseDistSum, @@ -5104,15 +5107,20 @@ void BMRequest_IndexGetValues::computeIndexValues( // if (sizes.at(j) < size050percentile) // continue; - qreal targetValue = -1.0; + int targetPos = -1; qreal distSum = -1.0; - if (rhInfo->getSmoothValue( - evalTimestamps.at(i), medianWinSize, &targetValue, false, &distSum)) { - qreal baseValue = -1.0; - ok = rhInfo->getSmoothValue(baseTimestamp, medianWinSize, &baseValue, true); + if (rhInfo->getSmoothPos( + evalTimestamps.at(i), medianWinSize, &targetPos, false, &distSum)) { + + const qreal targetVal = rhInfo->value(targetPos); + + int basePos = -1; + ok = rhInfo->getSmoothPos(baseTimestamp, medianWinSize, &basePos, true); Q_ASSERT(ok); - const qreal diff = BMMisc::log2diff(targetValue, baseValue, rhInfo->metric()); + const qreal baseVal = rhInfo->value(basePos); + + const qreal diff = BMMisc::log2diff(targetVal, baseVal, rhInfo->metric()); diffs.append(diff); contr.append(j); @@ -5167,7 +5175,6 @@ void BMRequest_IndexGetValues::computeIndexValues( fprintf(stderr, "computing index values (100%% done)\n"); } - QByteArray BMRequest_IndexGetValues::toReplyBuffer() { QDomElement argsElem = doc.elementsByTagName("args").at(0).toElement(); @@ -5278,13 +5285,9 @@ QByteArray BMRequest_IndexGetValues::toReplyBuffer() QSqlQuery *query; - - // ***************************************************************************************** - // *** PHASE 1: Build the list of result histories that will be examined for a - // contribution at each evaluation timestamp. - - // Get the BM context ID and metric for all result histories that match the - // logical OR-values. NOTE: Results measuring the "events" metric are skipped for now since + // Get the initial list of candidate result histories by getting the BM context ID and metric + // for all result histories that match the logical OR-values. + // NOTE: Results measuring the "events" metric are skipped for now since // they seem to be of little value when computing the performance index (they are // typically integers close to (and often at) zero) ... query = createQuery(); @@ -5370,15 +5373,6 @@ QByteArray BMRequest_IndexGetValues::toReplyBuffer() QList<ResultHistoryInfo *> rhInfos; - // Statistics of rejected result histories: - int nonPositiveRH = 0; // contains non-positive value - int invalidBaseRH = 0; // contains too few values before base timestamp - - QList<qreal> baseDistSums; // sums of distances of values contributing to base timestamp - qreal minBaseDistSum = -1.0; - qreal maxBaseDistSum = -1.0; - - // Loop over initial result history candidates ... for (int i = 0; i < bmcontextIds.size(); ++i) { @@ -5404,46 +5398,15 @@ QByteArray BMRequest_IndexGetValues::toReplyBuffer() } QList<int> timestamps; QList<qreal> values; - bool nonPositiveValueFound = false; while (query->next()) { timestamps += query->value(0).toInt(); - const qreal value = query->value(1).toDouble(); - if (value > 0.0) { - values += value; - } else { - nonPositiveValueFound = true; - break; - } + values += query->value(1).toDouble(); } deleteQuery(query); - if (nonPositiveValueFound) { - // Reject candidate since at least one non-positive value was found ... - ++nonPositiveRH; - continue; - } - - qreal baseDistSum; - if (!ResultHistoryInfo::findTargetPos( - timestamps, baseTimestamp, medianWinSize, 0, &baseDistSum)) { - // Reject candidate if the base timestamp is not preceded by enough values - // (and thus has a higher risk of being an outlier) ... - ++invalidBaseRH; - continue; - } - - // Accept candidate ... ResultHistoryInfo *rhInfo = new ResultHistoryInfo(bmcontextIds.at(i), timestamps, values, metrics.at(i)); rhInfos.append(rhInfo); - - baseDistSums.append(baseDistSum); - if (baseDistSums.size() == 1) { - minBaseDistSum = maxBaseDistSum = baseDistSum; - } else { - minBaseDistSum = qMin(minBaseDistSum, baseDistSum); - maxBaseDistSum = qMax(maxBaseDistSum, baseDistSum); - } } if (debugPrint) @@ -5453,16 +5416,57 @@ QByteArray BMRequest_IndexGetValues::toReplyBuffer() reply = QString("<reply type=\"%1\" >").arg(name()); + int nonPositiveRH = 0; + int baseValuePos; + if (!rhInfos.isEmpty()) { - computeIndexValues( - rhInfos, baseTimestamp, evalTimestamps, medianWinSize, baseDistSums, minBaseDistSum, - maxBaseDistSum, debugPrint, &reply); - // Free dynamic memory ... - for (int i = 0; i < rhInfos.size(); ++i) - delete rhInfos.at(i); - } + // Candidate result histories exist, so compute index values ... + IndexAlgorithm1 index( + rhInfos, baseTimestamp, evalTimestamps, medianWinSize, &nonPositiveRH); + if (!index.isValid()) { + return xmlConvert( + errorReply( + name(), QString("failed to compute index (1): %1").arg(index.invalidReason()))); + } + + QList<qreal> indexValues; + QList<int> contrCounts; + QList<QList<Index::RankedInfo> > topContr; + const int topContrLimit = 10; // ### hard-coded for now! + QString error_; + if (!index.computeValues( + &indexValues, &baseValuePos, &contrCounts, &error_, &topContr, topContrLimit)) { + return xmlConvert( + errorReply(name(), QString("failed to compute index (2): %1").arg(error_))); + } + + // Add index values to reply ... + Q_ASSERT(indexValues.size() == evalTimestamps.size()); + Q_ASSERT(indexValues.size() == contrCounts.size()); + for (int i = 0; i < indexValues.size(); ++i) { + reply += + QString("<value timestamp=\"%1\" indexValue=\"%2\" contributions=\"%3\" >") + .arg(evalTimestamps.at(i)) + .arg(indexValues.at(i)) + .arg(contrCounts.at(i)); + + for (int j = 0; j < topContr.at(i).size(); ++j) { + reply += + QString( + "<rankedInfo id=\"%1\" basePos=\"%2\" diffPos1=\"%3\" " + "diffPos2=\"%4\" descr=\"%5\" />") + .arg(topContr.at(i).at(j).bmcontextId) + .arg(topContr.at(i).at(j).basePos) + .arg(topContr.at(i).at(j).diffPos1) + .arg(topContr.at(i).at(j).diffPos2) + .arg(topContr.at(i).at(j).descr); + } + + reply += "</value>"; + } + } //---------------------------------------------------------------------------------------- @@ -5486,9 +5490,9 @@ QByteArray BMRequest_IndexGetValues::toReplyBuffer() cacheKey_ = Cache::instance()->nextId(); reply += QString( "<args baseTimestamp=\"%1\" medianWinSize=\"%2\" totalRH=\"%3\" nonPositiveRH=\"%4\" " - "invalidBaseRH=\"%5\" cacheKey=\"%6\" /></reply>") - .arg(baseTimestamp).arg(medianWinSize).arg(bmcontextIds.size()) - .arg(nonPositiveRH).arg(invalidBaseRH).arg(cacheKey_); + "baseValuePos=\"%5\" cacheKey=\"%6\" /></reply>") + .arg(baseTimestamp).arg(medianWinSize).arg(bmcontextIds.size()).arg(nonPositiveRH) + .arg(baseValuePos).arg(cacheKey_); Cache::instance()->put(cacheKey_, reply); return xmlConvert(reply); @@ -5513,6 +5517,14 @@ void BMRequest_IndexGetValues::appendToFilterTable( void BMRequest_IndexGetValues::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: error evaluating index: %1").arg(error)); + return; + } + QStringList optValues; // Get server ... @@ -5541,16 +5553,27 @@ void BMRequest_IndexGetValues::handleReply_HTML(const QStringList &args) const reply += "<span style=\"font-size:18\">Qt Performance Index</span>"; reply += "<br /><br /><table><tr><td style=\"background-color:#eeeeee\">"; - reply += "This page shows the performance index evaluated at certain timestamps "; - reply += "and for certain result histories (i.e. for certain benchmarks in certain contexts, "; - reply += "where the context represents platform etc.)."; + + reply += "This page shows the performance index for certain benchmarks in certain contexts "; + reply += "(where the context represents platform etc.)."; + reply += "<br /><br />"; - reply += "The index value at a given time shows the aggregated performance at that time "; - reply += "relative to the aggregated performance at a given base time in terms of percentage "; - reply += "of the latter. A value of 200 thus indicates a doubled performance (i.e. twice "; - reply += "the FPS or half the walltime), while 50 indicates a halved performance."; - reply += "</td></tr></table>"; + reply += "The <u>change</u> in index value between any two points in time indicates the "; + reply += "average performance increase during this time period in terms of a log2-based "; + reply += "difference. "; + reply += "An index value <u>change</u> of 1, 0, and -1 thus indicates a doubled, unchanged, "; + reply += "and halved performance respectively. "; + reply += "(The corresponding linear factors would be 2, 1, and 0.5.)."; + + reply += "<br /><br />"; + + reply += "<b>Note:</b> Individual index values bear no useful meaning when regarded in "; + reply += "isolation. The graph is however shifted vertically so that each individual index "; + reply += "value indicates the change from the index value at the given base time (the index "; + reply += "value of the latter is then 0 and 1 in the log2 and linear domain respectively)."; + + reply += "</td></tr></table>\n"; QDomElement argsElem = doc.elementsByTagName("args").at(0).toElement(); @@ -5561,7 +5584,11 @@ void BMRequest_IndexGetValues::handleReply_HTML(const QStringList &args) const // reply += QString("arg %1: >%2<<br />").arg(i).arg(args.at(i)); // reply += "<br />"; - reply += "<br />\n<img src=\"bmclientwrapper?command="; + // Image ... + reply += + "<br />\n<img usemap=\"#plotmap\" style=\"border-style:none\" " + "src=\"bmclientwrapper?command="; + for (int i = args.indexOf("-server"); i < args.size(); ++i) reply += QString("%1 ") .arg((args.at(i) == "detailspage") ? QLatin1String("plot") : args.at(i)); @@ -5569,15 +5596,96 @@ void BMRequest_IndexGetValues::handleReply_HTML(const QStringList &args) const reply += "-httpheader "; reply += "\" />\n<br />\n<br />\n"; + // Image map ... + reply += "<br />\n<map name=\"plotmap\">\n"; + bool ok; - error = doc.elementsByTagName("reply").at(0).toElement().attributeNode("error").value(); - if (!error.isEmpty()) { - reply = QString("failed to create table: error evaluating index: %1").arg(error); - BMMisc::printHTMLErrorPage(reply); + // ... get timestamps, index values, and contributions ... + QList<int> timestamps; + QList<qreal> indexValues; + QList<qreal> linearIndexValues; + QList<int> contributions; + QDomNodeList valueNodes = doc.elementsByTagName("value"); + for (int i = 0; i < valueNodes.size(); ++i) { + QDomElement valueElem = valueNodes.at(i).toElement(); + + timestamps += valueElem.attributeNode("timestamp").value().toInt(&ok); + Q_ASSERT(ok); + + const qreal indexValue = valueElem.attributeNode("indexValue").value().toDouble(&ok); + Q_ASSERT(ok); + indexValues += indexValue; + linearIndexValues += qPow(2, indexValue); + + contributions += valueElem.attributeNode("contributions").value().toInt(&ok); + Q_ASSERT(ok); + } + + // ... get point infos ... + Plotter *plotter = new IndexPlotter(timestamps, indexValues, contributions); + QList<Plotter::PointInfo> pointInfos; + if (plotter->createImage(&error, &pointInfos).isNull()) { + BMMisc::printHTMLErrorPage( + QString("failed to extract point infos: %1").arg(error)); + return; + } + Q_ASSERT(pointInfos.size() == timestamps.size()); + + // ... extract the web server from the style sheet ... + QRegExp rx("^(\\S+:\\d+)\\D+$"); + if (rx.indexIn(styleSheet) == -1) { + BMMisc::printHTMLErrorPage(QString("failed to extract web server from style sheet")); return; } + const QString webServer = rx.cap(1); + + // ... add <area> tags for valid points ... + for (int i = 0; i < pointInfos.size(); ++i) { + if (contributions.at(i) == 0) + continue; // skip invalid point + + QString url = QString("%1/cgi-bin/bmclientwrapper").arg(webServer); + url += QString("?command=-server %1").arg(server); + url += QString(" get ixhistories detailspage -stylesheet %1").arg(styleSheet); + url += QString(" -evaltimestamp %1").arg(timestamps.at(i)); + + QDomElement valueElem = valueNodes.at(i).toElement(); + + QDomNodeList rankedInfoNodes = valueElem.elementsByTagName("rankedInfo"); + if (rankedInfoNodes.size() == 0) + continue; // skip if no ranked infos were found (typically the case for for the first + // valid point) + + for (int j = 0; j < rankedInfoNodes.size(); ++j) { + QDomElement rankedInfoElem = rankedInfoNodes.at(j).toElement(); + + const int bmcontextId = rankedInfoElem.attributeNode("id").value().toInt(&ok); + Q_ASSERT(ok); + const int basePos = rankedInfoElem.attributeNode("basePos").value().toInt(&ok); + Q_ASSERT(ok); + const int diffPos1 = rankedInfoElem.attributeNode("diffPos1").value().toInt(&ok); + Q_ASSERT(ok); + const int diffPos2 = rankedInfoElem.attributeNode("diffPos2").value().toInt(&ok); + Q_ASSERT(ok); + const QString descr = rankedInfoElem.attributeNode("descr").value(); + + url += QString(" -rankedinfo %1 %2 %3 %4 '%5'") + .arg(bmcontextId).arg(basePos).arg(diffPos1).arg(diffPos2).arg(descr); + } + + QRect rect = pointInfos.at(i).rect.toAlignedRect(); + reply += QString( + "<area shape=\"rect\" coords=\"%1,%2,%3,%4\" title=\"%5\" href=\"%6\" />\n") + .arg(rect.topLeft().x()) + .arg(rect.topLeft().y()) + .arg(rect.bottomRight().x()) + .arg(rect.bottomRight().y()) + .arg(pointInfos.at(i).value) + .arg(url); + } + + reply += "</map>\n"; - bool ok; QDateTime dateTime; @@ -5637,12 +5745,7 @@ void BMRequest_IndexGetValues::handleReply_HTML(const QStringList &args) const reply += QString("<tr><td>Rejected due to existence of non-positive values:</td><td>%1</td></tr>\n") .arg(nonPositiveRH); - const int invalidBaseRH = argsElem.attributeNode("invalidBaseRH").value().toInt(&ok); - Q_ASSERT(ok); - reply += QString( - "<tr><td>Rejected due to too few values before the base time:</td><td>%1</td></tr>\n") - .arg(invalidBaseRH); - const int finalTotalRH = totalRH - (nonPositiveRH + invalidBaseRH); + const int finalTotalRH = totalRH - nonPositiveRH; Q_ASSERT(ok); reply += QString("<tr><td>Final set:</td><td>%1</td></tr>\n").arg(finalTotalRH); reply += "</table>\n"; @@ -5652,19 +5755,14 @@ void BMRequest_IndexGetValues::handleReply_HTML(const QStringList &args) const reply += "\n<br /><br /><table style=\"border: 0px\">\n"; reply += "<tr style=\"border: 0px\">\n"; - QDomNodeList valueNodes = doc.elementsByTagName("value"); - - // Left table ... + // Table 1 ... reply += "<td style=\"border: 0px\"><table>\n"; reply += "<tr><th></th><th>Date</th><th>Contributions</th></tr>\n"; for (int i = 0; i < valueNodes.size(); ++i) { const int i_ = (valueNodes.size() - 1) - i; - QDomElement valueElem = valueNodes.at(i_).toElement(); - const int timestamp = valueElem.attributeNode("timestamp").value().toInt(&ok); - Q_ASSERT(ok); - const int contributions = valueElem.attributeNode("contributions").value().toInt(&ok); - Q_ASSERT(ok); + const int timestamp = timestamps.at(i_); + const int contributions_ = contributions.at(i_); const QString style = QString("%1") .arg((timestamp >= baseTimestamp) @@ -5677,23 +5775,21 @@ void BMRequest_IndexGetValues::handleReply_HTML(const QStringList &args) const reply += QString("<td class=\"%1\">%2</td>").arg(style).arg(dateTime.toString()); reply += QString("<td class=\"%1\">%2%3</td>") .arg(style) - .arg(contributions) + .arg(contributions_) .arg((finalTotalRH == 0) ? QString() - : QString(" (%1%)").arg(100 * (qreal(contributions) / finalTotalRH), 0, 'f', 2)); + : QString(" (%1%)").arg(100 * (qreal(contributions_) / finalTotalRH), 0, 'f', 2)); reply += "</tr>\n"; } reply += "</td></table>\n"; - // Middle table ... + // Table 2 ... reply += "<td style=\"border: 0px\"><table>\n"; reply += "<tr><th>Timestamp</th></tr>\n"; for (int i = 0; i < valueNodes.size(); ++i) { const int i_ = (valueNodes.size() - 1) - i; - QDomElement valueElem = valueNodes.at(i_).toElement(); - const int timestamp = valueElem.attributeNode("timestamp").value().toInt(&ok); - Q_ASSERT(ok); + const int timestamp = timestamps.at(i_); const QString style = QString("%1") .arg((timestamp >= baseTimestamp) @@ -5706,28 +5802,29 @@ void BMRequest_IndexGetValues::handleReply_HTML(const QStringList &args) const } reply += "</td></table>\n"; - // Right table ... + const int baseValuePos = argsElem.attributeNode("baseValuePos").value().toInt(&ok); + Q_ASSERT(ok); + const qreal baseValue = indexValues.at(baseValuePos); + const qreal linearBaseValue = qPow(2, baseValue); + + // Table 3 (log2 base diff factor) ... reply += "<td style=\"border: 0px\"><table>\n"; - reply += "<tr><th>Value</th></tr>\n"; + reply += "<tr><th>Base diff (log2)</th></tr>\n"; for (int i = 0; i < valueNodes.size(); ++i) { const int i_ = (valueNodes.size() - 1) - i; - QDomElement valueElem = valueNodes.at(i_).toElement(); - const qreal indexValue = valueElem.attributeNode("indexValue").value().toDouble(&ok); - Q_ASSERT(ok); - const qreal powIndexValue = 100 * qPow(2, indexValue); - const int contributions = valueElem.attributeNode("contributions").value().toInt(&ok); - Q_ASSERT(ok); + const qreal indexValue = indexValues.at(i_) - baseValue; + const int contributions_ = contributions.at(i_); const QString style = "index_table"; reply += "<tr>"; - if (contributions > 0) { + if (contributions_ > 0) { reply += QString( "<td class=\"%1\" style=\"background-color:%2\">%3</td>") .arg(style) .arg(BMMisc::redToGreenColor(indexValue, 0.01, 0.5, true)) - .arg(powIndexValue); + .arg(indexValue); } else { reply += QString( "<td class=\"%1\" style=\"color:red; background-color:white\">n/a</td>").arg(style); @@ -5736,6 +5833,32 @@ void BMRequest_IndexGetValues::handleReply_HTML(const QStringList &args) const } reply += "</td></table>\n"; + // Table 4 (linear base diff factor) ... + reply += "<td style=\"border: 0px\"><table>\n"; + reply += "<tr><th>Base diff (linear)</th></tr>\n"; + + for (int i = 0; i < valueNodes.size(); ++i) { + const int i_ = (valueNodes.size() - 1) - i; + const qreal indexValue = indexValues.at(i_) - baseValue; + const qreal linearIndexValue = linearIndexValues.at(i_) - linearBaseValue; + const int contributions_ = contributions.at(i_); + + const QString style = "index_table"; + + reply += "<tr>"; + if (contributions_ > 0) { + reply += QString( + "<td class=\"%1\" style=\"background-color:%2\">%3</td>") + .arg(style) + .arg(BMMisc::redToGreenColor(indexValue, 0.01, 0.5, true)) + .arg(linearIndexValue); + } else { + reply += QString( + "<td class=\"%1\" style=\"color:red; background-color:white\">n/a</td>").arg(style); + } + reply += "</tr>\n"; + } + reply += "</td></table>\n"; reply += "</table>\n"; @@ -5758,6 +5881,8 @@ void BMRequest_IndexGetValues::handleReply_Image(const QStringList &args) const const int baseTimestamp = argsElem.attributeNode("baseTimestamp").value().toInt(&ok); Q_ASSERT(ok); + const int baseValuePos = argsElem.attributeNode("baseValuePos").value().toInt(&ok); + Q_ASSERT(ok); // Get timestamps, index values, and contributions ... QList<int> timestamps; @@ -5772,7 +5897,7 @@ void BMRequest_IndexGetValues::handleReply_Image(const QStringList &args) const const qreal indexValue = valueElem.attributeNode("indexValue").value().toDouble(&ok); Q_ASSERT(ok); - indexValues += 100 * qPow(2, indexValue); + indexValues += indexValue; contributions += valueElem.attributeNode("contributions").value().toInt(&ok); Q_ASSERT(ok); @@ -5780,7 +5905,8 @@ void BMRequest_IndexGetValues::handleReply_Image(const QStringList &args) const // Create the plot ... QString error_; - Plotter *plotter = new IndexPlotter(timestamps, indexValues, contributions, baseTimestamp); + Plotter *plotter = + new IndexPlotter(timestamps, indexValues, contributions, baseTimestamp, baseValuePos); const QImage image = plotter->createImage(&error_); if (!image.isNull()) { BMMisc::printImageOutput( @@ -6501,9 +6627,15 @@ QByteArray BMRequest_GetHistories::toReplyBuffer() void BMRequest_GetHistories::handleReply_HTML(const QStringList &args) const { - // 2 B DONE! - QString error; + + error = doc.elementsByTagName("reply").at(0).toElement().attributeNode("error").value(); + if (!error.isEmpty()) { + BMMisc::printHTMLErrorPage( + QString("failed to create details page: error getting histories: %1").arg(error)); + return; + } + QStringList optValues; // Get server ... @@ -6578,14 +6710,6 @@ void BMRequest_GetHistories::handleReply_HTML(const QStringList &args) const reply += "-httpheader "; reply += "\" />\n<br />\n<br />\n"; - - error = doc.elementsByTagName("reply").at(0).toElement().attributeNode("error").value(); - if (!error.isEmpty()) { - reply = QString("failed to create details page: error getting histories: %1").arg(error); - BMMisc::printHTMLErrorPage(reply); - return; - } - reply += "</body></html>"; BMMisc::printHTMLOutput(reply); @@ -6824,3 +6948,339 @@ void BMRequest_GetBMTree::handleReply_JSON(const QStringList &args) const BMMisc::printJSONOutput(reply); } + + +// --- GetIXHistories --- +QByteArray BMRequest_GetIXHistories::toRequestBuffer(QString *) +{ + QString request = + QString( + "<request type=\"%1\"><args evalTimestamp=\"%2\" cacheKey=\"%3\" />") + .arg(name()).arg(evalTimestamp).arg(cacheKey); + + for (int i = 0; i < rankedInfos.size(); ++i) + request += QString( + "<rankedInfo id=\"%1\" basePos=\"%2\" diffPos1=\"%3\" diffPos2=\"%4\" descr=\"%5\" />") + .arg(rankedInfos.at(i).bmcontextId) + .arg(rankedInfos.at(i).basePos) + .arg(rankedInfos.at(i).diffPos1) + .arg(rankedInfos.at(i).diffPos2) + .arg(rankedInfos.at(i).descr); + + request += "</request>"; + + return xmlConvert(request); +} + +QByteArray BMRequest_GetIXHistories::toReplyBuffer() +{ + QDomElement argsElem = doc.elementsByTagName("args").at(0).toElement(); + + QString error; + QString reply; + bool ok; + + // 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 ... + + // Get eval timestamp ... + evalTimestamp = argsElem.attributeNode("evalTimestamp").value().toInt(&ok); + Q_ASSERT(ok); + + // Get ranked infos ... + QDomNodeList rankedInfoNodes = doc.elementsByTagName("rankedInfo"); + for (int i = 0; i < rankedInfoNodes.size(); ++i) { + QDomElement rankedInfoElem = rankedInfoNodes.at(i).toElement(); + const int bmcontextId = rankedInfoElem.attributeNode("id").value().toInt(&ok); + Q_ASSERT(ok); + const int basePos = rankedInfoElem.attributeNode("basePos").value().toInt(&ok); + Q_ASSERT(ok); + const int diffPos1 = rankedInfoElem.attributeNode("diffPos1").value().toInt(&ok); + Q_ASSERT(ok); + const int diffPos2 = rankedInfoElem.attributeNode("diffPos2").value().toInt(&ok); + Q_ASSERT(ok); + const QString descr = rankedInfoElem.attributeNode("descr").value(); + + rankedInfos.append(Index::RankedInfo(bmcontextId, basePos, diffPos1, diffPos2, descr)); + } + + QSqlQuery *query; + + reply = QString("<reply type=\"%1\" >").arg(name()); + + // Add result histories to reply ... + for (int i = 0; i < rankedInfos.size(); ++i) { + + // Get meta info ... + query = createQuery(); + if (!query->exec( + QString( + "SELECT testCase, testFunction, dataTag, metric.name, platform.name, " + " host.name, branch.gitRepo, branch.gitBranch " + " FROM bmcontext, benchmark, context, metric, platform, host, branch" + " WHERE benchmarkid = benchmark.id" + " AND contextId = context.id" + " AND metricId = metric.id" + " AND platformId = platform.id" + " AND hostId = host.id" + " AND branchId = branch.id" + " AND bmcontext.id = %1;") + .arg(rankedInfos.at(i).bmcontextId))) { + deleteQuery(query); + return xmlConvert( + errorReply( + *query, name(), "failed to get result history meta info (exec() failed)")); + } + QString testCase; + QString testFunction; + QString dataTag; + QString metric; + QString platform; + QString host; + QString gitRepo; + QString gitBranch; + while (query->next()) { + testCase = query->value(0).toString(); + testFunction = query->value(1).toString(); + dataTag = query->value(2).toString(); + metric = query->value(3).toString(); + platform = query->value(4).toString(); + host = query->value(5).toString(); + gitRepo = query->value(6).toString(); + gitBranch = query->value(7).toString(); + } + deleteQuery(query); + + reply += QString( + "<resultHistory testCase=\"%1\" testFunction=\"%2\" dataTag=\"%3\" metric=\"%4\" " + "platform=\"%5\" host=\"%6\" gitRepo=\"%7\" gitBranch=\"%8\" basePos=\"%9\" " + "diffPos1=\"%10\" diffPos2=\"%11\" descr=\"%12\" >") + .arg(testCase) + .arg(testFunction) + .arg(dataTag) + .arg(metric) + .arg(platform) + .arg(host) + .arg(gitRepo) + .arg(gitBranch) + .arg(rankedInfos.at(i).basePos) + .arg(rankedInfos.at(i).diffPos1) + .arg(rankedInfos.at(i).diffPos2) + .arg(rankedInfos.at(i).descr); + + // Get time series ... + query = createQuery(); + if (!query->exec( + QString( + "SELECT timestamp, value FROM result " + "WHERE bmcontextId=%1 " + "ORDER BY timestamp ASC;") + .arg(rankedInfos.at(i).bmcontextId))) { + deleteQuery(query); + return xmlConvert( + errorReply(*query, name(), "failed to get time series (exec() failed)")); + } + QList<int> timestamps; + QList<qreal> values; + while (query->next()) { + timestamps += query->value(0).toInt(); + values += query->value(1).toDouble(); + } + deleteQuery(query); + + for (int j = 0; j < timestamps.size(); ++j) + reply += QString("<val t=\"%1\" v=\"%2\" />").arg(timestamps.at(j)).arg(values.at(j)); + + reply += "</resultHistory>"; + } + + // Finalize the reply and cache it ... + cacheKey_ = Cache::instance()->nextId(); + reply += QString("<args evalTimestamp=\"%1\" cacheKey=\"%2\" /></reply>") + .arg(evalTimestamp) + .arg(cacheKey_); + Cache::instance()->put(cacheKey_, reply); + + return xmlConvert(reply); +} + +void BMRequest_GetIXHistories::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 main index contributors: %1").arg(error)); + return; + } + + QStringList optValues; + + // Get server ... + QString server; + if (BMMisc::getOption(args, "-server", &optValues, 1, 0, &error)) { + server = optValues.first().trimmed(); + } else { + BMMisc::printHTMLErrorPage("-server option not found"); + return; + } + + // Get style sheet ... + QString styleSheet; + if (BMMisc::getOption(args, "-stylesheet", &optValues, 1, 0, &error)) { + styleSheet = optValues.first().trimmed(); + } else { + BMMisc::printHTMLErrorPage("-stylesheet option not found"); + return; + } + + QDomElement argsElem = doc.elementsByTagName("args").at(0).toElement(); + + bool ok; + + // Get eval timestamp ... + evalTimestamp = argsElem.attributeNode("evalTimestamp").value().toInt(&ok); + Q_ASSERT(ok); + QDateTime evalDateTime; + evalDateTime.setTime_t(evalTimestamp); + + // *** 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\">Main Index Value Contributors</span>"; + + reply += "<br /><br /><table><tr><td style=\"background-color:#eeeeee\">"; + reply += QString( + "This page shows the %1 result histories that contributed most significantly ") + .arg(doc.elementsByTagName("resultHistory").size()); + reply += QString("to the index value at timestamp %1 (%2)") + .arg(evalTimestamp).arg(evalDateTime.toString()); + 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 />"; + + reply += "<br />\n<img src=\"bmclientwrapper?command="; + for (int i = args.indexOf("-server"); i < args.size(); ++i) + reply += QString("%1 ") + .arg((args.at(i) == "detailspage") ? QLatin1String("plot") : args.at(i)); + reply += QString("-cachekey %1 ").arg(argsElem.attributeNode("cacheKey").value()); + reply += "-httpheader "; + reply += "\" />\n<br />\n<br />\n"; + + + reply += "</body></html>"; + + BMMisc::printHTMLOutput(reply); +} + +void BMRequest_GetIXHistories::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 main index contributors: %1").arg(error)); + return; + } + + QDomElement argsElem = doc.elementsByTagName("args").at(0).toElement(); + + bool ok; + + // Get eval timestamp ... + evalTimestamp = argsElem.attributeNode("evalTimestamp").value().toInt(&ok); + Q_ASSERT(ok); + + QList<ResultHistoryInfo *> rhInfos; + QList<int> basePos; + QList<QList<int> > extraPos; + QStringList descr; + + // Loop over result histories ... + QDomNodeList rhNodes = doc.elementsByTagName("resultHistory"); + for (int i = 0; i < rhNodes.size(); ++i) { + QDomElement rhElem = rhNodes.at(i).toElement(); + + // Get the context ... + const QString metric = rhElem.attributeNode("metric").value(); + const QString platform = rhElem.attributeNode("platform").value(); + const QString host = rhElem.attributeNode("host").value(); + const QString gitRepo = rhElem.attributeNode("gitRepo").value(); + const QString gitBranch = rhElem.attributeNode("gitBranch").value(); + + // Get the benchmark ... + const QString testCase = rhElem.attributeNode("testCase").value(); + const QString testFunction = rhElem.attributeNode("testFunction").value(); + const QString dataTag = rhElem.attributeNode("dataTag").value(); + + // Get the time series ... + QList<int> timestamps; + QList<qreal> values; + QDomNodeList valueNodes = rhElem.elementsByTagName("val"); + for (int j = 0; j < valueNodes.size(); ++j) { + bool ok; + QDomElement valueElem = valueNodes.at(j).toElement(); + timestamps += valueElem.attributeNode("t").value().toInt(&ok); + Q_ASSERT(ok); + values += valueElem.attributeNode("v").value().toDouble(&ok); + Q_ASSERT(ok); + } + + rhInfos.append( + new ResultHistoryInfo( + -1, timestamps, values, metric, platform, host, gitRepo, gitBranch, testCase, + testFunction, dataTag)); + + // Get the base- and diff positions and description ... + const int basePos_ = rhElem.attributeNode("basePos").value().toInt(&ok); + Q_ASSERT(ok); + const int diffPos1 = rhElem.attributeNode("diffPos1").value().toInt(&ok); + Q_ASSERT(ok); + const int diffPos2 = rhElem.attributeNode("diffPos2").value().toInt(&ok); + Q_ASSERT(ok); + + basePos.append(basePos_); + extraPos.append(QList<int>() << diffPos1 << diffPos2); + + descr.append(rhElem.attributeNode("descr").value()); + } + + // Create image ... + QString error_; + Plotter *plotter = + new HistoriesPlotter(rhInfos, true, evalTimestamp, &basePos, &extraPos, &descr); + 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_)); + } +} |