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