OLD | NEW |
1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
5 | 5 |
6 """Runs strace or dtrace on a test and processes the logs to extract the | 6 """Runs strace or dtrace on a test and processes the logs to extract the |
7 dependencies from the source tree. | 7 dependencies from the source tree. |
8 | 8 |
9 Automatically extracts directories where all the files are used to make the | 9 Automatically extracts directories where all the files are used to make the |
10 dependencies list more compact. | 10 dependencies list more compact. |
(...skipping 24 matching lines...) Expand all Loading... |
35 '/proc', | 35 '/proc', |
36 '/sys', | 36 '/sys', |
37 '/tmp', | 37 '/tmp', |
38 '/usr', | 38 '/usr', |
39 '/var', | 39 '/var', |
40 ) | 40 ) |
41 | 41 |
42 @staticmethod | 42 @staticmethod |
43 def gen_trace(cmd, cwd, logname): | 43 def gen_trace(cmd, cwd, logname): |
44 """Runs strace on an executable.""" | 44 """Runs strace on an executable.""" |
45 silent = not isEnabledFor(logging.DEBUG) | 45 silent = not isEnabledFor(logging.INFO) |
46 stdout = stderr = None | 46 stdout = stderr = None |
47 if silent: | 47 if silent: |
48 stdout = subprocess.PIPE | 48 stdout = subprocess.PIPE |
49 stderr = subprocess.PIPE | 49 stderr = subprocess.PIPE |
50 trace_cmd = ['strace', '-f', '-e', 'trace=open', '-o', logname] | 50 trace_cmd = ['strace', '-f', '-e', 'trace=open', '-o', logname] |
51 cmd = [os.path.normpath(os.path.join(cwd, c)) for c in cmd] | 51 cmd = [os.path.normpath(os.path.join(cwd, c)) for c in cmd] |
52 p = subprocess.Popen( | 52 p = subprocess.Popen( |
53 trace_cmd + cmd, cwd=cwd, stdout=stdout, stderr=stderr) | 53 trace_cmd + cmd, cwd=cwd, stdout=stdout, stderr=stderr) |
54 out, err = p.communicate() | 54 out, err = p.communicate() |
55 if p.returncode != 0: | 55 if p.returncode != 0: |
56 print 'Failure: %d' % p.returncode | 56 print 'Failure: %d' % p.returncode |
57 # pylint: disable=E1103 | 57 # pylint: disable=E1103 |
58 print ''.join(out.splitlines(True)[-100:]) | 58 if out: |
59 print ''.join(err.splitlines(True)[-100:]) | 59 print ''.join(out.splitlines(True)[-100:]) |
| 60 if err: |
| 61 print ''.join(err.splitlines(True)[-100:]) |
60 return p.returncode | 62 return p.returncode |
61 | 63 |
62 @staticmethod | 64 @staticmethod |
63 def parse_log(filename, blacklist): | 65 def parse_log(filename, blacklist): |
64 """Processes a strace log and returns the files opened and the files that do | 66 """Processes a strace log and returns the files opened and the files that do |
65 not exist. | 67 not exist. |
66 | 68 |
67 Most of the time, files that do not exist are temporary test files that | 69 Most of the time, files that do not exist are temporary test files that |
68 should be put in /tmp instead. See http://crbug.com/116251 | 70 should be put in /tmp instead. See http://crbug.com/116251 |
69 | 71 |
(...skipping 79 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
149 ppid, pid, execname, self->arg0, self->arg1, self->arg2, errno); | 151 ppid, pid, execname, self->arg0, self->arg1, self->arg2, errno); |
150 self->arg0 = 0; | 152 self->arg0 = 0; |
151 self->arg1 = 0; | 153 self->arg1 = 0; |
152 self->arg2 = 0; | 154 self->arg2 = 0; |
153 } | 155 } |
154 """ | 156 """ |
155 | 157 |
156 @classmethod | 158 @classmethod |
157 def gen_trace(cls, cmd, cwd, logname): | 159 def gen_trace(cls, cmd, cwd, logname): |
158 """Runs dtrace on an executable.""" | 160 """Runs dtrace on an executable.""" |
159 silent = not isEnabledFor(logging.DEBUG) | 161 silent = not isEnabledFor(logging.INFO) |
160 print 'Running: %s' % cmd | 162 print 'Running: %s' % cmd |
161 signal = 'Go!' | 163 signal = 'Go!' |
162 logging.debug('Our pid: %d' % os.getpid()) | 164 logging.debug('Our pid: %d' % os.getpid()) |
163 | 165 |
164 # Part 1: start the child process. | 166 # Part 1: start the child process. |
165 stdout = stderr = None | 167 stdout = stderr = None |
166 if silent: | 168 if silent: |
167 stdout = subprocess.PIPE | 169 stdout = subprocess.PIPE |
168 stderr = subprocess.PIPE | 170 stderr = subprocess.PIPE |
169 child_cmd = [ | 171 child_cmd = [ |
(...skipping 109 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
279 os.path.join(directory, f) for f in | 281 os.path.join(directory, f) for f in |
280 os.listdir(os.path.join(root, directory)) | 282 os.listdir(os.path.join(root, directory)) |
281 if not f.endswith(('.svn', '.pyc')) | 283 if not f.endswith(('.svn', '.pyc')) |
282 ) | 284 ) |
283 if not (actual - files): | 285 if not (actual - files): |
284 files -= actual | 286 files -= actual |
285 files.add(directory + '/') | 287 files.add(directory + '/') |
286 return sorted(files) | 288 return sorted(files) |
287 | 289 |
288 | 290 |
289 def trace_inputs(log, cmd, api): | 291 def trace_inputs(log, cmd, api, gyp_proj_dir, product_dir): |
290 """Tries to load the logs if available. If not, trace the test.""" | 292 """Tries to load the logs if available. If not, trace the test.""" |
| 293 def print_if(txt): |
| 294 if not gyp_proj_dir: |
| 295 print(txt) |
| 296 |
291 logname = os.path.join(BASE_DIR, os.path.basename(log)) | 297 logname = os.path.join(BASE_DIR, os.path.basename(log)) |
292 if not os.path.isfile(logname): | 298 if not os.path.isfile(logname): |
293 print 'Tracing... %s' % cmd | 299 print_if('Tracing... %s' % cmd) |
294 returncode = api.gen_trace(cmd, ROOT_DIR, logname) | 300 returncode = api.gen_trace(cmd, ROOT_DIR, logname) |
295 if returncode: | 301 if returncode: |
296 return returncode | 302 return returncode |
297 | 303 |
298 def blacklist(f): | 304 def blacklist(f): |
299 """Strips ignored paths.""" | 305 """Strips ignored paths.""" |
300 return f.startswith(api.IGNORED) or f.endswith('.pyc') | 306 return f.startswith(api.IGNORED) or f.endswith('.pyc') |
301 | 307 |
302 print 'Loading traces... %s' % logname | 308 print_if('Loading traces... %s' % logname) |
303 files, non_existent = api.parse_log(logname, blacklist) | 309 files, non_existent = api.parse_log(logname, blacklist) |
304 print('Total: %d' % len(files)) | 310 |
305 print('Non existent: %d' % len(non_existent)) | 311 print_if('Total: %d' % len(files)) |
| 312 print_if('Non existent: %d' % len(non_existent)) |
306 for f in non_existent: | 313 for f in non_existent: |
307 print(' %s' % f) | 314 print_if(' %s' % f) |
308 | 315 |
309 expected, unexpected = relevant_files(files, ROOT_DIR + '/') | 316 expected, unexpected = relevant_files(files, ROOT_DIR + '/') |
310 if unexpected: | 317 if unexpected: |
311 print('Unexpected: %d' % len(unexpected)) | 318 print_if('Unexpected: %d' % len(unexpected)) |
312 for f in unexpected: | 319 for f in unexpected: |
313 print(' %s' % f) | 320 print_if(' %s' % f) |
314 | 321 |
315 simplified = extract_directories(expected, ROOT_DIR) | 322 simplified = extract_directories(expected, ROOT_DIR) |
316 print('Interesting: %d reduced to %d' % (len(expected), len(simplified))) | 323 print_if('Interesting: %d reduced to %d' % (len(expected), len(simplified))) |
317 for f in simplified: | 324 for f in simplified: |
318 print(' %s' % f) | 325 print_if(' %s' % f) |
319 | 326 |
| 327 if gyp_proj_dir: |
| 328 def fix(f): |
| 329 if f.startswith(product_dir): |
| 330 return '<(PRODUCT_DIR)%s' % f[len(product_dir):] |
| 331 elif f.startswith(gyp_proj_dir): |
| 332 return f[len(gyp_proj_dir)+1:] |
| 333 else: |
| 334 return '<(DEPTH)/%s' % f |
| 335 corrected = [fix(f) for f in simplified] |
| 336 files = [f for f in corrected if not f.endswith('/')] |
| 337 dirs = [f for f in corrected if f.endswith('/')] |
| 338 # Constructs the python code manually. |
| 339 print( |
| 340 '{\n' |
| 341 ' \'variables\': {\n' |
| 342 ' \'isolate_files\': [\n') + ( |
| 343 ''.join(' \'%s\',\n' % f for f in files)) + ( |
| 344 ' ],\n' |
| 345 ' \'isolate_dirs\': [\n') + ( |
| 346 ''.join(' \'%s\',\n' % f for f in dirs)) + ( |
| 347 ' ],\n' |
| 348 ' },\n' |
| 349 '},') |
320 return 0 | 350 return 0 |
321 | 351 |
322 | 352 |
323 def main(): | 353 def main(): |
324 parser = optparse.OptionParser( | 354 parser = optparse.OptionParser( |
325 usage='%prog <options> [cmd line...]') | 355 usage='%prog <options> [cmd line...]') |
326 parser.allow_interspersed_args = False | 356 parser.allow_interspersed_args = False |
327 parser.add_option( | 357 parser.add_option( |
328 '-v', '--verbose', action='count', default=0, help='Use multiple times') | 358 '-v', '--verbose', action='count', default=0, help='Use multiple times') |
329 parser.add_option('-l', '--log', help='Log file') | 359 parser.add_option('-l', '--log', help='Log file') |
| 360 parser.add_option( |
| 361 '-g', '--gyp', |
| 362 help='When specified, outputs the inputs files in a way compatible for ' |
| 363 'gyp processing. Should be set to the relative path containing the ' |
| 364 'gyp file, e.g. \'chrome\' or \'net\'') |
| 365 parser.add_option( |
| 366 '-p', '--product-dir', default='out/Release', |
| 367 help='Directory for PRODUCT_DIR') |
330 | 368 |
331 options, args = parser.parse_args() | 369 options, args = parser.parse_args() |
332 level = [logging.ERROR, logging.INFO, logging.DEBUG][min(2, options.verbose)] | 370 level = [logging.ERROR, logging.INFO, logging.DEBUG][min(2, options.verbose)] |
333 logging.basicConfig( | 371 logging.basicConfig( |
334 level=level, | 372 level=level, |
335 format='%(levelname)5s %(module)15s(%(lineno)3d):%(message)s') | 373 format='%(levelname)5s %(module)15s(%(lineno)3d):%(message)s') |
336 | 374 |
337 if not args: | 375 if not args: |
338 parser.error('Must supply a command to run') | 376 parser.error('Must supply a command to run') |
339 if not options.log: | 377 if not options.log: |
340 parser.error('Must supply a log file with -l') | 378 parser.error('Must supply a log file with -l') |
341 | 379 |
342 if sys.platform == 'linux2': | 380 if sys.platform == 'linux2': |
343 api = Strace | 381 api = Strace |
344 elif sys.platform == 'darwin': | 382 elif sys.platform == 'darwin': |
345 api = Dtrace | 383 api = Dtrace |
346 else: | 384 else: |
347 print >> sys.stderr, 'Unsupported platform' | 385 print >> sys.stderr, 'Unsupported platform' |
348 return 1 | 386 return 1 |
349 | 387 |
350 return trace_inputs(options.log, args, api) | 388 return trace_inputs(options.log, args, api, options.gyp, options.product_dir) |
351 | 389 |
352 | 390 |
353 if __name__ == '__main__': | 391 if __name__ == '__main__': |
354 sys.exit(main()) | 392 sys.exit(main()) |
OLD | NEW |