blob: 9dff3a4bd899703d53fd62419e6befdfa872127a [file] [log] [blame]
/**
* @license Copyright 2012 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
/**
* @fileoverview Tools used by the Skia buildbot dashboards, including
* facilities for retrieving data from the buildbot master.
*/
"use strict";
var skiaTools = {
/**
* getVariable
*
* @return The value for the requested variable, as defined in the global
* variables file.
*/
getVariable: function(varName) {
var url = "http://108.170.217.252:8000/buildbot/site_config/global_variables.json";
try {
var request = new XMLHttpRequest();
} catch (error) {
alert(error);
}
request.open("GET", url, false);
request.send();
return eval("(" + request.responseText + ")")[varName].value;
},
/**
* host
*
* @return {string} IP address of the Skia buildbot master.
*/
host: function() {
return "http://" + this.getVariable("public_master_host");
},
/**
* port
*
* @return {string} Communication port of the Skia buildbot master.
*/
port: function() {
return this.getVariable("public_external_port");
},
/**
* Information about a single build.
*/
Build: function(builder, number, revision, result, startTime, endTime, steps) {
this.builder = builder;
this.number = number;
this.revision = revision;
this.result = result;
this.startTime = startTime;
this.endTime = endTime;
this.elapsedTime = endTime - startTime;
this.steps = steps;
/**
* getBuilder
*
* @return {string} The name of the builder who owns this build.
*/
this.getBuilder = function() { return this.builder; }
/**
* getNumber
*
* @return {number} The build number.
*/
this.getNumber = function() { return this.number; }
/**
* getResult
*
* @return {number} The result of the build. Will be 0 iff the build
* succeeded.
*/
this.getResult = function() { return this.result; }
/**
* getRevision
*
* @return {number} The revision number of this build.
*/
this.getRevision = function() { return this.revision; }
/**
* getStartTime
*
* @return {number} Start time of the build in UNIX seconds from epoch.
*/
this.getStartTime = function() { return this.startTime; }
/**
* getEndTime
*
* @return {number} End time of the build in UNIX seconds from epoch.
*/
this.getEndTime = function() { return this.endTime; }
/**
* getElapsedTime
*
* @return {number} Elapsed time of the build in seconds.
*/
this.getElapsedTime = function() { return this.elapsedTime; }
/**
* getStartTime
*
* @return {Array.<BuildStep>} Information about the steps of this build.
*/
this.getSteps = function() { return this.steps; }
},
/**
* Information about a single build step.
*/
BuildStep: function(name, elapsedTime, result, stdio) {
this.name = name;
this.elapsedTime = elapsedTime;
this.result = result;
this.stdio = stdio;
/**
* getName
*
* @return {string} The name of the build step.
*/
this.getName = function() { return this.name; }
/**
* getElapsedTime
*
* @return {number} Elapsed time of the build step in seconds.
*/
this.getElapsedTime = function() { return this.elapsedTime; }
/**
* getResult
*
* @return {number} The result of the build. Will be 0 iff the build step
* succeeded.
*/
this.getResult = function() { return this.result; }
/**
* getStdio
*
* @return {string} URL to the log output for this build step.
*/
this.getStdio = function() { return this.stdio; }
},
/**
* Information about a builder.
*/
Builder: function(name, basedir, cachedBuilds, category, currentBuilds, slaves,
state) {
this.name = name;
this.basedir = basedir;
this.cachedBuilds = cachedBuilds;
this.category = category;
this.currentBuilds = currentBuilds;
this.slaves = slaves;
this.state = state;
/**
* getName
*
* @return {string} The name of the builder.
*/
this.getName = function() { return this.name; }
/**
* getBaseDir
*
* @return {string} Directory on the build slave machine in which build
* information is stored. This is typically the same as the builder name.
*/
this.getBaseDir = function() { return this.basedir; }
/**
* getCachedBuilds
*
* @return {Array.<number>} List of recent builds for this builder.
*/
this.getCachedBuilds = function() { return this.cachedBuilds; }
/**
* getCategory
*
* @return {string} Category of this builder. This is the heading under
* which the builder is placed on the buildbot web page.
*/
this.getCategory = function() { return this.category; }
/**
* getCurrentBuilds
*
* @return {Array.<number>} List of currently-running builds for this
* builder.
*/
this.getCurrentBuilds = function() { return this.currentBuilds; }
/**
* getSlaves
*
* @return {Array.<string>} List of known build slaves which are capable of
* running builds for this builder.
*/
this.getSlaves = function() { return this.slaves; }
/**
* getState
*
* @return {string} Current status of the builder. Either "building" or
* "idle."
*/
this.getState = function() { return this.state; }
},
/**
* Information about a build slave.
*/
BuildSlave: function(admin, builders, connected, currentBuilds, host, name,
version) {
this.admin = admin;
this.builders = builders;
this.connected = connected;
this.currentBuilds = currentBuilds;
this.host = host;
this.name = name;
this.version = version;
/**
* getAdmin
*
* @return {string} Usernames of buildbot maintainers.
*/
this.getAdmin = function() { return this.admin; }
/**
* getBuilders
*
* @return {object} Dictionary whose keys are builder names and values are
* lists of build numbers indicating which builds for which builders this
* slave has performed.
*/
this.getBuilders = function() { return this.builders; }
/**
* isConnected
*
* @return {boolean} Whether or not the build slave is currently connected
* to the build master.
*/
this.isConnected = function() { return this.connected; }
/**
* getCurrentBuilds
*
* @return {Array.<object>} List of dictionaries containing information about
* currently-running builds on this slave.
*/
this.getCurrentBuilds = function() { return this.currentBuilds; }
/**
* getHost
*
* @return {string} Hostname of this build slave.
*/
this.getHost = function() { return this.host; }
/**
* getName
*
* @return {string} The name of this build slave.
*/
this.getName = function() { return this.name; }
/**
* getVersion
*
* @return {string} Version of BuildBot which this build slave is running.
*/
this.getVersion = function() { return this.version; }
},
/**
* Sends an {@code XMLHttpRequest} to the buildbot master, parses the JSON in
* the response, and returns a dictionary. This is synchronous and should be
* assumed to be very slow.
*
* @param {string} subdir Subdirectory of the buildbot master's JSON interface
* to query.
* @private
*/
loadDataFromBuildMaster: function(subdir) {
try {
var request = new XMLHttpRequest();
} catch (error) {
alert(error);
}
request.open("GET", this.host() + ":" + this.port() + "/json/" + subdir,
false);
request.send(null);
// We *should* use a JSON parser, but since we trust the buildbot master
// server, we allow this unsafe call
return eval("(" + request.responseText + ")");
},
/**
* Convenience function for populating a ComboBox or ListBox with a list of
* items. Existing items will be cleared.
*
* @param {string} menuId ID of the menu to populate.
* @param {Array.<string>} items A list of strings to insert into the menu.
*/
populateMenu: function(menuId, items) {
var menu = document.getElementById(menuId);
menu.options.length = 0;
for (var itemIdx = 0; itemIdx < items.length; itemIdx++) {
var item = items[itemIdx];
var newOption = document.createElement("option");
newOption.text = item;
newOption.value = item;
menu.options.add(newOption);
}
},
/**
* Obtain information about a single build from the buildbot master. This is
* synchronous and should be assumed to be very slow.
*
* @param {string} builder The name of the builder whose build should be
* retrieved.
* @param {number} build The number of the build which should be retrieved.
* @param {boolean} loadUnfinished Whether or not to load data for unfinished
* builds.
* @param {boolean} loadUnknownRevs Whether or not to load data for builds which
* do not have an associated revision number. This occurs when the source
* checkout step fails.
*
* @return {object|null} A Build instance containing information about the
* requested build.
*/
loadDataForBuild: function(builder, build, loadUnfinished, loadUnknownRevs) {
var buildData = this.loadDataFromBuildMaster("builders/" + builder +
"/builds/" + build + "/steps");
// Build step results.
var SUCCESS = 0;
var FAILURE = 2;
var SKIPPED = 3;
var steps = [];
var result = 0;
var startTime = 0;
var endTime = 0;
var revision = undefined;
var gotRevisionStr = "got_revision: ";
for (var step in buildData) {
var stepData = buildData[step];
if (stepData["isStarted"] && !stepData["isFinished"] && !loadUnfinished) {
// If the build isn't finished, ignore it
return null;
}
if (!stepData["isStarted"]) {
continue;
}
if (stepData["name"] == "Update") {
// The buildbot's JSON interface stores results as an array in which the
// first element is an integer indicating success or failure.
if (stepData["isStarted"] && stepData["isFinished"] &&
stepData["results"][0] == 0) {
// The "text" field is an array containing extra information about the
// build step. In the case of the Update step, its second element is a
// string indicating the revision obtained for the current build.
revision = parseInt(stepData["text"][1].substring(
gotRevisionStr.length));
} else if (!loadUnknownRevs) {
// If the Update step failed, we can't attach a revision, so we have
// to ignore this build.
console.log("Warning: Can't get a revision for build #" + build +
". Skipping.");
return null;
}
}
var times = stepData["times"];
var stepTime = times[1] - times[0];
if (startTime == 0) {
startTime = times[0];
}
endTime = times[1];
var stdout = null;
try {
stdout = stepData["logs"][0][1];
} catch(e) {
stdout = "None";
}
var buildStep = new this.BuildStep(stepData["name"], stepTime,
stepData["results"][0], stdout);
steps.push(buildStep);
if (buildStep.getResult() != SUCCESS && buildStep.getResult() != SKIPPED) {
result = FAILURE;
}
}
if (revision == undefined) {
console.log("Warning: could not find a revision for build #" + build);
}
return new this.Build(builder, build, revision, result, startTime, endTime,
steps);
},
/**
* Obtain information about the builds for a single builder. Works backward from
* the lastKnownBuild, loading builds until the requested number of revisions
* has been fulfilled or all of the builder's builds have been loaded. This is
* synchronous and should be assumed to be very slow.
*
* @param {string} builderName The name of the builder to load.
* @param {number} lastKnownBuild The build number of the latest build for this
* builder.
* @param {number} numRevs The number of revisions to load.
*
* @return {object} Instances of Build for the builder, indexed by revision.
*/
loadBuildsForBuilder: function(builderName, lastKnownBuild, numRevs) {
var data = {};
var latestRevision = -1;
for (var buildNum = lastKnownBuild; buildNum >= 0; buildNum--) {
var build = this.loadDataForBuild(builderName, buildNum, false, false);
if (null == build) { continue; }
var rev = build.getRevision();
if (rev == undefined) { continue; }
if (rev > latestRevision) {
latestRevision = rev;
}
data[rev] = build;
if (latestRevision - rev > numRevs) { break; }
}
return data;
},
/**
* Obtain information about recent builds for a build slave. This is synchronous
* and should be assumed to be very slow.
*
* @param {BuildSlave} slave An instance of BuildSlave.
* @param {number} rangeMin Builds before this time will not be loaded.
* @param {number} currentTime The time at which {@code slaveDict} was obtained
* from the build master. This value is passed in rather than obtaining the
* current time at the call of this function in case the state of the build
* slave has changed since {@code slaveDict} was obtained.
*
* @return {Array.<Build>} List of Build objects.
*/
loadBuildsForSlave: function(slave, rangeMin, currentTime) {
var builders = slave.getBuilders();
var buildList = [];
for (var builder in builders) {
var builds = builders[builder];
for (var buildIdx = 0; buildIdx < builds.length; buildIdx++) {
var buildNum = builds[buildIdx];
var build = this.loadDataForBuild(builder, buildNum, true, true);
if (build) {
buildList.push(build);
if (build.getEndTime() < rangeMin || build.getStartTime() < rangeMin) {
break;
}
}
}
}
var runningBuilds = slave.getCurrentBuilds();
for (var buildIdx = 0; buildIdx < runningBuilds.length; buildIdx++) {
var buildData = runningBuilds[buildIdx];
buildList.push(new this.Build(buildData["builderName"],
buildData["number"],
-1, 0, buildData["times"][0],
currentTime + 1, []));
}
buildList.sort(function(a, b) {
return a.getStartTime() - b.getStartTime();
});
return buildList;
},
/**
* Obtain high-level information about known builders. This is synchronous and
* should be assumed to be very slow.
*
* @return {Array.<Builder>} A list of Builder objects.
*/
loadBuilders: function() {
var builders = [];
var buildersData = this.loadDataFromBuildMaster("builders");
for (var builder in buildersData) {
var builderData = buildersData[builder];
builders.push(new this.Builder(builder,
builderData["basedir"],
builderData["cachedBuilds"],
builderData["category"],
builderData["currentBuilds"],
builderData["slaves"],
builderData["state"]));
}
return builders;
},
/**
* Obtain high-level information about known build slaves. This is synchronous
* and should be assumed to be very slow.
*
* @return {Array.<BuildSlave>} A list of BuildSlave objects.
*/
loadSlaves: function() {
var slaves = [];
var slavesData = this.loadDataFromBuildMaster("slaves");
for (var slave in slavesData) {
var slaveData = slavesData[slave];
var currentBuilds = slaveData["currentBuilds"];
if (currentBuilds == undefined) {
currentBuilds = [];
}
for (var buildIdx = 0; buildIdx < currentBuilds.length; buildIdx++) {
}
slaves.push(new this.BuildSlave(slaveData["admin"],
slaveData["builders"],
slaveData["connected"],
currentBuilds,
slaveData["host"],
slaveData["name"],
slaveData["version"]));
}
return slaves;
},
};