diff options
Diffstat (limited to 'round-robin-notify.sh')
-rwxr-xr-x | round-robin-notify.sh | 2414 |
1 files changed, 2414 insertions, 0 deletions
diff --git a/round-robin-notify.sh b/round-robin-notify.sh new file mode 100755 index 00000000..f95c4969 --- /dev/null +++ b/round-robin-notify.sh @@ -0,0 +1,2414 @@ +#!/bin/bash + +set -euf -o pipefail + +scripts=$(dirname $0) + +# shellcheck source=jenkins-helpers.sh +. $scripts/jenkins-helpers.sh + +# DEBUG +echo -e "\n$0 $*\n" + +convert_args_to_variables "$@" +shift "$SHIFT_CONVERTED_ARGS" + +obligatory_variables rr[top_artifacts] notify build_script +declare -A rr +declare notify build_script + +generate_all="${generate_all-true}" +generate_jira="${generate_jira-$generate_all}" +generate_mail="${generate_mail-$generate_all}" +generate_jenkins_html=${generate_jenkins_html-$generate_all} +generate_dashboard="${generate_dashboard-$generate_all}" +generate_lnt="${generate_lnt-$generate_all}" +post_all="${post_all-true}" +post_jira_comment="${post_jira_comment-$post_all}" +post_jira_card="${post_jira_card-$post_all}" +post_mail="${post_mail-$post_all}" +post_gcc_testresults="${post_gcc_testresults-$post_mail}" +post_icommits="${post_icommits-$post_all}" +post_dashboard="${post_dashboard-$post_all}" + +dryrun="${dryrun-false}" +icommits="interesting-commits" +stage="${stage-full}" +verbose="${verbose-false}" + +# Generate notification files based on current and previous artifacts, which +# are assumed to be from two consecutive builds. +# +# Notes: +# 1. Current artifacts are in $top_artifacts. All files from $top_artifacts +# are accessible read-only, and round-robin-notify.sh writes its files +# into $top_artifacts/notify/. +# 2. Files in "numbered" directories NN-* should not be used, since they +# will be eventually removed. +# 3. Previous artifacts are in base-artifacts/, which is a git repository. +# All files from base-artifacts/ are accessible read-only. +# 4. Round-robin-notify.sh can assume that files in $top_artifacts/ and +# in base-artifacts/ have been generated by the latest version of scripts. +# Developers will run round-robin-rewrite.sh when format of files changes +# to bring "history" results up-to-date with what current scripts expect. +# 5. Round-robin-notify.sh can assume that check_regression() step was run +# just before invocation of round-robin-notify.sh. + +if $verbose; then + set -x +fi + +if $dryrun; then + dryrun="echo DRYRUN: " +else + dryrun="" +fi + + +#======================================================================================== +# +# SETUP/FINALIZE PROCEDURES +# +#======================================================================================== + +# affect the following global variables +declare top_artifacts ci_project ci_config notify_email +setup_notify_environment () +{ + echo "# ${FUNCNAME[0]}" + + # setup global variables + top_artifacts="${rr[top_artifacts]}" + ci_project=$(get_current_manifest "{rr[ci_project]}") + ci_config=$(get_current_manifest "{rr[ci_config]}") + + # Debug dump + echo "# Debug traces :" + echo "# Baseline : $(get_baseline_manifest BUILD_URL)" + echo "# Using dir : base-artifacts" + echo "# Artifacts : $(get_current_manifest BUILD_URL)" + echo "# Using dir : $top_artifacts" + echo "" + + mkdir -p "$top_artifacts/notify" + + case "$notify" in + *"@"*) + # Normally pw[PROJECT_patch_submitter] will have been set to + # "$notify" by precommit-ssh-apply.sh or pw-apply.sh. Manually + # started jobs where ${component}_git doesn't start with "ssh://" or + # "pw://" are an exception though, because then those scripts aren't + # called and thus the $pw array will be empty. Let's save the value + # here in case it's needed later. + notify_email="$notify" + notify="precommit" + ;; + esac + + declare -Ag pw + if [ "$notify" = "precommit" ]; then + obligatory_variables pw_dir + declare -g pw_dir + + local pw_file + while IFS= read -r -d '' pw_file; do + # shellcheck disable=SC1090 + source "$pw_file" + done < <(find "$pw_dir" -type f -print0) + fi +} + +# Exit with 0 status if given component has a single commit compared to baseline. +# $1: component +single_commit_p () +{ + ( + set -euf -o pipefail + + local c="$1" + + local base_rev cur_rev sha1 + base_rev=$(get_baseline_git "${c}_rev") + cur_rev=$(get_current_git "${c}_rev") + + for sha1 in $(git_component_cmd "$c" rev-parse "$cur_rev^@"); do + if [ "$sha1" = "$base_rev" ]; then + # We have a single-commit build + return 0 + fi + done + + return 1 + ) +} + +# check_source_changes : Looks at base-artifacts and artifacts to compute the +# changes for the toolchain source files. +# This procedure updates the following global variables for the rest of notify pass. +declare change_kind changed_single_component last_good first_bad +declare -a changed_components + +check_source_changes () +{ + echo "# ${FUNCNAME[0]}" + + # Set ${changed_components[@]} unless this is "init" build. For "init" + # builds we have no baseline_git data, so leave changed_components empty. + if [ "$(get_current_manifest "{rr[update_baseline]}")" != "init" ]; then + IFS=" " read -r -a changed_components <<< "$(print_changed_components)" + else + changed_components=() + fi + + local c base_rev cur_rev c_commits + if [ ${#changed_components[@]} = 0 ]; then + change_kind="no_change" + changed_single_component="" + elif [ ${#changed_components[@]} = 1 ]; then + changed_single_component="${changed_components[0]}" + + first_bad="$(get_current_git ${changed_single_component}_rev)" + last_good="$(get_baseline_git ${changed_single_component}_rev)" + + # single_commit_p() depend on $c to exist + local res + git_component_cmd "$changed_single_component" rev-parse --verify "HEAD" \ + &>/dev/null & + res=0 && wait $! || res=$? + assert_with_msg "Cannot parse HEAD in repo $changed_single_component" \ + [ $res = 0 ] + + if single_commit_p "$changed_single_component"; then + change_kind="single_commit" + else + change_kind="single_component" + fi + else + change_kind="multiple_components" + changed_single_component="" + fi + + # Debug dump + echo "# Debug traces :" + echo "# change_kind=$change_kind : ${changed_components[*]}" + for c in "${changed_components[@]}"; do + base_rev=$(get_baseline_git ${c}_rev) + cur_rev=$(get_current_git ${c}_rev) + c_commits=$(git_component_cmd "$c" rev-list --count $base_rev..$cur_rev || echo "??") + echo "# rev for $c : $base_rev..$cur_rev ($c_commits commits)" + done + echo "" +} + +# print routines pointers +declare print_commits_f print_result_f print_config_f print_last_icommit_f + +setup_stages_to_run () +{ + # if everything is fine. No reason to report to jira & icommits + if [ "$notify" = "onregression" ] \ + && { [ "${rr[no_regression_result]}" = "0" ] \ + || [ "$change_kind" != "single_commit" ]; }; then + notify=ignore + elif [ "$notify" = "precommit" ] \ + && [ "${rr[no_regression_result]}" = "0" ]; then + notify=ignore + fi + + if [ "$notify" = "ignore" ] || [ "$notify" = "precommit" ]; then + post_jira_comment=false + post_jira_card=false + post_icommits=false + # Even in "ignore" mode (successful build with no regression), + # we want to post gcc_testresults if relevant. + if [ "$notify" = "ignore" ]; then + post_mail=false + fi + if [ "$notify" = "precommit" ]; then + post_gcc_testresults=false + fi + fi + + # Disable dashboard generation as results-vs-first is now disabled + generate_dashboard=false + post_dashboard=false + + # print routines pointers + print_commits_f=print_commits + print_result_f=print_result + print_config_f=print_config + print_last_icommit_f=print_last_icommit + generate_extra_details_f=generate_extra_details + case "$ci_project" in + tcwg_binutils*|tcwg_bootstrap*|tcwg_gcc*|tcwg_gdb*|tcwg_glibc*|tcwg_gnu*) + print_result_f=gnu_print_result + generate_extra_details_f=gnu_generate_extra_details + print_config_f=gnu_print_config + ;; + tcwg_bmk*) + print_result_f=bmk_print_result + print_config_f=bmk_print_config + generate_extra_details_f=bmk_generate_extra_details + ;; + *) + # By default keep generic ones set above + ;; + esac +} + +release_notification_files () +{ + echo "# ${FUNCNAME[0]}" + if ! [ -d $top_artifacts/jenkins ]; then + return + fi + + local f + for f in mail-body.txt mail-subject.txt mail-recipients.txt; do + # copy the file if exists, and not emtpy. + # this important for mail-recipient.txt that may be empty. + if [ -s $top_artifacts/notify/$f ]; then + cp $top_artifacts/notify/$f $top_artifacts/jenkins/$f + fi + done + + echo "... Done" +} + +release_gcc_testresults_files () +{ + echo "# ${FUNCNAME[0]}" + if ! [ -d $top_artifacts/jenkins ]; then + return + fi + + # Send GCC test results only if the baseline was updated more than + # 1 day ago, so that we send at most one such email per day. + if [ -f $top_artifacts/testresults/testresults-mail-body.txt ]; then + base_d=$(get_baseline_manifest "{rr[gcc_testresults_date]}") + cur_d=$(get_current_component_date gcc || true) + if [ x"$cur_d" = x"" ]; then + return + fi + if [ x"$base_d" = x"" ]; then + base_d=0 + fi + + if [ $(( $cur_d - $base_d )) -ge 86400 ]; then + # The current date will become the baseline for the next build + # if the current baseline occurred more than one day ago. + cp $top_artifacts/testresults/testresults-mail-subject.txt \ + $top_artifacts/testresults/testresults-mail-body.txt $top_artifacts/jenkins/ + echo "gcc-testresults@gcc.gnu.org" > $top_artifacts/jenkins/testresults-mail-recipients.txt + cat <<EOF | manifest_out +rr[gcc_testresults_date]=$cur_d +EOF + else + # Otherwise, record the baseline date as the current + # gcc_testresults_date. Failing that, the next build will + # compare its date with 0 and may send its results more + # often than every day. + cat <<EOF | manifest_out +rr[gcc_testresults_date]=$base_d +EOF + fi + fi + + echo "... Done" +} + +#======================================================================================== +# +# GENERATE EXTRA DETAILS +# +#======================================================================================== +gnu_generate_extra_details() +{ + ( + set -euf -o pipefail + + # Extract 'configure' and 'make' lines for steps where it makes sense. + local tmpfile + tmpfile=$(mktemp) + + echo > $top_artifacts/notify/configure-make.txt + + while read step_console + do + artifact_dir="$(dirname ${step_console})" + ( + # We want to accept that 'xzgrep' can have zero match below + set +o pipefail + xzgrep RUN: ${step_console} | sed 's/.* RUN: //' > $tmpfile + ) + + grep /configure $tmpfile > $tmpfile-configure || true + grep "^make " $tmpfile > $tmpfile-make || true + if [ -s $tmpfile-configure ] || [ -s $tmpfile-make ]; then + echo "# $(basename $artifact_dir)" >> $top_artifacts/notify/configure-make.txt + cat $tmpfile-configure $tmpfile-make >> $top_artifacts/notify/configure-make.txt + echo >> $top_artifacts/notify/configure-make.txt + fi + done < <(set +f ; find $top_artifacts/[0-9][0-9]-* -name console.log.xz) + + rm -f $tmpfile $tmpfile-configure $tmpfile-make + + [ -s $top_artifacts/notify/configure-make.txt ] || rm -f $top_artifacts/notify/configure-make.txt + + if ! [ -d $top_artifacts/sumfiles ]; then + return 0 + fi + + # Compare results using compare_tests for information purposes. + # It works well only when we compare complete testsuites. + # shellcheck disable=SC2154 + gcc-compare-results/compare_tests -compr none -pass-thresh 0.9 \ + base-artifacts/sumfiles \ + "$top_artifacts/sumfiles" \ + > $top_artifacts/notify/results.compare.txt & + wait $! || true + ) +} + +bmk_generate_extra_details() +{ + ( + set -euf -o pipefail + local artifacts_mail_dir + + artifacts_mail_dir=$top_artifacts/notify + + $scripts/../bmk-scripts/output-bmk-results.py \ + --compare_results $top_artifacts/results-vs-prev/compare-results-internal.csv \ + --variability_file $top_artifacts/results-vs-prev/bmk-specific-variability-avg.csv \ + --variability_file_data "avg" \ + --run_step_dir "$artifacts_mail_dir/" \ + --metric "${rr[metric_id]}" --mode "${rr[mode]}" \ + --details verbose > "$artifacts_mail_dir/output-bmk-results.log" & + + local res=0 && wait $! || res=$? + assert_with_msg "ERROR while trying to regenerate bmk-data results. Aborting.." [ $res = 0 ] + ) +} + +generate_extra_details() +{ + ( + # nothing to do + true + ) +} + +#======================================================================================== +# +# PRINT ROUTINES +# +#======================================================================================== + +dump_model_only=${dump_model_only-false} + +#========================================== +# *GENERIC* PRINT ROUTINES +#========================================== + +###### PRINT COMMITS +# --link : link to the commit (only if single_commit) +# --oneline : commit title +# --short : commit log +print_commits() +{ + ( + set -euf -o pipefail + $dump_model_only && echo "<<${FUNCNAME[0]} $*>>" && return + local print_arg=$1 + + if [ "$change_kind" = "no_change" ]; then + echo "baseline build" + return 0 + fi + + local more_lines + if [ "$change_kind" = "single_commit" ]; then + local c="${changed_single_component}" + + if [ "$print_arg" = "--link" ]; then + local url + url=$(get_baseline_git ${c}_url) + if [[ "$url" =~ git://sourceware.org/git/ ]]; then + url="${url#git://sourceware.org/git/}" + url="https://sourceware.org/git/?p=$url" + echo "$url;a=commitdiff;h=$first_bad" + elif [[ "$url" =~ https://github.com/ ]] \ + || [[ "$url" =~ https://gitlab.com/ ]]; then + echo "${url%.git}/commit/$first_bad" + elif [[ "$url" =~ https://git.linaro.org/ ]]; then + echo "${url}/commit/?id=$first_bad" + else + echo "See in $url" + fi + + return 0 + fi + + local describe + if [ "${pw[$c]-}" = "" ]; then + describe=$(describe_sha1 "$c" "$first_bad" true) + # Remove the leading "basepoints/" + describe=$(echo "$describe" | sed 's,^basepoints/,,') + else + describe="$c patch #${pw[${c}_patch_id]}" + fi + + if [ "$print_arg" = "--oneline" ]; then + echo "$describe" + return 0 + fi + + if [ "${pw[$c]-}" = "" ]; then + echo "commit $describe" + else + echo "$c patch ${pw[${c}_patch_url]}" + fi + local tmpfile + tmpfile=$(mktemp) + git -C "$c" log -n1 "$first_bad" | tail -n +2 > "$tmpfile" + head -n 10 $tmpfile + more_lines=$(($(cat "$tmpfile" | wc -l) - 10)) + if [ $more_lines -gt 0 ]; then + echo "... $more_lines lines of the commit log omitted." + fi + rm $tmpfile + + if [ "${pw[$c]-}" != "" ]; then + echo "... applied on top of baseline commit:" + git -C $c log -n1 --oneline $last_good || true + fi + return 0 + fi + + if [ "$change_kind" = "single_component" ] \ + || [ "$change_kind" = "multiple_components" ]; then + local new_commits c base_rev cur_rev c_commits components + + local commits_or_patches + if [ "${pw[project]-}" != "" ]; then + commits_or_patches="patches" + else + commits_or_patches="commits" + fi + + new_commits=0 + for c in "${changed_components[@]}"; do + base_rev=$(get_baseline_git ${c}_rev) + cur_rev=$(get_current_git ${c}_rev) + c_commits=$(git_component_cmd "$c" rev-list --count $base_rev..$cur_rev \ + || echo 0) + new_commits=$(($new_commits + $c_commits)) + done + components=$(echo "${changed_components[@]}" | tr ' ' ',') + + echo "$new_commits $commits_or_patches in $components" + + if [ "$print_arg" = "--oneline" ]; then + return 0 + fi + + for c in "${changed_components[@]}"; do + base_rev=$(get_baseline_git ${c}_rev) + cur_rev=$(get_current_git ${c}_rev) + c_commits=$(git_component_cmd "$c" rev-list --count $base_rev..$cur_rev \ + || echo 0) + + if [ "${pw[$c]-}" != "" ]; then + echo "Patchwork URL: ${pw[${c}_patch_url]}" + fi + + git -C $c log -n 5 --oneline $base_rev..$cur_rev || true + if [ $c_commits -gt 5 ]; then + echo "... and $(($c_commits-5)) more $commits_or_patches in $c" + fi + + if [ "${pw[$c]-}" != "" ]; then + echo "... applied on top of baseline commit:" + git -C $c log -n1 --oneline $base_rev || true + fi + done + + return 0 + fi + ) +} + +###### PRINT the RESULTS of this build +# --oneline : either success ot failure +# --short : change of results.txt file between baseline and artifact +# --long : idem +print_result() +{ + $dump_model_only && echo "<<${FUNCNAME[0]} $*>>" && return + + local print_arg=$1 + case "$print_arg" in + --oneline) + if [ "${rr[no_regression_result]}" = "0" ]; then + echo "Success" + else + echo "Failure" + fi + ;; + --short|--long) + echo "Results changed to" + echo "$(cat $top_artifacts/results)" + echo "" + echo "From" + echo "$(cat base-artifacts/results)" + ;; + esac +} + +###### PRINT LAST ICOMMIT +# Extract the last status from icommit +# --entry : output directory of icommit entry +# --status : will output the icommit status +# --reproduction_instructions_link : will output the link to the reproduction_instructions +print_last_icommit () +{ + ( + $dump_model_only && echo "<<${FUNCNAME[0]}>>" && return + set -euf -o pipefail + local print_arg="$1" + shift 1 + + if [ x"$change_kind" != x"single_commit" ]; then + return 0 + fi + + local isubdir + isubdir=$(interesting_subdir "$changed_single_component" "$first_bad" "$@") + + case "$print_arg" in + --entry) + echo "$icommits/$isubdir" + ;; + --status) + cat "$icommits/$isubdir/status.txt" + ;; + --reproduction_instructions_link) + print_icommits_link "$isubdir/reproduction_instructions.txt" + ;; + esac + ) +} + +# Print link url to interesting-commits.git repo for "$path". +print_icommits_link () +{ + ( + set -euf -o pipefail + local path="$1" + + local url="https://git-us.linaro.org/toolchain/ci/interesting-commits.git" + echo "$url/plain/$path" + ) +} + +# CHECK_IF_FIRST_REPORT : Check if this is the first report in icommits +# Result stored in first_icommit_to_report global variable +check_if_first_report() +{ + declare -g first_icommit_to_report + + first_icommit_to_report=false + if [ x"$change_kind" != x"single_commit" ]; then + return + fi + + local isubdir + isubdir=$(interesting_subdir "$changed_single_component" "$first_bad") + if ! [ -f "$icommits/$isubdir/first_url" ]; then + return + fi + + local first_url + first_url=$(cat "$icommits/$isubdir/first_url") + if [ "$first_url" = "$(get_current_manifest BUILD_URL)" ]; then + first_icommit_to_report=true + elif [ "$notify" = "onregression" ]; then + # Send out emails for post-commit "onregression" reports only for + # the first detection. + post_mail=false + fi +} + +###### PRINT the configuration of this build +# --oneline : target short name (eg: arm/aarch64) +# --short : short "pretty" version suitable for summary +# --long : full details +print_config() +{ + $dump_model_only && echo "<<${FUNCNAME[0]} $*>>" && return + + local print_arg=$1 + case "$print_arg" in + --oneline) + case "$ci_config" in + *arm*) echo "arm" ;; + *aarch64*) echo "aarch64" ;; + *) echo "$ci_config" ;; + esac + ;; + --short|--long) + echo "CI config $ci_project/$ci_config" + ;; + esac +} + +print_artifacts_url () +{ + ( + set -euf -o pipefail + + local url + url="$(get_current_manifest BUILD_URL)artifact/artifacts" + if [ "${pw[project]-}" != "" ]; then + url="$url/artifacts.precommit" + fi + echo "$url/$*" + ) +} + +#========================================== +# *GNU* PRINT ROUTINES +#========================================== + +###### PRINT the configuration of this build +# --oneline : target short name (eg: arm/aarch64) +# --short : short "pretty" version suitable for summary +# --long : full details +gnu_print_config() +{ + ( + $dump_model_only && echo "<<${FUNCNAME[0]} $*>>" && return + + # shellcheck source=tcwg_gnu-config.sh + . $scripts/tcwg_gnu-config.sh + + settings_for_ci_project_and_config "$ci_project" "$ci_config" + + local print_arg=$1 + case "$print_arg" in + --oneline) + print_config "$print_arg" + ;; + --short) + echo "${gnu_data[pretty_project]} ${gnu_data[pretty_config]}" + ;; + --long) + echo "CI config $ci_project ${gnu_data[long_config]}" + ;; + esac + ) +} + +# most of them mapped to generic implementation +gnu_print_result() +{ + $dump_model_only && echo "<<${FUNCNAME[0]} $*>>" && return + local print_arg="$1" + + if ! [ -d $top_artifacts/sumfiles ]; then + print_result "$@" + return 0 + fi + + # From now on, we should have called already gnu_generate_extra_details + + local validate_failures="gcc-compare-results/contrib/testsuite-management/validate_failures.py" + local xfails="$top_artifacts/sumfiles/xfails.xfail" + + if ! [ -f "$xfails" ]; then + return 0 + fi + + "$validate_failures" --manifest="$xfails" \ + --expiry_date="${rr[result_expiry_date]}" \ + --build_dir="$top_artifacts/sumfiles" --verbosity=1 \ + > $top_artifacts/notify/regressions.sum & + wait $! || true + "$validate_failures" --inverse_match --manifest="$xfails" \ + --expiry_date="${rr[result_expiry_date]}" \ + --build_dir="$top_artifacts/sumfiles" --verbosity=1 \ + > $top_artifacts/notify/progressions.sum & + wait $! || true + + grep -A10 "=== Results Summary ===" $top_artifacts/notify/regressions.sum \ + > $top_artifacts/notify/results-summary.txt + + local n_regressions n_progressions pass_fail=PASS + if [ "${rr[no_regression_result]}" != "0" ]; then + pass_fail=FAIL + fi + n_regressions=$(grep -c "^[A-Z]\+:" \ + $top_artifacts/notify/regressions.sum || true) + n_progressions=$(grep -c "^[A-Z]\+:" \ + $top_artifacts/notify/progressions.sum || true) + + printf "$pass_fail" + if [ "$n_regressions" != "0" ]; then + printf ": $n_regressions regressions" + else + rm $top_artifacts/notify/regressions.sum + fi + if [ "$n_progressions" != "0" ]; then + printf ": $n_progressions progressions" + else + rm $top_artifacts/notify/progressions.sum + fi + printf "\n" + + if [ "$print_arg" = "--oneline" ]; then + return 0 + fi + + local length=10 outfile n_lines + if [ "$print_arg" = "--long" ]; then + # Ask "head" to print out all the lines except for the last 0 lines. + length=-0 + fi + + for outfile in regressions.sum progressions.sum; do + if ! [ -f $top_artifacts/notify/$outfile ]; then + continue + fi + + echo + echo "$outfile:" + # Copy contents until "Results Summary" line (excluded) + n_lines=$(grep -n "Results Summary" $top_artifacts/notify/$outfile \ + | cut -d: -f1) + n_lines=$(($n_lines - 1)) + + if [ $n_lines -lt $length ]; then + length=$n_lines + fi + + head -n$length $top_artifacts/notify/$outfile + + n_lines=$(($n_lines - $length)) + if [ $n_lines -gt 0 ] && [ $length != -0 ]; then + echo "... and $n_lines more entries" + fi + done + + cat <<EOF + +You can find the failure logs in *.log.1.xz files in + - $(print_artifacts_url 00-sumfiles/) +The full lists of regressions and progressions as well as configure and make commands are in + - $(print_artifacts_url notify/) +The list of [ignored] baseline and flaky failures are in + - $(print_artifacts_url sumfiles/xfails.xfail) +EOF +} + + +#========================================== +# *BMK* PRINT ROUTINES +#========================================== + +# print_result. either oneline version or short/long. +# Here is an ex : +# --oneline : +# 644.nab_s slowed down of 4% +# --short/long : +# the following benchmarks slowed down by more than 3%: +# - 644.nab_s slowed down by 4% from 112963 to 117189 perf samples +bmk_print_result() +{ + ( + set -euf -o pipefail + + $dump_model_only && echo "<<${FUNCNAME[0]} $*>>" && return + + local print_arg=$1 + + artifacts_mail_dir=$top_artifacts/notify + + if [ "$stage" != "full" ]; then + return + fi + + ## Prepare data + + # From now on, we should have called already bmk_generate_extra_details + + # If there's one regression. Don't bother about improvements. + local improved_or_regressed + if [ -f $artifacts_mail_dir/exe.regression ] || [ -f $artifacts_mail_dir/symbol.regression ]; then + improved_or_regressed=regression + else + improved_or_regressed=improvement + fi + + declare -A changed_by_msg + changed_by_msg[size-regression]="grew in size by" + changed_by_msg[size-improvement]="reduced in size by" + changed_by_msg[sample-regression]="slowed down by" + changed_by_msg[sample-improvement]="speeds up by" + changed_by_msg[num_vect_loops-regression]="reduced the number of vect loops by" + changed_by_msg[num_vect_loops-improvement]="increased the number of vect loops by" + changed_by_msg[num_sve_loops-regression]="reduced the number of sve instructions by" + changed_by_msg[num_sve_loops-improvement]="increased the number of sve instructions by" + changed_by=${changed_by_msg[${rr[metric_id]}-$improved_or_regressed]} + + # FIXME: Remove hard-coded thresholds + # thresholds + case ${rr[metric_id]} in + size) + exe_threshold=1 # We use 1% tolerance for binary size + symbol_threshold=10 # and 10% tolerance for symbol size. + ;; + sample) + # Reduce thresholds when bisecting to avoid considering borderline + # regressions as spurious. This should break cycles of build and + # bisect jobs triggering each other on borderline regressions. + exe_threshold=3 + symbol_threshold=15 + ;; + num_vect_loops|num_sve_loops) + exe_threshold=0 + symbol_threshold=0 + ;; + *) assert false ;; + esac + + # Now print result + case "$print_arg" in + --oneline) + assert_with_msg "Builds with infra problems should never get here" \ + [ "${rr[no_regression_result]}" != "$EXTERNAL_FAIL" ] + + # Generate readable oneline diag + local metric bmk symbol short_diag long_diag + if [ -f $artifacts_mail_dir/exe.$improved_or_regressed ]; then + # shellcheck disable=SC2034 + IFS=, read metric bmk symbol short_diag long_diag < <(head -n1 $artifacts_mail_dir/exe.$improved_or_regressed) + elif [ -f $artifacts_mail_dir/symbol.$improved_or_regressed ]; then + # shellcheck disable=SC2034 + IFS=, read metric bmk symbol short_diag long_diag < <(head -n1 $artifacts_mail_dir/symbol.$improved_or_regressed) + else + short_diag="No change" + fi + echo "$short_diag" + ;; + + --short|--long) + # The following exe regressed/improved: + if [ -f $artifacts_mail_dir/exe.$improved_or_regressed ]; then + sort -gr -o $artifacts_mail_dir/exe.$improved_or_regressed \ + $artifacts_mail_dir/exe.$improved_or_regressed + + echo "the following benchmarks $changed_by more than ${exe_threshold}%:" + local metric exe symbol short_diag long_diag + while IFS=, read metric exe symbol short_diag long_diag; do + echo "- $long_diag" + if [ -f $artifacts_mail_dir/$exe.symbols-$improved_or_regressed ]; then + while IFS=, read metric bmk symbol short_diag long_diag; do + echo " - $long_diag" + done < $artifacts_mail_dir/$exe.symbols-$improved_or_regressed + # Delete $bmk.regressions so that it doesn't show up + # in symbol-regression loop below. + rm $artifacts_mail_dir/$exe.symbols-$improved_or_regressed + fi + done < $artifacts_mail_dir/exe.$improved_or_regressed + fi + + # The following functions regressed/improved: + if [ -f $artifacts_mail_dir/symbol.$improved_or_regressed ]; then + echo "the following hot functions $changed_by more than ${symbol_threshold}% (but their benchmarks $changed_by less than ${exe_threshold}%):" + local metric bmk symbol short_diag long_diag + # shellcheck disable=SC2034 + while IFS=, read metric bmk symbol short_diag long_diag; do + echo "- $long_diag" + done < $artifacts_mail_dir/symbol.$improved_or_regressed + fi + + if ! [ -f $artifacts_mail_dir/exe.$improved_or_regressed ] && ! [ -f $artifacts_mail_dir/symbol.$improved_or_regressed ]; then + echo "No change" + fi + ;; + esac + ) +} + +###### PRINT the configuration of this build +# --oneline : target short name (eg: arm/aarch64) +# --short : short "pretty" version suitable for summary +# --long : full details +bmk_print_config() +{ + # shellcheck source=tcwg_bmk-config.sh + . $scripts/tcwg_bmk-config.sh + + $dump_model_only && echo "<<${FUNCNAME[0]} $*>>" && return + + # ${ci_project}--${ci_config} format is : + # 'tcwg_bmk-#{PROFILE_NAME}-#{BMK}--#{TOOLCHAIN}-#{TARGET}-{toolchain_ver}-{cflags}' + IFS=- read -a ci_pjt_cfg <<EOF +$ci_project--$ci_config +EOF + local toolchain target cflags + toolchain="${ci_pjt_cfg[4]}" + target="${ci_pjt_cfg[5]}" + cflags="${ci_pjt_cfg[7]}" + + local compiler="" libc="" linker="" version="" bmk_flags="" hw="" + bmk_flags=$(echo "$cflags" | sed -e "s/_/ -/g") + + local print_arg=$1 + case "$print_arg" in + --oneline) + case "$ci_config" in + *arm*) echo "arm $bmk_flags" ;; + *aarch64*) echo "aarch64 $bmk_flags" ;; + *) echo "$ci_config" ;; + esac + return 0 + ;; + --short) + print_config "$print_arg" + return 0 + ;; + esac + + # --long as default + + local bmk_suite publish_save_temps + bmk_suite="" + publish_save_temps=false + case "$(tcwg_bmk_benchs)" in + coremark) + bmk_suite="EEMBC CoreMark" + ;; + spec2k6|4*) + bmk_suite="SPEC CPU2006" + publish_save_temps=true + ;; + spec2017*|5*|6*) + bmk_suite="SPEC CPU2017" + publish_save_temps=true + ;; + esac + + cat <<EOF +Below reproducer instructions can be used to re-build both "first_bad" and "last_good" cross-toolchains used in this bisection. Naturally, the scripts will fail when triggerring benchmarking jobs if you don\'t have access to Linaro TCWG CI. +EOF + + # Copy save-temps tarballs to artifacts, so that they are accessible. + # We can publish pre-processed source only for benchmarks derived from + # open-source projects. + # Note that we include save-temps artifacts for successful builds so that + # "last_good" build has the artifacts. + if $publish_save_temps; then + mkdir -p ${top_artifacts}/top-artifacts + local s_t + while read s_t; do + case "$s_t" in + 400.perlbench*|500.perlbench*|600.perlbench*) ;; + 401.bzip2*) ;; + 403.gcc*|502.gcc*|602.gcc*) ;; + 435.gromacs*) ;; + 436.cactusADM*|507.cactuBSSN*|607.cactuBSSN*) ;; + 445.gobmk*) ;; + 454.calculix*) ;; + 456.hmmer*) ;; + 462.libquantum*) ;; + 465.tonto*) ;; + 481.wrf*|521.wrf*|621.wrf*) ;; + 482.sphinx3*) ;; + 483.xalanc*) ;; + 505.mcf*|605.mcf*) + # 429.mcf is not present in redistributable_sources/ :-| + ;; + 511.povray*) ;; + 525.x264*|625.x264*) + # 464.h264ref is not present in redistributable_sources/ :-| + ;; + 526.blender*) ;; + 527.cam4*|627.cam4*) ;; + 538.imagick*|638.imagick*) ;; + 544.nab*|644.nab*) ;; + 554.roms*|654.roms*) ;; + 557.xz*|657.xz*) ;; + 628.pop2) ;; + *) + # Can't redistribute benchmark sources. + continue + ;; + esac + cp "$s_t" ${top_artifacts}/top-artifacts/save-temps/ + done < <(find results-1 -path "save.*.temps/*.tar.xz") + fi + + if [ -d ${top_artifacts}/top-artifacts/save-temps/ ]; then + cat <<EOF + +For your convenience, we have uploaded tarballs with pre-processed source and assembly files at: +- First_bad save-temps: \$FIRST_BAD_ARTIFACTS/save-temps/ +- Last_good save-temps: \$LAST_GOOD_ARTIFACTS/save-temps/ +- Baseline save-temps: \$BASELINE_ARTIFACTS/save-temps/ +EOF + fi + + case "$toolchain" in + gnu) + compiler="GCC" + libc="Glibc" + linker="GNU Linker" + ;; + gnu_eabi) + compiler="GCC" + libc="Newlib" + linker="GNU LD" + ;; + llvm) + compiler="Clang" + libc="Glibc" + linker="LLVM Linker" + ;; + esac + case "$ci_config" in + *-master-*) version="tip of trunk" ;; + *-release-*) version="latest release branch" ;; + esac + case "$(tcwg_bmk_hw)" in + *apm*) hw="APM Mustang 8x X-Gene1" ;; + *tk1*) hw="NVidia TK1 4x Cortex-A15" ;; + *tx1*) hw="NVidia TX1 4x Cortex-A57" ;; + *stm32*) hw="STMicroelectronics STM32L476RGTx 1x Cortex-M4" ;; + *fx*) hw="Fujitsu FX700 48x AA64" ;; + *qc*) hw="Qualcomm 8x AA64" ;; + *) hw="<unknown>" + esac + + cat <<EOF + +Configuration: +- Benchmark: $bmk_suite +- Toolchain: $compiler + $libc + $linker +- Version: all components were built from their $version +- Target: $(print_gnu_target $target) +- Compiler flags: $bmk_flags +- Hardware: $hw + +This benchmarking CI is work-in-progress, and we welcome feedback and suggestions at linaro-toolchain@lists.linaro.org . In our improvement plans is to add support for SPEC CPU2017 benchmarks and provide "perf report/annotate" data behind these reports. +EOF +} + +#======================================================================================== +# +# INTERESTING_COMMITS PROCEDURES +# +#======================================================================================== + +# Print out merged version of status-summary.txt files at a given subdir level. +# As an initial approximation, just pick a summary with the biggest number. +merge_status_summary () +{ + ( + set -euf -o pipefail + local subdir="$1" + + local cur_file cur best="" best_file + + while read -r cur_file; do + # Extract a number " N " or " N%" + cur=$(sed -e "s/.* \([0-9]\+\)[ %].*/\1/" "$cur_file") + if ! [ "$cur" -le "$best" ]; then + best="$cur" + best_file="$cur_file" + fi + done < <(find "$subdir" -mindepth 2 -maxdepth 2 \ + -name "status-summary.txt" | sort) + + cat "$best_file" + ) +} + +# update_interesting_commits : will update the local icommit repository with +# the new run. +# If it is a regression, single_commit, may be updating first_report variable +# +# Interesting-commits store regression history in the following hierarchy: +# 1. At the top level we have COMPONENT directories. +# 2. At the 2nd level we have SHA1 directories. +# 2a. We also have "git describe" symlinks to SHA1 directories for convenience. +# 3. At the 3rd level we have CI_PROJECT directories. +# 4. At the 4th level we have CI_CONFIG directories, and ... +# 4a. ... status.txt, which contains status of changes from SHA1 across all +# CI_CONFIGs in CI_PROJECT. +# 5. At the 5th level we have per-build files last_good, details.txt, etc. +update_interesting_commits () +{ + echo "# ${FUNCNAME[0]}" + + local stage="$1" + local jira_key="$2" + + # We have successfully identified a bad commit with a good parent! + # Add $first_bad to interesting commits. + + local subdir3 subdir4 subdir5 + # Commit top-level dir + subdir3=$(interesting_subdir "$changed_single_component" "$first_bad") + # Commit project-level dir + subdir4=$(interesting_subdir "$changed_single_component" "$first_bad" \ + "$ci_project") + # Commit config-level dir + subdir5=$(interesting_subdir "$changed_single_component" "$first_bad" \ + "$ci_project" "$ci_config") + + if ! [ -d "$icommits/$subdir3" ]; then + mkdir -p $icommits/$subdir3 + get_current_manifest BUILD_URL > $icommits/$subdir3/first_url + git -C $icommits add $subdir3/first_url + fi + + # Update interesting-commits/$component/$first_bad/$ci_project/$ci_config + mkdir -p "$icommits/$subdir5" + echo "$(get_current_manifest BUILD_URL)artifact/artifacts" > "$icommits/$subdir5/build_url" + echo "$last_good" > "$icommits/$subdir5/last_good" + git -C "$icommits" add "$subdir5/build_url" "$subdir5/last_good" + + if [ "$stage" != "full" ]; then + return + fi + + # $icommit/<comp>/sha1/<sha1>/.../status-summary.txt + $print_result_f --oneline > "$icommits/$subdir5/status-summary.txt" + merge_status_summary $icommits/$subdir4 \ + > "$icommits/$subdir4/status-summary.txt" + merge_status_summary $icommits/$subdir3 \ + > "$icommits/$subdir3/status-summary.txt" + git -C "$icommits" add \ + "$subdir5/status-summary.txt" \ + "$subdir4/status-summary.txt" \ + "$subdir3/status-summary.txt" + + $print_result_f --long > "$icommits/$subdir5/details.txt" + if [ -f $top_artifacts/notify/configure-make.txt ]; then + cat $top_artifacts/notify/configure-make.txt >> "$icommits/$subdir5/details.txt" + fi + + # $icommit/<comp>/sha1/<sha1>/<ci_project>/<ci_config>/status.txt + ( + cat "$icommits/$subdir5/status-summary.txt" + print_icommits_link "$subdir5/details.txt" + cat $icommits/$subdir5/build_url + ) | sed "s/^/* /" > "$icommits/$subdir5/status.txt" + + git -C "$icommits" add "$subdir5/details.txt" "$subdir5/status.txt" + + # FIXME: Remove transisional workaround for summary.txt -> status.txt + if [ -f "$icommits/$subdir5/summary.txt" ]; then + git -C "$icommits" rm "$subdir5/summary.txt" + fi + + # $icommit/<comp>/sha1/<sha1>/<ci_project>/<ci_config>/reproduction_instructions.txt + local bad_artifacts_url good_artifacts_url + bad_artifacts_url="$(get_current_manifest BUILD_URL)artifact/artifacts" + good_artifacts_url="$(get_baseline_manifest BUILD_URL)artifact/artifacts" + cat > $icommits/$subdir5/reproduction_instructions.txt << EOF +mkdir -p investigate-$changed_single_component-$first_bad +cd investigate-$changed_single_component-$first_bad + +# Fetch scripts +git clone https://git.linaro.org/toolchain/jenkins-scripts + +# Fetch manifests for bad and good builds +mkdir -p bad/artifacts good/artifacts +curl -o bad/artifacts/manifest.sh $bad_artifacts_url/manifest.sh --fail +curl -o good/artifacts/manifest.sh $good_artifacts_url/manifest.sh --fail + +# Reproduce bad build +(cd bad; ../jenkins-scripts/${build_script} @@rr[top_artifacts] artifacts) +# Reproduce good build +(cd good; ../jenkins-scripts/${build_script} @@rr[top_artifacts] artifacts) +EOF + git -C "$icommits" add $subdir5/reproduction_instructions.txt + + # $icommit/<comp>/sha1/<sha1>/<ci_project>/status.txt + local ci_config + while read ci_config; do + # FIXME: Remove transisional workaround for summary.txt -> status.txt + if [ -f "$icommits/$subdir4/$ci_config/summary.txt" ]; then + echo "* $ci_config" + cat $icommits/$subdir4/$ci_config/summary.txt | sed "s/^/** /" + continue + fi + + if ! [ -f "$icommits/$subdir4/$ci_config/status.txt" ]; then + continue + fi + + echo "* $ci_config" + cat $icommits/$subdir4/$ci_config/status.txt | sed "s/^/*/" + done < <(cd $icommits/$subdir4; ls) > "$icommits/$subdir4/status.txt" + git -C "$icommits" add "$subdir4/status.txt" + + # $icommit/<comp>/sha1/<sha1>/status.txt + local ci_project + while read ci_project; do + if ! [ -f "$icommits/$subdir3/$ci_project/status.txt" ]; then + continue + fi + + echo "* $ci_project" + cat $icommits/$subdir3/$ci_project/status.txt | sed "s/^/*/" + done < <(cd $icommits/$subdir3; ls) > "$icommits/$subdir3/status.txt" + git -C "$icommits" add "$subdir3/status.txt" + + # $icommit/<comp>/sha1/<sha1>/commit-log.txt + $print_commits_f --short > "$icommits/$subdir3/commit-log.txt" + git -C "$icommits" add "$subdir3/commit-log.txt" + + if $generate_jira; then + local jira_dir="$subdir3/jira" + + if [ -f "$icommits/$jira_dir/key" ]; then + assert_with_msg "Should not have created multiple jira cards" \ + [ "$jira_key" = "" ] + jira_key=$(cat "$icommits/$jira_dir/key") + fi + + # Recreate jira/ dir with up-to-date info. + if [ -e "$icommits/$jira_dir" ]; then + git -C "$icommits" rm -rf "$jira_dir" + fi + mkdir "$icommits/$jira_dir" + + if [ "$jira_key" != "" ]; then + echo "$jira_key" > "$icommits/$jira_dir/key" + git -C "$icommits" add "$jira_dir/key" + fi + + # Keep jira/summary in sync with mail-subject.txt + echo "$($print_commits_f --oneline):" \ + "$(cat "$icommits/$subdir3/status-summary.txt")" \ + > "$icommits/$jira_dir/summary" + git -C "$icommits" add "$jira_dir/summary" + + # FIXME: Unify format with email + # We stop posting updates to jira cards once they are closed to avoid + # spamming developers about resolved issues. Therefore, jira data in + # interesting-commits.git repo may be more up-to-date than in + # the actual jira cards. We add a link to jira/yaml in the card + # description to make it easy for developers to look at the latest + # info for closed cards. + cat > "$icommits/$jira_dir/description" <<EOF +Commit: $($print_commits_f --link) +$(cat "$icommits/$subdir3/commit-log.txt") + +$(cat "$icommits/$subdir3/status.txt") + +Latest data: $(print_icommits_link "$jira_dir/yaml") +EOF + git -C "$icommits" add "$jira_dir/description" + + update_jira_card + fi + + # Generate a $describe_sha1 symlink + local describe + describe=$(describe_sha1 "$changed_single_component" "$first_bad" false) + if [ "$describe" != "" ]; then + local d + d=$(dirname "$describe") + mkdir -p $icommits/$changed_single_component/$d + local symlink="" + while [ "$d" != "." ]; do + symlink="../$symlink" + d=$(dirname "$d") + done + symlink="${symlink}sha1/$first_bad" + # ??? For some reason I don't understand overwriting symlink with + # "ln -sf" may create a second [broken] symlink. + rm -f $icommits/$changed_single_component/$describe + ln -s $symlink $icommits/$changed_single_component/$describe + git -C $icommits add $changed_single_component/$describe + fi +} + + +# Generate entry in interesting-commits.git and, if $post_icommits, +# push it. This is applicable only for single-commit case. +# This is called twice -- first time with $stage == init, and second time +# with $stage == full. +# During "init" stage we fetch existing entries and create a very basic entry +# if there isn't one. This allows us to determine in check_if_first_report() +# whether this is the first build to discover $first_bad as interesting commit. +post_interesting_commits () +{ + ( + set -euf -o pipefail + echo "# ${FUNCNAME[0]}" + + local stage="$1" + + if [ "$change_kind" != "single_commit" ]; then + return + fi + + # Clone interesting-commits.git repo, which contains a regression + # summaries for SHA1s. These are the "first_bad" commits. + clone_or_update_repo $icommits master \ + https://git-us.linaro.org/toolchain/ci/interesting-commits.git \ + auto master + + if ! $post_icommits; then + dryrun="echo DRYRUN: " + fi + + local jira_dir jira_key="" + jira_dir=$(interesting_subdir "$changed_single_component" "$first_bad") + jira_dir="$jira_dir/jira" + if [ "$stage" = "full" ] && $post_jira_card && $first_icommit_to_report \ + && [ "$dryrun" = "" ]; then + if ! [ -f "$icommits/$jira_dir/key" ]; then + # Create jira card for this interesting commit. + # We first create the card, + # then add a link to it in interesting-commits.git, + # then we update the card's content every time we change entry in + # interesting-commits. + jira_key=$(create_jira_card) + else + echo "WARNING: jira card already exists $icommits/$jira_dir/key:" \ + "$(cat "$icommits/$jira_dir/key")" + fi + fi + + # Regenerate interesting-commits entry and try to push to upstream. + # - If failed to push, update icommits again + # This can happen if another job pushed to interesting-commits just + # before this job. + # - If successful we consider round-robin-notify.sh to be successful. + while true; do + # Reset to master branch + git -C $icommits remote update -p + git_clean $icommits refs/remotes/origin/master + + update_interesting_commits "$stage" "$jira_key" + + # Commit changes (if any) to local clone of interesting-commits.git + git -C $icommits commit \ + -m "Add entry $first_bad from $(get_current_manifest BUILD_URL)" \ + || break + + $dryrun git -C $icommits push \ + ssh://git-us.linaro.org/toolchain/ci/interesting-commits.git \ + HEAD:refs/heads/master & + if wait $!; then + # Push successful : stop here + break + fi + # Push failed. update icommit and retry pushing + done + ) +} + +#======================================================================================== +# +# JIRA RELATED PROCEDURES +# +#======================================================================================== + +print_jira_template_card () +{ + # Catch-all case for when project/config IDs change, so that we + # won't miss notifications. Forward all that to GNU-692. + local jira_card="GNU-692" + case "$ci_project/$ci_config:$changed_single_component" 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-*_speed*/gnu*) jira_card="GNU-689" ;; + tcwg_bmk-*_size*/gnu*) jira_card="GNU-686" ;; + tcwg_bmk-*_vect*/gnu*) jira_card="GNU-988" ;; + tcwg_bmk-*_sve*/gnu*) jira_card="GNU-988" ;; + tcwg_bmk-*_speed*/llvm*) jira_card="LLVM-651" ;; + tcwg_bmk-*_size*/llvm*) jira_card="LLVM-650" ;; + tcwg_bmk-*_vect*/llvm*) jira_card="LLVM-1013" ;; + tcwg_bmk-*_sve*/llvm*) jira_card="LLVM-1013" ;; + tcwg_aosp-*/*) jira_card="LLVM-1014" ;; + esac + echo "$jira_card" +} + +# Create jira card for this interesting commit. +# Link to this card is stored in $icommits/.../jira/key +create_jira_card () +{ + ( + set -euf -o pipefail + + local template project parent assignee yaml + template=$(print_jira_template_card) + project="${template%%-*}" + parent=$(jipsearch -j "key=$template" -s parent:key \ + | sed -e "s/.* , //") + assignee=$(jipsearch -j "key=$template" -s assignee:emailAddress \ + | sed -e "s/.* , //" || true) + if [ "$assignee" = "" ]; then + # The template card is unassigned, so use parent's assignee. + assignee=$(jipsearch -j "key=$parent" -s assignee:emailAddress \ + | sed -e "s/.* , //") + fi + + yaml=$(mktemp) + # shellcheck disable=SC2064 + trap "rm $yaml" EXIT + + cat > $yaml <<EOF +- Project: $project + IssueType: Sub-task + Parent: $parent + Summary: $changed_single_component:$first_bad + AssigneeEmail: $assignee +EOF + local key + key=$(jipcreate -f $yaml | sed -e "s#.*/##") + echo "$key" + ) +} + +# Print the existing jira card number for this interesting commit. +print_jira_card_key () +{ + ( + set -euf -o pipefail + + local jira_dir + jira_dir=$(interesting_subdir "$changed_single_component" "$first_bad") + jira_dir="$jira_dir/jira" + + if ! [ -f "$icommits/$jira_dir/key" ]; then + return 0 + fi + + cat "$icommits/$jira_dir/key" + ) +} + +# Update jira card for this interesting commit. +# Link to this card is stored in $icommits/.../jira/key +update_jira_card () +{ + ( + set -euf -o pipefail + echo "# ${FUNCNAME[0]}" + + local jira_dir + jira_dir=$(interesting_subdir "$changed_single_component" "$first_bad") + jira_dir="$jira_dir/jira" + + local -a components=() + case "$changed_single_component" in + binutils) components+=(Binutils) ;; + gcc) components+=(GCC) ;; + gdb) components+=(GDB) ;; + glibc) components+=(Glibc) ;; + linux) components+=(Linux) ;; + llvm) components+=(LLVM) ;; + newlib) components+=(Newlib) ;; + qemu) components+=(QEMU) ;; + *) components+=(CI) ;; + esac + (IFS=","; echo "${components[*]}") > "$icommits/$jira_dir/components" + git -C "$icommits" add "$jira_dir/components" + + local commit_date + commit_date=$(git -C "$changed_single_component" log -n1 \ + --pretty="%cd" --date=iso "$first_bad") + date -d "$commit_date" +%Y-%m-%d > "$icommits/$jira_dir/startdate" + git -C "$icommits" add "$jira_dir/startdate" + + local key project + key=$(print_jira_card_key) + if [ -z "$key" ]; then + echo "WARNING: no existing jira card $icommits/$jira_dir/key" + return 0 + fi + + project="${key%%-*}" + + local yaml="$icommits/$jira_dir/yaml" + + cat > "$yaml" <<EOF +- Project: $project + IssueType: Sub-task + Key: $key + Summary: | +EOF + # Summary can have spaces and other special-to-yaml symbols; + # quote using " |" + sed -e "s/^/ /" "$icommits/$jira_dir/summary" >> "$yaml" + cat >> "$yaml" <<EOF + Components: $(cat "$icommits/$jira_dir/components") + Start date: $(cat "$icommits/$jira_dir/startdate") + Description: | +EOF + sed -e "s/^/ /" "$icommits/$jira_dir/description" >> "$yaml" + git -C "$icommits" add "$jira_dir/yaml" + ) +} + +# Generate notify/jira/* files +generate_jira_dir() +{ + ( + set -euf -o pipefail + + local icommit_entry jira_key="" + icommit_entry=$($print_last_icommit_f --entry) + + if [ "$icommit_entry" != "" ] && [ -d "$icommit_entry/jira" ]; then + rsync -a "$icommit_entry/jira/" "$top_artifacts/notify/jira/" + if [ -f "$top_artifacts/notify/jira/key" ]; then + jira_key=$(cat "$top_artifacts/notify/jira/key") + fi + else + mkdir -p "$top_artifacts/notify/jira" + fi + + if [ "$jira_key" != "" ]; then + cat > $top_artifacts/notify/jira/comment-card.txt <<EOF +[$jira_key] +$($print_result_f --oneline) +Details: $(print_artifacts_url notify/mail-body.txt/*view*/) +EOF + cat > $top_artifacts/notify/jira/comment-template.txt <<EOF +[$(print_jira_template_card)] +https://linaro.atlassian.net/browse/$jira_key +$($print_result_f --oneline) +Details: $(print_artifacts_url notify/mail-body.txt/*view*/) +EOF + else + cat > $top_artifacts/notify/jira/comment-template.txt << EOF +[$(print_jira_template_card)] +$($print_result_f --oneline) +Details: $(print_artifacts_url notify/mail-body.txt/*view*/) +EOF + fi + ) +} + +# Post update to Jira +post_to_jira () +{ + ( + set -euf -o pipefail + echo "# ${FUNCNAME[0]}" + + local post_card_comment=$post_jira_comment + local post_template_comment=$post_jira_comment + + if $post_jira_card && [ -f $top_artifacts/notify/jira/yaml ]; then + local key status + key=$(print_jira_card_key) + status=$(jipsearch -j "key=$key" -s status:name \ + | sed -e "s/.* , //") + + # Do not update closed cards to avoid spamming developers about + # updating resolved issues. Note that this also skips posting + # comments below. + case "$status" in + "Closed") + post_card_comment=false + # In this case we may still post a comment to the template + # card if $post_jira_comment is true. + ;; + *) + $dryrun jipcreate -f $top_artifacts/notify/jira/yaml + post_template_comment=false + ;; + esac + fi + + if $post_card_comment \ + && [ -f $top_artifacts/notify/jira/comment-card.txt ]; then + echo y | $dryrun jipdate \ + -f $top_artifacts/notify/jira/comment-card.txt + fi + + if $post_template_comment \ + && [ -f $top_artifacts/notify/jira/comment-template.txt ]; then + echo y | $dryrun jipdate \ + -f $top_artifacts/notify/jira/comment-template.txt + fi + ) +} + + +#======================================================================================== +# +# MAIL RELATED PROCEDURES +# +#======================================================================================== +# Model for mail-recipient.txt (may contain some of the following): +# <author>, <compiler-mailing-list>, <linaro-toolchain-mailing-list>, <tcwg-validation-mailing-list> +# (generate_mail_recipients : Generic) +# +# Model for mail-subject.txt : +# [Linaro-TCWG-CI] <diag> after <changes> (generate_mail_subject : Generic) +# +# Model for mail-body.txt : +# <mail regression details> (generate_mail_body_regression : Specific to the kind of project) +# +# <reproduction instructions details> (generate_mail_body_reproduction_instructions : Generic) +# + +print_mail_recipients () +{ + ( + set -euf -o pipefail + + local c="$changed_single_component" + if [ "$c" = "" ]; then + echo "bcc:tcwg-validation@linaro.org" + return 0 + fi + + local -A emails + emails["tcwg-validation@linaro.org"]=bcc + emails["author"]=cc + emails["committer"]=to + + case "$ci_project/$ci_config:$c" in + *_fast_*/*:*) + # FIXME: testing maintainer-mode + emails["author"]=no + emails["committer"]=no + emails["christophe.lyon@linaro.org"]=to + ;; + tcwg_aosp-*/*:*) + # FIXME: stabilize and enable notifications. + emails["author"]=no + emails["committer"]=no + emails["antoine.moynault@linaro.org"]=to + ;; + tcwg_bmk-*/*:*) + # FIXME: stabilize and enable notifications. + emails["author"]=no + emails["committer"]=no + emails["maxim.kuvyrkov@linaro.org"]=to + ;; + tcwg_kernel/llvm-*:linux|tcwg_kernel/*:llvm) + emails["author"]=no + emails["committer"]=no + emails["llvm@lists.linux.dev"]=to_postcommit + ;; + tcwg_kernel/*:linux) + emails["author"]=no + emails["committer"]=no + emails["linaro-kernel@lists.linaro.org"]=to_postcommit + ;; + tcwg_kernel/*:*) + emails["author"]=no + emails["committer"]=no + emails["linaro-toolchain@lists.linaro.org"]=to_postcommit + ;; + */*:gcc) + emails["gcc-regression@gcc.gnu.org"]=cc_postcommit + ;; + */*:gdb) + emails["gdb-testers@sourceware.org"]=cc_postcommit + ;; + */*:*) + emails["linaro-toolchain@lists.linaro.org"]=cc_postcommit + ;; + esac + + local c email base_rev cur_rev + + # CC: author + base_rev=$(get_baseline_git "${c}_rev") + cur_rev=$(get_current_git "${c}_rev") + while read -r email; do + emails["$email"]="${emails[author]}" + done < <(git_component_cmd "$c" log --pretty='%ae' "$base_rev..$cur_rev" || true) + + + local precommit_postcommit=postcommit + if [ "$notify" != precommit ]; then + # TO: committer + base_rev=$(get_baseline_git "${c}_rev") + cur_rev=$(get_current_git "${c}_rev") + while read -r email; do + # shellcheck disable=SC2034 + emails["$email"]="${emails[committer]}" + done < <(git_component_cmd "$c" log --pretty='%ce' "$base_rev..$cur_rev" || true) + else + precommit_postcommit=precommit + + if [ "${notify_email-}" = "" ]; then + # TO: precommit submitter + # Note that for precommit testing "git log" will specify + # tcwg-buildslave@linaro.org as committer, so use patchwork submitter + # instead. + notify_email="${pw[${c}_patch_submitter]-}" + fi + + if [ "${notify_email-}" != "" ]; then + emails["$notify_email"]="${emails[committer]}" + fi + fi + + unset "emails[author]" "emails[committer]" + + local type + local -a recipients=() + for email in "${!emails[@]}"; do + type="${emails[$email]}" + case "$precommit_postcommit:$type" in + precommit:to_precommit) type=to ;; + precommit:to_postcommit) type=no ;; + precommit:cc_precommit) type=cc ;; + precommit:cc_postcommit) type=no ;; + postcommit:to_precommit) type=no ;; + postcommit:to_postcommit) type=to ;; + postcommit:cc_precommit) type=no ;; + postcommit:cc_postcommit) type=cc ;; + esac + + case "$type" in + no) ;; + to) recipients+=("$email") ;; + *) recipients+=("$type:$email") ;; + esac + done + + (IFS=","; echo "${recipients[*]}") + ) +} + +##### Model for GNU SPECIFIC mail-body-regression.txt regression details +# +# After commit <> +# +# The following .. slowed down/grew up .. +# +# The configuration is ... +# + +##### Generates mail/mail-body.txt file +print_mail_body() +{ + local bad_artifacts_url good_artifacts_url + bad_artifacts_url="$(get_current_manifest BUILD_URL)artifact/artifacts" + good_artifacts_url="$(get_baseline_manifest BUILD_URL)artifact/artifacts" + + local key="" + # We create a Jira card only for single-commit regressions in + # post-commit CI + if [ "$change_kind" = "single_commit" ] \ + && [ "${pw[project]-}" = "" ]; then + key=$(print_jira_card_key) + if [ -z "$key" ]; then + key=$(print_jira_template_card) + fi + fi + + cat << EOF +Dear contributor, our automatic CI has detected problems related to your \ +patch(es). Please find some details below. If you have any questions, \ +please follow up on linaro-toolchain@lists.linaro.org mailing list, Libera's \ +#linaro-tcwg channel, or ping your favourite Linaro toolchain developer \ +on the usual project channel. + +We appreciate that it might be difficult to find the necessary logs or \ +reproduce the issue locally. If you can't get what you need from our \ +CI within minutes, let us know and we will be happy to help. + +EOF + + if [ "$key" != "" ]; then + cat <<EOF +We track this report status in https://linaro.atlassian.net/browse/$key , \ +please let us know if you are looking at the problem and/or when you have a fix. + +EOF + fi + + cat <<EOF +In $($print_config_f --short) after: + +$($print_commits_f --short | sed -e 's/^/ | /') + +$($print_result_f --short) + +The configuration of this build is: +$($print_config_f --long) + +-----------------8<--------------------------8<--------------------------8<-------------------------- +The information below can be used to reproduce a debug environment: + +Current build : $bad_artifacts_url +Reference build : $good_artifacts_url + +EOF + + # FIXME: Remove this warning when we enable maintainer-mode in + # production. + if [ "${pw[project]-}" != "" ]; then + cat <<EOF +Warning: we do not enable maintainer-mode nor automatically update +generated files, which may lead to failures if the patch modifies the +master files. + +EOF + fi + + if [ "$change_kind" != "single_commit" ] \ + || [ "${pw[project]-}" != "" ]; then + return + fi + + cat <<EOF +Reproduce last good and first bad builds: $($print_last_icommit_f --reproduction_instructions_link "$ci_project" "$ci_config") + +Full commit : $($print_commits_f --link) + +List of configurations that regressed due to this commit : +$($print_last_icommit_f --status) + +EOF +} + +# Generate notify/mail-*.txt files +generate_mail_files() +{ + ( + set -euf -o pipefail + + print_mail_recipients > $top_artifacts/notify/mail-recipients.txt + + # Keep jira/summary in sync with mail-subject.txt + echo "[Linaro-TCWG-CI]" \ + "$($print_commits_f --oneline): $($print_result_f --oneline) on $($print_config_f --oneline)" \ + > $top_artifacts/notify/mail-subject.txt + + print_mail_body > $top_artifacts/notify/mail-body.txt + ) +} + +print_readme_header() +{ + ( + set -euf -o pipefail + + local text_type="$1" + + local msg="How to browse artifacts of this build" + case $text_type in + html) + cat <<EOF +<!DOCTYPE html> +<html> +<body> +<font color="black"> +<h2>$msg</h2> +EOF + ;; + txt) + cat <<EOF +$msg + +EOF + ;; + esac + ) +} + +# print MSG as a link, if appropriate +# $1: type of output (html/txt) +# $2: message (directory or file name) +# #3: directory where $2 resides, used to detertime the file type +print_readme_link() +{ + ( + set -euf -o pipefail + + local text_type="$1" + local msg="$2" + local home="$3" + + case $text_type in + html) + # Jenkins webserver need a /*view*/ decoration so that + # text files are displayed in the browser. + view="" + if [ -f "$home/$msg" ]; then + if file "$home/$msg" | grep -qw text ; then + view="/*view*/" + fi + fi + echo -n "<a href=\"$msg$view\">$msg</a>" + ;; + txt) + echo -n "$msg" + ;; + esac + ) +} +print_readme_footer() +{ + ( + set -euf -o pipefail + + local text_type="$1" + case $text_type in + html) + cat << EOF +</body> +</html> +EOF + ;; + esac + ) +} + +# Provide some hints to users, to help them find their way in our +# artifacts. +# $1: type of text (txt, html) +generate_readme() +{ + ( + set -euf -o pipefail + + local text_type="$1" + + local gnu_text=false + case "$ci_project" in + *check*) + case "$ci_project" in + *gnu*|*gcc*|*binutils*|*gdb*|*bootstrap*) + gnu_text=true + ;; + esac + ;; + esac + + local list_start="" + local list_end="" + local list_item="- " + local new_parag="" + + if [ "$text_type" = "html" ]; then + list_start="<ul>" + list_end="</ul>" + list_item="<li>" + new_parag="<p>" + fi + + print_readme_header $text_type + + cat << EOF +The artifact directories contain a lot of information related to the +results of this build. +$new_parag +Directories starting with a number contain the logs of each step of +the build. More synthetic information is available in other directories, +as described below: +$new_parag +$list_start +EOF + + if [ -d $top_artifacts/00-sumfiles ]; then + cat <<EOF +$list_item$(print_readme_link "$text_type" "00-sumfiles/" "") contains .log and possibly .sum files generated by the + build. Files with .0 suffix contain the results of the initial full + testsuite run, files with .1, .2 etc... contain logs restricted to + the parts (.exp) of the testsuite where we detected regressions. + .1, .2, .... represent the number of times this subset of the testsuite + was executed in order to also identify flaky tests. The last one + contains what is considered as the results of this build. + +EOF + fi + + cat <<EOF +$list_item$(print_readme_link "$text_type" "git/" "") contains the revision and repository of each toolchain + component built + +$list_item$(print_readme_link "$text_type" "jenkins/" "") contains information useful for the CI maintainers + +$list_item$(print_readme_link "$text_type" "notify/" "") contains the material used to build various + notifications/reports (emails, Jira, LNT, ...) +EOF + + if $gnu_text; then + cat <<EOF + +$list_item$(print_readme_link "$text_type" "sumfiles/" "") contains the .sum files produced by this build. +EOF + fi + + cat <<EOF +$list_end +$new_parag +If you received a notification about one of your patches causing +problems, the information you received is in $(print_readme_link "$text_type" "notify/" "") and has +links to other artifacts from this directory. +EOF + + if $gnu_text; then + local regressions="" + if [ -f $top_artifacts/notify/regressions.sum ]; then + regressions="$(print_readme_link "$text_type" "notify/regressions.sum" "$top_artifacts") and " + fi + cat <<EOF +$new_parag +If you are investigating such a problem, you are probably primarily +interested in: +$new_parag +$list_start +$list_item$regressions$(print_readme_link "$text_type" "notify/results.compare.txt" "$top_artifacts") (regression report). + +EOF + if [ -d $top_artifacts/00-sumfiles ]; then + cat <<EOF +$list_item$(print_readme_link "$text_type" "00-sumfiles/" "") .log files with detailed errors, to save + yourself reproducing the problem on your machine. +EOF + fi + + cat <<EOF +$list_end +EOF + fi + + # Print the list of files below top_artifacts, some users find + # this useful. + cat <<EOF +$new_parag +List of files below: +$new_parag +$list_start +EOF + + while read -r cur_file; do + echo "$list_item$(print_readme_link "$text_type" "$cur_file" "$top_artifacts")" + done < <(cd $top_artifacts ; find . -type f | sort) + + cat <<EOF +$list_end +EOF + + print_readme_footer $text_type + ) +} + +# Procedure to generate a nice html to publish in jenkins job +generate_jenkins_html_files() +{ + + ( + set -euf -o pipefail + + echo "# ${FUNCNAME[0]}" + if ! $generate_jenkins_html; then + echo "... Skipping" + return + fi + + case "$ci_project" in + tcwg_bmk-*) + ( + status_file="$top_artifacts/results-vs-prev/csv-results-1/status.csv" + if [ -f $status_file ]; then + # status is one of : success, failed-to-build or failed-to-run + nb_succeed=$(sort -u $status_file | grep -c ",success$") + nb_failed=$(sort -u $status_file | grep -c ",failed-to-") + title="$nb_succeed benchmarks succeeded" + if [ "$nb_failed" != "0" ]; then + title+=", <FONT COLOR=\"orange\">$nb_failed failed<FONT COLOR=\"black\">" + fi + cat << EOF + <!DOCTYPE html> + <html> + <body> + + <h2>Status of this run : $title</h2> + + <FONT COLOR="orange"> +EOF + + sort -u $status_file | grep ",failed-to-" | cut -d, -f1,3 | \ + sed -e 's|\(.*\),\(.*\)|<h3> - \1 : \2</h3>|' + + cat << EOF + <FONT COLOR="black"> + + </body> + </html> +EOF + fi + ) > $top_artifacts/jenkins/status.html + ;; + *) + # no implementation + echo "... Skipping" + return + ;; + esac + ) & + wait $! || true + + generate_readme html > $top_artifacts/README.html + generate_readme txt > $top_artifacts/README.txt +} + + +#======================================================================================== +# +# DASHBOARD RELATED PROCEDURES +# +#======================================================================================== +# Calculate a reasonable date to associate with the current results / artifacts +# and prints this date in manifest.sh. +calculate_results_date () +{ + ( + set -euf -o pipefail + + local c base_d cur_d results_date=0 + + # Firstly, set results_date to the max of commit dates of all components. + for c in $(get_current_manifest "{rr[components]}"); do + base_d="" + if [ -f base-artifacts/git/${c}_rev ]; then + base_d=$(get_baseline_component_date ${c} || true) + fi + cur_d=$(get_current_component_date ${c} || true) + if [ x"$base_d" != x"" ]; then + if [ x"$cur_d" = x"" ] || [ $cur_d -lt $base_d ]; then + cur_d="$base_d" + fi + fi + if [ x"$cur_d" = x"" ]; then + continue + fi + + if [ $cur_d -gt $results_date ]; then + results_date="$cur_d" + fi + done + + assert_with_msg "Failed to produce results_date" [ $results_date -gt 0 ] + + base_d=$(get_baseline_manifest "{rr[results_date]}") + + # Normally there's a rr[results_date] in baseline manifest. + # The rr[results_date] was useless at one point (no dashboard for a period + # of time) and disapeared from the manifest for that time. It is now useful + # to have it back because we are setting another dashboard backend. + # This assertion on the existing results blocks any new results. + # Disabling it. + # + # TODO: + # Once all base-artifacts history are rewritten with the results_date, + # we will re-enable this assertion. + #assert_with_msg "Missing rr[results_date] from baseline manifest" \ + # [ "$base_d" != "" ] + + if [ "$base_d" != "" ]; then + if [ $results_date -gt $base_d ]; then + # Average between our current results_date and baseline date. + # The reason behind average is to spread out dates between bursts + # of builds, which can occur when reducing a regression. + results_date=$((($results_date + $base_d) / 2)) + elif [ $results_date -eq $base_d ]; then + # If the dates are equal, then no point in taking average. + # Instead just add some arbitrary, but reasonable, amount. + results_date=$(($results_date + 600)) + else + # If the baseline date is in the future (e.g., because git commit + # dates are weird in one of the components), then add some + # arbitrary, but reasonable, amount. + results_date=$(($base_d + 600)) + fi + fi + + # Save results_date in the manifest so that we can fetch it as $base_d + # above for the next build. + rr[results_date]="$results_date" + cat <<EOF | manifest_out +rr[results_date]="$results_date" +EOF + ) +} + +generate_dashboard_squad () +{ + local results_date + + echo "# ${FUNCNAME[0]}" + if ! $generate_dashboard; then + echo "... Skipping" + return + fi + + results_date="${rr[results_date]}" + results_date=$(date --utc --iso-8601=seconds --date="@$results_date") + + $scripts/dashboard-generate-squad.sh \ + --top_artifacts "$top_artifacts" \ + --baseline_branch "$(get_current_manifest "{rr[baseline_branch]}")" \ + --components "$(get_current_manifest "{rr[components]}")" \ + --run_date "$results_date" \ + --relative_results true \ + --squad_mode "vs-first" + echo "... Done" +} + +post_dashboard_squad () +{ + echo "# ${FUNCNAME[0]}" + if ! $post_dashboard; then + echo "... Skipping" + return + fi + + if ! [ -d $top_artifacts/notify/dashboard/squad-vs-first ]; then + return + fi + + $dryrun $top_artifacts/notify/dashboard/squad-vs-first/dashboard-push-squad.sh + echo "... Done" +} + +generate_lnt_report() +{ + ( + set -euf -o pipefail + local results_date + + echo "# ${FUNCNAME[0]}" + if ! $generate_lnt; then + echo "... Skipping" + return + fi + + # shellcheck source=lnt-utils + . $scripts/lnt-utils.sh + + results_date="$(get_current_manifest "{rr[results_date]}")" + results_date=$(date +"%Y-%m-%d %H:%M:%S" --date "@$results_date") + + local jira_key="-" + if [ -f "$top_artifacts/notify/jira/key" ]; then + jira_key=$(cat "$top_artifacts/notify/jira/key") + fi + + case "$ci_project" in + tcwg_binutils*|tcwg_bootstrap*|tcwg_gcc*|tcwg_gdb*|tcwg_glibc*|tcwg_gnu*) + generate_lnt_gnu_check_report \ + "$(get_current_manifest BUILD_URL)" "$ci_project" "$ci_config" \ + "$results_date" "$jira_key" \ + $top_artifacts/notify/results-summary.txt \ + $top_artifacts/sumfiles \ + $top_artifacts/notify/lnt_report.json + ;; + tcwg_bmk-*) + local cc cur_rev describe + case "${rr[toolchain]}" in + llvm) cc=llvm ;; + gnu) cc=gcc ;; + *) false ;; + esac + cur_rev=$(get_current_git ${cc}_rev) + describe=$(describe_sha1 "${cc}" "$cur_rev" false) + generate_lnt_bmk_report \ + "$(get_current_manifest BUILD_URL)" "$ci_project" "$ci_config" \ + "$results_date" "$jira_key" \ + $top_artifacts/results-vs-prev/csv-results-1/size.csv \ + $top_artifacts/results-vs-prev/csv-results-1/perf.csv \ + $top_artifacts/results-vs-prev/csv-results-1/status.csv \ + $top_artifacts/results-vs-prev/bmk-specific-variability-avg.csv \ + $top_artifacts/results-vs-prev/bmk-specific-variability-max.csv \ + $top_artifacts/results-vs-prev/compare-results-internal.csv \ + $top_artifacts/notify/lnt_report.json + ;; + *) + # no lnt support + echo "... Skipping" + return + ;; + esac + + + + ) & + wait $! || true +} + +#======================================================================================== +# +# MAIN FLOW +# +#======================================================================================== + +# setup the environment to run notify stage +setup_notify_environment +check_source_changes +setup_stages_to_run + +# Initialize icommits +post_interesting_commits init + +if [ "$stage" != "full" ]; then + echo "Init stage ran successfully." + exit 0 +fi + +$generate_extra_details_f +calculate_results_date + +check_if_first_report + +# Update entry with full information +post_interesting_commits full + +if $generate_jira; then + generate_jira_dir +fi + +echo "# print all notification files" +if $generate_mail; then + generate_mail_files +fi + +if $generate_jenkins_html; then + generate_jenkins_html_files +fi + +# generate and post Dashboard +echo "# generate dashboard" +generate_dashboard_squad +post_dashboard_squad + +generate_lnt_report + +if $post_mail; then + release_notification_files +fi + +if $post_gcc_testresults; then + release_gcc_testresults_files +fi + +# Update jira card description +post_to_jira + +echo "Full stage ran successfully." |