1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318
|
#!/bin/bash
# Abort on errors
set -ex
display_help() {
echo "Usage: $(basename "$0") [-h] [--perf] [--perf-flamegraph]"
echo
echo "This is a very small and naive benchmark script designed to be suitable"
echo "for running in a CI system on every commit to detect severe performance"
echo "regressions."
echo
echo "optional arguments:"
echo " --name STRING identifier for the benchmark, added to the "
echo " folder name and (if --log is set) the log file "
echo " --threads \"STRING\" quoted string of space-separated integers "
echo " representing the threads to run."
echo " example: --threads \"1 32 64 128\""
echo " default: \"1 2 4 8 16\""
echo " --events INTEGER number of transactions per thread to execute for each test"
echo " default: 10000"
echo " --workload STRING sysbench workload to execute"
echo " default: oltp_read_write"
echo " --log logs the mini-benchmark stdout/stderr into the"
echo " benchmark folder."
echo " --perf measure CPU cycles and instruction count in for "
echo " sysbench runs"
echo " --perf-flamegraph record performance counters in perf.data.* and"
echo " generate flamegraphs automatically"
echo " --cpu-limit upper limit on the number of CPU cycles (in billions) used for the benchmark"
echo " default: 750"
echo " -h, --help display this help and exit"
}
# Default parameters
BENCHMARK_NAME='mini-benchmark'
THREADS='1 2 4 8 16'
EVENTS=10000
WORKLOAD='oltp_read_write'
while :
do
case "$1" in
-h | --help)
display_help
exit 0
;;
--version)
display_version
exit 0
;;
--name)
shift
BENCHMARK_NAME+='-'
BENCHMARK_NAME+=$1
shift
;;
--threads)
shift
THREADS=$1
shift
;;
--events)
shift
EVENTS=$1
shift
;;
--workload)
shift
WORKLOAD=$1
shift
;;
--log)
LOG=true
shift
;;
--perf)
PERF=true
shift
;;
--perf-flamegraph)
PERF_RECORD=true
shift
;;
--cpu-limit)
shift
CPU_CYCLE_LIMIT=$1
shift
;;
-*)
echo "Error: Unknown option: $1" >&2
## or call function display_help
exit 1
;;
*) # No more options
break
;;
esac
done
# Save results of this run in a subdirectory so that they are not overwritten by
# the next run
TIMESTAMP="$(date -Iseconds)"
mkdir "$BENCHMARK_NAME-$TIMESTAMP"
cd "$BENCHMARK_NAME-$TIMESTAMP" || exit 1
FAIL=false
(
# Check that the dependencies of this script are available
if [ ! -e /usr/bin/pgrep ]
then
echo "ERROR: Command 'pgrep' missing, please install package 'psproc'"
exit 1
fi
if [ ! -e /usr/bin/sysbench ]
then
echo "ERROR: Command 'sysbench' missing, please install package 'sysbench'"
exit 1
fi
# If there are multiple processes, assume the last one is the actual server and
# any potential other ones were just part of the service wrapper chain
# shellcheck disable=SC2005
MARIADB_SERVER_PID="$(echo "$(pgrep -f mariadbd || pgrep -f mysqld)" | tail -n 1)"
if [ -z "$MARIADB_SERVER_PID" ]
then
echo "ERROR: Server 'mariadbd' or 'mysqld' is not running, please start the service"
exit 1
fi
if [ "$PERF" == true ] && [ "$PERF_RECORD" == true ]
then
echo "ERROR: Cannot select both --perf and --perf-flamegraph options simultaneously. Please choose one or the other."
exit 1
fi
if [ "$PERF" == true ] || [ "$PERF_RECORD" == true ]
then
if [ ! -e /usr/bin/perf ]
then
echo "ERROR: Command 'perf' missing, please install package 'perf'"
exit 1
fi
fi
if [ "$PERF_RECORD" == true ]
then
if [ ! -e /usr/bin/flamegraph.pl ]
then
echo "ERROR: Command 'flamegraph.pl' missing, please install package 'flamegraph'"
exit 1
fi
if [ ! -e /usr/bin/stackcollapse-perf.pl ]
then
echo "ERROR: Command 'stackcollapse-perf.pl' missing, please install package 'flamegraph-stackcollapse-perf'"
exit 1
fi
if [ ! -e /usr/bin/debuginfo-install ]
then
echo "ERROR: Command 'debuginfo-install' missing, please install package 'dnf-utils'"
exit 1
fi
echo "Ensure the MariaDB Server debug symbols are installed"
for x in $(ldd /usr/sbin/mariadbd | grep -oE " /.* ")
do
rpm -q --whatprovides --qf '%{name}' "$x" | cut -d : -f 1
done | sort -u > mariadbd-dependencies.txt
# shellcheck disable=SC2046
debuginfo-install -y mariadb-server $(cat mariadbd-dependencies.txt)
if ! (perf record echo "testing perf") > /dev/null 2>&1
then
echo "perf does not have permission to run on this system. Skipping."
PERF_COMMAND=""
else
echo "Using 'perf' to record performance counters in perf.data files"
PERF_COMMAND="perf record -g --freq=99 --output=perf.data --timestamp-filename --pid=$MARIADB_SERVER_PID --"
fi
elif [ "$PERF" == true ]
then
# If flamegraphs were not requested, log normal perf counters if possible
if ! (perf stat echo "testing perf") > /dev/null 2>&1
then
echo "perf does not have permission to run on this system. Skipping."
PERF_COMMAND=""
else
echo "Using 'perf' to log basic performance counters for benchmark"
PERF_COMMAND="perf stat -p $MARIADB_SERVER_PID --"
fi
fi
# Run sysbench on another CPU if system has more than one available
if [ "$(nproc)" -gt 1 ]
then
TASKSET_SYSBENCH='taskset -c 1'
else
TASKSET_SYSBENCH=''
fi
echo "System hardware information:"
lscpu
free -m
df -h .
uname -a
echo
echo "Set highest priority for MariaDB Server process ID $MARIADB_SERVER_PID"
renice --priority -20 --pid "$MARIADB_SERVER_PID" || echo "renice failed. Not setting priority."
echo "Set CPU affinity 0 for MariaDB Server process ID $MARIADB_SERVER_PID"
taskset -cp 0 "$MARIADB_SERVER_PID" || echo "taskset failed. Not setting cpu affinity."
mariadb -e "
CREATE DATABASE IF NOT EXISTS sbtest;
CREATE USER IF NOT EXISTS sbtest@localhost;
GRANT ALL PRIVILEGES ON sbtest.* TO sbtest@localhost"
sysbench "$WORKLOAD" prepare --tables=20 --table-size=100000 | tee sysbench-prepare.log
sync && sleep 1 # Ensure writes were propagated to disk
# Run benchmark with increasing thread counts. The MariaDB Server will be using
# around 300 MB of RAM and mostly reading and writing in RAM, so I/O usage is
# also low. The benchmark will most likely be CPU bound to due to the load
# profile, and also guaranteed to be CPU bound because of being limited to a
# single CPU with 'tasksel'.
for t in $THREADS
do
# Prepend command with perf if defined
# Output stderr to stdout as perf outputs everything in stderr
# shellcheck disable=SC2086
$PERF_COMMAND $TASKSET_SYSBENCH sysbench "$WORKLOAD" run --threads=$t --events=$(($EVENTS * $t)) --report-interval=10 2>&1 | tee sysbench-run-$t.log
done
sysbench "$WORKLOAD" cleanup --tables=20 | tee sysbench-cleanup.log
# Store results from 4 thread run in a Gitlab-CI compatible metrics file
grep -oE '[a-z]+:[ ]+[0-9.]+' sysbench-run-4.log | sed -r 's/\s+/ /g' | tail -n 15 > metrics.txt
echo # Newline improves readability
echo "== SUMMARY =="
# Print performance counter summary if they were logged
if grep --quiet cycles sysbench-run-*.log
then
grep -e cycles sysbench-run-*.log | sort -k 2
echo "Total: $(grep -h -e cycles sysbench-run-*.log | sort -k 1 | awk '{s+=$1}END{print s}')"
echo # Newline improves readability
grep -e instructions sysbench-run-*.log | sort -k 2
echo "Total: $(grep -h -e instructions sysbench-run-*.log | sort -k 1 | awk '{s+=$1}END{print s}')"
echo # Newline improves readability
if [ -z "$CPU_CYCLE_LIMIT" ]
then
# 2024-09-10: We found this to be an appropriate default limit after running a few benchmarks
# Configure the limit with --cpu-limit if needed
CPU_CYCLE_LIMIT=125
fi
CPU_CYCLE_LIMIT_LONG="${CPU_CYCLE_LIMIT}000000000"
# Final verdict based on cpu cycle count
RESULT="$(grep -h -e cycles sysbench-run-*.log | sort -k 1 | awk '{s+=$1}END{print s}')"
echo "CPU cycle count: ${RESULT}"
if [ "$RESULT" -gt "$CPU_CYCLE_LIMIT_LONG" ]
then
echo # Newline improves readability
echo "Benchmark exceeded the allowed limit of ${CPU_CYCLE_LIMIT} billion CPU cycles"
echo "Performance most likely regressed!"
FAIL=true
fi
fi
# List all sysbench status lines at once
grep -h thds sysbench-run-*.log | sort -k 5 -h
echo # Newline improves readability
echo "Highest count for queries per second:"
sort -k 9 -h sysbench-run-*.log | tail -n 1
if [ "$PERF_RECORD" == true ]
then
for f in perf.data.*
do
perf script -i "$f" | stackcollapse-perf.pl | flamegraph.pl --width 1800 > "$f".svg
done
echo "Flamegraphs stored in folder $BENCHMARK_NAME-$TIMESTAMP/"
fi
# Fallback if CPU cycle count not available: final verdict based on peak QPS
RESULT="$(sort -k 9 -h sysbench-run-*.log | tail -n 1 | grep -oE "qps: [0-9]+" | grep -oE "[0-9]+")"
case $RESULT in
''|*[!0-9]*)
echo "ERROR: Benchmark result invalid, not an integer."
exit 1
;;
*)
# Reference number for GitLab runner on a 2 GB RAM and 2 CPU virtual
# machine on a AMD EPYC 7571 host reporting 4400 BogoMIPS
if [ "$RESULT" -lt 4500 ]
then
echo # Newline improves readability
echo "Benchmark did not reach 4500+ qps, performance most likely regressed!"
FAIL=true
else
echo "Benchmark passed with $RESULT queries per second as peak value"
fi
;;
esac
if $FAIL ; then exit 1 ; fi
# Record the output into the log file, if requested
) 2>&1 | ($LOG && tee "$BENCHMARK_NAME"-"$TIMESTAMP".log)
exit ${PIPESTATUS[0]} # Propagate errors in the sub-shell
|