runner.js in performance tests should define a class
authorrniwa@webkit.org <rniwa@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 31 Jan 2012 20:05:37 +0000 (20:05 +0000)
committerrniwa@webkit.org <rniwa@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 31 Jan 2012 20:05:37 +0000 (20:05 +0000)
https://bugs.webkit.org/show_bug.cgi?id=77074

Reviewed by Eric Seidel.

Wrap all functions in runner.js by PerfTestRunner and update tests that runner.js accordingly.
Also replace compute* functions in runner.js by more robust code from dom-perf.js.

* Bindings/event-target-wrapper.html:
* DOM/DOMTable.html:
* DOM/resources/dom-perf.js:
(BenchmarkSuite.prototype.RunSingle):
(runBenchmarkSuite):
* Mutation/append-child-deep.html:
* Mutation/append-child.html:
* Mutation/inner-html.html:
* Mutation/remove-child-deep.html:
* Mutation/remove-child.html:
* Parser/html-parser.html:
* Parser/html5-full-render.html:
* Parser/simple-url.html:
* Parser/tiny-innerHTML.html:
* Parser/url-parser.html:
* Parser/xml-parser.html:
* resources/runner.js:
(PerfTestRunner.log):
(PerfTestRunner.logInfo):
(PerfTestRunner.loadFile):
(PerfTestRunner.computeStatistics):
(PerfTestRunner.logStatistics):
(PerfTestRunner._runLoop.else):
(PerfTestRunner._runLoop):
(PerfTestRunner._runner):

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

17 files changed:
PerformanceTests/Bindings/event-target-wrapper.html
PerformanceTests/ChangeLog
PerformanceTests/DOM/DOMTable.html
PerformanceTests/DOM/resources/dom-perf.js
PerformanceTests/Dromaeo/resources/dromaeorunner.js
PerformanceTests/Mutation/append-child-deep.html
PerformanceTests/Mutation/append-child.html
PerformanceTests/Mutation/inner-html.html
PerformanceTests/Mutation/remove-child-deep.html
PerformanceTests/Mutation/remove-child.html
PerformanceTests/Parser/html-parser.html
PerformanceTests/Parser/html5-full-render.html
PerformanceTests/Parser/simple-url.html
PerformanceTests/Parser/tiny-innerHTML.html
PerformanceTests/Parser/url-parser.html
PerformanceTests/Parser/xml-parser.html
PerformanceTests/resources/runner.js

index f228525..a530a11 100644 (file)
@@ -13,12 +13,12 @@ link.addEventListener('click', function(event) {
     window.evt = event;
     event.preventDefault();
 
-    start(20, function() {
+    PerfTestRunner.run(function () {
         var e = window.evt;
         for (var x = 0; x < kIteratonsPerTest; x++) {
             e.target;
         }
-    }, 10);
+    });
 
     return false;
 }, false);
index ccebae4..ac47209 100644 (file)
@@ -1,3 +1,39 @@
+2012-01-31  Ryosuke Niwa  <rniwa@webkit.org>
+
+        runner.js in performance tests should define a class
+        https://bugs.webkit.org/show_bug.cgi?id=77074
+
+        Reviewed by Eric Seidel.
+
+        Wrap all functions in runner.js by PerfTestRunner and update tests that runner.js accordingly.
+        Also replace compute* functions in runner.js by more robust code from dom-perf.js.
+
+        * Bindings/event-target-wrapper.html:
+        * DOM/DOMTable.html:
+        * DOM/resources/dom-perf.js:
+        (BenchmarkSuite.prototype.RunSingle):
+        (runBenchmarkSuite):
+        * Mutation/append-child-deep.html:
+        * Mutation/append-child.html:
+        * Mutation/inner-html.html:
+        * Mutation/remove-child-deep.html:
+        * Mutation/remove-child.html:
+        * Parser/html-parser.html:
+        * Parser/html5-full-render.html:
+        * Parser/simple-url.html:
+        * Parser/tiny-innerHTML.html:
+        * Parser/url-parser.html:
+        * Parser/xml-parser.html:
+        * resources/runner.js:
+        (PerfTestRunner.log):
+        (PerfTestRunner.logInfo):
+        (PerfTestRunner.loadFile):
+        (PerfTestRunner.computeStatistics):
+        (PerfTestRunner.logStatistics):
+        (PerfTestRunner._runLoop.else):
+        (PerfTestRunner._runLoop):
+        (PerfTestRunner._runner):
+
 2012-01-31  Hajime Morrita  <morrita@chromium.org>
 
         [PerformanceTests] Add landing html for Dromaeo dom-query test
