Reverting all rolls in: https://build.chromium.org/p/chromium/builders/Win/builds...
[chromium-blink-merge.git] / tools / sheriffing / botinfo.js
blob9b7e1a1ab7fde3bad4a6d6458aa10d80fdefd4f3
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 /** Request and save data about a particular build. */
68 BotInfo.prototype.requestJson = function(rootJsonUrl, buildNumber) {
69   this.inFlight++;
70   gNumRequestsInFlight++;
72   var botInfo = this;
73   var url = rootJsonUrl + 'builders/' + this.name + '/builds/' + buildNumber;
74   var request = new XMLHttpRequest();
75   request.open('GET', url, true);
76   request.onreadystatechange = function() {
77     if (request.readyState == 4 && request.status == 200) {
78       botInfo.inFlight--;
79       gNumRequestsInFlight--;
81       var json = JSON.parse(request.responseText);
82       botInfo.builds[json.number] = new BuildInfo(json);
83       botInfo.updateIsSteadyGreen();
84       gWaterfallDataIsDirty = true;
85     }
86   };
87   request.send(null);
90 /** Guess the last known build a builder finished. */
91 BotInfo.prototype.guessLastCompletedBuildNumber = function(builderJson) {
92   // The cached builds line doesn't store every build so we can't just take the
93   // last number.
94   var buildNumbersRunning = builderJson.currentBuilds;
95   this.buildNumbersRunning = buildNumbersRunning;
97   var buildNumbersCached = builderJson.cachedBuilds;
98   if (buildNumbersRunning && buildNumbersRunning.length > 0) {
99     var maxBuildNumber =
100         Math.max(buildNumbersCached[buildNumbersCached.length - 1],
101                  buildNumbersRunning[buildNumbersRunning.length - 1]);
103     var completedBuildNumber = maxBuildNumber;
104     while (buildNumbersRunning.indexOf(completedBuildNumber) != -1 &&
105            completedBuildNumber >= 0) {
106       completedBuildNumber--;
107     }
108     return completedBuildNumber;
109   } else {
110     // Nothing's currently building.  Assume the last cached build is correct.
111     return buildNumbersCached[buildNumbersCached.length - 1];
112   }
116  * Returns true IFF the last few builds are all green.
117  * Also alerts the user if the last completed build goes red after being
118  * steadily green (if desired).
119  */
120 BotInfo.prototype.updateIsSteadyGreen = function() {
121   var ascendingBuildNumbers = Object.keys(this.builds);
122   ascendingBuildNumbers.sort();
124   var lastNumber =
125       ascendingBuildNumbers.length - 1 - NUM_PREVIOUS_BUILDS_TO_SHOW;
126   for (var j = ascendingBuildNumbers.length - 1;
127        j >= 0 && j >= lastNumber;
128        --j) {
129     var buildNumber = ascendingBuildNumbers[j];
130     if (!buildNumber) continue;
132     var buildInfo = this.builds[buildNumber];
133     if (!buildInfo) continue;
135     // Running builds throw heuristics out of whack.  Keep the bot visible.
136     if (buildInfo.state == 'running') return false;
138     if (buildInfo.state != 'success') {
139       if (this.isSteadyGreen &&
140           document.getElementById('checkbox-alert-steady-red').checked) {
141         alert(this.name +
142               ' has failed for the first time in a while. Consider looking.');
143       }
144       this.isSteadyGreen = false;
145       return;
146     }
147   }
149   this.isSteadyGreen = true;
150   return;
153 /** Creates HTML elements to display info about this bot. */
154 BotInfo.prototype.createHtml = function(waterfallBaseUrl) {
155   var botRowElement = document.createElement('tr');
157   // Insert a cell for the bot category.
158   var categoryCellElement = botRowElement.insertCell(-1);
159   categoryCellElement.innerHTML = this.category;
160   categoryCellElement.className = 'category';
162   // Insert a cell for the bot name.
163   var botUrl = waterfallBaseUrl + this.name;
164   var botElement = document.createElement('a');
165   botElement.href = botUrl;
166   botElement.innerHTML = this.name;
168   var nameCell = botRowElement.insertCell(-1);
169   nameCell.appendChild(botElement);
170   nameCell.className = 'bot-name' + (this.inFlight > 0 ? ' in-flight' : '');
172   // Create a cell to show how many CLs are waiting for a build.
173   var pendingCell = botRowElement.insertCell(-1);
174   pendingCell.className = 'pending-count';
175   pendingCell.title = 'Pending builds: ' + this.numPendingBuilds;
176   if (this.numPendingBuilds) {
177     pendingCell.innerHTML = '+' + this.numPendingBuilds;
178   }
180   // Create a cell to indicate what the bot is currently doing.
181   var runningElement = botRowElement.insertCell(-1);
182   if (this.buildNumbersRunning && this.buildNumbersRunning.length > 0) {
183     // Display the number of the highest numbered running build.
184     this.buildNumbersRunning.sort();
185     var numRunning = this.buildNumbersRunning.length;
186     var buildNumber = this.buildNumbersRunning[numRunning - 1];
187     var buildUrl = botUrl + '/builds/' + buildNumber;
188     createBuildHtml(runningElement,
189                     buildUrl,
190                     buildNumber,
191                     'Builds running: ' + numRunning,
192                     null,
193                     'running');
194   } else if (this.state == 'offline' && this.numUpdatesOffline >= 3) {
195     // The bot's supposedly offline. Waits a few updates since a bot can be
196     // marked offline in between builds and during reboots.
197     createBuildHtml(runningElement,
198                     botUrl,
199                     'offline',
200                     'Offline for: ' + this.numUpdatesOffline,
201                     null,
202                     'offline');
203   }
205   // Display information on the builds we have.
206   // This assumes that the build number always increases, but this is a bad
207   // assumption since builds get parallelized.
208   var buildNumbers = Object.keys(this.builds);
209   buildNumbers.sort();
210   for (var j = buildNumbers.length - 1;
211        j >= 0 && j >= buildNumbers.length - 1 - NUM_PREVIOUS_BUILDS_TO_SHOW;
212        --j) {
213     var buildNumber = buildNumbers[j];
214     if (!buildNumber) continue;
216     var buildInfo = this.builds[buildNumber];
217     if (!buildInfo) continue;
219     var buildNumberCell = botRowElement.insertCell(-1);
220     var isLastBuild = (j == buildNumbers.length - 1);
222     // Create and append the cell.
223     this.builds[buildNumber].createHtml(buildNumberCell, botUrl, isLastBuild);
224   }
226   return botRowElement;