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.
5 from threading
import Lock
, Thread
7 from common
import utils
11 """Represents a blame object.
13 The object contains blame information for one line of stack, and this
14 information is shown when there are no CLs that change the crashing files.
16 line_content: The content of the line to find the blame for.
17 component_name: The name of the component for this line.
18 stack_frame_index: The stack frame index of this file.
19 file_name: The name of the file.
20 line_number: The line that caused a crash.
21 author: The author of this line on the latest revision.
22 crash_revision: The revision that caused the crash.
23 revision: The latest revision of this line before the crash revision.
24 url: The url of the change for the revision.
25 range_start: The starting range of the regression for this component.
26 range_end: The ending range of the regression.
30 def __init__(self
, line_content
, component_name
, stack_frame_index
,
31 file_name
, line_number
, author
, revision
,
32 url
, range_start
, range_end
):
33 # Set all the variables from the arguments.
34 self
.line_content
= line_content
35 self
.component_name
= component_name
36 self
.stack_frame_index
= stack_frame_index
38 self
.line_number
= line_number
40 self
.revision
= revision
42 self
.range_start
= range_start
43 self
.range_end
= range_end
46 class BlameList(object):
47 """Represents a list of blame objects.
54 self
.blame_list_lock
= Lock()
56 def __getitem__(self
, index
):
57 return self
.blame_list
[index
]
59 def FindBlame(self
, callstack
, crash_revision_dict
, regression_dict
, parsers
,
61 """Given a stack within a stacktrace, retrieves blame information.
63 Only either first 'top_n_frames' or the length of stack, whichever is
64 shorter, results are returned. The default value of 'top_n_frames' is 10.
67 callstack: The list of stack frames.
68 crash_revision_dict: A dictionary that maps component to its crash
70 regression_dict: A dictionary that maps component to its revision
72 parsers: A list of two parsers, svn_parser and git_parser
73 top_n_frames: A number of stack frames to show the blame result for.
75 # Only return blame information for first 'top_n_frames' frames.
76 stack_frames
= callstack
.GetTopNFrames(top_n_frames
)
78 # Iterate through frames in stack.
79 for stack_frame
in stack_frames
:
80 # If the component this line is from does not have a crash revision,
81 # it is not possible to get blame information, so ignore this line.
82 component_path
= stack_frame
.component_path
83 if component_path
not in crash_revision_dict
:
86 crash_revision
= crash_revision_dict
[component_path
]['revision']
89 is_git
= utils
.IsGitHash(crash_revision
)
91 repository_parser
= parsers
['git']
93 repository_parser
= parsers
['svn']
95 # If the revision is in SVN, and if regression information is available,
96 # get it. For Git, we cannot know the ordering between hash numbers.
98 if regression_dict
and component_path
in regression_dict
:
99 component_object
= regression_dict
[component_path
]
100 range_start
= int(component_object
['old_revision'])
101 range_end
= int(component_object
['new_revision'])
103 # Generate blame entry, one thread for one entry.
104 blame_thread
= Thread(
105 target
=self
.__GenerateBlameEntry
,
106 args
=[repository_parser
, stack_frame
, crash_revision
,
107 range_start
, range_end
])
108 threads
.append(blame_thread
)
111 # Join the results before returning.
112 for blame_thread
in threads
:
115 def __GenerateBlameEntry(self
, repository_parser
, stack_frame
,
116 crash_revision
, range_start
, range_end
):
117 """Generates blame list from the arguments."""
118 stack_frame_index
= stack_frame
.index
119 component_path
= stack_frame
.component_path
120 component_name
= stack_frame
.component_name
121 file_name
= stack_frame
.file_name
122 file_path
= stack_frame
.file_path
123 crashed_line_number
= stack_frame
.crashed_line_number
125 # Parse blame information.
126 parsed_blame_info
= repository_parser
.ParseBlameInfo(
127 component_path
, file_path
, crashed_line_number
, crash_revision
)
129 # If it fails to retrieve information, do not do anything.
130 if not parsed_blame_info
or len(parsed_blame_info
) != 4:
133 # Create blame object from the parsed info and add it to the list.
134 (line_content
, revision
, author
, url
) = parsed_blame_info
135 blame
= Blame(line_content
, component_name
, stack_frame_index
, file_name
,
136 crashed_line_number
, author
, revision
, url
,
137 range_start
, range_end
)
139 with self
.blame_list_lock
:
140 self
.blame_list
.append(blame
)
142 def FilterAndSortBlameList(self
):
143 """Filters and sorts the blame list."""
144 # Sort the blame list by its position in stack.
145 self
.blame_list
.sort(key
=lambda blame
: blame
.stack_frame_index
)
147 filtered_blame_list
= []
149 for blame
in self
.blame_list
:
150 # If regression information is available, check if it needs to be
152 if blame
.range_start
and blame
.range_end
:
154 # Discards results that are after the end of regression.
155 if not utils
.IsGitHash(blame
.revision
) and (
156 int(blame
.range_end
) <= int(blame
.revision
)):
159 filtered_blame_list
.append(blame
)
161 self
.blame_list
= filtered_blame_list