Dashboard should show performance bots.
[WebKit-https.git] / Tools / BuildSlaveSupport / build.webkit.org-config / public_html / dashboard / Scripts / BuildbotQueue.js
1 /*
2  * Copyright (C) 2013 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 BuildbotQueue = function(buildbot, id, info)
27 {
28     BaseObject.call(this);
29
30     console.assert(buildbot);
31     console.assert(id);
32
33     this.buildbot = buildbot;
34     this.id = id;
35
36     this.branch = info.branch || null;
37     this.platform = info.platform.name || "unknown";
38     this.debug = info.debug || false;
39     this.builder = info.builder || false;
40     this.tester = info.tester || false;
41     this.performance = info.performance || false;
42     this.architecture = info.architecture || null;
43     this.testCategory = info.testCategory || null;
44     this.performanceTestName = info.performanceTestName || null;
45
46     this.iterations = [];
47     this._knownIterations = {};
48 };
49
50 BaseObject.addConstructorFunctions(BuildbotQueue);
51
52 BuildbotQueue.RecentIterationsToLoad = 10;
53
54 BuildbotQueue.Event = {
55     IterationsAdded: "iterations-added",
56     UnauthorizedAccess: "unauthorized-access"
57 };
58
59 BuildbotQueue.prototype = {
60     constructor: BuildbotQueue,
61     __proto__: BaseObject.prototype,
62
63     get baseURL()
64     {
65         return this.buildbot.baseURL + "json/builders/" + encodeURIComponent(this.id);
66     },
67
68     get allIterationsURL()
69     {
70         // Getting too many builds results in a timeout error, 10000 is OK.
71         return this.buildbot.baseURL + "json/builders/" + encodeURIComponent(this.id) + "/builds/_all/?max=10000";
72     },
73
74     get overviewURL()
75     {
76         return this.buildbot.baseURL + "builders/" + encodeURIComponent(this.id) + "?numbuilds=50";
77     },
78
79     get recentFailedIterationCount()
80     {
81         var firstFinishedIteration = this.mostRecentFinishedIteration;
82         var mostRecentSuccessfulIteration = this.mostRecentSuccessfulIteration;
83         return this.iterations.indexOf(mostRecentSuccessfulIteration) - this.iterations.indexOf(firstFinishedIteration);
84     },
85
86     get firstRecentUnsuccessfulIteration()
87     {
88         if (!this.iterations.length)
89             return null;
90
91         for (var i = 0; i < this.iterations.length; ++i) {
92             if (!this.iterations[i].finished || !this.iterations[i].successful)
93                 continue;
94             if (this.iterations[i - 1] && this.iterations[i - 1].finished && !this.iterations[i - 1].successful)
95                 return this.iterations[i - 1];
96             return null;
97         }
98
99         if (!this.iterations[this.iterations.length - 1].successful)
100             return this.iterations[this.iterations.length - 1];
101
102         return null;
103     },
104
105     get mostRecentFinishedIteration()
106     {
107         for (var i = 0; i < this.iterations.length; ++i) {
108             if (!this.iterations[i].finished)
109                 continue;
110             return this.iterations[i];
111         }
112
113         return null;
114     },
115
116     get mostRecentSuccessfulIteration()
117     {
118         for (var i = 0; i < this.iterations.length; ++i) {
119             if (!this.iterations[i].finished || !this.iterations[i].successful)
120                 continue;
121             return this.iterations[i];
122         }
123
124         return null;
125     },
126
127     _load: function(url, callback)
128     {
129         if (this.buildbot.needsAuthentication && this.buildbot.authenticationStatus === Buildbot.AuthenticationStatus.InvalidCredentials)
130             return;
131
132         JSON.load(
133             url,
134             function(data) {
135                 this.buildbot.isAuthenticated = true;
136                 callback(data);
137             }.bind(this),
138             function(data) {
139                 if (this.buildbot.isAuthenticated) {
140                     // FIXME (128006): Safari/WebKit should coalesce authentication requests with the same origin and authentication realm.
141                     // In absence of the fix, Safari presents additional authentication dialogs regardless of whether an earlier authentication
142                     // dialog was dismissed. As a way to ameliorate the user experience where a person authenticated successfully using an
143                     // earlier authentication dialog and cancelled the authentication dialog associated with the load for this queue, we call
144                     // ourself so that we can schedule another load, which should complete successfully now that we have credentials.
145                     this._load(url, callback);
146                     return;
147                 }
148                 if (data.errorType === JSON.LoadError && data.errorHTTPCode === 401) {
149                     this.buildbot.isAuthenticated = false;
150                     this.dispatchEventToListeners(BuildbotQueue.Event.UnauthorizedAccess, { });
151                 }
152             }.bind(this),
153             {withCredentials: this.buildbot.needsAuthentication}
154         );
155     },
156
157     loadMoreHistoricalIterations: function()
158     {
159         var indexOfFirstNewlyLoadingIteration;
160         for (var i = 0; i < this.iterations.length; ++i) {
161             if (indexOfFirstNewlyLoadingIteration !== undefined && i >= indexOfFirstNewlyLoadingIteration + BuildbotQueue.RecentIterationsToLoad)
162                 return;
163             var iteration = this.iterations[i];
164             if (!iteration.finished)
165                 continue;
166             if (iteration.isLoading) {
167                 // Caller lacks visibility into loading, so it is likely to call this function too often.
168                 // Give it a chance to analyze everything that's been already requested first, and then it can decide whether it needs more.
169                 return;
170             }
171             if (iteration.loaded && indexOfFirstNewlyLoadingIteration !== undefined) {
172                 // There was a gap between loaded iterations, which we've closed now.
173                 return;
174             }
175             if (!iteration.loaded) {
176                 if (indexOfFirstNewlyLoadingIteration === undefined)
177                     indexOfFirstNewlyLoadingIteration = i;
178                 iteration.update();
179             }
180         }
181     },
182
183     update: function()
184     {
185         this._load(this.baseURL, function(data) {
186             if (!(data.cachedBuilds instanceof Array))
187                 return;
188
189             var currentBuilds = {};
190             if (data.currentBuilds instanceof Array)
191                 data.currentBuilds.forEach(function(id) { currentBuilds[id] = true; });
192
193             var loadingStop = Math.max(0, data.cachedBuilds.length - BuildbotQueue.RecentIterationsToLoad);
194
195             var newIterations = [];
196
197             for (var i = data.cachedBuilds.length - 1; i >= 0; --i) {
198                 var iteration = this._knownIterations[data.cachedBuilds[i]];
199                 if (!iteration) {
200                     iteration = new BuildbotIteration(this, parseInt(data.cachedBuilds[i], 10), !(data.cachedBuilds[i] in currentBuilds));
201                     newIterations.push(iteration);
202                     this.iterations.push(iteration);
203                     this._knownIterations[iteration.id] = iteration;
204                 }
205
206                 if (i >= loadingStop && (!iteration.finished || !iteration.loaded))
207                     iteration.update();
208             }
209
210             if (!newIterations.length)
211                 return;
212
213             this.sortIterations();
214
215             this.dispatchEventToListeners(BuildbotQueue.Event.IterationsAdded, {addedIterations: newIterations});
216         }.bind(this));
217     },
218
219     loadAll: function(callback)
220     {
221         // FIXME: Don't load everything at once, do it incrementally as requested.
222         this._load(this.allIterationsURL, function(data) {
223             for (var idString in data) {
224                 console.assert(typeof idString === "string");
225                 iteration = new BuildbotIteration(this, data[idString]);
226                 this.iterations.push(iteration);
227                 this._knownIterations[iteration.id] = iteration;
228             }
229
230             callback(this);
231         }.bind(this));
232     },
233
234     sortIterations: function()
235     {
236         function compareIterations(a, b)
237         {
238             var result = b.openSourceRevision - a.openSourceRevision;
239             if (result)
240                 return result;
241
242             result = b.internalRevision - a.internalRevision;
243             if (result)
244                 return result;
245
246             return b.id - a.id;
247         }
248
249         this.iterations.sort(compareIterations);
250     }
251 };