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