Summary page should show warnings when current or baseline data is missing.
[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._sortedClusters = [];
14         this._primaryClusterEndTime = null;
15         this._clusterCount = null;
16         this._clusterStart = null;
17         this._clusterSize = null;
18         this._allFetches = {};
19         this._primaryClusterPromise = null;
20     }
21
22     platformId() { return this._platformId; }
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
39         function computeClusterStart(time) {
40             var diff = time - clusterStart;
41             return clusterStart + Math.floor(diff / clusterSize) * clusterSize;            
42         }
43
44         var clusters = [];
45         var clusterEnd = computeClusterStart(startTime);
46
47         var lastClusterEndTime = this._primaryClusterEndTime;
48         var firstClusterEndTime = lastClusterEndTime - clusterStart * this._clusterCount;
49         do {
50             clusterEnd += clusterSize;
51             if (firstClusterEndTime <= clusterEnd && clusterEnd <= this._primaryClusterEndTime)
52                 clusters.push(clusterEnd);
53         } while (clusterEnd < endTime);
54
55         return clusters;
56     }
57
58     fetchBetween(startTime, endTime, callback)
59     {
60         if (!this._primaryClusterPromise)
61             this._primaryClusterPromise = this._fetchPrimaryCluster();
62         var self = this;
63         this._primaryClusterPromise.catch(callback);
64         return this._primaryClusterPromise.then(function () {
65             var promiseList = [];
66             self.findClusters(startTime, endTime).map(function (clusterEndTime) {
67                 if(!self._allFetches[clusterEndTime])
68                     self._allFetches[clusterEndTime] = self._fetchSecondaryCluster(clusterEndTime);
69                 self._allFetches[clusterEndTime].then(callback, callback);
70                 promiseList.push(self._allFetches[clusterEndTime]);
71             });
72             return Promise.all(promiseList);
73         });
74     }
75
76     _constructUrl(useCache, clusterEndTime)
77     {
78         if (!useCache) {
79             return `../api/measurement-set?platform=${this._platformId}&metric=${this._metricId}`;
80         }
81         var url;
82         url = `../data/measurement-set-${this._platformId}-${this._metricId}`;
83         if (clusterEndTime)
84             url += '-' + +clusterEndTime;
85         url += '.json';
86         return url;
87     }
88
89     _fetchPrimaryCluster() {
90         var self = this;
91         return RemoteAPI.getJSONWithStatus(self._constructUrl(true, null)).then(function (data) {
92             if (+data['lastModified'] < self._lastModified)
93                 return RemoteAPI.getJSONWithStatus(self._constructUrl(false, null));
94             return data;
95         }).catch(function (error) {
96             if(error == 404)
97                 return RemoteAPI.getJSONWithStatus(self._constructUrl(false, null));
98             return Promise.reject(error);
99         }).then(function (data) {
100             self._didFetchJSON(true, data);
101             self._allFetches[self._primaryClusterEndTime] = self._primaryClusterPromise;
102         });
103     }
104
105     _fetchSecondaryCluster(endTime) {
106         var self = this;
107         return RemoteAPI.getJSONWithStatus(self._constructUrl(true, endTime)).then(function (data) {
108             self._didFetchJSON(false, data);
109         });
110     }
111
112     _didFetchJSON(isPrimaryCluster, response, clusterEndTime)
113     {
114         console.assert(isPrimaryCluster);
115
116         if (isPrimaryCluster) {
117             this._primaryClusterEndTime = response['endTime'];
118             this._clusterCount = response['clusterCount'];
119             this._clusterStart = response['clusterStart'];
120             this._clusterSize = response['clusterSize'];
121         } else
122             console.assert(this._primaryClusterEndTime);
123
124         this._addFetchedCluster(new MeasurementCluster(response));
125     }
126
127     _addFetchedCluster(cluster)
128     {
129         this._sortedClusters.push(cluster);
130         this._sortedClusters = this._sortedClusters.sort(function (c1, c2) {
131             return c1.startTime() - c2.startTime();
132         });
133     }
134
135     hasFetchedRange(startTime, endTime)
136     {
137         console.assert(startTime < endTime);
138         var hasHole = false;
139         var previousEndTime = null;
140         for (var cluster of this._sortedClusters) {
141             if (cluster.startTime() < startTime && startTime < cluster.endTime())
142                 hasHole = false;
143             if (previousEndTime !== null && previousEndTime != cluster.startTime())
144                 hasHole = true;
145             if (cluster.startTime() < endTime && endTime < cluster.endTime())
146                 break;
147             previousEndTime = cluster.endTime();
148         }
149         return !hasHole;
150     }
151
152     fetchedTimeSeries(configType, includeOutliers, extendToFuture)
153     {
154         Instrumentation.startMeasuringTime('MeasurementSet', 'fetchedTimeSeries');
155
156         // FIXME: Properly construct TimeSeries.
157         var series = new TimeSeries([]);
158         var idMap = {};
159         for (var cluster of this._sortedClusters)
160             cluster.addToSeries(series, configType, includeOutliers, idMap);
161
162         if (extendToFuture)
163             series.extendToFuture();
164
165         Instrumentation.endMeasuringTime('MeasurementSet', 'fetchedTimeSeries');
166
167         return series;
168     }
169 }
170
171 if (typeof module != 'undefined')
172     module.exports.MeasurementSet = MeasurementSet;