summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorjasplin <qt-info@nokia.com>2010-04-12 15:29:22 +0200
committerjasplin <qt-info@nokia.com>2010-04-12 15:29:22 +0200
commit842a970eb3b085b83df134166606fe04b37032d2 (patch)
tree6e0818a7a196c464c774c6b11dabbf00bdca43cb
parenta62277ff1c05b222a42d0dd670443f92640df655 (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.cpp9
-rw-r--r--src/bm/bmmisc.h1
-rw-r--r--src/bm/bmrequest.cpp465
-rw-r--r--src/bm/bmrequest.h38
-rw-r--r--src/bm/plotter.cpp310
-rw-r--r--src/bm/plotter.h21
-rw-r--r--src/bm/resulthistoryinfo.h25
-rw-r--r--src/bmclient/main.cpp98
-rw-r--r--src/bmweb/bmcomparatorsection.js240
-rw-r--r--src/bmweb/global.js23
-rw-r--r--src/bmweb/help.html8
-rw-r--r--src/bmweb/index.html2
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: &gt;%2&lt;<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> &timestamps_,
- const QList<qreal> &values)
- : bmcontextId_(bmcontextId), metric_(metric), timestamps_(timestamps_), values(values)
- , cachedTimestamp(-1)
+ const int bmcontextId, const QList<int> &timestamps_, 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> &timestamps, 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>