Chromium Code Reviews| 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 |
| (...skipping 18 matching lines...) Expand all Loading... | |
| 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 | |
| 40 import optparse | 39 import optparse |
| 41 import os | 40 import os |
| 42 import re | 41 import re |
| 43 import shlex | 42 import shlex |
| 44 import shutil | 43 import shutil |
| 45 import StringIO | 44 import StringIO |
| 46 import sys | 45 import sys |
| 47 import time | 46 import time |
| 48 import zipfile | 47 import zipfile |
| 49 | 48 |
| 50 sys.path.append(os.path.join( | 49 sys.path.append(os.path.join( |
| 51 os.path.dirname(__file__), os.path.pardir, 'telemetry')) | 50 os.path.dirname(__file__), os.path.pardir, 'telemetry')) |
| 52 | 51 |
| 52 from bisect_results import BisectResults | |
| 53 import bisect_utils | 53 import bisect_utils |
| 54 import builder | 54 import builder |
| 55 import math_utils | 55 import math_utils |
| 56 import request_build | 56 import request_build |
| 57 import source_control as source_control_module | 57 import source_control as source_control_module |
| 58 import ttest | |
| 59 from telemetry.util import cloud_storage | 58 from telemetry.util import cloud_storage |
| 60 | 59 |
| 61 # Below is the map of "depot" names to information about each depot. Each depot | 60 # Below is the map of "depot" names to information about each depot. Each depot |
| 62 # is a repository, and in the process of bisecting, revision ranges in these | 61 # is a repository, and in the process of bisecting, revision ranges in these |
| 63 # repositories may also be bisected. | 62 # repositories may also be bisected. |
| 64 # | 63 # |
| 65 # Each depot information dictionary may contain: | 64 # Each depot information dictionary may contain: |
| 66 # src: Path to the working directory. | 65 # src: Path to the working directory. |
| 67 # recurse: True if this repository will get bisected. | 66 # recurse: True if this repository will get bisected. |
| 68 # depends: A list of other repositories that are actually part of the same | 67 # depends: A list of other repositories that are actually part of the same |
| (...skipping 200 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 269 | 268 |
| 270 | 269 |
| 271 def _AddAdditionalDepotInfo(depot_info): | 270 def _AddAdditionalDepotInfo(depot_info): |
| 272 """Adds additional depot info to the global depot variables.""" | 271 """Adds additional depot info to the global depot variables.""" |
| 273 global DEPOT_DEPS_NAME | 272 global DEPOT_DEPS_NAME |
| 274 global DEPOT_NAMES | 273 global DEPOT_NAMES |
| 275 DEPOT_DEPS_NAME = dict(DEPOT_DEPS_NAME.items() + depot_info.items()) | 274 DEPOT_DEPS_NAME = dict(DEPOT_DEPS_NAME.items() + depot_info.items()) |
| 276 DEPOT_NAMES = DEPOT_DEPS_NAME.keys() | 275 DEPOT_NAMES = DEPOT_DEPS_NAME.keys() |
| 277 | 276 |
| 278 | 277 |
| 279 def ConfidenceScore(good_results_lists, bad_results_lists): | |
| 280 """Calculates a confidence score. | |
| 281 | |
| 282 This score is a percentage which represents our degree of confidence in the | |
| 283 proposition that the good results and bad results are distinct groups, and | |
| 284 their differences aren't due to chance alone. | |
| 285 | |
| 286 | |
| 287 Args: | |
| 288 good_results_lists: A list of lists of "good" result numbers. | |
| 289 bad_results_lists: A list of lists of "bad" result numbers. | |
| 290 | |
| 291 Returns: | |
| 292 A number in the range [0, 100]. | |
| 293 """ | |
| 294 # If there's only one item in either list, this means only one revision was | |
| 295 # classified good or bad; this isn't good enough evidence to make a decision. | |
| 296 # If an empty list was passed, that also implies zero confidence. | |
| 297 if len(good_results_lists) <= 1 or len(bad_results_lists) <= 1: | |
| 298 return 0.0 | |
| 299 | |
| 300 # Flatten the lists of results lists. | |
| 301 sample1 = sum(good_results_lists, []) | |
| 302 sample2 = sum(bad_results_lists, []) | |
| 303 | |
| 304 # If there were only empty lists in either of the lists (this is unexpected | |
| 305 # and normally shouldn't happen), then we also want to return 0. | |
| 306 if not sample1 or not sample2: | |
| 307 return 0.0 | |
| 308 | |
| 309 # The p-value is approximately the probability of obtaining the given set | |
| 310 # of good and bad values just by chance. | |
| 311 _, _, p_value = ttest.WelchsTTest(sample1, sample2) | |
| 312 return 100.0 * (1.0 - p_value) | |
| 313 | |
| 314 | |
| 315 def GetSHA1HexDigest(contents): | 278 def GetSHA1HexDigest(contents): |
| 316 """Returns SHA1 hex digest of the given string.""" | 279 """Returns SHA1 hex digest of the given string.""" |
| 317 return hashlib.sha1(contents).hexdigest() | 280 return hashlib.sha1(contents).hexdigest() |
| 318 | 281 |
| 319 | 282 |
| 320 def GetZipFileName(build_revision=None, target_arch='ia32', patch_sha=None): | 283 def GetZipFileName(build_revision=None, target_arch='ia32', patch_sha=None): |
| 321 """Gets the archive file name for the given revision.""" | 284 """Gets the archive file name for the given revision.""" |
| 322 def PlatformName(): | 285 def PlatformName(): |
| 323 """Return a string to be used in paths for the platform.""" | 286 """Return a string to be used in paths for the platform.""" |
| 324 if bisect_utils.IsWindowsHost(): | 287 if bisect_utils.IsWindowsHost(): |
| (...skipping 536 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 861 if step_count: | 824 if step_count: |
| 862 step_perf_time_avg = step_perf_time_avg / step_count | 825 step_perf_time_avg = step_perf_time_avg / step_count |
| 863 step_build_time_avg = step_build_time_avg / step_count | 826 step_build_time_avg = step_build_time_avg / step_count |
| 864 print | 827 print |
| 865 print 'Average build time : %s' % datetime.timedelta( | 828 print 'Average build time : %s' % datetime.timedelta( |
| 866 seconds=int(step_build_time_avg)) | 829 seconds=int(step_build_time_avg)) |
| 867 print 'Average test time : %s' % datetime.timedelta( | 830 print 'Average test time : %s' % datetime.timedelta( |
| 868 seconds=int(step_perf_time_avg)) | 831 seconds=int(step_perf_time_avg)) |
| 869 | 832 |
| 870 | 833 |
| 871 def _FindOtherRegressions(revision_data_sorted, bad_greater_than_good): | 834 class DepotDirectoryRegistry(object): |
| 872 """Compiles a list of other possible regressions from the revision data. | 835 """Manages depots (code repositories) and their directories.""" |
|
ojan
2014/09/26 17:34:30
IMO, this comment doesn't say anything the name of
| |
| 873 | 836 |
| 874 Args: | 837 def __init__(self): |
| 875 revision_data_sorted: Sorted list of (revision, revision data) pairs. | 838 self.depot_cwd = {} |
| 876 bad_greater_than_good: Whether the result value at the "bad" revision is | 839 self.cros_cwd = os.path.join(SRC_DIR, 'tools', 'cros') |
| 877 numerically greater than the result value at the "good" revision. | 840 for depot in DEPOT_NAMES: |
| 841 # The working directory of each depot is just the path to the depot, but | |
| 842 # since we're already in 'src', we can skip that part. | |
| 843 self.depot_cwd[depot] = os.path.join( | |
| 844 SRC_DIR, DEPOT_DEPS_NAME[depot]['src'][4:]) | |
| 878 | 845 |
| 879 Returns: | 846 def AddDepot(self, depot_name, depot_dir): |
| 880 A list of [current_rev, previous_rev, confidence] for other places where | 847 self.depot_cwd[depot_name] = depot_dir |
| 881 there may have been a regression. | |
| 882 """ | |
| 883 other_regressions = [] | |
| 884 previous_values = [] | |
| 885 previous_id = None | |
| 886 for current_id, current_data in revision_data_sorted: | |
| 887 current_values = current_data['value'] | |
| 888 if current_values: | |
| 889 current_values = current_values['values'] | |
| 890 if previous_values: | |
| 891 confidence = ConfidenceScore(previous_values, [current_values]) | |
| 892 mean_of_prev_runs = math_utils.Mean(sum(previous_values, [])) | |
| 893 mean_of_current_runs = math_utils.Mean(current_values) | |
| 894 | 848 |
| 895 # Check that the potential regression is in the same direction as | 849 def GetDepotDir(self, depot_name): |
| 896 # the overall regression. If the mean of the previous runs < the | 850 if depot_name == 'chromium': |
| 897 # mean of the current runs, this local regression is in same | 851 return SRC_DIR |
| 898 # direction. | 852 elif depot_name == 'cros': |
| 899 prev_less_than_current = mean_of_prev_runs < mean_of_current_runs | 853 return self.cros_cwd |
| 900 is_same_direction = (prev_less_than_current if | 854 elif depot_name in DEPOT_NAMES: |
| 901 bad_greater_than_good else not prev_less_than_current) | 855 return self.depot_cwd[depot_name] |
| 856 else: | |
| 857 assert False, ('Unknown depot [ %s ] encountered. Possibly a new one ' | |
| 858 'was added without proper support?' % depot_name) | |
| 902 | 859 |
| 903 # Only report potential regressions with high confidence. | 860 def ChangeToDepotDir(self, depot_name): |
| 904 if is_same_direction and confidence > 50: | 861 """Given a depot, changes to the appropriate working directory. |
| 905 other_regressions.append([current_id, previous_id, confidence]) | 862 |
| 906 previous_values.append(current_values) | 863 Args: |
| 907 previous_id = current_id | 864 depot_name: The name of the depot (see DEPOT_NAMES). |
| 908 return other_regressions | 865 """ |
| 866 os.chdir(self.GetDepotDir(depot_name)) | |
| 909 | 867 |
| 910 | 868 |
| 911 class BisectPerformanceMetrics(object): | 869 class BisectPerformanceMetrics(object): |
| 912 """This class contains functionality to perform a bisection of a range of | 870 """This class contains functionality to perform a bisection of a range of |
| 913 revisions to narrow down where performance regressions may have occurred. | 871 revisions to narrow down where performance regressions may have occurred. |
| 914 | 872 |
| 915 The main entry-point is the Run method. | 873 The main entry-point is the Run method. |
| 916 """ | 874 """ |
| 917 | 875 |
| 918 def __init__(self, source_control, opts): | 876 def __init__(self, source_control, opts): |
| 919 super(BisectPerformanceMetrics, self).__init__() | 877 super(BisectPerformanceMetrics, self).__init__() |
| 920 | 878 |
| 921 self.opts = opts | 879 self.opts = opts |
| 922 self.source_control = source_control | 880 self.source_control = source_control |
| 923 self.cros_cwd = os.path.join(SRC_DIR, 'tools', 'cros') | 881 self.depot_manager = DepotDirectoryRegistry() |
| 924 self.depot_cwd = {} | |
| 925 self.cleanup_commands = [] | 882 self.cleanup_commands = [] |
| 926 self.warnings = [] | 883 self.warnings = [] |
| 927 self.builder = builder.Builder.FromOpts(opts) | 884 self.builder = builder.Builder.FromOpts(opts) |
| 928 | 885 |
| 929 for depot in DEPOT_NAMES: | |
| 930 # The working directory of each depot is just the path to the depot, but | |
| 931 # since we're already in 'src', we can skip that part. | |
| 932 self.depot_cwd[depot] = os.path.join( | |
| 933 SRC_DIR, DEPOT_DEPS_NAME[depot]['src'][4:]) | |
| 934 | 886 |
| 935 def PerformCleanup(self): | 887 def PerformCleanup(self): |
| 936 """Performs cleanup when script is finished.""" | 888 """Performs cleanup when script is finished.""" |
| 937 os.chdir(SRC_DIR) | 889 os.chdir(SRC_DIR) |
| 938 for c in self.cleanup_commands: | 890 for c in self.cleanup_commands: |
| 939 if c[0] == 'mv': | 891 if c[0] == 'mv': |
| 940 shutil.move(c[1], c[2]) | 892 shutil.move(c[1], c[2]) |
| 941 else: | 893 else: |
| 942 assert False, 'Invalid cleanup command.' | 894 assert False, 'Invalid cleanup command.' |
| 943 | 895 |
| 944 def GetRevisionList(self, depot, bad_revision, good_revision): | 896 def GetRevisionList(self, depot, bad_revision, good_revision): |
| 945 """Retrieves a list of all the commits between the bad revision and | 897 """Retrieves a list of all the commits between the bad revision and |
| 946 last known good revision.""" | 898 last known good revision.""" |
| 947 | 899 |
| 948 revision_work_list = [] | 900 revision_work_list = [] |
| 949 | 901 |
| 950 if depot == 'cros': | 902 if depot == 'cros': |
| 951 revision_range_start = good_revision | 903 revision_range_start = good_revision |
| 952 revision_range_end = bad_revision | 904 revision_range_end = bad_revision |
| 953 | 905 |
| 954 cwd = os.getcwd() | 906 cwd = os.getcwd() |
| 955 self.ChangeToDepotWorkingDirectory('cros') | 907 self.depot_manager.ChangeToDepotDir('cros') |
| 956 | 908 |
| 957 # Print the commit timestamps for every commit in the revision time | 909 # Print the commit timestamps for every commit in the revision time |
| 958 # range. We'll sort them and bisect by that. There is a remote chance that | 910 # range. We'll sort them and bisect by that. There is a remote chance that |
| 959 # 2 (or more) commits will share the exact same timestamp, but it's | 911 # 2 (or more) commits will share the exact same timestamp, but it's |
| 960 # probably safe to ignore that case. | 912 # probably safe to ignore that case. |
| 961 cmd = ['repo', 'forall', '-c', | 913 cmd = ['repo', 'forall', '-c', |
| 962 'git log --format=%%ct --before=%d --after=%d' % ( | 914 'git log --format=%%ct --before=%d --after=%d' % ( |
| 963 revision_range_end, revision_range_start)] | 915 revision_range_end, revision_range_start)] |
| 964 output, return_code = bisect_utils.RunProcessAndRetrieveOutput(cmd) | 916 output, return_code = bisect_utils.RunProcessAndRetrieveOutput(cmd) |
| 965 | 917 |
| 966 assert not return_code, ('An error occurred while running ' | 918 assert not return_code, ('An error occurred while running ' |
| 967 '"%s"' % ' '.join(cmd)) | 919 '"%s"' % ' '.join(cmd)) |
| 968 | 920 |
| 969 os.chdir(cwd) | 921 os.chdir(cwd) |
| 970 | 922 |
| 971 revision_work_list = list(set( | 923 revision_work_list = list(set( |
| 972 [int(o) for o in output.split('\n') if bisect_utils.IsStringInt(o)])) | 924 [int(o) for o in output.split('\n') if bisect_utils.IsStringInt(o)])) |
| 973 revision_work_list = sorted(revision_work_list, reverse=True) | 925 revision_work_list = sorted(revision_work_list, reverse=True) |
| 974 else: | 926 else: |
| 975 cwd = self._GetDepotDirectory(depot) | 927 cwd = self.depot_manager.GetDepotDir(depot) |
| 976 revision_work_list = self.source_control.GetRevisionList(bad_revision, | 928 revision_work_list = self.source_control.GetRevisionList(bad_revision, |
| 977 good_revision, cwd=cwd) | 929 good_revision, cwd=cwd) |
| 978 | 930 |
| 979 return revision_work_list | 931 return revision_work_list |
| 980 | 932 |
| 981 def _GetV8BleedingEdgeFromV8TrunkIfMappable(self, revision): | 933 def _GetV8BleedingEdgeFromV8TrunkIfMappable(self, revision): |
| 982 commit_position = self.source_control.GetCommitPosition(revision) | 934 commit_position = self.source_control.GetCommitPosition(revision) |
| 983 | 935 |
| 984 if bisect_utils.IsStringInt(commit_position): | 936 if bisect_utils.IsStringInt(commit_position): |
| 985 # V8 is tricky to bisect, in that there are only a few instances when | 937 # V8 is tricky to bisect, in that there are only a few instances when |
| 986 # we can dive into bleeding_edge and get back a meaningful result. | 938 # we can dive into bleeding_edge and get back a meaningful result. |
| 987 # Try to detect a V8 "business as usual" case, which is when: | 939 # Try to detect a V8 "business as usual" case, which is when: |
| 988 # 1. trunk revision N has description "Version X.Y.Z" | 940 # 1. trunk revision N has description "Version X.Y.Z" |
| 989 # 2. bleeding_edge revision (N-1) has description "Prepare push to | 941 # 2. bleeding_edge revision (N-1) has description "Prepare push to |
| 990 # trunk. Now working on X.Y.(Z+1)." | 942 # trunk. Now working on X.Y.(Z+1)." |
| 991 # | 943 # |
| 992 # As of 01/24/2014, V8 trunk descriptions are formatted: | 944 # As of 01/24/2014, V8 trunk descriptions are formatted: |
| 993 # "Version 3.X.Y (based on bleeding_edge revision rZ)" | 945 # "Version 3.X.Y (based on bleeding_edge revision rZ)" |
| 994 # So we can just try parsing that out first and fall back to the old way. | 946 # So we can just try parsing that out first and fall back to the old way. |
| 995 v8_dir = self._GetDepotDirectory('v8') | 947 v8_dir = self.depot_manager.GetDepotDir('v8') |
| 996 v8_bleeding_edge_dir = self._GetDepotDirectory('v8_bleeding_edge') | 948 v8_bleeding_edge_dir = self.depot_manager.GetDepotDir('v8_bleeding_edge') |
| 997 | 949 |
| 998 revision_info = self.source_control.QueryRevisionInfo(revision, | 950 revision_info = self.source_control.QueryRevisionInfo(revision, |
| 999 cwd=v8_dir) | 951 cwd=v8_dir) |
| 1000 | 952 |
| 1001 version_re = re.compile("Version (?P<values>[0-9,.]+)") | 953 version_re = re.compile("Version (?P<values>[0-9,.]+)") |
| 1002 | 954 |
| 1003 regex_results = version_re.search(revision_info['subject']) | 955 regex_results = version_re.search(revision_info['subject']) |
| 1004 | 956 |
| 1005 if regex_results: | 957 if regex_results: |
| 1006 git_revision = None | 958 git_revision = None |
| (...skipping 19 matching lines...) Expand all Loading... | |
| 1026 | 978 |
| 1027 if git_revision: | 979 if git_revision: |
| 1028 revision_info = self.source_control.QueryRevisionInfo(git_revision, | 980 revision_info = self.source_control.QueryRevisionInfo(git_revision, |
| 1029 cwd=v8_bleeding_edge_dir) | 981 cwd=v8_bleeding_edge_dir) |
| 1030 | 982 |
| 1031 if 'Prepare push to trunk' in revision_info['subject']: | 983 if 'Prepare push to trunk' in revision_info['subject']: |
| 1032 return git_revision | 984 return git_revision |
| 1033 return None | 985 return None |
| 1034 | 986 |
| 1035 def _GetNearestV8BleedingEdgeFromTrunk(self, revision, search_forward=True): | 987 def _GetNearestV8BleedingEdgeFromTrunk(self, revision, search_forward=True): |
| 1036 cwd = self._GetDepotDirectory('v8') | 988 cwd = self.depot_manager.GetDepotDir('v8') |
| 1037 cmd = ['log', '--format=%ct', '-1', revision] | 989 cmd = ['log', '--format=%ct', '-1', revision] |
| 1038 output = bisect_utils.CheckRunGit(cmd, cwd=cwd) | 990 output = bisect_utils.CheckRunGit(cmd, cwd=cwd) |
| 1039 commit_time = int(output) | 991 commit_time = int(output) |
| 1040 commits = [] | 992 commits = [] |
| 1041 | 993 |
| 1042 if search_forward: | 994 if search_forward: |
| 1043 cmd = ['log', '--format=%H', '-10', '--after=%d' % commit_time, | 995 cmd = ['log', '--format=%H', '-10', '--after=%d' % commit_time, |
| 1044 'origin/master'] | 996 'origin/master'] |
| 1045 output = bisect_utils.CheckRunGit(cmd, cwd=cwd) | 997 output = bisect_utils.CheckRunGit(cmd, cwd=cwd) |
| 1046 output = output.split() | 998 output = output.split() |
| (...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1088 results = {} | 1040 results = {} |
| 1089 for depot_name, depot_data in DEPOT_DEPS_NAME.iteritems(): | 1041 for depot_name, depot_data in DEPOT_DEPS_NAME.iteritems(): |
| 1090 if (depot_data.get('platform') and | 1042 if (depot_data.get('platform') and |
| 1091 depot_data.get('platform') != os.name): | 1043 depot_data.get('platform') != os.name): |
| 1092 continue | 1044 continue |
| 1093 | 1045 |
| 1094 if (depot_data.get('recurse') and depot in depot_data.get('from')): | 1046 if (depot_data.get('recurse') and depot in depot_data.get('from')): |
| 1095 depot_data_src = depot_data.get('src') or depot_data.get('src_old') | 1047 depot_data_src = depot_data.get('src') or depot_data.get('src_old') |
| 1096 src_dir = deps_data.get(depot_data_src) | 1048 src_dir = deps_data.get(depot_data_src) |
| 1097 if src_dir: | 1049 if src_dir: |
| 1098 self.depot_cwd[depot_name] = os.path.join( | 1050 self.depot_manager.AddDepot( |
| 1099 SRC_DIR, depot_data_src[4:]) | 1051 depot_name, os.path.join(SRC_DIR, depot_data_src[4:])) |
| 1100 re_results = rxp.search(src_dir) | 1052 re_results = rxp.search(src_dir) |
| 1101 if re_results: | 1053 if re_results: |
| 1102 results[depot_name] = re_results.group('revision') | 1054 results[depot_name] = re_results.group('revision') |
| 1103 else: | 1055 else: |
| 1104 warning_text = ('Could not parse revision for %s while bisecting ' | 1056 warning_text = ('Could not parse revision for %s while bisecting ' |
| 1105 '%s' % (depot_name, depot)) | 1057 '%s' % (depot_name, depot)) |
| 1106 if not warning_text in self.warnings: | 1058 if not warning_text in self.warnings: |
| 1107 self.warnings.append(warning_text) | 1059 self.warnings.append(warning_text) |
| 1108 else: | 1060 else: |
| 1109 results[depot_name] = None | 1061 results[depot_name] = None |
| (...skipping 16 matching lines...) Expand all Loading... | |
| 1126 def _Get3rdPartyRevisions(self, depot): | 1078 def _Get3rdPartyRevisions(self, depot): |
| 1127 """Parses the DEPS file to determine WebKit/v8/etc... versions. | 1079 """Parses the DEPS file to determine WebKit/v8/etc... versions. |
| 1128 | 1080 |
| 1129 Args: | 1081 Args: |
| 1130 depot: A depot name. Should be in the DEPOT_NAMES list. | 1082 depot: A depot name. Should be in the DEPOT_NAMES list. |
| 1131 | 1083 |
| 1132 Returns: | 1084 Returns: |
| 1133 A dict in the format {depot: revision} if successful, otherwise None. | 1085 A dict in the format {depot: revision} if successful, otherwise None. |
| 1134 """ | 1086 """ |
| 1135 cwd = os.getcwd() | 1087 cwd = os.getcwd() |
| 1136 self.ChangeToDepotWorkingDirectory(depot) | 1088 self.depot_manager.ChangeToDepotDir(depot) |
| 1137 | 1089 |
| 1138 results = {} | 1090 results = {} |
| 1139 | 1091 |
| 1140 if depot == 'chromium' or depot == 'android-chrome': | 1092 if depot == 'chromium' or depot == 'android-chrome': |
| 1141 results = self._ParseRevisionsFromDEPSFile(depot) | 1093 results = self._ParseRevisionsFromDEPSFile(depot) |
| 1142 os.chdir(cwd) | 1094 os.chdir(cwd) |
| 1143 | 1095 |
| 1144 if depot == 'cros': | 1096 if depot == 'cros': |
| 1145 cmd = [ | 1097 cmd = [ |
| 1146 bisect_utils.CROS_SDK_PATH, | 1098 bisect_utils.CROS_SDK_PATH, |
| (...skipping 20 matching lines...) Expand all Loading... | |
| 1167 | 1119 |
| 1168 version = contents[2] | 1120 version = contents[2] |
| 1169 | 1121 |
| 1170 if contents[3] != '0': | 1122 if contents[3] != '0': |
| 1171 warningText = ('Chrome version: %s.%s but using %s.0 to bisect.' % | 1123 warningText = ('Chrome version: %s.%s but using %s.0 to bisect.' % |
| 1172 (version, contents[3], version)) | 1124 (version, contents[3], version)) |
| 1173 if not warningText in self.warnings: | 1125 if not warningText in self.warnings: |
| 1174 self.warnings.append(warningText) | 1126 self.warnings.append(warningText) |
| 1175 | 1127 |
| 1176 cwd = os.getcwd() | 1128 cwd = os.getcwd() |
| 1177 self.ChangeToDepotWorkingDirectory('chromium') | 1129 self.depot_manager.ChangeToDepotDir('chromium') |
| 1178 cmd = ['log', '-1', '--format=%H', | 1130 cmd = ['log', '-1', '--format=%H', |
| 1179 '--author=chrome-release@google.com', | 1131 '--author=chrome-release@google.com', |
| 1180 '--grep=to %s' % version, 'origin/master'] | 1132 '--grep=to %s' % version, 'origin/master'] |
| 1181 return_code = bisect_utils.CheckRunGit(cmd) | 1133 return_code = bisect_utils.CheckRunGit(cmd) |
| 1182 os.chdir(cwd) | 1134 os.chdir(cwd) |
| 1183 | 1135 |
| 1184 results['chromium'] = output.strip() | 1136 results['chromium'] = output.strip() |
| 1185 | 1137 |
| 1186 if depot == 'v8': | 1138 if depot == 'v8': |
| 1187 # We can't try to map the trunk revision to bleeding edge yet, because | 1139 # We can't try to map the trunk revision to bleeding edge yet, because |
| (...skipping 217 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1405 Returns: | 1357 Returns: |
| 1406 Updated DEPS content as string if deps key is found, otherwise None. | 1358 Updated DEPS content as string if deps key is found, otherwise None. |
| 1407 """ | 1359 """ |
| 1408 # Check whether the depot and revision pattern in DEPS file vars | 1360 # Check whether the depot and revision pattern in DEPS file vars |
| 1409 # e.g. for webkit the format is "webkit_revision": "12345". | 1361 # e.g. for webkit the format is "webkit_revision": "12345". |
| 1410 deps_revision = re.compile(r'(?<="%s": ")([0-9]+)(?=")' % deps_key, | 1362 deps_revision = re.compile(r'(?<="%s": ")([0-9]+)(?=")' % deps_key, |
| 1411 re.MULTILINE) | 1363 re.MULTILINE) |
| 1412 new_data = None | 1364 new_data = None |
| 1413 if re.search(deps_revision, deps_contents): | 1365 if re.search(deps_revision, deps_contents): |
| 1414 commit_position = self.source_control.GetCommitPosition( | 1366 commit_position = self.source_control.GetCommitPosition( |
| 1415 git_revision, self._GetDepotDirectory(depot)) | 1367 git_revision, self.depot_manager.GetDepotDir(depot)) |
| 1416 if not commit_position: | 1368 if not commit_position: |
| 1417 print 'Could not determine commit position for %s' % git_revision | 1369 print 'Could not determine commit position for %s' % git_revision |
| 1418 return None | 1370 return None |
| 1419 # Update the revision information for the given depot | 1371 # Update the revision information for the given depot |
| 1420 new_data = re.sub(deps_revision, str(commit_position), deps_contents) | 1372 new_data = re.sub(deps_revision, str(commit_position), deps_contents) |
| 1421 else: | 1373 else: |
| 1422 # Check whether the depot and revision pattern in DEPS file vars | 1374 # Check whether the depot and revision pattern in DEPS file vars |
| 1423 # e.g. for webkit the format is "webkit_revision": "559a6d4ab7a84c539..". | 1375 # e.g. for webkit the format is "webkit_revision": "559a6d4ab7a84c539..". |
| 1424 deps_revision = re.compile( | 1376 deps_revision = re.compile( |
| 1425 r'(?<=["\']%s["\']: ["\'])([a-fA-F0-9]{40})(?=["\'])' % deps_key, | 1377 r'(?<=["\']%s["\']: ["\'])([a-fA-F0-9]{40})(?=["\'])' % deps_key, |
| (...skipping 343 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1769 # Some SVN depots were split into multiple git depots, so we need to | 1721 # Some SVN depots were split into multiple git depots, so we need to |
| 1770 # figure out for each mirror which git revision to grab. There's no | 1722 # figure out for each mirror which git revision to grab. There's no |
| 1771 # guarantee that the SVN revision will exist for each of the dependent | 1723 # guarantee that the SVN revision will exist for each of the dependent |
| 1772 # depots, so we have to grep the git logs and grab the next earlier one. | 1724 # depots, so we have to grep the git logs and grab the next earlier one. |
| 1773 if (not is_base | 1725 if (not is_base |
| 1774 and DEPOT_DEPS_NAME[depot]['depends'] | 1726 and DEPOT_DEPS_NAME[depot]['depends'] |
| 1775 and self.source_control.IsGit()): | 1727 and self.source_control.IsGit()): |
| 1776 commit_position = self.source_control.GetCommitPosition(revision) | 1728 commit_position = self.source_control.GetCommitPosition(revision) |
| 1777 | 1729 |
| 1778 for d in DEPOT_DEPS_NAME[depot]['depends']: | 1730 for d in DEPOT_DEPS_NAME[depot]['depends']: |
| 1779 self.ChangeToDepotWorkingDirectory(d) | 1731 self.depot_manager.ChangeToDepotDir(d) |
| 1780 | 1732 |
| 1781 dependant_rev = self.source_control.ResolveToRevision( | 1733 dependant_rev = self.source_control.ResolveToRevision( |
| 1782 commit_position, d, DEPOT_DEPS_NAME, -1000) | 1734 commit_position, d, DEPOT_DEPS_NAME, -1000) |
| 1783 | 1735 |
| 1784 if dependant_rev: | 1736 if dependant_rev: |
| 1785 revisions_to_sync.append([d, dependant_rev]) | 1737 revisions_to_sync.append([d, dependant_rev]) |
| 1786 | 1738 |
| 1787 num_resolved = len(revisions_to_sync) | 1739 num_resolved = len(revisions_to_sync) |
| 1788 num_needed = len(DEPOT_DEPS_NAME[depot]['depends']) | 1740 num_needed = len(DEPOT_DEPS_NAME[depot]['depends']) |
| 1789 | 1741 |
| 1790 self.ChangeToDepotWorkingDirectory(depot) | 1742 self.depot_manager.ChangeToDepotDir(depot) |
| 1791 | 1743 |
| 1792 if not ((num_resolved - 1) == num_needed): | 1744 if not ((num_resolved - 1) == num_needed): |
| 1793 return None | 1745 return None |
| 1794 | 1746 |
| 1795 return revisions_to_sync | 1747 return revisions_to_sync |
| 1796 | 1748 |
| 1797 @staticmethod | 1749 @staticmethod |
| 1798 def PerformPreBuildCleanup(): | 1750 def PerformPreBuildCleanup(): |
| 1799 """Performs cleanup between runs.""" | 1751 """Performs cleanup between runs.""" |
| 1800 print 'Cleaning up between runs.' | 1752 print 'Cleaning up between runs.' |
| 1801 print | 1753 print |
| 1802 | 1754 |
| 1803 # Leaving these .pyc files around between runs may disrupt some perf tests. | 1755 # Leaving these .pyc files around between runs may disrupt some perf tests. |
| 1804 for (path, _, files) in os.walk(SRC_DIR): | 1756 for (path, _, files) in os.walk(SRC_DIR): |
| 1805 for cur_file in files: | 1757 for cur_file in files: |
| 1806 if cur_file.endswith('.pyc'): | 1758 if cur_file.endswith('.pyc'): |
| 1807 path_to_file = os.path.join(path, cur_file) | 1759 path_to_file = os.path.join(path, cur_file) |
| 1808 os.remove(path_to_file) | 1760 os.remove(path_to_file) |
| 1809 | 1761 |
| 1810 def PerformCrosChrootCleanup(self): | 1762 def PerformCrosChrootCleanup(self): |
| 1811 """Deletes the chroot. | 1763 """Deletes the chroot. |
| 1812 | 1764 |
| 1813 Returns: | 1765 Returns: |
| 1814 True if successful. | 1766 True if successful. |
| 1815 """ | 1767 """ |
| 1816 cwd = os.getcwd() | 1768 cwd = os.getcwd() |
| 1817 self.ChangeToDepotWorkingDirectory('cros') | 1769 self.depot_manager.ChangeToDepotDir('cros') |
| 1818 cmd = [bisect_utils.CROS_SDK_PATH, '--delete'] | 1770 cmd = [bisect_utils.CROS_SDK_PATH, '--delete'] |
| 1819 return_code = bisect_utils.RunProcess(cmd) | 1771 return_code = bisect_utils.RunProcess(cmd) |
| 1820 os.chdir(cwd) | 1772 os.chdir(cwd) |
| 1821 return not return_code | 1773 return not return_code |
| 1822 | 1774 |
| 1823 def CreateCrosChroot(self): | 1775 def CreateCrosChroot(self): |
| 1824 """Creates a new chroot. | 1776 """Creates a new chroot. |
| 1825 | 1777 |
| 1826 Returns: | 1778 Returns: |
| 1827 True if successful. | 1779 True if successful. |
| 1828 """ | 1780 """ |
| 1829 cwd = os.getcwd() | 1781 cwd = os.getcwd() |
| 1830 self.ChangeToDepotWorkingDirectory('cros') | 1782 self.depot_manager.ChangeToDepotDir('cros') |
| 1831 cmd = [bisect_utils.CROS_SDK_PATH, '--create'] | 1783 cmd = [bisect_utils.CROS_SDK_PATH, '--create'] |
| 1832 return_code = bisect_utils.RunProcess(cmd) | 1784 return_code = bisect_utils.RunProcess(cmd) |
| 1833 os.chdir(cwd) | 1785 os.chdir(cwd) |
| 1834 return not return_code | 1786 return not return_code |
| 1835 | 1787 |
| 1836 def _PerformPreSyncCleanup(self, depot): | 1788 def _PerformPreSyncCleanup(self, depot): |
| 1837 """Performs any necessary cleanup before syncing. | 1789 """Performs any necessary cleanup before syncing. |
| 1838 | 1790 |
| 1839 Args: | 1791 Args: |
| 1840 depot: Depot name. | 1792 depot: Depot name. |
| (...skipping 141 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1982 """Syncs multiple depots to particular revisions. | 1934 """Syncs multiple depots to particular revisions. |
| 1983 | 1935 |
| 1984 Args: | 1936 Args: |
| 1985 revisions_to_sync: A list of (depot, revision) pairs to be synced. | 1937 revisions_to_sync: A list of (depot, revision) pairs to be synced. |
| 1986 sync_client: Program used to sync, e.g. "gclient", "repo". Can be None. | 1938 sync_client: Program used to sync, e.g. "gclient", "repo". Can be None. |
| 1987 | 1939 |
| 1988 Returns: | 1940 Returns: |
| 1989 True if successful, False otherwise. | 1941 True if successful, False otherwise. |
| 1990 """ | 1942 """ |
| 1991 for depot, revision in revisions_to_sync: | 1943 for depot, revision in revisions_to_sync: |
| 1992 self.ChangeToDepotWorkingDirectory(depot) | 1944 self.depot_manager.ChangeToDepotDir(depot) |
| 1993 | 1945 |
| 1994 if sync_client: | 1946 if sync_client: |
| 1995 self.PerformPreBuildCleanup() | 1947 self.PerformPreBuildCleanup() |
| 1996 | 1948 |
| 1997 # When using gclient to sync, you need to specify the depot you | 1949 # When using gclient to sync, you need to specify the depot you |
| 1998 # want so that all the dependencies sync properly as well. | 1950 # want so that all the dependencies sync properly as well. |
| 1999 # i.e. gclient sync src@<SHA1> | 1951 # i.e. gclient sync src@<SHA1> |
| 2000 if sync_client == 'gclient': | 1952 if sync_client == 'gclient': |
| 2001 revision = '%s@%s' % (DEPOT_DEPS_NAME[depot]['src'], revision) | 1953 revision = '%s@%s' % (DEPOT_DEPS_NAME[depot]['src'], revision) |
| 2002 | 1954 |
| (...skipping 20 matching lines...) Expand all Loading... | |
| 2023 dist_to_good_value = abs(current_value['std_dev'] - | 1975 dist_to_good_value = abs(current_value['std_dev'] - |
| 2024 known_good_value['std_dev']) | 1976 known_good_value['std_dev']) |
| 2025 dist_to_bad_value = abs(current_value['std_dev'] - | 1977 dist_to_bad_value = abs(current_value['std_dev'] - |
| 2026 known_bad_value['std_dev']) | 1978 known_bad_value['std_dev']) |
| 2027 else: | 1979 else: |
| 2028 dist_to_good_value = abs(current_value['mean'] - known_good_value['mean']) | 1980 dist_to_good_value = abs(current_value['mean'] - known_good_value['mean']) |
| 2029 dist_to_bad_value = abs(current_value['mean'] - known_bad_value['mean']) | 1981 dist_to_bad_value = abs(current_value['mean'] - known_bad_value['mean']) |
| 2030 | 1982 |
| 2031 return dist_to_good_value < dist_to_bad_value | 1983 return dist_to_good_value < dist_to_bad_value |
| 2032 | 1984 |
| 2033 def _GetDepotDirectory(self, depot_name): | |
| 2034 if depot_name == 'chromium': | |
| 2035 return SRC_DIR | |
| 2036 elif depot_name == 'cros': | |
| 2037 return self.cros_cwd | |
| 2038 elif depot_name in DEPOT_NAMES: | |
| 2039 return self.depot_cwd[depot_name] | |
| 2040 else: | |
| 2041 assert False, ('Unknown depot [ %s ] encountered. Possibly a new one ' | |
| 2042 'was added without proper support?' % depot_name) | |
| 2043 | |
| 2044 def ChangeToDepotWorkingDirectory(self, depot_name): | |
| 2045 """Given a depot, changes to the appropriate working directory. | |
| 2046 | |
| 2047 Args: | |
| 2048 depot_name: The name of the depot (see DEPOT_NAMES). | |
| 2049 """ | |
| 2050 os.chdir(self._GetDepotDirectory(depot_name)) | |
| 2051 | |
| 2052 def _FillInV8BleedingEdgeInfo(self, min_revision_data, max_revision_data): | 1985 def _FillInV8BleedingEdgeInfo(self, min_revision_data, max_revision_data): |
| 2053 r1 = self._GetNearestV8BleedingEdgeFromTrunk(min_revision_data['revision'], | 1986 r1 = self._GetNearestV8BleedingEdgeFromTrunk(min_revision_data['revision'], |
| 2054 search_forward=True) | 1987 search_forward=True) |
| 2055 r2 = self._GetNearestV8BleedingEdgeFromTrunk(max_revision_data['revision'], | 1988 r2 = self._GetNearestV8BleedingEdgeFromTrunk(max_revision_data['revision'], |
| 2056 search_forward=False) | 1989 search_forward=False) |
| 2057 min_revision_data['external']['v8_bleeding_edge'] = r1 | 1990 min_revision_data['external']['v8_bleeding_edge'] = r1 |
| 2058 max_revision_data['external']['v8_bleeding_edge'] = r2 | 1991 max_revision_data['external']['v8_bleeding_edge'] = r2 |
| 2059 | 1992 |
| 2060 if (not self._GetV8BleedingEdgeFromV8TrunkIfMappable( | 1993 if (not self._GetV8BleedingEdgeFromV8TrunkIfMappable( |
| 2061 min_revision_data['revision']) | 1994 min_revision_data['revision']) |
| (...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 2117 end_revision: End of the revision range. | 2050 end_revision: End of the revision range. |
| 2118 start_revision: Start of the revision range. | 2051 start_revision: Start of the revision range. |
| 2119 previous_revision: The last revision we synced to on |previous_depot|. | 2052 previous_revision: The last revision we synced to on |previous_depot|. |
| 2120 | 2053 |
| 2121 Returns: | 2054 Returns: |
| 2122 A list containing the revisions between |start_revision| and | 2055 A list containing the revisions between |start_revision| and |
| 2123 |end_revision| inclusive. | 2056 |end_revision| inclusive. |
| 2124 """ | 2057 """ |
| 2125 # Change into working directory of external library to run | 2058 # Change into working directory of external library to run |
| 2126 # subsequent commands. | 2059 # subsequent commands. |
| 2127 self.ChangeToDepotWorkingDirectory(current_depot) | 2060 self.depot_manager.ChangeToDepotDir(current_depot) |
| 2128 | 2061 |
| 2129 # V8 (and possibly others) is merged in periodically. Bisecting | 2062 # V8 (and possibly others) is merged in periodically. Bisecting |
| 2130 # this directory directly won't give much good info. | 2063 # this directory directly won't give much good info. |
| 2131 if DEPOT_DEPS_NAME[current_depot].has_key('custom_deps'): | 2064 if DEPOT_DEPS_NAME[current_depot].has_key('custom_deps'): |
| 2132 config_path = os.path.join(SRC_DIR, '..') | 2065 config_path = os.path.join(SRC_DIR, '..') |
| 2133 if bisect_utils.RunGClientAndCreateConfig(self.opts, | 2066 if bisect_utils.RunGClientAndCreateConfig(self.opts, |
| 2134 DEPOT_DEPS_NAME[current_depot]['custom_deps'], cwd=config_path): | 2067 DEPOT_DEPS_NAME[current_depot]['custom_deps'], cwd=config_path): |
| 2135 return [] | 2068 return [] |
| 2136 if bisect_utils.RunGClient( | 2069 if bisect_utils.RunGClient( |
| 2137 ['sync', '--revision', previous_revision], cwd=SRC_DIR): | 2070 ['sync', '--revision', previous_revision], cwd=SRC_DIR): |
| 2138 return [] | 2071 return [] |
| 2139 | 2072 |
| 2140 if current_depot == 'v8_bleeding_edge': | 2073 if current_depot == 'v8_bleeding_edge': |
| 2141 self.ChangeToDepotWorkingDirectory('chromium') | 2074 self.depot_manager.ChangeToDepotDir('chromium') |
| 2142 | 2075 |
| 2143 shutil.move('v8', 'v8.bak') | 2076 shutil.move('v8', 'v8.bak') |
| 2144 shutil.move('v8_bleeding_edge', 'v8') | 2077 shutil.move('v8_bleeding_edge', 'v8') |
| 2145 | 2078 |
| 2146 self.cleanup_commands.append(['mv', 'v8', 'v8_bleeding_edge']) | 2079 self.cleanup_commands.append(['mv', 'v8', 'v8_bleeding_edge']) |
| 2147 self.cleanup_commands.append(['mv', 'v8.bak', 'v8']) | 2080 self.cleanup_commands.append(['mv', 'v8.bak', 'v8']) |
| 2148 | 2081 |
| 2149 self.depot_cwd['v8_bleeding_edge'] = os.path.join(SRC_DIR, 'v8') | 2082 self.depot_manager.AddDepot( |
| 2150 self.depot_cwd['v8'] = os.path.join(SRC_DIR, 'v8.bak') | 2083 'v8_bleeding_edge', os.path.join(SRC_DIR, 'v8')) |
| 2084 self.depot_manager.AddDepot('v8', os.path.join(SRC_DIR, 'v8.bak')) | |
| 2151 | 2085 |
| 2152 self.ChangeToDepotWorkingDirectory(current_depot) | 2086 self.depot_manager.ChangeToDepotDir(current_depot) |
| 2153 | 2087 |
| 2154 depot_revision_list = self.GetRevisionList(current_depot, | 2088 depot_revision_list = self.GetRevisionList(current_depot, |
| 2155 end_revision, | 2089 end_revision, |
| 2156 start_revision) | 2090 start_revision) |
| 2157 | 2091 |
| 2158 self.ChangeToDepotWorkingDirectory('chromium') | 2092 self.depot_manager.ChangeToDepotDir('chromium') |
| 2159 | 2093 |
| 2160 return depot_revision_list | 2094 return depot_revision_list |
| 2161 | 2095 |
| 2162 def GatherReferenceValues(self, good_rev, bad_rev, cmd, metric, target_depot): | 2096 def GatherReferenceValues(self, good_rev, bad_rev, cmd, metric, target_depot): |
| 2163 """Gathers reference values by running the performance tests on the | 2097 """Gathers reference values by running the performance tests on the |
| 2164 known good and bad revisions. | 2098 known good and bad revisions. |
| 2165 | 2099 |
| 2166 Args: | 2100 Args: |
| 2167 good_rev: The last known good revision where the performance regression | 2101 good_rev: The last known good revision where the performance regression |
| 2168 has not occurred yet. | 2102 has not occurred yet. |
| (...skipping 86 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 2255 | 2189 |
| 2256 Args: | 2190 Args: |
| 2257 good_revision: Number/tag of the known good revision. | 2191 good_revision: Number/tag of the known good revision. |
| 2258 bad_revision: Number/tag of the known bad revision. | 2192 bad_revision: Number/tag of the known bad revision. |
| 2259 | 2193 |
| 2260 Returns: | 2194 Returns: |
| 2261 True if the revisions are in the proper order (good earlier than bad). | 2195 True if the revisions are in the proper order (good earlier than bad). |
| 2262 """ | 2196 """ |
| 2263 if self.source_control.IsGit() and target_depot != 'cros': | 2197 if self.source_control.IsGit() and target_depot != 'cros': |
| 2264 cmd = ['log', '--format=%ct', '-1', good_revision] | 2198 cmd = ['log', '--format=%ct', '-1', good_revision] |
| 2265 cwd = self._GetDepotDirectory(target_depot) | 2199 cwd = self.depot_manager.GetDepotDir(target_depot) |
| 2266 | 2200 |
| 2267 output = bisect_utils.CheckRunGit(cmd, cwd=cwd) | 2201 output = bisect_utils.CheckRunGit(cmd, cwd=cwd) |
| 2268 good_commit_time = int(output) | 2202 good_commit_time = int(output) |
| 2269 | 2203 |
| 2270 cmd = ['log', '--format=%ct', '-1', bad_revision] | 2204 cmd = ['log', '--format=%ct', '-1', bad_revision] |
| 2271 output = bisect_utils.CheckRunGit(cmd, cwd=cwd) | 2205 output = bisect_utils.CheckRunGit(cmd, cwd=cwd) |
| 2272 bad_commit_time = int(output) | 2206 bad_commit_time = int(output) |
| 2273 | 2207 |
| 2274 return good_commit_time <= bad_commit_time | 2208 return good_commit_time <= bad_commit_time |
| 2275 else: | 2209 else: |
| (...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 2321 intermediate revisions to determine the CL where the performance regression | 2255 intermediate revisions to determine the CL where the performance regression |
| 2322 occurred. | 2256 occurred. |
| 2323 | 2257 |
| 2324 Args: | 2258 Args: |
| 2325 command_to_run: Specify the command to execute the performance test. | 2259 command_to_run: Specify the command to execute the performance test. |
| 2326 good_revision: Number/tag of the known good revision. | 2260 good_revision: Number/tag of the known good revision. |
| 2327 bad_revision: Number/tag of the known bad revision. | 2261 bad_revision: Number/tag of the known bad revision. |
| 2328 metric: The performance metric to monitor. | 2262 metric: The performance metric to monitor. |
| 2329 | 2263 |
| 2330 Returns: | 2264 Returns: |
| 2331 A dict with 2 members, 'revision_data' and 'error'. On success, | 2265 A BisectResults object. |
| 2332 'revision_data' will contain a dict mapping revision ids to | |
| 2333 data about that revision. Each piece of revision data consists of a | |
| 2334 dict with the following keys: | |
| 2335 | |
| 2336 'passed': Represents whether the performance test was successful at | |
| 2337 that revision. Possible values include: 1 (passed), 0 (failed), | |
| 2338 '?' (skipped), 'F' (build failed). | |
| 2339 'depot': The depot that this revision is from (i.e. WebKit) | |
| 2340 'external': If the revision is a 'src' revision, 'external' contains | |
| 2341 the revisions of each of the external libraries. | |
| 2342 'sort': A sort value for sorting the dict in order of commits. | |
| 2343 | |
| 2344 For example: | |
| 2345 { | |
| 2346 'error':None, | |
| 2347 'revision_data': | |
| 2348 { | |
| 2349 'CL #1': | |
| 2350 { | |
| 2351 'passed': False, | |
| 2352 'depot': 'chromium', | |
| 2353 'external': None, | |
| 2354 'sort': 0 | |
| 2355 } | |
| 2356 } | |
| 2357 } | |
| 2358 | |
| 2359 If an error occurred, the 'error' field will contain the message and | |
| 2360 'revision_data' will be empty. | |
| 2361 """ | 2266 """ |
| 2362 results = { | 2267 results = BisectResults(self.depot_manager, self.source_control) |
| 2363 'revision_data' : {}, | |
| 2364 'error' : None, | |
| 2365 } | |
| 2366 | 2268 |
| 2367 # Choose depot to bisect first | 2269 # Choose depot to bisect first |
| 2368 target_depot = 'chromium' | 2270 target_depot = 'chromium' |
| 2369 if self.opts.target_platform == 'cros': | 2271 if self.opts.target_platform == 'cros': |
| 2370 target_depot = 'cros' | 2272 target_depot = 'cros' |
| 2371 elif self.opts.target_platform == 'android-chrome': | 2273 elif self.opts.target_platform == 'android-chrome': |
| 2372 target_depot = 'android-chrome' | 2274 target_depot = 'android-chrome' |
| 2373 | 2275 |
| 2374 cwd = os.getcwd() | 2276 cwd = os.getcwd() |
| 2375 self.ChangeToDepotWorkingDirectory(target_depot) | 2277 self.depot_manager.ChangeToDepotDir(target_depot) |
| 2376 | 2278 |
| 2377 # If they passed SVN revisions, we can try match them to git SHA1 hashes. | 2279 # If they passed SVN revisions, we can try match them to git SHA1 hashes. |
| 2378 bad_revision = self.source_control.ResolveToRevision( | 2280 bad_revision = self.source_control.ResolveToRevision( |
| 2379 bad_revision_in, target_depot, DEPOT_DEPS_NAME, 100) | 2281 bad_revision_in, target_depot, DEPOT_DEPS_NAME, 100) |
| 2380 good_revision = self.source_control.ResolveToRevision( | 2282 good_revision = self.source_control.ResolveToRevision( |
| 2381 good_revision_in, target_depot, DEPOT_DEPS_NAME, -100) | 2283 good_revision_in, target_depot, DEPOT_DEPS_NAME, -100) |
| 2382 | 2284 |
| 2383 os.chdir(cwd) | 2285 os.chdir(cwd) |
| 2384 if bad_revision is None: | 2286 if bad_revision is None: |
| 2385 results['error'] = 'Couldn\'t resolve [%s] to SHA1.' % bad_revision_in | 2287 results.error = 'Couldn\'t resolve [%s] to SHA1.' % bad_revision_in |
| 2386 return results | 2288 return results |
| 2387 | 2289 |
| 2388 if good_revision is None: | 2290 if good_revision is None: |
| 2389 results['error'] = 'Couldn\'t resolve [%s] to SHA1.' % good_revision_in | 2291 results.error = 'Couldn\'t resolve [%s] to SHA1.' % good_revision_in |
| 2390 return results | 2292 return results |
| 2391 | 2293 |
| 2392 # Check that they didn't accidentally swap good and bad revisions. | 2294 # Check that they didn't accidentally swap good and bad revisions. |
| 2393 if not self.CheckIfRevisionsInProperOrder( | 2295 if not self.CheckIfRevisionsInProperOrder( |
| 2394 target_depot, good_revision, bad_revision): | 2296 target_depot, good_revision, bad_revision): |
| 2395 results['error'] = ('bad_revision < good_revision, did you swap these ' | 2297 results.error = ('bad_revision < good_revision, did you swap these ' |
| 2396 'by mistake?') | 2298 'by mistake?') |
| 2397 return results | 2299 return results |
| 2398 bad_revision, good_revision = self.NudgeRevisionsIfDEPSChange( | 2300 bad_revision, good_revision = self.NudgeRevisionsIfDEPSChange( |
| 2399 bad_revision, good_revision, good_revision_in) | 2301 bad_revision, good_revision, good_revision_in) |
| 2400 if self.opts.output_buildbot_annotations: | 2302 if self.opts.output_buildbot_annotations: |
| 2401 bisect_utils.OutputAnnotationStepStart('Gathering Revisions') | 2303 bisect_utils.OutputAnnotationStepStart('Gathering Revisions') |
| 2402 | 2304 |
| 2403 cannot_bisect = self.CanPerformBisect(good_revision, bad_revision) | 2305 cannot_bisect = self.CanPerformBisect(good_revision, bad_revision) |
| 2404 if cannot_bisect: | 2306 if cannot_bisect: |
| 2405 results['error'] = cannot_bisect.get('error') | 2307 results.error = cannot_bisect.get('error') |
| 2406 return results | 2308 return results |
| 2407 | 2309 |
| 2408 print 'Gathering revision range for bisection.' | 2310 print 'Gathering revision range for bisection.' |
| 2409 # Retrieve a list of revisions to do bisection on. | 2311 # Retrieve a list of revisions to do bisection on. |
| 2410 src_revision_list = self.GetRevisionList( | 2312 src_revision_list = self.GetRevisionList( |
| 2411 target_depot, bad_revision, good_revision) | 2313 target_depot, bad_revision, good_revision) |
| 2412 | 2314 |
| 2413 if self.opts.output_buildbot_annotations: | 2315 if self.opts.output_buildbot_annotations: |
| 2414 bisect_utils.OutputAnnotationStepClosed() | 2316 bisect_utils.OutputAnnotationStepClosed() |
| 2415 | 2317 |
| 2416 if src_revision_list: | 2318 if src_revision_list: |
| 2417 # revision_data will store information about a revision such as the | 2319 # revision_data will store information about a revision such as the |
| 2418 # depot it came from, the webkit/V8 revision at that time, | 2320 # depot it came from, the webkit/V8 revision at that time, |
| 2419 # performance timing, build state, etc... | 2321 # performance timing, build state, etc... |
| 2420 revision_data = results['revision_data'] | 2322 revision_data = results.revision_data |
| 2421 | 2323 |
| 2422 # revision_list is the list we're binary searching through at the moment. | 2324 # revision_list is the list we're binary searching through at the moment. |
| 2423 revision_list = [] | 2325 revision_list = [] |
| 2424 | 2326 |
| 2425 sort_key_ids = 0 | 2327 sort_key_ids = 0 |
| 2426 | 2328 |
| 2427 for current_revision_id in src_revision_list: | 2329 for current_revision_id in src_revision_list: |
| 2428 sort_key_ids += 1 | 2330 sort_key_ids += 1 |
| 2429 | 2331 |
| 2430 revision_data[current_revision_id] = { | 2332 revision_data[current_revision_id] = { |
| (...skipping 22 matching lines...) Expand all Loading... | |
| 2453 bad_results, good_results = self.GatherReferenceValues(good_revision, | 2355 bad_results, good_results = self.GatherReferenceValues(good_revision, |
| 2454 bad_revision, | 2356 bad_revision, |
| 2455 command_to_run, | 2357 command_to_run, |
| 2456 metric, | 2358 metric, |
| 2457 target_depot) | 2359 target_depot) |
| 2458 | 2360 |
| 2459 if self.opts.output_buildbot_annotations: | 2361 if self.opts.output_buildbot_annotations: |
| 2460 bisect_utils.OutputAnnotationStepClosed() | 2362 bisect_utils.OutputAnnotationStepClosed() |
| 2461 | 2363 |
| 2462 if bad_results[1]: | 2364 if bad_results[1]: |
| 2463 results['error'] = ('An error occurred while building and running ' | 2365 results.error = ('An error occurred while building and running ' |
| 2464 'the \'bad\' reference value. The bisect cannot continue without ' | 2366 'the \'bad\' reference value. The bisect cannot continue without ' |
| 2465 'a working \'bad\' revision to start from.\n\nError: %s' % | 2367 'a working \'bad\' revision to start from.\n\nError: %s' % |
| 2466 bad_results[0]) | 2368 bad_results[0]) |
| 2467 return results | 2369 return results |
| 2468 | 2370 |
| 2469 if good_results[1]: | 2371 if good_results[1]: |
| 2470 results['error'] = ('An error occurred while building and running ' | 2372 results.error = ('An error occurred while building and running ' |
| 2471 'the \'good\' reference value. The bisect cannot continue without ' | 2373 'the \'good\' reference value. The bisect cannot continue without ' |
| 2472 'a working \'good\' revision to start from.\n\nError: %s' % | 2374 'a working \'good\' revision to start from.\n\nError: %s' % |
| 2473 good_results[0]) | 2375 good_results[0]) |
| 2474 return results | 2376 return results |
| 2475 | 2377 |
| 2476 | 2378 |
| 2477 # We need these reference values to determine if later runs should be | 2379 # We need these reference values to determine if later runs should be |
| 2478 # classified as pass or fail. | 2380 # classified as pass or fail. |
| 2479 known_bad_value = bad_results[0] | 2381 known_bad_value = bad_results[0] |
| 2480 known_good_value = good_results[0] | 2382 known_good_value = good_results[0] |
| 2481 | 2383 |
| 2482 # Can just mark the good and bad revisions explicitly here since we | 2384 # Can just mark the good and bad revisions explicitly here since we |
| 2483 # already know the results. | 2385 # already know the results. |
| (...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 2528 break | 2430 break |
| 2529 | 2431 |
| 2530 earliest_revision = max_revision_data['external'][external_depot] | 2432 earliest_revision = max_revision_data['external'][external_depot] |
| 2531 latest_revision = min_revision_data['external'][external_depot] | 2433 latest_revision = min_revision_data['external'][external_depot] |
| 2532 | 2434 |
| 2533 new_revision_list = self.PrepareToBisectOnDepot( | 2435 new_revision_list = self.PrepareToBisectOnDepot( |
| 2534 external_depot, latest_revision, earliest_revision, | 2436 external_depot, latest_revision, earliest_revision, |
| 2535 previous_revision) | 2437 previous_revision) |
| 2536 | 2438 |
| 2537 if not new_revision_list: | 2439 if not new_revision_list: |
| 2538 results['error'] = ('An error occurred attempting to retrieve ' | 2440 results.error = ('An error occurred attempting to retrieve ' |
| 2539 'revision range: [%s..%s]' % | 2441 'revision range: [%s..%s]' % |
| 2540 (earliest_revision, latest_revision)) | 2442 (earliest_revision, latest_revision)) |
| 2541 return results | 2443 return results |
| 2542 | 2444 |
| 2543 _AddRevisionsIntoRevisionData( | 2445 _AddRevisionsIntoRevisionData( |
| 2544 new_revision_list, external_depot, min_revision_data['sort'], | 2446 new_revision_list, external_depot, min_revision_data['sort'], |
| 2545 revision_data) | 2447 revision_data) |
| 2546 | 2448 |
| 2547 # Reset the bisection and perform it on the newly inserted | 2449 # Reset the bisection and perform it on the newly inserted |
| 2548 # changelists. | 2450 # changelists. |
| 2549 revision_list = new_revision_list | 2451 revision_list = new_revision_list |
| 2550 min_revision = 0 | 2452 min_revision = 0 |
| 2551 max_revision = len(revision_list) - 1 | 2453 max_revision = len(revision_list) - 1 |
| 2552 sort_key_ids += len(revision_list) | 2454 sort_key_ids += len(revision_list) |
| 2553 | 2455 |
| 2554 print ('Regression in metric %s appears to be the result of ' | 2456 print ('Regression in metric %s appears to be the result of ' |
| 2555 'changes in [%s].' % (metric, external_depot)) | 2457 'changes in [%s].' % (metric, external_depot)) |
| 2556 | 2458 |
| 2557 self.PrintRevisionsToBisectMessage(revision_list, external_depot) | 2459 self.PrintRevisionsToBisectMessage(revision_list, external_depot) |
| 2558 | 2460 |
| 2559 continue | 2461 continue |
| 2560 else: | 2462 else: |
| 2561 break | 2463 break |
| 2562 else: | 2464 else: |
| 2563 next_revision_index = (int((max_revision - min_revision) / 2) + | 2465 next_revision_index = (int((max_revision - min_revision) / 2) + |
| 2564 min_revision) | 2466 min_revision) |
| 2565 | 2467 |
| 2566 next_revision_id = revision_list[next_revision_index] | 2468 next_revision_id = revision_list[next_revision_index] |
| 2567 next_revision_data = revision_data[next_revision_id] | 2469 next_revision_data = revision_data[next_revision_id] |
| 2568 next_revision_depot = next_revision_data['depot'] | 2470 next_revision_depot = next_revision_data['depot'] |
| 2569 | 2471 |
| 2570 self.ChangeToDepotWorkingDirectory(next_revision_depot) | 2472 self.depot_manager.ChangeToDepotDir(next_revision_depot) |
| 2571 | 2473 |
| 2572 if self.opts.output_buildbot_annotations: | 2474 if self.opts.output_buildbot_annotations: |
| 2573 step_name = 'Working on [%s]' % next_revision_id | 2475 step_name = 'Working on [%s]' % next_revision_id |
| 2574 bisect_utils.OutputAnnotationStepStart(step_name) | 2476 bisect_utils.OutputAnnotationStepStart(step_name) |
| 2575 | 2477 |
| 2576 print 'Working on revision: [%s]' % next_revision_id | 2478 print 'Working on revision: [%s]' % next_revision_id |
| 2577 | 2479 |
| 2578 run_results = self.RunTest( | 2480 run_results = self.RunTest( |
| 2579 next_revision_id, next_revision_depot, command_to_run, metric, | 2481 next_revision_id, next_revision_depot, command_to_run, metric, |
| 2580 skippable=True) | 2482 skippable=True) |
| (...skipping 28 matching lines...) Expand all Loading... | |
| 2609 # If the build is broken, remove it and redo search. | 2511 # If the build is broken, remove it and redo search. |
| 2610 revision_list.pop(next_revision_index) | 2512 revision_list.pop(next_revision_index) |
| 2611 | 2513 |
| 2612 max_revision -= 1 | 2514 max_revision -= 1 |
| 2613 | 2515 |
| 2614 if self.opts.output_buildbot_annotations: | 2516 if self.opts.output_buildbot_annotations: |
| 2615 self._PrintPartialResults(results) | 2517 self._PrintPartialResults(results) |
| 2616 bisect_utils.OutputAnnotationStepClosed() | 2518 bisect_utils.OutputAnnotationStepClosed() |
| 2617 else: | 2519 else: |
| 2618 # Weren't able to sync and retrieve the revision range. | 2520 # Weren't able to sync and retrieve the revision range. |
| 2619 results['error'] = ('An error occurred attempting to retrieve revision ' | 2521 results.error = ('An error occurred attempting to retrieve revision ' |
| 2620 'range: [%s..%s]' % (good_revision, bad_revision)) | 2522 'range: [%s..%s]' % (good_revision, bad_revision)) |
| 2621 | 2523 |
| 2622 return results | 2524 return results |
| 2623 | 2525 |
| 2624 def _PrintPartialResults(self, results_dict): | 2526 def _PrintPartialResults(self, results): |
| 2625 revision_data = results_dict['revision_data'] | 2527 results_dict = results.GetResultsDict() |
| 2626 revision_data_sorted = sorted(revision_data.iteritems(), | 2528 self._PrintTestedCommitsTable(results_dict['revision_data_sorted'], |
| 2627 key = lambda x: x[1]['sort']) | |
| 2628 results_dict = self._GetResultsDict(revision_data, revision_data_sorted) | |
| 2629 | |
| 2630 self._PrintTestedCommitsTable(revision_data_sorted, | |
| 2631 results_dict['first_working_revision'], | 2529 results_dict['first_working_revision'], |
| 2632 results_dict['last_broken_revision'], | 2530 results_dict['last_broken_revision'], |
| 2633 100, final_step=False) | 2531 100, final_step=False) |
| 2634 | 2532 |
| 2635 def _ConfidenceLevelStatus(self, results_dict): | 2533 def _ConfidenceLevelStatus(self, results_dict): |
| 2636 if not results_dict['confidence']: | 2534 if not results_dict['confidence']: |
| 2637 return None | 2535 return None |
| 2638 confidence_status = 'Successful with %(level)s confidence%(warning)s.' | 2536 confidence_status = 'Successful with %(level)s confidence%(warning)s.' |
| 2639 if results_dict['confidence'] >= HIGH_CONFIDENCE: | 2537 if results_dict['confidence'] >= HIGH_CONFIDENCE: |
| 2640 level = 'high' | 2538 level = 'high' |
| 2641 else: | 2539 else: |
| 2642 level = 'low' | 2540 level = 'low' |
| 2643 warning = ' and warnings' | 2541 warning = ' and warnings' |
| 2644 if not self.warnings: | 2542 if not self.warnings: |
| 2645 warning = '' | 2543 warning = '' |
| 2646 return confidence_status % {'level': level, 'warning': warning} | 2544 return confidence_status % {'level': level, 'warning': warning} |
| 2647 | 2545 |
| 2648 def _GetViewVCLinkFromDepotAndHash(self, cl, depot): | 2546 def _GetViewVCLinkFromDepotAndHash(self, cl, depot): |
| 2649 info = self.source_control.QueryRevisionInfo(cl, | 2547 info = self.source_control.QueryRevisionInfo(cl, |
| 2650 self._GetDepotDirectory(depot)) | 2548 self.depot_manager.GetDepotDir(depot)) |
| 2651 if depot and DEPOT_DEPS_NAME[depot].has_key('viewvc'): | 2549 if depot and DEPOT_DEPS_NAME[depot].has_key('viewvc'): |
| 2652 try: | 2550 try: |
| 2653 # Format is "git-svn-id: svn://....@123456 <other data>" | 2551 # Format is "git-svn-id: svn://....@123456 <other data>" |
| 2654 svn_line = [i for i in info['body'].splitlines() if 'git-svn-id:' in i] | 2552 svn_line = [i for i in info['body'].splitlines() if 'git-svn-id:' in i] |
| 2655 svn_revision = svn_line[0].split('@') | 2553 svn_revision = svn_line[0].split('@') |
| 2656 svn_revision = svn_revision[1].split(' ')[0] | 2554 svn_revision = svn_revision[1].split(' ')[0] |
| 2657 return DEPOT_DEPS_NAME[depot]['viewvc'] + svn_revision | 2555 return DEPOT_DEPS_NAME[depot]['viewvc'] + svn_revision |
| 2658 except IndexError: | 2556 except IndexError: |
| 2659 return '' | 2557 return '' |
| 2660 return '' | 2558 return '' |
| (...skipping 132 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 2793 if not previous_link: | 2691 if not previous_link: |
| 2794 previous_link = previous_id | 2692 previous_link = previous_id |
| 2795 | 2693 |
| 2796 print ' %8s %70s %s' % ( | 2694 print ' %8s %70s %s' % ( |
| 2797 current_data['depot'], current_link, | 2695 current_data['depot'], current_link, |
| 2798 ('%d%%' % confidence).center(10, ' ')) | 2696 ('%d%%' % confidence).center(10, ' ')) |
| 2799 print ' %8s %70s' % ( | 2697 print ' %8s %70s' % ( |
| 2800 previous_data['depot'], previous_link) | 2698 previous_data['depot'], previous_link) |
| 2801 print | 2699 print |
| 2802 | 2700 |
| 2803 def _GetResultsDict(self, revision_data, revision_data_sorted): | |
| 2804 # Find range where it possibly broke. | |
| 2805 first_working_revision = None | |
| 2806 first_working_revision_index = -1 | |
| 2807 last_broken_revision = None | |
| 2808 last_broken_revision_index = -1 | |
| 2809 | |
| 2810 culprit_revisions = [] | |
| 2811 other_regressions = [] | |
| 2812 regression_size = 0.0 | |
| 2813 regression_std_err = 0.0 | |
| 2814 confidence = 0.0 | |
| 2815 | |
| 2816 for i in xrange(len(revision_data_sorted)): | |
| 2817 k, v = revision_data_sorted[i] | |
| 2818 if v['passed'] == 1: | |
| 2819 if not first_working_revision: | |
| 2820 first_working_revision = k | |
| 2821 first_working_revision_index = i | |
| 2822 | |
| 2823 if not v['passed']: | |
| 2824 last_broken_revision = k | |
| 2825 last_broken_revision_index = i | |
| 2826 | |
| 2827 if last_broken_revision != None and first_working_revision != None: | |
| 2828 broken_means = [] | |
| 2829 for i in xrange(0, last_broken_revision_index + 1): | |
| 2830 if revision_data_sorted[i][1]['value']: | |
| 2831 broken_means.append(revision_data_sorted[i][1]['value']['values']) | |
| 2832 | |
| 2833 working_means = [] | |
| 2834 for i in xrange(first_working_revision_index, len(revision_data_sorted)): | |
| 2835 if revision_data_sorted[i][1]['value']: | |
| 2836 working_means.append(revision_data_sorted[i][1]['value']['values']) | |
| 2837 | |
| 2838 # Flatten the lists to calculate mean of all values. | |
| 2839 working_mean = sum(working_means, []) | |
| 2840 broken_mean = sum(broken_means, []) | |
| 2841 | |
| 2842 # Calculate the approximate size of the regression | |
| 2843 mean_of_bad_runs = math_utils.Mean(broken_mean) | |
| 2844 mean_of_good_runs = math_utils.Mean(working_mean) | |
| 2845 | |
| 2846 regression_size = 100 * math_utils.RelativeChange(mean_of_good_runs, | |
| 2847 mean_of_bad_runs) | |
| 2848 if math.isnan(regression_size): | |
| 2849 regression_size = 'zero-to-nonzero' | |
| 2850 | |
| 2851 regression_std_err = math.fabs(math_utils.PooledStandardError( | |
| 2852 [working_mean, broken_mean]) / | |
| 2853 max(0.0001, min(mean_of_good_runs, mean_of_bad_runs))) * 100.0 | |
| 2854 | |
| 2855 # Give a "confidence" in the bisect. At the moment we use how distinct the | |
| 2856 # values are before and after the last broken revision, and how noisy the | |
| 2857 # overall graph is. | |
| 2858 confidence = ConfidenceScore(working_means, broken_means) | |
| 2859 | |
| 2860 culprit_revisions = [] | |
| 2861 | |
| 2862 cwd = os.getcwd() | |
| 2863 self.ChangeToDepotWorkingDirectory( | |
| 2864 revision_data[last_broken_revision]['depot']) | |
| 2865 | |
| 2866 if revision_data[last_broken_revision]['depot'] == 'cros': | |
| 2867 # Want to get a list of all the commits and what depots they belong | |
| 2868 # to so that we can grab info about each. | |
| 2869 cmd = ['repo', 'forall', '-c', | |
| 2870 'pwd ; git log --pretty=oneline --before=%d --after=%d' % ( | |
| 2871 last_broken_revision, first_working_revision + 1)] | |
| 2872 output, return_code = bisect_utils.RunProcessAndRetrieveOutput(cmd) | |
| 2873 | |
| 2874 changes = [] | |
| 2875 assert not return_code, ('An error occurred while running ' | |
| 2876 '"%s"' % ' '.join(cmd)) | |
| 2877 last_depot = None | |
| 2878 cwd = os.getcwd() | |
| 2879 for l in output.split('\n'): | |
| 2880 if l: | |
| 2881 # Output will be in form: | |
| 2882 # /path_to_depot | |
| 2883 # /path_to_other_depot | |
| 2884 # <SHA1> | |
| 2885 # /path_again | |
| 2886 # <SHA1> | |
| 2887 # etc. | |
| 2888 if l[0] == '/': | |
| 2889 last_depot = l | |
| 2890 else: | |
| 2891 contents = l.split(' ') | |
| 2892 if len(contents) > 1: | |
| 2893 changes.append([last_depot, contents[0]]) | |
| 2894 for c in changes: | |
| 2895 os.chdir(c[0]) | |
| 2896 info = self.source_control.QueryRevisionInfo(c[1]) | |
| 2897 culprit_revisions.append((c[1], info, None)) | |
| 2898 else: | |
| 2899 for i in xrange(last_broken_revision_index, len(revision_data_sorted)): | |
| 2900 k, v = revision_data_sorted[i] | |
| 2901 if k == first_working_revision: | |
| 2902 break | |
| 2903 self.ChangeToDepotWorkingDirectory(v['depot']) | |
| 2904 info = self.source_control.QueryRevisionInfo(k) | |
| 2905 culprit_revisions.append((k, info, v['depot'])) | |
| 2906 os.chdir(cwd) | |
| 2907 | |
| 2908 # Check for any other possible regression ranges. | |
| 2909 other_regressions = _FindOtherRegressions( | |
| 2910 revision_data_sorted, mean_of_bad_runs > mean_of_good_runs) | |
| 2911 | |
| 2912 return { | |
| 2913 'first_working_revision': first_working_revision, | |
| 2914 'last_broken_revision': last_broken_revision, | |
| 2915 'culprit_revisions': culprit_revisions, | |
| 2916 'other_regressions': other_regressions, | |
| 2917 'regression_size': regression_size, | |
| 2918 'regression_std_err': regression_std_err, | |
| 2919 'confidence': confidence, | |
| 2920 } | |
| 2921 | |
| 2922 def _CheckForWarnings(self, results_dict): | 2701 def _CheckForWarnings(self, results_dict): |
| 2923 if len(results_dict['culprit_revisions']) > 1: | 2702 if len(results_dict['culprit_revisions']) > 1: |
| 2924 self.warnings.append('Due to build errors, regression range could ' | 2703 self.warnings.append('Due to build errors, regression range could ' |
| 2925 'not be narrowed down to a single commit.') | 2704 'not be narrowed down to a single commit.') |
| 2926 if self.opts.repeat_test_count == 1: | 2705 if self.opts.repeat_test_count == 1: |
| 2927 self.warnings.append('Tests were only set to run once. This may ' | 2706 self.warnings.append('Tests were only set to run once. This may ' |
| 2928 'be insufficient to get meaningful results.') | 2707 'be insufficient to get meaningful results.') |
| 2929 if 0 < results_dict['confidence'] < HIGH_CONFIDENCE: | 2708 if 0 < results_dict['confidence'] < HIGH_CONFIDENCE: |
| 2930 self.warnings.append('Confidence is not high. Try bisecting again ' | 2709 self.warnings.append('Confidence is not high. Try bisecting again ' |
| 2931 'with increased repeat_count, larger range, or ' | 2710 'with increased repeat_count, larger range, or ' |
| 2932 'on another metric.') | 2711 'on another metric.') |
| 2933 if not results_dict['confidence']: | 2712 if not results_dict['confidence']: |
| 2934 self.warnings.append('Confidence score is 0%. Try bisecting again on ' | 2713 self.warnings.append('Confidence score is 0%. Try bisecting again on ' |
| 2935 'another platform or another metric.') | 2714 'another platform or another metric.') |
| 2936 | 2715 |
| 2937 def FormatAndPrintResults(self, bisect_results): | 2716 def FormatAndPrintResults(self, bisect_results): |
| 2938 """Prints the results from a bisection run in a readable format. | 2717 """Prints the results from a bisection run in a readable format. |
| 2939 | 2718 |
| 2940 Args: | 2719 Args: |
| 2941 bisect_results: The results from a bisection test run. | 2720 bisect_results: The results from a bisection test run. |
| 2942 """ | 2721 """ |
| 2943 revision_data = bisect_results['revision_data'] | 2722 results_dict = bisect_results.GetResultsDict() |
| 2944 revision_data_sorted = sorted(revision_data.iteritems(), | |
| 2945 key = lambda x: x[1]['sort']) | |
| 2946 results_dict = self._GetResultsDict(revision_data, revision_data_sorted) | |
| 2947 | 2723 |
| 2948 self._CheckForWarnings(results_dict) | 2724 self._CheckForWarnings(results_dict) |
| 2949 | 2725 |
| 2950 if self.opts.output_buildbot_annotations: | 2726 if self.opts.output_buildbot_annotations: |
| 2951 bisect_utils.OutputAnnotationStepStart('Build Status Per Revision') | 2727 bisect_utils.OutputAnnotationStepStart('Build Status Per Revision') |
| 2952 | 2728 |
| 2953 print | 2729 print |
| 2954 print 'Full results of bisection:' | 2730 print 'Full results of bisection:' |
| 2955 for current_id, current_data in revision_data_sorted: | 2731 for current_id, current_data in results_dict['revision_data_sorted']: |
| 2956 build_status = current_data['passed'] | 2732 build_status = current_data['passed'] |
| 2957 | 2733 |
| 2958 if type(build_status) is bool: | 2734 if type(build_status) is bool: |
| 2959 if build_status: | 2735 if build_status: |
| 2960 build_status = 'Good' | 2736 build_status = 'Good' |
| 2961 else: | 2737 else: |
| 2962 build_status = 'Bad' | 2738 build_status = 'Bad' |
| 2963 | 2739 |
| 2964 print ' %20s %40s %s' % (current_data['depot'], | 2740 print ' %20s %40s %s' % (current_data['depot'], |
| 2965 current_id, build_status) | 2741 current_id, build_status) |
| 2966 print | 2742 print |
| 2967 | 2743 |
| 2968 if self.opts.output_buildbot_annotations: | 2744 if self.opts.output_buildbot_annotations: |
| 2969 bisect_utils.OutputAnnotationStepClosed() | 2745 bisect_utils.OutputAnnotationStepClosed() |
| 2970 # The perf dashboard scrapes the "results" step in order to comment on | 2746 # The perf dashboard scrapes the "results" step in order to comment on |
| 2971 # bugs. If you change this, please update the perf dashboard as well. | 2747 # bugs. If you change this, please update the perf dashboard as well. |
| 2972 bisect_utils.OutputAnnotationStepStart('Results') | 2748 bisect_utils.OutputAnnotationStepStart('Results') |
| 2973 | 2749 |
| 2974 self._PrintBanner(results_dict) | 2750 self._PrintBanner(results_dict) |
| 2975 self._PrintWarnings() | 2751 self._PrintWarnings() |
| 2976 | 2752 |
| 2977 if results_dict['culprit_revisions'] and results_dict['confidence']: | 2753 if results_dict['culprit_revisions'] and results_dict['confidence']: |
| 2978 for culprit in results_dict['culprit_revisions']: | 2754 for culprit in results_dict['culprit_revisions']: |
| 2979 cl, info, depot = culprit | 2755 cl, info, depot = culprit |
| 2980 self._PrintRevisionInfo(cl, info, depot) | 2756 self._PrintRevisionInfo(cl, info, depot) |
| 2981 if results_dict['other_regressions']: | 2757 if results_dict['other_regressions']: |
| 2982 self._PrintOtherRegressions(results_dict['other_regressions'], | 2758 self._PrintOtherRegressions(results_dict['other_regressions'], |
| 2983 revision_data) | 2759 results_dict['revision_data']) |
| 2984 self._PrintTestedCommitsTable(revision_data_sorted, | 2760 self._PrintTestedCommitsTable(results_dict['revision_data_sorted'], |
| 2985 results_dict['first_working_revision'], | 2761 results_dict['first_working_revision'], |
| 2986 results_dict['last_broken_revision'], | 2762 results_dict['last_broken_revision'], |
| 2987 results_dict['confidence']) | 2763 results_dict['confidence']) |
| 2988 _PrintStepTime(revision_data_sorted) | 2764 _PrintStepTime(results_dict['revision_data_sorted']) |
| 2989 self._PrintReproSteps() | 2765 self._PrintReproSteps() |
| 2990 _PrintThankYou() | 2766 _PrintThankYou() |
| 2991 if self.opts.output_buildbot_annotations: | 2767 if self.opts.output_buildbot_annotations: |
| 2992 bisect_utils.OutputAnnotationStepClosed() | 2768 bisect_utils.OutputAnnotationStepClosed() |
| 2993 | 2769 |
| 2994 def _PrintBanner(self, results_dict): | 2770 def _PrintBanner(self, results_dict): |
| 2995 if self._IsBisectModeReturnCode(): | 2771 if self._IsBisectModeReturnCode(): |
| 2996 metrics = 'N/A' | 2772 metrics = 'N/A' |
| 2997 change = 'Yes' | 2773 change = 'Yes' |
| 2998 else: | 2774 else: |
| (...skipping 390 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 3389 if (not source_control.IsInProperBranch() and | 3165 if (not source_control.IsInProperBranch() and |
| 3390 not opts.debug_ignore_sync and | 3166 not opts.debug_ignore_sync and |
| 3391 not opts.working_directory): | 3167 not opts.working_directory): |
| 3392 raise RuntimeError('You must switch to master branch to run bisection.') | 3168 raise RuntimeError('You must switch to master branch to run bisection.') |
| 3393 bisect_test = BisectPerformanceMetrics(source_control, opts) | 3169 bisect_test = BisectPerformanceMetrics(source_control, opts) |
| 3394 try: | 3170 try: |
| 3395 bisect_results = bisect_test.Run(opts.command, | 3171 bisect_results = bisect_test.Run(opts.command, |
| 3396 opts.bad_revision, | 3172 opts.bad_revision, |
| 3397 opts.good_revision, | 3173 opts.good_revision, |
| 3398 opts.metric) | 3174 opts.metric) |
| 3399 if bisect_results['error']: | 3175 if bisect_results.error: |
| 3400 raise RuntimeError(bisect_results['error']) | 3176 raise RuntimeError(bisect_results.error) |
| 3401 bisect_test.FormatAndPrintResults(bisect_results) | 3177 bisect_test.FormatAndPrintResults(bisect_results) |
| 3402 return 0 | 3178 return 0 |
| 3403 finally: | 3179 finally: |
| 3404 bisect_test.PerformCleanup() | 3180 bisect_test.PerformCleanup() |
| 3405 except RuntimeError, e: | 3181 except RuntimeError, e: |
| 3406 if opts.output_buildbot_annotations: | 3182 if opts.output_buildbot_annotations: |
| 3407 # The perf dashboard scrapes the "results" step in order to comment on | 3183 # The perf dashboard scrapes the "results" step in order to comment on |
| 3408 # bugs. If you change this, please update the perf dashboard as well. | 3184 # bugs. If you change this, please update the perf dashboard as well. |
| 3409 bisect_utils.OutputAnnotationStepStart('Results') | 3185 bisect_utils.OutputAnnotationStepStart('Results') |
| 3410 print 'Error: %s' % e.message | 3186 print 'Error: %s' % e.message |
| 3411 if opts.output_buildbot_annotations: | 3187 if opts.output_buildbot_annotations: |
| 3412 bisect_utils.OutputAnnotationStepClosed() | 3188 bisect_utils.OutputAnnotationStepClosed() |
| 3413 return 1 | 3189 return 1 |
| 3414 | 3190 |
| 3415 | 3191 |
| 3416 if __name__ == '__main__': | 3192 if __name__ == '__main__': |
| 3417 sys.exit(main()) | 3193 sys.exit(main()) |
| OLD | NEW |