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
--- /dev/null
+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; });
+ }
+}
--- /dev/null
+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);
+ }
+}
--- /dev/null
+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 });
+ }
+}
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