index 3badc8a..5db09e8 100644 (file)
@@ -6,6 +6,9 @@
 <script type="text/javascript" src="../resources/runner.js"></script>
 <script type="text/javascript" src="resources/dom-perf.js"></script>
 <script type="text/javascript" src="resources/dom-perf/domtable.js"></script>
-<script> runBenchmarkSuite(DOMTableTest); </script>
+<script>
+runBenchmarkSuite(DOMTableTest, 10);
+// runCount = 10 since this test is very slow (~12m per run on Core i5 2.53Hz MacBookPro)
+</script>
 </body>
 </html>
index c892836..a5ee6df 100644 (file)
@@ -210,58 +210,16 @@ function BenchmarkResult(benchmark, times, error, benchmarkContent) {
         }
     }
     if (!error) {
-        var data = times.slice();
-        var count = data.length;
-
-        // Sort the data so that all seemingly
-        // insignificant values such as 0.000000003 will
-        // be at the beginning of the array and their
-        // contribution to the mean and variance of the
-        // data will not be lost because of the precision
-        // of the CPU.
-        data.sort(BenchmarkSuite.Math.ascend);
-
-        // Since the data is now sorted, the minimum value
-        // is at the beginning of the array, the median
-        // value is in the middle of the array, and the
-        // maximum value is at the end of the array.
-        this.min = data[0];
-        var middle = Math.floor(data.length / 2);
-        if ((data.length % 2) !== 0)
-            this.median = data[middle];
-        else
-            this.median = (data[middle - 1] + data[middle]) / 2;
-        this.max = data[data.length - 1];
-
-        // Compute the mean and variance using a
-        // numerically stable algorithm.
-        var sqsum = 0;
-        this.mean = data[0];
-        var nZeros = 0;
-        for (var i = 1; i < data.length; ++i) {
-            var x = data[i];
-            var delta = x - this.mean;
-            var sweep = i + 1.0;
-            this.mean += delta / sweep;
-            sqsum += delta * delta * (i / sweep);
-        }
-        this.sum = this.mean * count;
-        this.variance = sqsum / count;
-
-        this.sdev = Math.sqrt(this.variance);
-        this.score = 1000 / this.mean;
+        var statistics = PerfTestRunner.computeStatistics(times);
+        this.min = statistics.min;
+        this.max = statistics.max;
+        this.median = statistics.median;
+        this.mean = statistics.mean;
+        this.sum = statistics.sum;
+        this.variance = statistics.variance;
+        this.stdev = statistics.stdev;
     }
 
-    this.toString = function() {
-        var s =
-          " min: " + this.min + 
-          " max: " + this.max + 
-          " mean: " + this.mean + 
-          " median: " + this.median + 
-          " sdev: " + this.sdev;
-        return s;
-    };
-
     // Convert results to numbers. Used by the geometric mean computation.
     this.valueOf = function() { return this.time; };
 }
