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