aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorGravatar epoger@google.com <epoger@google.com@2bbb7eff-a529-9590-31e7-b0007b416f81>2013-12-05 16:05:16 +0000
committerGravatar epoger@google.com <epoger@google.com@2bbb7eff-a529-9590-31e7-b0007b416f81>2013-12-05 16:05:16 +0000
commit2682c90860655f6c25c61f97c8d8db309d03087a (patch)
tree818ae515ee50cab344882494e17a22368f597f92
parentea4f3dbcf18ffdbbc662040eb6bd0f32b65de714 (diff)
rebaseline_server: start HTTP server immediately, auto-reload once results are available
BUG=skia:1877 (SkipBuildbotRuns) R=vandebo@chromium.org Review URL: https://codereview.chromium.org/106453002 git-svn-id: http://skia.googlecode.com/svn/trunk@12509 2bbb7eff-a529-9590-31e7-b0007b416f81
-rwxr-xr-xgm/rebaseline_server/server.py69
-rw-r--r--gm/rebaseline_server/static/loader.js129
2 files changed, 119 insertions, 79 deletions
diff --git a/gm/rebaseline_server/server.py b/gm/rebaseline_server/server.py
index 670c94de28..e8dbb6be73 100755
--- a/gm/rebaseline_server/server.py
+++ b/gm/rebaseline_server/server.py
@@ -62,6 +62,10 @@ MIME_TYPE_MAP = {'': 'application/octet-stream',
DEFAULT_ACTUALS_DIR = '.gm-actuals'
DEFAULT_PORT = 8888
+# How often (in seconds) clients should reload while waiting for initial
+# results to load.
+RELOAD_INTERVAL_UNTIL_READY = 10
+
_HTTP_HEADER_CONTENT_LENGTH = 'Content-Length'
_HTTP_HEADER_CONTENT_TYPE = 'Content-Type'
@@ -213,18 +217,24 @@ class Server(object):
expected_root=EXPECTATIONS_DIR,
generated_images_root=GENERATED_IMAGES_ROOT)
- def _result_reloader(self):
- """ Reload results at the appropriate interval. This never exits, so it
- should be run in its own thread.
+ def _result_loader(self, reload_seconds=0):
+ """ Call self.update_results(), either once or periodically.
+
+ Params:
+ reload_seconds: integer; if nonzero, reload results at this interval
+ (in which case, this method will never return!)
"""
- while True:
- time.sleep(self._reload_seconds)
- self.update_results()
+ self.update_results()
+ logging.info('Initial results loaded. Ready for requests on %s' % self._url)
+ if reload_seconds:
+ while True:
+ time.sleep(reload_seconds)
+ self.update_results()
def run(self):
- self.update_results()
- if self._reload_seconds:
- thread.start_new_thread(self._result_reloader, ())
+ arg_tuple = (self._reload_seconds,) # start_new_thread needs a tuple,
+ # even though it holds just one param
+ thread.start_new_thread(self._result_loader, arg_tuple)
if self._export:
server_address = ('', self._port)
@@ -237,8 +247,8 @@ class Server(object):
host = '127.0.0.1'
server_address = (host, self._port)
http_server = BaseHTTPServer.HTTPServer(server_address, HTTPRequestHandler)
- logging.info('Ready for requests on http://%s:%d' % (
- host, http_server.server_port))
+ self._url = 'http://%s:%d' % (host, http_server.server_port)
+ logging.info('Listening for requests on %s' % self._url)
http_server.serve_forever()
@@ -287,10 +297,34 @@ class HTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
# HTTPServer, and then it could be available to the handler via
# the handler's .server instance variable.
results_obj = _SERVER.results
- response_dict = results_obj.get_results_of_type(type)
- time_updated = results_obj.get_timestamp()
+ if results_obj:
+ response_dict = self.package_results(results_obj, type)
+ else:
+ now = int(time.time())
+ response_dict = {
+ 'header': {
+ 'resultsStillLoading': True,
+ 'timeUpdated': now,
+ 'timeNextUpdateAvailable': now + RELOAD_INTERVAL_UNTIL_READY,
+ },
+ }
+ self.send_json_dict(response_dict)
+ except:
+ self.send_error(404)
+ raise
+
+ def package_results(self, results_obj, type):
+ """ Given a nonempty "results" object, package it as a response_dict
+ as needed within do_GET_results.
- response_dict['header'] = {
+ Args:
+ results_obj: nonempty "results" object
+ type: string indicating which set of results to return;
+ must be one of the results.RESULTS_* constants
+ """
+ response_dict = results_obj.get_results_of_type(type)
+ time_updated = results_obj.get_timestamp()
+ response_dict['header'] = {
# Timestamps:
# 1. when this data was last updated
# 2. when the caller should check back for new data (if ever)
@@ -315,11 +349,8 @@ class HTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
# Whether the service is accessible from other hosts.
'isExported': _SERVER.is_exported,
- }
- self.send_json_dict(response_dict)
- except:
- self.send_error(404)
- raise
+ }
+ return response_dict
def do_GET_static(self, path):
""" Handle a GET request for a file under the 'static' directory.
diff --git a/gm/rebaseline_server/static/loader.js b/gm/rebaseline_server/static/loader.js
index 3b16e2e439..ccd29c205a 100644
--- a/gm/rebaseline_server/static/loader.js
+++ b/gm/rebaseline_server/static/loader.js
@@ -40,7 +40,7 @@ Loader.filter(
Loader.controller(
'Loader.Controller',
- function($scope, $http, $filter, $location) {
+ function($scope, $http, $filter, $location, $timeout) {
$scope.windowTitle = "Loading GM Results...";
var resultsToLoad = $location.search().resultsToLoad;
$scope.loadingMessage = "Loading results of type '" + resultsToLoad +
@@ -53,66 +53,75 @@ Loader.controller(
*/
$http.get("/results/" + resultsToLoad).success(
function(data, status, header, config) {
- $scope.loadingMessage = "Processing data, please wait...";
-
- $scope.header = data.header;
- $scope.categories = data.categories;
- $scope.testData = data.testData;
- $scope.sortColumn = 'weightedDiffMeasure';
- $scope.showTodos = false;
-
- $scope.showSubmitAdvancedSettings = false;
- $scope.submitAdvancedSettings = {};
- $scope.submitAdvancedSettings['reviewed-by-human'] = true;
- $scope.submitAdvancedSettings['ignore-failure'] = false;
- $scope.submitAdvancedSettings['bug'] = '';
-
- // Create the list of tabs (lists into which the user can file each
- // test). This may vary, depending on isEditable.
- $scope.tabs = [
- 'Unfiled', 'Hidden'
- ];
- if (data.header.isEditable) {
- $scope.tabs = $scope.tabs.concat(
- ['Pending Approval']);
+ if (data.header.resultsStillLoading) {
+ $scope.loadingMessage =
+ "Server is still loading initial results; will retry at " +
+ $scope.localTimeString(data.header.timeNextUpdateAvailable);
+ $timeout(
+ function(){location.reload();},
+ (data.header.timeNextUpdateAvailable * 1000) - new Date().getTime());
+ } else {
+ $scope.loadingMessage = "Processing data, please wait...";
+
+ $scope.header = data.header;
+ $scope.categories = data.categories;
+ $scope.testData = data.testData;
+ $scope.sortColumn = 'weightedDiffMeasure';
+ $scope.showTodos = false;
+
+ $scope.showSubmitAdvancedSettings = false;
+ $scope.submitAdvancedSettings = {};
+ $scope.submitAdvancedSettings['reviewed-by-human'] = true;
+ $scope.submitAdvancedSettings['ignore-failure'] = false;
+ $scope.submitAdvancedSettings['bug'] = '';
+
+ // Create the list of tabs (lists into which the user can file each
+ // test). This may vary, depending on isEditable.
+ $scope.tabs = [
+ 'Unfiled', 'Hidden'
+ ];
+ if (data.header.isEditable) {
+ $scope.tabs = $scope.tabs.concat(
+ ['Pending Approval']);
+ }
+ $scope.defaultTab = $scope.tabs[0];
+ $scope.viewingTab = $scope.defaultTab;
+
+ // Track the number of results on each tab.
+ $scope.numResultsPerTab = {};
+ for (var i = 0; i < $scope.tabs.length; i++) {
+ $scope.numResultsPerTab[$scope.tabs[i]] = 0;
+ }
+ $scope.numResultsPerTab[$scope.defaultTab] = $scope.testData.length;
+
+ // Add index and tab fields to all records.
+ for (var i = 0; i < $scope.testData.length; i++) {
+ $scope.testData[i].index = i;
+ $scope.testData[i].tab = $scope.defaultTab;
+ }
+
+ // Arrays within which the user can toggle individual elements.
+ $scope.selectedItems = [];
+
+ // Sets within which the user can toggle individual elements.
+ $scope.hiddenResultTypes = {
+ 'failure-ignored': true,
+ 'no-comparison': true,
+ 'succeeded': true,
+ };
+ $scope.allResultTypes = Object.keys(data.categories['resultType']);
+ $scope.hiddenConfigs = {};
+ $scope.allConfigs = Object.keys(data.categories['config']);
+
+ // Associative array of partial string matches per category.
+ $scope.categoryValueMatch = {};
+ $scope.categoryValueMatch.builder = "";
+ $scope.categoryValueMatch.test = "";
+
+ $scope.updateResults();
+ $scope.loadingMessage = "";
+ $scope.windowTitle = "Current GM Results";
}
- $scope.defaultTab = $scope.tabs[0];
- $scope.viewingTab = $scope.defaultTab;
-
- // Track the number of results on each tab.
- $scope.numResultsPerTab = {};
- for (var i = 0; i < $scope.tabs.length; i++) {
- $scope.numResultsPerTab[$scope.tabs[i]] = 0;
- }
- $scope.numResultsPerTab[$scope.defaultTab] = $scope.testData.length;
-
- // Add index and tab fields to all records.
- for (var i = 0; i < $scope.testData.length; i++) {
- $scope.testData[i].index = i;
- $scope.testData[i].tab = $scope.defaultTab;
- }
-
- // Arrays within which the user can toggle individual elements.
- $scope.selectedItems = [];
-
- // Sets within which the user can toggle individual elements.
- $scope.hiddenResultTypes = {
- 'failure-ignored': true,
- 'no-comparison': true,
- 'succeeded': true,
- };
- $scope.allResultTypes = Object.keys(data.categories['resultType']);
- $scope.hiddenConfigs = {};
- $scope.allConfigs = Object.keys(data.categories['config']);
-
- // Associative array of partial string matches per category.
- $scope.categoryValueMatch = {};
- $scope.categoryValueMatch.builder = "";
- $scope.categoryValueMatch.test = "";
-
- $scope.updateResults();
- $scope.loadingMessage = "";
- $scope.windowTitle = "Current GM Results";
}
).error(
function(data, status, header, config) {