Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(302)

Side by Side Diff: gm/rebaseline_server/server.py

Issue 106453002: rebaseline_server: start HTTP server immediately, auto-reload once results are available (Closed) Base URL: http://skia.googlecode.com/svn/trunk/
Patch Set: Created 7 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 #!/usr/bin/python 1 #!/usr/bin/python
2 2
3 """ 3 """
4 Copyright 2013 Google Inc. 4 Copyright 2013 Google Inc.
5 5
6 Use of this source code is governed by a BSD-style license that can be 6 Use of this source code is governed by a BSD-style license that can be
7 found in the LICENSE file. 7 found in the LICENSE file.
8 8
9 HTTP server for our HTML rebaseline viewer. 9 HTTP server for our HTML rebaseline viewer.
10 """ 10 """
(...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after
55 'html': 'text/html', 55 'html': 'text/html',
56 'css': 'text/css', 56 'css': 'text/css',
57 'png': 'image/png', 57 'png': 'image/png',
58 'js': 'application/javascript', 58 'js': 'application/javascript',
59 'json': 'application/json' 59 'json': 'application/json'
60 } 60 }
61 61
62 DEFAULT_ACTUALS_DIR = '.gm-actuals' 62 DEFAULT_ACTUALS_DIR = '.gm-actuals'
63 DEFAULT_PORT = 8888 63 DEFAULT_PORT = 8888
64 64
65 # How often (in seconds) clients should reload while waiting for initial
66 # results to load.
67 RELOAD_INTERVAL_UNTIL_READY = 10
68
65 _HTTP_HEADER_CONTENT_LENGTH = 'Content-Length' 69 _HTTP_HEADER_CONTENT_LENGTH = 'Content-Length'
66 _HTTP_HEADER_CONTENT_TYPE = 'Content-Type' 70 _HTTP_HEADER_CONTENT_TYPE = 'Content-Type'
67 71
68 _SERVER = None # This gets filled in by main() 72 _SERVER = None # This gets filled in by main()
69 73
70 74
71 def _run_command(args, directory): 75 def _run_command(args, directory):
72 """Runs a command and returns stdout as a single string. 76 """Runs a command and returns stdout as a single string.
73 77
74 Args: 78 Args:
(...skipping 131 matching lines...) Expand 10 before | Expand all | Expand 10 after
206 210
207 logging.info( 211 logging.info(
208 ('Parsing results from actuals in %s and expectations in %s, ' 212 ('Parsing results from actuals in %s and expectations in %s, '
209 + 'and generating pixel diffs (may take a while) ...') % ( 213 + 'and generating pixel diffs (may take a while) ...') % (
210 self._actuals_dir, EXPECTATIONS_DIR)) 214 self._actuals_dir, EXPECTATIONS_DIR))
211 self._results = results.Results( 215 self._results = results.Results(
212 actuals_root=self._actuals_dir, 216 actuals_root=self._actuals_dir,
213 expected_root=EXPECTATIONS_DIR, 217 expected_root=EXPECTATIONS_DIR,
214 generated_images_root=GENERATED_IMAGES_ROOT) 218 generated_images_root=GENERATED_IMAGES_ROOT)
215 219
216 def _result_reloader(self): 220 def _result_loader(self, reload_seconds=0):
217 """ Reload results at the appropriate interval. This never exits, so it 221 """ Call self.update_results(), either once or periodically.
218 should be run in its own thread. 222
223 Params:
224 reload_seconds: integer; if nonzero, reload results at this interval
225 (in which case, this method will never return!)
219 """ 226 """
220 while True: 227 self.update_results()
221 time.sleep(self._reload_seconds) 228 logging.info('Initial results loaded. Ready for requests on %s' % self._url)
222 self.update_results() 229 if reload_seconds:
230 while True:
231 time.sleep(reload_seconds)
232 self.update_results()
223 233
224 def run(self): 234 def run(self):
225 self.update_results() 235 arg_tuple = (self._reload_seconds,) # start_new_thread needs a tuple,
226 if self._reload_seconds: 236 # even though it holds just one param
227 thread.start_new_thread(self._result_reloader, ()) 237 thread.start_new_thread(self._result_loader, arg_tuple)
228 238
229 if self._export: 239 if self._export:
230 server_address = ('', self._port) 240 server_address = ('', self._port)
231 host = _get_routable_ip_address() 241 host = _get_routable_ip_address()
232 if self._editable: 242 if self._editable:
233 logging.warning('Running with combination of "export" and "editable" ' 243 logging.warning('Running with combination of "export" and "editable" '
234 'flags. Users on other machines will ' 244 'flags. Users on other machines will '
235 'be able to modify your GM expectations!') 245 'be able to modify your GM expectations!')
236 else: 246 else:
237 host = '127.0.0.1' 247 host = '127.0.0.1'
238 server_address = (host, self._port) 248 server_address = (host, self._port)
239 http_server = BaseHTTPServer.HTTPServer(server_address, HTTPRequestHandler) 249 http_server = BaseHTTPServer.HTTPServer(server_address, HTTPRequestHandler)
240 logging.info('Ready for requests on http://%s:%d' % ( 250 self._url = 'http://%s:%d' % (host, http_server.server_port)
241 host, http_server.server_port)) 251 logging.info('Listening for requests on %s' % self._url)
242 http_server.serve_forever() 252 http_server.serve_forever()
243 253
244 254
245 class HTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): 255 class HTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
246 """ HTTP request handlers for various types of queries this server knows 256 """ HTTP request handlers for various types of queries this server knows
247 how to handle (static HTML and Javascript, expected/actual results, etc.) 257 how to handle (static HTML and Javascript, expected/actual results, etc.)
248 """ 258 """
249 def do_GET(self): 259 def do_GET(self):
250 """ Handles all GET requests, forwarding them to the appropriate 260 """ Handles all GET requests, forwarding them to the appropriate
251 do_GET_* dispatcher. """ 261 do_GET_* dispatcher. """
(...skipping 28 matching lines...) Expand all
280 try: 290 try:
281 # Since we must make multiple calls to the Results object, grab a 291 # Since we must make multiple calls to the Results object, grab a
282 # reference to it in case it is updated to point at a new Results 292 # reference to it in case it is updated to point at a new Results
283 # object within another thread. 293 # object within another thread.
284 # 294 #
285 # TODO(epoger): Rather than using a global variable for the handler 295 # TODO(epoger): Rather than using a global variable for the handler
286 # to refer to the Server object, make Server a subclass of 296 # to refer to the Server object, make Server a subclass of
287 # HTTPServer, and then it could be available to the handler via 297 # HTTPServer, and then it could be available to the handler via
288 # the handler's .server instance variable. 298 # the handler's .server instance variable.
289 results_obj = _SERVER.results 299 results_obj = _SERVER.results
290 response_dict = results_obj.get_results_of_type(type) 300 if results_obj:
291 time_updated = results_obj.get_timestamp() 301 response_dict = self.package_results(results_obj, type)
302 else:
303 now = int(time.time())
304 response_dict = {
305 'header': {
306 'resultsStillLoading': True,
307 'timeUpdated': now,
308 'timeNextUpdateAvailable': now + RELOAD_INTERVAL_UNTIL_READY,
309 },
310 }
311 self.send_json_dict(response_dict)
312 except:
313 self.send_error(404)
314 raise
292 315
293 response_dict['header'] = { 316 def package_results(self, results_obj, type):
317 """ Given a nonempty "results" object, package it as a response_dict
318 as needed within do_GET_results.
319
320 Args:
321 results_obj: nonempty "results" object
322 type: string indicating which set of results to return;
323 must be one of the results.RESULTS_* constants
324 """
325 response_dict = results_obj.get_results_of_type(type)
326 time_updated = results_obj.get_timestamp()
327 response_dict['header'] = {
294 # Timestamps: 328 # Timestamps:
295 # 1. when this data was last updated 329 # 1. when this data was last updated
296 # 2. when the caller should check back for new data (if ever) 330 # 2. when the caller should check back for new data (if ever)
297 # 331 #
298 # We only return these timestamps if the --reload argument was passed; 332 # We only return these timestamps if the --reload argument was passed;
299 # otherwise, we have no idea when the expectations were last updated 333 # otherwise, we have no idea when the expectations were last updated
300 # (we allow the user to maintain her own expectations as she sees fit). 334 # (we allow the user to maintain her own expectations as she sees fit).
301 'timeUpdated': time_updated if _SERVER.reload_seconds else None, 335 'timeUpdated': time_updated if _SERVER.reload_seconds else None,
302 'timeNextUpdateAvailable': ( 336 'timeNextUpdateAvailable': (
303 (time_updated+_SERVER.reload_seconds) if _SERVER.reload_seconds 337 (time_updated+_SERVER.reload_seconds) if _SERVER.reload_seconds
304 else None), 338 else None),
305 339
306 # The type we passed to get_results_of_type() 340 # The type we passed to get_results_of_type()
307 'type': type, 341 'type': type,
308 342
309 # Hash of testData, which the client must return with any edits-- 343 # Hash of testData, which the client must return with any edits--
310 # this ensures that the edits were made to a particular dataset. 344 # this ensures that the edits were made to a particular dataset.
311 'dataHash': str(hash(repr(response_dict['testData']))), 345 'dataHash': str(hash(repr(response_dict['testData']))),
312 346
313 # Whether the server will accept edits back. 347 # Whether the server will accept edits back.
314 'isEditable': _SERVER.is_editable, 348 'isEditable': _SERVER.is_editable,
315 349
316 # Whether the service is accessible from other hosts. 350 # Whether the service is accessible from other hosts.
317 'isExported': _SERVER.is_exported, 351 'isExported': _SERVER.is_exported,
318 } 352 }
319 self.send_json_dict(response_dict) 353 return response_dict
320 except:
321 self.send_error(404)
322 raise
323 354
324 def do_GET_static(self, path): 355 def do_GET_static(self, path):
325 """ Handle a GET request for a file under the 'static' directory. 356 """ Handle a GET request for a file under the 'static' directory.
326 Only allow serving of files within the 'static' directory that is a 357 Only allow serving of files within the 'static' directory that is a
327 filesystem sibling of this script. 358 filesystem sibling of this script.
328 359
329 Args: 360 Args:
330 path: path to file (under static directory) to retrieve 361 path: path to file (under static directory) to retrieve
331 """ 362 """
332 # Strip arguments ('?resultsToLoad=all') from the path 363 # Strip arguments ('?resultsToLoad=all') from the path
(...skipping 163 matching lines...) Expand 10 before | Expand all | Expand 10 after
496 args = parser.parse_args() 527 args = parser.parse_args()
497 global _SERVER 528 global _SERVER
498 _SERVER = Server(actuals_dir=args.actuals_dir, 529 _SERVER = Server(actuals_dir=args.actuals_dir,
499 port=args.port, export=args.export, editable=args.editable, 530 port=args.port, export=args.export, editable=args.editable,
500 reload_seconds=args.reload) 531 reload_seconds=args.reload)
501 _SERVER.run() 532 _SERVER.run()
502 533
503 534
504 if __name__ == '__main__': 535 if __name__ == '__main__':
505 main() 536 main()
OLDNEW
« no previous file with comments | « no previous file | gm/rebaseline_server/static/loader.js » ('j') | gm/rebaseline_server/static/loader.js » ('J')

Powered by Google App Engine
This is Rietveld 408576698