| #!/bin/bash | |
| # Copyright (C) 2024 Red Hat, Inc. | |
| # | |
| # SPDX-License-Identifier: LGPL-2.1-or-later | |
| # Run ./build/gobject/tests/performance/performance for several commits | |
| # and compare the result. | |
| # | |
| # Howto: | |
| # | |
| # 1) configure the build. For example run | |
| # $ git clean -fdx | |
| # $ meson build -Dprefix=/tmp/glib/ -Db_lto=true --buildtype release -Ddebug=true | |
| # Beware, that running the script will check out other commits, | |
| # build the tree and run `ninja install`. Don't have important | |
| # work there. | |
| # | |
| # 2) run the script. Set $COMMITS to the list of commit sha's to test. | |
| # Environment variables: | |
| # COMMITS: list of git references to test. | |
| # PATCH: if set, after checking out each commit, run `git cherry-pick $PATCH` | |
| # before building. | |
| # PREPARE_CMD: if set, invoke $PREPARE_CMD after checking out commit. Can be used | |
| # to patch the sources. | |
| # PERF: if set, run performance test via perf. Set for example, `PERF="perf stat -r 10 -B"` | |
| # When setting `PERF`, you probably also want to set GLIB_PERFORMANCE_FACTOR, | |
| # which depends on the test (run the test in verbose mode to find a suitable factor). | |
| # STRIP: if set to 1, call `strip` on the library and binary before running. | |
| # Arguments: arguments are directly passed to performance. For example try "-s 1". | |
| # | |
| # Example: | |
| # | |
| # # once: | |
| # git clean -fdx | |
| # meson build -Dprefix=/tmp/glib/ -Db_lto=true --buildtype release -Ddebug=true | |
| # | |
| # # test: | |
| # COMMIT_END=my-test-branch | |
| # COMMIT_START="$(git merge-base origin/main "$COMMIT_END")" | |
| # PERF="" PATCH="" COMMITS=" $COMMIT_START $COMMIT_END " /tmp/performance-run.sh -s 5 | |
| # PERF="" PATCH="" COMMITS=" $COMMIT_START $( git log --reverse --pretty=%H "$COMMIT_START..$COMMIT_END" ) " /tmp/performance-run.sh -s 5 | |
| # | |
| # GLIB_PERFORMANCE_FACTOR=17.06 PERF='perf stat -r 3 -B' PATCH="" COMMITS=" $COMMIT_START $COMMIT_END " /tmp/performance-run.sh -s 1 property-get | |
| set -e | |
| usage() { | |
| sed -n '4,/^$/ s/^#\( \(.*\)\|\)$/\2/p' "$0" | |
| } | |
| if [ "$#" -eq 1 ] && [ "$1" == "-h" ] ; then | |
| usage | |
| exit 0 | |
| fi | |
| die() { | |
| printf "%s\n" "$*" | |
| exit 1 | |
| } | |
| die_with_last_cmd() { | |
| cat /tmp/glib-performance-last-cmd || : | |
| die "$@" | |
| } | |
| read -ra COMMITS_V -d '' <<<"$COMMITS" || : | |
| [ "${#COMMITS_V[@]}" -gt 0 ] || die "Must set \$COMMITS" | |
| COMMITS_SHA=() | |
| for commit in "${COMMITS_V[@]}" ; do | |
| c="$(git rev-parse --verify "$commit^{commit}")" || die "invalid git reference \"$commit\"" | |
| COMMITS_SHA+=( "$c" ) | |
| done | |
| _list_commits() { | |
| local PREFIX="$1" | |
| local idx | |
| local I | |
| I=0 | |
| for idx in "${!COMMITS_V[@]}" ; do | |
| I=$((I+1)) | |
| echo "${PREFIX}[$I] $(git log -n1 --oneline "${COMMITS_SHA[$idx]}") (${COMMITS_V[$idx]})" | |
| done | |
| } | |
| cat << EOF | |
| Testing commits: | |
| $(_list_commits " ") | |
| \$ meson build -Dprefix=/tmp/glib/ -Db_lto=true --buildtype release -Ddebug=true | |
| \$ PATCH=$(printf '%q' "$PATCH") \\ | |
| PREPARE_CMD=$(printf '%q' "$PREPARE_CMD") \\ | |
| PERF=$(printf '%q' "$PERF") \\ | |
| GLIB_PERFORMANCE_FACTOR=$(printf '%q' "$GLIB_PERFORMANCE_FACTOR") \\ | |
| STRIP=$(printf '%q' "$STRIP") \\ | |
| COMMITS=$(printf '%q' "$COMMITS") \\ | |
| $0$(printf ' %q' "$@") | |
| EOF | |
| _get_timing() { | |
| local LINE_NUM="$1" | |
| local LINE_PREFIX="$2" | |
| local FILE="$3" | |
| sed -n "$LINE_NUM s/^$LINE_PREFIX: //p" "$FILE" | |
| } | |
| _perc() { | |
| awk -v n1="$1" -v n2="$2" 'BEGIN { printf("%+0.3g%\n", (n2 - n1) / n1 * 100.0); }' | |
| } | |
| _cmd() { | |
| # printf ' $' ; printf ' %s' "$@" ; printf '\n' | |
| "$@" | |
| } | |
| I=0 | |
| for idx in "${!COMMITS_V[@]}" ; do | |
| commit="${COMMITS_V[$idx]}" | |
| commit_sha="${COMMITS_SHA[$idx]}" | |
| I=$((I+1)) | |
| echo -n ">>> [$I] test [ $(git log -n1 --oneline "$commit") ]" | |
| git checkout -f "$commit_sha" &> /tmp/glib-performance-last-cmd || (echo ...; die_with_last_cmd "Failure to checkout \"$commit\"") | |
| touch glib/gdataset.c gobject/gobject.c gobject/tests/performance/performance.c | |
| if [ "$PATCH" != "" ] ; then | |
| read -ra PATCH_ARR -d '' <<<"$PATCH" || : | |
| for p in "${PATCH_ARR[@]}" ; do | |
| git cherry-pick "$p" &> /tmp/glib-performance-last-cmd || (echo ...; die_with_last_cmd "Failure to cherry-pick \"$PATCH\"") | |
| done | |
| fi | |
| if [ "$PREPARE_CMD" != "" ] ; then | |
| I="$I" COMMIT="$commit" "$PREPARE_CMD" &> /tmp/glib-performance-last-cmd || (echo ...; die_with_last_cmd "\$PREPARE_CMD failed") | |
| fi | |
| echo " commit $(git rev-parse --verify HEAD) > /tmp/glib-performance-output.$I" | |
| ( ninja -C build || ninja -C build ) &> /tmp/glib-performance-last-cmd || die_with_last_cmd "Failure to build with \`ninja -C build\`" | |
| ninja -C build install &> /tmp/glib-performance-last-cmd || die_with_last_cmd "FAilure to install with \`ninja -C build install\`" | |
| if [ "$STRIP" = 1 ] ; then | |
| strip ./build/gobject/libgobject-2.0.so ./build/glib/libglib-2.0.so.0 ./build/gobject/tests/performance/performance | |
| fi | |
| # ls -lad -L \ | |
| # ./build/gobject/libgobject-2.0.so \ | |
| # ./build/glib/libglib-2.0.so \ | |
| # ./build/gobject/tests/performance/performance | |
| # ldd ./build/gobject/tests/performance/performance | |
| ( | |
| if [ -z "$PERF" ] ; then | |
| TESTRUN="$I" TESTCOMMIT="$commit" ./build/gobject/tests/performance/performance "$@" | |
| else | |
| TESTRUN="$I" TESTCOMMIT="$commit" $PERF ./build/gobject/tests/performance/performance "$@" | |
| fi | |
| ) |& tee "/tmp/glib-performance-output.$I" || : | |
| [ "${PIPESTATUS[0]}" -eq 0 ] || die "Performance test failed" | |
| done | |
| merge_output() { | |
| ( | |
| declare -A RES | |
| LINE_NUM=0 | |
| OLD_IFS="$IFS" | |
| while IFS=$'\n' read -r LINE ; do | |
| LINE="${LINE:1}" | |
| LINE_NUM=$((LINE_NUM+1)) | |
| line_regex='^([a-zA-Z0-9].*): +([0-9.]+)$' | |
| if [[ "$LINE" =~ $line_regex ]] ; then | |
| LINE_PREFIX="${BASH_REMATCH[1]}" | |
| T1="${BASH_REMATCH[2]}" | |
| echo -n "$LINE_PREFIX: $T1" | |
| RES["$LINE_PREFIX"]="${RES[$LINE_PREFIX]} $T1" | |
| for J in $(seq 2 "${#COMMITS_V[@]}") ; do | |
| T="$(_get_timing "$LINE_NUM" "$LINE_PREFIX" "/tmp/glib-performance-output.$J")" | |
| echo -n " $T ($(_perc "$T1" "$T"))" | |
| RES["$LINE_PREFIX"]="${RES[$LINE_PREFIX]} $T" | |
| done | |
| echo | |
| continue | |
| fi | |
| if [ -n "$PERF" ] ; then | |
| if [[ "$LINE" == *"seconds time elapsed"* ]] ; then | |
| echo "[1] $LINE" | |
| for J in $(seq 2 "${#COMMITS_V[@]}") ; do | |
| echo "[$J] $(sed -n "$LINE_NUM s/seconds time elapsed/\\0/p" "/tmp/glib-performance-output.$J")" | |
| done | |
| continue | |
| fi | |
| fi | |
| echo "$LINE" | |
| done < <( sed 's/^/x/' "/tmp/glib-performance-output.1" ) | |
| IFS="$OLD_IFS" | |
| if [ -n "$PERF" ] ; then | |
| for LINE_PREFIX in "${!RES[@]}"; do | |
| # The values are interleaved. To cumbersome to process in bash. | |
| read -ra T -d '' <<<"${RES[$LINE_PREFIX]}" || : | |
| read -ra T_SORTED -d '' < <(printf '%s\n' "${T[@]}" | awk -v C="${#COMMITS_V[@]}" '{ print (1 + (NR-1) % C) " " $0 }' | sort -n -s | sed 's/^[0-9]\+ *//' ) || : | |
| NGROUP="$(( "${#T_SORTED[@]}" / "${#COMMITS_V[@]}" ))" | |
| echo -n "$LINE_PREFIX: " | |
| SEP='' | |
| for J in $(seq 0 "${#T_SORTED[@]}") ; do | |
| echo -n "$SEP${T_SORTED[$J]}" | |
| if [ $(( (J+1) % NGROUP )) = 0 ]; then | |
| echo -n " ; " | |
| SEP='' | |
| else | |
| SEP=" , " | |
| fi | |
| done | |
| echo | |
| done | |
| fi | |
| ) | tee "/tmp/glib-performance-output.all" | |
| } | |
| echo ">>> combined result > /tmp/glib-performance-output.all" | |
| merge_output |