blob: f03922e9295fc28ce4595db50a224dd033ef1fc5 [file] [log] [blame]
# Copyright 2018 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
from google.appengine.ext import ndb
from dto.commit_id_range import CommitID
from dto.commit_id_range import CommitIDRange
from dto.int_range import IntRange
from dto.step_metadata import StepMetadata
from gae_libs.pipelines import SynchronousPipeline
from libs.structured_object import StructuredObject
from services import step_util
from services.flake_failure import heuristic_analysis
from services.flake_failure import lookback_algorithm
from services.flake_failure import next_commit_position_utils
from waterfall import build_util
class NextCommitPositionInput(StructuredObject):
# The urlsafe-key to the MasterflakeAnalysius in progress.
analysis_urlsafe_key = basestring
# The upper and lower bound commit positions not to exceed.
commit_position_range = IntRange
# Info about the test to identify nearby IsolatedTargets.
step_metadata = StepMetadata
class NextCommitPositionOutput(StructuredObject):
# The next commit_id (commit position and revision) that the flake analysis
# should run. Should be mutually exclusive with culprit_commit_id.
next_commit_id = CommitID
# The commit commit_id (commit position and revision) of the identified
# culprit. Should be mutually exclusive with next_commit_id.
culprit_commit_id = CommitID
class NextCommitPositionPipeline(SynchronousPipeline):
input_type = NextCommitPositionInput
output_type = NextCommitPositionOutput
def RunImpl(self, parameters):
"""Pipeline for determining the next commit position to analyze."""
analysis_urlsafe_key = parameters.analysis_urlsafe_key
analysis = ndb.Key(urlsafe=analysis_urlsafe_key).get()
assert analysis
master_name = analysis.master_name
builder_name = analysis.builder_name
specified_lower_bound = parameters.commit_position_range.lower
specified_upper_bound = parameters.commit_position_range.upper
data_points = analysis.GetDataPointsWithinCommitPositionRange(
IntRange(lower=specified_lower_bound, upper=specified_upper_bound))
# Data points must be sorted in reverse order by commit position before.
data_points = sorted(
data_points, key=lambda k: k.commit_position, reverse=True)
# A suspected build id is available when there is a regression range that
# spans a single build cycle. During this time, bisect is preferred to
# exponential search.
use_bisect = (
analysis.suspected_flake_build_number is not None or
analysis.suspected_build_id is not None)
latest_regression_range = analysis.GetLatestRegressionRange()
calculated_next_commit_id, culprit_commit_id = (
lookback_algorithm.GetNextCommitId(data_points, use_bisect,
latest_regression_range))
if calculated_next_commit_id is None:
# The analysis is finished according to the lookback algorithm.
return NextCommitPositionOutput(
next_commit_id=None, culprit_commit_id=culprit_commit_id)
cutoff_commit_position = (
next_commit_position_utils.GetEarliestCommitPosition(
specified_lower_bound, specified_upper_bound))
if calculated_next_commit_id.commit_position < cutoff_commit_position:
# Long-standing flake. Do not continue the analysis.
return NextCommitPositionOutput(
next_commit_id=None, culprit_commit_id=None)
# Try the analysis' heuristic results first, if any.
next_commit_id = (
next_commit_position_utils.GetNextCommitIdFromHeuristicResults(
analysis_urlsafe_key))
if next_commit_id is not None:
# Heuristic results are available and should be tried first.
assert not analysis.FindMatchingDataPointWithCommitPosition(
next_commit_id.commit_position), (
'Existing heuristic results suggest commit position {} which has '
'already been run'.format(next_commit_id.commit_position))
return NextCommitPositionOutput(
next_commit_id=next_commit_id, culprit_commit_id=None)
# Round off the next calculated commit position to the nearest builds on
# both sides.
reference_build_info = build_util.GetBuildInfo(master_name, builder_name,
analysis.build_number)
parent_mastername = reference_build_info.parent_mastername or master_name
parent_buildername = (
reference_build_info.parent_buildername or builder_name)
target_name = parameters.step_metadata.isolate_target_name
try:
lower_bound_target, upper_bound_target = (
step_util.GetBoundingIsolatedTargets(
parent_mastername, parent_buildername, target_name,
calculated_next_commit_id.commit_position))
# Update the analysis' suspected build cycle if identified.
analysis.UpdateSuspectedBuild(lower_bound_target, upper_bound_target)
lower_bound_commit_id, upper_bound_commit_id = (
next_commit_position_utils.GenerateCommitIDsForBoundingTargets(
data_points, lower_bound_target, upper_bound_target))
except AssertionError as e:
# Fallback to searching buildbot in case builds aren't indexed as
# IsolatedTargets.
# TODO(crbug.com/872992): Remove fallback logic.
analysis.LogError(e.message)
analysis.LogWarning(
('Failed to determine isolated targets surrounding {}. Falling back '
'to searching buildbot').format(
calculated_next_commit_id.commit_position))
upper_bound_build_number = analysis.GetLowestUpperBoundBuildNumber(
calculated_next_commit_id)
lower_bound_build, upper_bound_build = (
step_util.GetValidBoundingBuildsForStep(
master_name, builder_name, analysis.step_name, None,
upper_bound_build_number,
calculated_next_commit_id.commit_position))
# Update the analysis' suspected build cycle if identified.
analysis.UpdateSuspectedBuildUsingBuildInfo(lower_bound_build,
upper_bound_build)
lower_bound_commit_id = CommitID(
commit_position=lower_bound_build.commit_position,
revision=lower_bound_build
.chromium_revision) if lower_bound_build else None
upper_bound_commit_id = CommitID(
commit_position=upper_bound_build.commit_position,
revision=upper_bound_build
.chromium_revision) if upper_bound_build else None
# When identifying the neighboring builds of the requested commit position,
# heuristic analysis may become eligible if the neighboring builds are
# adjacent to one another.
if analysis.CanRunHeuristicAnalysis():
# Run heuristic analysis if eligible and not yet already done.
heuristic_analysis.RunHeuristicAnalysis(analysis)
# Try the newly computed heuristic results if any were identified.
next_commit_id = (
next_commit_position_utils.GetNextCommitIdFromHeuristicResults(
analysis_urlsafe_key))
if next_commit_id is not None: # pragma: no branch
assert not analysis.FindMatchingDataPointWithCommitPosition(
next_commit_id.commit_position
), ('Newly run heuristic results suggest commit position {} which has '
'already been run'.format(next_commit_id))
return NextCommitPositionOutput(
next_commit_id=next_commit_id, culprit_commit_id=None)
# Pick the commit position of the returned neighboring builds that has not
# yet been analyzed if possible, or the commit position itself when not.
build_range = CommitIDRange(
lower=lower_bound_commit_id, upper=upper_bound_commit_id)
actual_next_commit_id = (
next_commit_position_utils.GetNextCommitIDFromBuildRange(
analysis, build_range, calculated_next_commit_id))
assert not analysis.FindMatchingDataPointWithCommitPosition(
actual_next_commit_id.commit_position), (
'Rounded-off commit position {} has already been run'.format(
actual_next_commit_id.commit_position))
return NextCommitPositionOutput(
next_commit_id=actual_next_commit_id,
culprit_commit_id=culprit_commit_id)