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 |