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

Side by Side Diff: tools/findit/blame.py

Issue 421223003: [Findit] Plain objects to represent the returned result from running the algorithm, (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: addressed code review / added git support. Created 6 years, 4 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 | « no previous file | tools/findit/matchset.py » ('j') | tools/findit/matchset.py » ('J')
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 # Copyright (c) 2014 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 threading import Lock, Thread
6
7 import crash_utils
8 import gitparser
9 import svnparser
10 import utils
11
12
13 class Blame(object):
14 """Represents a blame object.
15
16 The object contains blame information for one line of stack, and this
17 information is shown when there are no CLs that change the crashing files.
18 Attributes:
19 content: The content of the line to find the blame for.
20 component_name: The name of the component this line is in.
21 stack_frame_index: The stack frame index of this file.
22 file_name: The name of the file.
23 line_number: The line that caused a crash.
24 author: The author of this line on the latest revision.
25 crash_revision: The revision that caused the crash.
26 revision: The latest revision of this line before the crash revision.
27 url: The url of the change for the revision.
28 regression: The regression range of the component, if it exists.
29
30 """
31
32 def __init__(self, content, component_name, stack_frame_index, file_name,
33 line_number, author, crash_revision, revision, url,
34 range_start, range_end):
35 # Set all the variables from the arguments.
36 self.content = content
37 self.component_name = component_name
38 self.stack_frame_index = stack_frame_index
39 self.file = file_name
40 self.line_number = line_number
41 self.author = author
42 self.revision = revision
43 self.url = url
44 self.distance = crash_utils.INFINITY
45 revision = int(revision)
46
47 # Calculate the distance, where it measures how far the last revision is
48 # from the regression range.
49 if range_start and range_end:
50 self.distance = min(abs(revision - range_start),
51 abs(revision - range_end))
52
53 # If the regression is in SVN but it does not have regression info, check
54 # how far the last revision is from crash revision.
55 elif not utils.IsGitHash(crash_revision):
56 self.distance = abs(int(crash_revision) - revision)
57
58
59 class BlameList(object):
60 """Represents a list of blame objects.
61
62 Thread-safe.
63 """
64
65 def __init__(self):
66 self.blame_list = []
67 self.blame_list_lock = Lock()
68
69 def __getitem__(self, index):
70 return self.blame_list[index]
71
72 def AddBlame(self, blame):
73 """Adds blame object to the set."""
74 with self.blame_list_lock:
75 self.blame_list.append(blame)
76
77 def sort(self, key=None):
78 return self.blame_list.sort(key=key)
79
80 def FindBlame(self, callstack, crash_revision_dict, regression_dict,
81 url_map, n=10):
stgao 2014/08/12 19:12:18 Maybe rename "n" to "top_n_frames"?
jeun 2014/08/12 20:21:05 Done.
82 """Given a stack within a stacktrace, retrieves blame information.
83
84 Only either first 'n' or the length of stack, whichever is shorter,
85 results are returned. The default value of 'n' is 10.
86
87 Args:
88 callstack: The list of stack frames.
89 crash_revision_dict: A dictionary that maps component to its crash
90 revision.
91 regression_dict: A dictionary that maps component to its revision
92 range.
93 url_map: A map from repository type to urls.
94 n: A number of stack frames to show the blame result for.
95 """
96 # Only return blame information for first 'n' frames.
97 stack_frames = callstack.GetTopNFrames(n)
98
99 threads = []
100 # Iterate through frames in stack.
101 for stack_frame in stack_frames:
102 # If the component this line is from does not have a crash revision,
103 # It is not possible to get blame information so ignore this line.
104 component_path = stack_frame.component_path
105 if component_path not in crash_revision_dict:
106 continue
107
108 crash_revision = crash_revision_dict[component_path]['revision']
109 range_start = None
110 range_end = None
111 is_svn = not utils.IsGitHash(crash_revision)
112
113 # If the revision is in SVN, and if regression information is available,
114 # get it. Not for Git because we cannot calculate the distance.
115 if is_svn:
116 repository_parser = svnparser.SVNParser(url_map['svn'])
117
118 if regression_dict and component_path in regression_dict:
119 component_object = regression_dict[component_path]
120 range_start = int(component_object['old_revision'])
121 range_end = int(component_object['new_revision'])
122 else:
123 repository_parser = gitparser.GitParser(regression_dict,
124 url_map['git'])
125
126 # Generate blame entry, one thread for one entry.
127 blame_thread = Thread(
128 target=self.__GenerateBlameEntry,
129 args=[repository_parser, stack_frame, crash_revision,
130 range_start, range_end])
131 threads.append(blame_thread)
132 blame_thread.start()
133
134 # Join the results before returning.
135 for blame_thread in threads:
136 blame_thread.join()
137
138 def __GenerateBlameEntry(self, repository_parser, stack_frame,
139 crash_revision, range_start, range_end):
140 """Generates blame list from the arguments."""
141 stack_frame_index = stack_frame.index
142 component_path = stack_frame.component_path
143 component_name = stack_frame.component_name
144 file_name = stack_frame.file_name
145 file_path = stack_frame.file_path
146 line = stack_frame.crashed_line_number
147
148 # Parse blame information.
149 parsed_blame_info = repository_parser.ParseBlameInfo(
150 component_path, file_path, line, crash_revision)
151
152 # If it fails to retrieve information, do not do anything.
153 if not parsed_blame_info:
154 return
155
156 # Create blame object from the parsed info and add it to the list.
157 (content, revision, author, url) = parsed_blame_info
158 blame = Blame(content, component_name, stack_frame_index, file_name, line,
159 author, crash_revision, revision, url,
160 range_start, range_end)
161 self.AddBlame(blame)
162
163 def PrettifyBlame(self):
164 """Returns a string representation of blame results.
165
166 Returns:
167 A string representation of blame_list.
168 """
169 blame_list = self.blame_list
170 return_string = []
171 return_string.append('Below are the blame information of the stacktrace.\n')
172 return_string.append('[\n')
173
174 # If blame info is not available, return the message.
175 if not blame_list:
176 return_string.append('No Blame Information Available.\n')
177 return ''.join(return_string)
178
179 # Sort the blame list by its distance, and its position in stack.
180 blame_list.sort(key=lambda blame: (blame.distance,
181 blame.stack_frame_index))
182
183 for blame in blame_list:
184 # If regression information is available, do some filtering.
185 if blame.range_start and blame.range_end:
186
187 # Discards results that are too far from the regression range.
188 # For example, if regression is 10000:11000, it is very not
189 # likely that a commit from revision 1000 would have caused a crash.
190 if (blame.distance > blame.range_start / 4) and (
191 blame.distance > blame.range_end / 4):
192 continue
193
194 return_string.append(' {\n')
195 return_string.append(
196 ' suspected_cl: %s\n' % crash_utils.AddHyperlink(
197 blame.revision,
198 blame.url))
199 return_string.append(' component: %s\n' % blame.component_name)
200 return_string.append(' owner: %s\n' % blame.author)
201 reason = (
202 'The CL changes line %s of file %s from stack %d.' %
203 (blame.line_number, blame.file, blame.stack_frame_index))
204 return_string.append(' reason: %s\n' % reason)
205 if blame.content:
206 return_string.append(' content: %s\n' % blame.content)
207 return_string.append(' }\n')
208
209 return_string.append(']\n')
210 return_string.append('-------------------------------------------\n')
211 return ''.join(return_string)
OLDNEW
« no previous file with comments | « no previous file | tools/findit/matchset.py » ('j') | tools/findit/matchset.py » ('J')

Powered by Google App Engine
This is Rietveld 408576698