1 // We don't use DS.Model for these object types because we can't afford to process millions of them.
3 if (!Array.prototype.find) {
4 Array.prototype.find = function (callback) {
5 for (var item of this) {
16 _maxNetworkLatency: 3 * 60 * 1000 /* 3 minutes */,
19 PrivilegedAPI.sendRequest = function (url, parameters)
21 return this._generateTokenInServerIfNeeded().then(function (token) {
22 return PrivilegedAPI._post(url, $.extend({token: token}, parameters));
26 PrivilegedAPI._generateTokenInServerIfNeeded = function ()
29 return new Ember.RSVP.Promise(function (resolve, reject) {
30 if (self._token && self._expiration > Date.now() + self._maxNetworkLatency)
33 PrivilegedAPI._post('generate-csrf-token')
34 .then(function (result, reject) {
35 self._token = result['token'];
36 self._expiration = new Date(result['expiration']);
42 PrivilegedAPI._post = function (url, parameters)
44 return new Ember.RSVP.Promise(function (resolve, reject) {
46 url: '../privileged-api/' + url,
48 contentType: 'application/json',
49 data: parameters ? JSON.stringify(parameters) : '{}',
51 }).done(function (data) {
52 if (data.status != 'OK') {
53 console.log('PrivilegedAPI failed', data);
57 }).fail(function (xhr, status, error) {
58 reject(xhr.status + (error ? ', ' + error : '') + '\n\nWith response:\n' + xhr.responseText);
64 _cachedCommitsByRepository: {}
67 CommitLogs.fetchForTimeRange = function (repository, from, to, keyword)
71 params.push(['from', from]);
72 params.push(['to', to]);
75 params.push(['keyword', keyword]);
77 // FIXME: We should be able to use the cache if all commits in the range have been cached.
78 var useCache = from && to && !keyword;
80 var url = '../api/commits/' + repository + '/?' + params.map(function (keyValue) {
81 return encodeURIComponent(keyValue[0]) + '=' + encodeURIComponent(keyValue[1]);
85 var cachedCommitsForRange = CommitLogs._cachedCommitsBetween(repository, from, to);
86 if (cachedCommitsForRange)
87 return new Ember.RSVP.Promise(function (resolve) { resolve(cachedCommitsForRange); });
90 return new Ember.RSVP.Promise(function (resolve, reject) {
91 $.getJSON(url, function (data) {
92 if (data.status != 'OK') {
97 var fetchedCommits = data.commits;
98 fetchedCommits.forEach(function (commit) { commit.time = new Date(commit.time); });
101 CommitLogs._cacheConsecutiveCommits(repository, from, to, fetchedCommits);
103 resolve(fetchedCommits);
104 }).fail(function (xhr, status, error) {
105 reject(xhr.status + (error ? ', ' + error : ''));
110 CommitLogs._cachedCommitsBetween = function (repository, from, to)
112 var cachedCommits = this._cachedCommitsByRepository[repository];
116 var startCommit = cachedCommits.commitsByRevision[from];
117 var endCommit = cachedCommits.commitsByRevision[to];
118 if (!startCommit || !endCommit)
121 return cachedCommits.commitsByTime.slice(startCommit.cacheIndex, endCommit.cacheIndex + 1);
124 CommitLogs._cacheConsecutiveCommits = function (repository, from, to, consecutiveCommits)
126 var cachedCommits = this._cachedCommitsByRepository[repository];
127 if (!cachedCommits) {
128 cachedCommits = {commitsByRevision: {}, commitsByTime: []};
129 this._cachedCommitsByRepository[repository] = cachedCommits;
132 consecutiveCommits.forEach(function (commit) {
133 if (cachedCommits.commitsByRevision[commit.revision])
135 cachedCommits.commitsByRevision[commit.revision] = commit;
136 cachedCommits.commitsByTime.push(commit);
139 cachedCommits.commitsByTime.sort(function (a, b) { return a.time - b.time; });
140 cachedCommits.commitsByTime.forEach(function (commit, index) { commit.cacheIndex = index; });
144 function Measurement(rawData)
149 var revisions = this._raw['revisions'];
150 // FIXME: Fix this in the server side.
151 if (Array.isArray(revisions))
153 this._raw['revisions'] = revisions;
155 for (var repositoryId in revisions) {
156 var commitTimeOrUndefined = revisions[repositoryId][1]; // e.g. ["162190", 1389945046000]
157 if (latestTime < commitTimeOrUndefined)
158 latestTime = commitTimeOrUndefined;
160 this._latestCommitTime = latestTime !== -1 ? new Date(latestTime) : null;
161 this._buildTime = new Date(this._raw['buildTime']);
162 this._confidenceInterval = undefined;
163 this._formattedRevisions = undefined;
166 Measurement.prototype.revisionForRepository = function (repositoryId)
168 var revisions = this._raw['revisions'];
169 var rawData = revisions[repositoryId];
170 return rawData ? rawData[0] : null;
173 Measurement.prototype.commitTimeForRepository = function (repositoryId)
175 var revisions = this._raw['revisions'];
176 var rawData = revisions[repositoryId];
177 return rawData ? new Date(rawData[1]) : null;
180 Measurement.prototype.formattedRevisions = function (previousMeasurement)
182 var revisions = this._raw['revisions'];
183 var previousRevisions = previousMeasurement ? previousMeasurement._raw['revisions'] : null;
184 var formattedRevisions = {};
185 for (var repositoryId in revisions) {
186 var currentRevision = revisions[repositoryId][0];
187 var previousRevision = previousRevisions && previousRevisions[repositoryId] ? previousRevisions[repositoryId][0] : null;
188 var formatttedRevision = Measurement.formatRevisionRange(currentRevision, previousRevision);
189 formattedRevisions[repositoryId] = formatttedRevision;
192 return formattedRevisions;
195 Measurement.formatRevisionRange = function (currentRevision, previousRevision)
197 var revisionChanged = false;
198 if (previousRevision == currentRevision)
199 previousRevision = null;
201 revisionChanged = true;
203 var revisionPrefix = '';
204 var revisionDelimiter = '-';
206 if (parseInt(currentRevision) == currentRevision) { // e.g. r12345.
207 currentRevision = parseInt(currentRevision);
208 revisionPrefix = 'r';
209 if (previousRevision)
210 previousRevision = (parseInt(previousRevision) + 1);
211 } else if (currentRevision.indexOf(' ') >= 0) // e.g. 10.9 13C64.
212 revisionDelimiter = ' - ';
213 else if (currentRevision.length == 40) { // e.g. git hash
214 var formattedCurrentHash = currentRevision.substring(0, 8);
215 if (previousRevision)
216 label = previousRevision.substring(0, 8) + '..' + formattedCurrentHash;
218 label = formattedCurrentHash;
222 if (previousRevision)
223 label = revisionPrefix + previousRevision + revisionDelimiter + revisionPrefix + currentRevision;
225 label = revisionPrefix + currentRevision;
230 previousRevision: previousRevision,
231 currentRevision: currentRevision,
232 revisionChanged: revisionChanged
236 Measurement.prototype.id = function ()
238 return this._raw['id'];
241 Measurement.prototype.mean = function()
243 return this._raw['mean'];
246 Measurement.prototype.confidenceInterval = function()
248 if (this._confidenceInterval === undefined) {
249 var delta = Statistics.confidenceIntervalDelta(0.95, this._raw["iterationCount"], this._raw["sum"], this._raw["squareSum"]);
250 var mean = this.mean();
251 this._confidenceInterval = isNaN(delta) ? null : [mean - delta, mean + delta];
253 return this._confidenceInterval;
256 Measurement.prototype.latestCommitTime = function()
258 return this._latestCommitTime || this._buildTime;
261 Measurement.prototype.buildId = function()
263 return this._raw['build'];
266 Measurement.prototype.buildNumber = function ()
268 return this._raw['buildNumber'];
271 Measurement.prototype.builderId = function ()
273 return this._raw['builder'];
276 Measurement.prototype.buildTime = function()
278 return this._buildTime;
281 Measurement.prototype.formattedBuildTime = function ()
283 return Measurement._formatDate(this.buildTime());
286 Measurement._formatDate = function (date)
288 return date.toISOString().replace('T', ' ').replace(/\.\d+Z$/, '');
291 Measurement.prototype.bugs = function ()
293 return this._raw['bugs'];
296 Measurement.prototype.hasBugs = function ()
298 var bugs = this.bugs();
299 return bugs && Object.keys(bugs).length;
302 Measurement.prototype.markedOutlier = function ()
304 return this._raw['markedOutlier'];
307 Measurement.prototype.setMarkedOutlier = function (markedOutlier)
309 var params = {'run': this.id(), 'markedOutlier': markedOutlier};
310 return PrivilegedAPI.sendRequest('update-run-status', params).then(function (data) {
311 }, function (error) {
312 alert('Failed to update the outlier status: ' + error);
316 function RunsData(rawData)
318 this._measurements = rawData.map(function (run) { return new Measurement(run); });
321 RunsData.prototype.count = function ()
323 return this._measurements.length;
326 RunsData.prototype.timeSeriesByCommitTime = function (includeOutliers)
328 return this._timeSeriesByTimeInternal(true, includeOutliers);
331 RunsData.prototype.timeSeriesByBuildTime = function (includeOutliers)
333 return this._timeSeriesByTimeInternal(false, includeOutliers);
336 RunsData.prototype._timeSeriesByTimeInternal = function (useCommitType, includeOutliers)
338 var series = new Array();
340 for (var measurement of this._measurements) {
341 if (measurement.markedOutlier() && !includeOutliers)
344 measurement: measurement,
345 time: useCommitType ? measurement.latestCommitTime() : measurement.buildTime(),
346 secondaryTime: measurement.buildTime(),
347 value: measurement.mean(),
348 interval: measurement.confidenceInterval(),
349 markedOutlier: measurement.markedOutlier(),
352 return new TimeSeries(series);
355 // FIXME: We need to devise a way to fetch runs in multiple chunks so that
356 // we don't have to fetch the entire time series to just show the last 3 days.
357 RunsData.fetchRuns = function (platformId, metricId, testGroupId, useCache)
359 var url = this.pathForFetchingRuns(platformId, metricId, testGroupId, useCache);
360 return new Ember.RSVP.Promise(function (resolve, reject) {
361 $.getJSON(url, function (response) {
362 if (response.status != 'OK') {
363 reject(response.status);
366 resolve(RunsData.createRunsDataInResponse(response));
367 }).fail(function (xhr, status, error) {
368 if (xhr.status == 404 && useCache)
371 reject(xhr.status + (error ? ', ' + error : ''));
376 RunsData.pathForFetchingRuns = function (platformId, metricId, testGroupId, useCache)
378 var path = useCache ? '/data/' : '/api/runs/';
380 path += platformId + '-' + metricId + '.json';
382 path += '?testGroup=' + testGroupId;
387 RunsData.createRunsDataInResponse = function (response)
389 delete response.status;
391 var data = response.configurations;
392 for (var config in data)
393 data[config] = new RunsData(data[config]);
395 if (response.lastModified)
396 response.lastModified = new Date(response.lastModified);
401 // FIXME: It was a mistake to put this in the client side. We should put this back in the JSON.
402 RunsData.unitFromMetricName = function (metricName)
404 var suffix = metricName.match('([A-z][a-z]+|FrameRate)$')[0];
411 'Allocations': 'bytes',
417 RunsData.isSmallerBetter = function (unit)
419 return unit != 'fps' && unit != '/s' && unit != 'pt';
422 function TimeSeries(series)
424 this._series = series.sort(function (a, b) {
425 var diff = a.time - b.time;
426 return diff ? diff : a.secondaryTime - b.secondaryTime;
432 this._series.forEach(function (point, index) {
434 point.seriesIndex = index;
435 if (min === undefined || min > point.value)
437 if (max === undefined || max < point.value)
444 TimeSeries.prototype.findPointByIndex = function (index)
446 if (!this._series || index < 0 || index >= this._series.length)
448 return this._series[index];
451 TimeSeries.prototype.findPointByBuild = function (buildId)
453 return this._series.find(function (point) { return point.measurement.buildId() == buildId; })
456 TimeSeries.prototype.findPointByRevisions = function (revisions)
458 return this._series.find(function (point, index) {
459 for (var repositoryId in revisions) {
460 if (point.measurement.revisionForRepository(repositoryId) != revisions[repositoryId])
467 TimeSeries.prototype.findPointByMeasurementId = function (measurementId)
469 return this._series.find(function (point) { return point.measurement.id() == measurementId; });
472 TimeSeries.prototype.findPointAfterTime = function (time)
474 return this._series.find(function (point) { return point.time >= time; });
477 TimeSeries.prototype.seriesBetweenPoints = function (startPoint, endPoint)
479 if (!startPoint.seriesIndex || !endPoint.seriesIndex)
481 return this._series.slice(startPoint.seriesIndex, endPoint.seriesIndex + 1);
484 TimeSeries.prototype.minMaxForTimeRange = function (startTime, endTime, ignoreOutlier)
486 var data = this._series;
488 if (startTime !== undefined) {
489 for (i = 0; i < data.length; i++) {
491 if (point.time >= startTime)
497 var min = Number.MAX_VALUE;
498 var max = Number.MIN_VALUE;
499 for (; i < data.length; i++) {
501 if (point.isOutlier && ignoreOutlier)
503 var currentMin = point.interval ? point.interval[0] : point.value;
504 var currentMax = point.interval ? point.interval[1] : point.value;
506 if (currentMin < min)
508 if (currentMax > max)
511 if (point.time >= endTime)
517 TimeSeries.prototype.series = function () { return this._series; }
519 TimeSeries.prototype.rawValues = function ()
521 return this._series.map(function (point) { return point.value });
524 TimeSeries.prototype.lastPoint = function ()
526 if (!this._series || !this._series.length)
528 return this._series[this._series.length - 1];
531 TimeSeries.prototype.previousPoint = function (point)
533 if (!point.seriesIndex)
535 return this._series[point.seriesIndex - 1];
538 TimeSeries.prototype.nextPoint = function (point)
540 if (!point.seriesIndex)
542 return this._series[point.seriesIndex + 1];
545 if (typeof module != 'undefined') {
546 Statistics = require('./js/statistics.js');
547 module.exports.Measurement = Measurement;
548 module.exports.RunsData = RunsData;
549 module.exports.TimeSeries = TimeSeries;