| OLD | NEW |
| 1 # Copyright 2016 The Chromium Authors. All rights reserved. | 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 | 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
| 4 | 4 |
| 5 import logging | 5 import logging |
| 6 | 6 |
| 7 from google.appengine.ext import ndb | 7 from google.appengine.ext import ndb |
| 8 | 8 |
| 9 from common import appengine_util | 9 from common import appengine_util |
| 10 from common.chrome_dependency_fetcher import ChromeDependencyFetcher |
| 10 from crash import detect_regression_range | 11 from crash import detect_regression_range |
| 11 from crash.chromecrash_parser import ChromeCrashParser | 12 from crash.chromecrash_parser import ChromeCrashParser |
| 13 from crash.chrome_crash_buffer import ChromeCrashBuffer |
| 12 from crash.component import Component | 14 from crash.component import Component |
| 13 from crash.component_classifier import ComponentClassifier | 15 from crash.component_classifier import ComponentClassifier |
| 14 from crash.findit import Findit | 16 from crash.findit import Findit |
| 15 from crash.loglinear.changelist_classifier import LogLinearChangelistClassifier | 17 from crash.loglinear.changelist_classifier import LogLinearChangelistClassifier |
| 16 from crash.loglinear.changelist_features.touch_crashed_file_meta import ( | 18 from crash.loglinear.changelist_features.touch_crashed_file_meta import ( |
| 17 TouchCrashedFileMetaFeature) | 19 TouchCrashedFileMetaFeature) |
| 18 from crash.loglinear.feature import WrapperMetaFeature | 20 from crash.loglinear.feature import WrapperMetaFeature |
| 19 from crash.loglinear.weight import MetaWeight | 21 from crash.loglinear.weight import MetaWeight |
| 20 from crash.loglinear.weight import Weight | 22 from crash.loglinear.weight import Weight |
| 21 from crash.predator import Predator | 23 from crash.predator import Predator |
| 22 from crash.project import Project | 24 from crash.project import Project |
| 23 from crash.project_classifier import ProjectClassifier | 25 from crash.project_classifier import ProjectClassifier |
| 24 from crash.type_enums import CrashClient | 26 from crash.type_enums import CrashClient |
| 25 from model.crash.cracas_crash_analysis import CracasCrashAnalysis | 27 from model.crash.cracas_crash_analysis import CracasCrashAnalysis |
| 26 from model.crash.crash_config import CrashConfig | |
| 27 from model.crash.fracas_crash_analysis import FracasCrashAnalysis | 28 from model.crash.fracas_crash_analysis import FracasCrashAnalysis |
| 28 | 29 |
| 29 # TODO(katesonia): Remove the default value after adding validity check to | 30 # TODO(katesonia): Remove the default value after adding validity check to |
| 30 # config. | 31 # config. |
| 31 _FRACAS_FEEDBACK_URL_TEMPLATE = 'https://%s/crash/fracas-result-feedback?key=%s' | 32 _FRACAS_FEEDBACK_URL_TEMPLATE = 'https://%s/crash/fracas-result-feedback?key=%s' |
| 32 | 33 |
| 33 # TODO(wrengr): [Note#1] in many places below we have to do some ugly | |
| 34 # defaulting in case crash_data is missing certain keys. If we had | |
| 35 # crash_data be a proper class, rather than an anonymous dict, then we | |
| 36 # could clean all this up by having the properties themselves do the check | |
| 37 # and return the default whenever keys are missing. This would also | |
| 38 # let us do things like have regression_range be automatically computed | |
| 39 # from historical_metadata (when historical_metadata is provided and | |
| 40 # regression_range is not). | |
| 41 | 34 |
| 42 | 35 class FinditForChromeCrash(Findit): # pylint: disable=W0223 |
| 43 class FinditForChromeCrash(Findit): | |
| 44 """Find culprits for crash reports from the Chrome Crash server.""" | 36 """Find culprits for crash reports from the Chrome Crash server.""" |
| 45 | 37 |
| 46 @classmethod | 38 @classmethod |
| 47 def _ClientID(cls): # pragma: no cover | 39 def _ClientID(cls): # pragma: no cover |
| 48 if cls is FinditForChromeCrash: | 40 if cls is FinditForChromeCrash: |
| 49 logging.warning('FinditForChromeCrash is abstract, ' | 41 logging.warning('FinditForChromeCrash is abstract, ' |
| 50 'but someone constructed an instance and called _ClientID') | 42 'but someone constructed an instance and called _ClientID') |
| 51 else: | 43 else: |
| 52 logging.warning( | 44 logging.warning( |
| 53 'FinditForChromeCrash subclass %s forgot to implement _ClientID', | 45 'FinditForChromeCrash subclass %s forgot to implement _ClientID', |
| 54 cls.__name__) | 46 cls.__name__) |
| 55 raise NotImplementedError() | 47 raise NotImplementedError() |
| 56 | 48 |
| 57 # TODO(http://crbug.com/659354): remove the dependency on CrashConfig | 49 def __init__(self, get_repository, config): |
| 58 # entirely, by passing the relevant data as arguments to this constructor. | 50 super(FinditForChromeCrash, self).__init__(get_repository, config) |
| 59 def __init__(self, get_repository): | |
| 60 super(FinditForChromeCrash, self).__init__(get_repository) | |
| 61 | |
| 62 # TODO(http://crbug.com/687670): Move meta weight initial value to config. | |
| 63 meta_weight = MetaWeight({ | 51 meta_weight = MetaWeight({ |
| 64 'TouchCrashedFileMeta': MetaWeight({ | 52 'TouchCrashedFileMeta': MetaWeight({ |
| 65 'MinDistance': Weight(1.), | 53 'MinDistance': Weight(1.), |
| 66 'TopFrameIndex': Weight(1.), | 54 'TopFrameIndex': Weight(1.), |
| 67 'TouchCrashedFile': Weight(1.), | 55 'TouchCrashedFile': Weight(1.), |
| 68 }) | 56 }) |
| 69 }) | 57 }) |
| 70 meta_feature = WrapperMetaFeature( | 58 meta_feature = WrapperMetaFeature( |
| 71 [TouchCrashedFileMetaFeature(get_repository)]) | 59 [TouchCrashedFileMetaFeature(get_repository)]) |
| 72 | 60 |
| 73 project_classifier_config = CrashConfig.Get().project_classifier | 61 self._predator = Predator(LogLinearChangelistClassifier(get_repository, |
| 74 projects = [Project(name, path_regexs, function_regexs, host_directories) | 62 meta_feature, |
| 75 for name, path_regexs, function_regexs, host_directories | 63 meta_weight), |
| 76 in project_classifier_config['project_path_function_hosts']] | 64 self._component_classifier, |
| 77 component_classifier_config = CrashConfig.Get().component_classifier | 65 self._project_classifier) |
| 78 components = [Component(component_name, path_regex, function_regex) | |
| 79 for path_regex, function_regex, component_name | |
| 80 in component_classifier_config['path_function_component']] | |
| 81 # The top_n is the number of frames we want to check to get component or | |
| 82 # project classifications. | |
| 83 self._predator = Predator( | |
| 84 cl_classifier = LogLinearChangelistClassifier(get_repository, | |
| 85 meta_feature, | |
| 86 meta_weight), | |
| 87 component_classifier = ComponentClassifier( | |
| 88 components, component_classifier_config['top_n']), | |
| 89 project_classifier = ProjectClassifier( | |
| 90 projects, project_classifier_config['top_n'], | |
| 91 project_classifier_config['non_chromium_project_rank_priority'])) | |
| 92 | 66 |
| 93 self._stacktrace_parser = ChromeCrashParser() | 67 def _Predator(self): # pragma: no cover |
| 68 return self._predator |
| 94 | 69 |
| 95 def _InitializeAnalysis(self, model, crash_data): | 70 def _CheckPolicy(self, crash_buffer): |
| 96 super(FinditForChromeCrash, self)._InitializeAnalysis(model, crash_data) | 71 if not super(FinditForChromeCrash, self)._CheckPolicy(crash_buffer): |
| 97 # TODO(wrengr): see Note#1 | |
| 98 customized_data = crash_data.get('customized_data', {}) | |
| 99 model.channel = customized_data.get('channel', None) | |
| 100 model.historical_metadata = customized_data.get('historical_metadata', []) | |
| 101 | |
| 102 # TODO(wrengr): see Note#1, which would allow us to lift this | |
| 103 # implementation to the Findit base class. | |
| 104 @ndb.transactional | |
| 105 def _NeedsNewAnalysis(self, crash_data): | |
| 106 crash_identifiers = crash_data['crash_identifiers'] | |
| 107 historical_metadata = crash_data['customized_data']['historical_metadata'] | |
| 108 model = self.GetAnalysis(crash_identifiers) | |
| 109 # N.B., for mocking reasons, we must not call DetectRegressionRange | |
| 110 # directly, but rather must access it indirectly through the module. | |
| 111 new_regression_range = detect_regression_range.DetectRegressionRange( | |
| 112 historical_metadata) | |
| 113 if (model and not model.failed and | |
| 114 new_regression_range == model.regression_range): | |
| 115 logging.info('The analysis of %s has already been done.', | |
| 116 repr(crash_identifiers)) | |
| 117 return False | 72 return False |
| 118 | 73 |
| 119 if not model: | 74 if crash_buffer.platform not in self.client_config[ |
| 120 model = self.CreateAnalysis(crash_identifiers) | 75 'supported_platform_list_by_channel'].get(crash_buffer.channel, []): |
| 76 # Bail out if either the channel or platform is not supported yet. |
| 77 logging.info('Analysis of channel %s, platform %s is not supported.', |
| 78 crash_buffer.channel, crash_buffer.platform) |
| 79 return False |
| 121 | 80 |
| 122 crash_data['regression_range'] = new_regression_range | |
| 123 self._InitializeAnalysis(model, crash_data) | |
| 124 model.put() | |
| 125 return True | 81 return True |
| 126 | 82 |
| 127 def CheckPolicy(self, crash_data): | 83 def GetCrashBuffer(self, crash_data): |
| 128 crash_identifiers = crash_data['crash_identifiers'] | 84 return ChromeCrashBuffer(crash_data, |
| 129 platform = crash_data['platform'] | 85 ChromeDependencyFetcher(self._get_repository), |
| 130 # TODO(wrengr): see Note#1 | 86 top_n_frames=self.client_config['top_n']) |
| 131 channel = crash_data.get('customized_data', {}).get('channel', None) | |
| 132 # TODO(katesonia): Remove the default value after adding validity check to | |
| 133 # config. | |
| 134 if platform not in self.config.get( | |
| 135 'supported_platform_list_by_channel', {}).get(channel, []): | |
| 136 # Bail out if either the channel or platform is not supported yet. | |
| 137 logging.info('Analysis of channel %s, platform %s is not supported. ' | |
| 138 'No analysis is scheduled for %s', | |
| 139 channel, platform, repr(crash_identifiers)) | |
| 140 return None | |
| 141 | |
| 142 signature = crash_data['signature'] | |
| 143 # TODO(wrengr): can this blacklist stuff be lifted to the base class? | |
| 144 # TODO(katesonia): Remove the default value after adding validity check to | |
| 145 # config. | |
| 146 for blacklist_marker in self.config.get('signature_blacklist_markers', []): | |
| 147 if blacklist_marker in signature: | |
| 148 logging.info('%s signature is not supported. ' | |
| 149 'No analysis is scheduled for %s', blacklist_marker, | |
| 150 repr(crash_identifiers)) | |
| 151 return None | |
| 152 | |
| 153 # TODO(wrengr): should we clone ``crash_data`` rather than mutating it? | |
| 154 crash_data['platform'] = self.RenamePlatform(platform) | |
| 155 return crash_data | |
| 156 | |
| 157 def ProcessResultForPublishing(self, result, key): # pragma: no cover. | |
| 158 """Client specific processing of result data for publishing.""" | |
| 159 # This method needs to get overwritten by subclasses FinditForCracas and | |
| 160 # FinditForFracas. | |
| 161 raise NotImplementedError() | |
| 162 | 87 |
| 163 | 88 |
| 164 # TODO(http://crbug.com/659346): we misplaced the coverage tests; find them! | 89 # TODO(http://crbug.com/659346): we misplaced the coverage tests; find them! |
| 165 class FinditForCracas(FinditForChromeCrash): # pragma: no cover | 90 class FinditForCracas( # pylint: disable=W0223 |
| 91 FinditForChromeCrash): # pragma: no cover |
| 92 |
| 166 @classmethod | 93 @classmethod |
| 167 def _ClientID(cls): | 94 def _ClientID(cls): |
| 168 return CrashClient.CRACAS | 95 return CrashClient.CRACAS |
| 169 | 96 |
| 170 def CreateAnalysis(self, crash_identifiers): | 97 def CreateAnalysis(self, crash_identifiers): |
| 171 # TODO: inline CracasCrashAnalysis.Create stuff here. | 98 # TODO: inline CracasCrashAnalysis.Create stuff here. |
| 172 return CracasCrashAnalysis.Create(crash_identifiers) | 99 return CracasCrashAnalysis.Create(crash_identifiers) |
| 173 | 100 |
| 174 def GetAnalysis(self, crash_identifiers): | 101 def GetAnalysis(self, crash_identifiers): |
| 175 # TODO: inline CracasCrashAnalysis.Get stuff here. | 102 # TODO: inline CracasCrashAnalysis.Get stuff here. |
| 176 return CracasCrashAnalysis.Get(crash_identifiers) | 103 return CracasCrashAnalysis.Get(crash_identifiers) |
| 177 | 104 |
| 178 def ProcessResultForPublishing(self, result, key): # pragma: no cover. | 105 def ProcessResultForPublishing(self, result, key): # pragma: no cover. |
| 179 """Cracas specific processing of result data for publishing.""" | 106 """Cracas specific processing of result data for publishing.""" |
| 180 # TODO(katesonia) Add feedback page link information to result after | 107 # TODO(katesonia) Add feedback page link information to result after |
| 181 # feedback page of Cracas is added. | 108 # feedback page of Cracas is added. |
| 182 return result | 109 return result |
| 183 | 110 |
| 184 | 111 |
| 185 class FinditForFracas(FinditForChromeCrash): | 112 class FinditForFracas(FinditForChromeCrash): # pylint: disable=W0223 |
| 186 @classmethod | 113 @classmethod |
| 187 def _ClientID(cls): | 114 def _ClientID(cls): |
| 188 return CrashClient.FRACAS | 115 return CrashClient.FRACAS |
| 189 | 116 |
| 190 def CreateAnalysis(self, crash_identifiers): | 117 def CreateAnalysis(self, crash_identifiers): |
| 191 # TODO: inline FracasCrashAnalysis.Create stuff here. | 118 # TODO: inline FracasCrashAnalysis.Create stuff here. |
| 192 return FracasCrashAnalysis.Create(crash_identifiers) | 119 return FracasCrashAnalysis.Create(crash_identifiers) |
| 193 | 120 |
| 194 def GetAnalysis(self, crash_identifiers): | 121 def GetAnalysis(self, crash_identifiers): |
| 195 # TODO: inline FracasCrashAnalysis.Get stuff here. | 122 # TODO: inline FracasCrashAnalysis.Get stuff here. |
| 196 return FracasCrashAnalysis.Get(crash_identifiers) | 123 return FracasCrashAnalysis.Get(crash_identifiers) |
| 197 | 124 |
| 198 def ProcessResultForPublishing(self, result, key): | 125 def ProcessResultForPublishing(self, result, key): |
| 199 """Fracas specific processing of result data for publishing.""" | 126 """Fracas specific processing of result data for publishing.""" |
| 200 result['feedback_url'] = _FRACAS_FEEDBACK_URL_TEMPLATE % ( | 127 result['feedback_url'] = _FRACAS_FEEDBACK_URL_TEMPLATE % ( |
| 201 appengine_util.GetDefaultVersionHostname(), key) | 128 appengine_util.GetDefaultVersionHostname(), key) |
| 202 return result | 129 return result |
| OLD | NEW |