Cache some of the TestFailures page's data in localStorage
authoraroben@apple.com <aroben@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 23 Jun 2011 19:49:25 +0000 (19:49 +0000)
committeraroben@apple.com <aroben@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 23 Jun 2011 19:49:25 +0000 (19:49 +0000)
This makes reloading TestFailures much faster. Right now we only store the number of failing
tests and the list of failing tests for each build. We may choose to store more later, but
it's easy to run up against quota limits.

Fixes <http://webkit.org/b/61520> TestFailures page should take advantage of LocalStorage
APIs (or similar) to improve loading performance

Reviewed by David Kilzer.

* BuildSlaveSupport/build.webkit.org-config/public_html/TestFailures/Builder.js:
(Builder.prototype.getNumberOfFailingTests): Changed to use the new PersistentCache object.
(Builder.prototype.startFetchingBuildHistory): Changed to pass whether or not we're still
fetching data to the callback.
(Builder.prototype._getFailingTests): Changed to use the new PersistentCache object. We now
store the tests in the cache just before calling the callback. (The previous code in this
function relied on being able to modify the tests object after storing it in the cache and
having the cached version be updated. This worked while it was a non-serialized cache, but
PersistentCache uses serialization.)

* BuildSlaveSupport/build.webkit.org-config/public_html/TestFailures/PersistentCache.js: Added.
(PersistentCache): This object wraps localStorage. It uses JSON to serialize/deserialize
values, and stores the date that each value was initially stored along with it. This is
later used for pruning the cache.
(PersistentCache.contains): Checks whether the key exists in localStorage.
(PersistentCache.get): Fetch the string we stored in localStorage and extract the original
value out of it.
(PersistentCache.set): Serialize the value, add the date to it, and store it in
localStorage. If this fails due to quota limits, empty the whole cache and try again.
(PersistentCache.prune): Delete any cached data that is deemed old enough.
(PersistentCache._addDateToJSONString): Prepend the current date to the string.
(PersistentCache._emptyCache): Delete everything from localStorage.
(PersistentCache._parseDateAndJSONFromString): Split apart the date and the JSON string and
return them.

* BuildSlaveSupport/build.webkit.org-config/public_html/TestFailures/ViewController.js:
(ViewController.prototype._displayBuilder): Updated for change to callback signature. When
we finish fetching data, prune the PersistentCache. While I was here I also fixed a bug
where we'd never show the new bug link for tests for which we couldn't determine a passing
revision.

* BuildSlaveSupport/build.webkit.org-config/public_html/TestFailures/index.html: Pull in
PersistentCache.js.

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

Tools/BuildSlaveSupport/build.webkit.org-config/public_html/TestFailures/Builder.js
Tools/BuildSlaveSupport/build.webkit.org-config/public_html/TestFailures/PersistentCache.js [new file with mode: 0644]
Tools/BuildSlaveSupport/build.webkit.org-config/public_html/TestFailures/ViewController.js
Tools/BuildSlaveSupport/build.webkit.org-config/public_html/TestFailures/index.html
Tools/ChangeLog

