| 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 from collections import namedtuple | 5 from collections import namedtuple |
| 6 import logging | 6 import logging |
| 7 import re | 7 import re |
| 8 | 8 |
| 9 from crash.classifier import Classifier | 9 from crash.occurrence import RankByOccurrence |
| 10 | 10 |
| 11 | 11 # TODO(http://crbug.com/659346): write coverage tests. |
| 12 # TODO(wrengr): write coverage tests the old version was lacking. | |
| 13 class Component(namedtuple('Component', | 12 class Component(namedtuple('Component', |
| 14 ['component_name', 'path_regex', 'function_regex'])): # pragma: no cover | 13 ['component_name', 'path_regex', 'function_regex'])): # pragma: no cover |
| 15 """A representation of a "component" in Chromium. | 14 """A representation of a "component" in Chromium. |
| 16 | 15 |
| 17 For example: 'Blink>DOM' or 'Blink>HTML'. Notably, a component knows | 16 For example: 'Blink>DOM' or 'Blink>HTML'. Notably, a component knows |
| 18 how to identify itself. Hence, given a stack frame or change list | 17 how to identify itself. Hence, given a stack frame or change list |
| 19 or whatever, we ask the Component whether it matches that frame, | 18 or whatever, we ask the Component whether it matches that frame, |
| 20 CL, etc.""" | 19 CL, etc.""" |
| 21 __slots__ = () | 20 __slots__ = () |
| 22 | 21 |
| 23 def __new__(cls, component_name, path_regex, function_regex=None): | 22 def __new__(cls, component_name, path_regex, function_regex=None): |
| 24 return super(cls, Component).__new__(cls, | 23 return super(cls, Component).__new__(cls, |
| 25 component_name, | 24 component_name, |
| 26 re.compile(path_regex), | 25 re.compile(path_regex), |
| 27 re.compile(function_regex) if function_regex else None) | 26 re.compile(function_regex) if function_regex else None) |
| 28 | 27 |
| 29 def MatchesStackFrame(self, frame): | 28 def MatchesStackFrame(self, frame): |
| 30 """Return true if this component matches the frame.""" | 29 """Return true if this component matches the frame.""" |
| 31 if not self.path_regex.match(frame.dep_path + frame.file_path): | 30 if not self.path_regex.match(frame.dep_path + frame.file_path): |
| 32 return False | 31 return False |
| 33 | 32 |
| 34 # We interpret function_regex=None to mean the regex that matches | 33 # We interpret function_regex=None to mean the regex that matches |
| 35 # everything. | 34 # everything. |
| 36 if not self.function_regex: | 35 if not self.function_regex: |
| 37 return True | 36 return True |
| 38 return self.function_regex.match(frame.function) | 37 return self.function_regex.match(frame.function) |
| 39 | 38 |
| 40 | 39 |
| 41 class ComponentClassifier(Classifier): | 40 class ComponentClassifier(object): |
| 42 """Determines the component of a crash. | 41 """Determines the component of a crash. |
| 43 | 42 |
| 44 For example: ['Blink>DOM', 'Blink>HTML']. | 43 For example: ['Blink>DOM', 'Blink>HTML']. |
| 45 """ | 44 """ |
| 46 | 45 |
| 47 def __init__(self, components, top_n): | 46 def __init__(self, components, top_n): |
| 48 """Build a classifier for components. | 47 """Build a classifier for components. |
| 49 | 48 |
| 50 Args: | 49 Args: |
| 51 components (list of crash.component.Component): the components to | 50 components (list of crash.component.Component): the components to |
| 52 check for. | 51 check for. |
| 53 top_n (int): how many frames of the callstack to look at""" | 52 top_n (int): how many frames of the callstack to look at""" |
| 54 super(ComponentClassifier, self).__init__() | 53 super(ComponentClassifier, self).__init__() |
| 55 if not components: | 54 if not components: |
| 56 logging.warning('Empty configuration for component classifier.') | 55 logging.warning('Empty configuration for component classifier.') |
| 57 components = [] # Ensure self.components is not None | 56 components = [] # Ensure self.components is not None |
| 58 self.components = components | 57 self.components = components |
| 59 self.top_n = top_n | 58 self.top_n = top_n |
| 60 | 59 |
| 61 def GetClassFromStackFrame(self, frame): | 60 def GetClassFromStackFrame(self, frame): |
| 62 """Determine which component is responsible for this frame.""" | 61 """Determine which component is responsible for this frame.""" |
| 63 for component in self.components: | 62 for component in self.components: |
| 64 if component.MatchesStackFrame(frame): | 63 if component.MatchesStackFrame(frame): |
| 65 return component.component_name | 64 return component.component_name |
| 66 | 65 |
| 67 return '' | 66 return '' |
| 68 | 67 |
| 68 # TODO(wrengr): refactor this into a method on Result which returns |
| 69 # the cannonical frame (and documents why it's the one we return). |
| 69 def GetClassFromResult(self, result): | 70 def GetClassFromResult(self, result): |
| 70 """Gets the component from a result. | 71 """Determine which component is responsible for this result. |
| 71 | 72 |
| 72 Note that Findit assumes files that the culprit result touched come from | 73 Note that Findit assumes files that the culprit result touched come from |
| 73 the same component. | 74 the same component. |
| 74 """ | 75 """ |
| 75 if result.file_to_stack_infos: | 76 if result.file_to_stack_infos: |
| 76 # A file in culprit result should always have its stack_info, namely a | 77 # file_to_stack_infos is a dict mapping file_path to stack_infos, |
| 77 # list of (frame, callstack_priority) pairs. | 78 # where stack_infos is a list of (frame, callstack_priority) |
| 78 frame, _ = result.file_to_stack_infos.values()[0][0] | 79 # pairs. So |.values()| returns a list of the stack_infos in an |
| 80 # arbitrary order; the first |[0]| grabs the "first" stack_infos; |
| 81 # the second |[0]| grabs the first pair from the list; and the third |
| 82 # |[0]| grabs the |frame| from the pair. |
| 83 # TODO(wrengr): why is that the right frame to look at? |
| 84 frame = result.file_to_stack_infos.values()[0][0][0] |
| 79 return self.GetClassFromStackFrame(frame) | 85 return self.GetClassFromStackFrame(frame) |
| 80 | 86 |
| 81 return '' | 87 return '' |
| 82 | 88 |
| 89 # TODO(http://crbug.com/657177): return the Component objects |
| 90 # themselves, rather than strings naming them. |
| 83 def Classify(self, results, crash_stack): | 91 def Classify(self, results, crash_stack): |
| 84 """Classifies project of a crash. | 92 """Classifies component of a crash. |
| 85 | 93 |
| 86 Args: | 94 Args: |
| 87 results (list of Result): Culprit results. | 95 results (list of Result): Culprit results. |
| 88 crash_stack (CallStack): The callstack that caused the crash. | 96 crash_stack (CallStack): The callstack that caused the crash. |
| 89 | 97 |
| 90 Returns: | 98 Returns: |
| 91 List of top 2 components. | 99 List of top 2 components. |
| 92 """ | 100 """ |
| 93 return self._Classify(results, crash_stack, self.top_n, 2) | 101 # If |results| are available, we use the components from there since |
| 102 # they're more reliable than the ones from the |crash_stack|. |
| 103 if results: |
| 104 classes = map(self.GetClassFromResult, results[:self.top_n]) |
| 105 else: |
| 106 classes = map(self.GetClassFromStackFrame, crash_stack[:self.top_n]) |
| 107 |
| 108 return RankByOccurrence(classes, 2) |
| OLD | NEW |