diff options
author | jasplin <qt-info@nokia.com> | 2010-04-12 15:29:22 +0200 |
---|---|---|
committer | jasplin <qt-info@nokia.com> | 2010-04-12 15:29:22 +0200 |
commit | 842a970eb3b085b83df134166606fe04b37032d2 (patch) | |
tree | 6e0818a7a196c464c774c6b11dabbf00bdca43cb | |
parent | a62277ff1c05b222a42d0dd670443f92640df655 (diff) |
Added cross-context comparison of a given benchmark.
This feature allows the user to select a benchmark
(i.e. a test case / test function / data tag combination)
and generate a plot of all the result histories generated
by this benchmark in different contexts.
This is is useful for manual correlation analysis (e.g.
a certain change in one context might also occur in
other contexts).
-rw-r--r-- | src/bm/bmmisc.cpp | 9 | ||||
-rw-r--r-- | src/bm/bmmisc.h | 1 | ||||
-rw-r--r-- | src/bm/bmrequest.cpp | 465 | ||||
-rw-r--r-- | src/bm/bmrequest.h | 38 | ||||
-rw-r--r-- | src/bm/plotter.cpp | 310 | ||||
-rw-r--r-- | src/bm/plotter.h | 21 | ||||
-rw-r--r-- | src/bm/resulthistoryinfo.h | 25 | ||||
-rw-r--r-- | src/bmclient/main.cpp | 98 | ||||
-rw-r--r-- | src/bmweb/bmcomparatorsection.js | 240 | ||||
-rw-r--r-- | src/bmweb/global.js | 23 | ||||
-rw-r--r-- | src/bmweb/help.html | 8 | ||||
-rw-r--r-- | src/bmweb/index.html | 2 |
12 files changed, 1222 insertions, 18 deletions
diff --git a/src/bm/bmmisc.cpp b/src/bm/bmmisc.cpp index 4fda06b..5ab7404 100644 --- a/src/bm/bmmisc.cpp +++ b/src/bm/bmmisc.cpp @@ -279,7 +279,7 @@ QString BMMisc::diffColor(qreal diff, bool higherIsBetter) bool BMMisc::lowerIsBetter(const QString &metric) { - if (metric == "FramesPerSeconds") // ### More to be added ... + if (metric == "FramesPerSecond") // ### More to be added ... return false; return true; } @@ -311,3 +311,10 @@ bool BMMisc::normalize(QList<qreal> &v) } return true; } + +QString BMMisc::compactDateString(int timestamp) +{ + QDateTime dateTime; + dateTime.setTime_t(timestamp); + return dateTime.toString("dd MMM yyyy"); +} diff --git a/src/bm/bmmisc.h b/src/bm/bmmisc.h index 52d0dca..a3d711a 100644 --- a/src/bm/bmmisc.h +++ b/src/bm/bmmisc.h @@ -68,6 +68,7 @@ public: static qreal log2val(qreal val, const QString &metric); static qreal log2diff(qreal val1, qreal val2, const QString &metric); static bool normalize(QList<qreal> &v); + static QString compactDateString(int timestamp); }; #endif // BMMISC_H diff --git a/src/bm/bmrequest.cpp b/src/bm/bmrequest.cpp index fffcb34..4da76f8 100644 --- a/src/bm/bmrequest.cpp +++ b/src/bm/bmrequest.cpp @@ -100,6 +100,10 @@ BMRequest * BMRequest::create(const QByteArray &data, const QString &msgType) request = new BMRequest_IndexPutConfig(doc); else if (type == "IndexDeleteConfig") request = new BMRequest_IndexDeleteConfig(doc); + else if (type == "GetHistories") + request = new BMRequest_GetHistories(doc); + else if (type == "GetBMTree") + request = new BMRequest_GetBMTree(doc); #ifdef BMDEBUG else qDebug() << "invalid request type:" << type; @@ -5414,7 +5418,7 @@ QByteArray BMRequest_IndexGetValues::toReplyBuffer() deleteQuery(query); if (nonPositiveValueFound) { - // Reject candidate if at least one non-positive is found ... + // Reject candidate since at least one non-positive value was found ... ++nonPositiveRH; continue; } @@ -5430,7 +5434,7 @@ QByteArray BMRequest_IndexGetValues::toReplyBuffer() // Accept candidate ... ResultHistoryInfo *rhInfo = - new ResultHistoryInfo(bmcontextIds.at(i), metrics.at(i), timestamps, values); + new ResultHistoryInfo(bmcontextIds.at(i), timestamps, values, metrics.at(i)); rhInfos.append(rhInfo); baseDistSums.append(baseDistSum); @@ -6363,3 +6367,460 @@ void BMRequest_IndexDeleteConfig::handleReply_JSON(const QStringList &args) cons BMMisc::printJSONOutput(reply); } + + +// --- GetHistories --- +QByteArray BMRequest_GetHistories::toRequestBuffer(QString *) +{ + const QString request = + QString( + "<request type=\"%1\"><args testCase=\"%2\" testFunction=\"%3\" dataTag=\"%4\" " + "cacheKey=\"%5\" /></request>") + .arg(name()).arg(testCase).arg(testFunction).arg(dataTag).arg(cacheKey); + return xmlConvert(request); +} + +QByteArray BMRequest_GetHistories::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 benchmark ... + testCase = argsElem.attributeNode("testCase").value(); + testFunction = argsElem.attributeNode("testFunction").value(); + dataTag = argsElem.attributeNode("dataTag").value(); + + QSqlQuery *query; + + // Get the BM context IDs and contexts for this benchmark ... + query = createQuery(); + if (!query->exec( + QString( + "SELECT bmcontext.id, 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 testCase = '%1' " + " AND testFunction = '%2' " + " AND dataTag = '%3';") + .arg(testCase) + .arg(testFunction) + .arg(dataTag))) { + deleteQuery(query); + return xmlConvert( + errorReply(*query, name(), "failed to get bmcontext IDs (exec() failed)")); + } + QList<int> bmcontextIds; + QStringList metrics; + QStringList platforms; + QStringList hosts; + QStringList gitRepos; + QStringList gitBranches; + while (query->next()) { + bmcontextIds += query->value(0).toInt(); + metrics += query->value(1).toString(); + platforms += query->value(2).toString(); + hosts += query->value(3).toString(); + gitRepos += query->value(4).toString(); + gitBranches += query->value(5).toString(); + } + deleteQuery(query); + + if (bmcontextIds.isEmpty()) + return xmlConvert(errorReply(name(), "no result histories found")); + + + //------------------------------------------------------------------------------ + + reply = QString("<reply type=\"%1\" >").arg(name()); + + // Add result histories to reply ... + for (int i = 0; i < bmcontextIds.size(); ++i) { + + reply += QString( + "<resultHistory metric=\"%1\" platform=\"%2\" host=\"%3\" gitRepo=\"%4\" " + "gitBranch=\"%5\" >") + .arg(metrics.at(i)) + .arg(platforms.at(i)) + .arg(hosts.at(i)) + .arg(gitRepos.at(i)) + .arg(gitBranches.at(i)); + + query = createQuery(); + if (!query->exec( + QString( + "SELECT timestamp, value FROM result " + "WHERE bmcontextId=%1 " + "ORDER BY timestamp ASC;") + .arg(bmcontextIds.at(i)))) { + 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 testCase=\"%1\" testFunction=\"%2\" dataTag=\"%3\" cacheKey=\"%4\" /></reply>") + .arg(testCase).arg(testFunction).arg(dataTag).arg(cacheKey_); + Cache::instance()->put(cacheKey_, reply); + + return xmlConvert(reply); +} + +void BMRequest_GetHistories::handleReply_HTML(const QStringList &args) const +{ + // 2 B DONE! + + QString error; + 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; + } + + // *** 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\">Benchmark Cross-Context Comparison</span>"; + + reply += "<br /><br /><table><tr><td style=\"background-color:#eeeeee\">"; + reply += "This page compares the result histories of a particular benchmark in "; + reply += "all available contexts."; + reply += "</td></tr></table>\n"; + + QDomElement argsElem = doc.elementsByTagName("args").at(0).toElement(); + + reply += "<br />"; + reply += QString( + "<table>\n" + + "<tr>\n" + "<td class=\"context_table_name\" >Test Case:</td>" + "<td class=\"context_table_bmark\" >%1</td>" + "</tr>\n" + + "<tr>\n" + "<td class=\"context_table_name\" >Test Function:</td>" + "<td class=\"context_table_bmark\" >%2</td>" + "</tr>\n" + + "<tr>\n" + "<td class=\"context_table_name\" >Data Tag:</td>" + "<td class=\"context_table_bmark\" >%3</td>" + "</tr>\n" + + "</table>\n") + .arg(argsElem.attributeNode("testCase").value()) + .arg(argsElem.attributeNode("testFunction").value()) + .arg(argsElem.attributeNode("dataTag").value()) + ; + + // *** 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"; + + + 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); +} + +void BMRequest_GetHistories::handleReply_Image(const QStringList &args) const +{ + const QString error = + doc.elementsByTagName("reply").at(0).toElement().attributeNode("error").value(); + + if (error.isEmpty()) { + + QList<ResultHistoryInfo *> rhInfos; + + // Get the time series and context for each result history ... + + // 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 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)); + } + + // Create image ... + QString error_; + Plotter *plotter = new HistoriesPlotter(rhInfos); + 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 histories plot: %1").arg(error_)); + } + + } else { + BMMisc::printHTMLErrorPage(QString("failed to get histories: %1").arg(error)); + } +} + + +// --- GetBMTree --- +QByteArray BMRequest_GetBMTree::toRequestBuffer(QString *) +{ + const QString request = QString("<request type=\"%1\" />").arg(name()); + return xmlConvert(request); +} + +struct TFInfo +{ + QString name; + TFInfo(const QString &name) : name(name) {} +}; + +struct TCInfo +{ + QList<TFInfo *> tfInfos; + QString name; + TCInfo(const QString &name) : name(name) {} + ~TCInfo() { + for (int i = 0; i < tfInfos.size(); ++i) + delete tfInfos.at(i); + } +}; + +struct TCInfos +{ + QList<TCInfo *> tcInfos; + ~TCInfos() { + for (int i = 0; i < tcInfos.size(); ++i) + delete tcInfos.at(i); + } +}; + +QByteArray BMRequest_GetBMTree::toReplyBuffer() +{ + QString reply = QString("<reply type=\"%1\">").arg(name()); + QSqlQuery *query; + + // Get all test cases ... + query = createQuery(); + if (!query->exec("SELECT DISTINCT testCase FROM benchmark ORDER BY testCase ASC;")) { + reply = errorReply(*query, name(), QString("failed to get test cases (exec() failed)")); + deleteQuery(query); + return xmlConvert(reply); + } + + TCInfos tcInfos_; + while (query->next()) { + TCInfo *tcInfo = new TCInfo(query->value(0).toString()); + tcInfos_.tcInfos.append(tcInfo); + } + deleteQuery(query); + + // Get all test functions for each test case ... + for (int i = 0; i < tcInfos_.tcInfos.size(); ++i) { + query = createQuery(); + if (!query->exec( + QString( + "SELECT DISTINCT testFunction FROM benchmark" + " WHERE testCase = '%1'" + " ORDER BY testFunction ASC;") + .arg(tcInfos_.tcInfos.at(i)->name))) { + reply = errorReply( + *query, name(), QString("failed to get test functions (exec() failed)")); + deleteQuery(query); + return xmlConvert(reply); + } + while (query->next()) { + TFInfo *tfInfo = new TFInfo(query->value(0).toString()); + tcInfos_.tcInfos.at(i)->tfInfos.append(tfInfo); + } + deleteQuery(query); + } + + // Get all data tags for each test function in each test case and output + // hierarchical structure to reply ... + for (int i = 0; i < tcInfos_.tcInfos.size(); ++i) { + + reply += QString("<tc n=\"%1\">").arg(tcInfos_.tcInfos.at(i)->name); + + for (int j = 0; j < tcInfos_.tcInfos.at(i)->tfInfos.size(); ++j) { + + reply += QString("<tf n=\"%1\">").arg(tcInfos_.tcInfos.at(i)->tfInfos.at(j)->name); + + query = createQuery(); + if (!query->exec( + QString( + "SELECT DISTINCT dataTag FROM benchmark" + " WHERE testCase = '%1'" + " AND testFunction = '%2'" + " ORDER BY dataTag ASC;") + .arg(tcInfos_.tcInfos.at(i)->name) + .arg(tcInfos_.tcInfos.at(i)->tfInfos.at(j)->name))) { + reply = errorReply( + *query, name(), QString("failed to get data tags (exec() failed)")); + deleteQuery(query); + return xmlConvert(reply); + } + while (query->next()) { + reply += QString("<dt n=\"%1\" />").arg(query->value(0).toString()); + } + deleteQuery(query); + + reply += "</tf>"; + } + + reply += "</tc>"; + } + + reply += "</reply>"; + + return xmlConvert(reply); +} + +void BMRequest_GetBMTree::handleReply_Raw(const QStringList &args) const +{ + printf("NOTE: raw output not supported yet; printing JSON output for now:\n\n"); + handleReply_JSON(args); +} + +void BMRequest_GetBMTree::handleReply_JSON(const QStringList &args) const +{ + Q_UNUSED(args); + + const QString error = + doc.elementsByTagName("reply").at(0).toElement().attributeNode("error").value(); + + QString reply; + + if (error.isEmpty()) { + + reply = QString("{\n\"testCases\":\n[\n"); + + QDomNodeList testCaseNodes = doc.elementsByTagName("tc"); + for (int i = 0; i < testCaseNodes.size(); ++i) { + QDomElement testCaseElem = testCaseNodes.at(i).toElement(); + reply += QString("%1{\"name\": \"%2\",\n\"testFunctions\":\n[\n") + .arg((i == 0) ? "" : ",\n") + .arg(testCaseElem.attributeNode("n").value()); + + QDomNodeList testFunctionNodes = testCaseElem.elementsByTagName("tf"); + for (int j = 0; j < testFunctionNodes.size(); ++j) { + QDomElement testFunctionElem = testFunctionNodes.at(j).toElement(); + reply += QString("%1{\"name\": \"%2\",\n\"dataTags\":\n[\n") + .arg((j == 0) ? "" : ",\n") + .arg(testFunctionElem.attributeNode("n").value()); + + QDomNodeList dataTagNodes = testFunctionElem.elementsByTagName("dt"); + for (int k = 0; k < dataTagNodes.size(); ++k) { + QDomElement dataTagElem = dataTagNodes.at(k).toElement(); + reply += QString("%1\"%2\"") + .arg((k == 0) ? "" : ",\n") + .arg(dataTagElem.attributeNode("n").value()); + } + + reply += "\n]\n}"; + } + + reply += "\n]\n}"; + } + + reply += "\n]\n}"; + + } else { + reply = QString("{\"error\": \"error getting benchmark tree: %1\"\n}").arg(error); + } + + BMMisc::printJSONOutput(reply); +} diff --git a/src/bm/bmrequest.h b/src/bm/bmrequest.h index 1d2c40f..a769e0f 100644 --- a/src/bm/bmrequest.h +++ b/src/bm/bmrequest.h @@ -692,4 +692,42 @@ private: void handleReply_JSON(const QStringList &args) const; }; +// ### 2 B DOCUMENTED (in bmclient.html and bmproto.html) +class BMRequest_GetHistories : public BMRequest +{ +public: + BMRequest_GetHistories( + const QString &testCase, const QString &testFunction, const QString &dataTag, + const QString &cacheKey) + : testCase(testCase), testFunction(testFunction), dataTag(dataTag), cacheKey(cacheKey) {} + BMRequest_GetHistories(const QDomDocument &doc) : BMRequest(doc) {} +private: + QString testCase; + QString testFunction; + QString dataTag; + QString cacheKey; + + QString name() const { return "GetHistories"; } + 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; +}; + +// ### 2 B DOCUMENTED (in bmclient.html and bmproto.html) +class BMRequest_GetBMTree : public BMRequest +{ +public: + BMRequest_GetBMTree() {} + BMRequest_GetBMTree(const QDomDocument &doc) : BMRequest(doc) {} +private: + QString name() const { return "GetBMTree"; } + QByteArray toRequestBuffer(QString *error); + QByteArray toReplyBuffer(); + void handleReply_Raw(const QStringList &args) const; + void handleReply_JSON(const QStringList &args) const; +}; + #endif // BMREQUEST_H diff --git a/src/bm/plotter.cpp b/src/bm/plotter.cpp index 180827a..d2e0ac4 100644 --- a/src/bm/plotter.cpp +++ b/src/bm/plotter.cpp @@ -79,7 +79,7 @@ QRectF ResultHistoryPlotter::sceneRect() const // ### 2 B DOCUMENTED! // ### 2 B DONE: Maybe factor out parts that this function has in common with -// IndexPlotter::drawScenes() +// other *::drawScenes() implementations bool ResultHistoryPlotter::drawScenes( QGraphicsScene *scene_far, QGraphicsScene *scene_mid_aa, QGraphicsScene *scene_near, QString *error) const @@ -103,7 +103,7 @@ bool ResultHistoryPlotter::drawScenes( const qreal xmax = width - pad_right; const qreal xdefault = 0.5 * (xmin + xmax); const qreal ymin = pad_top; - const qreal ymax = height - pad_bottom; // ### specific + const qreal ymax = height - pad_bottom; const qreal ydefault = 0.5 * (ymin + ymax); qreal loTimestamp = timestamps.first(); @@ -170,8 +170,6 @@ bool ResultHistoryPlotter::drawScenes( // Draw border rectangle ... scene_far->addRect(xmin, ymin, xmax - xmin, ymax - ymin, QPen(QColor(220, 220, 220, 255))); - // ### BEGIN specific - if (basePos >= 0) { // Draw line indicating the base position ... QColor color(0, 0, 255); @@ -179,8 +177,6 @@ bool ResultHistoryPlotter::drawScenes( scene_far->addLine(xmin, y.at(basePos), xmax, y.at(basePos), QPen(color, 2)); } - // ### END specific - const qreal dpSize = 4; // Draw history curve between data points ... @@ -364,7 +360,7 @@ QRectF IndexPlotter::sceneRect() const // ### 2 B DOCUMENTED! // ### 2 B DONE: Maybe factor out parts that this function has in common with -// ResultHistoryPlotter::drawScenes() +// other *::drawScenes() implementations bool IndexPlotter::drawScenes( QGraphicsScene *scene_far, QGraphicsScene *scene_mid_aa, QGraphicsScene *scene_near, QString *error) const @@ -609,3 +605,303 @@ bool IndexPlotter::drawScenes( return true; } + + +HistoriesPlotter::HistoriesPlotter(const QList<ResultHistoryInfo *> &rhInfos) + : rhInfos(rhInfos) + , width(1200) + , rhHeight(qMin(120, qMax(80, 800 / rhInfos.size()))) + , pad_top(50) + , pad_bottom(50) + , height(pad_top + pad_bottom + rhInfos.size() * rhHeight) +{ +} + +QRectF HistoriesPlotter::sceneRect() const +{ + return QRectF(0, 0, width, height); +} + +struct MetricInfo { + qreal vmin; + qreal vmax; + MetricInfo(const qreal vmin, const qreal vmax) + : vmin(vmin), vmax(vmax) {} +}; + +// ### 2 B DOCUMENTED! +// ### 2 B DONE: Maybe factor out parts that this function has in common with +// other *::drawScenes() implementations +bool HistoriesPlotter::drawScenes( + QGraphicsScene *scene_far, QGraphicsScene *scene_mid_aa, QGraphicsScene *scene_near, + QString *error) const +{ + if (rhInfos.isEmpty()) { + if (error) + *error = "no result histories to plot"; + return false; + } + + const qreal pad_left = 150; + const qreal pad_right = 300; + const qreal pad_rh = 10; + const qreal xmin = pad_left; + const qreal xmax = width - pad_right; + const qreal xdefault = 0.5 * (xmin + xmax); + + // Find global timestamp range ... + qreal loTimestamp = -1; + qreal hiTimestamp = -1; + for (int i = 0; i < rhInfos.size(); ++i) { + const ResultHistoryInfo *rhInfo = rhInfos.at(i); + if (i == 0) { + loTimestamp = rhInfo->timestamp(0); + hiTimestamp = rhInfo->timestamp(rhInfo->size() - 1); + } else { + loTimestamp = qMin(loTimestamp, rhInfo->timestamp(0)); + hiTimestamp = qMax(hiTimestamp, rhInfo->timestamp(rhInfo->size() - 1)); + } + } + + // Find global per-metric value ranges ... + QMap<QString, MetricInfo *> metricInfos; + for (int i = 0; i < rhInfos.size(); ++i) { + const ResultHistoryInfo *rhInfo = rhInfos.at(i); + const QString metric = rhInfo->metric(); + if (!metricInfos.contains(metric)) { + const qreal firstValue = rhInfo->value(0); + metricInfos.insert(metric, new MetricInfo(firstValue, firstValue)); + } + MetricInfo *metricInfo = metricInfos.value(metric); + for (int j = 0; j < rhInfo->size(); ++j) { + const qreal value = rhInfo->value(j); + metricInfo->vmin = qMin(metricInfo->vmin, value); + metricInfo->vmax = qMax(metricInfo->vmax, value); + } + } + + qreal xfact = xdefault; + if (loTimestamp < hiTimestamp) { + const qreal tfact = 1.0 / (hiTimestamp - loTimestamp); + xfact = tfact * (xmax - xmin); + } + + const int nDispTimestamps = 5; + const int dispTimestampStep = (hiTimestamp - loTimestamp) / (nDispTimestamps - 1); + QList<qreal> dispTimestamps = QList<qreal>() << loTimestamp << hiTimestamp; + for (int i = 1; i < nDispTimestamps - 1; ++i) + dispTimestamps.insert(i, loTimestamp + i * dispTimestampStep); + + QFont font; + const qreal labelPad = 10; + + // Plot the result histories below each other ... + for (int rh = 0; rh < rhInfos.size(); ++rh) { + + ResultHistoryInfo *rhInfo = rhInfos.at(rh); + + const qreal ymin = pad_top + rh * rhHeight + pad_rh; + const qreal ymax = pad_top + (rh + 1) * rhHeight - pad_rh; + const qreal ydefault = 0.5 * (ymin + ymax); + MetricInfo *metricInfo = metricInfos.value(rhInfo->metric()); + const qreal vmin = metricInfo->vmin; + const qreal vmax = metricInfo->vmax; + const qreal vfact = 1 / (vmax - vmin); // zero division handled elsewhere: + const qreal yfact = vfact * (ymax - ymin); + + // Compute scene coordinates of history curve ... + QList<qreal> x; + QList<qreal> y; + qDebug() << ""; + for (int i = 0; i < rhInfo->size(); ++i) { + const qreal t = rhInfo->timestamp(i); + const qreal v = rhInfo->value(i); + x.append(((rhInfo->size() > 1) || (loTimestamp < hiTimestamp)) + ? (xmin + (t - loTimestamp) * xfact) + : xdefault); + y.append(BMMisc::v2y(v, ymax, vmin, yfact, ydefault)); + } + + // Draw background ... + scene_far->setBackgroundBrush(QBrush(Qt::white)); + + // Draw axis indicators ... + const qreal indicatorSize = 5; + const QColor indicatorColor(119, 119, 255, 255); + // ... x-axis (all values) ... + { + QPainterPath path; + for (int i = 0; i < x.size(); ++i) { + path.moveTo(x.at(i), ymax); + path.lineTo(x.at(i), ymax + indicatorSize); + } + scene_far->addPath(path, QPen(indicatorColor)); + } + // ... y-axises (min and max value only) ... + { + QPainterPath path; + path.moveTo(xmin, ymin); + path.lineTo(xmin - indicatorSize, ymin); + path.moveTo(xmin, ymax); + path.lineTo(xmin - indicatorSize, ymax); + path.moveTo(xmax, ymin); + path.lineTo(xmax + indicatorSize, ymin); + path.moveTo(xmax, ymax); + path.lineTo(xmax + indicatorSize, ymax); + scene_far->addPath(path, QPen(indicatorColor)); + } + + // Draw background rectangle ... + scene_far->addRect( + xmin, ymin, xmax - xmin, ymax - ymin, QPen(), QBrush(QColor(245, 245, 245, 255))); + + // Draw border rectangle ... + scene_far->addRect(xmin, ymin, xmax - xmin, ymax - ymin, QPen(QColor(220, 220, 220, 255))); + + const qreal dpSize = 4; + + // Draw history curve between data points ... + { + QPainterPath path(QPointF(x.first(), y.first())); + for (int i = 0; i < x.size(); ++i) + path.lineTo(x.at(i), y.at(i)); + scene_mid_aa->addPath(path, QPen(QColor(100, 100, 100, 255))); + } + + // Draw data points ... + { + QPainterPath path; + path.setFillRule(Qt::WindingFill); + const qreal dpSize_2 = 0.5 * dpSize; + for (int i = 0; i < x.size(); ++i) + path.addRect(x.at(i) - dpSize_2, y.at(i) - dpSize_2, dpSize, dpSize); + + const QColor color(50, 50, 50, 255); + scene_near->addPath(path, QPen(color), QBrush(color)); + } + + font.setPointSize(12); + + // Draw labels on left y axis ... + { + QGraphicsSimpleTextItem *text = scene_near->addSimpleText(QString().setNum(vmin), font); + text->setPos( + xmin - labelPad - text->boundingRect().width(), + ymax - text->boundingRect().height() / 2); + } + { + QGraphicsSimpleTextItem *text = scene_near->addSimpleText(QString().setNum(vmax), font); + text->setPos( + xmin - labelPad - text->boundingRect().width(), + ymin - text->boundingRect().height() / 2); + } + + // Draw labels on right y axis ... + + qreal textYPos = ymin; + const qreal labelPad_rh_ver = 3; + font.setPointSize(10); + font.setFamily("mono"); + + // ... metric ... + { + QGraphicsSimpleTextItem *text = + scene_near->addSimpleText( + QString("M: %1").arg(rhInfo->metric()), font); + text->setPos(xmax + labelPad, textYPos); + textYPos += text->boundingRect().height() + labelPad_rh_ver; + + QColor color(255, 255, 200, 255); + QGraphicsItem *bgRect = scene_near->addRect( + text->boundingRect(), QPen(color), QBrush(color)); + bgRect->setPos(text->pos()); + bgRect->setZValue(-1); + } + + // ... platform ... + { + QGraphicsSimpleTextItem *text = + scene_near->addSimpleText( + QString("P: %1").arg(rhInfo->platform()), font); + text->setPos(xmax + labelPad, textYPos); + textYPos += text->boundingRect().height() + labelPad_rh_ver; + + QColor color(200, 255, 255, 255); + QGraphicsItem *bgRect = scene_near->addRect( + text->boundingRect(), QPen(color), QBrush(color)); + bgRect->setPos(text->pos()); + bgRect->setZValue(-1); + } + + // ... host ... + { + QGraphicsSimpleTextItem *text = + scene_near->addSimpleText( + QString("H: %1").arg(rhInfo->host()), font); + text->setPos(xmax + labelPad, textYPos); + textYPos += text->boundingRect().height() + labelPad_rh_ver; + + QColor color(230, 210, 255, 255); + QGraphicsItem *bgRect = scene_near->addRect( + text->boundingRect(), QPen(color), QBrush(color)); + bgRect->setPos(text->pos()); + bgRect->setZValue(-1); + } + + // ... git repo/branch ... + { + QGraphicsSimpleTextItem *text = + scene_near->addSimpleText( + QString("B: %1 %2").arg(rhInfo->gitRepo()).arg(rhInfo->gitBranch()), font); + text->setPos(xmax + labelPad, textYPos); + textYPos += text->boundingRect().height() + labelPad_rh_ver; + + QColor color(255, 230, 210, 255); + QGraphicsItem *bgRect = scene_near->addRect( + text->boundingRect(), QPen(color), QBrush(color)); + bgRect->setPos(text->pos()); + bgRect->setZValue(-1); + } + + // Draw vertical lines indicating display timestamps ... + for (int i = 0; i < dispTimestamps.size(); ++i) { + const qreal xpos = xmin + (dispTimestamps.at(i) - loTimestamp) * xfact; + scene_far->addLine(xpos, ymin, xpos, ymax, QPen(QColor(200, 200, 200), 1)); + } + } + + // Draw global labels ... + + // ... timestamps ... + + const qreal ymin = pad_top; + const qreal ymax = height - pad_bottom; + + for (int i = 0; i < dispTimestamps.size(); ++i) { + + const qreal timestamp = dispTimestamps.at(i); + + const QString xLabel = BMMisc::compactDateString(timestamp); + font.setPointSize(12); + + const qreal xpos = xmin + (timestamp - loTimestamp) * xfact; + + // ... top x axis label ... + { + QGraphicsSimpleTextItem *text = scene_near->addSimpleText(xLabel, font); + text->setPos( + xpos - text->boundingRect().width() / 2, + ymin - (text->boundingRect().height() + labelPad)); + } + + // ... bottom x axis label ... + { + QGraphicsSimpleTextItem *text = scene_near->addSimpleText(xLabel, font); + text->setPos( + xpos - text->boundingRect().width() / 2, + ymax + labelPad); + } + } + + return true; +} diff --git a/src/bm/plotter.h b/src/bm/plotter.h index ab0705a..e67dded 100644 --- a/src/bm/plotter.h +++ b/src/bm/plotter.h @@ -24,6 +24,7 @@ #ifndef PLOTTER_H #define PLOTTER_H +#include "resulthistoryinfo.h" #include <QList> #include <QString> #include <QImage> @@ -88,4 +89,24 @@ private: QString *error) const; }; +class HistoriesPlotter : public Plotter +{ +public: + HistoriesPlotter(const QList<ResultHistoryInfo *> &rhInfos); + +private: + QList<ResultHistoryInfo *> rhInfos; + + qreal width; + qreal rhHeight; + qreal pad_top; + qreal pad_bottom; + qreal height; + + QRectF sceneRect() const; + bool drawScenes( + QGraphicsScene *scene_far, QGraphicsScene *scene_mid_aa, QGraphicsScene *scene_near, + QString *error) const; +}; + #endif // PLOTTER_H diff --git a/src/bm/resulthistoryinfo.h b/src/bm/resulthistoryinfo.h index a60aad4..e91ecb1 100644 --- a/src/bm/resulthistoryinfo.h +++ b/src/bm/resulthistoryinfo.h @@ -30,13 +30,18 @@ class ResultHistoryInfo { public: ResultHistoryInfo( - const int bmcontextId, const QString &metric, const QList<int> ×tamps_, - const QList<qreal> &values) - : bmcontextId_(bmcontextId), metric_(metric), timestamps_(timestamps_), values(values) - , cachedTimestamp(-1) + const int bmcontextId, const QList<int> ×tamps_, const QList<qreal> &values, + const QString &metric, const QString &platform = QString(), const QString &host = QString(), + const QString &gitRepo = QString(), const QString &gitBranch = QString()) + : bmcontextId_(bmcontextId), timestamps_(timestamps_), values(values) + , metric_(metric), platform_(platform), host_(host), gitRepo_(gitRepo) + , gitBranch_(gitBranch), cachedTimestamp(-1) { Q_ASSERT(!timestamps_.isEmpty()); Q_ASSERT(timestamps_.size() == values.size()); + for (int i = 1; i < timestamps_.size(); ++i) { + Q_ASSERT(timestamps_.at(i - 1) <= timestamps_.at(i)); + } } bool getSmoothValue( @@ -46,9 +51,15 @@ public: bool getSmoothValues(int medianWinSize, QList<qreal> *smoothValues) const; QList<int> timestamps() const { return timestamps_; }; int size() const { return timestamps_.size(); } + qreal timestamp(int i) const { return static_cast<qreal>(timestamps_.at(i)); } + qreal value(int i) const { return values.at(i); } int bmcontextId() const { return bmcontextId_; } QString metric() const { return metric_; } + QString platform() const { return platform_; } + QString host() const { return host_; } + QString gitRepo() const { return gitRepo_; } + QString gitBranch() const { return gitBranch_; } static bool findTargetPos( const QList<int> ×tamps, int timestamp, int medianWinSize, int *pos = 0, @@ -56,9 +67,13 @@ public: private: int bmcontextId_; - QString metric_; QList<int> timestamps_; QList<qreal> values; + QString metric_; + QString platform_; + QString host_; + QString gitRepo_; + QString gitBranch_; mutable bool cachedTimestamp; mutable qreal cachedSmoothValue; }; diff --git a/src/bmclient/main.cpp b/src/bmclient/main.cpp index 63e18bc..391a7b7 100644 --- a/src/bmclient/main.cpp +++ b/src/bmclient/main.cpp @@ -114,6 +114,7 @@ private: const QStringList &args, QString *error, const QString &command = "index get values") const; BMRequest * createIndexPutConfigRequest(const QStringList &args, QString *error) const; + BMRequest * createGetHistoriesRequest(const QStringList &args, QString *error) const; mutable BMRequest::OutputFormat explicitOutputFormat; mutable bool useExplicitOutputFormat; BMRequest::OutputFormat outputFormat() const; @@ -731,6 +732,36 @@ BMRequest * Executor::createRequest(const QStringList &args, QString *error) con return new BMRequest_IndexDeleteConfig(configName); + } else if ( + (args.size() >= 3) && (args.at(0) == "get") && (args.at(1) == "histories") + && (args.at(2) != "plot") && (args.at(2) != "detailspage")) { + // --- 'get histories' command --- + + return createGetHistoriesRequest(args, error); + + } else if ( + (args.size() >= 3) && (args.at(0) == "get") && (args.at(1) == "histories") + && (args.at(2) == "plot")) { + // --- 'get histories plot' command --- + + BMRequest *request = createGetHistoriesRequest(args, error); + setOutputFormat(BMRequest::Image); + return request; + + } else if ( + (args.size() >= 3) && (args.at(0) == "get") && (args.at(1) == "histories") + && (args.at(2) == "detailspage")) { + // --- 'get histories detailspage' command --- + + BMRequest *request = createGetHistoriesRequest(args, error); + setOutputFormat(BMRequest::HTML); + return request; + + } else if ((args.size() >= 2) && (args.at(0) == "get") && (args.at(1) == "bmtree")) { + // --- 'get bmtree' command --- + + return new BMRequest_GetBMTree(); + } else if ((args.size() >= 2) && (args.at(0) == "get") && (args.at(1) == "detailspage")) { // --- 'get detailspage' command --- @@ -1143,6 +1174,59 @@ BMRequest * Executor::createIndexPutConfigRequest(const QStringList &args, QStri metricFilter, platformFilter, hostFilter, branchFilter); } +BMRequest * Executor::createGetHistoriesRequest(const QStringList &args, QString *error) const +{ + QStringList values; + + // Get test case ... + if (!BMMisc::getOption(args, "-testcase", &values, 1, 0, error)) { + if (error->isEmpty()) + *error = "-testcase option not found"; + return 0; + } + const QString testCase = values.first().trimmed(); + if (testCase.isEmpty()) { + *error = "empty test case"; + return 0; + } + + // Get test function ... + if (!BMMisc::getOption(args, "-testfunction", &values, 1, 0, error)) { + if (error->isEmpty()) + *error = "-testfunction option not found"; + return 0; + } + const QString testFunction = values.first().trimmed(); + if (testFunction.isEmpty()) { + *error = "empty test function"; + return 0; + } + + // Get data tag ... + if (!BMMisc::getOption(args, "-datatag", &values, 1, 0, error)) { + if (error->isEmpty()) + *error = "-datatag option not found"; + return 0; + } + const QString dataTag = values.first().trimmed(); // note that empty data tags are allowed + + // 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_GetHistories(testCase, testFunction, dataTag, cacheKey); +} + // ### 2 B DOCUMENTED! static void splitQuotedArgs(const QString &arg_s, QStringList *args) { @@ -1536,6 +1620,20 @@ class DirectExecutor : public Executor << "index delete config -name <...>\n" + << + "get histories -testcase <...> -testfunction <...> -datatag <...>\n" + + << + "get histories plot <SAME AS 'get histories' except that the optional \\\n" + " -httpHeader and -cachekey options are recognized>\n" + + << + "get histories detailspage <SAME AS 'get histories'> except that the mandatory \\\n" + " -stylesheet option is recognized\n" + + << + "get bmtree\n" + ; qDebug() << "\nNote: the -server option may be replaced by a BMSERVER=<host>:<port> " diff --git a/src/bmweb/bmcomparatorsection.js b/src/bmweb/bmcomparatorsection.js new file mode 100644 index 0000000..7877a02 --- /dev/null +++ b/src/bmweb/bmcomparatorsection.js @@ -0,0 +1,240 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the BM project on Qt Labs. +** +** This file may be used under the terms of the GNU General Public +** License version 2.0 or 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of +** this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** +** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE +** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +** +****************************************************************************/ + +function updateShowLink() +{ + // Initialize URL ... + url = "http://" + location.host + "/cgi-bin/bmclientwrapper"; + url += "?command="; + url += "-server " + server(); + url += " get histories detailspage"; + url += " -stylesheet " + document.styleSheets[0].href; + + // Add benchmark ... + url += " -testcase " + selectedOptions("bcTestCases")[0]; + url += " -testfunction " + selectedOptions("bcTestFunctions")[0]; + // The data tag may consist of "anything", so we need to quote it + // and escape problematic characters: + url += " -datatag " + "'" + encodeURIComponent(selectedOptions("bcDataTags")[0]) + "'"; + + // Update link ... + showLink = document.getElementById("bcShowLink"); + showLink.setAttribute("href", url); +} + +function BMComparatorSection() +{ + var bmtree; + + this.name = function() { return "bcSection"; } + + this.reset = function(handleDone) + { + this.finalize_reset = function() + { + setMessage(""); + document.getElementById("bcReset").disabled = false; + if (handleDone) + handleDone(); + } + + this.handleReply_reset = function(reply) + { + if (reply.error) { + alert("reset() failed when fetching benchmark tree (2): " + reply.error); + + this.finalize_reset(); + + } else { + + bmtree = reply; + + // Load the test cases ... + testCases = bmtree.testCases; + tcSelect = document.getElementById("bcTestCases"); + tcSelect.options.length = 0; + for (i = 0; i < testCases.length; ++i) + tcSelect.options[tcSelect.options.length] = new Option(testCases[i].name); + // ... and select the first one ... + tcSelect.options[0].selected = true; + this.selectTestCase(0); + } + + this.finalize_reset(); + } + + this.handleError_reset = function(error) + { + alert("reset() failed when fetching benchmark tree (1): " + error); + this.finalize_reset(); + } + + var thisSection = this; // ### hack? + + document.getElementById("bcReset").disabled = true; + + // Fetch benchmark tree ... + setMessage("fetching benchmark tree ..."); + var args = ["get", "bmtree"]; + sendRequest( + args, + function(reply) { thisSection.handleReply_reset(reply); }, + function(error) { thisSection.handleError_reset(error); }); + } + + this.selectTestFunction = function(tcIndex, tfIndex) { + // Load the data tags for this test case / test function ... + dataTags = bmtree.testCases[tcIndex].testFunctions[tfIndex].dataTags; + dtSelect = document.getElementById("bcDataTags"); + dtSelect.options.length = 0; + for (i = 0; i < dataTags.length; ++i) { + dtSelect.options[dtSelect.options.length] = new Option(dataTags[i]); + } + // ... and select the first one ... + dtSelect.options[0].selected = true; + } + + this.selectTestCase = function(tcIndex) { + // Load the test functions for this test case ... + testFunctions = bmtree.testCases[tcIndex].testFunctions; + tfSelect = document.getElementById("bcTestFunctions"); + tfSelect.options.length = 0; + for (i = 0; i < testFunctions.length; ++i) + tfSelect.options[tfSelect.options.length] = new Option(testFunctions[i].name); + // ... and select the first one ... + tfSelect.options[0].selected = true; + this.selectTestFunction(tcIndex, 0); + } + + this.appendAll = function(section) + { + var thisSection = this; // ### hack? + + section.appendChild(document.createElement("br")); + section.appendChild(document.createElement("br")); + + // Create a link that opens a page that shows the comparison ... + showLink = section.appendChild(document.createElement("a")); + showLink.id = "bcShowLink"; + showLink.appendChild(document.createTextNode("Show")); + showLink.setAttribute("href", "dummy"); // for correct initial rendering + showLink.setAttribute("onmouseover", "updateShowLink()"); + + section.appendChild(document.createElement("br")); + section.appendChild(document.createElement("br")); + + // Create 'Reset' button ... + input = section.appendChild(document.createElement("input")); + input.setAttribute("type", "button"); + input.setAttribute("id", "bcReset"); + input.setAttribute("value", "Reset"); + input.onclick = function() { + try { thisSection.reset(); } + catch (ex) { alert("thisSection.reset() failed: " + ex); } + } + + section.appendChild(document.createElement("br")); + section.appendChild(document.createElement("br")); + + // Create benchmark selectors ... + table = section.appendChild(document.createElement("table")); + table.setAttribute("style", "border:0px; padding:0px"); + // ... test case ... + tr = table.insertRow(0); + td = tr.insertCell(0); + td.innerHTML = "Test Case:"; + td = tr.insertCell(1); + select = td.appendChild(document.createElement("select")); + select.setAttribute("id", "bcTestCases"); + select.onchange = function() { + tcSelect = document.getElementById("bcTestCases"); + thisSection.selectTestCase(tcSelect.selectedIndex); + } + // ... test function ... + tr = table.insertRow(1); + td = tr.insertCell(0); + td.innerHTML = "Test Function:"; + td = tr.insertCell(1); + select = td.appendChild(document.createElement("select")); + select.setAttribute("id", "bcTestFunctions"); + select.onchange = function() { + tcSelect = document.getElementById("bcTestCases"); + tfSelect = document.getElementById("bcTestFunctions"); + thisSection.selectTestFunction(tcSelect.selectedIndex, tfSelect.selectedIndex); + } + // ... data tag ... + tr = table.insertRow(2); + td = tr.insertCell(0); + td.innerHTML = "Data Tag:"; + td = tr.insertCell(1); + select = td.appendChild(document.createElement("select")); + select.setAttribute("id", "bcDataTags"); + } + + this.create = function() + { + var sectionElem = document.createElement("div"); + insertTitle(sectionElem, "Benchmark Comparison"); + sectionElem.appendChild(document.createElement("br")); + appendHelpLink(sectionElem, "help.html#BMComparison"); + + this.appendAll(sectionElem); + + return sectionElem; + } + + this.state = function() { + var state_ = []; + + // 2 B DONE! + +// state_["bcTestState"] = document.getElementById("bcTestState").value; + + return state_; + } + + this.setState = function(state_, handleDone) { + + // 2 B DONE! + + // name = "bcTestState"; + // value = parseInt(state_[name]); + // if (!isNaN(value)) + // document.getElementById(name).value = value; + + handleDone(); + } + + SectionBase.call(this, "bmComparatorButton"); + this.sectionElem = this.create(); +} + +BMComparatorSection.prototype = new SectionBase(); +BMComparatorSection.prototype.constructor = BMComparatorSection; +BMComparatorSection.instance = function() +{ + if (!BMComparatorSection.instance_) + BMComparatorSection.instance_ = new BMComparatorSection(); + return BMComparatorSection.instance_; +} +function bcSection() { return BMComparatorSection.instance(); } diff --git a/src/bmweb/global.js b/src/bmweb/global.js index e6b80d6..d8f094a 100644 --- a/src/bmweb/global.js +++ b/src/bmweb/global.js @@ -205,6 +205,8 @@ function initSections() state_ = cookie.getDynamicProperties(); if (state_["currentSection"] == ixSection().name()) { ixSection().install(); + } else if (state_["currentSection"] == bcSection().name()) { + bcSection().install(); } else if (state_["currentSection"] == psSection().name()) { psSection().install(); } else if (state_["currentSection"] == bsSection().name()) { @@ -221,9 +223,14 @@ function initSections() loadStateFromCookie(ixSection(), finalizeInit); } + function loadBCStateFromCookie() + { + loadStateFromCookie(bcSection(), loadIXStateFromCookie); + } + function loadBSStateFromCookie() { - loadStateFromCookie(bsSection(), loadIXStateFromCookie); + loadStateFromCookie(bsSection(), loadBCStateFromCookie); } function loadPSStateFromCookie() @@ -236,10 +243,15 @@ function initSections() ixSection().reset(loadPSStateFromCookie); } + function resetBCSection() + { + bcSection().reset(resetIXSection); + } + function resetBSSection() { if (Cookie.enabled()) { - bsSection().reset(resetIXSection); + bsSection().reset(resetBCSection); } else { finalizeInit(); } @@ -608,6 +620,8 @@ function sectionElem2Name(sectionElem) { if (sectionElem == ixSection().sectionElem) return ixSection().name(); + if (sectionElem == bcSection().sectionElem) + return bcSection().name(); if (sectionElem == psSection().sectionElem) return psSection().name(); if (sectionElem == bsSection().sectionElem) @@ -639,6 +653,7 @@ function saveStateToCookie() } appendCookieState(ixSection().state()); + appendCookieState(bcSection().state()); appendCookieState(psSection().state()); appendCookieState(bsSection().state()); appendCookieState(settingsSection().state()); @@ -668,6 +683,9 @@ function initialize() document.getElementById("indexButton").setAttribute( "onclick", "try { ixSection().install(); } catch (ex) " + "{ alert('ixSection().install() failed: ' + ex); }"); + document.getElementById("bmComparatorButton").setAttribute( + "onclick", "try { bcSection().install(); } catch (ex) " + + "{ alert('bcSection().install() failed: ' + ex); }"); document.getElementById("platformSummaryButton").setAttribute( "onclick", "try { psSection().install(); } catch (ex) " + "{ alert('psSection().install() failed: ' + ex); }"); @@ -681,6 +699,7 @@ function initialize() // Create sections ... var sectionParent = document.getElementById("section"); sectionParent.appendChild(ixSection().sectionElem); + sectionParent.appendChild(bcSection().sectionElem); sectionParent.appendChild(psSection().sectionElem); sectionParent.appendChild(bsSection().sectionElem); sectionParent.appendChild(settingsSection().sectionElem); diff --git a/src/bmweb/help.html b/src/bmweb/help.html index e4fb3b8..7aee9f0 100644 --- a/src/bmweb/help.html +++ b/src/bmweb/help.html @@ -333,7 +333,13 @@ Table columns: <!-- //////////////////////////////////////////////////////////////////////////////////// --> <hr /> <a name="Index" /> -x<h2>Performance Index</h2> +<h2>Performance Index</h2> +<span style="color:red">2 B DONE</span> + +<!-- //////////////////////////////////////////////////////////////////////////////////// --> +<hr /> +<a name="BMComparison" /> +<h2>Benchmark Comparison</h2> <span style="color:red">2 B DONE</span> <!-- //////////////////////////////////////////////////////////////////////////////////// --> diff --git a/src/bmweb/index.html b/src/bmweb/index.html index e87161f..119aa06 100644 --- a/src/bmweb/index.html +++ b/src/bmweb/index.html @@ -4,6 +4,7 @@ <script src="sectionbase.js"></script> <script src="indexsection.js"></script> +<script src="bmcomparatorsection.js"></script> <script src="platformsection.js"></script> <script src="benchmarksection.js"></script> <script src="settingssection.js"></script> @@ -23,6 +24,7 @@ <span id="timing"></span> <br /><br /> <td><input type="button" id="indexButton" value="Index" /></td> +<td><input type="button" id="bmComparatorButton" value="BM Comparison" /></td> <td><input type="button" id="platformSummaryButton" value="Platform Summary"/></td> <td><input type="button" id="benchmarkSummaryButton" value="Benchmark Summary" /></td> <td><input type="button" id="settingsButton" value="Settings" /></td> |