#!/bin/bash set -ef -o pipefail scripts=$(dirname $0) . $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_branch baseline_branch build_script current_project BUILD_URL="${BUILD_URL:-$(pwd)}" 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 trap "eval \"echo ERROR at \${FUNCNAME[0]}:\${BASH_LINENO[0]}\" > $artifacts/failures" EXIT rebase_workaround=false 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 ${rr[linux_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 <> $artifacts/good_revs # 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. # # TODO: We could skip revisions (exit 125) that are worse than metric # for $bad_rev (result metric <1000 in the above scenario), so we would # skip revisions between rev_20 and rev_22. This might cause other edge # cases to be handled sub-optimally, though. cat > $artifacts/test.sh <> $rel_artifacts/bad_revs exit 1 else echo "\$rev" >> $rel_artifacts/good_revs exit 0 fi EOF chmod +x $artifacts/test.sh bad_rev="${bad_rev-$(git_rev_parse_long $current_project $bad_branch)}" cat < $artifacts/trigger-build-rebase < $artifacts/trigger-build-rebase <> $artifacts/trigger-build-rebase < $artifacts/trigger-build-retry < $artifacts/jenkins/mail-recipients.txt trap "" EXIT exit 0 fi ln -f -s "build-$bad_rev" "$artifacts/build-bad" ln -f -s "build-$bad_rev.sh" "$artifacts/manifests/build-bad.sh" git bisect start $bad_rev $baseline_rev 2>&1 | tee $artifacts/bisect.log if ! git bisect log > /dev/null; then # Bisect ended before it could start: $bad_rev^ == $baseline_rev first_bad=$bad_rev last_good=$baseline_rev else first_bad="" last_good="" 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_rev=${interesting_commits_rev-${rr[ci_project]}} clone_or_update_repo ../interesting-commits $interesting_commits_rev https://git-us.linaro.org/toolchain/ci/interesting-commits.git auto $interesting_commits_rev cat <> ../interesting-commits/$current_project fi done git -C ../interesting-commits add . git -C ../interesting-commits commit -m "Add commits from $BUILD_URL: $*" & local res=0 && wait $! || res=$? if [ x"$res" = x"0" ]; then git_init_linaro_local_remote ../interesting-commits baseline false git_push ../interesting-commits baseline ${rr[ci_project]} fi ) || true } # Print first_bad revision (if detected) get_first_bad () { ( set -euf -o pipefail if [ x"$first_bad" != x"" ]; then echo "$first_bad" return fi git bisect log | tail -n1 | grep "^# first bad commit:" \ | sed -e "s/^# first bad commit: \[\([0-9a-f]*\)\].*/\1/" ) } # Try to reduce bisection range by testing regressions (and their parents) # identified in other configurations. sha1="" touch ../interesting-commits/$current_project while [ x"$(get_first_bad &1 | tee -a $artifacts/bisect.log & res=0 && wait $! || res=$? if [ x"$res" = x"0" ]; then assert [ x"$(get_first_bad)" != x"" ] fi fi first_bad=$(get_first_bad) if [ x"$first_bad" != x"" ]; then # "git bisect run" succeeded. Check whether this is an actual regression # or bisection artifact. res=0 for last_good in $last_good $(git rev-parse $first_bad^@); do # It seems that git-bisect assumes parent commit as "good" on # the basis of one of its children being "good". Therefore we # can have a situation when we have parent P with children C1 and C2, # and child C1 has a child of its own CB. Git-bisect tests C2 as # "good", and CB as "bad". From C2 being good it assumes P as "good", # and it knows CB is "bad", so git-bisect returns C1 as the first bad # commit. # To simplify investigations we explicitly test parent of $first_bad. echo "Testing first_bad's parent $last_good (hoping for success)" git checkout --detach "$last_good" $artifacts/test.sh & res=0 && wait $! || res=$? if [ x"$res" = x"0" ]; then break fi done # Add both $last_good and $first_bad to interesting commits. push_interesting_commits $last_good $first_bad if [ x"$res" = x"0" ]; then # Success! Touch $artifacts/first-bad as a marker of successful bisect. echo $first_bad > $artifacts/first-bad else # It seems $last_good was on a path that tested good, even though # it itself is bad. # # We need to be careful to avoid re-trigger loops, so verify that # last_good is an ancestor of bad_rev. assert git merge-base --is-ancestor $last_good $bad_rev if git merge-base --is-ancestor $baseline_rev $last_good; then # $last_good is a child of $baseline_rev, so we can re-trigger # bisection with reduced bisection range. cat > $artifacts/trigger-bisect < $artifacts/jenkins/mail-recipients.txt trap "" EXIT exit 0 fi # This case will be handled similar to "git bisect run" failure below. # We are going to reset baseline to $first_bad. fi else # When "git bisect run" fails, e.g., due to merge-base of $baseline_rev and # $bad_rev is worse than $baseline_rev, we want to reset baseline to HEAD, # so that we catch most of the commits that introduced change in the result # metric. first_bad=$(git rev-parse HEAD) push_interesting_commits $first_bad fi cd .. # Save BISECT_* logs mkdir $artifacts/git-logs find "$current_project" -path "$current_project/.git/BISECT_*" -print0 | xargs -0 -I@ mv @ $artifacts/git-logs/ if [ -f $artifacts/first-bad ]; then mkdir -p $artifacts/jenkins touch $artifacts/jenkins/build-name 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" cat >> $artifacts/jenkins/mail-body.txt < $(git -C $current_project log -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/mail-body.txt < mkdir investigate-$current_project-$bad_sha1 cd investigate-$current_project-$bad_sha1 git clone https://git.linaro.org/toolchain/jenkins-scripts mkdir -p $rel_artifacts/manifests curl -o $rel_artifacts/manifests/build-baseline.sh ${BUILD_URL}artifact/$rel_artifacts/manifests/build-baseline.sh curl -o $rel_artifacts/manifests/build-parameters.sh ${BUILD_URL}artifact/$rel_artifacts/manifests/build-parameters.sh curl -o $rel_artifacts/test.sh ${BUILD_URL}artifact/$rel_artifacts/test.sh chmod +x $rel_artifacts/test.sh # Reproduce the baseline build (build all pre-requisites) $build_script @@ $rel_artifacts/manifests/build-baseline.sh cd $current_project # Reproduce $bad_name build git checkout --detach $bad_sha1 ../$rel_artifacts/test.sh # Reproduce $good_name build git checkout --detach $good_sha1 ../$rel_artifacts/test.sh cd .. History of pending regressions and results: https://git.linaro.org/toolchain/ci/base-artifacts.git/log/?h=linaro-local/ci/${rr[ci_project]}/${rr[ci_config]} Bisect log: ${BUILD_URL}artifact/$rel_artifacts/bisect.log/*view*/ Artifacts: ${BUILD_URL}artifact/$rel_artifacts/ Build URL: $BUILD_URL Build log: ${BUILD_URL}consoleText EOF if [ -f $artifacts/first-bad ]; then cat >> $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 "${rr[ci_project]}/${rr[ci_config]}:$current_project" in tcwg_kernel/gnu-*:linux) ;; tcwg_kernel/gnu-*:*) CI_MAIL_RECIPIENTS="$CI_MAIL_RECIPIENTS, tcwg-gcc@linaro.org" ;; tcwg_kernel/llvm-*:linux) CI_MAIL_RECIPIENTS="$CI_MAIL_RECIPIENTS, arnd@linaro.org, mark.brown@linaro.org, ndesaulniers@google.com, trong@google.com" ;; tcwg_kernel/llvm-*:llvm) CI_MAIL_RECIPIENTS="$CI_MAIL_RECIPIENTS, llvm@linaro.org, ndesaulniers@google.com, trong@google.com" ;; esac cat > $artifacts/jenkins/mail-recipients.txt <> $artifacts/trigger-build-1-reset # 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 trap "" EXIT