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

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: Created 4 years, 4 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 logging 6 import logging
7 7
8 from google.appengine.ext import ndb 8 from google.appengine.ext import ndb
9 9
10 from common.git_repository import GitRepository 10 from common.git_repository import GitRepository
11 from common.http_client_appengine import HttpClientAppengine as HttpClient 11 from common.http_client_appengine import HttpClientAppengine as HttpClient
12 from common.pipeline_wrapper import BasePipeline 12 from common.pipeline_wrapper import BasePipeline
13 from common.waterfall import failure_type 13 from common.waterfall import failure_type
14 from model import analysis_approach_type
14 from model import analysis_status 15 from model import analysis_status
15 from model import result_status 16 from model import result_status
16 from model.wf_analysis import WfAnalysis 17 from model.wf_analysis import WfAnalysis
18 from model.wf_suspected_cl import WfSuspectedCL
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)
21 23
22 24
23 GIT_REPO = GitRepository( 25 GIT_REPO = GitRepository(
24 'https://chromium.googlesource.com/chromium/src.git', HttpClient()) 26 'https://chromium.googlesource.com/chromium/src.git', HttpClient())
25 27
26 28
(...skipping 17 matching lines...) Expand all
44 if (try_job_found_culprit and 46 if (try_job_found_culprit and
45 (old_result_status is None or 47 (old_result_status is None or
46 old_result_status == result_status.NOT_FOUND_UNTRIAGED or 48 old_result_status == result_status.NOT_FOUND_UNTRIAGED or
47 old_result_status == result_status.NOT_FOUND_INCORRECT or 49 old_result_status == result_status.NOT_FOUND_INCORRECT or
48 old_result_status == result_status.NOT_FOUND_CORRECT)): 50 old_result_status == result_status.NOT_FOUND_CORRECT)):
49 return result_status.FOUND_UNTRIAGED 51 return result_status.FOUND_UNTRIAGED
50 52
51 return old_result_status 53 return old_result_status
52 54
53 55
54 def _GetSuspectedCLs(analysis, result): 56 def _GetTryJobSuspectedCLs(result):
57 """Returns all suspected CLs found by the try job.
58
59 Args:
60 result: A result dict containing the culprit from the results of
61 this try job.
62
63 Returns:
64 A list of suspected CLs found by this try job.
65 """
66 suspected_cls = []
67 if not result or not result.get('culprit'):
68 return suspected_cls
69
70 culprit = result.get('culprit')
71 compile_cl_info = culprit.get('compile')
72
73 if compile_cl_info:
74 # Suspected CL is from compile failure.
75 suspected_cls.append(compile_cl_info)
76 return suspected_cls
77
78 # Suspected CLs are from test failures.
79 suspected_cl_revisions = []
lijeffrey 2016/08/10 08:12:39 Move the test suspected_cls logic to a separate fu
chanli 2016/08/11 23:39:36 Done.
80 for results in culprit.itervalues():
81 for test_cl_info in results['tests'].values():
lijeffrey 2016/08/10 08:12:39 nit: if using itervalues() above, why not use it h
chanli 2016/08/11 23:39:36 Done.
82 revision = test_cl_info['revision']
83 if revision not in suspected_cl_revisions:
84 suspected_cl_revisions.append(revision)
85 suspected_cls.append(test_cl_info)
86
87 return suspected_cls
88
89
90 def _GetSuspectedCLs(analysis, try_job_suspected_cls):
55 """Returns a list of suspected CLs. 91 """Returns a list of suspected CLs.
56 92
57 Args: 93 Args:
58 analysis: The WfAnalysis entity corresponding to this try job. 94 analysis: The WfAnalysis entity corresponding to this try job.
59 result: A result dict containing the culprit from the results of 95 is_compile_try_job: True if the try job is for compile failure,
60 this try job. 96 otherwise False.
97 try_job_suspected_cls: A list of suspected CLs found by the try job.
61 98
62 Returns: 99 Returns:
63 A combined list of suspected CLs from those already in analysis and those 100 A combined list of suspected CLs from those already in analysis and those
64 found by this try job. 101 found by this try job.
65 """ 102 """
66 suspected_cls = analysis.suspected_cls[:] if analysis.suspected_cls else [] 103 suspected_cls = analysis.suspected_cls[:] if analysis.suspected_cls else []
67 suspected_cl_revisions = [cl['revision'] for cl in suspected_cls] 104 suspected_cl_revisions = [cl['revision'] for cl in suspected_cls]
68 culprit = result.get('culprit')
69 compile_cl_info = culprit.get('compile')
70 105
71 if compile_cl_info: 106 for try_job_suspected_cl in try_job_suspected_cls:
72 # Suspected CL is from compile failure. 107 if try_job_suspected_cl['revision'] not in suspected_cl_revisions:
73 revision = compile_cl_info.get('revision') 108 suspected_cl_revisions.append(try_job_suspected_cl['revision'])
74 if revision not in suspected_cl_revisions: 109 suspected_cls.append(try_job_suspected_cl)
75 suspected_cl_revisions.append(revision)
76 suspected_cls.append(compile_cl_info)
77 return suspected_cls
78
79 # Suspected CLs are from test failures.
80 for results in culprit.itervalues():
81 if results.get('revision'):
82 # Non swarming test failures, only have step level failure info.
83 revision = results.get('revision')
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 110
100 return suspected_cls 111 return suspected_cls
101 112
102 113
103 def _GetFailedRevisionFromResultsDict(results_dict): 114 def _GetFailedRevisionFromResultsDict(results_dict):
104 """Finds the failed revision from the given dict of revisions. 115 """Finds the failed revision from the given dict of revisions.
105 116
106 Args: 117 Args:
107 results_dict: (dict) A dict that maps revisions to their results. For 118 results_dict: (dict) A dict that maps revisions to their results. For
108 example: 119 example:
(...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after
161 if (not test_result['valid'] or 172 if (not test_result['valid'] or
162 test_result['status'] != 'failed'): # pragma: no cover 173 test_result['status'] != 'failed'): # pragma: no cover
163 continue 174 continue
164 175
165 failed_revisions.add(revision) 176 failed_revisions.add(revision)
166 177
167 if step not in culprit_map: 178 if step not in culprit_map:
168 culprit_map[step] = { 179 culprit_map[step] = {
169 'tests': {} 180 'tests': {}
170 } 181 }
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']: 182 for failed_test in test_result['failures']:
176 # Swarming tests, gets first failed revision for each test. 183 # Swarming tests, gets first failed revision for each test.
177 if failed_test not in culprit_map[step]['tests']: 184 if failed_test not in culprit_map[step]['tests']:
178 culprit_map[step]['tests'][failed_test] = { 185 culprit_map[step]['tests'][failed_test] = {
179 'revision': revision 186 'revision': revision
180 } 187 }
181 188
182 return culprit_map, list(failed_revisions) 189 return culprit_map, list(failed_revisions)
183 190
184 191
(...skipping 56 matching lines...) Expand 10 before | Expand all | Expand 10 after
241 } 248 }
242 failed_revisions.add(revision) 249 failed_revisions.add(revision)
243 return culprit_map, list(failed_revisions) 250 return culprit_map, list(failed_revisions)
244 251
245 return _GetCulpritsForTestsFromResultsDict( 252 return _GetCulpritsForTestsFromResultsDict(
246 blame_list, result['report'].get('result')) 253 blame_list, result['report'].get('result'))
247 254
248 def _UpdateCulpritMapWithCulpritInfo(self, culprit_map, culprits): 255 def _UpdateCulpritMapWithCulpritInfo(self, culprit_map, culprits):
249 """Fills in commit_position and review url for each failed rev in map.""" 256 """Fills in commit_position and review url for each failed rev in map."""
250 for step_culprit in culprit_map.values(): 257 for step_culprit in culprit_map.values():
251 if step_culprit.get('revision'):
252 culprit = culprits[step_culprit['revision']]
253 step_culprit['commit_position'] = culprit['commit_position']
254 step_culprit['url'] = culprit['url']
255 step_culprit['repo_name'] = culprit['repo_name']
256 for test_culprit in step_culprit.get('tests', {}).values(): 258 for test_culprit in step_culprit.get('tests', {}).values():
257 test_revision = test_culprit['revision'] 259 test_revision = test_culprit['revision']
258 test_culprit.update(culprits[test_revision]) 260 test_culprit.update(culprits[test_revision])
259 261
260 def _GetCulpritDataForTest(self, culprit_map): 262 def _GetCulpritDataForTest(self, culprit_map):
261 """Gets culprit revision for each failure for try job metadata.""" 263 """Gets culprit revision for each failure for try job metadata."""
262 culprit_data = {} 264 culprit_data = {}
263 for step, step_culprit in culprit_map.iteritems(): 265 for step, step_culprit in culprit_map.iteritems():
264 if step_culprit['tests']: 266 culprit_data[step] = {}
265 culprit_data[step] = {} 267 for test, test_culprit in step_culprit['tests'].iteritems():
266 for test, test_culprit in step_culprit['tests'].iteritems(): 268 culprit_data[step][test] = test_culprit['revision']
267 culprit_data[step][test] = test_culprit['revision']
268 else:
269 culprit_data[step] = step_culprit['revision']
270 return culprit_data 269 return culprit_data
271 270
272 # Arguments number differs from overridden method - pylint: disable=W0221 271 # Arguments number differs from overridden method - pylint: disable=W0221
273 def run( 272 def run(
274 self, master_name, builder_name, build_number, blame_list, try_job_type, 273 self, master_name, builder_name, build_number, blame_list, try_job_type,
275 try_job_id, result): 274 try_job_id, result):
276 """Identifies the information for failed revisions. 275 """Identifies the information for failed revisions.
277 276
278 Please refer to try_job_result_format.md for format check. 277 Please refer to try_job_result_format.md for format check.
279 """ 278 """
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after
311 try_job_result.test_results) 310 try_job_result.test_results)
312 if (result_to_update and 311 if (result_to_update and
313 result_to_update[-1]['try_job_id'] == try_job_id): 312 result_to_update[-1]['try_job_id'] == try_job_id):
314 result_to_update[-1].update(result) 313 result_to_update[-1].update(result)
315 else: # pragma: no cover 314 else: # pragma: no cover
316 result_to_update.append(result) 315 result_to_update.append(result)
317 try_job_result.status = analysis_status.COMPLETED 316 try_job_result.status = analysis_status.COMPLETED
318 try_job_result.put() 317 try_job_result.put()
319 318
320 @ndb.transactional 319 @ndb.transactional
321 def UpdateWfAnalysisWithTryJobResult(): 320 def UpdateWfAnalysisWithTryJobResult(try_job_suspected_cls):
322 if not culprits: 321 if not culprits:
323 return 322 return
324
325 # Update analysis result and suspected CLs with results of this try job if 323 # Update analysis result and suspected CLs with results of this try job if
326 # culprits were found. 324 # culprits were found.
327 analysis = WfAnalysis.Get(master_name, builder_name, build_number) 325 analysis = WfAnalysis.Get(master_name, builder_name, build_number)
328 updated_result_status = _GetResultAnalysisStatus(analysis, result) 326 updated_result_status = _GetResultAnalysisStatus(analysis, result)
329 updated_suspected_cls = _GetSuspectedCLs(analysis, result) 327 updated_suspected_cls = _GetSuspectedCLs(analysis, try_job_suspected_cls)
330
331 if (analysis.result_status != updated_result_status or 328 if (analysis.result_status != updated_result_status or
332 analysis.suspected_cls != updated_suspected_cls): 329 analysis.suspected_cls != updated_suspected_cls):
333 analysis.result_status = updated_result_status 330 analysis.result_status = updated_result_status
334 analysis.suspected_cls = updated_suspected_cls 331 analysis.suspected_cls = updated_suspected_cls
335 analysis.put() 332 analysis.put()
336 333
334 @ndb.transactional
335 def CreateOrUpdateSuspectedCL(repo_name, revision, commit_position):
lijeffrey 2016/08/10 08:12:39 this looks almost the same as _SaveSuspectedCL in
lijeffrey 2016/08/10 08:12:39 nit: Why not just name this UpdateSuspectedCL?
chanli 2016/08/11 23:39:36 Done.
chanli 2016/08/11 23:39:36 Done.
336 suspected_cl = WfSuspectedCL.Get(repo_name, revision)
lijeffrey 2016/08/10 08:12:39 suspected_cl = WfSuspectedCl.Get(...) or WfSuspect
chanli 2016/08/11 23:39:36 Done.
337 if not suspected_cl:
338 suspected_cl = WfSuspectedCL.Create(
339 repo_name, revision, commit_position)
340
341 if suspected_cl.approach == analysis_approach_type.HEURISTIC:
342 suspected_cl.approach = analysis_approach_type.BOTH
343 elif suspected_cl.approach is None: #pragma: no cover
344 suspected_cl.approach = analysis_approach_type.TRY_JOB
345
346 suspected_cl.failure_type = suspected_cl.failure_type or try_job_type
347
348 build_info = [master_name, builder_name, build_number]
349 if build_info not in suspected_cl.builds:
350 suspected_cl.builds.append(build_info)
351
352 suspected_cl.put()
353
354 def CreateOrUpdateSuspectedCLs(try_job_suspected_cls):
355 if not culprits:
356 return
357
358 # Creates or updates each suspected_cl.
359 for culprit in try_job_suspected_cls:
360 CreateOrUpdateSuspectedCL(
361 culprit['repo_name'], culprit['revision'],
362 culprit.get('commit_position'))
363
364 try_job_suspected_cls = _GetTryJobSuspectedCLs(result)
337 # Store try-job results. 365 # Store try-job results.
338 UpdateTryJobResult() 366 UpdateTryJobResult()
339 # Add try-job results to WfAnalysis. 367 # Add try-job results to WfAnalysis.
340 UpdateWfAnalysisWithTryJobResult() 368 UpdateWfAnalysisWithTryJobResult(try_job_suspected_cls)
369 # Updates suspected_cl
370 CreateOrUpdateSuspectedCLs(try_job_suspected_cls)
341 371
342 _NotifyCulprits(master_name, builder_name, build_number, culprits) 372 _NotifyCulprits(master_name, builder_name, build_number, culprits)
343 return result.get('culprit') if result else None 373 return result.get('culprit') if result else None
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698