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

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: Self review. Created 4 years, 3 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
17 from model.wf_try_job import WfTryJob 18 from model.wf_try_job import WfTryJob
18 from model.wf_try_job_data import WfTryJobData 19 from model.wf_try_job_data import WfTryJobData
19 from waterfall.send_notification_for_culprit_pipeline import ( 20 from waterfall.send_notification_for_culprit_pipeline import (
20 SendNotificationForCulpritPipeline) 21 SendNotificationForCulpritPipeline)
22 from waterfall import suspected_cl_util
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
27 def _GetResultAnalysisStatus(analysis, result): 29 def _GetResultAnalysisStatus(analysis, result):
28 """Returns the analysis status based on existing status and try job result. 30 """Returns the analysis status based on existing status and try job result.
29 31
30 Args: 32 Args:
(...skipping 13 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 _GetSuspectedCLs(analysis, culprits):
55 """Returns a list of suspected CLs. 57 """Returns a list of suspected CLs.
56 58
57 Args: 59 Args:
58 analysis: The WfAnalysis entity corresponding to this try job. 60 analysis: The WfAnalysis entity corresponding to this try job.
59 result: A result dict containing the culprit from the results of 61 culprits: A list of suspected CLs found by the try job.
60 this try job.
61 62
62 Returns: 63 Returns:
63 A combined list of suspected CLs from those already in analysis and those 64 A combined list of suspected CLs from those already in analysis and those
64 found by this try job. 65 found by this try job.
65 """ 66 """
66 suspected_cls = analysis.suspected_cls[:] if analysis.suspected_cls else [] 67 suspected_cls = analysis.suspected_cls[:] if analysis.suspected_cls else []
67 suspected_cl_revisions = [cl['revision'] for cl in suspected_cls] 68 suspected_cl_revisions = [cl['revision'] for cl in suspected_cls]
68 culprit = result.get('culprit')
69 compile_cl_info = culprit.get('compile')
70 69
71 if compile_cl_info: 70 for revision, try_job_suspected_cl in culprits.iteritems():
72 # Suspected CL is from compile failure.
73 revision = compile_cl_info.get('revision')
74 if revision not in suspected_cl_revisions: 71 if revision not in suspected_cl_revisions:
75 suspected_cl_revisions.append(revision) 72 suspected_cl_revisions.append(revision)
76 suspected_cls.append(compile_cl_info) 73 suspected_cls.append(try_job_suspected_cl)
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 74
100 return suspected_cls 75 return suspected_cls
101 76
102 77
103 def _GetFailedRevisionFromResultsDict(results_dict): 78 def _GetFailedRevisionFromResultsDict(results_dict):
104 """Finds the failed revision from the given dict of revisions. 79 """Finds the failed revision from the given dict of revisions.
105 80
106 Args: 81 Args:
107 results_dict: (dict) A dict that maps revisions to their results. For 82 results_dict: (dict) A dict that maps revisions to their results. For
108 example: 83 example:
(...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after
161 if (not test_result['valid'] or 136 if (not test_result['valid'] or
162 test_result['status'] != 'failed'): # pragma: no cover 137 test_result['status'] != 'failed'): # pragma: no cover
163 continue 138 continue
164 139
165 failed_revisions.add(revision) 140 failed_revisions.add(revision)
166 141
167 if step not in culprit_map: 142 if step not in culprit_map:
168 culprit_map[step] = { 143 culprit_map[step] = {
169 'tests': {} 144 'tests': {}
170 } 145 }
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']: 146 for failed_test in test_result['failures']:
176 # Swarming tests, gets first failed revision for each test. 147 # Swarming tests, gets first failed revision for each test.
177 if failed_test not in culprit_map[step]['tests']: 148 if failed_test not in culprit_map[step]['tests']:
178 culprit_map[step]['tests'][failed_test] = { 149 culprit_map[step]['tests'][failed_test] = {
179 'revision': revision 150 'revision': revision
180 } 151 }
181 152
182 return culprit_map, list(failed_revisions) 153 return culprit_map, list(failed_revisions)
183 154
184 155
(...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after
234 send_notification_right_now) 205 send_notification_right_now)
235 elif compile_suspected_cl: 206 elif compile_suspected_cl:
236 # A special case where try job didn't find any suspected cls, but 207 # A special case where try job didn't find any suspected cls, but
237 # heuristic found a suspected_cl. 208 # heuristic found a suspected_cl.
238 _StartSendNotificationPipeline( 209 _StartSendNotificationPipeline(
239 master_name, builder_name, build_number, 210 master_name, builder_name, build_number,
240 compile_suspected_cl['repo_name'], compile_suspected_cl['revision'], 211 compile_suspected_cl['repo_name'], compile_suspected_cl['revision'],
241 send_notification_right_now=True) 212 send_notification_right_now=True)
242 213
243 214
215 def _GetTestFailureCausedByCL(result):
216 if not result:
217 return None
218
219 failures = {}
220 for step_name, step_result in result.iteritems():
221 if step_result['status'] == 'failed':
222 failures[step_name] = step_result['failures']
223
224 return failures
225
226
244 class IdentifyTryJobCulpritPipeline(BasePipeline): 227 class IdentifyTryJobCulpritPipeline(BasePipeline):
245 """A pipeline to identify culprit CL info based on try job compile results.""" 228 """A pipeline to identify culprit CL info based on try job compile results."""
246 229
247 def _GetCulpritInfo(self, failed_revisions): 230 def _GetCulpritInfo(self, failed_revisions):
248 """Gets commit_positions and review urls for revisions.""" 231 """Gets commit_positions and review urls for revisions."""
249 culprits = {} 232 culprits = {}
250 # TODO(lijeffrey): remove hard-coded 'chromium' when DEPS file parsing is 233 # TODO(lijeffrey): remove hard-coded 'chromium' when DEPS file parsing is
251 # supported. 234 # supported.
252 for failed_revision in failed_revisions: 235 for failed_revision in failed_revisions:
253 culprits[failed_revision] = { 236 culprits[failed_revision] = {
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after
288 } 271 }
289 failed_revisions.add(revision) 272 failed_revisions.add(revision)
290 return culprit_map, list(failed_revisions) 273 return culprit_map, list(failed_revisions)
291 274
292 return _GetCulpritsForTestsFromResultsDict( 275 return _GetCulpritsForTestsFromResultsDict(
293 blame_list, result['report'].get('result')) 276 blame_list, result['report'].get('result'))
294 277
295 def _UpdateCulpritMapWithCulpritInfo(self, culprit_map, culprits): 278 def _UpdateCulpritMapWithCulpritInfo(self, culprit_map, culprits):
296 """Fills in commit_position and review url for each failed rev in map.""" 279 """Fills in commit_position and review url for each failed rev in map."""
297 for step_culprit in culprit_map.values(): 280 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(): 281 for test_culprit in step_culprit.get('tests', {}).values():
304 test_revision = test_culprit['revision'] 282 test_revision = test_culprit['revision']
305 test_culprit.update(culprits[test_revision]) 283 test_culprit.update(culprits[test_revision])
306 284
307 def _GetCulpritDataForTest(self, culprit_map): 285 def _GetCulpritDataForTest(self, culprit_map):
308 """Gets culprit revision for each failure for try job metadata.""" 286 """Gets culprit revision for each failure for try job metadata."""
309 culprit_data = {} 287 culprit_data = {}
310 for step, step_culprit in culprit_map.iteritems(): 288 for step, step_culprit in culprit_map.iteritems():
311 if step_culprit['tests']: 289 culprit_data[step] = {}
312 culprit_data[step] = {} 290 for test, test_culprit in step_culprit['tests'].iteritems():
313 for test, test_culprit in step_culprit['tests'].iteritems(): 291 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 292 return culprit_data
318 293
319 # Arguments number differs from overridden method - pylint: disable=W0221 294 # Arguments number differs from overridden method - pylint: disable=W0221
320 def run( 295 def run(
321 self, master_name, builder_name, build_number, blame_list, try_job_type, 296 self, master_name, builder_name, build_number, blame_list, try_job_type,
322 try_job_id, result): 297 try_job_id, result):
323 """Identifies the information for failed revisions. 298 """Identifies the information for failed revisions.
324 299
325 Please refer to try_job_result_format.md for format check. 300 Please refer to try_job_result_format.md for format check.
326 """ 301 """
(...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after
366 341
367 @ndb.transactional 342 @ndb.transactional
368 def UpdateWfAnalysisWithTryJobResult(): 343 def UpdateWfAnalysisWithTryJobResult():
369 if not culprits: 344 if not culprits:
370 return 345 return
371 346
372 analysis = WfAnalysis.Get(master_name, builder_name, build_number) 347 analysis = WfAnalysis.Get(master_name, builder_name, build_number)
373 # Update analysis result and suspected CLs with results of this try job if 348 # Update analysis result and suspected CLs with results of this try job if
374 # culprits were found. 349 # culprits were found.
375 updated_result_status = _GetResultAnalysisStatus(analysis, result) 350 updated_result_status = _GetResultAnalysisStatus(analysis, result)
376 updated_suspected_cls = _GetSuspectedCLs(analysis, result) 351 updated_suspected_cls = _GetSuspectedCLs(analysis, culprits)
377
378 if (analysis.result_status != updated_result_status or 352 if (analysis.result_status != updated_result_status or
379 analysis.suspected_cls != updated_suspected_cls): 353 analysis.suspected_cls != updated_suspected_cls):
380 analysis.result_status = updated_result_status 354 analysis.result_status = updated_result_status
381 analysis.suspected_cls = updated_suspected_cls 355 analysis.suspected_cls = updated_suspected_cls
382 analysis.put() 356 analysis.put()
383 357
358 def UpdateSuspectedCLs():
359 if not culprits:
360 return
361
362 # Creates or updates each suspected_cl.
363 for culprit in culprits.values():
364 revision = culprit['revision']
365 if try_job_type == failure_type.COMPILE:
366 failures = {'compile': []}
367 else:
368 failures = _GetTestFailureCausedByCL(
369 result.get('report', {}).get('result', {}).get(revision))
370
371 suspected_cl_util.UpdateSuspectedCL(
372 culprit['repo_name'], revision, culprit.get('commit_position'),
373 analysis_approach_type.TRY_JOB, master_name, builder_name,
374 build_number, try_job_type, failures, None)
375
376 # try_job_suspected_cls = _GetTryJobSuspectedCLs(result)
384 # Store try-job results. 377 # Store try-job results.
385 UpdateTryJobResult() 378 UpdateTryJobResult()
386 379
387 # Saves cls found by heuristic approach for later use. 380 # Saves cls found by heuristic approach for later use.
388 # This part must be before UpdateWfAnalysisWithTryJobResult(). 381 # This part must be before UpdateWfAnalysisWithTryJobResult().
389 analysis = WfAnalysis.Get(master_name, builder_name, build_number) 382 analysis = WfAnalysis.Get(master_name, builder_name, build_number)
390 heuristic_cls = _GetHeuristicSuspectedCLs(analysis) 383 heuristic_cls = _GetHeuristicSuspectedCLs(analysis)
391 compile_suspected_cl = ( 384 compile_suspected_cl = (
392 _GetSuspectedCLFoundByHeuristicForCompile(analysis) 385 _GetSuspectedCLFoundByHeuristicForCompile(analysis)
393 if try_job_type == failure_type.COMPILE else None) 386 if try_job_type == failure_type.COMPILE else None)
394 387
395 # Add try-job results to WfAnalysis. 388 # Add try-job results to WfAnalysis.
396 UpdateWfAnalysisWithTryJobResult() 389 UpdateWfAnalysisWithTryJobResult()
390 # Updates suspected_cl.
391 UpdateSuspectedCLs()
397 392
398 _NotifyCulprits(master_name, builder_name, build_number, culprits, 393 _NotifyCulprits(master_name, builder_name, build_number, culprits,
399 heuristic_cls, compile_suspected_cl) 394 heuristic_cls, compile_suspected_cl)
400 return result.get('culprit') if result else None 395 return result.get('culprit') if result else None
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698