Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / tools / sheriffing / botinfo.js
bloba5bdeb0a5b93286463606a882353c5173a90ca91
1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 /** Information about a particular bot. */
6 function BotInfo(name, category) {
7   // Chop off any digits at the beginning of category names.
8   if (category && category.length > 0) {
9     var splitterIndex = category.indexOf('|');
10     if (splitterIndex != -1) {
11       category = category.substr(0, splitterIndex);
12     }
14     while (category[0] >= '0' && category[0] <= '9') {
15       category = category.substr(1, category.length);
16     }
17   }
19   this.buildNumbersRunning = null;
20   this.builds = {};
21   this.category = category;
22   this.inFlight = 0;
23   this.isSteadyGreen = false;
24   this.name = name;
25   this.numUpdatesOffline = 0;
26   this.state = '';
29 /** Update info about the bot, including info about the builder's builds. */
30 BotInfo.prototype.update = function(rootJsonUrl, builderJson) {
31   // Update the builder's state.
32   this.buildNumbersRunning = builderJson.currentBuilds;
33   this.numPendingBuilds = builderJson.pendingBuilds;
34   this.state = builderJson.state;
36   // Check if an offline bot is still offline.
37   if (this.state == 'offline') {
38     this.numUpdatesOffline++;
39     console.log(this.name + ' has been offline for ' +
40                 this.numUpdatesOffline + ' update(s) in a row');
41   } else {
42     this.numUpdatesOffline = 0;
43   }
45   // Send asynchronous requests to get info about the builder's last builds.
46   var lastCompletedBuildNumber =
47       this.guessLastCompletedBuildNumber(builderJson);
48   if (lastCompletedBuildNumber) {
49     var startNumber = lastCompletedBuildNumber - NUM_PREVIOUS_BUILDS_TO_SHOW;
50     for (var buildNumber = startNumber;
51          buildNumber <= lastCompletedBuildNumber;
52          ++buildNumber) {
53       if (buildNumber < 0) continue;
55       // Use cached state after the builder indicates that it has finished.
56       if (this.builds[buildNumber] &&
57           this.builds[buildNumber].state != 'running') {
58         gNumRequestsIgnored++;
59         continue;
60       }
62       this.requestJson(rootJsonUrl, buildNumber);
63     }
64   }
67 /** Global callbacks. */
68 var gBotInfoCallbacks = {counter: 0};
70 /** Request and save data about a particular build. */
71 BotInfo.prototype.requestJson = function(rootJsonUrl, buildNumber) {
72   this.inFlight++;
73   gNumRequestsInFlight++;
75   // Create callback function.
76   var name = "fn" + gBotInfoCallbacks.counter++;
77   var botInfo = this;
78   gBotInfoCallbacks[name] = function(json) {
79     delete gBotInfoCallbacks[name];
81     botInfo.inFlight--;
82     gNumRequestsInFlight--;
83     botInfo.builds[json.number] = new BuildInfo(json);
84     botInfo.updateIsSteadyGreen();
85     gWaterfallDataIsDirty = true;
86   }
88   // Use JSONP to get data.
89   var url = rootJsonUrl + 'builders/' + this.name + '/builds/' + buildNumber;
90   var head = document.head;
91   var script = document.createElement('script');
92   script.type = 'text/javascript';
93   script.setAttribute('src', url + '?callback=gBotInfoCallbacks.' + name);
94   head.appendChild(script);
95   head.removeChild(script);
98 /** Guess the last known build a builder finished. */
99 BotInfo.prototype.guessLastCompletedBuildNumber = function(builderJson) {
100   // The cached builds line doesn't store every build so we can't just take the
101   // last number.
102   var buildNumbersRunning = builderJson.currentBuilds;
103   this.buildNumbersRunning = buildNumbersRunning;
105   var buildNumbersCached = builderJson.cachedBuilds;
106   if (buildNumbersRunning && buildNumbersRunning.length > 0) {
107     var maxBuildNumber =
108         Math.max(buildNumbersCached[buildNumbersCached.length - 1],
109                  buildNumbersRunning[buildNumbersRunning.length - 1]);
111     var completedBuildNumber = maxBuildNumber;
112     while (buildNumbersRunning.indexOf(completedBuildNumber) != -1 &&
113            completedBuildNumber >= 0) {
114       completedBuildNumber--;
115     }
116     return completedBuildNumber;
117   } else {
118     // Nothing's currently building.  Assume the last cached build is correct.
119     return buildNumbersCached[buildNumbersCached.length - 1];
120   }
124  * Returns true IFF the last few builds are all green.
125  * Also alerts the user if the last completed build goes red after being
126  * steadily green (if desired).
127  */
128 BotInfo.prototype.updateIsSteadyGreen = function() {
129   var ascendingBuildNumbers = Object.keys(this.builds);
130   ascendingBuildNumbers.sort();
132   var lastNumber =
133       ascendingBuildNumbers.length - 1 - NUM_PREVIOUS_BUILDS_TO_SHOW;
134   for (var j = ascendingBuildNumbers.length - 1;
135        j >= 0 && j >= lastNumber;
136        --j) {
137     var buildNumber = ascendingBuildNumbers[j];
138     if (!buildNumber) continue;
140     var buildInfo = this.builds[buildNumber];
141     if (!buildInfo) continue;
143     // Running builds throw heuristics out of whack.  Keep the bot visible.
144     if (buildInfo.state == 'running') return false;
146     if (buildInfo.state != 'success') {
147       if (this.isSteadyGreen &&
148           document.getElementById('checkbox-alert-steady-red').checked) {
149         alert(this.name +
150               ' has failed for the first time in a while. Consider looking.');
151       }
152       this.isSteadyGreen = false;
153       return;
154     }
155   }
157   this.isSteadyGreen = true;
158   return;
161 /** Creates HTML elements to display info about this bot. */
162 BotInfo.prototype.createHtml = function(waterfallBaseUrl) {
163   var botRowElement = document.createElement('tr');
165   // Insert a cell for the bot category.
166   var categoryCellElement = botRowElement.insertCell(-1);
167   categoryCellElement.innerHTML = this.category;
168   categoryCellElement.className = 'category';
170   // Insert a cell for the bot name.
171   var botUrl = waterfallBaseUrl + this.name;
172   var botElement = document.createElement('a');
173   botElement.href = botUrl;
174   botElement.innerHTML = this.name;
176   var nameCell = botRowElement.insertCell(-1);
177   nameCell.appendChild(botElement);
178   nameCell.className = 'bot-name' + (this.inFlight > 0 ? ' in-flight' : '');
180   // Create a cell to show how many CLs are waiting for a build.
181   var pendingCell = botRowElement.insertCell(-1);
182   pendingCell.className = 'pending-count';
183   pendingCell.title = 'Pending builds: ' + this.numPendingBuilds;
184   if (this.numPendingBuilds) {
185     pendingCell.innerHTML = '+' + this.numPendingBuilds;
186   }
188   // Create a cell to indicate what the bot is currently doing.
189   var runningElement = botRowElement.insertCell(-1);
190   if (this.buildNumbersRunning && this.buildNumbersRunning.length > 0) {
191     // Display the number of the highest numbered running build.
192     this.buildNumbersRunning.sort();
193     var numRunning = this.buildNumbersRunning.length;
194     var buildNumber = this.buildNumbersRunning[numRunning - 1];
195     var buildUrl = botUrl + '/builds/' + buildNumber;
196     createBuildHtml(runningElement,
197                     buildUrl,
198                     buildNumber,
199                     'Builds running: ' + numRunning,
200                     null,
201                     'running');
202   } else if (this.state == 'offline' && this.numUpdatesOffline >= 3) {
203     // The bot's supposedly offline. Waits a few updates since a bot can be
204     // marked offline in between builds and during reboots.
205     createBuildHtml(runningElement,
206                     botUrl,
207                     'offline',
208                     'Offline for: ' + this.numUpdatesOffline,
209                     null,
210                     'offline');
211   }
213   // Display information on the builds we have.
214   // This assumes that the build number always increases, but this is a bad
215   // assumption since builds get parallelized.
216   var buildNumbers = Object.keys(this.builds);
217   buildNumbers.sort();
218   for (var j = buildNumbers.length - 1;
219        j >= 0 && j >= buildNumbers.length - 1 - NUM_PREVIOUS_BUILDS_TO_SHOW;
220        --j) {
221     var buildNumber = buildNumbers[j];
222     if (!buildNumber) continue;
224     var buildInfo = this.builds[buildNumber];
225     if (!buildInfo) continue;
227     var buildNumberCell = botRowElement.insertCell(-1);
228     var isLastBuild = (j == buildNumbers.length - 1);
230     // Create and append the cell.
231     this.builds[buildNumber].createHtml(buildNumberCell, botUrl, isLastBuild);
232   }
234   return botRowElement;