| OLD | NEW |
| (Empty) |
| 1 <html> | |
| 2 <head> | |
| 3 <title>Skia Buildbot Self-Analysis</title> | |
| 4 <link rel="icon" href="favicon.ico"> | |
| 5 <script type="text/javascript" src="https://www.google.com/jsapi"></script> | |
| 6 <script type="text/javascript" src="skia_tools.js"></script> | |
| 7 <script language="JavaScript"> | |
| 8 "use strict"; | |
| 9 | |
| 10 var selectedMaster = null; | |
| 11 | |
| 12 // Build result codes. | |
| 13 var SUCCESS = 0; | |
| 14 | |
| 15 // Number of builds which should be loaded. | |
| 16 var DEFAULT_BUILDS_TO_LOAD = 50; | |
| 17 | |
| 18 // Object to manage Git revision history. | |
| 19 var gitHistory = new skiaTools.GitHistory(); | |
| 20 | |
| 21 // Configuration options for the charts | |
| 22 var LINE_CHART_OPTIONS = { | |
| 23 "title": "Build Times", | |
| 24 "width": "100%", | |
| 25 "height": "100%", | |
| 26 "chartArea": {left: "9%", top: "9%", width: "86%", | |
| 27 height: "70%"}, | |
| 28 "interpolateNulls": true, | |
| 29 "hAxis": {"title": "Revision"}, | |
| 30 "vAxis": {"title": "Build Time (s)"}, | |
| 31 "legend": {"textStyle": {"fontSize": 12}, | |
| 32 "position": "bottom"}, | |
| 33 }; | |
| 34 | |
| 35 var LINE_CHART_RANGE_FILTER_OPTIONS = { | |
| 36 "filterColumnIndex": 0, | |
| 37 "ui": { | |
| 38 "chartType": "AreaChart", | |
| 39 "minRangeSize": 1, | |
| 40 "chartOptions": { | |
| 41 "chartArea": {"width": "86%", | |
| 42 "height": "75%", | |
| 43 "left": "9%", | |
| 44 "right": "6%"}, | |
| 45 "width": "100%", | |
| 46 "height": "100%", | |
| 47 "colors": ["grey"], | |
| 48 "hAxis": {"baselineColor": "none"}, | |
| 49 "vAxis": {"baselineColor": "none"}, | |
| 50 "interpolateNulls": true, | |
| 51 }, | |
| 52 }, | |
| 53 }; | |
| 54 | |
| 55 var BAR_CHART_OPTIONS = { | |
| 56 "width": "100%", | |
| 57 "height": "100%", | |
| 58 "chartArea": {left: "17%", top: "9%", width: "65%", height: "70%"}, | |
| 59 "hAxis": {"title": "Time (s)"}, | |
| 60 "vAxis": {"title": "Build Step"}, | |
| 61 "tooltip": {"isHtml": true}, | |
| 62 "legend": {"position": "none"}, | |
| 63 }; | |
| 64 | |
| 65 // High-level information about each builder. | |
| 66 var builderData = null; | |
| 67 | |
| 68 var loadStart = null; | |
| 69 | |
| 70 // Load the Visualization API | |
| 71 google.load("visualization", "1.0", {"packages":["corechart", "controls"]}); | |
| 72 | |
| 73 // Set a callback to run when the Google Visualization API is loaded. | |
| 74 google.setOnLoadCallback(init); | |
| 75 | |
| 76 /** | |
| 77 * Display text or HTML in the logging div. | |
| 78 * @param {string} msg The HTML or text to display. | |
| 79 */ | |
| 80 function setMessage(msg) { | |
| 81 console.log(msg); | |
| 82 document.getElementById("logging_div").innerHTML = msg; | |
| 83 } | |
| 84 | |
| 85 /** | |
| 86 * Set the HTML content in the pane on the right side of the page. | |
| 87 * @param {string} content HTML to display. | |
| 88 */ | |
| 89 function setRightContent(content) { | |
| 90 document.getElementById("right_content_div").innerHTML = content; | |
| 91 } | |
| 92 | |
| 93 /** | |
| 94 * Clear the divs containing charts. Useful when charts need to be updated. | |
| 95 */ | |
| 96 function clearCharts() { | |
| 97 document.getElementById("line_chart_div").innerHTML = ""; | |
| 98 document.getElementById("line_chart_range_filter_div").innerHTML = ""; | |
| 99 document.getElementById("bar_chart_div").innerHTML = ""; | |
| 100 } | |
| 101 | |
| 102 /** | |
| 103 * Convert a duration in seconds to an easily-readable string. | |
| 104 * @param {number} seconds The duration to convert. | |
| 105 * @return {string} The duration in HH:MM:SS format. | |
| 106 */ | |
| 107 function formatTime(secondsParam) { | |
| 108 var seconds = Math.round(secondsParam); | |
| 109 var minutes = Math.floor(seconds / 60); | |
| 110 seconds -= seconds * 60; | |
| 111 var hours = Math.floor(minutes / 60); | |
| 112 minutes -= hours * 60; | |
| 113 var totalTime = ""; | |
| 114 if (seconds > 9) { | |
| 115 totalTime = seconds; | |
| 116 } else { | |
| 117 totalTime = "0" + seconds; | |
| 118 } | |
| 119 if (minutes > 9) { | |
| 120 totalTime = minutes + ":" + totalTime; | |
| 121 } else { | |
| 122 totalTime = "0" + minutes + ":" + totalTime; | |
| 123 } | |
| 124 if (hours > 9) { | |
| 125 totalTime = hours + ":" + totalTime; | |
| 126 } else { | |
| 127 totalTime = "0" + hours + ":" + totalTime; | |
| 128 } | |
| 129 return totalTime; | |
| 130 } | |
| 131 | |
| 132 /** | |
| 133 * Draw a line chart illustrating build times for a number of revisions for | |
| 134 * a set of builders. | |
| 135 * @param {Array.<string>} displayBuilders List of builders which should be | |
| 136 * included in the chart. | |
| 137 * @param {object} data Dictionary containing builds for various builders, | |
| 138 * indexed by commit hash. | |
| 139 */ | |
| 140 function drawLineChart(displayBuilders, data) { | |
| 141 var message = ""; | |
| 142 if (loadStart) { | |
| 143 var elapsedSeconds = (new Date().getTime() - loadStart) / 1000; | |
| 144 message = "Loaded data in " + elapsedSeconds + " seconds."; | |
| 145 } | |
| 146 setMessage(message); | |
| 147 var table = new google.visualization.DataTable(); | |
| 148 table.addColumn("string", "Revision"); | |
| 149 for (var i = 0; i < displayBuilders.length; i++) { | |
| 150 table.addColumn("number", displayBuilders[i]); | |
| 151 table.addColumn({"type": "string", "role": "annotation"}); | |
| 152 } | |
| 153 var lineChartColsPerBuilder = 2; | |
| 154 var longestBuildTime = -1; | |
| 155 var rows = []; | |
| 156 var allRevs = gitHistory.getRevList(); | |
| 157 for (var revIdx = 0; revIdx < allRevs.length; revIdx++) { | |
| 158 var rev = allRevs[revIdx]; | |
| 159 var row = [rev]; | |
| 160 var rowHasData = false; | |
| 161 for (var builderIdx = 0; builderIdx < displayBuilders.length; | |
| 162 builderIdx++) { | |
| 163 var builder = displayBuilders[builderIdx]; | |
| 164 if (data[rev] != undefined && data[rev][builder] != undefined) { | |
| 165 rowHasData = true; | |
| 166 var time = data[rev][builder].getElapsedTime(); | |
| 167 if (time > longestBuildTime) { | |
| 168 longestBuildTime = time; | |
| 169 } | |
| 170 row.push(time); | |
| 171 if (data[rev][builder].getResult() != SUCCESS) { | |
| 172 row.push("failed"); | |
| 173 } else { | |
| 174 row.push(null); | |
| 175 } | |
| 176 } else { | |
| 177 row.push(null); | |
| 178 row.push(null); | |
| 179 } | |
| 180 } | |
| 181 // Skip revisions which have no builds. | |
| 182 if (rowHasData) { | |
| 183 rows.push(row); | |
| 184 } | |
| 185 } | |
| 186 table.addRows(rows); | |
| 187 | |
| 188 var lineChart = new google.visualization.LineChart(document.getElementById( | |
| 189 "line_chart_div")); | |
| 190 google.visualization.events.addListener(lineChart, 'select', | |
| 191 function() { | |
| 192 var selected = lineChart.getSelection()[0]; | |
| 193 if (selected && | |
| 194 selected.column != undefined && | |
| 195 selected.row != undefined) { | |
| 196 var builder = null; | |
| 197 var builderIdx = Math.floor( | |
| 198 (selected.column - 1) / lineChartColsPerBuilder); | |
| 199 builder = displayBuilders[builderIdx]; | |
| 200 var rev = rows[selected.row][0] | |
| 201 var buildNum = data[rev][builder].getNumber(); | |
| 202 console.log("Selected: " + builder + " @ " + rev); | |
| 203 var url = "http://" + selectedMaster.getHost() + ":" + | |
| 204 selectedMaster.getPort() + "/buildstatus?builder=" + builder + | |
| 205 "&number=" + buildNum; | |
| 206 var rightContent = "<iframe style=\"width:100%; height:100%; " | |
| 207 + "padding:0px; margin=0px; overflow:scroll;\" src=\"" | |
| 208 + url + "\"></iframe>"; | |
| 209 setRightContent(rightContent); | |
| 210 drawBarChart(builder, rev, longestBuildTime, data); | |
| 211 } | |
| 212 }); | |
| 213 lineChart.draw(table, LINE_CHART_OPTIONS); | |
| 214 setRightContent(""); | |
| 215 } | |
| 216 | |
| 217 /** | |
| 218 * Draw a bar chart illustrating the running times of each step in a given | |
| 219 * build for a given builder. | |
| 220 * @param {string} builder The builder whose build should be displayed. | |
| 221 * @param {string} revision The revision of the build to display. | |
| 222 * @param {number} viewWindowMax The width of the viewing window. This is | |
| 223 * provided instead of being determined from the data so that each bar | |
| 224 * graph can be displayed with the same scale. | |
| 225 * @param {object} data Dictionary containing builds for various builders, | |
| 226 * indexed by commit hash. | |
| 227 */ | |
| 228 function drawBarChart(builder, revision, viewWindowMax, data) { | |
| 229 var table = new google.visualization.DataTable(); | |
| 230 table.addColumn("string", "Step"); | |
| 231 table.addColumn("number", "Time"); | |
| 232 table.addColumn({"type": "string", "role": "tooltip", "p": {"html": true}}); | |
| 233 var buildData = data[revision][builder]; | |
| 234 for (var stepIdx = 0; stepIdx < buildData.getSteps().length; stepIdx++) { | |
| 235 var step = buildData.getSteps()[stepIdx]; | |
| 236 var stepName = step.getName(); | |
| 237 var time = step.getElapsedTime(); | |
| 238 var stdio = step.getStdio(); | |
| 239 var result = step.getResult(); | |
| 240 var percent = time / buildData.getElapsedTime() * 100.0; | |
| 241 table.addRow(); | |
| 242 table.setValue(stepIdx, 0, stepName); | |
| 243 table.setValue(stepIdx, 1, time); | |
| 244 var tooltip = "<div style=\"font-family:Arial;font-size:14px;" | |
| 245 + "padding:10px;\">" + stepName; | |
| 246 if (result != 0) { | |
| 247 tooltip += " <span style=\"background-color:red;\">Failed</span>"; | |
| 248 } | |
| 249 tooltip += "<br/>" + formatTime(time) + " (" + percent.toFixed(2) | |
| 250 + "%)<br/>Log: <a href=\"" + stdio | |
| 251 + "\" target=\"_blank\">stdout</a></div>"; | |
| 252 table.setFormattedValue(stepIdx, 2, tooltip); | |
| 253 } | |
| 254 | |
| 255 // Instantiate and draw our chart, passing in some options. | |
| 256 var barChart = new google.visualization.BarChart( | |
| 257 document.getElementById("bar_chart_div")); | |
| 258 var options = BAR_CHART_OPTIONS; | |
| 259 options["title"] = builder + ": Build #" + buildData.getNumber() + " (" + | |
| 260 buildData.getRevision() + ")"; | |
| 261 options["hAxis"]["viewWindow"] = {"max": viewWindowMax}; | |
| 262 barChart.draw(table, BAR_CHART_OPTIONS); | |
| 263 } | |
| 264 | |
| 265 /** | |
| 266 * Callback function to use when the selected items of the listbox have | |
| 267 * changed. This causes data to be loaded for the selected builders and charts | |
| 268 * to be created for that data. | |
| 269 */ | |
| 270 function selectBuilders() { | |
| 271 if (!selectedMaster) { | |
| 272 console.warn("selectBuilders: No master selected!"); | |
| 273 return; | |
| 274 } | |
| 275 loadStart = new Date().getTime(); | |
| 276 var displayBuilders = []; | |
| 277 var msg = "<p style=\"font-size:0.8em;\">Loading builds for builders:<ul>"; | |
| 278 var menu = document.getElementById("builder_menu"); | |
| 279 for (var i = 0; i < menu.options.length; i++) { | |
| 280 if (menu.options[i].selected) { | |
| 281 var builder = menu.options[i].text; | |
| 282 if (displayBuilders.indexOf(builder) == -1) { | |
| 283 displayBuilders.push(builder); | |
| 284 msg += "<li style=\"font-size:0.8em;\">" + builder; | |
| 285 } | |
| 286 } | |
| 287 } | |
| 288 msg += "</ul></p>"; | |
| 289 clearCharts(); | |
| 290 setMessage(msg); | |
| 291 var data = {}; | |
| 292 | |
| 293 var revsToLoad = []; | |
| 294 var buildsToLoad = document.getElementById("builds_to_load").value; | |
| 295 var buildersToLoad = Array.prototype.slice.call(displayBuilders); | |
| 296 for (var i = 0; i < displayBuilders.length; i++) { | |
| 297 var builder = displayBuilders[i]; | |
| 298 var builderObj = builderData[builder]; | |
| 299 builderObj.loadBuilds(buildsToLoad, function(builder, dataForBuilder) { | |
| 300 for (var buildNum in dataForBuilder) { | |
| 301 var rev = dataForBuilder[buildNum].getRevision(); | |
| 302 if (revsToLoad.indexOf(rev) == -1) { | |
| 303 revsToLoad.push(rev); | |
| 304 } | |
| 305 if (!data[rev]) { | |
| 306 data[rev] = {}; | |
| 307 } | |
| 308 data[rev][builder] = dataForBuilder[buildNum]; | |
| 309 } | |
| 310 console.log("Got builds for " + builder); | |
| 311 buildersToLoad.splice(buildersToLoad.indexOf(builder), 1); | |
| 312 if (buildersToLoad.length == 0) { | |
| 313 // Ensure that all revision data has been loaded, draw the chart. | |
| 314 gitHistory.ensureLoaded(revsToLoad, function() { | |
| 315 drawLineChart(displayBuilders, data); | |
| 316 }); | |
| 317 } | |
| 318 }); | |
| 319 } | |
| 320 } | |
| 321 | |
| 322 /** | |
| 323 * Callback function called when the selected build master has changed. Loads | |
| 324 * the builders for the master in the master_menu dropdown. | |
| 325 */ | |
| 326 function selectMaster() { | |
| 327 selectedMaster = new skiaTools.Master( | |
| 328 document.getElementById("master_menu").value); | |
| 329 setMessage("Loading builders for " + selectedMaster.getName() + | |
| 330 " master..."); | |
| 331 selectedMaster.loadBuilders(function(builders) { | |
| 332 builderData = {}; | |
| 333 for (var builderNum = 0; builderNum < builders.length; builderNum++) { | |
| 334 var builder = builders[builderNum]; | |
| 335 builderData[builder.getName()] = builder; | |
| 336 } | |
| 337 skiaTools.populateMenu("builder_menu", Object.keys(builderData)); | |
| 338 setMessage("Loaded builders."); | |
| 339 setMessage("Select one or more builders."); | |
| 340 }); | |
| 341 } | |
| 342 | |
| 343 /** | |
| 344 * Callback function to use on page load. This causes the high-level builder | |
| 345 * data to be loaded and the builder menu populated. | |
| 346 */ | |
| 347 function init() { | |
| 348 setMessage("Loading masters..."); | |
| 349 document.getElementById("builds_to_load").value = DEFAULT_BUILDS_TO_LOAD; | |
| 350 skiaTools.loadMasterList(function(masters) { | |
| 351 skiaTools.populateMenu("master_menu", masters); | |
| 352 document.getElementById("master_menu").selectedIndex = -1; | |
| 353 setMessage("Loaded masters."); | |
| 354 setMessage("Please select a build master."); | |
| 355 }); | |
| 356 } | |
| 357 </script> | |
| 358 </head> | |
| 359 <body> | |
| 360 <div id="heading" style="font-size:2.5em; text-align:center; height:7%;"> | |
| 361 Skia Buildbots Self-Analysis | |
| 362 </div> | |
| 363 <div id="main_content_area" style="width:100%; height:90%; padding:0px; marg
in:0px;"> | |
| 364 <div id="builder_menu_div" style="float:left; width:18%; height:100%; padd
ing:0px; margin:0px;"> | |
| 365 <div style="width:100%;"> | |
| 366 <div style="float:left; width:50%;"> | |
| 367 Master:<br/> | |
| 368 <select id="master_menu" onChange="selectMaster();"> | |
| 369 <option value="" noselect></option> | |
| 370 </select> | |
| 371 </div> | |
| 372 <div> | |
| 373 <nobr>Builds to load:</nobr><br/> | |
| 374 <input type="text" id="builds_to_load" value="" /> | |
| 375 </div> | |
| 376 </div> | |
| 377 <div> | |
| 378 Builders:<br/> | |
| 379 <select id="builder_menu" multiple="multiple" | |
| 380 style="width:100%; height:90%; margin:0px; padding:0px;"> | |
| 381 </select> | |
| 382 <br/> | |
| 383 <input type="button" onClick="selectBuilders()" | |
| 384 value="Update Selection"/> | |
| 385 </div> | |
| 386 </div> | |
| 387 <div id="charts_div" style="float:left; width:67%; padding:0px; margin:0px
"> | |
| 388 <div id="logging_div" style="width:100%; padding:0px; margin:0px"></div> | |
| 389 <div id="line_chart_div" style="width:100%; height: 40%; padding:0px; ma
rgin:0px"></div> | |
| 390 <div id="line_chart_range_filter_div" style="width:100%; height: 10%; pa
dding:0px; margin:0px"></div> | |
| 391 <div id="bar_chart_div" style="width:100%; height: 50%; padding:0px; mar
gin:0px"></div> | |
| 392 </div> | |
| 393 <div id="right_content_div" style="float:right; width:15%; height:100%"></
div> | |
| 394 </div> | |
| 395 </body> | |
| 396 </html> | |
| OLD | NEW |