Add shared code for a new a graphics benchmark
authorcommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sat, 3 Oct 2015 03:10:13 +0000 (03:10 +0000)
committercommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sat, 3 Oct 2015 03:10:13 +0000 (03:10 +0000)
https://bugs.webkit.org/show_bug.cgi?id=149691

Patch by Said Abou-Hallawa <sabouhallawa@apple.com> on 2015-10-02
Reviewed by Ryosuke Niwa.

This set of classes will be shared and used by the tests and the runner
of a new graphics benchmark.

* Animometer/resources: Added.
* Animometer/resources/algorithm.js: Added.
(Array.prototype.swap): Swaps two elements in an array.
(Heap): Binary Min/Max Heap object
(Heap.prototype._parentIndex): Given the child node index, it returns the parent index.
(Heap.prototype._leftIndex): Given the parent node index, it returns the left node index.
(Heap.prototype._rightIndex): Given the parent node index, it returns the right node index.
(Heap.prototype._childIndex): Given the parent node index, it returns the child index that may violate the heap property.
(Heap.prototype.init): Initializes the heap state.
(Heap.prototype.top): Returns the value stored at the top of the heap.
(Heap.prototype.push): Pushes a new node at the top of the heap.
(Heap.prototype.pop): Extracts the top node of the heap.
(Heap.prototype._bubble): Fixes the heap property by moving upward.
(Heap.prototype._sink): Fixes the heap property by moving downward.
(Heap.prototype.str): Prints the nodes of the heap to a string.
(Heap.prototype.values): Returns the last "size" heap elements values.

(Algorithm.createMinHeap): Creates a size-bounded min-heap object.
(Algorithm.createMaxHeap): Creates a size-bounded max-heap object.

* Animometer/resources/extensions.js: Added.
(Point): Point object but can be used as size also.
(Point.pointOnCircle): Given, the radius of the circle and the angle of the point, it returns a point object.
(Point.pointOnEllipse): Given, the radiuses of the ellipse and the angle of the point, it returns a point object.
(Point.prototype.get width): Should be called when the point is used as size.
(Point.prototype.get height): Should be called when the point is used as size.
(Point.prototype.get center): Should be called when the point is used as size.
(Point.prototype.add): Returns a new point = this + other.
(Point.prototype.subtract): Returns a new point = this - other.
(Point.prototype.multiply): Returns a new point = this * other.
(Point.prototype.move): Moves the point in a given direction, velocity, time period.

(Insets): Represents borders of a container.
(Insets.prototype.get width): Returns left + right.
(Insets.prototype.get height): Returns top + bottom.

(SimplePromise):
(SimplePromise.prototype.then):
(SimplePromise.prototype.resolve):
Moved from Animometer/runner/resources/benchmark-runner.js since tests also need it.

(Options): Benchmark running options as they are set by the user.

(ProgressBar): Manages a progress bar element. The progress bar is divided into equal length ranges.
(ProgressBar.prototype._progressToPercent): Converts the progress into a percentage.
(ProgressBar.prototype.incRange): Moves to the next range (a range is the running time of a single test).
(ProgressBar.prototype.setPos): Draws the current progress in the current range.

(RecordTable): Shows the results of running a benchmark in a tabular form.
(RecordTable.prototype.clear): Clears the results table.
(RecordTable.prototype._showTitles): Shows the header titles and appends the sub-titles to a queue.
(RecordTable.prototype._showHeader): Shows the table header titles.
(RecordTable.prototype._showEmpty): Shows an empty table cell.
(RecordTable.prototype._showValue): Shows a number value in the results table.
(RecordTable.prototype._showSamples): Shows a button for the sampled data graph.
(RecordTable.prototype._showTest): Shows the results of a single test.
(RecordTable.prototype._showSuite): Shows the results of a single suite.
(RecordTable.prototype.showRecord): Shows a single iteration for a single test.
(RecordTable.prototype.showIterations): Shows the results of all the suites of the iterations.

