#!/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