| Index: master/public_html/buildsteps.html
|
| diff --git a/master/public_html/buildsteps.html b/master/public_html/buildsteps.html
|
| deleted file mode 100644
|
| index 2e846fc15fff6304b413926ec02a93f2214802bb..0000000000000000000000000000000000000000
|
| --- a/master/public_html/buildsteps.html
|
| +++ /dev/null
|
| @@ -1,601 +0,0 @@
|
| -<html>
|
| - <head>
|
| - <title>Skia Buildstep Dashboard</title>
|
| - <link rel="icon" href="favicon.ico">
|
| - <script language="JavaScript">
|
| - "use strict";
|
| -
|
| - var DEFAULT_MASTER_FILTER = "*";
|
| - var DEFAULT_BUILDER_FILTER = "*";
|
| - var DEFAULT_BUILDSTEP_FILTER = "*";
|
| - var DEFAULT_TIME_PERIOD = 24*60*60; // 24 hours in seconds.
|
| -
|
| - // Column indices for grouping Graphite data.
|
| - var GROUP_NODE_BUILDER = 2;
|
| - var GROUP_NODE_BUILDSTEP = 3;
|
| -
|
| - // We hard-code this giant interval to be passed to Graphite's summarize
|
| - // function to ensure that the data ends up grouped into one big bucket, no
|
| - // matter how we set the time period.
|
| - var SUMMARIZE_INTERVAL = "10year";
|
| -
|
| - // Timestamp of when we started loading data. Used to compute load time.
|
| - var loadStart = null;
|
| -
|
| - // Used to store information about what data is currently being loaded.
|
| - var currentlyLoadingData = null;
|
| -
|
| - // Metrics to load from Graphite.
|
| - // Takes the form: [[metric_name, merge_function], ...]
|
| - var metrics = [["success", "sum"],
|
| - ["failure", "sum"],
|
| - ["duration", "avg"]];
|
| -
|
| - // Metrics to derive from the loaded metrics above. We display these in the
|
| - // table. Takes the form: [[metric_name, derivation_fn], ...], where
|
| - // derivation_fn is a function which takes as a parameter a dictionary
|
| - // containing values for all of the above metrics for a given buildStep and
|
| - // returns a value, which may be null.
|
| - var derivedMetrics = [
|
| - ["Duration", function(stepData) {
|
| - if (stepData["duration"] == undefined) {
|
| - return null;
|
| - }
|
| - return stepData["duration"];
|
| - }],
|
| - ["Failure Rate", function(stepData) {
|
| - if (stepData["success"] == undefined ||
|
| - stepData["failure"] == undefined) {
|
| - return null;
|
| - }
|
| - var success = parseFloat(stepData["success"]);
|
| - var failure = parseFloat(stepData["failure"]);
|
| - var totalRuns = success + failure;
|
| - var failureRate = failure / totalRuns;
|
| - if (totalRuns <= 0) {
|
| - return null;
|
| - }
|
| - return failureRate;
|
| - }],
|
| - ["Total Runs", function(stepData) {
|
| - if (stepData["success"] == undefined ||
|
| - stepData["failure"] == undefined) {
|
| - return null;
|
| - }
|
| - var success = parseFloat(stepData["success"]);
|
| - var failure = parseFloat(stepData["failure"]);
|
| - return success + failure;
|
| - }],
|
| - ];
|
| -
|
| - // Use the second data column as the default sort index, since that typically
|
| - // indicates the most important column which isn't a label.
|
| - var defaultSortIndex = 1;
|
| -
|
| - /**
|
| - * 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;
|
| - }
|
| -
|
| - /**
|
| - * Function to call when starting to load data. Prints the given URL and
|
| - * stores some data for retrieval on loadingDone().
|
| - *
|
| - * @param {string} url The URL that we're loading.
|
| - * @param {Object} data Arbitrary data to store.s
|
| - */
|
| - function loadingStart(url, data) {
|
| - document.body.style.cursor = "wait";
|
| - document.getElementById("buildstep_div").style.display = "none";
|
| - document.getElementById("builder_div").style.display = "none";
|
| - document.getElementById("load_data_button").disabled = true;
|
| - loadStart = new Date().getTime();
|
| - setMessage("Loading data from " + url);
|
| - currentlyLoadingData = data;
|
| - }
|
| -
|
| - /**
|
| - * Function to call when done loading data. Returns any data stored on
|
| - * loadingStart().
|
| - */
|
| - function loadingDone() {
|
| - document.body.style.cursor = "auto";
|
| - document.getElementById("buildstep_div").style.display = "block";
|
| - document.getElementById("builder_div").style.display = "block";
|
| - document.getElementById("load_data_button").disabled = false;
|
| - var message = "";
|
| - if (loadStart) {
|
| - var elapsedSeconds = (new Date().getTime() - loadStart) / 1000;
|
| - message = "Loaded data in " + elapsedSeconds + " seconds.";
|
| - }
|
| - setMessage(message);
|
| - var retData = currentlyLoadingData;
|
| - currentlyLoadingData = null;
|
| - return retData;
|
| - }
|
| -
|
| - /**
|
| - * Load data from the given URL using JSONP.
|
| - *
|
| - * @param {string} url The URL from which to load data.
|
| - * @param {string} callbackName The name of the function to use as a callback.
|
| - */
|
| - function loadJSONP(url, callbackName) {
|
| - var script = document.createElement("script");
|
| - var join = "&";
|
| - if (url.indexOf("?") < 0) {
|
| - join = "?";
|
| - }
|
| - script.src = url + join + "jsonp=" + callbackName;
|
| - document.head.appendChild(script);
|
| - }
|
| -
|
| - /**
|
| - * Re-organize Graphite data into a dictionary.
|
| - *
|
| - * @param {Array<Object>} data Data to organize.
|
| - */
|
| - function graphiteDataDict(data) {
|
| - var dataDict = {};
|
| - for (var i = 0; i < data.length; ++i) {
|
| - var splitName = data[i]["target"].split(".");
|
| - var name = splitName.slice(0, splitName.length - 1).join(".");
|
| - var resultType = splitName[1];
|
| - var datapoints = data[i]["datapoints"];
|
| - var result = 0;
|
| - if (datapoints.length > 0) {
|
| - result = datapoints[datapoints.length - 1][0];
|
| - }
|
| - if (result == null) {
|
| - result = 0;
|
| - }
|
| - if (!dataDict[name]) {
|
| - dataDict[name] = {};
|
| - }
|
| - dataDict[name][resultType] = result;
|
| - dataDict[name]["name"] = name;
|
| - }
|
| - return dataDict;
|
| - }
|
| -
|
| - /**
|
| - * Create lists of column names and rows given a data dictionary.
|
| - *
|
| - * @param {Object} dataDict data to organize into columns and rows.
|
| - */
|
| - function getColsAndRows(dataDict) {
|
| - // Create the set of columns
|
| - var cols = ["Name"];
|
| - for (var i = 0; i < derivedMetrics.length; ++i) {
|
| - cols.push(derivedMetrics[i][0]);
|
| - }
|
| -
|
| - // Create a row of data for each buildStep.
|
| - var rows = [];
|
| - for (var name in dataDict) {
|
| - var row = [name];
|
| - // Fill in the data row.
|
| - for (var i = 0; i < derivedMetrics.length; ++i) {
|
| - var value = derivedMetrics[i][1](dataDict[name]);
|
| - row.push(value);
|
| - }
|
| - rows.push(row);
|
| - }
|
| - return [cols, rows]
|
| - }
|
| -
|
| - /**
|
| - * Build the data table given a set of data.
|
| - *
|
| - * @param {Array<Object>} data The data to put into the table.
|
| - * @param {string} title The title of the table.
|
| - * @param {string} containerId ID of the container to hold the table.
|
| - */
|
| - function makeTableFromData(data, title, containerId) {
|
| - var buildStepData = graphiteDataDict(data);
|
| - var colsAndRows = getColsAndRows(buildStepData);
|
| - rebuildTable(title, colsAndRows[0], colsAndRows[1], containerId,
|
| - null, true);
|
| - }
|
| -
|
| - /**
|
| - * Sort the rows on the given column index.
|
| - *
|
| - * @param {Array<Array>} rows The rows to sort.
|
| - * @param {number} sortIndex The index of the column by which to sort.
|
| - * @param {number} sortOrder 1 or -1; determines whether to sort ascending or
|
| - * descending.
|
| - */
|
| - function sortRows(rows, sortIndex, sortOrder) {
|
| - // Sort the table rows by sortIndex.
|
| - if (null == sortIndex || undefined == sortIndex) {
|
| - sortIndex = defaultSortIndex;
|
| - }
|
| - rows.sort(function(a, b) {
|
| - if (null == a) { return -sortOrder; }
|
| - if (null == b) { return sortOrder; }
|
| - if (null == a[sortIndex]) { return -sortOrder; }
|
| - if (null == b[sortIndex]) { return sortOrder; }
|
| - if (a[sortIndex] > b[sortIndex]) { return sortOrder; }
|
| - if (a[sortIndex] < b[sortIndex]) { return -sortOrder; }
|
| - return 0;
|
| - });
|
| - }
|
| -
|
| - /**
|
| - * Rebuild the data table.
|
| - *
|
| - * @param {string} title The title of the table.
|
| - * @param {Array<string>} cols Array of column names.
|
| - * @param {Array<Array>} rows Array of row data.
|
| - * @param {string} containerId ID of the container where the table goes.
|
| - * @param {number} sortIndex The index of the column by which to sort.
|
| - * @param {boolean} reload Whether or not we reloaded data. This affects the
|
| - * sorting of rows.
|
| - */
|
| - function rebuildTable(title, cols, rows, containerId, sortIndex, reload) {
|
| - var tableId = containerId + "_table";
|
| - var oldTable = document.getElementById(tableId);
|
| - var lastSortIndex = null;
|
| - var sortOrder = -1;
|
| - if (oldTable) {
|
| - lastSortIndex = oldTable.sortIndex;
|
| - if (reload) {
|
| - sortIndex = lastSortIndex;
|
| - } else if (sortIndex == lastSortIndex) {
|
| - sortOrder = -oldTable.sortOrder;
|
| - }
|
| - }
|
| - sortRows(rows, sortIndex, sortOrder);
|
| -
|
| - var table = document.createElement("table");
|
| - table.sortIndex = sortIndex;
|
| - table.sortOrder = sortOrder;
|
| - var thead = document.createElement("thead");
|
| -
|
| - for (var i = 0; i < cols.length; ++i) {
|
| - var th = document.createElement("th");
|
| - th.style.padding = "5px";
|
| - th.style.textAlign = "right";
|
| - var sortLink = document.createElement("a");
|
| - sortLink.href = "#";
|
| - sortLink.innerHTML = cols[i];
|
| - sortLink.tableTitle = title;
|
| - sortLink.containerId = containerId;
|
| - sortLink.sortIndex = i;
|
| - sortLink.rowsObj = rows;
|
| - sortLink.colsObj = cols;
|
| - sortLink.addEventListener("click", function(event) {
|
| - var cols = event.target.colsObj;
|
| - var rows = event.target.rowsObj;
|
| - var containerId = event.target.containerId;
|
| - var sortIndex = event.target.sortIndex;
|
| - var tableTitle = event.target.tableTitle;
|
| - rebuildTable(tableTitle, cols, rows, containerId, sortIndex, false);
|
| - });
|
| - th.appendChild(sortLink);
|
| - thead.appendChild(th);
|
| - }
|
| -
|
| - table.appendChild(thead);
|
| -
|
| - for (var i = 0; i < rows.length; ++i) {
|
| - var tr = document.createElement("tr");
|
| - for (var j = 0; j < rows[i].length; ++j) {
|
| - var td = document.createElement("td");
|
| - td.style.padding = "5px";
|
| - td.style.textAlign = "right";
|
| - var value = rows[i][j];
|
| - if (typeof value == "number") {
|
| - value = parseFloat(value).toFixed(2);
|
| - }
|
| - // Add links for builder breakdowns to the first column.
|
| - if (tableId == "buildstep_div_table" && j == 0) {
|
| - var a = document.createElement("a");
|
| - a.href = "#";
|
| - a.buildStepName = value;
|
| - a.addEventListener("click", function(event) {
|
| - var name = event.target.buildStepName;
|
| - loadGraphiteData(null, null, null, null, name,
|
| - name + " on ...", "builder_div",
|
| - GROUP_NODE_BUILDER);
|
| - });
|
| - a.innerHTML = value;
|
| - td.appendChild(a);
|
| - } else {
|
| - td.innerHTML = value;
|
| - }
|
| - tr.appendChild(td);
|
| - }
|
| - table.appendChild(tr);
|
| - }
|
| - var container = document.getElementById(containerId);
|
| - container.innerHTML = "";
|
| - table.id = tableId;
|
| - var h2 = document.createElement("h2");
|
| - h2.style.textAlign = "center";
|
| - h2.innerHTML = title;
|
| - container.appendChild(h2);
|
| - container.appendChild(table);
|
| - }
|
| -
|
| - /**
|
| - * Callback function for receiving data from Graphite. Organizes the data and
|
| - * rebuilds the data table.
|
| - *
|
| - * @param {Array<Object>} data Data obtained from Graphite.
|
| - */
|
| - function gotGraphiteData(data) {
|
| - var loadingData = loadingDone();
|
| - makeTableFromData(data, loadingData["tableTitle"],
|
| - loadingData["containerId"]);
|
| - }
|
| -
|
| - /**
|
| - * Helper function for building Graphite data URLs. If any of the parameters
|
| - * are not provided, it obtains defaults from the text boxes on the page.
|
| - *
|
| - * @param {string} timeStart Unix timestamp; time period beginning.
|
| - * @param {string} timeEnd Unix timestamp; time period end.
|
| - * @param {string} masterFilter Filter the data by build master.
|
| - * @param {string} builderFilter Filter the data by builder.
|
| - * @param {string} buildStepFilter Filter the data by build step.
|
| - * @param {number} groupNode Column index for grouping Graphite data.
|
| - */
|
| - function makeGraphiteURL(timeStart, timeEnd, masterFilter, builderFilter,
|
| - buildStepFilter, groupNode) {
|
| - if (!timeStart) {
|
| - timeStart =
|
| - dateTimeLocalToUnixUTC(document.getElementById("time_start").value);
|
| - }
|
| - if (!timeEnd) {
|
| - timeEnd =
|
| - dateTimeLocalToUnixUTC(document.getElementById("time_end").value);
|
| - }
|
| - if (!masterFilter) {
|
| - masterFilter = document.getElementById("master_filter").value;
|
| - }
|
| - if (!builderFilter) {
|
| - builderFilter = document.getElementById("builder_filter").value;
|
| - }
|
| - if (!buildStepFilter) {
|
| - buildStepFilter = document.getElementById("buildstep_filter").value;
|
| - }
|
| - var url = "http://skiamonitor.com/render?format=json&from=" +
|
| - timeStart + "&until=" + timeEnd;
|
| - var metricPrefixParts = ["buildbot", masterFilter, builderFilter,
|
| - buildStepFilter]
|
| - var metricPrefix = metricPrefixParts.join(".");
|
| -
|
| - for (var i = 0; i < metrics.length; ++i) {
|
| - var metric = metrics[i][0];
|
| - var summaryMode = metrics[i][1];
|
| - var fullMetricName = metricPrefix + "." + metric;
|
| - var groupByNode = "groupByNode(" + fullMetricName + "," + groupNode +
|
| - ",%22" + summaryMode + "%22)";
|
| - var summarize = "summarize(" + groupByNode + ",%22" + SUMMARIZE_INTERVAL +
|
| - "%22,%22" + summaryMode + "%22,true)";
|
| - var aliasByNode = "aliasByNode(" + summarize + ",0)";
|
| - var aliasSub = "aliasSub(" + aliasByNode + ",%22$%22,%22." + metric +
|
| - "%22)";
|
| - var fullTarget = "&target=" + aliasSub;
|
| - url += fullTarget;
|
| - }
|
| - return url;
|
| - }
|
| -
|
| - /**
|
| - * Load data from Graphite.
|
| - *
|
| - * @param {string} timeStart Unix timestamp; time period beginning.
|
| - * @param {string} timeEnd Unix timestamp; time period end.
|
| - * @param {string} masterFilter Filter the data by build master.
|
| - * @param {string} builderFilter Filter the data by builder.
|
| - * @param {string} buildStepFilter Filter the data by build step.
|
| - * @param {string} tableTitle Title for the table.
|
| - * @param {string} containerId ID of the element which will hold the table.
|
| - * @param {number} groupNode Column index for grouping Graphite data.
|
| - */
|
| - function loadGraphiteData(timeStart, timeEnd, masterFilter, builderFilter,
|
| - buildStepFilter, tableTitle, containerId, groupNode) {
|
| - var url = makeGraphiteURL(timeStart, timeEnd, masterFilter, builderFilter,
|
| - buildStepFilter, groupNode);
|
| - var data = {"tableTitle": tableTitle, "containerId": containerId};
|
| - loadingStart(url, data);
|
| - loadJSONP(url, "gotGraphiteData");
|
| - }
|
| -
|
| - /**
|
| - * Function called when the "Load data" button is clicked.
|
| - */
|
| - function reloadWithParams() {
|
| - var newQuery =
|
| - "from=" + dateTimeLocalToUnixUTC(document.getElementById("time_start").value) +
|
| - "&until=" + dateTimeLocalToUnixUTC(document.getElementById("time_end").value) +
|
| - "&master=" + document.getElementById("master_filter").value +
|
| - "&builder=" + document.getElementById("builder_filter").value +
|
| - "&buildstep=" + document.getElementById("buildstep_filter").value;
|
| - window.location.search = newQuery;
|
| - }
|
| -
|
| - /**
|
| - * Get the local timezone offset in seconds.
|
| - */
|
| - function getTimezoneOffset() {
|
| - return new Date().getTimezoneOffset() * 60;
|
| - }
|
| -
|
| - /**
|
| - * Convert UTC to Local time.
|
| - */
|
| - function UTCToLocal(unixDate) {
|
| - return unixDate - getTimezoneOffset();
|
| - }
|
| -
|
| - /**
|
| - * Convert Local to UTC time.
|
| - */
|
| - function localToUTC(unixDate) {
|
| - return unixDate + getTimezoneOffset();
|
| - }
|
| -
|
| - /**
|
| - * Get the date in Unix format.
|
| - */
|
| - function unixDate(date) {
|
| - var dateObj = null;
|
| - if (!date) {
|
| - dateObj = new Date()
|
| - } else {
|
| - dateObj = new Date(date);
|
| - }
|
| - return dateObj.getTime() / 1000;
|
| - }
|
| -
|
| - /**
|
| - * Convert from RFC 3339 to Unix format.
|
| - */
|
| - function RFC3339ToUnix(date) {
|
| - return unixDate(date);
|
| - }
|
| -
|
| - /**
|
| - * Convert from Unix to RFC 3339 format, with no time zone information.
|
| - */
|
| - function unixToRFC3339(timestamp) {
|
| - var date = new Date(parseInt(timestamp) * 1000);
|
| - function zfill(num) {
|
| - if (num < 10) {
|
| - return "0" + num;
|
| - } else {
|
| - return num;
|
| - }
|
| - }
|
| - return (date.getUTCFullYear() + "-" +
|
| - zfill(date.getUTCMonth() + 1) + "-" +
|
| - zfill(date.getUTCDate()) + "T" +
|
| - zfill(date.getUTCHours()) + ":" +
|
| - zfill(date.getUTCMinutes()) + ":" +
|
| - zfill(date.getUTCSeconds()));
|
| - }
|
| -
|
| - /**
|
| - * Convert from Unix timestamp to datetime-local RFC 3339 format.
|
| - */
|
| - function unixUTCToDateTimeLocal(unix) {
|
| - return unixToRFC3339(UTCToLocal(unix));
|
| - }
|
| -
|
| - /**
|
| - * Convert from datetime-local RFC 3339 format to Unix timestamp.
|
| - */
|
| - function dateTimeLocalToUnixUTC(rfcLocal) {
|
| - return localToUTC(RFC3339ToUnix(rfcLocal));
|
| - }
|
| -
|
| - /**
|
| - * Function called on page load.
|
| - */
|
| - function init() {
|
| - // Load default query values.
|
| - var currentTime = unixDate();
|
| - var timeEnd = currentTime;
|
| - var timeStart = currentTime - DEFAULT_TIME_PERIOD;
|
| - var masterFilter = DEFAULT_MASTER_FILTER;
|
| - var builderFilter = DEFAULT_BUILDER_FILTER;
|
| - var buildstepFilter = DEFAULT_BUILDSTEP_FILTER;
|
| -
|
| - // Retrieve any parameters from the URL.
|
| - var params = window.location.search.substring(1).split("&");
|
| - // Remove empty string from parameters.
|
| - while (params.indexOf("") != -1) {
|
| - params.splice(params.indexOf(""));
|
| - }
|
| - for (var i = 0; i < params.length; ++i) {
|
| - var splitParam = params[i].split("=");
|
| - if (splitParam[0] == "from") {
|
| - timeStart = splitParam[1];
|
| - } else if (splitParam[0] == "until") {
|
| - timeEnd = splitParam[1];
|
| - } else if (splitParam[0] == "master") {
|
| - masterFilter = splitParam[1];
|
| - } else if (splitParam[0] == "builder") {
|
| - builderFilter = splitParam[1];
|
| - } else if (splitParam[0] == "buildstep") {
|
| - buildstepFilter = splitParam[1];
|
| - } else {
|
| - console.error("Unknown parameter: " + splitParam[0]);
|
| - }
|
| - }
|
| -
|
| - // Fill the form with the appropriate values.
|
| - document.getElementById("time_start").value =
|
| - unixUTCToDateTimeLocal(timeStart);
|
| - document.getElementById("time_end").value =
|
| - unixUTCToDateTimeLocal(timeEnd);
|
| - document.getElementById("master_filter").value = masterFilter;
|
| - document.getElementById("builder_filter").value = builderFilter;
|
| - document.getElementById("buildstep_filter").value = buildstepFilter;
|
| -
|
| - // Decide what to do, based on the number of provided parameters.
|
| - if (params.length == 0) {
|
| - // Don't load data; we assume that the user might want to tweak the
|
| - // filters first.
|
| - } else if (params.length != 5) {
|
| - // Reload the page, filling in the rest of the parameters with their
|
| - // defaults.
|
| - reloadWithParams();
|
| - } else {
|
| - // All parameters were provided. Load data.
|
| - loadGraphiteData(timeStart, timeEnd, masterFilter, builderFilter,
|
| - buildstepFilter, "Build Steps", "buildstep_div",
|
| - GROUP_NODE_BUILDSTEP);
|
| - }
|
| - }
|
| -
|
| - </script>
|
| - </head>
|
| - <body>
|
| - <div id="heading" style="font-size:2.5em; text-align:center; height:7%;">
|
| - Skia Buildstep Dashboard
|
| - </div>
|
| - <div style="text-align:center;">
|
| - Click a column header to sort that column. Click a buildstep to see its
|
| - results broken down into builders.
|
| - </div>
|
| - <div id="main_content_area" style="width:100%; height:90%; padding:0px;
|
| - margin:0px;">
|
| - <div id="menu_div"
|
| - style="float:left; width:18%; height:100%; padding:0px; margin:0px;">
|
| - <div style="width:100%;">
|
| - <nobr>Time period:</nobr><br/>
|
| - From:<br/>
|
| - <input type="datetime-local" id="time_start" /><br/>
|
| - To:<br/>
|
| - <input type="datetime-local" id="time_end" /><br/>
|
| - <nobr>Build master filter:</nobr><br/>
|
| - <input type="text" id="master_filter" /><br/>
|
| - <nobr>Builder filter:</nobr><br/>
|
| - <input type="text" id="builder_filter" /><br/>
|
| - <nobr>Build step filter:</nobr><br/>
|
| - <input type="text" id="buildstep_filter" /><br/>
|
| - <input type="button" id="load_data_button" onClick="reloadWithParams();" value="Load Data"/>
|
| - </div>
|
| - <div id="logging_div" style="width:100%; padding:0px; margin:0px"></div>
|
| - </div>
|
| - <div id="buildstep_div"
|
| - style="float:left; width:41%; padding:0px; margin:0px">
|
| - </div>
|
| - <div id="builder_div"
|
| - style="float:left; width:41%; padding:0px; margin:0px">
|
| - </div>
|
| - </div>
|
| - <script type="text/javascript">init();</script>
|
| - </body>
|
| -</html>
|
| -
|
|
|