| 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 from collections import namedtuple |
| 6 | 7 |
| 7 from crash import classifier | 8 from crash.occurrence import RankByOccurrence |
| 8 from crash.type_enums import CallStackLanguageType | 9 from crash.type_enums import CallStackLanguageType |
| 9 from model.crash.crash_config import CrashConfig | 10 from model.crash.crash_config import CrashConfig |
| 10 | 11 |
| 11 | 12 class ProjectClassifier(object): |
| 12 class ProjectClassifier(classifier.Classifier): | |
| 13 """Determines the project of a crash - (project_name, project_path). | 13 """Determines the project of a crash - (project_name, project_path). |
| 14 | 14 |
| 15 For example: ('chromium', 'src/'), ('skia', 'src/skia/'), ...etc. | 15 For example: ('chromium', 'src/'), ('skia', 'src/skia/'), ...etc. |
| 16 """ | 16 """ |
| 17 | 17 |
| 18 # TODO(http://crbug.com/657177): remove dependency on CrashConfig. |
| 18 def __init__(self): | 19 def __init__(self): |
| 19 super(ProjectClassifier, self).__init__() | 20 super(ProjectClassifier, self).__init__() |
| 20 self.project_classifier_config = CrashConfig.Get().project_classifier | 21 self.project_classifier_config = CrashConfig.Get().project_classifier |
| 21 if self.project_classifier_config: | 22 if self.project_classifier_config: |
| 22 self.project_classifier_config['host_directories'].sort( | 23 self.project_classifier_config['host_directories'].sort( |
| 23 key=lambda host: -len(host.split('/'))) | 24 key=lambda host: -len(host.split('/'))) |
| 24 | 25 |
| 26 # TODO(http://crbug.com/657177): refactor this into a method on Project. |
| 25 def _GetProjectFromDepPath(self, dep_path): | 27 def _GetProjectFromDepPath(self, dep_path): |
| 26 """Returns the project name from a dep path.""" | 28 """Returns the project name from a dep path.""" |
| 27 if not dep_path: | 29 if not dep_path: |
| 28 return '' | 30 return '' |
| 29 | 31 |
| 30 if dep_path == 'src/': | 32 if dep_path == 'src/': |
| 31 return 'chromium' | 33 return 'chromium' |
| 32 | 34 |
| 33 for host_directory in self.project_classifier_config['host_directories']: | 35 for host_directory in self.project_classifier_config['host_directories']: |
| 34 if dep_path.startswith(host_directory): | 36 if dep_path.startswith(host_directory): |
| 35 path = dep_path[len(host_directory):] | 37 path = dep_path[len(host_directory):] |
| 36 return 'chromium-%s' % path.split('/')[0].lower() | 38 return 'chromium-%s' % path.split('/')[0].lower() |
| 37 | 39 |
| 38 # Unknown path, return the whole path as project name. | 40 # Unknown path, return the whole path as project name. |
| 39 return 'chromium-%s' % '_'.join(dep_path.split('/')) | 41 return 'chromium-%s' % '_'.join(dep_path.split('/')) |
| 40 | 42 |
| 43 # TODO(http://crbug.com/657177): refactor this into Project.MatchesStackFrame. |
| 41 def GetClassFromStackFrame(self, frame): | 44 def GetClassFromStackFrame(self, frame): |
| 42 """Returns a tuple (project_name, project_path) of a StackFrame.""" | 45 """Determine which project is responsible for this frame.""" |
| 43 for marker, name in self.project_classifier_config[ | 46 for marker, name in self.project_classifier_config[ |
| 44 'function_marker_to_project_name'].iteritems(): | 47 'function_marker_to_project_name'].iteritems(): |
| 45 if frame.function.startswith(marker): | 48 if frame.function.startswith(marker): |
| 46 return name | 49 return name |
| 47 | 50 |
| 48 for marker, name in self.project_classifier_config[ | 51 for marker, name in self.project_classifier_config[ |
| 49 'file_path_marker_to_project_name'].iteritems(): | 52 'file_path_marker_to_project_name'].iteritems(): |
| 50 if marker in frame.file_path or marker in frame.raw_file_path: | 53 if marker in frame.file_path or marker in frame.raw_file_path: |
| 51 return name | 54 return name |
| 52 | 55 |
| 53 return self._GetProjectFromDepPath(frame.dep_path) | 56 return self._GetProjectFromDepPath(frame.dep_path) |
| 54 | 57 |
| 58 # TODO(wrengr): refactor this into a method on Result which returns |
| 59 # the cannonical frame (and documents why it's the one we return). |
| 55 def GetClassFromResult(self, result): | 60 def GetClassFromResult(self, result): |
| 56 """Returns (project_name, project_path) of a Result.""" | 61 """Determine which project is responsible for this result.""" |
| 57 if result.file_to_stack_infos: | 62 if result.file_to_stack_infos: |
| 58 # A file in culprit result should always have its stack_info, namely a | 63 # file_to_stack_infos is a dict mapping file_path to stack_infos, |
| 59 # list of (frame, callstack_priority) pairs. | 64 # where stack_infos is a list of (frame, callstack_priority) |
| 60 frame, _ = result.file_to_stack_infos.values()[0][0] | 65 # pairs. So |.values()| returns a list of the stack_infos in an |
| 66 # arbitrary order; the first |[0]| grabs the "first" stack_infos; |
| 67 # the second |[0]| grabs the first pair from the list; and the third |
| 68 # |[0]| grabs the |frame| from the pair. |
| 69 # TODO(wrengr): why is that the right frame to look at? |
| 70 frame = result.file_to_stack_infos.values()[0][0][0] |
| 61 return self.GetClassFromStackFrame(frame) | 71 return self.GetClassFromStackFrame(frame) |
| 62 | 72 |
| 63 return '' | 73 return '' |
| 64 | 74 |
| 65 def Classify(self, results, crash_stack): | 75 def Classify(self, results, crash_stack): |
| 66 """Classify project of a crash. | 76 """Classify project of a crash. |
| 67 | 77 |
| 68 Args: | 78 Args: |
| 69 results (list of Result): culprit results. | 79 results (list of Result): culprit results. |
| 70 crash_stack (CallStack): the callstack that caused the crash. | 80 crash_stack (CallStack): the callstack that caused the crash. |
| 71 | 81 |
| 72 Returns: | 82 Returns: |
| 73 A tuple, project of the crash - (project_name, project_path). | 83 The name of the most-suspected project; or the empty string on failure. |
| 74 """ | 84 """ |
| 75 if not self.project_classifier_config: | 85 if not self.project_classifier_config: |
| 76 logging.warning('Empty configuration for project classifier.') | 86 logging.warning('ProjectClassifier.Classify: Empty configuration.') |
| 77 return '' | 87 return None |
| 78 | 88 |
| 79 def _GetRankFunction(language_type): | 89 rank_function = None |
| 80 if language_type == CallStackLanguageType.JAVA: | 90 if crash_stack.language_type == CallStackLanguageType.JAVA: |
| 81 def _RankFunctionForJava(occurrence): | 91 def _RankFunctionForJava(occurrence): |
| 82 project_name = occurrence.name | 92 # TODO(wrengr): why are we weighting by the length, instead of |
| 83 return (len(occurrence), | 93 # the negative length as we do in the DefaultOccurrenceRanging? |
| 84 0 if 'chromium' in project_name else | 94 weight = len(occurrence) |
| 85 self.project_classifier_config[ | 95 project_name = occurrence.name |
| 86 'non_chromium_project_rank_priority'][project_name]) | 96 if 'chromium' in project_name: |
| 97 index = 0 |
| 98 else: |
| 99 index = self.project_classifier_config[ |
| 100 'non_chromium_project_rank_priority'][project_name] |
| 101 return (weight, index) |
| 87 | 102 |
| 88 return _RankFunctionForJava | 103 rank_function = _RankFunctionForJava |
| 89 | 104 |
| 90 return classifier.DefaultRankFunction | 105 top_n_frames = self.project_classifier_config['top_n'] |
| 106 # If |results| are available, we use the projects from there since |
| 107 # they're more reliable than the ones from the |crash_stack|. |
| 108 if results: |
| 109 classes = map(self.GetClassFromResult, results[:top_n_frames]) |
| 110 else: |
| 111 classes = map(self.GetClassFromStackFrame, crash_stack[:top_n_frames]) |
| 91 | 112 |
| 92 # Set the max_classes to 1, so the returned projects only has one element. | 113 # Since we're only going to return the highest-ranked class, might |
| 93 projects = self._Classify( | 114 # as well set |max_classes| to 1. |
| 94 results, crash_stack, | 115 projects = RankByOccurrence(classes, 1, rank_function=rank_function) |
| 95 self.project_classifier_config['top_n'], 1, | |
| 96 rank_function=_GetRankFunction(crash_stack.language_type)) | |
| 97 | 116 |
| 98 if projects: | 117 if projects: |
| 99 return projects[0] | 118 return projects[0] |
| 100 | 119 |
| 120 logging.warning('ProjectClassifier.Classify: no projects found.') |
| 101 return '' | 121 return '' |
| OLD | NEW |