| OLD | NEW |
| (Empty) | |
| 1 # Copyright 2016 The Chromium Authors. All rights reserved. |
| 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. |
| 4 |
| 5 import json |
| 6 import logging |
| 7 import os |
| 8 import pickle |
| 9 import subprocess |
| 10 |
| 11 from crash_queries import crash_iterator |
| 12 from crash_queries.delta_test import delta_util |
| 13 |
| 14 AZALEA_RESULTS_DIRECTORY = os.path.join(os.path.dirname(__file__), |
| 15 'azalea_results') |
| 16 DELTA_TEST_DIRECTORY = os.path.dirname(__file__) |
| 17 |
| 18 |
| 19 class Delta(object): |
| 20 """Stands for delta between two results. |
| 21 |
| 22 Note, the 2 results should be the same kind and have the same structure. |
| 23 """ |
| 24 |
| 25 def __init__(self, result1, result2, fields): |
| 26 self._result1 = result1 |
| 27 self._result2 = result2 |
| 28 self._fields = fields |
| 29 self._delta_dict = {} |
| 30 self._delta_str_dict = {} |
| 31 |
| 32 @property |
| 33 def delta_dict(self): |
| 34 """Dict representation of delta. |
| 35 |
| 36 Returns: |
| 37 A dict. For example, for Culprit result, the delta dict is like below: |
| 38 { |
| 39 'project': 'chromium', |
| 40 'components': ['Blink>API'], |
| 41 'cls': [], |
| 42 'regression_range': ['52.0.1200.1', '52.0.1200.3'] |
| 43 } |
| 44 """ |
| 45 if self._delta_dict: |
| 46 return self._delta_dict |
| 47 |
| 48 for field in self._fields: |
| 49 value1 = getattr(self._result1, field) |
| 50 value2 = getattr(self._result2, field) |
| 51 if value1 != value2: |
| 52 if hasattr(value1, 'ToDict') and callable(value1.ToDict): |
| 53 value1 = value1.ToDict() |
| 54 value2 = value2.ToDict() |
| 55 self._delta_dict[field] = (value1, value2) |
| 56 |
| 57 return self._delta_dict |
| 58 |
| 59 @property |
| 60 def delta_str_dict(self): |
| 61 """Converts delta of each field to a string.""" |
| 62 if self._delta_str_dict: |
| 63 return self._delta_str_dict |
| 64 |
| 65 for key, (value1, value2) in self.delta_dict.iteritems(): |
| 66 self._delta_str_dict[key] = '%s: %s, %s' % (key, value1, value2) |
| 67 |
| 68 return self._delta_str_dict |
| 69 |
| 70 def ToDict(self): |
| 71 return self.delta_dict |
| 72 |
| 73 def __str__(self): |
| 74 return '\n'.join(self.delta_str_dict.values()) |
| 75 |
| 76 def __bool__(self): |
| 77 return bool(self.delta_dict) |
| 78 |
| 79 def __nonzero__(self): |
| 80 return self.__bool__() |
| 81 |
| 82 |
| 83 def GetDeltasFromTwoSetsOfResults(set1, set2): |
| 84 """Gets delta from two sets of results. |
| 85 |
| 86 Set1 and set2 are dicts mapping id to result. |
| 87 Results are a list of (message, matches, component_name, cr_label) |
| 88 Returns a list of delta results (results1, results2). |
| 89 """ |
| 90 deltas = {} |
| 91 for result_id, result1 in set1.iteritems(): |
| 92 # Even when the command are exactly the same, it's possible that one set is |
| 93 # loaded from local result file, another is just queried from database, |
| 94 # sometimes some crash results would get deleted. |
| 95 if result_id not in set2: |
| 96 continue |
| 97 |
| 98 result2 = set2[result_id] |
| 99 delta = Delta(result1, result2, result1.fields) |
| 100 if delta: |
| 101 deltas[result_id] = delta |
| 102 |
| 103 return deltas |
| 104 |
| 105 |
| 106 def GetResults(crashes, client_id, git_hash, result_path, verbose=False): |
| 107 """Returns an evaluator function to compute delta between 2 findit githashes. |
| 108 |
| 109 Args: |
| 110 crashes (list): A list of crash infos. |
| 111 client_id (str): Possible values - fracas/cracas/clustefuzz. |
| 112 git_hash (str): A git hash of findit repository. |
| 113 result_path (str): file path for subprocess to write results on. |
| 114 verbose (bool): If True, print all the findit results. |
| 115 |
| 116 Return: |
| 117 A dict mapping crash id to culprit for every crashes analyzed by |
| 118 git_hash version. |
| 119 """ |
| 120 if not crashes: |
| 121 return {} |
| 122 |
| 123 if verbose: |
| 124 logging.info('\n\n***************************') |
| 125 logging.info('Switching to git %s', git_hash) |
| 126 logging.info('***************************\n\n') |
| 127 |
| 128 with open(os.devnull, 'w') as null_handle: |
| 129 subprocess.check_call( |
| 130 'cd %s; git checkout %s' % (DELTA_TEST_DIRECTORY, git_hash), |
| 131 stdout=null_handle, |
| 132 stderr=null_handle, |
| 133 shell=True) |
| 134 |
| 135 if not os.path.exists(result_path): |
| 136 args = ['python', 'run-predator.py', result_path, '--client', client_id] |
| 137 if verbose: |
| 138 args.append('--verbose') |
| 139 p = subprocess.Popen(args, stdin=subprocess.PIPE) |
| 140 # TODO(katesonia): Cache crashes for crash_iterator and let subprocess read |
| 141 # corresponding cache file instead. |
| 142 p.communicate(input=json.dumps(crashes)) |
| 143 else: |
| 144 logging.info('\nLoading results from %s', result_path) |
| 145 |
| 146 if not os.path.exists(result_path): |
| 147 logging.error('Failed to get results.') |
| 148 return {} |
| 149 |
| 150 with open(result_path) as f: |
| 151 return pickle.load(f) |
| 152 |
| 153 return {} |
| 154 |
| 155 |
| 156 def DeltaEvaluator(git_hash1, git_hash2, |
| 157 client_id, app_id, |
| 158 start_date, end_date, batch_size, |
| 159 property_values=None, verbose=False): |
| 160 """Evaluates delta between git_hash1 and git_hash2 on a set of Testcases. |
| 161 |
| 162 Args: |
| 163 git_hash1 (str): A git hash of findit repository. |
| 164 git_hash2 (str): A git hash of findit repository. |
| 165 start_date (str): Run delta test on testcases after (including) |
| 166 the start_date, format should be '%Y-%m-%d'. |
| 167 end_date (str): Run delta test on testcases before (not including) |
| 168 the end_date, format should be '%Y-%m-%d'. |
| 169 client_id (CrashClient): Possible values are 'fracas', 'cracas', |
| 170 'cluterfuzz'. |
| 171 app_id (str): Appengine app id to query. |
| 172 batch_size (int): Size of a batch that can be queried at one time. |
| 173 property_values (dict): Property values to query. |
| 174 batch_size (int): The size of crashes that can be queried at one time. |
| 175 verbose (bool): If True, print all the findit results. |
| 176 Return: |
| 177 (deltas, crash_count). |
| 178 deltas (dict): Mappings id to delta for each culprit value. |
| 179 crash_count (int): Total count of all the crashes. |
| 180 """ |
| 181 head_branch_name = subprocess.check_output( |
| 182 ['git', 'rev-parse', '--abbrev-ref', 'HEAD']).replace('\n', '') |
| 183 try: |
| 184 deltas = {} |
| 185 crash_count = 0 |
| 186 for index, crashes in enumerate( |
| 187 crash_iterator.IterateCrashes(client_id, app_id, |
| 188 property_values=property_values, |
| 189 start_date=start_date, |
| 190 end_date=end_date, |
| 191 batch_size=batch_size, |
| 192 batch_run=True)): |
| 193 |
| 194 results = [] |
| 195 for git_hash in [git_hash1, git_hash2]: |
| 196 result_path = os.path.join( |
| 197 AZALEA_RESULTS_DIRECTORY, delta_util.GenerateFileName( |
| 198 client_id, property_values, start_date, end_date, |
| 199 batch_size, index, git_hash)) |
| 200 results.append(GetResults(crashes, client_id, git_hash, result_path, |
| 201 verbose=verbose)) |
| 202 |
| 203 crash_count += len(crashes) |
| 204 deltas.update(GetDeltasFromTwoSetsOfResults(*results)) |
| 205 |
| 206 return deltas, crash_count |
| 207 finally: |
| 208 with open(os.devnull, 'w') as null_handle: |
| 209 subprocess.check_call(['git', 'checkout', head_branch_name], |
| 210 stdout=null_handle, |
| 211 stderr=null_handle) |
| OLD | NEW |