#!/bin/bash set -ef -o pipefail scripts=$(dirname $0) # shellcheck source=jenkins-helpers.sh . $scripts/jenkins-helpers.sh # Relative artifacts are used for generation of manifests and reproduction # instructions. rel_artifacts=artifacts artifacts=$(pwd)/$rel_artifacts fresh_dir $artifacts "$artifacts/manifests/*" "$artifacts/jenkins/*" # Process bisect-only args convert_args_to_variables "$@" shift "$SHIFT_CONVERTED_ARGS" obligatory_variables bad_url bad_branch build_script current_project declare bad_url bad_branch build_script current_project BUILD_URL="${BUILD_URL:-$(pwd)}" replay_log="${replay_log-}" reproduce_bisect="${reproduce_bisect:-false}" # Process build args and record them in build-parameters.sh convert_args_to_variables ^^ $reproduce_bisect %% $artifacts/manifests/build-parameters.sh "$@" $reproduce_bisect || manifest_pop obligatory_variables rr[ci_project] rr[ci_config] declare -A rr verbose="${verbose-true}" set -u if $verbose; then set -x; fi mkdir -p $artifacts/jenkins touch $artifacts/jenkins/build-name trap print_traceback EXIT rebase_workaround=false rebase_workaround_opts=() case "${rr[ci_project]}/${rr[ci_config]}:$current_project" in tcwg_kernel/*-next-*:linux) # Workaround linux-next/master rebasing on top of linux-next/stable. # Search for regressions against linux-mainline:master (aka linux-next:stable). clone_or_update_repo $current_project stable $bad_url # Just in case linux-next:stable has advanced between the build and bisect jobs, # use merge base between linux-next:stable and $bad_branch. bad_rev="${bad_rev-$(git_rev_parse_long $current_project $bad_branch)}" linux_next_stable="${linux_next_stable-$(git -C $current_project merge-base HEAD $bad_rev)}" cat <> "$xfail" git -C gcc-compare-results add "$xfail_short" git -C gcc-compare-results commit -m "From $BUILD_URL" git -C gcc-compare-results review -s git -C gcc-compare-results push gerrit HEAD:refs/heads/master ) & res=0 && wait $! || res=$? # Ignore any failures in the above. ;; esac mkdir -p ./bisect # Save baseline build state, which we restore before individual bisect tests. # This ensures that "bad" bisect tests won't affect "good" ones and vice versa. baseline_exclude=( --exclude /bisect/ --exclude "/$rel_artifacts/" --exclude "/$current_project/" ) rsync -a --del --delete-excluded "${baseline_exclude[@]}" ./ ./bisect/baseline/ cd $current_project mkdir $artifacts/git-logs # Make sure the sources are clean before bisecting git reset --hard if [ -f "$replay_log" ]; then cp "$replay_log" $artifacts/git-logs/bisect_replay.sh git bisect replay $artifacts/git-logs/bisect_replay.sh else git bisect start fi # Hard-link BISECT_LOG inside $artifacts to guarantee its upload to jenkins. ln -f "$(pwd)"/.git/BISECT_LOG $artifacts/git-logs/bisect_log if ! git bisect log | grep -q "^git bisect .* $baseline_rev\$"; then git bisect good $baseline_rev fi # Bisect script. # # With this script we find the first commit that has regressed compared # to baseline, but not, necessarily, the commit that caused regression in # $bad_rev. Consider the scenario: # - rev_10 produced good result "2000" -- this is current baseline # - rev_20 completely broke the build (say, result "10") # - rev_22 fixed the build # - rev_30 regressed the build to result "1000" -- this is the regression we # detected vs "2000" baseline. # # The script will identify rev_20 as the first failing commit, which will # cause the baseline to be reset to rev_20 with metric "10". When we then # rebuild master (at rev_30) we will see a /progression/ from "10" to "1000", # thus missing the regression of "2000" to "1000". # # To catch the "2000" to "1000" regression someone would need to manually # trigger bisect between rev_22 and rev_30. # # We reduce the impact of the above by assigning negative values to result # metric for builds that look very broken, and do not bisect regressions into # the "negative" side. cat > $artifacts/test.sh < $artifacts/trigger-build-rebase < $artifacts/trigger-build-rebase <> $artifacts/trigger-build-rebase < $artifacts/trigger-build-reset < $artifacts/trigger-build-reset < $artifacts/jenkins/mail-recipients.txt trap "" EXIT exit 0 elif [ x"$res" = x"125" ]; then # We have confirmed a regression, but not what we have been triggered # to bisect. echo "WARNING: build for bad_rev $bad_rev showed uninteresting regression" sed -i -e "s/\$/-uninteresting/" $artifacts/jenkins/build-name echo > $artifacts/jenkins/mail-recipients.txt trap "" EXIT exit 0 fi if ! git bisect log | grep -q "^git bisect .* $bad_rev\$"; then git bisect bad $bad_rev ln -f -s "build-$bad_rev" "$artifacts/build-bad" ln -f -s "build-$bad_rev.sh" "$artifacts/manifests/build-bad.sh" fi # Clone interesting-commits.git repo, which contains a list of SHA1s # that might cut down bisection time. Mostly, these are first_bad and # last_good commits. interesting_commits="../bisect/interesting-commits" interesting_commits_url=https://git-us.linaro.org/toolchain/ci/interesting-commits.git interesting_commits_branch=linaro-local/ci/${rr[ci_project]} if ! git ls-remote --heads $interesting_commits_url \ | grep -q ".* refs/heads/$interesting_commits_branch"; then interesting_commits_branch=empty fi interesting_commits_rev=${interesting_commits_rev-$interesting_commits_branch} clone_or_update_repo $interesting_commits $interesting_commits_rev $interesting_commits_url auto $interesting_commits_branch interesting_commits_rev=$(git -C $interesting_commits rev-parse HEAD) cat <> $interesting_commits/$current_project fi if [ x"$kind" = x"regression" ]; then mapfile -t configs < <(grep "^$sha1" $interesting_commits/$current_project | sed -e "s/^$sha1 *//") configs+=("${rr[ci_project]}"/"${rr[ci_config]}") mapfile -t configs < <(echo "${configs[@]}" | tr ' ' '\n' | sort -u) sed -i -e "s#^$sha1.*\$#$sha1 ${configs[*]}#" $interesting_commits/$current_project fi git -C $interesting_commits add . if [ x"$(git -C $interesting_commits status --short)" = x"" ]; then # No file has changed. We've been here before... # E.g., this is a re-occuring regression in linux-next. exit 125 fi git -C $interesting_commits commit -m "Add $kind $sha1 from $BUILD_URL ${configs[*]}" & local res=0 && wait $! || res=$? if [ x"$res" = x"0" ]; then # Interesting-commits.git do not have .gitreview, so it's # simpler to push via gitolite. git_init_linaro_local_remote $interesting_commits baseline false git_push $interesting_commits baseline linaro-local/ci/${rr[ci_project]} fi ) & wait $! || push_interesting_commit_result=$? } # Print first_bad revision (if detected) get_first_bad () { ( # Allow pipefail to handle error exit codes from git bisect log and grep. # Note that child shell inherits settings from parent shell, so we need # excplicitly set "+o pipefail". set -euf +o pipefail git bisect log | tail -n1 | grep "^# first bad commit:" \ | sed -e "s/^# first bad commit: \[\([0-9a-f]*\)\].*/\1/" ) } # Print revs tested during bisect. # $1 -- Revision kind -- good/bad/skip. # Print all revisions if no $1 given. print_tested_revs () { ( # Allow pipefail to handle error exit codes from git bisect log and grep. # Note that child shell inherits settings from parent shell, so we need # excplicitly set "+o pipefail". set -euf +o pipefail local kind="${1-[a-z]*}" git bisect log | grep "^git bisect $kind " | sed -e "s/^git bisect $kind //" ) } # Try to reduce bisection range by testing regressions (and their parents) # identified in other configurations. touch $interesting_commits/$current_project # Generate list of commits inside the bisection range. commits_to_test=$artifacts/git-logs/commits_to_test git bisect view --pretty=%H > $commits_to_test # Record commits in the bisect range. These are commits_to_test plus # commits that have been tested. commits_in_range=$artifacts/git-logs/commits_in_range cp $commits_to_test $commits_in_range print_tested_revs >> $commits_in_range # This loop can generate lots of console noise. set +x while [ x"$(get_first_bad $commits_to_test ) $artifacts/trigger-bisect < $artifacts/jenkins/mail-recipients.txt trap "" EXIT exit 0 fi else # "Git bisect" didn't find the first_bad commit. # Possible reasons include: # - We have marked the regressing commit as "skip", which can happen # to tcwg_bmk* projects when benchmarking infra has a problem. # # - Merge-base of $baseline_rev and $bad_rev is worse than $baseline_rev. # We want to reset baseline to HEAD in this case, so that we catch most # of the commits that introduced change in the result metric. # # So, to make at least some progress on narrowing down the regression ... # - look for the last commit that tested "good". If it's not $baseline_rev, # then we have narrowed down the bisection range somewhat. Therefore, # trigger two builds: # - one to ADVANCE baseline to the last GOOD commit, and # - another to expose the regression again. # # - If $baseline_rev is the only good commit, then we have got outselves # to a tricky situation: we can't find the regression, and can't make # a good build, which would advance the baseline. To resolve this, # trigger two builds: # - one to RESET baseline to the last BAD commit, and # - another to advance the baseline and expose another regression. # Note that this approach correctly handles case when merge-base of # $baseline_rev and $bad_rev tests "bad". last_good=$(print_tested_revs good | tail -n1) assert_with_msg "We should have at least baseline_rev as a good commit" \ [ x"$last_good" != x"" ] if [ x"$last_good" != x"$baseline_rev" ]; then # $reset_rev=="" due to $first_bad=="" cat > $artifacts/trigger-build-1-advance <> $artifacts/trigger-build-1-reset fi # Trigger master build now instead of waiting for next timed SCM trigger. cp $artifacts/build-$bad_rev/trigger-build-$current_project \ $artifacts/trigger-build-2-default # Save BISECT_* logs find "$current_project" -path "$current_project/.git/BISECT_*" -print0 | xargs -0 -I@ mv @ $artifacts/git-logs/ # Remove any fail-safe email body rm -f $artifacts/jenkins/mail-body.txt if [ x"$first_bad" != x"" ]; then mkdir -p $artifacts/jenkins sed -i -e "s/\$/-$first_bad/" $artifacts/jenkins/build-name ln -f -s "build-$first_bad" "$artifacts/build-first_bad" ln -f -s "build-$first_bad.sh" "$artifacts/manifests/build-first_bad.sh" good_name="last_good" good_sha1="$last_good" bad_name="first_bad" bad_sha1="$first_bad" ln -f -s "build-$last_good" "$artifacts/build-last_good" ln -f -s "build-$last_good.sh" "$artifacts/manifests/build-last_good.sh" occurences="$(cat $current_project/$interesting_commits/$current_project | grep "^$first_bad" | sed -e "s/^$first_bad *//" | tr ' ' '\n' | sed "s#^# - #")" if [ "$(echo "$occurences" | wc -l)" -le 1 ]; then git_log_level="medium" notify_author=$(git -C $current_project log --pretty="%an <%ae>" -n 1 \ "$first_bad") else git_log_level="short" fi cat >> $artifacts/jenkins/mail-body.txt < $(git -C $current_project log --pretty=$git_log_level -n 1 $first_bad) EOF else good_name="baseline_rev" good_sha1="$baseline_rev" bad_name="bad" bad_sha1="$bad_rev" cat >> $artifacts/jenkins/mail-body.txt < $artifacts/jenkins/jira-body.txt < $HOME/.jipdate.yml <> $artifacts/jenkins/mail-body.txt <> $artifacts/jenkins/mail-body.txt <> $artifacts/jenkins/mail-body.txt <> $artifacts/jenkins/mail-body.txt <> $artifacts/jenkins/mail-body.txt <> $artifacts/jenkins/mail-body.txt < $(git -C $current_project show --stat --patch $first_bad | head -n 1000) EOF fi # Set mail recipients last to preserve catch-error value from .yaml file. # Email developers. CI_MAIL_RECIPIENTS=("tcwg-validation@linaro.org") case "$notify_author@${rr[ci_project]}/${rr[ci_config]}:$current_project" in ""@*/*:*) ;; *@tcwg_gnu/*-check_bootstrap*:*) ;; # We are building up list of flaky tests *@tcwg_cross/*-check_cross:*) ;; # We are building up list of flaky tests *@tcwg_gnu/*:*) CI_MAIL_RECIPIENTS+=("$notify_author") CI_MAIL_RECIPIENTS+=("linaro-toolchain@lists.linaro.org") ;; *@tcwg_cross/*:*) CI_MAIL_RECIPIENTS+=("$notify_author") CI_MAIL_RECIPIENTS+=("linaro-toolchain@lists.linaro.org") ;; *@tcwg_kernel/llvm-*:linux) CI_MAIL_RECIPIENTS+=("$notify_author") CI_MAIL_RECIPIENTS+=("linaro-toolchain@lists.linaro.org") CI_MAIL_RECIPIENTS+=("clang-built-linux@googlegroups.com") CI_MAIL_RECIPIENTS+=("arnd@linaro.org") ;; *@tcwg_kernel/llvm-*:llvm) CI_MAIL_RECIPIENTS+=("$notify_author") CI_MAIL_RECIPIENTS+=("linaro-toolchain@lists.linaro.org") CI_MAIL_RECIPIENTS+=("clang-built-linux@googlegroups.com") ;; *@tcwg_bmk*/*:*) # Don't notify patch authors until we improve benchmarking email # reporting. #CI_MAIL_RECIPIENTS+=("$notify_author") CI_MAIL_RECIPIENTS+=("linaro-toolchain@lists.linaro.org") ;; *@*/*:*) CI_MAIL_RECIPIENTS+=("linaro-toolchain@lists.linaro.org") ;; esac if $notify_devs; then ( IFS="," cat > $artifacts/jenkins/mail-recipients.txt <