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')
46 }).fail(function (xhr, status, error) {
47 reject(xhr.status + (error ? ', ' + error : '') + '\n\nWith response:\n' + xhr.responseText);
53 _cachedCommitsByRepository: {}
56 CommitLogs.fetchForTimeRange = function (repository, from, to, keyword)
60 params.push(['from', from]);
61 params.push(['to', to]);
64 params.push(['keyword', keyword]);
66 // FIXME: We should be able to use the cache if all commits in the range have been cached.
67 var useCache = from && to && !keyword;
69 var url = '../api/commits/' + repository + '/?' + params.map(function (keyValue) {
70 return encodeURIComponent(keyValue[0]) + '=' + encodeURIComponent(keyValue[1]);
74 var cachedCommitsForRange = CommitLogs._cachedCommitsBetween(repository, from, to);
75 if (cachedCommitsForRange)
76 return new Ember.RSVP.Promise(function (resolve) { resolve(cachedCommitsForRange); });
79 return new Ember.RSVP.Promise(function (resolve, reject) {
80 $.getJSON(url, function (data) {
81 if (data.status != 'OK') {
86 var fetchedCommits = data.commits;
87 fetchedCommits.forEach(function (commit) { commit.time = new Date(commit.time.replace(' ', 'T')); });
90 CommitLogs._cacheConsecutiveCommits(repository, from, to, fetchedCommits);
92 resolve(fetchedCommits);
93 }).fail(function (xhr, status, error) {
94 reject(xhr.status + (error ? ', ' + error : ''));
99 CommitLogs._cachedCommitsBetween = function (repository, from, to)
101 var cachedCommits = this._cachedCommitsByRepository[repository];
105 var startCommit = cachedCommits.commitsByRevision[from];
106 var endCommit = cachedCommits.commitsByRevision[to];
107 if (!startCommit || !endCommit)
110 return cachedCommits.commitsByTime.slice(startCommit.cacheIndex, endCommit.cacheIndex + 1);
113 CommitLogs._cacheConsecutiveCommits = function (repository, from, to, consecutiveCommits)
115 var cachedCommits = this._cachedCommitsByRepository[repository];
116 if (!cachedCommits) {
117 cachedCommits = {commitsByRevision: {}, commitsByTime: []};
118 this._cachedCommitsByRepository[repository] = cachedCommits;
121 consecutiveCommits.forEach(function (commit) {
122 if (cachedCommits.commitsByRevision[commit.revision])
124 cachedCommits.commitsByRevision[commit.revision] = commit;
125 cachedCommits.commitsByTime.push(commit);
128 cachedCommits.commitsByTime.sort(function (a, b) { return a.time - b.time; });
129 cachedCommits.commitsByTime.forEach(function (commit, index) { commit.cacheIndex = index; });
133 function Measurement(rawData)
138 var revisions = this._raw['revisions'];
139 // FIXME: Fix this in the server side.
140 if (Array.isArray(revisions))
142 this._raw['revisions'] = revisions;
144 for (var repositoryName in revisions) {
145 var commitTimeOrUndefined = revisions[repositoryName][1]; // e.g. ["162190", 1389945046000]
146 if (latestTime < commitTimeOrUndefined)
147 latestTime = commitTimeOrUndefined;
149 this._latestCommitTime = latestTime !== -1 ? new Date(latestTime) : null;
150 this._buildTime = new Date(this._raw['buildTime']);
151 this._confidenceInterval = undefined;
152 this._formattedRevisions = undefined;
155 Measurement.prototype.commitTimeForRepository = function (repositoryName)
157 var revisions = this._raw['revisions'];
158 var rawData = revisions[repositoryName];
161 return new Date(rawData[1]);
164 Measurement.prototype.formattedRevisions = function (previousMeasurement)
166 var revisions = this._raw['revisions'];
167 var previousRevisions = previousMeasurement ? previousMeasurement._raw['revisions'] : null;
168 var formattedRevisions = {};
169 for (var repositoryName in revisions) {
170 var currentRevision = revisions[repositoryName][0];
171 var previousRevision = previousRevisions ? previousRevisions[repositoryName][0] : null;
172 var formatttedRevision = this._formatRevisionRange(previousRevision, currentRevision);
173 formattedRevisions[repositoryName] = formatttedRevision;
176 return formattedRevisions;
179 Measurement.prototype._formatRevisionRange = function (previousRevision, currentRevision)
181 var revisionChanged = false;
182 if (previousRevision == currentRevision)
183 previousRevision = null;
185 revisionChanged = true;
187 var revisionPrefix = '';
188 var revisionDelimiter = '-';
190 if (parseInt(currentRevision) == currentRevision) { // e.g. r12345.
191 currentRevision = parseInt(currentRevision);
192 revisionPrefix = 'r';
193 if (previousRevision)
194 previousRevision = (parseInt(previousRevision) + 1);
195 } else if (currentRevision.indexOf(' ') >= 0) // e.g. 10.9 13C64.
196 revisionDelimiter = ' - ';
197 else if (currentRevision.length == 40) { // e.g. git hash
198 var formattedCurrentHash = currentRevision.substring(0, 8);
199 if (previousRevision)
200 label = previousRevision.substring(0, 8) + '..' + formattedCurrentHash;
202 label = formattedCurrentHash;
206 if (previousRevision)
207 label = revisionPrefix + previousRevision + revisionDelimiter + revisionPrefix + currentRevision;
209 label = revisionPrefix + currentRevision;
214 previousRevision: previousRevision,
215 currentRevision: currentRevision,
216 revisionChanged: revisionChanged
220 Measurement.prototype.id = function ()
222 return this._raw['id'];
225 Measurement.prototype.mean = function()
227 return this._raw['mean'];
230 Measurement.prototype.confidenceInterval = function()
232 if (this._confidenceInterval === undefined) {
233 var delta = Statistics.confidenceIntervalDelta(0.95, this._raw["iterationCount"], this._raw["sum"], this._raw["squareSum"]);
234 var mean = this.mean();
235 this._confidenceInterval = isNaN(delta) ? null : [mean - delta, mean + delta];
237 return this._confidenceInterval;
240 Measurement.prototype.latestCommitTime = function()
242 return this._latestCommitTime || this._buildTime;
245 Measurement.prototype.buildNumber = function ()
247 return this._raw['buildNumber'];
250 Measurement.prototype.builderId = function ()
252 return this._raw['builder'];
255 Measurement.prototype.buildTime = function()
257 return this._buildTime;
260 Measurement.prototype.formattedBuildTime = function ()
262 return Measurement._formatDate(this.buildTime());
265 Measurement._formatDate = function (date)
267 return date.toISOString().replace('T', ' ').replace(/\.\d+Z$/, '');
270 Measurement.prototype.bugs = function ()
272 return this._raw['bugs'];
275 Measurement.prototype.hasBugs = function ()
277 var bugs = this.bugs();
278 return bugs && Object.keys(bugs).length;
281 Measurement.prototype.associateBug = function (trackerId, bugNumber)
283 var bugs = this._raw['bugs'];
284 trackerId = parseInt(trackerId);
285 bugNumber = bugNumber ? parseInt(bugNumber) : null;
286 return PrivilegedAPI.sendRequest('associate-bug', {
289 bugNumber: bugNumber,
290 }).then(function () {
292 bugs[trackerId] = bugNumber;
294 delete bugs[trackerId];
298 function RunsData(rawData)
300 this._measurements = rawData.map(function (run) { return new Measurement(run); });
303 RunsData.prototype.count = function ()
305 return this._measurements.length;
308 RunsData.prototype.timeSeriesByCommitTime = function ()
310 return new TimeSeries(this._measurements.map(function (measurement) {
311 var confidenceInterval = measurement.confidenceInterval();
313 measurement: measurement,
314 time: measurement.latestCommitTime(),
315 value: measurement.mean(),
316 interval: measurement.confidenceInterval(),
321 RunsData.prototype.timeSeriesByBuildTime = function ()
323 return new TimeSeries(this._measurements.map(function (measurement) {
325 measurement: measurement,
326 time: measurement.buildTime(),
327 value: measurement.mean(),
328 interval: measurement.confidenceInterval(),
333 // FIXME: We need to devise a way to fetch runs in multiple chunks so that
334 // we don't have to fetch the entire time series to just show the last 3 days.
335 RunsData.fetchRuns = function (platformId, metricId)
337 var filename = platformId + '-' + metricId + '.json';
339 return new Ember.RSVP.Promise(function (resolve, reject) {
340 $.getJSON('../api/runs/' + filename, function (data) {
341 if (data.status != 'OK') {
347 for (var config in data)
348 data[config] = new RunsData(data[config]);
351 }).fail(function (xhr, status, error) {
352 reject(xhr.status + (error ? ', ' + error : ''));
357 function TimeSeries(series)
359 this._series = series.sort(function (a, b) { return a.time - b.time; });
363 this._series.forEach(function (point, index) {
365 point.seriesIndex = index;
366 if (min === undefined || min > point.value)
368 if (max === undefined || max < point.value)
375 TimeSeries.prototype.findPointByMeasurementId = function (measurementId)
377 return this._series.find(function (point) { return point.measurement.id() == measurementId; });
380 TimeSeries.prototype.seriesBetweenPoints = function (startPoint, endPoint)
382 if (!startPoint.seriesIndex || !endPoint.seriesIndex)
384 return this._series.slice(startPoint.seriesIndex, endPoint.seriesIndex + 1);
387 TimeSeries.prototype.minMaxForTimeRange = function (startTime, endTime)
389 var data = this._series;
391 if (startTime !== undefined) {
392 for (i = 0; i < data.length; i++) {
394 if (point.time >= startTime)
400 var min = Number.MAX_VALUE;
401 var max = Number.MIN_VALUE;
402 for (; i < data.length; i++) {
404 var currentMin = point.interval ? point.interval[0] : point.value;
405 var currentMax = point.interval ? point.interval[1] : point.value;
407 if (currentMin < min)
409 if (currentMax > max)
412 if (point.time >= endTime)
418 TimeSeries.prototype.series = function () { return this._series; }
420 TimeSeries.prototype.previousPoint = function (point)
422 if (!point.seriesIndex)
424 return this._series[point.seriesIndex - 1];
427 TimeSeries.prototype.nextPoint = function (point)
429 if (!point.seriesIndex)
431 return this._series[point.seriesIndex + 1];