| <html> |
| <head> |
| <title>Skia Buildbot Self-Analysis</title> |
| <link rel="icon" href="favicon.ico"> |
| <script type="text/javascript" src="https://www.google.com/jsapi"></script> |
| <script type="text/javascript" src="skia_tools.js"></script> |
| <script language="JavaScript"> |
| "use strict"; |
| |
| // Build result codes. |
| var SUCCESS = 0; |
| |
| // Number of revisions which should be loaded. |
| // TODO(borenet): Make this configurable in the UI. |
| var REVS_TO_LOAD = 100; |
| |
| // Configuration options for the charts |
| var LINE_CHART_OPTIONS = { |
| "title": "Build Times", |
| "width": "100%", |
| "height": "100%", |
| "chartArea": {left: "9%", top: "9%", width: "86%", |
| height: "70%"}, |
| "interpolateNulls": true, |
| "hAxis": {"title": "Revision"}, |
| "vAxis": {"title": "Build Time (s)"}, |
| "legend": {"textStyle": {"fontSize": 12}, |
| "position": "bottom"}, |
| }; |
| |
| var LINE_CHART_RANGE_FILTER_OPTIONS = { |
| "filterColumnIndex": 0, |
| "ui": { |
| "chartType": "AreaChart", |
| "minRangeSize": 1, |
| "chartOptions": { |
| "chartArea": {"width": "86%", |
| "height": "75%", |
| "left": "9%", |
| "right": "6%"}, |
| "width": "100%", |
| "height": "100%", |
| "colors": ["grey"], |
| "hAxis": {"baselineColor": "none"}, |
| "vAxis": {"baselineColor": "none"}, |
| "interpolateNulls": true, |
| }, |
| }, |
| }; |
| |
| var BAR_CHART_OPTIONS = { |
| "width": "100%", |
| "height": "100%", |
| "chartArea": {left: "17%", top: "9%", width: "65%", height: "70%"}, |
| "hAxis": {"title": "Time (s)"}, |
| "vAxis": {"title": "Build Step"}, |
| "tooltip": {"isHtml": true}, |
| "legend": {"position": "none"}, |
| }; |
| |
| // High-level information about each builder. |
| var builderData = null; |
| |
| // Data for all builds for all builders, indexed by revision number. Filled in |
| // on-demand. |
| var data = {}; |
| |
| // Dictionary with builder names as keys and booleans as values, indicating |
| // whether or not build data has been obtained for each builder. |
| var gotData = {}; |
| |
| // Load the Visualization API |
| google.load("visualization", "1.0", {"packages":["corechart", "controls"]}); |
| |
| // Set a callback to run when the Google Visualization API is loaded. |
| google.setOnLoadCallback(init); |
| |
| /** |
| * Display text or HTML in the logging div. |
| * @param {string} msg The HTML or text to display. |
| */ |
| function setMessage(msg) { |
| console.log(msg); |
| document.getElementById("logging_div").innerHTML = msg; |
| } |
| |
| /** |
| * Set the HTML content in the pane on the right side of the page. |
| * @param {string} content HTML to display. |
| */ |
| function setRightContent(content) { |
| document.getElementById("right_content_div").innerHTML = content; |
| } |
| |
| /** |
| * Clear the divs containing charts. Useful when charts need to be updated. |
| */ |
| function clearCharts() { |
| document.getElementById("line_chart_div").innerHTML = ""; |
| document.getElementById("line_chart_range_filter_div").innerHTML = ""; |
| document.getElementById("bar_chart_div").innerHTML = ""; |
| } |
| |
| /** |
| * Convert a duration in seconds to an easily-readable string. |
| * @param {number} seconds The duration to convert. |
| * @return {string} The duration in HH:MM:SS format. |
| */ |
| function formatTime(secondsParam) { |
| var seconds = Math.round(secondsParam); |
| var minutes = Math.floor(seconds / 60); |
| seconds -= seconds * 60; |
| var hours = Math.floor(minutes / 60); |
| minutes -= hours * 60; |
| var totalTime = ""; |
| if (seconds > 9) { |
| totalTime = seconds; |
| } else { |
| totalTime = "0" + seconds; |
| } |
| if (minutes > 9) { |
| totalTime = minutes + ":" + totalTime; |
| } else { |
| totalTime = "0" + minutes + ":" + totalTime; |
| } |
| if (hours > 9) { |
| totalTime = hours + ":" + totalTime; |
| } else { |
| totalTime = "0" + hours + ":" + totalTime; |
| } |
| return totalTime; |
| } |
| |
| /** |
| * Draw a line chart illustrating build times for a number of revisions for |
| * a set of builders. |
| * @param {Array.<string>} displayBuilders List of builders which should be |
| * included in the chart. |
| * @param {Array.<number>} allRevs List of revision numbers which should be |
| * included in the chart. |
| */ |
| function drawLineChart(displayBuilders, allRevs) { |
| var table = new google.visualization.DataTable(); |
| table.addColumn("number", "Revision"); |
| for (var i = 0; i < displayBuilders.length; i++) { |
| table.addColumn("number", displayBuilders[i]); |
| table.addColumn({"type": "string", "role": "annotation"}); |
| } |
| var lineChartColsPerBuilder = 2; |
| var longestBuildTime = -1; |
| var rows = []; |
| for (var revIdx = 0; revIdx < allRevs.length; revIdx++) { |
| var rev = allRevs[revIdx]; |
| var row = [rev]; |
| for (var builderIdx = 0; builderIdx < displayBuilders.length; |
| builderIdx++) { |
| var builder = displayBuilders[builderIdx]; |
| if (data[rev][builder] != undefined) { |
| var time = data[rev][builder].getElapsedTime(); |
| if (time > longestBuildTime) { |
| longestBuildTime = time; |
| } |
| row.push(time); |
| if (data[rev][builder].getResult() != SUCCESS) { |
| row.push("failed"); |
| } else { |
| row.push(null); |
| } |
| } else { |
| row.push(null); |
| row.push(null); |
| } |
| } |
| rows.push(row); |
| } |
| table.addRows(rows); |
| |
| var lineChart = new google.visualization.LineChart(document.getElementById( |
| "line_chart_div")); |
| google.visualization.events.addListener(lineChart, 'select', |
| function() { |
| var selected = lineChart.getSelection()[0]; |
| if (selected && |
| selected.column != undefined && |
| selected.row != undefined) { |
| var builder = null; |
| var builderIdx = Math.floor( |
| (selected.column - 1) / lineChartColsPerBuilder); |
| builder = displayBuilders[builderIdx]; |
| var rev = allRevs[selected.row]; |
| var buildNum = data[rev][builder].getNumber(); |
| console.log("Selected: " + builder + " @ " + rev); |
| var url = skiaTools.host() + ":" + skiaTools.port() + |
| "/buildstatus?builder=" + builder + "&number=" + buildNum; |
| var rightContent = "<iframe style=\"width:100%; height:100%; " |
| + "padding:0px; margin=0px; overflow:scroll;\" src=\"" |
| + url + "\"></iframe>"; |
| setRightContent(rightContent); |
| drawBarChart(builder, rev, longestBuildTime); |
| } |
| }); |
| lineChart.draw(table, LINE_CHART_OPTIONS); |
| |
| var firstRev = allRevs[0]; |
| var lastRev = allRevs[allRevs.length - 1]; |
| var lineChartRangeFilter = new google.visualization.ChartRangeFilter( |
| document.getElementById("line_chart_range_filter_div")); |
| google.visualization.events.addListener(lineChartRangeFilter, |
| "statechange", |
| function() { |
| var range = lineChartRangeFilter.getState().range; |
| LINE_CHART_OPTIONS.hAxis.viewWindow = {min: range.start, |
| max: range.end}; |
| lineChart.draw(table, LINE_CHART_OPTIONS); |
| }); |
| lineChartRangeFilter.draw(table, LINE_CHART_RANGE_FILTER_OPTIONS, |
| {"range": {"start": firstRev, "end": lastRev}}); |
| setRightContent(""); |
| } |
| |
| /** |
| * Draw a bar chart illustrating the running times of each step in a given |
| * build for a given builder. |
| * @param {string} builder The builder whose build should be displayed. |
| * @param {number} revision The revision of the build to display. |
| * @param {number} viewWindowMax The width of the viewing window. This is |
| * provided instead of being determined from the data so that each bar |
| * graph can be displayed with the same scale. |
| */ |
| function drawBarChart(builder, revision, viewWindowMax) { |
| var table = new google.visualization.DataTable(); |
| table.addColumn("string", "Step"); |
| table.addColumn("number", "Time"); |
| table.addColumn({"type": "string", "role": "tooltip", "p": {"html": true}}); |
| var buildData = data[revision][builder]; |
| for (var stepIdx = 0; stepIdx < buildData.getSteps().length; stepIdx++) { |
| var step = buildData.getSteps()[stepIdx]; |
| var stepName = step.getName(); |
| var time = step.getElapsedTime(); |
| var stdio = step.getStdio(); |
| var result = step.getResult(); |
| var percent = time / buildData.getElapsedTime() * 100.0; |
| table.addRow(); |
| table.setValue(stepIdx, 0, stepName); |
| table.setValue(stepIdx, 1, time); |
| var tooltip = "<div style=\"font-family:Arial;font-size:14px;" |
| + "padding:10px;\">" + stepName; |
| if (result != 0) { |
| tooltip += " <span style=\"background-color:red;\">Failed</span>"; |
| } |
| tooltip += "<br/>" + formatTime(time) + " (" + percent.toFixed(2) |
| + "%)<br/>Log: <a href=\"" + stdio |
| + "\" target=\"_blank\">stdout</a></div>"; |
| table.setFormattedValue(stepIdx, 2, tooltip); |
| } |
| |
| // Instantiate and draw our chart, passing in some options. |
| var barChart = new google.visualization.BarChart( |
| document.getElementById("bar_chart_div")); |
| var options = BAR_CHART_OPTIONS; |
| options["title"] = builder + ": Build #" + buildData.getNumber() + ", r" + |
| revision; |
| options["hAxis"]["viewWindow"] = {"max": viewWindowMax}; |
| barChart.draw(table, BAR_CHART_OPTIONS); |
| } |
| |
| /** |
| * Callback function to use when the selected items of the listbox have |
| * changed. This causes data to be loaded for the selected builders and charts |
| * to be created for that data. |
| */ |
| function selectBuilders() { |
| var displayBuilders = []; |
| var msg = "<p style=\"font-size:0.8em;\">Loading builds for builders:<ul>"; |
| var menu = document.getElementById("builder_menu"); |
| for (var i = 0; i < menu.options.length; i++) { |
| if (menu.options[i].selected) { |
| var builder = menu.options[i].text; |
| if (displayBuilders.indexOf(builder) == -1) { |
| displayBuilders.push(builder); |
| msg += "<li style=\"font-size:0.8em;\">" + builder; |
| } |
| } |
| } |
| msg += "</ul></p>"; |
| clearCharts(); |
| setMessage(msg); |
| setTimeout(function(){ |
| var allRevs = []; |
| for (var i = 0; i < displayBuilders.length; i++) { |
| var builder = displayBuilders[i]; |
| if (!gotData[builder] || gotData[builder] == undefined) { |
| console.log("Loading builds for " + builder + "..."); |
| var cachedBuilds = builderData[builder].getCachedBuilds(); |
| var lastBuild = cachedBuilds[cachedBuilds.length - 1]; |
| var dataForBuilder = skiaTools.loadBuildsForBuilder(builder, |
| lastBuild, |
| REVS_TO_LOAD); |
| for (var rev in dataForBuilder) { |
| var revisionNumber = parseInt(rev); |
| if (allRevs.indexOf(revisionNumber) == -1) { |
| allRevs.push(revisionNumber); |
| } |
| if (data[revisionNumber] == undefined) { |
| data[revisionNumber] = {}; |
| } |
| data[revisionNumber][builder] = dataForBuilder[revisionNumber]; |
| } |
| gotData[builder] = true; |
| console.log("Loaded builds for " + builder + "."); |
| } |
| } |
| allRevs.sort(); |
| drawLineChart(displayBuilders, allRevs); |
| setMessage(""); |
| }, 0); |
| } |
| |
| /** |
| * Callback function to use on page load. This causes the high-level builder |
| * data to be loaded and the builder menu populated. |
| */ |
| function init() { |
| setMessage("Loading builders..."); |
| setTimeout(function() { |
| var builders = skiaTools.loadBuilders(); |
| var allBuilders = []; |
| builderData = {}; |
| for (var builderNum = 0; builderNum < builders.length; builderNum++) { |
| var builder = builders[builderNum]; |
| builderData[builder.getName()] = builder; |
| allBuilders.push(builder.getName()); |
| gotData[builder.getName()] = false; |
| } |
| skiaTools.populateMenu("builder_menu", allBuilders); |
| setMessage("Loaded builders."); |
| setMessage("Select one or more builders."); |
| }, 0); |
| } |
| </script> |
| </head> |
| <body> |
| <div id="heading" style="font-size:2.5em; text-align:center; height:7%;"> |
| Skia Buildbots Self-Analysis |
| </div> |
| <div id="main_content_area" style="width:100%; height:90%; padding:0px; margin:0px;"> |
| <div id="builder_menu_div" style="float:left; width:18%; height:100%; padding:0px; margin:0px;"> |
| Builders:<br/> |
| <select id="builder_menu" multiple="multiple" |
| style="width:100%; height:95%; margin:0px; padding:0px;"> |
| </select> |
| <br/> |
| <input type="button" onClick="selectBuilders()" |
| value="Update Selection"/> |
| </div> |
| <div id="charts_div" style="float:left; width:67%; padding:0px; margin:0px"> |
| <div id="logging_div" style="width:100%; padding:0px; margin:0px"></div> |
| <div id="line_chart_div" style="width:100%; height: 40%; padding:0px; margin:0px"></div> |
| <div id="line_chart_range_filter_div" style="width:100%; height: 10%; padding:0px; margin:0px"></div> |
| <div id="bar_chart_div" style="width:100%; height: 50%; padding:0px; margin:0px"></div> |
| </div> |
| <div id="right_content_div" style="float:right; width:15%; height:100%"></div> |
| </div> |
| </body> |
| </html> |