OLD | NEW |
---|---|
(Empty) | |
1 """Automate the setup process of Chrome Endure environment. | |
2 | |
3 Usage: | |
4 ./endure.py [fetch|server] [option] | |
5 | |
6 Command "fetch" will automatically setup Chrome Endure environment. | |
7 The script will do the following things: | |
8 1) Set up depot_tool if necessary. | |
9 2) Check out python code which Chrome Endure depends on. | |
10 3) Check out pre-built binary of Chrome, PyAuto lib and chrome driver | |
11 4) Check out chrome graphing file which are needed to show the test results. | |
12 | |
13 After fetching Chrome Endure, you should be able to run the endure test. Try: | |
14 TEST_LENGTH=60 LOCAL_GRAPH='./chrome_graph' | |
15 python ./src/chrome/test/functional/perf_endure.py | |
16 perf_endure.ChromeEndureGmailTest.testGmailComposeDiscard | |
17 The above commands runs an Chrome Endure test for 60 seconds and saves | |
18 the results to ./chrome_graph. LOCAL_GRAPH is the directory where you have | |
19 chrome graphing files. By default, it is <DIR_OF_THIS_SCIRPT>/chrome_endure. | |
20 | |
21 Command "server" will start a local HTTP server to serve the directory which | |
22 contains the result data files. Run this command after you have test results | |
23 output to <DIR_OF_THIS_SCRIPT>/chrome_endure and a server will be started with | |
24 a port that is automatically picked. You can then view the result graphs | |
25 via http://localhost:<GIVEN_PORT>. | |
26 | |
27 Use ./endure [fetch|server] --help for more options. | |
28 """ | |
29 | |
30 import BaseHTTPServer | |
31 import optparse | |
32 import os | |
33 import platform | |
34 import shutil | |
35 import SimpleHTTPServer | |
36 import subprocess | |
37 import sys | |
38 import urllib | |
39 import urllib2 | |
40 import zipfile | |
41 | |
42 | |
43 class SetupError(Exception): | |
44 """Catch errors in setting up Chrome Endure.""" | |
45 | |
46 def __init__(self, label, message): | |
47 super(SetupError, self).__init__() | |
48 self.label = label | |
49 self.message = message | |
50 | |
51 def __str__(self): | |
52 return self.label+'\n'+str(self.message) | |
53 | |
54 | |
55 class PlainHelpFormatter(optparse.IndentedHelpFormatter): | |
56 """Format the help message of this script.""" | |
57 | |
58 def format_description(self, description): | |
59 if description: | |
60 return description + '\n' | |
61 else: | |
62 return '' | |
63 | |
64 | |
65 class CmdFetch(object): | |
66 """Fetch Chrome Endure. | |
67 | |
68 Usage: | |
69 ./endure.py fetch [options] | |
70 Examples: | |
71 ./endure.py fetch | |
72 Fetch latest version of Chrome Endure to the same directory as this script. | |
73 | |
74 ./endure.py fetch --endure-dir=/home/user/endure_dir --revision=18043 | |
75 Fetch revision 18043 to /home/user/endure_dir | |
76 """ | |
77 _URLS = {'depot_tools': ('http://src.chromium.org' | |
78 '/chrome/trunk/tools/depot_tools'), | |
79 'pyauto': ('https://src.chromium.org/' | |
80 'chrome/trunk/src/chrome/test/functional.DEPS'), | |
81 'binary': ('http://commondatastorage.googleapis.com/' | |
82 'chromium-browser-continuous/{os_type}/{revision}'), | |
83 # TODO(fdeng): change to an external address after graph code | |
84 # is checked in | |
85 'graph': ('http://www.corp.google.com/' | |
86 '~dennisjeffrey/chrome_perf/local_graphs.zip'), | |
87 } | |
88 NAME = 'fetch' | |
89 DESCRIPTION = 'Fetch Chrome Endure.' | |
90 | |
91 def _ParseArgs(self, argv): | |
92 parser = optparse.OptionParser( | |
93 usage='%%prog %s [options]' % self.NAME, | |
94 formatter=PlainHelpFormatter(), | |
95 description=self.__doc__) | |
96 parser.add_option( | |
97 '-d', '--endure-dir', type='string', default=os.path.dirname(__file__), | |
98 help='Directory in which to setup or update.') | |
99 parser.add_option( | |
100 '-r', '--revision', type='string', default=None, | |
101 help='Revision of Chrome tree to get Chrome Endure.') | |
102 return parser.parse_args(argv) | |
103 | |
104 def Run(self, argv): | |
105 """Run this command.""" | |
106 options, _ = self._ParseArgs(argv) | |
107 self._endure_dir = os.path.abspath(options.endure_dir) | |
108 self._revision = options.revision | |
109 if not self._revision: | |
110 self._revision = self._GetLatestRevision(self._GetCurrentOSType()) | |
111 self._os_type = self._GetCurrentOSType() | |
112 self._depot_dir = os.path.join(self._endure_dir, 'depot_tools') | |
113 self._gclient = os.path.join(self._depot_dir, 'gclient') | |
114 self._fetch_py = os.path.join(self._endure_dir, 'src', 'chrome', | |
115 'test', 'pyautolib', | |
116 'fetch_prebuilt_pyauto.py') | |
117 self._binary_dir = os.path.join(self._endure_dir, 'src', 'out', 'Release') | |
118 self._graph_dir = os.path.join(self._endure_dir, 'chrome_graph') | |
119 | |
120 if not os.path.isdir(self._endure_dir): | |
121 os.makedirs(self._endure_dir) | |
122 | |
123 print 'Checking depot tools...' | |
124 self._FetchDepot() | |
125 print 'Fetching PyAuto (python code)...' | |
126 self._FetchPyAuto(self._revision) | |
127 print 'Fetching binaries(chrome, pyautolib, chrome driver)...' | |
128 self._FetchBinaries(self._os_type, self._revision) | |
129 # TODO(fdeng): remove this after it is check into the chrome tree. | |
130 print 'Fetching chrome graphing files...' | |
131 self._FetchGraph() | |
132 return 0 | |
133 | |
134 def _FetchDepot(self): | |
135 """Fetch depot_tool if not installed in the system.""" | |
136 try: | |
137 subprocess.call(['gclient', '--version']) | |
138 self._gclient = 'gclient' | |
139 except OSError: | |
140 try: | |
141 # TODO(fdeng): how can I know gclient is not installed | |
142 # without checking OSError, a better way? | |
143 url = self._URLS['depot_tools'] | |
144 print 'Fetching depot tools: %s' % url | |
145 subprocess.call(['svn', 'co', self._URLS['depot_tools'], | |
146 self._depot_dir]) | |
147 subprocess.call([self._gclient, '--version']) | |
148 except Exception as e: | |
149 raise SetupError('Unable to set up depot tools:', str(e)) | |
150 | |
151 def _FetchPyAuto(self, revision): | |
152 """Use gclient to fetch python code.""" | |
153 cur_dir = os.getcwd() | |
154 try: | |
155 os.chdir(self._endure_dir) | |
156 # gclient config | |
157 config_cmd = [self._gclient, 'config', self._URLS['pyauto']] | |
158 code = subprocess.call(config_cmd) | |
159 if code != 0: | |
160 raise Exception('Running "%s" failed.' % ' '.join(config_cmd)) | |
161 # gclient sync | |
162 sync_cmd = [self._gclient, 'sync'] | |
163 if revision: | |
164 sync_cmd.extend(['--revision', 'functional.DEPS@'+(revision)]) | |
fdeng1
2012/08/07 22:19:45
Here the script tries to run "gclient sync --revis
dennis_jeffrey
2012/08/08 23:41:07
As discussed offline, I think it'll be fine for no
| |
165 code = subprocess.call(sync_cmd) | |
166 if code != 0: | |
167 raise Exception('Running "%s" failed.' % ' '.join(sync_cmd)) | |
168 except Exception as e: | |
169 raise SetupError('Unable to fetch PyAuto code', str(e)) | |
170 finally: | |
171 os.chdir(cur_dir) | |
172 | |
173 def _FetchBinaries(self, os_type, revision): | |
174 """Get the prebuilt binaries from continuous build archive.""" | |
175 try: | |
176 if not os.path.exists(self._fetch_py): | |
177 raise Exception( | |
178 'Unable to find %s, did fetching python code succeed?' | |
179 % self._fetch_py) | |
180 print 'Cleaning %s' % self._binary_dir | |
181 if os.path.exists(self._binary_dir): | |
182 shutil.rmtree(self._binary_dir) | |
183 print 'Downloading...' | |
184 cmd = [self._fetch_py, '-d', self._binary_dir, | |
185 self._URLS['binary'].format(os_type=os_type, revision=revision)] | |
186 code = subprocess.call(cmd) | |
187 if code != 0: | |
188 raise Exception('Running "%s" failed.' % ' '.join(cmd)) | |
189 except Exception as e: | |
190 raise SetupError( | |
191 'Unable to fetch binaries for chrome/pyauto/chrome_driver:', str(e)) | |
192 | |
193 def _FetchGraph(self): | |
194 """Fetch graph code.""" | |
195 try: | |
196 graph_zip = urllib.urlretrieve(self._URLS['graph'])[0] | |
197 if os.path.exists(self._graph_dir): | |
198 print 'Cleaning %s, ' % self._graph_dir, | |
199 print 'data files(.dat) will be preserved.' | |
200 files = os.listdir(self._graph_dir) | |
201 for f in files: | |
202 path = os.path.join(self._graph_dir, f) | |
203 if os.path.isdir(path): | |
204 shutil.rmtree(path) | |
205 elif os.path.splitext(f) != 'dat': | |
206 os.remove(path) | |
207 else: | |
208 os.mkdir(self._graph_dir) | |
209 self._UnzipFilenameToDir(graph_zip, self._graph_dir) | |
210 except Exception as e: | |
211 raise SetupError('Unable to fetch graph files:', str(e)) | |
212 | |
213 def _GetCurrentOSType(self): | |
214 """Get a string representation for the current os. | |
215 | |
216 Returns: | |
217 'Mac', 'Win', 'Linux', or 'Linux_64' | |
218 | |
219 Raises: | |
220 RuntimeError: if os can't be identified. | |
221 """ | |
222 if sys.platform == 'darwin': | |
223 os_type = 'Mac' | |
224 if sys.platform == 'win32': | |
225 os_type = 'Win' | |
226 if sys.platform.startswith('linux'): | |
227 os_type = 'Linux' | |
228 if platform.architecture()[0] == '64bit': | |
229 os_type += '_x64' | |
230 else: | |
231 raise RuntimeError('Unknown platform') | |
232 return os_type | |
233 | |
234 def _GetLatestRevision(self, os_type): | |
235 """Figure out the latest revision number of the prebuilt binary archive. | |
236 | |
237 Args: | |
238 os_type: 'Mac', 'Win', 'Linux', or 'Linux_64' | |
239 | |
240 Returns: | |
241 A string of latest revision number or None on fail. | |
242 """ | |
243 last_change_url = ('http://commondatastorage.googleapis.com/' | |
244 'chromium-browser-continuous/%s/LAST_CHANGE' % os_type) | |
245 response = urllib2.urlopen(last_change_url) | |
246 last_change = response.read() | |
247 if not last_change: | |
248 print 'Unable to get latest revision number from %s' % last_change_url | |
249 return None | |
250 return last_change | |
251 | |
252 @classmethod | |
253 def _UnzipFilenameToDir(cls, filename, directory): | |
254 """Unzip |filename| to directory |directory|. | |
255 | |
256 This works with as low as python2.4 (used on win). | |
257 """ | |
258 zf = zipfile.ZipFile(filename) | |
259 pushd = os.getcwd() | |
260 if not os.path.isdir(directory): | |
261 os.mkdir(directory) | |
262 os.chdir(directory) | |
263 # Extract files. | |
264 for info in zf.infolist(): | |
265 name = info.filename | |
266 print name | |
267 if name.endswith('/'): # dir | |
268 if not os.path.isdir(name): | |
269 os.makedirs(name) | |
270 else: # file | |
271 directory = os.path.dirname(name) | |
272 if directory and not os.path.isdir(directory): | |
273 os.makedirs(directory) | |
274 out = open(name, 'wb') | |
275 out.write(zf.read(name)) | |
276 out.close() | |
277 # Set permissions. Permission info in external_attr is shifted 16 bits. | |
278 os.chmod(name, info.external_attr >> 16L) | |
279 os.chdir(pushd) | |
280 | |
281 | |
282 class CmdServer(object): | |
283 """Start an http server which serves the Chrome Endure test results. | |
284 | |
285 Usage: | |
286 ./endure server [options] | |
287 Examples: | |
288 ./endure.py server | |
289 By default, it will serve the directory <DIR_OF_THIS_SCIRPT>/chrome_graph | |
290 where DIR_OF_THIS_SCIRPT is the directory of this script (endure.py). | |
291 | |
292 ./endure.py server --graph-dir=/home/user/Document/graph_dir | |
293 Use --graph-dir if you have your graph directory at a different place. | |
294 """ | |
295 NAME = 'server' | |
296 DESCRIPTION = 'Start an http server for viewing the' \ | |
297 'tests results from a browser.' | |
298 | |
299 def _ParseArgs(self, argv): | |
300 parser = optparse.OptionParser( | |
301 usage='%%prog %s [options]' % self.NAME, | |
302 formatter=PlainHelpFormatter(), | |
303 description=self.__doc__) | |
304 parser.add_option( | |
305 '-g', '--graph-dir', type='string', | |
306 default=os.path.join(os.path.dirname(__file__), 'chrome_graph'), | |
307 help='The directory that contains graphing files' \ | |
308 'and data files of test results') | |
309 return parser.parse_args(argv) | |
310 | |
311 def Run(self, argv): | |
312 """Run this command.""" | |
313 options, _ = self._ParseArgs(argv) | |
314 self._graph_dir = os.path.abspath(options.graph_dir) | |
315 cur_dir = os.getcwd() | |
316 os.chdir(self._graph_dir) | |
317 httpd = BaseHTTPServer.HTTPServer(('', 0), | |
318 EndureHTTPRequestHandler) | |
319 try: | |
320 print 'Serving %s at port %d' % (self._graph_dir, httpd.server_port) | |
321 print 'View test results at http://localhost:%d' % httpd.server_port | |
322 print 'Press Ctrl-C to stop the server.' | |
323 httpd.serve_forever() | |
324 except KeyboardInterrupt: | |
325 print 'Shutting down ...' | |
326 httpd.shutdown() | |
327 finally: | |
328 os.chdir(cur_dir) | |
329 return 0 | |
330 | |
331 | |
332 class EndureHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): | |
fdeng1
2012/08/07 22:09:43
I tried to reuse the TestServer in pyautolib.py. I
dennis_jeffrey
2012/08/08 23:41:07
I think it's fine to have our own basic test serve
| |
333 """A simple HTTP request handler for showing Chrome Endure test results.""" | |
334 | |
335 def send_head(self): | |
fdeng1
2012/08/07 22:09:43
I wish I didn't have to override this function. Bu
dennis_jeffrey
2012/08/08 23:41:07
As discussed offline, let's no longer override thi
fdeng1
2012/08/09 03:26:16
I am able to solve the problem by modifying the se
dennis_jeffrey
2012/08/09 17:16:27
Great! I made the change on the live chrome endur
| |
336 """Override send_head so it won't redirect if path is not ended with "/". | |
337 | |
338 Common code for GET and HEAD commands. This sends the response | |
339 code and MIME headers. | |
340 | |
341 Returns: | |
342 either a file object (which has to be copied | |
343 to the outputfile by the caller unless the command was HEAD, | |
344 and must be closed by the caller under all circumstances), or | |
345 None, in which case the caller has nothing further to do. | |
346 """ | |
347 path = self.translate_path(self.path) | |
348 f = None | |
349 if os.path.isdir(path): | |
350 for index in 'index.html', 'index.htm': | |
351 index = os.path.join(path, index) | |
352 if os.path.exists(index): | |
353 path = index | |
354 break | |
355 else: | |
356 return self.list_directory(path) | |
357 ctype = self.guess_type(path) | |
358 try: | |
359 # Always read in binary mode. Opening files in text mode may cause | |
360 # newline translations, making the actual size of the content | |
361 # transmitted *less* than the content-length! | |
362 f = open(path, 'rb') | |
363 except IOError: | |
364 self.send_error(404, 'File not found') | |
365 return None | |
366 self.send_response(200) | |
367 self.send_header('Content-type', ctype) | |
368 fs = os.fstat(f.fileno()) | |
369 self.send_header('Content-Length', str(fs[6])) | |
370 self.send_header('Last-Modified', self.date_time_string(fs.st_mtime)) | |
371 self.end_headers() | |
372 return f | |
373 | |
374 | |
375 class CmdHelp(object): | |
376 """Print a list of commands or help for a specific command.""" | |
377 NAME = 'help' | |
378 DESCRIPTION = __doc__ | |
379 | |
380 def __init__(self, cmds): | |
381 """Initialize help command. | |
382 | |
383 Args: | |
384 cmds: Commands for which help information will be printed. | |
385 """ | |
386 self.cmds = cmds | |
387 | |
388 def Run(self, args): | |
389 """Run this command.""" | |
390 if len(args) == 1: | |
391 return Main(args + ['--help']) | |
392 print 'Usage:\n ./endure.py [options] command' | |
393 print 'Commands are:' | |
394 for cmd in self.cmds: | |
395 print '\t%s\t\t%s' % (cmd.NAME, cmd.DESCRIPTION) | |
396 | |
397 print 'Examples:' | |
398 print ' ./endure.py %s' % CmdFetch.NAME | |
399 return 0 | |
400 | |
401 | |
402 def Main(argv): | |
403 if argv and argv[0] == CmdFetch.NAME: | |
404 command = CmdFetch() | |
405 elif argv and argv[0] == CmdServer.NAME: | |
406 command = CmdServer() | |
407 else: | |
408 cmds = [CmdFetch, CmdServer, CmdHelp] | |
409 command = CmdHelp(cmds) | |
410 return command.Run(argv[1:]) | |
411 | |
412 | |
413 if '__main__' == __name__: | |
414 sys.exit(Main(sys.argv[1:])) | |
OLD | NEW |