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

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: 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
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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, timedelta
chanli 2016/06/02 00:55:31 Nit: Imports should be on separate lines. Please
josiahk 2016/06/07 18:34:41 Done.
13 13
14 from google.appengine.api import users 14 from google.appengine.api import users
15 from google.appengine.ext import ndb 15 from google.appengine.ext import ndb
16 16
17 from common.base_handler import BaseHandler 17 from common.base_handler import BaseHandler
18 from common.base_handler import Permission 18 from common.base_handler import Permission
19 from model.wf_analysis import WfAnalysis 19 from model.wf_analysis import WfAnalysis
20 from model import result_status 20 from model import result_status
21 from waterfall import buildbot 21 from waterfall import buildbot
22 22
23 23 # TODO: Is there a better place to put this constant?
chanli 2016/06/02 00:55:31 I think we can just keep this constant here.
josiahk 2016/06/07 18:34:41 Done.
24 @ndb.transactional 24 # TODO: Change this constant to 1 for production. 7 is helpful to get older
25 # results.
chanli 2016/06/02 00:55:31 Please remove these TODOs before you committing th
josiahk 2016/06/10 18:34:26 Done.
26 MATCHING_ANALYSIS_DAYS_AGO = 7
stgao 2016/06/03 07:09:34 Leaving it here for this CL is fine. But seems bet
josiahk 2016/06/10 18:34:26 Acknowledged.
27
28 def genPotentialCulpritTupleList(analysis):
chanli 2016/06/02 00:55:31 Function name nit: the convention for function na
josiahk 2016/06/07 18:34:41 Done.
29 potentialCulpritTupleList = []
chanli 2016/06/02 00:55:31 Naming nit: variable names should be like 'var_nam
josiahk 2016/06/10 18:34:26 Done.
30
31 # Iterate through the failures, tests, and suspected_cls, appending potential
chanli 2016/06/02 00:55:31 Nit: Iterates
josiahk 2016/06/07 18:34:41 Done.
32 # (step_name, test_name, revision) and (step_name, revision) culprit tuples to
33 # the list
chanli 2016/06/02 00:55:31 Nit: ending .
josiahk 2016/06/07 18:34:41 Done.
34 for failure in analysis.result["failures"]:
lijeffrey 2016/06/06 23:00:59 not sure if this has been mentioned in someone els
josiahk 2016/06/07 18:34:41 Done.
35 if "tests" in failure:
36 for test in failure["tests"]:
37 if "suspected_cls" in test:
38 for suspected_cl in test["suspected_cls"]:
39 potentialCulpritTupleList.append((
40 failure["step_name"],
lijeffrey 2016/06/06 23:00:59 4 spaces if putting arguments to a function call o
josiahk 2016/06/10 18:34:26 Done.
41 test["test_name"],
42 suspected_cl["revision"]))
43 if "suspected_cls" in failure:
chanli 2016/06/02 00:55:30 Shouldn't here be elif?
josiahk 2016/06/07 18:34:41 I don't think so, but maybe you see a case I don't
josiahk 2016/06/10 18:34:26 Fixed with 'else'.
44 for suspected_cl in failure["suspected_cls"]:
45 potentialCulpritTupleList.append((
46 failure["step_name"],
47 suspected_cl["revision"]))
48
49 return potentialCulpritTupleList
50
51 def doAnalysesMatch(analysis1, analysis2):
chanli 2016/06/02 00:55:31 argument name nits Please refer to: https://engdo
josiahk 2016/06/07 18:34:41 I have renamed to analysis_1 and analysis_2. Is th
josiahk 2016/06/10 18:34:26 Done.
52 # Both analyses must have result property
53 if not analysis1.result or not analysis2.result:
54 return False
55
56 # Both analyses must have result['failures'] property
57 if not analysis1.result['failures'] or not analysis2.result['failures']:
chanli 2016/06/02 00:55:31 analysis1.result.get('failures') or not analysis2.
josiahk 2016/06/07 18:34:41 Ah, thanks! Also, could I use this instead? if
josiahk 2016/06/10 18:34:26 Relevant code removed.
58 return False
59
60 # Get list of potential culprit tuples
61 potentialCulpritTupleList1 = genPotentialCulpritTupleList(analysis1)
62 potentialCulpritTupleList2 = genPotentialCulpritTupleList(analysis2)
63
64 # Both analyses must have non-empty potential culprit lists
65 if not potentialCulpritTupleList1 or not potentialCulpritTupleList2:
66 return False
67
68 # Both analyses must have matching potential culprit lists
69 if sorted(potentialCulpritTupleList1) != sorted(potentialCulpritTupleList2):
70 return False
71
72 return True
73
74 def doAnalysesMatchTests():
chanli 2016/06/02 00:55:31 If this is a test for you to monitor some middle r
josiahk 2016/06/10 18:34:26 Done.
75 analysis1 = WfAnalysis()
76 analysis2 = WfAnalysis()
77 analysis1760 = WfAnalysis()
78 analysis1761 = WfAnalysis()
79 analysis1762 = WfAnalysis()
80 analysis1.result = {u'failures': [{u'first_failure': 49103, u'step_name':
81 u'content_unittests on Windows-7-SP1', u'last_pass': 49102, u'supported':
82 True, u'suspected_cls': []}, {u'first_failure': 49103, u'step_name':
83 u'content_browsertests on Windows-7-SP1', u'last_pass': 49102, u'supported':
84 True, u'suspected_cls': []}, {u'first_failure': 49103, u'step_name':
85 u'browser_tests on Windows-7-SP1', u'last_pass': 49102, u'supported': True,
86 u'suspected_cls': []}]}
87 analysis2.result = {u'failures': [{u'first_failure': 1684, u'step_name':
88 u'interactive_ui_tests', u'suspected_cls': [{u'repo_name': u'chromium',
89 u'hints': {u'modified site_per_process_interactive_browsertest.cc[540, 464,'+\
90 ' 580, 380] (and it was in log)': 4}, u'commit_position': 395500,
91 u'build_number': 1684, u'revision':
92 u'3cf9343f4602d4ec11717cb6ff56a793c1d5f84b', u'url':
stgao 2016/06/03 07:09:34 Format and move the test to the corresponding unit
josiahk 2016/06/10 18:34:26 Done.
93 u'https://codereview.chromium.org/1914643005', u'score': 4}], u'supported':
94 True, u'last_pass': 1683}]}
95 analysis1760.result = {u'failures': [{u'first_failure': 1739, u'last_pass':
96 None, u'supported': True, u'suspected_cls': [], u'step_name':
97 u'browser_tests'}, {u'first_failure': 1739, u'last_pass': None, u'supported':
98 True, u'suspected_cls': [{u'hints': {u'modified'+\
99 ' dump_accessibility_browsertest_base.cc (and it was in log)': 2},
100 u'build_number': 1753, u'score': 2, u'url':
101 u'https://codereview.chromium.org/2023453003', u'commit_position': 396751,
102 u'revision': u'0e8dc209f5e4a6140e43551de0e036324c68a383', u'repo_name':
103 u'chromium'}], u'step_name': u'content_browsertests'}]}
104 analysis1761.result = {u'failures': [{u'supported': True, u'last_pass': None,
105 u'suspected_cls': [], u'step_name': u'browser_tests', u'first_failure': 1740},
106 {u'supported': True, u'last_pass': None, u'suspected_cls': [{u'hints':
107 {u'modified dump_accessibility_browsertest_base.cc (and it was in log)': 2},
108 u'url': u'https://codereview.chromium.org/2023453003', u'repo_name':
109 u'chromium', u'revision': u'0e8dc209f5e4a6140e43551de0e036324c68a383',
110 u'score': 2, u'commit_position': 396751, u'build_number': 1753}],
111 u'step_name': u'content_browsertests', u'first_failure': 1740}]}
112 analysis1762.result = {u'failures': [{u'suspected_cls': [], u'first_failure':
113 1741, u'step_name': u'browser_tests', u'last_pass': None, u'supported': True},
114 {u'suspected_cls': [{u'url': u'https://codereview.chromium.org/2023453003',
115 u'build_number': 1753, u'revision':
116 u'0e8dc209f5e4a6140e43551de0e036324c68a383', u'commit_position': 396751,
117 u'hints': {u'modified dump_accessibility_browsertest_base.cc (and it was in'+\
118 ' log)': 2}, u'repo_name': u'chromium', u'score': 2}, {u'url':
119 u'https://codereview.chromium.org/2024053003', u'build_number': 1762,
120 u'revision': u'292b41bbd603ae2f11d239f457a8a5f04387fa85', u'commit_position':
121 396830,
122 u'hints': {u'modified dump_accessibility_events_browsertest.cc (and it'+\
123 ' was in log)': 2}, u'repo_name': u'chromium', u'score': 2}, {u'url':
124 u'https://codereview.chromium.org/2025923002', u'build_number': 1762,
125 u'revision': u'f6c9ef029e28a6bef28e727cd70751d782963e21', u'commit_position':
126 396833, u'hints': {u'modified dump_accessibility_events_browsertest.cc[166]'+\
127 ' (and it was in log)': 4}, u'repo_name': u'chromium', u'score': 4}],
128 u'first_failure': 1741, u'step_name': u'content_browsertests', u'last_pass':
129 None, u'supported': True}]}
130 assert not doAnalysesMatch(analysis1, analysis2) # Different analyses
131 assert not doAnalysesMatch(analysis1, analysis1) # Zero culprit-tuples
132 assert doAnalysesMatch(analysis2, analysis2) # Has culprit-tuples
133 assert doAnalysesMatch(analysis1760, analysis1761)
134 assert not doAnalysesMatch(analysis1761, analysis1762)
135
136 # TODO: Put this back (uncomment @ndb.transactional)
137 # @ndb.transactional
chanli 2016/06/02 00:55:31 Just a thought: I didn't do the experiment but it
stgao 2016/06/03 07:09:34 As we are now dealing with multiple NDB entities,
josiahk 2016/06/07 18:34:41 Yay! The concept of your idea works! Except... _Tr
stgao 2016/06/13 23:39:49 That's due to Cross-NDB-group query. I had a comme
25 def _UpdateAnalysisResultStatus( 138 def _UpdateAnalysisResultStatus(
26 master_name, builder_name, build_number, correct, user_name=None): 139 master_name, builder_name, build_number, correct, user_name=None):
140 doAnalysesMatchTests()
chanli 2016/06/02 00:55:31 Move this to ./test/triage_analysis_test.py
josiahk 2016/06/10 18:34:26 Done.
141
27 analysis = WfAnalysis.Get(master_name, builder_name, build_number) 142 analysis = WfAnalysis.Get(master_name, builder_name, build_number)
28 if not analysis or not analysis.completed: 143 if not analysis or not analysis.completed:
29 return False 144 return False
30 145
31 if correct: 146 # TODO: Limit query range to only yesterday and not today and yesterday.
chanli 2016/06/02 00:55:31 To do it, you can also specify end date
josiahk 2016/06/07 18:34:41 Done.
32 if analysis.suspected_cls: 147 start_date = datetime.utcnow() - timedelta(MATCHING_ANALYSIS_DAYS_AGO)
33 analysis.result_status = result_status.FOUND_CORRECT 148 start_date = start_date.replace(hour=0, minute=0, second=0, microsecond=0)
34 analysis.culprit_cls = analysis.suspected_cls 149
150 # TODO: Remove helpful-for-development FOUND_CORRECT and FOUND_INCORRECT
151 # (use commented code instead).
152 analysis_results = WfAnalysis.query(ndb.AND(
153 WfAnalysis.build_start_time >= start_date,
154 ndb.OR(
chanli 2016/06/02 00:55:31 IMO, You should only query for FOUND_UNTRIAGED her
josiahk 2016/06/10 18:34:25 Done.
155 WfAnalysis.result_status == result_status.FOUND_UNTRIAGED,
156 WfAnalysis.result_status == result_status.FOUND_CORRECT,
157 WfAnalysis.result_status == result_status.FOUND_INCORRECT
158 ))).order(
159 -WfAnalysis.build_start_time).fetch()
chanli 2016/06/02 00:55:31 No need to order
josiahk 2016/06/07 18:34:41 Done.
160 # analysis_results = WfAnalysis.query(ndb.AND(
161 # WfAnalysis.build_start_time >= start_date,
162 # WfAnalysis.result_status == result_status.FOUND_UNTRIAGED
163 # )).order(
164 # -WfAnalysis.build_start_time).fetch()
165
166 # TODO: Remove helpful-for-development print statements
167 print
168 print "############## ALL ANALYSES (%d) ##############" % len(
169 analysis_results)
170 for i, a in enumerate(analysis_results):
chanli 2016/06/02 00:55:31 Use descriptive variable names
josiahk 2016/06/10 18:34:26 Relevant code removed.
171 print "======= Analysis #%d, Build number: %d =======:" \
172 % (i, a.build_number)
173 print a
174 print
175
176 # Generate list of matching build analyses
177 matchingAnalyses = [a for a in analysis_results if
178 doAnalysesMatch(a, analysis)]
179
180 # TODO: Detect if current analysis was already in the list of matching
181 # analyses; if so, don't re-add it.
182
183 # Add current analysis to the list of matching analyses
184 matchingAnalyses.append(analysis)
185
186 # TODO: Remove helpful-for-development print statements
187 print
188 print "############## MATCHING ANALYSES (%d) ##############" % len(
189 matchingAnalyses)
190 for i, ma in enumerate(matchingAnalyses):
191 print "======= MatchingAnalysis #%d, Build number: %d =======:" \
192 % (i, ma.build_number)
193 print ma
194 print
195
196 for a in matchingAnalyses:
197 if correct:
198 if a.suspected_cls:
199 a.result_status = result_status.FOUND_CORRECT
200 a.culprit_cls = a.suspected_cls
201 else:
202 a.result_status = result_status.NOT_FOUND_CORRECT
203 a.culprit_cls = None
35 else: 204 else:
36 analysis.result_status = result_status.NOT_FOUND_CORRECT 205 a.culprit_cls = None
37 analysis.culprit_cls = None 206 if a.suspected_cls:
38 else: 207 a.result_status = result_status.FOUND_INCORRECT
39 analysis.culprit_cls = None 208 else:
40 if analysis.suspected_cls: 209 a.result_status = result_status.NOT_FOUND_INCORRECT
41 analysis.result_status = result_status.FOUND_INCORRECT 210
42 else: 211 triage_record = {
43 analysis.result_status = result_status.NOT_FOUND_INCORRECT 212 'triage_timestamp': calendar.timegm(datetime.utcnow().timetuple()),
44 213 'user_name': user_name,
45 triage_record = { 214 'result_status': a.result_status,
46 'triage_timestamp': calendar.timegm(datetime.utcnow().timetuple()), 215 'version': a.version,
47 'user_name': user_name, 216 }
48 'result_status': analysis.result_status, 217 if not a.triage_history:
49 'version': analysis.version, 218 a.triage_history = []
50 } 219 a.triage_history.append(triage_record)
51 if not analysis.triage_history: 220
52 analysis.triage_history = [] 221 a.put()
53 analysis.triage_history.append(triage_record)
54
55 analysis.put()
56 return True 222 return True
57 223
58 224
59 class TriageAnalysis(BaseHandler): 225 class TriageAnalysis(BaseHandler):
60 PERMISSION_LEVEL = Permission.CORP_USER 226 PERMISSION_LEVEL = Permission.CORP_USER
61 227
62 def HandleGet(self): # pragma: no cover 228 def HandleGet(self): # pragma: no cover
63 return self.HandlePost() 229 return self.HandlePost()
64 230
65 def HandlePost(self): 231 def HandlePost(self):
66 """Sets the manual triage result for the analysis. 232 """Sets the manual triage result for the analysis.
67 233
68 Mark the analysis result as correct/wrong/etc. 234 Mark the analysis result as correct/wrong/etc.
69 TODO: make it possible to set the real culprit CLs. 235 TODO: make it possible to set the real culprit CLs.
70 """ 236 """
71 url = self.request.get('url').strip() 237 url = self.request.get('url').strip()
72 build_info = buildbot.ParseBuildUrl(url) 238 build_info = buildbot.ParseBuildUrl(url)
73 if not build_info: 239 if not build_info:
74 return {'data': {'success': False}} 240 return {'data': {'success': False}}
75 master_name, builder_name, build_number = build_info 241 master_name, builder_name, build_number = build_info
76 242
77 correct = self.request.get('correct').lower() == 'true' 243 correct = self.request.get('correct').lower() == 'true'
78 # As the permission level is CORP_USER, we could assume the current user 244 # As the permission level is CORP_USER, we could assume the current user
79 # already logged in. 245 # already logged in.
80 user_name = users.get_current_user().email().split('@')[0] 246 user_name = users.get_current_user().email().split('@')[0]
81 success = _UpdateAnalysisResultStatus( 247 success = _UpdateAnalysisResultStatus(
82 master_name, builder_name, build_number, correct, user_name) 248 master_name, builder_name, build_number, correct, user_name)
83 return {'data': {'success': success}} 249 return {'data': {'success': success}}
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698