blob: 2a456119aae540406dc7594883bbe94a63234e4f [file] [log] [blame]
<html>
<head>
<title>Skia Buildslave Idle Time 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">
// Configuration options for the charts
var LINE_CHART_OPTIONS = {
"title": "Buildslave Busy Times",
"width": "100%",
"height": "100%",
"chartArea": {left: "9%", top: "9%", width: "86%",
height: "70%"},
"vAxis": {"gridlines": {"count": 0}},
"legend": {"textStyle": {"fontSize": 12},
"position": "bottom"},
"lineWidth": 20,
};
var LINE_CHART_RANGE_FILTER_OPTIONS = {
"filterColumnIndex": 0,
"ui": {
"chartType": "LineChart",
"minRangeSize": 1,
"chartOptions": {
"chartArea": {"width": "86%",
"height": "75%",
"left": "9%",
"right": "6%"},
"width": "100%",
"height": "100%",
"colors": ["grey"],
"hAxis": {"baselineColor": "none"},
"vAxis": {"baselineColor": "none"},
},
},
};
var PIE_CHART_OPTIONS = {"title": "Busy/Idle Time"};
// Data for all builds for all build slaves. Filled in on-demand.
var data = null;
// Dictionary with build slave names as keys and booleans as values,
// indicating whether or not build data has been obtained for each slave.
var gotData = {};
// Time window for data analysis in seconds.
// TODO(borenet): Make this configurable in the UI.
var RANGE_SECS = 86400;
// Current time in UNIX seconds from epoch as of page load.
var CURRENT_TIME = Math.ceil(new Date().getTime() / 1000);
// Beginning of the data analysis time window.
var MIN_TIME = CURRENT_TIME - RANGE_SECS;
// 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("pie_chart_div").innerHTML = "";
}
/**
* Draw a timeline-style line chart over the given range for the given set of
* build slaves.
* @param {Array.<string>} displaySlaves List of slave names indicating which
* slaves should be included in the chart.
* @param {number} rangeMin Beginning of the data analysis time window, in
UNIX seconds from epoch.
* @param {number} rangeMax End of the data analysis time window, in UNIX
seconds from epoch.
*/
function drawLineChart(displaySlaves, rangeMin, rangeMax) {
var table = new google.visualization.DataTable();
table.addColumn("datetime", "Time");
var dateMin = new Date(rangeMin * 1000);
var dateMax = new Date(rangeMax * 1000);
var times = [rangeMin];
var currentBuild = {};
for (var i = 0; i < displaySlaves.length; i++) {
var slave = displaySlaves[i];
table.addColumn("number", displaySlaves[i]);
table.addColumn({"type": "string",
"role": "tooltip",
"p": {"html": true}});
var slaveData = data[slave];
currentBuild[slave] = null;
for (var j = 0; j < slaveData["allBuilds"].length; j++) {
build = slaveData["allBuilds"][j];
if (build.getStartTime() > rangeMin &&
build.getStartTime() < rangeMax) {
times.push(Math.round(build.getStartTime() - 1));
times.push(Math.round(build.getStartTime() + 1));
if (currentBuild[slave] == null) {
currentBuild[slave] = j;
}
}
if (build.getEndTime() > rangeMin && build.getEndTime() < rangeMax) {
times.push(Math.round(build.getEndTime() - 1));
times.push(Math.round(build.getEndTime() + 1));
if (currentBuild[slave] == null) {
currentBuild[slave] = j;
}
}
}
}
times.push(rangeMax);
times.sort();
var rows = [];
for (var i = 0; i < times.length; i++) {
time = times[i];
var row = [new Date(time * 1000)];
for (j = 0; j < displaySlaves.length; j++) {
var slave = displaySlaves[j];
var val = null;
var tooltip = null;
if (null != currentBuild[slave]) {
var buildIdx = currentBuild[slave];
var buildData = data[slave]["allBuilds"][buildIdx]
var min = buildData.getStartTime();
var max = buildData.getEndTime();
if (time >= min) {
if (time <= max) {
val = j + 1;
tooltip = slave + ": " + buildData.getBuilder() + " Build #" +
buildData.getNumber();
} else {
if (data[slave]["allBuilds"].length > currentBuild[slave] + 1) {
currentBuild[slave]++;
} else {
currentBuild[slave] = null;
}
}
}
}
row.push(val);
row.push(tooltip);
}
rows.push(row);
}
table.addRows(rows);
var lineChart = new google.visualization.LineChart(document.getElementById(
"line_chart_div"));
var line_chart_options = LINE_CHART_OPTIONS;
line_chart_options["vAxis"]["viewWindow"] = {"min": 0,
"max": displaySlaves.length + 1};
line_chart_options["hAxis"] = {"viewWindow": {"min": dateMin,
"max": dateMax}};
lineChart.draw(table, line_chart_options);
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);
if (displaySlaves.length == 1) {
drawPieChart(data[displaySlaves[0]]["allBuilds"],
range.start.getTime() / 1000,
range.end.getTime() / 1000);
}
});
var line_chart_range_filter_options = LINE_CHART_RANGE_FILTER_OPTIONS;
line_chart_range_filter_options["ui"]["chartOptions"]["hAxis"]
["viewWindow"] = {"min": dateMin, "max": dateMax};
lineChartRangeFilter.draw(table, line_chart_range_filter_options,
{"range": {"start": dateMin, "end": dateMax}});
if (displaySlaves.length == 1) {
drawPieChart(data[displaySlaves[0]]["allBuilds"], rangeMin,
rangeMax);
}
setRightContent("");
}
/**
* Draw a pie chart to display the proportions of busy and idle time over a
* particular time period for a given slave.
* @param {Array.<object>} builds List of builds for a slave, each
* represented as a list containing:
* <ul>
* <li>builderName {string} Name of the builder.
* <li>buildNum {number} Build number.
* <li>startTime {number} Time of the start of the build, in UNIX seconds
* from epoch.
* <li>endTime {number} Time of the end of the build, in UNIX seconds from
* epoch.
* </ul>
* @param {number} rangeMin Beginning of the analysis window in UNIX seconds
* from epoch.
* @param {number} rangeMax End of the analysis window in UNIX seconds from
* epoch.
*/
function drawPieChart(builds, rangeMin, rangeMax) {
var busyTime = 0;
for (var i = 0; i < builds.length; i++) {
var build = builds[i];
var buildStart = build.getStartTime();
var buildEnd = build.getEndTime();
if (buildEnd < rangeMin || buildStart > rangeMax) {
continue;
}
if (buildStart < rangeMin) {
buildStart = rangeMin;
}
if (buildEnd > rangeMax) {
buildEnd = rangeMax;
}
busyTime += buildEnd - buildStart;
}
var idleTime = rangeMax - rangeMin - busyTime;
var table = new google.visualization.arrayToDataTable([
["Busy/Idle", "Time"],
["Busy", busyTime],
["Idle", idleTime],
]);
// Set chart options
var pie_chart_options = PIE_CHART_OPTIONS;
// Instantiate and draw our chart, passing in some options.
pieChart = new google.visualization.PieChart(
document.getElementById("pie_chart_div"));
pieChart.draw(table, pie_chart_options);
}
/**
* Callback function to use when the selected items of the listbox have
* changed. This causes data to be loaded for the selected slaves and charts
* to be created for that data.
*/
function selectSlaves() {
var displaySlaves = [];
var msg = "<p style=\"font-size:0.8em;\">Loading builds for slaves:<ul>";
var menu = document.getElementById("slave_menu");
for (var i = 0; i < menu.options.length; i++) {
if (menu.options[i].selected) {
var slave = menu.options[i].text;
if (displaySlaves.indexOf(slave) == -1) {
displaySlaves.push(slave);
msg += "<li style=\"font-size:0.8em;\">" + slave;
}
}
}
msg += "</ul></p>";
clearCharts();
setMessage(msg);
setTimeout(function(){
for (var i = 0; i < displaySlaves.length; i++) {
var slave = displaySlaves[i];
if (!gotData[slave] || gotData[slave] == undefined) {
console.log("Loading builds for " + slave + "...");
buildList = skiaTools.loadBuildsForSlave(data[slave], MIN_TIME,
CURRENT_TIME);
data[slave]["allBuilds"] = buildList;
gotData[slave] = true;
console.log("Loaded builds for " + slave + ".");
}
}
drawLineChart(displaySlaves, MIN_TIME, CURRENT_TIME);
setMessage("");
}, 0);
}
/**
* Callback function to use on page load. This causes the high-level build
* slave data to be loaded and the slave menu populated.
*/
function init() {
setMessage("Loading buildslaves...");
setTimeout(function() {
var slaveData = skiaTools.loadSlaves();
var allSlaves = [];
data = {}
for (var slaveIdx = 0; slaveIdx < slaveData.length; slaveIdx++) {
var slave = slaveData[slaveIdx];
data[slave.getName()] = slave;
allSlaves.push(slave.getName());
gotData[slave.getName()] = false;
}
skiaTools.populateMenu("slave_menu", allSlaves);
setMessage("Loaded buildslaves.");
setMessage("Select one or more buildslaves.");
}, 0);
}
</script>
</head>
<body>
<div id="heading" style="font-size:2.5em; text-align:center; height:7%;">
Skia Buildslave Idle Time Analysis
</div>
<div id="main_content_area" style="width:100%; height:90%; padding:0px; margin:0px;">
<div id="slave_menu_div" style="float:left; width:18%; height:100%; padding:0px; margin:0px;">
Buildslaves:<br/>
<select id="slave_menu" multiple="multiple"
style="width:100%; height:95%; margin:0px; padding:0px;">
</select>
<br/>
<input type="button" onClick="selectSlaves()"
value="Update Selection"/>
</div>
<div id="charts_div" style="float:left; width:51%; 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="pie_chart_div" style="width:100%; height: 50%; padding:0px; margin:0px"></div>
</div>
<div id="right_content_div" style="float:right; width:31%; height:100%"></div>
</div>
</body>
</html>