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. 4 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 } 13 14 while (category[0] >= '0' && category[0] <= '9') { 15 category = category.substr(1, category.length); 16 } 17 } 18 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 = ''; 27 } 28 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; 35 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 } 44 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; 54 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 } 61 62 this.requestJson(rootJsonUrl, buildNumber); 63 } 64 } 65 }; 66 67 /** Request and save data about a particular build. */ 68 BotInfo.prototype.requestJson = function(rootJsonUrl, buildNumber) { 69 this.inFlight++; 70 gNumRequestsInFlight++; 71 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--; 80 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); 88 }; 89 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; 96 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]); 102 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 } 113 }; 114 115 /** 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(); 123 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; 131 132 var buildInfo = this.builds[buildNumber]; 133 if (!buildInfo) continue; 134 135 // Running builds throw heuristics out of whack. Keep the bot visible. 136 if (buildInfo.state == 'running') return false; 137 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 } 148 149 this.isSteadyGreen = true; 150 return; 151 }; 152 153 /** Creates HTML elements to display info about this bot. */ 154 BotInfo.prototype.createHtml = function(waterfallBaseUrl) { 155 var botRowElement = document.createElement('tr'); 156 157 // Insert a cell for the bot category. 158 var categoryCellElement = botRowElement.insertCell(-1); 159 categoryCellElement.innerHTML = this.category; 160 categoryCellElement.className = 'category'; 161 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; 167 168 var nameCell = botRowElement.insertCell(-1); 169 nameCell.appendChild(botElement); 170 nameCell.className = 'bot-name' + (this.inFlight > 0 ? ' in-flight' : ''); 171 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 } 179 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 } 204 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; 215 216 var buildInfo = this.builds[buildNumber]; 217 if (!buildInfo) continue; 218 219 var buildNumberCell = botRowElement.insertCell(-1); 220 var isLastBuild = (j == buildNumbers.length - 1); 221 222 // Create and append the cell. 223 this.builds[buildNumber].createHtml(buildNumberCell, botUrl, isLastBuild); 224 } 225 226 return botRowElement; 227 }; 228