index 97d9ce05f829db367b894d0f179077ea0a7eff98..f0e01ee1a7f294e5eb0ff0bfaf4cfaa42951608e 100644 (file)
@@ -91,9 +91,9 @@ Builder.prototype = {
     },
 
     getNumberOfFailingTests: function(buildNumber, callback) {
-        var cacheKey = 'getNumberOfFailingTests_' + buildNumber;
-        if (cacheKey in this._cache) {
-            callback(this._cache[cacheKey]);
+        var cacheKey = this.name + '_getNumberOfFailingTests_' + buildNumber;
+        if (PersistentCache.contains(cacheKey)) {
+            callback(PersistentCache.get(cacheKey));
             return;
         }
 
@@ -101,22 +101,22 @@ Builder.prototype = {
         self._getBuildJSON(buildNumber, function(data) {
             var layoutTestStep = data.steps.findFirst(function(step) { return step.name === 'layout-test'; });
             if (!layoutTestStep) {
-                self._cache[cacheKey] = -1;
-                callback(self._cache[cacheKey], false);
+                PersistentCache.set(cacheKey, -1);
+                callback(PersistentCache.get(cacheKey), false);
                 return;
             }
 
             if (!('isStarted' in layoutTestStep)) {
                 // run-webkit-tests never even ran.
-                self._cache[cacheKey] = -1;
-                callback(self._cache[cacheKey], false);
+                PersistentCache.set(cacheKey, -1);
+                callback(PersistentCache.get(cacheKey), false);
                 return;
             }
 
             if (!('results' in layoutTestStep) || layoutTestStep.results[0] === 0) {
                 // All tests passed.
-                self._cache[cacheKey] = 0;
-                callback(self._cache[cacheKey], false);
+                PersistentCache.set(cacheKey, -1);
+                callback(PersistentCache.get(cacheKey), false);
                 return;
             }
 
@@ -134,7 +134,7 @@ Builder.prototype = {
                 return sum + parseInt(match[1], 10);
             }, 0);
 
-            self._cache[cacheKey] = failureCount;
+            PersistentCache.set(cacheKey, failureCount);
             callback(failureCount, tooManyFailures);
         });
     },
@@ -174,11 +174,11 @@ Builder.prototype = {
         self._getBuildNames(function(buildNames) {
             function inner(buildIndex) {
                 self._incorporateBuildHistory(buildNames, buildIndex, history, function(callAgain) {
-                    callback(history);
-                    if (!callAgain)
-                        return;
                     var nextIndex = buildIndex + 1;
                     if (nextIndex >= buildNames.length)
+                        callAgain = false;
+                    callback(history, callAgain);
+                    if (!callAgain)
                         return;
                     setTimeout(function() { inner(nextIndex) }, 0);
                 });
@@ -235,14 +235,13 @@ Builder.prototype = {
     },
 
     _getFailingTests: function(buildName, callback, errorCallback) {
-        var cacheKey = '_getFailingTests_' + buildName;
-        if (cacheKey in this._cache) {
-            callback(this._cache[cacheKey]);
+        var cacheKey = this.name + '__getFailingTests_' + buildName;
+        if (PersistentCache.contains(cacheKey)) {
+            callback(PersistentCache.get(cacheKey));
             return;
         }
 
         var tests = {};
-        this._cache[cacheKey] = tests;
 
         var buildNumber = this.buildbot.parseBuildName(buildName).buildNumber;
 
@@ -250,12 +249,14 @@ Builder.prototype = {
         self.getNumberOfFailingTests(buildNumber, function(failingTestCount, tooManyFailures) {
             if (failingTestCount < 0) {
                 // The number of failing tests couldn't be determined.
+                PersistentCache.set(cacheKey, tests);
                 errorCallback(tests, tooManyFailures);
                 return;
             }
 
             if (!failingTestCount) {
                 // All tests passed.
+                PersistentCache.set(cacheKey, tests);
                 callback(tests, tooManyFailures);
                 return;
             }
@@ -291,10 +292,12 @@ Builder.prototype = {
                     tests[name] = 'webprocess crash';
                 });
 
+                PersistentCache.set(cacheKey, tests);
                 callback(tests, tooManyFailures);
             },
             function(xhr) {
                 // We failed to fetch results.html. run-webkit-tests must have aborted early.
+                PersistentCache.set(cacheKey, tests);
                 errorCallback(tests, tooManyFailures);
             });
         });
diff --git a/Tools/BuildSlaveSupport/build.webkit.org-config/public_html/TestFailures/PersistentCache.js b/Tools/BuildSlaveSupport/build.webkit.org-config/public_html/TestFailures/PersistentCache.js
new file mode 100644 (file)
index 0000000..176c90c
--- /dev/null
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2011 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+var PersistentCache = {
+    contains: function(key) {
+        return key in localStorage;
+    },
+
+    get: function(key) {
+        var string = localStorage[key];
+        if (!string)
+            return string;
+
+        // FIXME: We could update the date stored with the value here to make this more of an MRU
+        // cache (instead of most-recently-stored), but that would result in extra disk access that
+        // might not be so great.
+        return JSON.parse(this._parseDateAndJSONFromString(string).json);
+    },
+
+    set: function(key, value) {
+        try {
+            localStorage[key] = this._addDateToJSONString(JSON.stringify(value));
+        } catch (e) {
+            if (e.code !== 22) // QUOTA_EXCEEDED_ERR
+                throw e;
+
+            // We've run out of space in localStorage. Let's just throw away everything and try
+            // again.
+            this._emptyCache();
+            this.set(key, value);
+        }
+    },
+
+    prune: function() {
+        var now = Date.now();
+        for (var key in localStorage) {
+            var date = this._parseDateAndJSONFromString(localStorage[key]).date;
+            if (now - date <= this._dataAgeLimitMS)
+                continue;
+            delete localStorage[key];
+        }
+
+        this.set(this._lastPruneDateKey, now);
+    },
+
+    _addDateToJSONString: function(jsonString) {
+        return Date.now() + this._dateAndJSONSeparator + jsonString;
+    },
+
+    _dataAgeLimitMS: 1000 * 60 * 60 * 24 * 1.1, // Just over one day
+
+    _dateAndJSONSeparator: ': ',
+
+    _emptyCache: function() {
+        for (var key in localStorage)
+            delete localStorage[key];
+    },
+
+    _parseDateAndJSONFromString: function(string) {
+        var components = string.split(this._dateAndJSONSeparator);
+        return {
+            date: new Date(parseInt(components[0], 10)),
+            json: components[1],
+        };
+    },
+};
index 4757c2d7caf139c31f8d4d4cfeb17c3fe99f6f14..a885bb95b15d5f8bfe99bd54814b80fb64d4ea85 100644 (file)
@@ -47,7 +47,7 @@ ViewController.prototype = {
 
     _displayBuilder: function(builder) {
         var self = this;
-        builder.startFetchingBuildHistory(function(history) {
+        builder.startFetchingBuildHistory(function(history, stillFetchingData) {
             var list = document.createElement('ol');
             list.id = 'failure-history';
             Object.keys(history).forEach(function(buildName, buildIndex, buildNameArray) {
@@ -86,7 +86,7 @@ ViewController.prototype = {
                     dlItems.push([document.createTextNode('Passed'), self._domForBuildName(builder, buildNameArray[buildIndex + 1])]);
                 item.appendChild(createDefinitionList(dlItems));
 
-                if (passingBuildName)
+                if (passingBuildName || !stillFetchingData)
                     item.appendChild(self._domForNewAndExistingBugs(builder, buildName, passingBuildName, failingTestNames));
             });
 
@@ -96,6 +96,9 @@ ViewController.prototype = {
             document.title = builder.name;
             document.body.appendChild(header);
             document.body.appendChild(list);
+
+            if (!stillFetchingData)
+                PersistentCache.prune();
         });
     },
 
index 15c47bddabf6e9ed5acefc9862c2d1d80ab23aa5..5fad98dd5e977f3746a057f5d9f3341baccd802e 100644 (file)
@@ -30,6 +30,7 @@ THE POSSIBILITY OF SUCH DAMAGE.
     <script src="Bugzilla.js"></script>
     <script src="Buildbot.js"></script>
     <script src="Builder.js"></script>
+    <script src="PersistentCache.js"></script>
     <script src="Utilities.js"></script>
     <script src="ViewController.js"></script>
 
index 816fc302aacffea00b8c09f136b8a4c2a325f8b9..abae707a9a383f25d98e7d065edbd4cd812629c2 100644 (file)
@@ -1,3 +1,50 @@
+2011-06-23  Adam Roben  <aroben@apple.com>
+
+        Cache some of the TestFailures page's data in localStorage
+
+        This makes reloading TestFailures much faster. Right now we only store the number of failing
+        tests and the list of failing tests for each build. We may choose to store more later, but
+        it's easy to run up against quota limits.
+
+        Fixes <http://webkit.org/b/61520> TestFailures page should take advantage of LocalStorage
+        APIs (or similar) to improve loading performance
+
+        Reviewed by David Kilzer.
+
+        * BuildSlaveSupport/build.webkit.org-config/public_html/TestFailures/Builder.js:
+        (Builder.prototype.getNumberOfFailingTests): Changed to use the new PersistentCache object.
+        (Builder.prototype.startFetchingBuildHistory): Changed to pass whether or not we're still
+        fetching data to the callback.
+        (Builder.prototype._getFailingTests): Changed to use the new PersistentCache object. We now
+        store the tests in the cache just before calling the callback. (The previous code in this
+        function relied on being able to modify the tests object after storing it in the cache and
+        having the cached version be updated. This worked while it was a non-serialized cache, but
+        PersistentCache uses serialization.)
+
+        * BuildSlaveSupport/build.webkit.org-config/public_html/TestFailures/PersistentCache.js: Added.
+        (PersistentCache): This object wraps localStorage. It uses JSON to serialize/deserialize
+        values, and stores the date that each value was initially stored along with it. This is
+        later used for pruning the cache.
+        (PersistentCache.contains): Checks whether the key exists in localStorage.
+        (PersistentCache.get): Fetch the string we stored in localStorage and extract the original
+        value out of it.
+        (PersistentCache.set): Serialize the value, add the date to it, and store it in
+        localStorage. If this fails due to quota limits, empty the whole cache and try again.
+        (PersistentCache.prune): Delete any cached data that is deemed old enough.
+        (PersistentCache._addDateToJSONString): Prepend the current date to the string.
+        (PersistentCache._emptyCache): Delete everything from localStorage.
+        (PersistentCache._parseDateAndJSONFromString): Split apart the date and the JSON string and
+        return them.
+
+        * BuildSlaveSupport/build.webkit.org-config/public_html/TestFailures/ViewController.js:
+        (ViewController.prototype._displayBuilder): Updated for change to callback signature. When
+        we finish fetching data, prune the PersistentCache. While I was here I also fixed a bug
+        where we'd never show the new bug link for tests for which we couldn't determine a passing
+        revision.
+
+        * BuildSlaveSupport/build.webkit.org-config/public_html/TestFailures/index.html: Pull in
+        PersistentCache.js.
+
 2011-06-23  Adam Roben  <aroben@apple.com>
 
         Make finding existing bugs and filing new bugs work on TestFailures even when lots of tests are failing