[perf-web] Allow finding out more information about commits
- From: Owen Taylor <otaylor src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [perf-web] Allow finding out more information about commits
- Date: Thu, 18 Sep 2014 04:02:37 +0000 (UTC)
commit c507590ea3566cd568148128b228378d10cbca66
Author: Owen W. Taylor <otaylor fishsoup net>
Date: Wed Sep 17 20:50:53 2014 -0400
Allow finding out more information about commits
When in the day view, allow double-clicking on a data point to find out
what changed with the commit that the measurement corresponds to - a pane
is opened at the bottom of the screen. Add keybindings for navigating to
adjacent points.
metrics/views.py | 28 ++-
static/images/README.txt | 19 ++
static/images/go-next.png | Bin 0 -> 231 bytes
static/images/go-previous.png | Bin 0 -> 235 bytes
static/images/window-close.png | Bin 0 -> 315 bytes
static/main.js | 452 +++++++++++++++++++++++++++++++++++++---
static/perf.css | 133 ++++++++++++
templates/metrics/chart.html | 1 +
templates/metrics/common.html | 12 +
templates/metrics/home.html | 1 +
templates/metrics/metric.html | 1 +
templates/metrics/target.html | 1 +
12 files changed, 618 insertions(+), 30 deletions(-)
---
diff --git a/metrics/views.py b/metrics/views.py
index afd3e3d..cd33ef9 100644
--- a/metrics/views.py
+++ b/metrics/views.py
@@ -21,11 +21,11 @@ _EPOCH = datetime(1970, 1, 1, tzinfo=timezone.utc)
# timedelta.total_seconds added in 2.7
if hasattr(timedelta, 'total_seconds'):
def unix_time(dt):
- return (dt - _EPOCH).total_seconds()
+ return int(round((dt - _EPOCH).total_seconds()))
else:
def unix_time(dt):
td = dt - _EPOCH
- return float(td.microseconds + (td.seconds + td.days * 24 * 3600) * 10**6) / 10**6
+ return int(round(float(td.microseconds + (td.seconds + td.days * 24 * 3600) * 10**6) / 10**6))
def home(request):
t = loader.get_template('metrics/home.html')
@@ -148,10 +148,13 @@ def values(request):
if not group in _SUMMARY_CLASSES:
return HttpResponseBadRequest("Invalid group type")
- result = []
+ result = {}
+ result['metrics'] = metrics = []
summaryCls = _SUMMARY_CLASSES[group]
if summaryCls is None:
+ target_map = {}
+
qs = Value.objects.all()
qs = Value.filter_and_order(qs, start, end, metric, target)
@@ -166,11 +169,12 @@ def values(request):
'name': metric_name,
'targets': []
}
- result.append(metric_data)
+ metrics.append(metric_data)
last_metric = metric_name
last_target = None
target_name = value.report.target.name
+
if target_name != last_target:
target_data = {
'name': target_name,
@@ -179,10 +183,24 @@ def values(request):
metric_data['targets'].append(target_data)
last_target = target_name
+ if target_name in target_map:
+ revisions = target_map[target_name]
+ else:
+ revisions = target_map[target_name] = {}
+
+ pull_time_str = str(unix_time(value.report.pull_time))
+ if not pull_time_str in revisions:
+ revisions[pull_time_str] = value.report.revision
+
target_data['values'].append({
'time': unix_time(value.report.pull_time),
'value': value.value
})
+
+ result['targets'] = targets = []
+ for name, revisions in target_map.iteritems():
+ targets.append({'name': name,
+ 'revisions': revisions});
else:
summaries = summaryCls.get_summaries(start, end, target, metric)
@@ -197,7 +215,7 @@ def values(request):
'name': metric_name,
'targets': []
}
- result.append(metric_data)
+ metrics.append(metric_data)
last_metric = metric_name
last_target = None
diff --git a/static/images/README.txt b/static/images/README.txt
new file mode 100644
index 0000000..5645551
--- /dev/null
+++ b/static/images/README.txt
@@ -0,0 +1,19 @@
+These icons are selected from the GNOME Project's Adwaita Icon theme,
+which is licensed under the following terms:
+
+===
+This work is licenced under the terms of either the GNU LGPL v3 or
+Creative Commons Attribution-Share Alike 3.0 United States License.
+
+To view a copy of the CC-BY-SA licence, visit
+http://creativecommons.org/licenses/by-sa/3.0/ or send a letter to Creative
+Commons, 171 Second Street, Suite 300, San Francisco, California 94105, USA.
+
+When attributing the artwork, using "GNOME Project" is enough.
+Please link to http://www.gnome.org where available.
+===
+
+The source files for this artwork can be found in the adwaiata-icon-theme
+module:
+
+ https://git.gnome.org/browse/adwaita-icon-theme
diff --git a/static/images/go-next.png b/static/images/go-next.png
new file mode 100644
index 0000000..e1504a1
Binary files /dev/null and b/static/images/go-next.png differ
diff --git a/static/images/go-previous.png b/static/images/go-previous.png
new file mode 100644
index 0000000..2dea27a
Binary files /dev/null and b/static/images/go-previous.png differ
diff --git a/static/images/window-close.png b/static/images/window-close.png
new file mode 100644
index 0000000..0c02038
Binary files /dev/null and b/static/images/window-close.png differ
diff --git a/static/main.js b/static/main.js
index f77f64d..ae27bf3 100644
--- a/static/main.js
+++ b/static/main.js
@@ -84,9 +84,11 @@ function Chart(svg) {
this.body = $( svg ).find( ".chart-body" ).get(0);
this.grid = $( svg ).find( ".chart-grid" ).get(0);
this.chartData = $( svg ).find( ".chart-data" ).get(0);
+ this.highlights = $( svg ).find( ".chart-highlights" ).get(0);
this.polylines = [];
this.pointPaths = [];
+ this.highlightMarkers = [];
this.xGridLines = [];
this.yGridLines = [];
this.xLabelTexts = [];
@@ -94,13 +96,8 @@ function Chart(svg) {
this.top = 10;
this.bottom = 10;
- svg.addEventListener("dblclick", function(event) {
- var offset = $( this.body ).offset();
- var x = event.pageX - offset.left;
- var centerTime = this.time(x);
-
- theDisplay.zoomIn(centerTime);
- }.bind(this));
+ svg.addEventListener("dblclick", this.onDblClick.bind(this));
+ svg.addEventListener("mousemove", this.onMouseMove.bind(this));
}
Chart.prototype.x = function(time) {
@@ -115,6 +112,80 @@ Chart.prototype.y = function(value) {
return this.bodyHeight * (1 - (value - this.bottom) / (this.top - this.bottom));
}
+Chart.prototype.setHover = function(target, time) {
+ var topY = this.y(this.topValue);
+ var bottomY = this.y(this.bottomValue);
+ var values = target.values;
+ for (var j = 0; j < 2 * values.length; j++) {
+ if (values.data[j] == time) {
+ var offset = $( this.body ).offset();
+ var x = this.x(time);
+ var y = this.y(values.data[j + 1]);
+
+ theDisplay.setHover({
+ x: offset.left + x,
+ y: offset.top + y,
+ above: y < (topY + bottomY) / 2 - 3,
+ target: target.target,
+ metric: this.metric,
+ units: this.units,
+ time: time,
+ revision: theDisplay.allTargets[target.target].revisions[time],
+ value: values.data[j + 1] * this.multiplier,
+ });
+
+ return;
+ }
+ }
+}
+
+Chart.prototype.onMouseMove = function(event) {
+ var offset = $( this.body ).offset();
+ var x = event.pageX - offset.left;
+ var y = event.pageY - offset.top;
+
+ var targets = this.getTargets();
+ var closestTarget = null;
+ var closetDist2, closestTime;
+
+ for (var i = 0; i < targets.length; i++) {
+ var values = targets[i].values;
+
+ var j;
+ for (j = 0; j < 2 * values.length; j++) {
+ var pointX = this.x(values.data[j]);
+ var pointY = this.y(values.data[j + 1]);
+ var dist2 = (pointX - x) * (pointX - x) + (pointY - y) * (pointY - y);
+ if (dist2 < 25 && (closestTarget == null || dist2 < closestDist2)) {
+ closestTarget = targets[i];
+ closestTime = values.data[j];
+ closestDist2 = dist2;
+
+ }
+ }
+ }
+
+ if (closestTarget)
+ this.setHover(closestTarget, closestTime);
+ else
+ theDisplay.setHover(null);
+}
+
+Chart.prototype.onDblClick = function(event) {
+ var offset = $( this.body ).offset();
+ var x = event.pageX - offset.left;
+ var centerTime = this.time(x);
+
+ if (theDisplay.rangeType != 'day') {
+ theDisplay.zoomIn(centerTime);
+ } else {
+ if (theDisplay.hoverInfo)
+ theDisplay.setDetails({ time: theDisplay.hoverInfo.time,
+ metric: theDisplay.hoverInfo.metric,
+ revision: theDisplay.hoverInfo.revision });
+ }
+}
+
function createElement(name, cls) {
var element = document.createElementNS("http://www.w3.org/2000/svg", name);
if (cls != null)
@@ -172,9 +243,19 @@ Chart.prototype.removeExtraElements = function(elements, nToSave) {
Chart.prototype.drawTarget = function(targetData) {
var values = targetData.values;
var index = targetData.index;
+ var highlightTime = null;
+ var highlightX, highlightY;
- if (values.length == 0)
+ if (theDisplay.detailsInfo)
+ highlightTime = theDisplay.detailsInfo.time;
+
+ if (values.length == 0) {
+ if (this.highlightMarkers[index]) {
+ this.highlights.removeChild(this.highlightMarkers[index]);
+ delete this.highlightMarkers[index];
+ }
return;
+ }
var path;
if (theDisplay.loadedGroup != 'none') {
@@ -225,16 +306,37 @@ Chart.prototype.drawTarget = function(targetData) {
path = "";
for (var i = 0; i < 2 * values.length; i += 2) {
- var x = this.x(values.data[i]);
+ var time = values.data[i];
+ var x = this.x(time);
if (x + 2 <= 0 || x - 2 >= this.bodyWidth)
continue;
var y = this.y(values.data[i + 1]);
- path += "M" + (x - 2) + " " + (y - 2) + " " + "h4v4h-4z"
+ path += "M" + (x - 2) + " " + (y - 2) + " " + "h4v4h-4z";
+
+ if (time == highlightTime) {
+ highlightX = x;
+ highlightY = y;
+ }
}
pointPath.setAttribute("d", path);
+
+ if (highlightX) {
+ var rect = this.highlightMarkers[index];
+ if (rect == null) {
+ this.highlightMarkers[index] = rect = createElement("rect", "chart-highlight");
+ this.highlights.appendChild(rect);
+ }
+
+ allocateElement(rect, highlightX - 2.5, highlightY - 2.5, 5, 5);
+ } else {
+ if (this.highlightMarkers[index]) {
+ this.highlights.removeChild(this.highlightMarkers[index]);
+ delete this.highlightMarkers[index];
+ }
+ }
}
Chart.prototype.getTargets = function() {
@@ -426,6 +528,9 @@ Chart.prototype.drawYLabels = function(targets) {
// Always include the X-axis
var bottom = 0;
var top = 0;
+ // But we also want the real-range for tooltip positioning
+ var realBottom = null;
+ var realTop = null;
for (var i = 0; i < targets.length; i++) {
var targetData = targets[i];
@@ -439,9 +544,23 @@ Chart.prototype.drawYLabels = function(targets) {
bottom = Math.min(value, bottom);
top = Math.max(value, top);
}
+
+ if (realTop == null) {
+ realTop = realBottom = value;
+ } else {
+ realBottom = Math.min(value, realBottom);
+ realTop = Math.max(value, realTop);
+ }
}
}
+ if (realTop == null) {
+ this.bottomValue = this.topValue = 0;
+ } else {
+ this.bottomValue = realBottom;
+ this.topValue = realTop;
+ }
+
// We want to find a multiple of 2, 5, or 10 that covers the range
// bottom to top in about 5 steps
var step;
@@ -487,24 +606,30 @@ Chart.prototype.drawYLabels = function(targets) {
var displayTop = top;
var displayBottom = bottom;
- units = this.svg.getAttribute('data-metric-units');
+ var units = this.svg.getAttribute('data-metric-units');
if (units == 's' || units == 'ms' || units == 'us') {
- if (units == 's') {
- displayTop *= 1000000;
- displayBottom *= 1000000;
- } else if (units == 'ms') {
- displayTop *= 1000;
- displayBottom *= 1000;
- }
+ if (units == 's')
+ multiplier = 1000000;
+ else if (units == 'ms')
+ multiplier = 1000;
+ else
+ multiplier = 1;
if (bottom <= -1000000 || top >= 1000000) {
- displayTop /= 1000000;
- displayBottom /= 1000000;
+ multiplier /= 1000000;
+ units = 's';
} else if (bottom <= -100 || top >= 1000) {
- displayTop /= 1000;
- displayBottom /= 1000;
+ multiplier /= 1000;
+ units = 'ms';
+ } else {
+ units = 'us';
}
+
+ displayTop *= multiplier;
+ displayBottom *= multiplier;
}
+ this.units = units;
+ this.multiplier = multiplier;
$( this.upperText ).empty();
this.upperText.appendChild(document.createTextNode(displayTop));
@@ -555,6 +680,12 @@ Chart.prototype.draw = function() {
delete this.polylines[index];
}
}
+ for (index in this.highlightMarkers) {
+ if (!(index in usedIndices)) {
+ this.highlights.removeChild(this.highlightMarkers[index]);
+ delete this.highlightMarkers[index];
+ }
+ }
}
////////////////////////////////////////////////////////////////////////
@@ -1057,6 +1188,9 @@ function PerfDisplay(target, metric, dataMinTime, dataMaxTime, centerTime, range
this.loadedGroup = null;
this.loadedRanges = new TimeRanges();
+ this.revisions = {};
+ this.loadedRevisions = {};
+
this.setPositionAndRange(centerTime, rangeType, false);
$(window).load(this.onWindowLoaded.bind(this));
}
@@ -1065,6 +1199,11 @@ PerfDisplay.prototype.setPositionAndRange = function(centerTime, rangeType, clam
var rangeTypeChanged = (this.rangeType != rangeType);
this.rangeType = rangeType;
+ if (rangeType != 'day')
+ this.setDetails(null);
+ if (rangeTypeChanged)
+ this.setHover(null);
+
switch (rangeType) {
case 'day':
this.rangeSeconds = DAY_MSECS / 1000;
@@ -1253,8 +1392,9 @@ PerfDisplay.prototype._loadRange = function(group, start, end) {
this.loadedRanges.add(start, end);
- for (var i = 0; i < data.length; i++) {
- var metricData = data[i];
+ var metrics = data.metrics;
+ for (var i = 0; i < metrics.length; i++) {
+ var metricData = metrics[i];
if (!(metricData.name in this.data))
this.data[metricData.name] = {};
@@ -1286,7 +1426,22 @@ PerfDisplay.prototype._loadRange = function(group, start, end) {
else
this.data[metricData.name][targetData.name] = values;
- this.allTargets[targetData.name] = 1;
+ if (!(targetData.name in this.allTargets))
+ this.allTargets[targetData.name] = { revisions: {} };
+ }
+ }
+
+ if (data.targets != null) {
+ var targets = data.targets;
+ for (var i = 0; i < targets.length; i++) {
+ var targetData = targets[i];
+ if (!(targetData.name in this.allTargets))
+ this.allTargets[targetData.name] = { revisions: {} };
+
+ var revisions = this.allTargets[targetData.name].revisions;
+ var newRevisions = targetData.revisions;
+ for (var time in newRevisions)
+ revisions[time] = newRevisions[time];
}
}
@@ -1314,6 +1469,9 @@ PerfDisplay.prototype.updateElementsForRange = function() {
}
PerfDisplay.prototype.scrollByDeltaX = function(startTime, deltaX) {
+ if (deltaX > 3)
+ this.setHover(null);
+
var chart = $( ".chart" ).first().get(0);
var chartBodyWidth = chart.parentNode.clientWidth - (MARGIN_LEFT + MARGIN_RIGHT + 2);
var newPos = startTime + deltaX * this.rangeSeconds / chartBodyWidth;
@@ -1406,6 +1564,18 @@ PerfDisplay.prototype.onWindowLoaded = function() {
this.scrollByDeltaX(this.centerTime, 10 * event.deltaX);
}.bind(this));
+ $("#detailsClose").click(function() {
+ this.setDetails(null);
+ }.bind(this));
+
+ $("#detailsPrevious").click(function() {
+ this.moveDetails(-1);
+ }.bind(this));
+
+ $("#detailsNext").click(function() {
+ this.moveDetails(+1);
+ }.bind(this));
+
this.scrollHandler = new ScrollHandler({
exclusive: true,
source: mainLeft,
@@ -1421,6 +1591,9 @@ PerfDisplay.prototype.onWindowLoaded = function() {
});
$( document.body ).keypress(function(e) {
+ if (e.ctrlKey || e.altKey || e.metaKey)
+ return;
+
if (e.which == 43) { /* + */
e.preventDefault();
this.zoomIn();
@@ -1430,6 +1603,22 @@ PerfDisplay.prototype.onWindowLoaded = function() {
}
}.bind(this));
+ $( document.body ).keydown(function(e) {
+ if (e.ctrlKey || e.altKey || e.metaKey)
+ return;
+
+ if (e.which == 37) { /* left */
+ e.preventDefault();
+ this.moveDetails(-1);
+ } else if (e.which == 39) { /* right */
+ e.preventDefault();
+ this.moveDetails(+1);
+ } else if (e.which == 27) { /* esc */
+ e.preventDefault();
+ this.setDetails(null);
+ }
+ }.bind(this));
+
$( window ).resize(function() {
this.refresh();
}.bind(this));
@@ -1438,6 +1627,219 @@ PerfDisplay.prototype.onWindowLoaded = function() {
this.refresh();
}
+PerfDisplay.prototype.setHover = function(hoverInfo)
+{
+ var hover = $( "#hover" ).get(0);
+ this.hoverInfo = hoverInfo;
+ if (hoverInfo) {
+ var date = new Date(hoverInfo.time * 1000.);
+ var dateStr = (date.getUTCFullYear() + '-' + pad(date.getUTCMonth() + 1) + '-' +
pad(date.getUTCDate()) + ' ' +
+ pad(date.getUTCHours()) + ':' + pad(date.getUTCMinutes()) + ':' +
pad(date.getUTCSeconds()));
+ $( "#hover" ).text (hoverInfo.value.toPrecision(4) + hoverInfo.units);
+ var x = Math.max(0, hoverInfo.x - hover.offsetWidth / 2);
+ var y;
+ if (hoverInfo.above)
+ y = hoverInfo.y - hover.offsetHeight - 10;
+ else
+ y = hoverInfo.y + 10;
+ $( hover ).css('top', y).css('left', x).show();
+ } else {
+ $( hover ).hide();
+ }
+}
+
+PerfDisplay.prototype.setDetails = function(detailsInfo)
+{
+ this.detailsInfo = detailsInfo;
+ if (this.detailsInfo) {
+ this.refreshDetails();
+ $( "body" ).addClass("details-showing");
+ } else {
+ $( "body" ).removeClass("details-showing");
+ }
+
+ this.queueRefresh();
+}
+
+PerfDisplay.prototype.moveDetails = function(direction)
+{
+ if (this.detailsInfo == null)
+ return;
+
+ var chart;
+ for (var i = 0; i < this.charts.length; i++) {
+ if (this.charts[i].metric == this.detailsInfo.metric) {
+ chart = this.charts[i];
+ break;
+ }
+ }
+
+ var targets = chart.getTargets();
+ var newTime = null;
+ var newTarget = null;
+ var newRevision = null;
+ var oldTime = this.detailsInfo.time;
+
+ for (var i = 0; i < targets.length; i++) {
+ var target = targets[i];
+ var targetRevisions = this.allTargets[target.target].revisions;
+ var targetTime = null;
+
+ if (direction > 0) {
+ for (var j = 0; j <= 2 * target.values.length; j += 2) {
+ var time = target.values.data[j];
+ if (time > oldTime &&
+ targetRevisions[time] != this.detailsInfo.revision) {
+ targetTime = time;
+ break;
+ }
+ }
+
+ if (targetTime != null && (newTime == null || targetTime < newTime)) {
+ newTime = targetTime;
+ newRevision = targetRevisions[targetTime];
+ newTarget = target;
+ }
+ } else {
+ for (var j = 2 * target.values.length - 2; j >= 0; j -= 2) {
+ var time = target.values.data[j];
+ if (time < oldTime &&
+ targetRevisions[time] != this.detailsInfo.revision) {
+ targetTime = time;
+ break;
+ }
+ }
+
+ if (targetTime != null && (newTime == null || targetTime > newTime)) {
+ newTime = targetTime;
+ newRevision = targetRevisions[targetTime];
+ newTarget = target;
+ }
+ }
+ }
+
+ if (newTime != null) {
+ chart.setHover(newTarget, newTime);
+
+ this.detailsInfo.time = newTime;
+ this.detailsInfo.revision = newRevision;
+ this.refreshDetails();
+ this.queueRefresh();
+ }
+}
+
+PerfDisplay.prototype.refreshDetails = function() {
+ if (!this.detailsInfo)
+ return;
+
+ $( "#detailsDetails" ).empty();
+ $( "#detailsLink" ).empty();
+
+ if (this.detailsInfo.time < this.startSeconds + this.rangeSeconds * 0.1)
+ this.setPositionAndRange(this.detailsInfo.time + this.rangeSeconds * 0.4, this.rangeType, true);
+ else if (this.detailsInfo.time > this.startSeconds + this.rangeSeconds * 0.9)
+ this.setPositionAndRange(this.detailsInfo.time - this.rangeSeconds * 0.4, this.rangeType, true);
+
+ if (this.detailsInfo.revision in this.revisions) {
+ this.updateDetails();
+ return;
+ }
+
+ var date = new Date(this.detailsInfo.time * 1000.);
+ this.fetchRevisions(date);
+
+ if (date.getUTCHours() == 23)
+ this.fetchRevisions(new Date(date.getTime() + DAY_MSECS));
+ else if (date.getUTCHours() == 0)
+ this.fetchRevisions(new Date(date.getTime() - DAY_MSECS));
+}
+
+PerfDisplay.prototype.updateDetails = function() {
+ if (!this.detailsInfo)
+ return;
+
+ var revision = this.detailsInfo.revision;
+ var build = this.revisions[this.detailsInfo.revision];
+ if (build == null)
+ return;
+
+ var parts = build.split("/");
+ var id = parts[0] + parts[1] + parts[2] + "." + parts[3];
+ $( "#detailsLink" )
+ .attr('href', 'http://build.gnome.org/#/build/' + id)
+ .text(id);
+
+ var url = 'http://build.gnome.org/continuous/work/builds/' + build + '/bdiff.json';
+ $.ajax({datatype: "json",
+ url: url,
+ success:
+ function(data) {
+ if (!this.detailsInfo || this.detailsInfo.revision != revision)
+ return;
+
+ this.fillDetails(data);
+ }.bind(this)});
+}
+
+PerfDisplay.prototype.fillDetails = function(data) {
+ $( "#detailsDetails" ).empty();
+
+ for (var i = 0; i < data.modified.length; i++) {
+ var modified = data.modified[i];
+ var name = modified.latest.name;
+ var src = modified.latest.src;
+
+ var cgitBase = null;
+
+ var m;
+ m = /^git:git:\/\/git.gnome.org\/(.*)/.exec(src);
+ if (m)
+ cgitBase = 'http://git.gnome.org/browse/' + m[1];
+ m = /^git:git:\/\/anongit.freedesktop.org\/git\/(.*)/.exec(src);
+ if (m)
+ cgitBase = 'http://cgit.freedesktop.org/' + m[1];
+
+ var divQ = $( "<div class='module-detail'></div> ").appendTo("#detailsDetails");
+ $( "<div class='module-title' /> ").appendTo(divQ).text(name);
+
+
+ for (var j = 0; j < modified.gitlog.length; j++) {
+ var log = modified.gitlog[j];
+ var subject;
+ if (log.Subject.length > 60)
+ subject = log.Subject.substring(0, 57) + "...";
+ else
+ subject = log.Subject;
+ if (cgitBase) {
+ var commitQ = $( "<div class='module-commit' />" ).appendTo(divQ);
+ var url = cgitBase + '/commit/?id=' + log.Checksum;
+ $( "<a />" ).attr('href', url).attr('target',
'commitDetails').text(subject).appendTo(commitQ);
+ } else {
+ $( "<div class='module-commit' /> ").appendTo(divQ).text(subject);
+ }
+ }
+ }
+}
+
+PerfDisplay.prototype.fetchRevisions = function(date) {
+ var datePath = date.getUTCFullYear() + '/' + pad(date.getUTCMonth() + 1) + '/' + pad(date.getUTCDate());
+ if (datePath in this.loadedRevisions)
+ return;
+
+ this.loadedRevisions[datePath] = 1;
+
+ var url = 'http://build.gnome.org/continuous/work/builds/' + datePath + '/index.json';
+ $.ajax({datatype: "json",
+ url: url,
+ success:
+ function(data) {
+ var targetMap = data.targetMap;
+ for (var revision in targetMap)
+ this.revisions[revision] = datePath + '/' + targetMap[revision][0];
+ this.updateDetails();
+ }.bind(this)});
+}
+
//////////////////////////////////////////////////////////////////////////////////
function setRange(e, a, rangeType) {
diff --git a/static/perf.css b/static/perf.css
index 58e13c5..e3f876c 100644
--- a/static/perf.css
+++ b/static/perf.css
@@ -12,6 +12,10 @@ body {
cursor: grabbing;
}
+.clear {
+ clear: both;
+}
+
html, body {
margin: 0px;
padding: 0px;
@@ -132,6 +136,11 @@ h1 {
fill: #aaaaaa;
}
+.chart-highlight {
+ fill: none;
+ stroke: red;
+}
+
a {
color: #3465A4;
}
@@ -160,6 +169,10 @@ a {
overflow: scroll;
}
+body.details-showing #mainLeft {
+ bottom: 100px;
+}
+
#mainLeft, #mainRight {
top: 160px;
}
@@ -177,6 +190,10 @@ a {
z-index: 0;
}
+body.details-showing #mainRight {
+ bottom: 100px;
+}
+
#mainRight table {
font-size: 11px;
table-layout: fixed;
@@ -296,6 +313,10 @@ a {
bottom: 100px;
}
+#metricPage.details-showing .chart-div {
+ bottom: 0px;
+}
+
#metricPage .chart {
height: 100%;
}
@@ -303,3 +324,115 @@ a {
#metricPage #mainLeft {
overflow: hidden;
}
+
+#hover {
+ display: none;
+ font-size: 10px;
+ padding: 2px;
+ position: fixed;
+ background: rgba(0, 0, 0, 0.5);
+ color: white;
+ left: 0px;
+}
+
+#details {
+ position: fixed;
+ display: none;
+ height: 100px;
+ bottom: 0px;
+ left: 0px;
+ right: 0px;
+ border-top: 1px solid #aaaaaa;
+ overflow-y: scroll;
+ font-size: 11px;
+}
+
+#details a {
+ text-decoration: none;
+}
+
+#detailsTitle {
+ font-size: 12px;
+ margin-left: 3px;
+ margin-bottom: 3px;
+}
+
+body.details-showing #details {
+ display: block;
+}
+
+#detailsButtons {
+ position: absolute;
+ right: 0px;
+ top: 0px;
+ width: 30px;
+ height: 10px;
+}
+
+#detailsButtons div {
+ position: absolute;
+ top: 0px;
+ width: 10px;
+ height: 10px;
+ background-size: contain;
+ box-shadow: inset -1px -1px 3px -1px #aaaaaa;
+ border-bottom: 1px solid #aaaaaa;
+ border-left: 1px solid #aaaaaa;
+}
+
+#detailsButtons div:active {
+ box-shadow: inset 1px 1px 3px -1px #888888;
+}
+
+#detailsPrevious {
+ left: 0px;
+ background: url(/static/images/go-previous.png),#eeeeee;
+}
+
+#detailsPrevious:hover {
+ background: url(/static/images/go-previous.png);
+ background-size: contain;
+}
+
+#detailsPrevious:active {
+ background: url(/static/images/go-previous.png),#aaaaaa;
+ background-size: contain;
+}
+
+#detailsNext {
+ left: 10px;
+ background: url(/static/images/go-next.png),#eeeeee;
+}
+
+#detailsNext:hover {
+ background: url(/static/images/go-next.png);
+ background-size: contain;
+}
+
+#detailsNext:active {
+ background: url(/static/images/go-next.png),#aaaaaa;
+ background-size: contain;
+}
+
+#detailsClose {
+ left: 20px;
+ background: url(/static/images/window-close.png),#eeeeee;
+}
+
+#detailsClose:hover {
+ background: url(/static/images/window-close.png);
+ background-size: contain;
+}
+
+#detailsClose:active {
+ background: url(/static/images/window-close.png),#aaaaaa;
+ background-size: contain;
+}
+
+.module-detail {
+ float: left;
+ border: 1px solid #aaaaaa;
+ padding: 2px;
+ margin-right: 3px;
+ margin-bottom: 3px;
+}
diff --git a/templates/metrics/chart.html b/templates/metrics/chart.html
index 7068262..357fc70 100644
--- a/templates/metrics/chart.html
+++ b/templates/metrics/chart.html
@@ -10,6 +10,7 @@
<svg class="chart-body">
<g class="chart-grid" />
<g class="chart-data" />
+ <g class="chart-highlights" />
</svg>
</svg>
</div>
diff --git a/templates/metrics/common.html b/templates/metrics/common.html
new file mode 100644
index 0000000..45e998e
--- /dev/null
+++ b/templates/metrics/common.html
@@ -0,0 +1,12 @@
+ <div id="hover">
+ </div>
+ <div id="details">
+ <div id="detailsTitle"><a id="detailsLink" target="buildDetails"></a></div>
+ <div id="detailsDetails"></div>
+ <div class="clear"></div>
+ <div id="detailsButtons">
+ <div id="detailsPrevious"> </div>
+ <div id="detailsNext"> </div>
+ <div id="detailsClose"> </div>
+ </div>
+ </div>
diff --git a/templates/metrics/home.html b/templates/metrics/home.html
index c44ba8f..5ba9d89 100644
--- a/templates/metrics/home.html
+++ b/templates/metrics/home.html
@@ -36,5 +36,6 @@
{% endfor %}
</ul>
</div>
+ {% include "metrics/common.html" %}
</body>
</html>
diff --git a/templates/metrics/metric.html b/templates/metrics/metric.html
index 03ab43a..171bbb9 100644
--- a/templates/metrics/metric.html
+++ b/templates/metrics/metric.html
@@ -82,5 +82,6 @@
</table>
</div>
</div>
+ {% include "metrics/common.html" %}
</body>
</html>
diff --git a/templates/metrics/target.html b/templates/metrics/target.html
index 1947dcf..362f89e 100644
--- a/templates/metrics/target.html
+++ b/templates/metrics/target.html
@@ -101,5 +101,6 @@
</table>
</div>
</div>
+ {% include "metrics/common.html" %}
</body>
</html>
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]