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

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

Issue 2029873002: [Findit] Cross-platform triage (Closed) Base URL: https://chromium.googlesource.com/infra/infra.git@master
Patch Set: Naming, periods, and whitespace. Created 4 years, 6 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
11 import calendar 11 import calendar
12 from datetime import datetime 12 from datetime import datetime
13 from datetime import timedelta
13 14
14 from google.appengine.api import users 15 from google.appengine.api import users
15 from google.appengine.ext import ndb 16 from google.appengine.ext import ndb
17 import pytz.gae
16 18
17 from common.base_handler import BaseHandler 19 from common.base_handler import BaseHandler
18 from common.base_handler import Permission 20 from common.base_handler import Permission
21 from model import result_status
19 from model.wf_analysis import WfAnalysis 22 from model.wf_analysis import WfAnalysis
20 from model import result_status
21 from waterfall import buildbot 23 from waterfall import buildbot
22 24
23 25
24 @ndb.transactional 26 MATCHING_ANALYSIS_HOURS_AGO_START = 24
25 def _UpdateAnalysisResultStatus( 27 MATCHING_ANALYSIS_HOURS_AGO_END = 24
26 master_name, builder_name, build_number, correct, user_name=None): 28 MATCHING_ANALYSIS_END_BOUND_TIME_ZONE = 'US/Pacific'
27 analysis = WfAnalysis.Get(master_name, builder_name, build_number) 29
28 if not analysis or not analysis.completed: 30
31 def _GenPotentialCulpritTupleList(analysis):
32 """Generates a list of potential culprit tuples.
33
34 Args:
35 analysis: the analysis from which to generate a potenial culript tuple list.
36
37 Returns:
38 A list of cultprit tuples that each could look like:
39
40 (step_name, test_name, revision)
41
42 or could look like:
43
44 (step_name, revision)
45 """
46 potential_culprit_tuple_list = []
47
48 # Iterates through the failures, tests, and suspected_cls, appending potential
49 # (step_name, test_name, revision) and (step_name, revision) culprit tuples to
50 # the list.
51 for failure in analysis.result['failures']:
52 if failure.get('tests'):
53 for test in failure['tests']:
54 for suspected_cl in test.get('suspected_cls', []):
55 potential_culprit_tuple_list.append((
56 failure['step_name'],
57 test['test_name'],
58 suspected_cl['revision']))
59 else:
60 for suspected_cl in failure['suspected_cls']:
61 potential_culprit_tuple_list.append((
62 failure['step_name'],
63 suspected_cl['revision']))
64
65 return potential_culprit_tuple_list
66
67
68 def _DoAnalysesMatch(analysis_1, analysis_2):
69 """Checks if two analyses match.
70
71 Args:
72 analysis_1: The first analysis to compare.
73 analysis_2: The second analysis to compare.
74
75 Returns:
76 True if the two analyses' sorted potential culprit lists match, otherwise
77 False.
78 """
79
80 # Get list of potential culprit tuples.
81 potential_culprit_tuple_list_1 = _GenPotentialCulpritTupleList(analysis_1)
82 potential_culprit_tuple_list_2 = _GenPotentialCulpritTupleList(analysis_2)
83
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:
29 return False 86 return False
30 87
31 if correct: 88 # Both analyses must have matching potential culprit lists.
89 return (sorted(potential_culprit_tuple_list_1) ==
90 sorted(potential_culprit_tuple_list_2))
91
92
93 def _AppendTriageHistoryRecord(analysis, is_correct, user_name):
94 """Appends a triage history record to the given analysis.
95
96 Args:
97 analysis: The analysis to which to append the history record.
98 is_correct: True if the history record should indicate a correct judgement,
99 otherwise False.
100 user_name: The user_name of the person to include in the triage record.
101 """
102 if is_correct:
32 if analysis.suspected_cls: 103 if analysis.suspected_cls:
33 analysis.result_status = result_status.FOUND_CORRECT 104 analysis.result_status = result_status.FOUND_CORRECT
34 analysis.culprit_cls = analysis.suspected_cls 105 analysis.culprit_cls = analysis.suspected_cls
35 else: 106 else:
36 analysis.result_status = result_status.NOT_FOUND_CORRECT 107 analysis.result_status = result_status.NOT_FOUND_CORRECT
37 analysis.culprit_cls = None 108 analysis.culprit_cls = None
38 else: 109 else:
39 analysis.culprit_cls = None 110 analysis.culprit_cls = None
40 if analysis.suspected_cls: 111 if analysis.suspected_cls:
41 analysis.result_status = result_status.FOUND_INCORRECT 112 analysis.result_status = result_status.FOUND_INCORRECT
42 else: 113 else:
43 analysis.result_status = result_status.NOT_FOUND_INCORRECT 114 analysis.result_status = result_status.NOT_FOUND_INCORRECT
44 115
45 triage_record = { 116 triage_record = {
46 'triage_timestamp': calendar.timegm(datetime.utcnow().timetuple()), 117 'triage_timestamp': calendar.timegm(datetime.utcnow().timetuple()),
47 'user_name': user_name, 118 'user_name': user_name,
48 'result_status': analysis.result_status, 119 'result_status': analysis.result_status,
49 'version': analysis.version, 120 'version': analysis.version,
50 } 121 }
51 if not analysis.triage_history: 122 if not analysis.triage_history:
52 analysis.triage_history = [] 123 analysis.triage_history = []
53 analysis.triage_history.append(triage_record) 124 analysis.triage_history.append(triage_record)
54 125
55 analysis.put() 126 analysis.put()
56 return True 127
128
129 @ndb.transactional
130 def _UpdateAnalysisResultStatus(
131 master_name, builder_name, build_number, is_correct, user_name=None):
132 analysis = WfAnalysis.Get(master_name, builder_name, build_number)
133 if not analysis or not analysis.completed:
134 return False, None
135
136 _AppendTriageHistoryRecord(analysis, is_correct, user_name)
137
138 return True, analysis
139
140
141 def _GetDuplicateAnalyses(original_analysis):
142 start_time = (original_analysis.build_start_time -
143 timedelta(hours=MATCHING_ANALYSIS_HOURS_AGO_START))
144 end_time = (original_analysis.build_start_time +
145 timedelta(hours=MATCHING_ANALYSIS_HOURS_AGO_END))
146
147 # Don't count any analyses from today (except for exactly at midnight local
148 # time).
149 # Get current time (UTC).
150 current_time_as_utc = pytz.utc.localize(datetime.utcnow())
151
152 # Convert to local time.
153 current_time_as_local = current_time_as_utc.astimezone(
154 pytz.timezone(MATCHING_ANALYSIS_END_BOUND_TIME_ZONE))
155
156 # Set hours and minutes to 0 to get midnight.
157 local_midnight_as_local = current_time_as_local.replace(
158 hour=0, minute=0, second=0, microsecond=0)
159
160 # Convert back to UTC time.
161 local_midnight_as_utc = local_midnight_as_local.astimezone(pytz.utc)
162
163 # Strip timezone.
164 local_midnight = local_midnight_as_utc.replace(tzinfo=None)
165
166 if end_time > local_midnight:
167 end_time = local_midnight
168
169 # Retrieve potential duplicate build analyses.
170 analysis_results = WfAnalysis.query(ndb.AND(
171 WfAnalysis.build_start_time >= start_time,
172 WfAnalysis.build_start_time <= end_time,
173 WfAnalysis.result_status == result_status.FOUND_UNTRIAGED
174 )).fetch()
175
176 # Further filter potential duplicates and return them.
177 return [analysis for analysis in analysis_results if
178 _DoAnalysesMatch(original_analysis, analysis) and
179 original_analysis.key is not analysis.key and
180 analysis.completed]
181
182
183 def _TriageDuplicateResults(original_analysis, is_correct, user_name=None):
184 matching_analyses = _GetDuplicateAnalyses(original_analysis)
185
186 for analysis in matching_analyses:
187 _AppendTriageHistoryRecord(analysis, is_correct, user_name)
57 188
58 189
59 class TriageAnalysis(BaseHandler): 190 class TriageAnalysis(BaseHandler):
60 PERMISSION_LEVEL = Permission.CORP_USER 191 PERMISSION_LEVEL = Permission.CORP_USER
61 192
62 def HandleGet(self): # pragma: no cover 193 def HandleGet(self): # pragma: no cover
63 return self.HandlePost() 194 return self.HandlePost()
64 195
65 def HandlePost(self): 196 def HandlePost(self):
66 """Sets the manual triage result for the analysis. 197 """Sets the manual triage result for the analysis.
67 198
68 Mark the analysis result as correct/wrong/etc. 199 Mark the analysis result as correct/wrong/etc.
69 TODO: make it possible to set the real culprit CLs. 200 TODO: make it possible to set the real culprit CLs.
70 """ 201 """
71 url = self.request.get('url').strip() 202 url = self.request.get('url').strip()
72 build_info = buildbot.ParseBuildUrl(url) 203 build_info = buildbot.ParseBuildUrl(url)
73 if not build_info: 204 if not build_info:
74 return {'data': {'success': False}} 205 return {'data': {'success': False}}
75 master_name, builder_name, build_number = build_info 206 master_name, builder_name, build_number = build_info
76 207
77 correct = self.request.get('correct').lower() == 'true' 208 is_correct = self.request.get('correct').lower() == 'true'
78 # As the permission level is CORP_USER, we could assume the current user 209 # As the permission level is CORP_USER, we could assume the current user
79 # already logged in. 210 # already logged in.
80 user_name = users.get_current_user().email().split('@')[0] 211 user_name = users.get_current_user().email().split('@')[0]
81 success = _UpdateAnalysisResultStatus( 212 success, original_analysis = _UpdateAnalysisResultStatus(
82 master_name, builder_name, build_number, correct, user_name) 213 master_name, builder_name, build_number, is_correct, user_name)
214 if success:
215 _TriageDuplicateResults(original_analysis, is_correct, user_name)
83 return {'data': {'success': success}} 216 return {'data': {'success': success}}
OLDNEW
« no previous file with comments | « appengine/findit/handlers/test/triage_analysis_test.py ('k') | appengine/findit/third_party/gae-pytz » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698