| 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 defaultdict | 6 from collections import defaultdict |
| 7 from collections import namedtuple |
| 7 | 8 |
| 8 from common import chrome_dependency_fetcher | 9 from common import chrome_dependency_fetcher |
| 9 from crash import crash_util | 10 from crash import crash_util |
| 10 from crash.results import MatchResults | 11 from crash.results import MatchResults |
| 11 from crash.scorers.aggregated_scorer import AggregatedScorer | 12 from crash.scorers.aggregated_scorer import AggregatedScorer |
| 12 from crash.scorers.min_distance import MinDistance | 13 from crash.scorers.min_distance import MinDistance |
| 13 from crash.scorers.top_frame_index import TopFrameIndex | 14 from crash.scorers.top_frame_index import TopFrameIndex |
| 14 from crash.stacktrace import CallStack | 15 from crash.stacktrace import CallStack |
| 15 from crash.stacktrace import Stacktrace | 16 from crash.stacktrace import Stacktrace |
| 16 from lib.gitiles.diff import ChangeType | 17 from lib.gitiles.diff import ChangeType |
| 17 | 18 |
| 18 # TODO(http://crbug.com/661822): convert this into a namedtuple. | 19 class ChangelistClassifier(namedtuple('ChangelistClassifier', |
| 19 class ChangelistClassifier(object): | 20 ['repository', 'top_n_frames', 'top_n_results', 'confidence_threshold'])): |
| 20 def __init__(self, repository, | 21 __slots__ = () |
| 22 |
| 23 def __new__(cls, repository, |
| 21 top_n_frames, top_n_results=3, confidence_threshold=0.999): | 24 top_n_frames, top_n_results=3, confidence_threshold=0.999): |
| 22 """Args: | 25 """Args: |
| 23 repository (Repository): the Git repository for getting CLs to classify. | 26 repository (Repository): the Git repository for getting CLs to classify. |
| 24 top_n_frames (int): how many frames of each callstack to look at. | 27 top_n_frames (int): how many frames of each callstack to look at. |
| 25 top_n_results (int): maximum number of results to return. | 28 top_n_results (int): maximum number of results to return. |
| 26 confidence_threshold (float): In [0,1], above which we only return | 29 confidence_threshold (float): In [0,1], above which we only return |
| 27 the first result. | 30 the first result. |
| 28 """ | 31 """ |
| 29 self._repository = repository | 32 return super(cls, ChangelistClassifier).__new__(cls, |
| 30 self.top_n_frames = top_n_frames | 33 repository, top_n_frames, top_n_results, confidence_threshold) |
| 31 self.top_n_results = top_n_results | |
| 32 self.confidence_threshold = confidence_threshold | |
| 33 | 34 |
| 34 def __str__(self): # pragma: no cover | 35 def __str__(self): # pragma: no cover |
| 35 return ('%s(top_n_frames=%d, top_n_results=%d, confidence_threshold=%g)' | 36 return ('%s(top_n_frames=%d, top_n_results=%d, confidence_threshold=%g)' |
| 36 % (self.__class__.__name__, | 37 % (self.__class__.__name__, |
| 37 self.top_n_frames, | 38 self.top_n_frames, |
| 38 self.top_n_results, | 39 self.top_n_results, |
| 39 self.confidence_threshold)) | 40 self.confidence_threshold)) |
| 40 | 41 |
| 41 def __call__(self, report): | 42 def __call__(self, report): |
| 42 """Finds changelists suspected of being responsible for the crash report. | 43 """Finds changelists suspected of being responsible for the crash report. |
| (...skipping 22 matching lines...) Expand all Loading... |
| 65 for stack in report.stacktrace]) | 66 for stack in report.stacktrace]) |
| 66 | 67 |
| 67 # We are only interested in the deps in crash stack (the callstack that | 68 # We are only interested in the deps in crash stack (the callstack that |
| 68 # caused the crash). | 69 # caused the crash). |
| 69 # TODO(wrengr): we may want to receive the crash deps as an argument, | 70 # TODO(wrengr): we may want to receive the crash deps as an argument, |
| 70 # so that when this method is called via Findit.FindCulprit, we avoid | 71 # so that when this method is called via Findit.FindCulprit, we avoid |
| 71 # doing redundant work creating it. | 72 # doing redundant work creating it. |
| 72 stack_deps = GetDepsInCrashStack( | 73 stack_deps = GetDepsInCrashStack( |
| 73 report.stacktrace.crash_stack, | 74 report.stacktrace.crash_stack, |
| 74 chrome_dependency_fetcher.ChromeDependencyFetcher( | 75 chrome_dependency_fetcher.ChromeDependencyFetcher( |
| 75 self._repository).GetDependency(report.crashed_version, | 76 self.repository).GetDependency(report.crashed_version, |
| 76 report.platform)) | 77 report.platform)) |
| 77 | 78 |
| 78 # Get dep and file to changelogs, stack_info and blame dicts. | 79 # Get dep and file to changelogs, stack_info and blame dicts. |
| 79 dep_rolls = chrome_dependency_fetcher.ChromeDependencyFetcher( | 80 dep_rolls = chrome_dependency_fetcher.ChromeDependencyFetcher( |
| 80 self._repository).GetDependencyRollsDict( | 81 self.repository).GetDependencyRollsDict( |
| 81 last_good_version, first_bad_version, report.platform) | 82 last_good_version, first_bad_version, report.platform) |
| 82 | 83 |
| 83 # Regression of a dep added/deleted (old_revision/new_revision is None) can | 84 # Regression of a dep added/deleted (old_revision/new_revision is None) can |
| 84 # not be known for sure and this case rarely happens, so just filter them | 85 # not be known for sure and this case rarely happens, so just filter them |
| 85 # out. | 86 # out. |
| 86 regression_deps_rolls = {} | 87 regression_deps_rolls = {} |
| 87 for dep_path, dep_roll in dep_rolls.iteritems(): | 88 for dep_path, dep_roll in dep_rolls.iteritems(): |
| 88 if not dep_roll.old_revision or not dep_roll.new_revision: | 89 if not dep_roll.old_revision or not dep_roll.new_revision: |
| 89 logging.info('Skip %s denpendency %s', | 90 logging.info('Skip %s denpendency %s', |
| 90 'added' if dep_roll.new_revision else 'deleted', dep_path) | 91 'added' if dep_roll.new_revision else 'deleted', dep_path) |
| 91 continue | 92 continue |
| 92 regression_deps_rolls[dep_path] = dep_roll | 93 regression_deps_rolls[dep_path] = dep_roll |
| 93 | 94 |
| 94 dep_to_file_to_changelogs, ignore_cls = GetChangeLogsForFilesGroupedByDeps( | 95 dep_to_file_to_changelogs, ignore_cls = GetChangeLogsForFilesGroupedByDeps( |
| 95 regression_deps_rolls, stack_deps, self._repository) | 96 regression_deps_rolls, stack_deps, self.repository) |
| 96 dep_to_file_to_stack_infos = GetStackInfosForFilesGroupedByDeps( | 97 dep_to_file_to_stack_infos = GetStackInfosForFilesGroupedByDeps( |
| 97 stacktrace, stack_deps) | 98 stacktrace, stack_deps) |
| 98 | 99 |
| 99 # TODO: argument order is inconsistent from others. Repository should | 100 # TODO: argument order is inconsistent from others. Repository should |
| 100 # be last argument. | 101 # be last argument. |
| 101 results = FindMatchResults(dep_to_file_to_changelogs, | 102 results = FindMatchResults(dep_to_file_to_changelogs, |
| 102 dep_to_file_to_stack_infos, | 103 dep_to_file_to_stack_infos, |
| 103 stack_deps, self._repository, ignore_cls) | 104 stack_deps, self.repository, ignore_cls) |
| 104 if not results: | 105 if not results: |
| 105 return [] | 106 return [] |
| 106 | 107 |
| 107 # TODO(wrengr): we should be able to do this map/filter/sort in one pass. | 108 # TODO(wrengr): we should be able to do this map/filter/sort in one pass. |
| 108 # Set result.confidence, result.reasons and result.changed_files. | 109 # Set result.confidence, result.reasons and result.changed_files. |
| 109 aggregated_scorer = AggregatedScorer([TopFrameIndex(), MinDistance()]) | 110 aggregated_scorer = AggregatedScorer([TopFrameIndex(), MinDistance()]) |
| 110 map(aggregated_scorer.Score, results) | 111 map(aggregated_scorer.Score, results) |
| 111 | 112 |
| 112 # Filter all the 0 confidence results. | 113 # Filter all the 0 confidence results. |
| 113 results = filter(lambda r: r.confidence != 0, results) | 114 results = filter(lambda r: r.confidence != 0, results) |
| (...skipping 187 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 301 repository.repo_url = stack_deps[dep].repo_url | 302 repository.repo_url = stack_deps[dep].repo_url |
| 302 blame = repository.GetBlame(touched_file_path, | 303 blame = repository.GetBlame(touched_file_path, |
| 303 stack_deps[dep].revision) | 304 stack_deps[dep].revision) |
| 304 | 305 |
| 305 # Generate/update each result(changelog) in changelogs, blame is used | 306 # Generate/update each result(changelog) in changelogs, blame is used |
| 306 # to calculate distance between touched lines and crashed lines in file. | 307 # to calculate distance between touched lines and crashed lines in file. |
| 307 match_results.GenerateMatchResults( | 308 match_results.GenerateMatchResults( |
| 308 touched_file_path, dep, stack_infos, changelogs, blame) | 309 touched_file_path, dep, stack_infos, changelogs, blame) |
| 309 | 310 |
| 310 return match_results.values() | 311 return match_results.values() |
| OLD | NEW |