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