Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 # Copyright 2016 The Chromium Authors. All rights reserved. | 1 # Copyright 2016 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 datetime import datetime | 5 from datetime import datetime |
| 6 import logging | 6 import logging |
| 7 import textwrap | 7 import textwrap |
| 8 | 8 |
| 9 from google.appengine.ext import ndb | 9 from google.appengine.ext import ndb |
| 10 | 10 |
| 11 from common import time_util | 11 from common import time_util |
| 12 from common.git_repository import GitRepository | 12 from common.git_repository import GitRepository |
| 13 from common.http_client_appengine import HttpClientAppengine as HttpClient | 13 from common.http_client_appengine import HttpClientAppengine as HttpClient |
| 14 from common.pipeline_wrapper import BasePipeline | 14 from common.pipeline_wrapper import BasePipeline |
| 15 from common.rietveld import Rietveld | 15 from common.rietveld import Rietveld |
| 16 from model import analysis_status as status | 16 from model import analysis_status as status |
| 17 from model.wf_analysis import WfAnalysis | |
| 17 from model.wf_culprit import WfCulprit | 18 from model.wf_culprit import WfCulprit |
| 18 from waterfall import build_util | 19 from waterfall import build_util |
| 19 from waterfall import waterfall_config | 20 from waterfall import waterfall_config |
| 20 | 21 |
| 21 | 22 |
| 23 def _PassAdditionalCriteria(additional_criteria): | |
| 24 """Check if the all the additional criteria have passed. | |
| 25 | |
| 26 Checks for individual criterion have been done before this function. | |
| 27 """ | |
| 28 return False if False in additional_criteria.values() else True | |
|
stgao
2016/09/03 06:14:23
return all(additional_criteria.values())
chanli
2016/09/05 21:49:45
Done.
| |
| 29 | |
| 30 | |
| 22 @ndb.transactional | 31 @ndb.transactional |
| 23 def _ShouldSendNotification( | 32 def _ShouldSendNotification( |
| 24 master_name, builder_name, build_number, repo_name, revision, | 33 master_name, builder_name, build_number, repo_name, revision, |
| 25 commit_position, build_num_threshold, time_limit_passed): | 34 commit_position, build_num_threshold, additional_criteria, |
| 35 send_notification_right_now): | |
| 26 """Returns True if a notification for the culprit should be sent.""" | 36 """Returns True if a notification for the culprit should be sent.""" |
| 27 culprit = (WfCulprit.Get(repo_name, revision) or | 37 culprit = (WfCulprit.Get(repo_name, revision) or |
| 28 WfCulprit.Create(repo_name, revision, commit_position)) | 38 WfCulprit.Create(repo_name, revision, commit_position)) |
| 29 if [master_name, builder_name, build_number] in culprit.builds: | 39 if [master_name, builder_name, build_number] in culprit.builds: |
| 30 return False | 40 return False |
| 31 | 41 |
| 32 culprit.builds.append([master_name, builder_name, build_number]) | 42 culprit.builds.append([master_name, builder_name, build_number]) |
| 33 # Send notification only when: | 43 # Send notification only when: |
| 34 # 1. It was not processed yet. | 44 # 1. It was not processed yet. |
| 35 # 2. The culprit is for multiple failures in different builds to avoid false | 45 # 2. It is not too late after the culprit was committed. |
|
stgao
2016/09/03 06:14:24
Should we still keep this one?
chanli
2016/09/05 21:49:45
Done.
| |
| 36 # positive due to flakiness. | |
| 37 # 3. It is not too late after the culprit was committed. | |
| 38 # * Try-job takes too long to complete and the failure got fixed. | 46 # * Try-job takes too long to complete and the failure got fixed. |
| 39 # * The whole analysis was rerun a long time after the failure occurred. | 47 # * The whole analysis was rerun a long time after the failure occurred. |
| 40 should_send = not (culprit.cr_notification_processed or | 48 should_send = not (culprit.cr_notification_processed or |
| 41 len(culprit.builds) < build_num_threshold or | 49 not _PassAdditionalCriteria(additional_criteria)) |
| 42 time_limit_passed) | 50 if not send_notification_right_now: |
| 51 should_send = should_send and len(culprit.builds) >= build_num_threshold | |
| 52 | |
| 43 if should_send: | 53 if should_send: |
| 44 culprit.cr_notification_status = status.RUNNING | 54 culprit.cr_notification_status = status.RUNNING |
| 45 culprit.put() | 55 culprit.put() |
| 46 return should_send | 56 return should_send |
| 47 | 57 |
| 48 | 58 |
| 49 @ndb.transactional | 59 @ndb.transactional |
| 50 def _UpdateNotificationStatus(repo_name, revision, new_status): | 60 def _UpdateNotificationStatus(repo_name, revision, new_status): |
| 51 culprit = WfCulprit.Get(repo_name, revision) | 61 culprit = WfCulprit.Get(repo_name, revision) |
| 52 culprit.cr_notification_status = new_status | 62 culprit.cr_notification_status = new_status |
| 53 if culprit.cr_notified: | 63 if culprit.cr_notified: |
| 54 culprit.cr_notification_time = time_util.GetUTCNow() | 64 culprit.cr_notification_time = time_util.GetUTCNow() |
| 55 culprit.put() | 65 culprit.put() |
| 56 | 66 |
| 57 | 67 |
| 58 def _SendNotificationForCulprit( | 68 def _SendNotificationForCulprit( |
| 59 repo_name, revision, commit_position, code_review_url): | 69 repo_name, revision, commit_position, code_review_url): |
| 60 sent = False | 70 sent = False |
| 61 if code_review_url: | 71 if code_review_url: |
| 62 # Occasionally, a commit was not uploaded for code-review. | 72 # Occasionally, a commit was not uploaded for code-review. |
| 63 culprit = WfCulprit.Get(repo_name, revision) | 73 culprit = WfCulprit.Get(repo_name, revision) |
| 64 rietveld = Rietveld() | 74 rietveld = Rietveld() |
| 65 message = textwrap.dedent(""" | 75 message = textwrap.dedent(""" |
| 66 FYI: Findit try jobs (rerunning failed compile or tests) identified this CL | 76 FYI: Findit identified this CL at revision %s as the culprit for |
| 67 at revision %s as the culprit for failures in the build cycles as shown on: | 77 failures in the build cycles as shown on: |
| 68 https://findit-for-me.appspot.com/waterfall/culprit?key=%s | 78 https://findit-for-me.appspot.com/waterfall/culprit?key=%s""") % ( |
| 69 """) % (commit_position or revision, culprit.key.urlsafe()) | 79 commit_position or revision, culprit.key.urlsafe()) |
| 70 sent = rietveld.PostMessage(code_review_url, message) | 80 sent = rietveld.PostMessage(code_review_url, message) |
| 71 else: | 81 else: |
| 72 logging.error('No code-review url for %s/%s', repo_name, revision) | 82 logging.error('No code-review url for %s/%s', repo_name, revision) |
| 73 | 83 |
| 74 _UpdateNotificationStatus(repo_name, revision, | 84 _UpdateNotificationStatus(repo_name, revision, |
| 75 status.COMPLETED if sent else status.ERROR) | 85 status.COMPLETED if sent else status.ERROR) |
| 76 return sent | 86 return sent |
| 77 | 87 |
| 78 | 88 |
| 79 def _GetCulpritInfo(repo_name, revision): | 89 def _GetCulpritInfo(repo_name, revision): |
| 80 """Returns commit position/time and code-review url of the given revision.""" | 90 """Returns commit position/time and code-review url of the given revision.""" |
| 81 # TODO(stgao): get repo url at runtime based on the given repo name. | 91 # TODO(stgao): get repo url at runtime based on the given repo name. |
| 82 # unused arg - pylint: disable=W0612,W0613 | 92 # unused arg - pylint: disable=W0612,W0613 |
| 83 repo = GitRepository( | 93 repo = GitRepository( |
| 84 'https://chromium.googlesource.com/chromium/src.git', HttpClient()) | 94 'https://chromium.googlesource.com/chromium/src.git', HttpClient()) |
| 85 change_log = repo.GetChangeLog(revision) | 95 change_log = repo.GetChangeLog(revision) |
| 86 return change_log.commit_position, change_log.code_review_url | 96 return change_log.commit_position, change_log.code_review_url |
| 87 | 97 |
| 88 | 98 |
| 89 def _NotificationTimeLimitPassed(build_end_time, latency_limit_minutes): | 99 def _WithinNotificationTimeLimit(build_end_time, latency_limit_minutes): |
| 90 """Returns True if it is too late to send notification.""" | 100 """Returns True if it is still in time to send notification.""" |
| 91 latency_seconds = (time_util.GetUTCNow() - build_end_time).total_seconds() | 101 latency_seconds = (time_util.GetUTCNow() - build_end_time).total_seconds() |
| 92 return latency_seconds > latency_limit_minutes * 60 | 102 return latency_seconds <= latency_limit_minutes * 60 |
| 93 | 103 |
| 94 | 104 |
| 95 class SendNotificationForCulpritPipeline(BasePipeline): | 105 class SendNotificationForCulpritPipeline(BasePipeline): |
| 96 | 106 |
| 97 # Arguments number differs from overridden method - pylint: disable=W0221 | 107 # Arguments number differs from overridden method - pylint: disable=W0221 |
| 98 def run(self, master_name, builder_name, build_number, repo_name, revision): | 108 def run( |
| 109 self, master_name, builder_name, build_number, repo_name, revision, | |
| 110 send_notification_right_now): | |
| 99 action_settings = waterfall_config.GetActionSettings() | 111 action_settings = waterfall_config.GetActionSettings() |
| 100 # Set some impossible default values to prevent notification by default. | 112 # Set some impossible default values to prevent notification by default. |
| 101 build_threshold = action_settings.get( | 113 build_num_threshold = action_settings.get( |
| 102 'cr_notification_build_threshold', 100000) | 114 'cr_notification_build_threshold', 100000) |
| 103 latency_limit_minutes = action_settings.get( | 115 latency_limit_minutes = action_settings.get( |
| 104 'cr_notification_latency_limit_minutes', 1) | 116 'cr_notification_latency_limit_minutes', 1) |
| 105 | 117 |
| 106 commit_position, code_review_url = _GetCulpritInfo( | 118 commit_position, code_review_url = _GetCulpritInfo( |
| 107 repo_name, revision) | 119 repo_name, revision) |
| 108 build_end_time = build_util.GetBuildEndTime( | 120 build_end_time = build_util.GetBuildEndTime( |
| 109 master_name, builder_name, build_number) | 121 master_name, builder_name, build_number) |
| 110 time_limit_passed = _NotificationTimeLimitPassed( | 122 within_time_limit = _WithinNotificationTimeLimit( |
| 111 build_end_time, latency_limit_minutes) | 123 build_end_time, latency_limit_minutes) |
| 112 | 124 |
| 125 # Additional criteria that will help decide if a notification | |
| 126 # should be sent. | |
| 127 # TODO (chanli): Add check for if confidence for the culprit is | |
| 128 # over threshold. | |
| 129 additional_criteria = { | |
| 130 'within_time_limit': within_time_limit | |
| 131 } | |
| 132 | |
| 113 if not _ShouldSendNotification( | 133 if not _ShouldSendNotification( |
| 114 master_name, builder_name, build_number, repo_name, | 134 master_name, builder_name, build_number, repo_name, |
| 115 revision, commit_position, build_threshold, time_limit_passed): | 135 revision, commit_position, build_num_threshold, additional_criteria, |
| 136 send_notification_right_now): | |
| 116 return False | 137 return False |
| 117 return _SendNotificationForCulprit( | 138 return _SendNotificationForCulprit( |
| 118 repo_name, revision, commit_position, code_review_url) | 139 repo_name, revision, commit_position, code_review_url) |
| OLD | NEW |