From 159815ad9df69136ff8be2c0cf6bda8794151fc5 Mon Sep 17 00:00:00 2001 From: stevenknight Date: Fri, 18 Dec 2009 08:03:50 +0000 Subject: [PATCH] Move the timings-specific pieces of the buildbot infrastructure into the trunk/timings directory. We'll map them into the buildbot directory using svn:externals. This will let us keep all the pieces of a timing configuration, including its buildbot pieces, in one place, and will let us simplify the Master initialization (since it will be able to look on-disk for the configurations for which it should set up buildbot steps, instead of querying the SVN server). git-svn-id: http://scons.tigris.org/svn/scons/trunk@4562 fdb21ef1-2011-0410-befe-b5e4ea1792b1 --- timings/changelog.html | 204 +++++++++++++++++++ timings/graph.html | 411 ++++++++++++++++++++++++++++++++++++++ timings/index.html | 196 ++++++++++++++++++ timings/js/common.js | 96 +++++++++ timings/js/coordinates.js | 125 ++++++++++++ timings/js/plotter.js | 336 +++++++++++++++++++++++++++++++ 6 files changed, 1368 insertions(+) create mode 100644 timings/changelog.html create mode 100644 timings/graph.html create mode 100644 timings/index.html create mode 100644 timings/js/common.js create mode 100644 timings/js/coordinates.js create mode 100644 timings/js/plotter.js diff --git a/timings/changelog.html b/timings/changelog.html new file mode 100644 index 00000000..586ebadc --- /dev/null +++ b/timings/changelog.html @@ -0,0 +1,204 @@ + + + + + + +
+ SVN path: + SVN revision range: + text + html + +
+ + + + + diff --git a/timings/graph.html b/timings/graph.html new file mode 100644 index 00000000..e418069d --- /dev/null +++ b/timings/graph.html @@ -0,0 +1,411 @@ + + + + + + + + + + + + + + + + + +
+ + + +
+ +
+Builds generated by the buildbot +are run through + +and the results of that test are charted here. +
+ +
+The vertical axis is measured values, and the horizontal +axis is the revision number for the build being tested. +
+

+
+ +
+
+
+
+ +
+
+ +
+
+

