| Index: appengine/findit/crash/crash_report_with_dependencies.py
|
| diff --git a/appengine/findit/crash/crash_report_with_dependencies.py b/appengine/findit/crash/crash_report_with_dependencies.py
|
| deleted file mode 100644
|
| index c673f30a9296bfd6108cf92be8c302c1b54bbf0c..0000000000000000000000000000000000000000
|
| --- a/appengine/findit/crash/crash_report_with_dependencies.py
|
| +++ /dev/null
|
| @@ -1,180 +0,0 @@
|
| -# Copyright 2016 The Chromium Authors. All rights reserved.
|
| -# Use of this source code is governed by a BSD-style license that can be
|
| -# found in the LICENSE file.
|
| -
|
| -from collections import defaultdict
|
| -from collections import namedtuple
|
| -import logging
|
| -
|
| -
|
| -class _FrozenDict(dict):
|
| - """An immutable ``dict`` (or some approximation thereof).
|
| -
|
| - The goal of this class is to render a ``dict`` hashable, so
|
| - that it can be used as a key in other ``dict``s, so that in
|
| - turn we can use our ``MemoizedFunction`` on functions taking
|
| - ``CrashReportWithDependencies`` as an argument.
|
| -
|
| - For now, we simply define the ``__hash__`` method and assume clients
|
| - will not try to mutate instances after they have been stored as keys
|
| - in another ``dict``. In the future it may be worth taking further
|
| - steps to make it more difficult for clients to do such mutation.
|
| -
|
| - N.B., the ``__init__`` method will clone the ``dict`` argument. There
|
| - doesn't seem to be an easy way around this.
|
| - """
|
| - def __hash__(self):
|
| - return hash(tuple(sorted(self.items())))
|
| -
|
| -
|
| -class CrashReportWithDependencies(
|
| - namedtuple('CrashReportWithDependencies', [
|
| - 'crashed_version', 'signature', 'platform', 'stacktrace',
|
| - 'last_good_version', 'first_bad_version', 'dependencies',
|
| - 'dependency_rolls'])):
|
| - """A crash report annotated with extra information for the CL classifier.
|
| -
|
| - This class extends ``CrashReport`` with some additional information
|
| - about deps. In particular, this is useful for the CL-classifier since
|
| - this information is feature-independent and suspect-independent,
|
| - so we can compute it just once and store the results. In addition,
|
| - this class happens to be the "X" for the loglinear model underlying
|
| - ``LogLinearChangelistClassifier``.
|
| -
|
| - Properties:
|
| - crashed_version (str): The version of Chrome in which the crash occurred.
|
| - signature (str): The signature of the crash on the Chrome crash server.
|
| - platform (str): The platform affected by the crash; e.g., 'win',
|
| - 'mac', 'linux', 'android', 'ios', etc.
|
| - stacktrace (Stacktrace): The stacktrace of the crash. N.B., this is
|
| - an object generated by parsing the string containing the stack trace;
|
| - we do not store the string itself.
|
| - last_good_version (str): the last known-good revision for this crash.
|
| - first_bad_version (str): the first known-bad revision for this crash.
|
| - regression_range (pair of str): a pair of ``last_good_version`` and
|
| - ``first_bad_version``. Offered for convenience, to match the API of
|
| - ``CrashReport``. Note that, unlike ``CrashReport``, we do not permit
|
| - ``None`` as a regression range.
|
| - dependencies (_FrozenDict): An immutable dict from dependency paths to
|
| - ``Dependency`` objects. The keys are all those deps which are
|
| - used by both the ``crashed_version`` of the code, and at least
|
| - one frame in the ``stacktrace.crash_stack``.
|
| - dependency_rolls (_FrozenDict) An immutable dict from dependency
|
| - paths to ``DependencyRoll`` objects. The keys are all those
|
| - dependencies which (1) occur in the regression range for the
|
| - ``platform`` where the crash occurred, (2) neither add nor delete
|
| - a dependency, and (3) are also keys of ``dependencies``.
|
| - """
|
| - __slots__ = ()
|
| -
|
| - def __new__(cls, report, dependency_fetcher):
|
| - """Annotate a crash report with dependencies.
|
| -
|
| - N.B., because the CL-classifier does not currenly support missing
|
| - regression ranges, if the regression range of the ``report`` is
|
| - ``None`` then this constructor will return ``None`` rather than
|
| - constructing the new annotated crash report. If one (or both) of the
|
| - components of the regression range are ``None``, we will log a warning
|
| - but nevertheless construct the annotated crash report to the extent
|
| - we can. This is to maintain compatibility with the current unittests,
|
| - but really we should return ``None`` since the CL-classifiers do not
|
| - support open or half-open regression ranges either. In the future
|
| - it would probably be better to raise a ``ValueError`` rather than
|
| - returning ``None``
|
| -
|
| - Args:
|
| - report (CrashReport): The original crash report given to Predator,
|
| - which we extend.
|
| - dependency_fetcher (ChromeDependencyFetcher): For getting dep information.
|
| - """
|
| - if not report.regression_range:
|
| - logging.warning('%s.__new__: Missing regression range for report: %s',
|
| - cls.__name__, str(report))
|
| - # Give up.
|
| - return None
|
| -
|
| - last_good_version = report.regression_range[0]
|
| - if not last_good_version:
|
| - logging.warning('%s.__new__: Missing last-good version for report: %s',
|
| - cls.__name__, str(report))
|
| - # Proceed as if everything was normal.
|
| -
|
| - first_bad_version = report.regression_range[1]
|
| - if not first_bad_version:
|
| - logging.warning('%s.__new__: Missing first-bad version for report: %s',
|
| - cls.__name__, str(report))
|
| - # Proceed as if everything was normal.
|
| -
|
| - logging.info('%s.__new__: Regression range %s:%s',
|
| - cls.__name__, last_good_version, first_bad_version)
|
| -
|
| - # Short-circuit when we know the deps must be empty.
|
| - if not report.stacktrace.crash_stack:
|
| - logging.warning('%s.__new__: Missing or empty crash stack for report: %s',
|
| - cls.__name__, str(report))
|
| - dependencies = _FrozenDict()
|
| - else:
|
| - # Get all the dependencies used by the version that crashed.
|
| - crashed_version_deps = dependency_fetcher.GetDependency(
|
| - report.crashed_version, report.platform)
|
| - # Filter them to only retain those which are used by some frame in
|
| - # the callstack causing the crash (and which are truthy).
|
| - dependencies = _FrozenDict({
|
| - dep_path: crashed_version_deps[dep_path]
|
| - for dep_path in (
|
| - # N.B., returning duplicate dep paths works just fine.
|
| - frame.dep_path
|
| - for frame in report.stacktrace.crash_stack)
|
| - if dep_path and dep_path in crashed_version_deps
|
| - })
|
| -
|
| - # Short-circuit when we know the deprolls must be empty.
|
| - if not dependencies:
|
| - logging.warning('%s.__new__: Empty deps and dep rolls for report: %s',
|
| - cls.__name__, str(report))
|
| - dependency_rolls = _FrozenDict()
|
| - else:
|
| - # Get ``DependencyRoll` objects for all dependencies in the regression
|
| - # range (for the particular platform that crashed).
|
| - regression_range_dep_rolls = dependency_fetcher.GetDependencyRollsDict(
|
| - last_good_version, first_bad_version, report.platform)
|
| - # Filter out the ones which add or delete a dependency, because we
|
| - # can't really be sure whether to blame them or not. This rarely
|
| - # happens, so our inability to decide shouldn't be too much of a problem.
|
| - def HasBothRevisions(dep_path, dep_roll):
|
| - has_both_revisions = bool(dep_roll.old_revision) and bool(
|
| - dep_roll.new_revision)
|
| - if not has_both_revisions:
|
| - logging.info(
|
| - 'Skip %s dependency %s',
|
| - 'added' if dep_roll.new_revision else 'deleted',
|
| - dep_path)
|
| - return has_both_revisions
|
| - # Apply the above filter, and also filter to only retain those
|
| - # which occur in ``crashed_stack_deps``.
|
| - dependency_rolls = _FrozenDict({
|
| - dep_path: dep_roll
|
| - for dep_path, dep_roll in regression_range_dep_rolls.iteritems()
|
| - if HasBothRevisions(dep_path, dep_roll) and dep_path in dependencies
|
| - })
|
| -
|
| - return super(cls, CrashReportWithDependencies).__new__(
|
| - cls,
|
| - crashed_version = report.crashed_version,
|
| - signature = report.signature,
|
| - platform = report.platform,
|
| - stacktrace = report.stacktrace,
|
| - last_good_version = last_good_version,
|
| - first_bad_version = first_bad_version,
|
| - dependencies = dependencies,
|
| - dependency_rolls = dependency_rolls,
|
| - )
|
| -
|
| - @property
|
| - def regression_range(self):
|
| - """Returns a pair of the last-good and first-bad revisions.
|
| -
|
| - Note that even if both revisions are ``None``, this property still
|
| - returns a pair. It never returns ``None``.
|
| - """
|
| - return self.last_good_version, self.first_bad_version
|
|
|