| 1 | #!/bin/bash |
| 2 | |
| 3 | blkzone=$(type -p blkzone 2>/dev/null) |
| 4 | sg_inq=$(type -p sg_inq 2>/dev/null) |
| 5 | zbc_report_zones=$(type -p zbc_report_zones 2>/dev/null) |
| 6 | zbc_reset_zone=$(type -p zbc_reset_zone 2>/dev/null) |
| 7 | zbc_info=$(type -p zbc_info 2>/dev/null) |
| 8 | if [ -z "${blkzone}" ] && |
| 9 | { [ -z "${zbc_report_zones}" ] || [ -z "${zbc_reset_zone}" ]; }; then |
| 10 | echo "Error: neither blkzone nor zbc_report_zones is available" |
| 11 | exit 1 |
| 12 | fi |
| 13 | |
| 14 | if [ -n "${use_libzbc}" ] && |
| 15 | { [ -z "${zbc_report_zones}" ] || [ -z "${zbc_reset_zone}" ] || |
| 16 | [ -z "${zbc_info}" ]; }; then |
| 17 | echo "Error: zbc_report_zones, or zbc_reset_zone or zbc_info is not available" |
| 18 | echo "Error: reinstall libzbc tools" |
| 19 | exit 1 |
| 20 | fi |
| 21 | |
| 22 | blkzone_reports_capacity() { |
| 23 | local dev="${1}" |
| 24 | |
| 25 | [[ -n "${blkzone}" ]] && |
| 26 | "${blkzone}" report -c 1 -o 0 "${dev}" | grep -q 'cap ' |
| 27 | } |
| 28 | |
| 29 | # Whether or not $1 (/dev/...) is a NVME ZNS device. |
| 30 | is_nvme_zns() { |
| 31 | local s |
| 32 | |
| 33 | s=/sys/block/$(basename "${1}")/device/subsystem |
| 34 | |
| 35 | if [[ ! -h "${s}" || $(realpath "${s}") != /sys/class/nvme ]]; then |
| 36 | return 1 |
| 37 | fi |
| 38 | |
| 39 | [[ $(</sys/block/$(basename "${1}")/queue/zoned) == host-managed ]] |
| 40 | } |
| 41 | |
| 42 | # Whether or not $1 (/dev/...) is a null_blk device with zone capacity smaller |
| 43 | # than zone size. |
| 44 | is_nullb_with_zone_cap() { |
| 45 | local f |
| 46 | |
| 47 | f=/sys/kernel/config/nullb/$(basename "${1}") |
| 48 | [[ -r "${f}/zone_capacity" && |
| 49 | $(<"${f}/zone_capacity") -lt $(<"${f}/zone_size") ]] |
| 50 | } |
| 51 | |
| 52 | # Check if blkzone is available and suitable for the test target device. If not |
| 53 | # available, print error message and return 1. Otherwise return 0. |
| 54 | check_blkzone() { |
| 55 | local dev="${1}" |
| 56 | |
| 57 | # If the device supports zone capacity, mandate zone capacity report by |
| 58 | # blkzone. |
| 59 | if (is_nvme_zns "${dev}" || is_nullb_with_zone_cap "${dev}") && |
| 60 | ! blkzone_reports_capacity "${dev}"; then |
| 61 | echo "Error: blkzone does not report zone capacity" |
| 62 | echo "Error: install latest util-linux with blkzone" |
| 63 | return 1 |
| 64 | fi |
| 65 | } |
| 66 | |
| 67 | # Reports the starting sector and length of the first sequential zone of device |
| 68 | # $1. |
| 69 | first_sequential_zone() { |
| 70 | local dev=$1 |
| 71 | |
| 72 | if [ -n "${blkzone}" ] && [ ! -n "${use_libzbc}" ]; then |
| 73 | ${blkzone} report "$dev" | |
| 74 | sed -n 's/^[[:blank:]]*start:[[:blank:]]\([0-9a-zA-Z]*\),[[:blank:]]len[[:blank:]]\([0-9a-zA-Z]*\),.*zcond:\(14\|[[:blank:]][0-4]\)(.*type:[[:blank:]]\([2]\)(.*/\1 \2/p' | |
| 75 | { |
| 76 | read -r starting_sector length && |
| 77 | # Convert from hex to decimal |
| 78 | echo $((starting_sector)) $((length)) |
| 79 | } |
| 80 | else |
| 81 | ${zbc_report_zones} "$dev" | |
| 82 | sed -n 's/^Zone [0-9]*: type 0x2 .*,[[:blank:]]cond[[:blank:]]0x[0-4e][[:blank:]].*, sector \([0-9]*\), \([0-9]*\) sectors.*$/\1 \2/p' | |
| 83 | head -n1 |
| 84 | fi |
| 85 | } |
| 86 | |
| 87 | # Reports the summed zone capacity of $1 number of zones starting from offset $2 |
| 88 | # on device $3. |
| 89 | total_zone_capacity() { |
| 90 | local nr_zones=$1 |
| 91 | local sector=$(($2 / 512)) |
| 92 | local dev=$3 |
| 93 | local capacity=0 num |
| 94 | local grep_str |
| 95 | |
| 96 | if [ -z "$is_zbd" ]; then |
| 97 | # For regular block devices, handle zone size as zone capacity. |
| 98 | echo $((zone_size * nr_zones)) |
| 99 | return |
| 100 | fi |
| 101 | |
| 102 | if [ -n "${blkzone}" ] && [ ! -n "${use_libzbc}" ]; then |
| 103 | if blkzone_reports_capacity "${dev}"; then |
| 104 | grep_str='cap \K[0-9a-zA-Z]*' |
| 105 | else |
| 106 | # If zone capacity is not reported, refer zone length. |
| 107 | grep_str='len \K[0-9a-zA-Z]*' |
| 108 | fi |
| 109 | while read num; do |
| 110 | capacity=$((capacity + num)) |
| 111 | done < <(${blkzone} report -c "$nr_zones" -o "$sector" "$dev" | |
| 112 | grep -Po "${grep_str}") |
| 113 | else |
| 114 | # ZBC devices do not have zone capacity. Use zone size. |
| 115 | while read num; do |
| 116 | capacity=$((capacity + num)) |
| 117 | done < <(${zbc_report_zones} -nz "$nr_zones" -start "$sector" \ |
| 118 | "$dev" | grep -Po 'sector [0-9]*, \K[0-9]*') |
| 119 | fi |
| 120 | |
| 121 | echo $((capacity * 512)) |
| 122 | } |
| 123 | |
| 124 | # Reports the starting sector and length of the first zone of device $1 |
| 125 | # that is not in offline (or similar) condition. |
| 126 | first_online_zone() { |
| 127 | local dev=$1 |
| 128 | |
| 129 | if [ -z "$is_zbd" ]; then |
| 130 | echo 0 |
| 131 | return |
| 132 | fi |
| 133 | |
| 134 | if [ -n "${blkzone}" ] && [ ! -n "${use_libzbc}" ]; then |
| 135 | ${blkzone} report "$dev" | |
| 136 | sed -n 's/^[[:blank:]]*start:[[:blank:]]\([0-9a-zA-Z]*\),[[:blank:]]len[[:blank:]]\([0-9a-zA-Z]*\),.*zcond:\(14\|[[:blank:]][0-4]\)(.*type:[[:blank:]][12](.*/\1/p' | |
| 137 | head -n1 | |
| 138 | { |
| 139 | read -r starting_sector && |
| 140 | # Convert from hex to decimal |
| 141 | echo $((starting_sector)) |
| 142 | } |
| 143 | else |
| 144 | ${zbc_report_zones} "$dev" | |
| 145 | sed -n 's/^Zone[[:blank:]][0-9]*:[[:blank:]]type[[:blank:]]0x[12][[:blank:]].*,[[:blank:]]cond[[:blank:]]0x[0-4e][[:blank:]].*,[[:blank:]]sector[[:blank:]]\([0-9]*\),.*$/\1/p' | |
| 146 | head -n1 |
| 147 | fi |
| 148 | } |
| 149 | |
| 150 | # Reports the starting sector and length of the last zone of device $1 |
| 151 | # that is not in offline (or similar) condition. |
| 152 | last_online_zone() { |
| 153 | local dev=$1 |
| 154 | |
| 155 | if [ -z "$is_zbd" ]; then |
| 156 | echo 0 |
| 157 | return |
| 158 | fi |
| 159 | |
| 160 | if [ -n "${blkzone}" ] && [ ! -n "${use_libzbc}" ]; then |
| 161 | ${blkzone} report "$dev" | |
| 162 | sed -n 's/^[[:blank:]]*start:[[:blank:]]\([0-9a-zA-Z]*\),[[:blank:]]len[[:blank:]]\([0-9a-zA-Z]*\),.*zcond:\(14\|[[:blank:]][0-4]\)(.*type:[[:blank:]][12](.*/\1/p' | |
| 163 | tail -1 | |
| 164 | { |
| 165 | read -r starting_sector && |
| 166 | # Convert from hex to decimal |
| 167 | echo $((starting_sector)) |
| 168 | } |
| 169 | else |
| 170 | ${zbc_report_zones} "$dev" | |
| 171 | sed -n 's/^Zone[[:blank:]][0-9]*:[[:blank:]]type[[:blank:]]0x[12][[:blank:]].*,[[:blank:]]cond[[:blank:]]0x[0-4e][[:blank:]].*,[[:blank:]]sector[[:blank:]]\([0-9]*\),.*$/\1/p' | |
| 172 | tail -1 |
| 173 | fi |
| 174 | } |
| 175 | |
| 176 | # Get max_open_zones of SMR drives using sg_inq or libzbc tools. Two test cases |
| 177 | # 31 and 32 use this max_open_zones value. The test case 31 uses max_open_zones |
| 178 | # to decide number of write target zones. The test case 32 passes max_open_zones |
| 179 | # value to fio with --max_open_zones option. Of note is that fio itself has the |
| 180 | # feature to get max_open_zones from the device through sysfs or ioengine |
| 181 | # specific implementation. This max_open_zones fetch by test script is required |
| 182 | # in case fio is running on an old Linux kernel version which lacks |
| 183 | # max_open_zones in sysfs, or which lacks zoned block device support completely. |
| 184 | max_open_zones() { |
| 185 | local dev=$1 |
| 186 | |
| 187 | if [ -n "${sg_inq}" ] && [ ! -n "${use_libzbc}" ]; then |
| 188 | if ! ${sg_inq} -e --page=0xB6 --len=20 --hex "$dev" \ |
| 189 | > /dev/null 2>&1; then |
| 190 | # When sg_inq can not get max open zones, specify 0 which indicates |
| 191 | # fio to get max open zones limit from the device. |
| 192 | echo 0 |
| 193 | else |
| 194 | ${sg_inq} -e --page=0xB6 --len=20 --hex "$dev" | tail -1 | |
| 195 | { |
| 196 | read -r offset b0 b1 b2 b3 trailer || return $? |
| 197 | # Convert from hex to decimal |
| 198 | max_nr_open_zones=$((0x${b0})) |
| 199 | max_nr_open_zones=$((max_nr_open_zones * 256 + 0x${b1})) |
| 200 | max_nr_open_zones=$((max_nr_open_zones * 256 + 0x${b2})) |
| 201 | max_nr_open_zones=$((max_nr_open_zones * 256 + 0x${b3})) |
| 202 | echo ${max_nr_open_zones} |
| 203 | } |
| 204 | fi |
| 205 | else |
| 206 | ${zbc_report_zones} "$dev" | |
| 207 | sed -n 's/^[[:blank:]]*Maximum number of open sequential write required zones:[[:blank:]]*//p' |
| 208 | fi |
| 209 | } |
| 210 | |
| 211 | is_zbc() { |
| 212 | local dev=$1 |
| 213 | |
| 214 | [[ -z "$(${zbc_info} "$dev" | grep "is not a zoned block device")" ]] |
| 215 | } |
| 216 | |
| 217 | zbc_logical_block_size() { |
| 218 | local dev=$1 |
| 219 | |
| 220 | ${zbc_info} "$dev" | |
| 221 | grep "logical blocks" | |
| 222 | sed -n 's/^[[:blank:]]*[0-9]* logical blocks of[[:blank:]]*//p' | |
| 223 | sed 's/ B//' |
| 224 | } |
| 225 | |
| 226 | zbc_disk_sectors() { |
| 227 | local dev=$1 |
| 228 | |
| 229 | zbc_info "$dev" | |
| 230 | grep "512-bytes sectors" | |
| 231 | sed -e 's/[[:blank:]]*\([0-9]*\)512-bytes sectors.*/\1/' |
| 232 | } |
| 233 | |
| 234 | # Reset the write pointer of one zone on device $1 at offset $2. The offset |
| 235 | # must be specified in units of 512 byte sectors. Offset -1 means reset all |
| 236 | # zones. |
| 237 | reset_zone() { |
| 238 | local dev=$1 offset=$2 sectors |
| 239 | |
| 240 | if [ -n "${blkzone}" ] && [ ! -n "${use_libzbc}" ]; then |
| 241 | if [ "$offset" -lt 0 ]; then |
| 242 | ${blkzone} reset "$dev" |
| 243 | else |
| 244 | ${blkzone} reset -o "${offset}" -c 1 "$dev" |
| 245 | fi |
| 246 | else |
| 247 | if [ "$offset" -lt 0 ]; then |
| 248 | ${zbc_reset_zone} -all "$dev" >/dev/null |
| 249 | else |
| 250 | ${zbc_reset_zone} -sector "$dev" "${offset}" >/dev/null |
| 251 | fi |
| 252 | fi |
| 253 | } |
| 254 | |
| 255 | # Extract the number of bytes that have been transferred from a line like |
| 256 | # READ: bw=6847KiB/s (7011kB/s), 6847KiB/s-6847KiB/s (7011kB/s-7011kB/s), io=257MiB (269MB), run=38406-38406msec |
| 257 | fio_io() { |
| 258 | sed -n 's/^[[:blank:]]*'"$1"'.*, io=\([^[:blank:]]*\).*/\1/p' | |
| 259 | tail -n 1 | |
| 260 | ( |
| 261 | read -r io; |
| 262 | # Parse <number>.<number><suffix> into n1, n2 and s. See also |
| 263 | # num2str(). |
| 264 | shopt -s extglob |
| 265 | n1=${io%${io##*([0-9])}} |
| 266 | s=${io#${io%%*([a-zA-Z])}} |
| 267 | n2=${io#${n1}} |
| 268 | n2=${n2#.} |
| 269 | n2=${n2%$s}000 |
| 270 | n2=${n2:0:3} |
| 271 | case "$s" in |
| 272 | KiB) m=10;; |
| 273 | MiB) m=20;; |
| 274 | GiB) m=30;; |
| 275 | B) m=0;; |
| 276 | *) return 1;; |
| 277 | esac |
| 278 | [ -n "$n1" ] || return 1 |
| 279 | echo $(((n1 << m) + (n2 << m) / 1000)) |
| 280 | ) |
| 281 | } |
| 282 | |
| 283 | fio_read() { |
| 284 | fio_io 'READ:' |
| 285 | } |
| 286 | |
| 287 | fio_written() { |
| 288 | fio_io 'WRITE:' |
| 289 | } |
| 290 | |
| 291 | fio_reset_count() { |
| 292 | local count |
| 293 | |
| 294 | count=$(sed -n 's/^.*write:[^;]*; \([0-9]*\) zone resets$/\1/p') |
| 295 | echo "${count:-0}" |
| 296 | } |