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

Side by Side Diff: appengine/findit/waterfall/identify_try_job_culprit_pipeline.py

Issue 2230103002: [Findit] Pipeline change to save suspected cls to data store. (Closed) Base URL: https://chromium.googlesource.com/infra/infra.git@0808-resubmit-suspected_cl_model
Patch Set: rebase Created 4 years, 2 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 2015 The Chromium Authors. All rights reserved. 1 # Copyright 2015 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 5 from collections import defaultdict
6 import copy
6 import logging 7 import logging
7 8
8 from google.appengine.ext import ndb 9 from google.appengine.ext import ndb
9 10
10 from common.git_repository import GitRepository 11 from common.git_repository import GitRepository
11 from common.http_client_appengine import HttpClientAppengine as HttpClient 12 from common.http_client_appengine import HttpClientAppengine as HttpClient
12 from common.pipeline_wrapper import BasePipeline 13 from common.pipeline_wrapper import BasePipeline
13 from common.waterfall import failure_type 14 from common.waterfall import failure_type
15 from model import analysis_approach_type
14 from model import analysis_status 16 from model import analysis_status
15 from model import result_status 17 from model import result_status
16 from model.wf_analysis import WfAnalysis 18 from model.wf_analysis import WfAnalysis
17 from model.wf_try_job import WfTryJob 19 from model.wf_try_job import WfTryJob
18 from model.wf_try_job_data import WfTryJobData 20 from model.wf_try_job_data import WfTryJobData
19 from waterfall.send_notification_for_culprit_pipeline import ( 21 from waterfall.send_notification_for_culprit_pipeline import (
20 SendNotificationForCulpritPipeline) 22 SendNotificationForCulpritPipeline)
23 from waterfall import suspected_cl_util
21 24
22 25
23 GIT_REPO = GitRepository( 26 GIT_REPO = GitRepository(
24 'https://chromium.googlesource.com/chromium/src.git', HttpClient()) 27 'https://chromium.googlesource.com/chromium/src.git', HttpClient())
25 28
26 29
27 def _GetResultAnalysisStatus(analysis, result): 30 def _GetResultAnalysisStatus(analysis, result):
28 """Returns the analysis status based on existing status and try job result. 31 """Returns the analysis status based on existing status and try job result.
29 32
30 Args: 33 Args:
(...skipping 13 matching lines...) Expand all
44 if (try_job_found_culprit and 47 if (try_job_found_culprit and
45 (old_result_status is None or 48 (old_result_status is None or
46 old_result_status == result_status.NOT_FOUND_UNTRIAGED or 49 old_result_status == result_status.NOT_FOUND_UNTRIAGED or
47 old_result_status == result_status.NOT_FOUND_INCORRECT or 50 old_result_status == result_status.NOT_FOUND_INCORRECT or
48 old_result_status == result_status.NOT_FOUND_CORRECT)): 51 old_result_status == result_status.NOT_FOUND_CORRECT)):
49 return result_status.FOUND_UNTRIAGED 52 return result_status.FOUND_UNTRIAGED
50 53
51 return old_result_status 54 return old_result_status
52 55
53 56
54 def _GetSuspectedCLs(analysis, result): 57 def _GetSuspectedCLs(analysis, try_job_type, result, culprits):
55 """Returns a list of suspected CLs. 58 """Returns a list of suspected CLs.
56 59
57 Args: 60 Args:
58 analysis: The WfAnalysis entity corresponding to this try job. 61 analysis: The WfAnalysis entity corresponding to this try job.
59 result: A result dict containing the culprit from the results of 62 try_job_type: Try job type, COMPILE or TEST, the same with failure type.
60 this try job. 63 result: A result dict containing the result of this try job.
64 culprits: A list of suspected CLs found by the try job.
61 65
62 Returns: 66 Returns:
63 A combined list of suspected CLs from those already in analysis and those 67 A combined list of suspected CLs from those already in analysis and those
64 found by this try job. 68 found by this try job.
65 """ 69 """
66 suspected_cls = analysis.suspected_cls[:] if analysis.suspected_cls else [] 70 suspected_cls = analysis.suspected_cls[:] if analysis.suspected_cls else []
67 suspected_cl_revisions = [cl['revision'] for cl in suspected_cls] 71 suspected_cl_revisions = [cl['revision'] for cl in suspected_cls]
68 culprit = result.get('culprit')
69 compile_cl_info = culprit.get('compile')
70 72
71 if compile_cl_info: 73 for revision, try_job_suspected_cl in culprits.iteritems():
72 # Suspected CL is from compile failure. 74 suspected_cl_copy = copy.deepcopy(try_job_suspected_cl)
73 revision = compile_cl_info.get('revision')
74 if revision not in suspected_cl_revisions: 75 if revision not in suspected_cl_revisions:
75 suspected_cl_revisions.append(revision) 76 suspected_cl_revisions.append(revision)
76 suspected_cls.append(compile_cl_info) 77 if try_job_type == failure_type.COMPILE:
77 return suspected_cls 78 failures = {'compile': []}
78 79 else:
79 # Suspected CLs are from test failures. 80 failures = _GetTestFailureCausedByCL(
80 for results in culprit.itervalues(): 81 result.get('report', {}).get('result', {}).get(revision))
81 if results.get('revision'): 82 suspected_cl_copy['failures'] = failures
82 # Non swarming test failures, only have step level failure info. 83 suspected_cl_copy['top_score'] = None
83 revision = results.get('revision') 84 suspected_cls.append(suspected_cl_copy)
84 cl_info = {
85 'url': results.get('url'),
86 'repo_name': results.get('repo_name'),
87 'revision': results.get('revision'),
88 'commit_position': results.get('commit_position')
89 }
90 if revision not in suspected_cl_revisions:
91 suspected_cl_revisions.append(revision)
92 suspected_cls.append(cl_info)
93 else:
94 for test_cl_info in results['tests'].values():
95 revision = test_cl_info.get('revision')
96 if revision not in suspected_cl_revisions:
97 suspected_cl_revisions.append(revision)
98 suspected_cls.append(test_cl_info)
99 85
100 return suspected_cls 86 return suspected_cls
101 87
102 88
103 def _GetFailedRevisionFromResultsDict(results_dict): 89 def _GetFailedRevisionFromResultsDict(results_dict):
104 """Finds the failed revision from the given dict of revisions. 90 """Finds the failed revision from the given dict of revisions.
105 91
106 Args: 92 Args:
107 results_dict: (dict) A dict that maps revisions to their results. For 93 results_dict: (dict) A dict that maps revisions to their results. For
108 example: 94 example:
(...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after
161 if (not test_result['valid'] or 147 if (not test_result['valid'] or
162 test_result['status'] != 'failed'): # pragma: no cover 148 test_result['status'] != 'failed'): # pragma: no cover
163 continue 149 continue
164 150
165 failed_revisions.add(revision) 151 failed_revisions.add(revision)
166 152
167 if step not in culprit_map: 153 if step not in culprit_map:
168 culprit_map[step] = { 154 culprit_map[step] = {
169 'tests': {} 155 'tests': {}
170 } 156 }
171 if (not test_result['failures'] and
172 not culprit_map[step].get('revision')):
173 # Non swarming test failures, only have step level failure info.
174 culprit_map[step]['revision'] = revision
175 for failed_test in test_result['failures']: 157 for failed_test in test_result['failures']:
176 # Swarming tests, gets first failed revision for each test. 158 # Swarming tests, gets first failed revision for each test.
177 if failed_test not in culprit_map[step]['tests']: 159 if failed_test not in culprit_map[step]['tests']:
178 culprit_map[step]['tests'][failed_test] = { 160 culprit_map[step]['tests'][failed_test] = {
179 'revision': revision 161 'revision': revision
180 } 162 }
181 163
182 return culprit_map, list(failed_revisions) 164 return culprit_map, list(failed_revisions)
183 165
184 166
(...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after
234 send_notification_right_now) 216 send_notification_right_now)
235 elif compile_suspected_cl: 217 elif compile_suspected_cl:
236 # A special case where try job didn't find any suspected cls, but 218 # A special case where try job didn't find any suspected cls, but
237 # heuristic found a suspected_cl. 219 # heuristic found a suspected_cl.
238 _StartSendNotificationPipeline( 220 _StartSendNotificationPipeline(
239 master_name, builder_name, build_number, 221 master_name, builder_name, build_number,
240 compile_suspected_cl['repo_name'], compile_suspected_cl['revision'], 222 compile_suspected_cl['repo_name'], compile_suspected_cl['revision'],
241 send_notification_right_now=True) 223 send_notification_right_now=True)
242 224
243 225
226 def _GetTestFailureCausedByCL(result):
227 if not result:
228 return None
229
230 failures = {}
231 for step_name, step_result in result.iteritems():
232 if step_result['status'] == 'failed':
233 failures[step_name] = step_result['failures']
234
235 return failures
236
237
244 class IdentifyTryJobCulpritPipeline(BasePipeline): 238 class IdentifyTryJobCulpritPipeline(BasePipeline):
245 """A pipeline to identify culprit CL info based on try job compile results.""" 239 """A pipeline to identify culprit CL info based on try job compile results."""
246 240
247 def _GetCulpritInfo(self, failed_revisions): 241 def _GetCulpritInfo(self, failed_revisions):
248 """Gets commit_positions and review urls for revisions.""" 242 """Gets commit_positions and review urls for revisions."""
249 culprits = {} 243 culprits = {}
250 # TODO(lijeffrey): remove hard-coded 'chromium' when DEPS file parsing is 244 # TODO(lijeffrey): remove hard-coded 'chromium' when DEPS file parsing is
251 # supported. 245 # supported.
252 for failed_revision in failed_revisions: 246 for failed_revision in failed_revisions:
253 culprits[failed_revision] = { 247 culprits[failed_revision] = {
254 'revision': failed_revision, 248 'revision': failed_revision,
255 'repo_name': 'chromium' 249 'repo_name': 'chromium'
256 } 250 }
257 change_log = GIT_REPO.GetChangeLog(failed_revision) 251 change_log = GIT_REPO.GetChangeLog(failed_revision)
258 if change_log: 252 if change_log:
259 culprits[failed_revision]['commit_position'] = ( 253 culprits[failed_revision]['commit_position'] = (
260 change_log.commit_position) 254 change_log.commit_position)
261 culprits[failed_revision]['url'] = ( 255 culprits[failed_revision]['url'] = (
262 change_log.code_review_url or change_log.commit_url) 256 change_log.code_review_url or change_log.commit_url)
263 257
264 return culprits 258 return culprits
265 259
266 def _FindCulpritForEachTestFailure(self, blame_list, result): 260 def _FindCulpritForEachTestFailure(self, blame_list, result):
267 # For test failures, we need to traverse the result dict in chronological 261 # For test failures, we need to traverse the result dict in chronological
268 # order to identify the culprits for each failed step or test. 262 # order to identify the culprits for each failed step or test.
269 # The earliest revision that a test failed is the culprit. 263 # The earliest revision that a test failed is the culprit.
270 culprit_map = defaultdict(dict) 264 culprit_map = defaultdict(dict)
271 failed_revisions = set() 265 failed_revisions = set()
272 266
273 # Recipe should return culprits with the farmat as: 267 # Recipe should return culprits with the format as:
274 # 'culprits': { 268 # 'culprits': {
275 # 'step1': { 269 # 'step1': {
276 # 'test1': 'rev1', 270 # 'test1': 'rev1',
277 # 'test2': 'rev2', 271 # 'test2': 'rev2',
278 # ... 272 # ...
279 # }, 273 # },
280 # ... 274 # ...
281 # } 275 # }
282 if result['report'].get('culprits'): 276 if result['report'].get('culprits'):
283 for step_name, tests in result['report']['culprits'].iteritems(): 277 for step_name, tests in result['report']['culprits'].iteritems():
284 culprit_map[step_name]['tests'] = {} 278 culprit_map[step_name]['tests'] = {}
285 for test_name, revision in tests.iteritems(): 279 for test_name, revision in tests.iteritems():
286 culprit_map[step_name]['tests'][test_name] = { 280 culprit_map[step_name]['tests'][test_name] = {
287 'revision': revision 281 'revision': revision
288 } 282 }
289 failed_revisions.add(revision) 283 failed_revisions.add(revision)
290 return culprit_map, list(failed_revisions) 284 return culprit_map, list(failed_revisions)
291 285
292 return _GetCulpritsForTestsFromResultsDict( 286 return _GetCulpritsForTestsFromResultsDict(
293 blame_list, result['report'].get('result')) 287 blame_list, result['report'].get('result'))
294 288
295 def _UpdateCulpritMapWithCulpritInfo(self, culprit_map, culprits): 289 def _UpdateCulpritMapWithCulpritInfo(self, culprit_map, culprits):
296 """Fills in commit_position and review url for each failed rev in map.""" 290 """Fills in commit_position and review url for each failed rev in map."""
297 for step_culprit in culprit_map.values(): 291 for step_culprit in culprit_map.values():
298 if step_culprit.get('revision'):
299 culprit = culprits[step_culprit['revision']]
300 step_culprit['commit_position'] = culprit['commit_position']
301 step_culprit['url'] = culprit['url']
302 step_culprit['repo_name'] = culprit['repo_name']
303 for test_culprit in step_culprit.get('tests', {}).values(): 292 for test_culprit in step_culprit.get('tests', {}).values():
304 test_revision = test_culprit['revision'] 293 test_revision = test_culprit['revision']
305 test_culprit.update(culprits[test_revision]) 294 test_culprit.update(culprits[test_revision])
306 295
307 def _GetCulpritDataForTest(self, culprit_map): 296 def _GetCulpritDataForTest(self, culprit_map):
308 """Gets culprit revision for each failure for try job metadata.""" 297 """Gets culprit revision for each failure for try job metadata."""
309 culprit_data = {} 298 culprit_data = {}
310 for step, step_culprit in culprit_map.iteritems(): 299 for step, step_culprit in culprit_map.iteritems():
311 if step_culprit['tests']: 300 culprit_data[step] = {}
312 culprit_data[step] = {} 301 for test, test_culprit in step_culprit['tests'].iteritems():
313 for test, test_culprit in step_culprit['tests'].iteritems(): 302 culprit_data[step][test] = test_culprit['revision']
314 culprit_data[step][test] = test_culprit['revision']
315 else:
316 culprit_data[step] = step_culprit['revision']
317 return culprit_data 303 return culprit_data
318 304
319 # Arguments number differs from overridden method - pylint: disable=W0221 305 # Arguments number differs from overridden method - pylint: disable=W0221
320 def run( 306 def run(
321 self, master_name, builder_name, build_number, blame_list, try_job_type, 307 self, master_name, builder_name, build_number, blame_list, try_job_type,
322 try_job_id, result): 308 try_job_id, result):
323 """Identifies the information for failed revisions. 309 """Identifies the information for failed revisions.
324 310
325 Please refer to try_job_result_format.md for format check. 311 Please refer to try_job_result_format.md for format check.
326 """ 312 """
(...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after
366 352
367 @ndb.transactional 353 @ndb.transactional
368 def UpdateWfAnalysisWithTryJobResult(): 354 def UpdateWfAnalysisWithTryJobResult():
369 if not culprits: 355 if not culprits:
370 return 356 return
371 357
372 analysis = WfAnalysis.Get(master_name, builder_name, build_number) 358 analysis = WfAnalysis.Get(master_name, builder_name, build_number)
373 # Update analysis result and suspected CLs with results of this try job if 359 # Update analysis result and suspected CLs with results of this try job if
374 # culprits were found. 360 # culprits were found.
375 updated_result_status = _GetResultAnalysisStatus(analysis, result) 361 updated_result_status = _GetResultAnalysisStatus(analysis, result)
376 updated_suspected_cls = _GetSuspectedCLs(analysis, result) 362 updated_suspected_cls = _GetSuspectedCLs(
377 363 analysis, try_job_type, result, culprits)
378 if (analysis.result_status != updated_result_status or 364 if (analysis.result_status != updated_result_status or
379 analysis.suspected_cls != updated_suspected_cls): 365 analysis.suspected_cls != updated_suspected_cls):
380 analysis.result_status = updated_result_status 366 analysis.result_status = updated_result_status
381 analysis.suspected_cls = updated_suspected_cls 367 analysis.suspected_cls = updated_suspected_cls
382 analysis.put() 368 analysis.put()
383 369
370 def UpdateSuspectedCLs():
371 if not culprits:
372 return
373
374 # Creates or updates each suspected_cl.
375 for culprit in culprits.values():
376 revision = culprit['revision']
377 if try_job_type == failure_type.COMPILE:
378 failures = {'compile': []}
379 else:
380 failures = _GetTestFailureCausedByCL(
381 result.get('report', {}).get('result', {}).get(revision))
382
383 suspected_cl_util.UpdateSuspectedCL(
384 culprit['repo_name'], revision, culprit.get('commit_position'),
385 analysis_approach_type.TRY_JOB, master_name, builder_name,
386 build_number, try_job_type, failures, None)
387
384 # Store try-job results. 388 # Store try-job results.
385 UpdateTryJobResult() 389 UpdateTryJobResult()
386 390
387 # Saves cls found by heuristic approach for later use. 391 # Saves cls found by heuristic approach for later use.
388 # This part must be before UpdateWfAnalysisWithTryJobResult(). 392 # This part must be before UpdateWfAnalysisWithTryJobResult().
389 analysis = WfAnalysis.Get(master_name, builder_name, build_number) 393 analysis = WfAnalysis.Get(master_name, builder_name, build_number)
390 heuristic_cls = _GetHeuristicSuspectedCLs(analysis) 394 heuristic_cls = _GetHeuristicSuspectedCLs(analysis)
391 compile_suspected_cl = ( 395 compile_suspected_cl = (
392 _GetSuspectedCLFoundByHeuristicForCompile(analysis) 396 _GetSuspectedCLFoundByHeuristicForCompile(analysis)
393 if try_job_type == failure_type.COMPILE else None) 397 if try_job_type == failure_type.COMPILE else None)
394 398
395 # Add try-job results to WfAnalysis. 399 # Add try-job results to WfAnalysis.
396 UpdateWfAnalysisWithTryJobResult() 400 UpdateWfAnalysisWithTryJobResult()
397 401
402 # TODO (chanli): Update suspected_cl for builds in the same group with
403 # current build.
404 # Updates suspected_cl.
405 UpdateSuspectedCLs()
406
398 _NotifyCulprits(master_name, builder_name, build_number, culprits, 407 _NotifyCulprits(master_name, builder_name, build_number, culprits,
399 heuristic_cls, compile_suspected_cl) 408 heuristic_cls, compile_suspected_cl)
400 return result.get('culprit') if result else None 409 return result.get('culprit') if result else None
OLDNEW
« no previous file with comments | « appengine/findit/waterfall/identify_culprit_pipeline.py ('k') | appengine/findit/waterfall/suspected_cl_util.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698