[perf-web] Optimize updating of the graphs
- From: Owen Taylor <otaylor src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [perf-web] Optimize updating of the graphs
- Date: Sun, 14 Sep 2014 20:06:18 +0000 (UTC)
commit b5e364779debf1d3429e1c51fad8b60b59c967ef
Author: Owen W. Taylor <otaylor fishsoup net>
Date: Sun Sep 14 14:58:41 2014 -0400
Optimize updating of the graphs
* Do the updates in a requestAnimationFrame callback rather than synchronously
in response to mouse moves and scrolls - this prevents us from spending
all our time updating and none updating.
* Draw the points with a single SVG <path> element so that we don't have to
change huge number of attributes or create DOM elements when we scroll
other elements into view.
* Cache SVG elements of different types and just update-them and move them
about rather than adding and removing them.
* Don't include points outside of the visible range.
static/main.js | 154 ++++++++++++++++++++++++++++++++++-------
templates/metrics/chart.html | 2 +
2 files changed, 129 insertions(+), 27 deletions(-)
---
diff --git a/static/main.js b/static/main.js
index 58b80ea..0f09543 100644
--- a/static/main.js
+++ b/static/main.js
@@ -82,6 +82,14 @@ function Chart(svg) {
this.lowerText = $( svg ).find( ".chart-lower" ).get(0);
this.background = $( svg ).find( ".chart-background" ).get(0);
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.polylines = [];
+ this.pointPaths = [];
+ this.xGridLines = [];
+ this.yGridLines = [];
+ this.xLabelTexts = [];
this.top = 10;
this.bottom = 10;
@@ -141,6 +149,14 @@ Chart.prototype.allocate = function() {
allocateElement(this.body, MARGIN_LEFT + 1, MARGIN_TOP + 1, this.bodyWidth,
this.bodyHeight);
}
+Chart.prototype.removeExtraElements = function(elements, nToSave) {
+ if (elements.length > nToSave) {
+ for (var j = nToSave; j < elements.length; j++)
+ elements[j].parentNode.removeChild(elements[j]);
+ elements.splice(nToSave, elements.length - nToSave);
+ }
+}
+
Chart.prototype.drawTarget = function(targetData) {
var values = targetData.values;
var index = targetData.index;
@@ -148,29 +164,64 @@ Chart.prototype.drawTarget = function(targetData) {
if (values.length == 0)
return;
+ var path;
+
+ var polyline = this.polylines[index];
+ if (this.polylines[index] == null) {
+ this.polylines[index] = polyline = createElement("polyline");
+ polyline.setAttribute("stroke", strokeStyles[index % strokeStyles.length]);
+ polyline.setAttribute("fill", "none");
+ this.chartData.appendChild(polyline);
+ }
+
path = "";
- for (var i = 0; i < 2 * values.length; i += 2)
- path += this.x(values.data[i]) + "," + this.y(values.data[i + 1]) + " ";
- var polyline = createElement("polyline");
- polyline.setAttribute("stroke", strokeStyles[index % strokeStyles.length]);
- polyline.setAttribute("fill", "none");
+ // Start at the point *before* the first in-range point
+ var i;
+ for (i = 0; i < 2 * values.length; i += 2) {
+ var x = this.x(values.data[i]);
+ if (x >= 0) {
+ i = Math.max(i - 2, 0);
+ break;
+ }
+ }
+
+ // Then continue to first point past the range
+ for (; i < 2 * values.length; i += 2) {
+ var x = this.x(values.data[i]);
+ var y = this.y(values.data[i + 1])
+ path += x + "," + y + " ";
+ if (x >= this.bodyWidth)
+ break;
+ }
+
polyline.setAttribute("points", path);
- this.body.appendChild(polyline);
+ var pointPath = this.pointPaths[index];
+ if (this.pointPaths[index] == null) {
+ this.pointPaths[index] = pointPath = createElement("path");
+ pointPath.setAttribute("fill", strokeStyles[index % strokeStyles.length]);
+ this.chartData.appendChild(pointPath);
+ }
+
+ path = "";
for (var i = 0; i < 2 * values.length; i += 2) {
var x = this.x(values.data[i]);
+ if (x + 2 <= 0 || x - 2 >= this.bodyWidth)
+ continue;
+
var y = this.y(values.data[i + 1]);
- var rect = createElement("rect");
- allocateElement(rect, x - 2, y - 2, 4, 4);
- rect.setAttribute("fill", strokeStyles[index % strokeStyles.length]);
- this.body.appendChild(rect);
+ path += "M" + (x - 2) + " " + (y - 2) + " " + "h4v4h-4z"
}
+
+ pointPath.setAttribute("d", path);
}
Chart.prototype.getTargets = function() {
var targets = [];
+ if (!(this.metric in theDisplay.data))
+ return targets;
for (var i = 0; i < theDisplay.allTargetsSorted.length; i++) {
var target = theDisplay.allTargetsSorted[i];
@@ -292,8 +343,6 @@ TIME_OPS = {
};
Chart.prototype.drawXLabels = function() {
- $( this.xLabels ).empty();
-
var truncate;
var step;
var format;
@@ -319,21 +368,39 @@ Chart.prototype.drawXLabels = function() {
var endTime = theDisplay.endSeconds * 1000;
var date = new Date(startTime);
+ var nUsed = 0;
+
timeOps.truncate(date);
while (date.getTime() < endTime) {
if (date.getTime() >= startTime) {
+ var t = this.xLabelTexts[nUsed];
var x = this.x(date.getTime() / 1000);
- var t = createElement("text", "chart-xlabel");
- positionElement(t, x, 10);
+
+ if (t == null) {
+ this.xLabelTexts[nUsed] = t = createElement("text", "chart-xlabel");
+ this.xLabels.appendChild(t);
+ } else {
+ t.removeChild(t.firstChild);
+ }
+
t.appendChild(document.createTextNode(timeOps.format(date)));
- this.xLabels.appendChild(t);
+ positionElement(t, x, 10);
+
+ var rect = this.xGridLines[nUsed];
+ if (rect == null) {
+ this.xGridLines[nUsed] = rect = createElement("rect", "chart-grid");
+ this.grid.appendChild(rect);
+ }
- var rect = createElement("rect", "chart-grid");
allocateElement(rect, x - 0.5, 0, 1, this.bodyHeight);
- this.body.appendChild(rect);
+
+ nUsed++;
}
timeOps.next(date);
}
+
+ this.removeExtraElements(this.xGridLines, nUsed);
+ this.removeExtraElements(this.xLabelTexts, nUsed);
}
Chart.prototype.drawYLabels = function(targets) {
@@ -429,28 +496,46 @@ Chart.prototype.drawYLabels = function(targets) {
this.top = top;
for (i = 1; i < steps; i++) {
- var rect = createElement("rect", "chart-grid");
+ var rect = this.yGridLines[i - 1];
+ if (rect == null) {
+ this.yGridLines[i - 1] = rect = createElement("rect", "chart-grid");
+ this.grid.appendChild(rect);
+ }
+
allocateElement(rect, 0, this.y(bottom + i * step) - 0.5, this.bodyWidth, 1);
- this.body.appendChild(rect);
}
+
+ this.removeExtraElements(this.yGridLines, steps - 1);
}
Chart.prototype.draw = function() {
this.allocate();
- $( this.body ).empty();
-
this.drawXLabels();
- if (!(this.metric in theDisplay.data))
- return;
-
- targets = this.getTargets();
+ var targets = this.getTargets();
+ var usedIndices = {};
this.drawYLabels(targets);
- for (var i = 0; i < targets.length; i++)
+ for (var i = 0; i < targets.length; i++) {
this.drawTarget(targets[i]);
+ usedIndices[i] = true;
+ }
+
+ for (index in this.pointPaths) {
+ if (!(index in usedIndices)) {
+ this.chartData.removeChild(this.pointPaths[index]);
+ delete this.pointPaths[index];
+ }
+ }
+
+ for (index in this.polylines) {
+ if (!(index in usedIndices)) {
+ this.chartData.removeChild(this.polylines[index]);
+ delete this.polylines[index];
+ }
+ }
}
////////////////////////////////////////////////////////////////////////
@@ -978,12 +1063,17 @@ PerfDisplay.prototype.setPositionAndRange = function(centerTime, rangeType, clam
this.centerTime = this.endSeconds - this.rangeSeconds / 2;
this.load();
- this.refresh();
+ this.queueRefresh();
if (rangeTypeChanged)
history.replaceState(null, null, "?r=" + rangeType);
}
PerfDisplay.prototype.refresh = function() {
+ if (this.refreshQueueId != null) {
+ window.cancelAnimationFrame(this.refreshQueueId);
+ this.refreshQueueId = null;
+ }
+
if (!this.windowLoaded)
return;
@@ -994,6 +1084,16 @@ PerfDisplay.prototype.refresh = function() {
this.table.refresh();
}
+PerfDisplay.prototype.queueRefresh = function() {
+ if (this.refreshQueueId != null)
+ return;
+
+ requestAnimationFrame(function() {
+ this.refreshQueueId = null;
+ theDisplay.refresh();
+ });
+}
+
var TIME_OFFSETS = {
'none': 0,
'hour6': 3 * 60 * 60,
diff --git a/templates/metrics/chart.html b/templates/metrics/chart.html
index ca5c95d..7068262 100644
--- a/templates/metrics/chart.html
+++ b/templates/metrics/chart.html
@@ -8,6 +8,8 @@
<g class="chart-x-labels" />
<rect class="chart-background" fill="white" stroke="black"/>
<svg class="chart-body">
+ <g class="chart-grid" />
+ <g class="chart-data" />
</svg>
</svg>
</div>
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]