| 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 import logging |
| 6 |
| 5 from common import appengine_util | 7 from common import appengine_util |
| 6 from common import constants | 8 from common import constants |
| 9 from common import time_util |
| 7 from common.pipeline_wrapper import BasePipeline | 10 from common.pipeline_wrapper import BasePipeline |
| 8 | 11 |
| 9 from model import analysis_status | 12 from model import analysis_status |
| 10 from model.flake.flake_swarming_task import FlakeSwarmingTask | 13 from model.flake.flake_swarming_task import FlakeSwarmingTask |
| 11 from model.flake.master_flake_analysis import MasterFlakeAnalysis | 14 from model.flake.master_flake_analysis import MasterFlakeAnalysis |
| 12 from waterfall import waterfall_config | 15 from waterfall import waterfall_config |
| 13 from waterfall.process_flake_swarming_task_result_pipeline import ( | 16 from waterfall.process_flake_swarming_task_result_pipeline import ( |
| 14 ProcessFlakeSwarmingTaskResultPipeline) | 17 ProcessFlakeSwarmingTaskResultPipeline) |
| 15 from waterfall.trigger_flake_swarming_task_pipeline import ( | 18 from waterfall.trigger_flake_swarming_task_pipeline import ( |
| 16 TriggerFlakeSwarmingTaskPipeline) | 19 TriggerFlakeSwarmingTaskPipeline) |
| 17 | 20 |
| 18 | 21 |
| 22 def _UpdateAnalysisStatusUponCompletion(master_flake_analysis, status, error): |
| 23 master_flake_analysis.completed_time = time_util.GetUTCNow() |
| 24 master_flake_analysis.status = status |
| 25 |
| 26 if error: |
| 27 master_flake_analysis.error = error |
| 28 |
| 29 master_flake_analysis.put() |
| 30 |
| 31 |
| 19 class RecursiveFlakePipeline(BasePipeline): | 32 class RecursiveFlakePipeline(BasePipeline): |
| 20 | 33 |
| 21 # Arguments number differs from overridden method - pylint: disable=W0221 | 34 # Arguments number differs from overridden method - pylint: disable=W0221 |
| 22 def run(self, master_name, builder_name, run_build_number, step_name, | 35 def run(self, master_name, builder_name, run_build_number, step_name, |
| 23 test_name, master_build_number, flakiness_algorithm_results_dict, | 36 test_name, version_number, master_build_number, |
| 24 queue_name=constants.DEFAULT_QUEUE): | 37 flakiness_algorithm_results_dict, queue_name=constants.DEFAULT_QUEUE): |
| 25 """Pipeline to determine the regression range of a flaky test. | 38 """Pipeline to determine the regression range of a flaky test. |
| 26 | 39 |
| 27 Args: | 40 Args: |
| 28 master_name (str): The master name. | 41 master_name (str): The master name. |
| 29 builder_name (str): The builder name. | 42 builder_name (str): The builder name. |
| 30 run_build_number (int): The build number of the current swarming rerun. | 43 run_build_number (int): The build number of the current swarming rerun. |
| 31 step_name (str): The step name. | 44 step_name (str): The step name. |
| 32 test_name (str): The test name. | 45 test_name (str): The test name. |
| 46 version_number (int): The version to save analysis results and data to. |
| 33 master_build_number (int): The build number of the Master_Flake_analysis. | 47 master_build_number (int): The build number of the Master_Flake_analysis. |
| 34 flakiness_algorithm_results_dict (dict): A dictionary used by | 48 flakiness_algorithm_results_dict (dict): A dictionary used by |
| 35 NextBuildNumberPipeline | 49 NextBuildNumberPipeline |
| 36 queue_name (str): Which queue to run on. | 50 queue_name (str): Which queue to run on. |
| 37 | 51 |
| 38 Returns: | 52 Returns: |
| 39 A dict of lists for reliable/flaky tests. | 53 A dict of lists for reliable/flaky tests. |
| 40 """ | 54 """ |
| 41 master = MasterFlakeAnalysis.Get(master_name, builder_name, | 55 flake_analysis = MasterFlakeAnalysis.GetVersion( |
| 42 master_build_number, step_name, test_name) | 56 master_name, builder_name, master_build_number, step_name, test_name, |
| 43 if master.status != analysis_status.RUNNING: # pragma: no branch | 57 version=version_number) |
| 44 master.status = analysis_status.RUNNING | 58 logging.info( |
| 45 master.put() | 59 'Running RecursiveFlakePipeline on MasterFlakeAnalysis %s/%s/%s/%s/%s', |
| 60 master_name, builder_name, master_build_number, step_name, test_name) |
| 61 logging.info( |
| 62 'MasterFlakeAnalysis %s version %s', flake_analysis, version_number) |
| 63 |
| 64 if flake_analysis.status != analysis_status.RUNNING: # pragma: no branch |
| 65 flake_analysis.status = analysis_status.RUNNING |
| 66 flake_analysis.put() |
| 46 | 67 |
| 47 # Call trigger pipeline (flake style). | 68 # Call trigger pipeline (flake style). |
| 48 task_id = yield TriggerFlakeSwarmingTaskPipeline( | 69 task_id = yield TriggerFlakeSwarmingTaskPipeline( |
| 49 master_name, builder_name, run_build_number, step_name, [test_name]) | 70 master_name, builder_name, run_build_number, step_name, [test_name]) |
| 50 # Pass the trigger pipeline into a process pipeline. | 71 # Pass the trigger pipeline into a process pipeline. |
| 51 test_result_future = yield ProcessFlakeSwarmingTaskResultPipeline( | 72 test_result_future = yield ProcessFlakeSwarmingTaskResultPipeline( |
| 52 master_name, builder_name, run_build_number, | 73 master_name, builder_name, run_build_number, |
| 53 step_name, task_id, master_build_number, test_name) | 74 step_name, task_id, master_build_number, test_name, version_number) |
| 54 yield NextBuildNumberPipeline( | 75 yield NextBuildNumberPipeline( |
| 55 master_name, builder_name, master_build_number, run_build_number, | 76 master_name, builder_name, master_build_number, run_build_number, |
| 56 step_name, test_name, test_result_future, queue_name, | 77 step_name, test_name, version_number, test_result_future, queue_name, |
| 57 flakiness_algorithm_results_dict) | 78 flakiness_algorithm_results_dict) |
| 58 | 79 |
| 59 | 80 |
| 60 def get_next_run(master, flakiness_algorithm_results_dict): | 81 def get_next_run(master_flake_analysis, flakiness_algorithm_results_dict): |
| 61 # A description of this algorithm can be found at: | 82 # A description of this algorithm can be found at: |
| 62 # https://docs.google.com/document/d/1wPYFZ5OT998Yn7O8wGDOhgfcQ98mknoX13AesJaS
6ig/edit | 83 # https://docs.google.com/document/d/1wPYFZ5OT998Yn7O8wGDOhgfcQ98mknoX13AesJaS
6ig/edit |
| 63 # Get the last result. | 84 # Get the last result. |
| 64 last_result = master.success_rates[-1] | 85 last_result = master_flake_analysis.data_points[-1].pass_rate |
| 65 cur_run = min(master.build_numbers) | 86 cur_run = min([d.build_number for d in master_flake_analysis.data_points]) |
| 66 flake_settings = waterfall_config.GetCheckFlakeSettings() | 87 flake_settings = waterfall_config.GetCheckFlakeSettings() |
| 67 lower_flake_threshold = flake_settings.get('lower_flake_threshold') | 88 lower_flake_threshold = flake_settings.get('lower_flake_threshold') |
| 68 upper_flake_threshold = flake_settings.get('upper_flake_threshold') | 89 upper_flake_threshold = flake_settings.get('upper_flake_threshold') |
| 69 max_stable_in_a_row = flake_settings.get('max_stable_in_a_row') | 90 max_stable_in_a_row = flake_settings.get('max_stable_in_a_row') |
| 70 max_flake_in_a_row = flake_settings.get('max_flake_in_a_row') | 91 max_flake_in_a_row = flake_settings.get('max_flake_in_a_row') |
| 71 | 92 |
| 72 if last_result < 0: # Test doesn't exist in the current build number. | 93 if last_result < 0: # Test doesn't exist in the current build number. |
| 73 flakiness_algorithm_results_dict['stable_in_a_row'] += 1 | 94 flakiness_algorithm_results_dict['stable_in_a_row'] += 1 |
| 74 flakiness_algorithm_results_dict['stabled_out'] = True | 95 flakiness_algorithm_results_dict['stabled_out'] = True |
| 75 flakiness_algorithm_results_dict['flaked_out'] = True | 96 flakiness_algorithm_results_dict['flaked_out'] = True |
| 76 flakiness_algorithm_results_dict['lower_boundary_result'] = 'STABLE' | 97 flakiness_algorithm_results_dict['lower_boundary_result'] = 'STABLE' |
| 77 | 98 |
| 78 lower_boundary = master.build_numbers[ | 99 lower_boundary = master_flake_analysis.data_points[ |
| 79 -flakiness_algorithm_results_dict['stable_in_a_row']] | 100 -flakiness_algorithm_results_dict['stable_in_a_row']].build_number |
| 80 | 101 |
| 81 flakiness_algorithm_results_dict['lower_boundary'] = lower_boundary | 102 flakiness_algorithm_results_dict['lower_boundary'] = lower_boundary |
| 82 flakiness_algorithm_results_dict['sequential_run_index'] += 1 | 103 flakiness_algorithm_results_dict['sequential_run_index'] += 1 |
| 83 return lower_boundary + 1 | 104 return lower_boundary + 1 |
| 84 elif (last_result < lower_flake_threshold or | 105 elif (last_result < lower_flake_threshold or |
| 85 last_result > upper_flake_threshold): # Stable result. | 106 last_result > upper_flake_threshold): # Stable result. |
| 86 flakiness_algorithm_results_dict['stable_in_a_row'] += 1 | 107 flakiness_algorithm_results_dict['stable_in_a_row'] += 1 |
| 87 if (flakiness_algorithm_results_dict['stable_in_a_row'] > | 108 if (flakiness_algorithm_results_dict['stable_in_a_row'] > |
| 88 max_stable_in_a_row): # Identified a stable region. | 109 max_stable_in_a_row): # Identified a stable region. |
| 89 flakiness_algorithm_results_dict['stabled_out'] = True | 110 flakiness_algorithm_results_dict['stabled_out'] = True |
| 90 if (flakiness_algorithm_results_dict['stabled_out'] and | 111 if (flakiness_algorithm_results_dict['stabled_out'] and |
| 91 not flakiness_algorithm_results_dict['flaked_out']): | 112 not flakiness_algorithm_results_dict['flaked_out']): |
| 92 # Identified a candidate for the upper boundary. | 113 # Identified a candidate for the upper boundary. |
| 93 # Earliest stable point to the right of a flaky region. | 114 # Earliest stable point to the right of a flaky region. |
| 94 flakiness_algorithm_results_dict['upper_boundary'] = cur_run | 115 flakiness_algorithm_results_dict['upper_boundary'] = cur_run |
| 95 flakiness_algorithm_results_dict['lower_boundary'] = None | 116 flakiness_algorithm_results_dict['lower_boundary'] = None |
| (...skipping 24 matching lines...) Expand all Loading... |
| 120 not flakiness_algorithm_results_dict['lower_boundary']): | 141 not flakiness_algorithm_results_dict['lower_boundary']): |
| 121 # Identified a candidate for the lower boundary. | 142 # Identified a candidate for the lower boundary. |
| 122 # Latest flaky point to the left of a stable region. | 143 # Latest flaky point to the left of a stable region. |
| 123 flakiness_algorithm_results_dict['lower_boundary'] = cur_run | 144 flakiness_algorithm_results_dict['lower_boundary'] = cur_run |
| 124 flakiness_algorithm_results_dict['lower_boundary_result'] = 'FLAKE' | 145 flakiness_algorithm_results_dict['lower_boundary_result'] = 'FLAKE' |
| 125 flakiness_algorithm_results_dict['stable_in_a_row'] = 0 | 146 flakiness_algorithm_results_dict['stable_in_a_row'] = 0 |
| 126 step_size = flakiness_algorithm_results_dict['flakes_in_a_row'] + 1 | 147 step_size = flakiness_algorithm_results_dict['flakes_in_a_row'] + 1 |
| 127 return cur_run - step_size | 148 return cur_run - step_size |
| 128 | 149 |
| 129 | 150 |
| 130 def sequential_next_run(master, flakiness_algorithm_results_dict): | 151 def sequential_next_run( |
| 131 last_result = master.success_rates[-1] | 152 master_flake_analysis, flakiness_algorithm_results_dict): |
| 153 last_result = master_flake_analysis.data_points[-1].pass_rate |
| 132 last_result_status = 'FLAKE' | 154 last_result_status = 'FLAKE' |
| 133 flake_settings = waterfall_config.GetCheckFlakeSettings() | 155 flake_settings = waterfall_config.GetCheckFlakeSettings() |
| 134 lower_flake_threshold = flake_settings.get('lower_flake_threshold') | 156 lower_flake_threshold = flake_settings.get('lower_flake_threshold') |
| 135 upper_flake_threshold = flake_settings.get('upper_flake_threshold') | 157 upper_flake_threshold = flake_settings.get('upper_flake_threshold') |
| 136 | 158 |
| 137 if (last_result < lower_flake_threshold or | 159 if (last_result < lower_flake_threshold or |
| 138 last_result > upper_flake_threshold): | 160 last_result > upper_flake_threshold): |
| 139 last_result_status = 'STABLE' | 161 last_result_status = 'STABLE' |
| 140 if flakiness_algorithm_results_dict['sequential_run_index'] > 0: | 162 if flakiness_algorithm_results_dict['sequential_run_index'] > 0: |
| 141 if (last_result_status != | 163 if (last_result_status != |
| 142 flakiness_algorithm_results_dict['lower_boundary_result']): | 164 flakiness_algorithm_results_dict['lower_boundary_result']): |
| 143 master.suspected_flake_build_number = ( | 165 master_flake_analysis.suspected_flake_build_number = ( |
| 144 flakiness_algorithm_results_dict['lower_boundary'] + | 166 flakiness_algorithm_results_dict['lower_boundary'] + |
| 145 flakiness_algorithm_results_dict['sequential_run_index']) | 167 flakiness_algorithm_results_dict['sequential_run_index']) |
| 146 master.put() | 168 master_flake_analysis.put() |
| 147 return 0 | 169 return 0 |
| 148 flakiness_algorithm_results_dict['sequential_run_index'] += 1 | 170 flakiness_algorithm_results_dict['sequential_run_index'] += 1 |
| 149 return (flakiness_algorithm_results_dict['lower_boundary'] + | 171 return (flakiness_algorithm_results_dict['lower_boundary'] + |
| 150 flakiness_algorithm_results_dict['sequential_run_index']) | 172 flakiness_algorithm_results_dict['sequential_run_index']) |
| 151 | 173 |
| 152 | 174 |
| 153 class NextBuildNumberPipeline(BasePipeline): | 175 class NextBuildNumberPipeline(BasePipeline): |
| 154 | 176 |
| 155 # Arguments number differs from overridden method - pylint: disable=W0221 | 177 # Arguments number differs from overridden method - pylint: disable=W0221 |
| 156 # Unused argument - pylint: disable=W0613 | 178 # Unused argument - pylint: disable=W0613 |
| 157 def run(self, master_name, builder_name, master_build_number, | 179 def run(self, master_name, builder_name, master_build_number, |
| 158 run_build_number, step_name, test_name, test_result_future, | 180 run_build_number, step_name, test_name, version_number, |
| 159 queue_name, flakiness_algorithm_results_dict): | 181 test_result_future, queue_name, flakiness_algorithm_results_dict): |
| 160 | 182 |
| 161 # Get MasterFlakeAnalysis success list corresponding to parameters. | 183 # Get MasterFlakeAnalysis success list corresponding to parameters. |
| 162 master = MasterFlakeAnalysis.Get(master_name, builder_name, | 184 master_flake_analysis = MasterFlakeAnalysis.GetVersion( |
| 163 master_build_number, step_name, test_name) | 185 master_name, builder_name, master_build_number, step_name, test_name, |
| 186 version=version_number) |
| 164 # Don't call another pipeline if we fail. | 187 # Don't call another pipeline if we fail. |
| 165 flake_swarming_task = FlakeSwarmingTask.Get( | 188 flake_swarming_task = FlakeSwarmingTask.Get( |
| 166 master_name, builder_name, run_build_number, step_name, test_name) | 189 master_name, builder_name, run_build_number, step_name, test_name) |
| 167 | 190 |
| 168 if flake_swarming_task.status == analysis_status.ERROR: | 191 if flake_swarming_task.status == analysis_status.ERROR: |
| 169 master.status = analysis_status.ERROR | 192 # TODO(lijeffrey): Implement more detailed error detection and reporting, |
| 170 master.put() | 193 # such as timeouts, dead bots, etc. |
| 194 error = { |
| 195 'error': 'Swarming task failed', |
| 196 'message': 'Swarming task failed' |
| 197 } |
| 198 _UpdateAnalysisStatusUponCompletion( |
| 199 master_flake_analysis, analysis_status.ERROR, error) |
| 171 return | 200 return |
| 172 | 201 |
| 173 # Figure out what build_number we should call, if any | 202 # Figure out what build_number we should call, if any |
| 174 if (flakiness_algorithm_results_dict['stabled_out'] and | 203 if (flakiness_algorithm_results_dict['stabled_out'] and |
| 175 flakiness_algorithm_results_dict['flaked_out']): | 204 flakiness_algorithm_results_dict['flaked_out']): |
| 176 next_run = sequential_next_run(master, flakiness_algorithm_results_dict) | 205 next_run = sequential_next_run( |
| 206 master_flake_analysis, flakiness_algorithm_results_dict) |
| 177 else: | 207 else: |
| 178 next_run = get_next_run(master, flakiness_algorithm_results_dict) | 208 next_run = get_next_run( |
| 209 master_flake_analysis, flakiness_algorithm_results_dict) |
| 179 | 210 |
| 180 if next_run < flakiness_algorithm_results_dict['last_build_number']: | 211 if next_run < flakiness_algorithm_results_dict['last_build_number']: |
| 181 next_run = 0 | 212 next_run = 0 |
| 182 elif next_run >= master_build_number: | 213 elif next_run >= master_build_number: |
| 183 next_run = 0 | 214 next_run = 0 |
| 184 | 215 |
| 185 if next_run: | 216 if next_run: |
| 186 pipeline_job = RecursiveFlakePipeline( | 217 pipeline_job = RecursiveFlakePipeline( |
| 187 master_name, builder_name, next_run, step_name, test_name, | 218 master_name, builder_name, next_run, step_name, test_name, |
| 188 master_build_number, | 219 version_number, master_build_number, |
| 189 flakiness_algorithm_results_dict=flakiness_algorithm_results_dict) | 220 flakiness_algorithm_results_dict=flakiness_algorithm_results_dict) |
| 190 # pylint: disable=W0201 | 221 # pylint: disable=W0201 |
| 191 pipeline_job.target = appengine_util.GetTargetNameForModule( | 222 pipeline_job.target = appengine_util.GetTargetNameForModule( |
| 192 constants.WATERFALL_BACKEND) | 223 constants.WATERFALL_BACKEND) |
| 193 pipeline_job.start(queue_name=queue_name) | 224 pipeline_job.start(queue_name=queue_name) |
| 194 else: | 225 else: |
| 195 master.status = analysis_status.COMPLETED | 226 _UpdateAnalysisStatusUponCompletion( |
| 196 master.put() | 227 master_flake_analysis, analysis_status.COMPLETED, None) |
| OLD | NEW |