* Animometer/resources/sampler.js: Added.
(Statistics.sampleMean): Returns the sample mean.
(Statistics.unbiasedSampleStandardDeviation): Returns the unbiased sample variance (i.e. with Bessel's correction)
(Statistics.geometricMean): Returns the geometric mean.

(Experiment): Represents a sampling experiment.
(Experiment.prototype._init): Called when the object is created and when startSampling() is called.
(Experiment.prototype.startSampling): Called after warmup period. Restarts collecting sampled data points.
(Experiment.prototype.sample): Add a new data point.
(Experiment.prototype.mean): Returns the sample mean for the current sampled data points.
(Experiment.prototype.standardDeviation): Returns the sample standard deviation for the current sampled data points.
(Experiment.prototype.percentage): Returns the percentage of the standard deviation divided to the mean.
(Experiment.prototype.confidenceIntervalDelta): Calculates the confidence delta for the current sampled data given a confidence level.
(Experiment.prototype.concern): Returns the average of the worst given percentage from the sampled data.
(Experiment.prototype.score): Returns a score for the sampled data. It is the geometric mean of sampleMean and concern.

(Sampler): Represents a compound experiment. It manages sampling multiple data points at the same time offset.
(Sampler.prototype.startSampling): Called after warming up period. Restarts collecting sampled data points.
(Sampler.prototype.sample): Add a new data vector at a given time offset.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@190541 268f45cc-cd09-0410-ab3c-d52691b4dbfc

PerformanceTests/Animometer/resources/algorithm.js [new file with mode: 0644]
PerformanceTests/Animometer/resources/extensions.js [new file with mode: 0644]
PerformanceTests/Animometer/resources/sampler.js [new file with mode: 0644]
PerformanceTests/ChangeLog

diff --git a/PerformanceTests/Animometer/resources/algorithm.js b/PerformanceTests/Animometer/resources/algorithm.js
new file mode 100644 (file)
index 0000000..3d3971f
--- /dev/null
@@ -0,0 +1,142 @@
+Array.prototype.swap = function(i, j)
+{
+    var t = this[i];
+    this[i] = this[j];
+    this[j] = t;
+    return this;
+}
+
+function Heap(maxSize, compare)
+{
+    this._maxSize = maxSize;
+    this._compare = compare;
+    this._size = 0;
+    this._values = new Array(this._maxSize);
+}
+
+Heap.prototype =
+{
+    // This is a binary heap represented in an array. The root element is stored
+    // in the first element in the array. The root is followed by its two children.
+    // Then its four grandchildren and so on. So every level in the binary heap is
+    // doubled in the following level. Here is an example of the node indices and
+    // how they are related to their parents and children.
+    // ===========================================================================
+    //              0       1       2       3       4       5       6
+    // PARENT       -1      0       0       1       1       2       2
+    // LEFT         1       3       5       7       9       11      13
+    // RIGHT        2       4       6       8       10      12      14
+    // ===========================================================================
+    _parentIndex: function(i)
+    {
+        return i > 0 ? Math.floor((i - 1) / 2) : -1;
+    },
+    
+    _leftIndex: function(i)
+    {
+        var leftIndex = i * 2 + 1;
+        return leftIndex < this._size ? leftIndex : -1;
+    },
+    
+    _rightIndex: function(i)
+    {
+        var rightIndex = i * 2 + 2;
+        return rightIndex < this._size ? rightIndex : -1;
+    },
+    
+    // Return the child index that may violate the heap property at index i.
+    _childIndex: function(i)
+    {
+        var left = this._leftIndex(i);
+        var right = this._rightIndex(i);
+
+        if (left != -1 && right != -1)
+            return this._compare(this._values[left], this._values[right]) > 0 ? left : right;
+        
+        return left != -1 ? left : right;
+    },
+    
+    init: function()
+    {
+        this._size = 0;
+    },
+    
+    top: function()
+    {
+        return this._size ? this._values[0] : NaN;
+    },
+    
+    push: function(value)
+    {
+        if (this._size == this._maxSize) {
+            // If size is bounded and the new value can be a parent of the top()
+            // if the size were unbounded, just ignore the new value.
+            if (this._compare(value, this.top()) > 0)
+                return;
+            this.pop();
+        }
+        this._values[this._size++] = value;
+        this._bubble(this._size - 1);
+    },
+
+    pop: function()
+    {
+        if (!this._size)
+            return NaN;
+        
+        this._values[0] = this._values[--this._size];
+        this._sink(0);
+    },
+    
+    _bubble: function(i)
+    {
+        // Fix the heap property at index i given that parent is the only node that
+        // may violate the heap property.
+        for (var pi = this._parentIndex(i); pi != -1; i = pi, pi = this._parentIndex(pi)) {
+            if (this._compare(this._values[pi], this._values[i]) > 0)
+                break;
+                
+            this._values.swap(pi, i);
+        }
+    },
+    
+    _sink: function(i)
+    {
+        // Fix the heap property at index i given that each of the left and the right
+        // sub-trees satisfies the heap property.
+        for (var ci = this._childIndex(i); ci != -1; i = ci, ci = this._childIndex(ci)) {
+            if (this._compare(this._values[i], this._values[ci]) > 0)
+                break;
+            
+            this._values.swap(ci, i);
+        }
+    },
+    
+    str: function()
+    {
+        var out = "Heap[" + this._size + "] = [";
+        for (var i = 0; i < this._size; ++i) {
+            out += this._values[i];
+            if (i < this._size - 1)
+                out += ", ";
+        }
+        return out + "]";
+    },
+    
+    values: function(size) {
+        // Return the last "size" heap elements values.
+        var values = this._values.slice(0, this._size);
+        return values.sort(this._compare).slice(0, Math.min(size, this._size));
+    }
+}
+
+var Algorithm = {
+    createMinHeap: function(maxSize)
+    {
+        return new Heap(maxSize, function(a, b) { return b - a; });
+    },
+    
+    createMaxHeap: function(maxSize) {
+        return new Heap(maxSize, function(a, b) { return a - b; });
+    }
+}
diff --git a/PerformanceTests/Animometer/resources/extensions.js b/PerformanceTests/Animometer/resources/extensions.js
new file mode 100644 (file)
index 0000000..66b13b7
--- /dev/null
@@ -0,0 +1,306 @@
+function Point(x, y)
+{
+    this.x = x;
+    this.y = y;
+}
+
+Point.pointOnCircle = function(angle, radius)
+{
+    return new Point(radius * Math.cos(angle), radius * Math.sin(angle));
+}
+
+Point.pointOnEllipse = function(angle, radiuses)
+{
+    return new Point(radiuses.x * Math.cos(angle), radiuses.y * Math.sin(angle));
+}
+
+Point.prototype =
+{
+    // Used when the point object is used as a size object.
+    get width()
+    {
+        return this.x;
+    },
+    
+    // Used when the point object is used as a size object.
+    get height()
+    {
+        return this.y;
+    },
+    
+    // Used when the point object is used as a size object.
+    get center()
+    {
+        return new Point(this.x / 2, this.y / 2);
+    },
+    
+    add: function(other)
+    {
+        return new Point(this.x + other.x, this.y + other.y);
+    },
+    
+    subtract: function(other)
+    {
+        return new Point(this.x - other.x, this.y - other.y);
+    },
+    
+    multiply: function(other)
+    {
+        return new Point(this.x * other.x, this.y * other.y);
+    },
+    
+    move: function(angle, velocity, timeDelta)
+    {
+        return this.add(Point.pointOnCircle(angle, velocity * (timeDelta / 1000)));
+    }
+}
+
+function Insets(top, right, bottom, left)
+{
+    this.top = top;
+    this.right = right;
+    this.bottom = bottom;
+    this.left = left;
+}
+
+Insets.prototype =
+{
+    get width() {
+        return this.left + this.right;
+    },
+
+    get height() {
+        return this.top + this.bottom;
+    }
+}
+
+function SimplePromise()
+{
+    this._chainedPromise = null;
+    this._callback = null;
+}
+
+SimplePromise.prototype.then = function (callback)
+{
+    if (this._callback)
+        throw "SimplePromise doesn't support multiple calls to then";
+        
+    this._callback = callback;
+    this._chainedPromise = new SimplePromise;
+    
+    if (this._resolved)
+        this.resolve(this._resolvedValue);
+
+    return this._chainedPromise;
+}
+
+SimplePromise.prototype.resolve = function (value)
+{
+    if (!this._callback) {
+        this._resolved = true;
+        this._resolvedValue = value;
+        return;
+    }
+
+    var result = this._callback(value);
+    if (result instanceof SimplePromise) {
+        var chainedPromise = this._chainedPromise;
+        result.then(function (result) { chainedPromise.resolve(result); });
+    } else
+        this._chainedPromise.resolve(result);
+}
+
+function Options(testInterval, frameRate)
+{
+    this.testInterval = testInterval;
+    this.frameRate = frameRate;
+}
+
+function ProgressBar(element, ranges)
+{
+    this.element = element;
+    this.ranges = ranges;
+    this.currentRange = 0;
+}
+
+ProgressBar.prototype =
+{
+    _progressToPercent: function(progress)
+    {
+        return progress * (100 / this.ranges);
+    },
+    
+    incRange: function()
+    {
+        ++this.currentRange;
+    },
+    
+    setPos: function(progress)
+    {
+        this.element.style.width = this._progressToPercent(this.currentRange + progress) + "%";
+    }
+}
+
+function RecordTable(element)
+{
+    this.element = element;
+    this.clear();
+}
+
+RecordTable.prototype =
+{
+    clear: function()
+    {
+        this.element.innerHTML = "";
+    },
+    
+    _showTitles: function(row, queue, titles, message)
+    {
+        titles.forEach(function (title) {
+            var th = document.createElement("th");
+            th.textContent = title.text;
+            if (typeof message != "undefined" && message.length) {
+                th.appendChild(document.createElement('br'));
+                th.appendChild(document.createTextNode('[' + message +']'));
+                message = "";
+            }
+            if ("width" in title)
+                th.width = title.width + "%";
+            row.appendChild(th);
+            queue.push({element: th, titles: title.children });
+        });
+    },
+    
+    _showHeader: function(suiteName, titles, message)
+    {
+        var row = document.createElement("tr");
+
+        var queue = [];
+        this._showTitles(row, queue, titles, message);
+        this.element.appendChild(row);
+
+        while (queue.length) {
+            var row = null;
+            var entries = [];
+
+            for (var i = 0, len = queue.length; i < len; ++i) {
+                var entry = queue.shift();
+
+                if (!entry.titles.length) {
+                    entries.push(entry.element);
+                    continue;
+                }
+
+                if (!row)
+                    var row = document.createElement("tr");
+
+                this._showTitles(row, queue, entry.titles, "");
+                entry.element.colSpan = entry.titles.length;
+            }
+
+            if (row) {
+                this.element.appendChild(row);
+                entries.forEach(function(entry) {
+                    ++entry.rowSpan;
+                });
+            }
+        }
+    },
+    
+    _showEmpty: function(row, testName)
+    {
+        var td = document.createElement("td");
+        row.appendChild(td);
+    },
+    
+    _showValue: function(row, testName, value)
+    {
+        var td = document.createElement("td");
+        td.textContent = value.toFixed(2);
+        row.appendChild(td);
+    },
+    
+    _showSamples: function(row, testName, axes, samples, samplingTimeOffset)
+    {
+        var td = document.createElement("td");
+        var button = document.createElement("div");
+        button.className = "small-button";
+            
+        button.addEventListener("click", function() {
+            window.showGraph(testName, axes, samples, samplingTimeOffset);
+        });
+            
+        button.textContent = "Graph...";
+        td.appendChild(button);
+        row.appendChild(td);
+    },
+    
+    _showTest: function(testName, titles, sampler, finalResults)
+    {
+        var row = document.createElement("tr");
+        var td = document.createElement("td");
+        
+        td.textContent = testName;
+        row.appendChild(td);
+        
+        var axes = [];
+        sampler.experiments.forEach(function(experiment, index) {
+            this._showValue(row, testName, experiment.mean());
+            this._showValue(row, testName, experiment.concern(Experiment.defaults.CONCERN));
+            this._showValue(row, testName, experiment.standardDeviation());
+            this._showValue(row, testName, experiment.percentage());
+            axes.push(titles[index + 1].text);
+            
+        }, this);
+
+        this._showValue(row, testName, sampler.experiments[0].score(Experiment.defaults.CONCERN));
+
+        if (finalResults)
+            this._showSamples(row, testName, axes, sampler.samples, sampler.samplingTimeOffset);
+        else
+            this._showEmpty(row, testName);
+            
+        this.element.appendChild(row);
+    },
+    
+    _showSuite: function(suite, suiteSamplers)
+    {
+        var scores = [];        
+        for (var testName in suiteSamplers) {
+            var test = testFromName(suite, testName);
+            var sampler = suiteSamplers[testName]; 
+            this._showTest(testName, suite.titles, sampler, true);
+            scores.push(sampler.experiments[0].score(Experiment.defaults.CONCERN));
+        }
+        return scores;
+    },
+    
+    showRecord: function(suite, test, sampler, message)
+    {
+        this.clear();        
+        this._showHeader("", suite.titles, message);
+        this._showTest(test.name, suite.titles, sampler, false);        
+    },
+    
+    showIterations: function(iterationsSamplers)
+    {
+        this.clear();
+
+        var scores = [];
+        var titles = null;
+        iterationsSamplers.forEach(function(suitesSamplers) {
+            for (var suiteName in suitesSamplers) {
+                var suite = suiteFromName(suiteName);
+                if (titles != suite.titles) {
+                    titles = suite.titles;
+                    this._showHeader(suiteName, titles, "");
+                }
+
+                var suiteScores = this._showSuite(suite, suitesSamplers[suiteName]);
+                scores.push.apply(scores, suiteScores);
+            }
+        }, this);
+
+        return Statistics.geometricMean(scores);
+    }
+}
diff --git a/PerformanceTests/Animometer/resources/sampler.js b/PerformanceTests/Animometer/resources/sampler.js
new file mode 100644 (file)
index 0000000..dcf9550
--- /dev/null
@@ -0,0 +1,124 @@
+var Statistics =
+{
+    sampleMean: function(numberOfSamples, sum)
+    {
+        if (numberOfSamples < 1)
+            return 0;
+        return sum / numberOfSamples;
+    },
+
+    // With sum and sum of squares, we can compute the sample standard deviation in O(1).
+    // See https://rniwa.com/2012-11-10/sample-standard-deviation-in-terms-of-sum-and-square-sum-of-samples/
+    unbiasedSampleStandardDeviation: function(numberOfSamples, sum, squareSum)
+    {
+        if (numberOfSamples < 2)
+            return 0;
+        return Math.sqrt((squareSum - sum * sum / numberOfSamples) / (numberOfSamples - 1));
+    },
+
+    geometricMean: function(values)
+    {
+        if (!values.length)
+            return 0;
+        var roots = values.map(function(value) { return  Math.pow(value, 1 / values.length); })
+        return roots.reduce(function(a, b) { return a * b; });
+    }   
+}
+
+function Experiment()
+{
+    this._init();
+    this._maxHeap = Algorithm.createMaxHeap(Experiment.defaults.CONCERN_SIZE);
+}
+
+Experiment.defaults =
+{
+    CONCERN: 5,
+    CONCERN_SIZE: 100,
+}
+
+Experiment.prototype =
+{
+    _init: function()
+    {
+        this._sum = 0;
+        this._squareSum = 0;
+        this._numberOfSamples = 0;
+    },
+    
+    // Called after a warm-up period
+    startSampling: function()
+    {
+        var mean = this.mean();
+        this._init();
+        this._maxHeap.init();
+        this.sample(mean);
+    },
+    
+    sample: function(value)
+    {
+        this._sum += value;
+        this._squareSum += value * value;
+        this._maxHeap.push(value);
+        ++this._numberOfSamples;
+    },
+    
+    mean: function()
+    {
+        return Statistics.sampleMean(this._numberOfSamples, this._sum);
+    },
+    
+    standardDeviation: function()
+    {
+        return Statistics.unbiasedSampleStandardDeviation(this._numberOfSamples, this._sum, this._squareSum);
+    },
+    
+    percentage: function()
+    {
+        var mean = this.mean();
+        return mean ? this.standardDeviation() * 100 / mean : 0;
+    },
+    
+    concern: function(percentage)
+    {
+        var size = Math.ceil(this._numberOfSamples * percentage / 100);
+        var values = this._maxHeap.values(size);
+        return values.length ? values.reduce(function(a, b) { return a + b; }) / values.length : 0;
+    },
+    
+    score: function(percentage)
+    {
+        return Statistics.geometricMean([this.mean(), Math.max(this.concern(percentage), 1)]);
+    }
+}
+
+function Sampler(count)
+{
+    this.experiments = [];
+    while (count--)
+        this.experiments.push(new Experiment());
+    this.samples = [];
+    this.samplingTimeOffset = 0;
+}
+
+Sampler.prototype =
+{
+    startSampling: function(timeOffset)
+    {
+        for (var index = 0; index < this.experiments.length; ++index)
+            this.experiments[index].startSampling();
+            
+        this.samplingTimeOffset = timeOffset / 1000;
+    },
+    
+    sample: function(timeOffset, values)
+    {
+        if (values.length < this.experiments.length)
+            throw "Not enough sample points";
+                    
+        for (var index = 0; index < this.experiments.length; ++index)
+            this.experiments[index].sample(values[index]);
+            
+        this.samples.push({ timeOffset: timeOffset / 1000, values: values });
+    }
+}
index 997937e..fec2627 100644 (file)
@@ -1,5 +1,95 @@
 2015-10-02  Said Abou-Hallawa  <sabouhallawa@apple.com>
 
+        Add shared code for a new a graphics benchmark
+        https://bugs.webkit.org/show_bug.cgi?id=149691
+
+        Reviewed by Ryosuke Niwa.
+
+        This set of classes will be shared and used by the tests and the runner
+        of a new graphics benchmark.
+
+        * Animometer/resources: Added.
+        * Animometer/resources/algorithm.js: Added.
+        (Array.prototype.swap): Swaps two elements in an array.
+        (Heap): Binary Min/Max Heap object
+        (Heap.prototype._parentIndex): Given the child node index, it returns the parent index.
+        (Heap.prototype._leftIndex): Given the parent node index, it returns the left node index.
+        (Heap.prototype._rightIndex): Given the parent node index, it returns the right node index.
+        (Heap.prototype._childIndex): Given the parent node index, it returns the child index that may violate the heap property.
+        (Heap.prototype.init): Initializes the heap state.
+        (Heap.prototype.top): Returns the value stored at the top of the heap.
+        (Heap.prototype.push): Pushes a new node at the top of the heap.
+        (Heap.prototype.pop): Extracts the top node of the heap.
+        (Heap.prototype._bubble): Fixes the heap property by moving upward.
+        (Heap.prototype._sink): Fixes the heap property by moving downward.
+        (Heap.prototype.str): Prints the nodes of the heap to a string.
+        (Heap.prototype.values): Returns the last "size" heap elements values.
+
+        (Algorithm.createMinHeap): Creates a size-bounded min-heap object.
+        (Algorithm.createMaxHeap): Creates a size-bounded max-heap object.
+        
+        * Animometer/resources/extensions.js: Added.
+        (Point): Point object but can be used as size also.
+        (Point.pointOnCircle): Given, the radius of the circle and the angle of the point, it returns a point object.
+        (Point.pointOnEllipse): Given, the radiuses of the ellipse and the angle of the point, it returns a point object.
+        (Point.prototype.get width): Should be called when the point is used as size.
+        (Point.prototype.get height): Should be called when the point is used as size.
+        (Point.prototype.get center): Should be called when the point is used as size.
+        (Point.prototype.add): Returns a new point = this + other.
+        (Point.prototype.subtract): Returns a new point = this - other.
+        (Point.prototype.multiply): Returns a new point = this * other.
+        (Point.prototype.move): Moves the point in a given direction, velocity, time period.
+
+        (Insets): Represents borders of a container.
+        (Insets.prototype.get width): Returns left + right.
+        (Insets.prototype.get height): Returns top + bottom.
+
+        (SimplePromise):
+        (SimplePromise.prototype.then):
+        (SimplePromise.prototype.resolve):
+        Moved from Animometer/runner/resources/benchmark-runner.js since tests also need it.
+
+        (Options): Benchmark running options as they are set by the user.
+
+        (ProgressBar): Manages a progress bar element. The progress bar is divided into equal length ranges.
+        (ProgressBar.prototype._progressToPercent): Converts the progress into a percentage.
+        (ProgressBar.prototype.incRange): Moves to the next range (a range is the running time of a single test).
+        (ProgressBar.prototype.setPos): Draws the current progress in the current range.
+
+        (RecordTable): Shows the results of running a benchmark in a tabular form.
+        (RecordTable.prototype.clear): Clears the results table.
+        (RecordTable.prototype._showTitles): Shows the header titles and appends the sub-titles to a queue.
+        (RecordTable.prototype._showHeader): Shows the table header titles.
+        (RecordTable.prototype._showEmpty): Shows an empty table cell.
+        (RecordTable.prototype._showValue): Shows a number value in the results table.
+        (RecordTable.prototype._showSamples): Shows a button for the sampled data graph.
+        (RecordTable.prototype._showTest): Shows the results of a single test.
+        (RecordTable.prototype._showSuite): Shows the results of a single suite.
+        (RecordTable.prototype.showRecord): Shows a single iteration for a single test.
+        (RecordTable.prototype.showIterations): Shows the results of all the suites of the iterations. 
+
+        * Animometer/resources/sampler.js: Added.
+        (Statistics.sampleMean): Returns the sample mean.
+        (Statistics.unbiasedSampleStandardDeviation): Returns the unbiased sample variance (i.e. with Bessel's correction)
+        (Statistics.geometricMean): Returns the geometric mean.
+
+        (Experiment): Represents a sampling experiment. 
+        (Experiment.prototype._init): Called when the object is created and when startSampling() is called.
+        (Experiment.prototype.startSampling): Called after warmup period. Restarts collecting sampled data points.
+        (Experiment.prototype.sample): Add a new data point.
+        (Experiment.prototype.mean): Returns the sample mean for the current sampled data points.
+        (Experiment.prototype.standardDeviation): Returns the sample standard deviation for the current sampled data points.
+        (Experiment.prototype.percentage): Returns the percentage of the standard deviation divided to the mean.
+        (Experiment.prototype.confidenceIntervalDelta): Calculates the confidence delta for the current sampled data given a confidence level.
+        (Experiment.prototype.concern): Returns the average of the worst given percentage from the sampled data.
+        (Experiment.prototype.score): Returns a score for the sampled data. It is the geometric mean of sampleMean and concern.
+
+        (Sampler): Represents a compound experiment. It manages sampling multiple data points at the same time offset.
+        (Sampler.prototype.startSampling): Called after warming up period. Restarts collecting sampled data points.
+        (Sampler.prototype.sample): Add a new data vector at a given time offset.
+        
+2015-10-02  Said Abou-Hallawa  <sabouhallawa@apple.com>
+
         Add the test runner for a new a graphics benchmark
         https://bugs.webkit.org/show_bug.cgi?id=149683