#!/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/*" declare -A rr # Process bisect-only args convert_args_to_variables "$@" shift "$SHIFT_CONVERTED_ARGS" obligatory_variables bad_git build_script current_project declare bad_git 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] verbose="${verbose-true}" set -u if $verbose; then set -x; fi mkdir -p $artifacts/jenkins touch $artifacts/jenkins/build-name trap print_traceback EXIT bad_url="${bad_git%#*}" bad_branch="${bad_git#*#}" 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_git. 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" fi ( echo echo "# From $BUILD_URL:" cat "$fails" | sed -e "s/^\([A-Z]\+: \)/flaky \| \1/" ) >> "$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 pull --rebase gerrit refs/heads/master 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-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 [ x"$(git ls-remote --heads $interesting_commits_url refs/heads/$interesting_commits_branch | wc -l)" != x"1" ]; 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-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/ if [ x"$first_bad" != x"" ]; then 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" ln -f -s "build-$last_good" "$artifacts/build-last_good" ln -f -s "build-$last_good.sh" "$artifacts/manifests/build-last_good.sh" else notify_devs=false fi case "${rr[ci_project]}" in tcwg_gcc_check*|tcwg_gnu_*_check_*) # We are building up lists of flaky tests and we need to stabilize # GCC and GDB testsuites. Currently dejagnu testsuites are too # flaky for automated regression detection. notify_devs=false ;; esac if $notify_devs; then 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 notify_author=$(git -C $current_project log --pretty="%an <%ae>" -n 1 \ "$first_bad") fi case "${rr[ci_project]}/${rr[ci_config]}:$current_project" in tcwg_kernel/gnu-*:linux) jira_card="GNU-681" ;; tcwg_kernel/gnu-*:*) jira_card="GNU-680" ;; tcwg_kernel/llvm-*:linux) jira_card="LLVM-647" ;; tcwg_kernel/llvm-*:*) jira_card="LLVM-646" ;; tcwg_bmk_*/gnu*-O[23]*) jira_card="GNU-689" ;; tcwg_bmk_*/gnu*) jira_card="GNU-686" ;; tcwg_bmk_*/llvm-*O[23]*) jira_card="LLVM-651" ;; tcwg_bmk_*/llvm-*) jira_card="LLVM-650" ;; tcwg_binutils/*) jira_card="GNU-692" ;; tcwg_cross/*) jira_card="GNU-692" ;; tcwg_gnu/*) jira_card="GNU-692" ;; # Catch-all case for when project/config IDs change, so that we # won't miss notifications. Forward all that to GNU-692. *) jira_card="GNU-692" ;; esac rm -f $artifacts/jenkins/mail-body.draft if [ x"${TCWG_JIRA_TOKEN+set}" = x"set" ] && [ x"$jira_card" != x"" ]; then cat > $artifacts/jenkins/jira-body.txt < $HOME/.jipdate.yml <> $artifacts/jenkins/mail-body.draft <> $artifacts/jenkins/mail-body.draft < mkdir investigate-$current_project-$first_bad cd investigate-$current_project-$first_bad # Fetch scripts git clone https://git.linaro.org/toolchain/jenkins-scripts # Fetch manifests and test.sh script mkdir -p $rel_artifacts/manifests curl -o $rel_artifacts/manifests/build-baseline.sh ${BUILD_URL}artifact/$rel_artifacts/manifests/build-baseline.sh --fail curl -o $rel_artifacts/manifests/build-parameters.sh ${BUILD_URL}artifact/$rel_artifacts/manifests/build-parameters.sh --fail curl -o $rel_artifacts/test.sh ${BUILD_URL}artifact/$rel_artifacts/test.sh --fail chmod +x $rel_artifacts/test.sh # Reproduce the baseline build (build all pre-requisites) $build_script @@ $rel_artifacts/manifests/build-baseline.sh # Save baseline build state (which is then restored in $rel_artifacts/test.sh) mkdir -p ./bisect rsync -a --del --delete-excluded ${baseline_exclude[@]} ./ ./bisect/baseline/ cd $current_project # Reproduce first_bad build git checkout --detach $first_bad ../$rel_artifacts/test.sh # Reproduce last_good build git checkout --detach $last_good ../$rel_artifacts/test.sh cd .. Full commit (up to 1000 lines): $(git -C $current_project show --stat --patch $first_bad | head -n 1000) EOF cp $artifacts/build-$first_bad/mail/mail-subject.txt \ $artifacts/jenkins/mail-subject.draft # Set mail recipients last to preserve catch-error value from .yaml file. # Email developers. CI_MAIL_RECIPIENTS=("bcc:tcwg-validation@linaro.org") case "$notify_author@${rr[ci_project]}/${rr[ci_config]}:$current_project" in ""@*/*:*) ;; *@tcwg_gcc*/*:*) CI_MAIL_RECIPIENTS+=("$notify_author") CI_MAIL_RECIPIENTS+=("cc:linaro-toolchain@lists.linaro.org") ;; *@tcwg_gnu*/*:*) CI_MAIL_RECIPIENTS+=("$notify_author") CI_MAIL_RECIPIENTS+=("cc:linaro-toolchain@lists.linaro.org") ;; *@tcwg_kernel/llvm-*:linux) CI_MAIL_RECIPIENTS+=("$notify_author") CI_MAIL_RECIPIENTS+=("cc:linaro-toolchain@lists.linaro.org") CI_MAIL_RECIPIENTS+=("cc:llvm@lists.linux.dev") CI_MAIL_RECIPIENTS+=("arnd@linaro.org") ;; *@tcwg_kernel/llvm-*:llvm) CI_MAIL_RECIPIENTS+=("$notify_author") CI_MAIL_RECIPIENTS+=("cc:linaro-toolchain@lists.linaro.org") CI_MAIL_RECIPIENTS+=("cc:llvm@lists.linux.dev") ;; *@tcwg_kernel/*:*) CI_MAIL_RECIPIENTS+=("$notify_author") CI_MAIL_RECIPIENTS+=("cc:linaro-toolchain@lists.linaro.org") ;; *@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 ( IFS="," cat > $artifacts/jenkins/mail-recipients.draft < $artifacts/jenkins/$i.txt done ) else # Remove any fail-safe email stubs echo > $artifacts/jenkins/mail-recipients.txt echo > $artifacts/jenkins/mail-subject.txt echo > $artifacts/jenkins/mail-body.txt fi trap "" EXIT