97e9206c876a88dc41efa8b6fe8964392028382a
[WebKit-https.git] / Websites / perf.webkit.org / public / v3 / models / measurement-set.js
1 'use strict';
2
3 if (!Array.prototype.includes)
4     Array.prototype.includes = function (value) { return this.indexOf(value) >= 0; }
5
6 class MeasurementSet {
7     constructor(platformId, metricId, lastModified)
8     {
9         this._platformId = platformId;
10         this._metricId = metricId;
11         this._lastModified = +lastModified;
12
13         this._waitingForPrimaryCluster = null;
14         this._fetchedPrimary = false;
15         this._endTimeToCallback = {};
16
17         this._sortedClusters = [];
18         this._primaryClusterEndTime = null;
19         this._clusterCount = null;
20         this._clusterStart = null;
21         this._clusterSize = null;
22     }
23
24     static findSet(platformId, metricId, lastModified)
25     {
26         if (!this._set)
27             this._set = {};
28         var key = platformId + '-' + metricId;
29         if (!this._set[key])
30             this._set[key] = new MeasurementSet(platformId, metricId, lastModified);
31         return this._set[key];
32     }
33
34     findClusters(startTime, endTime)
35     {
36         var clusterStart = this._clusterStart;
37         var clusterSize = this._clusterSize;
38         console.assert(clusterStart && clusterSize);
39
40         function computeClusterStart(time) {
41             var diff = time - clusterStart;
42             return clusterStart + Math.floor(diff / clusterSize) * clusterSize;            
43         }
44
45         var clusters = [];
46         var clusterEnd = computeClusterStart(startTime);
47
48         var lastClusterEndTime = this._primaryClusterEndTime;
49         var firstClusterEndTime = lastClusterEndTime - clusterStart * this._clusterCount;
50         do {
51             clusterEnd += clusterSize;
52             if (firstClusterEndTime <= clusterEnd && clusterEnd <= this._primaryClusterEndTime)
53                 clusters.push(clusterEnd);
54         } while (clusterEnd < endTime);
55
56         return clusters;
57     }
58
59     fetchBetween(startTime, endTime, callback)
60     {
61         if (!this._fetchedPrimary) {
62             var primaryFetchHadFailed = this._waitingForPrimaryCluster === false;
63             if (primaryFetchHadFailed) {
64                 callback();
65                 return;
66             }
67
68             var shouldStartPrimaryFetch = !this._waitingForPrimaryCluster;
69             if (shouldStartPrimaryFetch)
70                 this._waitingForPrimaryCluster = [];
71
72             this._waitingForPrimaryCluster.push({startTime: startTime, endTime: endTime, callback: callback});
73
74             if (shouldStartPrimaryFetch)
75                 this._fetch(null, true);
76
77             return;
78         }
79
80         this._fetchSecondaryClusters(startTime, endTime, callback);
81     }
82
83     _fetchSecondaryClusters(startTime, endTime, callback)
84     {
85         console.assert(this._fetchedPrimary);
86         console.assert(this._clusterStart && this._clusterSize);
87         console.assert(this._sortedClusters.length);
88
89         var clusters = this.findClusters(startTime, endTime);
90         var shouldInvokeCallackNow = false;
91         for (var endTime of clusters) {
92             var isPrimaryCluster = endTime == this._primaryClusterEndTime;
93             var shouldStartFetch = !isPrimaryCluster && !(endTime in this._endTimeToCallback);
94             if (shouldStartFetch)
95                 this._endTimeToCallback[endTime] = [];
96
97             var callbackList = this._endTimeToCallback[endTime];
98             if (isPrimaryCluster || callbackList === true)
99                 shouldInvokeCallackNow = true;
100             else if (!callbackList.includes(callback))
101                 callbackList.push(callback);
102
103             if (shouldStartFetch)
104                 this._fetch(endTime, true);
105         }
106
107         if (shouldInvokeCallackNow)
108             callback();
109     }
110
111     _fetch(clusterEndTime, useCache)
112     {
113         console.assert(!clusterEndTime || useCache);
114
115         var url;
116         if (useCache) {
117             url = `../data/measurement-set-${this._platformId}-${this._metricId}`;
118             if (clusterEndTime)
119                 url += '-' + +clusterEndTime;
120             url += '.json';
121         } else
122             url = `../api/measurement-set?platform=${this._platformId}&metric=${this._metricId}`;
123
124         var self = this;
125
126         return RemoteAPI.getJSONWithStatus(url).then(function (data) {
127             if (!clusterEndTime && useCache && +data['lastModified'] < self._lastModified)
128                 self._fetch(clusterEndTime, false);
129             else
130                 self._didFetchJSON(!clusterEndTime, data);
131         }, function (error, xhr) {
132             if (!clusterEndTime && error == 404 && useCache)
133                 self._fetch(clusterEndTime, false);
134             else
135                 self._failedToFetchJSON(clusterEndTime, error);
136         });
137     }
138
139     _didFetchJSON(isPrimaryCluster, response, clusterEndTime)
140     {
141         console.assert(isPrimaryCluster || this._fetchedPrimary);
142
143         if (isPrimaryCluster) {
144             this._primaryClusterEndTime = response['endTime'];
145             this._clusterCount = response['clusterCount'];
146             this._clusterStart = response['clusterStart'];
147             this._clusterSize = response['clusterSize'];
148         } else
149             console.assert(this._primaryClusterEndTime);
150
151         this._addFetchedCluster(new MeasurementCluster(response));
152
153         console.assert(this._waitingForPrimaryCluster);
154         if (!isPrimaryCluster) {
155             this._invokeCallbacks(response.endTime);
156             return;
157         }
158         console.assert(this._waitingForPrimaryCluster instanceof Array);
159
160         this._fetchedPrimary = true;
161         for (var entry of this._waitingForPrimaryCluster)
162             this._fetchSecondaryClusters(entry.startTime, entry.endTime, entry.callback);
163         this._waitingForPrimaryCluster = true;
164     }
165
166     _failedToFetchJSON(clusterEndTime, error)
167     {
168         if (clusterEndTime) {
169             this._invokeCallbacks(clusterEndTime, error || true);
170             return;
171         }
172
173         console.assert(!this._fetchedPrimary);
174         console.assert(this._waitingForPrimaryCluster instanceof Array);
175         for (var entry of this._waitingForPrimaryCluster)
176             entry.callback(error || true);
177         this._waitingForPrimaryCluster = false;
178     }
179
180     _invokeCallbacks(clusterEndTime, error)
181     {
182         var callbackList = this._endTimeToCallback[clusterEndTime];
183         for (var callback of callbackList)
184             callback(error);
185         this._endTimeToCallback[clusterEndTime] = true;
186     }
187
188     _addFetchedCluster(cluster)
189     {
190         this._sortedClusters.push(cluster);
191         this._sortedClusters = this._sortedClusters.sort(function (c1, c2) {
192             return c1.startTime() - c2.startTime();
193         });
194     }
195
196     hasFetchedRange(startTime, endTime)
197     {
198         console.assert(startTime < endTime);
199         var hasHole = false;
200         var previousEndTime = null;
201         for (var cluster of this._sortedClusters) {
202             if (cluster.startTime() < startTime && startTime < cluster.endTime())
203                 hasHole = false;
204             if (previousEndTime !== null && previousEndTime != cluster.startTime())
205                 hasHole = true;
206             if (cluster.startTime() < endTime && endTime < cluster.endTime())
207                 break;
208             previousEndTime = cluster.endTime();
209         }
210         return !hasHole;
211     }
212
213     fetchedTimeSeries(configType, includeOutliers, extendToFuture)
214     {
215         Instrumentation.startMeasuringTime('MeasurementSet', 'fetchedTimeSeries');
216
217         // FIXME: Properly construct TimeSeries.
218         var series = new TimeSeries([]);
219         var idMap = {};
220         for (var cluster of this._sortedClusters)
221             cluster.addToSeries(series, configType, includeOutliers, idMap);
222
223         if (extendToFuture)
224             series.extendToFuture();
225
226         Instrumentation.endMeasuringTime('MeasurementSet', 'fetchedTimeSeries');
227
228         return series;
229     }
230 }
231
232 if (typeof module != 'undefined')
233     module.exports.MeasurementSet = MeasurementSet;