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