blob: c9303d3e47cf74e467b11fb64ffcfc58496f7732 [file] [log] [blame]
# Copyright 2021 The Fuchsia 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 recipe_engine import recipe_api
AUTOCORRELATOR_PATH = "fuchsia/infra/autocorrelator/${platform}"
AUTOCORRELATOR_VERSION = "git_revision:25ebd0de9766d0a16ca0fdf39618576e7b597369"
class AutocorrelatorApi(recipe_api.RecipeApi):
"""APIs for finding correlations between failures in Fuchsia CI/CQ."""
def __init__(self, *args, **kwargs):
super(AutocorrelatorApi, self).__init__(*args, **kwargs)
self.findings = []
def check_ci(
self,
step_name,
base_commit,
builder,
build_status,
summary_markdown,
score_threshold=0.95,
):
"""Compare summary markdown similarity to a CI-like builder. Record a
finding if it meets the score threshold.
Args:
step_name (str): Name of the step.
base_commit (str): Base commit as sha1 to check against `builder`.
builder (str): Fully-qualified Buildbucket builder name of the
CI-like builder to check.
build_status (common_pb2.Status): Buildbucket status.
summary_markdown (str): Summary markdown to compare.
score_threshold (float): Record a finding if it meets this
threshold.
"""
with self.m.step.nest(step_name):
args = [
"check-ci",
"-base-commit",
base_commit,
"-builder",
builder,
"-build-status",
build_status,
"-summary-markdown-path",
self._summary_markdown_path(summary_markdown),
"-json-output",
self.m.json.output(),
]
step = self("run autocorrelator", args)
finding = step.json.output
if not finding or finding["is_green"]:
return step
score = finding["score"]
if score >= score_threshold:
ci_build_link = self.m.buildbucket.build_url(
build_id=finding["build_id"]
)
step.presentation.links["correlated_ci_build"] = ci_build_link
self.findings.append(
"Correlated failure found in CI %s: %0.2f similarity, %d git "
"commit distance" % (ci_build_link, score, finding["commit_dist"]),
)
return step
def check_try(
self,
step_name,
builder,
change_num,
build_status,
summary_markdown,
ignore_skipped_build=False,
ignore_skipped_tests=False,
score_threshold=0.95,
build_frequency_threshold=0.75,
):
"""Compare summary markdown similarity to a try-like builder. Record an
aggregate finding if there are one or more findings that meet the score
threshold, and the frequency of findings meets the build frequency
threshold.
Args:
step_name (str): Name of the step.
builder (str): Fully-qualified Buildbucket builder name of the
try-like builder to check.
change_num (int): Gerrit change number.
build_status (common_pb2.Status): Buildbucket status.
summary_markdown (str): Summary markdown to compare.
ignore_skipped_build (bool): Whether to ignore try builds with
unaffected build graphs.
ignore_skipped_tests (bool): Whether to ignore builds which skipped
testing.
score_threshold (float): Count a finding as part of the aggregate if
its score meets this threshold.
build_frequency_threshold (float): Record an aggregate finding if
the frequency of findings meets this threshold, i.e. a 0.5
threshold means that a finding will be recorded if
num_findings / num_inspected_builds >= 0.5.
"""
with self.m.step.nest(step_name):
args = [
"check-try",
"-builder",
builder,
"-change-num",
change_num,
"-build-status",
build_status,
"-summary-markdown-path",
self._summary_markdown_path(summary_markdown),
"-json-output",
self.m.json.output(),
]
if ignore_skipped_build:
args.append("-ignore-skipped-build")
if ignore_skipped_tests:
args.append("-ignore-skipped-tests")
step = self("run autocorrelator", args)
findings = step.json.output
if not findings:
return step
findings_meeting_threshold = []
for finding in findings:
if finding["score"] >= score_threshold:
findings_meeting_threshold.append(finding)
try_build_link = self.m.buildbucket.build_url(
build_id=finding["build_id"]
)
step.presentation.links[
"correlated_try_build: %s" % finding["build_id"]
] = try_build_link
build_frequency = len(findings_meeting_threshold) / float(len(findings))
if build_frequency >= build_frequency_threshold:
self.findings.append(
"Correlated failures found in try: %0.2f avg similarity in %d "
"builds\n\n%s"
% (
sum(
(finding["score"] for finding in findings_meeting_threshold)
)
/ len(findings_meeting_threshold),
len(findings_meeting_threshold),
"\n".join(
build_link
for build_link in step.presentation.links.itervalues()
),
),
)
return step
def __call__(self, step_name, args):
return self.m.step(
step_name, [self._autocorrelator_tool] + args, infra_step=True
)
def _summary_markdown_path(self, summary_markdown):
"""Write summary markdown to a file and return the Path."""
path = self.m.path.mkstemp("summary_md")
self.m.file.write_text("write summary markdown", path, summary_markdown)
return path
@property
def _autocorrelator_tool(self):
return self.m.cipd.ensure_tool(AUTOCORRELATOR_PATH, AUTOCORRELATOR_VERSION)