| OLD | NEW |
| 1 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 # Copyright (c) 2012 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 import re | 5 import re |
| 6 | 6 |
| 7 from twisted.python import log | 7 from twisted.python import log |
| 8 | 8 |
| 9 from buildbot.status.builder import FAILURE, SUCCESS | 9 from buildbot.status.builder import FAILURE, SUCCESS |
| 10 | 10 |
| (...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 54 | 54 |
| 55 def _InitRecentResults(self): | 55 def _InitRecentResults(self): |
| 56 """Initializes a new failures history object to store results.""" | 56 """Initializes a new failures history object to store results.""" |
| 57 self.recent_results = FailuresHistory(expiration_time=_EXPIRATION_TIME, | 57 self.recent_results = FailuresHistory(expiration_time=_EXPIRATION_TIME, |
| 58 size_limit=1000) | 58 size_limit=1000) |
| 59 | 59 |
| 60 def _InitNewEmailResults(self): | 60 def _InitNewEmailResults(self): |
| 61 """Initializes a new email results used by each email sent.""" | 61 """Initializes a new email results used by each email sent.""" |
| 62 self.new_email_results = {REGRESS: [], IMPROVE: []} | 62 self.new_email_results = {REGRESS: [], IMPROVE: []} |
| 63 | 63 |
| 64 def _UpdateResults(self, results): | 64 def _UpdateResults(self, builder_name, results): |
| 65 """Updates the results by adding/removing from the history. | 65 """Updates the results by adding/removing from the history. |
| 66 | 66 |
| 67 Args: | 67 Args: |
| 68 results: List of result tuples, each tuple is of the form | 68 results: List of result tuples, each tuple is of the form |
| 69 ('REGRESS|IMPROVE', 'value_name'). | 69 ('REGRESS|IMPROVE', 'value_name', 'builder'). |
| 70 """ | 70 """ |
| 71 new_results_ids = [' '.join(result) for result in results] | 71 new_results_ids = [' '.join(result) for result in results] |
| 72 # Delete the old results if the new results do not have them. | 72 # Delete the old results if the new results do not have them. |
| 73 to_delete = [old_id for old_id in self.recent_results.failures | 73 to_delete = [old_id for old_id in self.recent_results.failures |
| 74 if old_id not in new_results_ids] | 74 if (old_id not in new_results_ids and |
| 75 old_id.endswith(builder_name))] |
| 75 | 76 |
| 76 for old_id in to_delete: | 77 for old_id in to_delete: |
| 77 self._DeleteResult(old_id) | 78 self._DeleteResult(old_id) |
| 78 | 79 |
| 79 # Update the new results history | 80 # Update the new results history |
| 80 for new_id in results: | 81 for new_id in results: |
| 81 self._StoreResult(new_id) | 82 self._StoreResult(new_id) |
| 82 | 83 |
| 83 def _StoreResult(self, result): | 84 def _StoreResult(self, result): |
| 84 """Stores the result value and removes counter results. | 85 """Stores the result value and removes counter results. |
| 85 | 86 |
| 86 Example: if this is a REGRESS result then it is stored and its counter | 87 Example: if this is a REGRESS result then it is stored and its counter |
| 87 IMPROVE result, if any, is reset. | 88 IMPROVE result, if any, is reset. |
| 88 | 89 |
| 89 Args: | 90 Args: |
| 90 result: A tuple of the form ('REGRESS|IMPROVE', 'value_name'). | 91 result: A tuple of the form ('REGRESS|IMPROVE', 'value_name', 'builder'). |
| 91 """ | 92 """ |
| 92 self.recent_results.Put(' '.join(result)) | 93 self.recent_results.Put(' '.join(result)) |
| 93 if result[0] == REGRESS: | 94 if result[0] == REGRESS: |
| 94 counter_id = IMPROVE + ' ' + result[1] | 95 counter_id = IMPROVE + ' '.join(result[1:]) |
| 95 else: | 96 else: |
| 96 counter_id = REGRESS + ' ' + result[1] | 97 counter_id = REGRESS + ' '.join(result[1:]) |
| 97 # Reset counter_id count since this breaks the consecutive count of it. | 98 # Reset counter_id count since this breaks the consecutive count of it. |
| 98 self._DeleteResult(counter_id) | 99 self._DeleteResult(counter_id) |
| 99 | 100 |
| 100 def _DeleteResult(self, result_id): | 101 def _DeleteResult(self, result_id): |
| 101 """Removes the history of results identified by result_id. | 102 """Removes the history of results identified by result_id. |
| 102 | 103 |
| 103 Args: | 104 Args: |
| 104 result_id: The id of the history entry (see _StoreResult() for details). | 105 result_id: The id of the history entry (see _StoreResult() for details). |
| 105 """ | 106 """ |
| 106 num_results = self.recent_results.GetCount(result_id) | 107 num_results = self.recent_results.GetCount(result_id) |
| 107 if num_results > 0: | 108 if num_results > 0: |
| 108 # This is a hack into FailuresHistory since it does not allow to delete | 109 # This is a hack into FailuresHistory since it does not allow to delete |
| 109 # entries in its history unless they are expired. | 110 # entries in its history unless they are expired. |
| 110 # FailuresHistory.failures_count is the total number of entries in the | 111 # FailuresHistory.failures_count is the total number of entries in the |
| 111 # history limitted by FailuresHistory.size_limit. | 112 # history limitted by FailuresHistory.size_limit. |
| 112 del self.recent_results.failures[result_id] | 113 del self.recent_results.failures[result_id] |
| 113 self.recent_results.failures_count -= num_results | 114 self.recent_results.failures_count -= num_results |
| 114 | 115 |
| 116 def _DeleteAllForBuild(self, builder_name): |
| 117 """Deletes all results related to a builder.""" |
| 118 to_delete = [result for result in self.recent_results.failures |
| 119 if result.endswith(builder_name)] |
| 120 for result in to_delete: |
| 121 self._DeleteResult(result) |
| 122 |
| 115 def _IsPerfStep(self, step_status): | 123 def _IsPerfStep(self, step_status): |
| 116 """Checks if the step name is one of the defined perf tests names.""" | 124 """Checks if the step name is one of the defined perf tests names.""" |
| 117 return self.getName(step_status) in self.step_names | 125 return self.getName(step_status) in self.step_names |
| 118 | 126 |
| 119 def isInterestingStep(self, build_status, step_status, results): | 127 def isInterestingStep(self, build_status, step_status, results): |
| 120 """Ignore the step if it is not one of the perf results steps. | 128 """Ignore the step if it is not one of the perf results steps. |
| 121 | 129 |
| 122 Returns: | 130 Returns: |
| 123 True: - if a REGRESS|IMPROVE happens consecutive minimum number of times. | 131 True: - if a REGRESS|IMPROVE happens consecutive minimum number of times. |
| 124 - if it is not a SUCCESS step and neither REGRESS|IMPROVE. | 132 - if it is not a SUCCESS step and neither REGRESS|IMPROVE. |
| 125 False: - if it is a SUCCESS step. | 133 False: - if it is a SUCCESS step. |
| 126 - if it is a notification which has already been notified. | 134 - if it is a notification which has already been notified. |
| 127 """ | 135 """ |
| 128 if not self._IsPerfStep(step_status): | 136 if not self._IsPerfStep(step_status): |
| 129 return False | 137 return False |
| 130 | 138 |
| 131 # In case of exceptions, sometimes results output is empty. | 139 # In case of exceptions, sometimes results output is empty. |
| 132 if not results: | 140 if not results: |
| 133 results = [FAILURE] | 141 results = [FAILURE] |
| 134 | 142 |
| 143 builder_name = build_status.getName() |
| 135 # If it is a success step, i.e. not interesting, then reset counters. | 144 # If it is a success step, i.e. not interesting, then reset counters. |
| 136 if results[0] == SUCCESS: | 145 if results[0] == SUCCESS: |
| 137 self._InitRecentResults() | 146 self._DeleteAllForBuild(builder_name) |
| 138 return False | 147 return False |
| 139 | 148 |
| 140 # step_text is similar to: | 149 # step_text is similar to: |
| 141 # media_tests_av_perf <div class="BuildResultInfo"> PERF_REGRESS: | 150 # media_tests_av_perf <div class="BuildResultInfo"> PERF_REGRESS: |
| 142 # time/t (89.07%) PERF_IMPROVE: fps/video (5.40%) </div> | 151 # time/t (89.07%) PERF_IMPROVE: fps/video (5.40%) </div> |
| 143 # | 152 # |
| 144 # regex would return tuples of the form: | 153 # regex would return tuples of the form: |
| 145 # ('REGRESS', 'time/t') | 154 # ('REGRESS', 'time/t', 'linux-rel') |
| 146 # ('IMPROVE', 'fps/video') | 155 # ('IMPROVE', 'fps/video', 'win-debug') |
| 156 # |
| 157 # It is important to put the builder name as the last element in the tuple |
| 158 # since it is used to check tests that belong to same builder. |
| 147 step_text = ' '.join(step_status.getText()) | 159 step_text = ' '.join(step_status.getText()) |
| 148 log.msg('[PerfCountNotifier] Analyzing failure text: %s.' % step_text) | 160 log.msg('[PerfCountNotifier] Analyzing failure text: %s.' % step_text) |
| 149 | 161 |
| 150 perf_regress = perf_improve = '' | 162 perf_regress = perf_improve = '' |
| 151 perf_results = [] | 163 perf_results = [] |
| 152 | |
| 153 if PERF_REGRESS in step_text: | 164 if PERF_REGRESS in step_text: |
| 154 perf_regress = step_text[step_text.find(PERF_REGRESS) + len(PERF_REGRESS) | 165 perf_regress = step_text[step_text.find(PERF_REGRESS) + len(PERF_REGRESS) |
| 155 + 1: step_text.find(PERF_IMPROVE)] | 166 + 1: step_text.find(PERF_IMPROVE)] |
| 156 perf_results.extend([(REGRESS, test_name) for test_name in | 167 perf_results.extend([(REGRESS, test_name, builder_name) for test_name in |
| 157 re.findall('(\S+) (?=\(.+\))', perf_regress)]) | 168 re.findall('(\S+) (?=\(.+\))', perf_regress)]) |
| 158 | 169 |
| 159 if PERF_IMPROVE in step_text: | 170 if PERF_IMPROVE in step_text: |
| 160 # Based on log_parser/process_log.py PerformanceChangesAsText() function, | 171 # Based on log_parser/process_log.py PerformanceChangesAsText() function, |
| 161 # we assume that PERF_REGRESS (if any) appears before PERF_IMPROVE. | 172 # we assume that PERF_REGRESS (if any) appears before PERF_IMPROVE. |
| 162 perf_improve = step_text[step_text.find(PERF_IMPROVE) + len(PERF_IMPROVE) | 173 perf_improve = step_text[step_text.find(PERF_IMPROVE) + len(PERF_IMPROVE) |
| 163 + 1:] | 174 + 1:] |
| 164 perf_results.extend([(IMPROVE, test_name) for test_name in | 175 perf_results.extend([(IMPROVE, test_name, builder_name) for test_name in |
| 165 re.findall('(\S+) (?=\(.+\))', perf_improve)]) | 176 re.findall('(\S+) (?=\(.+\))', perf_improve)]) |
| 166 | 177 |
| 167 # If there is no regress or improve then this could be warning or exception. | 178 # If there is no regress or improve then this could be warning or exception. |
| 168 if not perf_results: | 179 if not perf_results: |
| 169 if not self.notifications.GetCount(step_text): | 180 if not self.notifications.GetCount(step_text): |
| 170 log.msg('[PerfCountNotifier] Unrecognized step status encountered. ' | 181 log.msg('[PerfCountNotifier] Unrecognized step status encountered. ' |
| 171 'Reporting status as interesting.') | 182 'Reporting status as interesting.') |
| 172 self.notifications.Put(step_text) | 183 self.notifications.Put(step_text) |
| 173 return True | 184 return True |
| 174 else: | 185 else: |
| 175 log.msg('[PerfCountNotifier] This problem has already been notified.') | 186 log.msg('[PerfCountNotifier] This problem has already been notified.') |
| 176 return False | 187 return False |
| 177 | 188 |
| 178 is_interesting = False | 189 is_interesting = False |
| 179 update_list = [] | 190 update_list = [] |
| 180 for result in perf_results: | 191 for result in perf_results: |
| 181 if len(result) != 2: | 192 if len(result) != 3: |
| 182 # We expect a tuple similar to ('REGRESS', 'time/t') | 193 # We expect a tuple similar to ('REGRESS', 'time/t', 'linux-rel') |
| 183 continue | 194 continue |
| 184 result_id = ' '.join(result) | 195 result_id = ' '.join(result) |
| 185 update_list.append(result) | 196 update_list.append(result) |
| 186 log.msg('[PerfCountNotifier] Result: %s happened %d times in a row.' % | 197 log.msg('[PerfCountNotifier] Result: %s happened %d times in a row.' % |
| 187 (result_id, self.recent_results.GetCount(result_id) + 1)) | 198 (result_id, self.recent_results.GetCount(result_id) + 1)) |
| 188 if self.recent_results.GetCount(result_id) >= self.minimum_count - 1: | 199 if self.recent_results.GetCount(result_id) >= self.minimum_count - 1: |
| 189 # This is an interesting result! We got the minimum consecutive count of | 200 # This is an interesting result! We got the minimum consecutive count of |
| 190 # this result, however we still need to check if its been notified. | 201 # this result, however we still need to check if its been notified. |
| 191 if not self.notifications.GetCount(result_id): | 202 if not self.notifications.GetCount(result_id): |
| 192 log.msg('[PerfCountNotifier] Result: %s happened enough consecutive ' | 203 log.msg('[PerfCountNotifier] Result: %s happened enough consecutive ' |
| 193 'times to be reported.' % result_id) | 204 'times to be reported.' % result_id) |
| 194 self.notifications.Put(result_id) | 205 self.notifications.Put(result_id) |
| 195 # New results that cause email notifications. | 206 # New results that cause email notifications. |
| 196 self.new_email_results[result[0]].append(result[1]) | 207 self.new_email_results[result[0]].append(result[1]) |
| 197 is_interesting = True | 208 is_interesting = True |
| 198 else: | 209 else: |
| 199 log.msg('[PerfCountNotifier] Result: %s has already been notified.' % | 210 log.msg('[PerfCountNotifier] Result: %s has already been notified.' % |
| 200 result_id) | 211 result_id) |
| 201 | 212 |
| 202 self._UpdateResults(update_list) | 213 self._UpdateResults(builder_name, update_list) |
| 203 | 214 |
| 204 return is_interesting | 215 return is_interesting |
| 205 | 216 |
| 206 def buildMessage(self, builder_name, build_status, results, step_name): | 217 def buildMessage(self, builder_name, build_status, results, step_name): |
| 207 """Send an email about this interesting step. | 218 """Send an email about this interesting step. |
| 208 | 219 |
| 209 Add the perf regressions/improvements that resulted in this email if any. | 220 Add the perf regressions/improvements that resulted in this email if any. |
| 210 """ | 221 """ |
| 211 original_header = self.status_header | 222 original_header = self.status_header |
| 212 msg = '' | 223 msg = '' |
| 213 if self.new_email_results[REGRESS]: | 224 if self.new_email_results[REGRESS]: |
| 214 msg += '%s: %s.\n' % (PERF_REGRESS, | 225 msg += '%s: %s.\n' % (PERF_REGRESS, |
| 215 ', '.join(self.new_email_results[REGRESS])) | 226 ', '.join(self.new_email_results[REGRESS])) |
| 216 if self.new_email_results[IMPROVE]: | 227 if self.new_email_results[IMPROVE]: |
| 217 msg += '%s: %s.\n' % (PERF_IMPROVE, | 228 msg += '%s: %s.\n' % (PERF_IMPROVE, |
| 218 ', '.join(self.new_email_results[IMPROVE])) | 229 ', '.join(self.new_email_results[IMPROVE])) |
| 219 if msg: | 230 if msg: |
| 220 self.status_header += ('\n\nNew perf results in this email:\n%s' % msg) | 231 self.status_header += ('\n\nNew perf results in this email:\n%s' % msg) |
| 221 email_msg = ChromiumNotifier.buildMessage(self, builder_name, build_status, | 232 email_msg = ChromiumNotifier.buildMessage(self, builder_name, build_status, |
| 222 results, step_name) | 233 results, step_name) |
| 223 # Reset header and notification list. | 234 # Reset header and notification list. |
| 224 self.status_header = original_header | 235 self.status_header = original_header |
| 225 self._InitNewEmailResults() | 236 self._InitNewEmailResults() |
| 226 return email_msg | 237 return email_msg |
| OLD | NEW |