| #!/bin/bash |
| # SPDX-License-Identifier: GPL-3.0+ |
| # Copyright (C) 2017 Omar Sandoval |
| # |
| # fio helper functions. |
| |
| . common/shellcheck |
| |
| _have_fio() { |
| if ! _have_program fio; then |
| return 1 |
| fi |
| if ! fio --parse-only --terse-version=4 >/dev/null 2>&1; then |
| SKIP_REASON="Fio version too old (does not support --terse-version=4)" |
| return 1 |
| fi |
| return 0 |
| } |
| |
| _have_fio_zbd_zonemode() { |
| _have_fio || return $? |
| if ! fio --cmdhelp=zonemode 2>&1 | grep -q zbd; then |
| SKIP_REASON="Fio version too old (does not support --zonemode=zbd)" |
| return 1 |
| fi |
| return 0 |
| } |
| |
| declare -A FIO_TERSE_FIELDS |
| FIO_TERSE_FIELDS=( |
| # Read status |
| ["read io"]=6 |
| ["read bandwidth"]=7 |
| ["read iops"]=8 |
| ["read runtime"]=9 |
| ["read slat min"]=10 |
| ["read slat max"]=11 |
| ["read slat mean"]=12 |
| ["read slat stdev"]=13 |
| ["read clat min"]=14 |
| ["read clat max"]=15 |
| ["read clat mean"]=16 |
| ["read clat stdev"]=17 |
| # read clat percentiles are 18-37 |
| ["read lat min"]=38 |
| ["read lat max"]=39 |
| ["read lat mean"]=40 |
| ["read lat stdev"]=41 |
| ["read bandwidth min"]=42 |
| ["read bandwidth max"]=43 |
| ["read bandwidth %"]=44 |
| ["read bandwidth mean"]=45 |
| ["read bandwidth stdev"]=46 |
| |
| # Write status |
| ["write io"]=47 |
| ["write bandwidth"]=48 |
| ["write iops"]=49 |
| ["write runtime"]=50 |
| ["write slat min"]=51 |
| ["write slat max"]=52 |
| ["write slat mean"]=53 |
| ["write slat stdev"]=54 |
| ["write clat min"]=55 |
| ["write clat max"]=56 |
| ["write clat mean"]=57 |
| ["write clat stdev"]=58 |
| # write clat percentiles are 59-78 |
| ["write lat min"]=79 |
| ["write lat max"]=80 |
| ["write lat mean"]=81 |
| ["write lat stdev"]=82 |
| ["write bandwidth min"]=83 |
| ["write bandwidth max"]=84 |
| ["write bandwidth %"]=85 |
| ["write bandwidth mean"]=86 |
| ["write bandwidth stdev"]=87 |
| |
| # Trim status |
| ["trim io"]=88 |
| ["trim bandwidth"]=89 |
| ["trim iops"]=90 |
| ["trim runtime"]=91 |
| ["trim slat min"]=92 |
| ["trim slat max"]=93 |
| ["trim slat mean"]=94 |
| ["trim slat stdev"]=95 |
| ["trim clat min"]=96 |
| ["trim clat max"]=97 |
| ["trim clat mean"]=98 |
| ["trim clat stdev"]=99 |
| # trim clat percentiles are 100-119 |
| ["trim lat min"]=120 |
| ["trim lat max"]=121 |
| ["trim lat mean"]=122 |
| ["trim lat stdev"]=123 |
| ["trim bandwidth min"]=124 |
| ["trim bandwidth max"]=125 |
| ["trim bandwidth %"]=126 |
| ["trim bandwidth mean"]=127 |
| ["trim bandwidth stdev"]=128 |
| |
| # CPU usage |
| ["user cpu"]=129 |
| ["system cpu"]=130 |
| ["context switches"]=131 |
| ["major page faults"]=132 |
| ["minor page faults"]=133 |
| |
| # IO depth distribution |
| ["io depth <=1"]=134 |
| ["io depth 2"]=135 |
| ["io depth 4"]=136 |
| ["io depth 8"]=137 |
| ["io depth 16"]=138 |
| ["io depth 32"]=139 |
| ["io depth >=64"]=140 |
| |
| # IO latency distribution |
| ["io latency <=2 us"]=141 |
| ["io latency 4 us"]=142 |
| ["io latency 10 us"]=143 |
| ["io latency 20 us"]=144 |
| ["io latency 50 us"]=145 |
| ["io latency 100 us"]=146 |
| ["io latency 250 us"]=147 |
| ["io latency 500 us"]=148 |
| ["io latency 750 us"]=149 |
| ["io latency 1000 us"]=150 |
| ["io latency <=2 ms"]=151 |
| ["io latency 4 ms"]=152 |
| ["io latency 10 ms"]=153 |
| ["io latency 20 ms"]=154 |
| ["io latency 50 ms"]=155 |
| ["io latency 100 ms"]=156 |
| ["io latency 250 ms"]=157 |
| ["io latency 500 ms"]=158 |
| ["io latency 750 ms"]=159 |
| ["io latency 1000 ms"]=160 |
| ["io latency 2000 ms"]=161 |
| ["io latency >=2000 ms"]=162 |
| |
| # Disk utilization (11 fields per disk) |
| ) |
| |
| # Run fio and report performance data. The metrics to gather are specified by |
| # the $FIO_PERF_FIELDS array. E.g., FIO_PERF_FIELDS=("read iops" "system cpu"). |
| # The possible fields are specified above. The optional $FIO_PERF_PREFIX |
| # variable is prepended to the field name when reporting. |
| _fio_perf() { |
| _run_fio "$@" |
| _fio_perf_report |
| } |
| |
| # Wrapper around fio that handles: |
| # - Recording perf results |
| # - $TIMEOUT |
| # You should usually use this instead of calling fio directly. An explicitly |
| # passed --runtime will override the configured $TIMEOUT, which is useful for |
| # tests that should run for a specific amount of time. |
| _run_fio() { |
| local args=("--output=$TMPDIR/fio_perf" "--output-format=terse" "--terse-version=4" "--group_reporting=1") |
| |
| if [[ "${TIMEOUT:-}" ]]; then |
| args+=("--runtime=$TIMEOUT") |
| fi |
| |
| if ! fio "${args[@]}" "$@"; then |
| echo "fio exited with status $?" |
| cat "$TMPDIR"/fio_perf |
| fi |
| } |
| |
| # Wrapper around _run_fio used if you need some I/O but don't really care much |
| # about the details |
| _run_fio_rand_io() { |
| _run_fio --bs=4k --rw=randread --norandommap --numjobs="$(nproc)" \ |
| --name=reads --direct=1 "$@" |
| } |
| |
| _run_fio_verify_io() { |
| _run_fio --name=verify --rw=randwrite --direct=1 --ioengine=libaio --bs=4k \ |
| --norandommap --iodepth=16 --verify=crc32c "$@" |
| } |
| |
| _fio_perf_report() { |
| # If there is more than one group, we don't know what to report. |
| if [[ $(wc -l < "$TMPDIR/fio_perf") -gt 1 ]]; then |
| echo "_fio_perf: too many terse lines" >&2 |
| return |
| fi |
| |
| local name field value |
| for name in "${FIO_PERF_FIELDS[@]}"; do |
| field="${FIO_TERSE_FIELDS["$name"]}" |
| if [[ -z $field ]]; then |
| echo "_fio_perf: unknown fio terse field '$name'" >&2 |
| continue |
| fi |
| value="$(cut -d ';' -f "$field" "$TMPDIR/fio_perf")" |
| TEST_RUN["$FIO_PERF_PREFIX$name"]="$value" |
| done |
| } |