diff options
author | 2013-12-05 16:05:16 +0000 | |
---|---|---|
committer | 2013-12-05 16:05:16 +0000 | |
commit | 2682c90860655f6c25c61f97c8d8db309d03087a (patch) | |
tree | 818ae515ee50cab344882494e17a22368f597f92 | |
parent | ea4f3dbcf18ffdbbc662040eb6bd0f32b65de714 (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-x | gm/rebaseline_server/server.py | 69 | ||||
-rw-r--r-- | gm/rebaseline_server/static/loader.js | 129 |
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) { |