| 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 |