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