Chromium Code Reviews| OLD | NEW |
|---|---|
| 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 | 5 from collections import defaultdict |
| 6 from datetime import datetime | 6 from datetime import datetime |
| 7 | 7 |
| 8 from google.appengine.api import users | 8 from google.appengine.api import users |
| 9 | 9 |
| 10 from common import constants | 10 from common import constants |
| 11 from common.base_handler import BaseHandler | 11 from common.base_handler import BaseHandler |
| 12 from common.base_handler import Permission | 12 from common.base_handler import Permission |
| 13 from common.waterfall import failure_type | 13 from common.waterfall import failure_type |
| 14 from handlers import handlers_util | 14 from handlers import handlers_util |
| 15 from handlers import result_status | 15 from handlers import result_status |
| 16 from handlers.result_status import NO_TRY_JOB_REASON_MAP | 16 from handlers.result_status import NO_TRY_JOB_REASON_MAP |
| 17 from model import analysis_status | 17 from model import analysis_status |
| 18 from model.result_status import RESULT_STATUS_TO_DESCRIPTION | |
| 19 from model.suspected_cl_status import CL_STATUS_TO_DESCRIPTION | |
| 18 from model.wf_analysis import WfAnalysis | 20 from model.wf_analysis import WfAnalysis |
| 21 from model.wf_suspected_cl import WfSuspectedCL | |
| 19 from model.wf_try_job import WfTryJob | 22 from model.wf_try_job import WfTryJob |
| 20 from model.result_status import RESULT_STATUS_TO_DESCRIPTION | |
| 21 from waterfall import build_failure_analysis_pipelines | 23 from waterfall import build_failure_analysis_pipelines |
| 22 from waterfall import buildbot | 24 from waterfall import buildbot |
| 23 from waterfall import waterfall_config | 25 from waterfall import waterfall_config |
| 24 | 26 |
| 25 | 27 |
| 26 NON_SWARMING = object() | 28 NON_SWARMING = object() |
| 27 | 29 |
| 28 | 30 |
| 29 def _FormatDatetime(dt): | 31 def _FormatDatetime(dt): |
| 30 if not dt: | 32 if not dt: |
| 31 return None | 33 return None |
| 32 else: | 34 else: |
| 33 return dt.strftime('%Y-%m-%d %H:%M:%S UTC') | 35 return dt.strftime('%Y-%m-%d %H:%M:%S UTC') |
| 34 | 36 |
| 35 | 37 |
| 38 def _GetCLDict(analysis, cl_info): | |
| 39 if not cl_info: | |
| 40 return None | |
|
lijeffrey
2016/09/23 05:09:09
nit: how about instead of returning None, return {
chanli
2016/09/24 01:09:40
Done.
| |
| 41 | |
| 42 cl_keys = cl_info.split('/') | |
| 43 repo_name = cl_keys[0] | |
| 44 revision = cl_keys[1] | |
| 45 for cl in analysis.suspected_cls: | |
| 46 if cl['repo_name'] == repo_name and cl['revision'] == revision: | |
| 47 return cl | |
| 48 return None | |
| 49 | |
| 50 | |
| 36 def _GetTriageHistory(analysis): | 51 def _GetTriageHistory(analysis): |
| 37 if (not users.is_current_user_admin() or | 52 if (not users.is_current_user_admin() or |
| 38 not analysis.completed or | 53 not analysis.completed or |
| 39 not analysis.triage_history): | 54 not analysis.triage_history): |
| 40 return None | 55 return None |
| 41 | 56 |
| 42 triage_history = [] | 57 triage_history = [] |
| 43 for triage_record in analysis.triage_history: | 58 for triage_record in analysis.triage_history: |
| 59 | |
| 44 triage_history.append({ | 60 triage_history.append({ |
| 45 'triage_time': _FormatDatetime( | 61 'triage_time': _FormatDatetime( |
| 46 datetime.utcfromtimestamp(triage_record['triage_timestamp'])), | 62 datetime.utcfromtimestamp(triage_record['triage_timestamp'])), |
| 47 'user_name': triage_record['user_name'], | 63 'user_name': triage_record['user_name'], |
| 48 'result_status': RESULT_STATUS_TO_DESCRIPTION.get( | 64 'triaged_cl': _GetCLDict(analysis, triage_record.get('triaged_cl')), |
| 49 triage_record['result_status']), | 65 'result_status': ( |
| 66 RESULT_STATUS_TO_DESCRIPTION.get(triage_record.get('result_status')) | |
| 67 or CL_STATUS_TO_DESCRIPTION.get(triage_record.get('cl_status'))), | |
| 50 'version': triage_record.get('version'), | 68 'version': triage_record.get('version'), |
| 51 }) | 69 }) |
| 52 | 70 |
| 53 return triage_history | 71 return triage_history |
| 54 | 72 |
| 55 | 73 |
| 56 def _GetOrganizedAnalysisResultBySuspectedCL(analysis_result): | 74 def _GetOrganizedAnalysisResultBySuspectedCL(analysis_result): |
| 57 """Group tests it they have the same suspected CLs.""" | 75 """Group tests it they have the same suspected CLs.""" |
| 58 organized_results = defaultdict(list) | 76 organized_results = defaultdict(list) |
| 59 | 77 |
| (...skipping 235 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 295 compile_failure = None | 313 compile_failure = None |
| 296 for failure in analysis.result.get('failures', []): | 314 for failure in analysis.result.get('failures', []): |
| 297 if failure['step_name'] == constants.COMPILE_STEP_NAME: | 315 if failure['step_name'] == constants.COMPILE_STEP_NAME: |
| 298 compile_failure = failure | 316 compile_failure = failure |
| 299 if compile_failure: # pragma: no branch. | 317 if compile_failure: # pragma: no branch. |
| 300 data['first_failure'] = compile_failure['first_failure'] | 318 data['first_failure'] = compile_failure['first_failure'] |
| 301 data['last_pass'] = compile_failure['last_pass'] | 319 data['last_pass'] = compile_failure['last_pass'] |
| 302 data['suspected_cls_by_heuristic'] = compile_failure['suspected_cls'] | 320 data['suspected_cls_by_heuristic'] = compile_failure['suspected_cls'] |
| 303 | 321 |
| 304 | 322 |
| 323 def _GetAllSuspectedCLsAndCheckStatus( | |
| 324 master_name, builder_name, build_number, analysis): | |
| 325 build_key = '%s/%s/%d' % (master_name, builder_name, build_number) | |
|
lijeffrey
2016/09/23 05:09:09
nit: how about import base_build_model and use Cre
stgao
2016/09/23 19:35:41
This encoding and decoding should be abstracted in
chanli
2016/09/24 01:09:40
Done.
chanli
2016/09/24 01:09:40
Done.
| |
| 326 suspected_cls = analysis.suspected_cls | |
|
stgao
2016/09/23 19:35:41
Should we do a deep copy first before modifying?
chanli
2016/09/24 01:09:40
Oops, Done.
| |
| 327 if not suspected_cls: | |
| 328 return [] | |
| 329 | |
| 330 for cl in suspected_cls: | |
| 331 cl['status'] = None | |
| 332 cl_object = WfSuspectedCL.Get(cl['repo_name'], cl['revision']) | |
|
stgao
2016/09/23 19:35:41
Can we map var name to its class_name for easier r
chanli
2016/09/24 01:09:40
Done.
| |
| 333 # TODO(chanli): Write a script to convert legacy data before enable | |
| 334 # this change. | |
| 335 if not cl_object: | |
| 336 continue | |
| 337 | |
| 338 build = cl_object.builds.get(build_key) | |
| 339 import json | |
| 340 print json.dumps(build, indent =2) | |
|
lijeffrey
2016/09/23 05:09:09
nit: remove print before committing
chanli
2016/09/24 01:09:40
Done.
| |
| 341 if build: | |
| 342 cl['status'] = build['status'] | |
| 343 | |
| 344 return suspected_cls | |
| 345 | |
| 346 | |
| 305 class BuildFailure(BaseHandler): | 347 class BuildFailure(BaseHandler): |
| 306 PERMISSION_LEVEL = Permission.ANYONE | 348 PERMISSION_LEVEL = Permission.ANYONE |
| 307 | 349 |
| 308 def _ShowDebugInfo(self): | 350 def _ShowDebugInfo(self): |
| 309 # Show debug info only if the app is run locally during development, if the | 351 # Show debug info only if the app is run locally during development, if the |
| 310 # currently logged-in user is an admin, or if it is explicitly requested | 352 # currently logged-in user is an admin, or if it is explicitly requested |
| 311 # with parameter 'debug=1'. | 353 # with parameter 'debug=1'. |
| 312 return users.is_current_user_admin() or self.request.get('debug') == '1' | 354 return users.is_current_user_admin() or self.request.get('debug') == '1' |
| 313 | 355 |
| 314 def _ShowTriageHelpButton(self): | 356 def _ShowTriageHelpButton(self): |
| (...skipping 18 matching lines...) Expand all Loading... | |
| 333 'triage_history': _GetTriageHistory(analysis), | 375 'triage_history': _GetTriageHistory(analysis), |
| 334 'show_triage_help_button': self._ShowTriageHelpButton(), | 376 'show_triage_help_button': self._ShowTriageHelpButton(), |
| 335 'triage_reference_analysis_master_name': | 377 'triage_reference_analysis_master_name': |
| 336 analysis.triage_reference_analysis_master_name, | 378 analysis.triage_reference_analysis_master_name, |
| 337 'triage_reference_analysis_builder_name': | 379 'triage_reference_analysis_builder_name': |
| 338 analysis.triage_reference_analysis_builder_name, | 380 analysis.triage_reference_analysis_builder_name, |
| 339 'triage_reference_analysis_build_number': | 381 'triage_reference_analysis_build_number': |
| 340 analysis.triage_reference_analysis_build_number | 382 analysis.triage_reference_analysis_build_number |
| 341 } | 383 } |
| 342 | 384 |
| 343 def _PrepareDataForCompileFailure(self, analysis): | 385 def _PrepareDataForCompileFailure(self, analysis, data): |
| 344 data = self._PrepareCommonDataForFailure(analysis) | |
| 345 | 386 |
| 346 # Check result from heuristic analysis. | 387 # Check result from heuristic analysis. |
| 347 _PopulateHeuristicDataForCompileFailure(analysis, data) | 388 _PopulateHeuristicDataForCompileFailure(analysis, data) |
| 348 # Check result from try job. | 389 # Check result from try job. |
| 349 data['try_job'] = _PrepareTryJobDataForCompileFailure(analysis) | 390 data['try_job'] = _PrepareTryJobDataForCompileFailure(analysis) |
| 350 | 391 |
| 351 return data | 392 def _PrepareDataForTestFailures(self, analysis, build_info, data, |
| 393 show_debug_info=False): | |
| 352 | 394 |
| 353 def _PrepareDataForTestFailures(self, analysis, build_info, | |
| 354 show_debug_info=False): | |
| 355 data = self._PrepareCommonDataForFailure(analysis) | |
| 356 data['status_message_map'] = result_status.STATUS_MESSAGE_MAP | 395 data['status_message_map'] = result_status.STATUS_MESSAGE_MAP |
| 357 | 396 |
| 358 organized_results = _GetOrganizedAnalysisResultBySuspectedCL( | 397 organized_results = _GetOrganizedAnalysisResultBySuspectedCL( |
| 359 analysis.result) | 398 analysis.result) |
| 360 analysis_result = _GetAnalysisResultWithTryJobInfo( | 399 analysis_result = _GetAnalysisResultWithTryJobInfo( |
| 361 show_debug_info, organized_results, *build_info) | 400 show_debug_info, organized_results, *build_info) |
| 362 | 401 |
| 363 data['analysis_result'] = analysis_result | 402 data['analysis_result'] = analysis_result |
| 364 return data | 403 return data |
| 365 | 404 |
| (...skipping 29 matching lines...) Expand all Loading... | |
| 395 build_completed = (users.is_current_user_admin() and | 434 build_completed = (users.is_current_user_admin() and |
| 396 self.request.get('build_completed') == '1') | 435 self.request.get('build_completed') == '1') |
| 397 force_try_job = (users.is_current_user_admin() and | 436 force_try_job = (users.is_current_user_admin() and |
| 398 self.request.get('force_try_job') == '1') | 437 self.request.get('force_try_job') == '1') |
| 399 analysis = build_failure_analysis_pipelines.ScheduleAnalysisIfNeeded( | 438 analysis = build_failure_analysis_pipelines.ScheduleAnalysisIfNeeded( |
| 400 master_name, builder_name, build_number, | 439 master_name, builder_name, build_number, |
| 401 build_completed=build_completed, force=force, | 440 build_completed=build_completed, force=force, |
| 402 force_try_job=force_try_job, | 441 force_try_job=force_try_job, |
| 403 queue_name=constants.WATERFALL_ANALYSIS_QUEUE) | 442 queue_name=constants.WATERFALL_ANALYSIS_QUEUE) |
| 404 | 443 |
| 444 data = self._PrepareCommonDataForFailure(analysis) | |
| 445 data['suspected_cls'] = _GetAllSuspectedCLsAndCheckStatus( | |
| 446 master_name, builder_name, build_number, analysis) | |
| 447 | |
| 405 if analysis.failure_type == failure_type.COMPILE: | 448 if analysis.failure_type == failure_type.COMPILE: |
| 449 self._PrepareDataForCompileFailure(analysis, data) | |
| 406 return { | 450 return { |
| 407 'template': 'waterfall/compile_failure.html', | 451 'template': 'waterfall/compile_failure.html', |
| 408 'data': self._PrepareDataForCompileFailure(analysis), | 452 'data': data |
| 409 } | 453 } |
| 410 else: | 454 else: |
| 455 self._PrepareDataForTestFailures( | |
| 456 analysis, build_info, data, self._ShowDebugInfo()) | |
| 411 return { | 457 return { |
| 412 'template': 'waterfall/test_failure.html', | 458 'template': 'waterfall/test_failure.html', |
| 413 'data': self._PrepareDataForTestFailures(analysis, build_info, | 459 'data': data |
| 414 self._ShowDebugInfo()), | |
| 415 } | 460 } |
| 416 | 461 |
| 462 | |
|
lijeffrey
2016/09/23 05:09:09
nit: too many blank lines
chanli
2016/09/24 01:09:40
Done.
| |
| 417 def HandlePost(self): # pragma: no cover | 463 def HandlePost(self): # pragma: no cover |
| 418 return self.HandleGet() | 464 return self.HandleGet() |
| OLD | NEW |