@@ -284,7 +242,7 @@ BenchmarkSuite.prototype.RunSingle = function(benchmark, times) {
             this.benchmarkContentHolder.removeChild(this.benchmarkContent);
         this.benchmarkContent = this.benchmarkContentProto.cloneNode();
         this.benchmarkContentHolder.appendChild(this.benchmarkContent);
-        gc();
+        PerfTestRunner.gc();
 
         try {
             if (benchmark.setup) {
@@ -384,8 +342,8 @@ BenchmarkSuite.prototype.generateLargeTree = function() {
     return this.generateDOMTree(26, 26, 4);
 };
 
-function runBenchmarkSuite(suite) {
-    startCustom(20, function () {
+function runBenchmarkSuite(suite, runCount) {
+    PerfTestRunner.run(function () {
         var container = document.getElementById('container');
         var content = document.getElementById('benchmark_content');
         suite.benchmarkContentHolder = container;
@@ -399,7 +357,7 @@ function runBenchmarkSuite(suite) {
                 totalMeanTime += result.mean;
         }
         return totalMeanTime;
-    }, function () {
+    }, 1, runCount || 20, function () {
         var container = document.getElementById('container');
         if (container.firstChild)
             container.removeChild(container.firstChild);
index aac3c56..d6b833d 100644 (file)
@@ -3,11 +3,11 @@
          baseURL: "./resources/dromaeo/web/index.html",
 
          computeScores: function (results) {
-             var avg = 0, min = 0, max = 0, stdev = 0, varsum = 0;
+             var mean = 0, min = 0, max = 0, stdev = 0, varsum = 0;
 
              for (var i = 0; i < results.length; ++i) {
                  var item = results[i];
-                 avg += item.mean;
+                 mean += item.mean;
                  min += item.min;
                  max += item.max;
                  varsum += item.deviation * item.deviation;
@@ -15,7 +15,7 @@
 
              return {
                  median: 0,
-                 avg: avg,
+                 mean: mean,
                  min: min,
                  max: max,
                  stdev: Math.sqrt(varsum)
@@ -61,7 +61,7 @@
 
          teardown: function(data) {
              var scores = DRT.computeScores(data.result);
-             printStatistics(scores, DRT.log);
+             PerfTestRunner.printStatistics(scores, DRT.log);
              window.setTimeout(function() {
                  if (window.layoutTestController)
                      layoutTestController.notifyDone();
index bb47d9d..783d6f8 100644 (file)
@@ -31,7 +31,7 @@ function listener(mutations) {
     if (start) {
         var time = Date.now() - start;
         times.push(time);
-        log(time);
+        PerfTestRunner.log(time);
     }
     if (numRuns-- >= 0) {
         runAgain();
@@ -44,19 +44,19 @@ function listener(mutations) {
         for (var i = 0; i < elems.length; ++i)
             node.appendChild(elems[i]);
     } else {
-        logStatistics(times);
+        PerfTestRunner.logStatistics(times);
         if (!observing) {
             observing = true;
             resetState();
-            log('\n------------\n');
-            log('Running ' + numRuns + ' times with observation');
+            PerfTestRunner.log('\n------------\n');
+            PerfTestRunner.log('Running ' + numRuns + ' times with observation');
             setTimeout(runAgain, 0);
         }
     }
 }
 
 resetState();
-log('Running ' + numRuns + ' times without observation');
+PerfTestRunner.log('Running ' + numRuns + ' times without observation');
 window.addEventListener('load', runAgain);
 </script>
 </body>
index 8b42b72..3a128b1 100644 (file)
@@ -28,7 +28,7 @@ function listener(mutations) {
     if (start) {
         var time = Date.now() - start;
         times.push(time);
-        log(time);
+        PerfTestRunner.log(time);
     }
     if (numRuns-- >= 0) {
         runAgain();
@@ -41,19 +41,19 @@ function listener(mutations) {
         for (var i = 0; i < elems.length; ++i)
             sandbox.appendChild(elems[i]);
     } else {
-        logStatistics(times);
+        PerfTestRunner.logStatistics(times);
         if (!observing) {
             observing = true;
             resetState();
-            log('\n------------\n');
-            log('Running ' + numRuns + ' times with observation');
+            PerfTestRunner.log('\n------------\n');
+            PerfTestRunner.log('Running ' + numRuns + ' times with observation');
             setTimeout(runAgain, 0);
         }
     }
 }
 
 resetState();
-log('Running ' + numRuns + ' times without observation');
+PerfTestRunner.log('Running ' + numRuns + ' times without observation');
 window.addEventListener('load', runAgain);
 </script>
 </body>
index 15f6085..3fa45a3 100644 (file)
@@ -28,7 +28,7 @@ function listener(mutations) {
     if (start) {
         var time = Date.now() - start;
         times.push(time);
-        log(time);
+        PerfTestRunner.log(time);
     }
     if (numRuns-- >= 0) {
         runAgain();
@@ -36,20 +36,20 @@ function listener(mutations) {
         for (var i = 0; i < 100; ++i)
             sandbox.innerHTML = html;
     } else {
-        logStatistics(times);
+        PerfTestRunner.logStatistics(times);
         if (!observing) {
             observer.observe(sandbox, {childList: true});
             observing = true;
             resetState();
-            log('\n------------\n');
-            log('Running ' + numRuns + ' times with observation');
+            PerfTestRunner.log('\n------------\n');
+            PerfTestRunner.log('Running ' + numRuns + ' times with observation');
             setTimeout(runAgain, 0);
         }
     }
 }
 
 resetState();
-log('Running ' + numRuns + ' times without observation');
+PerfTestRunner.log('Running ' + numRuns + ' times without observation');
 window.addEventListener('load', runAgain);
 </script>
 </body>
index 72eada4..ffba075 100644 (file)
@@ -36,7 +36,7 @@ function listener(mutations) {
     if (start) {
         var time = Date.now() - start;
         times.push(time);
-        log(time);
+        PerfTestRunner.log(time);
     }
     if (numRuns-- >= 0) {
         runAgain();
@@ -48,19 +48,19 @@ function listener(mutations) {
         while (node.firstChild)
             node.removeChild(node.firstChild);
     } else {
-        logStatistics(times);
+        PerfTestRunner.logStatistics(times);
         if (!observing) {
             observing = true;
             resetState();
-            log('\n------------\n');
-            log('Running ' + numRuns + ' times with observation');
+            PerfTestRunner.log('\n------------\n');
+            PerfTestRunner.log('Running ' + numRuns + ' times with observation');
             setTimeout(runAgain, 0);
         }
     }
 }
 
 resetState();
-log('Running ' + numRuns + ' times without observation');
+PerfTestRunner.log('Running ' + numRuns + ' times without observation');
 window.addEventListener('load', runAgain);
 </script>
 </body>
index 22f80e1..182f28b 100644 (file)
@@ -33,7 +33,7 @@ function listener(mutations) {
     if (start) {
         var time = Date.now() - start;
         times.push(time);
-        log(time);
+        PerfTestRunner.log(time);
     }
     if (numRuns-- >= 0) {
         runAgain();
@@ -45,20 +45,20 @@ function listener(mutations) {
         while (sandbox.firstChild)
             sandbox.removeChild(sandbox.firstChild);
     } else {
-        logStatistics(times);
+        PerfTestRunner.logStatistics(times);
         if (!observing) {
             observer.observe(sandbox, {childList: true});
             observing = true;
             resetState();
-            log('\n------------\n');
-            log('Running ' + numRuns + ' times with observation');
+            PerfTestRunner.log('\n------------\n');
+            PerfTestRunner.log('Running ' + numRuns + ' times with observation');
             setTimeout(runAgain, 0);
         }
     }
 }
 
 resetState();
-log('Running ' + numRuns + ' times without observation');
+PerfTestRunner.log('Running ' + numRuns + ' times without observation');
 window.addEventListener('load', runAgain);
 </script>
 </body>
index e8b3fe2..a016ffa 100644 (file)
@@ -3,9 +3,9 @@
 <pre id="log"></pre>
 <script src="../resources/runner.js"></script>
 <script>
-var spec = loadFile("resources/html5.html");
+var spec = PerfTestRunner.loadFile("resources/html5.html");
 
-start(10, function() {
+PerfTestRunner.run(function() {
     var iframe = document.createElement("iframe");
     iframe.style.display = "none";  // Prevent creation of the rendering tree, so we only test HTML parsing.
     iframe.sandbox = '';  // Prevent external script loads which could cause write() to return before completing the parse.
index bda2fe6..78962ec 100644 (file)
@@ -45,12 +45,12 @@ function loadChunkedSpecIntoIframe(iframe) {
 // Running from the onload callback just makes the UI nicer as it shows the logs before starting the test.
 window.onload = function() {
     // Depending on the chosen chunk size, iterations can take over 60s to run on a fast machine, so we only run 2.
-    start(2, function() {
+    PerfTestRunner.run(function() {
         var iframe = document.createElement("iframe");
         document.body.appendChild(iframe);
         loadChunkedSpecIntoIframe(iframe);
         document.body.removeChild(iframe);
-    }, 1); // We only loop once for each run, again because this test is so slow.
+    }, 1, 2); // We only loop once for each run, again because this test is so slow.
 }
 
 </script>
index f57e0f6..7dbf992 100644 (file)
@@ -4,7 +4,7 @@
 <script src="../resources/runner.js"></script>
 <script>
 var anchor = document.createElement("a");
-start(20, function() {
+PerfTestRunner.run(function() {
     for (var x = 0; x < 200000; x++) {
         anchor.href = "http://www.apple.com/"
     }
index 51d896c..4abdec6 100644 (file)
@@ -3,7 +3,7 @@
 <pre id="log"></pre>
 <script src="../resources/runner.js"></script>
 <script>
-start(20, function() {
+PerfTestRunner.run(function() {
     var testDiv = document.createElement("div");
     testDiv.style.display = "none";
     document.body.appendChild(testDiv);
index 03bfe3f..0483d67 100644 (file)
@@ -3,10 +3,10 @@
 <pre id="log"></pre>
 <script src="../resources/runner.js"></script>
 <script>
-var urls = loadFile("resources/final-url-en").split("\n");
+var urls = PerfTestRunner.loadFile("resources/final-url-en").split("\n");
 var anchor = document.createElement("a");
 
-start(20, function() {
+PerfTestRunner.run(function() {
     for (var x = 0; x < urls.length; x++) {
         anchor.href = urls[x];
     }
index 7933d7d..91c7b85 100644 (file)
@@ -12,7 +12,7 @@ for (var i = 0; i < 0x7FFF; ++i)
 xmlArray.push('</root>')
 var xmlData = xmlArray.join('');
 
-start(20, function() {
+PerfTestRunner.run(function() {
     domParser.parseFromString(xmlData, "text/xml");
 });
 </script>
index 24fff9a..18503ae 100644 (file)
@@ -1,91 +1,72 @@
-function log(text) {
+
+var PerfTestRunner = {};
+
+PerfTestRunner.log = function (text) {
     document.getElementById("log").innerHTML += text + "\n";
     window.scrollTo(0, document.body.height);
 }
 
-function logInfo(text) {
+PerfTestRunner.logInfo = function (text) {
     if (!window.layoutTestController)
-        log(text);
+        this.log(text);
 }
 
-function loadFile(path) {
+PerfTestRunner.loadFile = function (path) {
     var xhr = new XMLHttpRequest();
     xhr.open("GET", path, false);
     xhr.send(null);
     return xhr.responseText;
 }
 
-var runCount = -1;
-var runFunction = function() {};
-var completedRuns = -1; // Discard the any runs < 0.
-var times = [];
-
-function computeAverage(values) {
-    var sum = 0;
-    for (var i = 0; i < values.length; i++)
-        sum += values[i];
-    return sum / values.length;
-}
-
-function computeMax(values) {
-    var max = values.length ? values[0] : 0;
-    for (var i = 1; i < values.length; i++) {
-        if (max < values[i])
-            max = values[i];
+PerfTestRunner.computeStatistics = function (times) {
+    var data = times.slice();
+
+    // Add values from the smallest to the largest to avoid the loss of significance
+    data.sort();
+
+    var middle = Math.floor(data.length / 2);
+    var result = {
+        min: data[0],
+        max: data[data.length - 1],
+        median: data.length % 2 ? data[middle] : (data[middle - 1] + data[middle]) / 2,
+    };
+
+    // Compute the mean and variance using a numerically stable algorithm.
+    var squareSum = 0;
+    result.mean = data[0];
+    result.sum = data[0];
+    for (var i = 1; i < data.length; ++i) {
+        var x = data[i];
+        var delta = x - result.mean;
+        var sweep = i + 1.0;
+        result.mean += delta / sweep;
+        result.sum += x;
+        squareSum += delta * delta * (i / sweep);
     }
-    return max;
-}
+    result.variance = squareSum / data.length;
+    result.stdev = Math.sqrt(result.variance);
 
-function computeMedian(values) {
-    values.sort(function(a, b) { return a - b; });
-    var len = values.length;
-    if (len % 2)
-        return values[(len-1)/2];
-    return (values[len/2-1] + values[len/2]) / 2;
+    return result;
 }
 
-function computeMin(values) {
-    var min = values.length ? values[0] : 0;
-    for (var i = 1; i < values.length; i++) {
-        if (min > values[i])
-            min = values[i];
-    }
-    return min;
-}
-
-function computeStdev(values) {
-    var average = computeAverage(values);
-    var sumOfSquaredDeviations = 0;
-    for (var i = 0; i < values.length; ++i) {
-        var deviation = values[i] - average;
-        sumOfSquaredDeviations += deviation * deviation;
-    }
-    return Math.sqrt(sumOfSquaredDeviations / values.length);
+PerfTestRunner.logStatistics = function (times) {
+    this.log("");
+    var statistics = this.computeStatistics(times);
+    this.printStatistics(statistics, this.log);
 }
 
-function printStatistics(stats, printFunction)
-{
+PerfTestRunner.printStatistics = function (statistics, printFunction) {
     printFunction("");
-    printFunction("avg " + stats.avg);
-    printFunction("median " + stats.median);
-    printFunction("stdev " + stats.stdev);
-    printFunction("min " + stats.min);
-    printFunction("max " + stats.max);
+    printFunction("avg " + statistics.mean);
+    printFunction("median " + statistics.median);
+    printFunction("stdev " + statistics.stdev);
+    printFunction("min " + statistics.min);
+    printFunction("max " + statistics.max);
 }
 
-function logStatistics(times) {
-    printStatistics({
-        avg: computeAverage(times),
-        median: computeMedian(times),
-        stdev: computeStdev(times),
-        min: computeMin(times),
-        max: computeMax(times)
-    }, log);
-}
-
-function gc() {
+PerfTestRunner.gc = function () {
     if (window.GCController)
-        GCController.collect();
+        window.GCController.collect();
     else {
         function gcRec(n) {
             if (n < 1)
@@ -99,57 +80,55 @@ function gc() {
     }
 }
 
-function runLoop()
-{
-    if (window.completedRuns < window.runCount) {
-        gc();
-        window.setTimeout(run, 0);
+PerfTestRunner._runLoop = function () {
+    if (this._completedRuns < this._runCount) {
+        this.gc();
+        window.setTimeout(function () { PerfTestRunner._runner(); }, 0);
     } else {
-        logStatistics(times);
-        window.doneFunction();
+        this.logStatistics(this._times);
+        this._doneFunction();
         if (window.layoutTestController)
             layoutTestController.notifyDone();
     }
 }
 
-function run() {
-    if (window.customRunFunction)
-        var time = window.customRunFunction();
-    else {
-        var start = new Date();
-        for (var i = 0; i < window.loopsPerRun; ++i)
-            window.runFunction();
-        var time = new Date() - start;
-    }
+PerfTestRunner._runner = function () {
+    var start = Date.now();
+    var totalTime = 0;
 
-    window.completedRuns++;
-    if (window.completedRuns <= 0) {
-        log("Ignoring warm-up run (" + time + ")");
-    } else {
-        times.push(time);
-        log(time);
+    for (var i = 0; i < this._loopsPerRun; ++i) {
+        var returnValue = this._runFunction.call(window);
+        if (returnValue - 0 === returnValue) {
+            if (returnValue <= 0)
+                this.log("runFunction returned a non-positive value: " + returnValue);
+            totalTime += returnValue;
+        }
     }
-    runLoop()
-}
 
-function start(runCount, runFunction, loopsPerRun, doneFunction) {
-    window.runCount = runCount;
-    window.runFunction = runFunction;
-    window.loopsPerRun = loopsPerRun || 10;
-    window.doneFunction = doneFunction || function() {};
+    // Assume totalTime can never be zero when _runFunction returns a number.
+    var time = totalTime ? totalTime : Date.now() - start;
 
-    log("Running " + runCount + " times");
-    runLoop();
+    this._completedRuns++;
+    if (this._completedRuns <= 0)
+        this.log("Ignoring warm-up run (" + time + ")");
+    else {
+        this._times.push(time);
+        this.log(time);
+    }
+    this._runLoop();
 }
 
-function startCustom(runCount, customRunFunction, doneFunction) {
-    window.runCount = runCount;
-    window.customRunFunction = customRunFunction;
-    window.loopsPerRun = 1;
-    window.doneFunction = doneFunction || function() {};
+PerfTestRunner.run = function (runFunction, loopsPerRun, runCount, doneFunction) {
+    this._runFunction = runFunction;
+    this._loopsPerRun = loopsPerRun || 10;
+    this._runCount = runCount || 20;
+    this._doneFunction = doneFunction || function () {};
+    this._completedRuns = -1;
+    this.customRunFunction = null;
+    this._times = [];
 
-    log("Running " + runCount + " times");
-    runLoop();
+    this.log("Running " + this._runCount + " times");
+    this._runLoop();
 }
 
 if (window.layoutTestController) {