+
+
+
diff --git a/timings/index.html b/timings/index.html
new file mode 100644
index 00000000..de102ead
--- /dev/null
+++ b/timings/index.html
@@ -0,0 +1,196 @@
+
+  
+    SCons Timings
+    
+    
+  
+  
+    
+ +
+ + diff --git a/timings/js/common.js b/timings/js/common.js new file mode 100644 index 00000000..80510b32 --- /dev/null +++ b/timings/js/common.js @@ -0,0 +1,96 @@ +/* + Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. + Use of this source code is governed by a BSD-style license that can be + found in the LICENSE file. +*/ + +/* + Common methods for performance-plotting JS. +*/ + +function Fetch(url, callback) { + var r = new XMLHttpRequest(); + r.open("GET", url, true); + r.setRequestHeader("pragma", "no-cache"); + r.setRequestHeader("cache-control", "no-cache"); + r.onreadystatechange = function() { + if (r.readyState == 4) { + var error; + var text = r.responseText; + if (r.status != 200) { + error = url + ": " + r.status + ": " + r.statusText; + } else if (! text) { + error = url + ": null response"; + } + callback(text, error); + } + } + + r.send(null); +} + +// Returns the keys of an object. +function Keys(obj) { + result = []; + for (key in obj) { + result.push(key) + } + return result +} + +// Returns the "directory name" portion of the string (URL), +// stripping the last element. +function DirName(s) { + elements = s.split('/') + elements.pop() + return elements.join('/') +} + +// Returns an Object with properties given by the parameters specified in the +// URL's query string. +function ParseParams() { + var result = new Object(); + var s = window.location.search.substring(1).split('&'); + for (i = 0; i < s.length; ++i) { + var v = s[i].split('='); + result[v[0]] = unescape(v[1]); + } + return result; +} + +// Creates the URL constructed from the current pathname and the given params. +function MakeURL(params) { + var url = window.location.pathname; + var sep = '?'; + for (p in params) { + if (!p) + continue; + url = url + sep + p + '=' + params[p]; + sep = '&'; + } + return url; +} + +// Returns a string describing an object, recursively. On the initial call, +// |name| is optionally the name of the object and |indent| is not needed. +function DebugDump(obj, opt_name, opt_indent) { + var name = opt_name || ''; + var indent = opt_indent || ''; + if (typeof obj == "object") { + var child = null; + var output = indent + name + "\n"; + + for (var item in obj) { + try { + child = obj[item]; + } catch (e) { + child = ""; + } + output += DebugDump(child, item, indent + " "); + } + + return output; + } else { + return indent + name + ": " + obj + "\n"; + } +} diff --git a/timings/js/coordinates.js b/timings/js/coordinates.js new file mode 100644 index 00000000..69cb4c22 --- /dev/null +++ b/timings/js/coordinates.js @@ -0,0 +1,125 @@ +/* + Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. + Use of this source code is governed by a BSD-style license that can be + found in the LICENSE file. +*/ + +/** + * 'Understands' plot data positioning. + * @constructor + * + * @param {Array} plotData data that will be displayed + */ +function Coordinates(plotData) { + this.plotData = plotData; + + height = window.innerHeight - 16; + width = window.innerWidth - 16; + + this.widthMax = width; + this.heightMax = Math.min(400, height - 85); + + this.xMinValue = -0.5; + this.xMaxValue = (this.plotData[0].length - 1)+ 0.5; + this.processYValues_(); +} + +Coordinates.prototype.processYValues_ = function () { + var merged = []; + for (var i = 0; i < this.plotData.length; i++) + for (var j = 0; j < this.plotData[i].length; j++) + merged.push(this.plotData[i][j][0]); + var max = Math.max.apply( Math, merged ); + var min = Math.min.apply( Math, merged ); + + // If we have a missing value, find the real max and min the hard way. + if (isNaN(min)) { + for (var i = 0; i < merged.length; ++i) { + if (isNaN(min) || merged[i] < min) + min = merged[i]; + if (isNaN(max) || merged[i] > max) + max = merged[i]; + } + } + var yd = (max - min) / 10.0; + if (yd == 0) + yd = max / 10; + this.yMinValue = min - yd; + this.yMaxValue = max + yd; +}; + +/** + * Difference between horizontal max min values. + */ +Coordinates.prototype.xValueRange = function() { + return this.xMaxValue - this.xMinValue; +}; + +/** + * Difference between vertical max min values. + */ +Coordinates.prototype.yValueRange = function() { + return this.yMaxValue - this.yMinValue +}; + +/** + * Converts horizontal data value to pixel value on canvas. + * @param {number} value horizontal data value + */ +Coordinates.prototype.xPoints = function(value) { + return this.widthMax * ((value - this.xMinValue) / this.xValueRange()); +}; + +/** + * Converts vertical data value to pixel value on canvas. + * @param {number} value vertical data value + */ +Coordinates.prototype.yPoints = function(value) { + /* Converts value to canvas Y position in pixels. */ + return this.heightMax - this.heightMax * (value - this.yMinValue) / + this.yValueRange(); +}; + +/** + * Converts X point on canvas to value it represents. + * @param {number} position horizontal point on canvas. + */ +Coordinates.prototype.xValue = function(position) { + /* Converts canvas X pixels to value. */ + return position / this.widthMax * (this.xValueRange()) + this.xMinValue; +}; + +/** + * Converts Y point on canvas to value it represents. + * @param {number} position vertical point on canvas. + */ +Coordinates.prototype.yValue = function(position) { + /* Converts canvas Y pixels to value. + position is point value is from top. + */ + var position = this.heightMax - position; + var ratio = parseFloat(this.heightMax / position); + return this.yMinValue + this.yValueRange() / ratio; +}; + +/** + * Converts canvas X pixel to data index. + * @param {number} xPosition horizontal point on canvas + */ +Coordinates.prototype.dataSampleIndex = function(xPosition) { + var xValue = this.xValue(xPosition); + var index; + if (xValue < 0) { + index = 0; + } else if (xValue > this.plotData[0].length - 1) { + index = this.plotData[0].length - 1; + } else { + index = xValue.toFixed(0); + } + return index; +}; + +Coordinates.prototype.log = function(val) { + document.getElementById('log').appendChild( + document.createTextNode(val + '\n')); +}; diff --git a/timings/js/plotter.js b/timings/js/plotter.js new file mode 100644 index 00000000..86fb2304 --- /dev/null +++ b/timings/js/plotter.js @@ -0,0 +1,336 @@ +/* + Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. + Use of this source code is governed by a BSD-style license that can be + found in the LICENSE file. +*/ + +// Collection of classes used to plot data in a . Create a Plotter() +// to generate a plot. + +// vertical marker for columns +function Marker(color) { + var m = document.createElement("DIV"); + m.setAttribute("class", "plot-cursor"); + m.style.backgroundColor = color; + m.style.opacity = "0.3"; + m.style.position = "absolute"; + m.style.left = "-2px"; + m.style.top = "-2px"; + m.style.width = "0px"; + m.style.height = "0px"; + return m; +} + +/** + * HorizontalMarker class + * Create a horizontal marker at the indicated mouse location. + * @constructor + * + * @param canvasRect {Object} The canvas bounds (in client coords). + * @param clientY {Number} The vertical mouse click location that spawned + * the marker, in the client coordinate space. + * @param yValue {Number} The plotted value corresponding to the clientY + * click location. + */ +function HorizontalMarker(canvasRect, clientY, yValue) { + // Add a horizontal line to the graph. + var m = document.createElement("DIV"); + m.setAttribute("class", "plot-baseline"); + m.style.backgroundColor = HorizontalMarker.COLOR; + m.style.opacity = "0.3"; + m.style.position = "absolute"; + m.style.left = canvasRect.offsetLeft; + var h = HorizontalMarker.HEIGHT; + m.style.top = (clientY - h/2).toFixed(0) + "px"; + m.style.width = canvasRect.width + "px"; + m.style.height = h + "px"; + this.markerDiv_ = m; + + this.value = yValue; +} + +HorizontalMarker.HEIGHT = 5; +HorizontalMarker.COLOR = "rgb(0,100,100)"; + +// Remove the horizontal line from the graph. +HorizontalMarker.prototype.remove_ = function() { + this.markerDiv_.parentNode.removeChild(this.markerDiv_); +} + +/** + * Plotter class + * @constructor + * + * Draws a chart using CANVAS element. Takes array of lines to draw with + * deviations values for each data sample. + * + * @param {Array} clNumbers list of clNumbers for each data sample. + * @param {Array} plotData list of arrays that represent individual lines. + * The line itself is an Array of value and stdd. + * @param {Array} dataDescription list of data description for each line + * in plotData. + * @units {string} units name of measurement used to describe plotted data. + * + * Example of the plotData: + * [ + * [line 1 data], + * [line 2 data] + * ]. + * Line data looks like [[point one], [point two]]. + * And individual points are [value, deviation value] + */ +function Plotter(clNumbers, plotData, dataDescription, units, resultNode) { + this.clNumbers_ = clNumbers; + this.plotData_ = plotData; + this.dataDescription_ = dataDescription; + this.resultNode_ = resultNode; + this.units_ = units; + this.coordinates = new Coordinates(plotData); + + // A color palette that's unambigous for normal and color-deficient viewers. + // Values are (red, green, blue) on a scale of 255. + // Taken from http://jfly.iam.u-tokyo.ac.jp/html/manuals/pdf/color_blind.pdf + this.colors = [[0, 114, 178], // blue + [230, 159, 0], // orange + [0, 158, 115], // green + [204, 121, 167], // purplish pink + [86, 180, 233], // sky blue + [213, 94, 0], // dark orange + [0, 0, 0], // black + [240, 228, 66] // yellow + ]; +} + +/** + * Does the actual plotting. + */ +Plotter.prototype.plot = function() { + var canvas = this.canvas(); + this.coordinates_div_ = this.coordinates_(); + this.ruler_div_ = this.ruler(); + // marker for the result-point that the mouse is currently over + this.cursor_div_ = new Marker("rgb(100,80,240)"); + // marker for the result-point for which details are shown + this.marker_div_ = new Marker("rgb(100,100,100)"); + var ctx = canvas.getContext("2d"); + for (var i = 0; i < this.plotData_.length; i++) + this.plotLine_(ctx, this.nextColor(i), this.plotData_[i]); + + this.resultNode_.appendChild(canvas); + this.resultNode_.appendChild(this.coordinates_div_); + + this.resultNode_.appendChild(this.ruler_div_); + this.resultNode_.appendChild(this.cursor_div_); + this.resultNode_.appendChild(this.marker_div_); + this.attachEventListeners(canvas); + this.canvasRectangle = { + "offsetLeft": canvas.offsetLeft, + "offsetTop": canvas.offsetTop, + "width": canvas.offsetWidth, + "height": canvas.offsetHeight + }; +}; + +Plotter.prototype.drawDeviationBar_ = function(context, strokeStyles, x, y, + deviationValue) { + context.strokeStyle = strokeStyles; + context.lineWidth = 1.0; + context.beginPath(); + context.moveTo(x, (y + deviationValue)); + context.lineTo(x, (y - deviationValue)); + context.moveTo(x, (y - deviationValue)); + context.closePath(); + context.stroke(); +}; + +Plotter.prototype.plotLine_ = function(ctx, strokeStyles, data) { + ctx.strokeStyle = strokeStyles; + ctx.lineWidth = 2.0; + ctx.beginPath(); + var initial = true; + var deviationData = []; + for (var i = 0; i < data.length; i++) { + var x = this.coordinates.xPoints(i); + var value = data[i][0]; + var stdd = data[i][1]; + var y = 0.0; + var err = 0.0; + if (isNaN(value)) { + // Re-set 'initial' if we're at a gap in the data. + initial = true; + } else { + y = this.coordinates.yPoints(value); + // We assume that the stdd will only be NaN (missing) when the value is. + if (parseFloat(value) != 0.0) + err = y * parseFloat(stdd) / parseFloat(value); + if (initial) + initial = false; + else + ctx.lineTo(x, y); + } + + ctx.moveTo(x, y); + deviationData.push([x, y, err]) + } + ctx.closePath(); + ctx.stroke(); + + for (var i = 0; i < deviationData.length; i++) { + this.drawDeviationBar_(ctx, strokeStyles, deviationData[i][0], + deviationData[i][1], deviationData[i][2]); + } +}; + +Plotter.prototype.attachEventListeners = function(canvas) { + var self = this; + canvas.parentNode.addEventListener( + "mousemove", function(evt) { self.onMouseMove_(evt); }, false); + this.cursor_div_.addEventListener( + "click", function(evt) { self.onMouseClick_(evt); }, false); +}; + +Plotter.prototype.updateRuler_ = function(evt) { + var r = this.ruler_div_; + r.style.left = this.canvasRectangle.offsetLeft + "px"; + + r.style.top = this.canvasRectangle.offsetTop + "px"; + r.style.width = this.canvasRectangle.width + "px"; + var h = evt.clientY - this.canvasRectangle.offsetTop; + if (h > this.canvasRectangle.height) + h = this.canvasRectangle.height; + r.style.height = h + "px"; +}; + +Plotter.prototype.updateCursor_ = function() { + var c = this.cursor_div_; + c.style.top = this.canvasRectangle.offsetTop + "px"; + c.style.height = this.canvasRectangle.height + "px"; + var w = this.canvasRectangle.width / this.clNumbers_.length; + var x = (this.canvasRectangle.offsetLeft + + w * this.current_index_).toFixed(0); + c.style.left = x + "px"; + c.style.width = w + "px"; +}; + + +Plotter.prototype.onMouseMove_ = function(evt) { + var canvas = evt.currentTarget.firstChild; + var positionX = evt.clientX - this.canvasRectangle.offsetLeft; + var positionY = evt.clientY - this.canvasRectangle.offsetTop; + + this.current_index_ = this.coordinates.dataSampleIndex(positionX); + var yValue = this.coordinates.yValue(positionY); + + this.coordinates_td_.innerHTML = + "r" + this.clNumbers_[this.current_index_] + ": " + + this.plotData_[0][this.current_index_][0].toFixed(2) + " " + + this.units_ + " +/- " + + this.plotData_[0][this.current_index_][1].toFixed(2) + " " + + yValue.toFixed(2) + " " + this.units_; + + // If there is a horizontal marker, also display deltas relative to it. + if (this.horizontal_marker_) { + var baseline = this.horizontal_marker_.value; + var delta = yValue - baseline + var fraction = delta / baseline; // allow division by 0 + + var deltaStr = (delta >= 0 ? "+" : "") + delta.toFixed(0) + " " + + this.units_; + var percentStr = (fraction >= 0 ? "+" : "") + + (fraction * 100).toFixed(3) + "%"; + + this.baseline_deltas_td_.innerHTML = deltaStr + ": " + percentStr; + } + + this.updateRuler_(evt); + this.updateCursor_(); +}; + +Plotter.prototype.onMouseClick_ = function(evt) { + // Shift-click controls the horizontal reference line. + if (evt.shiftKey) { + if (this.horizontal_marker_) { + this.horizontal_marker_.remove_(); + } + + var canvasY = evt.clientY - this.canvasRectangle.offsetTop; + this.horizontal_marker_ = new HorizontalMarker(this.canvasRectangle, + evt.clientY, this.coordinates.yValue(canvasY)); + + // Insert before cursor node, otherwise it catches clicks. + this.cursor_div_.parentNode.insertBefore( + this.horizontal_marker_.markerDiv_, this.cursor_div_); + } else { + var index = this.current_index_; + var m = this.marker_div_; + var c = this.cursor_div_; + m.style.top = c.style.top; + m.style.left = c.style.left; + m.style.width = c.style.width; + m.style.height = c.style.height; + if ("onclick" in this) { + var this_x = this.clNumbers_[index]; + var prev_x = index > 0 ? (parseInt(this.clNumbers_[index-1]) + 1) : + this_x; + this.onclick(prev_x, this_x); + } + } +}; + +Plotter.prototype.canvas = function() { + var canvas = document.createElement("CANVAS"); + canvas.setAttribute("id", "_canvas"); + canvas.setAttribute("class", "plot"); + canvas.setAttribute("width", this.coordinates.widthMax); + canvas.setAttribute("height", this.coordinates.heightMax); + return canvas; +}; + +Plotter.prototype.ruler = function() { + ruler = document.createElement("DIV"); + ruler.setAttribute("class", "plot-ruler"); + ruler.style.borderBottom = "1px dotted black"; + ruler.style.position = "absolute"; + ruler.style.left = "-2px"; + ruler.style.top = "-2px"; + ruler.style.width = "0px"; + ruler.style.height = "0px"; + return ruler; +}; + +Plotter.prototype.coordinates_ = function() { + var coordinatesDiv = document.createElement("DIV"); + var table_html = + "" + + "" + + "" + + "" + + "
Legend: "; + for (var i = 0; i < this.dataDescription_.length; i++) { + if (i > 0) + table_html += ", "; + table_html += "" + this.dataDescription_[i] + ""; + } + table_html += "
move mouse over graphShift-click to place baseline
"; + coordinatesDiv.innerHTML = table_html; + + var tr = coordinatesDiv.firstChild.firstChild.childNodes[1]; + this.coordinates_td_ = tr.childNodes[0]; + this.baseline_deltas_td_ = tr.childNodes[1]; + + return coordinatesDiv; +}; + +Plotter.prototype.nextColor = function(i) { + var index = i % this.colors.length; + return "rgb(" + this.colors[index][0] + "," + + this.colors[index][1] + "," + + this.colors[index][2] + ")"; +}; + +Plotter.prototype.log = function(val) { + document.getElementById('log').appendChild( + document.createTextNode(val + '\n')); +}; -- 2.26.2