| OLD | NEW |
| (Empty) |
| 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 /** Global callbacks. */ | |
| 68 var gBotInfoCallbacks = {counter: 0}; | |
| 69 | |
| 70 /** Request and save data about a particular build. */ | |
| 71 BotInfo.prototype.requestJson = function(rootJsonUrl, buildNumber) { | |
| 72 this.inFlight++; | |
| 73 gNumRequestsInFlight++; | |
| 74 | |
| 75 // Create callback function. | |
| 76 var name = "fn" + gBotInfoCallbacks.counter++; | |
| 77 var botInfo = this; | |
| 78 gBotInfoCallbacks[name] = function(json) { | |
| 79 delete gBotInfoCallbacks[name]; | |
| 80 | |
| 81 botInfo.inFlight--; | |
| 82 gNumRequestsInFlight--; | |
| 83 botInfo.builds[json.number] = new BuildInfo(json); | |
| 84 botInfo.updateIsSteadyGreen(); | |
| 85 gWaterfallDataIsDirty = true; | |
| 86 } | |
| 87 | |
| 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); | |
| 96 }; | |
| 97 | |
| 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; | |
| 104 | |
| 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]); | |
| 110 | |
| 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 } | |
| 121 }; | |
| 122 | |
| 123 /** | |
| 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(); | |
| 131 | |
| 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; | |
| 139 | |
| 140 var buildInfo = this.builds[buildNumber]; | |
| 141 if (!buildInfo) continue; | |
| 142 | |
| 143 // Running builds throw heuristics out of whack. Keep the bot visible. | |
| 144 if (buildInfo.state == 'running') return false; | |
| 145 | |
| 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 } | |
| 156 | |
| 157 this.isSteadyGreen = true; | |
| 158 return; | |
| 159 }; | |
| 160 | |
| 161 /** Creates HTML elements to display info about this bot. */ | |
| 162 BotInfo.prototype.createHtml = function(waterfallBaseUrl) { | |
| 163 var botRowElement = document.createElement('tr'); | |
| 164 | |
| 165 // Insert a cell for the bot category. | |
| 166 var categoryCellElement = botRowElement.insertCell(-1); | |
| 167 categoryCellElement.innerHTML = this.category; | |
| 168 categoryCellElement.className = 'category'; | |
| 169 | |
| 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; | |
| 175 | |
| 176 var nameCell = botRowElement.insertCell(-1); | |
| 177 nameCell.appendChild(botElement); | |
| 178 nameCell.className = 'bot-name' + (this.inFlight > 0 ? ' in-flight' : ''); | |
| 179 | |
| 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 } | |
| 187 | |
| 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 } | |
| 212 | |
| 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; | |
| 223 | |
| 224 var buildInfo = this.builds[buildNumber]; | |
| 225 if (!buildInfo) continue; | |
| 226 | |
| 227 var buildNumberCell = botRowElement.insertCell(-1); | |
| 228 var isLastBuild = (j == buildNumbers.length - 1); | |
| 229 | |
| 230 // Create and append the cell. | |
| 231 this.builds[buildNumber].createHtml(buildNumberCell, botUrl, isLastBuild); | |
| 232 } | |
| 233 | |
| 234 return botRowElement; | |
| 235 }; | |
| OLD | NEW |