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

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: address comments 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
« no previous file with comments | « no previous file | appengine/findit/handlers/test/build_failure_test.py » ('j') | 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 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'
26 NON_SWARMING = object()
20 27
21 28
22 def _FormatDatetime(dt): 29 def _FormatDatetime(dt):
23 if not dt: 30 if not dt:
24 return None 31 return None
25 else: 32 else:
26 return dt.strftime('%Y-%m-%d %H:%M:%S UTC') 33 return dt.strftime('%Y-%m-%d %H:%M:%S UTC')
27 34
28 35
29 def _GetTriageHistory(analysis): 36 def _GetTriageHistory(analysis):
30 if (not users.is_current_user_admin() or 37 if (not users.is_current_user_admin() or
31 not analysis.completed or 38 not analysis.completed or
32 not analysis.triage_history): 39 not analysis.triage_history):
33 return None 40 return None
34 41
35 triage_history = [] 42 triage_history = []
36 for triage_record in analysis.triage_history: 43 for triage_record in analysis.triage_history:
37 triage_history.append({ 44 triage_history.append({
38 'triage_time': _FormatDatetime( 45 'triage_time': _FormatDatetime(
39 datetime.utcfromtimestamp(triage_record['triage_timestamp'])), 46 datetime.utcfromtimestamp(triage_record['triage_timestamp'])),
40 'user_name': triage_record['user_name'], 47 'user_name': triage_record['user_name'],
41 'result_status': RESULT_STATUS_TO_DESCRIPTION.get( 48 'result_status': RESULT_STATUS_TO_DESCRIPTION.get(
42 triage_record['result_status']), 49 triage_record['result_status']),
43 'version': triage_record.get('version'), 50 'version': triage_record.get('version'),
44 }) 51 })
45 52
46 return triage_history 53 return triage_history
47 54
48 55
56 def _GetOrganizedAnalysisResultBySuspectedCL(analysis_result):
57 """Group tests it they have the same suspected CLs."""
58 organized_results = defaultdict(list)
59
60 if not analysis_result:
61 return organized_results
62
63 for step_failure in analysis_result.get('failures', []):
64 step_name = step_failure['step_name']
65 supported = step_failure.get('supported', True)
66 step_revisions_index = {}
67 organized_suspected_cls = organized_results[step_name]
68
69 if not step_failure.get('tests'):
70 # Non swarming, just group the whole step together.
71 shared_result = {
72 'first_failure': step_failure['first_failure'],
73 'last_pass': step_failure.get('last_pass'),
74 'supported': supported,
75 'tests': [],
76 'suspected_cls': step_failure['suspected_cls']
77 }
78 organized_suspected_cls.append(shared_result)
79 continue
80
81 # Swarming tests.
82 for index, cl in enumerate(step_failure['suspected_cls']):
83 step_revisions_index[cl['revision']] = index
84
85 # Groups tests by suspected CLs' revision.
86 # Keys are the indices of each test in the test list.
87 # Format is as below:
88 # {
89 # 1: {
90 # 'tests': ['test1', 'test2'],
91 # 'revisions': ['rev1'],
92 # 'suspected_cls': [
93 # # suspected cl info for rev1 at step level.
94 # ]
95 # },
96 # 3: {
97 # 'tests': ['test3'],
98 # 'revisions': ['rev3', 'rev2'],
99 # 'suspected_cls': [
100 # # suspected cl info for rev2, rev3 at step level.
101 # ]
102 # }
103 # }
104 tests_group = defaultdict(list)
105 for index, test in enumerate(step_failure['tests']):
106 # Get all revisions for this test and check if there is
107 # any other test has the same culprit(represented by revision) set.
108 test_name = test['test_name']
109 found_group = False
110 revisions = set()
111 for cl in test['suspected_cls']:
112 revisions.add(cl['revision'])
113 for group in tests_group.values():
114 # Found tests that have the same culprit(represented by revision),
115 # add current test to group.
116 if revisions == set(group['revisions']):
117 group['tests'].append(test_name)
118 found_group = True
119 break
120 if not found_group:
121 # First test with that revision set, add a new group.
122 group_suspected_cls = []
123 for revision in revisions:
124 group_suspected_cls.append(
125 step_failure['suspected_cls'][step_revisions_index[revision]])
126 tests_group[index] = {
127 'tests': [test_name],
128 'revisions': list(revisions),
129 'suspected_cls': group_suspected_cls
130 }
131
132 for index, group in tests_group.iteritems():
133 # Reorganize heuristic results by culprits.
134 test_result = step_failure['tests'][index]
135 shared_result = {
136 'first_failure': test_result['first_failure'],
137 'last_pass': test_result.get('last_pass'),
138 'supported': supported,
139 'tests': group['tests'],
140 'suspected_cls': group['suspected_cls']
141 }
142 organized_suspected_cls.append(shared_result)
143
144 return organized_results
145
146
147 def _GetAnalysisResultWithTryJobInfo(
148 organized_results, master_name, builder_name, build_number):
149 """Reorganizes analysis result and try job result by step_name and culprit.
150
151 Returns:
152 update_result (dict): A dict of classified results.
153
154 The format for those dicts are as below:
155 {
156 # A dict of results that contains both
157 # heuristic analysis results and try job results.
158 'reliable_failures': {
159 'step1': {
160 'results': [
161 {
162 'try_job':{
163 'ref_name': 'step1',
164 'try_job_key': 'm/b/119',
165 'status': wf_analysis_status.ANALYZED,
166 'try_job_url': 'url/121',
167 'try_job_build_number': 121,
168 },
169 'heuristic_analysis': {
170 'suspected_cls': [
171 {
172 'build_number': 98,
173 'repo_name': 'chromium',
174 'revision': 'r98_1',
175 'commit_position': None,
176 'url': None,
177 'score': 5,
178 'hints': {
179 'added f98.cc (and it was in log)': 5,
180 },
181 }
182 ]
183 }
184 'tests': ['test1', 'test2'],
185 'first_failure': 98,
186 'last_pass': 97,
187 'supported': True
188 }
189 ]
190 }
191 },
192 # A dict of result for flaky tests.
193 'flaky_failures': {...},
194 # A dict of results for all the other conditions,
195 # such as non-swarming tests or swarming rerun failed.
196 'unclassified_failures': {...}
197 }
198 """
199 updated_results = defaultdict(lambda: defaultdict(lambda: defaultdict(list)))
200
201 if not organized_results:
202 return updated_results
203
204 try_job_info = handlers_util.GetAllTryJobResults(
205 master_name, builder_name, build_number)
206 if not try_job_info:
207 return updated_results
208
209 for step_name, try_jobs in try_job_info.iteritems():
210 try_jobs = try_jobs['try_jobs']
211 step_heuristic_results = organized_results[step_name]
212 step_updated_results = updated_results[step_name]['results']
213
214 # Finds out try job result index and heuristic result index for each test.
215 test_result_map = defaultdict(lambda: defaultdict(int))
216
217 for index, try_job in enumerate(try_jobs):
218 if not try_job.get('tests'): # Compile or non-swarming.
219 test_result_map[NON_SWARMING]['try_job_index'] = index
220 continue
221 for test_name in try_job['tests']:
222 test_result_map[test_name]['try_job_index'] = index
223
224 for index, heuristic_result in enumerate(step_heuristic_results):
225 if not heuristic_result.get('tests'): # Compile or non-swarming.
226 test_result_map[NON_SWARMING]['heuristic_index'] = index
227 continue
228 for test_name in heuristic_result['tests']:
229 test_result_map[test_name]['heuristic_index'] = index
230
231 # Group tests based on indices.
232 indices_test_map = defaultdict(list)
233 for test_name, indices in test_result_map.iteritems():
234 indices_test_map[
235 (indices['try_job_index'], indices['heuristic_index'])].append(
236 test_name)
237
238 for (try_job_index, heuristic_index), tests in indices_test_map.iteritems():
239 try_job_result = try_jobs[try_job_index]
240 heuristic_result = step_heuristic_results[heuristic_index]
241
242 final_result = {
243 'try_job': try_job_result,
244 'heuristic_analysis': {
245 'suspected_cls': heuristic_result['suspected_cls']
246 },
247 'tests': tests if tests != [NON_SWARMING] else [],
248 'first_failure': heuristic_result['first_failure'],
249 'last_pass': heuristic_result['last_pass'],
250 'supported': heuristic_result['supported']
251 }
252
253 if try_job_result['status'] == result_status.FLAKY:
254 step_updated_results['flaky_failures'].append(final_result)
255 elif try_job_result['status'] in NO_TRY_JOB_REASON_MAP.values():
256 # There is no try job info but only heuristic result.
257 step_updated_results['unclassified_failures'].append(final_result)
258 else:
259 step_updated_results['reliable_failures'].append(final_result)
260
261 return updated_results
262
263
49 class BuildFailure(BaseHandler): 264 class BuildFailure(BaseHandler):
50 PERMISSION_LEVEL = Permission.ANYONE 265 PERMISSION_LEVEL = Permission.ANYONE
51 266
52 def _ShowDebugInfo(self): 267 def _ShowDebugInfo(self):
53 # Show debug info only if the app is run locally during development, if the 268 # 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 269 # currently logged-in user is an admin, or if it is explicitly requested
55 # with parameter 'debug=1'. 270 # with parameter 'debug=1'.
56 return (os.environ['SERVER_SOFTWARE'].startswith('Development') or 271 return (os.environ['SERVER_SOFTWARE'].startswith('Development') or
57 users.is_current_user_admin() or self.request.get('debug') == '1') 272 users.is_current_user_admin() or self.request.get('debug') == '1')
58 273
(...skipping 29 matching lines...) Expand all
88 # Only allow admin to force a re-run and set the build_completed. 303 # Only allow admin to force a re-run and set the build_completed.
89 force = (users.is_current_user_admin() and 304 force = (users.is_current_user_admin() and
90 self.request.get('force') == '1') 305 self.request.get('force') == '1')
91 build_completed = (users.is_current_user_admin() and 306 build_completed = (users.is_current_user_admin() and
92 self.request.get('build_completed') == '1') 307 self.request.get('build_completed') == '1')
93 analysis = build_failure_analysis_pipelines.ScheduleAnalysisIfNeeded( 308 analysis = build_failure_analysis_pipelines.ScheduleAnalysisIfNeeded(
94 master_name, builder_name, build_number, 309 master_name, builder_name, build_number,
95 build_completed=build_completed, force=force, 310 build_completed=build_completed, force=force,
96 queue_name=BUILD_FAILURE_ANALYSIS_TASKQUEUE) 311 queue_name=BUILD_FAILURE_ANALYSIS_TASKQUEUE)
97 312
313 organized_results = _GetOrganizedAnalysisResultBySuspectedCL(
314 analysis.result)
315 analysis_result = _GetAnalysisResultWithTryJobInfo(
316 organized_results, *build_info)
317
98 data = { 318 data = {
99 'master_name': analysis.master_name, 319 'master_name': analysis.master_name,
100 'builder_name': analysis.builder_name, 320 'builder_name': analysis.builder_name,
101 'build_number': analysis.build_number, 321 'build_number': analysis.build_number,
102 'pipeline_status_path': analysis.pipeline_status_path, 322 'pipeline_status_path': analysis.pipeline_status_path,
103 'show_debug_info': self._ShowDebugInfo(), 323 'show_debug_info': self._ShowDebugInfo(),
104 'analysis_request_time': _FormatDatetime(analysis.request_time), 324 'analysis_request_time': _FormatDatetime(analysis.request_time),
105 'analysis_start_time': _FormatDatetime(analysis.start_time), 325 'analysis_start_time': _FormatDatetime(analysis.start_time),
106 'analysis_end_time': _FormatDatetime(analysis.end_time), 326 'analysis_end_time': _FormatDatetime(analysis.end_time),
107 'analysis_duration': analysis.duration, 327 'analysis_duration': analysis.duration,
108 'analysis_update_time': _FormatDatetime(analysis.updated_time), 328 'analysis_update_time': _FormatDatetime(analysis.updated_time),
109 'analysis_completed': analysis.completed, 329 'analysis_completed': analysis.completed,
110 'analysis_failed': analysis.failed, 330 'analysis_failed': analysis.failed,
111 'analysis_result': analysis.result, 331 'analysis_result': analysis_result,
112 'analysis_correct': analysis.correct, 332 'analysis_correct': analysis.correct,
113 'triage_history': _GetTriageHistory(analysis), 333 'triage_history': _GetTriageHistory(analysis),
114 'show_triage_help_button': self._ShowTriageHelpButton(), 334 'show_triage_help_button': self._ShowTriageHelpButton(),
335 'status_message_map': result_status.STATUS_MESSAGE_MAP
115 } 336 }
116 337
117 return {'template': 'build_failure.html', 'data': data} 338 return {'template': 'build_failure.html', 'data': data}
118 339
119 def HandlePost(self): # pragma: no cover 340 def HandlePost(self): # pragma: no cover
120 return self.HandleGet() 341 return self.HandleGet()
OLDNEW
« no previous file with comments | « no previous file | appengine/findit/handlers/test/build_failure_test.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698