1 // We don't use DS.Model for these object types because we can't afford to process millions of them.
6 _maxNetworkLatency: 3 * 60 * 1000 /* 3 minutes */,
9 PrivilegedAPI.sendRequest = function (url, parameters)
11 return this._generateTokenInServerIfNeeded().then(function (token) {
12 return PrivilegedAPI._post(url, $.extend({token: token}, parameters));
16 PrivilegedAPI._generateTokenInServerIfNeeded = function ()
19 return new Ember.RSVP.Promise(function (resolve, reject) {
20 if (self._token && self._expiration > Date.now() + self._maxNetworkLatency)
23 PrivilegedAPI._post('generate-csrf-token')
24 .then(function (result, reject) {
25 self._token = result['token'];
26 self._expiration = new Date(result['expiration']);
32 PrivilegedAPI._post = function (url, parameters)
34 return new Ember.RSVP.Promise(function (resolve, reject) {
36 url: '../privileged-api/' + url,
38 contentType: 'application/json',
39 data: parameters ? JSON.stringify(parameters) : '{}',
41 }).done(function (data) {
42 if (data.status != 'OK') {
43 console.log('PrivilegedAPI failed', data);
47 }).fail(function (xhr, status, error) {
48 reject(xhr.status + (error ? ', ' + error : '') + '\n\nWith response:\n' + xhr.responseText);
54 _cachedCommitsByRepository: {}
57 CommitLogs.fetchForTimeRange = function (repository, from, to, keyword)
61 params.push(['from', from]);
62 params.push(['to', to]);
65 params.push(['keyword', keyword]);
67 // FIXME: We should be able to use the cache if all commits in the range have been cached.
68 var useCache = from && to && !keyword;
70 var url = '../api/commits/' + repository + '/?' + params.map(function (keyValue) {
71 return encodeURIComponent(keyValue[0]) + '=' + encodeURIComponent(keyValue[1]);
75 var cachedCommitsForRange = CommitLogs._cachedCommitsBetween(repository, from, to);
76 if (cachedCommitsForRange)
77 return new Ember.RSVP.Promise(function (resolve) { resolve(cachedCommitsForRange); });
80 return new Ember.RSVP.Promise(function (resolve, reject) {
81 $.getJSON(url, function (data) {
82 if (data.status != 'OK') {
87 var fetchedCommits = data.commits;
88 fetchedCommits.forEach(function (commit) { commit.time = new Date(commit.time); });
91 CommitLogs._cacheConsecutiveCommits(repository, from, to, fetchedCommits);
93 resolve(fetchedCommits);
94 }).fail(function (xhr, status, error) {
95 reject(xhr.status + (error ? ', ' + error : ''));
100 CommitLogs._cachedCommitsBetween = function (repository, from, to)
102 var cachedCommits = this._cachedCommitsByRepository[repository];
106 var startCommit = cachedCommits.commitsByRevision[from];
107 var endCommit = cachedCommits.commitsByRevision[to];
108 if (!startCommit || !endCommit)
111 return cachedCommits.commitsByTime.slice(startCommit.cacheIndex, endCommit.cacheIndex + 1);
114 CommitLogs._cacheConsecutiveCommits = function (repository, from, to, consecutiveCommits)
116 var cachedCommits = this._cachedCommitsByRepository[repository];
117 if (!cachedCommits) {
118 cachedCommits = {commitsByRevision: {}, commitsByTime: []};
119 this._cachedCommitsByRepository[repository] = cachedCommits;
122 consecutiveCommits.forEach(function (commit) {
123 if (cachedCommits.commitsByRevision[commit.revision])
125 cachedCommits.commitsByRevision[commit.revision] = commit;
126 cachedCommits.commitsByTime.push(commit);
129 cachedCommits.commitsByTime.sort(function (a, b) { return a.time - b.time; });
130 cachedCommits.commitsByTime.forEach(function (commit, index) { commit.cacheIndex = index; });
134 function Measurement(rawData)
139 var revisions = this._raw['revisions'];
140 // FIXME: Fix this in the server side.
141 if (Array.isArray(revisions))
143 this._raw['revisions'] = revisions;
145 for (var repositoryId in revisions) {
146 var commitTimeOrUndefined = revisions[repositoryId][1]; // e.g. ["162190", 1389945046000]
147 if (latestTime < commitTimeOrUndefined)
148 latestTime = commitTimeOrUndefined;
150 this._latestCommitTime = latestTime !== -1 ? new Date(latestTime) : null;
151 this._buildTime = new Date(this._raw['buildTime']);
152 this._confidenceInterval = undefined;
153 this._formattedRevisions = undefined;
156 Measurement.prototype.revisionForRepository = function (repositoryId)
158 var revisions = this._raw['revisions'];
159 var rawData = revisions[repositoryId];
160 return rawData ? rawData[0] : null;
163 Measurement.prototype.commitTimeForRepository = function (repositoryId)
165 var revisions = this._raw['revisions'];
166 var rawData = revisions[repositoryId];
167 return rawData ? new Date(rawData[1]) : null;
170 Measurement.prototype.formattedRevisions = function (previousMeasurement)
172 var revisions = this._raw['revisions'];
173 var previousRevisions = previousMeasurement ? previousMeasurement._raw['revisions'] : null;
174 var formattedRevisions = {};
175 for (var repositoryId in revisions) {
176 var currentRevision = revisions[repositoryId][0];
177 var previousRevision = previousRevisions && previousRevisions[repositoryId] ? previousRevisions[repositoryId][0] : null;
178 var formatttedRevision = Measurement.formatRevisionRange(currentRevision, previousRevision);
179 formattedRevisions[repositoryId] = formatttedRevision;
182 return formattedRevisions;
185 Measurement.formatRevisionRange = function (currentRevision, previousRevision)
187 var revisionChanged = false;
188 if (previousRevision == currentRevision)
189 previousRevision = null;
191 revisionChanged = true;
193 var revisionPrefix = '';
194 var revisionDelimiter = '-';
196 if (parseInt(currentRevision) == currentRevision) { // e.g. r12345.
197 currentRevision = parseInt(currentRevision);
198 revisionPrefix = 'r';
199 if (previousRevision)
200 previousRevision = (parseInt(previousRevision) + 1);
201 } else if (currentRevision.indexOf(' ') >= 0) // e.g. 10.9 13C64.
202 revisionDelimiter = ' - ';
203 else if (currentRevision.length == 40) { // e.g. git hash
204 var formattedCurrentHash = currentRevision.substring(0, 8);
205 if (previousRevision)
206 label = previousRevision.substring(0, 8) + '..' + formattedCurrentHash;
208 label = formattedCurrentHash;
212 if (previousRevision)
213 label = revisionPrefix + previousRevision + revisionDelimiter + revisionPrefix + currentRevision;
215 label = revisionPrefix + currentRevision;
220 previousRevision: previousRevision,
221 currentRevision: currentRevision,
222 revisionChanged: revisionChanged
226 Measurement.prototype.id = function ()
228 return this._raw['id'];
231 Measurement.prototype.mean = function()
233 return this._raw['mean'];
236 Measurement.prototype.confidenceInterval = function()
238 if (this._confidenceInterval === undefined) {
239 var delta = Statistics.confidenceIntervalDelta(0.95, this._raw["iterationCount"], this._raw["sum"], this._raw["squareSum"]);
240 var mean = this.mean();
241 this._confidenceInterval = isNaN(delta) ? null : [mean - delta, mean + delta];
243 return this._confidenceInterval;
246 Measurement.prototype.latestCommitTime = function()
248 return this._latestCommitTime || this._buildTime;
251 Measurement.prototype.buildId = function()
253 return this._raw['build'];
256 Measurement.prototype.buildNumber = function ()
258 return this._raw['buildNumber'];
261 Measurement.prototype.builderId = function ()
263 return this._raw['builder'];
266 Measurement.prototype.buildTime = function()
268 return this._buildTime;
271 Measurement.prototype.formattedBuildTime = function ()
273 return Measurement._formatDate(this.buildTime());
276 Measurement._formatDate = function (date)
278 return date.toISOString().replace('T', ' ').replace(/\.\d+Z$/, '');
281 Measurement.prototype.bugs = function ()
283 return this._raw['bugs'];
286 Measurement.prototype.hasBugs = function ()
288 var bugs = this.bugs();
289 return bugs && Object.keys(bugs).length;
292 Measurement.prototype.markedOutlier = function ()
294 return this._raw['markedOutlier'];
297 Measurement.prototype.setMarkedOutlier = function (markedOutlier)
299 var params = {'run': this.id(), 'markedOutlier': markedOutlier};
300 return PrivilegedAPI.sendRequest('update-run-status', params).then(function (data) {
301 }, function (error) {
302 alert('Failed to update the outlier status: ' + error);
306 function RunsData(rawData)
308 this._measurements = rawData.map(function (run) { return new Measurement(run); });
311 RunsData.prototype.count = function ()
313 return this._measurements.length;
316 RunsData.prototype.timeSeriesByCommitTime = function (includeOutliers)
318 return this._timeSeriesByTimeInternal(true, includeOutliers);
321 RunsData.prototype.timeSeriesByBuildTime = function (includeOutliers)
323 return this._timeSeriesByTimeInternal(false, includeOutliers);
326 RunsData.prototype._timeSeriesByTimeInternal = function (useCommitType, includeOutliers)
328 var series = new Array();
330 for (var measurement of this._measurements) {
331 if (measurement.markedOutlier() && !includeOutliers)
334 measurement: measurement,
335 time: useCommitType ? measurement.latestCommitTime() : measurement.buildTime(),
336 value: measurement.mean(),
337 interval: measurement.confidenceInterval(),
338 markedOutlier: measurement.markedOutlier(),
341 return new TimeSeries(series);
344 // FIXME: We need to devise a way to fetch runs in multiple chunks so that
345 // we don't have to fetch the entire time series to just show the last 3 days.
346 RunsData.fetchRuns = function (platformId, metricId, testGroupId, useCache)
348 var url = useCache ? '../data/' : '../api/runs/';
350 url += platformId + '-' + metricId + '.json';
352 url += '?testGroup=' + testGroupId;
354 return new Ember.RSVP.Promise(function (resolve, reject) {
355 $.getJSON(url, function (response) {
356 if (response.status != 'OK') {
357 reject(response.status);
360 delete response.status;
362 var data = response.configurations;
363 for (var config in data)
364 data[config] = new RunsData(data[config]);
366 if (response.lastModified)
367 response.lastModified = new Date(response.lastModified);
370 }).fail(function (xhr, status, error) {
371 if (xhr.status == 404 && useCache)
374 reject(xhr.status + (error ? ', ' + error : ''));
379 function TimeSeries(series)
381 this._series = series.sort(function (a, b) { return a.time - b.time; });
385 this._series.forEach(function (point, index) {
387 point.seriesIndex = index;
388 if (min === undefined || min > point.value)
390 if (max === undefined || max < point.value)
397 TimeSeries.prototype.findPointByBuild = function (buildId)
399 return this._series.find(function (point) { return point.measurement.buildId() == buildId; })
402 TimeSeries.prototype.findPointByRevisions = function (revisions)
404 return this._series.find(function (point, index) {
405 for (var repositoryId in revisions) {
406 if (point.measurement.revisionForRepository(repositoryId) != revisions[repositoryId])
413 TimeSeries.prototype.findPointByMeasurementId = function (measurementId)
415 return this._series.find(function (point) { return point.measurement.id() == measurementId; });
418 TimeSeries.prototype.findPointAfterTime = function (time)
420 return this._series.find(function (point) { return point.time >= time; });
423 TimeSeries.prototype.seriesBetweenPoints = function (startPoint, endPoint)
425 if (!startPoint.seriesIndex || !endPoint.seriesIndex)
427 return this._series.slice(startPoint.seriesIndex, endPoint.seriesIndex + 1);
430 TimeSeries.prototype.minMaxForTimeRange = function (startTime, endTime, ignoreOutlier)
432 var data = this._series;
434 if (startTime !== undefined) {
435 for (i = 0; i < data.length; i++) {
437 if (point.time >= startTime)
443 var min = Number.MAX_VALUE;
444 var max = Number.MIN_VALUE;
445 for (; i < data.length; i++) {
447 if (point.isOutlier && ignoreOutlier)
449 var currentMin = point.interval ? point.interval[0] : point.value;
450 var currentMax = point.interval ? point.interval[1] : point.value;
452 if (currentMin < min)
454 if (currentMax > max)
457 if (point.time >= endTime)
463 TimeSeries.prototype.series = function () { return this._series; }
465 TimeSeries.prototype.lastPoint = function ()
467 if (!this._series || !this._series.length)
469 return this._series[this._series.length - 1];
472 TimeSeries.prototype.previousPoint = function (point)
474 if (!point.seriesIndex)
476 return this._series[point.seriesIndex - 1];
479 TimeSeries.prototype.nextPoint = function (point)
481 if (!point.seriesIndex)
483 return this._series[point.seriesIndex + 1];