| OLD | NEW |
| (Empty) |
| 1 <html> | |
| 2 <head> | |
| 3 <title>Skia Buildslave Idle Time 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 // Configuration options for the charts | |
| 13 var LINE_CHART_OPTIONS = { | |
| 14 "title": "Buildslave Busy Times", | |
| 15 "width": "100%", | |
| 16 "height": "100%", | |
| 17 "chartArea": {left: "9%", top: "9%", width: "86%", | |
| 18 height: "70%"}, | |
| 19 "vAxis": {"gridlines": {"count": 0}}, | |
| 20 "legend": {"textStyle": {"fontSize": 12}, | |
| 21 "position": "bottom"}, | |
| 22 "lineWidth": 20, | |
| 23 }; | |
| 24 | |
| 25 var LINE_CHART_RANGE_FILTER_OPTIONS = { | |
| 26 "filterColumnIndex": 0, | |
| 27 "ui": { | |
| 28 "chartType": "LineChart", | |
| 29 "minRangeSize": 1, | |
| 30 "chartOptions": { | |
| 31 "chartArea": {"width": "86%", | |
| 32 "height": "75%", | |
| 33 "left": "9%", | |
| 34 "right": "6%"}, | |
| 35 "width": "100%", | |
| 36 "height": "100%", | |
| 37 "colors": ["grey"], | |
| 38 "hAxis": {"baselineColor": "none"}, | |
| 39 "vAxis": {"baselineColor": "none"}, | |
| 40 }, | |
| 41 }, | |
| 42 }; | |
| 43 | |
| 44 var PIE_CHART_OPTIONS = {"title": "Busy/Idle Time"}; | |
| 45 | |
| 46 // Data for all builds for all build slaves. Filled in on-demand. | |
| 47 var data = null; | |
| 48 | |
| 49 // Dictionary with build slave names as keys and booleans as values, | |
| 50 // indicating whether or not build data has been obtained for each slave. | |
| 51 var gotData = {}; | |
| 52 | |
| 53 // Time window for data analysis in seconds. | |
| 54 // TODO(borenet): Make this configurable in the UI. | |
| 55 var RANGE_SECS = 86400; | |
| 56 | |
| 57 // Current time in UNIX seconds from epoch as of page load. | |
| 58 var CURRENT_TIME = Math.ceil(new Date().getTime() / 1000); | |
| 59 | |
| 60 // Beginning of the data analysis time window. | |
| 61 var MIN_TIME = CURRENT_TIME - RANGE_SECS; | |
| 62 | |
| 63 // Load the Visualization API | |
| 64 google.load("visualization", "1.0", {"packages":["corechart", "controls"]}); | |
| 65 | |
| 66 // Set a callback to run when the Google Visualization API is loaded. | |
| 67 google.setOnLoadCallback(init); | |
| 68 | |
| 69 /** | |
| 70 * Display text or HTML in the logging div. | |
| 71 * @param {string} msg The HTML or text to display. | |
| 72 */ | |
| 73 function setMessage(msg) { | |
| 74 console.log(msg); | |
| 75 document.getElementById("logging_div").innerHTML = msg; | |
| 76 } | |
| 77 | |
| 78 /** | |
| 79 * Set the HTML content in the pane on the right side of the page. | |
| 80 * @param {string} content HTML to display. | |
| 81 */ | |
| 82 function setRightContent(content) { | |
| 83 document.getElementById("right_content_div").innerHTML = content; | |
| 84 } | |
| 85 | |
| 86 /** | |
| 87 * Clear the divs containing charts. Useful when charts need to be updated. | |
| 88 */ | |
| 89 function clearCharts() { | |
| 90 document.getElementById("line_chart_div").innerHTML = ""; | |
| 91 document.getElementById("line_chart_range_filter_div").innerHTML = ""; | |
| 92 document.getElementById("pie_chart_div").innerHTML = ""; | |
| 93 } | |
| 94 | |
| 95 /** | |
| 96 * Draw a timeline-style line chart over the given range for the given set of | |
| 97 * build slaves. | |
| 98 * @param {Array.<string>} displaySlaves List of slave names indicating which | |
| 99 * slaves should be included in the chart. | |
| 100 * @param {number} rangeMin Beginning of the data analysis time window, in | |
| 101 UNIX seconds from epoch. | |
| 102 * @param {number} rangeMax End of the data analysis time window, in UNIX | |
| 103 seconds from epoch. | |
| 104 */ | |
| 105 function drawLineChart(displaySlaves, rangeMin, rangeMax) { | |
| 106 setMessage(""); | |
| 107 var table = new google.visualization.DataTable(); | |
| 108 table.addColumn("datetime", "Time"); | |
| 109 var dateMin = new Date(rangeMin * 1000); | |
| 110 var dateMax = new Date(rangeMax * 1000); | |
| 111 var times = [rangeMin]; | |
| 112 var currentBuild = {}; | |
| 113 for (var i = 0; i < displaySlaves.length; i++) { | |
| 114 var slave = displaySlaves[i]; | |
| 115 table.addColumn("number", displaySlaves[i]); | |
| 116 table.addColumn({"type": "string", | |
| 117 "role": "tooltip", | |
| 118 "p": {"html": true}}); | |
| 119 var slaveData = data[slave]; | |
| 120 currentBuild[slave] = null; | |
| 121 for (var j = 0; j < slaveData["allBuilds"].length; j++) { | |
| 122 var build = slaveData["allBuilds"][j]; | |
| 123 if (build.getStartTime() > rangeMin && | |
| 124 build.getStartTime() < rangeMax) { | |
| 125 times.push(Math.round(build.getStartTime() - 1)); | |
| 126 times.push(Math.round(build.getStartTime() + 1)); | |
| 127 if (currentBuild[slave] == null) { | |
| 128 currentBuild[slave] = j; | |
| 129 } | |
| 130 } | |
| 131 if (build.getEndTime() > rangeMin && build.getEndTime() < rangeMax) { | |
| 132 times.push(Math.round(build.getEndTime() - 1)); | |
| 133 times.push(Math.round(build.getEndTime() + 1)); | |
| 134 if (currentBuild[slave] == null) { | |
| 135 currentBuild[slave] = j; | |
| 136 } | |
| 137 } | |
| 138 } | |
| 139 } | |
| 140 times.push(rangeMax); | |
| 141 times.sort(); | |
| 142 var rows = []; | |
| 143 for (var i = 0; i < times.length; i++) { | |
| 144 var time = times[i]; | |
| 145 var row = [new Date(time * 1000)]; | |
| 146 for (j = 0; j < displaySlaves.length; j++) { | |
| 147 var slave = displaySlaves[j]; | |
| 148 var val = null; | |
| 149 var tooltip = null; | |
| 150 if (null != currentBuild[slave]) { | |
| 151 var buildIdx = currentBuild[slave]; | |
| 152 var buildData = data[slave]["allBuilds"][buildIdx] | |
| 153 var min = buildData.getStartTime(); | |
| 154 var max = buildData.getEndTime(); | |
| 155 if (time >= min) { | |
| 156 if (time <= max) { | |
| 157 val = j + 1; | |
| 158 tooltip = slave + ": " + buildData.getBuilder() + " Build #" + | |
| 159 buildData.getNumber(); | |
| 160 } else { | |
| 161 if (data[slave]["allBuilds"].length > currentBuild[slave] + 1) { | |
| 162 currentBuild[slave]++; | |
| 163 } else { | |
| 164 currentBuild[slave] = null; | |
| 165 } | |
| 166 } | |
| 167 } | |
| 168 } | |
| 169 row.push(val); | |
| 170 row.push(tooltip); | |
| 171 } | |
| 172 rows.push(row); | |
| 173 } | |
| 174 table.addRows(rows); | |
| 175 | |
| 176 var lineChart = new google.visualization.LineChart(document.getElementById( | |
| 177 "line_chart_div")); | |
| 178 var line_chart_options = LINE_CHART_OPTIONS; | |
| 179 line_chart_options["vAxis"]["viewWindow"] = {"min": 0, | |
| 180 "max": displaySlaves.length + 1}; | |
| 181 line_chart_options["hAxis"] = {"viewWindow": {"min": dateMin, | |
| 182 "max": dateMax}}; | |
| 183 lineChart.draw(table, line_chart_options); | |
| 184 | |
| 185 var lineChartRangeFilter = new google.visualization.ChartRangeFilter( | |
| 186 document.getElementById("line_chart_range_filter_div")); | |
| 187 google.visualization.events.addListener(lineChartRangeFilter, | |
| 188 "statechange", | |
| 189 function() { | |
| 190 var range = lineChartRangeFilter.getState().range; | |
| 191 line_chart_options["hAxis"]["viewWindow"] = {min: range.start, | |
| 192 max: range.end}; | |
| 193 lineChart.draw(table, line_chart_options); | |
| 194 if (displaySlaves.length == 1) { | |
| 195 drawPieChart(data[displaySlaves[0]]["allBuilds"], | |
| 196 range.start.getTime() / 1000, | |
| 197 range.end.getTime() / 1000); | |
| 198 } | |
| 199 }); | |
| 200 var line_chart_range_filter_options = LINE_CHART_RANGE_FILTER_OPTIONS; | |
| 201 line_chart_range_filter_options["ui"]["chartOptions"]["hAxis"] | |
| 202 ["viewWindow"] = {"min": dateMin, "max": dateMax}; | |
| 203 lineChartRangeFilter.draw(table, line_chart_range_filter_options, | |
| 204 {"range": {"start": dateMin, "end": dateMax}}); | |
| 205 if (displaySlaves.length == 1) { | |
| 206 drawPieChart(data[displaySlaves[0]]["allBuilds"], rangeMin, | |
| 207 rangeMax); | |
| 208 } | |
| 209 setRightContent(""); | |
| 210 } | |
| 211 | |
| 212 /** | |
| 213 * Draw a pie chart to display the proportions of busy and idle time over a | |
| 214 * particular time period for a given slave. | |
| 215 * @param {Array.<object>} builds List of builds for a slave, each | |
| 216 * represented as a list containing: | |
| 217 * <ul> | |
| 218 * <li>builderName {string} Name of the builder. | |
| 219 * <li>buildNum {number} Build number. | |
| 220 * <li>startTime {number} Time of the start of the build, in UNIX seconds | |
| 221 * from epoch. | |
| 222 * <li>endTime {number} Time of the end of the build, in UNIX seconds from | |
| 223 * epoch. | |
| 224 * </ul> | |
| 225 * @param {number} rangeMin Beginning of the analysis window in UNIX seconds | |
| 226 * from epoch. | |
| 227 * @param {number} rangeMax End of the analysis window in UNIX seconds from | |
| 228 * epoch. | |
| 229 */ | |
| 230 function drawPieChart(builds, rangeMin, rangeMax) { | |
| 231 var busyTime = 0; | |
| 232 for (var i = 0; i < builds.length; i++) { | |
| 233 var build = builds[i]; | |
| 234 var buildStart = build.getStartTime(); | |
| 235 var buildEnd = build.getEndTime(); | |
| 236 if (buildEnd < rangeMin || buildStart > rangeMax) { | |
| 237 continue; | |
| 238 } | |
| 239 if (buildStart < rangeMin) { | |
| 240 buildStart = rangeMin; | |
| 241 } | |
| 242 if (buildEnd > rangeMax) { | |
| 243 buildEnd = rangeMax; | |
| 244 } | |
| 245 busyTime += buildEnd - buildStart; | |
| 246 } | |
| 247 var idleTime = rangeMax - rangeMin - busyTime; | |
| 248 var table = new google.visualization.arrayToDataTable([ | |
| 249 ["Busy/Idle", "Time"], | |
| 250 ["Busy", busyTime], | |
| 251 ["Idle", idleTime], | |
| 252 ]); | |
| 253 | |
| 254 // Set chart options | |
| 255 var pie_chart_options = PIE_CHART_OPTIONS; | |
| 256 | |
| 257 // Instantiate and draw our chart, passing in some options. | |
| 258 var pieChart = new google.visualization.PieChart( | |
| 259 document.getElementById("pie_chart_div")); | |
| 260 pieChart.draw(table, pie_chart_options); | |
| 261 } | |
| 262 | |
| 263 /** | |
| 264 * Callback function to use when the selected items of the listbox have | |
| 265 * changed. This causes data to be loaded for the selected slaves and charts | |
| 266 * to be created for that data. | |
| 267 */ | |
| 268 function selectSlaves() { | |
| 269 var displaySlaves = []; | |
| 270 var msg = "<p style=\"font-size:0.8em;\">Loading builds for slaves:<ul>"; | |
| 271 var menu = document.getElementById("slave_menu"); | |
| 272 for (var i = 0; i < menu.options.length; i++) { | |
| 273 if (menu.options[i].selected) { | |
| 274 var slave = menu.options[i].text; | |
| 275 if (displaySlaves.indexOf(slave) == -1) { | |
| 276 displaySlaves.push(slave); | |
| 277 msg += "<li style=\"font-size:0.8em;\">" + slave; | |
| 278 } | |
| 279 } | |
| 280 } | |
| 281 msg += "</ul></p>"; | |
| 282 clearCharts(); | |
| 283 setMessage(msg); | |
| 284 var slavesToLoad = []; | |
| 285 for (var i = 0; i < displaySlaves.length; i++) { | |
| 286 var slave = displaySlaves[i]; | |
| 287 if (!gotData[slave] || gotData[slave] == undefined) { | |
| 288 console.log("Loading builds for " + slave + "..."); | |
| 289 slavesToLoad.push(slave); | |
| 290 data[slave].loadBuilds(MIN_TIME, CURRENT_TIME, function(buildList) { | |
| 291 data[slave]["allBuilds"] = buildList; | |
| 292 gotData[slave] = true; | |
| 293 console.log("Loaded builds for " + slave + "."); | |
| 294 slavesToLoad.splice(slave, 1); | |
| 295 if (slavesToLoad.length == 0) { | |
| 296 drawLineChart(displaySlaves, MIN_TIME, CURRENT_TIME); | |
| 297 } | |
| 298 }); | |
| 299 } | |
| 300 } | |
| 301 } | |
| 302 | |
| 303 /** | |
| 304 * Callback function use when the selected build master has changed. Loads | |
| 305 * the buildslaves for the given master. | |
| 306 */ | |
| 307 function selectMaster() { | |
| 308 var masterSelect = document.getElementById("master_menu"); | |
| 309 selectedMaster = new skiaTools.Master(masterSelect.value); | |
| 310 setMessage("Loading buildslaves for " + selectedMaster.getName() + " master.
.."); | |
| 311 selectedMaster.loadSlaves(function(slaveData) { | |
| 312 var allSlaves = []; | |
| 313 data = {}; | |
| 314 for (var slaveIdx = 0; slaveIdx < slaveData.length; slaveIdx++) { | |
| 315 var slave = slaveData[slaveIdx]; | |
| 316 data[slave.getName()] = slave; | |
| 317 allSlaves.push(slave.getName()); | |
| 318 gotData[slave.getName()] = false; | |
| 319 } | |
| 320 skiaTools.populateMenu("slave_menu", allSlaves); | |
| 321 setMessage("Loaded buildslaves."); | |
| 322 setMessage("Select one or more buildslaves."); | |
| 323 }); | |
| 324 } | |
| 325 | |
| 326 /** | |
| 327 * Callback function to use on page load. This causes the high-level builder | |
| 328 * data to be loaded and the builder menu populated. | |
| 329 */ | |
| 330 function init() { | |
| 331 setMessage("Loading masters..."); | |
| 332 skiaTools.loadMasterList(function(masters) { | |
| 333 skiaTools.populateMenu("master_menu", masters); | |
| 334 document.getElementById("master_menu").selectedIndex = -1; | |
| 335 setMessage("Loaded masters."); | |
| 336 setMessage("Please select a build master."); | |
| 337 }); | |
| 338 } | |
| 339 </script> | |
| 340 </head> | |
| 341 <body> | |
| 342 <div id="heading" style="font-size:2.5em; text-align:center; height:7%;"> | |
| 343 Skia Buildslave Idle Time Analysis | |
| 344 </div> | |
| 345 <div id="main_content_area" style="width:100%; height:90%; padding:0px; marg
in:0px;"> | |
| 346 <div id="slave_menu_div" style="float:left; width:18%; height:95%; padding
:0px; margin:0px;"> | |
| 347 <p> | |
| 348 Master:<br/> | |
| 349 <select id="master_menu" onChange="selectMaster();"> | |
| 350 <option value="" noselect></option> | |
| 351 </select> | |
| 352 </p> | |
| 353 <p> | |
| 354 Buildslaves:<br/> | |
| 355 <select id="slave_menu" multiple="multiple" | |
| 356 style="width:100%; height:95%; margin:0px; padding:0px;"> | |
| 357 </select> | |
| 358 <br/> | |
| 359 <input type="button" onClick="selectSlaves()" | |
| 360 value="Update Selection"/> | |
| 361 </p> | |
| 362 </div> | |
| 363 <div id="charts_div" style="float:left; width:51%; padding:0px; margin:0px
"> | |
| 364 <div id="logging_div" style="width:100%; padding:0px; margin:0px"></div> | |
| 365 <div id="line_chart_div" style="width:100%; height: 40%; padding:0px; ma
rgin:0px"></div> | |
| 366 <div id="line_chart_range_filter_div" style="width:100%; height: 10%; pa
dding:0px; margin:0px"></div> | |
| 367 <div id="pie_chart_div" style="width:100%; height: 50%; padding:0px; mar
gin:0px"></div> | |
| 368 </div> | |
| 369 <div id="right_content_div" style="float:right; width:31%; height:100%"></
div> | |
| 370 </div> | |
| 371 </body> | |
| 372 </html> | |
| OLD | NEW |