Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(152)

Side by Side Diff: appengine/findit/crash/crash_report_with_dependencies.py

Issue 2663063007: [Predator] Switch from anonymous dict to CrashData. (Closed)
Patch Set: Rebase and fix delta test. Created 3 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « appengine/findit/crash/crash_report.py ('k') | appengine/findit/crash/findit.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
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
3 # found in the LICENSE file.
4
5 from collections import defaultdict
6 from collections import namedtuple
7 import logging
8
9
10 class _FrozenDict(dict):
11 """An immutable ``dict`` (or some approximation thereof).
12
13 The goal of this class is to render a ``dict`` hashable, so
14 that it can be used as a key in other ``dict``s, so that in
15 turn we can use our ``MemoizedFunction`` on functions taking
16 ``CrashReportWithDependencies`` as an argument.
17
18 For now, we simply define the ``__hash__`` method and assume clients
19 will not try to mutate instances after they have been stored as keys
20 in another ``dict``. In the future it may be worth taking further
21 steps to make it more difficult for clients to do such mutation.
22
23 N.B., the ``__init__`` method will clone the ``dict`` argument. There
24 doesn't seem to be an easy way around this.
25 """
26 def __hash__(self):
27 return hash(tuple(sorted(self.items())))
28
29
30 class CrashReportWithDependencies(
31 namedtuple('CrashReportWithDependencies', [
32 'crashed_version', 'signature', 'platform', 'stacktrace',
33 'last_good_version', 'first_bad_version', 'dependencies',
34 'dependency_rolls'])):
35 """A crash report annotated with extra information for the CL classifier.
36
37 This class extends ``CrashReport`` with some additional information
38 about deps. In particular, this is useful for the CL-classifier since
39 this information is feature-independent and suspect-independent,
40 so we can compute it just once and store the results. In addition,
41 this class happens to be the "X" for the loglinear model underlying
42 ``LogLinearChangelistClassifier``.
43
44 Properties:
45 crashed_version (str): The version of Chrome in which the crash occurred.
46 signature (str): The signature of the crash on the Chrome crash server.
47 platform (str): The platform affected by the crash; e.g., 'win',
48 'mac', 'linux', 'android', 'ios', etc.
49 stacktrace (Stacktrace): The stacktrace of the crash. N.B., this is
50 an object generated by parsing the string containing the stack trace;
51 we do not store the string itself.
52 last_good_version (str): the last known-good revision for this crash.
53 first_bad_version (str): the first known-bad revision for this crash.
54 regression_range (pair of str): a pair of ``last_good_version`` and
55 ``first_bad_version``. Offered for convenience, to match the API of
56 ``CrashReport``. Note that, unlike ``CrashReport``, we do not permit
57 ``None`` as a regression range.
58 dependencies (_FrozenDict): An immutable dict from dependency paths to
59 ``Dependency`` objects. The keys are all those deps which are
60 used by both the ``crashed_version`` of the code, and at least
61 one frame in the ``stacktrace.crash_stack``.
62 dependency_rolls (_FrozenDict) An immutable dict from dependency
63 paths to ``DependencyRoll`` objects. The keys are all those
64 dependencies which (1) occur in the regression range for the
65 ``platform`` where the crash occurred, (2) neither add nor delete
66 a dependency, and (3) are also keys of ``dependencies``.
67 """
68 __slots__ = ()
69
70 def __new__(cls, report, dependency_fetcher):
71 """Annotate a crash report with dependencies.
72
73 N.B., because the CL-classifier does not currenly support missing
74 regression ranges, if the regression range of the ``report`` is
75 ``None`` then this constructor will return ``None`` rather than
76 constructing the new annotated crash report. If one (or both) of the
77 components of the regression range are ``None``, we will log a warning
78 but nevertheless construct the annotated crash report to the extent
79 we can. This is to maintain compatibility with the current unittests,
80 but really we should return ``None`` since the CL-classifiers do not
81 support open or half-open regression ranges either. In the future
82 it would probably be better to raise a ``ValueError`` rather than
83 returning ``None``
84
85 Args:
86 report (CrashReport): The original crash report given to Predator,
87 which we extend.
88 dependency_fetcher (ChromeDependencyFetcher): For getting dep information.
89 """
90 if not report.regression_range:
91 logging.warning('%s.__new__: Missing regression range for report: %s',
92 cls.__name__, str(report))
93 # Give up.
94 return None
95
96 last_good_version = report.regression_range[0]
97 if not last_good_version:
98 logging.warning('%s.__new__: Missing last-good version for report: %s',
99 cls.__name__, str(report))
100 # Proceed as if everything was normal.
101
102 first_bad_version = report.regression_range[1]
103 if not first_bad_version:
104 logging.warning('%s.__new__: Missing first-bad version for report: %s',
105 cls.__name__, str(report))
106 # Proceed as if everything was normal.
107
108 logging.info('%s.__new__: Regression range %s:%s',
109 cls.__name__, last_good_version, first_bad_version)
110
111 # Short-circuit when we know the deps must be empty.
112 if not report.stacktrace.crash_stack:
113 logging.warning('%s.__new__: Missing or empty crash stack for report: %s',
114 cls.__name__, str(report))
115 dependencies = _FrozenDict()
116 else:
117 # Get all the dependencies used by the version that crashed.
118 crashed_version_deps = dependency_fetcher.GetDependency(
119 report.crashed_version, report.platform)
120 # Filter them to only retain those which are used by some frame in
121 # the callstack causing the crash (and which are truthy).
122 dependencies = _FrozenDict({
123 dep_path: crashed_version_deps[dep_path]
124 for dep_path in (
125 # N.B., returning duplicate dep paths works just fine.
126 frame.dep_path
127 for frame in report.stacktrace.crash_stack)
128 if dep_path and dep_path in crashed_version_deps
129 })
130
131 # Short-circuit when we know the deprolls must be empty.
132 if not dependencies:
133 logging.warning('%s.__new__: Empty deps and dep rolls for report: %s',
134 cls.__name__, str(report))
135 dependency_rolls = _FrozenDict()
136 else:
137 # Get ``DependencyRoll` objects for all dependencies in the regression
138 # range (for the particular platform that crashed).
139 regression_range_dep_rolls = dependency_fetcher.GetDependencyRollsDict(
140 last_good_version, first_bad_version, report.platform)
141 # Filter out the ones which add or delete a dependency, because we
142 # can't really be sure whether to blame them or not. This rarely
143 # happens, so our inability to decide shouldn't be too much of a problem.
144 def HasBothRevisions(dep_path, dep_roll):
145 has_both_revisions = bool(dep_roll.old_revision) and bool(
146 dep_roll.new_revision)
147 if not has_both_revisions:
148 logging.info(
149 'Skip %s dependency %s',
150 'added' if dep_roll.new_revision else 'deleted',
151 dep_path)
152 return has_both_revisions
153 # Apply the above filter, and also filter to only retain those
154 # which occur in ``crashed_stack_deps``.
155 dependency_rolls = _FrozenDict({
156 dep_path: dep_roll
157 for dep_path, dep_roll in regression_range_dep_rolls.iteritems()
158 if HasBothRevisions(dep_path, dep_roll) and dep_path in dependencies
159 })
160
161 return super(cls, CrashReportWithDependencies).__new__(
162 cls,
163 crashed_version = report.crashed_version,
164 signature = report.signature,
165 platform = report.platform,
166 stacktrace = report.stacktrace,
167 last_good_version = last_good_version,
168 first_bad_version = first_bad_version,
169 dependencies = dependencies,
170 dependency_rolls = dependency_rolls,
171 )
172
173 @property
174 def regression_range(self):
175 """Returns a pair of the last-good and first-bad revisions.
176
177 Note that even if both revisions are ``None``, this property still
178 returns a pair. It never returns ``None``.
179 """
180 return self.last_good_version, self.first_bad_version
OLDNEW
« no previous file with comments | « appengine/findit/crash/crash_report.py ('k') | appengine/findit/crash/findit.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698