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

Side by Side Diff: appengine/findit/handlers/triage_analysis.py

Issue 2086113004: [Findit] Show build analysis references in UI for Findit Cross-platform auto-triage (Closed) Base URL: https://chromium.googlesource.com/infra/infra.git@build-matching
Patch Set: Rebased on tip of tree Created 4 years, 5 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
OLDNEW
1 # Copyright 2014 The Chromium Authors. All rights reserved. 1 # Copyright 2014 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be 2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file. 3 # found in the LICENSE file.
4 4
5 """This module is to handle manual triage of analysis result. 5 """This module is to handle manual triage of analysis result.
6 6
7 This handler will flag the analysis result as correct or incorrect. 7 This handler will flag the analysis result as correct or incorrect.
8 TODO: work on an automatic or semi-automatic way to triage analysis result. 8 TODO: work on an automatic or semi-automatic way to triage analysis result.
9 """ 9 """
10 10
(...skipping 72 matching lines...) Expand 10 before | Expand all | Expand 10 after
83 83
84 # Both analyses must have non-empty potential culprit lists. 84 # Both analyses must have non-empty potential culprit lists.
85 if not potential_culprit_tuple_list_1 or not potential_culprit_tuple_list_2: 85 if not potential_culprit_tuple_list_1 or not potential_culprit_tuple_list_2:
86 return False 86 return False
87 87
88 # Both analyses must have matching potential culprit lists. 88 # Both analyses must have matching potential culprit lists.
89 return (sorted(potential_culprit_tuple_list_1) == 89 return (sorted(potential_culprit_tuple_list_1) ==
90 sorted(potential_culprit_tuple_list_2)) 90 sorted(potential_culprit_tuple_list_2))
91 91
92 92
93 def _AppendTriageHistoryRecord(analysis, is_correct, user_name): 93 def _AppendTriageHistoryRecord(
94 analysis, is_correct, user_name, is_duplicate=False):
94 """Appends a triage history record to the given analysis. 95 """Appends a triage history record to the given analysis.
95 96
96 Args: 97 Args:
97 analysis: The analysis to which to append the history record. 98 analysis: The analysis to which to append the history record.
98 is_correct: True if the history record should indicate a correct judgement, 99 is_correct: True if the history record should indicate a correct judgement,
99 otherwise False. 100 otherwise False.
100 user_name: The user_name of the person to include in the triage record. 101 user_name: The user_name of the person to include in the triage record.
102 is_duplicate: Whether or not this analysis is a duplicate of another
103 analysis. If this analysis is a duplicate, then set the result_status
104 accordingly. If this analysis is not a duplicate, reset the reference to
105 the 'first-cause' analaysis.
101 """ 106 """
102 if is_correct: 107 if is_correct:
103 if analysis.suspected_cls: 108 if analysis.suspected_cls:
104 analysis.result_status = result_status.FOUND_CORRECT 109 if is_duplicate:
110 analysis.result_status = result_status.FOUND_CORRECT_DUPLICATE
111 else:
112 analysis.result_status = result_status.FOUND_CORRECT
105 analysis.culprit_cls = analysis.suspected_cls 113 analysis.culprit_cls = analysis.suspected_cls
106 else: 114 else:
107 analysis.result_status = result_status.NOT_FOUND_CORRECT 115 analysis.result_status = result_status.NOT_FOUND_CORRECT
108 analysis.culprit_cls = None 116 analysis.culprit_cls = None
109 else: 117 else:
110 analysis.culprit_cls = None 118 analysis.culprit_cls = None
111 if analysis.suspected_cls: 119 if analysis.suspected_cls:
112 analysis.result_status = result_status.FOUND_INCORRECT 120 if is_duplicate:
121 analysis.result_status = result_status.FOUND_INCORRECT_DUPLICATE
122 else:
123 analysis.result_status = result_status.FOUND_INCORRECT
113 else: 124 else:
114 analysis.result_status = result_status.NOT_FOUND_INCORRECT 125 analysis.result_status = result_status.NOT_FOUND_INCORRECT
115 126
127 if not is_duplicate:
128 # Resets the reference to the 'first-cause' triage analysis.
129 # When another 'first-cause' build analysis is triaged, and this build
130 # analysis is marked as a duplicate from that other 'first-cause' build
131 # analysis, these are the variables that hold the reference back to that
132 # 'first-cause' build analysis. It's possible that someone could then
133 # manually re-triage this build analysis, in which case this build analysis
134 # is no longer a duplicate, and we want to erase the reference to the
135 # no-longer-relevant 'first-cause' build_analysis.
136 analysis.triage_reference_analysis_master_name = None
137 analysis.triage_reference_analysis_builder_name = None
138 analysis.triage_reference_analysis_build_number = None
139
116 triage_record = { 140 triage_record = {
117 'triage_timestamp': calendar.timegm(datetime.utcnow().timetuple()), 141 'triage_timestamp': calendar.timegm(datetime.utcnow().timetuple()),
118 'user_name': user_name, 142 'user_name': user_name,
119 'result_status': analysis.result_status, 143 'result_status': analysis.result_status,
120 'version': analysis.version, 144 'version': analysis.version,
121 } 145 }
122 if not analysis.triage_history: 146 if not analysis.triage_history:
123 analysis.triage_history = [] 147 analysis.triage_history = []
124 analysis.triage_history.append(triage_record) 148 analysis.triage_history.append(triage_record)
125 149
126 analysis.put() 150 analysis.put()
127 151
128 152
129 @ndb.transactional 153 @ndb.transactional
130 def _UpdateAnalysisResultStatus( 154 def _UpdateAnalysisResultStatus(
131 master_name, builder_name, build_number, is_correct, user_name=None): 155 master_name, builder_name, build_number, is_correct, user_name=None):
132 analysis = WfAnalysis.Get(master_name, builder_name, build_number) 156 analysis = WfAnalysis.Get(master_name, builder_name, build_number)
133 if not analysis or not analysis.completed: 157 if not analysis or not analysis.completed:
134 return False, None 158 return False, None
135 159
136 _AppendTriageHistoryRecord(analysis, is_correct, user_name) 160 _AppendTriageHistoryRecord(analysis, is_correct, user_name,
161 is_duplicate=False)
137 162
138 return True, analysis 163 return True, analysis
139 164
140 165
141 def _GetDuplicateAnalyses(original_analysis): 166 def _GetDuplicateAnalyses(original_analysis):
142 start_time = (original_analysis.build_start_time - 167 start_time = (original_analysis.build_start_time -
143 timedelta(hours=MATCHING_ANALYSIS_HOURS_AGO_START)) 168 timedelta(hours=MATCHING_ANALYSIS_HOURS_AGO_START))
144 end_time = (original_analysis.build_start_time + 169 end_time = (original_analysis.build_start_time +
145 timedelta(hours=MATCHING_ANALYSIS_HOURS_AGO_END)) 170 timedelta(hours=MATCHING_ANALYSIS_HOURS_AGO_END))
146 171
147 # Don't count any analyses from today (except for exactly at midnight local 172 # Don't count any analyses from today (except for exactly at midnight local
148 # time). 173 # time).
149 # Get current time (UTC). 174 # Get current time (UTC).
150 current_time_as_utc = pytz.utc.localize(datetime.utcnow()) 175 current_time_as_utc = pytz.utc.localize(datetime.utcnow())
151 176
152 # Convert to local time. 177 # Convert to local time.
153 current_time_as_local = current_time_as_utc.astimezone( 178 current_time_as_local = current_time_as_utc.astimezone(
154 pytz.timezone(MATCHING_ANALYSIS_END_BOUND_TIME_ZONE)) 179 pytz.timezone(MATCHING_ANALYSIS_END_BOUND_TIME_ZONE))
155 180
156 # Set hours and minutes to 0 to get midnight. 181 # Set hours and minutes to 0 to get midnight.
157 local_midnight_as_local = current_time_as_local.replace( 182 local_midnight_as_local = current_time_as_local.replace(
158 hour=0, minute=0, second=0, microsecond=0) 183 hour=0, minute=0, second=0, microsecond=0)
159 184
160 # Convert back to UTC time. 185 # Convert back to UTC time.
161 local_midnight_as_utc = local_midnight_as_local.astimezone(pytz.utc) 186 local_midnight_as_utc = local_midnight_as_local.astimezone(pytz.utc)
162 187
163 # Strip timezone. 188 # Strip timezone.
164 local_midnight = local_midnight_as_utc.replace(tzinfo=None) 189 local_midnight = local_midnight_as_utc.replace(tzinfo=None)
165 190
166 if end_time > local_midnight: # pragma: no branch 191 if end_time > local_midnight:
167 end_time = local_midnight 192 end_time = local_midnight
168 193
169 # Retrieve potential duplicate build analyses. 194 # Retrieve potential duplicate build analyses.
170 analysis_results = WfAnalysis.query(ndb.AND( 195 analysis_results = WfAnalysis.query(ndb.AND(
171 WfAnalysis.build_start_time >= start_time, 196 WfAnalysis.build_start_time >= start_time,
172 WfAnalysis.build_start_time <= end_time, 197 WfAnalysis.build_start_time <= end_time,
173 WfAnalysis.result_status == result_status.FOUND_UNTRIAGED 198 WfAnalysis.result_status == result_status.FOUND_UNTRIAGED
174 )).fetch() 199 )).fetch()
175 200
176 # Further filter potential duplicates and return them. 201 # Further filter potential duplicates and return them.
177 return [analysis for analysis in analysis_results if 202 return [analysis for analysis in analysis_results if
203 analysis.completed and
204 analysis.result and
178 _DoAnalysesMatch(original_analysis, analysis) and 205 _DoAnalysesMatch(original_analysis, analysis) and
179 original_analysis.key is not analysis.key and 206 original_analysis.key is not analysis.key]
180 analysis.completed]
181 207
182 208
183 def _TriageDuplicateResults(original_analysis, is_correct, user_name=None): 209 def _TriageAndCountDuplicateResults(original_analysis, is_correct,
210 user_name=None):
184 matching_analyses = _GetDuplicateAnalyses(original_analysis) 211 matching_analyses = _GetDuplicateAnalyses(original_analysis)
185 212
186 for analysis in matching_analyses: 213 for analysis in matching_analyses:
187 _AppendTriageHistoryRecord(analysis, is_correct, user_name) 214 analysis.triage_reference_analysis_master_name = (
215 original_analysis.master_name)
216 analysis.triage_reference_analysis_builder_name = (
217 original_analysis.builder_name)
218 analysis.triage_reference_analysis_build_number = (
219 original_analysis.build_number)
220 _AppendTriageHistoryRecord(analysis, is_correct, user_name,
221 is_duplicate=True)
222
223 return len(matching_analyses)
188 224
189 225
190 class TriageAnalysis(BaseHandler): 226 class TriageAnalysis(BaseHandler):
191 PERMISSION_LEVEL = Permission.CORP_USER 227 PERMISSION_LEVEL = Permission.CORP_USER
192 228
193 def HandleGet(self): # pragma: no cover 229 def HandleGet(self): # pragma: no cover
194 return self.HandlePost() 230 return self.HandlePost()
195 231
196 def HandlePost(self): 232 def HandlePost(self):
197 """Sets the manual triage result for the analysis. 233 """Sets the manual triage result for the analysis.
198 234
199 Mark the analysis result as correct/wrong/etc. 235 Mark the analysis result as correct/wrong/etc.
200 TODO: make it possible to set the real culprit CLs. 236 TODO: make it possible to set the real culprit CLs.
201 """ 237 """
202 url = self.request.get('url').strip() 238 url = self.request.get('url').strip()
203 build_info = buildbot.ParseBuildUrl(url) 239 build_info = buildbot.ParseBuildUrl(url)
204 if not build_info: 240 if not build_info:
205 return {'data': {'success': False}} 241 return {'data': {'success': False}}
206 master_name, builder_name, build_number = build_info 242 master_name, builder_name, build_number = build_info
207 243
208 is_correct = self.request.get('correct').lower() == 'true' 244 is_correct = self.request.get('correct').lower() == 'true'
209 # As the permission level is CORP_USER, we could assume the current user 245 # As the permission level is CORP_USER, we could assume the current user
210 # already logged in. 246 # already logged in.
211 user_name = users.get_current_user().email().split('@')[0] 247 user_name = users.get_current_user().email().split('@')[0]
212 success, original_analysis = _UpdateAnalysisResultStatus( 248 success, original_analysis = _UpdateAnalysisResultStatus(
213 master_name, builder_name, build_number, is_correct, user_name) 249 master_name, builder_name, build_number, is_correct, user_name)
250 num_duplicate_analyses = 0
214 if success: 251 if success:
215 _TriageDuplicateResults(original_analysis, is_correct, user_name) 252 num_duplicate_analyses = _TriageAndCountDuplicateResults(
216 return {'data': {'success': success}} 253 original_analysis, is_correct, user_name)
254 return {'data': {'success': success,
255 'num_duplicate_analyses': num_duplicate_analyses}}
OLDNEW
« no previous file with comments | « appengine/findit/handlers/test/triage_analysis_test.py ('k') | appengine/findit/model/test/wf_analysis_test.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698