blob: 8946b1e5de3f22d5a0da22decc445ac0c7622b56 [file] [log] [blame]
<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>