OLD | NEW |
(Empty) | |
| 1 #!/usr/bin/env python |
| 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 |
| 4 # found in the LICENSE file. |
| 5 |
| 6 """Runs strace on a test and processes the logs to extract the dependencies from |
| 7 the source tree. |
| 8 |
| 9 Automatically extracts directories where all the files are used to make the |
| 10 dependencies list more compact. |
| 11 """ |
| 12 |
| 13 import os |
| 14 import re |
| 15 import subprocess |
| 16 import sys |
| 17 |
| 18 |
| 19 BASE_DIR = os.path.dirname(os.path.abspath(__file__)) |
| 20 ROOT_DIR = os.path.dirname(os.path.dirname(BASE_DIR)) |
| 21 |
| 22 IGNORED = ( |
| 23 '/dev', |
| 24 '/etc', |
| 25 '/home', |
| 26 '/lib', |
| 27 '/proc', |
| 28 '/sys', |
| 29 '/tmp', |
| 30 '/usr', |
| 31 '/var', |
| 32 ) |
| 33 |
| 34 |
| 35 def gen_trace(cmd, cwd, logname, silent): |
| 36 """Runs strace on an executable.""" |
| 37 strace = ['strace', '-f', '-e', 'trace=open', '-o', logname] |
| 38 stdout = stderr = None |
| 39 if silent: |
| 40 stdout = subprocess.PIPE |
| 41 stderr = subprocess.PIPE |
| 42 |
| 43 cmd = [os.path.normpath(os.path.join(cwd, c)) for c in cmd] |
| 44 p = subprocess.Popen( |
| 45 strace + cmd, cwd=cwd, stdout=stdout, stderr=stderr) |
| 46 out, err = p.communicate() |
| 47 if p.returncode != 0: |
| 48 print 'Failure: %d' % p.returncode |
| 49 # pylint: disable=E1103 |
| 50 print ''.join(out.splitlines(True)[-100:]) |
| 51 print ''.join(err.splitlines(True)[-100:]) |
| 52 return p.returncode |
| 53 |
| 54 |
| 55 def parse_log(filename, blacklist): |
| 56 """Processes a strace log and returns the files opened and the files that do |
| 57 not exist. |
| 58 |
| 59 Most of the time, files that do not exist are temporary test files that should |
| 60 be put in /tmp instead. See http://crbug.com/116251 |
| 61 |
| 62 TODO(maruel): Process chdir() calls so relative paths can be processed. |
| 63 """ |
| 64 files = set() |
| 65 non_existent = set() |
| 66 for line in open(filename): |
| 67 # 1=pid, 2=filepath, 3=mode, 4=result |
| 68 m = re.match(r'^(\d+)\s+open\("([^"]+)", ([^\)]+)\)\s+= (.+)$', line) |
| 69 if not m: |
| 70 continue |
| 71 if m.group(4).startswith('-1') or 'O_DIRECTORY' in m.group(3): |
| 72 # Not present or a directory. |
| 73 continue |
| 74 filepath = m.group(2) |
| 75 if blacklist(filepath): |
| 76 continue |
| 77 if not os.path.isfile(filepath): |
| 78 non_existent.add(filepath) |
| 79 else: |
| 80 files.add(filepath) |
| 81 return files, non_existent |
| 82 |
| 83 |
| 84 def relevant_files(files, root): |
| 85 """Trims the list of files to keep the expected files and unexpected files. |
| 86 |
| 87 Unexpected files are files that are not based inside the |root| directory. |
| 88 """ |
| 89 expected = [] |
| 90 unexpected = [] |
| 91 for f in files: |
| 92 if f.startswith(root): |
| 93 expected.append(f[len(root):]) |
| 94 else: |
| 95 unexpected.append(f) |
| 96 return sorted(set(expected)), sorted(set(unexpected)) |
| 97 |
| 98 |
| 99 def extract_directories(files, root): |
| 100 """Detects if all the files in a directory were loaded and if so, replace the |
| 101 individual files by the directory entry. |
| 102 """ |
| 103 directories = set(os.path.dirname(f) for f in files) |
| 104 files = set(files) |
| 105 for directory in sorted(directories, reverse=True): |
| 106 actual = set( |
| 107 os.path.join(directory, f) for f in |
| 108 os.listdir(os.path.join(root, directory)) |
| 109 if not f.endswith(('.svn', '.pyc')) |
| 110 ) |
| 111 if not (actual - files): |
| 112 files -= actual |
| 113 files.add(directory + '/') |
| 114 return sorted(files) |
| 115 |
| 116 |
| 117 def strace_inputs(unittest, cmd): |
| 118 """Tries to load the logs if available. If not, strace the test.""" |
| 119 logname = os.path.join(BASE_DIR, os.path.basename(unittest)) |
| 120 if not os.path.isfile(logname): |
| 121 returncode = gen_trace(cmd, ROOT_DIR, logname, True) |
| 122 if returncode: |
| 123 return returncode |
| 124 |
| 125 def blacklist(f): |
| 126 """Strips ignored paths.""" |
| 127 return f.startswith(IGNORED) or f.endswith('.pyc') |
| 128 |
| 129 files, non_existent = parse_log(logname, blacklist) |
| 130 print('Total: %d' % len(files)) |
| 131 print('Non existent: %d' % len(non_existent)) |
| 132 for f in non_existent: |
| 133 print(' %s' % f) |
| 134 |
| 135 expected, unexpected = relevant_files(files, ROOT_DIR + '/') |
| 136 if unexpected: |
| 137 print('Unexpected: %d' % len(unexpected)) |
| 138 for f in unexpected: |
| 139 print(' %s' % f) |
| 140 |
| 141 simplified = extract_directories(expected, ROOT_DIR) |
| 142 print('Interesting: %d reduced to %d' % (len(expected), len(simplified))) |
| 143 for f in simplified: |
| 144 print(' %s' % f) |
| 145 |
| 146 return 0 |
| 147 |
| 148 |
| 149 def main(): |
| 150 if len(sys.argv) < 3: |
| 151 print >> sys.stderr, ( |
| 152 'Usage: strace_inputs.py [testname] [cmd line...]\n' |
| 153 '\n' |
| 154 'Example:\n' |
| 155 ' ./strace_inputs.py base_unittests testing/xvfb.py out/Release ' |
| 156 'out/Release/base_unittests') |
| 157 return 1 |
| 158 return strace_inputs(sys.argv[1], sys.argv[2:]) |
| 159 |
| 160 |
| 161 if __name__ == '__main__': |
| 162 sys.exit(main()) |
OLD | NEW |