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

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

Issue 1833833002: [Findit] Modify build_failure to prepare for new UI. (Closed) Base URL: https://chromium.googlesource.com/infra/infra.git@0314-result-page
Patch Set: . Created 4 years, 8 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 from collections import defaultdict
6 import copy
5 from datetime import datetime 7 from datetime import datetime
6 import os 8 import os
7 9
8 from google.appengine.api import users 10 from google.appengine.api import users
9 11
10 from base_handler import BaseHandler 12 from base_handler import BaseHandler
11 from base_handler import Permission 13 from base_handler import Permission
14 from handlers import handlers_util
15 from handlers import result_status
16 from handlers.result_status import NO_TRY_JOB_REASON_MAP
17 from model import wf_analysis_status
12 from model.wf_analysis import WfAnalysis 18 from model.wf_analysis import WfAnalysis
13 from model.wf_analysis_result_status import RESULT_STATUS_TO_DESCRIPTION 19 from model.wf_analysis_result_status import RESULT_STATUS_TO_DESCRIPTION
14 from waterfall import build_failure_analysis_pipelines 20 from waterfall import build_failure_analysis_pipelines
15 from waterfall import buildbot 21 from waterfall import buildbot
16 from waterfall import waterfall_config 22 from waterfall import waterfall_config
17 23
18 24
19 BUILD_FAILURE_ANALYSIS_TASKQUEUE = 'build-failure-analysis-queue' 25 BUILD_FAILURE_ANALYSIS_TASKQUEUE = 'build-failure-analysis-queue'
20 26
21 27
(...skipping 17 matching lines...) Expand all
39 datetime.utcfromtimestamp(triage_record['triage_timestamp'])), 45 datetime.utcfromtimestamp(triage_record['triage_timestamp'])),
40 'user_name': triage_record['user_name'], 46 'user_name': triage_record['user_name'],
41 'result_status': RESULT_STATUS_TO_DESCRIPTION.get( 47 'result_status': RESULT_STATUS_TO_DESCRIPTION.get(
42 triage_record['result_status']), 48 triage_record['result_status']),
43 'version': triage_record.get('version'), 49 'version': triage_record.get('version'),
44 }) 50 })
45 51
46 return triage_history 52 return triage_history
47 53
48 54
55 def _GetOrganizedAnalysisResultBySuspectedCL(analysis_result):
56 """Group tests it they have the same suspected CLs."""
57
stgao 2016/03/28 23:29:26 nit: empty line.
chanli 2016/03/29 01:35:56 Done.
58 organized_results = defaultdict(list)
59
60 if not analysis_result:
61 return organized_results
62
63 for failure in analysis_result.get('failures', []):
stgao 2016/03/28 23:29:26 Maybe rename ``failure`` to ``step_failure`` to im
chanli 2016/03/29 01:35:56 Done.
64 step_name = failure['step_name']
65 supported = failure.get('supported', True)
66 step_revisions_index = {}
67 organized_suspected_cls = organized_results[step_name]
68
69 if not failure.get('tests'):
70 # Non swraming, just group the whole step together.
lijeffrey 2016/03/29 00:29:38 nit: swarming
chanli 2016/03/29 01:35:56 Done.
71 shared_result = {
72 'first_failure': failure['first_failure'],
73 'last_pass': failure.get('last_pass'),
74 'supported': supported,
75 'tests': [],
76 'suspected_cls': failure['suspected_cls']
77 }
78 organized_suspected_cls.append(shared_result)
79 continue
80
81 # Swarming tests.
82 for index, cl in enumerate(failure['suspected_cls']):
83 step_revisions_index[cl['revision']] = index
84
85 # Groups tests by suspected CLs' revision.
stgao 2016/03/28 23:29:26 Maybe explain what does the key in the dict mean?
chanli 2016/03/29 01:35:57 Done.
86 # Format is as below:
87 # {
88 # 1: {
89 # 'tests': ['test1', 'test2'],
90 # 'revisions': ['rev1'],
91 # 'suspected_cls': [
92 # # suspected cl info for rev1 at step level.
93 # ]
94 # },
95 # 3: {
96 # 'tests': ['test3'],
97 # 'revisions': ['rev3', 'rev2'],
98 # 'suspected_cls': [
99 # # suspected cl info for rev2, rev3 at step level.
100 # ]
101 # }
102 # }
103 tests_group = defaultdict(list)
104 for index, test in enumerate(failure['tests']):
105 # Get all revisions for this test and check if there is
106 # any other test has the same revision set.
107 test_name = test['test_name']
108 found_group = False
109 revisions = set()
110 for cl in test['suspected_cls']:
111 revisions.add(cl['revision'])
112 for group in tests_group.values():
113 # Found tests that have the same revision, add current test to group.
stgao 2016/03/28 23:29:26 "same revision" -> "same culprits"? Possibly updat
chanli 2016/03/29 01:35:56 Done.
114 if revisions == set(group['revisions']):
115 group['tests'].append(test_name)
116 found_group = True
117 break
118 if not found_group:
119 # First test with that revision set, add a new group.
120 group_suspected_cls = []
121 for revision in revisions:
122 group_suspected_cls.append(
123 failure['suspected_cls'][step_revisions_index[revision]])
124 tests_group[index] = {
125 'tests': [test_name],
126 'revisions': list(revisions),
127 'suspected_cls': group_suspected_cls
128 }
129
130 for index, group in tests_group.iteritems():
stgao 2016/03/28 23:29:26 How about a comment for what this logic block is f
chanli 2016/03/29 01:35:56 Done.
131 test_result = failure['tests'][index]
132 shared_result = {
133 'first_failure': test_result['first_failure'],
134 'last_pass': test_result.get('last_pass'),
135 'supported': supported,
136 'tests': group['tests'],
137 'suspected_cls': group['suspected_cls']
138 }
139 organized_suspected_cls.append(shared_result)
140
141 return organized_results
142
143
144 def _GetAnalysisResultWithTryJobInfo(
145 organized_results, master_name, builder_name, build_number):
146 """Reorganizes analysis result and try job result by step_name and culprit.
147
148 Returns:
149 update_result (dict): A dict of classified results.
150
151 The format for those dicts are as below:
152 {
153 # A dict of results that contains both
154 # heuristic analysis results and try job results.
155 'determined_results': {
156 'step1': {
157 'results': [
158 {
159 'try_job':{
160 'ref_name': 'step1',
161 'try_job_key': 'm/b/119',
162 'status': wf_analysis_status.ANALYZED,
163 'try_job_url': 'url/121',
164 'try_job_build_number': 121,
165 },
166 'heuristic_analysis': {
167 'suspected_cls': [
168 {
169 'build_number': 98,
170 'repo_name': 'chromium',
171 'revision': 'r98_1',
172 'commit_position': None,
173 'url': None,
174 'score': 5,
175 'hints': {
176 'added f98.cc (and it was in log)': 5,
177 },
178 }
179 ]
180 }
181 'tests': ['test1', 'test2'],
182 'first_failure': 98,
183 'last_pass': 97,
184 'supported': True
185 }
186 ]
187 }
188 },
189 # A dict of result for flaky tests.
190 'flaky_results': {...},
191 # A dict of results for all the other conditions,
192 # such as non-swarming tests or swarming rerun failed.
193 'undetermined_results': {...}
194 }
195 """
196 update_results = defaultdict(lambda: defaultdict(lambda: defaultdict(list)))
197
198 if not organized_results:
199 return update_results
200
201 try_job_info = handlers_util.GetAllTryJobResults(
202 master_name, builder_name, build_number)
203 if not try_job_info:
204 return update_results
205
206 for step_name, try_jobs in try_job_info.iteritems():
207 try_jobs = try_jobs['try_jobs']
208 step_heuristic_results = organized_results[step_name]
209 step_updated_results = update_results[step_name]['results']
stgao 2016/03/28 23:29:26 To be consistent, should we rename ``update_result
lijeffrey 2016/03/29 00:29:38 +1
chanli 2016/03/29 01:35:56 Done.
210
211 # Finds out try job result index and heuristic result index for each test.
212 test_result_map = defaultdict(lambda: defaultdict(int))
213
214 for index, try_job in enumerate(try_jobs):
215 if not try_job.get('tests'): # Compile or non-swarming.
216 test_result_map['non-swarming']['try_job_index'] = index
217 continue
218 for test_name in try_job['tests']:
219 test_result_map[test_name]['try_job_index'] = index
220 for index, heuristic_result in enumerate(step_heuristic_results):
lijeffrey 2016/03/29 00:29:38 nit: 1 blank line before this for
chanli 2016/03/29 01:35:57 Done.
221 if not heuristic_result.get('tests'): # Compile or non-swarming.
222 test_result_map['non-swarming']['heuristic_index'] = index
223 continue
224 for test_name in heuristic_result['tests']:
225 test_result_map[test_name]['heuristic_index'] = index
226
227 # Group tests based on indices.
228 indices_test_map = defaultdict(list)
229 for test_name, indices in test_result_map.iteritems():
230 indices_test_map[
231 (indices['try_job_index'], indices['heuristic_index'])].append(
stgao 2016/03/28 23:29:26 What if the test has result only from heuristic or
chanli 2016/03/29 01:35:57 There should still be entries for those tests, jus
232 test_name)
233
234 for indices, tests in indices_test_map.iteritems():
stgao 2016/03/28 23:29:26 For readability, how about below if it works? for
chanli 2016/03/29 01:35:56 Done.
235 try_job_result = try_jobs[indices[0]]
236 heuristic_result = step_heuristic_results[indices[1]]
237
238 final_result = {
239 'try_job': try_job_result,
240 'heuristic_analysis': {
241 'suspected_cls': heuristic_result['suspected_cls']
242 },
243 'tests': tests if tests != ['non-swarming'] else [],
stgao 2016/03/28 23:29:26 Maybe make 'non-swarming' a singleton python objec
chanli 2016/03/29 01:35:56 Done.
244 'first_failure': heuristic_result['first_failure'],
245 'last_pass': heuristic_result['last_pass'],
246 'supported': heuristic_result['supported']
247 }
248
249 if try_job_result['status'] == result_status.FLAKY:
250 step_updated_results['flaky_results'].append(final_result)
251 elif try_job_result['status'] in NO_TRY_JOB_REASON_MAP.values():
252 # There is no try job info but only heuristic result.
253 step_updated_results['undetermined_results'].append(final_result)
254 else:
255 step_updated_results['determined_results'].append(final_result)
256
257 return update_results
258
259
49 class BuildFailure(BaseHandler): 260 class BuildFailure(BaseHandler):
50 PERMISSION_LEVEL = Permission.ANYONE 261 PERMISSION_LEVEL = Permission.ANYONE
51 262
52 def _ShowDebugInfo(self): 263 def _ShowDebugInfo(self):
53 # Show debug info only if the app is run locally during development, if the 264 # Show debug info only if the app is run locally during development, if the
54 # currently logged-in user is an admin, or if it is explicitly requested 265 # currently logged-in user is an admin, or if it is explicitly requested
55 # with parameter 'debug=1'. 266 # with parameter 'debug=1'.
56 return (os.environ['SERVER_SOFTWARE'].startswith('Development') or 267 return (os.environ['SERVER_SOFTWARE'].startswith('Development') or
57 users.is_current_user_admin() or self.request.get('debug') == '1') 268 users.is_current_user_admin() or self.request.get('debug') == '1')
58 269
(...skipping 29 matching lines...) Expand all
88 # Only allow admin to force a re-run and set the build_completed. 299 # Only allow admin to force a re-run and set the build_completed.
89 force = (users.is_current_user_admin() and 300 force = (users.is_current_user_admin() and
90 self.request.get('force') == '1') 301 self.request.get('force') == '1')
91 build_completed = (users.is_current_user_admin() and 302 build_completed = (users.is_current_user_admin() and
92 self.request.get('build_completed') == '1') 303 self.request.get('build_completed') == '1')
93 analysis = build_failure_analysis_pipelines.ScheduleAnalysisIfNeeded( 304 analysis = build_failure_analysis_pipelines.ScheduleAnalysisIfNeeded(
94 master_name, builder_name, build_number, 305 master_name, builder_name, build_number,
95 build_completed=build_completed, force=force, 306 build_completed=build_completed, force=force,
96 queue_name=BUILD_FAILURE_ANALYSIS_TASKQUEUE) 307 queue_name=BUILD_FAILURE_ANALYSIS_TASKQUEUE)
97 308
309 organized_results = _GetOrganizedAnalysisResultBySuspectedCL(
310 analysis.result)
311 analysis_result = _GetAnalysisResultWithTryJobInfo(
312 organized_results, *build_info)
313
98 data = { 314 data = {
99 'master_name': analysis.master_name, 315 'master_name': analysis.master_name,
100 'builder_name': analysis.builder_name, 316 'builder_name': analysis.builder_name,
101 'build_number': analysis.build_number, 317 'build_number': analysis.build_number,
102 'pipeline_status_path': analysis.pipeline_status_path, 318 'pipeline_status_path': analysis.pipeline_status_path,
103 'show_debug_info': self._ShowDebugInfo(), 319 'show_debug_info': self._ShowDebugInfo(),
104 'analysis_request_time': _FormatDatetime(analysis.request_time), 320 'analysis_request_time': _FormatDatetime(analysis.request_time),
105 'analysis_start_time': _FormatDatetime(analysis.start_time), 321 'analysis_start_time': _FormatDatetime(analysis.start_time),
106 'analysis_end_time': _FormatDatetime(analysis.end_time), 322 'analysis_end_time': _FormatDatetime(analysis.end_time),
107 'analysis_duration': analysis.duration, 323 'analysis_duration': analysis.duration,
108 'analysis_update_time': _FormatDatetime(analysis.updated_time), 324 'analysis_update_time': _FormatDatetime(analysis.updated_time),
109 'analysis_completed': analysis.completed, 325 'analysis_completed': analysis.completed,
110 'analysis_failed': analysis.failed, 326 'analysis_failed': analysis.failed,
111 'analysis_result': analysis.result, 327 'analysis_result': analysis_result,
112 'analysis_correct': analysis.correct, 328 'analysis_correct': analysis.correct,
113 'triage_history': _GetTriageHistory(analysis), 329 'triage_history': _GetTriageHistory(analysis),
114 'show_triage_help_button': self._ShowTriageHelpButton(), 330 'show_triage_help_button': self._ShowTriageHelpButton(),
331 'status_message_map': result_status.ToStatusMessage()
115 } 332 }
116 333
117 return {'template': 'build_failure.html', 'data': data} 334 return {'template': 'build_failure.html', 'data': data}
118 335
119 def HandlePost(self): # pragma: no cover 336 def HandlePost(self): # pragma: no cover
120 return self.HandleGet() 337 return self.HandleGet()
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698