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

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

Issue 303223006: rebaseline_server: download actual-results.json files from Google Storage, not skia-autogen (Closed) Base URL: https://skia.googlesource.com/skia.git@master
Patch Set: Created 6 years, 6 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
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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 for svn.py, and the 'gm' directory for 31 # We need to add the 'gm' directory for gm_json.py .
32 # gm_json.py . 32 # Make sure it is in the PYTHONPATH, but add it at the *end*
33 # that directory.
34 # Make sure that the 'tools' dir is in the PYTHONPATH, but add it at the *end*
35 # so any dirs that are already in the PYTHONPATH will be preferred. 33 # so any dirs that are already in the PYTHONPATH will be preferred.
36 PARENT_DIRECTORY = os.path.dirname(os.path.realpath(__file__)) 34 PARENT_DIRECTORY = os.path.dirname(os.path.realpath(__file__))
37 GM_DIRECTORY = os.path.dirname(PARENT_DIRECTORY) 35 GM_DIRECTORY = os.path.dirname(PARENT_DIRECTORY)
38 TRUNK_DIRECTORY = os.path.dirname(GM_DIRECTORY) 36 TRUNK_DIRECTORY = os.path.dirname(GM_DIRECTORY)
39 TOOLS_DIRECTORY = os.path.join(TRUNK_DIRECTORY, 'tools')
40 if TOOLS_DIRECTORY not in sys.path:
41 sys.path.append(TOOLS_DIRECTORY)
42 import svn
43 if GM_DIRECTORY not in sys.path: 37 if GM_DIRECTORY not in sys.path:
44 sys.path.append(GM_DIRECTORY) 38 sys.path.append(GM_DIRECTORY)
45 import gm_json 39 import gm_json
46 40
47 # Imports from local dir 41 # Imports from local dir
48 # 42 #
49 # Note: we import results under a different name, to avoid confusion with the 43 # Note: we import results under a different name, to avoid confusion with the
50 # Server.results() property. See discussion at 44 # Server.results() property. See discussion at
51 # https://codereview.chromium.org/195943004/diff/1/gm/rebaseline_server/server.p y#newcode44 45 # https://codereview.chromium.org/195943004/diff/1/gm/rebaseline_server/server.p y#newcode44
52 import compare_configs 46 import compare_configs
(...skipping 14 matching lines...) Expand all
67 'json': 'application/json' 61 'json': 'application/json'
68 } 62 }
69 63
70 # Keys that server.py uses to create the toplevel content header. 64 # Keys that server.py uses to create the toplevel content header.
71 # NOTE: Keep these in sync with static/constants.js 65 # NOTE: Keep these in sync with static/constants.js
72 KEY__EDITS__MODIFICATIONS = 'modifications' 66 KEY__EDITS__MODIFICATIONS = 'modifications'
73 KEY__EDITS__OLD_RESULTS_HASH = 'oldResultsHash' 67 KEY__EDITS__OLD_RESULTS_HASH = 'oldResultsHash'
74 KEY__EDITS__OLD_RESULTS_TYPE = 'oldResultsType' 68 KEY__EDITS__OLD_RESULTS_TYPE = 'oldResultsType'
75 69
76 DEFAULT_ACTUALS_DIR = results_mod.DEFAULT_ACTUALS_DIR 70 DEFAULT_ACTUALS_DIR = results_mod.DEFAULT_ACTUALS_DIR
77 DEFAULT_ACTUALS_REPO_REVISION = 'HEAD' 71 DEFAULT_ACTUALS_GS_ROOT = 'gs://chromium-skia-gm-summaries'
rmistry 2014/05/30 14:15:28 Should we add this to global_variables.json? I bel
78 DEFAULT_ACTUALS_REPO_URL = 'http://skia-autogen.googlecode.com/svn/gm-actual'
79 DEFAULT_PORT = 8888 72 DEFAULT_PORT = 8888
80 73
81 # Directory, relative to PARENT_DIRECTORY, within which the server will serve 74 # Directory, relative to PARENT_DIRECTORY, within which the server will serve
82 # out live results (not static files). 75 # out live results (not static files).
83 RESULTS_SUBDIR = 'results' 76 RESULTS_SUBDIR = 'results'
84 # Directory, relative to PARENT_DIRECTORY, within which the server will serve 77 # Directory, relative to PARENT_DIRECTORY, within which the server will serve
85 # out static files. 78 # out static files.
86 STATIC_CONTENTS_SUBDIR = 'static' 79 STATIC_CONTENTS_SUBDIR = 'static'
87 # All of the GENERATED_*_SUBDIRS are relative to STATIC_CONTENTS_SUBDIR 80 # All of the GENERATED_*_SUBDIRS are relative to STATIC_CONTENTS_SUBDIR
88 GENERATED_HTML_SUBDIR = 'generated-html' 81 GENERATED_HTML_SUBDIR = 'generated-html'
(...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after
132 """Returns routable IP address of this host (the IP address of its network 125 """Returns routable IP address of this host (the IP address of its network
133 interface that would be used for most traffic, not its localhost 126 interface that would be used for most traffic, not its localhost
134 interface). See http://stackoverflow.com/a/166589 """ 127 interface). See http://stackoverflow.com/a/166589 """
135 sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 128 sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
136 sock.connect(('8.8.8.8', 80)) 129 sock.connect(('8.8.8.8', 80))
137 host = sock.getsockname()[0] 130 host = sock.getsockname()[0]
138 sock.close() 131 sock.close()
139 return host 132 return host
140 133
141 134
142 def _create_svn_checkout(dir_path, repo_url):
143 """Creates local checkout of an SVN repository at the specified directory
144 path, returning an svn.Svn object referring to the local checkout.
145
146 Args:
147 dir_path: path to the local checkout; if this directory does not yet exist,
148 it will be created and the repo will be checked out into it
149 repo_url: URL of SVN repo to check out into dir_path (unless the local
150 checkout already exists)
151 Returns: an svn.Svn object referring to the local checkout.
152 """
153 local_checkout = svn.Svn(dir_path)
154 if not os.path.isdir(dir_path):
155 os.makedirs(dir_path)
156 local_checkout.Checkout(repo_url, '.')
157 return local_checkout
158
159
160 def _create_index(file_path, config_pairs): 135 def _create_index(file_path, config_pairs):
161 """Creates an index file linking to all results available from this server. 136 """Creates an index file linking to all results available from this server.
162 137
163 Prior to https://codereview.chromium.org/215503002 , we had a static 138 Prior to https://codereview.chromium.org/215503002 , we had a static
164 index.html within our repo. But now that the results may or may not include 139 index.html within our repo. But now that the results may or may not include
165 config comparisons, index.html needs to be generated differently depending 140 config comparisons, index.html needs to be generated differently depending
166 on which results are included. 141 on which results are included.
167 142
168 TODO(epoger): Instead of including raw HTML within the Python code, 143 TODO(epoger): Instead of including raw HTML within the Python code,
169 consider restoring the index.html file as a template and using django (or 144 consider restoring the index.html file as a template and using django (or
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after
206 file_handle.write('</li>') 181 file_handle.write('</li>')
207 file_handle.write('</ul>') 182 file_handle.write('</ul>')
208 file_handle.write('</ul></body></html>') 183 file_handle.write('</ul></body></html>')
209 184
210 185
211 class Server(object): 186 class Server(object):
212 """ HTTP server for our HTML rebaseline viewer. """ 187 """ HTTP server for our HTML rebaseline viewer. """
213 188
214 def __init__(self, 189 def __init__(self,
215 actuals_dir=DEFAULT_ACTUALS_DIR, 190 actuals_dir=DEFAULT_ACTUALS_DIR,
216 actuals_repo_revision=DEFAULT_ACTUALS_REPO_REVISION, 191 actuals_gs_root=DEFAULT_ACTUALS_GS_ROOT,
217 actuals_repo_url=DEFAULT_ACTUALS_REPO_URL,
218 port=DEFAULT_PORT, export=False, editable=True, 192 port=DEFAULT_PORT, export=False, editable=True,
219 reload_seconds=0, config_pairs=None, builder_regex_list=None): 193 reload_seconds=0, config_pairs=None, builder_regex_list=None):
220 """ 194 """
221 Args: 195 Args:
222 actuals_dir: directory under which we will check out the latest actual 196 actuals_dir: directory into which we will download the latest actual
rmistry 2014/05/30 14:15:28 [optional] since we are not going to be dealing wi
rmistry 2014/05/30 14:16:27 So many typos: "since we are going to be dealing w
223 GM results 197 GM results
224 actuals_repo_revision: revision of actual-results.json files to process 198 actuals_gs_root: Google Storage root to download actual-results.json files
225 actuals_repo_url: SVN repo to download actual-results.json files from; 199 from; if None or '', don't fetch new actual-results files at all,
226 if None or '', don't fetch new actual-results files at all,
227 just compare to whatever files are already in actuals_dir 200 just compare to whatever files are already in actuals_dir
228 port: which TCP port to listen on for HTTP requests 201 port: which TCP port to listen on for HTTP requests
229 export: whether to allow HTTP clients on other hosts to access this server 202 export: whether to allow HTTP clients on other hosts to access this server
230 editable: whether HTTP clients are allowed to submit new baselines 203 editable: whether HTTP clients are allowed to submit new baselines
231 reload_seconds: polling interval with which to check for new results; 204 reload_seconds: polling interval with which to check for new results;
232 if 0, don't check for new results at all 205 if 0, don't check for new results at all
233 config_pairs: List of (string, string) tuples; for each tuple, compare 206 config_pairs: List of (string, string) tuples; for each tuple, compare
234 actual results of these two configs. If None or empty, 207 actual results of these two configs. If None or empty,
235 don't compare configs at all. 208 don't compare configs at all.
236 builder_regex_list: List of regular expressions specifying which builders 209 builder_regex_list: List of regular expressions specifying which builders
237 we will process. If None, process all builders. 210 we will process. If None, process all builders.
238 """ 211 """
239 self._actuals_dir = actuals_dir 212 self._actuals_dir = actuals_dir
240 self._actuals_repo_revision = actuals_repo_revision 213 self._actuals_gs_root = actuals_gs_root
241 self._actuals_repo_url = actuals_repo_url
242 self._port = port 214 self._port = port
243 self._export = export 215 self._export = export
244 self._editable = editable 216 self._editable = editable
245 self._reload_seconds = reload_seconds 217 self._reload_seconds = reload_seconds
246 self._config_pairs = config_pairs or [] 218 self._config_pairs = config_pairs or []
247 self._builder_regex_list = builder_regex_list 219 self._builder_regex_list = builder_regex_list
248 _create_index( 220 _create_index(
249 file_path=os.path.join( 221 file_path=os.path.join(
250 PARENT_DIRECTORY, STATIC_CONTENTS_SUBDIR, GENERATED_HTML_SUBDIR, 222 PARENT_DIRECTORY, STATIC_CONTENTS_SUBDIR, GENERATED_HTML_SUBDIR,
251 "index.html"), 223 "index.html"),
252 config_pairs=config_pairs) 224 config_pairs=config_pairs)
253 if actuals_repo_url:
254 self._actuals_repo = _create_svn_checkout(
255 dir_path=actuals_dir, repo_url=actuals_repo_url)
256 225
257 # Reentrant lock that must be held whenever updating EITHER of: 226 # Reentrant lock that must be held whenever updating EITHER of:
258 # 1. self._results 227 # 1. self._results
259 # 2. the expected or actual results on local disk 228 # 2. the expected or actual results on local disk
260 self.results_rlock = threading.RLock() 229 self.results_rlock = threading.RLock()
261 # self._results will be filled in by calls to update_results() 230 # self._results will be filled in by calls to update_results()
262 self._results = None 231 self._results = None
263 232
264 @property 233 @property
265 def results(self): 234 def results(self):
(...skipping 27 matching lines...) Expand all
293 the same time. 262 the same time.
294 263
295 Args: 264 Args:
296 invalidate: if True, invalidate self._results immediately upon entry; 265 invalidate: if True, invalidate self._results immediately upon entry;
297 otherwise, we will let readers see those results until we 266 otherwise, we will let readers see those results until we
298 replace them 267 replace them
299 """ 268 """
300 with self.results_rlock: 269 with self.results_rlock:
301 if invalidate: 270 if invalidate:
302 self._results = None 271 self._results = None
303 if self._actuals_repo_url: 272 if self._actuals_gs_root:
304 logging.info( 273 logging.info('Updating actual GM results in %s from %s ...' % (
305 'Updating actual GM results in %s to revision %s from repo %s ...' 274 self._actuals_dir, self._actuals_gs_root))
306 % ( 275 if os.path.isdir(self._actuals_dir):
307 self._actuals_dir, self._actuals_repo_revision, 276 shutil.rmtree(self._actuals_dir)
308 self._actuals_repo_url)) 277 os.makedirs(self._actuals_dir)
309 self._actuals_repo.Update( 278 # EPOGER: what if the user does not have gsutil installed in her $PATH?
epoger 2014/05/29 21:19:57 Any thoughts on what to do about this? I had orig
rmistry 2014/05/30 14:15:28 Maybe add a check at the top to make sure that the
310 path='.', revision=self._actuals_repo_revision) 279 _run_command(
280 args=['gsutil', '-m', 'cp', '-R',
281 posixpath.join(self._actuals_gs_root, '*'), '.'],
282 directory=self._actuals_dir)
311 283
312 # We only update the expectations dir if the server was run with a 284 # We only update the expectations dir if the server was run with a
313 # nonzero --reload argument; otherwise, we expect the user to maintain 285 # nonzero --reload argument; otherwise, we expect the user to maintain
314 # her own expectations as she sees fit. 286 # her own expectations as she sees fit.
315 # 287 #
316 # Because the Skia repo is moving from SVN to git, and git does not 288 # Because the Skia repo is moving from SVN to git, and git does not
317 # support updating a single directory tree, we have to update the entire 289 # support updating a single directory tree, we have to update the entire
318 # repo checkout. 290 # repo checkout.
319 # 291 #
320 # Because Skia uses depot_tools, we have to update using "gclient sync" 292 # Because Skia uses depot_tools, we have to update using "gclient sync"
(...skipping 289 matching lines...) Expand 10 before | Expand all | Expand 10 after
610 self.end_headers() 582 self.end_headers()
611 json.dump(json_dict, self.wfile) 583 json.dump(json_dict, self.wfile)
612 584
613 585
614 def main(): 586 def main():
615 logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s', 587 logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s',
616 datefmt='%m/%d/%Y %H:%M:%S', 588 datefmt='%m/%d/%Y %H:%M:%S',
617 level=logging.INFO) 589 level=logging.INFO)
618 parser = argparse.ArgumentParser() 590 parser = argparse.ArgumentParser()
619 parser.add_argument('--actuals-dir', 591 parser.add_argument('--actuals-dir',
620 help=('Directory into which we will check out the latest ' 592 help=('Directory into which we will download the latest '
621 'actual GM results. If this directory does not ' 593 'actual GM results. If this directory does not '
622 'exist, it will be created. Defaults to %(default)s'), 594 'exist, it will be created. Defaults to %(default)s'),
623 default=DEFAULT_ACTUALS_DIR) 595 default=DEFAULT_ACTUALS_DIR)
624 parser.add_argument('--actuals-repo', 596 parser.add_argument('--actuals-gs-root',
625 help=('URL of SVN repo to download actual-results.json ' 597 help=('Google Storage root to download actual-results.json '
626 'files from. Defaults to %(default)s ; if set to ' 598 'files from. Defaults to %(default)s ; if set to '
627 'empty string, just compare to actual-results ' 599 'empty string, just compare to actual-results '
628 'already found in ACTUALS_DIR.'), 600 'already found in ACTUALS_DIR.'),
629 default=DEFAULT_ACTUALS_REPO_URL) 601 default=DEFAULT_ACTUALS_GS_ROOT)
630 parser.add_argument('--actuals-revision',
631 help=('revision of actual-results.json files to process. '
632 'Defaults to %(default)s . Beware of setting this '
633 'argument in conjunction with --editable; you '
634 'probably only want to edit results at HEAD.'),
635 default=DEFAULT_ACTUALS_REPO_REVISION)
636 parser.add_argument('--builders', metavar='BUILDER_REGEX', nargs='+', 602 parser.add_argument('--builders', metavar='BUILDER_REGEX', nargs='+',
637 help=('Only process builders matching these regular ' 603 help=('Only process builders matching these regular '
638 'expressions. If unspecified, process all ' 604 'expressions. If unspecified, process all '
639 'builders.')) 605 'builders.'))
640 parser.add_argument('--compare-configs', action='store_true', 606 parser.add_argument('--compare-configs', action='store_true',
641 help=('In addition to generating differences between ' 607 help=('In addition to generating differences between '
642 'expectations and actuals, also generate ' 608 'expectations and actuals, also generate '
643 'differences between these config pairs: ' 609 'differences between these config pairs: '
644 + str(CONFIG_PAIRS_TO_COMPARE))) 610 + str(CONFIG_PAIRS_TO_COMPARE)))
645 parser.add_argument('--editable', action='store_true', 611 parser.add_argument('--editable', action='store_true',
646 help=('Allow HTTP clients to submit new baselines.')) 612 help=('Allow HTTP clients to submit new baselines.'))
647 parser.add_argument('--export', action='store_true', 613 parser.add_argument('--export', action='store_true',
648 help=('Instead of only allowing access from HTTP clients ' 614 help=('Instead of only allowing access from HTTP clients '
649 'on localhost, allow HTTP clients on other hosts ' 615 'on localhost, allow HTTP clients on other hosts '
650 'to access this server. WARNING: doing so will ' 616 'to access this server. WARNING: doing so will '
651 'allow users on other hosts to modify your ' 617 'allow users on other hosts to modify your '
652 'GM expectations, if combined with --editable.')) 618 'GM expectations, if combined with --editable.'))
653 parser.add_argument('--port', type=int, 619 parser.add_argument('--port', type=int,
654 help=('Which TCP port to listen on for HTTP requests; ' 620 help=('Which TCP port to listen on for HTTP requests; '
655 'defaults to %(default)s'), 621 'defaults to %(default)s'),
656 default=DEFAULT_PORT) 622 default=DEFAULT_PORT)
657 parser.add_argument('--reload', type=int, 623 parser.add_argument('--reload', type=int,
658 help=('How often (a period in seconds) to update the ' 624 help=('How often (a period in seconds) to update the '
659 'results. If specified, both expected and actual ' 625 'results. If specified, both expected and actual '
660 'results will be updated by running "gclient sync" ' 626 'results will be updated, and "gclient sync" will '
661 'on your Skia checkout as a whole. ' 627 'be run on your Skia checkout as a whole. '
662 'By default, we do not reload at all, and you ' 628 'By default, we do not reload at all, and you '
663 'must restart the server to pick up new data.'), 629 'must restart the server to pick up new data.'),
664 default=0) 630 default=0)
665 args = parser.parse_args() 631 args = parser.parse_args()
666 if args.compare_configs: 632 if args.compare_configs:
667 config_pairs = CONFIG_PAIRS_TO_COMPARE 633 config_pairs = CONFIG_PAIRS_TO_COMPARE
668 else: 634 else:
669 config_pairs = None 635 config_pairs = None
670 636
671 global _SERVER 637 global _SERVER
672 _SERVER = Server(actuals_dir=args.actuals_dir, 638 _SERVER = Server(actuals_dir=args.actuals_dir,
673 actuals_repo_revision=args.actuals_revision, 639 actuals_gs_root=args.actuals_gs_root,
674 actuals_repo_url=args.actuals_repo,
675 port=args.port, export=args.export, editable=args.editable, 640 port=args.port, export=args.export, editable=args.editable,
676 reload_seconds=args.reload, config_pairs=config_pairs, 641 reload_seconds=args.reload, config_pairs=config_pairs,
677 builder_regex_list=args.builders) 642 builder_regex_list=args.builders)
678 _SERVER.run() 643 _SERVER.run()
679 644
680 645
681 if __name__ == '__main__': 646 if __name__ == '__main__':
682 main() 647 main()
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698