[perf-web] Improve loading logic
- From: Owen Taylor <otaylor src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [perf-web] Improve loading logic
- Date: Wed, 23 Jul 2014 00:37:48 +0000 (UTC)
commit a94ed45fbbb1082cd7c22444520f13329f3e7d07
Author: Owen W. Taylor <otaylor fishsoup net>
Date: Sun Jun 29 13:43:58 2014 -0400
Improve loading logic
If the user is scrolling, we might load data multiple times before
we get a reply. Deal with this and edge cases with a TimeRanges
class to handle internval arithmetic.
static/main.js | 187 +++++++++++++++++++++++++++++++++++++++++++++++---------
1 files changed, 157 insertions(+), 30 deletions(-)
---
diff --git a/static/main.js b/static/main.js
index d56d9c1..b78c6e6 100644
--- a/static/main.js
+++ b/static/main.js
@@ -765,6 +765,91 @@ TargetTable.prototype.prepareColumns = function() {
}
////////////////////////////////////////////////////////////////////////
+// Arithmetic on a list of half-open intervals. Simplicity of implementation
+// was favored in writing this over performance
+////////////////////////////////////////////////////////////////////////
+
+function TimeRanges() {
+ this.ranges = [];
+}
+
+TimeRanges.prototype.add = function(start, end) {
+ this.subtract(start, end);
+
+ var toInsert = {
+ start: start,
+ end: end
+ };
+ for (var i = 0; i < this.ranges.length; i++) {
+ if (start < this.ranges[i].start) {
+ this.ranges.splice(i, 0, toInsert);
+ toInsert = null;
+ break;
+ }
+ }
+
+ if (toInsert)
+ this.ranges.push(toInsert);
+
+ for (var i = 1; i < this.ranges.length; i++) {
+ if (this.ranges[i].start == this.ranges[i - 1].end) {
+ this.ranges[i - 1].end = this.ranges[i].end;
+ this.ranges.splice(i, 1);
+ i--;
+ }
+ }
+}
+
+TimeRanges.prototype.addRanges = function(other) {
+ for (var i = 0; i < other.ranges.length; i++)
+ this.add(other.ranges[i].start, other.ranges[i].end);
+}
+
+TimeRanges.prototype.subtract = function(start, end) {
+ for (var i = 0; i < this.ranges.length; i++) {
+ var range = this.ranges[i];
+ if (end > range.start && start < range.end) {
+ if (start <= range.start) {
+ range.start = Math.min(range.end, end);
+ if (range.start == range.end) {
+ this.ranges.splice(i, 1);
+ i--;
+ }
+ } else if (end >= range.end) {
+ range.end = start;
+ } else {
+ this.ranges.splice(i + 1, 0,
+ {
+ start: end,
+ end: range.end
+ });
+ range.end = start;
+ i++;
+ }
+ }
+ }
+}
+
+TimeRanges.prototype.subtractRanges = function(other) {
+ for (var i = 0; i < other.ranges.length; i++)
+ this.subtract(other.ranges[i].start, other.ranges[i].end);
+}
+
+TimeRanges.prototype.isEmpty = function() {
+ return this.ranges.length == 0;
+}
+
+TimeRanges.prototype.toString = function() {
+ var result = '';
+ for (var i = 0; i < this.ranges.length; i++) {
+ if (result.length > 0)
+ result += ' ';
+ result += '[' + this.ranges[i].start + ',' + this.ranges[i].end + ')';
+ }
+ return result;
+}
+
+////////////////////////////////////////////////////////////////////////
function PerfDisplay(target, metric, dataMinTime, dataMaxTime, centerTime, rangeType) {
this.target = target;
@@ -779,9 +864,10 @@ function PerfDisplay(target, metric, dataMinTime, dataMaxTime, centerTime, range
this.allTargetsSorted = [];
this.windowLoaded = false;
+
+ this.pendingLoads = [];
this.loadedGroup = null;
- this.loadedStartSeconds = null;
- this.loadedEndSeconds = null;
+ this.loadedRanges = new TimeRanges();
this.setPositionAndRange(centerTime, rangeType, false);
$(window).load(this.onWindowLoaded.bind(this));
@@ -870,25 +956,12 @@ PerfDisplay.prototype.load = function() {
if (endSeconds <= startSeconds)
return;
- if (group == this.loadedGroup) {
- // Or for data that we already have
- if (startSeconds >= this.loadedStartSeconds) {
- startSeconds = Math.max(startSeconds, this.loadedEndSeconds);
- if (startSeconds >= endSeconds)
- return;
- } else if (endSeconds <= this.loadedEndSeconds) {
- endSeconds = Math.min(endSeconds, this.loadedStartSeconds);
- if (endSeconds <= startSeconds)
- return;
- }
- }
-
- // API is day => day, inclusive, and retrieves all summaries that
+ // API is in terms of days, retrieves all summaries that
// overlap that range of days; we round our retrieved range to
- // summary boundaries so that the "all overlapping" doesn't
+ // the boundaries of the API so that the "all overlapping" doesn't
// affect the retrieved range
- // Find the half-open day range
+ // Find the day range
var startDate = new Date(startSeconds * 1000);
TIME_OPS['day'].truncate(startDate);
var endDate = new Date(endSeconds * 1000);
@@ -908,10 +981,38 @@ PerfDisplay.prototype.load = function() {
TIME_OPS[group].next(endDate);
}
- startSeconds = startDate.getTime() / 1000;
- endSeconds = endDate.getTime() / 1000;
+ var timeRanges = new TimeRanges();
+ timeRanges.add(startDate.getTime() / 1000, endDate.getTime() / 1000);
+
+ var i;
+ var groupChanged = false;
+
+ // Now remove data that we are loading
+ for (i = this.pendingLoads.length - 1; i >= 0; i--) {
+ if (this.pendingLoads[i].group != group) {
+ groupChanged = true;
+ break;
+ }
+ timeRanges.subtract(this.pendingLoads[i].start, this.pendingLoads[i].end);
+ }
+
+ // And data we already have
+ if (!groupChanged && this.loadedGroup == group)
+ timeRanges.subtractRanges(this.loadedRanges);
+
+ for (var i = 0; i < timeRanges.ranges.length; i++) {
+ var range = timeRanges.ranges[i];
+ this._loadRange(group, range.start, range.end);
+ }
+}
+
+PerfDisplay.prototype._loadRange = function(group, start, end) {
+ var startDate = new Date(start * 1000);
+ TIME_OPS['day'].truncate(startDate);
+ var endDate = new Date(end * 1000);
+ TIME_OPS['day'].truncate(endDate);
- // Now make the day-range closed, as the API requires
+ // Make the day-range closed, as the API requires
endDate.setTime(endDate.getTime() - DAY_MSECS);
var url = '/api/values?start=' + formatDay(startDate) + '&end=' + formatDay(endDate) + '&group=' + group;
@@ -919,19 +1020,32 @@ PerfDisplay.prototype.load = function() {
url += '&target=' + encodeURIComponent(this.target);
if (this.metric != null)
url += '&metric=' + encodeURIComponent(this.metric);
- $.getJSON(url,
+
+ var loadInfo = {
+ group: group,
+ start: start,
+ end: end,
+ };
+
+ this.pendingLoads.push(loadInfo);
+
+ $.ajax({datatype: "json",
+ url: url,
+ success:
function(data) {
+ var addedData = false;
+
+ this.pendingLoads.splice(this.pendingLoads.indexOf(loadInfo), 1);
+
var timeOffset = TIME_OFFSETS[group];
if (group != this.loadedGroup) {
this.data = {};
- this.loadedStartSeconds = startSeconds;
- this.loadedEndSeconds = endSeconds;
- } else {
- this.loadedStartSeconds = Math.min(startSeconds, this.loadedStartSeconds);
- this.loadedEndSeconds = Math.max(endSeconds, this.loadedEndSeconds);
+ this.loadedRanges = new TimeRanges();
+ this.loadedGroup = group;
}
- this.loadedGroup = group;
+
+ this.loadedRanges.add(start, end);
for (var i = 0; i < data.length; i++) {
var metricData = data[i];
@@ -942,6 +1056,12 @@ PerfDisplay.prototype.load = function() {
var targetData = metricData.targets[j];
var valuesData = targetData.values;
+
+ if (valuesData.length > 0)
+ addedData = true;
+ else
+ continue;
+
var values = new ValueBuffer(valuesData.length);
if (group == 'none') {
for (var k = 0; k < valuesData.length; k++) {
@@ -964,13 +1084,20 @@ PerfDisplay.prototype.load = function() {
}
}
+ if (!addedData)
+ return;
+
this.allTargetsSorted = [];
for (var target in this.allTargets)
theDisplay.allTargetsSorted.push(target);
this.allTargetsSorted.sort();
this.refresh();
- }.bind(this));
+ }.bind(this),
+ error:
+ function() {
+ this.pendingLoads.splice(this.pendingLoads.indexOf(loadInfo), 1);
+ }});
}
PerfDisplay.prototype.onWindowLoaded = function() {
@@ -1006,7 +1133,7 @@ PerfDisplay.prototype.onWindowLoaded = function() {
this.refresh();
}.bind(this));
- if (this.loadedStartSeconds != null)
+ if (!this.loadedRanges.isEmpty())
this.refresh();
}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]