OLD | NEW |
1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
2 # Copyright (c) 2013 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2013 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 """Performance Test Bisect Tool | 6 """Performance Test Bisect Tool |
7 | 7 |
8 This script bisects a series of changelists using binary search. It starts at | 8 This script bisects a series of changelists using binary search. It starts at |
9 a bad revision where a performance metric has regressed, and asks for a last | 9 a bad revision where a performance metric has regressed, and asks for a last |
10 known-good revision. It will then binary search across this revision range by | 10 known-good revision. It will then binary search across this revision range by |
11 syncing, building, and running a performance test. If the change is | 11 syncing, building, and running a performance test. If the change is |
12 suspected to occur as a result of WebKit/V8 changes, the script will | 12 suspected to occur as a result of WebKit/V8 changes, the script will |
13 further bisect changes to those depots and attempt to narrow down the revision | 13 further bisect changes to those depots and attempt to narrow down the revision |
14 range. | 14 range. |
15 | 15 |
16 Example usage using SVN revisions: | 16 Example usage using SVN revisions: |
17 | 17 |
18 ./tools/bisect-perf-regression.py -c\ | 18 ./tools/bisect_perf_regression.py -c\ |
19 "out/Release/performance_ui_tests --gtest_filter=ShutdownTest.SimpleUserQuit"\ | 19 "out/Release/performance_ui_tests --gtest_filter=ShutdownTest.SimpleUserQuit"\ |
20 -g 168222 -b 168232 -m shutdown/simple-user-quit | 20 -g 168222 -b 168232 -m shutdown/simple-user-quit |
21 | 21 |
22 Be aware that if you're using the git workflow and specify an SVN revision, | 22 Be aware that if you're using the git workflow and specify an SVN revision, |
23 the script will attempt to find the git SHA1 where SVN changes up to that | 23 the script will attempt to find the git SHA1 where SVN changes up to that |
24 revision were merged in. | 24 revision were merged in. |
25 | 25 |
26 Example usage using git hashes: | 26 Example usage using git hashes: |
27 | 27 |
28 ./tools/bisect-perf-regression.py -c\ | 28 ./tools/bisect_perf_regression.py -c\ |
29 "out/Release/performance_ui_tests --gtest_filter=ShutdownTest.SimpleUserQuit"\ | 29 "out/Release/performance_ui_tests --gtest_filter=ShutdownTest.SimpleUserQuit"\ |
30 -g 1f6e67861535121c5c819c16a666f2436c207e7b\ | 30 -g 1f6e67861535121c5c819c16a666f2436c207e7b\ |
31 -b b732f23b4f81c382db0b23b9035f3dadc7d925bb\ | 31 -b b732f23b4f81c382db0b23b9035f3dadc7d925bb\ |
32 -m shutdown/simple-user-quit | 32 -m shutdown/simple-user-quit |
33 """ | 33 """ |
34 | 34 |
35 import copy | 35 import copy |
36 import datetime | 36 import datetime |
37 import errno | 37 import errno |
38 import hashlib | 38 import hashlib |
39 import math | 39 import math |
40 import optparse | 40 import optparse |
41 import os | 41 import os |
42 import re | 42 import re |
43 import shlex | 43 import shlex |
44 import shutil | 44 import shutil |
45 import StringIO | 45 import StringIO |
46 import sys | 46 import sys |
47 import time | 47 import time |
48 import zipfile | 48 import zipfile |
49 | 49 |
50 sys.path.append(os.path.join(os.path.dirname(__file__), 'telemetry')) | 50 sys.path.append(os.path.join( |
| 51 os.path.dirname(__file__), os.path.pardir, 'telemetry')) |
51 | 52 |
52 from auto_bisect import bisect_utils | 53 import bisect_utils |
53 from auto_bisect import builder | 54 import builder |
54 from auto_bisect import math_utils | 55 import math_utils |
55 from auto_bisect import request_build | 56 import request_build |
56 from auto_bisect import source_control as source_control_module | 57 import source_control as source_control_module |
57 from auto_bisect import ttest | 58 import ttest |
58 from telemetry.util import cloud_storage | 59 from telemetry.util import cloud_storage |
59 | 60 |
60 # Below is the map of "depot" names to information about each depot. Each depot | 61 # Below is the map of "depot" names to information about each depot. Each depot |
61 # is a repository, and in the process of bisecting, revision ranges in these | 62 # is a repository, and in the process of bisecting, revision ranges in these |
62 # repositories may also be bisected. | 63 # repositories may also be bisected. |
63 # | 64 # |
64 # Each depot information dictionary may contain: | 65 # Each depot information dictionary may contain: |
65 # src: Path to the working directory. | 66 # src: Path to the working directory. |
66 # recurse: True if this repository will get bisected. | 67 # recurse: True if this repository will get bisected. |
67 # depends: A list of other repositories that are actually part of the same | 68 # depends: A list of other repositories that are actually part of the same |
(...skipping 75 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
143 'svn': 'http://skia.googlecode.com/svn/trunk/gyp', | 144 'svn': 'http://skia.googlecode.com/svn/trunk/gyp', |
144 'depends': None, | 145 'depends': None, |
145 'from': ['chromium'], | 146 'from': ['chromium'], |
146 'viewvc': 'https://code.google.com/p/skia/source/detail?r=', | 147 'viewvc': 'https://code.google.com/p/skia/source/detail?r=', |
147 'deps_var': 'None' | 148 'deps_var': 'None' |
148 } | 149 } |
149 } | 150 } |
150 | 151 |
151 DEPOT_NAMES = DEPOT_DEPS_NAME.keys() | 152 DEPOT_NAMES = DEPOT_DEPS_NAME.keys() |
152 | 153 |
| 154 # The script is in chromium/src/tools/auto_bisect. Throughout this script, |
| 155 # we use paths to other things in the chromium/src repository. |
| 156 |
153 CROS_CHROMEOS_PATTERN = 'chromeos-base/chromeos-chrome' | 157 CROS_CHROMEOS_PATTERN = 'chromeos-base/chromeos-chrome' |
154 | 158 |
155 # Possible return values from BisectPerformanceMetrics.RunTest. | 159 # Possible return values from BisectPerformanceMetrics.RunTest. |
156 BUILD_RESULT_SUCCEED = 0 | 160 BUILD_RESULT_SUCCEED = 0 |
157 BUILD_RESULT_FAIL = 1 | 161 BUILD_RESULT_FAIL = 1 |
158 BUILD_RESULT_SKIPPED = 2 | 162 BUILD_RESULT_SKIPPED = 2 |
159 | 163 |
160 # Maximum time in seconds to wait after posting build request to the try server. | 164 # Maximum time in seconds to wait after posting build request to the try server. |
161 # TODO: Change these values based on the actual time taken by buildbots on | 165 # TODO: Change these values based on the actual time taken by buildbots on |
162 # the try server. | 166 # the try server. |
(...skipping 743 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
906 revisions to narrow down where performance regressions may have occurred. | 910 revisions to narrow down where performance regressions may have occurred. |
907 | 911 |
908 The main entry-point is the Run method. | 912 The main entry-point is the Run method. |
909 """ | 913 """ |
910 | 914 |
911 def __init__(self, source_control, opts): | 915 def __init__(self, source_control, opts): |
912 super(BisectPerformanceMetrics, self).__init__() | 916 super(BisectPerformanceMetrics, self).__init__() |
913 | 917 |
914 self.opts = opts | 918 self.opts = opts |
915 self.source_control = source_control | 919 self.source_control = source_control |
| 920 |
| 921 # The src directory here is NOT the src/ directory for the repository |
| 922 # where the bisect script is running from. Instead, it's the src/ directory |
| 923 # inside the bisect/ directory which is created before running. |
916 self.src_cwd = os.getcwd() | 924 self.src_cwd = os.getcwd() |
917 self.cros_cwd = os.path.join(os.getcwd(), '..', 'cros') | 925 self.cros_cwd = os.path.join(os.getcwd(), '..', 'cros') |
918 self.depot_cwd = {} | 926 self.depot_cwd = {} |
919 self.cleanup_commands = [] | 927 self.cleanup_commands = [] |
920 self.warnings = [] | 928 self.warnings = [] |
921 self.builder = builder.Builder.FromOpts(opts) | 929 self.builder = builder.Builder.FromOpts(opts) |
922 | 930 |
923 for d in DEPOT_NAMES: | 931 for depot in DEPOT_NAMES: |
924 # The working directory of each depot is just the path to the depot, but | 932 # The working directory of each depot is just the path to the depot, but |
925 # since we're already in 'src', we can skip that part. | 933 # since we're already in 'src', we can skip that part. |
926 | 934 self.depot_cwd[depot] = os.path.join( |
927 self.depot_cwd[d] = os.path.join( | 935 self.src_cwd, DEPOT_DEPS_NAME[depot]['src'][4:]) |
928 self.src_cwd, DEPOT_DEPS_NAME[d]['src'][4:]) | |
929 | 936 |
930 def PerformCleanup(self): | 937 def PerformCleanup(self): |
931 """Performs cleanup when script is finished.""" | 938 """Performs cleanup when script is finished.""" |
932 os.chdir(self.src_cwd) | 939 os.chdir(self.src_cwd) |
933 for c in self.cleanup_commands: | 940 for c in self.cleanup_commands: |
934 if c[0] == 'mv': | 941 if c[0] == 'mv': |
935 shutil.move(c[1], c[2]) | 942 shutil.move(c[1], c[2]) |
936 else: | 943 else: |
937 assert False, 'Invalid cleanup command.' | 944 assert False, 'Invalid cleanup command.' |
938 | 945 |
(...skipping 144 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1083 results = {} | 1090 results = {} |
1084 for depot_name, depot_data in DEPOT_DEPS_NAME.iteritems(): | 1091 for depot_name, depot_data in DEPOT_DEPS_NAME.iteritems(): |
1085 if (depot_data.get('platform') and | 1092 if (depot_data.get('platform') and |
1086 depot_data.get('platform') != os.name): | 1093 depot_data.get('platform') != os.name): |
1087 continue | 1094 continue |
1088 | 1095 |
1089 if (depot_data.get('recurse') and depot in depot_data.get('from')): | 1096 if (depot_data.get('recurse') and depot in depot_data.get('from')): |
1090 depot_data_src = depot_data.get('src') or depot_data.get('src_old') | 1097 depot_data_src = depot_data.get('src') or depot_data.get('src_old') |
1091 src_dir = deps_data.get(depot_data_src) | 1098 src_dir = deps_data.get(depot_data_src) |
1092 if src_dir: | 1099 if src_dir: |
1093 self.depot_cwd[depot_name] = os.path.join(self.src_cwd, | 1100 self.depot_cwd[depot_name] = os.path.join( |
1094 depot_data_src[4:]) | 1101 self.src_cwd, depot_data_src[4:]) |
1095 re_results = rxp.search(src_dir) | 1102 re_results = rxp.search(src_dir) |
1096 if re_results: | 1103 if re_results: |
1097 results[depot_name] = re_results.group('revision') | 1104 results[depot_name] = re_results.group('revision') |
1098 else: | 1105 else: |
1099 warning_text = ('Could not parse revision for %s while bisecting ' | 1106 warning_text = ('Could not parse revision for %s while bisecting ' |
1100 '%s' % (depot_name, depot)) | 1107 '%s' % (depot_name, depot)) |
1101 if not warning_text in self.warnings: | 1108 if not warning_text in self.warnings: |
1102 self.warnings.append(warning_text) | 1109 self.warnings.append(warning_text) |
1103 else: | 1110 else: |
1104 results[depot_name] = None | 1111 results[depot_name] = None |
(...skipping 455 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1560 def _IsBisectModeUsingMetric(self): | 1567 def _IsBisectModeUsingMetric(self): |
1561 return self.opts.bisect_mode in [BISECT_MODE_MEAN, BISECT_MODE_STD_DEV] | 1568 return self.opts.bisect_mode in [BISECT_MODE_MEAN, BISECT_MODE_STD_DEV] |
1562 | 1569 |
1563 def _IsBisectModeReturnCode(self): | 1570 def _IsBisectModeReturnCode(self): |
1564 return self.opts.bisect_mode in [BISECT_MODE_RETURN_CODE] | 1571 return self.opts.bisect_mode in [BISECT_MODE_RETURN_CODE] |
1565 | 1572 |
1566 def _IsBisectModeStandardDeviation(self): | 1573 def _IsBisectModeStandardDeviation(self): |
1567 return self.opts.bisect_mode in [BISECT_MODE_STD_DEV] | 1574 return self.opts.bisect_mode in [BISECT_MODE_STD_DEV] |
1568 | 1575 |
1569 def GetCompatibleCommand(self, command_to_run, revision, depot): | 1576 def GetCompatibleCommand(self, command_to_run, revision, depot): |
1570 # Prior to crrev.com/274857 *only* android-chromium-testshell | 1577 """Return a possibly modified test command depending on the revision. |
1571 # Then until crrev.com/276628 *both* (android-chromium-testshell and | 1578 |
1572 # android-chrome-shell) work. After that rev 276628 *only* | 1579 Prior to crrev.com/274857 *only* android-chromium-testshell |
1573 # android-chrome-shell works. bisect-perf-regression.py script should | 1580 Then until crrev.com/276628 *both* (android-chromium-testshell and |
1574 # handle these cases and set appropriate browser type based on revision. | 1581 android-chrome-shell) work. After that rev 276628 *only* |
| 1582 android-chrome-shell works. The bisect_perf_regression.py script should |
| 1583 handle these cases and set appropriate browser type based on revision. |
| 1584 """ |
1575 if self.opts.target_platform in ['android']: | 1585 if self.opts.target_platform in ['android']: |
1576 # When its a third_party depot, get the chromium revision. | 1586 # When its a third_party depot, get the chromium revision. |
1577 if depot != 'chromium': | 1587 if depot != 'chromium': |
1578 revision = bisect_utils.CheckRunGit( | 1588 revision = bisect_utils.CheckRunGit( |
1579 ['rev-parse', 'HEAD'], cwd=self.src_cwd).strip() | 1589 ['rev-parse', 'HEAD'], cwd=self.src_cwd).strip() |
1580 commit_position = self.source_control.GetCommitPosition(revision, | 1590 commit_position = self.source_control.GetCommitPosition(revision, |
1581 cwd=self.src_cwd) | 1591 cwd=self.src_cwd) |
1582 if not commit_position: | 1592 if not commit_position: |
1583 return command_to_run | 1593 return command_to_run |
1584 cmd_re = re.compile('--browser=(?P<browser_type>\S+)') | 1594 cmd_re = re.compile('--browser=(?P<browser_type>\S+)') |
(...skipping 691 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
2276 | 2286 |
2277 Args: | 2287 Args: |
2278 good_revision: Known good revision. | 2288 good_revision: Known good revision. |
2279 bad_revision: Known bad revision. | 2289 bad_revision: Known bad revision. |
2280 | 2290 |
2281 Returns: | 2291 Returns: |
2282 A dictionary indicating the result. If revision is not bisectable, | 2292 A dictionary indicating the result. If revision is not bisectable, |
2283 this will contain the field "error", otherwise None. | 2293 this will contain the field "error", otherwise None. |
2284 """ | 2294 """ |
2285 if self.opts.target_platform == 'android': | 2295 if self.opts.target_platform == 'android': |
2286 revision_to_check = self.source_control.GetCommitPosition(good_revision) | 2296 good_revision = self.source_control.GetCommitPosition(good_revision) |
2287 if (bisect_utils.IsStringInt(good_revision) | 2297 if (bisect_utils.IsStringInt(good_revision) |
2288 and good_revision < 265549): | 2298 and good_revision < 265549): |
2289 return {'error': ( | 2299 return {'error': ( |
2290 'Bisect cannot continue for the given revision range.\n' | 2300 'Bisect cannot continue for the given revision range.\n' |
2291 'It is impossible to bisect Android regressions ' | 2301 'It is impossible to bisect Android regressions ' |
2292 'prior to r265549, which allows the bisect bot to ' | 2302 'prior to r265549, which allows the bisect bot to ' |
2293 'rely on Telemetry to do apk installation of the most recently ' | 2303 'rely on Telemetry to do apk installation of the most recently ' |
2294 'built local ChromeShell(refer to crbug.com/385324).\n' | 2304 'built local ChromeShell(refer to crbug.com/385324).\n' |
2295 'Please try bisecting revisions greater than or equal to r265549.')} | 2305 'Please try bisecting revisions greater than or equal to r265549.')} |
2296 | 2306 |
(...skipping 1100 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
3397 # bugs. If you change this, please update the perf dashboard as well. | 3407 # bugs. If you change this, please update the perf dashboard as well. |
3398 bisect_utils.OutputAnnotationStepStart('Results') | 3408 bisect_utils.OutputAnnotationStepStart('Results') |
3399 print 'Error: %s' % e.message | 3409 print 'Error: %s' % e.message |
3400 if opts.output_buildbot_annotations: | 3410 if opts.output_buildbot_annotations: |
3401 bisect_utils.OutputAnnotationStepClosed() | 3411 bisect_utils.OutputAnnotationStepClosed() |
3402 return 1 | 3412 return 1 |
3403 | 3413 |
3404 | 3414 |
3405 if __name__ == '__main__': | 3415 if __name__ == '__main__': |
3406 sys.exit(main()) | 3416 sys.exit(main()) |
OLD | NEW |