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

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

Issue 197213033: rebaseline_server: serve JSON from static file, rather than generating it live (Closed) Base URL: https://skia.googlesource.com/skia.git@master
Patch Set: Created 6 years, 9 months 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
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 10 matching lines...) Expand all
21 import socket 21 import socket
22 import subprocess 22 import subprocess
23 import sys 23 import sys
24 import thread 24 import thread
25 import threading 25 import threading
26 import time 26 import time
27 import urlparse 27 import urlparse
28 28
29 # Imports from within Skia 29 # Imports from within Skia
30 # 30 #
31 # We need to add the 'tools' directory, so that we can import svn.py within 31 # We need to add the 'tools' directory for svn.py, and the 'gm' directory for
32 # that directory. 32 # gm_json.py .
33 # Make sure that the 'tools' dir is in the PYTHONPATH, but add it at the *end* 33 # Make sure that these dirs are in the PYTHONPATH, but add them at the *end*
34 # so any dirs that are already in the PYTHONPATH will be preferred. 34 # so any dirs that are already in the PYTHONPATH will be preferred.
35 PARENT_DIRECTORY = os.path.dirname(os.path.realpath(__file__)) 35 PARENT_DIRECTORY = os.path.dirname(os.path.realpath(__file__))
36 TRUNK_DIRECTORY = os.path.dirname(os.path.dirname(PARENT_DIRECTORY)) 36 GM_DIRECTORY = os.path.dirname(PARENT_DIRECTORY)
37 TRUNK_DIRECTORY = os.path.dirname(GM_DIRECTORY)
37 TOOLS_DIRECTORY = os.path.join(TRUNK_DIRECTORY, 'tools') 38 TOOLS_DIRECTORY = os.path.join(TRUNK_DIRECTORY, 'tools')
38 if TOOLS_DIRECTORY not in sys.path: 39 if TOOLS_DIRECTORY not in sys.path:
39 sys.path.append(TOOLS_DIRECTORY) 40 sys.path.append(TOOLS_DIRECTORY)
40 import svn 41 import svn
42 if GM_DIRECTORY not in sys.path:
43 sys.path.append(GM_DIRECTORY)
44 import gm_json
41 45
42 # Imports from local dir 46 # Imports from local dir
43 # 47 #
44 # Note: we import results under a different name, to avoid confusion with the 48 # Note: we import results under a different name, to avoid confusion with the
45 # Server.results() property. See discussion at 49 # Server.results() property. See discussion at
46 # https://codereview.chromium.org/195943004/diff/1/gm/rebaseline_server/server.p y#newcode44 50 # https://codereview.chromium.org/195943004/diff/1/gm/rebaseline_server/server.p y#newcode44
47 import imagepairset 51 import imagepairset
48 import results as results_mod 52 import results as results_mod
49 53
50 PATHSPLIT_RE = re.compile('/([^/]+)/(.+)') 54 PATHSPLIT_RE = re.compile('/([^/]+)/(.+)')
(...skipping 13 matching lines...) Expand all
64 # NOTE: Keep these in sync with static/constants.js 68 # NOTE: Keep these in sync with static/constants.js
65 KEY__EDITS__MODIFICATIONS = 'modifications' 69 KEY__EDITS__MODIFICATIONS = 'modifications'
66 KEY__EDITS__OLD_RESULTS_HASH = 'oldResultsHash' 70 KEY__EDITS__OLD_RESULTS_HASH = 'oldResultsHash'
67 KEY__EDITS__OLD_RESULTS_TYPE = 'oldResultsType' 71 KEY__EDITS__OLD_RESULTS_TYPE = 'oldResultsType'
68 72
69 DEFAULT_ACTUALS_DIR = results_mod.DEFAULT_ACTUALS_DIR 73 DEFAULT_ACTUALS_DIR = results_mod.DEFAULT_ACTUALS_DIR
70 DEFAULT_ACTUALS_REPO_REVISION = 'HEAD' 74 DEFAULT_ACTUALS_REPO_REVISION = 'HEAD'
71 DEFAULT_ACTUALS_REPO_URL = 'http://skia-autogen.googlecode.com/svn/gm-actual' 75 DEFAULT_ACTUALS_REPO_URL = 'http://skia-autogen.googlecode.com/svn/gm-actual'
72 DEFAULT_PORT = 8888 76 DEFAULT_PORT = 8888
73 77
78 # Directory within which the server will serve out static files.
79 STATIC_CONTENTS_DIR = os.path.realpath(os.path.join(PARENT_DIRECTORY, 'static'))
80 GENERATED_IMAGES_DIR = os.path.join(STATIC_CONTENTS_DIR, 'generated-images')
81 GENERATED_JSON_DIR = os.path.join(STATIC_CONTENTS_DIR, 'generated-json')
epoger 2014/03/19 21:13:51 FOR NOW we still need the server running so the cl
82
74 # How often (in seconds) clients should reload while waiting for initial 83 # How often (in seconds) clients should reload while waiting for initial
75 # results to load. 84 # results to load.
76 RELOAD_INTERVAL_UNTIL_READY = 10 85 RELOAD_INTERVAL_UNTIL_READY = 10
77 86
78 _HTTP_HEADER_CONTENT_LENGTH = 'Content-Length' 87 _HTTP_HEADER_CONTENT_LENGTH = 'Content-Length'
79 _HTTP_HEADER_CONTENT_TYPE = 'Content-Type' 88 _HTTP_HEADER_CONTENT_TYPE = 'Content-Type'
80 89
81 _SERVER = None # This gets filled in by main() 90 _SERVER = None # This gets filled in by main()
82 91
83 92
(...skipping 141 matching lines...) Expand 10 before | Expand all | Expand 10 after
225 # 234 #
226 # Because Skia uses depot_tools, we have to update using "gclient sync" 235 # Because Skia uses depot_tools, we have to update using "gclient sync"
227 # instead of raw git (or SVN) update. Happily, this will work whether 236 # instead of raw git (or SVN) update. Happily, this will work whether
228 # the checkout was created using git or SVN. 237 # the checkout was created using git or SVN.
229 if self._reload_seconds: 238 if self._reload_seconds:
230 logging.info( 239 logging.info(
231 'Updating expected GM results in %s by syncing Skia repo ...' % 240 'Updating expected GM results in %s by syncing Skia repo ...' %
232 results_mod.DEFAULT_EXPECTATIONS_DIR) 241 results_mod.DEFAULT_EXPECTATIONS_DIR)
233 _run_command(['gclient', 'sync'], TRUNK_DIRECTORY) 242 _run_command(['gclient', 'sync'], TRUNK_DIRECTORY)
234 243
235 self._results = results_mod.Results(actuals_root=self._actuals_dir) 244 new_results = results_mod.Results(
245 actuals_root=self._actuals_dir,
246 generated_images_root=GENERATED_IMAGES_DIR,
247 diff_base_url=os.path.relpath(
epoger 2014/03/19 21:13:51 Because this uses a relative diff_base_url, this C
248 GENERATED_IMAGES_DIR, GENERATED_JSON_DIR))
249 if not os.path.isdir(GENERATED_JSON_DIR):
250 os.makedirs(GENERATED_JSON_DIR)
251 for summary_type in [results_mod.KEY__HEADER__RESULTS_ALL,
252 results_mod.KEY__HEADER__RESULTS_FAILURES]:
253 gm_json.WriteToFile(
254 new_results.get_packaged_results_of_type(results_type=summary_type),
255 os.path.join(GENERATED_JSON_DIR, '%s.json' % summary_type))
256
257 self._results = new_results
epoger 2014/03/19 21:13:51 Even though we no longer serve the JSON from the l
236 258
237 def _result_loader(self, reload_seconds=0): 259 def _result_loader(self, reload_seconds=0):
238 """ Call self.update_results(), either once or periodically. 260 """ Call self.update_results(), either once or periodically.
239 261
240 Params: 262 Params:
241 reload_seconds: integer; if nonzero, reload results at this interval 263 reload_seconds: integer; if nonzero, reload results at this interval
242 (in which case, this method will never return!) 264 (in which case, this method will never return!)
243 """ 265 """
244 self.update_results() 266 self.update_results()
245 logging.info('Initial results loaded. Ready for requests on %s' % self._url) 267 logging.info('Initial results loaded. Ready for requests on %s' % self._url)
(...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after
289 self.redirect_to('/static/favicon.ico') 311 self.redirect_to('/static/favicon.ico')
290 return 312 return
291 313
292 # All requests must be of this form: 314 # All requests must be of this form:
293 # /dispatcher/remainder 315 # /dispatcher/remainder
294 # where 'dispatcher' indicates which do_GET_* dispatcher to run 316 # where 'dispatcher' indicates which do_GET_* dispatcher to run
295 # and 'remainder' is the remaining path sent to that dispatcher. 317 # and 'remainder' is the remaining path sent to that dispatcher.
296 normpath = posixpath.normpath(self.path) 318 normpath = posixpath.normpath(self.path)
297 (dispatcher_name, remainder) = PATHSPLIT_RE.match(normpath).groups() 319 (dispatcher_name, remainder) = PATHSPLIT_RE.match(normpath).groups()
298 dispatchers = { 320 dispatchers = {
299 'results': self.do_GET_results,
300 'static': self.do_GET_static, 321 'static': self.do_GET_static,
301 } 322 }
302 dispatcher = dispatchers[dispatcher_name] 323 dispatcher = dispatchers[dispatcher_name]
303 dispatcher(remainder) 324 dispatcher(remainder)
304 except: 325 except:
305 self.send_error(404) 326 self.send_error(404)
306 raise 327 raise
307 328
308 def do_GET_results(self, results_type):
309 """ Handle a GET request for GM results.
310
311 Args:
312 results_type: string indicating which set of results to return;
313 must be one of the results_mod.RESULTS_* constants
314 """
315 logging.debug('do_GET_results: sending results of type "%s"' % results_type)
316 # Since we must make multiple calls to the Results object, grab a
317 # reference to it in case it is updated to point at a new Results
318 # object within another thread.
319 #
320 # TODO(epoger): Rather than using a global variable for the handler
321 # to refer to the Server object, make Server a subclass of
322 # HTTPServer, and then it could be available to the handler via
323 # the handler's .server instance variable.
324 results_obj = _SERVER.results
325 if results_obj:
326 response_dict = results_obj.get_packaged_results_of_type(
327 results_type=results_type, reload_seconds=_SERVER.reload_seconds,
328 is_editable=_SERVER.is_editable, is_exported=_SERVER.is_exported)
329 else:
330 now = int(time.time())
331 response_dict = {
332 results_mod.KEY__HEADER: {
333 results_mod.KEY__HEADER__SCHEMA_VERSION: (
334 results_mod.REBASELINE_SERVER_SCHEMA_VERSION_NUMBER),
335 results_mod.KEY__HEADER__IS_STILL_LOADING: True,
336 results_mod.KEY__HEADER__TIME_UPDATED: now,
337 results_mod.KEY__HEADER__TIME_NEXT_UPDATE_AVAILABLE: (
338 now + RELOAD_INTERVAL_UNTIL_READY),
339 },
340 }
341 self.send_json_dict(response_dict)
342
343 def do_GET_static(self, path): 329 def do_GET_static(self, path):
344 """ Handle a GET request for a file under the 'static' directory. 330 """ Handle a GET request for a file under the 'static' directory.
345 Only allow serving of files within the 'static' directory that is a 331 Only allow serving of files within the 'static' directory that is a
346 filesystem sibling of this script. 332 filesystem sibling of this script.
347 333
348 Args: 334 Args:
349 path: path to file (under static directory) to retrieve 335 path: path to file (under static directory) to retrieve
350 """ 336 """
351 # Strip arguments ('?resultsToLoad=all') from the path 337 # Strip arguments ('?resultsToLoad=all') from the path
352 path = urlparse.urlparse(path).path 338 path = urlparse.urlparse(path).path
353 339
354 logging.debug('do_GET_static: sending file "%s"' % path) 340 logging.debug('do_GET_static: sending file "%s"' % path)
355 static_dir = os.path.realpath(os.path.join(PARENT_DIRECTORY, 'static')) 341 full_path = os.path.realpath(os.path.join(STATIC_CONTENTS_DIR, path))
356 full_path = os.path.realpath(os.path.join(static_dir, path)) 342 if full_path.startswith(STATIC_CONTENTS_DIR):
357 if full_path.startswith(static_dir):
358 self.send_file(full_path) 343 self.send_file(full_path)
359 else: 344 else:
360 logging.error( 345 logging.error(
361 'Attempted do_GET_static() of path [%s] outside of static dir [%s]' 346 'Attempted do_GET_static() of path [%s] outside of static dir [%s]'
362 % (full_path, static_dir)) 347 % (full_path, STATIC_CONTENTS_DIR))
363 self.send_error(404) 348 self.send_error(404)
364 349
365 def do_POST(self): 350 def do_POST(self):
366 """ Handles all POST requests, forwarding them to the appropriate 351 """ Handles all POST requests, forwarding them to the appropriate
367 do_POST_* dispatcher. """ 352 do_POST_* dispatcher. """
368 # All requests must be of this form: 353 # All requests must be of this form:
369 # /dispatcher 354 # /dispatcher
370 # where 'dispatcher' indicates which do_POST_* dispatcher to run. 355 # where 'dispatcher' indicates which do_POST_* dispatcher to run.
371 logging.debug('do_POST: path="%s"' % self.path) 356 logging.debug('do_POST: path="%s"' % self.path)
372 normpath = posixpath.normpath(self.path) 357 normpath = posixpath.normpath(self.path)
(...skipping 91 matching lines...) Expand 10 before | Expand all | Expand 10 after
464 # Open the file and send it over HTTP 449 # Open the file and send it over HTTP
465 if os.path.isfile(path): 450 if os.path.isfile(path):
466 with open(path, 'rb') as sending_file: 451 with open(path, 'rb') as sending_file:
467 self.send_response(200) 452 self.send_response(200)
468 self.send_header('Content-type', mime_type) 453 self.send_header('Content-type', mime_type)
469 self.end_headers() 454 self.end_headers()
470 self.wfile.write(sending_file.read()) 455 self.wfile.write(sending_file.read())
471 else: 456 else:
472 self.send_error(404) 457 self.send_error(404)
473 458
474 def send_json_dict(self, json_dict):
475 """ Send the contents of this dictionary in JSON format, with a JSON
476 mimetype.
477
478 Args:
479 json_dict: dictionary to send
480 """
481 self.send_response(200)
482 self.send_header('Content-type', 'application/json')
483 self.end_headers()
484 json.dump(json_dict, self.wfile)
485
486 459
487 def main(): 460 def main():
488 logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s', 461 logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s',
489 datefmt='%m/%d/%Y %H:%M:%S', 462 datefmt='%m/%d/%Y %H:%M:%S',
490 level=logging.INFO) 463 level=logging.INFO)
491 parser = argparse.ArgumentParser() 464 parser = argparse.ArgumentParser()
492 parser.add_argument('--actuals-dir', 465 parser.add_argument('--actuals-dir',
493 help=('Directory into which we will check out the latest ' 466 help=('Directory into which we will check out the latest '
494 'actual GM results. If this directory does not ' 467 'actual GM results. If this directory does not '
495 'exist, it will be created. Defaults to %(default)s'), 468 'exist, it will be created. Defaults to %(default)s'),
(...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after
529 _SERVER = Server(actuals_dir=args.actuals_dir, 502 _SERVER = Server(actuals_dir=args.actuals_dir,
530 actuals_repo_revision=args.actuals_revision, 503 actuals_repo_revision=args.actuals_revision,
531 actuals_repo_url=args.actuals_repo, 504 actuals_repo_url=args.actuals_repo,
532 port=args.port, export=args.export, editable=args.editable, 505 port=args.port, export=args.export, editable=args.editable,
533 reload_seconds=args.reload) 506 reload_seconds=args.reload)
534 _SERVER.run() 507 _SERVER.run()
535 508
536 509
537 if __name__ == '__main__': 510 if __name__ == '__main__':
538 main() 511 main()
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698