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

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

Issue 2588513002: [Predator] renamed "Result" to "Suspect" (Closed)
Patch Set: Removing redundant import Created 4 years 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
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 namedtuple
6
7
8 # TODO(wrengr): we should change things to use integers with None as
9 # \"infinity\", rather than using floats.
10 # TODO(http://crbug.com/644476): this class needs a better name.
11 class AnalysisInfo(namedtuple('AnalysisInfo',
12 ['min_distance', 'min_distance_frame'])):
13 __slots__ = ()
14
15 def __str__(self): # pragma: no cover
16 return ('AnalysisInfo(min_distance = %d, min_distance_frame = %s)'
17 % (self.min_distance, self.min_distance_frame))
18
19
20 # TODO(wrengr): it's not clear why the ``priority`` is stored at all,
21 # given that every use in this file discards it. ``Result.file_to_stack_infos``
22 # should just store pointers directly to the frames themselves rather
23 # than needing this intermediate object.
24 # TODO(http://crbug.com/644476): this class needs a better name.
25 class StackInfo(namedtuple('StackInfo', ['frame', 'priority'])):
26 """Pair of a frame and the ``priority`` of the ``CallStack`` it came from."""
27 __slots__ = ()
28
29 def __str__(self): # pragma: no cover
30 return 'StackInfo(frame = %s, priority = %f)' % (self.frame, self.priority)
31
32
33 # TODO(http://crbug.com/644476): this class needs a better name.
34 class Result(object):
35 """Represents findit culprit result."""
36
37 def __init__(self, changelog, dep_path,
38 confidence=None, reasons=None, changed_files=None):
39 assert isinstance(confidence, (int, float, type(None))), TypeError(
40 'In the ``confidence`` argument to the Result constructor, '
41 'expected a number or None, but got a %s object instead.'
42 % confidence.__class__.__name__)
43 self.changelog = changelog
44 self.dep_path = dep_path
45 self.confidence = None if confidence is None else float(confidence)
46 self.reasons = reasons
47 self.changed_files = changed_files
48
49 # TODO(wrengr): (a) make these two fields private/readonly
50 # TODO(wrengr): (b) zip them together.
51 self.file_to_stack_infos = {}
52 self.file_to_analysis_info = {}
53
54 def ToDict(self):
55 return {
56 'url': self.changelog.commit_url,
57 'review_url': self.changelog.code_review_url,
58 'revision': self.changelog.revision,
59 'project_path': self.dep_path,
60 'author': self.changelog.author_email,
61 'time': str(self.changelog.author_time),
62 'reasons': self.reasons,
63 'changed_files': self.changed_files,
64 'confidence': self.confidence,
65 }
66
67 # TODO(katesonia): This is unusable for logging because in all the
68 # cases that need logging it returns the empty string! We should print
69 # this out in a more useful way (e.g., how CrashConfig is printed)
70 # so that callers don't have to use ``str(result.ToDict())`` instead. If
71 # we want a method that does what this one does, we should give it a
72 # different name that indicates what it's actually printing out.
73 def ToString(self):
74 if not self.file_to_stack_infos:
75 return ''
76
77 lines = []
78 for file_path, stack_infos in self.file_to_stack_infos.iteritems():
79 line_parts = []
80 for frame, _ in stack_infos:
81 line_parts.append('frame #%d' % frame.index)
82
83 lines.append('Changed file %s crashed in %s' % (
84 file_path, ', '.join(line_parts)))
85
86 return '\n'.join(lines)
87
88 def __str__(self):
89 return self.ToString()
90
91
92 class MatchResult(Result):
93 """Represents findit culprit result got from match algorithm."""
94
95 def Update(self, file_path, stack_infos, blame):
96 """Updates a match result with file path and its stack_infos and blame.
97
98 When a file_path is found both shown in stacktrace and touched by
99 the revision of this result, update result with the information of
100 this file.
101
102 Inserts the file path and its stack infos, and updates the min distance
103 if less distance is found between touched lines of this result and
104 crashed lines in the file path.
105
106 Args:
107 file_path (str): File path of the crashed file.
108 stack_infos (list of StackInfo): List of the frames of this file
109 together with their callstack priorities.
110 blame (Blame): Blame oject of this file.
111 """
112 self.file_to_stack_infos[file_path] = stack_infos
113
114 if not blame:
115 return
116
117 min_distance = float('inf')
118 min_distance_frame = stack_infos[0][0]
119 for region in blame:
120 if region.revision != self.changelog.revision:
121 continue
122
123 region_start = region.start
124 region_end = region_start + region.count - 1
125 for frame, _ in stack_infos:
126 frame_start = frame.crashed_line_numbers[0]
127 frame_end = frame.crashed_line_numbers[-1]
128 distance = _DistanceBetweenLineRanges((frame_start, frame_end),
129 (region_start, region_end))
130 if distance < min_distance:
131 min_distance = distance
132 min_distance_frame = frame
133
134 self.file_to_analysis_info[file_path] = AnalysisInfo(
135 min_distance = min_distance,
136 min_distance_frame = min_distance_frame,
137 )
138
139
140 def _DistanceBetweenLineRanges((start1, end1), (start2, end2)):
141 """Given two ranges, compute the (unsigned) distance between them.
142
143 Args:
144 start1: the start of the first range
145 end1: the end of the first range. Must be greater than start1.
146 start2: the start of the second range
147 end2: the end of the second range. Must be greater than start2.
148
149 Returns:
150 If the end of the earlier range comes before the start of the later
151 range, then the difference between those points. Otherwise, returns
152 zero (because the ranges overlap)."""
153 assert end1 >= start1, ValueError(
154 'the first range is empty: %d < %d' % (end1, start1))
155 assert end2 >= start2, ValueError(
156 'the second range is empty: %d < %d' % (end2, start2))
157 # There are six possible cases, but in all the cases where the two
158 # ranges overlap, the latter two differences will be negative.
159 return max(0, start2 - end1, start1 - end2)
160
161
162 class MatchResults(dict):
163 """A map from revisions to the MatchResult object for that revision."""
164
165 def __init__(self, ignore_cls=None):
166 super(MatchResults, self).__init__()
167 self._ignore_cls = ignore_cls
168
169 def GenerateMatchResults(self, file_path, dep_path,
170 stack_infos, changelogs, blame):
171 """Compute match results from a list of CLs, and store them.
172
173 Match results are generated based on newly found file path, its stack_infos,
174 and all the changelogs that touched this file in the dep in regression
175 ranges, those reverted changelogs should be ignored.
176
177 Args:
178 file_path (str): File path of the crashed file.
179 dep_path (str): Path of the dependency of the file.
180 stack_infos (list): List of stack_info dicts, represents frames of this
181 file and the callstack priorities of those frames.
182 changelogs (list): List of Changelog objects in the dep in regression
183 range which touched the file.
184 blame (Blame): Blame of the file.
185 """
186 for changelog in changelogs:
187 if self._ignore_cls and changelog.revision in self._ignore_cls:
188 continue
189
190 if changelog.revision not in self:
191 self[changelog.revision] = MatchResult(changelog, dep_path)
192
193 match_result = self[changelog.revision]
194 match_result.Update(file_path, stack_infos, blame)
OLDNEW
« no previous file with comments | « appengine/findit/crash/project_classifier.py ('k') | appengine/findit/crash/scorers/min_distance.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698