| OLD | NEW |
| 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 """This module is to provide Findit service APIs through Cloud Endpoints: | 5 """This module is to provide Findit service APIs through Cloud Endpoints: |
| 6 | 6 |
| 7 Current APIs include: | 7 Current APIs include: |
| 8 1. Analysis of compile/test failures in Chromium waterfalls. | 8 1. Analysis of compile/test failures in Chromium waterfalls. |
| 9 Analyzes failures and detects suspected CLs. | 9 Analyzes failures and detects suspected CLs. |
| 10 2. Analysis of flakes on Commit Queue. | 10 2. Analysis of flakes on Commit Queue. |
| 11 """ | 11 """ |
| 12 | 12 |
| 13 import json | 13 import json |
| 14 import logging | 14 import logging |
| 15 import pickle | 15 import pickle |
| 16 | 16 |
| 17 import endpoints | 17 import endpoints |
| 18 from google.appengine.api import taskqueue | 18 from google.appengine.api import taskqueue |
| 19 from protorpc import messages | 19 from protorpc import messages |
| 20 from protorpc import remote | 20 from protorpc import remote |
| 21 | 21 |
| 22 from common import appengine_util | 22 from common import appengine_util |
| 23 from common import auth_util | 23 from common import auth_util |
| 24 from common import constants | 24 from common import constants |
| 25 from common import time_util | 25 from common import time_util |
| 26 from common.waterfall import failure_type | 26 from common.waterfall import failure_type |
| 27 from model import analysis_approach_type |
| 27 from model.flake.flake_analysis_request import FlakeAnalysisRequest | 28 from model.flake.flake_analysis_request import FlakeAnalysisRequest |
| 29 from model.suspected_cl_confidence import SuspectedCLConfidence |
| 28 from model.wf_analysis import WfAnalysis | 30 from model.wf_analysis import WfAnalysis |
| 31 from model.wf_suspected_cl import WfSuspectedCL |
| 29 from model.wf_swarming_task import WfSwarmingTask | 32 from model.wf_swarming_task import WfSwarmingTask |
| 30 from model.wf_try_job import WfTryJob | 33 from model.wf_try_job import WfTryJob |
| 34 from waterfall import build_util |
| 31 from waterfall import buildbot | 35 from waterfall import buildbot |
| 36 from waterfall import suspected_cl_util |
| 32 from waterfall import waterfall_config | 37 from waterfall import waterfall_config |
| 33 from waterfall.flake import flake_analysis_service | 38 from waterfall.flake import flake_analysis_service |
| 34 | 39 |
| 35 | 40 |
| 36 # This is used by the underlying ProtoRpc when creating names for the ProtoRPC | 41 # This is used by the underlying ProtoRpc when creating names for the ProtoRPC |
| 37 # messages below. This package name will show up as a prefix to the message | 42 # messages below. This package name will show up as a prefix to the message |
| 38 # class names in the discovery doc and client libraries. | 43 # class names in the discovery doc and client libraries. |
| 39 package = 'FindIt' | 44 package = 'FindIt' |
| 40 | 45 |
| 41 | 46 |
| 42 # These subclasses of Message are basically definitions of Protocol RPC | 47 # These subclasses of Message are basically definitions of Protocol RPC |
| 43 # messages. https://cloud.google.com/appengine/docs/python/tools/protorpc/ | 48 # messages. https://cloud.google.com/appengine/docs/python/tools/protorpc/ |
| 44 class _BuildFailure(messages.Message): | 49 class _BuildFailure(messages.Message): |
| 45 master_url = messages.StringField(1, required=True) | 50 master_url = messages.StringField(1, required=True) |
| 46 builder_name = messages.StringField(2, required=True) | 51 builder_name = messages.StringField(2, required=True) |
| 47 build_number = messages.IntegerField(3, variant=messages.Variant.INT32, | 52 build_number = messages.IntegerField(3, variant=messages.Variant.INT32, |
| 48 required=True) | 53 required=True) |
| 49 # All failed steps of the build reported by the client. | 54 # All failed steps of the build reported by the client. |
| 50 failed_steps = messages.StringField(4, repeated=True, required=False) | 55 failed_steps = messages.StringField(4, repeated=True, required=False) |
| 51 | 56 |
| 52 | 57 |
| 53 class _BuildFailureCollection(messages.Message): | 58 class _BuildFailureCollection(messages.Message): |
| 54 """Represents a request from a client, eg. builder_alerts.""" | 59 """Represents a request from a client, eg. builder_alerts.""" |
| 55 builds = messages.MessageField(_BuildFailure, 1, repeated=True) | 60 builds = messages.MessageField(_BuildFailure, 1, repeated=True) |
| 56 | 61 |
| 57 | 62 |
| 63 class _AnalysisApproach(messages.Enum): |
| 64 HEURISTIC = analysis_approach_type.HEURISTIC |
| 65 TRY_JOB = analysis_approach_type.TRY_JOB |
| 66 |
| 67 |
| 58 class _SuspectedCL(messages.Message): | 68 class _SuspectedCL(messages.Message): |
| 59 repo_name = messages.StringField(1, required=True) | 69 repo_name = messages.StringField(1, required=True) |
| 60 revision = messages.StringField(2, required=True) | 70 revision = messages.StringField(2, required=True) |
| 61 commit_position = messages.IntegerField(3, variant=messages.Variant.INT32) | 71 commit_position = messages.IntegerField(3, variant=messages.Variant.INT32) |
| 72 confidence = messages.IntegerField(4, variant=messages.Variant.INT32) |
| 73 analysis_approach = messages.EnumField(_AnalysisApproach, 5) |
| 62 | 74 |
| 63 | 75 |
| 64 class _AnalysisApproach(messages.Enum): | 76 class _TryJobStatus(messages.Enum): |
| 65 HEURISTIC = 1 | 77 # Try job is pending or running. Can expect result from try job. |
| 66 TRY_JOB = 2 | 78 RUNNING = 1 |
| 79 # There is no try job, try job completed or try job finished with error. |
| 80 # Result from try job is ready or no need to continue waiting for it. |
| 81 FINISHED = 2 |
| 67 | 82 |
| 68 | 83 |
| 69 class _BuildFailureAnalysisResult(messages.Message): | 84 class _BuildFailureAnalysisResult(messages.Message): |
| 70 master_url = messages.StringField(1, required=True) | 85 master_url = messages.StringField(1, required=True) |
| 71 builder_name = messages.StringField(2, required=True) | 86 builder_name = messages.StringField(2, required=True) |
| 72 build_number = messages.IntegerField(3, variant=messages.Variant.INT32, | 87 build_number = messages.IntegerField(3, variant=messages.Variant.INT32, |
| 73 required=True) | 88 required=True) |
| 74 step_name = messages.StringField(4, required=True) | 89 step_name = messages.StringField(4, required=True) |
| 75 is_sub_test = messages.BooleanField(5, variant=messages.Variant.BOOL, | 90 is_sub_test = messages.BooleanField(5, variant=messages.Variant.BOOL, |
| 76 required=True) | 91 required=True) |
| 77 test_name = messages.StringField(6) | 92 test_name = messages.StringField(6) |
| 78 first_known_failed_build_number = messages.IntegerField( | 93 first_known_failed_build_number = messages.IntegerField( |
| 79 7, variant=messages.Variant.INT32) | 94 7, variant=messages.Variant.INT32) |
| 80 suspected_cls = messages.MessageField(_SuspectedCL, 8, repeated=True) | 95 suspected_cls = messages.MessageField(_SuspectedCL, 8, repeated=True) |
| 81 analysis_approach = messages.EnumField(_AnalysisApproach, 9) | 96 analysis_approach = messages.EnumField(_AnalysisApproach, 9) |
| 97 try_job_status = messages.EnumField(_TryJobStatus, 10) |
| 98 is_flaky_test = messages.BooleanField(11, variant=messages.Variant.BOOL) |
| 82 | 99 |
| 83 | 100 |
| 84 class _BuildFailureAnalysisResultCollection(messages.Message): | 101 class _BuildFailureAnalysisResultCollection(messages.Message): |
| 85 """Represents a response to the client, eg. builder_alerts.""" | 102 """Represents a response to the client, eg. builder_alerts.""" |
| 86 results = messages.MessageField(_BuildFailureAnalysisResult, 1, repeated=True) | 103 results = messages.MessageField(_BuildFailureAnalysisResult, 1, repeated=True) |
| 87 | 104 |
| 88 | 105 |
| 89 class _BuildStep(messages.Message): | 106 class _BuildStep(messages.Message): |
| 90 master_name = messages.StringField(1, required=True) | 107 master_name = messages.StringField(1, required=True) |
| 91 builder_name = messages.StringField(2, required=True) | 108 builder_name = messages.StringField(2, required=True) |
| (...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 132 payload=payload, target=target, | 149 payload=payload, target=target, |
| 133 queue_name=constants.WATERFALL_FLAKE_ANALYSIS_REQUEST_QUEUE) | 150 queue_name=constants.WATERFALL_FLAKE_ANALYSIS_REQUEST_QUEUE) |
| 134 | 151 |
| 135 | 152 |
| 136 # Create a Cloud Endpoints API. | 153 # Create a Cloud Endpoints API. |
| 137 # https://cloud.google.com/appengine/docs/python/endpoints/create_api | 154 # https://cloud.google.com/appengine/docs/python/endpoints/create_api |
| 138 @endpoints.api(name='findit', version='v1', description='FindIt API') | 155 @endpoints.api(name='findit', version='v1', description='FindIt API') |
| 139 class FindItApi(remote.Service): | 156 class FindItApi(remote.Service): |
| 140 """FindIt API v1.""" | 157 """FindIt API v1.""" |
| 141 | 158 |
| 159 def _GetConfidenceAndApproachForCL( |
| 160 self, repo_name, revision, confidences, build, first_failure): |
| 161 cl = WfSuspectedCL.Get(repo_name, revision) |
| 162 if not cl: |
| 163 return None, None |
| 164 |
| 165 master_name = buildbot.GetMasterNameFromUrl(build.master_url) |
| 166 builder_name = build.builder_name |
| 167 current_build = build.build_number |
| 168 |
| 169 # If the CL is found by a try job, only the first failure will be recorded. |
| 170 # So we might need to go to the first failure to get CL information. |
| 171 build_info = (cl.GetBuildInfo(master_name, builder_name, current_build) or |
| 172 cl.GetBuildInfo(master_name, builder_name, first_failure)) |
| 173 |
| 174 confidence = suspected_cl_util.GetSuspectedCLConfidenceScore( |
| 175 confidences, build_info) |
| 176 |
| 177 cl_approach = ( |
| 178 _AnalysisApproach.TRY_JOB if analysis_approach_type.TRY_JOB in |
| 179 build_info['approaches'] else _AnalysisApproach.HEURISTIC) |
| 180 |
| 181 return confidence, cl_approach |
| 182 |
| 142 def _GenerateBuildFailureAnalysisResult( | 183 def _GenerateBuildFailureAnalysisResult( |
| 143 self, build, suspected_cls_in_result, step_name, | 184 self, build, suspected_cls_in_result, step_name, first_failure, test_name, |
| 144 first_failure, test_name=None, | 185 analysis_approach, confidences, try_job_status, is_flaky_test): |
| 145 analysis_approach=_AnalysisApproach.HEURISTIC): | 186 |
| 146 suspected_cls = [] | 187 suspected_cls = [] |
| 147 for suspected_cl in suspected_cls_in_result: | 188 for suspected_cl in suspected_cls_in_result: |
| 189 repo_name = suspected_cl['repo_name'] |
| 190 revision = suspected_cl['revision'] |
| 191 commit_position = suspected_cl['commit_position'] |
| 192 confidence, cl_approach = self._GetConfidenceAndApproachForCL( |
| 193 repo_name, revision, confidences, build, first_failure) |
| 194 cl_approach = cl_approach or analysis_approach |
| 195 |
| 148 suspected_cls.append(_SuspectedCL( | 196 suspected_cls.append(_SuspectedCL( |
| 149 repo_name=suspected_cl['repo_name'], | 197 repo_name=repo_name, revision=revision, |
| 150 revision=suspected_cl['revision'], | 198 commit_position=commit_position, confidence=confidence, |
| 151 commit_position=suspected_cl['commit_position'])) | 199 analysis_approach=cl_approach)) |
| 152 | 200 |
| 153 return _BuildFailureAnalysisResult( | 201 return _BuildFailureAnalysisResult( |
| 154 master_url=build.master_url, | 202 master_url=build.master_url, |
| 155 builder_name=build.builder_name, | 203 builder_name=build.builder_name, |
| 156 build_number=build.build_number, | 204 build_number=build.build_number, |
| 157 step_name=step_name, | 205 step_name=step_name, |
| 158 is_sub_test=test_name is not None, | 206 is_sub_test=test_name is not None, |
| 159 test_name=test_name, | 207 test_name=test_name, |
| 160 first_known_failed_build_number=first_failure, | 208 first_known_failed_build_number=first_failure, |
| 161 suspected_cls=suspected_cls, | 209 suspected_cls=suspected_cls, |
| 162 analysis_approach=analysis_approach) | 210 analysis_approach=analysis_approach, |
| 211 try_job_status=try_job_status, |
| 212 is_flaky_test=is_flaky_test) |
| 163 | 213 |
| 164 def _GetCulpritFromTryJob( | 214 def _GetStatusAndCulpritFromTryJob( |
| 165 self, try_job_map, build_failure_type, step_name, test_name=None): | 215 self, try_job_map, build_failure_type, step_name, test_name=None): |
| 166 """Returns the culprit found by try-job for the given step or test.""" | 216 """Returns the culprit found by try-job for the given step or test.""" |
| 167 if not try_job_map: | 217 if not try_job_map: |
| 168 return None | 218 return _TryJobStatus.FINISHED, None |
| 169 | 219 |
| 170 if test_name is None: | 220 if test_name is None: |
| 171 try_job_key = try_job_map.get(step_name) | 221 try_job_key = try_job_map.get(step_name) |
| 172 else: | 222 else: |
| 173 try_job_key = try_job_map.get(step_name, {}).get(test_name) | 223 try_job_key = try_job_map.get(step_name, {}).get(test_name) |
| 174 | 224 |
| 175 if not try_job_key: | 225 if not try_job_key: |
| 176 return None | 226 return _TryJobStatus.FINISHED, None |
| 177 | 227 |
| 178 try_job = WfTryJob.Get(*try_job_key.split('/')) | 228 try_job = WfTryJob.Get(*build_util.GetBuildInfoFromId(try_job_key)) |
| 179 if not try_job or not try_job.completed or try_job.failed: | 229 if not try_job or try_job.failed: |
| 180 return None | 230 return _TryJobStatus.FINISHED, None |
| 231 |
| 232 if not try_job.completed: |
| 233 return _TryJobStatus.RUNNING, None |
| 181 | 234 |
| 182 if build_failure_type == failure_type.COMPILE: | 235 if build_failure_type == failure_type.COMPILE: |
| 183 if not try_job.compile_results: # pragma: no cover. | 236 if not try_job.compile_results: # pragma: no cover. |
| 184 return None | 237 return _TryJobStatus.FINISHED, None |
| 185 return try_job.compile_results[-1].get('culprit', {}).get(step_name) | 238 return ( |
| 239 _TryJobStatus.FINISHED, |
| 240 try_job.compile_results[-1].get('culprit', {}).get(step_name)) |
| 186 | 241 |
| 187 if not try_job.test_results: # pragma: no cover. | 242 if not try_job.test_results: # pragma: no cover. |
| 188 return None | 243 return _TryJobStatus.FINISHED, None |
| 189 | 244 |
| 190 if test_name is None: | 245 if test_name is None: |
| 191 step_info = try_job.test_results[-1].get('culprit', {}).get(step_name) | 246 step_info = try_job.test_results[-1].get('culprit', {}).get(step_name) |
| 192 if not step_info or step_info.get('tests'): # pragma: no cover. | 247 if not step_info or step_info.get('tests'): # pragma: no cover. |
| 193 # TODO(chanli): For some steps like checkperms/sizes/etc, the culprit | 248 # TODO(chanli): For some steps like checkperms/sizes/etc, the culprit |
| 194 # finding try-job might have test-level results. | 249 # finding try-job might have test-level results. |
| 195 return None | 250 return _TryJobStatus.FINISHED, None |
| 196 return step_info | 251 return _TryJobStatus.FINISHED, step_info |
| 197 | 252 |
| 198 task = WfSwarmingTask.Get(*try_job_key.split('/'), step_name=step_name) | 253 task = WfSwarmingTask.Get(*build_util.GetBuildInfoFromId(try_job_key), |
| 254 step_name=step_name) |
| 199 ref_name = (task.parameters.get('ref_name') if task and task.parameters | 255 ref_name = (task.parameters.get('ref_name') if task and task.parameters |
| 200 else None) | 256 else None) |
| 201 return try_job.test_results[-1].get('culprit', {}).get( | 257 return ( |
| 202 ref_name or step_name, {}).get('tests', {}).get(test_name) | 258 _TryJobStatus.FINISHED, try_job.test_results[-1].get('culprit', {}).get( |
| 259 ref_name or step_name, {}).get('tests', {}).get(test_name)) |
| 260 |
| 261 def _CheckIsFlaky(self, try_job_map, step_name, test_name): |
| 262 """Checks if the test is flaky.""" |
| 263 if not try_job_map or not test_name: |
| 264 return False |
| 265 |
| 266 try_job_key = try_job_map.get(step_name, {}).get(test_name) |
| 267 if not try_job_key: |
| 268 return False |
| 269 |
| 270 swarming_task = WfSwarmingTask.Get( |
| 271 *build_util.GetBuildInfoFromId(try_job_key), step_name=step_name) |
| 272 if not swarming_task or not swarming_task.classified_tests: |
| 273 return False |
| 274 |
| 275 return test_name in swarming_task.classified_tests.get('flaky_tests', []) |
| 203 | 276 |
| 204 def _PopulateResult( | 277 def _PopulateResult( |
| 205 self, results, build, try_job_map, build_failure_type, | 278 self, results, build, try_job_map, build_failure_type, |
| 206 heuristic_result, step_name, test_name=None): | 279 heuristic_result, step_name, confidences, test_name=None): |
| 207 """Appends an analysis result for the given step or test. | 280 """Appends an analysis result for the given step or test. |
| 208 | 281 |
| 209 Try-job results are always given priority over heuristic results. | 282 Try-job results are always given priority over heuristic results. |
| 210 """ | 283 """ |
| 211 # Default to heuristic analysis. | 284 # Default to heuristic analysis. |
| 212 suspected_cls = heuristic_result['suspected_cls'] | 285 suspected_cls = heuristic_result['suspected_cls'] |
| 213 analysis_approach = _AnalysisApproach.HEURISTIC | 286 analysis_approach = _AnalysisApproach.HEURISTIC |
| 214 | 287 |
| 215 # Check analysis result from try-job. | 288 # Check if the test is flaky. |
| 216 culprit = self._GetCulpritFromTryJob( | 289 is_flaky_test = self._CheckIsFlaky(try_job_map, step_name, test_name) |
| 217 try_job_map, build_failure_type, step_name, test_name=test_name) | |
| 218 if culprit: | |
| 219 suspected_cls = [culprit] | |
| 220 analysis_approach = _AnalysisApproach.TRY_JOB | |
| 221 | 290 |
| 222 if not suspected_cls: | 291 if is_flaky_test: |
| 292 suspected_cls = [] |
| 293 try_job_status = _TryJobStatus.FINISHED # There will be no try job. |
| 294 else: |
| 295 # Check analysis result from try-job. |
| 296 try_job_status, culprit = self._GetStatusAndCulpritFromTryJob( |
| 297 try_job_map, build_failure_type, step_name, test_name=test_name) |
| 298 if culprit: |
| 299 suspected_cls = [culprit] |
| 300 analysis_approach = _AnalysisApproach.TRY_JOB |
| 301 |
| 302 if not is_flaky_test and not suspected_cls: |
| 223 return | 303 return |
| 224 | 304 |
| 225 results.append(self._GenerateBuildFailureAnalysisResult( | 305 results.append(self._GenerateBuildFailureAnalysisResult( |
| 226 build, suspected_cls, step_name, heuristic_result['first_failure'], | 306 build, suspected_cls, step_name, heuristic_result['first_failure'], |
| 227 test_name=test_name, analysis_approach=analysis_approach)) | 307 test_name, analysis_approach, confidences, try_job_status, |
| 308 is_flaky_test)) |
| 228 | 309 |
| 229 def _GenerateResultsForBuild(self, build, heuristic_analysis, results): | 310 def _GenerateResultsForBuild( |
| 311 self, build, heuristic_analysis, results, confidences): |
| 230 for failure in heuristic_analysis.result['failures']: | 312 for failure in heuristic_analysis.result['failures']: |
| 231 if failure.get('tests'): # Test-level analysis. | 313 if failure.get('tests'): # Test-level analysis. |
| 232 for test in failure['tests']: | 314 for test in failure['tests']: |
| 233 self._PopulateResult( | 315 self._PopulateResult( |
| 234 results, build, heuristic_analysis.failure_result_map, | 316 results, build, heuristic_analysis.failure_result_map, |
| 235 heuristic_analysis.failure_type, test, | 317 heuristic_analysis.failure_type, test, |
| 236 failure['step_name'], test_name=test['test_name']) | 318 failure['step_name'], confidences, test_name=test['test_name']) |
| 237 else: | 319 else: |
| 238 self._PopulateResult( | 320 self._PopulateResult( |
| 239 results, build, heuristic_analysis.failure_result_map, | 321 results, build, heuristic_analysis.failure_result_map, |
| 240 heuristic_analysis.failure_type, failure, failure['step_name']) | 322 heuristic_analysis.failure_type, failure, failure['step_name'], |
| 323 confidences) |
| 241 | 324 |
| 242 @endpoints.method( | 325 @endpoints.method( |
| 243 _BuildFailureCollection, _BuildFailureAnalysisResultCollection, | 326 _BuildFailureCollection, _BuildFailureAnalysisResultCollection, |
| 244 path='buildfailure', name='buildfailure') | 327 path='buildfailure', name='buildfailure') |
| 245 def AnalyzeBuildFailures(self, request): | 328 def AnalyzeBuildFailures(self, request): |
| 246 """Returns analysis results for the given build failures in the request. | 329 """Returns analysis results for the given build failures in the request. |
| 247 | 330 |
| 248 Analysis of build failures will be triggered automatically on demand. | 331 Analysis of build failures will be triggered automatically on demand. |
| 249 | 332 |
| 250 Args: | 333 Args: |
| 251 request (_BuildFailureCollection): A list of build failures. | 334 request (_BuildFailureCollection): A list of build failures. |
| 252 | 335 |
| 253 Returns: | 336 Returns: |
| 254 _BuildFailureAnalysisResultCollection | 337 _BuildFailureAnalysisResultCollection |
| 255 A list of analysis results for the given build failures. | 338 A list of analysis results for the given build failures. |
| 256 """ | 339 """ |
| 257 results = [] | 340 results = [] |
| 258 supported_builds = [] | 341 supported_builds = [] |
| 342 confidences = SuspectedCLConfidence.Get() |
| 259 | 343 |
| 260 for build in request.builds: | 344 for build in request.builds: |
| 261 master_name = buildbot.GetMasterNameFromUrl(build.master_url) | 345 master_name = buildbot.GetMasterNameFromUrl(build.master_url) |
| 262 if not (master_name and waterfall_config.MasterIsSupported(master_name)): | 346 if not (master_name and waterfall_config.MasterIsSupported(master_name)): |
| 263 logging.info('%s/%s/%s is not supported', | 347 logging.info('%s/%s/%s is not supported', |
| 264 build.master_url, build.builder_name, build.build_number) | 348 build.master_url, build.builder_name, build.build_number) |
| 265 continue | 349 continue |
| 266 | 350 |
| 267 supported_builds.append({ | 351 supported_builds.append({ |
| 268 'master_name': master_name, | 352 'master_name': master_name, |
| 269 'builder_name': build.builder_name, | 353 'builder_name': build.builder_name, |
| 270 'build_number': build.build_number, | 354 'build_number': build.build_number, |
| 271 'failed_steps': build.failed_steps, | 355 'failed_steps': build.failed_steps, |
| 272 }) | 356 }) |
| 273 | 357 |
| 274 # If the build failure was already analyzed and a new analysis is | 358 # If the build failure was already analyzed and a new analysis is |
| 275 # scheduled to analyze new failed steps, the returned WfAnalysis will | 359 # scheduled to analyze new failed steps, the returned WfAnalysis will |
| 276 # still have the result from last completed analysis. | 360 # still have the result from last completed analysis. |
| 277 # If there is no analysis yet, no result is returned. | 361 # If there is no analysis yet, no result is returned. |
| 278 heuristic_analysis = WfAnalysis.Get( | 362 heuristic_analysis = WfAnalysis.Get( |
| 279 master_name, build.builder_name, build.build_number) | 363 master_name, build.builder_name, build.build_number) |
| 280 if not heuristic_analysis: | 364 if not heuristic_analysis: |
| 281 continue | 365 continue |
| 282 | 366 |
| 283 if heuristic_analysis.failed or not heuristic_analysis.result: | 367 if heuristic_analysis.failed or not heuristic_analysis.result: |
| 284 # Bail out if the analysis failed or there is no result yet. | 368 # Bail out if the analysis failed or there is no result yet. |
| 285 continue | 369 continue |
| 286 | 370 |
| 287 self._GenerateResultsForBuild(build, heuristic_analysis, results) | 371 self._GenerateResultsForBuild( |
| 372 build, heuristic_analysis, results, confidences) |
| 288 | 373 |
| 289 logging.info('%d build failure(s), while %d are supported', | 374 logging.info('%d build failure(s), while %d are supported', |
| 290 len(request.builds), len(supported_builds)) | 375 len(request.builds), len(supported_builds)) |
| 291 try: | 376 try: |
| 292 _AsyncProcessFailureAnalysisRequests(supported_builds) | 377 _AsyncProcessFailureAnalysisRequests(supported_builds) |
| 293 except Exception: # pragma: no cover. | 378 except Exception: # pragma: no cover. |
| 294 # If we fail to post a task to the task queue, we ignore and wait for next | 379 # If we fail to post a task to the task queue, we ignore and wait for next |
| 295 # request. | 380 # request. |
| 296 logging.exception('Failed to add analysis request to task queue: %s', | 381 logging.exception('Failed to add analysis request to task queue: %s', |
| 297 repr(supported_builds)) | 382 repr(supported_builds)) |
| (...skipping 24 matching lines...) Expand all Loading... |
| 322 | 407 |
| 323 try: | 408 try: |
| 324 _AsyncProcessFlakeReport(flake_analysis_request, user_email, is_admin) | 409 _AsyncProcessFlakeReport(flake_analysis_request, user_email, is_admin) |
| 325 queued = True | 410 queued = True |
| 326 except Exception: | 411 except Exception: |
| 327 # Ignore the report when fail to queue it for async processing. | 412 # Ignore the report when fail to queue it for async processing. |
| 328 queued = False | 413 queued = False |
| 329 logging.exception('Failed to queue flake report for async processing') | 414 logging.exception('Failed to queue flake report for async processing') |
| 330 | 415 |
| 331 return _FlakeAnalysis(queued=queued) | 416 return _FlakeAnalysis(queued=queued) |
| OLD | NEW |