New perf dashboard UI tries to fetch commits all the time
[WebKit-https.git] / Websites / perf.webkit.org / public / v2 / data.js
1 // We don't use DS.Model for these object types because we can't afford to process millions of them.
2
3 function FetchCommitsForTimeRange(repository, from, to)
4 {
5     var url = '../api/commits/' + repository.get('id') + '/' + from + '-' + to;
6
7     var cachedCommits = FetchCommitsForTimeRange._cachedCommitsByRepository[repository];
8     if (!cachedCommits) {
9         cachedCommits = {commitsByRevision: {}, commitsByTime: []};
10         FetchCommitsForTimeRange._cachedCommitsByRepository[repository] = cachedCommits;
11     }
12
13     if (cachedCommits) {
14         var startCommit = cachedCommits.commitsByRevision[from];
15         var endCommit = cachedCommits.commitsByRevision[to];
16         if (startCommit && endCommit) {
17             return new Ember.RSVP.Promise(function (resolve) {
18                 resolve(cachedCommits.commitsByTime.slice(startCommit.index, endCommit.index + 1)) });
19         }
20     }
21
22     console.log('Fecthing ' + url);
23
24     return new Ember.RSVP.Promise(function (resolve, reject) {
25         $.getJSON(url, function (data) {
26             if (data.status != 'OK') {
27                 reject(data.status);
28                 return;
29             }
30
31             data.commits.forEach(function (commit) {
32                 if (cachedCommits.commitsByRevision[commit.revision])
33                     return;
34                 commit.time = new Date(commit.time.replace(' ', 'T'));
35                 cachedCommits.commitsByRevision[commit.revision] = commit;
36                 cachedCommits.commitsByTime.push(commit);
37             });
38
39             cachedCommits.commitsByTime.sort(function (a, b) { return a.time - b.time; });
40             cachedCommits.commitsByTime.forEach(function (commit, index) { commit.index = index; });
41
42             resolve(data.commits);
43         }).fail(function (xhr, status, error) {
44             reject(xhr.status + (error ? ', ' + error : ''));
45         })
46     });
47 }
48
49 FetchCommitsForTimeRange._cachedCommitsByRepository = {};
50
51 function Measurement(rawData)
52 {
53     this._raw = rawData;
54
55     var latestTime = -1;
56     var revisions = this._raw['revisions'];
57     // FIXME: Fix this in the server side.
58     if (Array.isArray(revisions))
59         revisions = {};
60     this._raw['revisions'] = revisions;
61
62     for (var repositoryName in revisions) {
63         var commitTimeOrUndefined = revisions[repositoryName][1]; // e.g. ["162190", 1389945046000]
64         if (latestTime < commitTimeOrUndefined)
65             latestTime = commitTimeOrUndefined;
66     }
67     this._latestCommitTime = latestTime !== -1 ? new Date(latestTime) : null;
68     this._buildTime = new Date(this._raw['buildTime']);
69     this._confidenceInterval = undefined;
70     this._formattedRevisions = undefined;
71 }
72
73 Measurement.prototype.formattedRevisions = function (previousMeasurement)
74 {
75     var revisions = this._raw['revisions'];
76     var previousRevisions = previousMeasurement ? previousMeasurement._raw['revisions'] : null;
77     var formattedRevisions = {};
78     for (var repositoryName in revisions) {
79         var currentRevision = revisions[repositoryName][0];
80         var commitTimeInPOSIX = revisions[repositoryName][1];
81
82         var previousRevision = previousRevisions ? previousRevisions[repositoryName][0] : null;
83
84         var formatttedRevision = this._formatRevisionRange(previousRevision, currentRevision);
85         if (commitTimeInPOSIX)
86             formatttedRevision['commitTime'] = new Date(commitTimeInPOSIX);
87         formattedRevisions[repositoryName] = formatttedRevision;
88     }
89
90     return formattedRevisions;
91 }
92
93 Measurement.prototype._formatRevisionRange = function (previousRevision, currentRevision)
94 {
95     var revisionChanged = false;
96     if (previousRevision == currentRevision)
97         previousRevision = null;
98     else
99         revisionChanged = true;
100
101     var revisionPrefix = '';
102     var revisionDelimiter = '-';
103     var label = '';
104     if (parseInt(currentRevision) == currentRevision) { // e.g. r12345.
105         currentRevision = parseInt(currentRevision);
106         revisionPrefix = 'r';
107         if (previousRevision)
108             previousRevision = (parseInt(previousRevision) + 1);
109     } else if (currentRevision.indexOf(' ') >= 0) // e.g. 10.9 13C64.
110         revisionDelimiter = ' - ';
111     else if (currentRevision.length == 40) { // e.g. git hash
112         formattedCurrentHash = currentRevision.substring(0, 8);
113         if (previousRevision)
114             label = previousRevision.substring(0, 8) + '..' + formattedCurrentHash;
115         else
116             label = 'At ' + formattedCurrentHash;
117     }
118
119     if (!label) {
120         if (previousRevision)
121             label = revisionPrefix + previousRevision + revisionDelimiter + revisionPrefix + currentRevision;
122         else
123             label = 'At ' + revisionPrefix + currentRevision;
124     }
125
126     return {
127         label: label,
128         previousRevision: previousRevision,
129         currentRevision: currentRevision,
130         revisionChanged: revisionChanged
131     };
132 }
133
134 Measurement.prototype.id = function ()
135 {
136     return this._raw['id'];
137 }
138
139 Measurement.prototype.mean = function()
140 {
141     return this._raw['mean'];
142 }
143
144 Measurement.prototype.confidenceInterval = function()
145 {
146     if (this._confidenceInterval === undefined) {
147         var delta = Statistics.confidenceIntervalDelta(0.95, this._raw["iterationCount"], this._raw["sum"], this._raw["squareSum"]);
148         var mean = this.mean();
149         this._confidenceInterval = isNaN(delta) ? null : [mean - delta, mean + delta];
150     }
151     return this._confidenceInterval;
152 }
153
154 Measurement.prototype.latestCommitTime = function()
155 {
156     return this._latestCommitTime || this._buildTime;
157 }
158
159 Measurement.prototype.buildNumber = function ()
160 {
161     return this._raw['buildNumber'];
162 }
163
164 Measurement.prototype.builderId = function ()
165 {
166     return this._raw['builder'];
167 }
168
169 Measurement.prototype.buildTime = function()
170 {
171     return this._buildTime;
172 }
173
174 Measurement.prototype.formattedBuildTime = function ()
175 {
176     return Measurement._formatDate(this.buildTime());
177 }
178
179 Measurement._formatDate = function (date)
180 {
181     return date.toISOString().replace('T', ' ').replace(/\.\d+Z$/, '');
182 }
183
184 function RunsData(rawData)
185 {
186     this._measurements = rawData.map(function (run) { return new Measurement(run); });
187 }
188
189 RunsData.prototype.count = function ()
190 {
191     return this._measurements.length;
192 }
193
194 RunsData.prototype.timeSeriesByCommitTime = function ()
195 {
196     return new TimeSeries(this._measurements.map(function (measurement) {
197         var confidenceInterval = measurement.confidenceInterval();
198         return {
199             measurement: measurement,
200             time: measurement.latestCommitTime(),
201             value: measurement.mean(),
202             interval: measurement.confidenceInterval(),
203         };
204     }));
205 }
206
207 RunsData.prototype.timeSeriesByBuildTime = function ()
208 {
209     return new TimeSeries(this._measurements.map(function (measurement) {
210         return {
211             measurement: measurement,
212             time: measurement.buildTime(),
213             value: measurement.mean(),
214             interval: measurement.confidenceInterval(),
215         };
216     }));
217 }
218
219 // FIXME: We need to devise a way to fetch runs in multiple chunks so that
220 // we don't have to fetch the entire time series to just show the last 3 days.
221 RunsData.fetchRuns = function (platformId, metricId)
222 {
223     var filename = platformId + '-' + metricId + '.json';
224
225     return new Ember.RSVP.Promise(function (resolve, reject) {
226         $.getJSON('../api/runs/' + filename, function (data) {
227             if (data.status != 'OK') {
228                 reject(data.status);
229                 return;
230             }
231             delete data.status;
232
233             for (var config in data)
234                 data[config] = new RunsData(data[config]);
235
236             resolve(data);
237         }).fail(function (xhr, status, error) {
238             reject(xhr.status + (error ? ', ' + error : ''));
239         })
240     });
241 }
242
243 function TimeSeries(series)
244 {
245     this._series = series.sort(function (a, b) { return a.time - b.time; });
246     var self = this;
247     var min = undefined;
248     var max = undefined;
249     this._series.forEach(function (point, index) {
250         point.series = self;
251         point.seriesIndex = index;
252         if (min === undefined || min > point.value)
253             min = point.value;
254         if (max === undefined || max < point.value)
255             max = point.value;
256     });
257     this._min = min;
258     this._max = max;
259 }
260
261 TimeSeries.prototype.minMaxForTimeRange = function (startTime, endTime)
262 {
263     var data = this._series;
264     var i = 0;
265     if (startTime !== undefined) {
266         for (i = 0; i < data.length; i++) {
267             var point = data[i];
268             if (point.time >= startTime)
269                 break;
270         }
271         if (i)
272             i--;
273     }
274     var min = Number.MAX_VALUE;
275     var max = Number.MIN_VALUE;
276     for (; i < data.length; i++) {
277         var point = data[i];
278         var currentMin = point.interval ? point.interval[0] : point.value;
279         var currentMax = point.interval ? point.interval[1] : point.value;
280
281         if (currentMin < min)
282             min = currentMin;
283         if (currentMax > max)
284             max = currentMax;
285
286         if (point.time >= endTime)
287             break;
288     }
289     return [min, max];
290 }
291
292 TimeSeries.prototype.series = function () { return this._series; }
293
294 TimeSeries.prototype.previousPoint = function (point)
295 {
296     if (!point.seriesIndex)
297         return null;
298     return this._series[point.seriesIndex - 1];
299 }