From ce1b5612ce9960e50a15ab3b80bb40ef946b47a7 Mon Sep 17 00:00:00 2001 From: Sitsofe Wheeler Date: Sun, 26 Jul 2020 15:20:45 +0100 Subject: [PATCH 0001/1097] ci: add CI via GitHub Actions - Add GitHub Actions CI on push and pull requests for: - Ubuntu 20.04 x86_64 gcc - Ubuntu 20.04 x86_64 clang - Ubuntu 20.04 i686 gcc - macOS 10.15 - Set the same dpkg flags as found in a default Ubuntu docker container. This has the following benefits: - Reduction in the amount of fsyncing dpkg/apt does - Installation/configuring of documentation is skipped - On macOS Speed up homewbrew by not updating on install v2: - Use macOS 10.15 rather than 11.0 because 11.0 is only private preview (https://docs.github.com/en/actions/reference/specifications-for-github-hosted-runners ). - Workaround "Could not perform immediate configuration on 'libgcc-s1:i386'." on the Ubuntu 20.04 i686 configuration (see https://bugs.launchpad.net/ubuntu-cdimage/+bug/1871268/comments/170 for details of the underlying issue). - Install i386 development zlib. Thanks to Lukasz Dorau for pointing the above out! Signed-off-by: Sitsofe Wheeler --- .github/workflows/ci.yml | 45 ++++++++++++++++++++ ci/actions-build.sh | 37 ++++++++++++++++ ci/actions-full-test.sh | 15 +++++++ ci/actions-install.sh | 91 ++++++++++++++++++++++++++++++++++++++++ ci/actions-smoke-test.sh | 10 +++++ ci/common.sh | 34 +++++++++++++++ 6 files changed, 232 insertions(+) create mode 100644 .github/workflows/ci.yml create mode 100755 ci/actions-build.sh create mode 100755 ci/actions-full-test.sh create mode 100755 ci/actions-install.sh create mode 100755 ci/actions-smoke-test.sh create mode 100644 ci/common.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000000..a766cfa8e2 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,45 @@ +name: CI + +on: + push: + pull_request: + +jobs: + build: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + build: + - linux-gcc + - linux-clang + - macos + - linux-i686-gcc + include: + - build: linux-gcc + os: ubuntu-20.04 + cc: gcc + - build: linux-clang + os: ubuntu-20.04 + cc: clang + - build: macos + os: macos-10.15 + - build: linux-i686-gcc + os: ubuntu-20.04 + arch: i686 + + env: + CI_TARGET_ARCH: ${{ matrix.arch }} + CC: ${{ matrix.cc }} + + steps: + - name: Checkout repo + uses: actions/checkout@v2 + - name: Install dependencies + run: ./ci/actions-install.sh + - name: Build + run: ./ci/actions-build.sh + - name: Smoke test + run: ./ci/actions-smoke-test.sh + - name: Full test + run: ./ci/actions-full-test.sh diff --git a/ci/actions-build.sh b/ci/actions-build.sh new file mode 100755 index 0000000000..74a6fdcbf0 --- /dev/null +++ b/ci/actions-build.sh @@ -0,0 +1,37 @@ +#!/bin/bash +# This script expects to be invoked from the base fio directory. +set -eu + +SCRIPT_DIR=$(dirname "$0") +# shellcheck disable=SC1091 +. "${SCRIPT_DIR}/common.sh" + +main() { + local extra_cflags="-Werror" + local configure_flags=() + + set_ci_target_os + case "${CI_TARGET_OS}" in + "linux") + case "${CI_TARGET_ARCH}" in + "i686") + extra_cflags="${extra_cflags} -m32" + export LDFLAGS="-m32" + ;; + "x86_64") + configure_flags+=( + "--enable-cuda" + "--enable-libiscsi" + "--enable-libnbd" + ) + ;; + esac + ;; + esac + configure_flags+=(--extra-cflags="${extra_cflags}") + + ./configure "${configure_flags[@]}" + make -j 2 +} + +main diff --git a/ci/actions-full-test.sh b/ci/actions-full-test.sh new file mode 100755 index 0000000000..4ae1dba10b --- /dev/null +++ b/ci/actions-full-test.sh @@ -0,0 +1,15 @@ +#!/bin/bash +# This script expects to be invoked from the base fio directory. +set -eu + +main() { + echo "Running long running tests..." + export PYTHONUNBUFFERED="TRUE" + if [[ "${CI_TARGET_ARCH}" == "arm64" ]]; then + sudo python3 t/run-fio-tests.py --skip 6 1007 1008 --debug -p 1010:"--skip 15 16 17 18 19 20" + else + sudo python3 t/run-fio-tests.py --skip 6 1007 1008 --debug + fi +} + +main diff --git a/ci/actions-install.sh b/ci/actions-install.sh new file mode 100755 index 0000000000..7408ccb4f9 --- /dev/null +++ b/ci/actions-install.sh @@ -0,0 +1,91 @@ +#!/bin/bash +# This script expects to be invoked from the base fio directory. +set -eu + +SCRIPT_DIR=$(dirname "$0") +# shellcheck disable=SC1091 +. "${SCRIPT_DIR}/common.sh" + +install_ubuntu() { + local pkgs + + cat < /dev/null +# Skip fsync +force-unsafe-io +# Don't install documentation +path-exclude=/usr/share/man/* +path-exclude=/usr/share/locale/*/LC_MESSAGES/*.mo +path-exclude=/usr/share/doc/* +DPKGCFG + # Packages available on i686 and x86_64 + pkgs=( + libaio-dev + libcunit1-dev + libcurl4-openssl-dev + libfl-dev + libibverbs-dev + libnuma-dev + librdmacm-dev + valgrind + ) + case "${CI_TARGET_ARCH}" in + "i686") + sudo dpkg --add-architecture i386 + pkgs=("${pkgs[@]/%/:i386}") + pkgs+=( + gcc-multilib + pkg-config:i386 + zlib1g-dev:i386 + ) + ;; + "x86_64") + pkgs+=( + libglusterfs-dev + libgoogle-perftools-dev + libiscsi-dev + libnbd-dev + libpmem-dev + libpmemblk-dev + librbd-dev + libtcmalloc-minimal4 + nvidia-cuda-dev + ) + ;; + esac + + # Architecture-independent packages and packages for which we don't + # care about the architecture. + pkgs+=( + python3-scipy + ) + + echo "Updating APT..." + sudo apt-get -qq update + echo "Installing packages..." + sudo apt-get install -o APT::Immediate-Configure=false --no-install-recommends -qq -y "${pkgs[@]}" +} + +install_linux() { + install_ubuntu +} + +install_macos() { + # Assumes homebrew and python3 are already installed + #echo "Updating homebrew..." + #brew update >/dev/null 2>&1 + echo "Installing packages..." + HOMEBREW_NO_AUTO_UPDATE=1 brew install cunit + pip3 install scipy six +} + +main() { + set_ci_target_os + + install_function="install_${CI_TARGET_OS}" + ${install_function} + + echo "Python3 path: $(type -p python3 2>&1)" + echo "Python3 version: $(python3 -V 2>&1)" +} + +main diff --git a/ci/actions-smoke-test.sh b/ci/actions-smoke-test.sh new file mode 100755 index 0000000000..c129c89fad --- /dev/null +++ b/ci/actions-smoke-test.sh @@ -0,0 +1,10 @@ +#!/bin/bash +# This script expects to be invoked from the base fio directory. +set -eu + +main() { + echo "Running smoke tests..." + make test +} + +main diff --git a/ci/common.sh b/ci/common.sh new file mode 100644 index 0000000000..8861f843f0 --- /dev/null +++ b/ci/common.sh @@ -0,0 +1,34 @@ +# shellcheck shell=bash + +function set_ci_target_os { + # Function that exports CI_TARGET_OS to the current OS if it is not already + # set. + + # Don't override CI_TARGET_OS if already set + CI_TARGET_OS=${CI_TARGET_OS:-} + if [[ -z ${CI_TARGET_OS} ]]; then + # Detect operating system + case "${OSTYPE}" in + linux*) + CI_TARGET_OS="linux" + ;; + darwin*) + CI_TARGET_OS="macos" + ;; + msys*) + CI_TARGET_OS="windows" + ;; + bsd*) + CI_TARGET_OS="bsd" + ;; + *) + CI_TARGET_OS="" + esac + fi + + # Don't override CI_TARGET_ARCH if already set + CI_TARGET_ARCH=${CI_TARGET_ARCH:-} + if [[ -z ${CI_TARGET_ARCH} ]]; then + CI_TARGET_ARCH="$(uname -m)" + fi +} From 39233f7eb40c051299cce1c38f14d1561ce66c50 Mon Sep 17 00:00:00 2001 From: Sitsofe Wheeler Date: Sun, 27 Dec 2020 16:32:04 +0000 Subject: [PATCH 0002/1097] ci: retire travis configuration Travis CI was kind enough to offer free builds to open source for many years (thanks!). Unfortunately, the inevitable abuse means that travis-ci.org is ending on the 31st December 2020 (https://docs.travis-ci.com/user/migrate/open-source-repository-migration#frequently-asked-questions). Current travis-ci.org users who wish to continue with Travis have to create an account on travis-ci.com and will be put on a trial plan (see https://blog.travis-ci.com/2020-11-02-travis-ci-new-billing ). This becomes complicated for the fio project because although users are gifted are 10,000 credits: - If those credits are used for Linux builds you will be limited to 1000 minutes worth of builds (each fio CI run does about 60 minutes of Linux builds) - If those credits are used for macOS builds you will be limited to 200 minutes worth of builds (each fio CI run does two macOS builds that take up about 25 minutes together) - Even if you still have credit, the trial plan expires in a year - An open source project can ask for more credit but it is a manual process that requires manually creating a support request - The above means the initial credit would run out before the sixth CI run Rather than going through a migration and the risk of using up all the free credits let's retire Travis builds. Signed-off-by: Sitsofe Wheeler --- .travis.yml | 37 ------------------------- ci/travis-build.sh | 32 ---------------------- ci/travis-install.sh | 65 -------------------------------------------- 3 files changed, 134 deletions(-) delete mode 100644 .travis.yml delete mode 100755 ci/travis-build.sh delete mode 100755 ci/travis-install.sh diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index e35aff394b..0000000000 --- a/.travis.yml +++ /dev/null @@ -1,37 +0,0 @@ -language: c -dist: bionic -os: - - linux -compiler: - - clang - - gcc -arch: - - amd64 - - arm64 -env: - global: - - MAKEFLAGS="-j 2" -matrix: - include: - - os: linux - compiler: gcc - arch: amd64 - env: BUILD_ARCH="x86" # Only do the gcc x86 build to reduce clutter - # Default xcode image - - os: osx - compiler: clang # Workaround travis setting CC=["clang", "gcc"] - arch: amd64 - # Latest xcode image (needs periodic updating) - - os: osx - compiler: clang - osx_image: xcode11.2 - arch: amd64 - exclude: - - os: osx - compiler: gcc - -install: - - ci/travis-install.sh - -script: - - ci/travis-build.sh diff --git a/ci/travis-build.sh b/ci/travis-build.sh deleted file mode 100755 index 923d882d57..0000000000 --- a/ci/travis-build.sh +++ /dev/null @@ -1,32 +0,0 @@ -#!/bin/bash -set -eu - -CI_TARGET_ARCH="${BUILD_ARCH:-$TRAVIS_CPU_ARCH}" -EXTRA_CFLAGS="-Werror" -export PYTHONUNBUFFERED=TRUE -CONFIGURE_FLAGS=() - -case "$TRAVIS_OS_NAME" in - "linux") - CONFIGURE_FLAGS+=(--enable-libiscsi) - case "$CI_TARGET_ARCH" in - "x86") - EXTRA_CFLAGS="${EXTRA_CFLAGS} -m32" - export LDFLAGS="-m32" - ;; - "amd64") - CONFIGURE_FLAGS+=(--enable-cuda) - ;; - esac - ;; -esac -CONFIGURE_FLAGS+=(--extra-cflags="${EXTRA_CFLAGS}") - -./configure "${CONFIGURE_FLAGS[@]}" && - make && - make test && - if [[ "$CI_TARGET_ARCH" == "arm64" ]]; then - sudo python3 t/run-fio-tests.py --skip 6 1007 1008 --debug -p 1010:"--skip 15 16 17 18 19 20" - else - sudo python3 t/run-fio-tests.py --skip 6 1007 1008 --debug - fi diff --git a/ci/travis-install.sh b/ci/travis-install.sh deleted file mode 100755 index 4c4c04c5d6..0000000000 --- a/ci/travis-install.sh +++ /dev/null @@ -1,65 +0,0 @@ -#!/bin/bash -set -eu - -CI_TARGET_ARCH="${BUILD_ARCH:-$TRAVIS_CPU_ARCH}" -case "$TRAVIS_OS_NAME" in - "linux") - # Architecture-dependent packages. - pkgs=( - libaio-dev - libcunit1-dev - libfl-dev - libgoogle-perftools-dev - libibverbs-dev - libiscsi-dev - libnuma-dev - librbd-dev - librdmacm-dev - libz-dev - ) - case "$CI_TARGET_ARCH" in - "x86") - pkgs=("${pkgs[@]/%/:i386}") - pkgs+=( - gcc-multilib - pkg-config:i386 - ) - ;; - "amd64") - pkgs+=(nvidia-cuda-dev) - ;; - esac - if [[ $CI_TARGET_ARCH != "x86" ]]; then - pkgs+=(glusterfs-common) - fi - # Architecture-independent packages and packages for which we don't - # care about the architecture. - pkgs+=( - bison - flex - python3 - python3-scipy - python3-six - ) - sudo apt-get -qq update - sudo apt-get install --no-install-recommends -qq -y "${pkgs[@]}" - # librpma is supported on the amd64 (x86_64) architecture for now - if [[ $CI_TARGET_ARCH == "amd64" ]]; then - # install libprotobuf-c-dev required by librpma_gpspm - sudo apt-get install --no-install-recommends -qq -y libprotobuf-c-dev - # PMDK libraries have to be installed, because - # libpmem is a dependency of the librpma fio engine - ci/travis-install-pmdk.sh - # install librpma from sources from GitHub - ci/travis-install-librpma.sh - fi - ;; - "osx") - brew update >/dev/null 2>&1 - brew install cunit - pip3 install scipy six - ;; -esac - -echo "Python3 path: $(type -p python3 2>&1)" -echo "Python3 version: $(python3 -V 2>&1)" From 2a92925771284c222229bd0fb2576b8972f3f6bc Mon Sep 17 00:00:00 2001 From: Nikolaus Rath Date: Tue, 20 Apr 2021 12:07:46 +0100 Subject: [PATCH 0003/1097] I/O size: fix description of filesize Update description of `size` option to match implementation: Setting `size` does not actually limit the total size of files created if `filesize` is specified. Fixes: #1218 Signed-Off-By: Nikolaus Rath --- HOWTO | 11 ++++++----- fio.1 | 8 ++++---- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/HOWTO b/HOWTO index e6078c5f1e..943e99d2a5 100644 --- a/HOWTO +++ b/HOWTO @@ -1836,11 +1836,12 @@ I/O size .. option:: filesize=irange(int) - Individual file sizes. May be a range, in which case fio will select sizes - for files at random within the given range and limited to :option:`size` in - total (if that is given). If not given, each created file is the same size. - This option overrides :option:`size` in terms of file size, which means - this value is used as a fixed size or possible range of each file. + Individual file sizes. May be a range, in which case fio will select sizes for + files at random within the given range. If not given, each created file is the + same size. This option overrides :option:`size` in terms of file size, i.e. if + :option:`filesize` is specified then :option:`size` becomes merely the default + for :option:`io_size` and has no effect at all if :option:`io_size` is set + explicitly. .. option:: file_append=bool diff --git a/fio.1 b/fio.1 index 18dc156ad0..95456a3258 100644 --- a/fio.1 +++ b/fio.1 @@ -1625,10 +1625,10 @@ In this case \fBio_size\fR multiplies \fBsize\fR= value. .TP .BI filesize \fR=\fPirange(int) Individual file sizes. May be a range, in which case fio will select sizes -for files at random within the given range and limited to \fBsize\fR in -total (if that is given). If not given, each created file is the same size. -This option overrides \fBsize\fR in terms of file size, which means -this value is used as a fixed size or possible range of each file. +for files at random within the given range. If not given, each created file +is the same size. This option overrides \fBsize\fR in terms of file size, +i.e. \fBsize\fR becomes merely the default for \fBio_size\fR (and +has no effect it all if \fBio_size\fR is set explicitly). .TP .BI file_append \fR=\fPbool Perform I/O after the end of the file. Normally fio will operate within the From 8375d82ad1e147e6365bd195ab7eacd4d26ad307 Mon Sep 17 00:00:00 2001 From: David Korczynski Date: Tue, 30 Nov 2021 13:29:23 +0000 Subject: [PATCH 0004/1097] ci/Github actions: add CIFuzz integration --- .github/workflows/cifuzz.yml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 .github/workflows/cifuzz.yml diff --git a/.github/workflows/cifuzz.yml b/.github/workflows/cifuzz.yml new file mode 100644 index 0000000000..acc8d4828c --- /dev/null +++ b/.github/workflows/cifuzz.yml @@ -0,0 +1,24 @@ +name: CIFuzz +on: [pull_request] +jobs: + Fuzzing: + runs-on: ubuntu-latest + steps: + - name: Build Fuzzers + id: build + uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master + with: + oss-fuzz-project-name: 'fio' + dry-run: false + - name: Run Fuzzers + uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master + with: + oss-fuzz-project-name: 'fio' + fuzz-seconds: 600 + dry-run: false + - name: Upload Crash + uses: actions/upload-artifact@v1 + if: failure() && steps.build.outcome == 'success' + with: + name: artifacts + path: ./out/artifacts From 2550c71f17c1963eaa140dd71ed34ace64315288 Mon Sep 17 00:00:00 2001 From: Thomas Munro Date: Sun, 12 Dec 2021 01:25:38 +1300 Subject: [PATCH 0005/1097] Update comments about availability of fdatasync(). FreeBSD 11 added fdatasync(2), in 2016. Signed-off-by: Thomas Munro --- HOWTO | 2 +- fio.1 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/HOWTO b/HOWTO index 8c9e41356b..901305aad5 100644 --- a/HOWTO +++ b/HOWTO @@ -1338,7 +1338,7 @@ I/O type .. option:: fdatasync=int Like :option:`fsync` but uses :manpage:`fdatasync(2)` to only sync data and - not metadata blocks. In Windows, FreeBSD, DragonFlyBSD or OSX there is no + not metadata blocks. In Windows, DragonFlyBSD or OSX there is no :manpage:`fdatasync(2)` so this falls back to using :manpage:`fsync(2)`. Defaults to 0, which means fio does not periodically issue and wait for a data-only sync to complete. diff --git a/fio.1 b/fio.1 index a3ebb67d36..379225289a 100644 --- a/fio.1 +++ b/fio.1 @@ -1122,7 +1122,7 @@ see \fBend_fsync\fR and \fBfsync_on_close\fR. .TP .BI fdatasync \fR=\fPint Like \fBfsync\fR but uses \fBfdatasync\fR\|(2) to only sync data and -not metadata blocks. In Windows, FreeBSD, DragonFlyBSD or OSX there is no +not metadata blocks. In Windows, DragonFlyBSD or OSX there is no \fBfdatasync\fR\|(2) so this falls back to using \fBfsync\fR\|(2). Defaults to 0, which means fio does not periodically issue and wait for a data-only sync to complete. From cea3243fb3bb44d541c2b3fb82ee45eb669b6fe6 Mon Sep 17 00:00:00 2001 From: Niklas Cassel Date: Tue, 14 Dec 2021 11:18:03 +0000 Subject: [PATCH 0006/1097] ci: temporarily remove linux-i686-gcc build GitHub Actions was recently enabled in commit ce1b5612ce99 ("ci: add CI via GitHub Actions"). The new CI configuration was not properly tested before being merged, as the linux-i686-gcc build currently fails for the master branch: https://github.com/axboe/fio/actions The problem appears to be related to ci/actions-install.sh wanting to install broken packages on linux-i686-gcc. The new CI configuration will also cause fio forks on GitHub to trigger a GitHub Action (inside the forked repo) for every push. Since the linux-i686-gcc build currently fails, this will currently cause error emails to be sent out for every push to a forked repo. In order to avoid spamming everyone who has forked fio on GitHub, let's temporarily remove the linux-i686-gcc build until ci/actions-install.sh specifies a working list of packages. Once that is done, this commit can simply be reverted. Signed-off-by: Niklas Cassel Link: https://lore.kernel.org/r/20211214111756.52968-1-Niklas.Cassel@wdc.com Signed-off-by: Jens Axboe --- .github/workflows/ci.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a766cfa8e2..04351dd51a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,6 @@ jobs: - linux-gcc - linux-clang - macos - - linux-i686-gcc include: - build: linux-gcc os: ubuntu-20.04 @@ -24,9 +23,6 @@ jobs: cc: clang - build: macos os: macos-10.15 - - build: linux-i686-gcc - os: ubuntu-20.04 - arch: i686 env: CI_TARGET_ARCH: ${{ matrix.arch }} From d685f07ecb0f2ff64e475fe0ddf86e69b295fbf6 Mon Sep 17 00:00:00 2001 From: Niklas Cassel Date: Tue, 14 Dec 2021 11:18:04 +0000 Subject: [PATCH 0007/1097] ci: use macos 11 in virtual environment GitHub Actions was recently enabled in commit ce1b5612ce99 ("ci: add CI via GitHub Actions"). The commit has a AuthorDate of 2020. The commit mentions that it uses macOS 10.15 rather than 11.0 because 11.0 is only private preview. This was true in 2020, but looking at: https://docs.github.com/en/actions/reference/specifications-for-github-hosted-runners macos-11 is no longer marked as private preview, so let's use it in the virtual environment. Signed-off-by: Niklas Cassel Link: https://lore.kernel.org/r/20211214111756.52968-2-Niklas.Cassel@wdc.com Signed-off-by: Jens Axboe --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 04351dd51a..8167e3d163 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,7 +22,7 @@ jobs: os: ubuntu-20.04 cc: clang - build: macos - os: macos-10.15 + os: macos-11 env: CI_TARGET_ARCH: ${{ matrix.arch }} From 12324d569861b83fb76e874122ddfcf28ee8d485 Mon Sep 17 00:00:00 2001 From: Damien Le Moal Date: Tue, 14 Dec 2021 10:24:02 +0900 Subject: [PATCH 0008/1097] fio: Improve documentation of ignore_zone_limits option In the manual pages, change the description of the option ignore_zone_limits to its action when set, instead of the confusing text describing what happens when it is not set. Also add the description of this option in the HOWTO file as it is missing. Signed-off-by: Damien Le Moal Reviewed-by: Niklas Cassel Link: https://lore.kernel.org/r/20211214012413.464798-2-damien.lemoal@opensource.wdc.com Signed-off-by: Jens Axboe --- HOWTO | 6 ++++++ fio.1 | 6 +++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/HOWTO b/HOWTO index 8c9e41356b..2956e50d7d 100644 --- a/HOWTO +++ b/HOWTO @@ -1063,6 +1063,12 @@ Target file/device Limit on the number of simultaneously opened zones per single thread/process. +.. option:: ignore_zone_limits=bool + If this option is used, fio will ignore the maximum number of open + zones limit of the zoned block device in use, thus allowing the + option :option:`max_open_zones` value to be larger than the device + reported limit. Default: false. + .. option:: zone_reset_threshold=float A number between zero and one that indicates the ratio of logical diff --git a/fio.1 b/fio.1 index a3ebb67d36..e0458c22e2 100644 --- a/fio.1 +++ b/fio.1 @@ -838,9 +838,9 @@ threads/processes. Limit on the number of simultaneously opened zones per single thread/process. .TP .BI ignore_zone_limits \fR=\fPbool -If this isn't set, fio will query the max open zones limit from the zoned block -device, and exit if the specified \fBmax_open_zones\fR value is larger than the -limit reported by the device. Default: false. +If this option is used, fio will ignore the maximum number of open zones limit +of the zoned block device in use, thus allowing the option \fBmax_open_zones\fR +value to be larger than the device reported limit. Default: false. .TP .BI zone_reset_threshold \fR=\fPfloat A number between zero and one that indicates the ratio of logical blocks with From 38334c1347e624237118d08f467aff35a8fcafe6 Mon Sep 17 00:00:00 2001 From: Damien Le Moal Date: Tue, 14 Dec 2021 10:24:03 +0900 Subject: [PATCH 0009/1097] zbd: define local functions as static Define zbd_get_zoned_model(), zbd_report_zones(), zbd_reset_wp() and zbd_get_max_open_zones() as static since these functions are used locally only. No functional changes. Signed-off-by: Damien Le Moal Reviewed-by: Niklas Cassel Link: https://lore.kernel.org/r/20211214012413.464798-3-damien.lemoal@opensource.wdc.com Signed-off-by: Jens Axboe --- zbd.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/zbd.c b/zbd.c index c18998c46f..44e7722799 100644 --- a/zbd.c +++ b/zbd.c @@ -27,8 +27,8 @@ * @td: FIO thread data * @f: FIO file for which to get model information */ -int zbd_get_zoned_model(struct thread_data *td, struct fio_file *f, - enum zbd_zoned_model *model) +static int zbd_get_zoned_model(struct thread_data *td, struct fio_file *f, + enum zbd_zoned_model *model) { int ret; @@ -71,9 +71,9 @@ int zbd_get_zoned_model(struct thread_data *td, struct fio_file *f, * upon failure. If the zone report is empty, always assume an error (device * problem) and return -EIO. */ -int zbd_report_zones(struct thread_data *td, struct fio_file *f, - uint64_t offset, struct zbd_zone *zones, - unsigned int nr_zones) +static int zbd_report_zones(struct thread_data *td, struct fio_file *f, + uint64_t offset, struct zbd_zone *zones, + unsigned int nr_zones) { int ret; @@ -105,8 +105,8 @@ int zbd_report_zones(struct thread_data *td, struct fio_file *f, * Reset the write pointer of all zones in the range @offset...@offset+@length. * Returns 0 upon success and a negative error code upon failure. */ -int zbd_reset_wp(struct thread_data *td, struct fio_file *f, - uint64_t offset, uint64_t length) +static int zbd_reset_wp(struct thread_data *td, struct fio_file *f, + uint64_t offset, uint64_t length) { int ret; @@ -133,8 +133,8 @@ int zbd_reset_wp(struct thread_data *td, struct fio_file *f, * * Returns 0 upon success and a negative error code upon failure. */ -int zbd_get_max_open_zones(struct thread_data *td, struct fio_file *f, - unsigned int *max_open_zones) +static int zbd_get_max_open_zones(struct thread_data *td, struct fio_file *f, + unsigned int *max_open_zones) { int ret; From 410a071c59d7992968af7073bf39546df18542ff Mon Sep 17 00:00:00 2001 From: Damien Le Moal Date: Tue, 14 Dec 2021 10:24:04 +0900 Subject: [PATCH 0010/1097] zbd: move and cleanup code Move zone manipulation helper functions at the beginning of the zbd.c file to avoid forward declarations and to group these functions together apart from the IO manipulation functions. Also fix function comments. No functional changes. Signed-off-by: Damien Le Moal Signed-off-by: Shin'ichiro Kawasaki Reviewed-by: Niklas Cassel Link: https://lore.kernel.org/r/20211214012413.464798-4-damien.lemoal@opensource.wdc.com Signed-off-by: Jens Axboe --- zbd.c | 582 ++++++++++++++++++++++++++++++---------------------------- 1 file changed, 301 insertions(+), 281 deletions(-) diff --git a/zbd.c b/zbd.c index 44e7722799..20d53b156d 100644 --- a/zbd.c +++ b/zbd.c @@ -22,6 +22,112 @@ #include "pshared.h" #include "zbd.h" +static bool is_valid_offset(const struct fio_file *f, uint64_t offset) +{ + return (uint64_t)(offset - f->file_offset) < f->io_size; +} + +static inline unsigned int zbd_zone_nr(const struct fio_file *f, + struct fio_zone_info *zone) +{ + return zone - f->zbd_info->zone_info; +} + +/** + * zbd_zone_idx - convert an offset into a zone number + * @f: file pointer. + * @offset: offset in bytes. If this offset is in the first zone_size bytes + * past the disk size then the index of the sentinel is returned. + */ +static uint32_t zbd_zone_idx(const struct fio_file *f, uint64_t offset) +{ + uint32_t zone_idx; + + if (f->zbd_info->zone_size_log2 > 0) + zone_idx = offset >> f->zbd_info->zone_size_log2; + else + zone_idx = offset / f->zbd_info->zone_size; + + return min(zone_idx, f->zbd_info->nr_zones); +} + +/** + * zbd_zone_end - Return zone end location + * @z: zone info pointer. + */ +static inline uint64_t zbd_zone_end(const struct fio_zone_info *z) +{ + return (z+1)->start; +} + +/** + * zbd_zone_capacity_end - Return zone capacity limit end location + * @z: zone info pointer. + */ +static inline uint64_t zbd_zone_capacity_end(const struct fio_zone_info *z) +{ + return z->start + z->capacity; +} + +/** + * zbd_zone_full - verify whether a minimum number of bytes remain in a zone + * @f: file pointer. + * @z: zone info pointer. + * @required: minimum number of bytes that must remain in a zone. + * + * The caller must hold z->mutex. + */ +static bool zbd_zone_full(const struct fio_file *f, struct fio_zone_info *z, + uint64_t required) +{ + assert((required & 511) == 0); + + return z->has_wp && + z->wp + required > zbd_zone_capacity_end(z); +} + +static void zone_lock(struct thread_data *td, const struct fio_file *f, + struct fio_zone_info *z) +{ + struct zoned_block_device_info *zbd = f->zbd_info; + uint32_t nz = z - zbd->zone_info; + + /* A thread should never lock zones outside its working area. */ + assert(f->min_zone <= nz && nz < f->max_zone); + + assert(z->has_wp); + + /* + * Lock the io_u target zone. The zone will be unlocked if io_u offset + * is changed or when io_u completes and zbd_put_io() executed. + * To avoid multiple jobs doing asynchronous I/Os from deadlocking each + * other waiting for zone locks when building an io_u batch, first + * only trylock the zone. If the zone is already locked by another job, + * process the currently queued I/Os so that I/O progress is made and + * zones unlocked. + */ + if (pthread_mutex_trylock(&z->mutex) != 0) { + if (!td_ioengine_flagged(td, FIO_SYNCIO)) + io_u_quiesce(td); + pthread_mutex_lock(&z->mutex); + } +} + +static inline void zone_unlock(struct fio_zone_info *z) +{ + int ret; + + assert(z->has_wp); + ret = pthread_mutex_unlock(&z->mutex); + assert(!ret); +} + +static inline struct fio_zone_info *get_zone(const struct fio_file *f, + unsigned int zone_nr) +{ + return &f->zbd_info->zone_info[zone_nr]; +} + /** * zbd_get_zoned_model - Get a device zoned model * @td: FIO thread data @@ -123,6 +229,126 @@ static int zbd_reset_wp(struct thread_data *td, struct fio_file *f, return ret; } +/** + * zbd_reset_zone - reset the write pointer of a single zone + * @td: FIO thread data. + * @f: FIO file associated with the disk for which to reset a write pointer. + * @z: Zone to reset. + * + * Returns 0 upon success and a negative error code upon failure. + * + * The caller must hold z->mutex. + */ +static int zbd_reset_zone(struct thread_data *td, struct fio_file *f, + struct fio_zone_info *z) +{ + uint64_t offset = z->start; + uint64_t length = (z+1)->start - offset; + uint64_t data_in_zone = z->wp - z->start; + int ret = 0; + + if (!data_in_zone) + return 0; + + assert(is_valid_offset(f, offset + length - 1)); + + dprint(FD_ZBD, "%s: resetting wp of zone %u.\n", f->file_name, + zbd_zone_nr(f, z)); + switch (f->zbd_info->model) { + case ZBD_HOST_AWARE: + case ZBD_HOST_MANAGED: + ret = zbd_reset_wp(td, f, offset, length); + if (ret < 0) + return ret; + break; + default: + break; + } + + pthread_mutex_lock(&f->zbd_info->mutex); + f->zbd_info->sectors_with_data -= data_in_zone; + f->zbd_info->wp_sectors_with_data -= data_in_zone; + pthread_mutex_unlock(&f->zbd_info->mutex); + z->wp = z->start; + z->verify_block = 0; + + td->ts.nr_zone_resets++; + + return ret; +} + +/** + * zbd_close_zone - Remove a zone from the open zones array. + * @td: FIO thread data. + * @f: FIO file associated with the disk for which to reset a write pointer. + * @zone_idx: Index of the zone to remove. + * + * The caller must hold f->zbd_info->mutex. + */ +static void zbd_close_zone(struct thread_data *td, const struct fio_file *f, + unsigned int zone_idx) +{ + uint32_t open_zone_idx = 0; + + for (; open_zone_idx < f->zbd_info->num_open_zones; open_zone_idx++) { + if (f->zbd_info->open_zones[open_zone_idx] == zone_idx) + break; + } + if (open_zone_idx == f->zbd_info->num_open_zones) + return; + + dprint(FD_ZBD, "%s: closing zone %d\n", f->file_name, zone_idx); + memmove(f->zbd_info->open_zones + open_zone_idx, + f->zbd_info->open_zones + open_zone_idx + 1, + (ZBD_MAX_OPEN_ZONES - (open_zone_idx + 1)) * + sizeof(f->zbd_info->open_zones[0])); + f->zbd_info->num_open_zones--; + td->num_open_zones--; + get_zone(f, zone_idx)->open = 0; +} + +/** + * zbd_reset_zones - Reset a range of zones. + * @td: fio thread data. + * @f: fio file for which to reset zones + * @zb: first zone to reset. + * @ze: first zone not to reset. + * + * Returns 0 upon success and 1 upon failure. + */ +static int zbd_reset_zones(struct thread_data *td, struct fio_file *f, + struct fio_zone_info *const zb, + struct fio_zone_info *const ze) +{ + struct fio_zone_info *z; + const uint64_t min_bs = td->o.min_bs[DDIR_WRITE]; + int res = 0; + + assert(min_bs); + + dprint(FD_ZBD, "%s: examining zones %u .. %u\n", f->file_name, + zbd_zone_nr(f, zb), zbd_zone_nr(f, ze)); + for (z = zb; z < ze; z++) { + uint32_t nz = zbd_zone_nr(f, z); + + if (!z->has_wp) + continue; + zone_lock(td, f, z); + pthread_mutex_lock(&f->zbd_info->mutex); + zbd_close_zone(td, f, nz); + pthread_mutex_unlock(&f->zbd_info->mutex); + if (z->wp != z->start) { + dprint(FD_ZBD, "%s: resetting zone %u\n", + f->file_name, zbd_zone_nr(f, z)); + if (zbd_reset_zone(td, f, z) < 0) + res = 1; + } + zone_unlock(z); + } + + return res; +} + /** * zbd_get_max_open_zones - Get the maximum number of open zones * @td: FIO thread data @@ -152,103 +378,99 @@ static int zbd_get_max_open_zones(struct thread_data *td, struct fio_file *f, } /** - * zbd_zone_idx - convert an offset into a zone number - * @f: file pointer. - * @offset: offset in bytes. If this offset is in the first zone_size bytes - * past the disk size then the index of the sentinel is returned. + * is_zone_open - Test if a zone is already in the array of open zones. + * @td: fio thread data. + * @f: fio file for which to test zones. + * @zone_idx: Index of the zone to check. + * + * The caller must hold f->zbd_info->mutex. */ -static uint32_t zbd_zone_idx(const struct fio_file *f, uint64_t offset) +static bool is_zone_open(const struct thread_data *td, const struct fio_file *f, + unsigned int zone_idx) { - uint32_t zone_idx; - - if (f->zbd_info->zone_size_log2 > 0) - zone_idx = offset >> f->zbd_info->zone_size_log2; - else - zone_idx = offset / f->zbd_info->zone_size; + struct zoned_block_device_info *zbdi = f->zbd_info; + int i; - return min(zone_idx, f->zbd_info->nr_zones); -} + /* + * This function should never be called when zbdi->max_open_zones == 0. + */ + assert(zbdi->max_open_zones); + assert(td->o.job_max_open_zones == 0 || + td->num_open_zones <= td->o.job_max_open_zones); + assert(td->o.job_max_open_zones <= zbdi->max_open_zones); + assert(zbdi->num_open_zones <= zbdi->max_open_zones); -/** - * zbd_zone_end - Return zone end location - * @z: zone info pointer. - */ -static inline uint64_t zbd_zone_end(const struct fio_zone_info *z) -{ - return (z+1)->start; -} + for (i = 0; i < zbdi->num_open_zones; i++) + if (zbdi->open_zones[i] == zone_idx) + return true; -/** - * zbd_zone_capacity_end - Return zone capacity limit end location - * @z: zone info pointer. - */ -static inline uint64_t zbd_zone_capacity_end(const struct fio_zone_info *z) -{ - return z->start + z->capacity; + return false; } /** - * zbd_zone_full - verify whether a minimum number of bytes remain in a zone - * @f: file pointer. - * @z: zone info pointer. - * @required: minimum number of bytes that must remain in a zone. + * zbd_open_zone - Add a zone to the array of open zones. + * @td: fio thread data. + * @f: fio file that has the open zones to add. + * @zone_idx: Index of the zone to add. * - * The caller must hold z->mutex. + * Open a ZBD zone if it is not already open. Returns true if either the zone + * was already open or if the zone was successfully added to the array of open + * zones without exceeding the maximum number of open zones. Returns false if + * the zone was not already open and opening the zone would cause the zone limit + * to be exceeded. */ -static bool zbd_zone_full(const struct fio_file *f, struct fio_zone_info *z, - uint64_t required) -{ - assert((required & 511) == 0); - - return z->has_wp && - z->wp + required > zbd_zone_capacity_end(z); -} - -static void zone_lock(struct thread_data *td, const struct fio_file *f, - struct fio_zone_info *z) +static bool zbd_open_zone(struct thread_data *td, const struct fio_file *f, + uint32_t zone_idx) { - struct zoned_block_device_info *zbd = f->zbd_info; - uint32_t nz = z - zbd->zone_info; - - /* A thread should never lock zones outside its working area. */ - assert(f->min_zone <= nz && nz < f->max_zone); + const uint64_t min_bs = td->o.min_bs[DDIR_WRITE]; + struct zoned_block_device_info *zbdi = f->zbd_info; + struct fio_zone_info *z = get_zone(f, zone_idx); + bool res = true; - assert(z->has_wp); + if (z->cond == ZBD_ZONE_COND_OFFLINE) + return false; /* - * Lock the io_u target zone. The zone will be unlocked if io_u offset - * is changed or when io_u completes and zbd_put_io() executed. - * To avoid multiple jobs doing asynchronous I/Os from deadlocking each - * other waiting for zone locks when building an io_u batch, first - * only trylock the zone. If the zone is already locked by another job, - * process the currently queued I/Os so that I/O progress is made and - * zones unlocked. + * Skip full zones with data verification enabled because resetting a + * zone causes data loss and hence causes verification to fail. */ - if (pthread_mutex_trylock(&z->mutex) != 0) { - if (!td_ioengine_flagged(td, FIO_SYNCIO)) - io_u_quiesce(td); - pthread_mutex_lock(&z->mutex); - } -} - -static inline void zone_unlock(struct fio_zone_info *z) -{ - int ret; + if (td->o.verify != VERIFY_NONE && zbd_zone_full(f, z, min_bs)) + return false; - assert(z->has_wp); - ret = pthread_mutex_unlock(&z->mutex); - assert(!ret); -} + /* + * zbdi->max_open_zones == 0 means that there is no limit on the maximum + * number of open zones. In this case, do no track open zones in + * zbdi->open_zones array. + */ + if (!zbdi->max_open_zones) + return true; -static bool is_valid_offset(const struct fio_file *f, uint64_t offset) -{ - return (uint64_t)(offset - f->file_offset) < f->io_size; -} + pthread_mutex_lock(&zbdi->mutex); + if (is_zone_open(td, f, zone_idx)) { + /* + * If the zone is already open and going to be full by writes + * in-flight, handle it as a full zone instead of an open zone. + */ + if (z->wp >= zbd_zone_capacity_end(z)) + res = false; + goto out; + } + res = false; + /* Zero means no limit */ + if (td->o.job_max_open_zones > 0 && + td->num_open_zones >= td->o.job_max_open_zones) + goto out; + if (zbdi->num_open_zones >= zbdi->max_open_zones) + goto out; + dprint(FD_ZBD, "%s: opening zone %d\n", f->file_name, zone_idx); + zbdi->open_zones[zbdi->num_open_zones++] = zone_idx; + td->num_open_zones++; + z->open = 1; + res = true; -static inline struct fio_zone_info *get_zone(const struct fio_file *f, - unsigned int zone_nr) -{ - return &f->zbd_info->zone_info[zone_nr]; +out: + pthread_mutex_unlock(&zbdi->mutex); + return res; } /* Verify whether direct I/O is used for all host-managed zoned drives. */ @@ -751,11 +973,6 @@ static int zbd_init_zone_info(struct thread_data *td, struct fio_file *file) return ret; } -static bool zbd_open_zone(struct thread_data *td, const struct fio_file *f, - uint32_t zone_idx); -static int zbd_reset_zone(struct thread_data *td, struct fio_file *f, - struct fio_zone_info *z); - int zbd_init_files(struct thread_data *td) { struct fio_file *f; @@ -879,123 +1096,6 @@ int zbd_setup_files(struct thread_data *td) return 0; } -static inline unsigned int zbd_zone_nr(const struct fio_file *f, - struct fio_zone_info *zone) -{ - return zone - f->zbd_info->zone_info; -} - -/** - * zbd_reset_zone - reset the write pointer of a single zone - * @td: FIO thread data. - * @f: FIO file associated with the disk for which to reset a write pointer. - * @z: Zone to reset. - * - * Returns 0 upon success and a negative error code upon failure. - * - * The caller must hold z->mutex. - */ -static int zbd_reset_zone(struct thread_data *td, struct fio_file *f, - struct fio_zone_info *z) -{ - uint64_t offset = z->start; - uint64_t length = (z+1)->start - offset; - uint64_t data_in_zone = z->wp - z->start; - int ret = 0; - - if (!data_in_zone) - return 0; - - assert(is_valid_offset(f, offset + length - 1)); - - dprint(FD_ZBD, "%s: resetting wp of zone %u.\n", f->file_name, - zbd_zone_nr(f, z)); - switch (f->zbd_info->model) { - case ZBD_HOST_AWARE: - case ZBD_HOST_MANAGED: - ret = zbd_reset_wp(td, f, offset, length); - if (ret < 0) - return ret; - break; - default: - break; - } - - pthread_mutex_lock(&f->zbd_info->mutex); - f->zbd_info->sectors_with_data -= data_in_zone; - f->zbd_info->wp_sectors_with_data -= data_in_zone; - pthread_mutex_unlock(&f->zbd_info->mutex); - z->wp = z->start; - z->verify_block = 0; - - td->ts.nr_zone_resets++; - - return ret; -} - -/* The caller must hold f->zbd_info->mutex */ -static void zbd_close_zone(struct thread_data *td, const struct fio_file *f, - unsigned int zone_idx) -{ - uint32_t open_zone_idx = 0; - - for (; open_zone_idx < f->zbd_info->num_open_zones; open_zone_idx++) { - if (f->zbd_info->open_zones[open_zone_idx] == zone_idx) - break; - } - if (open_zone_idx == f->zbd_info->num_open_zones) - return; - - dprint(FD_ZBD, "%s: closing zone %d\n", f->file_name, zone_idx); - memmove(f->zbd_info->open_zones + open_zone_idx, - f->zbd_info->open_zones + open_zone_idx + 1, - (ZBD_MAX_OPEN_ZONES - (open_zone_idx + 1)) * - sizeof(f->zbd_info->open_zones[0])); - f->zbd_info->num_open_zones--; - td->num_open_zones--; - get_zone(f, zone_idx)->open = 0; -} - -/* - * Reset a range of zones. Returns 0 upon success and 1 upon failure. - * @td: fio thread data. - * @f: fio file for which to reset zones - * @zb: first zone to reset. - * @ze: first zone not to reset. - */ -static int zbd_reset_zones(struct thread_data *td, struct fio_file *f, - struct fio_zone_info *const zb, - struct fio_zone_info *const ze) -{ - struct fio_zone_info *z; - const uint64_t min_bs = td->o.min_bs[DDIR_WRITE]; - int res = 0; - - assert(min_bs); - - dprint(FD_ZBD, "%s: examining zones %u .. %u\n", f->file_name, - zbd_zone_nr(f, zb), zbd_zone_nr(f, ze)); - for (z = zb; z < ze; z++) { - uint32_t nz = zbd_zone_nr(f, z); - - if (!z->has_wp) - continue; - zone_lock(td, f, z); - pthread_mutex_lock(&f->zbd_info->mutex); - zbd_close_zone(td, f, nz); - pthread_mutex_unlock(&f->zbd_info->mutex); - if (z->wp != z->start) { - dprint(FD_ZBD, "%s: resetting zone %u\n", - f->file_name, zbd_zone_nr(f, z)); - if (zbd_reset_zone(td, f, z) < 0) - res = 1; - } - zone_unlock(z); - } - - return res; -} - /* * Reset zbd_info.write_cnt, the counter that counts down towards the next * zone reset. @@ -1112,86 +1212,6 @@ void zbd_file_reset(struct thread_data *td, struct fio_file *f) zbd_reset_write_cnt(td, f); } -/* The caller must hold f->zbd_info->mutex. */ -static bool is_zone_open(const struct thread_data *td, const struct fio_file *f, - unsigned int zone_idx) -{ - struct zoned_block_device_info *zbdi = f->zbd_info; - int i; - - /* This function should never be called when zbdi->max_open_zones == 0 */ - assert(zbdi->max_open_zones); - assert(td->o.job_max_open_zones == 0 || td->num_open_zones <= td->o.job_max_open_zones); - assert(td->o.job_max_open_zones <= zbdi->max_open_zones); - assert(zbdi->num_open_zones <= zbdi->max_open_zones); - - for (i = 0; i < zbdi->num_open_zones; i++) - if (zbdi->open_zones[i] == zone_idx) - return true; - - return false; -} - -/* - * Open a ZBD zone if it was not yet open. Returns true if either the zone was - * already open or if opening a new zone is allowed. Returns false if the zone - * was not yet open and opening a new zone would cause the zone limit to be - * exceeded. - */ -static bool zbd_open_zone(struct thread_data *td, const struct fio_file *f, - uint32_t zone_idx) -{ - const uint64_t min_bs = td->o.min_bs[DDIR_WRITE]; - struct zoned_block_device_info *zbdi = f->zbd_info; - struct fio_zone_info *z = get_zone(f, zone_idx); - bool res = true; - - if (z->cond == ZBD_ZONE_COND_OFFLINE) - return false; - - /* - * Skip full zones with data verification enabled because resetting a - * zone causes data loss and hence causes verification to fail. - */ - if (td->o.verify != VERIFY_NONE && zbd_zone_full(f, z, min_bs)) - return false; - - /* - * zbdi->max_open_zones == 0 means that there is no limit on the maximum - * number of open zones. In this case, do no track open zones in - * zbdi->open_zones array. - */ - if (!zbdi->max_open_zones) - return true; - - pthread_mutex_lock(&zbdi->mutex); - if (is_zone_open(td, f, zone_idx)) { - /* - * If the zone is already open and going to be full by writes - * in-flight, handle it as a full zone instead of an open zone. - */ - if (z->wp >= zbd_zone_capacity_end(z)) - res = false; - goto out; - } - res = false; - /* Zero means no limit */ - if (td->o.job_max_open_zones > 0 && - td->num_open_zones >= td->o.job_max_open_zones) - goto out; - if (zbdi->num_open_zones >= zbdi->max_open_zones) - goto out; - dprint(FD_ZBD, "%s: opening zone %d\n", f->file_name, zone_idx); - zbdi->open_zones[zbdi->num_open_zones++] = zone_idx; - td->num_open_zones++; - z->open = 1; - res = true; - -out: - pthread_mutex_unlock(&zbdi->mutex); - return res; -} - /* Return random zone index for one of the open zones. */ static uint32_t pick_random_zone_idx(const struct fio_file *f, const struct io_u *io_u) From b5a0f7ce303d5fa5cca51af0652b7f7cf9bc5d61 Mon Sep 17 00:00:00 2001 From: Damien Le Moal Date: Tue, 14 Dec 2021 10:24:05 +0900 Subject: [PATCH 0011/1097] zbd: remove is_zone_open() helper The helper function is_zone_open() is useless as a each zone has an open flag indicating if it is part of the array of open zones. Remove this function code and use the zone open flag in zbd_open_zone(). Signed-off-by: Damien Le Moal Reviewed-by: Niklas Cassel Link: https://lore.kernel.org/r/20211214012413.464798-5-damien.lemoal@opensource.wdc.com Signed-off-by: Jens Axboe --- zbd.c | 38 +++++--------------------------------- 1 file changed, 5 insertions(+), 33 deletions(-) diff --git a/zbd.c b/zbd.c index 20d53b156d..70afdd825f 100644 --- a/zbd.c +++ b/zbd.c @@ -377,36 +377,6 @@ static int zbd_get_max_open_zones(struct thread_data *td, struct fio_file *f, return ret; } -/** - * is_zone_open - Test if a zone is already in the array of open zones. - * @td: fio thread data. - * @f: fio file for which to test zones. - * @zone_idx: Index of the zone to check. - * - * The caller must hold f->zbd_info->mutex. - */ -static bool is_zone_open(const struct thread_data *td, const struct fio_file *f, - unsigned int zone_idx) -{ - struct zoned_block_device_info *zbdi = f->zbd_info; - int i; - - /* - * This function should never be called when zbdi->max_open_zones == 0. - */ - assert(zbdi->max_open_zones); - assert(td->o.job_max_open_zones == 0 || - td->num_open_zones <= td->o.job_max_open_zones); - assert(td->o.job_max_open_zones <= zbdi->max_open_zones); - assert(zbdi->num_open_zones <= zbdi->max_open_zones); - - for (i = 0; i < zbdi->num_open_zones; i++) - if (zbdi->open_zones[i] == zone_idx) - return true; - - return false; -} - /** * zbd_open_zone - Add a zone to the array of open zones. * @td: fio thread data. @@ -446,10 +416,12 @@ static bool zbd_open_zone(struct thread_data *td, const struct fio_file *f, return true; pthread_mutex_lock(&zbdi->mutex); - if (is_zone_open(td, f, zone_idx)) { + + if (z->open) { /* - * If the zone is already open and going to be full by writes - * in-flight, handle it as a full zone instead of an open zone. + * If the zone is going to be completely filled by writes + * already in-flight, handle it as a full zone instead of an + * open zone. */ if (z->wp >= zbd_zone_capacity_end(z)) res = false; From 0bf93a1a7ed700b24177edb6db6c4c42e93ca7b2 Mon Sep 17 00:00:00 2001 From: Damien Le Moal Date: Tue, 14 Dec 2021 10:24:06 +0900 Subject: [PATCH 0012/1097] zbd: introduce zbd_zone_align_file_sizes() helper Move the code for the innermost loop of the function zbd_verify_sizes() to the new helper function zbd_zone_align_file_sizes(). This helper avoids large indentation of the code in zbd_verify_sizes() and makes the code easier to read. No functional changes. Signed-off-by: Damien Le Moal Reviewed-by: Niklas Cassel Link: https://lore.kernel.org/r/20211214012413.464798-6-damien.lemoal@opensource.wdc.com Signed-off-by: Jens Axboe --- zbd.c | 138 ++++++++++++++++++++++++++++++++-------------------------- 1 file changed, 77 insertions(+), 61 deletions(-) diff --git a/zbd.c b/zbd.c index 70afdd825f..11c15c622f 100644 --- a/zbd.c +++ b/zbd.c @@ -482,79 +482,95 @@ static bool zbd_is_seq_job(struct fio_file *f) return false; } +/* + * Verify whether the file offset and size parameters are aligned with zone + * boundaries. If the file offset is not aligned, align it down to the start of + * the zone containing the start offset and align up the file io_size parameter. + */ +static bool zbd_zone_align_file_sizes(struct thread_data *td, + struct fio_file *f) +{ + const struct fio_zone_info *z; + uint64_t new_offset, new_end; + uint32_t zone_idx; + + if (!f->zbd_info) + return true; + if (f->file_offset >= f->real_file_size) + return true; + if (!zbd_is_seq_job(f)) + return true; + + if (!td->o.zone_size) { + td->o.zone_size = f->zbd_info->zone_size; + if (!td->o.zone_size) { + log_err("%s: invalid 0 zone size\n", + f->file_name); + return false; + } + } else if (td->o.zone_size != f->zbd_info->zone_size) { + log_err("%s: zonesize %llu does not match the device zone size %"PRIu64".\n", + f->file_name, td->o.zone_size, + f->zbd_info->zone_size); + return false; + } + + if (td->o.zone_skip % td->o.zone_size) { + log_err("%s: zoneskip %llu is not a multiple of the device zone size %llu.\n", + f->file_name, td->o.zone_skip, + td->o.zone_size); + return false; + } + + zone_idx = zbd_zone_idx(f, f->file_offset); + z = get_zone(f, zone_idx); + if ((f->file_offset != z->start) && + (td->o.td_ddir != TD_DDIR_READ)) { + new_offset = zbd_zone_end(z); + if (new_offset >= f->file_offset + f->io_size) { + log_info("%s: io_size must be at least one zone\n", + f->file_name); + return false; + } + log_info("%s: rounded up offset from %"PRIu64" to %"PRIu64"\n", + f->file_name, f->file_offset, + new_offset); + f->io_size -= (new_offset - f->file_offset); + f->file_offset = new_offset; + } + + zone_idx = zbd_zone_idx(f, f->file_offset + f->io_size); + z = get_zone(f, zone_idx); + new_end = z->start; + if ((td->o.td_ddir != TD_DDIR_READ) && + (f->file_offset + f->io_size != new_end)) { + if (new_end <= f->file_offset) { + log_info("%s: io_size must be at least one zone\n", + f->file_name); + return false; + } + log_info("%s: rounded down io_size from %"PRIu64" to %"PRIu64"\n", + f->file_name, f->io_size, + new_end - f->file_offset); + f->io_size = new_end - f->file_offset; + } + + return true; +} + /* * Verify whether offset and size parameters are aligned with zone boundaries. */ static bool zbd_verify_sizes(void) { - const struct fio_zone_info *z; struct thread_data *td; struct fio_file *f; - uint64_t new_offset, new_end; - uint32_t zone_idx; int i, j; for_each_td(td, i) { for_each_file(td, f, j) { - if (!f->zbd_info) - continue; - if (f->file_offset >= f->real_file_size) - continue; - if (!zbd_is_seq_job(f)) - continue; - - if (!td->o.zone_size) { - td->o.zone_size = f->zbd_info->zone_size; - if (!td->o.zone_size) { - log_err("%s: invalid 0 zone size\n", - f->file_name); - return false; - } - } else if (td->o.zone_size != f->zbd_info->zone_size) { - log_err("%s: job parameter zonesize %llu does not match disk zone size %"PRIu64".\n", - f->file_name, td->o.zone_size, - f->zbd_info->zone_size); + if (!zbd_zone_align_file_sizes(td, f)) return false; - } - - if (td->o.zone_skip % td->o.zone_size) { - log_err("%s: zoneskip %llu is not a multiple of the device zone size %llu.\n", - f->file_name, td->o.zone_skip, - td->o.zone_size); - return false; - } - - zone_idx = zbd_zone_idx(f, f->file_offset); - z = get_zone(f, zone_idx); - if ((f->file_offset != z->start) && - (td->o.td_ddir != TD_DDIR_READ)) { - new_offset = zbd_zone_end(z); - if (new_offset >= f->file_offset + f->io_size) { - log_info("%s: io_size must be at least one zone\n", - f->file_name); - return false; - } - log_info("%s: rounded up offset from %"PRIu64" to %"PRIu64"\n", - f->file_name, f->file_offset, - new_offset); - f->io_size -= (new_offset - f->file_offset); - f->file_offset = new_offset; - } - zone_idx = zbd_zone_idx(f, f->file_offset + f->io_size); - z = get_zone(f, zone_idx); - new_end = z->start; - if ((td->o.td_ddir != TD_DDIR_READ) && - (f->file_offset + f->io_size != new_end)) { - if (new_end <= f->file_offset) { - log_info("%s: io_size must be at least one zone\n", - f->file_name); - return false; - } - log_info("%s: rounded down io_size from %"PRIu64" to %"PRIu64"\n", - f->file_name, f->io_size, - new_end - f->file_offset); - f->io_size = new_end - f->file_offset; - } } } From 139d8dc666e5e4a05eaa9f5e603fc3453c5fe43f Mon Sep 17 00:00:00 2001 From: Damien Le Moal Date: Tue, 14 Dec 2021 10:24:07 +0900 Subject: [PATCH 0013/1097] zbd: fix code style issues Avoid overly long lines, remove unnecessary curly brackets and add blank lines to make the code more readable. No functional changes. Signed-off-by: Damien Le Moal Signed-off-by: Shin'ichiro Kawasaki Reviewed-by: Niklas Cassel Link: https://lore.kernel.org/r/20211214012413.464798-7-damien.lemoal@opensource.wdc.com Signed-off-by: Jens Axboe --- zbd.c | 177 +++++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 125 insertions(+), 52 deletions(-) diff --git a/zbd.c b/zbd.c index 11c15c622f..b87fefc494 100644 --- a/zbd.c +++ b/zbd.c @@ -252,8 +252,9 @@ static int zbd_reset_zone(struct thread_data *td, struct fio_file *f, assert(is_valid_offset(f, offset + length - 1)); - dprint(FD_ZBD, "%s: resetting wp of zone %u.\n", f->file_name, - zbd_zone_nr(f, z)); + dprint(FD_ZBD, "%s: resetting wp of zone %u.\n", + f->file_name, zbd_zone_nr(f, z)); + switch (f->zbd_info->model) { case ZBD_HOST_AWARE: case ZBD_HOST_MANAGED: @@ -269,6 +270,7 @@ static int zbd_reset_zone(struct thread_data *td, struct fio_file *f, f->zbd_info->sectors_with_data -= data_in_zone; f->zbd_info->wp_sectors_with_data -= data_in_zone; pthread_mutex_unlock(&f->zbd_info->mutex); + z->wp = z->start; z->verify_block = 0; @@ -297,11 +299,14 @@ static void zbd_close_zone(struct thread_data *td, const struct fio_file *f, if (open_zone_idx == f->zbd_info->num_open_zones) return; - dprint(FD_ZBD, "%s: closing zone %d\n", f->file_name, zone_idx); + dprint(FD_ZBD, "%s: closing zone %d\n", + f->file_name, zone_idx); + memmove(f->zbd_info->open_zones + open_zone_idx, f->zbd_info->open_zones + open_zone_idx + 1, (ZBD_MAX_OPEN_ZONES - (open_zone_idx + 1)) * sizeof(f->zbd_info->open_zones[0])); + f->zbd_info->num_open_zones--; td->num_open_zones--; get_zone(f, zone_idx)->open = 0; @@ -326,23 +331,27 @@ static int zbd_reset_zones(struct thread_data *td, struct fio_file *f, assert(min_bs); - dprint(FD_ZBD, "%s: examining zones %u .. %u\n", f->file_name, - zbd_zone_nr(f, zb), zbd_zone_nr(f, ze)); + dprint(FD_ZBD, "%s: examining zones %u .. %u\n", + f->file_name, zbd_zone_nr(f, zb), zbd_zone_nr(f, ze)); + for (z = zb; z < ze; z++) { uint32_t nz = zbd_zone_nr(f, z); if (!z->has_wp) continue; + zone_lock(td, f, z); pthread_mutex_lock(&f->zbd_info->mutex); zbd_close_zone(td, f, nz); pthread_mutex_unlock(&f->zbd_info->mutex); + if (z->wp != z->start) { dprint(FD_ZBD, "%s: resetting zone %u\n", f->file_name, zbd_zone_nr(f, z)); if (zbd_reset_zone(td, f, z) < 0) res = 1; } + zone_unlock(z); } @@ -427,6 +436,7 @@ static bool zbd_open_zone(struct thread_data *td, const struct fio_file *f, res = false; goto out; } + res = false; /* Zero means no limit */ if (td->o.job_max_open_zones > 0 && @@ -434,7 +444,10 @@ static bool zbd_open_zone(struct thread_data *td, const struct fio_file *f, goto out; if (zbdi->num_open_zones >= zbdi->max_open_zones) goto out; - dprint(FD_ZBD, "%s: opening zone %d\n", f->file_name, zone_idx); + + dprint(FD_ZBD, "%s: opening zone %d\n", + f->file_name, zone_idx); + zbdi->open_zones[zbdi->num_open_zones++] = zone_idx; td->num_open_zones++; z->open = 1; @@ -471,8 +484,10 @@ static bool zbd_is_seq_job(struct fio_file *f) uint32_t zone_idx, zone_idx_b, zone_idx_e; assert(f->zbd_info); + if (f->io_size == 0) return false; + zone_idx_b = zbd_zone_idx(f, f->file_offset); zone_idx_e = zbd_zone_idx(f, f->file_offset + f->io_size - 1); for (zone_idx = zone_idx_b; zone_idx <= zone_idx_e; zone_idx++) @@ -595,6 +610,7 @@ static bool zbd_verify_bs(void) if (!f->zbd_info) continue; + zone_size = f->zbd_info->zone_size; if (td_trim(td) && td->o.bs[DDIR_TRIM] != zone_size) { log_info("%s: trim block size %llu is not the zone size %"PRIu64"\n", @@ -739,8 +755,8 @@ static int parse_zone_info(struct thread_data *td, struct fio_file *f) goto out; } - dprint(FD_ZBD, "Device %s has %d zones of size %"PRIu64" KB\n", f->file_name, - nr_zones, zone_size / 1024); + dprint(FD_ZBD, "Device %s has %d zones of size %"PRIu64" KB\n", + f->file_name, nr_zones, zone_size / 1024); zbd_info = scalloc(1, sizeof(*zbd_info) + (nr_zones + 1) * sizeof(zbd_info->zone_info[0])); @@ -756,6 +772,7 @@ static int parse_zone_info(struct thread_data *td, struct fio_file *f) PTHREAD_MUTEX_RECURSIVE); p->start = z->start; p->capacity = z->capacity; + switch (z->cond) { case ZBD_ZONE_COND_NOT_WP: case ZBD_ZONE_COND_FULL: @@ -789,6 +806,7 @@ static int parse_zone_info(struct thread_data *td, struct fio_file *f) offset = z->start + z->len; if (j >= nr_zones) break; + nrz = zbd_report_zones(td, f, offset, zones, min((uint32_t)(nr_zones - j), ZBD_REPORT_MAX_ZONES)); @@ -856,7 +874,8 @@ static int zbd_set_max_open_zones(struct thread_data *td, struct fio_file *f) /* Ensure that the limit is not larger than FIO's internal limit */ if (zbd->max_open_zones > ZBD_MAX_OPEN_ZONES) { td_verror(td, EINVAL, "'max_open_zones' value is too large"); - log_err("'max_open_zones' value is larger than %u\n", ZBD_MAX_OPEN_ZONES); + log_err("'max_open_zones' value is larger than %u\n", + ZBD_MAX_OPEN_ZONES); return -EINVAL; } @@ -958,6 +977,7 @@ static int zbd_init_zone_info(struct thread_data *td, struct fio_file *file) ret = zbd_create_zone_info(td, file); if (ret < 0) td_verror(td, -ret, "zbd_create_zone_info() failed"); + return ret; } @@ -970,6 +990,7 @@ int zbd_init_files(struct thread_data *td) if (zbd_init_zone_info(td, f)) return 1; } + return 0; } @@ -980,27 +1001,24 @@ void zbd_recalc_options_with_zone_granularity(struct thread_data *td) for_each_file(td, f, i) { struct zoned_block_device_info *zbd = f->zbd_info; - // zonemode=strided doesn't get per-file zone size. - uint64_t zone_size = zbd ? zbd->zone_size : td->o.zone_size; + uint64_t zone_size; + /* zonemode=strided doesn't get per-file zone size. */ + zone_size = zbd ? zbd->zone_size : td->o.zone_size; if (zone_size == 0) continue; - if (td->o.size_nz > 0) { + if (td->o.size_nz > 0) td->o.size = td->o.size_nz * zone_size; - } - if (td->o.io_size_nz > 0) { + if (td->o.io_size_nz > 0) td->o.io_size = td->o.io_size_nz * zone_size; - } - if (td->o.start_offset_nz > 0) { + if (td->o.start_offset_nz > 0) td->o.start_offset = td->o.start_offset_nz * zone_size; - } - if (td->o.offset_increment_nz > 0) { - td->o.offset_increment = td->o.offset_increment_nz * zone_size; - } - if (td->o.zone_skip_nz > 0) { + if (td->o.offset_increment_nz > 0) + td->o.offset_increment = + td->o.offset_increment_nz * zone_size; + if (td->o.zone_skip_nz > 0) td->o.zone_skip = td->o.zone_skip_nz * zone_size; - } } } @@ -1143,6 +1161,7 @@ static uint64_t zbd_process_swd(struct thread_data *td, } swd += z->wp - z->start; } + pthread_mutex_lock(&f->zbd_info->mutex); switch (a) { case CHECK_SWD: @@ -1155,6 +1174,7 @@ static uint64_t zbd_process_swd(struct thread_data *td, break; } pthread_mutex_unlock(&f->zbd_info->mutex); + for (z = zb; z < ze; z++) if (z->has_wp) zone_unlock(z); @@ -1188,8 +1208,10 @@ void zbd_file_reset(struct thread_data *td, struct fio_file *f) zb = get_zone(f, f->min_zone); ze = get_zone(f, f->max_zone); swd = zbd_process_swd(td, f, SET_SWD); - dprint(FD_ZBD, "%s(%s): swd = %" PRIu64 "\n", __func__, f->file_name, - swd); + + dprint(FD_ZBD, "%s(%s): swd = %" PRIu64 "\n", + __func__, f->file_name, swd); + /* * If data verification is enabled reset the affected zones before * writing any data to avoid that a zone reset has to be issued while @@ -1204,8 +1226,8 @@ void zbd_file_reset(struct thread_data *td, struct fio_file *f) static uint32_t pick_random_zone_idx(const struct fio_file *f, const struct io_u *io_u) { - return (io_u->offset - f->file_offset) * f->zbd_info->num_open_zones / - f->io_size; + return (io_u->offset - f->file_offset) * + f->zbd_info->num_open_zones / f->io_size; } static bool any_io_in_flight(void) @@ -1258,7 +1280,9 @@ static struct fio_zone_info *zbd_convert_to_open_zone(struct thread_data *td, zone_idx = f->min_zone; else if (zone_idx >= f->max_zone) zone_idx = f->max_zone - 1; - dprint(FD_ZBD, "%s(%s): starting from zone %d (offset %lld, buflen %lld)\n", + + dprint(FD_ZBD, + "%s(%s): starting from zone %d (offset %lld, buflen %lld)\n", __func__, f->file_name, zone_idx, io_u->offset, io_u->buflen); /* @@ -1273,10 +1297,13 @@ static struct fio_zone_info *zbd_convert_to_open_zone(struct thread_data *td, z = get_zone(f, zone_idx); if (z->has_wp) zone_lock(td, f, z); + pthread_mutex_lock(&zbdi->mutex); + if (z->has_wp) { if (z->cond != ZBD_ZONE_COND_OFFLINE && - zbdi->max_open_zones == 0 && td->o.job_max_open_zones == 0) + zbdi->max_open_zones == 0 && + td->o.job_max_open_zones == 0) goto examine_zone; if (zbdi->num_open_zones == 0) { dprint(FD_ZBD, "%s(%s): no zones are open\n", @@ -1286,14 +1313,15 @@ static struct fio_zone_info *zbd_convert_to_open_zone(struct thread_data *td, } /* - * List of opened zones is per-device, shared across all threads. - * Start with quasi-random candidate zone. - * Ignore zones which don't belong to thread's offset/size area. + * List of opened zones is per-device, shared across all + * threads. Start with quasi-random candidate zone. Ignore + * zones which don't belong to thread's offset/size area. */ open_zone_idx = pick_random_zone_idx(f, io_u); assert(!open_zone_idx || open_zone_idx < zbdi->num_open_zones); tmp_idx = open_zone_idx; + for (i = 0; i < zbdi->num_open_zones; i++) { uint32_t tmpz; @@ -1310,9 +1338,12 @@ static struct fio_zone_info *zbd_convert_to_open_zone(struct thread_data *td, dprint(FD_ZBD, "%s(%s): no candidate zone\n", __func__, f->file_name); + pthread_mutex_unlock(&zbdi->mutex); + if (z->has_wp) zone_unlock(z); + return NULL; found_candidate_zone: @@ -1320,7 +1351,9 @@ static struct fio_zone_info *zbd_convert_to_open_zone(struct thread_data *td, if (new_zone_idx == zone_idx) break; zone_idx = new_zone_idx; + pthread_mutex_unlock(&zbdi->mutex); + if (z->has_wp) zone_unlock(z); } @@ -1351,7 +1384,8 @@ static struct fio_zone_info *zbd_convert_to_open_zone(struct thread_data *td, * zone close before opening a new zone. */ if (wait_zone_close) { - dprint(FD_ZBD, "%s(%s): quiesce to allow open zones to close\n", + dprint(FD_ZBD, + "%s(%s): quiesce to allow open zones to close\n", __func__, f->file_name); io_u_quiesce(td); } @@ -1404,7 +1438,8 @@ static struct fio_zone_info *zbd_convert_to_open_zone(struct thread_data *td, */ in_flight = any_io_in_flight(); if (in_flight || should_retry) { - dprint(FD_ZBD, "%s(%s): wait zone close and retry open zones\n", + dprint(FD_ZBD, + "%s(%s): wait zone close and retry open zones\n", __func__, f->file_name); pthread_mutex_unlock(&zbdi->mutex); zone_unlock(z); @@ -1415,17 +1450,22 @@ static struct fio_zone_info *zbd_convert_to_open_zone(struct thread_data *td, } pthread_mutex_unlock(&zbdi->mutex); + zone_unlock(z); - dprint(FD_ZBD, "%s(%s): did not open another zone\n", __func__, - f->file_name); + + dprint(FD_ZBD, "%s(%s): did not open another zone\n", + __func__, f->file_name); + return NULL; out: - dprint(FD_ZBD, "%s(%s): returning zone %d\n", __func__, f->file_name, - zone_idx); + dprint(FD_ZBD, "%s(%s): returning zone %d\n", + __func__, f->file_name, zone_idx); + io_u->offset = z->start; assert(z->has_wp); assert(z->cond != ZBD_ZONE_COND_OFFLINE); + return z; } @@ -1444,18 +1484,20 @@ static struct fio_zone_info *zbd_replay_write_order(struct thread_data *td, } if (z->verify_block * min_bs >= z->capacity) { - log_err("%s: %d * %"PRIu64" >= %"PRIu64"\n", f->file_name, z->verify_block, - min_bs, z->capacity); + log_err("%s: %d * %"PRIu64" >= %"PRIu64"\n", + f->file_name, z->verify_block, min_bs, z->capacity); /* * If the assertion below fails during a test run, adding * "--experimental_verify=1" to the command line may help. */ assert(false); } + io_u->offset = z->start + z->verify_block * min_bs; if (io_u->offset + io_u->buflen >= zbd_zone_capacity_end(z)) { - log_err("%s: %llu + %llu >= %"PRIu64"\n", f->file_name, io_u->offset, - io_u->buflen, zbd_zone_capacity_end(z)); + log_err("%s: %llu + %llu >= %"PRIu64"\n", + f->file_name, io_u->offset, io_u->buflen, + zbd_zone_capacity_end(z)); assert(false); } z->verify_block += io_u->buflen / min_bs; @@ -1493,6 +1535,7 @@ zbd_find_zone(struct thread_data *td, struct io_u *io_u, uint64_t min_bytes, } else if (!td_random(td)) { break; } + if (td_random(td) && z2 >= zf && z2->cond != ZBD_ZONE_COND_OFFLINE) { if (z2->has_wp) @@ -1503,8 +1546,11 @@ zbd_find_zone(struct thread_data *td, struct io_u *io_u, uint64_t min_bytes, zone_unlock(z2); } } - dprint(FD_ZBD, "%s: no zone has %"PRIu64" bytes of readable data\n", + + dprint(FD_ZBD, + "%s: no zone has %"PRIu64" bytes of readable data\n", f->file_name, min_bytes); + return NULL; } @@ -1567,11 +1613,12 @@ static void zbd_queue_io(struct thread_data *td, struct io_u *io_u, int q, case DDIR_WRITE: zone_end = min((uint64_t)(io_u->offset + io_u->buflen), zbd_zone_capacity_end(z)); - pthread_mutex_lock(&zbd_info->mutex); + /* * z->wp > zone_end means that one or more I/O errors * have occurred. */ + pthread_mutex_lock(&zbd_info->mutex); if (z->wp <= zone_end) { zbd_info->sectors_with_data += zone_end - z->wp; zbd_info->wp_sectors_with_data += zone_end - z->wp; @@ -1671,8 +1718,8 @@ void setup_zbd_zone_mode(struct thread_data *td, struct io_u *io_u) * sequential write, skip to zone end if the latest position is at the * zone capacity limit. */ - if (z->capacity < f->zbd_info->zone_size && !td_random(td) && - ddir == DDIR_WRITE && + if (z->capacity < f->zbd_info->zone_size && + !td_random(td) && ddir == DDIR_WRITE && f->last_pos[ddir] >= zbd_zone_capacity_end(z)) { dprint(FD_ZBD, "%s: Jump from zone capacity limit to zone end:" @@ -1770,6 +1817,7 @@ enum io_u_action zbd_adjust_block(struct thread_data *td, struct io_u *io_u) assert(min_bs); assert(is_valid_offset(f, io_u->offset)); assert(io_u->buflen); + zone_idx_b = zbd_zone_idx(f, io_u->offset); zb = get_zone(f, zone_idx_b); orig_zb = zb; @@ -1778,6 +1826,7 @@ enum io_u_action zbd_adjust_block(struct thread_data *td, struct io_u *io_u) /* Accept non-write I/Os for conventional zones. */ if (io_u->ddir != DDIR_WRITE) return io_u_accept; + /* * Make sure that writes to conventional zones * don't cross over to any sequential zones. @@ -1791,12 +1840,16 @@ enum io_u_action zbd_adjust_block(struct thread_data *td, struct io_u *io_u) "%s: off=%llu + min_bs=%"PRIu64" > next zone %"PRIu64"\n", f->file_name, io_u->offset, min_bs, (zb + 1)->start); - io_u->offset = zb->start + (zb + 1)->start - io_u->offset; - new_len = min(io_u->buflen, (zb + 1)->start - io_u->offset); + io_u->offset = + zb->start + (zb + 1)->start - io_u->offset; + new_len = min(io_u->buflen, + (zb + 1)->start - io_u->offset); } else { new_len = (zb + 1)->start - io_u->offset; } + io_u->buflen = new_len / min_bs * min_bs; + return io_u_accept; } @@ -1818,6 +1871,7 @@ enum io_u_action zbd_adjust_block(struct thread_data *td, struct io_u *io_u) zb = zbd_replay_write_order(td, io_u, zb); goto accept; } + /* * Check that there is enough written data in the zone to do an * I/O of at least min_bs B. If there isn't, find a new zone for @@ -1847,6 +1901,7 @@ enum io_u_action zbd_adjust_block(struct thread_data *td, struct io_u *io_u) if (!td_random(td)) io_u->offset = zb->start; } + /* * Make sure the I/O is within the zone valid data range while * maximizing the I/O size and preserving randomness. @@ -1857,12 +1912,14 @@ enum io_u_action zbd_adjust_block(struct thread_data *td, struct io_u *io_u) io_u->offset = zb->start + ((io_u->offset - orig_zb->start) % (range - io_u->buflen)) / min_bs * min_bs; + /* * When zbd_find_zone() returns a conventional zone, * we can simply accept the new i/o offset here. */ if (!zb->has_wp) return io_u_accept; + /* * Make sure the I/O does not cross over the zone wp position. */ @@ -1874,9 +1931,12 @@ enum io_u_action zbd_adjust_block(struct thread_data *td, struct io_u *io_u) dprint(FD_IO, "Changed length from %u into %llu\n", orig_len, io_u->buflen); } + assert(zb->start <= io_u->offset); assert(io_u->offset + io_u->buflen <= zb->wp); + goto accept; + case DDIR_WRITE: if (io_u->buflen > zbdi->zone_size) { td_verror(td, EINVAL, "I/O buflen exceeds zone size"); @@ -1885,6 +1945,7 @@ enum io_u_action zbd_adjust_block(struct thread_data *td, struct io_u *io_u) f->file_name, io_u->buflen, zbdi->zone_size); goto eof; } + if (!zbd_open_zone(td, f, zone_idx_b)) { zone_unlock(zb); zb = zbd_convert_to_open_zone(td, io_u); @@ -1894,14 +1955,14 @@ enum io_u_action zbd_adjust_block(struct thread_data *td, struct io_u *io_u) goto eof; } } + /* Check whether the zone reset threshold has been exceeded */ if (td->o.zrf.u.f) { - if (zbdi->wp_sectors_with_data >= - f->io_size * td->o.zrt.u.f && - zbd_dec_and_reset_write_cnt(td, f)) { + if (zbdi->wp_sectors_with_data >= f->io_size * td->o.zrt.u.f && + zbd_dec_and_reset_write_cnt(td, f)) zb->reset_zone = 1; - } } + /* Reset the zone pointer if necessary */ if (zb->reset_zone || zbd_zone_full(f, zb, min_bs)) { assert(td->o.verify == VERIFY_NONE); @@ -1924,6 +1985,7 @@ enum io_u_action zbd_adjust_block(struct thread_data *td, struct io_u *io_u) goto eof; } } + /* Make writes occur at the write pointer */ assert(!zbd_zone_full(f, zb, min_bs)); io_u->offset = zb->wp; @@ -1933,6 +1995,7 @@ enum io_u_action zbd_adjust_block(struct thread_data *td, struct io_u *io_u) f->file_name, io_u->offset); goto eof; } + /* * Make sure that the buflen is a multiple of the minimal * block size. Give up if shrinking would make the request too @@ -1949,10 +2012,13 @@ enum io_u_action zbd_adjust_block(struct thread_data *td, struct io_u *io_u) orig_len, io_u->buflen); goto accept; } + td_verror(td, EIO, "zone remainder too small"); log_err("zone remainder %lld smaller than min block size %"PRIu64"\n", (zbd_zone_capacity_end(zb) - io_u->offset), min_bs); + goto eof; + case DDIR_TRIM: /* Check random trim targets a non-empty zone */ if (!td_random(td) || zb->wp > zb->start) @@ -1968,7 +2034,9 @@ enum io_u_action zbd_adjust_block(struct thread_data *td, struct io_u *io_u) f->file_name, io_u->offset); goto accept; } + goto eof; + case DDIR_SYNC: /* fall-through */ case DDIR_DATASYNC: @@ -1986,19 +2054,23 @@ enum io_u_action zbd_adjust_block(struct thread_data *td, struct io_u *io_u) assert(zb->cond != ZBD_ZONE_COND_OFFLINE); assert(!io_u->zbd_queue_io); assert(!io_u->zbd_put_io); + io_u->zbd_queue_io = zbd_queue_io; io_u->zbd_put_io = zbd_put_io; + /* * Since we return with the zone lock still held, * add an annotation to let Coverity know that it * is intentional. */ /* coverity[missing_unlock] */ + return io_u_accept; eof: if (zb && zb->has_wp) zone_unlock(zb); + return io_u_eof; } @@ -2036,7 +2108,8 @@ int zbd_do_io_u_trim(const struct thread_data *td, struct io_u *io_u) return 0; if (io_u->offset != z->start) { - log_err("Trim offset not at zone start (%lld)\n", io_u->offset); + log_err("Trim offset not at zone start (%lld)\n", + io_u->offset); return -EINVAL; } From a23411bb2e1c7de1e97c505ca2ec7270a35f7be1 Mon Sep 17 00:00:00 2001 From: Damien Le Moal Date: Tue, 14 Dec 2021 10:24:08 +0900 Subject: [PATCH 0014/1097] zbd: simplify zbd_close_zone() Change the interface of zbd_close_zone() to directly use a pointer to a zone information structure as all callers already have this information. Also do nothing for zones that are not marked as open instead of figuring out this fact by searching the array of open zones. No functional changes. Signed-off-by: Damien Le Moal Reviewed-by: Niklas Cassel Link: https://lore.kernel.org/r/20211214012413.464798-8-damien.lemoal@opensource.wdc.com Signed-off-by: Jens Axboe --- zbd.c | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/zbd.c b/zbd.c index b87fefc494..2638733506 100644 --- a/zbd.c +++ b/zbd.c @@ -288,28 +288,31 @@ static int zbd_reset_zone(struct thread_data *td, struct fio_file *f, * The caller must hold f->zbd_info->mutex. */ static void zbd_close_zone(struct thread_data *td, const struct fio_file *f, - unsigned int zone_idx) + struct fio_zone_info *z) { - uint32_t open_zone_idx = 0; + uint32_t ozi; - for (; open_zone_idx < f->zbd_info->num_open_zones; open_zone_idx++) { - if (f->zbd_info->open_zones[open_zone_idx] == zone_idx) + if (!z->open) + return; + + for (ozi = 0; ozi < f->zbd_info->num_open_zones; ozi++) { + if (get_zone(f, f->zbd_info->open_zones[ozi]) == z) break; } - if (open_zone_idx == f->zbd_info->num_open_zones) + if (ozi == f->zbd_info->num_open_zones) return; - dprint(FD_ZBD, "%s: closing zone %d\n", - f->file_name, zone_idx); + dprint(FD_ZBD, "%s: closing zone %u\n", + f->file_name, zbd_zone_nr(f, z)); - memmove(f->zbd_info->open_zones + open_zone_idx, - f->zbd_info->open_zones + open_zone_idx + 1, - (ZBD_MAX_OPEN_ZONES - (open_zone_idx + 1)) * + memmove(f->zbd_info->open_zones + ozi, + f->zbd_info->open_zones + ozi + 1, + (ZBD_MAX_OPEN_ZONES - (ozi + 1)) * sizeof(f->zbd_info->open_zones[0])); f->zbd_info->num_open_zones--; td->num_open_zones--; - get_zone(f, zone_idx)->open = 0; + z->open = 0; } /** @@ -335,14 +338,12 @@ static int zbd_reset_zones(struct thread_data *td, struct fio_file *f, f->file_name, zbd_zone_nr(f, zb), zbd_zone_nr(f, ze)); for (z = zb; z < ze; z++) { - uint32_t nz = zbd_zone_nr(f, z); - if (!z->has_wp) continue; zone_lock(td, f, z); pthread_mutex_lock(&f->zbd_info->mutex); - zbd_close_zone(td, f, nz); + zbd_close_zone(td, f, z); pthread_mutex_unlock(&f->zbd_info->mutex); if (z->wp != z->start) { @@ -1571,7 +1572,7 @@ static void zbd_end_zone_io(struct thread_data *td, const struct io_u *io_u, if (io_u->ddir == DDIR_WRITE && io_u->offset + io_u->buflen >= zbd_zone_capacity_end(z)) { pthread_mutex_lock(&f->zbd_info->mutex); - zbd_close_zone(td, f, zbd_zone_nr(f, z)); + zbd_close_zone(td, f, z); pthread_mutex_unlock(&f->zbd_info->mutex); } } From aad7c276b01304d3fa128c84dc885eb557944b29 Mon Sep 17 00:00:00 2001 From: Damien Le Moal Date: Tue, 14 Dec 2021 10:24:09 +0900 Subject: [PATCH 0015/1097] zbd: simplify zbd_open_zone() Similarly to zbd_close_zone(), directly pass a pointer to a zone information structure to zbd_open_zone() instead of a zone number. No functional changes. Signed-off-by: Damien Le Moal Signed-off-by: Shin'ichiro Kawasaki Reviewed-by: Niklas Cassel Link: https://lore.kernel.org/r/20211214012413.464798-9-damien.lemoal@opensource.wdc.com Signed-off-by: Jens Axboe --- zbd.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/zbd.c b/zbd.c index 2638733506..b917fa4275 100644 --- a/zbd.c +++ b/zbd.c @@ -400,11 +400,11 @@ static int zbd_get_max_open_zones(struct thread_data *td, struct fio_file *f, * to be exceeded. */ static bool zbd_open_zone(struct thread_data *td, const struct fio_file *f, - uint32_t zone_idx) + struct fio_zone_info *z) { const uint64_t min_bs = td->o.min_bs[DDIR_WRITE]; struct zoned_block_device_info *zbdi = f->zbd_info; - struct fio_zone_info *z = get_zone(f, zone_idx); + uint32_t zone_idx = zbd_zone_nr(f, z); bool res = true; if (z->cond == ZBD_ZONE_COND_OFFLINE) @@ -446,7 +446,7 @@ static bool zbd_open_zone(struct thread_data *td, const struct fio_file *f, if (zbdi->num_open_zones >= zbdi->max_open_zones) goto out; - dprint(FD_ZBD, "%s: opening zone %d\n", + dprint(FD_ZBD, "%s: opening zone %u\n", f->file_name, zone_idx); zbdi->open_zones[zbdi->num_open_zones++] = zone_idx; @@ -1087,7 +1087,7 @@ int zbd_setup_files(struct thread_data *td) if (z->cond != ZBD_ZONE_COND_IMP_OPEN && z->cond != ZBD_ZONE_COND_EXP_OPEN) continue; - if (zbd_open_zone(td, f, zi)) + if (zbd_open_zone(td, f, z)) continue; /* * If the number of open zones exceeds specified limits, @@ -1409,7 +1409,7 @@ static struct fio_zone_info *zbd_convert_to_open_zone(struct thread_data *td, zone_lock(td, f, z); if (z->open) continue; - if (zbd_open_zone(td, f, zone_idx)) + if (zbd_open_zone(td, f, z)) goto out; } @@ -1478,7 +1478,7 @@ static struct fio_zone_info *zbd_replay_write_order(struct thread_data *td, const struct fio_file *f = io_u->file; const uint64_t min_bs = td->o.min_bs[DDIR_WRITE]; - if (!zbd_open_zone(td, f, zbd_zone_nr(f, z))) { + if (!zbd_open_zone(td, f, z)) { zone_unlock(z); z = zbd_convert_to_open_zone(td, io_u); assert(z); @@ -1947,7 +1947,7 @@ enum io_u_action zbd_adjust_block(struct thread_data *td, struct io_u *io_u) goto eof; } - if (!zbd_open_zone(td, f, zone_idx_b)) { + if (!zbd_open_zone(td, f, zb)) { zone_unlock(zb); zb = zbd_convert_to_open_zone(td, io_u); if (!zb) { From dc8a3d629310d1ac9143d62c9f37ff16331737d5 Mon Sep 17 00:00:00 2001 From: Damien Le Moal Date: Tue, 14 Dec 2021 10:24:10 +0900 Subject: [PATCH 0016/1097] zbd: rename zbd_zone_idx() and zbd_zone_nr() Rename zbd_zone_idx() to zbd_offset_to_zone_idx() to make it clear that the argument determining the zone is a file offset. To be consistent, rename zbd_zone_nr() to zbd_zone_idx() to avoid confusion with a number of zones. While at it, have both functions return value be of the same unsigned int type. No functional changes. Signed-off-by: Damien Le Moal Reviewed-by: Niklas Cassel Link: https://lore.kernel.org/r/20211214012413.464798-10-damien.lemoal@opensource.wdc.com Signed-off-by: Jens Axboe --- zbd.c | 45 ++++++++++++++++++++++++--------------------- 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/zbd.c b/zbd.c index b917fa4275..592f7c0319 100644 --- a/zbd.c +++ b/zbd.c @@ -27,19 +27,20 @@ static bool is_valid_offset(const struct fio_file *f, uint64_t offset) return (uint64_t)(offset - f->file_offset) < f->io_size; } -static inline unsigned int zbd_zone_nr(const struct fio_file *f, - struct fio_zone_info *zone) +static inline unsigned int zbd_zone_idx(const struct fio_file *f, + struct fio_zone_info *zone) { return zone - f->zbd_info->zone_info; } /** - * zbd_zone_idx - convert an offset into a zone number + * zbd_offset_to_zone_idx - convert an offset into a zone number * @f: file pointer. * @offset: offset in bytes. If this offset is in the first zone_size bytes * past the disk size then the index of the sentinel is returned. */ -static uint32_t zbd_zone_idx(const struct fio_file *f, uint64_t offset) +static unsigned int zbd_offset_to_zone_idx(const struct fio_file *f, + uint64_t offset) { uint32_t zone_idx; @@ -253,7 +254,7 @@ static int zbd_reset_zone(struct thread_data *td, struct fio_file *f, assert(is_valid_offset(f, offset + length - 1)); dprint(FD_ZBD, "%s: resetting wp of zone %u.\n", - f->file_name, zbd_zone_nr(f, z)); + f->file_name, zbd_zone_idx(f, z)); switch (f->zbd_info->model) { case ZBD_HOST_AWARE: @@ -303,7 +304,7 @@ static void zbd_close_zone(struct thread_data *td, const struct fio_file *f, return; dprint(FD_ZBD, "%s: closing zone %u\n", - f->file_name, zbd_zone_nr(f, z)); + f->file_name, zbd_zone_idx(f, z)); memmove(f->zbd_info->open_zones + ozi, f->zbd_info->open_zones + ozi + 1, @@ -335,7 +336,7 @@ static int zbd_reset_zones(struct thread_data *td, struct fio_file *f, assert(min_bs); dprint(FD_ZBD, "%s: examining zones %u .. %u\n", - f->file_name, zbd_zone_nr(f, zb), zbd_zone_nr(f, ze)); + f->file_name, zbd_zone_idx(f, zb), zbd_zone_idx(f, ze)); for (z = zb; z < ze; z++) { if (!z->has_wp) @@ -348,7 +349,7 @@ static int zbd_reset_zones(struct thread_data *td, struct fio_file *f, if (z->wp != z->start) { dprint(FD_ZBD, "%s: resetting zone %u\n", - f->file_name, zbd_zone_nr(f, z)); + f->file_name, zbd_zone_idx(f, z)); if (zbd_reset_zone(td, f, z) < 0) res = 1; } @@ -404,7 +405,7 @@ static bool zbd_open_zone(struct thread_data *td, const struct fio_file *f, { const uint64_t min_bs = td->o.min_bs[DDIR_WRITE]; struct zoned_block_device_info *zbdi = f->zbd_info; - uint32_t zone_idx = zbd_zone_nr(f, z); + uint32_t zone_idx = zbd_zone_idx(f, z); bool res = true; if (z->cond == ZBD_ZONE_COND_OFFLINE) @@ -489,8 +490,9 @@ static bool zbd_is_seq_job(struct fio_file *f) if (f->io_size == 0) return false; - zone_idx_b = zbd_zone_idx(f, f->file_offset); - zone_idx_e = zbd_zone_idx(f, f->file_offset + f->io_size - 1); + zone_idx_b = zbd_offset_to_zone_idx(f, f->file_offset); + zone_idx_e = + zbd_offset_to_zone_idx(f, f->file_offset + f->io_size - 1); for (zone_idx = zone_idx_b; zone_idx <= zone_idx_e; zone_idx++) if (get_zone(f, zone_idx)->has_wp) return true; @@ -538,7 +540,7 @@ static bool zbd_zone_align_file_sizes(struct thread_data *td, return false; } - zone_idx = zbd_zone_idx(f, f->file_offset); + zone_idx = zbd_offset_to_zone_idx(f, f->file_offset); z = get_zone(f, zone_idx); if ((f->file_offset != z->start) && (td->o.td_ddir != TD_DDIR_READ)) { @@ -555,7 +557,7 @@ static bool zbd_zone_align_file_sizes(struct thread_data *td, f->file_offset = new_offset; } - zone_idx = zbd_zone_idx(f, f->file_offset + f->io_size); + zone_idx = zbd_offset_to_zone_idx(f, f->file_offset + f->io_size); z = get_zone(f, zone_idx); new_end = z->start; if ((td->o.td_ddir != TD_DDIR_READ) && @@ -1046,8 +1048,9 @@ int zbd_setup_files(struct thread_data *td) assert(zbd); - f->min_zone = zbd_zone_idx(f, f->file_offset); - f->max_zone = zbd_zone_idx(f, f->file_offset + f->io_size); + f->min_zone = zbd_offset_to_zone_idx(f, f->file_offset); + f->max_zone = + zbd_offset_to_zone_idx(f, f->file_offset + f->io_size); /* * When all zones in the I/O range are conventional, io_size @@ -1275,7 +1278,7 @@ static struct fio_zone_info *zbd_convert_to_open_zone(struct thread_data *td, */ zone_idx = zbdi->open_zones[pick_random_zone_idx(f, io_u)]; } else { - zone_idx = zbd_zone_idx(f, io_u->offset); + zone_idx = zbd_offset_to_zone_idx(f, io_u->offset); } if (zone_idx < f->min_zone) zone_idx = f->min_zone; @@ -1597,7 +1600,7 @@ static void zbd_queue_io(struct thread_data *td, struct io_u *io_u, int q, assert(zbd_info); - zone_idx = zbd_zone_idx(f, io_u->offset); + zone_idx = zbd_offset_to_zone_idx(f, io_u->offset); assert(zone_idx < zbd_info->nr_zones); z = get_zone(f, zone_idx); @@ -1655,7 +1658,7 @@ static void zbd_put_io(struct thread_data *td, const struct io_u *io_u) assert(zbd_info); - zone_idx = zbd_zone_idx(f, io_u->offset); + zone_idx = zbd_offset_to_zone_idx(f, io_u->offset); assert(zone_idx < zbd_info->nr_zones); z = get_zone(f, zone_idx); @@ -1711,7 +1714,7 @@ void setup_zbd_zone_mode(struct thread_data *td, struct io_u *io_u) assert(td->o.zone_size); assert(f->zbd_info); - zone_idx = zbd_zone_idx(f, f->last_pos[ddir]); + zone_idx = zbd_offset_to_zone_idx(f, f->last_pos[ddir]); z = get_zone(f, zone_idx); /* @@ -1819,7 +1822,7 @@ enum io_u_action zbd_adjust_block(struct thread_data *td, struct io_u *io_u) assert(is_valid_offset(f, io_u->offset)); assert(io_u->buflen); - zone_idx_b = zbd_zone_idx(f, io_u->offset); + zone_idx_b = zbd_offset_to_zone_idx(f, io_u->offset); zb = get_zone(f, zone_idx_b); orig_zb = zb; @@ -2102,7 +2105,7 @@ int zbd_do_io_u_trim(const struct thread_data *td, struct io_u *io_u) uint32_t zone_idx; int ret; - zone_idx = zbd_zone_idx(f, io_u->offset); + zone_idx = zbd_offset_to_zone_idx(f, io_u->offset); z = get_zone(f, zone_idx); if (!z->has_wp) From 39e06ee779cae55cb7d02eb11a5f12ca7f21a337 Mon Sep 17 00:00:00 2001 From: Damien Le Moal Date: Tue, 14 Dec 2021 10:24:11 +0900 Subject: [PATCH 0017/1097] zbd: rename get_zone() Rename get_zone() to zbd_get_zone() to be consistent with the naming pattern of most zbd functions. No functional changes. Signed-off-by: Damien Le Moal Reviewed-by: Niklas Cassel Link: https://lore.kernel.org/r/20211214012413.464798-11-damien.lemoal@opensource.wdc.com Signed-off-by: Jens Axboe --- zbd.c | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/zbd.c b/zbd.c index 592f7c0319..095a6badf0 100644 --- a/zbd.c +++ b/zbd.c @@ -123,10 +123,10 @@ static inline void zone_unlock(struct fio_zone_info *z) assert(!ret); } -static inline struct fio_zone_info *get_zone(const struct fio_file *f, - unsigned int zone_nr) +static inline struct fio_zone_info *zbd_get_zone(const struct fio_file *f, + unsigned int zone_idx) { - return &f->zbd_info->zone_info[zone_nr]; + return &f->zbd_info->zone_info[zone_idx]; } /** @@ -297,7 +297,7 @@ static void zbd_close_zone(struct thread_data *td, const struct fio_file *f, return; for (ozi = 0; ozi < f->zbd_info->num_open_zones; ozi++) { - if (get_zone(f, f->zbd_info->open_zones[ozi]) == z) + if (zbd_get_zone(f, f->zbd_info->open_zones[ozi]) == z) break; } if (ozi == f->zbd_info->num_open_zones) @@ -494,7 +494,7 @@ static bool zbd_is_seq_job(struct fio_file *f) zone_idx_e = zbd_offset_to_zone_idx(f, f->file_offset + f->io_size - 1); for (zone_idx = zone_idx_b; zone_idx <= zone_idx_e; zone_idx++) - if (get_zone(f, zone_idx)->has_wp) + if (zbd_get_zone(f, zone_idx)->has_wp) return true; return false; @@ -541,7 +541,7 @@ static bool zbd_zone_align_file_sizes(struct thread_data *td, } zone_idx = zbd_offset_to_zone_idx(f, f->file_offset); - z = get_zone(f, zone_idx); + z = zbd_get_zone(f, zone_idx); if ((f->file_offset != z->start) && (td->o.td_ddir != TD_DDIR_READ)) { new_offset = zbd_zone_end(z); @@ -558,7 +558,7 @@ static bool zbd_zone_align_file_sizes(struct thread_data *td, } zone_idx = zbd_offset_to_zone_idx(f, f->file_offset + f->io_size); - z = get_zone(f, zone_idx); + z = zbd_get_zone(f, zone_idx); new_end = z->start; if ((td->o.td_ddir != TD_DDIR_READ) && (f->file_offset + f->io_size != new_end)) { @@ -1156,8 +1156,8 @@ static uint64_t zbd_process_swd(struct thread_data *td, uint64_t swd = 0; uint64_t wp_swd = 0; - zb = get_zone(f, f->min_zone); - ze = get_zone(f, f->max_zone); + zb = zbd_get_zone(f, f->min_zone); + ze = zbd_get_zone(f, f->max_zone); for (z = zb; z < ze; z++) { if (z->has_wp) { zone_lock(td, f, z); @@ -1209,8 +1209,8 @@ void zbd_file_reset(struct thread_data *td, struct fio_file *f) if (!f->zbd_info || !td_write(td)) return; - zb = get_zone(f, f->min_zone); - ze = get_zone(f, f->max_zone); + zb = zbd_get_zone(f, f->min_zone); + ze = zbd_get_zone(f, f->max_zone); swd = zbd_process_swd(td, f, SET_SWD); dprint(FD_ZBD, "%s(%s): swd = %" PRIu64 "\n", @@ -1298,7 +1298,7 @@ static struct fio_zone_info *zbd_convert_to_open_zone(struct thread_data *td, for (;;) { uint32_t tmp_idx; - z = get_zone(f, zone_idx); + z = zbd_get_zone(f, zone_idx); if (z->has_wp) zone_lock(td, f, z); @@ -1404,7 +1404,7 @@ static struct fio_zone_info *zbd_convert_to_open_zone(struct thread_data *td, if (!is_valid_offset(f, z->start)) { /* Wrap-around. */ zone_idx = f->min_zone; - z = get_zone(f, zone_idx); + z = zbd_get_zone(f, zone_idx); } assert(is_valid_offset(f, z->start)); if (!z->has_wp) @@ -1427,7 +1427,7 @@ static struct fio_zone_info *zbd_convert_to_open_zone(struct thread_data *td, pthread_mutex_unlock(&zbdi->mutex); zone_unlock(z); - z = get_zone(f, zone_idx); + z = zbd_get_zone(f, zone_idx); zone_lock(td, f, z); if (z->wp + min_bs <= zbd_zone_capacity_end(z)) @@ -1522,7 +1522,7 @@ zbd_find_zone(struct thread_data *td, struct io_u *io_u, uint64_t min_bytes, { struct fio_file *f = io_u->file; struct fio_zone_info *z1, *z2; - const struct fio_zone_info *const zf = get_zone(f, f->min_zone); + const struct fio_zone_info *const zf = zbd_get_zone(f, f->min_zone); /* * Skip to the next non-empty zone in case of sequential I/O and to @@ -1602,7 +1602,7 @@ static void zbd_queue_io(struct thread_data *td, struct io_u *io_u, int q, zone_idx = zbd_offset_to_zone_idx(f, io_u->offset); assert(zone_idx < zbd_info->nr_zones); - z = get_zone(f, zone_idx); + z = zbd_get_zone(f, zone_idx); assert(z->has_wp); @@ -1660,7 +1660,7 @@ static void zbd_put_io(struct thread_data *td, const struct io_u *io_u) zone_idx = zbd_offset_to_zone_idx(f, io_u->offset); assert(zone_idx < zbd_info->nr_zones); - z = get_zone(f, zone_idx); + z = zbd_get_zone(f, zone_idx); assert(z->has_wp); @@ -1715,7 +1715,7 @@ void setup_zbd_zone_mode(struct thread_data *td, struct io_u *io_u) assert(f->zbd_info); zone_idx = zbd_offset_to_zone_idx(f, f->last_pos[ddir]); - z = get_zone(f, zone_idx); + z = zbd_get_zone(f, zone_idx); /* * When the zone capacity is smaller than the zone size and the I/O is @@ -1823,7 +1823,7 @@ enum io_u_action zbd_adjust_block(struct thread_data *td, struct io_u *io_u) assert(io_u->buflen); zone_idx_b = zbd_offset_to_zone_idx(f, io_u->offset); - zb = get_zone(f, zone_idx_b); + zb = zbd_get_zone(f, zone_idx_b); orig_zb = zb; if (!zb->has_wp) { @@ -1886,7 +1886,7 @@ enum io_u_action zbd_adjust_block(struct thread_data *td, struct io_u *io_u) if (range < min_bs || ((!td_random(td)) && (io_u->offset + min_bs > zb->wp))) { zone_unlock(zb); - zl = get_zone(f, f->max_zone); + zl = zbd_get_zone(f, f->max_zone); zb = zbd_find_zone(td, io_u, min_bs, zb, zl); if (!zb) { dprint(FD_ZBD, @@ -2030,7 +2030,7 @@ enum io_u_action zbd_adjust_block(struct thread_data *td, struct io_u *io_u) /* Find out a non-empty zone to trim */ zone_unlock(zb); - zl = get_zone(f, f->max_zone); + zl = zbd_get_zone(f, f->max_zone); zb = zbd_find_zone(td, io_u, 1, zb, zl); if (zb) { io_u->offset = zb->start; @@ -2106,7 +2106,7 @@ int zbd_do_io_u_trim(const struct thread_data *td, struct io_u *io_u) int ret; zone_idx = zbd_offset_to_zone_idx(f, io_u->offset); - z = get_zone(f, zone_idx); + z = zbd_get_zone(f, zone_idx); if (!z->has_wp) return 0; From 53aa61719b906bdf8e058c94ca93e537b90806b1 Mon Sep 17 00:00:00 2001 From: Damien Le Moal Date: Tue, 14 Dec 2021 10:24:12 +0900 Subject: [PATCH 0018/1097] zbd: introduce zbd_offset_to_zone() helper Introduce the helper function zbd_offset_to_zone() to get a zone structure using a file offset. In many functions, this replaces the two line code pattern: zone_idx = zbd_offset_to_zone_idx(f, offset); z = zbd_get_zone(f, zone_idx); with a single line of code: z = zbd_offset_to_zone(f, offset); Signed-off-by: Damien Le Moal Reviewed-by: Niklas Cassel Link: https://lore.kernel.org/r/20211214012413.464798-12-damien.lemoal@opensource.wdc.com Signed-off-by: Jens Axboe --- zbd.c | 44 ++++++++++++++++---------------------------- 1 file changed, 16 insertions(+), 28 deletions(-) diff --git a/zbd.c b/zbd.c index 095a6badf0..b1fd6b4bb0 100644 --- a/zbd.c +++ b/zbd.c @@ -129,6 +129,12 @@ static inline struct fio_zone_info *zbd_get_zone(const struct fio_file *f, return &f->zbd_info->zone_info[zone_idx]; } +static inline struct fio_zone_info * +zbd_offset_to_zone(const struct fio_file *f, uint64_t offset) +{ + return zbd_get_zone(f, zbd_offset_to_zone_idx(f, offset)); +} + /** * zbd_get_zoned_model - Get a device zoned model * @td: FIO thread data @@ -510,7 +516,6 @@ static bool zbd_zone_align_file_sizes(struct thread_data *td, { const struct fio_zone_info *z; uint64_t new_offset, new_end; - uint32_t zone_idx; if (!f->zbd_info) return true; @@ -540,8 +545,7 @@ static bool zbd_zone_align_file_sizes(struct thread_data *td, return false; } - zone_idx = zbd_offset_to_zone_idx(f, f->file_offset); - z = zbd_get_zone(f, zone_idx); + z = zbd_offset_to_zone(f, f->file_offset); if ((f->file_offset != z->start) && (td->o.td_ddir != TD_DDIR_READ)) { new_offset = zbd_zone_end(z); @@ -557,8 +561,7 @@ static bool zbd_zone_align_file_sizes(struct thread_data *td, f->file_offset = new_offset; } - zone_idx = zbd_offset_to_zone_idx(f, f->file_offset + f->io_size); - z = zbd_get_zone(f, zone_idx); + z = zbd_offset_to_zone(f, f->file_offset + f->io_size); new_end = z->start; if ((td->o.td_ddir != TD_DDIR_READ) && (f->file_offset + f->io_size != new_end)) { @@ -1595,15 +1598,11 @@ static void zbd_queue_io(struct thread_data *td, struct io_u *io_u, int q, const struct fio_file *f = io_u->file; struct zoned_block_device_info *zbd_info = f->zbd_info; struct fio_zone_info *z; - uint32_t zone_idx; uint64_t zone_end; assert(zbd_info); - zone_idx = zbd_offset_to_zone_idx(f, io_u->offset); - assert(zone_idx < zbd_info->nr_zones); - z = zbd_get_zone(f, zone_idx); - + z = zbd_offset_to_zone(f, io_u->offset); assert(z->has_wp); if (!success) @@ -1611,7 +1610,7 @@ static void zbd_queue_io(struct thread_data *td, struct io_u *io_u, int q, dprint(FD_ZBD, "%s: queued I/O (%lld, %llu) for zone %u\n", - f->file_name, io_u->offset, io_u->buflen, zone_idx); + f->file_name, io_u->offset, io_u->buflen, zbd_zone_idx(f, z)); switch (io_u->ddir) { case DDIR_WRITE: @@ -1654,19 +1653,15 @@ static void zbd_put_io(struct thread_data *td, const struct io_u *io_u) const struct fio_file *f = io_u->file; struct zoned_block_device_info *zbd_info = f->zbd_info; struct fio_zone_info *z; - uint32_t zone_idx; assert(zbd_info); - zone_idx = zbd_offset_to_zone_idx(f, io_u->offset); - assert(zone_idx < zbd_info->nr_zones); - z = zbd_get_zone(f, zone_idx); - + z = zbd_offset_to_zone(f, io_u->offset); assert(z->has_wp); dprint(FD_ZBD, "%s: terminate I/O (%lld, %llu) for zone %u\n", - f->file_name, io_u->offset, io_u->buflen, zone_idx); + f->file_name, io_u->offset, io_u->buflen, zbd_zone_idx(f, z)); zbd_end_zone_io(td, io_u, z); @@ -1708,14 +1703,12 @@ void setup_zbd_zone_mode(struct thread_data *td, struct io_u *io_u) struct fio_file *f = io_u->file; enum fio_ddir ddir = io_u->ddir; struct fio_zone_info *z; - uint32_t zone_idx; assert(td->o.zone_mode == ZONE_MODE_ZBD); assert(td->o.zone_size); assert(f->zbd_info); - zone_idx = zbd_offset_to_zone_idx(f, f->last_pos[ddir]); - z = zbd_get_zone(f, zone_idx); + z = zbd_offset_to_zone(f, f->last_pos[ddir]); /* * When the zone capacity is smaller than the zone size and the I/O is @@ -1729,7 +1722,7 @@ void setup_zbd_zone_mode(struct thread_data *td, struct io_u *io_u) "%s: Jump from zone capacity limit to zone end:" " (%"PRIu64" -> %"PRIu64") for zone %u (%"PRIu64")\n", f->file_name, f->last_pos[ddir], - zbd_zone_end(z), zone_idx, z->capacity); + zbd_zone_end(z), zbd_zone_idx(f, z), z->capacity); td->io_skip_bytes += zbd_zone_end(z) - f->last_pos[ddir]; f->last_pos[ddir] = zbd_zone_end(z); } @@ -1810,7 +1803,6 @@ enum io_u_action zbd_adjust_block(struct thread_data *td, struct io_u *io_u) { struct fio_file *f = io_u->file; struct zoned_block_device_info *zbdi = f->zbd_info; - uint32_t zone_idx_b; struct fio_zone_info *zb, *zl, *orig_zb; uint32_t orig_len = io_u->buflen; uint64_t min_bs = td->o.min_bs[io_u->ddir]; @@ -1822,8 +1814,7 @@ enum io_u_action zbd_adjust_block(struct thread_data *td, struct io_u *io_u) assert(is_valid_offset(f, io_u->offset)); assert(io_u->buflen); - zone_idx_b = zbd_offset_to_zone_idx(f, io_u->offset); - zb = zbd_get_zone(f, zone_idx_b); + zb = zbd_offset_to_zone(f, io_u->offset); orig_zb = zb; if (!zb->has_wp) { @@ -2102,12 +2093,9 @@ int zbd_do_io_u_trim(const struct thread_data *td, struct io_u *io_u) { struct fio_file *f = io_u->file; struct fio_zone_info *z; - uint32_t zone_idx; int ret; - zone_idx = zbd_offset_to_zone_idx(f, io_u->offset); - z = zbd_get_zone(f, zone_idx); - + z = zbd_offset_to_zone(f, io_u->offset); if (!z->has_wp) return 0; From 9ffe433d729101a34d9709030d7d4dd2444347ef Mon Sep 17 00:00:00 2001 From: Shin'ichiro Kawasaki Date: Tue, 14 Dec 2021 10:24:13 +0900 Subject: [PATCH 0019/1097] t/zbd: Avoid inappropriate blkzone command call in zone_cap_bs When the script test-zbd-support is run for regular block devices or SG nodes, blkzone command shall not be called. However, zone_cap_bs() helper function calls the command regardless of the zone model or device type, and results in error messages such as "unable to determine zone size" or "not a block device". Avoid the command call by returning the zone size argument passed to this function when the test device is a regular block device or a SG node. Fixes: 1ae82d673cf5 ("t/zbd: Align block size to zone capacity") Signed-off-by: Shin'ichiro Kawasaki Signed-off-by: Damien Le Moal Reviewed-by: Niklas Cassel Link: https://lore.kernel.org/r/20211214012413.464798-13-damien.lemoal@opensource.wdc.com Signed-off-by: Jens Axboe --- t/zbd/functions | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/t/zbd/functions b/t/zbd/functions index e4e248b9ff..7cff18fd18 100644 --- a/t/zbd/functions +++ b/t/zbd/functions @@ -72,9 +72,11 @@ zone_cap_bs() { local sed_str='s/.*len \([0-9A-Za-z]*\), cap \([0-9A-Za-z]*\).*/\1 \2/p' local cap bs="$zone_size" - # When blkzone is not available or blkzone does not report capacity, + # When blkzone command is neither available nor relevant to the + # test device, or when blkzone command does not report capacity, # assume that zone capacity is same as zone size for all zones. - if [ -z "${blkzone}" ] || ! blkzone_reports_capacity "${dev}"; then + if [ -z "${blkzone}" ] || [ -z "$is_zbd" ] || [ -c "$dev" ] || + ! blkzone_reports_capacity "${dev}"; then echo "$zone_size" return fi From d5b3cfd4064d5414c1bce4acc65c181db86a8634 Mon Sep 17 00:00:00 2001 From: aggieNick02 Date: Tue, 14 Dec 2021 13:56:58 -0600 Subject: [PATCH 0020/1097] Support for alternate epochs in fio log files Add options log_alternate_epoch and log_alternate_epoch_clock_id. This is similar to the log_unix_epoch option. This resolves the issue raised in Issue #1314 log_alternate_epoch, if true, causes log files to use the same epoch used used by the clock_id parameter to the unix clock_gettime function, where clock_id is specified by the log_alternate_epoch_clock_id option. This is particularly useful as it allows us to specify a clock id like CLOCK_MONOTONIC_RAW, which is natural for synchronizing log files between processes. The current log_unix_epoch is problematic for that purpose because that clock is not monotonic or continuous. It turns out that log_unix_epoch is actually equivalent to log_alternate_epoch with log_alternate_epoch_clock_id set to CLOCK_REALTIME=0. Since this is the default value of the log_alternate_epoch_clock_id option anyways, we treat log_alternate_epoch and log_unix_epoch as equivalent in functionality, retaining the latter to avoid breaking existing clients. Signed-off-by: Nick Neumann --- HOWTO | 13 +++++++++++++ backend.c | 2 +- cconv.c | 4 ++++ fio.1 | 11 +++++++++++ fio.h | 2 +- fio_time.h | 2 +- libfio.c | 2 +- options.c | 18 ++++++++++++++++++ os/windows/posix.c | 11 ++++++++--- rate-submit.c | 2 +- stat.c | 2 +- thread_options.h | 4 ++++ time.c | 12 ++++++------ 13 files changed, 70 insertions(+), 15 deletions(-) diff --git a/HOWTO b/HOWTO index 8c9e41356b..99fb575126 100644 --- a/HOWTO +++ b/HOWTO @@ -3624,6 +3624,19 @@ Measurements and reporting write_type_log for each log type, instead of the default zero-based timestamps. +.. option:: log_alternate_epoch=bool + + If set, fio will log timestamps based on the epoch used by the clock specified + in the log_alternate_epoch_clock_id option, to the log files produced by + enabling write_type_log for each log type, instead of the default zero-based + timestamps. + +.. option:: log_alternate_epoch_clock_id=int + + Specifies the clock_id to be used by clock_gettime to obtain the alternate epoch + if either log_unix_epoch or log_alternate_epoch are true. Otherwise has no + effect. Default value is 0, or CLOCK_REALTIME. + .. option:: block_error_percentiles=bool If set, record errors in trim block-sized units from writes and trims and diff --git a/backend.c b/backend.c index c167f90862..151a561555 100644 --- a/backend.c +++ b/backend.c @@ -1828,7 +1828,7 @@ static void *thread_main(void *data) if (rate_submit_init(td, sk_out)) goto err; - set_epoch_time(td, o->log_unix_epoch); + set_epoch_time(td, o->log_unix_epoch | o->log_alternate_epoch, o->log_alternate_epoch_clock_id); fio_getrusage(&td->ru_start); memcpy(&td->bw_sample_time, &td->epoch, sizeof(td->epoch)); memcpy(&td->iops_sample_time, &td->epoch, sizeof(td->epoch)); diff --git a/cconv.c b/cconv.c index 4f8d27eb2d..62d02e366e 100644 --- a/cconv.c +++ b/cconv.c @@ -197,6 +197,8 @@ void convert_thread_options_to_cpu(struct thread_options *o, o->log_gz = le32_to_cpu(top->log_gz); o->log_gz_store = le32_to_cpu(top->log_gz_store); o->log_unix_epoch = le32_to_cpu(top->log_unix_epoch); + o->log_alternate_epoch = le32_to_cpu(top->log_alternate_epoch); + o->log_alternate_epoch_clock_id = le32_to_cpu(top->log_alternate_epoch_clock_id); o->norandommap = le32_to_cpu(top->norandommap); o->softrandommap = le32_to_cpu(top->softrandommap); o->bs_unaligned = le32_to_cpu(top->bs_unaligned); @@ -425,6 +427,8 @@ void convert_thread_options_to_net(struct thread_options_pack *top, top->log_gz = cpu_to_le32(o->log_gz); top->log_gz_store = cpu_to_le32(o->log_gz_store); top->log_unix_epoch = cpu_to_le32(o->log_unix_epoch); + top->log_alternate_epoch = cpu_to_le32(o->log_alternate_epoch); + top->log_alternate_epoch_clock_id = cpu_to_le32(o->log_alternate_epoch_clock_id); top->norandommap = cpu_to_le32(o->norandommap); top->softrandommap = cpu_to_le32(o->softrandommap); top->bs_unaligned = cpu_to_le32(o->bs_unaligned); diff --git a/fio.1 b/fio.1 index a3ebb67d36..74f1a6ea07 100644 --- a/fio.1 +++ b/fio.1 @@ -3323,6 +3323,17 @@ If set, fio will log Unix timestamps to the log files produced by enabling write_type_log for each log type, instead of the default zero-based timestamps. .TP +.BI log_alternate_epoch \fR=\fPbool +If set, fio will log timestamps based on the epoch used by the clock specified +in the \fBlog_alternate_epoch_clock_id\fR option, to the log files produced by +enabling write_type_log for each log type, instead of the default zero-based +timestamps. +.TP +.BI log_alternate_epoch_clock_id \fR=\fPint +Specifies the clock_id to be used by clock_gettime to obtain the alternate epoch +if either \fBBlog_unix_epoch\fR or \fBlog_alternate_epoch\fR are true. Otherwise has no +effect. Default value is 0, or CLOCK_REALTIME. +.TP .BI block_error_percentiles \fR=\fPbool If set, record errors in trim block-sized units from writes and trims and output a histogram of how many trims it took to get to errors, and what kind diff --git a/fio.h b/fio.h index 6bb21ebb2a..e9cdba94a6 100644 --- a/fio.h +++ b/fio.h @@ -380,7 +380,7 @@ struct thread_data { struct timespec start; /* start of this loop */ struct timespec epoch; /* time job was started */ - unsigned long long unix_epoch; /* Time job was started, unix epoch based. */ + unsigned long long alternate_epoch; /* Time job was started, clock_gettime's clock_id epoch based. */ struct timespec last_issue; long time_offset; struct timespec ts_cache; diff --git a/fio_time.h b/fio_time.h index b3bbd4c011..59f25d82a5 100644 --- a/fio_time.h +++ b/fio_time.h @@ -30,6 +30,6 @@ extern bool ramp_time_over(struct thread_data *); extern bool in_ramp_time(struct thread_data *); extern void fio_time_init(void); extern void timespec_add_msec(struct timespec *, unsigned int); -extern void set_epoch_time(struct thread_data *, int); +extern void set_epoch_time(struct thread_data *, int, int); #endif diff --git a/libfio.c b/libfio.c index 198eaf2eb7..01fa74529f 100644 --- a/libfio.c +++ b/libfio.c @@ -142,7 +142,7 @@ void reset_all_stats(struct thread_data *td) td->ts.runtime[i] = 0; } - set_epoch_time(td, td->o.log_unix_epoch); + set_epoch_time(td, td->o.log_unix_epoch | td->o.log_alternate_epoch, td->o.log_alternate_epoch_clock_id); memcpy(&td->start, &td->epoch, sizeof(td->epoch)); memcpy(&td->iops_sample_time, &td->epoch, sizeof(td->epoch)); memcpy(&td->bw_sample_time, &td->epoch, sizeof(td->epoch)); diff --git a/options.c b/options.c index 102bcf5661..0d7dc07973 100644 --- a/options.c +++ b/options.c @@ -4392,6 +4392,24 @@ struct fio_option fio_options[FIO_MAX_OPTS] = { .category = FIO_OPT_C_LOG, .group = FIO_OPT_G_INVALID, }, + { + .name = "log_alternate_epoch", + .lname = "Log epoch alternate", + .type = FIO_OPT_BOOL, + .off1 = offsetof(struct thread_options, log_alternate_epoch), + .help = "Use alternate epoch time in log files. Uses the same epoch as that is used by clock_gettime with specified log_alternate_epoch_clock_id.", + .category = FIO_OPT_C_LOG, + .group = FIO_OPT_G_INVALID, + }, + { + .name = "log_alternate_epoch_clock_id", + .lname = "Log alternate epoch clock_id", + .type = FIO_OPT_INT, + .off1 = offsetof(struct thread_options, log_alternate_epoch_clock_id), + .help = "If log_alternate_epoch or log_unix_epoch is true, this option specifies the clock_id from clock_gettime whose epoch should be used. If neither of those is true, this option has no effect. Default value is 0, or CLOCK_REALTIME", + .category = FIO_OPT_C_LOG, + .group = FIO_OPT_G_INVALID, + }, { .name = "block_error_percentiles", .lname = "Block error percentiles", diff --git a/os/windows/posix.c b/os/windows/posix.c index 09c2e4a785..f1df2d76e0 100644 --- a/os/windows/posix.c +++ b/os/windows/posix.c @@ -537,16 +537,21 @@ int fcntl(int fildes, int cmd, ...) return 0; } +#ifndef CLOCK_MONOTONIC_RAW +#define CLOCK_MONOTONIC_RAW 4 +#endif + /* * Get the value of a local clock source. - * This implementation supports 2 clocks: CLOCK_MONOTONIC provides high-accuracy - * relative time, while CLOCK_REALTIME provides a low-accuracy wall time. + * This implementation supports 3 clocks: CLOCK_MONOTONIC/CLOCK_MONOTONIC_RAW + * provide high-accuracy relative time, while CLOCK_REALTIME provides a + * low-accuracy wall time. */ int clock_gettime(clockid_t clock_id, struct timespec *tp) { int rc = 0; - if (clock_id == CLOCK_MONOTONIC) { + if (clock_id == CLOCK_MONOTONIC || clock_id == CLOCK_MONOTONIC_RAW) { static LARGE_INTEGER freq = {{0,0}}; LARGE_INTEGER counts; uint64_t t; diff --git a/rate-submit.c b/rate-submit.c index 13dbe7a2e9..13a0d706e0 100644 --- a/rate-submit.c +++ b/rate-submit.c @@ -173,7 +173,7 @@ static int io_workqueue_init_worker_fn(struct submit_worker *sw) if (td->io_ops->post_init && td->io_ops->post_init(td)) goto err_io_init; - set_epoch_time(td, td->o.log_unix_epoch); + set_epoch_time(td, td->o.log_unix_epoch | td->o.log_alternate_epoch, td->o.log_alternate_epoch_clock_id); fio_getrusage(&td->ru_start); clear_io_state(td, 1); diff --git a/stat.c b/stat.c index 7e84058d9b..98f30107d1 100644 --- a/stat.c +++ b/stat.c @@ -2854,7 +2854,7 @@ static void __add_log_sample(struct io_log *iolog, union io_sample_data data, s = get_sample(iolog, cur_log, cur_log->nr_samples); s->data = data; - s->time = t + (iolog->td ? iolog->td->unix_epoch : 0); + s->time = t + (iolog->td ? iolog->td->alternate_epoch : 0); io_sample_set_ddir(iolog, s, ddir); s->bs = bs; s->priority = priority; diff --git a/thread_options.h b/thread_options.h index 8f4c8a5996..450e7ddeee 100644 --- a/thread_options.h +++ b/thread_options.h @@ -166,6 +166,8 @@ struct thread_options { unsigned int log_gz; unsigned int log_gz_store; unsigned int log_unix_epoch; + unsigned int log_alternate_epoch; + unsigned int log_alternate_epoch_clock_id; unsigned int norandommap; unsigned int softrandommap; unsigned int bs_unaligned; @@ -482,6 +484,8 @@ struct thread_options_pack { uint32_t log_gz; uint32_t log_gz_store; uint32_t log_unix_epoch; + uint32_t log_alternate_epoch; + uint32_t log_alternate_epoch_clock_id; uint32_t norandommap; uint32_t softrandommap; uint32_t bs_unaligned; diff --git a/time.c b/time.c index cd0e2a8914..5c4d6de039 100644 --- a/time.c +++ b/time.c @@ -172,14 +172,14 @@ void set_genesis_time(void) fio_gettime(&genesis, NULL); } -void set_epoch_time(struct thread_data *td, int log_unix_epoch) +void set_epoch_time(struct thread_data *td, int log_alternate_epoch, clockid_t clock_id) { fio_gettime(&td->epoch, NULL); - if (log_unix_epoch) { - struct timeval tv; - gettimeofday(&tv, NULL); - td->unix_epoch = (unsigned long long)(tv.tv_sec) * 1000 + - (unsigned long long)(tv.tv_usec) / 1000; + if (log_alternate_epoch) { + struct timespec ts; + clock_gettime(clock_id, &ts); + td->alternate_epoch = (unsigned long long)(ts.tv_sec) * 1000 + + (unsigned long long)(ts.tv_nsec) / 1000000; } } From e86afa536b175a90546e20d7d19f2418ee1bca78 Mon Sep 17 00:00:00 2001 From: Niklas Cassel Date: Wed, 15 Dec 2021 12:26:04 +0000 Subject: [PATCH 0021/1097] stat: sum sync_stat before reassigning bool first Currently, sum_stat(&dst->sync_stat, &src->sync_stat, first, false) is called after the summing the stats on a per ddir level. The for-loop that sums the stats on a per ddir level will reassign bool first to false when unified_rw_rep is used. This means that the call to sum_stat() for sync_stat will be called with first == false, even when it is the first sync_stat being summed, leading to incorrect sync_stat calculations when unified_rw_rep is used. In order to ensure that sync_stat is not incorrectly affected by the reassignment of first, move the sync_stat summing before the for-loop. Signed-off-by: Niklas Cassel Link: https://lore.kernel.org/r/20211215122557.95600-1-Niklas.Cassel@wdc.com Signed-off-by: Jens Axboe --- stat.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/stat.c b/stat.c index 7e84058d9b..ec44c79e01 100644 --- a/stat.c +++ b/stat.c @@ -2130,6 +2130,8 @@ void sum_thread_stats(struct thread_stat *dst, struct thread_stat *src, { int k, l, m; + sum_stat(&dst->sync_stat, &src->sync_stat, first, false); + for (l = 0; l < DDIR_RWDIR_CNT; l++) { if (!(dst->unified_rw_rep == UNIFIED_MIXED)) { sum_stat(&dst->clat_stat[l], &src->clat_stat[l], first, false); @@ -2166,7 +2168,6 @@ void sum_thread_stats(struct thread_stat *dst, struct thread_stat *src, } } - sum_stat(&dst->sync_stat, &src->sync_stat, first, false); dst->usr_time += src->usr_time; dst->sys_time += src->sys_time; dst->ctx += src->ctx; From 4fd07b23121e45fa4e7e434d23f3257c8a0ef588 Mon Sep 17 00:00:00 2001 From: james rizzo Date: Thu, 16 Dec 2021 13:13:51 -0700 Subject: [PATCH 0022/1097] Avoid client calls to recv() without prior poll() Signed-off-by: james rizzo --- client.c | 5 +++-- server.c | 24 +++++++++++++++++++++++- server.h | 1 + 3 files changed, 27 insertions(+), 3 deletions(-) diff --git a/client.c b/client.c index 8b230617f7..0bcee29b57 100644 --- a/client.c +++ b/client.c @@ -284,9 +284,10 @@ static int fio_client_dec_jobs_eta(struct client_eta *eta, client_eta_op eta_fn) static void fio_drain_client_text(struct fio_client *client) { do { - struct fio_net_cmd *cmd; + struct fio_net_cmd *cmd = NULL; - cmd = fio_net_recv_cmd(client->fd, false); + if (fio_server_poll_fd(client->fd, POLLIN, 0)) + cmd = fio_net_recv_cmd(client->fd, false); if (!cmd) break; diff --git a/server.c b/server.c index 90c52e01ac..1f627e8f11 100644 --- a/server.c +++ b/server.c @@ -250,6 +250,27 @@ static int fio_send_data(int sk, const void *p, unsigned int len) return fio_sendv_data(sk, &iov, 1); } +bool fio_server_poll_fd(int fd, short events, int timeout) { + struct pollfd pfd = { + .fd = fd, + .events = events, + }; + int ret; + + ret = poll(&pfd, 1, timeout); + if (ret < 0) { + if (errno == EINTR) + return false; + log_err("fio: poll: %s\n", strerror(errno)); + return false; + } else if (!ret) { + return false; + } + if (pfd.revents & events) + return true; + return false; +} + static int fio_recv_data(int sk, void *buf, unsigned int len, bool wait) { int flags; @@ -1238,7 +1259,8 @@ static int handle_connection(struct sk_out *sk_out) if (ret < 0) break; - cmd = fio_net_recv_cmd(sk_out->sk, true); + if (pfd.revents & POLLIN) + cmd = fio_net_recv_cmd(sk_out->sk, true); if (!cmd) { ret = -1; break; diff --git a/server.h b/server.h index 25b6bbdc25..75600df209 100644 --- a/server.h +++ b/server.h @@ -222,6 +222,7 @@ extern void fio_server_send_gs(struct group_run_stats *); extern void fio_server_send_du(void); extern void fio_server_send_job_options(struct flist_head *, unsigned int); extern int fio_server_get_verify_state(const char *, int, void **); +extern bool fio_server_poll_fd(int fd, short events, int timeout); extern struct fio_net_cmd *fio_net_recv_cmd(int sk, bool wait); From f8fef4c68889f8a7ae7d556d9a747b60c71b1b43 Mon Sep 17 00:00:00 2001 From: james rizzo Date: Thu, 16 Dec 2021 15:08:50 -0700 Subject: [PATCH 0023/1097] Add Windows support for --server. This required working around two calls to fork() The first fork() was in accept_loop() where it creates a new process to work on handle_connection(). This change set uses Window's CreateProcess() in place of fork(). In order to support this, it duplicates the socket connection via WSADuplicateSocket() and passes the resulting WSAPROTOCOL_INFO to the process via a named pipe. The pipe name is then passed to the process via a new command line argument(--server-internal). The second fork() was in handle_run_cmd() where it creates a new process to work on fio_backend_thread(). This change set runs fio_backend_thread() as a thread via pthread_create() instead of a forked process. There is also some supporting work in the monitoring of spawned processes/threads in fio_server_check_conns() and fio_server_check_jobs(). Signed-off-by: james rizzo --- init.c | 13 +++ os/os-windows.h | 2 + os/windows/posix.c | 171 +++++++++++++++++++++++++++++++ server.c | 245 ++++++++++++++++++++++++++++++++++++++++++--- server.h | 4 + 5 files changed, 421 insertions(+), 14 deletions(-) diff --git a/init.c b/init.c index 5f069d9a5b..8a6066872a 100644 --- a/init.c +++ b/init.c @@ -224,6 +224,13 @@ static struct option l_opts[FIO_NR_OPTIONS] = { .has_arg = optional_argument, .val = 'S', }, +#ifdef WIN32 + { + .name = (char *) "server-internal", + .has_arg = required_argument, + .val = 'N', + }, +#endif { .name = (char *) "daemonize", .has_arg = required_argument, .val = 'D', @@ -2789,6 +2796,12 @@ int parse_cmd_line(int argc, char *argv[], int client_type) exit_val = 1; #endif break; +#ifdef WIN32 + case 'N': + did_arg = true; + fio_server_internal_set(optarg); + break; +#endif case 'D': if (pid_file) free(pid_file); diff --git a/os/os-windows.h b/os/os-windows.h index 59da9dba1a..510b8143db 100644 --- a/os/os-windows.h +++ b/os/os-windows.h @@ -110,6 +110,8 @@ int nanosleep(const struct timespec *rqtp, struct timespec *rmtp); ssize_t pread(int fildes, void *buf, size_t nbyte, off_t offset); ssize_t pwrite(int fildes, const void *buf, size_t nbyte, off_t offset); +HANDLE windows_handle_connection(HANDLE hjob, int sk); +HANDLE windows_create_job(void); static inline int blockdev_size(struct fio_file *f, unsigned long long *bytes) { diff --git a/os/windows/posix.c b/os/windows/posix.c index 09c2e4a785..0978bcf6f4 100644 --- a/os/windows/posix.c +++ b/os/windows/posix.c @@ -1026,3 +1026,174 @@ in_addr_t inet_network(const char *cp) hbo = ((nbo & 0xFF) << 24) + ((nbo & 0xFF00) << 8) + ((nbo & 0xFF0000) >> 8) + ((nbo & 0xFF000000) >> 24); return hbo; } + +static HANDLE create_named_pipe(char *pipe_name, int wait_connect_time) +{ + HANDLE hpipe; + + hpipe = CreateNamedPipe ( + pipe_name, + PIPE_ACCESS_DUPLEX, + PIPE_WAIT | PIPE_TYPE_BYTE, + 1, 0, 0, wait_connect_time, NULL); + + if (hpipe == INVALID_HANDLE_VALUE) { + log_err("ConnectNamedPipe failed (%lu).\n", GetLastError()); + return INVALID_HANDLE_VALUE; + } + + if (!ConnectNamedPipe(hpipe, NULL)) { + log_err("ConnectNamedPipe failed (%lu).\n", GetLastError()); + CloseHandle(hpipe); + return INVALID_HANDLE_VALUE; + } + + return hpipe; +} + +static BOOL windows_create_process(PROCESS_INFORMATION *pi, const char *args, HANDLE *hjob) +{ + LPSTR this_cmd_line = GetCommandLine(); + LPSTR new_process_cmd_line = malloc((strlen(this_cmd_line)+strlen(args)) * sizeof(char *)); + STARTUPINFO si = {0}; + DWORD flags = 0; + + strcpy(new_process_cmd_line, this_cmd_line); + strcat(new_process_cmd_line, args); + + si.cb = sizeof(si); + memset(pi, 0, sizeof(*pi)); + + if ((hjob != NULL) && (*hjob != INVALID_HANDLE_VALUE)) + flags = CREATE_SUSPENDED | CREATE_BREAKAWAY_FROM_JOB; + + flags |= CREATE_NEW_CONSOLE; + + if( !CreateProcess( NULL, + new_process_cmd_line, + NULL, /* Process handle not inherited */ + NULL, /* Thread handle not inherited */ + TRUE, /* no handle inheritance */ + flags, + NULL, /* Use parent's environment block */ + NULL, /* Use parent's starting directory */ + &si, + pi ) + ) + { + log_err("CreateProcess failed (%lu).\n", GetLastError() ); + free(new_process_cmd_line); + return 1; + } + if ((hjob != NULL) && (*hjob != INVALID_HANDLE_VALUE)) { + BOOL ret = AssignProcessToJobObject(*hjob, pi->hProcess); + if (!ret) { + log_err("AssignProcessToJobObject failed (%lu).\n", GetLastError() ); + return 1; + } + + ResumeThread(pi->hThread); + } + + free(new_process_cmd_line); + return 0; +} + +HANDLE windows_create_job(void) +{ + JOBOBJECT_EXTENDED_LIMIT_INFORMATION jeli = { 0 }; + BOOL success; + HANDLE hjob = CreateJobObject(NULL, NULL); + + jeli.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; + success = SetInformationJobObject(hjob, JobObjectExtendedLimitInformation, &jeli, sizeof(jeli)); + if ( success == 0 ) { + log_err( "SetInformationJobObject failed: error %lu\n", GetLastError() ); + return INVALID_HANDLE_VALUE; + } + return hjob; +} + +/* wait for a child process to either exit or connect to a child */ +static bool monitor_process_till_connect(PROCESS_INFORMATION *pi, HANDLE *hpipe) +{ + bool connected = FALSE; + bool process_alive = TRUE; + char buffer[32] = {0}; + DWORD bytes_read; + + do { + DWORD exit_code; + GetExitCodeProcess(pi->hProcess, &exit_code); + if (exit_code != STILL_ACTIVE) { + dprint(FD_PROCESS, "process %u exited %d\n", GetProcessId(pi->hProcess), exit_code); + break; + } + + memset(buffer, 0, sizeof(buffer)); + ReadFile(*hpipe, &buffer, sizeof(buffer) - 1, &bytes_read, NULL); + if (bytes_read && strstr(buffer, "connected")) { + dprint(FD_PROCESS, "process %u connected to client\n", GetProcessId(pi->hProcess)); + connected = TRUE; + } + usleep(10*1000); + } while (process_alive && !connected); + return connected; +} + +/*create a process with --server-internal to emulate fork() */ +HANDLE windows_handle_connection(HANDLE hjob, int sk) +{ + char pipe_name[64] = "\\\\.\\pipe\\fiointernal-"; + char args[128] = " --server-internal="; + PROCESS_INFORMATION pi; + HANDLE hpipe = INVALID_HANDLE_VALUE; + WSAPROTOCOL_INFO protocol_info; + HANDLE ret; + + sprintf(pipe_name+strlen(pipe_name), "%d", GetCurrentProcessId()); + sprintf(args+strlen(args), "%s", pipe_name); + + if (windows_create_process(&pi, args, &hjob) != 0) + return INVALID_HANDLE_VALUE; + else + ret = pi.hProcess; + + /* duplicate socket and write the protocol_info to pipe so child can + * duplicate the communciation socket */ + if (WSADuplicateSocket(sk, GetProcessId(pi.hProcess), &protocol_info)) { + log_err("WSADuplicateSocket failed (%lu).\n", GetLastError()); + ret = INVALID_HANDLE_VALUE; + goto cleanup; + } + + /* make a pipe with a unique name based upon processid */ + hpipe = create_named_pipe(pipe_name, 1000); + if (hpipe == INVALID_HANDLE_VALUE) { + ret = INVALID_HANDLE_VALUE; + goto cleanup; + } + + if (!WriteFile(hpipe, &protocol_info, sizeof(protocol_info), NULL, NULL)) { + log_err("WriteFile failed (%lu).\n", GetLastError()); + ret = INVALID_HANDLE_VALUE; + goto cleanup; + } + + dprint(FD_PROCESS, "process %d created child process %u\n", GetCurrentProcessId(), GetProcessId(pi.hProcess)); + + /* monitor the process until it either exits or connects. This level + * doesnt care which of those occurs because the result is that it + * needs to loop around and create another child process to monitor */ + if (!monitor_process_till_connect(&pi, &hpipe)) + ret = INVALID_HANDLE_VALUE; + +cleanup: + /* close the handles and pipes because this thread is done monitoring them */ + if (ret == INVALID_HANDLE_VALUE) + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + DisconnectNamedPipe(hpipe); + CloseHandle(hpipe); + return ret; +} \ No newline at end of file diff --git a/server.c b/server.c index 1f627e8f11..331c8c9fbf 100644 --- a/server.c +++ b/server.c @@ -63,12 +63,28 @@ static char me[128]; static pthread_key_t sk_out_key; +#ifdef WIN32 +static char *fio_server_pipe_name = NULL; +static HANDLE hjob = INVALID_HANDLE_VALUE; +struct ffi_element { + union { + pthread_t thread; + HANDLE hProcess; + }; + bool is_thread; +}; +#endif + struct fio_fork_item { struct flist_head list; int exitval; int signal; int exited; +#ifdef WIN32 + struct ffi_element element; +#else pid_t pid; +#endif }; struct cmd_reply { @@ -672,6 +688,63 @@ static int fio_net_queue_stop(int error, int signal) return fio_net_send_ack(NULL, error, signal); } +#ifdef WIN32 +static void fio_server_add_fork_item(struct ffi_element *element, struct flist_head *list) +{ + struct fio_fork_item *ffi; + + ffi = malloc(sizeof(*ffi)); + ffi->exitval = 0; + ffi->signal = 0; + ffi->exited = 0; + ffi->element = *element; + flist_add_tail(&ffi->list, list); +} + +static void fio_server_add_conn_pid(struct flist_head *conn_list, HANDLE hProcess) +{ + struct ffi_element element = {.hProcess = hProcess, .is_thread=FALSE}; + dprint(FD_NET, "server: forked off connection job (tid=%u)\n", (int) element.thread); + + fio_server_add_fork_item(&element, conn_list); +} + +static void fio_server_add_job_pid(struct flist_head *job_list, pthread_t thread) +{ + struct ffi_element element = {.thread = thread, .is_thread=TRUE}; + dprint(FD_NET, "server: forked off job job (tid=%u)\n", (int) element.thread); + fio_server_add_fork_item(&element, job_list); +} + +static void fio_server_check_fork_item(struct fio_fork_item *ffi) +{ + int ret; + + if (ffi->element.is_thread) { + + ret = pthread_kill(ffi->element.thread, 0); + if (ret) { + int rev_val; + pthread_join(ffi->element.thread, (void**) &rev_val); /*if the thread is dead, then join it to get status*/ + + ffi->exitval = rev_val; + if (ffi->exitval) + log_err("thread (tid=%u) exited with %x\n", (int) ffi->element.thread, (int) ffi->exitval); + dprint(FD_PROCESS, "thread (tid=%u) exited with %x\n", (int) ffi->element.thread, (int) ffi->exitval); + ffi->exited = 1; + } + } else { + DWORD exit_val; + GetExitCodeProcess(ffi->element.hProcess, &exit_val); + + if (exit_val != STILL_ACTIVE) { + dprint(FD_PROCESS, "process %u exited with %d\n", GetProcessId(ffi->element.hProcess), exit_val); + ffi->exited = 1; + ffi->exitval = exit_val; + } + } +} +#else static void fio_server_add_fork_item(pid_t pid, struct flist_head *list) { struct fio_fork_item *ffi; @@ -719,10 +792,21 @@ static void fio_server_check_fork_item(struct fio_fork_item *ffi) } } } +#endif static void fio_server_fork_item_done(struct fio_fork_item *ffi, bool stop) { +#ifdef WIN32 + if (ffi->element.is_thread) + dprint(FD_NET, "tid %u exited, sig=%u, exitval=%d\n", (int) ffi->element.thread, ffi->signal, ffi->exitval); + else { + dprint(FD_NET, "pid %u exited, sig=%u, exitval=%d\n", (int) GetProcessId(ffi->element.hProcess), ffi->signal, ffi->exitval); + CloseHandle(ffi->element.hProcess); + ffi->element.hProcess = INVALID_HANDLE_VALUE; + } +#else dprint(FD_NET, "pid %u exited, sig=%u, exitval=%d\n", (int) ffi->pid, ffi->signal, ffi->exitval); +#endif /* * Fold STOP and QUIT... @@ -783,27 +867,62 @@ static int handle_load_file_cmd(struct fio_net_cmd *cmd) return 0; } -static int handle_run_cmd(struct sk_out *sk_out, struct flist_head *job_list, - struct fio_net_cmd *cmd) +#ifdef WIN32 +static void *fio_backend_thread(void *data) { - pid_t pid; int ret; + struct sk_out *sk_out = (struct sk_out *) data; sk_out_assign(sk_out); + ret = fio_backend(sk_out); + sk_out_drop(); + + pthread_exit((void*) (intptr_t) ret); + return NULL; +} +#endif + +static int handle_run_cmd(struct sk_out *sk_out, struct flist_head *job_list, + struct fio_net_cmd *cmd) +{ + int ret; + fio_time_init(); set_genesis_time(); - pid = fork(); - if (pid) { - fio_server_add_job_pid(job_list, pid); - return 0; +#ifdef WIN32 + { + pthread_t thread; + /* both this thread and backend_thread call sk_out_assign() to double increment + * the ref count. This ensures struct is valid until both threads are done with it + */ + sk_out_assign(sk_out); + ret = pthread_create(&thread, NULL, fio_backend_thread, sk_out); + if (ret) { + log_err("pthread_create: %s\n", strerror(ret)); + return ret; + } + + fio_server_add_job_pid(job_list, thread); + return ret; } +#else + { + pid_t pid; + sk_out_assign(sk_out); + pid = fork(); + if (pid) { + fio_server_add_job_pid(job_list, pid); + return 0; + } - ret = fio_backend(sk_out); - free_threads_shm(); - sk_out_drop(); - _exit(ret); + ret = fio_backend(sk_out); + free_threads_shm(); + sk_out_drop(); + _exit(ret); + } +#endif } static int handle_job_cmd(struct fio_net_cmd *cmd) @@ -1322,6 +1441,73 @@ static int get_my_addr_str(int sk) return 0; } +#ifdef WIN32 +static int handle_connection_process(void) +{ + WSAPROTOCOL_INFO protocol_info; + DWORD bytes_read; + HANDLE hpipe; + int sk; + struct sk_out *sk_out; + int ret; + char *msg = (char *) "connected"; + + log_info("server enter accept loop. ProcessID %d\n", GetCurrentProcessId()); + + hpipe = CreateFile( + fio_server_pipe_name, + GENERIC_READ | GENERIC_WRITE, + 0, NULL, + OPEN_EXISTING, + 0, NULL); + + if (hpipe == INVALID_HANDLE_VALUE) { + log_err("couldnt open pipe %s error %lu\n", + fio_server_pipe_name, GetLastError()); + return -1; + } + + if (!ReadFile(hpipe, &protocol_info, sizeof(protocol_info), &bytes_read, NULL)) { + log_err("couldnt read pi from pipe %s error %lu\n", fio_server_pipe_name, + GetLastError()); + } + + if (use_ipv6) /* use protocol_info to create a duplicate of parents socket */ + sk = WSASocket(AF_INET6, SOCK_STREAM, 0, &protocol_info, 0, 0); + else + sk = WSASocket(AF_INET, SOCK_STREAM, 0, &protocol_info, 0, 0); + + sk_out = scalloc(1, sizeof(*sk_out)); + if (!sk_out) { + CloseHandle(hpipe); + close(sk); + return -1; + } + + sk_out->sk = sk; + sk_out->hProcess = INVALID_HANDLE_VALUE; + INIT_FLIST_HEAD(&sk_out->list); + __fio_sem_init(&sk_out->lock, FIO_SEM_UNLOCKED); + __fio_sem_init(&sk_out->wait, FIO_SEM_LOCKED); + __fio_sem_init(&sk_out->xmit, FIO_SEM_UNLOCKED); + + get_my_addr_str(sk); + + if (!WriteFile(hpipe, msg, strlen(msg), NULL, NULL)) { + log_err("couldnt write pipe\n"); + close(sk); + return -1; + } + CloseHandle(hpipe); + + sk_out_assign(sk_out); + + ret = handle_connection(sk_out); + __sk_out_drop(sk_out); + return ret; +} +#endif + static int accept_loop(int listen_sk) { struct sockaddr_in addr; @@ -1339,8 +1525,11 @@ static int accept_loop(int listen_sk) struct sk_out *sk_out; const char *from; char buf[64]; +#ifdef WIN32 + HANDLE hProcess; +#else pid_t pid; - +#endif pfd.fd = listen_sk; pfd.events = POLLIN; do { @@ -1398,6 +1587,13 @@ static int accept_loop(int listen_sk) __fio_sem_init(&sk_out->wait, FIO_SEM_LOCKED); __fio_sem_init(&sk_out->xmit, FIO_SEM_UNLOCKED); +#ifdef WIN32 + hProcess = windows_handle_connection(hjob, sk); + if (hProcess == INVALID_HANDLE_VALUE) + return -1; + sk_out->hProcess = hProcess; + fio_server_add_conn_pid(&conn_list, hProcess); +#else pid = fork(); if (pid) { close(sk); @@ -1414,6 +1610,7 @@ static int accept_loop(int listen_sk) */ sk_out_assign(sk_out); handle_connection(sk_out); +#endif } return exitval; @@ -2511,12 +2708,25 @@ static int fio_server(void) if (fio_handle_server_arg()) return -1; + set_sig_handlers(); + +#ifdef WIN32 + /* if this is a child process, go handle the connection */ + if (fio_server_pipe_name != NULL) { + ret = handle_connection_process(); + return ret; + } + + /* job to link child processes so they terminate together */ + hjob = windows_create_job(); + if (hjob == INVALID_HANDLE_VALUE) + return -1; +#endif + sk = fio_init_server_connection(); if (sk < 0) return -1; - set_sig_handlers(); - ret = accept_loop(sk); close(sk); @@ -2657,3 +2867,10 @@ void fio_server_set_arg(const char *arg) { fio_server_arg = strdup(arg); } + +#ifdef WIN32 +void fio_server_internal_set(const char *arg) +{ + fio_server_pipe_name = strdup(arg); +} +#endif \ No newline at end of file diff --git a/server.h b/server.h index 75600df209..35a3dc1d55 100644 --- a/server.h +++ b/server.h @@ -15,6 +15,9 @@ struct sk_out { unsigned int refs; /* frees sk_out when it drops to zero. * protected by below ->lock */ +#ifdef WIN32 + HANDLE hProcess; /* process handle of handle_connection_process*/ +#endif int sk; /* socket fd to talk to client */ struct fio_sem lock; /* protects ref and below list */ struct flist_head list; /* list of pending transmit work */ @@ -212,6 +215,7 @@ extern int fio_server_text_output(int, const char *, size_t); extern int fio_net_send_cmd(int, uint16_t, const void *, off_t, uint64_t *, struct flist_head *); extern int fio_net_send_simple_cmd(int, uint16_t, uint64_t, struct flist_head *); extern void fio_server_set_arg(const char *); +extern void fio_server_internal_set(const char *); extern int fio_server_parse_string(const char *, char **, bool *, int *, struct in_addr *, struct in6_addr *, int *); extern int fio_server_parse_host(const char *, int, struct in_addr *, struct in6_addr *); extern const char *fio_server_op(unsigned int); From 6dc75062f450e02a4ed49566d0536c209719a60a Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Sat, 18 Dec 2021 07:06:12 -0700 Subject: [PATCH 0024/1097] stat: code cleanup and leak free This file is somewhat of a mess. Only functional change is the free of ts_lcl in show_mixed_ddir_status(), rest is just a vague attempt at bringing some sanity back into this file. Fixes: https://github.com/axboe/fio/issues/1319 Signed-off-by: Jens Axboe --- stat.c | 84 ++++++++++++++++++++++++++++++++++------------------------ 1 file changed, 50 insertions(+), 34 deletions(-) diff --git a/stat.c b/stat.c index ec44c79e01..99de129453 100644 --- a/stat.c +++ b/stat.c @@ -289,9 +289,10 @@ void show_mixed_group_stats(struct group_run_stats *rs, struct buf_output *out) { char *io, *agg, *min, *max; char *ioalt, *aggalt, *minalt, *maxalt; - uint64_t io_mix = 0, agg_mix = 0, min_mix = -1, max_mix = 0, min_run = -1, max_run = 0; - int i; + uint64_t io_mix = 0, agg_mix = 0, min_mix = -1, max_mix = 0; + uint64_t min_run = -1, max_run = 0; const int i2p = is_power_of_2(rs->kb_base); + int i; for (i = 0; i < DDIR_RWDIR_CNT; i++) { if (!rs->max_run[i]) @@ -363,9 +364,9 @@ void show_group_stats(struct group_run_stats *rs, struct buf_output *out) free(minalt); free(maxalt); } - + /* Need to aggregate statisitics to show mixed values */ - if (rs->unified_rw_rep == UNIFIED_BOTH) + if (rs->unified_rw_rep == UNIFIED_BOTH) show_mixed_group_stats(rs, out); } @@ -473,30 +474,35 @@ static double convert_agg_kbytes_percent(struct group_run_stats *rs, int ddir, i return p_of_agg; } -static void show_mixed_ddir_status(struct group_run_stats *rs, struct thread_stat *ts, - struct buf_output *out) +static void show_mixed_ddir_status(struct group_run_stats *rs, + struct thread_stat *ts, + struct buf_output *out) { unsigned long runt; unsigned long long min, max, bw, iops; double mean, dev; char *io_p, *bw_p, *bw_p_alt, *iops_p, *post_st = NULL; struct thread_stat *ts_lcl; - int i2p; int ddir = 0; - /* Handle aggregation of Reads (ddir = 0), Writes (ddir = 1), and Trims (ddir = 2) */ + /* + * Handle aggregation of Reads (ddir = 0), Writes (ddir = 1), and + * Trims (ddir = 2) */ ts_lcl = malloc(sizeof(struct thread_stat)); memset((void *)ts_lcl, 0, sizeof(struct thread_stat)); - ts_lcl->unified_rw_rep = UNIFIED_MIXED; /* calculate mixed stats */ + /* calculate mixed stats */ + ts_lcl->unified_rw_rep = UNIFIED_MIXED; init_thread_stat_min_vals(ts_lcl); sum_thread_stats(ts_lcl, ts, 1); assert(ddir_rw(ddir)); - if (!ts_lcl->runtime[ddir]) + if (!ts_lcl->runtime[ddir]) { + free(ts_lcl); return; + } i2p = is_power_of_2(rs->kb_base); runt = ts_lcl->runtime[ddir]; @@ -560,10 +566,9 @@ static void show_mixed_ddir_status(struct group_run_stats *rs, struct thread_sta else samples = ts_lcl->clat_stat[ddir].samples; - /* Only print this if some high and low priority stats were collected */ + /* Only print if high and low priority stats were collected */ if (ts_lcl->clat_high_prio_stat[ddir].samples > 0 && - ts_lcl->clat_low_prio_stat[ddir].samples > 0) - { + ts_lcl->clat_low_prio_stat[ddir].samples > 0) { sprintf(prio_name, "high prio (%.2f%%) %s", 100. * (double) ts_lcl->clat_high_prio_stat[ddir].samples / (double) samples, name); @@ -1222,9 +1227,8 @@ void show_disk_util(int terse, struct json_object *parent, if (!is_running_backend()) return; - if (flist_empty(&disk_list)) { + if (flist_empty(&disk_list)) return; - } if ((output_format & FIO_OUTPUT_JSON) && parent) do_json = true; @@ -1234,9 +1238,9 @@ void show_disk_util(int terse, struct json_object *parent, if (!terse && !do_json) log_buf(out, "\nDisk stats (read/write):\n"); - if (do_json) + if (do_json) { json_object_add_disk_utils(parent, &disk_list); - else if (output_format & ~(FIO_OUTPUT_JSON | FIO_OUTPUT_JSON_PLUS)) { + } else if (output_format & ~(FIO_OUTPUT_JSON | FIO_OUTPUT_JSON_PLUS)) { flist_for_each(entry, &disk_list) { du = flist_entry(entry, struct disk_util, list); @@ -1396,19 +1400,20 @@ static void show_ddir_status_terse(struct thread_stat *ts, else log_buf(out, ";%llu;%llu;%f;%f", 0ULL, 0ULL, 0.0, 0.0); - if (ts->lat_percentiles) + if (ts->lat_percentiles) { len = calc_clat_percentiles(ts->io_u_plat[FIO_LAT][ddir], ts->lat_stat[ddir].samples, ts->percentile_list, &ovals, &maxv, &minv); - else if (ts->clat_percentiles) + } else if (ts->clat_percentiles) { len = calc_clat_percentiles(ts->io_u_plat[FIO_CLAT][ddir], ts->clat_stat[ddir].samples, ts->percentile_list, &ovals, &maxv, &minv); - else + } else { len = 0; - + } + for (i = 0; i < FIO_IO_U_LIST_MAX_LEN; i++) { if (i >= len) { log_buf(out, ";0%%=0"); @@ -1435,8 +1440,9 @@ static void show_ddir_status_terse(struct thread_stat *ts, } log_buf(out, ";%llu;%llu;%f%%;%f;%f", min, max, p_of_agg, mean, dev); - } else + } else { log_buf(out, ";%llu;%llu;%f%%;%f;%f", 0ULL, 0ULL, 0.0, 0.0, 0.0); + } if (ver == 5) { if (bw_stat) @@ -1458,15 +1464,19 @@ static void show_mixed_ddir_status_terse(struct thread_stat *ts, { struct thread_stat *ts_lcl; - /* Handle aggregation of Reads (ddir = 0), Writes (ddir = 1), and Trims (ddir = 2) */ + /* + * Handle aggregation of Reads (ddir = 0), Writes (ddir = 1), and + * Trims (ddir = 2) + */ ts_lcl = malloc(sizeof(struct thread_stat)); memset((void *)ts_lcl, 0, sizeof(struct thread_stat)); - ts_lcl->unified_rw_rep = UNIFIED_MIXED; /* calculate mixed stats */ + /* calculate mixed stats */ + ts_lcl->unified_rw_rep = UNIFIED_MIXED; init_thread_stat_min_vals(ts_lcl); ts_lcl->lat_percentiles = ts->lat_percentiles; ts_lcl->clat_percentiles = ts->clat_percentiles; ts_lcl->slat_percentiles = ts->slat_percentiles; - ts_lcl->percentile_precision = ts->percentile_precision; + ts_lcl->percentile_precision = ts->percentile_precision; memcpy(ts_lcl->percentile_list, ts->percentile_list, sizeof(ts->percentile_list)); sum_thread_stats(ts_lcl, ts, 1); @@ -1476,8 +1486,10 @@ static void show_mixed_ddir_status_terse(struct thread_stat *ts, free(ts_lcl); } -static struct json_object *add_ddir_lat_json(struct thread_stat *ts, uint32_t percentiles, - struct io_stat *lat_stat, uint64_t *io_u_plat) +static struct json_object *add_ddir_lat_json(struct thread_stat *ts, + uint32_t percentiles, + struct io_stat *lat_stat, + uint64_t *io_u_plat) { char buf[120]; double mean, dev; @@ -1650,15 +1662,19 @@ static void add_mixed_ddir_status_json(struct thread_stat *ts, { struct thread_stat *ts_lcl; - /* Handle aggregation of Reads (ddir = 0), Writes (ddir = 1), and Trims (ddir = 2) */ + /* + * Handle aggregation of Reads (ddir = 0), Writes (ddir = 1), and + * Trims (ddir = 2) + */ ts_lcl = malloc(sizeof(struct thread_stat)); memset((void *)ts_lcl, 0, sizeof(struct thread_stat)); - ts_lcl->unified_rw_rep = UNIFIED_MIXED; /* calculate mixed stats */ + /* calculate mixed stats */ + ts_lcl->unified_rw_rep = UNIFIED_MIXED; init_thread_stat_min_vals(ts_lcl); ts_lcl->lat_percentiles = ts->lat_percentiles; ts_lcl->clat_percentiles = ts->clat_percentiles; ts_lcl->slat_percentiles = ts->slat_percentiles; - ts_lcl->percentile_precision = ts->percentile_precision; + ts_lcl->percentile_precision = ts->percentile_precision; memcpy(ts_lcl->percentile_list, ts->percentile_list, sizeof(ts->percentile_list)); sum_thread_stats(ts_lcl, ts, 1); @@ -2133,7 +2149,7 @@ void sum_thread_stats(struct thread_stat *dst, struct thread_stat *src, sum_stat(&dst->sync_stat, &src->sync_stat, first, false); for (l = 0; l < DDIR_RWDIR_CNT; l++) { - if (!(dst->unified_rw_rep == UNIFIED_MIXED)) { + if (dst->unified_rw_rep != UNIFIED_MIXED) { sum_stat(&dst->clat_stat[l], &src->clat_stat[l], first, false); sum_stat(&dst->clat_high_prio_stat[l], &src->clat_high_prio_stat[l], first, false); sum_stat(&dst->clat_low_prio_stat[l], &src->clat_low_prio_stat[l], first, false); @@ -2188,7 +2204,7 @@ void sum_thread_stats(struct thread_stat *dst, struct thread_stat *src, dst->io_u_lat_m[k] += src->io_u_lat_m[k]; for (k = 0; k < DDIR_RWDIR_CNT; k++) { - if (!(dst->unified_rw_rep == UNIFIED_MIXED)) { + if (dst->unified_rw_rep != UNIFIED_MIXED) { dst->total_io_u[k] += src->total_io_u[k]; dst->short_io_u[k] += src->short_io_u[k]; dst->drop_io_u[k] += src->drop_io_u[k]; @@ -2204,7 +2220,7 @@ void sum_thread_stats(struct thread_stat *dst, struct thread_stat *src, for (k = 0; k < FIO_LAT_CNT; k++) for (l = 0; l < DDIR_RWDIR_CNT; l++) for (m = 0; m < FIO_IO_U_PLAT_NR; m++) - if (!(dst->unified_rw_rep == UNIFIED_MIXED)) + if (dst->unified_rw_rep != UNIFIED_MIXED) dst->io_u_plat[k][l][m] += src->io_u_plat[k][l][m]; else dst->io_u_plat[k][0][m] += src->io_u_plat[k][l][m]; @@ -2214,7 +2230,7 @@ void sum_thread_stats(struct thread_stat *dst, struct thread_stat *src, for (k = 0; k < DDIR_RWDIR_CNT; k++) { for (m = 0; m < FIO_IO_U_PLAT_NR; m++) { - if (!(dst->unified_rw_rep == UNIFIED_MIXED)) { + if (dst->unified_rw_rep != UNIFIED_MIXED) { dst->io_u_plat_high_prio[k][m] += src->io_u_plat_high_prio[k][m]; dst->io_u_plat_low_prio[k][m] += src->io_u_plat_low_prio[k][m]; } else { From 9b46661c289d01dbfe5182189a7abea9ce2f9e04 Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Sat, 18 Dec 2021 07:09:32 -0700 Subject: [PATCH 0025/1097] Fio 3.29 Signed-off-by: Jens Axboe --- FIO-VERSION-GEN | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FIO-VERSION-GEN b/FIO-VERSION-GEN index e9d563c124..60f7bb21c0 100755 --- a/FIO-VERSION-GEN +++ b/FIO-VERSION-GEN @@ -1,7 +1,7 @@ #!/bin/sh GVF=FIO-VERSION-FILE -DEF_VER=fio-3.28 +DEF_VER=fio-3.29 LF=' ' From 1420399f6620b417d9da4b801d3c049cf66e58f0 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Sat, 18 Dec 2021 19:46:51 -0500 Subject: [PATCH 0026/1097] ci: workaround for problem with i686 builds GitHub Actions currently has package dependency problems with some i386 packages. The work-around suggested in https://github.com/actions/virtual-environments/issues/4620 appears to resolve the issue for our builds. Signed-off-by: Vincent Fu --- ci/actions-install.sh | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ci/actions-install.sh b/ci/actions-install.sh index 7408ccb4f9..b3486a475d 100755 --- a/ci/actions-install.sh +++ b/ci/actions-install.sh @@ -31,14 +31,17 @@ DPKGCFG case "${CI_TARGET_ARCH}" in "i686") sudo dpkg --add-architecture i386 + opts="--allow-downgrades" pkgs=("${pkgs[@]/%/:i386}") pkgs+=( gcc-multilib pkg-config:i386 zlib1g-dev:i386 + libpcre2-8-0=10.34-7 ) ;; "x86_64") + opts="" pkgs+=( libglusterfs-dev libgoogle-perftools-dev @@ -62,7 +65,7 @@ DPKGCFG echo "Updating APT..." sudo apt-get -qq update echo "Installing packages..." - sudo apt-get install -o APT::Immediate-Configure=false --no-install-recommends -qq -y "${pkgs[@]}" + sudo apt-get install "$opts" -o APT::Immediate-Configure=false --no-install-recommends -qq -y "${pkgs[@]}" } install_linux() { From 2fe224d8b49e759138fa68b519161257dc1ce600 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Sat, 18 Dec 2021 19:51:49 -0500 Subject: [PATCH 0027/1097] Revert "ci: temporarily remove linux-i686-gcc build" This reverts commit cea3243fb3bb44d541c2b3fb82ee45eb669b6fe6. 1420399f6620b417d9da4b801d3c049cf66e58f0 provides a work-around for the problem with i686 builds. So we can now re-enable them. Signed-off-by: Vincent Fu --- .github/workflows/ci.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8167e3d163..cd8ce1429c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,6 +14,7 @@ jobs: - linux-gcc - linux-clang - macos + - linux-i686-gcc include: - build: linux-gcc os: ubuntu-20.04 @@ -23,6 +24,9 @@ jobs: cc: clang - build: macos os: macos-11 + - build: linux-i686-gcc + os: ubuntu-20.04 + arch: i686 env: CI_TARGET_ARCH: ${{ matrix.arch }} From 8310c57050e7c89a1454cbf7732e81ba83d668d7 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Tue, 21 Dec 2021 11:07:58 -0500 Subject: [PATCH 0028/1097] t/io_uring: fix 32-bit build warnings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Also change the type for offset to long long since that's what is expected by io_prep_pread. Latency measurement for t/io_uring is actually broken on 32-bit builds because iocb->data and io_event->data are 32-bit void pointers. So it is not possible to fit both fileno and clock_index in there. On a 32-bit build with 4-byte longs, the following warnings appear: t/io_uring.c: In function ‘prep_more_ios_aio’: t/io_uring.c:666:44: error: left shift count >= width of type [-Werror=shift-count-overflow] 666 | data |= ((unsigned long) s->clock_index << 32); | ^~ t/io_uring.c: In function ‘reap_events_aio’: t/io_uring.c:688:27: error: right shift count >= width of type [-Werror=shift-count-overflow] 688 | int clock_index = data >> 32; | ^~ Explicitly specify 64-bit types to resolve these warnings. Signed-off-by: Vincent Fu --- t/io_uring.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/t/io_uring.c b/t/io_uring.c index a98f78fd4a..755796dd2c 100644 --- a/t/io_uring.c +++ b/t/io_uring.c @@ -634,7 +634,8 @@ static int submitter_init(struct submitter *s) #ifdef CONFIG_LIBAIO static int prep_more_ios_aio(struct submitter *s, int max_ios, struct iocb *iocbs) { - unsigned long offset, data; + uint64_t data; + long long offset; struct file *f; unsigned index; long r; @@ -663,7 +664,7 @@ static int prep_more_ios_aio(struct submitter *s, int max_ios, struct iocb *iocb data = f->fileno; if (stats && stats_running) - data |= ((unsigned long) s->clock_index << 32); + data |= (((uint64_t) s->clock_index) << 32); iocb->data = (void *) (uintptr_t) data; index++; } @@ -676,7 +677,7 @@ static int reap_events_aio(struct submitter *s, struct io_event *events, int evs int reaped = 0; while (evs) { - unsigned long data = (uintptr_t) events[reaped].data; + uint64_t data = (uintptr_t) events[reaped].data; struct file *f = &s->files[data & 0xffffffff]; f->pending_ios--; From 5d3ea96a4ea88203cbdcc536eb538ca07cd771a9 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Thu, 23 Dec 2021 16:08:09 -0500 Subject: [PATCH 0029/1097] t/io_uring: fix help defaults for aio and random_io The positions of the default values for aio and random_io were swapped in the help message. Put the default values in their proper positions. Signed-off-by: Vincent Fu --- t/io_uring.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/t/io_uring.c b/t/io_uring.c index 755796dd2c..e8365a79d2 100644 --- a/t/io_uring.c +++ b/t/io_uring.c @@ -1095,7 +1095,7 @@ static void usage(char *argv, int status) " -a : Use legacy aio, default %d\n", argv, DEPTH, BATCH_SUBMIT, BATCH_COMPLETE, BS, polled, fixedbufs, dma_map, register_files, nthreads, !buffered, do_nop, - stats, runtime == 0 ? "unlimited" : runtime_str, aio, random_io); + stats, runtime == 0 ? "unlimited" : runtime_str, random_io, aio); exit(status); } From b5e99df6ec605b4dc6a3488203f32d5c5bfce8df Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Sun, 9 Jan 2022 19:34:27 -0700 Subject: [PATCH 0030/1097] engines/io_uring: don't set CQSIZE clamp unconditionally For older kernels without IORING_SETUP_CQSIZE, we'll get EINVAL if we set it. Just retry the ring setup if that happens. Link: https://github.com/axboe/fio/issues/1324 Signed-off-by: Jens Axboe --- engines/io_uring.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/engines/io_uring.c b/engines/io_uring.c index 00ae34823f..a2533c88e6 100644 --- a/engines/io_uring.c +++ b/engines/io_uring.c @@ -699,9 +699,15 @@ static int fio_ioring_queue_init(struct thread_data *td) p.flags |= IORING_SETUP_CQSIZE; p.cq_entries = depth; +retry: ret = syscall(__NR_io_uring_setup, depth, &p); - if (ret < 0) + if (ret < 0) { + if (errno == EINVAL && p.flags & IORING_SETUP_CQSIZE) { + p.flags &= ~IORING_SETUP_CQSIZE; + goto retry; + } return ret; + } ld->ring_fd = ret; From 016869bebe9bef7cae5a7f9dc0762162b0612226 Mon Sep 17 00:00:00 2001 From: Niklas Cassel Date: Mon, 10 Jan 2022 09:01:39 +0000 Subject: [PATCH 0031/1097] stat: remove unnecessary bool parameter to sum_thread_stats() We can deduce if it is the first struct io_stat src being added to the struct io_stat dst by checking if the current amount of samples in dst is zero. Therefore, remove the bool parameter "first" to sum_stat(). Since sum_stat() was the only user of the bool parameter "first" to the sum_thread_stats() function, we can remove it from sum_thread_stats() as well. Signed-off-by: Niklas Cassel Reviewed-by: Damien Le Moal Link: https://lore.kernel.org/r/20220110090133.69955-1-Niklas.Cassel@wdc.com Signed-off-by: Jens Axboe --- client.c | 2 +- gclient.c | 2 +- rate-submit.c | 2 +- stat.c | 53 ++++++++++++++++++++++----------------------------- stat.h | 2 +- 5 files changed, 27 insertions(+), 34 deletions(-) diff --git a/client.c b/client.c index 8b230617f7..be8411d823 100644 --- a/client.c +++ b/client.c @@ -1111,7 +1111,7 @@ static void handle_ts(struct fio_client *client, struct fio_net_cmd *cmd) if (sum_stat_clients <= 1) return; - sum_thread_stats(&client_ts, &p->ts, sum_stat_nr == 1); + sum_thread_stats(&client_ts, &p->ts); sum_group_stats(&client_gs, &p->rs); client_ts.members++; diff --git a/gclient.c b/gclient.c index e0e0e7bf92..ac0635361c 100644 --- a/gclient.c +++ b/gclient.c @@ -292,7 +292,7 @@ static void gfio_thread_status_op(struct fio_client *client, if (sum_stat_clients == 1) return; - sum_thread_stats(&client_ts, &p->ts, sum_stat_nr == 1); + sum_thread_stats(&client_ts, &p->ts); sum_group_stats(&client_gs, &p->rs); client_ts.members++; diff --git a/rate-submit.c b/rate-submit.c index 13dbe7a2e9..752c30a5f1 100644 --- a/rate-submit.c +++ b/rate-submit.c @@ -195,7 +195,7 @@ static void io_workqueue_exit_worker_fn(struct submit_worker *sw, struct thread_data *td = sw->priv; (*sum_cnt)++; - sum_thread_stats(&sw->wq->td->ts, &td->ts, *sum_cnt == 1); + sum_thread_stats(&sw->wq->td->ts, &td->ts); fio_options_free(td); close_and_free_files(td); diff --git a/stat.c b/stat.c index 99de129453..36742a2598 100644 --- a/stat.c +++ b/stat.c @@ -495,7 +495,7 @@ static void show_mixed_ddir_status(struct group_run_stats *rs, ts_lcl->unified_rw_rep = UNIFIED_MIXED; init_thread_stat_min_vals(ts_lcl); - sum_thread_stats(ts_lcl, ts, 1); + sum_thread_stats(ts_lcl, ts); assert(ddir_rw(ddir)); @@ -1479,7 +1479,7 @@ static void show_mixed_ddir_status_terse(struct thread_stat *ts, ts_lcl->percentile_precision = ts->percentile_precision; memcpy(ts_lcl->percentile_list, ts->percentile_list, sizeof(ts->percentile_list)); - sum_thread_stats(ts_lcl, ts, 1); + sum_thread_stats(ts_lcl, ts); /* add the aggregated stats to json parent */ show_ddir_status_terse(ts_lcl, rs, DDIR_READ, ver, out); @@ -1677,7 +1677,7 @@ static void add_mixed_ddir_status_json(struct thread_stat *ts, ts_lcl->percentile_precision = ts->percentile_precision; memcpy(ts_lcl->percentile_list, ts->percentile_list, sizeof(ts->percentile_list)); - sum_thread_stats(ts_lcl, ts, 1); + sum_thread_stats(ts_lcl, ts); /* add the aggregated stats to json parent */ add_ddir_status_json(ts_lcl, rs, DDIR_READ, parent); @@ -2089,9 +2089,10 @@ static void __sum_stat(struct io_stat *dst, struct io_stat *src, bool first) * numbers. For group_reporting, we should just add those up, not make * them the mean of everything. */ -static void sum_stat(struct io_stat *dst, struct io_stat *src, bool first, - bool pure_sum) +static void sum_stat(struct io_stat *dst, struct io_stat *src, bool pure_sum) { + bool first = dst->samples == 0; + if (src->samples == 0) return; @@ -2141,49 +2142,41 @@ void sum_group_stats(struct group_run_stats *dst, struct group_run_stats *src) dst->sig_figs = src->sig_figs; } -void sum_thread_stats(struct thread_stat *dst, struct thread_stat *src, - bool first) +void sum_thread_stats(struct thread_stat *dst, struct thread_stat *src) { int k, l, m; - sum_stat(&dst->sync_stat, &src->sync_stat, first, false); - for (l = 0; l < DDIR_RWDIR_CNT; l++) { if (dst->unified_rw_rep != UNIFIED_MIXED) { - sum_stat(&dst->clat_stat[l], &src->clat_stat[l], first, false); - sum_stat(&dst->clat_high_prio_stat[l], &src->clat_high_prio_stat[l], first, false); - sum_stat(&dst->clat_low_prio_stat[l], &src->clat_low_prio_stat[l], first, false); - sum_stat(&dst->slat_stat[l], &src->slat_stat[l], first, false); - sum_stat(&dst->lat_stat[l], &src->lat_stat[l], first, false); - sum_stat(&dst->bw_stat[l], &src->bw_stat[l], first, true); - sum_stat(&dst->iops_stat[l], &src->iops_stat[l], first, true); + sum_stat(&dst->clat_stat[l], &src->clat_stat[l], false); + sum_stat(&dst->clat_high_prio_stat[l], &src->clat_high_prio_stat[l], false); + sum_stat(&dst->clat_low_prio_stat[l], &src->clat_low_prio_stat[l], false); + sum_stat(&dst->slat_stat[l], &src->slat_stat[l], false); + sum_stat(&dst->lat_stat[l], &src->lat_stat[l], false); + sum_stat(&dst->bw_stat[l], &src->bw_stat[l], true); + sum_stat(&dst->iops_stat[l], &src->iops_stat[l], true); dst->io_bytes[l] += src->io_bytes[l]; if (dst->runtime[l] < src->runtime[l]) dst->runtime[l] = src->runtime[l]; } else { - sum_stat(&dst->clat_stat[0], &src->clat_stat[l], first, false); - sum_stat(&dst->clat_high_prio_stat[0], &src->clat_high_prio_stat[l], first, false); - sum_stat(&dst->clat_low_prio_stat[0], &src->clat_low_prio_stat[l], first, false); - sum_stat(&dst->slat_stat[0], &src->slat_stat[l], first, false); - sum_stat(&dst->lat_stat[0], &src->lat_stat[l], first, false); - sum_stat(&dst->bw_stat[0], &src->bw_stat[l], first, true); - sum_stat(&dst->iops_stat[0], &src->iops_stat[l], first, true); + sum_stat(&dst->clat_stat[0], &src->clat_stat[l], false); + sum_stat(&dst->clat_high_prio_stat[0], &src->clat_high_prio_stat[l], false); + sum_stat(&dst->clat_low_prio_stat[0], &src->clat_low_prio_stat[l], false); + sum_stat(&dst->slat_stat[0], &src->slat_stat[l], false); + sum_stat(&dst->lat_stat[0], &src->lat_stat[l], false); + sum_stat(&dst->bw_stat[0], &src->bw_stat[l], true); + sum_stat(&dst->iops_stat[0], &src->iops_stat[l], true); dst->io_bytes[0] += src->io_bytes[l]; if (dst->runtime[0] < src->runtime[l]) dst->runtime[0] = src->runtime[l]; - - /* - * We're summing to the same destination, so override - * 'first' after the first iteration of the loop - */ - first = false; } } + sum_stat(&dst->sync_stat, &src->sync_stat, false); dst->usr_time += src->usr_time; dst->sys_time += src->sys_time; dst->ctx += src->ctx; @@ -2417,7 +2410,7 @@ void __show_run_stats(void) for (k = 0; k < ts->nr_block_infos; k++) ts->block_infos[k] = td->ts.block_infos[k]; - sum_thread_stats(ts, &td->ts, idx == 1); + sum_thread_stats(ts, &td->ts); if (td->o.ss_dur) { ts->ss_state = td->ss.state; diff --git a/stat.h b/stat.h index 9ef8caa438..15ca4eff59 100644 --- a/stat.h +++ b/stat.h @@ -325,7 +325,7 @@ extern void __show_run_stats(void); extern int __show_running_run_stats(void); extern void show_running_run_stats(void); extern void check_for_running_stats(void); -extern void sum_thread_stats(struct thread_stat *dst, struct thread_stat *src, bool first); +extern void sum_thread_stats(struct thread_stat *dst, struct thread_stat *src); extern void sum_group_stats(struct group_run_stats *dst, struct group_run_stats *src); extern void init_thread_stat_min_vals(struct thread_stat *ts); extern void init_thread_stat(struct thread_stat *ts); From ef37053efdfb8c3b8b6deef43c0969753e6adb44 Mon Sep 17 00:00:00 2001 From: Damien Le Moal Date: Mon, 17 Jan 2022 11:11:27 +0900 Subject: [PATCH 0032/1097] init: do not create lat logs when not needed When any of the options disable_lat, disable_slat and disable_clat are used, there is no need to create the lat log associated with the disabled latency. In addition, when write_lat_log is also specified, this change avoids the creation of empty latency log files. Signed-off-by: Damien Le Moal Reviewed-by: Niklas Cassel Link: https://lore.kernel.org/r/20220117021127.9259-1-damien.lemoal@wdc.com Signed-off-by: Jens Axboe --- init.c | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/init.c b/init.c index 5f069d9a5b..07daaa84b1 100644 --- a/init.c +++ b/init.c @@ -1586,17 +1586,23 @@ static int add_job(struct thread_data *td, const char *jobname, int job_add_num, else suf = "log"; - gen_log_name(logname, sizeof(logname), "lat", pre, - td->thread_number, suf, o->per_job_logs); - setup_log(&td->lat_log, &p, logname); + if (!o->disable_lat) { + gen_log_name(logname, sizeof(logname), "lat", pre, + td->thread_number, suf, o->per_job_logs); + setup_log(&td->lat_log, &p, logname); + } - gen_log_name(logname, sizeof(logname), "slat", pre, - td->thread_number, suf, o->per_job_logs); - setup_log(&td->slat_log, &p, logname); + if (!o->disable_slat) { + gen_log_name(logname, sizeof(logname), "slat", pre, + td->thread_number, suf, o->per_job_logs); + setup_log(&td->slat_log, &p, logname); + } - gen_log_name(logname, sizeof(logname), "clat", pre, - td->thread_number, suf, o->per_job_logs); - setup_log(&td->clat_log, &p, logname); + if (!o->disable_clat) { + gen_log_name(logname, sizeof(logname), "clat", pre, + td->thread_number, suf, o->per_job_logs); + setup_log(&td->clat_log, &p, logname); + } } From 53d966f8177e0ada3bf52930f75f7b30817ea8c2 Mon Sep 17 00:00:00 2001 From: Niklas Cassel Date: Mon, 17 Jan 2022 15:50:53 +0000 Subject: [PATCH 0033/1097] stat: remove duplicated code in show_mixed_ddir_status() When using unified_rw_reporting=mixed, show_ddir_status() is called, and is solely responsible for printing the mixed stats. When using unified_rw_reporting=both, show_ddir_status() is called and prints the regular output, after that, show_mixed_ddir_status() is called to print the mixed stats. The way that show_mixed_ddir_status_terse() and add_mixed_ddir_status_json() is implemented, is to alloc a new local ts that will hold the mixed result, and then simply call the regular non-mixed print function show_ddir_status_terse()/add_ddir_status_json() with this local ts. show_mixed_ddir_status() also allocates a new local ts, but fails to initialize the lat percentiles and the percentile_list in the new local ts. Therefore, show_mixed_ddir_status() has duplicated all the code from show_ddir_status(), except that it uses the lat percentiles and the percentile_list from the original ts. Simplify show_mixed_ddir_status(), to behave in the same way as show_mixed_ddir_status_terse() and add_mixed_ddir_status_json(). In other words, initialize the lat percentiles and the percentile_list in the new local ts, and replace all the duplicated code with a simple call to the regular non-mixed print function (show_ddir_status()). Signed-off-by: Niklas Cassel Link: https://lore.kernel.org/r/20220117155045.311453-2-Niklas.Cassel@wdc.com Signed-off-by: Jens Axboe --- stat.c | 184 +++++++++------------------------------------------------ 1 file changed, 27 insertions(+), 157 deletions(-) diff --git a/stat.c b/stat.c index 36742a2598..42be600dc2 100644 --- a/stat.c +++ b/stat.c @@ -474,163 +474,6 @@ static double convert_agg_kbytes_percent(struct group_run_stats *rs, int ddir, i return p_of_agg; } -static void show_mixed_ddir_status(struct group_run_stats *rs, - struct thread_stat *ts, - struct buf_output *out) -{ - unsigned long runt; - unsigned long long min, max, bw, iops; - double mean, dev; - char *io_p, *bw_p, *bw_p_alt, *iops_p, *post_st = NULL; - struct thread_stat *ts_lcl; - int i2p; - int ddir = 0; - - /* - * Handle aggregation of Reads (ddir = 0), Writes (ddir = 1), and - * Trims (ddir = 2) */ - ts_lcl = malloc(sizeof(struct thread_stat)); - memset((void *)ts_lcl, 0, sizeof(struct thread_stat)); - /* calculate mixed stats */ - ts_lcl->unified_rw_rep = UNIFIED_MIXED; - init_thread_stat_min_vals(ts_lcl); - - sum_thread_stats(ts_lcl, ts); - - assert(ddir_rw(ddir)); - - if (!ts_lcl->runtime[ddir]) { - free(ts_lcl); - return; - } - - i2p = is_power_of_2(rs->kb_base); - runt = ts_lcl->runtime[ddir]; - - bw = (1000 * ts_lcl->io_bytes[ddir]) / runt; - io_p = num2str(ts_lcl->io_bytes[ddir], ts->sig_figs, 1, i2p, N2S_BYTE); - bw_p = num2str(bw, ts->sig_figs, 1, i2p, ts->unit_base); - bw_p_alt = num2str(bw, ts->sig_figs, 1, !i2p, ts->unit_base); - - iops = (1000 * ts_lcl->total_io_u[ddir]) / runt; - iops_p = num2str(iops, ts->sig_figs, 1, 0, N2S_NONE); - - log_buf(out, " mixed: IOPS=%s, BW=%s (%s)(%s/%llumsec)%s\n", - iops_p, bw_p, bw_p_alt, io_p, - (unsigned long long) ts_lcl->runtime[ddir], - post_st ? : ""); - - free(post_st); - free(io_p); - free(bw_p); - free(bw_p_alt); - free(iops_p); - - if (calc_lat(&ts_lcl->slat_stat[ddir], &min, &max, &mean, &dev)) - display_lat("slat", min, max, mean, dev, out); - if (calc_lat(&ts_lcl->clat_stat[ddir], &min, &max, &mean, &dev)) - display_lat("clat", min, max, mean, dev, out); - if (calc_lat(&ts_lcl->lat_stat[ddir], &min, &max, &mean, &dev)) - display_lat(" lat", min, max, mean, dev, out); - if (calc_lat(&ts_lcl->clat_high_prio_stat[ddir], &min, &max, &mean, &dev)) { - display_lat(ts_lcl->lat_percentiles ? "high prio_lat" : "high prio_clat", - min, max, mean, dev, out); - if (calc_lat(&ts_lcl->clat_low_prio_stat[ddir], &min, &max, &mean, &dev)) - display_lat(ts_lcl->lat_percentiles ? "low prio_lat" : "low prio_clat", - min, max, mean, dev, out); - } - - if (ts->slat_percentiles && ts_lcl->slat_stat[ddir].samples > 0) - show_clat_percentiles(ts_lcl->io_u_plat[FIO_SLAT][ddir], - ts_lcl->slat_stat[ddir].samples, - ts->percentile_list, - ts->percentile_precision, "slat", out); - if (ts->clat_percentiles && ts_lcl->clat_stat[ddir].samples > 0) - show_clat_percentiles(ts_lcl->io_u_plat[FIO_CLAT][ddir], - ts_lcl->clat_stat[ddir].samples, - ts->percentile_list, - ts->percentile_precision, "clat", out); - if (ts->lat_percentiles && ts_lcl->lat_stat[ddir].samples > 0) - show_clat_percentiles(ts_lcl->io_u_plat[FIO_LAT][ddir], - ts_lcl->lat_stat[ddir].samples, - ts->percentile_list, - ts->percentile_precision, "lat", out); - - if (ts->clat_percentiles || ts->lat_percentiles) { - const char *name = ts->lat_percentiles ? "lat" : "clat"; - char prio_name[32]; - uint64_t samples; - - if (ts->lat_percentiles) - samples = ts_lcl->lat_stat[ddir].samples; - else - samples = ts_lcl->clat_stat[ddir].samples; - - /* Only print if high and low priority stats were collected */ - if (ts_lcl->clat_high_prio_stat[ddir].samples > 0 && - ts_lcl->clat_low_prio_stat[ddir].samples > 0) { - sprintf(prio_name, "high prio (%.2f%%) %s", - 100. * (double) ts_lcl->clat_high_prio_stat[ddir].samples / (double) samples, - name); - show_clat_percentiles(ts_lcl->io_u_plat_high_prio[ddir], - ts_lcl->clat_high_prio_stat[ddir].samples, - ts->percentile_list, - ts->percentile_precision, prio_name, out); - - sprintf(prio_name, "low prio (%.2f%%) %s", - 100. * (double) ts_lcl->clat_low_prio_stat[ddir].samples / (double) samples, - name); - show_clat_percentiles(ts_lcl->io_u_plat_low_prio[ddir], - ts_lcl->clat_low_prio_stat[ddir].samples, - ts->percentile_list, - ts->percentile_precision, prio_name, out); - } - } - - if (calc_lat(&ts_lcl->bw_stat[ddir], &min, &max, &mean, &dev)) { - double p_of_agg = 100.0, fkb_base = (double)rs->kb_base; - const char *bw_str; - - if ((rs->unit_base == 1) && i2p) - bw_str = "Kibit"; - else if (rs->unit_base == 1) - bw_str = "kbit"; - else if (i2p) - bw_str = "KiB"; - else - bw_str = "kB"; - - p_of_agg = convert_agg_kbytes_percent(rs, ddir, mean); - - if (rs->unit_base == 1) { - min *= 8.0; - max *= 8.0; - mean *= 8.0; - dev *= 8.0; - } - - if (mean > fkb_base * fkb_base) { - min /= fkb_base; - max /= fkb_base; - mean /= fkb_base; - dev /= fkb_base; - bw_str = (rs->unit_base == 1 ? "Mibit" : "MiB"); - } - - log_buf(out, " bw (%5s/s): min=%5llu, max=%5llu, per=%3.2f%%, " - "avg=%5.02f, stdev=%5.02f, samples=%" PRIu64 "\n", - bw_str, min, max, p_of_agg, mean, dev, - (&ts_lcl->bw_stat[ddir])->samples); - } - if (calc_lat(&ts_lcl->iops_stat[ddir], &min, &max, &mean, &dev)) { - log_buf(out, " iops : min=%5llu, max=%5llu, " - "avg=%5.02f, stdev=%5.02f, samples=%" PRIu64 "\n", - min, max, mean, dev, (&ts_lcl->iops_stat[ddir])->samples); - } - - free(ts_lcl); -} - static void show_ddir_status(struct group_run_stats *rs, struct thread_stat *ts, int ddir, struct buf_output *out) { @@ -797,6 +640,33 @@ static void show_ddir_status(struct group_run_stats *rs, struct thread_stat *ts, } } +static void show_mixed_ddir_status(struct group_run_stats *rs, + struct thread_stat *ts, + struct buf_output *out) +{ + struct thread_stat *ts_lcl; + + /* + * Handle aggregation of Reads (ddir = 0), Writes (ddir = 1), and + * Trims (ddir = 2) + */ + ts_lcl = malloc(sizeof(struct thread_stat)); + memset((void *)ts_lcl, 0, sizeof(struct thread_stat)); + /* calculate mixed stats */ + ts_lcl->unified_rw_rep = UNIFIED_MIXED; + init_thread_stat_min_vals(ts_lcl); + ts_lcl->lat_percentiles = ts->lat_percentiles; + ts_lcl->clat_percentiles = ts->clat_percentiles; + ts_lcl->slat_percentiles = ts->slat_percentiles; + ts_lcl->percentile_precision = ts->percentile_precision; + memcpy(ts_lcl->percentile_list, ts->percentile_list, sizeof(ts->percentile_list)); + + sum_thread_stats(ts_lcl, ts); + + show_ddir_status(rs, ts_lcl, DDIR_READ, out); + free(ts_lcl); +} + static bool show_lat(double *io_u_lat, int nr, const char **ranges, const char *msg, struct buf_output *out) { From b182f07738d39f7969c3376375131dd2d3d7016a Mon Sep 17 00:00:00 2001 From: Niklas Cassel Date: Mon, 17 Jan 2022 15:50:54 +0000 Subject: [PATCH 0034/1097] stat: move unified=both mixed allocation and calculation to new helper When using unified_rw_reporting=both, we need to print both the per ddir stats, as well as the mixed stats. In order to print both, the regular printing functions are responsible for printing the per ddir stats from the unmodified struct thread_stat, and show_mixed_ddir_status(), show_mixed_ddir_status_terse() or add_mixed_ddir_status_json() is responsible for calculating and printing the mixed stats. In order to keep the original struct thread_stat intact, these three functions have to allocate a new local thread_stat, where the mixed ddir result can be stored before printing. Move the allocation and calculation of this new struct thread_stat to a new helper function, so that the code is easier to follow. Signed-off-by: Niklas Cassel Link: https://lore.kernel.org/r/20220117155045.311453-3-Niklas.Cassel@wdc.com Signed-off-by: Jens Axboe --- stat.c | 95 ++++++++++++++++++++++++---------------------------------- 1 file changed, 39 insertions(+), 56 deletions(-) diff --git a/stat.c b/stat.c index 42be600dc2..b08d2f25df 100644 --- a/stat.c +++ b/stat.c @@ -462,6 +462,35 @@ static void display_lat(const char *name, unsigned long long min, free(maxp); } +static struct thread_stat *gen_mixed_ddir_stats_from_ts(struct thread_stat *ts) +{ + struct thread_stat *ts_lcl; + + /* + * Handle aggregation of Reads (ddir = 0), Writes (ddir = 1), and + * Trims (ddir = 2) + */ + ts_lcl = malloc(sizeof(struct thread_stat)); + if (!ts_lcl) { + log_err("fio: failed to allocate local thread stat\n"); + return NULL; + } + + init_thread_stat(ts_lcl); + + /* calculate mixed stats */ + ts_lcl->unified_rw_rep = UNIFIED_MIXED; + ts_lcl->lat_percentiles = ts->lat_percentiles; + ts_lcl->clat_percentiles = ts->clat_percentiles; + ts_lcl->slat_percentiles = ts->slat_percentiles; + ts_lcl->percentile_precision = ts->percentile_precision; + memcpy(ts_lcl->percentile_list, ts->percentile_list, sizeof(ts->percentile_list)); + + sum_thread_stats(ts_lcl, ts); + + return ts_lcl; +} + static double convert_agg_kbytes_percent(struct group_run_stats *rs, int ddir, int mean) { double p_of_agg = 100.0; @@ -644,26 +673,11 @@ static void show_mixed_ddir_status(struct group_run_stats *rs, struct thread_stat *ts, struct buf_output *out) { - struct thread_stat *ts_lcl; + struct thread_stat *ts_lcl = gen_mixed_ddir_stats_from_ts(ts); - /* - * Handle aggregation of Reads (ddir = 0), Writes (ddir = 1), and - * Trims (ddir = 2) - */ - ts_lcl = malloc(sizeof(struct thread_stat)); - memset((void *)ts_lcl, 0, sizeof(struct thread_stat)); - /* calculate mixed stats */ - ts_lcl->unified_rw_rep = UNIFIED_MIXED; - init_thread_stat_min_vals(ts_lcl); - ts_lcl->lat_percentiles = ts->lat_percentiles; - ts_lcl->clat_percentiles = ts->clat_percentiles; - ts_lcl->slat_percentiles = ts->slat_percentiles; - ts_lcl->percentile_precision = ts->percentile_precision; - memcpy(ts_lcl->percentile_list, ts->percentile_list, sizeof(ts->percentile_list)); - - sum_thread_stats(ts_lcl, ts); + if (ts_lcl) + show_ddir_status(rs, ts_lcl, DDIR_READ, out); - show_ddir_status(rs, ts_lcl, DDIR_READ, out); free(ts_lcl); } @@ -1332,27 +1346,11 @@ static void show_mixed_ddir_status_terse(struct thread_stat *ts, struct group_run_stats *rs, int ver, struct buf_output *out) { - struct thread_stat *ts_lcl; + struct thread_stat *ts_lcl = gen_mixed_ddir_stats_from_ts(ts); - /* - * Handle aggregation of Reads (ddir = 0), Writes (ddir = 1), and - * Trims (ddir = 2) - */ - ts_lcl = malloc(sizeof(struct thread_stat)); - memset((void *)ts_lcl, 0, sizeof(struct thread_stat)); - /* calculate mixed stats */ - ts_lcl->unified_rw_rep = UNIFIED_MIXED; - init_thread_stat_min_vals(ts_lcl); - ts_lcl->lat_percentiles = ts->lat_percentiles; - ts_lcl->clat_percentiles = ts->clat_percentiles; - ts_lcl->slat_percentiles = ts->slat_percentiles; - ts_lcl->percentile_precision = ts->percentile_precision; - memcpy(ts_lcl->percentile_list, ts->percentile_list, sizeof(ts->percentile_list)); - - sum_thread_stats(ts_lcl, ts); + if (ts_lcl) + show_ddir_status_terse(ts_lcl, rs, DDIR_READ, ver, out); - /* add the aggregated stats to json parent */ - show_ddir_status_terse(ts_lcl, rs, DDIR_READ, ver, out); free(ts_lcl); } @@ -1530,27 +1528,12 @@ static void add_ddir_status_json(struct thread_stat *ts, static void add_mixed_ddir_status_json(struct thread_stat *ts, struct group_run_stats *rs, struct json_object *parent) { - struct thread_stat *ts_lcl; - - /* - * Handle aggregation of Reads (ddir = 0), Writes (ddir = 1), and - * Trims (ddir = 2) - */ - ts_lcl = malloc(sizeof(struct thread_stat)); - memset((void *)ts_lcl, 0, sizeof(struct thread_stat)); - /* calculate mixed stats */ - ts_lcl->unified_rw_rep = UNIFIED_MIXED; - init_thread_stat_min_vals(ts_lcl); - ts_lcl->lat_percentiles = ts->lat_percentiles; - ts_lcl->clat_percentiles = ts->clat_percentiles; - ts_lcl->slat_percentiles = ts->slat_percentiles; - ts_lcl->percentile_precision = ts->percentile_precision; - memcpy(ts_lcl->percentile_list, ts->percentile_list, sizeof(ts->percentile_list)); - - sum_thread_stats(ts_lcl, ts); + struct thread_stat *ts_lcl = gen_mixed_ddir_stats_from_ts(ts); /* add the aggregated stats to json parent */ - add_ddir_status_json(ts_lcl, rs, DDIR_READ, parent); + if (ts_lcl) + add_ddir_status_json(ts_lcl, rs, DDIR_READ, parent); + free(ts_lcl); } From e8ab121c88d61624c0925b54013cd57c2dc171f2 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Mon, 15 Nov 2021 20:07:17 +0000 Subject: [PATCH 0035/1097] sg: add support for VERIFY command using write modes fio does not have an explicit verify data direction and creating a new data direction just for SCSI VERIFY commands probably is not worthwhile. The format of SCSI VERIFY commands matches that of write operations since VERIFY commands can include data transfer to the device. So it seems reasonable to have VERIFY commands be accounted for as write operations by fio. Use the sg_write_mode option to support SCSI VERIFY commands with different BYTCHK values. BYTCHK Description 00 No data is transferred to the device; device data is checked 01 Device data is compared with data transferred to device 11 Same as 01 except that only one sector of data is transferred to the device and each sector specified in the verification extent is compared against this transferred data. Also update documentation and add a couple example jobs files. Signed-off-by: Vincent Fu Link: https://lore.kernel.org/r/20211115200807.117138-2-vincent.fu@samsung.com Signed-off-by: Jens Axboe --- HOWTO | 14 +++++++++ engines/sg.c | 41 ++++++++++++++++++++++++-- examples/sg_verify-fail.fio | 48 +++++++++++++++++++++++++++++++ examples/sg_verify.fio | 57 +++++++++++++++++++++++++++++++++++++ fio.1 | 24 +++++++++++++--- 5 files changed, 178 insertions(+), 6 deletions(-) create mode 100644 examples/sg_verify-fail.fio create mode 100644 examples/sg_verify.fio diff --git a/HOWTO b/HOWTO index 2956e50d7d..111d2342de 100644 --- a/HOWTO +++ b/HOWTO @@ -2511,6 +2511,20 @@ with the caveat that when used on the command line, they must come after the for each command but only the first 512 bytes will be used and transferred to the device. The writefua option is ignored with this selection. + **verify_bytchk_00** + Issue VERIFY commands with BYTCHK set to 00. This directs the + device to carry out a medium verification with no data comparison. + **verify_bytchk_01** + Issue VERIFY commands with BYTCHK set to 01. This directs the device to + compare the data on the device with the data transferred to the device. + **verify_bytchk_11** + Issue VERIFY commands with BYTCHK set to 11. This transfers a + single block to the device and compares the contents of this block with the + data on the device beginning at the specified offset. fio's block size + parameter specifies the total amount of data compared with this command. + However, only one block (sector) worth of data is transferred to the device. + This is similar to the WRITE SAME command except that data is compared instead + of written. .. option:: hipri : [sg] diff --git a/engines/sg.c b/engines/sg.c index 1c0193840d..0c525fae8f 100644 --- a/engines/sg.c +++ b/engines/sg.c @@ -66,8 +66,11 @@ enum { FIO_SG_WRITE = 1, - FIO_SG_WRITE_VERIFY = 2, - FIO_SG_WRITE_SAME = 3 + FIO_SG_WRITE_VERIFY, + FIO_SG_WRITE_SAME, + FIO_SG_VERIFY_BYTCHK_00, + FIO_SG_VERIFY_BYTCHK_01, + FIO_SG_VERIFY_BYTCHK_11, }; struct sg_options { @@ -128,6 +131,18 @@ static struct fio_option options[] = { .oval = FIO_SG_WRITE_SAME, .help = "Issue SCSI WRITE SAME commands", }, + { .ival = "verify_bytchk_00", + .oval = FIO_SG_VERIFY_BYTCHK_00, + .help = "Issue SCSI VERIFY commands with BYTCHK set to 00", + }, + { .ival = "verify_bytchk_01", + .oval = FIO_SG_VERIFY_BYTCHK_01, + .help = "Issue SCSI VERIFY commands with BYTCHK set to 01", + }, + { .ival = "verify_bytchk_11", + .oval = FIO_SG_VERIFY_BYTCHK_11, + .help = "Issue SCSI VERIFY commands with BYTCHK set to 11", + }, }, .category = FIO_OPT_C_ENGINE, .group = FIO_OPT_G_SG, @@ -576,6 +591,28 @@ static int fio_sgio_prep(struct thread_data *td, struct io_u *io_u) else hdr->cmdp[0] = 0x93; // write same(16) break; + case FIO_SG_VERIFY_BYTCHK_00: + if (lba < MAX_10B_LBA) + hdr->cmdp[0] = 0x2f; // VERIFY(10) + else + hdr->cmdp[0] = 0x8f; // VERIFY(16) + hdr->dxfer_len = 0; + break; + case FIO_SG_VERIFY_BYTCHK_01: + if (lba < MAX_10B_LBA) + hdr->cmdp[0] = 0x2f; // VERIFY(10) + else + hdr->cmdp[0] = 0x8f; // VERIFY(16) + hdr->cmdp[1] |= 0x02; // BYTCHK = 01b + break; + case FIO_SG_VERIFY_BYTCHK_11: + if (lba < MAX_10B_LBA) + hdr->cmdp[0] = 0x2f; // VERIFY(10) + else + hdr->cmdp[0] = 0x8f; // VERIFY(16) + hdr->cmdp[1] |= 0x06; // BYTCHK = 11b + hdr->dxfer_len = sd->bs; + break; }; fio_sgio_rw_lba(hdr, lba, nr_blocks); diff --git a/examples/sg_verify-fail.fio b/examples/sg_verify-fail.fio new file mode 100644 index 0000000000..64feece3bc --- /dev/null +++ b/examples/sg_verify-fail.fio @@ -0,0 +1,48 @@ +# +# ********************************** +# * !!THIS IS A DESTRUCTIVE TEST!! * +# * IF NOT CHANGED THIS TEST WILL * +# * DESTROY DATA ON /dev/sdb * +# ********************************** +# +# Test SCSI VERIFY commands issued via the sg ioengine +# The jobs with fail in the name should produce errors +# +# job description +# precon precondition the device by writing with a known +# pattern +# verify01 verify each block one at a time by comparing to known +# pattern +# verify01-fail verifying one too many blocks should produce a failure +# verify11-one_ios verify all 20 blocks by sending only 512 bytes +# verify11-fail verifying beyond the preconditioned region should +# produce a failure + +[global] +filename=/dev/sdb +buffer_pattern=0x01 +ioengine=sg +rw=write +bs=512 +number_ios=20 +stonewall + +[precon] + +[verify01] +sg_write_mode=verify_bytchk_01 +number_ios=20 + +[verify01-fail] +sg_write_mode=verify_bytchk_01 +number_ios=21 + +[verify11-one_ios] +sg_write_mode=verify_bytchk_11 +number_ios=1 +bs=10240 + +[verify11-fail] +sg_write_mode=verify_bytchk_11 +number_ios=1 +bs=10752 diff --git a/examples/sg_verify.fio b/examples/sg_verify.fio new file mode 100644 index 0000000000..6db0dd0a62 --- /dev/null +++ b/examples/sg_verify.fio @@ -0,0 +1,57 @@ +# +# ********************************** +# * !!THIS IS A DESTRUCTIVE TEST!! * +# * IF NOT CHANGED THIS TEST WILL * +# * DESTROY DATA ON /dev/sdb * +# ********************************** +# +# Test SCSI VERIFY commands issued via the sg ioengine +# All of the jobs below should complete without error +# +# job description +# precon precondition the device by writing with a known +# pattern +# verify00 verify written data on medium only +# verify01 verify each block one at a time by comparing to known +# pattern +# verify01-two_ios verify same data but with only two VERIFY operations +# verify11 verify each block one at a time +# verify11-five_ios verify data with five IOs, four blocks at a time, +# sending 512 bytes for each IO +# verify11-one_ios verify all 20 blocks by sending only 512 bytes +# + +[global] +filename=/dev/sdb +buffer_pattern=0x01 +ioengine=sg +rw=write +bs=512 +number_ios=20 +stonewall + +[precon] + +[verify00] +sg_write_mode=verify_bytchk_00 + +[verify01] +sg_write_mode=verify_bytchk_01 + +[verify01-two_ios] +sg_write_mode=verify_bytchk_01 +bs=5120 +number_ios=2 + +[verify11] +sg_write_mode=verify_bytchk_11 + +[verify11-five_ios] +sg_write_mode=verify_bytchk_11 +bs=2048 +number_ios=5 + +[verify11-one_ios] +sg_write_mode=verify_bytchk_11 +bs=10240 +number_ios=1 diff --git a/fio.1 b/fio.1 index e0458c22e2..4206360afa 100644 --- a/fio.1 +++ b/fio.1 @@ -2284,7 +2284,7 @@ With writefua option set to 1, write operations include the force unit access (fua) flag. Default: 0. .TP .BI (sg)sg_write_mode \fR=\fPstr -Specify the type of write commands to issue. This option can take three +Specify the type of write commands to issue. This option can take multiple values: .RS .RS @@ -2293,9 +2293,9 @@ values: Write opcodes are issued as usual .TP .B verify -Issue WRITE AND VERIFY commands. The BYTCHK bit is set to 0. This -directs the device to carry out a medium verification with no data -comparison. The writefua option is ignored with this selection. +Issue WRITE AND VERIFY commands. The BYTCHK bit is set to 00b. This directs the +device to carry out a medium verification with no data comparison for the data +that was written. The writefua option is ignored with this selection. .TP .B same Issue WRITE SAME commands. This transfers a single block to the device @@ -2308,6 +2308,22 @@ blocksize=8k will write 16 sectors with each command. fio will still generate 8k of data for each command butonly the first 512 bytes will be used and transferred to the device. The writefua option is ignored with this selection. +.TP +.B verify_bytchk_00 +Issue VERIFY commands with BYTCHK set to 00. This directs the device to carry +out a medium verification with no data comparison. +.TP +.B verify_bytchk_01 +Issue VERIFY commands with BYTCHK set to 01. This directs the device to +compare the data on the device with the data transferred to the device. +.TP +.B verify_bytchk_11 +Issue VERIFY commands with BYTCHK set to 11. This transfers a single block to +the device and compares the contents of this block with the data on the device +beginning at the specified offset. fio's block size parameter specifies the +total amount of data compared with this command. However, only one block +(sector) worth of data is transferred to the device. This is similar to the +WRITE SAME command except that data is compared instead of written. .RE .RE .TP From 91e13ff509253667c11fce864693849d2cb77b67 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Mon, 15 Nov 2021 20:07:17 +0000 Subject: [PATCH 0036/1097] sg: add support for WRITE SAME(16) commands with NDOB flag set Add the sg_write_mode option write_same_ndob to issue WRITE SAME(16) commands with the no data output buffer flag set. This flag is not supported for WRITE SAME(10). So all commands with this option will be WRITE SAME(16). Also include an example job file. Signed-off-by: Vincent Fu Link: https://lore.kernel.org/r/20211115200807.117138-3-vincent.fu@samsung.com Signed-off-by: Jens Axboe --- HOWTO | 5 ++++ engines/sg.c | 19 +++++++++++--- examples/sg_write_same_ndob.fio | 44 +++++++++++++++++++++++++++++++++ fio.1 | 6 +++++ 4 files changed, 70 insertions(+), 4 deletions(-) create mode 100644 examples/sg_write_same_ndob.fio diff --git a/HOWTO b/HOWTO index 111d2342de..370e04aa3f 100644 --- a/HOWTO +++ b/HOWTO @@ -2511,6 +2511,11 @@ with the caveat that when used on the command line, they must come after the for each command but only the first 512 bytes will be used and transferred to the device. The writefua option is ignored with this selection. + **write_same_ndob** + Issue WRITE SAME(16) commands as above but with the No Data Output + Buffer (NDOB) bit set. No data will be transferred to the device with + this bit set. Data written will be a pre-determined pattern such as + all zeroes. **verify_bytchk_00** Issue VERIFY commands with BYTCHK set to 00. This directs the device to carry out a medium verification with no data comparison. diff --git a/engines/sg.c b/engines/sg.c index 0c525fae8f..3c4e986de7 100644 --- a/engines/sg.c +++ b/engines/sg.c @@ -68,6 +68,7 @@ enum { FIO_SG_WRITE = 1, FIO_SG_WRITE_VERIFY, FIO_SG_WRITE_SAME, + FIO_SG_WRITE_SAME_NDOB, FIO_SG_VERIFY_BYTCHK_00, FIO_SG_VERIFY_BYTCHK_01, FIO_SG_VERIFY_BYTCHK_11, @@ -131,6 +132,10 @@ static struct fio_option options[] = { .oval = FIO_SG_WRITE_SAME, .help = "Issue SCSI WRITE SAME commands", }, + { .ival = "write_same_ndob", + .oval = FIO_SG_WRITE_SAME_NDOB, + .help = "Issue SCSI WRITE SAME(16) commands with NDOB flag set", + }, { .ival = "verify_bytchk_00", .oval = FIO_SG_VERIFY_BYTCHK_00, .help = "Issue SCSI VERIFY commands with BYTCHK set to 00", @@ -517,9 +522,9 @@ static enum fio_q_status fio_sgio_doio(struct thread_data *td, } static void fio_sgio_rw_lba(struct sg_io_hdr *hdr, unsigned long long lba, - unsigned long long nr_blocks) + unsigned long long nr_blocks, bool override16) { - if (lba < MAX_10B_LBA) { + if (lba < MAX_10B_LBA && !override16) { sgio_set_be32((uint32_t) lba, &hdr->cmdp[2]); sgio_set_be16((uint16_t) nr_blocks, &hdr->cmdp[7]); } else { @@ -560,7 +565,7 @@ static int fio_sgio_prep(struct thread_data *td, struct io_u *io_u) if (o->readfua) hdr->cmdp[1] |= 0x08; - fio_sgio_rw_lba(hdr, lba, nr_blocks); + fio_sgio_rw_lba(hdr, lba, nr_blocks, false); } else if (io_u->ddir == DDIR_WRITE) { sgio_hdr_init(sd, hdr, io_u, 1); @@ -591,6 +596,11 @@ static int fio_sgio_prep(struct thread_data *td, struct io_u *io_u) else hdr->cmdp[0] = 0x93; // write same(16) break; + case FIO_SG_WRITE_SAME_NDOB: + hdr->cmdp[0] = 0x93; // write same(16) + hdr->cmdp[1] |= 0x1; // no data output buffer + hdr->dxfer_len = 0; + break; case FIO_SG_VERIFY_BYTCHK_00: if (lba < MAX_10B_LBA) hdr->cmdp[0] = 0x2f; // VERIFY(10) @@ -615,7 +625,8 @@ static int fio_sgio_prep(struct thread_data *td, struct io_u *io_u) break; }; - fio_sgio_rw_lba(hdr, lba, nr_blocks); + fio_sgio_rw_lba(hdr, lba, nr_blocks, + o->write_mode == FIO_SG_WRITE_SAME_NDOB); } else if (io_u->ddir == DDIR_TRIM) { struct sgio_trim *st; diff --git a/examples/sg_write_same_ndob.fio b/examples/sg_write_same_ndob.fio new file mode 100644 index 0000000000..fb0473196b --- /dev/null +++ b/examples/sg_write_same_ndob.fio @@ -0,0 +1,44 @@ +# +# ********************************** +# * !!THIS IS A DESTRUCTIVE TEST!! * +# * IF NOT CHANGED THIS TEST WILL * +# * DESTROY DATA ON /dev/sdb * +# ********************************** +# +# Test WRITE SAME commands with the NDOB flag set +# issued via the sg ioengine +# All of the jobs below should complete without error +# except the last one +# +# job description +# precon Precondition the device by writing 20 blocks with a +# known pattern +# write_same_ndob Write 19 sectors of all zeroes with the NDOB flag set +# verify-pass Verify 19 blocks of all zeroes +# verify-fail Verify 20 blocks of all zeroes. This should fail. +# + +[global] +filename=/dev/sdb +buffer_pattern=0x01 +ioengine=sg +rw=write +bs=512 +stonewall + +[precon] +number_ios=20 + +[write_same_ndob] +sg_write_mode=write_same_ndob +number_ios=19 + +[verify-pass] +sg_write_mode=verify_bytchk_01 +buffer_pattern=0x00 +number_ios=19 + +[verify-fail] +sg_write_mode=verify_bytchk_01 +buffer_pattern=0x00 +number_ios=20 diff --git a/fio.1 b/fio.1 index 4206360afa..5a66e32680 100644 --- a/fio.1 +++ b/fio.1 @@ -2309,6 +2309,12 @@ generate 8k of data for each command butonly the first 512 bytes will be used and transferred to the device. The writefua option is ignored with this selection. .TP +.B write_same_ndob +Issue WRITE SAME(16) commands as above but with the No Data Output +Buffer (NDOB) bit set. No data will be transferred to the device with +this bit set. Data written will be a pre-determined pattern such as +all zeroes. +.TP .B verify_bytchk_00 Issue VERIFY commands with BYTCHK set to 00. This directs the device to carry out a medium verification with no data comparison. From eadf32608213dc2a44303fce0b417fbb95d1a0a6 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Mon, 15 Nov 2021 20:07:17 +0000 Subject: [PATCH 0037/1097] sg: improve sg_write_mode option names There is a name collision for the sg_write_mode options for the WRITE AND VERIFY and VERIFY commands. Deprecate the 'verify' option and use 'write_and_verify' instead. Do the same thing for 'same' and 'write_same' to have a consistent naming scheme. The original option names are still supported for backward compatibility but list them as deprecated. Here are the new sg_write_mode options: Option SCSI command write WRITE (default) write_and_verify WRITE AND VERIFY verify (deprecated) WRITE AND VERIFY write_same WRITE SAME same (deprecated) WRITE SAME write_same_ndob WRITE SAME with NDOB flag set verify_bytchk_00 VERIFY with BYTCHK set to 00 verify_bytchk_01 VERIFY with BYTCHK set to 01 verify_bytchk_11 VERIFY with BYTCHK set to 11 Signed-off-by: Vincent Fu Link: https://lore.kernel.org/r/20211115200807.117138-4-vincent.fu@samsung.com Signed-off-by: Jens Axboe --- HOWTO | 8 ++++++-- engines/sg.c | 14 ++++++++++++-- fio.1 | 10 ++++++++-- 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/HOWTO b/HOWTO index 370e04aa3f..2563bdef24 100644 --- a/HOWTO +++ b/HOWTO @@ -2496,11 +2496,13 @@ with the caveat that when used on the command line, they must come after the **write** This is the default where write opcodes are issued as usual. - **verify** + **write_and_verify** Issue WRITE AND VERIFY commands. The BYTCHK bit is set to 0. This directs the device to carry out a medium verification with no data comparison. The writefua option is ignored with this selection. - **same** + **verify** + This option is deprecated. Use write_and_verify instead. + **write_same** Issue WRITE SAME commands. This transfers a single block to the device and writes this same block of data to a contiguous sequence of LBAs beginning at the specified offset. fio's block size parameter specifies @@ -2511,6 +2513,8 @@ with the caveat that when used on the command line, they must come after the for each command but only the first 512 bytes will be used and transferred to the device. The writefua option is ignored with this selection. + **same** + This option is deprecated. Use write_same instead. **write_same_ndob** Issue WRITE SAME(16) commands as above but with the No Data Output Buffer (NDOB) bit set. No data will be transferred to the device with diff --git a/engines/sg.c b/engines/sg.c index 3c4e986de7..d15b438fdc 100644 --- a/engines/sg.c +++ b/engines/sg.c @@ -124,14 +124,24 @@ static struct fio_option options[] = { .oval = FIO_SG_WRITE, .help = "Issue standard SCSI WRITE commands", }, - { .ival = "verify", + { .ival = "write_and_verify", .oval = FIO_SG_WRITE_VERIFY, .help = "Issue SCSI WRITE AND VERIFY commands", }, - { .ival = "same", + { .ival = "verify", + .oval = FIO_SG_WRITE_VERIFY, + .help = "Issue SCSI WRITE AND VERIFY commands. This " + "option is deprecated. Use write_and_verify instead.", + }, + { .ival = "write_same", .oval = FIO_SG_WRITE_SAME, .help = "Issue SCSI WRITE SAME commands", }, + { .ival = "same", + .oval = FIO_SG_WRITE_SAME, + .help = "Issue SCSI WRITE SAME commands. This " + "option is deprecated. Use write_same instead.", + }, { .ival = "write_same_ndob", .oval = FIO_SG_WRITE_SAME_NDOB, .help = "Issue SCSI WRITE SAME(16) commands with NDOB flag set", diff --git a/fio.1 b/fio.1 index 5a66e32680..48baebc502 100644 --- a/fio.1 +++ b/fio.1 @@ -2292,12 +2292,15 @@ values: .B write (default) Write opcodes are issued as usual .TP -.B verify +.B write_and_verify Issue WRITE AND VERIFY commands. The BYTCHK bit is set to 00b. This directs the device to carry out a medium verification with no data comparison for the data that was written. The writefua option is ignored with this selection. .TP -.B same +.B verify +This option is deprecated. Use write_and_verify instead. +.TP +.B write_same Issue WRITE SAME commands. This transfers a single block to the device and writes this same block of data to a contiguous sequence of LBAs beginning at the specified offset. fio's block size parameter @@ -2309,6 +2312,9 @@ generate 8k of data for each command butonly the first 512 bytes will be used and transferred to the device. The writefua option is ignored with this selection. .TP +.B same +This option is deprecated. Use write_same instead. +.TP .B write_same_ndob Issue WRITE SAME(16) commands as above but with the No Data Output Buffer (NDOB) bit set. No data will be transferred to the device with From 9917adb5d3b9c480ac710af4bc2c9c0421c6a5df Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Mon, 15 Nov 2021 20:07:17 +0000 Subject: [PATCH 0038/1097] sg: add support for WRITE STREAM(16) commands Add the "write_stream" option to sg_write_mode to send WRITE STREAM(16) commands. Use the new stream_id option to set the stream identifier. Example: sg_stream_ctl -o /dev/sdb Assigned stream id: 1 ./fio --name=test --filename=/dev/sdb --ioengine=sg --sg_write_mode=write_stream --stream_id=1 --rw=randwrite --time_based --runtime=10s ... sg_stream_ctl -c --id=1 /dev/sdb Signed-off-by: Vincent Fu Link: https://lore.kernel.org/r/20211115200807.117138-5-vincent.fu@samsung.com Signed-off-by: Jens Axboe --- engines/sg.c | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/engines/sg.c b/engines/sg.c index d15b438fdc..b51edb078b 100644 --- a/engines/sg.c +++ b/engines/sg.c @@ -69,6 +69,7 @@ enum { FIO_SG_WRITE_VERIFY, FIO_SG_WRITE_SAME, FIO_SG_WRITE_SAME_NDOB, + FIO_SG_WRITE_STREAM, FIO_SG_VERIFY_BYTCHK_00, FIO_SG_VERIFY_BYTCHK_01, FIO_SG_VERIFY_BYTCHK_11, @@ -80,6 +81,7 @@ struct sg_options { unsigned int readfua; unsigned int writefua; unsigned int write_mode; + uint16_t stream_id; }; static struct fio_option options[] = { @@ -158,10 +160,24 @@ static struct fio_option options[] = { .oval = FIO_SG_VERIFY_BYTCHK_11, .help = "Issue SCSI VERIFY commands with BYTCHK set to 11", }, + { .ival = "write_stream", + .oval = FIO_SG_WRITE_STREAM, + .help = "Issue SCSI WRITE STREAM(16) commands", + }, }, .category = FIO_OPT_C_ENGINE, .group = FIO_OPT_G_SG, }, + { + .name = "stream_id", + .lname = "stream id for WRITE STREAM(16) commands", + .type = FIO_OPT_INT, + .off1 = offsetof(struct sg_options, stream_id), + .help = "Stream ID for WRITE STREAM(16) commands", + .def = "0", + .category = FIO_OPT_C_ENGINE, + .group = FIO_OPT_G_SG, + }, { .name = NULL, }, @@ -611,6 +627,14 @@ static int fio_sgio_prep(struct thread_data *td, struct io_u *io_u) hdr->cmdp[1] |= 0x1; // no data output buffer hdr->dxfer_len = 0; break; + case FIO_SG_WRITE_STREAM: + hdr->cmdp[0] = 0x9a; // write stream (16) + if (o->writefua) + hdr->cmdp[1] |= 0x08; + sgio_set_be64(lba, &hdr->cmdp[2]); + sgio_set_be16(o->stream_id, &hdr->cmdp[10]); + sgio_set_be16((uint16_t) nr_blocks, &hdr->cmdp[12]); + break; case FIO_SG_VERIFY_BYTCHK_00: if (lba < MAX_10B_LBA) hdr->cmdp[0] = 0x2f; // VERIFY(10) @@ -635,8 +659,9 @@ static int fio_sgio_prep(struct thread_data *td, struct io_u *io_u) break; }; - fio_sgio_rw_lba(hdr, lba, nr_blocks, - o->write_mode == FIO_SG_WRITE_SAME_NDOB); + if (o->write_mode != FIO_SG_WRITE_STREAM) + fio_sgio_rw_lba(hdr, lba, nr_blocks, + o->write_mode == FIO_SG_WRITE_SAME_NDOB); } else if (io_u->ddir == DDIR_TRIM) { struct sgio_trim *st; From 5f647ee93b0b8ee07d6cbcef571986f8a18b1234 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Mon, 15 Nov 2021 20:07:17 +0000 Subject: [PATCH 0039/1097] sg: allow fio to open and close streams for WRITE STREAM(16) commands If --stream_id=0 then fio will open a stream for WRITE STREAM(16) commands and close the stream when the device file is closed. Example: ./fio --name=test --filename=/dev/sdb --ioengine=sg --number_ios=1 --debug=file,io --sg_write_mode=write_stream --rw=randwrite fio: set debug option file fio: set debug option io test: (g=0): rw=randwrite, bs=(R) 4096B-4096B, (W) 4096B-4096B, (T) 4096B-4096B, ioengine=sg, iodepth=1 fio-3.27 Starting 1 process file 1072297 setup files file 1072297 get file size for 0x7f0306fa5110/0//dev/sdb file 1072307 trying file /dev/sdb 290 file 1072307 fd open /dev/sdb file 1072307 file not found in hash /dev/sdb file 1072307 sgio_stream_control: opened stream 1 file 1072307 get file /dev/sdb, ref=0 io 1072307 drop page cache /dev/sdb file 1072307 goodf=1, badf=2, ff=2b1 file 1072307 get_next_file_rr: 0x7f0306fa5110 file 1072307 get_next_file: 0x7f0306fa5110 [/dev/sdb] file 1072307 get file /dev/sdb, ref=1 io 1072307 fill: io_u 0xb55700: off=0x35ef554000,len=0x1000,ddir=1,file=/dev/sdb io 1072307 prep: io_u 0xb55700: off=0x35ef554000,len=0x1000,ddir=1,file=/dev/sdb io 1072307 prep: io_u 0xb55700: ret=0 io 1072307 queue: io_u 0xb55700: off=0x35ef554000,len=0x1000,ddir=1,file=/dev/sdb io 1072307 complete: io_u 0xb55700: off=0x35ef554000,len=0x1000,ddir=1,file=/dev/sdb file 1072307 put file /dev/sdb, ref=2 file 1072307 close files file 1072307 put file /dev/sdb, ref=1 file 1072307 sgio_stream_control: closed stream 1 file 1072307 fd close /dev/sdb io 1072307 close ioengine sg io 1072307 free ioengine sg test: (groupid=0, jobs=1): err= 0: pid=1072307: Mon Aug 16 14:25:45 2021 write: IOPS=200, BW=800KiB/s (819kB/s)(4096B/5msec); 0 zone resets clat (nsec): min=93339, max=93339, avg=93339.00, stdev= 0.00 lat (nsec): min=96201, max=96201, avg=96201.00, stdev= 0.00 clat percentiles (nsec): | 1.00th=[93696], 5.00th=[93696], 10.00th=[93696], 20.00th=[93696], | 30.00th=[93696], 40.00th=[93696], 50.00th=[93696], 60.00th=[93696], | 70.00th=[93696], 80.00th=[93696], 90.00th=[93696], 95.00th=[93696], | 99.00th=[93696], 99.50th=[93696], 99.90th=[93696], 99.95th=[93696], | 99.99th=[93696] lat (usec) : 100=100.00% cpu : usr=100.00%, sys=0.00%, ctx=2, majf=0, minf=20 IO depths : 1=100.0%, 2=0.0%, 4=0.0%, 8=0.0%, 16=0.0%, 32=0.0%, >=64=0.0% submit : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.0% complete : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.0% issued rwts: total=0,1,0,0 short=0,0,0,0 dropped=0,0,0,0 latency : target=0, window=0, percentile=100.00%, depth=1 Run status group 0 (all jobs): WRITE: bw=800KiB/s (819kB/s), 800KiB/s-800KiB/s (819kB/s-819kB/s), io=4096B (4096B), run=5-5msec Signed-off-by: Vincent Fu Link: https://lore.kernel.org/r/20211115200807.117138-6-vincent.fu@samsung.com Signed-off-by: Jens Axboe --- engines/sg.c | 84 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 82 insertions(+), 2 deletions(-) diff --git a/engines/sg.c b/engines/sg.c index b51edb078b..72ee07ba45 100644 --- a/engines/sg.c +++ b/engines/sg.c @@ -217,6 +217,11 @@ struct sgio_data { #endif }; +static inline uint16_t sgio_get_be16(uint8_t *buf) +{ + return be16_to_cpu(*((uint16_t *) buf)); +} + static inline uint32_t sgio_get_be32(uint8_t *buf) { return be32_to_cpu(*((uint32_t *) buf)); @@ -632,7 +637,7 @@ static int fio_sgio_prep(struct thread_data *td, struct io_u *io_u) if (o->writefua) hdr->cmdp[1] |= 0x08; sgio_set_be64(lba, &hdr->cmdp[2]); - sgio_set_be16(o->stream_id, &hdr->cmdp[10]); + sgio_set_be16((uint16_t) io_u->file->engine_pos, &hdr->cmdp[10]); sgio_set_be16((uint16_t) nr_blocks, &hdr->cmdp[12]); break; case FIO_SG_VERIFY_BYTCHK_00: @@ -1053,9 +1058,60 @@ static int fio_sgio_type_check(struct thread_data *td, struct fio_file *f) return 0; } +static int fio_sgio_stream_control(struct fio_file *f, bool open_stream, uint16_t *stream_id) +{ + struct sg_io_hdr hdr; + unsigned char cmd[16]; + unsigned char sb[64]; + unsigned char buf[8]; + int ret; + + memset(&hdr, 0, sizeof(hdr)); + memset(cmd, 0, sizeof(cmd)); + memset(sb, 0, sizeof(sb)); + memset(buf, 0, sizeof(buf)); + + hdr.interface_id = 'S'; + hdr.cmdp = cmd; + hdr.cmd_len = 16; + hdr.sbp = sb; + hdr.mx_sb_len = sizeof(sb); + hdr.timeout = SCSI_TIMEOUT_MS; + hdr.cmdp[0] = 0x9e; + hdr.dxfer_direction = SG_DXFER_FROM_DEV; + hdr.dxferp = buf; + hdr.dxfer_len = sizeof(buf); + sgio_set_be32(sizeof(buf), &hdr.cmdp[10]); + + if (open_stream) + hdr.cmdp[1] = 0x34; + else { + hdr.cmdp[1] = 0x54; + sgio_set_be16(*stream_id, &hdr.cmdp[4]); + } + + ret = ioctl(f->fd, SG_IO, &hdr); + + if (ret < 0) + return ret; + + if (hdr.info & SG_INFO_CHECK) + return 1; + + if (open_stream) { + *stream_id = sgio_get_be16(&buf[4]); + dprint(FD_FILE, "sgio_stream_control: opened stream %u\n", (unsigned int) *stream_id); + assert(*stream_id != 0); + } else + dprint(FD_FILE, "sgio_stream_control: closed stream %u\n", (unsigned int) *stream_id); + + return 0; +} + static int fio_sgio_open(struct thread_data *td, struct fio_file *f) { struct sgio_data *sd = td->io_ops_data; + struct sg_options *o = td->eo; int ret; ret = generic_open_file(td, f); @@ -1067,9 +1123,33 @@ static int fio_sgio_open(struct thread_data *td, struct fio_file *f) return ret; } + if (o->write_mode == FIO_SG_WRITE_STREAM) { + if (o->stream_id) + f->engine_pos = o->stream_id; + else { + ret = fio_sgio_stream_control(f, true, (uint16_t *) &f->engine_pos); + if (ret) + return ret; + } + } + return 0; } +int fio_sgio_close(struct thread_data *td, struct fio_file *f) +{ + struct sg_options *o = td->eo; + int ret; + + if (!o->stream_id && o->write_mode == FIO_SG_WRITE_STREAM) { + ret = fio_sgio_stream_control(f, false, (uint16_t *) &f->engine_pos); + if (ret) + return ret; + } + + return generic_close_file(td, f); +} + /* * Build an error string with details about the driver, host or scsi * error contained in the sg header Caller will use as necessary. @@ -1344,7 +1424,7 @@ static struct ioengine_ops ioengine = { .event = fio_sgio_event, .cleanup = fio_sgio_cleanup, .open_file = fio_sgio_open, - .close_file = generic_close_file, + .close_file = fio_sgio_close, .get_file_size = fio_sgio_get_file_size, .flags = FIO_SYNCIO | FIO_RAWIO, .options = options, From 71efbed61dfb157dfa7fe550f500b53f9731e1cb Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Mon, 15 Nov 2021 20:07:17 +0000 Subject: [PATCH 0040/1097] docs: documentation for sg WRITE STREAM(16) Signed-off-by: Vincent Fu Link: https://lore.kernel.org/r/20211115200807.117138-7-vincent.fu@samsung.com Signed-off-by: Jens Axboe --- HOWTO | 9 +++++++++ fio.1 | 9 +++++++++ 2 files changed, 18 insertions(+) diff --git a/HOWTO b/HOWTO index 2563bdef24..f9e7c85720 100644 --- a/HOWTO +++ b/HOWTO @@ -2520,6 +2520,9 @@ with the caveat that when used on the command line, they must come after the Buffer (NDOB) bit set. No data will be transferred to the device with this bit set. Data written will be a pre-determined pattern such as all zeroes. + **write_stream** + Issue WRITE STREAM(16) commands. Use the **stream_id** option to specify + the stream identifier. **verify_bytchk_00** Issue VERIFY commands with BYTCHK set to 00. This directs the device to carry out a medium verification with no data comparison. @@ -2535,6 +2538,12 @@ with the caveat that when used on the command line, they must come after the This is similar to the WRITE SAME command except that data is compared instead of written. +.. option:: stream_id=int : [sg] + + Set the stream identifier for WRITE STREAM commands. If this is set to 0 (which is not + a valid stream identifier) fio will open a stream and then close it when done. Default + is 0. + .. option:: hipri : [sg] If this option is set, fio will attempt to use polled IO completions. diff --git a/fio.1 b/fio.1 index 48baebc502..34aa874d2d 100644 --- a/fio.1 +++ b/fio.1 @@ -2321,6 +2321,10 @@ Buffer (NDOB) bit set. No data will be transferred to the device with this bit set. Data written will be a pre-determined pattern such as all zeroes. .TP +.B write_stream +Issue WRITE STREAM(16) commands. Use the stream_id option to specify +the stream identifier. +.TP .B verify_bytchk_00 Issue VERIFY commands with BYTCHK set to 00. This directs the device to carry out a medium verification with no data comparison. @@ -2339,6 +2343,11 @@ WRITE SAME command except that data is compared instead of written. .RE .RE .TP +.BI (sg)stream_id \fR=\fPint +Set the stream identifier for WRITE STREAM commands. If this is set to 0 (which is not +a valid stream identifier) fio will open a stream and then close it when done. Default +is 0. +.TP .BI (nbd)uri \fR=\fPstr Specify the NBD URI of the server to test. The string is a standard NBD URI (see From 5ab088aa50aec5875d37f0ccc31711a9d50ededc Mon Sep 17 00:00:00 2001 From: Lukas Straub Date: Wed, 19 Jan 2022 21:14:16 +0000 Subject: [PATCH 0041/1097] blktrace.c: Use file stream interface instead of fifo Like in iolog.c use the file stream interface for accessing the iolog file. Signed-off-by: Lukas Straub Link: https://lore.kernel.org/r/5f52a20f95ebead7fa9ae8bce0acf8f0570219ca.1642626314.git.lukasstraub2@web.de Signed-off-by: Jens Axboe --- blktrace.c | 130 +++++++++++++++-------------------------------------- blktrace.h | 2 +- 2 files changed, 36 insertions(+), 96 deletions(-) diff --git a/blktrace.c b/blktrace.c index 64a610a959..c9a00eb130 100644 --- a/blktrace.c +++ b/blktrace.c @@ -4,6 +4,7 @@ #include #include #include +#include #include "flist.h" #include "fio.h" @@ -11,64 +12,19 @@ #include "blktrace_api.h" #include "oslib/linux-dev-lookup.h" -#define TRACE_FIFO_SIZE 8192 - -/* - * fifo refill frontend, to avoid reading data in trace sized bites - */ -static int refill_fifo(struct thread_data *td, struct fifo *fifo, int fd) -{ - char buf[TRACE_FIFO_SIZE]; - unsigned int total; - int ret; - - total = sizeof(buf); - if (total > fifo_room(fifo)) - total = fifo_room(fifo); - - ret = read(fd, buf, total); - if (ret < 0) { - int read_err = errno; - - assert(read_err > 0); - td_verror(td, read_err, "read blktrace file"); - return -read_err; - } - - if (ret > 0) - ret = fifo_put(fifo, buf, ret); - - dprint(FD_BLKTRACE, "refill: filled %d bytes\n", ret); - return ret; -} - -/* - * Retrieve 'len' bytes from the fifo, refilling if necessary. - */ -static int trace_fifo_get(struct thread_data *td, struct fifo *fifo, int fd, - void *buf, unsigned int len) -{ - if (fifo_len(fifo) < len) { - int ret = refill_fifo(td, fifo, fd); - - if (ret < 0) - return ret; - } - - return fifo_get(fifo, buf, len); -} - /* * Just discard the pdu by seeking past it. */ -static int discard_pdu(struct thread_data *td, struct fifo *fifo, int fd, - struct blk_io_trace *t) +static int discard_pdu(FILE* f, struct blk_io_trace *t) { if (t->pdu_len == 0) return 0; dprint(FD_BLKTRACE, "discard pdu len %u\n", t->pdu_len); - return trace_fifo_get(td, fifo, fd, NULL, t->pdu_len); + if (fseek(f, t->pdu_len, SEEK_CUR) < 0) + return -errno; + + return t->pdu_len; } /* @@ -444,33 +400,32 @@ bool load_blktrace(struct thread_data *td, const char *filename, int need_swap) unsigned long ios[DDIR_RWDIR_SYNC_CNT] = { }; unsigned int rw_bs[DDIR_RWDIR_CNT] = { }; unsigned long skipped_writes; - struct fifo *fifo; - int fd, i, old_state, max_depth; - struct fio_file *f; + FILE *f; + int i, old_state, max_depth; + struct fio_file *fiof; int this_depth[DDIR_RWDIR_CNT] = { }; int depth[DDIR_RWDIR_CNT] = { }; - fd = open(filename, O_RDONLY); - if (fd < 0) { + f = fopen(filename, "rb"); + if (!f) { td_verror(td, errno, "open blktrace file"); return false; } - fifo = fifo_alloc(TRACE_FIFO_SIZE); - old_state = td_bump_runstate(td, TD_SETTING_UP); td->o.size = 0; skipped_writes = 0; do { - int ret = trace_fifo_get(td, fifo, fd, &t, sizeof(t)); + int ret = fread(&t, 1, sizeof(t), f); - if (ret < 0) + if (ferror(f)) { + td_verror(td, errno, "read blktrace file"); goto err; - else if (!ret) + } else if (feof(f)) { break; - else if (ret < (int) sizeof(t)) { - log_err("fio: short fifo get\n"); + } else if (ret < (int) sizeof(t)) { + log_err("fio: iolog short read\n"); break; } @@ -487,13 +442,10 @@ bool load_blktrace(struct thread_data *td, const char *filename, int need_swap) t.magic & 0xff); goto err; } - ret = discard_pdu(td, fifo, fd, &t); + ret = discard_pdu(f, &t); if (ret < 0) { td_verror(td, -ret, "blktrace lseek"); goto err; - } else if (t.pdu_len != ret) { - log_err("fio: discarded %d of %d\n", ret, t.pdu_len); - goto err; } if ((t.action & BLK_TC_ACT(BLK_TC_NOTIFY)) == 0) { if ((t.action & 0xffff) == __BLK_TA_QUEUE) @@ -513,11 +465,10 @@ bool load_blktrace(struct thread_data *td, const char *filename, int need_swap) handle_trace(td, &t, ios, rw_bs); } while (1); - for_each_file(td, f, i) - trace_add_open_close_event(td, f->fileno, FIO_LOG_CLOSE_FILE); + for_each_file(td, fiof, i) + trace_add_open_close_event(td, fiof->fileno, FIO_LOG_CLOSE_FILE); - fifo_free(fifo); - close(fd); + fclose(f); td_restore_runstate(td, old_state); @@ -579,8 +530,7 @@ bool load_blktrace(struct thread_data *td, const char *filename, int need_swap) return true; err: - close(fd); - fifo_free(fifo); + fclose(f); return false; } @@ -625,15 +575,14 @@ static void merge_finish_file(struct blktrace_cursor *bcs, int i, int *nr_logs) { bcs[i].iter++; if (bcs[i].iter < bcs[i].nr_iter) { - lseek(bcs[i].fd, 0, SEEK_SET); + fseek(bcs[i].f, 0, SEEK_SET); return; } *nr_logs -= 1; /* close file */ - fifo_free(bcs[i].fifo); - close(bcs[i].fd); + fclose(bcs[i].f); /* keep active files contiguous */ memmove(&bcs[i], &bcs[*nr_logs], sizeof(bcs[i])); @@ -646,15 +595,16 @@ static int read_trace(struct thread_data *td, struct blktrace_cursor *bc) read_skip: /* read an io trace */ - ret = trace_fifo_get(td, bc->fifo, bc->fd, t, sizeof(*t)); - if (ret < 0) { + ret = fread(&t, 1, sizeof(t), bc->f); + if (ferror(bc->f)) { + td_verror(td, errno, "read blktrace file"); return ret; - } else if (!ret) { + } else if (feof(bc->f)) { if (!bc->length) bc->length = bc->t.time; return ret; } else if (ret < (int) sizeof(*t)) { - log_err("fio: short fifo get\n"); + log_err("fio: iolog short read\n"); return -1; } @@ -664,14 +614,10 @@ static int read_trace(struct thread_data *td, struct blktrace_cursor *bc) /* skip over actions that fio does not care about */ if ((t->action & 0xffff) != __BLK_TA_QUEUE || t_get_ddir(t) == DDIR_INVAL) { - ret = discard_pdu(td, bc->fifo, bc->fd, t); + ret = discard_pdu(bc->f, t); if (ret < 0) { td_verror(td, -ret, "blktrace lseek"); return ret; - } else if (t->pdu_len != ret) { - log_err("fio: discarded %d of %d\n", ret, - t->pdu_len); - return -1; } goto read_skip; } @@ -729,14 +675,13 @@ int merge_blktrace_iologs(struct thread_data *td) str = ptr = strdup(td->o.read_iolog_file); nr_logs = 0; for (i = 0; (name = get_next_str(&ptr)) != NULL; i++) { - bcs[i].fd = open(name, O_RDONLY); - if (bcs[i].fd < 0) { + bcs[i].f = fopen(name, "rb"); + if (!bcs[i].f) { log_err("fio: could not open file: %s\n", name); - ret = bcs[i].fd; + ret = -errno; free(str); goto err_file; } - bcs[i].fifo = fifo_alloc(TRACE_FIFO_SIZE); nr_logs++; if (!is_blktrace(name, &bcs[i].swap)) { @@ -761,14 +706,10 @@ int merge_blktrace_iologs(struct thread_data *td) i = find_earliest_io(bcs, nr_logs); bc = &bcs[i]; /* skip over the pdu */ - ret = discard_pdu(td, bc->fifo, bc->fd, &bc->t); + ret = discard_pdu(bc->f, &bc->t); if (ret < 0) { td_verror(td, -ret, "blktrace lseek"); goto err_file; - } else if (bc->t.pdu_len != ret) { - log_err("fio: discarded %d of %d\n", ret, - bc->t.pdu_len); - goto err_file; } ret = write_trace(merge_fp, &bc->t); @@ -786,8 +727,7 @@ int merge_blktrace_iologs(struct thread_data *td) err_file: /* cleanup */ for (i = 0; i < nr_logs; i++) { - fifo_free(bcs[i].fifo); - close(bcs[i].fd); + fclose(bcs[i].f); } err_merge_buf: free(merge_buf); diff --git a/blktrace.h b/blktrace.h index a0e82faa05..b2ebdba3b0 100644 --- a/blktrace.h +++ b/blktrace.h @@ -10,7 +10,7 @@ struct blktrace_cursor { struct fifo *fifo; // fifo queue for reading - int fd; // blktrace file + FILE *f; // blktrace file __u64 length; // length of trace struct blk_io_trace t; // current io trace int swap; // bitwise reverse required From a21ed2b50116e96f5f2d9c0dcf2e3506d7397ef8 Mon Sep 17 00:00:00 2001 From: Lukas Straub Date: Wed, 19 Jan 2022 21:14:20 +0000 Subject: [PATCH 0042/1097] iolog.c: Make iolog_items_to_fetch public This function be needed in the next patch. Signed-off-by: Lukas Straub Link: https://lore.kernel.org/r/81c9fbb31bbf0c487dc0ebff5eb85ca764fb14ef.1642626314.git.lukasstraub2@web.de Signed-off-by: Jens Axboe --- iolog.c | 2 +- iolog.h | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/iolog.c b/iolog.c index 1aeb7a76b2..3d4646a87a 100644 --- a/iolog.c +++ b/iolog.c @@ -355,7 +355,7 @@ void write_iolog_close(struct thread_data *td) td->iolog_buf = NULL; } -static int64_t iolog_items_to_fetch(struct thread_data *td) +int64_t iolog_items_to_fetch(struct thread_data *td) { struct timespec now; uint64_t elapsed; diff --git a/iolog.h b/iolog.h index 7d66b7c42f..a39863095a 100644 --- a/iolog.h +++ b/iolog.h @@ -254,6 +254,7 @@ extern void trim_io_piece(const struct io_u *); extern void queue_io_piece(struct thread_data *, struct io_piece *); extern void prune_io_piece_log(struct thread_data *); extern void write_iolog_close(struct thread_data *); +int64_t iolog_items_to_fetch(struct thread_data *td); extern int iolog_compress_init(struct thread_data *, struct sk_out *); extern void iolog_compress_exit(struct thread_data *); extern size_t log_chunk_sizes(struct io_log *); From 10f74940073380f6cb15608053b36a796f879dec Mon Sep 17 00:00:00 2001 From: Lukas Straub Date: Wed, 19 Jan 2022 21:14:23 +0000 Subject: [PATCH 0043/1097] blktrace.c: Add support for read_iolog_chunked Signed-off-by: Lukas Straub Link: https://lore.kernel.org/r/d43a8a2d5fd23d9756cdcf280cd2f3572585f264.1642626314.git.lukasstraub2@web.de Signed-off-by: Jens Axboe --- blktrace.c | 154 ++++++++++++++++++++++++++++++++++++++--------------- blktrace.h | 12 ++++- fio.h | 1 + iolog.c | 13 +++-- 4 files changed, 131 insertions(+), 49 deletions(-) diff --git a/blktrace.c b/blktrace.c index c9a00eb130..f1dbd1a6fd 100644 --- a/blktrace.c +++ b/blktrace.c @@ -8,6 +8,7 @@ #include "flist.h" #include "fio.h" +#include "iolog.h" #include "blktrace.h" #include "blktrace_api.h" #include "oslib/linux-dev-lookup.h" @@ -171,7 +172,7 @@ static void store_ipo(struct thread_data *td, unsigned long long offset, queue_io_piece(td, ipo); } -static void handle_trace_notify(struct blk_io_trace *t) +static bool handle_trace_notify(struct blk_io_trace *t) { switch (t->action) { case BLK_TN_PROCESS: @@ -188,18 +189,19 @@ static void handle_trace_notify(struct blk_io_trace *t) dprint(FD_BLKTRACE, "unknown trace act %x\n", t->action); break; } + return false; } -static void handle_trace_discard(struct thread_data *td, +static bool handle_trace_discard(struct thread_data *td, struct blk_io_trace *t, unsigned long long ttime, - unsigned long *ios, unsigned int *bs) + unsigned long *ios, unsigned long long *bs) { struct io_piece *ipo; int fileno; if (td->o.replay_skip & (1u << DDIR_TRIM)) - return; + return false; ipo = calloc(1, sizeof(*ipo)); init_ipo(ipo); @@ -226,6 +228,7 @@ static void handle_trace_discard(struct thread_data *td, ipo->offset, ipo->len, ipo->delay); queue_io_piece(td, ipo); + return true; } static void dump_trace(struct blk_io_trace *t) @@ -233,9 +236,9 @@ static void dump_trace(struct blk_io_trace *t) log_err("blktrace: ignoring zero byte trace: action=%x\n", t->action); } -static void handle_trace_fs(struct thread_data *td, struct blk_io_trace *t, +static bool handle_trace_fs(struct thread_data *td, struct blk_io_trace *t, unsigned long long ttime, unsigned long *ios, - unsigned int *bs) + unsigned long long *bs) { int rw; int fileno; @@ -246,16 +249,16 @@ static void handle_trace_fs(struct thread_data *td, struct blk_io_trace *t, if (rw) { if (td->o.replay_skip & (1u << DDIR_WRITE)) - return; + return false; } else { if (td->o.replay_skip & (1u << DDIR_READ)) - return; + return false; } if (!t->bytes) { if (!fio_did_warn(FIO_WARN_BTRACE_ZERO)) dump_trace(t); - return; + return false; } if (t->bytes > bs[rw]) @@ -264,16 +267,17 @@ static void handle_trace_fs(struct thread_data *td, struct blk_io_trace *t, ios[rw]++; td->o.size += t->bytes; store_ipo(td, t->sector, t->bytes, rw, ttime, fileno); + return true; } -static void handle_trace_flush(struct thread_data *td, struct blk_io_trace *t, +static bool handle_trace_flush(struct thread_data *td, struct blk_io_trace *t, unsigned long long ttime, unsigned long *ios) { struct io_piece *ipo; int fileno; if (td->o.replay_skip & (1u << DDIR_SYNC)) - return; + return false; ipo = calloc(1, sizeof(*ipo)); init_ipo(ipo); @@ -286,20 +290,21 @@ static void handle_trace_flush(struct thread_data *td, struct blk_io_trace *t, ios[DDIR_SYNC]++; dprint(FD_BLKTRACE, "store flush delay=%lu\n", ipo->delay); queue_io_piece(td, ipo); + return true; } /* * We only care for queue traces, most of the others are side effects * due to internal workings of the block layer. */ -static void handle_trace(struct thread_data *td, struct blk_io_trace *t, - unsigned long *ios, unsigned int *bs) +static bool queue_trace(struct thread_data *td, struct blk_io_trace *t, + unsigned long *ios, unsigned long long *bs) { static unsigned long long last_ttime; unsigned long long delay = 0; if ((t->action & 0xffff) != __BLK_TA_QUEUE) - return; + return false; if (!(t->action & BLK_TC_ACT(BLK_TC_NOTIFY))) { if (!last_ttime || td->o.no_stall) @@ -320,13 +325,13 @@ static void handle_trace(struct thread_data *td, struct blk_io_trace *t, t_bytes_align(&td->o, t); if (t->action & BLK_TC_ACT(BLK_TC_NOTIFY)) - handle_trace_notify(t); + return handle_trace_notify(t); else if (t->action & BLK_TC_ACT(BLK_TC_DISCARD)) - handle_trace_discard(td, t, delay, ios, bs); + return handle_trace_discard(td, t, delay, ios, bs); else if (t->action & BLK_TC_ACT(BLK_TC_FLUSH)) - handle_trace_flush(td, t, delay, ios); + return handle_trace_flush(td, t, delay, ios); else - handle_trace_fs(td, t, delay, ios, bs); + return handle_trace_fs(td, t, delay, ios, bs); } static void byteswap_trace(struct blk_io_trace *t) @@ -394,27 +399,62 @@ static void depth_end(struct blk_io_trace *t, int *this_depth, int *depth) * Load a blktrace file by reading all the blk_io_trace entries, and storing * them as io_pieces like the fio text version would do. */ -bool load_blktrace(struct thread_data *td, const char *filename, int need_swap) +bool init_blktrace_read(struct thread_data *td, const char *filename, int need_swap) +{ + int old_state; + + td->io_log_rfile = fopen(filename, "rb"); + if (!td->io_log_rfile) { + td_verror(td, errno, "open blktrace file"); + goto err; + } + td->io_log_blktrace_swap = need_swap; + td->o.size = 0; + + free_release_files(td); + + old_state = td_bump_runstate(td, TD_SETTING_UP); + + if (!read_blktrace(td)) { + goto err; + } + + td_restore_runstate(td, old_state); + + if (!td->files_index) { + log_err("fio: did not find replay device(s)\n"); + return false; + } + + return true; + +err: + if (td->io_log_rfile) { + fclose(td->io_log_rfile); + td->io_log_rfile = NULL; + } + return false; +} + +bool read_blktrace(struct thread_data* td) { struct blk_io_trace t; unsigned long ios[DDIR_RWDIR_SYNC_CNT] = { }; - unsigned int rw_bs[DDIR_RWDIR_CNT] = { }; + unsigned long long rw_bs[DDIR_RWDIR_CNT] = { }; unsigned long skipped_writes; - FILE *f; - int i, old_state, max_depth; + FILE *f = td->io_log_rfile; + int i, max_depth; struct fio_file *fiof; int this_depth[DDIR_RWDIR_CNT] = { }; int depth[DDIR_RWDIR_CNT] = { }; + int64_t items_to_fetch = 0; - f = fopen(filename, "rb"); - if (!f) { - td_verror(td, errno, "open blktrace file"); - return false; + if (td->o.read_iolog_chunked) { + items_to_fetch = iolog_items_to_fetch(td); + if (!items_to_fetch) + return true; } - old_state = td_bump_runstate(td, TD_SETTING_UP); - - td->o.size = 0; skipped_writes = 0; do { int ret = fread(&t, 1, sizeof(t), f); @@ -429,7 +469,7 @@ bool load_blktrace(struct thread_data *td, const char *filename, int need_swap) break; } - if (need_swap) + if (td->io_log_blktrace_swap) byteswap_trace(&t); if ((t.magic & 0xffffff00) != BLK_IO_TRACE_MAGIC) { @@ -462,21 +502,53 @@ bool load_blktrace(struct thread_data *td, const char *filename, int need_swap) } } - handle_trace(td, &t, ios, rw_bs); - } while (1); + if (!queue_trace(td, &t, ios, rw_bs)) + continue; - for_each_file(td, fiof, i) - trace_add_open_close_event(td, fiof->fileno, FIO_LOG_CLOSE_FILE); + if (td->o.read_iolog_chunked) { + td->io_log_current++; + items_to_fetch--; + if (items_to_fetch == 0) + break; + } + } while (1); - fclose(f); + if (td->o.read_iolog_chunked) { + td->io_log_highmark = td->io_log_current; + td->io_log_checkmark = (td->io_log_highmark + 1) / 2; + fio_gettime(&td->io_log_highmark_time, NULL); + } - td_restore_runstate(td, old_state); + if (skipped_writes) + log_err("fio: %s skips replay of %lu writes due to read-only\n", + td->o.name, skipped_writes); - if (!td->files_index) { - log_err("fio: did not find replay device(s)\n"); - return false; + if (td->o.read_iolog_chunked) { + if (td->io_log_current == 0) { + return false; + } + td->o.td_ddir = TD_DDIR_RW; + if ((rw_bs[DDIR_READ] > td->o.max_bs[DDIR_READ] || + rw_bs[DDIR_WRITE] > td->o.max_bs[DDIR_WRITE] || + rw_bs[DDIR_TRIM] > td->o.max_bs[DDIR_TRIM]) && + td->orig_buffer) + { + td->o.max_bs[DDIR_READ] = max(td->o.max_bs[DDIR_READ], rw_bs[DDIR_READ]); + td->o.max_bs[DDIR_WRITE] = max(td->o.max_bs[DDIR_WRITE], rw_bs[DDIR_WRITE]); + td->o.max_bs[DDIR_TRIM] = max(td->o.max_bs[DDIR_TRIM], rw_bs[DDIR_TRIM]); + io_u_quiesce(td); + free_io_mem(td); + init_io_u_buffers(td); + } + return true; } + for_each_file(td, fiof, i) + trace_add_open_close_event(td, fiof->fileno, FIO_LOG_CLOSE_FILE); + + fclose(td->io_log_rfile); + td->io_log_rfile = NULL; + /* * For stacked devices, we don't always get a COMPLETE event so * the depth grows to insane values. Limit it to something sane(r). @@ -490,10 +562,6 @@ bool load_blktrace(struct thread_data *td, const char *filename, int need_swap) max_depth = max(depth[i], max_depth); } - if (skipped_writes) - log_err("fio: %s skips replay of %lu writes due to read-only\n", - td->o.name, skipped_writes); - if (!ios[DDIR_READ] && !ios[DDIR_WRITE] && !ios[DDIR_TRIM] && !ios[DDIR_SYNC]) { log_err("fio: found no ios in blktrace data\n"); diff --git a/blktrace.h b/blktrace.h index b2ebdba3b0..c53b717ba4 100644 --- a/blktrace.h +++ b/blktrace.h @@ -20,7 +20,9 @@ struct blktrace_cursor { }; bool is_blktrace(const char *, int *); -bool load_blktrace(struct thread_data *, const char *, int); +bool init_blktrace_read(struct thread_data *, const char *, int); +bool read_blktrace(struct thread_data* td); + int merge_blktrace_iologs(struct thread_data *td); #else @@ -30,12 +32,18 @@ static inline bool is_blktrace(const char *fname, int *need_swap) return false; } -static inline bool load_blktrace(struct thread_data *td, const char *fname, +static inline bool init_blktrace_read(struct thread_data *td, const char *fname, int need_swap) { return false; } +static inline bool read_blktrace(struct thread_data* td) +{ + return false; +} + + static inline int merge_blktrace_iologs(struct thread_data *td) { return false; diff --git a/fio.h b/fio.h index 6bb21ebb2a..5c68ad80b7 100644 --- a/fio.h +++ b/fio.h @@ -428,6 +428,7 @@ struct thread_data { struct flist_head io_log_list; FILE *io_log_rfile; unsigned int io_log_blktrace; + unsigned int io_log_blktrace_swap; unsigned int io_log_current; unsigned int io_log_checkmark; unsigned int io_log_highmark; diff --git a/iolog.c b/iolog.c index 3d4646a87a..5a41e93fa2 100644 --- a/iolog.c +++ b/iolog.c @@ -152,10 +152,15 @@ int read_iolog_get(struct thread_data *td, struct io_u *io_u) while (!flist_empty(&td->io_log_list)) { int ret; - if (!td->io_log_blktrace && td->o.read_iolog_chunked) { + if (td->o.read_iolog_chunked) { if (td->io_log_checkmark == td->io_log_current) { - if (!read_iolog2(td)) - return 1; + if (td->io_log_blktrace) { + if (!read_blktrace(td)) + return 1; + } else { + if (!read_iolog2(td)) + return 1; + } } td->io_log_current--; } @@ -709,7 +714,7 @@ bool init_iolog(struct thread_data *td) */ if (is_blktrace(fname, &need_swap)) { td->io_log_blktrace = 1; - ret = load_blktrace(td, fname, need_swap); + ret = init_blktrace_read(td, fname, need_swap); } else { td->io_log_blktrace = 0; ret = init_iolog_read(td, fname); From d9d60dbf49bfa62b5bd26f078533088f1de17b6f Mon Sep 17 00:00:00 2001 From: Lukas Straub Date: Wed, 19 Jan 2022 21:14:26 +0000 Subject: [PATCH 0044/1097] linux-dev-lookup.c: Put the check for replay_redirect in the beginning The machine may not have any block device nodes (like my dev container) which makes this function fail despite replay_redirect being set. Move the check to the beginning to fix this. Signed-off-by: Lukas Straub Link: https://lore.kernel.org/r/0dd4b6407f7b7f5f15f1fcad409554ff339ffca1.1642626314.git.lukasstraub2@web.de Signed-off-by: Jens Axboe --- oslib/linux-dev-lookup.c | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/oslib/linux-dev-lookup.c b/oslib/linux-dev-lookup.c index 1dda93f2a0..4335faf99b 100644 --- a/oslib/linux-dev-lookup.c +++ b/oslib/linux-dev-lookup.c @@ -16,6 +16,16 @@ int blktrace_lookup_device(const char *redirect, char *path, unsigned int maj, int found = 0; DIR *D; + /* + * If replay_redirect is set then always return this device + * upon lookup which overrides the device lookup based on + * major minor in the actual blktrace + */ + if (redirect) { + strcpy(path, redirect); + return 1; + } + D = opendir(path); if (!D) return 0; @@ -44,17 +54,6 @@ int blktrace_lookup_device(const char *redirect, char *path, unsigned int maj, if (!S_ISBLK(st.st_mode)) continue; - /* - * If replay_redirect is set then always return this device - * upon lookup which overrides the device lookup based on - * major minor in the actual blktrace - */ - if (redirect) { - strcpy(path, redirect); - found = 1; - break; - } - if (maj == major(st.st_rdev) && min == minor(st.st_rdev)) { strcpy(path, full_path); found = 1; From d0b12843fd441460bd9a0172169b028a74f76b10 Mon Sep 17 00:00:00 2001 From: Lukas Straub Date: Wed, 19 Jan 2022 21:14:30 +0000 Subject: [PATCH 0045/1097] blktrace.c: Don't hardcode direct-io This is unexpected if one wants to test performance of a standard filesystem (by pointing replay_redirect to a standard file) with buffered io. Signed-off-by: Lukas Straub Link: https://lore.kernel.org/r/239cc0c47c346408607772fb423aa5745a3779dd.1642626314.git.lukasstraub2@web.de Signed-off-by: Jens Axboe --- blktrace.c | 8 -------- 1 file changed, 8 deletions(-) diff --git a/blktrace.c b/blktrace.c index f1dbd1a6fd..7682a4d5dd 100644 --- a/blktrace.c +++ b/blktrace.c @@ -582,14 +582,6 @@ bool read_blktrace(struct thread_data* td) td->o.max_bs[DDIR_TRIM] = rw_bs[DDIR_TRIM]; } - /* - * We need to do direct/raw ios to the device, to avoid getting - * read-ahead in our way. But only do so if the minimum block size - * is a multiple of 4k, otherwise we don't know if it's safe to do so. - */ - if (!fio_option_is_set(&td->o, odirect) && !(td_min_bs(td) & 4095)) - td->o.odirect = 1; - /* * If depth wasn't manually set, use probed depth */ From f36bd1341690e0c63718c32465e173874bf56727 Mon Sep 17 00:00:00 2001 From: Lukas Straub Date: Wed, 19 Jan 2022 21:14:33 +0000 Subject: [PATCH 0046/1097] blktrace.c: Don't sleep indefinitely if there is a wrong timestamp Each of my traces have a single entry with a wrong timestamp that causes a underflow followed by a infinite sleep. Fix this by checking for underflow. Signed-off-by: Lukas Straub Link: https://lore.kernel.org/r/a19b7ea899093c4c0ed98d2d9a310f2f0f01fddd.1642626314.git.lukasstraub2@web.de Signed-off-by: Jens Axboe --- blktrace.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blktrace.c b/blktrace.c index 7682a4d5dd..1faa83bf03 100644 --- a/blktrace.c +++ b/blktrace.c @@ -307,7 +307,7 @@ static bool queue_trace(struct thread_data *td, struct blk_io_trace *t, return false; if (!(t->action & BLK_TC_ACT(BLK_TC_NOTIFY))) { - if (!last_ttime || td->o.no_stall) + if (!last_ttime || td->o.no_stall || t->time < last_ttime) delay = 0; else if (td->o.replay_time_scale == 100) delay = t->time - last_ttime; From 661592d44383069fe949ec68154c2d8079cf02a0 Mon Sep 17 00:00:00 2001 From: Lukas Straub Date: Wed, 19 Jan 2022 21:14:36 +0000 Subject: [PATCH 0047/1097] blktrace.c: Make thread-safe by removing local static variables Local static variables are not thread-safe. Make the functions in blktrace.c safe by replacing them. Signed-off-by: Lukas Straub Link: https://lore.kernel.org/r/b805bb3f6acf6c5b4d8811872c62af939aac62a7.1642626314.git.lukasstraub2@web.de Signed-off-by: Jens Axboe --- blktrace.c | 63 ++++++++++++++++++++++++++++++++---------------------- fio.h | 1 + 2 files changed, 38 insertions(+), 26 deletions(-) diff --git a/blktrace.c b/blktrace.c index 1faa83bf03..e180476589 100644 --- a/blktrace.c +++ b/blktrace.c @@ -13,6 +13,12 @@ #include "blktrace_api.h" #include "oslib/linux-dev-lookup.h" +struct file_cache { + unsigned int maj; + unsigned int min; + unsigned int fileno; +}; + /* * Just discard the pdu by seeking past it. */ @@ -87,28 +93,28 @@ static void trace_add_open_close_event(struct thread_data *td, int fileno, enum flist_add_tail(&ipo->list, &td->io_log_list); } -static int trace_add_file(struct thread_data *td, __u32 device) +static int trace_add_file(struct thread_data *td, __u32 device, + struct file_cache *cache) { - static unsigned int last_maj, last_min, last_fileno; unsigned int maj = FMAJOR(device); unsigned int min = FMINOR(device); struct fio_file *f; char dev[256]; unsigned int i; - if (last_maj == maj && last_min == min) - return last_fileno; + if (cache->maj == maj && cache->min == min) + return cache->fileno; - last_maj = maj; - last_min = min; + cache->maj = maj; + cache->min = min; /* * check for this file in our list */ for_each_file(td, f, i) if (f->major == maj && f->minor == min) { - last_fileno = f->fileno; - return last_fileno; + cache->fileno = f->fileno; + return cache->fileno; } strcpy(dev, "/dev"); @@ -128,10 +134,10 @@ static int trace_add_file(struct thread_data *td, __u32 device) td->files[fileno]->major = maj; td->files[fileno]->minor = min; trace_add_open_close_event(td, fileno, FIO_LOG_OPEN_FILE); - last_fileno = fileno; + cache->fileno = fileno; } - return last_fileno; + return cache->fileno; } static void t_bytes_align(struct thread_options *o, struct blk_io_trace *t) @@ -195,7 +201,8 @@ static bool handle_trace_notify(struct blk_io_trace *t) static bool handle_trace_discard(struct thread_data *td, struct blk_io_trace *t, unsigned long long ttime, - unsigned long *ios, unsigned long long *bs) + unsigned long *ios, unsigned long long *bs, + struct file_cache *cache) { struct io_piece *ipo; int fileno; @@ -205,7 +212,7 @@ static bool handle_trace_discard(struct thread_data *td, ipo = calloc(1, sizeof(*ipo)); init_ipo(ipo); - fileno = trace_add_file(td, t->device); + fileno = trace_add_file(td, t->device, cache); ios[DDIR_TRIM]++; if (t->bytes > bs[DDIR_TRIM]) @@ -238,12 +245,12 @@ static void dump_trace(struct blk_io_trace *t) static bool handle_trace_fs(struct thread_data *td, struct blk_io_trace *t, unsigned long long ttime, unsigned long *ios, - unsigned long long *bs) + unsigned long long *bs, struct file_cache *cache) { int rw; int fileno; - fileno = trace_add_file(td, t->device); + fileno = trace_add_file(td, t->device, cache); rw = (t->action & BLK_TC_ACT(BLK_TC_WRITE)) != 0; @@ -271,7 +278,8 @@ static bool handle_trace_fs(struct thread_data *td, struct blk_io_trace *t, } static bool handle_trace_flush(struct thread_data *td, struct blk_io_trace *t, - unsigned long long ttime, unsigned long *ios) + unsigned long long ttime, unsigned long *ios, + struct file_cache *cache) { struct io_piece *ipo; int fileno; @@ -281,7 +289,7 @@ static bool handle_trace_flush(struct thread_data *td, struct blk_io_trace *t, ipo = calloc(1, sizeof(*ipo)); init_ipo(ipo); - fileno = trace_add_file(td, t->device); + fileno = trace_add_file(td, t->device, cache); ipo->delay = ttime / 1000; ipo->ddir = DDIR_SYNC; @@ -298,28 +306,29 @@ static bool handle_trace_flush(struct thread_data *td, struct blk_io_trace *t, * due to internal workings of the block layer. */ static bool queue_trace(struct thread_data *td, struct blk_io_trace *t, - unsigned long *ios, unsigned long long *bs) + unsigned long *ios, unsigned long long *bs, + struct file_cache *cache) { - static unsigned long long last_ttime; + unsigned long long *last_ttime = &td->io_log_blktrace_last_ttime; unsigned long long delay = 0; if ((t->action & 0xffff) != __BLK_TA_QUEUE) return false; if (!(t->action & BLK_TC_ACT(BLK_TC_NOTIFY))) { - if (!last_ttime || td->o.no_stall || t->time < last_ttime) + if (!*last_ttime || td->o.no_stall || t->time < *last_ttime) delay = 0; else if (td->o.replay_time_scale == 100) - delay = t->time - last_ttime; + delay = t->time - *last_ttime; else { - double tmp = t->time - last_ttime; + double tmp = t->time - *last_ttime; double scale; scale = (double) 100.0 / (double) td->o.replay_time_scale; tmp *= scale; delay = tmp; } - last_ttime = t->time; + *last_ttime = t->time; } t_bytes_align(&td->o, t); @@ -327,11 +336,11 @@ static bool queue_trace(struct thread_data *td, struct blk_io_trace *t, if (t->action & BLK_TC_ACT(BLK_TC_NOTIFY)) return handle_trace_notify(t); else if (t->action & BLK_TC_ACT(BLK_TC_DISCARD)) - return handle_trace_discard(td, t, delay, ios, bs); + return handle_trace_discard(td, t, delay, ios, bs, cache); else if (t->action & BLK_TC_ACT(BLK_TC_FLUSH)) - return handle_trace_flush(td, t, delay, ios); + return handle_trace_flush(td, t, delay, ios, cache); else - return handle_trace_fs(td, t, delay, ios, bs); + return handle_trace_fs(td, t, delay, ios, bs, cache); } static void byteswap_trace(struct blk_io_trace *t) @@ -409,6 +418,7 @@ bool init_blktrace_read(struct thread_data *td, const char *filename, int need_s goto err; } td->io_log_blktrace_swap = need_swap; + td->io_log_blktrace_last_ttime = 0; td->o.size = 0; free_release_files(td); @@ -439,6 +449,7 @@ bool init_blktrace_read(struct thread_data *td, const char *filename, int need_s bool read_blktrace(struct thread_data* td) { struct blk_io_trace t; + struct file_cache cache = { }; unsigned long ios[DDIR_RWDIR_SYNC_CNT] = { }; unsigned long long rw_bs[DDIR_RWDIR_CNT] = { }; unsigned long skipped_writes; @@ -502,7 +513,7 @@ bool read_blktrace(struct thread_data* td) } } - if (!queue_trace(td, &t, ios, rw_bs)) + if (!queue_trace(td, &t, ios, rw_bs, &cache)) continue; if (td->o.read_iolog_chunked) { diff --git a/fio.h b/fio.h index 5c68ad80b7..1ea3d064c9 100644 --- a/fio.h +++ b/fio.h @@ -429,6 +429,7 @@ struct thread_data { FILE *io_log_rfile; unsigned int io_log_blktrace; unsigned int io_log_blktrace_swap; + unsigned long long io_log_blktrace_last_ttime; unsigned int io_log_current; unsigned int io_log_checkmark; unsigned int io_log_highmark; From 3a3e5c6e7606e727df1788a73d04db56d77ba00d Mon Sep 17 00:00:00 2001 From: Lukas Straub Date: Wed, 19 Jan 2022 21:14:40 +0000 Subject: [PATCH 0048/1097] iolog.c: Fix memory leak for blkparse case init_blkparse_read (load_blkparse previously) didn't free the filename. Fix this by freeing it in the init_iolog function and handling it for both init_iolog_read and init_blkparse_read. Signed-off-by: Lukas Straub Link: https://lore.kernel.org/r/e4acf183ab789b7284bfa96089ebe1256e15f98d.1642626314.git.lukasstraub2@web.de Signed-off-by: Jens Axboe --- iolog.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/iolog.c b/iolog.c index 5a41e93fa2..a2cf0c1ccd 100644 --- a/iolog.c +++ b/iolog.c @@ -631,8 +631,6 @@ static bool init_iolog_read(struct thread_data *td, char *fname) } else f = fopen(fname, "r"); - free(fname); - if (!f) { perror("fopen read iolog"); return false; @@ -719,6 +717,7 @@ bool init_iolog(struct thread_data *td) td->io_log_blktrace = 0; ret = init_iolog_read(td, fname); } + free(fname); } else if (td->o.write_iolog_file) ret = init_iolog_write(td); else From 82250ffc96497652b7f6f9b1b707ae1bee4d8f89 Mon Sep 17 00:00:00 2001 From: ben-ihelputech <50592898+ben-ihelputech@users.noreply.github.com> Date: Fri, 21 Jan 2022 09:01:13 -0600 Subject: [PATCH 0049/1097] Update README to markdown format - Updated README to README.md to make it look nicer when rendered on Github. --- README => README.md | 78 ++++++++++++++++++++++----------------------- 1 file changed, 38 insertions(+), 40 deletions(-) rename README => README.md (94%) diff --git a/README b/README.md similarity index 94% rename from README rename to README.md index d566fae3de..b10b1688ff 100644 --- a/README +++ b/README.md @@ -1,5 +1,5 @@ -Overview and history --------------------- +# Fio README +## Overview and history Fio was originally written to save me the hassle of writing special test case programs when I wanted to test a specific workload, either for performance @@ -22,14 +22,13 @@ that setting is given. The typical use of fio is to write a job file matching the I/O load one wants to simulate. -Source ------- +## Source Fio resides in a git repo, the canonical place is: git://git.kernel.dk/fio.git -When inside a corporate firewall, git:// URL sometimes does not work. +When inside a corporate firewall, `git://` URL sometimes does not work. If git:// does not work, use the http protocol instead: http://git.kernel.dk/fio.git @@ -55,8 +54,8 @@ or https://github.com/axboe/fio.git -Mailing list ------------- +## Mailing list + The fio project mailing list is meant for anything related to fio including general discussion, bug reporting, questions, and development. For bug reporting, @@ -81,8 +80,8 @@ and archives for the old list can be found here: http://maillist.kernel.dk/fio-devel/ -Author ------- +## Author + Fio was written by Jens Axboe to enable flexible testing of the Linux I/O subsystem and schedulers. He got tired of writing specific test @@ -92,56 +91,55 @@ benchmark/test tools out there weren't flexible enough to do what he wanted. Jens Axboe 20060905 -Binary packages ---------------- +## Binary packages -Debian: +**Debian:** Starting with Debian "Squeeze", fio packages are part of the official Debian repository. http://packages.debian.org/search?keywords=fio . -Ubuntu: +**Ubuntu:** Starting with Ubuntu 10.04 LTS (aka "Lucid Lynx"), fio packages are part of the Ubuntu "universe" repository. http://packages.ubuntu.com/search?keywords=fio . -Red Hat, Fedora, CentOS & Co: +**Red Hat, Fedora, CentOS & Co:** Starting with Fedora 9/Extra Packages for Enterprise Linux 4, fio packages are part of the Fedora/EPEL repositories. https://apps.fedoraproject.org/packages/fio . -Mandriva: +**Mandriva:** Mandriva has integrated fio into their package repository, so installing on that distro should be as easy as typing ``urpmi fio``. -Arch Linux: +**Arch Linux:** An Arch Linux package is provided under the Community sub-repository: https://www.archlinux.org/packages/?sort=&q=fio -Solaris: +**Solaris:** Packages for Solaris are available from OpenCSW. Install their pkgutil tool (http://www.opencsw.org/get-it/pkgutil/) and then install fio via ``pkgutil -i fio``. -Windows: +**Windows:** Rebecca Cran has fio packages for Windows at https://bsdio.com/fio/ . The latest builds for Windows can also be grabbed from https://ci.appveyor.com/project/axboe/fio by clicking the latest x86 or x64 build, then selecting the ARTIFACTS tab. -BSDs: +**BSDs:** Packages for BSDs may be available from their binary package repositories. Look for a package "fio" using their binary package managers. -Building --------- - -Just type:: +## Building - $ ./configure - $ make - $ make install +Just type:: +``` +./configure +make +make install +``` Note that GNU make is required. On BSDs it's available from devel/gmake within ports directory; on Solaris it's in the SUNWgmake package. On platforms where GNU make isn't the default, type ``gmake`` instead of ``make``. @@ -155,18 +153,18 @@ to be installed. gfio isn't built automatically and can be enabled with a ``--enable-gfio`` option to configure. To build fio with a cross-compiler:: - - $ make clean - $ make CROSS_COMPILE=/path/to/toolchain/prefix - +``` +make clean +make CROSS_COMPILE=/path/to/toolchain/prefix +``` Configure will attempt to determine the target platform automatically. It's possible to build fio for ESX as well, use the ``--esx`` switch to configure. -Windows -~~~~~~~ +## Windows + The minimum versions of Windows for building/runing fio are Windows 7/Windows Server 2008 R2. On Windows, Cygwin (https://www.cygwin.com/) is required in @@ -174,7 +172,7 @@ order to build fio. To create an MSI installer package install WiX from https://wixtoolset.org and run :file:`dobuild.cmd` from the :file:`os/windows` directory. -How to compile fio on 64-bit Windows: +### How to compile fio on 64-bit Windows: 1. Install Cygwin (http://www.cygwin.com/). Install **make** and all packages starting with **mingw64-x86_64**. Ensure @@ -196,21 +194,21 @@ https://github.com/mintty/mintty/wiki/Tips#inputoutput-interaction-with-alien-pr for details). -Documentation -~~~~~~~~~~~~~ +## Documentation + Fio uses Sphinx_ to generate documentation from the reStructuredText_ files. To build HTML formatted documentation run ``make -C doc html`` and direct your browser to :file:`./doc/output/html/index.html`. To build manual page run ``make -C doc man`` and then ``man doc/output/man/fio.1``. To see what other output formats are supported run ``make -C doc help``. - +``` .. _reStructuredText: http://www.sphinx-doc.org/rest.html .. _Sphinx: http://www.sphinx-doc.org +``` +## Platforms -Platforms ---------- Fio works on (at least) Linux, Solaris, AIX, HP-UX, OSX, NetBSD, OpenBSD, Windows, FreeBSD, and DragonFly. Some features and/or options may only be @@ -252,8 +250,8 @@ POSIX aio should work now. To make the change permanent:: posix_aio0 changed -Running fio ------------ +## Running fio + Running fio is normally the easiest part - you just give it the job file (or job files) as parameters:: From e662bc9815de906e3498f4261ec5a28481872a18 Mon Sep 17 00:00:00 2001 From: Lukasz Dorau Date: Mon, 24 Jan 2022 23:56:47 +0100 Subject: [PATCH 0050/1097] rpma: RPMA engine requires librpma>=v0.10.0 with rpma_mr_advise() Signed-off-by: Lukasz Dorau --- configure | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/configure b/configure index 84ccce040e..0efde7d6a8 100755 --- a/configure +++ b/configure @@ -955,17 +955,16 @@ print_config "rdmacm" "$rdmacm" ########################################## # librpma probe +# The librpma engine requires librpma>=v0.10.0 with rpma_mr_advise(). if test "$librpma" != "yes" ; then librpma="no" fi cat > $TMPC << EOF -#include #include -int main(int argc, char **argv) +int main(void) { - enum rpma_conn_event event = RPMA_CONN_REJECTED; - (void) event; /* unused */ - rpma_log_set_threshold(RPMA_LOG_THRESHOLD, RPMA_LOG_LEVEL_INFO); + void *ptr = rpma_mr_advise; + (void) ptr; /* unused */ return 0; } EOF From a6becc33deec29a715c3326c2286082cdc60a197 Mon Sep 17 00:00:00 2001 From: "Wang, Long" Date: Tue, 25 Jan 2022 10:18:14 +0100 Subject: [PATCH 0051/1097] rpma: add support for File System DAX File System DAX is handled in a different way than Device DAX: 1) In case of File System DAX, each thread uses a separate file from this file system and no offset is needed. In case of Device DAX, each thread uses a separate offset within the same Device DAX. 2) File System DAX requires rpma_mr_advise(3)(ibv_advise_mr(3)) to be called for the registered memory to avoid page faults and degraded performance. Ref: https://github.com/axboe/fio/issues/1238 Signed-off-by: Wang, Long --- engines/librpma_fio.c | 44 ++++++++++++++++++++++++++++++++----------- engines/librpma_fio.h | 2 +- 2 files changed, 34 insertions(+), 12 deletions(-) diff --git a/engines/librpma_fio.c b/engines/librpma_fio.c index 3d605ed6c3..9d6ebf38ee 100644 --- a/engines/librpma_fio.c +++ b/engines/librpma_fio.c @@ -108,7 +108,7 @@ char *librpma_fio_allocate_dram(struct thread_data *td, size_t size, return mem_ptr; } -char *librpma_fio_allocate_pmem(struct thread_data *td, const char *filename, +char *librpma_fio_allocate_pmem(struct thread_data *td, struct fio_file *f, size_t size, struct librpma_fio_mem *mem) { size_t size_mmap = 0; @@ -122,18 +122,24 @@ char *librpma_fio_allocate_pmem(struct thread_data *td, const char *filename, return NULL; } - ws_offset = (td->thread_number - 1) * size; + if (f->filetype == FIO_TYPE_CHAR) { + /* Each thread uses a separate offset within DeviceDAX. */ + ws_offset = (td->thread_number - 1) * size; + } else { + /* Each thread uses a separate FileSystemDAX file. No offset is needed. */ + ws_offset = 0; + } - if (!filename) { + if (!f->file_name) { log_err("fio: filename is not set\n"); return NULL; } /* map the file */ - mem_ptr = pmem_map_file(filename, 0 /* len */, 0 /* flags */, + mem_ptr = pmem_map_file(f->file_name, 0 /* len */, 0 /* flags */, 0 /* mode */, &size_mmap, &is_pmem); if (mem_ptr == NULL) { - log_err("fio: pmem_map_file(%s) failed\n", filename); + log_err("fio: pmem_map_file(%s) failed\n", f->file_name); /* pmem_map_file() sets errno on failure */ td_verror(td, errno, "pmem_map_file"); return NULL; @@ -142,7 +148,7 @@ char *librpma_fio_allocate_pmem(struct thread_data *td, const char *filename, /* pmem is expected */ if (!is_pmem) { log_err("fio: %s is not located in persistent memory\n", - filename); + f->file_name); goto err_unmap; } @@ -150,12 +156,12 @@ char *librpma_fio_allocate_pmem(struct thread_data *td, const char *filename, if (size_mmap < ws_offset + size) { log_err( "fio: %s is too small to handle so many threads (%zu < %zu)\n", - filename, size_mmap, ws_offset + size); + f->file_name, size_mmap, ws_offset + size); goto err_unmap; } log_info("fio: size of memory mapped from the file %s: %zu\n", - filename, size_mmap); + f->file_name, size_mmap); mem->mem_ptr = mem_ptr; mem->size_mmap = size_mmap; @@ -893,6 +899,7 @@ int librpma_fio_server_open_file(struct thread_data *td, struct fio_file *f, size_t mem_size = td->o.size; size_t mr_desc_size; void *ws_ptr; + bool is_dram; int usage_mem_type; int ret; @@ -910,14 +917,14 @@ int librpma_fio_server_open_file(struct thread_data *td, struct fio_file *f, return -1; } - if (strcmp(f->file_name, "malloc") == 0) { + is_dram = !strcmp(f->file_name, "malloc"); + if (is_dram) { /* allocation from DRAM using posix_memalign() */ ws_ptr = librpma_fio_allocate_dram(td, mem_size, &csd->mem); usage_mem_type = RPMA_MR_USAGE_FLUSH_TYPE_VISIBILITY; } else { /* allocation from PMEM using pmem_map_file() */ - ws_ptr = librpma_fio_allocate_pmem(td, f->file_name, - mem_size, &csd->mem); + ws_ptr = librpma_fio_allocate_pmem(td, f, mem_size, &csd->mem); usage_mem_type = RPMA_MR_USAGE_FLUSH_TYPE_PERSISTENT; } @@ -934,6 +941,21 @@ int librpma_fio_server_open_file(struct thread_data *td, struct fio_file *f, goto err_free; } + if (!is_dram && f->filetype == FIO_TYPE_FILE) { + ret = rpma_mr_advise(mr, 0, mem_size, + IBV_ADVISE_MR_ADVICE_PREFETCH_WRITE, + IBV_ADVISE_MR_FLAG_FLUSH); + if (ret) { + librpma_td_verror(td, ret, "rpma_mr_advise"); + /* an invalid argument is an error */ + if (ret == RPMA_E_INVAL) + goto err_mr_dereg; + + /* log_err used instead of log_info to avoid corruption of the JSON output */ + log_err("Note: having rpma_mr_advise(3) failed because of RPMA_E_NOSUPP or RPMA_E_PROVIDER may come with a performance penalty, but it is not a blocker for running the benchmark.\n"); + } + } + /* get size of the memory region's descriptor */ if ((ret = rpma_mr_get_descriptor_size(mr, &mr_desc_size))) { librpma_td_verror(td, ret, "rpma_mr_get_descriptor_size"); diff --git a/engines/librpma_fio.h b/engines/librpma_fio.h index fb89d99d69..2c507e9c5c 100644 --- a/engines/librpma_fio.h +++ b/engines/librpma_fio.h @@ -77,7 +77,7 @@ struct librpma_fio_mem { char *librpma_fio_allocate_dram(struct thread_data *td, size_t size, struct librpma_fio_mem *mem); -char *librpma_fio_allocate_pmem(struct thread_data *td, const char *filename, +char *librpma_fio_allocate_pmem(struct thread_data *td, struct fio_file *f, size_t size, struct librpma_fio_mem *mem); void librpma_fio_free(struct librpma_fio_mem *mem); From 04525c29025b075f4c1d1220a9705cd4925f4189 Mon Sep 17 00:00:00 2001 From: Eric Sandeen Date: Tue, 25 Jan 2022 12:57:39 -0600 Subject: [PATCH 0052/1097] t/io_uring: link with libaio when necessary When CONFIG_LIBAIO is enabled, we need t/io_uring to link with it. (libaio_LIBS only affects the aio engine, AFAICT.) Signed-off-by: Eric Sandeen Signed-off-by: Jens Axboe --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index 5d17bcab90..76eb0d7d5d 100644 --- a/Makefile +++ b/Makefile @@ -99,6 +99,7 @@ endif ifdef CONFIG_LIBAIO libaio_SRCS = engines/libaio.c cmdprio_SRCS = engines/cmdprio.c + LIBS += -laio libaio_LIBS = -laio ENGINES += libaio endif From 2b3d4a6a924e0aa82654d3b96fb134085af7a98a Mon Sep 17 00:00:00 2001 From: Eric Sandeen Date: Wed, 26 Jan 2022 08:49:45 -0600 Subject: [PATCH 0053/1097] fio: use LDFLAGS when linking dynamic engines Without this, locally defined LDFLAGS won't be applied when linking the dynamically loaded IO engines. Signed-off-by: Eric Sandeen Signed-off-by: Jens Axboe --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 76eb0d7d5d..00e7953918 100644 --- a/Makefile +++ b/Makefile @@ -295,7 +295,7 @@ define engine_template = $(1)_OBJS := $$($(1)_SRCS:.c=.o) $$($(1)_OBJS): CFLAGS := -fPIC $$($(1)_CFLAGS) $(CFLAGS) engines/fio-$(1).so: $$($(1)_OBJS) - $$(QUIET_LINK)$(CC) -shared -rdynamic -fPIC -Wl,-soname,fio-$(1).so.1 -o $$@ $$< $$($(1)_LIBS) + $$(QUIET_LINK)$(CC) $(DYNAMIC) -shared -rdynamic -fPIC -Wl,-soname,fio-$(1).so.1 -o $$@ $$< $$($(1)_LIBS) ENGS_OBJS += engines/fio-$(1).so endef else # !CONFIG_DYNAMIC_ENGINES From 3e6c7afba1098a057c275e0a415085b5d17f88db Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Fri, 28 Jan 2022 14:29:26 -0500 Subject: [PATCH 0054/1097] Revert "Update README to markdown format" This reverts commit 82250ffc96497652b7f6f9b1b707ae1bee4d8f89. Signed-off-by: Vincent Fu --- README.md => README | 78 +++++++++++++++++++++++---------------------- 1 file changed, 40 insertions(+), 38 deletions(-) rename README.md => README (94%) diff --git a/README.md b/README similarity index 94% rename from README.md rename to README index b10b1688ff..d566fae3de 100644 --- a/README.md +++ b/README @@ -1,5 +1,5 @@ -# Fio README -## Overview and history +Overview and history +-------------------- Fio was originally written to save me the hassle of writing special test case programs when I wanted to test a specific workload, either for performance @@ -22,13 +22,14 @@ that setting is given. The typical use of fio is to write a job file matching the I/O load one wants to simulate. -## Source +Source +------ Fio resides in a git repo, the canonical place is: git://git.kernel.dk/fio.git -When inside a corporate firewall, `git://` URL sometimes does not work. +When inside a corporate firewall, git:// URL sometimes does not work. If git:// does not work, use the http protocol instead: http://git.kernel.dk/fio.git @@ -54,8 +55,8 @@ or https://github.com/axboe/fio.git -## Mailing list - +Mailing list +------------ The fio project mailing list is meant for anything related to fio including general discussion, bug reporting, questions, and development. For bug reporting, @@ -80,8 +81,8 @@ and archives for the old list can be found here: http://maillist.kernel.dk/fio-devel/ -## Author - +Author +------ Fio was written by Jens Axboe to enable flexible testing of the Linux I/O subsystem and schedulers. He got tired of writing specific test @@ -91,55 +92,56 @@ benchmark/test tools out there weren't flexible enough to do what he wanted. Jens Axboe 20060905 -## Binary packages +Binary packages +--------------- -**Debian:** +Debian: Starting with Debian "Squeeze", fio packages are part of the official Debian repository. http://packages.debian.org/search?keywords=fio . -**Ubuntu:** +Ubuntu: Starting with Ubuntu 10.04 LTS (aka "Lucid Lynx"), fio packages are part of the Ubuntu "universe" repository. http://packages.ubuntu.com/search?keywords=fio . -**Red Hat, Fedora, CentOS & Co:** +Red Hat, Fedora, CentOS & Co: Starting with Fedora 9/Extra Packages for Enterprise Linux 4, fio packages are part of the Fedora/EPEL repositories. https://apps.fedoraproject.org/packages/fio . -**Mandriva:** +Mandriva: Mandriva has integrated fio into their package repository, so installing on that distro should be as easy as typing ``urpmi fio``. -**Arch Linux:** +Arch Linux: An Arch Linux package is provided under the Community sub-repository: https://www.archlinux.org/packages/?sort=&q=fio -**Solaris:** +Solaris: Packages for Solaris are available from OpenCSW. Install their pkgutil tool (http://www.opencsw.org/get-it/pkgutil/) and then install fio via ``pkgutil -i fio``. -**Windows:** +Windows: Rebecca Cran has fio packages for Windows at https://bsdio.com/fio/ . The latest builds for Windows can also be grabbed from https://ci.appveyor.com/project/axboe/fio by clicking the latest x86 or x64 build, then selecting the ARTIFACTS tab. -**BSDs:** +BSDs: Packages for BSDs may be available from their binary package repositories. Look for a package "fio" using their binary package managers. -## Building - +Building +-------- Just type:: -``` -./configure -make -make install -``` + + $ ./configure + $ make + $ make install + Note that GNU make is required. On BSDs it's available from devel/gmake within ports directory; on Solaris it's in the SUNWgmake package. On platforms where GNU make isn't the default, type ``gmake`` instead of ``make``. @@ -153,18 +155,18 @@ to be installed. gfio isn't built automatically and can be enabled with a ``--enable-gfio`` option to configure. To build fio with a cross-compiler:: -``` -make clean -make CROSS_COMPILE=/path/to/toolchain/prefix -``` + + $ make clean + $ make CROSS_COMPILE=/path/to/toolchain/prefix + Configure will attempt to determine the target platform automatically. It's possible to build fio for ESX as well, use the ``--esx`` switch to configure. -## Windows - +Windows +~~~~~~~ The minimum versions of Windows for building/runing fio are Windows 7/Windows Server 2008 R2. On Windows, Cygwin (https://www.cygwin.com/) is required in @@ -172,7 +174,7 @@ order to build fio. To create an MSI installer package install WiX from https://wixtoolset.org and run :file:`dobuild.cmd` from the :file:`os/windows` directory. -### How to compile fio on 64-bit Windows: +How to compile fio on 64-bit Windows: 1. Install Cygwin (http://www.cygwin.com/). Install **make** and all packages starting with **mingw64-x86_64**. Ensure @@ -194,21 +196,21 @@ https://github.com/mintty/mintty/wiki/Tips#inputoutput-interaction-with-alien-pr for details). -## Documentation - +Documentation +~~~~~~~~~~~~~ Fio uses Sphinx_ to generate documentation from the reStructuredText_ files. To build HTML formatted documentation run ``make -C doc html`` and direct your browser to :file:`./doc/output/html/index.html`. To build manual page run ``make -C doc man`` and then ``man doc/output/man/fio.1``. To see what other output formats are supported run ``make -C doc help``. -``` + .. _reStructuredText: http://www.sphinx-doc.org/rest.html .. _Sphinx: http://www.sphinx-doc.org -``` -## Platforms +Platforms +--------- Fio works on (at least) Linux, Solaris, AIX, HP-UX, OSX, NetBSD, OpenBSD, Windows, FreeBSD, and DragonFly. Some features and/or options may only be @@ -250,8 +252,8 @@ POSIX aio should work now. To make the change permanent:: posix_aio0 changed -## Running fio - +Running fio +----------- Running fio is normally the easiest part - you just give it the job file (or job files) as parameters:: From 79e1e2802e1c457bde36d75225d01e1d9113ee52 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Fri, 28 Jan 2022 14:30:40 -0500 Subject: [PATCH 0055/1097] docs: rename README to README.rst GitHub can display reStructuredText. So just add the appropriate extension to the README. Signed-off-by: Vincent Fu --- README => README.rst | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename README => README.rst (100%) diff --git a/README b/README.rst similarity index 100% rename from README rename to README.rst From 421d8291795dc8715222460afac732cf6328c7e2 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Fri, 28 Jan 2022 19:13:23 +0000 Subject: [PATCH 0056/1097] docs: update fio docs to pull from README.rst doc/fio_doc.rst and doc/fio_man.rst originally included fio/README. Change both to include fio/README.rst instead. Signed-off-by: Vincent Fu --- doc/fio_doc.rst | 2 +- doc/fio_man.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/fio_doc.rst b/doc/fio_doc.rst index b5987b52a8..8e1216f02c 100644 --- a/doc/fio_doc.rst +++ b/doc/fio_doc.rst @@ -2,7 +2,7 @@ fio - Flexible I/O tester rev. |version| ======================================== -.. include:: ../README +.. include:: ../README.rst .. include:: ../HOWTO diff --git a/doc/fio_man.rst b/doc/fio_man.rst index c6a6438ff3..44312f16ac 100644 --- a/doc/fio_man.rst +++ b/doc/fio_man.rst @@ -6,7 +6,7 @@ Fio Manpage (rev. |release|) -.. include:: ../README +.. include:: ../README.rst .. include:: ../HOWTO From dd75d6d0d4121ab9ca6ca571fbc1b2bf7fe89dad Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Fri, 28 Jan 2022 21:46:37 +0000 Subject: [PATCH 0057/1097] Makefile: build t/fio-dedupe only if zlib support is found df284fbdc23974c931865a8ddb7d171606b3c778 added zlib support as a requirement for building fio-dedupe. The current patch changes the Makefile to avoid trying to build fio-dedupe when zlib support is not present. Link: https://lore.kernel.org/fio/51b79acb-c314-143b-514c-a22ff9462829@gmail.com/T/#u Reported-by: Professor Pro Signed-off-by: Vincent Fu Link: https://lore.kernel.org/r/20220128214611.165312-1-vincent.fu@samsung.com Signed-off-by: Jens Axboe --- Makefile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Makefile b/Makefile index 00e7953918..2432f5192f 100644 --- a/Makefile +++ b/Makefile @@ -430,7 +430,9 @@ T_TEST_PROGS += $(T_AXMAP_PROGS) T_TEST_PROGS += $(T_LFSR_TEST_PROGS) T_TEST_PROGS += $(T_GEN_RAND_PROGS) T_PROGS += $(T_BTRACE_FIO_PROGS) +ifdef CONFIG_ZLIB T_PROGS += $(T_DEDUPE_PROGS) +endif T_PROGS += $(T_VS_PROGS) T_TEST_PROGS += $(T_MEMLOCK_PROGS) ifdef CONFIG_PREAD @@ -618,8 +620,10 @@ t/fio-btrace2fio: $(T_BTRACE_FIO_OBJS) $(QUIET_LINK)$(CC) $(LDFLAGS) -o $@ $(T_BTRACE_FIO_OBJS) $(LIBS) endif +ifdef CONFIG_ZLIB t/fio-dedupe: $(T_DEDUPE_OBJS) $(QUIET_LINK)$(CC) $(LDFLAGS) -o $@ $(T_DEDUPE_OBJS) $(LIBS) +endif t/fio-verify-state: $(T_VS_OBJS) $(QUIET_LINK)$(CC) $(LDFLAGS) -o $@ $(T_VS_OBJS) $(LIBS) From 1388e47301d6ea788542f8e5da4fee94ce2b0137 Mon Sep 17 00:00:00 2001 From: james rizzo Date: Thu, 16 Dec 2021 15:35:45 -0700 Subject: [PATCH 0058/1097] =?UTF-8?q?Added=20a=20new=20windows=20only=20IO?= =?UTF-8?q?=20engine=20option=20=E2=80=9Cno=5Fcompletion=5Fthread=E2=80=9D?= =?UTF-8?q?.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Without this option, Windows FIO creates a completion polling thread for each worker thread. This also requires an event queue for the completion thread to forward completions to the worker thread. Polling directly improves performance and better matches the linuxaio engine model. Signed-off-by: james rizzo --- engines/windowsaio.c | 134 +++++++++++++++++++++++++++++++++++-------- optgroup.h | 2 + 2 files changed, 113 insertions(+), 23 deletions(-) diff --git a/engines/windowsaio.c b/engines/windowsaio.c index 9868e816ad..d82c805361 100644 --- a/engines/windowsaio.c +++ b/engines/windowsaio.c @@ -11,6 +11,7 @@ #include #include "../fio.h" +#include "../optgroup.h" typedef BOOL (WINAPI *CANCELIOEX)(HANDLE hFile, LPOVERLAPPED lpOverlapped); @@ -35,6 +36,26 @@ struct thread_ctx { struct windowsaio_data *wd; }; +struct windowsaio_options { + struct thread_data *td; + unsigned int no_completion_thread; +}; + +static struct fio_option options[] = { + { + .name = "no_completion_thread", + .lname = "No completion polling thread", + .type = FIO_OPT_STR_SET, + .off1 = offsetof(struct windowsaio_options, no_completion_thread), + .help = "Use to avoid separate completion polling thread", + .category = FIO_OPT_C_ENGINE, + .group = FIO_OPT_G_WINDOWSAIO, + }, + { + .name = NULL, + }, +}; + static DWORD WINAPI IoCompletionRoutine(LPVOID lpParameter); static int fio_windowsaio_init(struct thread_data *td) @@ -80,6 +101,7 @@ static int fio_windowsaio_init(struct thread_data *td) struct thread_ctx *ctx; struct windowsaio_data *wd; HANDLE hFile; + struct windowsaio_options *o = td->eo; hFile = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0); if (hFile == INVALID_HANDLE_VALUE) { @@ -91,29 +113,30 @@ static int fio_windowsaio_init(struct thread_data *td) wd->iothread_running = TRUE; wd->iocp = hFile; - if (!rc) - ctx = malloc(sizeof(struct thread_ctx)); + if (o->no_completion_thread == 0) { + if (!rc) + ctx = malloc(sizeof(struct thread_ctx)); - if (!rc && ctx == NULL) { - log_err("windowsaio: failed to allocate memory for thread context structure\n"); - CloseHandle(hFile); - rc = 1; - } + if (!rc && ctx == NULL) { + log_err("windowsaio: failed to allocate memory for thread context structure\n"); + CloseHandle(hFile); + rc = 1; + } - if (!rc) { - DWORD threadid; + if (!rc) { + DWORD threadid; - ctx->iocp = hFile; - ctx->wd = wd; - wd->iothread = CreateThread(NULL, 0, IoCompletionRoutine, ctx, 0, &threadid); - if (!wd->iothread) - log_err("windowsaio: failed to create io completion thread\n"); - else if (fio_option_is_set(&td->o, cpumask)) - fio_setaffinity(threadid, td->o.cpumask); + ctx->iocp = hFile; + ctx->wd = wd; + wd->iothread = CreateThread(NULL, 0, IoCompletionRoutine, ctx, 0, &threadid); + if (!wd->iothread) + log_err("windowsaio: failed to create io completion thread\n"); + else if (fio_option_is_set(&td->o, cpumask)) + fio_setaffinity(threadid, td->o.cpumask); + } + if (rc || wd->iothread == NULL) + rc = 1; } - - if (rc || wd->iothread == NULL) - rc = 1; } return rc; @@ -302,9 +325,63 @@ static struct io_u* fio_windowsaio_event(struct thread_data *td, int event) return wd->aio_events[event]; } -static int fio_windowsaio_getevents(struct thread_data *td, unsigned int min, - unsigned int max, - const struct timespec *t) +/* dequeue completion entrees directly (no separate completion thread) */ +static int fio_windowsaio_getevents_nothread(struct thread_data *td, unsigned int min, + unsigned int max, const struct timespec *t) +{ + struct windowsaio_data *wd = td->io_ops_data; + unsigned int dequeued = 0; + struct io_u *io_u; + DWORD start_count = 0; + DWORD end_count = 0; + DWORD mswait = 250; + struct fio_overlapped *fov; + + if (t != NULL) { + mswait = (t->tv_sec * 1000) + (t->tv_nsec / 1000000); + start_count = GetTickCount(); + end_count = start_count + (t->tv_sec * 1000) + (t->tv_nsec / 1000000); + } + + do { + BOOL ret; + OVERLAPPED *ovl; + + ULONG entries = min(16, max-dequeued); + OVERLAPPED_ENTRY oe[16]; + ret = GetQueuedCompletionStatusEx(wd->iocp, oe, 16, &entries, mswait, 0); + if (ret && entries) { + int entry_num; + + for (entry_num=0; entry_numio_u; + + if (ovl->Internal == ERROR_SUCCESS) { + io_u->resid = io_u->xfer_buflen - ovl->InternalHigh; + io_u->error = 0; + } else { + io_u->resid = io_u->xfer_buflen; + io_u->error = win_to_posix_error(GetLastError()); + } + + fov->io_complete = FALSE; + wd->aio_events[dequeued] = io_u; + dequeued++; + } + } + + if (dequeued >= min || + (t != NULL && timeout_expired(start_count, end_count))) + break; + } while (1); + return dequeued; +} + +/* dequeue completion entrees creates by separate IoCompletionRoutine thread */ +static int fio_windowaio_getevents_thread(struct thread_data *td, unsigned int min, + unsigned int max, const struct timespec *t) { struct windowsaio_data *wd = td->io_ops_data; unsigned int dequeued = 0; @@ -334,7 +411,6 @@ static int fio_windowsaio_getevents(struct thread_data *td, unsigned int min, wd->aio_events[dequeued] = io_u; dequeued++; } - } if (dequeued >= min) break; @@ -353,6 +429,16 @@ static int fio_windowsaio_getevents(struct thread_data *td, unsigned int min, return dequeued; } +static int fio_windowsaio_getevents(struct thread_data *td, unsigned int min, + unsigned int max, const struct timespec *t) +{ + struct windowsaio_options *o = td->eo; + + if (o->no_completion_thread) + return fio_windowsaio_getevents_nothread(td, min, max, t); + return fio_windowaio_getevents_thread(td, min, max, t); +} + static enum fio_q_status fio_windowsaio_queue(struct thread_data *td, struct io_u *io_u) { @@ -484,6 +570,8 @@ static struct ioengine_ops ioengine = { .get_file_size = generic_get_file_size, .io_u_init = fio_windowsaio_io_u_init, .io_u_free = fio_windowsaio_io_u_free, + .options = options, + .option_struct_size = sizeof(struct windowsaio_options), }; static void fio_init fio_windowsaio_register(void) diff --git a/optgroup.h b/optgroup.h index 1fb84a296b..3ac8f62a81 100644 --- a/optgroup.h +++ b/optgroup.h @@ -71,6 +71,7 @@ enum opt_category_group { __FIO_OPT_G_LIBCUFILE, __FIO_OPT_G_DFS, __FIO_OPT_G_NFS, + __FIO_OPT_G_WINDOWSAIO, FIO_OPT_G_RATE = (1ULL << __FIO_OPT_G_RATE), FIO_OPT_G_ZONE = (1ULL << __FIO_OPT_G_ZONE), @@ -116,6 +117,7 @@ enum opt_category_group { FIO_OPT_G_FILESTAT = (1ULL << __FIO_OPT_G_FILESTAT), FIO_OPT_G_LIBCUFILE = (1ULL << __FIO_OPT_G_LIBCUFILE), FIO_OPT_G_DFS = (1ULL << __FIO_OPT_G_DFS), + FIO_OPT_G_WINDOWSAIO = (1ULL << __FIO_OPT_G_WINDOWSAIO), }; extern const struct opt_group *opt_group_from_mask(uint64_t *mask); From 8a596ff1ec09e929a16b78533c271a94724409a0 Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Thu, 3 Feb 2022 15:28:16 -0700 Subject: [PATCH 0059/1097] server: fix formatting issue Signed-off-by: Jens Axboe --- server.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/server.c b/server.c index 331c8c9fbf..cc4cbc2a95 100644 --- a/server.c +++ b/server.c @@ -266,7 +266,8 @@ static int fio_send_data(int sk, const void *p, unsigned int len) return fio_sendv_data(sk, &iov, 1); } -bool fio_server_poll_fd(int fd, short events, int timeout) { +bool fio_server_poll_fd(int fd, short events, int timeout) +{ struct pollfd pfd = { .fd = fd, .events = events, @@ -2873,4 +2874,4 @@ void fio_server_internal_set(const char *arg) { fio_server_pipe_name = strdup(arg); } -#endif \ No newline at end of file +#endif From 13f475ad58ff97e5ca0ecd311a6f039f74d51c12 Mon Sep 17 00:00:00 2001 From: Niklas Cassel Date: Thu, 3 Feb 2022 19:28:24 +0000 Subject: [PATCH 0060/1097] init: verify option lat_percentiles consistency for all jobs in group lat_percentiles is used to control if the high/low latency statistics (which are saved in ts->io_u_plat_high_prio/ts->io_u_plat_low_prio) should collect and display total latencies instead of completion latencies. When doing group reporting, stat.c:__show_run_stats() happily overwrites the dst ts with the setting of each job, which means that the summing can take total lat samples for some jobs, and clat samples for some jobs, while adding samples into the same group result. The output summary will claim that the results are of whatever type the final job in the group is set to. To make sure that this cannot happen, verify that the option lat_percentiles is consistent for all jobs in group. Signed-off-by: Niklas Cassel Reviewed-by: Damien Le Moal Link: https://lore.kernel.org/r/20220203192814.18552-2-Niklas.Cassel@wdc.com Signed-off-by: Jens Axboe --- init.c | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/init.c b/init.c index f983dbbb6d..139351527b 100644 --- a/init.c +++ b/init.c @@ -1452,6 +1452,26 @@ static bool wait_for_ok(const char *jobname, struct thread_options *o) return true; } +static int verify_per_group_options(struct thread_data *td, const char *jobname) +{ + struct thread_data *td2; + int i; + + for_each_td(td2, i) { + if (td->groupid != td2->groupid) + continue; + + if (td->o.stats && + td->o.lat_percentiles != td2->o.lat_percentiles) { + log_err("fio: lat_percentiles in job: %s differs from group\n", + jobname); + return 1; + } + } + + return 0; +} + /* * Treat an empty log file name the same as a one not given */ @@ -1570,6 +1590,10 @@ static int add_job(struct thread_data *td, const char *jobname, int job_add_num, td->groupid = groupid; prev_group_jobs++; + if (td->o.group_reporting && prev_group_jobs > 1 && + verify_per_group_options(td, jobname)) + goto err; + if (setup_rate(td)) goto err; From 61b20c5c7b86d2c0840237843002c073345e34fd Mon Sep 17 00:00:00 2001 From: Niklas Cassel Date: Thu, 3 Feb 2022 19:28:24 +0000 Subject: [PATCH 0061/1097] backend: do ioprio_set() before calling the ioengine init callback To be able to report clat stats on a per priority granularity (instead of only high/low priority), we need to do ioprio_set(), and the matching td->ioprio assignment, before calling the io engine init callback. When a thread is using more than a single priority (e.g. option cmdprio_percentage is used), fio_cmdprio_init() will need to allocate and initialize an array that will hold the clat stats for all the different priorities that will be used by the struct td. For fio_cmdprio_init() to be able to initialize a per priority clat array properly, we need to assign td->ioprio before calling td_io_init(). Signed-off-by: Niklas Cassel Reviewed-by: Damien Le Moal Link: https://lore.kernel.org/r/20220203192814.18552-3-Niklas.Cassel@wdc.com Signed-off-by: Jens Axboe --- backend.c | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/backend.c b/backend.c index c167f90862..f7398b2315 100644 --- a/backend.c +++ b/backend.c @@ -1777,6 +1777,17 @@ static void *thread_main(void *data) if (!init_iolog(td)) goto err; + /* ioprio_set() has to be done before td_io_init() */ + if (fio_option_is_set(o, ioprio) || + fio_option_is_set(o, ioprio_class)) { + ret = ioprio_set(IOPRIO_WHO_PROCESS, 0, o->ioprio_class, o->ioprio); + if (ret == -1) { + td_verror(td, errno, "ioprio_set"); + goto err; + } + td->ioprio = ioprio_value(o->ioprio_class, o->ioprio); + } + if (td_io_init(td)) goto err; @@ -1789,16 +1800,6 @@ static void *thread_main(void *data) if (o->verify_async && verify_async_init(td)) goto err; - if (fio_option_is_set(o, ioprio) || - fio_option_is_set(o, ioprio_class)) { - ret = ioprio_set(IOPRIO_WHO_PROCESS, 0, o->ioprio_class, o->ioprio); - if (ret == -1) { - td_verror(td, errno, "ioprio_set"); - goto err; - } - td->ioprio = ioprio_value(o->ioprio_class, o->ioprio); - } - if (o->cgroup && cgroup_setup(td, cgroup_list, &cgroup_mnt)) goto err; From 2df7f0812fa7aa3bd8e700aeb0004f8eb522a76a Mon Sep 17 00:00:00 2001 From: Niklas Cassel Date: Thu, 3 Feb 2022 19:28:25 +0000 Subject: [PATCH 0062/1097] stat: save the default ioprio in struct thread_stat To be able to report clat stats on a per priority granularity (instead of only high/low priority), we need to be able to get the priority value that was used for the stats in clat_stat. When a thread is using a single priority (e.g. option prio/prioclass is used (without any cmdprio options)), all the clat stats for this thread will be stored in clat_stat. The problem with this is sum_thread_stats() does not know the priority value that corresponds to the stats stored in clat_stat. Since we cannot access td->ioprio from sum_thread_stats(), simply mirror td->ioprio inside struct thread_stat. This way, sum_thread_stats() will be able to reuse the global clat stats in clat_stat, without the need to duplicate the data for per priority stats, in the case where there is only a single priority in use. Server version is intentionally not incremented, as it will be incremented in a later patch in the series. No need to bump it multiple times for the same patch series. Signed-off-by: Niklas Cassel Reviewed-by: Damien Le Moal Link: https://lore.kernel.org/r/20220203192814.18552-4-Niklas.Cassel@wdc.com Signed-off-by: Jens Axboe --- backend.c | 1 + client.c | 1 + server.c | 1 + stat.h | 3 +++ 4 files changed, 6 insertions(+) diff --git a/backend.c b/backend.c index f7398b2315..abaaeeb850 100644 --- a/backend.c +++ b/backend.c @@ -1786,6 +1786,7 @@ static void *thread_main(void *data) goto err; } td->ioprio = ioprio_value(o->ioprio_class, o->ioprio); + td->ts.ioprio = td->ioprio; } if (td_io_init(td)) diff --git a/client.c b/client.c index 7a4bfc0d37..83be6a57bf 100644 --- a/client.c +++ b/client.c @@ -954,6 +954,7 @@ static void convert_ts(struct thread_stat *dst, struct thread_stat *src) dst->pid = le32_to_cpu(src->pid); dst->members = le32_to_cpu(src->members); dst->unified_rw_rep = le32_to_cpu(src->unified_rw_rep); + dst->ioprio = le32_to_cpu(src->ioprio); for (i = 0; i < DDIR_RWDIR_CNT; i++) { convert_io_stat(&dst->clat_stat[i], &src->clat_stat[i]); diff --git a/server.c b/server.c index cc4cbc2a95..6905df9050 100644 --- a/server.c +++ b/server.c @@ -1703,6 +1703,7 @@ void fio_server_send_ts(struct thread_stat *ts, struct group_run_stats *rs) p.ts.pid = cpu_to_le32(ts->pid); p.ts.members = cpu_to_le32(ts->members); p.ts.unified_rw_rep = cpu_to_le32(ts->unified_rw_rep); + p.ts.ioprio = cpu_to_le32(ts->ioprio); for (i = 0; i < DDIR_RWDIR_CNT; i++) { convert_io_stat(&p.ts.clat_stat[i], &ts->clat_stat[i]); diff --git a/stat.h b/stat.h index 15ca4eff59..3ce821a701 100644 --- a/stat.h +++ b/stat.h @@ -252,6 +252,9 @@ struct thread_stat { fio_fp64_t ss_deviation; fio_fp64_t ss_criterion; + /* A mirror of td->ioprio. */ + uint32_t ioprio; + uint64_t io_u_plat_high_prio[DDIR_RWDIR_CNT][FIO_IO_U_PLAT_NR] __attribute__((aligned(8)));; uint64_t io_u_plat_low_prio[DDIR_RWDIR_CNT][FIO_IO_U_PLAT_NR]; struct io_stat clat_high_prio_stat[DDIR_RWDIR_CNT] __attribute__((aligned(8))); From 2d5bf2a5912738e967e844084b46f976fb655443 Mon Sep 17 00:00:00 2001 From: Niklas Cassel Date: Thu, 3 Feb 2022 19:28:25 +0000 Subject: [PATCH 0063/1097] client/server: convert ss_data to use an offset instead of fixed position Store the location of the ss_data in the payload itself, rather than assuming that it is always located at a fixed location, directly after the cmd_ts_pdu data. This is done as a cleanup patch in order to be able to handle clat_prio_stats, which just like ss_data, may or may not be part of the payload. Server version is intentionally not incremented, as it will be incremented in a later patch in the series. No need to bump it multiple times for the same patch series. Signed-off-by: Niklas Cassel Reviewed-by: Damien Le Moal Link: https://lore.kernel.org/r/20220203192814.18552-5-Niklas.Cassel@wdc.com Signed-off-by: Jens Axboe --- client.c | 10 ++++++---- server.c | 59 ++++++++++++++++++++++++++++++++++++++++++-------------- stat.h | 10 ++++++++++ 3 files changed, 60 insertions(+), 19 deletions(-) diff --git a/client.c b/client.c index 83be6a57bf..cf082c74e6 100644 --- a/client.c +++ b/client.c @@ -1762,7 +1762,6 @@ int fio_handle_client(struct fio_client *client) { struct client_ops *ops = client->ops; struct fio_net_cmd *cmd; - int size; dprint(FD_NET, "client: handle %s\n", client->hostname); @@ -1796,14 +1795,17 @@ int fio_handle_client(struct fio_client *client) } case FIO_NET_CMD_TS: { struct cmd_ts_pdu *p = (struct cmd_ts_pdu *) cmd->payload; + uint64_t offset; dprint(FD_NET, "client: ts->ss_state = %u\n", (unsigned int) le32_to_cpu(p->ts.ss_state)); if (le32_to_cpu(p->ts.ss_state) & FIO_SS_DATA) { dprint(FD_NET, "client: received steadystate ring buffers\n"); - size = le64_to_cpu(p->ts.ss_dur); - p->ts.ss_iops_data = (uint64_t *) ((struct cmd_ts_pdu *)cmd->payload + 1); - p->ts.ss_bw_data = p->ts.ss_iops_data + size; + offset = le64_to_cpu(p->ts.ss_iops_data_offset); + p->ts.ss_iops_data = (uint64_t *)((char *)p + offset); + + offset = le64_to_cpu(p->ts.ss_bw_data_offset); + p->ts.ss_bw_data = (uint64_t *)((char *)p + offset); } convert_ts(&p->ts, &p->ts); diff --git a/server.c b/server.c index 6905df9050..05b65631fb 100644 --- a/server.c +++ b/server.c @@ -1685,8 +1685,10 @@ void fio_server_send_ts(struct thread_stat *ts, struct group_run_stats *rs) { struct cmd_ts_pdu p; int i, j, k; - void *ss_buf; - uint64_t *ss_iops, *ss_bw; + size_t ss_extra_size = 0; + size_t extended_buf_size = 0; + void *extended_buf; + void *extended_buf_wp; dprint(FD_NET, "server sending end stats\n"); @@ -1810,26 +1812,53 @@ void fio_server_send_ts(struct thread_stat *ts, struct group_run_stats *rs) convert_gs(&p.rs, rs); dprint(FD_NET, "ts->ss_state = %d\n", ts->ss_state); - if (ts->ss_state & FIO_SS_DATA) { - dprint(FD_NET, "server sending steadystate ring buffers\n"); + if (ts->ss_state & FIO_SS_DATA) + ss_extra_size = 2 * ts->ss_dur * sizeof(uint64_t); - ss_buf = malloc(sizeof(p) + 2*ts->ss_dur*sizeof(uint64_t)); + extended_buf_size += ss_extra_size; + if (!extended_buf_size) { + fio_net_queue_cmd(FIO_NET_CMD_TS, &p, sizeof(p), NULL, SK_F_COPY); + return; + } - memcpy(ss_buf, &p, sizeof(p)); + extended_buf_size += sizeof(p); + extended_buf = calloc(1, extended_buf_size); + if (!extended_buf) { + log_err("fio: failed to allocate FIO_NET_CMD_TS buffer\n"); + return; + } + + memcpy(extended_buf, &p, sizeof(p)); + extended_buf_wp = (struct cmd_ts_pdu *)extended_buf + 1; - ss_iops = (uint64_t *) ((struct cmd_ts_pdu *)ss_buf + 1); - ss_bw = ss_iops + (int) ts->ss_dur; - for (i = 0; i < ts->ss_dur; i++) { + if (ss_extra_size) { + uint64_t *ss_iops, *ss_bw; + uint64_t offset; + struct cmd_ts_pdu *ptr = extended_buf; + + dprint(FD_NET, "server sending steadystate ring buffers\n"); + + /* ss iops */ + ss_iops = (uint64_t *) extended_buf_wp; + for (i = 0; i < ts->ss_dur; i++) ss_iops[i] = cpu_to_le64(ts->ss_iops_data[i]); - ss_bw[i] = cpu_to_le64(ts->ss_bw_data[i]); - } - fio_net_queue_cmd(FIO_NET_CMD_TS, ss_buf, sizeof(p) + 2*ts->ss_dur*sizeof(uint64_t), NULL, SK_F_COPY); + offset = (char *)extended_buf_wp - (char *)extended_buf; + ptr->ts.ss_iops_data_offset = cpu_to_le64(offset); + extended_buf_wp = ss_iops + (int) ts->ss_dur; + + /* ss bw */ + ss_bw = extended_buf_wp; + for (i = 0; i < ts->ss_dur; i++) + ss_bw[i] = cpu_to_le64(ts->ss_bw_data[i]); - free(ss_buf); + offset = (char *)extended_buf_wp - (char *)extended_buf; + ptr->ts.ss_bw_data_offset = cpu_to_le64(offset); + extended_buf_wp = ss_bw + (int) ts->ss_dur; } - else - fio_net_queue_cmd(FIO_NET_CMD_TS, &p, sizeof(p), NULL, SK_F_COPY); + + fio_net_queue_cmd(FIO_NET_CMD_TS, extended_buf, extended_buf_size, NULL, SK_F_COPY); + free(extended_buf); } void fio_server_send_gs(struct group_run_stats *rs) diff --git a/stat.h b/stat.h index 3ce821a701..5fa20f7441 100644 --- a/stat.h +++ b/stat.h @@ -262,11 +262,21 @@ struct thread_stat { union { uint64_t *ss_iops_data; + /* + * For FIO_NET_CMD_TS, the pointed to data will temporarily + * be stored at this offset from the start of the payload. + */ + uint64_t ss_iops_data_offset; uint64_t pad4; }; union { uint64_t *ss_bw_data; + /* + * For FIO_NET_CMD_TS, the pointed to data will temporarily + * be stored at this offset from the start of the payload. + */ + uint64_t ss_bw_data_offset; uint64_t pad5; }; From 4ad856497c0bf74c1161192ac10bb01bed92ce3d Mon Sep 17 00:00:00 2001 From: Niklas Cassel Date: Thu, 3 Feb 2022 19:28:26 +0000 Subject: [PATCH 0064/1097] stat: add a new function to allocate a clat_prio_stat array To be able to report clat stats on a per priority granularity (instead of only high/low priority), we need a new function which allocates a clat_prio_stat array. This array will hold the clat stats for all the different priorities that will be used by the struct td. The clat_prio_stat array will eventually replace io_u_plat_high_prio, io_u_plat_low_prio, clat_high_prio_stat, and clat_low_prio_stat. A follow up patch will convert stat.c to use the new array. Signed-off-by: Niklas Cassel Reviewed-by: Damien Le Moal Link: https://lore.kernel.org/r/20220203192814.18552-6-Niklas.Cassel@wdc.com Signed-off-by: Jens Axboe --- stat.c | 40 ++++++++++++++++++++++++++++++++++++++++ stat.h | 19 +++++++++++++++++++ 2 files changed, 59 insertions(+) diff --git a/stat.c b/stat.c index b08d2f25df..c8680a679b 100644 --- a/stat.c +++ b/stat.c @@ -1995,6 +1995,46 @@ void sum_group_stats(struct group_run_stats *dst, struct group_run_stats *src) dst->sig_figs = src->sig_figs; } +/* + * Free the clat_prio_stat arrays allocated by alloc_clat_prio_stat_ddir(). + */ +void free_clat_prio_stats(struct thread_stat *ts) +{ + enum fio_ddir ddir; + + for (ddir = 0; ddir < DDIR_RWDIR_CNT; ddir++) { + sfree(ts->clat_prio[ddir]); + ts->clat_prio[ddir] = NULL; + ts->nr_clat_prio[ddir] = 0; + } +} + +/* + * Allocate a clat_prio_stat array. The array has to be allocated/freed using + * smalloc/sfree, so that it is accessible by the process/thread summing the + * thread_stats. + */ +int alloc_clat_prio_stat_ddir(struct thread_stat *ts, enum fio_ddir ddir, + int nr_prios) +{ + struct clat_prio_stat *clat_prio; + int i; + + clat_prio = scalloc(nr_prios, sizeof(*ts->clat_prio[ddir])); + if (!clat_prio) { + log_err("fio: failed to allocate ts clat data\n"); + return 1; + } + + for (i = 0; i < nr_prios; i++) + clat_prio[i].clat_stat.min_val = ULONG_MAX; + + ts->clat_prio[ddir] = clat_prio; + ts->nr_clat_prio[ddir] = nr_prios; + + return 0; +} + void sum_thread_stats(struct thread_stat *dst, struct thread_stat *src) { int k, l, m; diff --git a/stat.h b/stat.h index 5fa20f7441..188c57f3f8 100644 --- a/stat.h +++ b/stat.h @@ -158,6 +158,12 @@ enum fio_lat { FIO_LAT_CNT = 3, }; +struct clat_prio_stat { + uint64_t io_u_plat[FIO_IO_U_PLAT_NR]; + struct io_stat clat_stat; + uint32_t ioprio; +}; + struct thread_stat { char name[FIO_JOBNAME_SIZE]; char verror[FIO_VERROR_SIZE]; @@ -280,6 +286,17 @@ struct thread_stat { uint64_t pad5; }; + union { + struct clat_prio_stat *clat_prio[DDIR_RWDIR_CNT]; + /* + * For FIO_NET_CMD_TS, the pointed to data will temporarily + * be stored at this offset from the start of the payload. + */ + uint64_t clat_prio_offset[DDIR_RWDIR_CNT]; + uint64_t pad6; + }; + uint32_t nr_clat_prio[DDIR_RWDIR_CNT]; + uint64_t cachehit; uint64_t cachemiss; } __attribute__((packed)); @@ -368,6 +385,8 @@ extern void add_bw_sample(struct thread_data *, struct io_u *, extern void add_sync_clat_sample(struct thread_stat *ts, unsigned long long nsec); extern int calc_log_samples(void); +extern void free_clat_prio_stats(struct thread_stat *); +extern int alloc_clat_prio_stat_ddir(struct thread_stat *, enum fio_ddir, int); extern void print_disk_util(struct disk_util_stat *, struct disk_util_agg *, int terse, struct buf_output *); extern void json_array_add_disk_util(struct disk_util_stat *dus, From 971eef801cabdb52e82f0db549346f66fc331677 Mon Sep 17 00:00:00 2001 From: Niklas Cassel Date: Thu, 3 Feb 2022 19:28:26 +0000 Subject: [PATCH 0065/1097] os: define min/max prio class and level for systems without ioprio In order to avoid additional ifdef FIO_HAVE_IOPRIO_CLASS/FIO_HAVE_IOPRIO from being added to the code, define IOPRIO_{MIN,MAX}_PRIO_CLASS and IOPRIO_{MIN,MAX}_PRIO_CLASS as zero for systems without support for ioprio. Signed-off-by: Niklas Cassel Link: https://lore.kernel.org/r/20220203192814.18552-7-Niklas.Cassel@wdc.com Signed-off-by: Jens Axboe --- os/os.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/os/os.h b/os/os.h index 5965d7b806..810e616685 100644 --- a/os/os.h +++ b/os/os.h @@ -119,10 +119,14 @@ extern int fio_cpus_split(os_cpu_mask_t *mask, unsigned int cpu); #ifndef FIO_HAVE_IOPRIO_CLASS #define ioprio_value_is_class_rt(prio) (false) +#define IOPRIO_MIN_PRIO_CLASS 0 +#define IOPRIO_MAX_PRIO_CLASS 0 #endif #ifndef FIO_HAVE_IOPRIO #define ioprio_value(prioclass, prio) (0) #define ioprio_set(which, who, prioclass, prio) (0) +#define IOPRIO_MIN_PRIO 0 +#define IOPRIO_MAX_PRIO 0 #endif #ifndef FIO_HAVE_ODIRECT From 68be99668eab06f2917eebf56d77832cfc5c4a2d Mon Sep 17 00:00:00 2001 From: Niklas Cassel Date: Thu, 3 Feb 2022 19:28:27 +0000 Subject: [PATCH 0066/1097] options: add a parsing function for an additional cmdprio_bssplit format The cmdprio_bssplit ioengine option for io_uring/libaio is currently parsed using split_parse_ddir(). While this function works fine for parsing the existing cmdprio_bssplit entry format, it forces every cmdprio_bssplit entry to use the priority defined by cmdprio and cmdprio_class. This means that there will only ever be at most two different priority values used in the job. To enable us to use more than two different priority values, add a new parsing function, split_parse_prio_ddir(), that will support parsing the existing cmdprio_bssplit entry format (blocksize/percentage), and a new cmdprio_bssplit entry format (blocksize/percentage/prioclass/priolevel). Since IO engines can be compiled as plugins, having the parse function in options.c avoids potential problems with ioengines having different versions of the same parsing function. A follow up patch will change to the new parsing function. Signed-off-by: Niklas Cassel Link: https://lore.kernel.org/r/20220203192814.18552-8-Niklas.Cassel@wdc.com Signed-off-by: Jens Axboe --- options.c | 122 +++++++++++++++++++++++++++++++++++++++++++++++ thread_options.h | 10 ++++ 2 files changed, 132 insertions(+) diff --git a/options.c b/options.c index 102bcf5661..dcf5e09fc2 100644 --- a/options.c +++ b/options.c @@ -278,6 +278,128 @@ static int str_bssplit_cb(void *data, const char *input) return ret; } +static int parse_cmdprio_bssplit_entry(struct thread_options *o, + struct split_prio *entry, char *str) +{ + int matches = 0; + char *bs_str = NULL; + long long bs_val; + unsigned int perc = 0, class, level; + + /* + * valid entry formats: + * bs/ - %s/ - set perc to 0, prio to -1. + * bs/perc - %s/%u - set prio to -1. + * bs/perc/class/level - %s/%u/%u/%u + */ + matches = sscanf(str, "%m[^/]/%u/%u/%u", &bs_str, &perc, &class, &level); + if (matches < 1) { + log_err("fio: invalid cmdprio_bssplit format\n"); + return 1; + } + + if (str_to_decimal(bs_str, &bs_val, 1, o, 0, 0)) { + log_err("fio: split conversion failed\n"); + free(bs_str); + return 1; + } + free(bs_str); + + entry->bs = bs_val; + entry->perc = min(perc, 100u); + entry->prio = -1; + switch (matches) { + case 1: /* bs/ case */ + case 2: /* bs/perc case */ + break; + case 4: /* bs/perc/class/level case */ + class = min(class, (unsigned int) IOPRIO_MAX_PRIO_CLASS); + level = min(level, (unsigned int) IOPRIO_MAX_PRIO); + entry->prio = ioprio_value(class, level); + break; + default: + log_err("fio: invalid cmdprio_bssplit format\n"); + return 1; + } + + return 0; +} + +/* + * Returns a negative integer if the first argument should be before the second + * argument in the sorted list. A positive integer if the first argument should + * be after the second argument in the sorted list. A zero if they are equal. + */ +static int fio_split_prio_cmp(const void *p1, const void *p2) +{ + const struct split_prio *tmp1 = p1; + const struct split_prio *tmp2 = p2; + + if (tmp1->bs > tmp2->bs) + return 1; + if (tmp1->bs < tmp2->bs) + return -1; + return 0; +} + +int split_parse_prio_ddir(struct thread_options *o, struct split_prio **entries, + int *nr_entries, char *str) +{ + struct split_prio *tmp_entries; + unsigned int nr_bssplits; + char *str_cpy, *p, *fname; + + /* strsep modifies the string, dup it so that we can use strsep twice */ + p = str_cpy = strdup(str); + if (!p) + return 1; + + nr_bssplits = 0; + while ((fname = strsep(&str_cpy, ":")) != NULL) { + if (!strlen(fname)) + break; + nr_bssplits++; + } + free(p); + + if (nr_bssplits > BSSPLIT_MAX) { + log_err("fio: too many cmdprio_bssplit entries\n"); + return 1; + } + + tmp_entries = calloc(nr_bssplits, sizeof(*tmp_entries)); + if (!tmp_entries) + return 1; + + nr_bssplits = 0; + while ((fname = strsep(&str, ":")) != NULL) { + struct split_prio *entry; + + if (!strlen(fname)) + break; + + entry = &tmp_entries[nr_bssplits]; + + if (parse_cmdprio_bssplit_entry(o, entry, fname)) { + log_err("fio: failed to parse cmdprio_bssplit entry\n"); + free(tmp_entries); + return 1; + } + + /* skip zero perc entries, they provide no useful information */ + if (entry->perc) + nr_bssplits++; + } + + qsort(tmp_entries, nr_bssplits, sizeof(*tmp_entries), + fio_split_prio_cmp); + + *entries = tmp_entries; + *nr_entries = nr_bssplits; + + return 0; +} + static int str2error(char *str) { const char *err[] = { "EPERM", "ENOENT", "ESRCH", "EINTR", "EIO", diff --git a/thread_options.h b/thread_options.h index 8f4c8a5996..ace658b714 100644 --- a/thread_options.h +++ b/thread_options.h @@ -50,6 +50,12 @@ struct split { unsigned long long val2[ZONESPLIT_MAX]; }; +struct split_prio { + uint64_t bs; + int32_t prio; + uint32_t perc; +}; + struct bssplit { uint64_t bs; uint32_t perc; @@ -702,4 +708,8 @@ extern int str_split_parse(struct thread_data *td, char *str, extern int split_parse_ddir(struct thread_options *o, struct split *split, char *str, bool absolute, unsigned int max_splits); +extern int split_parse_prio_ddir(struct thread_options *o, + struct split_prio **entries, int *nr_entries, + char *str); + #endif From f0547200d6d48dd847cae48f8db08cda0a38fd89 Mon Sep 17 00:00:00 2001 From: Niklas Cassel Date: Thu, 3 Feb 2022 19:28:27 +0000 Subject: [PATCH 0067/1097] cmdprio: add support for a new cmdprio_bssplit entry format Add support for a new cmdprio_bssplit format, while keeping support for the old format, by migrating to the split_parse_prio_ddir() parsing function. In this new format, a priority class and priority level is defined inside each entry itself. In comparison with the old format, the new format does not restrict all entries to share the same priority class and priority level. Therefore, this new format is very useful if you need to submit I/Os with multiple IO priority class + IO priority level combinations, e.g. when testing or verifying an IO scheduler. cmdprio will allocate a clat_prio_stat array that holds all unique priorities (including the default priority). Finally, it will set the clat_prio pointer in the struct thread_stat (td->ts.clat_prio) to the newly allocated array. We also add a clat_prio_stat index to io_u.h, that will inform which array element (which priority value) this specific I/O was submitted with. The clat_prio_stat index will be used by the stat.c code, to avoid a costly search operation to find the correct array element to use, for each and every add_sample(). Note that while this patch will send down the correct I/O pattern to the drive (potentially using multiple different priorities), it will not display the cmdprio_{bssplit,percentage} stats correctly until a later commit in the series (which changes stat.c to report clat stats on a per priority granularity). This was done to ease reviewing. Signed-off-by: Niklas Cassel Reviewed-by: Damien Le Moal Link: https://lore.kernel.org/r/20220203192814.18552-9-Niklas.Cassel@wdc.com Signed-off-by: Jens Axboe --- HOWTO | 26 ++- backend.c | 3 + engines/cmdprio.c | 440 ++++++++++++++++++++++++++++++++++++++-------- engines/cmdprio.h | 22 ++- fio.1 | 32 +++- io_u.c | 1 + io_u.h | 1 + 7 files changed, 440 insertions(+), 85 deletions(-) diff --git a/HOWTO b/HOWTO index c72ec8cd01..cb794b0db6 100644 --- a/HOWTO +++ b/HOWTO @@ -2212,10 +2212,28 @@ with the caveat that when used on the command line, they must come after the depending on the block size of the IO. This option is useful only when used together with the :option:`bssplit` option, that is, multiple different block sizes are used for reads and writes. - The format for this option is the same as the format of the - :option:`bssplit` option, with the exception that values for - trim IOs are ignored. This option is mutually exclusive with the - :option:`cmdprio_percentage` option. + + The first accepted format for this option is the same as the format of + the :option:`bssplit` option: + + cmdprio_bssplit=blocksize/percentage:blocksize/percentage + + In this case, each entry will use the priority class and priority + level defined by the options :option:`cmdprio_class` and + :option:`cmdprio` respectively. + + The second accepted format for this option is: + + cmdprio_bssplit=blocksize/percentage/class/level:blocksize/percentage/class/level + + In this case, the priority class and priority level is defined inside + each entry. In comparison with the first accepted format, the second + accepted format does not restrict all entries to have the same priority + class and priority level. + + For both formats, only the read and write data directions are supported, + values for trim IOs are ignored. This option is mutually exclusive with + the :option:`cmdprio_percentage` option. .. option:: fixedbufs : [io_uring] diff --git a/backend.c b/backend.c index abaaeeb850..933d84144f 100644 --- a/backend.c +++ b/backend.c @@ -2613,6 +2613,9 @@ int fio_backend(struct sk_out *sk_out) } for_each_td(td, i) { + struct thread_stat *ts = &td->ts; + + free_clat_prio_stats(ts); steadystate_free(td); fio_options_free(td); fio_dump_options_free(td); diff --git a/engines/cmdprio.c b/engines/cmdprio.c index 92b752aecd..dd358754de 100644 --- a/engines/cmdprio.c +++ b/engines/cmdprio.c @@ -5,45 +5,201 @@ #include "cmdprio.h" -static int fio_cmdprio_bssplit_ddir(struct thread_options *to, void *cb_arg, - enum fio_ddir ddir, char *str, bool data) +/* + * Temporary array used during parsing. Will be freed after the corresponding + * struct bsprio_desc has been generated and saved in cmdprio->bsprio_desc. + */ +struct cmdprio_parse_result { + struct split_prio *entries; + int nr_entries; +}; + +/* + * Temporary array used during init. Will be freed after the corresponding + * struct clat_prio_stat array has been saved in td->ts.clat_prio and the + * matching clat_prio_indexes have been saved in each struct cmdprio_prio. + */ +struct cmdprio_values { + unsigned int *prios; + int nr_prios; +}; + +static int find_clat_prio_index(unsigned int *all_prios, int nr_prios, + int32_t prio) { - struct cmdprio *cmdprio = cb_arg; - struct split split; - unsigned int i; + int i; - if (ddir == DDIR_TRIM) - return 0; + for (i = 0; i < nr_prios; i++) { + if (all_prios[i] == prio) + return i; + } - memset(&split, 0, sizeof(split)); + return -1; +} - if (split_parse_ddir(to, &split, str, data, BSSPLIT_MAX)) +/** + * assign_clat_prio_index - In order to avoid stat.c the need to loop through + * all possible priorities each time add_clat_sample() / add_lat_sample() is + * called, save which index to use in each cmdprio_prio. This will later be + * propagated to the io_u, if the specific io_u was determined to use a cmdprio + * priority value. + */ +static void assign_clat_prio_index(struct cmdprio_prio *prio, + struct cmdprio_values *values) +{ + int clat_prio_index = find_clat_prio_index(values->prios, + values->nr_prios, + prio->prio); + if (clat_prio_index == -1) { + clat_prio_index = values->nr_prios; + values->prios[clat_prio_index] = prio->prio; + values->nr_prios++; + } + prio->clat_prio_index = clat_prio_index; +} + +/** + * init_cmdprio_values - Allocate a temporary array that can hold all unique + * priorities (per ddir), so that we can assign_clat_prio_index() for each + * cmdprio_prio during setup. This temporary array is freed after setup. + */ +static int init_cmdprio_values(struct cmdprio_values *values, + int max_unique_prios, struct thread_stat *ts) +{ + values->prios = calloc(max_unique_prios + 1, + sizeof(*values->prios)); + if (!values->prios) return 1; - if (!split.nr) - return 0; - cmdprio->bssplit_nr[ddir] = split.nr; - cmdprio->bssplit[ddir] = malloc(split.nr * sizeof(struct bssplit)); - if (!cmdprio->bssplit[ddir]) + /* td->ioprio/ts->ioprio is always stored at index 0. */ + values->prios[0] = ts->ioprio; + values->nr_prios++; + + return 0; +} + +/** + * init_ts_clat_prio - Allocates and fills a clat_prio_stat array which holds + * all unique priorities (per ddir). + */ +static int init_ts_clat_prio(struct thread_stat *ts, enum fio_ddir ddir, + struct cmdprio_values *values) +{ + int i; + + if (alloc_clat_prio_stat_ddir(ts, ddir, values->nr_prios)) return 1; - for (i = 0; i < split.nr; i++) { - cmdprio->bssplit[ddir][i].bs = split.val1[i]; - if (split.val2[i] == -1U) { - cmdprio->bssplit[ddir][i].perc = 0; - } else { - if (split.val2[i] > 100) - cmdprio->bssplit[ddir][i].perc = 100; - else - cmdprio->bssplit[ddir][i].perc = split.val2[i]; + for (i = 0; i < values->nr_prios; i++) + ts->clat_prio[ddir][i].ioprio = values->prios[i]; + + return 0; +} + +static int fio_cmdprio_fill_bsprio(struct cmdprio_bsprio *bsprio, + struct split_prio *entries, + struct cmdprio_values *values, + int implicit_cmdprio, int start, int end) +{ + struct cmdprio_prio *prio; + int i = end - start + 1; + + bsprio->prios = calloc(i, sizeof(*bsprio->prios)); + if (!bsprio->prios) + return 1; + + bsprio->bs = entries[start].bs; + bsprio->nr_prios = 0; + for (i = start; i <= end; i++) { + prio = &bsprio->prios[bsprio->nr_prios]; + prio->perc = entries[i].perc; + if (entries[i].prio == -1) + prio->prio = implicit_cmdprio; + else + prio->prio = entries[i].prio; + assign_clat_prio_index(prio, values); + bsprio->tot_perc += entries[i].perc; + if (bsprio->tot_perc > 100) { + log_err("fio: cmdprio_bssplit total percentage " + "for bs: %"PRIu64" exceeds 100\n", + bsprio->bs); + free(bsprio->prios); + return 1; } + bsprio->nr_prios++; + } + + return 0; +} + +static int +fio_cmdprio_generate_bsprio_desc(struct cmdprio_bsprio_desc *bsprio_desc, + struct cmdprio_parse_result *parse_res, + struct cmdprio_values *values, + int implicit_cmdprio) +{ + struct split_prio *entries = parse_res->entries; + int nr_entries = parse_res->nr_entries; + struct cmdprio_bsprio *bsprio; + int i, start, count = 0; + + /* + * The parsed result is sorted by blocksize, so count only the number + * of different blocksizes, to know how many cmdprio_bsprio we need. + */ + for (i = 0; i < nr_entries; i++) { + while (i + 1 < nr_entries && entries[i].bs == entries[i + 1].bs) + i++; + count++; + } + + /* + * This allocation is not freed on error. Instead, the calling function + * is responsible for calling fio_cmdprio_cleanup() on error. + */ + bsprio_desc->bsprios = calloc(count, sizeof(*bsprio_desc->bsprios)); + if (!bsprio_desc->bsprios) + return 1; + + start = 0; + bsprio_desc->nr_bsprios = 0; + for (i = 0; i < nr_entries; i++) { + while (i + 1 < nr_entries && entries[i].bs == entries[i + 1].bs) + i++; + bsprio = &bsprio_desc->bsprios[bsprio_desc->nr_bsprios]; + /* + * All parsed entries with the same blocksize get saved in the + * same cmdprio_bsprio, to expedite the search in the hot path. + */ + if (fio_cmdprio_fill_bsprio(bsprio, entries, values, + implicit_cmdprio, start, i)) + return 1; + + start = i + 1; + bsprio_desc->nr_bsprios++; } return 0; } -int fio_cmdprio_bssplit_parse(struct thread_data *td, const char *input, - struct cmdprio *cmdprio) +static int fio_cmdprio_bssplit_ddir(struct thread_options *to, void *cb_arg, + enum fio_ddir ddir, char *str, bool data) +{ + struct cmdprio_parse_result *parse_res_arr = cb_arg; + struct cmdprio_parse_result *parse_res = &parse_res_arr[ddir]; + + if (ddir == DDIR_TRIM) + return 0; + + if (split_parse_prio_ddir(to, &parse_res->entries, + &parse_res->nr_entries, str)) + return 1; + + return 0; +} + +static int fio_cmdprio_bssplit_parse(struct thread_data *td, const char *input, + struct cmdprio_parse_result *parse_res) { char *str, *p; int ret = 0; @@ -53,26 +209,39 @@ int fio_cmdprio_bssplit_parse(struct thread_data *td, const char *input, strip_blank_front(&str); strip_blank_end(str); - ret = str_split_parse(td, str, fio_cmdprio_bssplit_ddir, cmdprio, + ret = str_split_parse(td, str, fio_cmdprio_bssplit_ddir, parse_res, false); free(p); return ret; } -static int fio_cmdprio_percentage(struct cmdprio *cmdprio, struct io_u *io_u) +/** + * fio_cmdprio_percentage - Returns the percentage of I/Os that should + * use a cmdprio priority value (rather than the default context priority). + * + * For CMDPRIO_MODE_BSSPLIT, if the percentage is non-zero, we will also + * return the matching bsprio, to avoid the same linear search elsewhere. + * For CMDPRIO_MODE_PERC, we will never return a bsprio. + */ +static int fio_cmdprio_percentage(struct cmdprio *cmdprio, struct io_u *io_u, + struct cmdprio_bsprio **bsprio) { + struct cmdprio_bsprio *bsprio_entry; enum fio_ddir ddir = io_u->ddir; - struct cmdprio_options *options = cmdprio->options; int i; switch (cmdprio->mode) { case CMDPRIO_MODE_PERC: - return options->percentage[ddir]; + *bsprio = NULL; + return cmdprio->perc_entry[ddir].perc; case CMDPRIO_MODE_BSSPLIT: - for (i = 0; i < cmdprio->bssplit_nr[ddir]; i++) { - if (cmdprio->bssplit[ddir][i].bs == io_u->buflen) - return cmdprio->bssplit[ddir][i].perc; + for (i = 0; i < cmdprio->bsprio_desc[ddir].nr_bsprios; i++) { + bsprio_entry = &cmdprio->bsprio_desc[ddir].bsprios[i]; + if (bsprio_entry->bs == io_u->buflen) { + *bsprio = bsprio_entry; + return bsprio_entry->tot_perc; + } } break; default: @@ -83,6 +252,11 @@ static int fio_cmdprio_percentage(struct cmdprio *cmdprio, struct io_u *io_u) assert(0); } + /* + * This is totally fine, the given blocksize simply does not + * have any (non-zero) cmdprio_bssplit entries defined. + */ + *bsprio = NULL; return 0; } @@ -100,52 +274,162 @@ static int fio_cmdprio_percentage(struct cmdprio *cmdprio, struct io_u *io_u) bool fio_cmdprio_set_ioprio(struct thread_data *td, struct cmdprio *cmdprio, struct io_u *io_u) { - enum fio_ddir ddir = io_u->ddir; - struct cmdprio_options *options = cmdprio->options; - unsigned int p; - unsigned int cmdprio_value = - ioprio_value(options->class[ddir], options->level[ddir]); - - p = fio_cmdprio_percentage(cmdprio, io_u); - if (p && rand_between(&td->prio_state, 0, 99) < p) { - io_u->ioprio = cmdprio_value; - if (!td->ioprio || cmdprio_value < td->ioprio) { - /* - * The async IO priority is higher (has a lower value) - * than the default priority (which is either 0 or the - * value set by "prio" and "prioclass" options). - */ - io_u->flags |= IO_U_F_HIGH_PRIO; - } + struct cmdprio_bsprio *bsprio; + unsigned int p, rand; + uint32_t perc = 0; + int i; + + p = fio_cmdprio_percentage(cmdprio, io_u, &bsprio); + if (!p) + return false; + + rand = rand_between(&td->prio_state, 0, 99); + if (rand >= p) + return false; + + switch (cmdprio->mode) { + case CMDPRIO_MODE_PERC: + io_u->ioprio = cmdprio->perc_entry[io_u->ddir].prio; + io_u->clat_prio_index = + cmdprio->perc_entry[io_u->ddir].clat_prio_index; return true; + case CMDPRIO_MODE_BSSPLIT: + assert(bsprio); + for (i = 0; i < bsprio->nr_prios; i++) { + struct cmdprio_prio *prio = &bsprio->prios[i]; + + perc += prio->perc; + if (rand < perc) { + io_u->ioprio = prio->prio; + io_u->clat_prio_index = prio->clat_prio_index; + return true; + } + } + break; + default: + assert(0); } - if (td->ioprio && td->ioprio < cmdprio_value) { + /* When rand < p (total perc), we should always find a cmdprio_prio. */ + assert(0); + return false; +} + +static int fio_cmdprio_gen_perc(struct thread_data *td, struct cmdprio *cmdprio) +{ + struct cmdprio_options *options = cmdprio->options; + struct cmdprio_prio *prio; + struct cmdprio_values values[CMDPRIO_RWDIR_CNT] = {0}; + struct thread_stat *ts = &td->ts; + enum fio_ddir ddir; + int ret; + + for (ddir = 0; ddir < CMDPRIO_RWDIR_CNT; ddir++) { /* - * The IO will be executed with the default priority (which is - * either 0 or the value set by "prio" and "prioclass options), - * and this priority is higher (has a lower value) than the - * async IO priority. + * Do not allocate a clat_prio array nor set the cmdprio struct + * if zero percent of the I/Os (for the ddir) should use a + * cmdprio priority value, or when the ddir is not enabled. */ - io_u->flags |= IO_U_F_HIGH_PRIO; + if (!options->percentage[ddir] || + (ddir == DDIR_READ && !td_read(td)) || + (ddir == DDIR_WRITE && !td_write(td))) + continue; + + ret = init_cmdprio_values(&values[ddir], 1, ts); + if (ret) + goto err; + + prio = &cmdprio->perc_entry[ddir]; + prio->perc = options->percentage[ddir]; + prio->prio = ioprio_value(options->class[ddir], + options->level[ddir]); + assign_clat_prio_index(prio, &values[ddir]); + + ret = init_ts_clat_prio(ts, ddir, &values[ddir]); + if (ret) + goto err; + + free(values[ddir].prios); + values[ddir].prios = NULL; + values[ddir].nr_prios = 0; } - return false; + return 0; + +err: + for (ddir = 0; ddir < CMDPRIO_RWDIR_CNT; ddir++) + free(values[ddir].prios); + free_clat_prio_stats(ts); + + return ret; } static int fio_cmdprio_parse_and_gen_bssplit(struct thread_data *td, struct cmdprio *cmdprio) { struct cmdprio_options *options = cmdprio->options; - int ret; - - ret = fio_cmdprio_bssplit_parse(td, options->bssplit_str, cmdprio); + struct cmdprio_parse_result parse_res[CMDPRIO_RWDIR_CNT] = {0}; + struct cmdprio_values values[CMDPRIO_RWDIR_CNT] = {0}; + struct thread_stat *ts = &td->ts; + int ret, implicit_cmdprio; + enum fio_ddir ddir; + + ret = fio_cmdprio_bssplit_parse(td, options->bssplit_str, + &parse_res[0]); if (ret) goto err; + for (ddir = 0; ddir < CMDPRIO_RWDIR_CNT; ddir++) { + /* + * Do not allocate a clat_prio array nor set the cmdprio structs + * if there are no non-zero entries (for the ddir), or when the + * ddir is not enabled. + */ + if (!parse_res[ddir].nr_entries || + (ddir == DDIR_READ && !td_read(td)) || + (ddir == DDIR_WRITE && !td_write(td))) { + free(parse_res[ddir].entries); + parse_res[ddir].entries = NULL; + parse_res[ddir].nr_entries = 0; + continue; + } + + ret = init_cmdprio_values(&values[ddir], + parse_res[ddir].nr_entries, ts); + if (ret) + goto err; + + implicit_cmdprio = ioprio_value(options->class[ddir], + options->level[ddir]); + + ret = fio_cmdprio_generate_bsprio_desc(&cmdprio->bsprio_desc[ddir], + &parse_res[ddir], + &values[ddir], + implicit_cmdprio); + if (ret) + goto err; + + free(parse_res[ddir].entries); + parse_res[ddir].entries = NULL; + parse_res[ddir].nr_entries = 0; + + ret = init_ts_clat_prio(ts, ddir, &values[ddir]); + if (ret) + goto err; + + free(values[ddir].prios); + values[ddir].prios = NULL; + values[ddir].nr_prios = 0; + } + return 0; err: + for (ddir = 0; ddir < CMDPRIO_RWDIR_CNT; ddir++) { + free(parse_res[ddir].entries); + free(values[ddir].prios); + } + free_clat_prio_stats(ts); fio_cmdprio_cleanup(cmdprio); return ret; @@ -157,40 +441,46 @@ static int fio_cmdprio_parse_and_gen(struct thread_data *td, struct cmdprio_options *options = cmdprio->options; int i, ret; + /* + * If cmdprio_percentage/cmdprio_bssplit is set and cmdprio_class + * is not set, default to RT priority class. + */ + for (i = 0; i < CMDPRIO_RWDIR_CNT; i++) { + /* + * A cmdprio value is only used when fio_cmdprio_percentage() + * returns non-zero, so it is safe to set a class even for a + * DDIR that will never use it. + */ + if (!options->class[i]) + options->class[i] = IOPRIO_CLASS_RT; + } + switch (cmdprio->mode) { case CMDPRIO_MODE_BSSPLIT: ret = fio_cmdprio_parse_and_gen_bssplit(td, cmdprio); break; case CMDPRIO_MODE_PERC: - ret = 0; + ret = fio_cmdprio_gen_perc(td, cmdprio); break; default: assert(0); return 1; } - /* - * If cmdprio_percentage/cmdprio_bssplit is set and cmdprio_class - * is not set, default to RT priority class. - */ - for (i = 0; i < CMDPRIO_RWDIR_CNT; i++) { - if (options->percentage[i] || cmdprio->bssplit_nr[i]) { - if (!options->class[i]) - options->class[i] = IOPRIO_CLASS_RT; - } - } - return ret; } void fio_cmdprio_cleanup(struct cmdprio *cmdprio) { - int ddir; + enum fio_ddir ddir; + int i; for (ddir = 0; ddir < CMDPRIO_RWDIR_CNT; ddir++) { - free(cmdprio->bssplit[ddir]); - cmdprio->bssplit[ddir] = NULL; - cmdprio->bssplit_nr[ddir] = 0; + for (i = 0; i < cmdprio->bsprio_desc[ddir].nr_bsprios; i++) + free(cmdprio->bsprio_desc[ddir].bsprios[i].prios); + free(cmdprio->bsprio_desc[ddir].bsprios); + cmdprio->bsprio_desc[ddir].bsprios = NULL; + cmdprio->bsprio_desc[ddir].nr_bsprios = 0; } /* diff --git a/engines/cmdprio.h b/engines/cmdprio.h index 0c7bd6cf4b..755da8d0f8 100644 --- a/engines/cmdprio.h +++ b/engines/cmdprio.h @@ -17,6 +17,24 @@ enum { CMDPRIO_MODE_BSSPLIT, }; +struct cmdprio_prio { + int32_t prio; + uint32_t perc; + uint16_t clat_prio_index; +}; + +struct cmdprio_bsprio { + uint64_t bs; + uint32_t tot_perc; + unsigned int nr_prios; + struct cmdprio_prio *prios; +}; + +struct cmdprio_bsprio_desc { + struct cmdprio_bsprio *bsprios; + unsigned int nr_bsprios; +}; + struct cmdprio_options { unsigned int percentage[CMDPRIO_RWDIR_CNT]; unsigned int class[CMDPRIO_RWDIR_CNT]; @@ -26,8 +44,8 @@ struct cmdprio_options { struct cmdprio { struct cmdprio_options *options; - unsigned int bssplit_nr[CMDPRIO_RWDIR_CNT]; - struct bssplit *bssplit[CMDPRIO_RWDIR_CNT]; + struct cmdprio_prio perc_entry[CMDPRIO_RWDIR_CNT]; + struct cmdprio_bsprio_desc bsprio_desc[CMDPRIO_RWDIR_CNT]; unsigned int mode; }; diff --git a/fio.1 b/fio.1 index b87d2309ac..3c26a48dd4 100644 --- a/fio.1 +++ b/fio.1 @@ -1995,10 +1995,34 @@ To get a finer control over I/O priority, this option allows specifying the percentage of IOs that must have a priority set depending on the block size of the IO. This option is useful only when used together with the option \fBbssplit\fR, that is, multiple different block sizes are used for reads and -writes. The format for this option is the same as the format of the -\fBbssplit\fR option, with the exception that values for trim IOs are -ignored. This option is mutually exclusive with the \fBcmdprio_percentage\fR -option. +writes. +.RS +.P +The first accepted format for this option is the same as the format of the +\fBbssplit\fR option: +.RS +.P +cmdprio_bssplit=blocksize/percentage:blocksize/percentage +.RE +.P +In this case, each entry will use the priority class and priority level defined +by the options \fBcmdprio_class\fR and \fBcmdprio\fR respectively. +.P +The second accepted format for this option is: +.RS +.P +cmdprio_bssplit=blocksize/percentage/class/level:blocksize/percentage/class/level +.RE +.P +In this case, the priority class and priority level is defined inside each +entry. In comparison with the first accepted format, the second accepted format +does not restrict all entries to have the same priority class and priority +level. +.P +For both formats, only the read and write data directions are supported, values +for trim IOs are ignored. This option is mutually exclusive with the +\fBcmdprio_percentage\fR option. +.RE .TP .BI (io_uring)fixedbufs If fio is asked to do direct IO, then Linux will map pages for each IO call, and diff --git a/io_u.c b/io_u.c index 3c72d63d0d..656b46106d 100644 --- a/io_u.c +++ b/io_u.c @@ -1803,6 +1803,7 @@ struct io_u *get_io_u(struct thread_data *td) * Remember the issuing context priority. The IO engine may change this. */ io_u->ioprio = td->ioprio; + io_u->clat_prio_index = 0; out: assert(io_u->file); if (!td_io_prep(td, io_u)) { diff --git a/io_u.h b/io_u.h index bdbac52577..d88d5f2c3f 100644 --- a/io_u.h +++ b/io_u.h @@ -50,6 +50,7 @@ struct io_u { * IO priority. */ unsigned short ioprio; + unsigned short clat_prio_index; /* * Allocated/set buffer and length From 4927ccc96357b182746f3b7ce9fa3d7162131547 Mon Sep 17 00:00:00 2001 From: Niklas Cassel Date: Thu, 3 Feb 2022 19:28:28 +0000 Subject: [PATCH 0068/1097] examples: add new cmdprio_bssplit format examples Add examples of the new cmdprio_bssplit format to cmdprio-bssplit.fio. In this new format, a priority class and a priority level can be specified in the cmdprio_bssplit entry itself. Add the new cmdprio_bssplit format examples as new jobs, as the old format is still supported. Signed-off-by: Niklas Cassel Reviewed-by: Damien Le Moal Link: https://lore.kernel.org/r/20220203192814.18552-10-Niklas.Cassel@wdc.com Signed-off-by: Jens Axboe --- examples/cmdprio-bssplit.fio | 39 ++++++++++++++++++++++++++++++------ 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/examples/cmdprio-bssplit.fio b/examples/cmdprio-bssplit.fio index 47e9a79060..f3b2fac02d 100644 --- a/examples/cmdprio-bssplit.fio +++ b/examples/cmdprio-bssplit.fio @@ -1,17 +1,44 @@ ; Randomly read/write a block device file at queue depth 16. -; 40 % of read IOs are 64kB and 60% are 1MB. 100% of writes are 1MB. -; 100% of the 64kB reads are executed at the highest priority and -; all other IOs executed without a priority set. [global] filename=/dev/sda direct=1 write_lat_log=prio-run.log log_prio=1 - -[randrw] rw=randrw -bssplit=64k/40:1024k/60,1024k/100 ioengine=libaio iodepth=16 + +; Simple cmdprio_bssplit format. All non-zero percentage entries will +; use the same prio class and prio level defined by the cmdprio_class +; and cmdprio options. +[cmdprio] +; 40% of read IOs are 64kB and 60% are 1MB. 100% of writes are 1MB. +; 100% of the 64kB reads are executed with prio class 1 and prio level 0. +; All other IOs are executed without a priority set. +bssplit=64k/40:1024k/60,1024k/100 cmdprio_bssplit=64k/100:1024k/0,1024k/0 cmdprio_class=1 +cmdprio=0 + +; Advanced cmdprio_bssplit format. Each non-zero percentage entry can +; use a different prio class and prio level (appended to each entry). +[cmdprio-adv] +; 40% of read IOs are 64kB and 60% are 1MB. 100% of writes are 1MB. +; 25% of the 64kB reads are executed with prio class 1 and prio level 1, +; 75% of the 64kB reads are executed with prio class 3 and prio level 2. +; All other IOs are executed without a priority set. +stonewall +bssplit=64k/40:1024k/60,1024k/100 +cmdprio_bssplit=64k/25/1/1:64k/75/3/2:1024k/0,1024k/0 + +; Identical to the previous example, but with a default priority defined. +[cmdprio-adv-def] +; 40% of read IOs are 64kB and 60% are 1MB. 100% of writes are 1MB. +; 25% of the 64kB reads are executed with prio class 1 and prio level 1, +; 75% of the 64kB reads are executed with prio class 3 and prio level 2. +; All other IOs are executed with prio class 2 and prio level 7. +stonewall +prioclass=2 +prio=7 +bssplit=64k/40:1024k/60,1024k/100 +cmdprio_bssplit=64k/25/1/1:64k/75/3/2:1024k/0,1024k/0 From 2e5455223ed239eaa516491d33aed8e49325ec04 Mon Sep 17 00:00:00 2001 From: Niklas Cassel Date: Thu, 3 Feb 2022 19:28:28 +0000 Subject: [PATCH 0069/1097] stat: use enum fio_ddir consistently Most functions in stat.c uses enum fio_ddir dir both as a parameter and as a local variable in functions. int ddir is used in a very few places. Convert the int ddir uses to enum fio_ddir dir so that the code is consistent. Signed-off-by: Niklas Cassel Reviewed-by: Damien Le Moal Link: https://lore.kernel.org/r/20220203192814.18552-11-Niklas.Cassel@wdc.com Signed-off-by: Jens Axboe --- stat.c | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/stat.c b/stat.c index c8680a679b..d7f7362b5d 100644 --- a/stat.c +++ b/stat.c @@ -491,7 +491,8 @@ static struct thread_stat *gen_mixed_ddir_stats_from_ts(struct thread_stat *ts) return ts_lcl; } -static double convert_agg_kbytes_percent(struct group_run_stats *rs, int ddir, int mean) +static double convert_agg_kbytes_percent(struct group_run_stats *rs, + enum fio_ddir ddir, int mean) { double p_of_agg = 100.0; if (rs && rs->agg[ddir] > 1024) { @@ -504,7 +505,7 @@ static double convert_agg_kbytes_percent(struct group_run_stats *rs, int ddir, i } static void show_ddir_status(struct group_run_stats *rs, struct thread_stat *ts, - int ddir, struct buf_output *out) + enum fio_ddir ddir, struct buf_output *out) { unsigned long runt; unsigned long long min, max, bw, iops; @@ -1251,8 +1252,9 @@ static void show_thread_status_normal(struct thread_stat *ts, } static void show_ddir_status_terse(struct thread_stat *ts, - struct group_run_stats *rs, int ddir, - int ver, struct buf_output *out) + struct group_run_stats *rs, + enum fio_ddir ddir, int ver, + struct buf_output *out) { unsigned long long min, max, minv, maxv, bw, iops; unsigned long long *ovals = NULL; @@ -1407,7 +1409,8 @@ static struct json_object *add_ddir_lat_json(struct thread_stat *ts, } static void add_ddir_status_json(struct thread_stat *ts, - struct group_run_stats *rs, int ddir, struct json_object *parent) + struct group_run_stats *rs, enum fio_ddir ddir, + struct json_object *parent) { unsigned long long min, max; unsigned long long bw_bytes, bw; @@ -2353,7 +2356,7 @@ void __show_run_stats(void) } for (i = 0; i < groupid + 1; i++) { - int ddir; + enum fio_ddir ddir; rs = &runstats[i]; @@ -2861,7 +2864,7 @@ static void __add_stat_to_log(struct io_log *iolog, enum fio_ddir ddir, static void _add_stat_to_log(struct io_log *iolog, unsigned long elapsed, bool log_max) { - int ddir; + enum fio_ddir ddir; for (ddir = 0; ddir < DDIR_RWDIR_CNT; ddir++) __add_stat_to_log(iolog, ddir, elapsed, log_max); From a381c018f3811e701a1fdbb2c0ed2a6981a18443 Mon Sep 17 00:00:00 2001 From: Niklas Cassel Date: Thu, 3 Feb 2022 19:28:29 +0000 Subject: [PATCH 0070/1097] stat: increment members counter after call to sum_thread_stats() Increment ts->members after the call to sum_thread_stats(), just like how it's done in client.c and gclient.c. There is no reason why stat.c should increment ts->members before calling sum_thread_stats(). Change stat.c so that it is consistent with client.c and gclient.c. This way, sum_thread_stats() could actually make use of ts->members (if it wanted to), since it is now being updated consistently. No logical change, as currently, ts->members is only used in show_thread_status_normal(), which is always called after the call to sum_thread_stats(). Signed-off-by: Niklas Cassel Reviewed-by: Damien Le Moal Link: https://lore.kernel.org/r/20220203192814.18552-12-Niklas.Cassel@wdc.com Signed-off-by: Jens Axboe --- stat.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/stat.c b/stat.c index d7f7362b5d..633415121c 100644 --- a/stat.c +++ b/stat.c @@ -2241,7 +2241,6 @@ void __show_run_stats(void) opt_lists[j] = &td->opt_list; idx++; - ts->members++; if (ts->groupid == -1) { /* @@ -2308,6 +2307,8 @@ void __show_run_stats(void) sum_thread_stats(ts, &td->ts); + ts->members++; + if (td->o.ss_dur) { ts->ss_state = td->ss.state; ts->ss_dur = td->ss.dur; From 2f045d2e1129e53ad69fe98488194e946d9d8102 Mon Sep 17 00:00:00 2001 From: Niklas Cassel Date: Thu, 3 Feb 2022 19:28:29 +0000 Subject: [PATCH 0071/1097] stat: add helper for resetting the latency buckets Add a helper for resetting the latency buckets, and call it where appropriate. This makes the code easier to read, and puts the reset of the DDIR_SYNC latency buckets together with the other statements for DDIR_SYNC. A follow up patch will also make use of this new helper function. Signed-off-by: Niklas Cassel Reviewed-by: Damien Le Moal Link: https://lore.kernel.org/r/20220203192814.18552-13-Niklas.Cassel@wdc.com Signed-off-by: Jens Axboe --- stat.c | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/stat.c b/stat.c index 633415121c..baaa29f2d1 100644 --- a/stat.c +++ b/stat.c @@ -2786,10 +2786,18 @@ static inline void reset_io_stat(struct io_stat *ios) ios->mean.u.f = ios->S.u.f = 0; } +static inline void reset_io_u_plat(uint64_t *io_u_plat) +{ + int i; + + for (i = 0; i < FIO_IO_U_PLAT_NR; i++) + io_u_plat[i] = 0; +} + void reset_io_stats(struct thread_data *td) { struct thread_stat *ts = &td->ts; - int i, j, k; + int i, j; for (i = 0; i < DDIR_RWDIR_CNT; i++) { reset_io_stat(&ts->clat_high_prio_stat[i]); @@ -2806,20 +2814,16 @@ void reset_io_stats(struct thread_data *td) ts->short_io_u[i] = 0; ts->drop_io_u[i] = 0; - for (j = 0; j < FIO_IO_U_PLAT_NR; j++) { - ts->io_u_plat_high_prio[i][j] = 0; - ts->io_u_plat_low_prio[i][j] = 0; - if (!i) - ts->io_u_sync_plat[j] = 0; - } + reset_io_u_plat(ts->io_u_plat_high_prio[i]); + reset_io_u_plat(ts->io_u_plat_low_prio[i]); } for (i = 0; i < FIO_LAT_CNT; i++) for (j = 0; j < DDIR_RWDIR_CNT; j++) - for (k = 0; k < FIO_IO_U_PLAT_NR; k++) - ts->io_u_plat[i][j][k] = 0; + reset_io_u_plat(ts->io_u_plat[i][j]); ts->total_io_u[DDIR_SYNC] = 0; + reset_io_u_plat(ts->io_u_sync_plat); for (i = 0; i < FIO_IO_U_MAP_NR; i++) { ts->io_u_map[i] = 0; From 691310e297f1d1c9b016d8ff36b00991ece951e7 Mon Sep 17 00:00:00 2001 From: Niklas Cassel Date: Thu, 3 Feb 2022 19:28:30 +0000 Subject: [PATCH 0072/1097] stat: disable per prio stats where not needed In order to avoid allocating a clat_prio_stat array for threadstats that we know will never be able to contain more than a single priority, introduce a new member disable_prio_stat in struct thread_stat. The naming prefix is disable, since we want the default value to be 0 (enabled). This is because in default case, we do want sum_thread_stats() to generate a per prio stat array. Only in the case where we know that we don't want per priority stats to be generated, should this member be set to 1. Server version is intentionally not incremented, as it will be incremented in a later patch in the series. No need to bump it multiple times for the same patch series. Signed-off-by: Niklas Cassel Reviewed-by: Damien Le Moal Link: https://lore.kernel.org/r/20220203192814.18552-14-Niklas.Cassel@wdc.com Signed-off-by: Jens Axboe --- client.c | 1 + rate-submit.c | 9 +++++++++ server.c | 1 + stat.c | 54 +++++++++++++++++++++++++++++++++++++++++++++++++++ stat.h | 1 + 5 files changed, 66 insertions(+) diff --git a/client.c b/client.c index cf082c74e6..d7a80f0285 100644 --- a/client.c +++ b/client.c @@ -955,6 +955,7 @@ static void convert_ts(struct thread_stat *dst, struct thread_stat *src) dst->members = le32_to_cpu(src->members); dst->unified_rw_rep = le32_to_cpu(src->unified_rw_rep); dst->ioprio = le32_to_cpu(src->ioprio); + dst->disable_prio_stat = le32_to_cpu(src->disable_prio_stat); for (i = 0; i < DDIR_RWDIR_CNT; i++) { convert_io_stat(&dst->clat_stat[i], &src->clat_stat[i]); diff --git a/rate-submit.c b/rate-submit.c index 752c30a5f1..e3a7116801 100644 --- a/rate-submit.c +++ b/rate-submit.c @@ -195,6 +195,15 @@ static void io_workqueue_exit_worker_fn(struct submit_worker *sw, struct thread_data *td = sw->priv; (*sum_cnt)++; + + /* + * io_workqueue_update_acct_fn() doesn't support per prio stats, and + * even if it did, offload can't be used with all async IO engines. + * If group reporting is set in the parent td, the group result + * generated by __show_run_stats() can still contain multiple prios + * from different offloaded jobs. + */ + sw->wq->td->ts.disable_prio_stat = 1; sum_thread_stats(&sw->wq->td->ts, &td->ts); fio_options_free(td); diff --git a/server.c b/server.c index 05b65631fb..6b45ff2ee1 100644 --- a/server.c +++ b/server.c @@ -1706,6 +1706,7 @@ void fio_server_send_ts(struct thread_stat *ts, struct group_run_stats *rs) p.ts.members = cpu_to_le32(ts->members); p.ts.unified_rw_rep = cpu_to_le32(ts->unified_rw_rep); p.ts.ioprio = cpu_to_le32(ts->ioprio); + p.ts.disable_prio_stat = cpu_to_le32(ts->disable_prio_stat); for (i = 0; i < DDIR_RWDIR_CNT; i++) { convert_io_stat(&p.ts.clat_stat[i], &ts->clat_stat[i]); diff --git a/stat.c b/stat.c index baaa29f2d1..f783aed851 100644 --- a/stat.c +++ b/stat.c @@ -2171,6 +2171,58 @@ void init_thread_stat(struct thread_stat *ts) ts->groupid = -1; } +static void init_per_prio_stats(struct thread_stat *threadstats, int nr_ts) +{ + struct thread_data *td; + struct thread_stat *ts; + int i, j, last_ts, idx; + enum fio_ddir ddir; + + j = 0; + last_ts = -1; + idx = 0; + + /* + * Loop through all tds, if a td requires per prio stats, temporarily + * store a 1 in ts->disable_prio_stat, and then do an additional + * loop at the end where we invert the ts->disable_prio_stat values. + */ + for_each_td(td, i) { + if (!td->o.stats) + continue; + if (idx && + (!td->o.group_reporting || + (td->o.group_reporting && last_ts != td->groupid))) { + idx = 0; + j++; + } + + last_ts = td->groupid; + ts = &threadstats[j]; + + /* idx == 0 means first td in group, or td is not in a group. */ + if (idx == 0) + ts->ioprio = td->ioprio; + else if (td->ioprio != ts->ioprio) + ts->disable_prio_stat = 1; + + for (ddir = 0; ddir < DDIR_RWDIR_CNT; ddir++) { + if (td->ts.clat_prio[ddir]) { + ts->disable_prio_stat = 1; + break; + } + } + + idx++; + } + + /* Loop through all dst threadstats and fixup the values. */ + for (i = 0; i < nr_ts; i++) { + ts = &threadstats[i]; + ts->disable_prio_stat = !ts->disable_prio_stat; + } +} + void __show_run_stats(void) { struct group_run_stats *runstats, *rs; @@ -2217,6 +2269,8 @@ void __show_run_stats(void) opt_lists[i] = NULL; } + init_per_prio_stats(threadstats, nr_ts); + j = 0; last_ts = -1; idx = 0; diff --git a/stat.h b/stat.h index 188c57f3f8..6c86fa22b9 100644 --- a/stat.h +++ b/stat.h @@ -174,6 +174,7 @@ struct thread_stat { char description[FIO_JOBDESC_SIZE]; uint32_t members; uint32_t unified_rw_rep; + uint32_t disable_prio_stat; /* * bandwidth and latency stats From 692dec0cfb4bcf2ddcb6438cfbe73d585c7a3bbc Mon Sep 17 00:00:00 2001 From: Niklas Cassel Date: Thu, 3 Feb 2022 19:28:30 +0000 Subject: [PATCH 0073/1097] stat: report clat stats on a per priority granularity Convert the stat code to report clat stats on a per priority granularity, rather than simply supporting high/low priority. This is made possible by using the new clat_prio_stat array (per ddir), together with the clat_prio_stat index which is saved in each io_u. The per priority samples are only printed when there are samples for more than one priority in the clat_prio_stat array. If there are only samples for one priority, that means that all I/Os where submitted using the same priority, so no need to print. For example, running the following fio command: fio --name=test --filename=/dev/sdc --direct=1 --runtime=60 --rw=randread \ --ioengine=io_uring --ioscheduler=mq-deadline --iodepth=32 --bs=32k \ --prioclass=2 --prio=7 --cmdprio_bssplit=32k/20/3/0:32k/10/1/4 Now results in the following output: test: (groupid=0, jobs=1): err= 0: pid=465655: Tue Feb 1 02:24:47 2022 read: IOPS=146, BW=4695KiB/s (4808kB/s)(276MiB/60239msec) slat (usec): min=18, max=335, avg=62.87, stdev=22.59 clat (msec): min=2, max=2135, avg=217.97, stdev=287.26 lat (msec): min=2, max=2135, avg=218.03, stdev=287.26 clat prio 2/7 (msec): min=3, max=606, avg=106.57, stdev=86.64 clat prio 3/0 (msec): min=10, max=2135, avg=664.94, stdev=339.42 clat prio 1/4 (msec): min=2, max=300, avg=52.29, stdev=42.52 clat percentiles (msec): | 1.00th=[ 8], 5.00th=[ 14], 10.00th=[ 19], 20.00th=[ 33], | 30.00th=[ 52], 40.00th=[ 77], 50.00th=[ 108], 60.00th=[ 144], | 70.00th=[ 192], 80.00th=[ 300], 90.00th=[ 684], 95.00th=[ 911], | 99.00th=[ 1234], 99.50th=[ 1318], 99.90th=[ 1687], 99.95th=[ 1770], | 99.99th=[ 2140] clat prio 2/7 (69.25% of IOs) percentiles (msec): | 1.00th=[ 7], 5.00th=[ 13], 10.00th=[ 17], 20.00th=[ 28], | 30.00th=[ 44], 40.00th=[ 64], 50.00th=[ 85], 60.00th=[ 111], | 70.00th=[ 140], 80.00th=[ 174], 90.00th=[ 226], 95.00th=[ 279], | 99.00th=[ 368], 99.50th=[ 418], 99.90th=[ 502], 99.95th=[ 567], | 99.99th=[ 609] clat prio 3/0 (20.91% of IOs) percentiles (msec): | 1.00th=[ 44], 5.00th=[ 138], 10.00th=[ 205], 20.00th=[ 347], | 30.00th=[ 464], 40.00th=[ 558], 50.00th=[ 659], 60.00th=[ 760], | 70.00th=[ 860], 80.00th=[ 961], 90.00th=[ 1099], 95.00th=[ 1217], | 99.00th=[ 1485], 99.50th=[ 1687], 99.90th=[ 1871], 99.95th=[ 2140], | 99.99th=[ 2140] clat prio 1/4 (9.84% of IOs) percentiles (msec): | 1.00th=[ 7], 5.00th=[ 10], 10.00th=[ 13], 20.00th=[ 18], | 30.00th=[ 24], 40.00th=[ 30], 50.00th=[ 39], 60.00th=[ 51], | 70.00th=[ 63], 80.00th=[ 84], 90.00th=[ 114], 95.00th=[ 136], | 99.00th=[ 188], 99.50th=[ 197], 99.90th=[ 300], 99.95th=[ 300], | 99.99th=[ 300] bw ( KiB/s): min= 3456, max= 5888, per=100.00%, avg=4697.60, stdev=472.38, samples=120 iops : min= 108, max= 184, avg=146.80, stdev=14.76, samples=120 lat (msec) : 4=0.11%, 10=2.57%, 20=8.67%, 50=18.21%, 100=18.34% lat (msec) : 250=28.87%, 500=9.41%, 750=5.22%, 1000=5.09%, 2000=3.50% lat (msec) : >=2000=0.01% cpu : usr=0.16%, sys=0.97%, ctx=17715, majf=0, minf=262 IO depths : 1=0.1%, 2=0.1%, 4=0.1%, 8=0.1%, 16=0.2%, 32=99.6%, >=64=0.0% submit : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.0% complete : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.1%, 64=0.0%, >=64=0.0% issued rwts: total=8839,0,0,0 short=0,0,0,0 dropped=0,0,0,0 latency : target=0, window=0, percentile=100.00%, depth=32 Signed-off-by: Niklas Cassel Link: https://lore.kernel.org/r/20220203192814.18552-15-Niklas.Cassel@wdc.com Signed-off-by: Jens Axboe --- client.c | 31 +++- engines/filecreate.c | 2 +- engines/filedelete.c | 2 +- engines/filestat.c | 2 +- io_u.c | 6 +- io_u.h | 2 - server.c | 40 ++++- server.h | 2 +- stat.c | 351 +++++++++++++++++++++++++++++++++---------- stat.h | 4 +- 10 files changed, 338 insertions(+), 104 deletions(-) diff --git a/client.c b/client.c index d7a80f0285..605a3ce573 100644 --- a/client.c +++ b/client.c @@ -1038,14 +1038,6 @@ static void convert_ts(struct thread_stat *dst, struct thread_stat *src) dst->nr_block_infos = le64_to_cpu(src->nr_block_infos); for (i = 0; i < dst->nr_block_infos; i++) dst->block_infos[i] = le32_to_cpu(src->block_infos[i]); - for (i = 0; i < DDIR_RWDIR_CNT; i++) { - for (j = 0; j < FIO_IO_U_PLAT_NR; j++) { - dst->io_u_plat_high_prio[i][j] = le64_to_cpu(src->io_u_plat_high_prio[i][j]); - dst->io_u_plat_low_prio[i][j] = le64_to_cpu(src->io_u_plat_low_prio[i][j]); - } - convert_io_stat(&dst->clat_high_prio_stat[i], &src->clat_high_prio_stat[i]); - convert_io_stat(&dst->clat_low_prio_stat[i], &src->clat_low_prio_stat[i]); - } dst->ss_dur = le64_to_cpu(src->ss_dur); dst->ss_state = le32_to_cpu(src->ss_state); @@ -1055,6 +1047,19 @@ static void convert_ts(struct thread_stat *dst, struct thread_stat *src) dst->ss_deviation.u.f = fio_uint64_to_double(le64_to_cpu(src->ss_deviation.u.i)); dst->ss_criterion.u.f = fio_uint64_to_double(le64_to_cpu(src->ss_criterion.u.i)); + for (i = 0; i < DDIR_RWDIR_CNT; i++) { + dst->nr_clat_prio[i] = le32_to_cpu(src->nr_clat_prio[i]); + for (j = 0; j < dst->nr_clat_prio[i]; j++) { + for (k = 0; k < FIO_IO_U_PLAT_NR; k++) + dst->clat_prio[i][j].io_u_plat[k] = + le64_to_cpu(src->clat_prio[i][j].io_u_plat[k]); + convert_io_stat(&dst->clat_prio[i][j].clat_stat, + &src->clat_prio[i][j].clat_stat); + dst->clat_prio[i][j].ioprio = + le32_to_cpu(dst->clat_prio[i][j].ioprio); + } + } + if (dst->ss_state & FIO_SS_DATA) { for (i = 0; i < dst->ss_dur; i++ ) { dst->ss_iops_data[i] = le64_to_cpu(src->ss_iops_data[i]); @@ -1797,6 +1802,15 @@ int fio_handle_client(struct fio_client *client) case FIO_NET_CMD_TS: { struct cmd_ts_pdu *p = (struct cmd_ts_pdu *) cmd->payload; uint64_t offset; + int i; + + for (i = 0; i < DDIR_RWDIR_CNT; i++) { + if (le32_to_cpu(p->ts.nr_clat_prio[i])) { + offset = le64_to_cpu(p->ts.clat_prio_offset[i]); + p->ts.clat_prio[i] = + (struct clat_prio_stat *)((char *)p + offset); + } + } dprint(FD_NET, "client: ts->ss_state = %u\n", (unsigned int) le32_to_cpu(p->ts.ss_state)); if (le32_to_cpu(p->ts.ss_state) & FIO_SS_DATA) { @@ -2157,6 +2171,7 @@ int fio_handle_clients(struct client_ops *ops) fio_client_json_fini(); + free_clat_prio_stats(&client_ts); free(pfds); return retval || error_clients; } diff --git a/engines/filecreate.c b/engines/filecreate.c index 4bb13c348c..7884752d31 100644 --- a/engines/filecreate.c +++ b/engines/filecreate.c @@ -49,7 +49,7 @@ static int open_file(struct thread_data *td, struct fio_file *f) uint64_t nsec; nsec = ntime_since_now(&start); - add_clat_sample(td, data->stat_ddir, nsec, 0, 0, 0, false); + add_clat_sample(td, data->stat_ddir, nsec, 0, 0, 0, 0); } return 0; diff --git a/engines/filedelete.c b/engines/filedelete.c index e882ccf017..df388ac920 100644 --- a/engines/filedelete.c +++ b/engines/filedelete.c @@ -51,7 +51,7 @@ static int delete_file(struct thread_data *td, struct fio_file *f) uint64_t nsec; nsec = ntime_since_now(&start); - add_clat_sample(td, data->stat_ddir, nsec, 0, 0, 0, false); + add_clat_sample(td, data->stat_ddir, nsec, 0, 0, 0, 0); } return 0; diff --git a/engines/filestat.c b/engines/filestat.c index 003112474b..e587eb542d 100644 --- a/engines/filestat.c +++ b/engines/filestat.c @@ -125,7 +125,7 @@ static int stat_file(struct thread_data *td, struct fio_file *f) uint64_t nsec; nsec = ntime_since_now(&start); - add_clat_sample(td, data->stat_ddir, nsec, 0, 0, 0, false); + add_clat_sample(td, data->stat_ddir, nsec, 0, 0, 0, 0); } return 0; diff --git a/io_u.c b/io_u.c index 656b46106d..059637e592 100644 --- a/io_u.c +++ b/io_u.c @@ -1595,7 +1595,7 @@ struct io_u *__get_io_u(struct thread_data *td) assert(io_u->flags & IO_U_F_FREE); io_u_clear(td, io_u, IO_U_F_FREE | IO_U_F_NO_FILE_PUT | IO_U_F_TRIMMED | IO_U_F_BARRIER | - IO_U_F_VER_LIST | IO_U_F_HIGH_PRIO); + IO_U_F_VER_LIST); io_u->error = 0; io_u->acct_ddir = -1; @@ -1890,7 +1890,7 @@ static void account_io_completion(struct thread_data *td, struct io_u *io_u, tnsec = ntime_since(&io_u->start_time, &icd->time); add_lat_sample(td, idx, tnsec, bytes, io_u->offset, - io_u->ioprio, io_u_is_high_prio(io_u)); + io_u->ioprio, io_u->clat_prio_index); if (td->flags & TD_F_PROFILE_OPS) { struct prof_io_ops *ops = &td->prof_io_ops; @@ -1912,7 +1912,7 @@ static void account_io_completion(struct thread_data *td, struct io_u *io_u, if (ddir_rw(idx)) { if (!td->o.disable_clat) { add_clat_sample(td, idx, llnsec, bytes, io_u->offset, - io_u->ioprio, io_u_is_high_prio(io_u)); + io_u->ioprio, io_u->clat_prio_index); io_u_mark_latency(td, llnsec); } diff --git a/io_u.h b/io_u.h index d88d5f2c3f..206e24fee0 100644 --- a/io_u.h +++ b/io_u.h @@ -21,7 +21,6 @@ enum { IO_U_F_TRIMMED = 1 << 5, IO_U_F_BARRIER = 1 << 6, IO_U_F_VER_LIST = 1 << 7, - IO_U_F_HIGH_PRIO = 1 << 8, }; /* @@ -194,6 +193,5 @@ static inline enum fio_ddir acct_ddir(struct io_u *io_u) td_flags_clear((td), &(io_u->flags), (val)) #define io_u_set(td, io_u, val) \ td_flags_set((td), &(io_u)->flags, (val)) -#define io_u_is_high_prio(io_u) (io_u->flags & IO_U_F_HIGH_PRIO) #endif diff --git a/server.c b/server.c index 6b45ff2ee1..914a8c74cd 100644 --- a/server.c +++ b/server.c @@ -1685,6 +1685,7 @@ void fio_server_send_ts(struct thread_stat *ts, struct group_run_stats *rs) { struct cmd_ts_pdu p; int i, j, k; + size_t clat_prio_stats_extra_size = 0; size_t ss_extra_size = 0; size_t extended_buf_size = 0; void *extended_buf; @@ -1801,16 +1802,13 @@ void fio_server_send_ts(struct thread_stat *ts, struct group_run_stats *rs) p.ts.cachehit = cpu_to_le64(ts->cachehit); p.ts.cachemiss = cpu_to_le64(ts->cachemiss); + convert_gs(&p.rs, rs); + for (i = 0; i < DDIR_RWDIR_CNT; i++) { - for (j = 0; j < FIO_IO_U_PLAT_NR; j++) { - p.ts.io_u_plat_high_prio[i][j] = cpu_to_le64(ts->io_u_plat_high_prio[i][j]); - p.ts.io_u_plat_low_prio[i][j] = cpu_to_le64(ts->io_u_plat_low_prio[i][j]); - } - convert_io_stat(&p.ts.clat_high_prio_stat[i], &ts->clat_high_prio_stat[i]); - convert_io_stat(&p.ts.clat_low_prio_stat[i], &ts->clat_low_prio_stat[i]); + if (ts->nr_clat_prio[i]) + clat_prio_stats_extra_size += ts->nr_clat_prio[i] * sizeof(*ts->clat_prio[i]); } - - convert_gs(&p.rs, rs); + extended_buf_size += clat_prio_stats_extra_size; dprint(FD_NET, "ts->ss_state = %d\n", ts->ss_state); if (ts->ss_state & FIO_SS_DATA) @@ -1832,6 +1830,32 @@ void fio_server_send_ts(struct thread_stat *ts, struct group_run_stats *rs) memcpy(extended_buf, &p, sizeof(p)); extended_buf_wp = (struct cmd_ts_pdu *)extended_buf + 1; + if (clat_prio_stats_extra_size) { + for (i = 0; i < DDIR_RWDIR_CNT; i++) { + struct clat_prio_stat *prio = (struct clat_prio_stat *) extended_buf_wp; + + for (j = 0; j < ts->nr_clat_prio[i]; j++) { + for (k = 0; k < FIO_IO_U_PLAT_NR; k++) + prio->io_u_plat[k] = + cpu_to_le64(ts->clat_prio[i][j].io_u_plat[k]); + convert_io_stat(&prio->clat_stat, + &ts->clat_prio[i][j].clat_stat); + prio->ioprio = cpu_to_le32(ts->clat_prio[i][j].ioprio); + prio++; + } + + if (ts->nr_clat_prio[i]) { + uint64_t offset = (char *)extended_buf_wp - (char *)extended_buf; + struct cmd_ts_pdu *ptr = extended_buf; + + ptr->ts.clat_prio_offset[i] = cpu_to_le64(offset); + ptr->ts.nr_clat_prio[i] = cpu_to_le32(ts->nr_clat_prio[i]); + } + + extended_buf_wp = prio; + } + } + if (ss_extra_size) { uint64_t *ss_iops, *ss_bw; uint64_t offset; diff --git a/server.h b/server.h index 35a3dc1d55..0e62b6dfe8 100644 --- a/server.h +++ b/server.h @@ -51,7 +51,7 @@ struct fio_net_cmd_reply { }; enum { - FIO_SERVER_VER = 95, + FIO_SERVER_VER = 96, FIO_SERVER_MAX_FRAGMENT_PDU = 1024, FIO_SERVER_MAX_CMD_MB = 2048, diff --git a/stat.c b/stat.c index f783aed851..a6810d9b65 100644 --- a/stat.c +++ b/stat.c @@ -265,6 +265,18 @@ static void show_clat_percentiles(uint64_t *io_u_plat, unsigned long long nr, free(ovals); } +static int get_nr_prios_with_samples(struct thread_stat *ts, enum fio_ddir ddir) +{ + int i, nr_prios_with_samples = 0; + + for (i = 0; i < ts->nr_clat_prio[ddir]; i++) { + if (ts->clat_prio[ddir][i].clat_stat.samples) + nr_prios_with_samples++; + } + + return nr_prios_with_samples; +} + bool calc_lat(struct io_stat *is, unsigned long long *min, unsigned long long *max, double *mean, double *dev) { @@ -511,7 +523,8 @@ static void show_ddir_status(struct group_run_stats *rs, struct thread_stat *ts, unsigned long long min, max, bw, iops; double mean, dev; char *io_p, *bw_p, *bw_p_alt, *iops_p, *post_st = NULL; - int i2p; + int i2p, i; + const char *clat_type = ts->lat_percentiles ? "lat" : "clat"; if (ddir_sync(ddir)) { if (calc_lat(&ts->sync_stat, &min, &max, &mean, &dev)) { @@ -572,12 +585,22 @@ static void show_ddir_status(struct group_run_stats *rs, struct thread_stat *ts, display_lat("clat", min, max, mean, dev, out); if (calc_lat(&ts->lat_stat[ddir], &min, &max, &mean, &dev)) display_lat(" lat", min, max, mean, dev, out); - if (calc_lat(&ts->clat_high_prio_stat[ddir], &min, &max, &mean, &dev)) { - display_lat(ts->lat_percentiles ? "high prio_lat" : "high prio_clat", - min, max, mean, dev, out); - if (calc_lat(&ts->clat_low_prio_stat[ddir], &min, &max, &mean, &dev)) - display_lat(ts->lat_percentiles ? "low prio_lat" : "low prio_clat", - min, max, mean, dev, out); + + /* Only print per prio stats if there are >= 2 prios with samples */ + if (get_nr_prios_with_samples(ts, ddir) >= 2) { + for (i = 0; i < ts->nr_clat_prio[ddir]; i++) { + if (calc_lat(&ts->clat_prio[ddir][i].clat_stat, &min, + &max, &mean, &dev)) { + char buf[64]; + + snprintf(buf, sizeof(buf), + "%s prio %u/%u", + clat_type, + ts->clat_prio[ddir][i].ioprio >> 13, + ts->clat_prio[ddir][i].ioprio & 7); + display_lat(buf, min, max, mean, dev, out); + } + } } if (ts->slat_percentiles && ts->slat_stat[ddir].samples > 0) @@ -597,8 +620,7 @@ static void show_ddir_status(struct group_run_stats *rs, struct thread_stat *ts, ts->percentile_precision, "lat", out); if (ts->clat_percentiles || ts->lat_percentiles) { - const char *name = ts->lat_percentiles ? "lat" : "clat"; - char prio_name[32]; + char prio_name[64]; uint64_t samples; if (ts->lat_percentiles) @@ -606,25 +628,24 @@ static void show_ddir_status(struct group_run_stats *rs, struct thread_stat *ts, else samples = ts->clat_stat[ddir].samples; - /* Only print this if some high and low priority stats were collected */ - if (ts->clat_high_prio_stat[ddir].samples > 0 && - ts->clat_low_prio_stat[ddir].samples > 0) - { - sprintf(prio_name, "high prio (%.2f%%) %s", - 100. * (double) ts->clat_high_prio_stat[ddir].samples / (double) samples, - name); - show_clat_percentiles(ts->io_u_plat_high_prio[ddir], - ts->clat_high_prio_stat[ddir].samples, - ts->percentile_list, - ts->percentile_precision, prio_name, out); - - sprintf(prio_name, "low prio (%.2f%%) %s", - 100. * (double) ts->clat_low_prio_stat[ddir].samples / (double) samples, - name); - show_clat_percentiles(ts->io_u_plat_low_prio[ddir], - ts->clat_low_prio_stat[ddir].samples, - ts->percentile_list, - ts->percentile_precision, prio_name, out); + /* Only print per prio stats if there are >= 2 prios with samples */ + if (get_nr_prios_with_samples(ts, ddir) >= 2) { + for (i = 0; i < ts->nr_clat_prio[ddir]; i++) { + uint64_t prio_samples = ts->clat_prio[ddir][i].clat_stat.samples; + + if (prio_samples > 0) { + snprintf(prio_name, sizeof(prio_name), + "%s prio %u/%u (%.2f%% of IOs)", + clat_type, + ts->clat_prio[ddir][i].ioprio >> 13, + ts->clat_prio[ddir][i].ioprio & 7, + 100. * (double) prio_samples / (double) samples); + show_clat_percentiles(ts->clat_prio[ddir][i].io_u_plat, + prio_samples, ts->percentile_list, + ts->percentile_precision, + prio_name, out); + } + } } } @@ -679,6 +700,7 @@ static void show_mixed_ddir_status(struct group_run_stats *rs, if (ts_lcl) show_ddir_status(rs, ts_lcl, DDIR_READ, out); + free_clat_prio_stats(ts_lcl); free(ts_lcl); } @@ -1353,6 +1375,7 @@ static void show_mixed_ddir_status_terse(struct thread_stat *ts, if (ts_lcl) show_ddir_status_terse(ts_lcl, rs, DDIR_READ, ver, out); + free_clat_prio_stats(ts_lcl); free(ts_lcl); } @@ -1537,6 +1560,7 @@ static void add_mixed_ddir_status_json(struct thread_stat *ts, if (ts_lcl) add_ddir_status_json(ts_lcl, rs, DDIR_READ, parent); + free_clat_prio_stats(ts_lcl); free(ts_lcl); } @@ -2038,6 +2062,175 @@ int alloc_clat_prio_stat_ddir(struct thread_stat *ts, enum fio_ddir ddir, return 0; } +static int grow_clat_prio_stat(struct thread_stat *dst, enum fio_ddir ddir) +{ + int curr_len = dst->nr_clat_prio[ddir]; + void *new_arr; + + new_arr = scalloc(curr_len + 1, sizeof(*dst->clat_prio[ddir])); + if (!new_arr) { + log_err("fio: failed to grow clat prio array\n"); + return 1; + } + + memcpy(new_arr, dst->clat_prio[ddir], + curr_len * sizeof(*dst->clat_prio[ddir])); + sfree(dst->clat_prio[ddir]); + + dst->clat_prio[ddir] = new_arr; + dst->clat_prio[ddir][curr_len].clat_stat.min_val = ULONG_MAX; + dst->nr_clat_prio[ddir]++; + + return 0; +} + +static int find_clat_prio_index(struct thread_stat *dst, enum fio_ddir ddir, + uint32_t ioprio) +{ + int i, nr_prios = dst->nr_clat_prio[ddir]; + + for (i = 0; i < nr_prios; i++) { + if (dst->clat_prio[ddir][i].ioprio == ioprio) + return i; + } + + return -1; +} + +static int alloc_or_get_clat_prio_index(struct thread_stat *dst, + enum fio_ddir ddir, uint32_t ioprio, + int *idx) +{ + int index = find_clat_prio_index(dst, ddir, ioprio); + + if (index == -1) { + index = dst->nr_clat_prio[ddir]; + + if (grow_clat_prio_stat(dst, ddir)) + return 1; + + dst->clat_prio[ddir][index].ioprio = ioprio; + } + + *idx = index; + + return 0; +} + +static int clat_prio_stats_copy(struct thread_stat *dst, struct thread_stat *src, + enum fio_ddir dst_ddir, enum fio_ddir src_ddir) +{ + size_t sz = sizeof(*src->clat_prio[src_ddir]) * + src->nr_clat_prio[src_ddir]; + + dst->clat_prio[dst_ddir] = smalloc(sz); + if (!dst->clat_prio[dst_ddir]) { + log_err("fio: failed to alloc clat prio array\n"); + return 1; + } + + memcpy(dst->clat_prio[dst_ddir], src->clat_prio[src_ddir], sz); + dst->nr_clat_prio[dst_ddir] = src->nr_clat_prio[src_ddir]; + + return 0; +} + +static int clat_prio_stat_add_samples(struct thread_stat *dst, + enum fio_ddir dst_ddir, uint32_t ioprio, + struct io_stat *io_stat, + uint64_t *io_u_plat) +{ + int i, dst_index; + + if (!io_stat->samples) + return 0; + + if (alloc_or_get_clat_prio_index(dst, dst_ddir, ioprio, &dst_index)) + return 1; + + sum_stat(&dst->clat_prio[dst_ddir][dst_index].clat_stat, io_stat, + false); + + for (i = 0; i < FIO_IO_U_PLAT_NR; i++) + dst->clat_prio[dst_ddir][dst_index].io_u_plat[i] += io_u_plat[i]; + + return 0; +} + +static int sum_clat_prio_stats_src_single_prio(struct thread_stat *dst, + struct thread_stat *src, + enum fio_ddir dst_ddir, + enum fio_ddir src_ddir) +{ + struct io_stat *io_stat; + uint64_t *io_u_plat; + + /* + * If src ts has no clat_prio_stat array, then all I/Os were submitted + * using src->ioprio. Thus, the global samples in src->clat_stat (or + * src->lat_stat) can be used as the 'per prio' samples for src->ioprio. + */ + assert(!src->clat_prio[src_ddir]); + assert(src->nr_clat_prio[src_ddir] == 0); + + if (src->lat_percentiles) { + io_u_plat = src->io_u_plat[FIO_LAT][src_ddir]; + io_stat = &src->lat_stat[src_ddir]; + } else { + io_u_plat = src->io_u_plat[FIO_CLAT][src_ddir]; + io_stat = &src->clat_stat[src_ddir]; + } + + return clat_prio_stat_add_samples(dst, dst_ddir, src->ioprio, io_stat, + io_u_plat); +} + +static int sum_clat_prio_stats_src_multi_prio(struct thread_stat *dst, + struct thread_stat *src, + enum fio_ddir dst_ddir, + enum fio_ddir src_ddir) +{ + int i; + + /* + * If src ts has a clat_prio_stat array, then there are multiple prios + * in use (i.e. src ts had cmdprio_percentage or cmdprio_bssplit set). + * The samples for the default prio will exist in the src->clat_prio + * array, just like the samples for any other prio. + */ + assert(src->clat_prio[src_ddir]); + assert(src->nr_clat_prio[src_ddir]); + + /* If the dst ts doesn't yet have a clat_prio array, simply memcpy. */ + if (!dst->clat_prio[dst_ddir]) + return clat_prio_stats_copy(dst, src, dst_ddir, src_ddir); + + /* The dst ts already has a clat_prio_array, add src stats into it. */ + for (i = 0; i < src->nr_clat_prio[src_ddir]; i++) { + struct io_stat *io_stat = &src->clat_prio[src_ddir][i].clat_stat; + uint64_t *io_u_plat = src->clat_prio[src_ddir][i].io_u_plat; + uint32_t ioprio = src->clat_prio[src_ddir][i].ioprio; + + if (clat_prio_stat_add_samples(dst, dst_ddir, ioprio, io_stat, io_u_plat)) + return 1; + } + + return 0; +} + +static int sum_clat_prio_stats(struct thread_stat *dst, struct thread_stat *src, + enum fio_ddir dst_ddir, enum fio_ddir src_ddir) +{ + if (dst->disable_prio_stat) + return 0; + + if (!src->clat_prio[src_ddir]) + return sum_clat_prio_stats_src_single_prio(dst, src, dst_ddir, + src_ddir); + + return sum_clat_prio_stats_src_multi_prio(dst, src, dst_ddir, src_ddir); +} + void sum_thread_stats(struct thread_stat *dst, struct thread_stat *src) { int k, l, m; @@ -2045,12 +2238,11 @@ void sum_thread_stats(struct thread_stat *dst, struct thread_stat *src) for (l = 0; l < DDIR_RWDIR_CNT; l++) { if (dst->unified_rw_rep != UNIFIED_MIXED) { sum_stat(&dst->clat_stat[l], &src->clat_stat[l], false); - sum_stat(&dst->clat_high_prio_stat[l], &src->clat_high_prio_stat[l], false); - sum_stat(&dst->clat_low_prio_stat[l], &src->clat_low_prio_stat[l], false); sum_stat(&dst->slat_stat[l], &src->slat_stat[l], false); sum_stat(&dst->lat_stat[l], &src->lat_stat[l], false); sum_stat(&dst->bw_stat[l], &src->bw_stat[l], true); sum_stat(&dst->iops_stat[l], &src->iops_stat[l], true); + sum_clat_prio_stats(dst, src, l, l); dst->io_bytes[l] += src->io_bytes[l]; @@ -2058,12 +2250,11 @@ void sum_thread_stats(struct thread_stat *dst, struct thread_stat *src) dst->runtime[l] = src->runtime[l]; } else { sum_stat(&dst->clat_stat[0], &src->clat_stat[l], false); - sum_stat(&dst->clat_high_prio_stat[0], &src->clat_high_prio_stat[l], false); - sum_stat(&dst->clat_low_prio_stat[0], &src->clat_low_prio_stat[l], false); sum_stat(&dst->slat_stat[0], &src->slat_stat[l], false); sum_stat(&dst->lat_stat[0], &src->lat_stat[l], false); sum_stat(&dst->bw_stat[0], &src->bw_stat[l], true); sum_stat(&dst->iops_stat[0], &src->iops_stat[l], true); + sum_clat_prio_stats(dst, src, 0, l); dst->io_bytes[0] += src->io_bytes[l]; @@ -2117,19 +2308,6 @@ void sum_thread_stats(struct thread_stat *dst, struct thread_stat *src) for (k = 0; k < FIO_IO_U_PLAT_NR; k++) dst->io_u_sync_plat[k] += src->io_u_sync_plat[k]; - for (k = 0; k < DDIR_RWDIR_CNT; k++) { - for (m = 0; m < FIO_IO_U_PLAT_NR; m++) { - if (dst->unified_rw_rep != UNIFIED_MIXED) { - dst->io_u_plat_high_prio[k][m] += src->io_u_plat_high_prio[k][m]; - dst->io_u_plat_low_prio[k][m] += src->io_u_plat_low_prio[k][m]; - } else { - dst->io_u_plat_high_prio[0][m] += src->io_u_plat_high_prio[k][m]; - dst->io_u_plat_low_prio[0][m] += src->io_u_plat_low_prio[k][m]; - } - - } - } - dst->total_run_time += src->total_run_time; dst->total_submit += src->total_submit; dst->total_complete += src->total_complete; @@ -2157,8 +2335,6 @@ void init_thread_stat_min_vals(struct thread_stat *ts) ts->lat_stat[i].min_val = ULONG_MAX; ts->bw_stat[i].min_val = ULONG_MAX; ts->iops_stat[i].min_val = ULONG_MAX; - ts->clat_high_prio_stat[i].min_val = ULONG_MAX; - ts->clat_low_prio_stat[i].min_val = ULONG_MAX; } ts->sync_stat.min_val = ULONG_MAX; } @@ -2517,6 +2693,12 @@ void __show_run_stats(void) log_info_flush(); free(runstats); + + /* free arrays allocated by sum_thread_stats(), if any */ + for (i = 0; i < nr_ts; i++) { + ts = &threadstats[i]; + free_clat_prio_stats(ts); + } free(threadstats); free(opt_lists); } @@ -2643,6 +2825,14 @@ static inline void add_stat_sample(struct io_stat *is, unsigned long long data) is->samples++; } +static inline void add_stat_prio_sample(struct clat_prio_stat *clat_prio, + unsigned short clat_prio_index, + unsigned long long nsec) +{ + if (clat_prio) + add_stat_sample(&clat_prio[clat_prio_index].clat_stat, nsec); +} + /* * Return a struct io_logs, which is added to the tail of the log * list for 'iolog'. @@ -2848,14 +3038,28 @@ static inline void reset_io_u_plat(uint64_t *io_u_plat) io_u_plat[i] = 0; } +static inline void reset_clat_prio_stats(struct thread_stat *ts) +{ + enum fio_ddir ddir; + int i; + + for (ddir = 0; ddir < DDIR_RWDIR_CNT; ddir++) { + if (!ts->clat_prio[ddir]) + continue; + + for (i = 0; i < ts->nr_clat_prio[ddir]; i++) { + reset_io_stat(&ts->clat_prio[ddir][i].clat_stat); + reset_io_u_plat(ts->clat_prio[ddir][i].io_u_plat); + } + } +} + void reset_io_stats(struct thread_data *td) { struct thread_stat *ts = &td->ts; int i, j; for (i = 0; i < DDIR_RWDIR_CNT; i++) { - reset_io_stat(&ts->clat_high_prio_stat[i]); - reset_io_stat(&ts->clat_low_prio_stat[i]); reset_io_stat(&ts->clat_stat[i]); reset_io_stat(&ts->slat_stat[i]); reset_io_stat(&ts->lat_stat[i]); @@ -2867,15 +3071,14 @@ void reset_io_stats(struct thread_data *td) ts->total_io_u[i] = 0; ts->short_io_u[i] = 0; ts->drop_io_u[i] = 0; - - reset_io_u_plat(ts->io_u_plat_high_prio[i]); - reset_io_u_plat(ts->io_u_plat_low_prio[i]); } for (i = 0; i < FIO_LAT_CNT; i++) for (j = 0; j < DDIR_RWDIR_CNT; j++) reset_io_u_plat(ts->io_u_plat[i][j]); + reset_clat_prio_stats(ts); + ts->total_io_u[DDIR_SYNC] = 0; reset_io_u_plat(ts->io_u_sync_plat); @@ -3028,22 +3231,21 @@ static inline void add_lat_percentile_sample(struct thread_stat *ts, ts->io_u_plat[lat][ddir][idx]++; } -static inline void add_lat_percentile_prio_sample(struct thread_stat *ts, - unsigned long long nsec, - enum fio_ddir ddir, - bool high_prio) +static inline void +add_lat_percentile_prio_sample(struct thread_stat *ts, unsigned long long nsec, + enum fio_ddir ddir, + unsigned short clat_prio_index) { unsigned int idx = plat_val_to_idx(nsec); - if (!high_prio) - ts->io_u_plat_low_prio[ddir][idx]++; - else - ts->io_u_plat_high_prio[ddir][idx]++; + if (ts->clat_prio[ddir]) + ts->clat_prio[ddir][clat_prio_index].io_u_plat[idx]++; } void add_clat_sample(struct thread_data *td, enum fio_ddir ddir, unsigned long long nsec, unsigned long long bs, - uint64_t offset, unsigned int ioprio, bool high_prio) + uint64_t offset, unsigned int ioprio, + unsigned short clat_prio_index) { const bool needs_lock = td_async_processing(td); unsigned long elapsed, this_window; @@ -3056,7 +3258,7 @@ void add_clat_sample(struct thread_data *td, enum fio_ddir ddir, add_stat_sample(&ts->clat_stat[ddir], nsec); /* - * When lat_percentiles=1 (default 0), the reported high/low priority + * When lat_percentiles=1 (default 0), the reported per priority * percentiles and stats are used for describing total latency values, * even though the variable names themselves start with clat_. * @@ -3064,12 +3266,9 @@ void add_clat_sample(struct thread_data *td, enum fio_ddir ddir, * lat_percentiles=0. add_lat_sample() will add the prio stat sample * when lat_percentiles=1. */ - if (!ts->lat_percentiles) { - if (high_prio) - add_stat_sample(&ts->clat_high_prio_stat[ddir], nsec); - else - add_stat_sample(&ts->clat_low_prio_stat[ddir], nsec); - } + if (!ts->lat_percentiles) + add_stat_prio_sample(ts->clat_prio[ddir], clat_prio_index, + nsec); if (td->clat_log) add_log_sample(td, td->clat_log, sample_val(nsec), ddir, bs, @@ -3084,7 +3283,7 @@ void add_clat_sample(struct thread_data *td, enum fio_ddir ddir, add_lat_percentile_sample(ts, nsec, ddir, FIO_CLAT); if (!ts->lat_percentiles) add_lat_percentile_prio_sample(ts, nsec, ddir, - high_prio); + clat_prio_index); } if (iolog && iolog->hist_msec) { @@ -3157,7 +3356,8 @@ void add_slat_sample(struct thread_data *td, enum fio_ddir ddir, void add_lat_sample(struct thread_data *td, enum fio_ddir ddir, unsigned long long nsec, unsigned long long bs, - uint64_t offset, unsigned int ioprio, bool high_prio) + uint64_t offset, unsigned int ioprio, + unsigned short clat_prio_index) { const bool needs_lock = td_async_processing(td); struct thread_stat *ts = &td->ts; @@ -3175,7 +3375,7 @@ void add_lat_sample(struct thread_data *td, enum fio_ddir ddir, offset, ioprio); /* - * When lat_percentiles=1 (default 0), the reported high/low priority + * When lat_percentiles=1 (default 0), the reported per priority * percentiles and stats are used for describing total latency values, * even though the variable names themselves start with clat_. * @@ -3186,12 +3386,9 @@ void add_lat_sample(struct thread_data *td, enum fio_ddir ddir, */ if (ts->lat_percentiles) { add_lat_percentile_sample(ts, nsec, ddir, FIO_LAT); - add_lat_percentile_prio_sample(ts, nsec, ddir, high_prio); - if (high_prio) - add_stat_sample(&ts->clat_high_prio_stat[ddir], nsec); - else - add_stat_sample(&ts->clat_low_prio_stat[ddir], nsec); - + add_lat_percentile_prio_sample(ts, nsec, ddir, clat_prio_index); + add_stat_prio_sample(ts->clat_prio[ddir], clat_prio_index, + nsec); } if (needs_lock) __td_io_u_unlock(td); diff --git a/stat.h b/stat.h index 6c86fa22b9..05228fd2c1 100644 --- a/stat.h +++ b/stat.h @@ -373,9 +373,9 @@ extern void update_rusage_stat(struct thread_data *); extern void clear_rusage_stat(struct thread_data *); extern void add_lat_sample(struct thread_data *, enum fio_ddir, unsigned long long, - unsigned long long, uint64_t, unsigned int, bool); + unsigned long long, uint64_t, unsigned int, unsigned short); extern void add_clat_sample(struct thread_data *, enum fio_ddir, unsigned long long, - unsigned long long, uint64_t, unsigned int, bool); + unsigned long long, uint64_t, unsigned int, unsigned short); extern void add_slat_sample(struct thread_data *, enum fio_ddir, unsigned long long, unsigned long long, uint64_t, unsigned int); extern void add_agg_sample(union io_sample_data, enum fio_ddir, unsigned long long); From 1cbbba655caf4f68d0dfcaabff5069a51b8cbb9e Mon Sep 17 00:00:00 2001 From: Niklas Cassel Date: Thu, 3 Feb 2022 19:28:31 +0000 Subject: [PATCH 0074/1097] stat: convert json output to a new per priority granularity format The JSON output will no longer contain high_prio/low_prio entries, but will instead include a new list "prios", which will include an object per prioclass/priolevel combination. Each of these objects will either have a "clat_ns" object or a "lat_ns" object, depending on which latency type was being tracked. This JSON structure should make it easy if the per priority stats were ever extended to be able to track multiple latency types at the same time, as each prioclass/priolevel object will then simply contain (e.g.) both a "clat_ns" and a "lat_ns" object. Convert the JSON output to this new per priority granularity format, and convert the tests to work with the new JSON output. Signed-off-by: Niklas Cassel Reviewed-by: Damien Le Moal Link: https://lore.kernel.org/r/20220203192814.18552-16-Niklas.Cassel@wdc.com Signed-off-by: Jens Axboe --- stat.c | 44 +++++++++------ t/latency_percentiles.py | 118 +++++++++++++++++++-------------------- 2 files changed, 87 insertions(+), 75 deletions(-) diff --git a/stat.c b/stat.c index a6810d9b65..24fc679fa1 100644 --- a/stat.c +++ b/stat.c @@ -1493,25 +1493,37 @@ static void add_ddir_status_json(struct thread_stat *ts, if (!ddir_rw(ddir)) return; - /* Only print PRIO latencies if some high priority samples were gathered */ - if (ts->clat_high_prio_stat[ddir].samples > 0) { - const char *high, *low; + /* Only include per prio stats if there are >= 2 prios with samples */ + if (get_nr_prios_with_samples(ts, ddir) >= 2) { + struct json_array *array = json_create_array(); + const char *obj_name; + int i; - if (ts->lat_percentiles) { - high = "lat_high_prio"; - low = "lat_low_prio"; - } else { - high = "clat_high_prio"; - low = "clat_low_prio"; - } + if (ts->lat_percentiles) + obj_name = "lat_ns"; + else + obj_name = "clat_ns"; - tmp_object = add_ddir_lat_json(ts, ts->clat_percentiles | ts->lat_percentiles, - &ts->clat_high_prio_stat[ddir], ts->io_u_plat_high_prio[ddir]); - json_object_add_value_object(dir_object, high, tmp_object); + json_object_add_value_array(dir_object, "prios", array); - tmp_object = add_ddir_lat_json(ts, ts->clat_percentiles | ts->lat_percentiles, - &ts->clat_low_prio_stat[ddir], ts->io_u_plat_low_prio[ddir]); - json_object_add_value_object(dir_object, low, tmp_object); + for (i = 0; i < ts->nr_clat_prio[ddir]; i++) { + if (ts->clat_prio[ddir][i].clat_stat.samples > 0) { + struct json_object *obj = json_create_object(); + unsigned long long class, level; + + class = ts->clat_prio[ddir][i].ioprio >> 13; + json_object_add_value_int(obj, "prioclass", class); + level = ts->clat_prio[ddir][i].ioprio & 7; + json_object_add_value_int(obj, "prio", level); + + tmp_object = add_ddir_lat_json(ts, + ts->clat_percentiles | ts->lat_percentiles, + &ts->clat_prio[ddir][i].clat_stat, + ts->clat_prio[ddir][i].io_u_plat); + json_object_add_value_object(obj, obj_name, tmp_object); + json_array_add_value_object(array, obj); + } + } } if (calc_lat(&ts->bw_stat[ddir], &min, &max, &mean, &dev)) { diff --git a/t/latency_percentiles.py b/t/latency_percentiles.py index cc4374262e..62c4cc911d 100755 --- a/t/latency_percentiles.py +++ b/t/latency_percentiles.py @@ -80,6 +80,7 @@ import argparse import platform import subprocess +from collections import Counter from pathlib import Path @@ -363,20 +364,19 @@ def check_empty(job): def check_nocmdprio_lat(self, job): """ - Make sure no high/low priority latencies appear. + Make sure no per priority latencies appear. job JSON object to check """ for ddir in ['read', 'write', 'trim']: if ddir in job: - if 'lat_high_prio' in job[ddir] or 'lat_low_prio' in job[ddir] or \ - 'clat_high_prio' in job[ddir] or 'clat_low_prio' in job[ddir]: - print("Unexpected high/low priority latencies found in %s output" % ddir) + if 'prios' in job[ddir]: + print("Unexpected per priority latencies found in %s output" % ddir) return False if self.debug: - print("No high/low priority latencies found") + print("No per priority latencies found") return True @@ -497,7 +497,7 @@ def check_terse(self, terse, jsondata): return retval def check_prio_latencies(self, jsondata, clat=True, plus=False): - """Check consistency of high/low priority latencies. + """Check consistency of per priority latencies. clat True if we should check clat data; other check lat data plus True if we have json+ format data where additional checks can @@ -506,78 +506,78 @@ def check_prio_latencies(self, jsondata, clat=True, plus=False): """ if clat: - high = 'clat_high_prio' - low = 'clat_low_prio' - combined = 'clat_ns' + obj = combined = 'clat_ns' else: - high = 'lat_high_prio' - low = 'lat_low_prio' - combined = 'lat_ns' + obj = combined = 'lat_ns' - if not high in jsondata or not low in jsondata or not combined in jsondata: - print("Error identifying high/low priority latencies") + if not 'prios' in jsondata or not combined in jsondata: + print("Error identifying per priority latencies") return False - if jsondata[high]['N'] + jsondata[low]['N'] != jsondata[combined]['N']: - print("High %d + low %d != combined sample size %d" % \ - (jsondata[high]['N'], jsondata[low]['N'], jsondata[combined]['N'])) + sum_sample_size = sum([x[obj]['N'] for x in jsondata['prios']]) + if sum_sample_size != jsondata[combined]['N']: + print("Per prio sample size sum %d != combined sample size %d" % + (sum_sample_size, jsondata[combined]['N'])) return False elif self.debug: - print("High %d + low %d == combined sample size %d" % \ - (jsondata[high]['N'], jsondata[low]['N'], jsondata[combined]['N'])) + print("Per prio sample size sum %d == combined sample size %d" % + (sum_sample_size, jsondata[combined]['N'])) - if min(jsondata[high]['min'], jsondata[low]['min']) != jsondata[combined]['min']: - print("Min of high %d, low %d min latencies does not match min %d from combined data" % \ - (jsondata[high]['min'], jsondata[low]['min'], jsondata[combined]['min'])) + min_val = min([x[obj]['min'] for x in jsondata['prios']]) + if min_val != jsondata[combined]['min']: + print("Min per prio min latency %d does not match min %d from combined data" % + (min_val, jsondata[combined]['min'])) return False elif self.debug: - print("Min of high %d, low %d min latencies matches min %d from combined data" % \ - (jsondata[high]['min'], jsondata[low]['min'], jsondata[combined]['min'])) + print("Min per prio min latency %d matches min %d from combined data" % + (min_val, jsondata[combined]['min'])) - if max(jsondata[high]['max'], jsondata[low]['max']) != jsondata[combined]['max']: - print("Max of high %d, low %d max latencies does not match max %d from combined data" % \ - (jsondata[high]['max'], jsondata[low]['max'], jsondata[combined]['max'])) + max_val = max([x[obj]['max'] for x in jsondata['prios']]) + if max_val != jsondata[combined]['max']: + print("Max per prio max latency %d does not match max %d from combined data" % + (max_val, jsondata[combined]['max'])) return False elif self.debug: - print("Max of high %d, low %d max latencies matches max %d from combined data" % \ - (jsondata[high]['max'], jsondata[low]['max'], jsondata[combined]['max'])) + print("Max per prio max latency %d matches max %d from combined data" % + (max_val, jsondata[combined]['max'])) - weighted_avg = (jsondata[high]['mean'] * jsondata[high]['N'] + \ - jsondata[low]['mean'] * jsondata[low]['N']) / jsondata[combined]['N'] + weighted_vals = [x[obj]['mean'] * x[obj]['N'] for x in jsondata['prios']] + weighted_avg = sum(weighted_vals) / jsondata[combined]['N'] delta = abs(weighted_avg - jsondata[combined]['mean']) if (delta / jsondata[combined]['mean']) > 0.0001: - print("Difference between weighted average %f of high, low means " + print("Difference between merged per prio weighted average %f mean " "and actual mean %f exceeds 0.01%%" % (weighted_avg, jsondata[combined]['mean'])) return False elif self.debug: - print("Weighted average %f of high, low means matches actual mean %f" % \ - (weighted_avg, jsondata[combined]['mean'])) + print("Merged per prio weighted average %f mean matches actual mean %f" % + (weighted_avg, jsondata[combined]['mean'])) if plus: - if not self.check_jsonplus(jsondata[high]): - return False - if not self.check_jsonplus(jsondata[low]): - return False + for prio in jsondata['prios']: + if not self.check_jsonplus(prio[obj]): + return False - bins = {**jsondata[high]['bins'], **jsondata[low]['bins']} - for duration in bins.keys(): - if duration in jsondata[high]['bins'] and duration in jsondata[low]['bins']: - bins[duration] = jsondata[high]['bins'][duration] + \ - jsondata[low]['bins'][duration] + counter = Counter() + for prio in jsondata['prios']: + counter.update(prio[obj]['bins']) + + bins = dict(counter) if len(bins) != len(jsondata[combined]['bins']): - print("Number of combined high/low bins does not match number of overall bins") + print("Number of merged bins %d does not match number of overall bins %d" % + (len(bins), len(jsondata[combined]['bins']))) return False elif self.debug: - print("Number of bins from merged high/low data matches number of overall bins") + print("Number of merged bins %d matches number of overall bins %d" % + (len(bins), len(jsondata[combined]['bins']))) for duration in bins.keys(): if bins[duration] != jsondata[combined]['bins'][duration]: - print("Merged high/low count does not match overall count for duration %d" \ - % duration) + print("Merged per prio count does not match overall count for duration %d" % + duration) return False - print("Merged high/low priority latency data match combined latency data") + print("Merged per priority latency data match combined latency data") return True def check(self): @@ -602,7 +602,7 @@ def check(self): print("Unexpected trim data found in output") retval = False if not self.check_nocmdprio_lat(job): - print("Unexpected high/low priority latencies found") + print("Unexpected per priority latencies found") retval = False retval &= self.check_latencies(job['read'], 0, slat=False) @@ -626,7 +626,7 @@ def check(self): print("Unexpected trim data found in output") retval = False if not self.check_nocmdprio_lat(job): - print("Unexpected high/low priority latencies found") + print("Unexpected per priority latencies found") retval = False retval &= self.check_latencies(job['write'], 1, slat=False, clat=False) @@ -650,7 +650,7 @@ def check(self): print("Unexpected write data found in output") retval = False if not self.check_nocmdprio_lat(job): - print("Unexpected high/low priority latencies found") + print("Unexpected per priority latencies found") retval = False retval &= self.check_latencies(job['trim'], 2, slat=False, tlat=False) @@ -674,7 +674,7 @@ def check(self): print("Unexpected trim data found in output") retval = False if not self.check_nocmdprio_lat(job): - print("Unexpected high/low priority latencies found") + print("Unexpected per priority latencies found") retval = False retval &= self.check_latencies(job['read'], 0, plus=True) @@ -698,7 +698,7 @@ def check(self): print("Unexpected trim data found in output") retval = False if not self.check_nocmdprio_lat(job): - print("Unexpected high/low priority latencies found") + print("Unexpected per priority latencies found") retval = False retval &= self.check_latencies(job['write'], 1, slat=False, plus=True) @@ -722,7 +722,7 @@ def check(self): print("Unexpected trim data found in output") retval = False if not self.check_nocmdprio_lat(job): - print("Unexpected high/low priority latencies found") + print("Unexpected per priority latencies found") retval = False retval &= self.check_latencies(job['read'], 0, slat=False, tlat=False, plus=True) @@ -743,7 +743,7 @@ def check(self): print("Unexpected trim data found in output") retval = False if not self.check_nocmdprio_lat(job): - print("Unexpected high/low priority latencies found") + print("Unexpected per priority latencies found") retval = False retval &= self.check_latencies(job['read'], 0, clat=False, tlat=False, plus=True) @@ -765,7 +765,7 @@ def check(self): print("Unexpected data direction found in fio output") retval = False if not self.check_nocmdprio_lat(job): - print("Unexpected high/low priority latencies found") + print("Unexpected per priority latencies found") retval = False retval &= self.check_latencies(job['mixed'], 0, plus=True, unified=True) @@ -792,7 +792,7 @@ def check(self): print("Error checking fsync latency data") retval = False if not self.check_nocmdprio_lat(job): - print("Unexpected high/low priority latencies found") + print("Unexpected per priority latencies found") retval = False retval &= self.check_latencies(job['write'], 1, slat=False, plus=True) @@ -813,7 +813,7 @@ def check(self): print("Unexpected trim data found in output") retval = False if not self.check_nocmdprio_lat(job): - print("Unexpected high/low priority latencies found") + print("Unexpected per priority latencies found") retval = False retval &= self.check_latencies(job['read'], 0, plus=True) @@ -839,7 +839,7 @@ def check(self): print("Unexpected trim data found in output") retval = False if not self.check_nocmdprio_lat(job): - print("Unexpected high/low priority latencies found") + print("Unexpected per priority latencies found") retval = False retval &= self.check_latencies(job['read'], 0, slat=False, clat=False, plus=True) From c53048b3a68bd0fc6a1ee219141b547363ac74ca Mon Sep 17 00:00:00 2001 From: Niklas Cassel Date: Thu, 3 Feb 2022 19:28:31 +0000 Subject: [PATCH 0075/1097] gfio: drop support for high/low priority latency results High/low priority latencies have been replaced by a per prio array. This allows us to have latency results for more than just two priorities. Unfortunately this currently means that we have to drop the support for visualizing the high/low priority latencies. If someone wants to know the per prio latency results, both the regular output and the json output contain this information. The GUI could be extended to support the new per priority format at a later time, if anyone has a huge need for this feature. Signed-off-by: Niklas Cassel Reviewed-by: Damien Le Moal Link: https://lore.kernel.org/r/20220203192814.18552-17-Niklas.Cassel@wdc.com Signed-off-by: Jens Axboe --- gclient.c | 55 ++++--------------------------------------------------- 1 file changed, 4 insertions(+), 51 deletions(-) diff --git a/gclient.c b/gclient.c index ac0635361c..c59bcfe2f6 100644 --- a/gclient.c +++ b/gclient.c @@ -1155,21 +1155,18 @@ static void gfio_show_clat_percentiles(struct gfio_client *gc, #define GFIO_CLAT 1 #define GFIO_SLAT 2 #define GFIO_LAT 4 -#define GFIO_HILAT 8 -#define GFIO_LOLAT 16 static void gfio_show_ddir_status(struct gfio_client *gc, GtkWidget *mbox, struct group_run_stats *rs, struct thread_stat *ts, int ddir) { const char *ddir_label[3] = { "Read", "Write", "Trim" }; - const char *hilat, *lolat; GtkWidget *frame, *label, *box, *vbox, *main_vbox; - unsigned long long min[5], max[5]; + unsigned long long min[3], max[3]; unsigned long runt; unsigned long long bw, iops; unsigned int flags = 0; - double mean[5], dev[5]; + double mean[3], dev[3]; char *io_p, *io_palt, *bw_p, *bw_palt, *iops_p; char tmp[128]; int i2p; @@ -1268,14 +1265,6 @@ static void gfio_show_ddir_status(struct gfio_client *gc, GtkWidget *mbox, flags |= GFIO_CLAT; if (calc_lat(&ts->lat_stat[ddir], &min[2], &max[2], &mean[2], &dev[2])) flags |= GFIO_LAT; - if (calc_lat(&ts->clat_high_prio_stat[ddir], &min[3], &max[3], &mean[3], &dev[3])) { - flags |= GFIO_HILAT; - if (calc_lat(&ts->clat_low_prio_stat[ddir], &min[4], &max[4], &mean[4], &dev[4])) - flags |= GFIO_LOLAT; - /* we only want to print low priority statistics if other IOs were - * submitted with the priority bit set - */ - } if (flags) { frame = gtk_frame_new("Latency"); @@ -1284,24 +1273,12 @@ static void gfio_show_ddir_status(struct gfio_client *gc, GtkWidget *mbox, vbox = gtk_vbox_new(FALSE, 3); gtk_container_add(GTK_CONTAINER(frame), vbox); - if (ts->lat_percentiles) { - hilat = "High priority total latency"; - lolat = "Low priority total latency"; - } else { - hilat = "High priority completion latency"; - lolat = "Low priority completion latency"; - } - if (flags & GFIO_SLAT) gfio_show_lat(vbox, "Submission latency", min[0], max[0], mean[0], dev[0]); if (flags & GFIO_CLAT) gfio_show_lat(vbox, "Completion latency", min[1], max[1], mean[1], dev[1]); if (flags & GFIO_LAT) gfio_show_lat(vbox, "Total latency", min[2], max[2], mean[2], dev[2]); - if (flags & GFIO_HILAT) - gfio_show_lat(vbox, hilat, min[3], max[3], mean[3], dev[3]); - if (flags & GFIO_LOLAT) - gfio_show_lat(vbox, lolat, min[4], max[4], mean[4], dev[4]); } if (ts->slat_percentiles && flags & GFIO_SLAT) @@ -1309,40 +1286,16 @@ static void gfio_show_ddir_status(struct gfio_client *gc, GtkWidget *mbox, ts->io_u_plat[FIO_SLAT][ddir], ts->slat_stat[ddir].samples, "Submission"); - if (ts->clat_percentiles && flags & GFIO_CLAT) { + if (ts->clat_percentiles && flags & GFIO_CLAT) gfio_show_clat_percentiles(gc, main_vbox, ts, ddir, ts->io_u_plat[FIO_CLAT][ddir], ts->clat_stat[ddir].samples, "Completion"); - if (!ts->lat_percentiles) { - if (flags & GFIO_HILAT) - gfio_show_clat_percentiles(gc, main_vbox, ts, ddir, - ts->io_u_plat_high_prio[ddir], - ts->clat_high_prio_stat[ddir].samples, - "High priority completion"); - if (flags & GFIO_LOLAT) - gfio_show_clat_percentiles(gc, main_vbox, ts, ddir, - ts->io_u_plat_low_prio[ddir], - ts->clat_low_prio_stat[ddir].samples, - "Low priority completion"); - } - } - if (ts->lat_percentiles && flags & GFIO_LAT) { + if (ts->lat_percentiles && flags & GFIO_LAT) gfio_show_clat_percentiles(gc, main_vbox, ts, ddir, ts->io_u_plat[FIO_LAT][ddir], ts->lat_stat[ddir].samples, "Total"); - if (flags & GFIO_HILAT) - gfio_show_clat_percentiles(gc, main_vbox, ts, ddir, - ts->io_u_plat_high_prio[ddir], - ts->clat_high_prio_stat[ddir].samples, - "High priority total"); - if (flags & GFIO_LOLAT) - gfio_show_clat_percentiles(gc, main_vbox, ts, ddir, - ts->io_u_plat_low_prio[ddir], - ts->clat_low_prio_stat[ddir].samples, - "Low priority total"); - } free(io_p); free(bw_p); From 5f6ecbcd49bef3b1be4ccbb648af6b80e13a3feb Mon Sep 17 00:00:00 2001 From: Niklas Cassel Date: Thu, 3 Feb 2022 19:28:32 +0000 Subject: [PATCH 0076/1097] stat: remove unused high/low prio struct members Now when all users have moved to the new clat_prio_stat arrays, remove io_u_plat_high_prio, io_u_plat_low_prio, clat_high_prio_stat, and clat_low_prio_stat, as they are no longer used. Signed-off-by: Niklas Cassel Reviewed-by: Damien Le Moal Link: https://lore.kernel.org/r/20220203192814.18552-18-Niklas.Cassel@wdc.com Signed-off-by: Jens Axboe --- stat.h | 5 ----- 1 file changed, 5 deletions(-) diff --git a/stat.h b/stat.h index 05228fd2c1..dce0bb0dc9 100644 --- a/stat.h +++ b/stat.h @@ -262,11 +262,6 @@ struct thread_stat { /* A mirror of td->ioprio. */ uint32_t ioprio; - uint64_t io_u_plat_high_prio[DDIR_RWDIR_CNT][FIO_IO_U_PLAT_NR] __attribute__((aligned(8)));; - uint64_t io_u_plat_low_prio[DDIR_RWDIR_CNT][FIO_IO_U_PLAT_NR]; - struct io_stat clat_high_prio_stat[DDIR_RWDIR_CNT] __attribute__((aligned(8))); - struct io_stat clat_low_prio_stat[DDIR_RWDIR_CNT]; - union { uint64_t *ss_iops_data; /* From f79e4dea85ad1ea20e7e512a9c4a6d1baadd9f49 Mon Sep 17 00:00:00 2001 From: Niklas Cassel Date: Thu, 3 Feb 2022 19:28:32 +0000 Subject: [PATCH 0077/1097] t/latency_percentiles.py: add tests for the new cmdprio_bssplit format Add two new test cases for the new cmdprio_bssplit format. While at it, fixup some small typos in the existing code. Signed-off-by: Niklas Cassel Reviewed-by: Damien Le Moal Link: https://lore.kernel.org/r/20220203192814.18552-19-Niklas.Cassel@wdc.com Signed-off-by: Jens Axboe --- t/latency_percentiles.py | 93 ++++++++++++++++++++++++++++++++-------- 1 file changed, 75 insertions(+), 18 deletions(-) diff --git a/t/latency_percentiles.py b/t/latency_percentiles.py index 62c4cc911d..9e37d9fee5 100755 --- a/t/latency_percentiles.py +++ b/t/latency_percentiles.py @@ -126,7 +126,8 @@ def run_fio(self, fio_path): "--output-format={output-format}".format(**self.test_options), ] for opt in ['slat_percentiles', 'clat_percentiles', 'lat_percentiles', - 'unified_rw_reporting', 'fsync', 'fdatasync', 'numjobs', 'cmdprio_percentage']: + 'unified_rw_reporting', 'fsync', 'fdatasync', 'numjobs', + 'cmdprio_percentage', 'bssplit', 'cmdprio_bssplit']: if opt in self.test_options: option = '--{0}={{{0}}}'.format(opt) fio_args.append(option.format(**self.test_options)) @@ -761,7 +762,7 @@ def check(self): job = self.json_data['jobs'][0] retval = True - if 'read' in job or 'write'in job or 'trim' in job: + if 'read' in job or 'write' in job or 'trim' in job: print("Unexpected data direction found in fio output") retval = False if not self.check_nocmdprio_lat(job): @@ -953,7 +954,7 @@ def check(self): job = self.json_data['jobs'][0] retval = True - if 'read' in job or 'write'in job or 'trim' in job: + if 'read' in job or 'write' in job or 'trim' in job: print("Unexpected data direction found in fio output") retval = False @@ -963,6 +964,27 @@ def check(self): return retval +class Test021(FioLatTest): + """Test object for Test 21.""" + + def check(self): + """Check Test 21 output.""" + + job = self.json_data['jobs'][0] + + retval = True + if not self.check_empty(job['trim']): + print("Unexpected trim data found in output") + retval = False + + retval &= self.check_latencies(job['read'], 0, slat=False, tlat=False, plus=True) + retval &= self.check_latencies(job['write'], 1, slat=False, tlat=False, plus=True) + retval &= self.check_prio_latencies(job['read'], clat=True, plus=True) + retval &= self.check_prio_latencies(job['write'], clat=True, plus=True) + + return retval + + def parse_args(): """Parse command-line arguments.""" @@ -1007,7 +1029,7 @@ def main(): # randread, null # enable slat, clat, lat # only clat and lat will appear because - # because the null ioengine is syncrhonous + # because the null ioengine is synchronous "test_id": 1, "runtime": 2, "output-format": "json", @@ -1047,7 +1069,7 @@ def main(): { # randread, aio # enable slat, clat, lat - # all will appear because liaio is asynchronous + # all will appear because libaio is asynchronous "test_id": 4, "runtime": 5, "output-format": "json+", @@ -1153,9 +1175,9 @@ def main(): # randread, null # enable slat, clat, lat # only clat and lat will appear because - # because the null ioengine is syncrhonous - # same as Test 1 except - # numjobs = 4 to test sum_thread_stats() changes + # because the null ioengine is synchronous + # same as Test 1 except add numjobs = 4 to test + # sum_thread_stats() changes "test_id": 12, "runtime": 2, "output-format": "json", @@ -1170,9 +1192,9 @@ def main(): { # randread, aio # enable slat, clat, lat - # all will appear because liaio is asynchronous - # same as Test 4 except - # numjobs = 4 to test sum_thread_stats() changes + # all will appear because libaio is asynchronous + # same as Test 4 except add numjobs = 4 to test + # sum_thread_stats() changes "test_id": 13, "runtime": 5, "output-format": "json+", @@ -1187,8 +1209,8 @@ def main(): { # 50/50 r/w, aio, unified_rw_reporting # enable slat, clat, lata - # same as Test 8 except - # numjobs = 4 to test sum_thread_stats() changes + # same as Test 8 except add numjobs = 4 to test + # sum_thread_stats() changes "test_id": 14, "runtime": 5, "output-format": "json+", @@ -1204,7 +1226,7 @@ def main(): { # randread, aio # enable slat, clat, lat - # all will appear because liaio is asynchronous + # all will appear because libaio is asynchronous # same as Test 4 except add cmdprio_percentage "test_id": 15, "runtime": 5, @@ -1278,8 +1300,8 @@ def main(): { # 50/50 r/w, aio, unified_rw_reporting # enable slat, clat, lat - # same as Test 19 except - # add numjobs = 4 to test sum_thread_stats() changes + # same as Test 19 except add numjobs = 4 to test + # sum_thread_stats() changes "test_id": 20, "runtime": 5, "output-format": "json+", @@ -1293,6 +1315,40 @@ def main(): 'numjobs': 4, "test_obj": Test019, }, + { + # r/w, aio + # enable only clat + # test bssplit and cmdprio_bssplit + "test_id": 21, + "runtime": 5, + "output-format": "json+", + "slat_percentiles": 0, + "clat_percentiles": 1, + "lat_percentiles": 0, + "ioengine": aio, + 'rw': 'randrw', + 'bssplit': '64k/40:1024k/60', + 'cmdprio_bssplit': '64k/25/1/1:64k/75/3/2:1024k/0', + "test_obj": Test021, + }, + { + # r/w, aio + # enable only clat + # same as Test 21 except add numjobs = 4 to test + # sum_thread_stats() changes + "test_id": 22, + "runtime": 5, + "output-format": "json+", + "slat_percentiles": 0, + "clat_percentiles": 1, + "lat_percentiles": 0, + "ioengine": aio, + 'rw': 'randrw', + 'bssplit': '64k/40:1024k/60', + 'cmdprio_bssplit': '64k/25/1/1:64k/75/3/2:1024k/0', + 'numjobs': 4, + "test_obj": Test021, + }, ] passed = 0 @@ -1304,9 +1360,10 @@ def main(): (args.run_only and test['test_id'] not in args.run_only): skipped = skipped + 1 outcome = 'SKIPPED (User request)' - elif (platform.system() != 'Linux' or os.geteuid() != 0) and 'cmdprio_percentage' in test: + elif (platform.system() != 'Linux' or os.geteuid() != 0) and \ + ('cmdprio_percentage' in test or 'cmdprio_bssplit' in test): skipped = skipped + 1 - outcome = 'SKIPPED (Linux root required for cmdprio_percentage tests)' + outcome = 'SKIPPED (Linux root required for cmdprio tests)' else: test_obj = test['test_obj'](artifact_root, test, args.debug) status = test_obj.run_fio(fio) From 62e9ece4d540ff2af865e4b43811f3150b8b846b Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Thu, 3 Feb 2022 16:05:02 -0700 Subject: [PATCH 0078/1097] fio: use correct function declaration for set_epoch_time() Fixes: d5b3cfd4064d ("Support for alternate epochs in fio log files") Signed-off-by: Jens Axboe --- fio_time.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fio_time.h b/fio_time.h index 59f25d82a5..62d92120a5 100644 --- a/fio_time.h +++ b/fio_time.h @@ -30,6 +30,6 @@ extern bool ramp_time_over(struct thread_data *); extern bool in_ramp_time(struct thread_data *); extern void fio_time_init(void); extern void timespec_add_msec(struct timespec *, unsigned int); -extern void set_epoch_time(struct thread_data *, int, int); +extern void set_epoch_time(struct thread_data *, int, clockid_t); #endif From e23aa8174d6a1242d81491ce30f10a8d5f9acb10 Mon Sep 17 00:00:00 2001 From: Niklas Cassel Date: Fri, 4 Feb 2022 00:17:49 +0000 Subject: [PATCH 0079/1097] stat: make free_clat_prio_stats() safe against NULL The sfree() in free_clat_prio_stats() itself handles NULL, so the function already handles a struct thread_stat without any per priority stats. (Per priority stats are disabled on threads/thread_stats that we know will never be able to contain more than a single priority.) However, if malloc() in e.g. gen_mixed_ddir_stats_from_ts() or __show_run_stats() failed to allocate memory, free_clat_prio_stats() will be supplied a NULL pointer. Fix free_clat_prio_stats() to handle a NULL pointer gracefully. Fixes: 4ad856497c0b ("stat: add a new function to allocate a clat_prio_stat array") Signed-off-by: Niklas Cassel Link: https://lore.kernel.org/r/20220204001741.34419-1-Niklas.Cassel@wdc.com Signed-off-by: Jens Axboe --- stat.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/stat.c b/stat.c index 0876222a1b..1764eebc69 100644 --- a/stat.c +++ b/stat.c @@ -2041,6 +2041,9 @@ void free_clat_prio_stats(struct thread_stat *ts) { enum fio_ddir ddir; + if (!ts) + return; + for (ddir = 0; ddir < DDIR_RWDIR_CNT; ddir++) { sfree(ts->clat_prio[ddir]); ts->clat_prio[ddir] = NULL; From b65c1fc07d4794920224312c56c785de2f3f1692 Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Fri, 4 Feb 2022 09:02:49 -0700 Subject: [PATCH 0080/1097] t/io_uring: fix warnings for !ARCH_HAVE_CPU_CLOCK Signed-off-by: Jens Axboe --- t/io_uring.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/t/io_uring.c b/t/io_uring.c index e8365a79d2..faf5978c4a 100644 --- a/t/io_uring.c +++ b/t/io_uring.c @@ -287,6 +287,7 @@ static void show_clat_percentiles(unsigned long *io_u_plat, unsigned long nr, free(ovals); } +#ifdef ARCH_HAVE_CPU_CLOCK static unsigned int plat_val_to_idx(unsigned long val) { unsigned int msb, error_bits, base, offset, idx; @@ -322,6 +323,7 @@ static unsigned int plat_val_to_idx(unsigned long val) return idx; } +#endif static void add_stat(struct submitter *s, int clock_index, int nr) { @@ -789,9 +791,12 @@ static void *submitter_uring_fn(void *data) { struct submitter *s = data; struct io_sq_ring *ring = &s->sq_ring; - int ret, prepped, nr_batch; - - nr_batch = submitter_init(s); + int ret, prepped; +#ifdef ARCH_HAVE_CPU_CLOCK + int nr_batch = submitter_init(s); +#else + submitter_init(s); +#endif prepped = 0; do { From 8a7bf04c288ac3a46629b22871d06f5c112e85c9 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Fri, 28 Jan 2022 18:45:29 +0000 Subject: [PATCH 0081/1097] docs: document cpumode option for the cpuio ioengine The cpumode option for the cpuio ioengine never had its own entry in the documentation. Add an entry so that the documentation builds cleanly. Signed-off-by: Vincent Fu --- HOWTO | 10 ++++++++++ fio.1 | 13 +++++++++++++ 2 files changed, 23 insertions(+) diff --git a/HOWTO b/HOWTO index 74ba7216e1..4d8c799ef5 100644 --- a/HOWTO +++ b/HOWTO @@ -2318,6 +2318,16 @@ with the caveat that when used on the command line, they must come after the Split the load into cycles of the given time. In microseconds. +.. option:: cpumode=str : [cpuio] + + Specify how to stress the CPU. It can take these two values: + + **noop** + This is the default where the CPU executes noop instructions. + **qsort** + Replace the default noop instructions loop with a qsort algorithm to + consume more energy. + .. option:: exit_on_io_done=bool : [cpuio] Detect when I/O threads are done, then exit. diff --git a/fio.1 b/fio.1 index f32d791594..e23d4092cc 100644 --- a/fio.1 +++ b/fio.1 @@ -2091,6 +2091,19 @@ option when using cpuio I/O engine. .BI (cpuio)cpuchunks \fR=\fPint Split the load into cycles of the given time. In microseconds. .TP +.BI (cpuio)cpumode \fR=\fPstr +Specify how to stress the CPU. It can take these two values: +.RS +.RS +.TP +.B noop +This is the default and directs the CPU to execute noop instructions. +.TP +.B qsort +Replace the default noop instructions with a qsort algorithm to consume more energy. +.RE +.RE +.TP .BI (cpuio)exit_on_io_done \fR=\fPbool Detect when I/O threads are done, then exit. .TP From a18b0121aae1e46736733c931a45ec402e3a661f Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Fri, 28 Jan 2022 18:50:11 +0000 Subject: [PATCH 0082/1097] docs: update Makefile in order to detect build failures With the -W option sphinx-docs will yield a non-zero return code when it encounters warnings. With the --keep-going option sphinx-docs will continue running to the end of the build even if it encounters errors. Signed-off-by: Vincent Fu --- doc/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/Makefile b/doc/Makefile index 3b979f9acb..a444d83a50 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -2,7 +2,7 @@ # # You can set these variables from the command line. -SPHINXOPTS = +SPHINXOPTS = -W --keep-going SPHINXBUILD = sphinx-build PAPER = BUILDDIR = output From 7c1f6a2f8837dd129923f54fd6bcedb7cf3abe7c Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Fri, 4 Feb 2022 15:08:24 -0500 Subject: [PATCH 0083/1097] docs: rename HOWTO to HOWTO.rst Since the HOWTO uses the rst format, we should identify it that way. It will display nicely on github.com. Also update the documentation to refer to the new HOWTO.rst file. Signed-off-by: Vincent Fu --- HOWTO => HOWTO.rst | 0 doc/fio_doc.rst | 2 +- doc/fio_man.rst | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename HOWTO => HOWTO.rst (100%) diff --git a/HOWTO b/HOWTO.rst similarity index 100% rename from HOWTO rename to HOWTO.rst diff --git a/doc/fio_doc.rst b/doc/fio_doc.rst index 8e1216f02c..34e7fde988 100644 --- a/doc/fio_doc.rst +++ b/doc/fio_doc.rst @@ -5,7 +5,7 @@ fio - Flexible I/O tester rev. |version| .. include:: ../README.rst -.. include:: ../HOWTO +.. include:: ../HOWTO.rst diff --git a/doc/fio_man.rst b/doc/fio_man.rst index 44312f16ac..dc1d1c0ddc 100644 --- a/doc/fio_man.rst +++ b/doc/fio_man.rst @@ -9,4 +9,4 @@ Fio Manpage .. include:: ../README.rst -.. include:: ../HOWTO +.. include:: ../HOWTO.rst From 68522f38ad38d8a326ab9da3d5f2a17ba39823b2 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Fri, 4 Feb 2022 15:34:10 -0500 Subject: [PATCH 0084/1097] HOWTO: combine multiple pool option listings Listing the pool option in multiple places makes it impossible to link to it in the documentation. Combine the two pool option listings into one to resolve the doc build warning. Also clean up a few small formatting issues. Signed-off-by: Vincent Fu --- HOWTO.rst | 54 ++++++++++++++++++++++++++++++------------------------ 1 file changed, 30 insertions(+), 24 deletions(-) diff --git a/HOWTO.rst b/HOWTO.rst index 4d8c799ef5..db30ed1f88 100644 --- a/HOWTO.rst +++ b/HOWTO.rst @@ -2137,8 +2137,10 @@ I/O engine Asynchronous read and write using DDN's Infinite Memory Engine (IME). This engine will try to stack as much IOs as possible by creating requests for IME. FIO will then decide when to commit these requests. + **libiscsi** Read and write iscsi lun with libiscsi. + **nbd** Read and write a Network Block Device (NBD). @@ -2149,6 +2151,7 @@ I/O engine unless :option:`verify` is set or :option:`cuda_io` is `posix`. :option:`iomem` must not be `cudamalloc`. This ioengine defines engine specific options. + **dfs** I/O engine supporting asynchronous read and write operations to the DAOS File System (DFS) via libdfs. @@ -2175,8 +2178,8 @@ with the caveat that when used on the command line, they must come after the Set the percentage of I/O that will be issued with the highest priority. Default: 0. A single value applies to reads and writes. Comma-separated values may be specified for reads and writes. For this option to be - effective, NCQ priority must be supported and enabled, and `direct=1' - option must be used. fio must also be run as the root user. Unlike + effective, NCQ priority must be supported and enabled, and the :option:`direct` + option must be set. fio must also be run as the root user. Unlike slat/clat/lat stats, which can be tracked and reported independently, per priority stats only track and report a single type of latency. By default, completion latency (clat) will be reported, if :option:`lat_percentiles` is @@ -2207,6 +2210,7 @@ with the caveat that when used on the command line, they must come after the meaning of priority may differ. See also the :option:`prio` option. .. option:: cmdprio_bssplit=str[,str] : [io_uring] [libaio] + To get a finer control over I/O priority, this option allows specifying the percentage of IOs that must have a priority set depending on the block size of the IO. This option is useful only @@ -2454,10 +2458,6 @@ with the caveat that when used on the command line, they must come after the Specifies the name of the RBD. -.. option:: pool=str : [rbd,rados] - - Specifies the name of the Ceph pool containing RBD or RADOS data. - .. option:: clientname=str : [rbd,rados] Specifies the username (without the 'client.' prefix) used to access the @@ -2476,6 +2476,30 @@ with the caveat that when used on the command line, they must come after the Touching all objects affects ceph caches and likely impacts test results. Enabled by default. +.. option:: pool=str : + + [rbd,rados] + + Specifies the name of the Ceph pool containing RBD or RADOS data. + + [dfs] + + Specify the label or UUID of the DAOS pool to connect to. + +.. option:: cont=str : [dfs] + + Specify the label or UUID of the DAOS container to open. + +.. option:: chunk_size=int : [dfs] + + Specificy a different chunk size (in bytes) for the dfs file. + Use DAOS container's chunk size by default. + +.. option:: object_class=str : [dfs] + + Specificy a different object class for the dfs file. + Use DAOS container's object class by default. + .. option:: skip_bad=bool : [mtd] Skip operations against known bad blocks. @@ -2664,24 +2688,6 @@ with the caveat that when used on the command line, they must come after the GPU to RAM before a write and copied from RAM to GPU after a read. :option:`verify` does not affect use of cudaMemcpy. -.. option:: pool=str : [dfs] - - Specify the label or UUID of the DAOS pool to connect to. - -.. option:: cont=str : [dfs] - - Specify the label or UUID of the DAOS container to open. - -.. option:: chunk_size=int : [dfs] - - Specificy a different chunk size (in bytes) for the dfs file. - Use DAOS container's chunk size by default. - -.. option:: object_class=str : [dfs] - - Specificy a different object class for the dfs file. - Use DAOS container's object class by default. - .. option:: nfs_url=str : [nfs] URL in libnfs format, eg nfs:///path[?arg=val[&arg=val]*] From 8253a66bea54ce36e4cc34378c4eed06a07de65b Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Fri, 4 Feb 2022 15:59:37 -0500 Subject: [PATCH 0085/1097] HOWTO: combine separate hipri listings into a single one Resolve doc build warnings about multiple appearances of the hipri option. Signed-off-by: Vincent Fu --- HOWTO.rst | 54 +++++++++++++++++++++++++++++------------------------- 1 file changed, 29 insertions(+), 25 deletions(-) diff --git a/HOWTO.rst b/HOWTO.rst index db30ed1f88..0e49d8a88e 100644 --- a/HOWTO.rst +++ b/HOWTO.rst @@ -2247,14 +2247,6 @@ with the caveat that when used on the command line, they must come after the map and release for each IO. This is more efficient, and reduces the IO latency as well. -.. option:: hipri : [io_uring] - - If this option is set, fio will attempt to use polled IO completions. - Normal IO completions generate interrupts to signal the completion of - IO, polled completions do not. Hence they are require active reaping - by the application. The benefits are more efficient IO for high IOPS - scenarios, and lower latencies for low queue depth IO. - .. option:: registerfiles : [io_uring] With this option, fio registers the set of files being used with the @@ -2275,6 +2267,35 @@ with the caveat that when used on the command line, they must come after the When :option:`sqthread_poll` is set, this option provides a way to define which CPU should be used for the polling thread. +.. option:: hipri + + [io_uring] + + If this option is set, fio will attempt to use polled IO completions. + Normal IO completions generate interrupts to signal the completion of + IO, polled completions do not. Hence they are require active reaping + by the application. The benefits are more efficient IO for high IOPS + scenarios, and lower latencies for low queue depth IO. + + + [pvsync2] + + Set RWF_HIPRI on I/O, indicating to the kernel that it's of higher priority + than normal. + + + [sg] + + If this option is set, fio will attempt to use polled IO completions. + This will have a similar effect as (io_uring)hipri. Only SCSI READ and + WRITE commands will have the SGV4_FLAG_HIPRI set (not UNMAP (trim) nor + VERIFY). Older versions of the Linux sg driver that do not support + hipri will simply ignore this flag and do normal IO. The Linux SCSI + Low Level Driver (LLD) that "owns" the device also needs to support + hipri (also known as iopoll and mq_poll). The MegaRAID driver is an + example of a SCSI LLD. Default: clear (0) which does normal + (interrupted based) IO. + .. option:: userspace_reap : [libaio] Normally, with the libaio engine in use, fio will use the @@ -2283,11 +2304,6 @@ with the caveat that when used on the command line, they must come after the reap events. The reaping mode is only enabled when polling for a minimum of 0 events (e.g. when :option:`iodepth_batch_complete` `=0`). -.. option:: hipri : [pvsync2] - - Set RWF_HIPRI on I/O, indicating to the kernel that it's of higher priority - than normal. - .. option:: hipri_percentage : [pvsync2] When hipri is set this determines the probability of a pvsync2 I/O being high @@ -2597,18 +2613,6 @@ with the caveat that when used on the command line, they must come after the a valid stream identifier) fio will open a stream and then close it when done. Default is 0. -.. option:: hipri : [sg] - - If this option is set, fio will attempt to use polled IO completions. - This will have a similar effect as (io_uring)hipri. Only SCSI READ and - WRITE commands will have the SGV4_FLAG_HIPRI set (not UNMAP (trim) nor - VERIFY). Older versions of the Linux sg driver that do not support - hipri will simply ignore this flag and do normal IO. The Linux SCSI - Low Level Driver (LLD) that "owns" the device also needs to support - hipri (also known as iopoll and mq_poll). The MegaRAID driver is an - example of a SCSI LLD. Default: clear (0) which does normal - (interrupted based) IO. - .. option:: http_host=str : [http] Hostname to connect to. For S3, this could be the bucket hostname. From 19d8e50abe9f87be6c6793379b2a83abfb8f23cb Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Fri, 4 Feb 2022 16:05:37 -0500 Subject: [PATCH 0086/1097] HOWTO: combine two chunk_size listings into a single one This resolves the documentation build warning about multiple listings for the chunk_size option. Signed-off-by: Vincent Fu --- HOWTO.rst | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/HOWTO.rst b/HOWTO.rst index 0e49d8a88e..ac1f347870 100644 --- a/HOWTO.rst +++ b/HOWTO.rst @@ -2277,13 +2277,11 @@ with the caveat that when used on the command line, they must come after the by the application. The benefits are more efficient IO for high IOPS scenarios, and lower latencies for low queue depth IO. - [pvsync2] Set RWF_HIPRI on I/O, indicating to the kernel that it's of higher priority than normal. - [sg] If this option is set, fio will attempt to use polled IO completions. @@ -2506,11 +2504,17 @@ with the caveat that when used on the command line, they must come after the Specify the label or UUID of the DAOS container to open. -.. option:: chunk_size=int : [dfs] +.. option:: chunk_size=int + + [dfs] Specificy a different chunk size (in bytes) for the dfs file. Use DAOS container's chunk size by default. + [libhdfs] + + The size of the chunk to use for each file. + .. option:: object_class=str : [dfs] Specificy a different object class for the dfs file. @@ -2524,10 +2528,6 @@ with the caveat that when used on the command line, they must come after the libhdfs will create chunk in this HDFS directory. -.. option:: chunk_size : [libhdfs] - - The size of the chunk to use for each file. - .. option:: verb=str : [rdma] The RDMA verb to use on this side of the RDMA ioengine connection. Valid From df597be63e26ef59c1538b3ce2026c83684ff7fb Mon Sep 17 00:00:00 2001 From: Eric Sandeen Date: Tue, 8 Feb 2022 10:00:39 -0600 Subject: [PATCH 0087/1097] fio: really use LDFLAGS when linking dynamic engines Fix stupid braino on my part. Fixes: 2b3d4a6a924e ("fio: use LDFLAGS when linking dynamic engines") Signed-off-by: Eric Sandeen Link: https://lore.kernel.org/r/1644336039-12774-1-git-send-email-sandeen@redhat.com Signed-off-by: Jens Axboe --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 2432f5192f..0ab4f82c32 100644 --- a/Makefile +++ b/Makefile @@ -295,7 +295,7 @@ define engine_template = $(1)_OBJS := $$($(1)_SRCS:.c=.o) $$($(1)_OBJS): CFLAGS := -fPIC $$($(1)_CFLAGS) $(CFLAGS) engines/fio-$(1).so: $$($(1)_OBJS) - $$(QUIET_LINK)$(CC) $(DYNAMIC) -shared -rdynamic -fPIC -Wl,-soname,fio-$(1).so.1 -o $$@ $$< $$($(1)_LIBS) + $$(QUIET_LINK)$(CC) $(LDFLAGS) -shared -rdynamic -fPIC -Wl,-soname,fio-$(1).so.1 -o $$@ $$< $$($(1)_LIBS) ENGS_OBJS += engines/fio-$(1).so endef else # !CONFIG_DYNAMIC_ENGINES From 71989c1b8d102f88c8a34d61c356cd6f0ba680c9 Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Fri, 11 Feb 2022 06:42:13 -0700 Subject: [PATCH 0088/1097] t/io_uring: avoid unused `nr_batch` warning If we have libaio support, but not an appropriate CPU clock, then the build throws a warning on nr_batch being assigned but never used. Mirror what was done on the io_uring init path and only defined and set `nr_batch` if we have CPU clock support. Signed-off-by: Jens Axboe --- t/io_uring.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/t/io_uring.c b/t/io_uring.c index faf5978c4a..4520de436e 100644 --- a/t/io_uring.c +++ b/t/io_uring.c @@ -714,12 +714,15 @@ static int reap_events_aio(struct submitter *s, struct io_event *events, int evs static void *submitter_aio_fn(void *data) { struct submitter *s = data; - int i, ret, prepped, nr_batch; + int i, ret, prepped; struct iocb **iocbsptr; struct iocb *iocbs; struct io_event *events; - - nr_batch = submitter_init(s); +#ifdef ARCH_HAVE_CPU_CLOCK + int nr_batch = submitter_init(s); +#else + submitter_init(s); +#endif iocbsptr = calloc(depth, sizeof(struct iocb *)); iocbs = calloc(depth, sizeof(struct iocb)); From f57a3a31c8d074b309f797a4336101e4c1e5ed39 Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Fri, 11 Feb 2022 06:58:12 -0700 Subject: [PATCH 0089/1097] Add aarch64 cpu clock support We can use cntvct_el0 to read the CPU clock. Signed-off-by: Jens Axboe --- arch/arch-aarch64.h | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/arch/arch-aarch64.h b/arch/arch-aarch64.h index 2a86cc5ab4..94571709eb 100644 --- a/arch/arch-aarch64.h +++ b/arch/arch-aarch64.h @@ -27,4 +27,21 @@ static inline int arch_ffz(unsigned long bitmask) #define ARCH_HAVE_FFZ +static inline unsigned long long get_cpu_clock(void) +{ + unsigned long val; + + asm volatile("mrs %0, cntvct_el0" : "=r" (val)); + return val; +} +#define ARCH_HAVE_CPU_CLOCK + +#define ARCH_HAVE_INIT +extern bool tsc_reliable; +static inline int arch_init(char *envp[]) +{ + tsc_reliable = true; + return 0; +} + #endif From 5b28b75b8cca843d616d2d54a77fdb5797c00f54 Mon Sep 17 00:00:00 2001 From: aggieNick02 Date: Fri, 11 Feb 2022 14:46:12 -0600 Subject: [PATCH 0090/1097] Fix issues (assert or uninit var, hang) with check_min_rate and offloading Using rate_min/rate_iops_min when io_submit_mode=offload option is set leads to intermittent asserts and doesn't work. The variable comp_time is never set in do_io in backend.c in the offload case, and comp_time is then used in the calls to check_min_rate. The time computations in check_min_rate either assert and terminate fio, or return meaningless values, so any rate checking is not correct. This first issue is fixed by adding a call to fio_gettime in the offloading case. Once that is done though, there is still another problem remaining. When the min rate is not achieved (with the offloading option), fio detects it and tries to exit but fails. It ends up in a state where ctrl-C will not cause an exit either. This happens because cleanup_pending_aio(td) in the error case hangs in its second call to io_u_queued_complete. Calling workqueue_flush in the error case (when offloading) fixes the problem by making sure nothing is left in the work queues and cleanup can proceed as it does in the non-offload case. Signed-off-by: Nick Neumann --- backend.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/backend.c b/backend.c index 061e3b329d..c035baed60 100644 --- a/backend.c +++ b/backend.c @@ -1091,8 +1091,10 @@ static void do_io(struct thread_data *td, uint64_t *bytes_done) td->rate_io_issue_bytes[__ddir] += blen; } - if (should_check_rate(td)) + if (should_check_rate(td)) { td->rate_next_io_time[__ddir] = usec_for_io(td, __ddir); + fio_gettime(&comp_time, NULL); + } } else { ret = io_u_submit(td, io_u); @@ -1172,8 +1174,11 @@ static void do_io(struct thread_data *td, uint64_t *bytes_done) f->file_name); } } - } else + } else { + if (td->o.io_submit_mode == IO_MODE_OFFLOAD) + workqueue_flush(&td->io_wq); cleanup_pending_aio(td); + } /* * stop job if we failed doing any IO From 5c997f9c230de5edbab6804bc882d3eb42b9f622 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Fri, 4 Feb 2022 16:19:04 -0500 Subject: [PATCH 0091/1097] ci: install sphinx packages and add doc building to GitHub Actions To better detect breakage in our documentation builds let's add them to our GitHub Actions CI. Signed-off-by: Vincent Fu --- ci/actions-full-test.sh | 1 + ci/actions-install.sh | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/ci/actions-full-test.sh b/ci/actions-full-test.sh index 4ae1dba10b..9179066456 100755 --- a/ci/actions-full-test.sh +++ b/ci/actions-full-test.sh @@ -10,6 +10,7 @@ main() { else sudo python3 t/run-fio-tests.py --skip 6 1007 1008 --debug fi + make -C doc html } main diff --git a/ci/actions-install.sh b/ci/actions-install.sh index b3486a475d..0e472717d2 100755 --- a/ci/actions-install.sh +++ b/ci/actions-install.sh @@ -60,6 +60,7 @@ DPKGCFG # care about the architecture. pkgs+=( python3-scipy + python3-sphinx ) echo "Updating APT..." @@ -78,7 +79,7 @@ install_macos() { #brew update >/dev/null 2>&1 echo "Installing packages..." HOMEBREW_NO_AUTO_UPDATE=1 brew install cunit - pip3 install scipy six + pip3 install scipy six sphinx } main() { From c1e190b527f9b89effb011c5890033d6bb2dc105 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Fri, 11 Feb 2022 16:55:41 -0500 Subject: [PATCH 0092/1097] windows: update the installer build for renamed files Update the MSI build instructions to point to the new README.rst and HOWTO.rst Signed-off-by: Vincent Fu --- os/windows/install.wxs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/os/windows/install.wxs b/os/windows/install.wxs index 7773bb3b86..f2753289a9 100755 --- a/os/windows/install.wxs +++ b/os/windows/install.wxs @@ -33,13 +33,13 @@ - + - + From f5204b8af34dfda32fd6fec768c97d6c17ed3da4 Mon Sep 17 00:00:00 2001 From: aggieNick02 Date: Sun, 13 Feb 2022 21:42:27 -0600 Subject: [PATCH 0093/1097] Cleanup __check_min_rate This is a cleanup of __check_min_rate. In looking at stuff for previous fixes, it seems like there are a lot of boolean checks of things that are always true or always false. I'll explain my reasoning for each change; it is possible I'm missing something somehow but I've run through it a few times. Here's my logic: 1) td->rate_bytes and td->rate_blocks are 0 on first call to __check_min_rate, and then are the previous iteration's value of td->this_io_bytes and td->this_io_blocks on subsequent calls 2) bytes and iops are the current iteration's values of td->this_io_bytes and td->this_io_blocks 3) The values of td->this_io_bytes and td->this_io_blocks are monotonic with respect to each call of __check_min_rate Therefore, bytes and iops are always greater than or equal to td->rate_bytes and td->rate_blocks. This means the "if (bytes < td->rate_bytes[ddir]) {" on line 176 can never happen. Now, I want to say the same thing about line 197, but that line is weird/wrong in another way. rate_iops is td->o.rate_iops, the specified desired iops rate from the job. So I believe that is a bug - the specified desired iops rate should not even be examined in this function, just like the same is true for the desired bytes rate. I'm pretty sure what is meant is to compare iops to td->rate_blocks just like bytes is compared to td->rate_bytes in line 176, which would similarly always be false. Now we can focus on the else caluses (lines 180-192 and lines 202-213). If spent is 0, we should just be returning false early like in 169-170, so let's move that case up with it. The "if (rate < ratemin || bytes < td->rate_bytes[ddir]) {" and "if (rate < rate_iops_min || iops < td->rate_blocks[ddir]) {" both have impossibilities as the second part of the or clause. All we really want is to compare computed bytes rate to ratemin, and computed iops rate to rate_iops_min. With all of that, this function becomes a lot simpler. The rest of the cleanup is renaming of variables to make what they are clearer, and some other simple things (like initializing the variables directly instead of initializing to zero and then doing +=). The renames are as follows: - td->lastrate to td->last_rate_check_time, the last time a min rate check was performed - bytes to current_rate_check_bytes, the number of bytes transferred so far at the time this call to __check_min_rate was made - iops to current_rate_check_blocks, the number of blocks transferred so far at the time this call to __check_min_rate was made - rate to current_rate_bytes or current_rate_iops, depending on if it is used as the current cycle's byte rate or block rate - ratemin to option_rate_bytes_min, the user supplied desired minimum bytes rate - rate_iops eliminated - should not be used in this function - rate_iops_min to option_rate_iops_min, the user supplied desired minimum block rate - td->rate_bytes to td->last_rate_check_bytes - the number of bytes transferred the *last* time a minimum rate check was called *and* passed (not shortcircuited because not enough time had elapsed for the cycle or settling) - td->rate_blocks to td->last_rate_check_blocks - the number of blocks transferred the *last* time a minimum rate check was called *and* passed (not shortcircuited because not enough time had elapsed for the cycle or settling) Signed-off-by: Nick Neumann nick@pcpartpicker.com --- backend.c | 81 +++++++++++++++++++------------------------------------ fio.h | 6 ++--- libfio.c | 4 +-- 3 files changed, 32 insertions(+), 59 deletions(-) diff --git a/backend.c b/backend.c index c035baed60..a21dfef637 100644 --- a/backend.c +++ b/backend.c @@ -136,13 +136,10 @@ static void set_sig_handlers(void) static bool __check_min_rate(struct thread_data *td, struct timespec *now, enum fio_ddir ddir) { - unsigned long long bytes = 0; - unsigned long iops = 0; - unsigned long spent; - unsigned long long rate; - unsigned long long ratemin = 0; - unsigned int rate_iops = 0; - unsigned int rate_iops_min = 0; + unsigned long long current_rate_check_bytes = td->this_io_bytes[ddir]; + unsigned long current_rate_check_blocks = td->this_io_blocks[ddir]; + unsigned long long option_rate_bytes_min = td->o.ratemin[ddir]; + unsigned int option_rate_iops_min = td->o.rate_iops_min[ddir]; assert(ddir_rw(ddir)); @@ -155,68 +152,44 @@ static bool __check_min_rate(struct thread_data *td, struct timespec *now, if (mtime_since(&td->start, now) < 2000) return false; - iops += td->this_io_blocks[ddir]; - bytes += td->this_io_bytes[ddir]; - ratemin += td->o.ratemin[ddir]; - rate_iops += td->o.rate_iops[ddir]; - rate_iops_min += td->o.rate_iops_min[ddir]; - /* - * if rate blocks is set, sample is running + * if last_rate_check_blocks or last_rate_check_bytes is set, + * we can compute a rate per ratecycle */ - if (td->rate_bytes[ddir] || td->rate_blocks[ddir]) { - spent = mtime_since(&td->lastrate[ddir], now); - if (spent < td->o.ratecycle) + if (td->last_rate_check_bytes[ddir] || td->last_rate_check_blocks[ddir]) { + unsigned long spent = mtime_since(&td->last_rate_check_time[ddir], now); + if (spent < td->o.ratecycle || spent==0) return false; - if (td->o.rate[ddir] || td->o.ratemin[ddir]) { + if (td->o.ratemin[ddir]) { /* * check bandwidth specified rate */ - if (bytes < td->rate_bytes[ddir]) { - log_err("%s: rate_min=%lluB/s not met, only transferred %lluB\n", - td->o.name, ratemin, bytes); + unsigned long long current_rate_bytes = + ((current_rate_check_bytes - td->last_rate_check_bytes[ddir]) * 1000) / spent; + if (current_rate_bytes < option_rate_bytes_min) { + log_err("%s: rate_min=%lluB/s not met, got %lluB/s\n", + td->o.name, option_rate_bytes_min, current_rate_bytes); return true; - } else { - if (spent) - rate = ((bytes - td->rate_bytes[ddir]) * 1000) / spent; - else - rate = 0; - - if (rate < ratemin || - bytes < td->rate_bytes[ddir]) { - log_err("%s: rate_min=%lluB/s not met, got %lluB/s\n", - td->o.name, ratemin, rate); - return true; - } } } else { /* * checks iops specified rate */ - if (iops < rate_iops) { - log_err("%s: rate_iops_min=%u not met, only performed %lu IOs\n", - td->o.name, rate_iops, iops); + unsigned long long current_rate_iops = + ((current_rate_check_blocks - td->last_rate_check_blocks[ddir]) * 1000) / spent; + + if (current_rate_iops < option_rate_iops_min) { + log_err("%s: rate_iops_min=%u not met, got %llu IOPS\n", + td->o.name, option_rate_iops_min, current_rate_iops); return true; - } else { - if (spent) - rate = ((iops - td->rate_blocks[ddir]) * 1000) / spent; - else - rate = 0; - - if (rate < rate_iops_min || - iops < td->rate_blocks[ddir]) { - log_err("%s: rate_iops_min=%u not met, got %llu IOPS\n", - td->o.name, rate_iops_min, rate); - return true; - } } } } - td->rate_bytes[ddir] = bytes; - td->rate_blocks[ddir] = iops; - memcpy(&td->lastrate[ddir], now, sizeof(*now)); + td->last_rate_check_bytes[ddir] = current_rate_check_bytes; + td->last_rate_check_blocks[ddir] = current_rate_check_blocks; + memcpy(&td->last_rate_check_time[ddir], now, sizeof(*now)); return false; } @@ -1845,11 +1818,11 @@ static void *thread_main(void *data) if (o->ratemin[DDIR_READ] || o->ratemin[DDIR_WRITE] || o->ratemin[DDIR_TRIM]) { - memcpy(&td->lastrate[DDIR_READ], &td->bw_sample_time, + memcpy(&td->last_rate_check_time[DDIR_READ], &td->bw_sample_time, sizeof(td->bw_sample_time)); - memcpy(&td->lastrate[DDIR_WRITE], &td->bw_sample_time, + memcpy(&td->last_rate_check_time[DDIR_WRITE], &td->bw_sample_time, sizeof(td->bw_sample_time)); - memcpy(&td->lastrate[DDIR_TRIM], &td->bw_sample_time, + memcpy(&td->last_rate_check_time[DDIR_TRIM], &td->bw_sample_time, sizeof(td->bw_sample_time)); } diff --git a/fio.h b/fio.h index 7b0ca84359..88df117de4 100644 --- a/fio.h +++ b/fio.h @@ -335,10 +335,10 @@ struct thread_data { */ uint64_t rate_bps[DDIR_RWDIR_CNT]; uint64_t rate_next_io_time[DDIR_RWDIR_CNT]; - unsigned long long rate_bytes[DDIR_RWDIR_CNT]; - unsigned long rate_blocks[DDIR_RWDIR_CNT]; + unsigned long long last_rate_check_bytes[DDIR_RWDIR_CNT]; + unsigned long last_rate_check_blocks[DDIR_RWDIR_CNT]; unsigned long long rate_io_issue_bytes[DDIR_RWDIR_CNT]; - struct timespec lastrate[DDIR_RWDIR_CNT]; + struct timespec last_rate_check_time[DDIR_RWDIR_CNT]; int64_t last_usec[DDIR_RWDIR_CNT]; struct frand_state poisson_state[DDIR_RWDIR_CNT]; diff --git a/libfio.c b/libfio.c index 01fa74529f..1a8917768b 100644 --- a/libfio.c +++ b/libfio.c @@ -87,8 +87,8 @@ static void reset_io_counters(struct thread_data *td, int all) td->this_io_bytes[ddir] = 0; td->stat_io_blocks[ddir] = 0; td->this_io_blocks[ddir] = 0; - td->rate_bytes[ddir] = 0; - td->rate_blocks[ddir] = 0; + td->last_rate_check_bytes[ddir] = 0; + td->last_rate_check_blocks[ddir] = 0; td->bytes_done[ddir] = 0; td->rate_io_issue_bytes[ddir] = 0; td->rate_next_io_time[ddir] = 0; From b832240d981b2c0addb5d90490ee493429f6bfda Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Tue, 15 Feb 2022 13:30:30 +0000 Subject: [PATCH 0094/1097] ci: detect Windows installer build failures When the Windows installer build fails, the build script actually continues running and does not detect the failure. Use ls to determine if the MSI file exists in order to detect whether or not the installer build succeeded. Signed-off-by: Vincent Fu Link: https://lore.kernel.org/r/20220215133027.931-1-vincent.fu@samsung.com Signed-off-by: Jens Axboe --- .appveyor.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.appveyor.yml b/.appveyor.yml index 42b79958d4..b94eefe318 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -44,6 +44,7 @@ after_build: - file.exe fio.exe - make.exe test - 'cd os\windows && dobuild.cmd %ARCHITECTURE% && cd ..' + - ls.exe ./os/windows/*.msi - ps: Get-ChildItem .\os\windows\*.msi | % { Push-AppveyorArtifact $_.FullName -FileName $_.Name -DeploymentName fio.msi } test_script: From 21cab03cc713bd14bcab60849b72f70a75329a2e Mon Sep 17 00:00:00 2001 From: aggieNick02 Date: Mon, 14 Feb 2022 15:13:50 -0600 Subject: [PATCH 0095/1097] Fix ETA display when rate and/or rate_min are specified The base passed to num2str in the ETA display code passed the wrong base (0 instead of 1). Additionally, je->sig_figs was never set and defaulted to 0. Both of these caused the desired range in the ETA code to always display 0-0 when rate or rate_min was specified. Signed-off-by: Nick Neumann nick@pcpartpicker.com --- eta.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/eta.c b/eta.c index ea1781f3b7..17970c78db 100644 --- a/eta.c +++ b/eta.c @@ -420,6 +420,7 @@ bool calc_thread_status(struct jobs_eta *je, int force) if (is_power_of_2(td->o.kb_base)) je->is_pow2 = 1; je->unit_base = td->o.unit_base; + je->sig_figs = td->o.sig_figs; if (td->o.bw_avg_time < bw_avg_time) bw_avg_time = td->o.bw_avg_time; if (td->runstate == TD_RUNNING || td->runstate == TD_VERIFYING @@ -600,9 +601,9 @@ void display_thread_status(struct jobs_eta *je) char *tr, *mr; mr = num2str(je->m_rate[0] + je->m_rate[1] + je->m_rate[2], - je->sig_figs, 0, je->is_pow2, N2S_BYTEPERSEC); + je->sig_figs, 1, je->is_pow2, N2S_BYTEPERSEC); tr = num2str(je->t_rate[0] + je->t_rate[1] + je->t_rate[2], - je->sig_figs, 0, je->is_pow2, N2S_BYTEPERSEC); + je->sig_figs, 1, je->is_pow2, N2S_BYTEPERSEC); p += sprintf(p, ", %s-%s", mr, tr); free(tr); From 54833d02a6ac307a894ed4445db9fe25e530b518 Mon Sep 17 00:00:00 2001 From: aggieNick02 Date: Tue, 15 Feb 2022 11:59:34 -0600 Subject: [PATCH 0096/1097] Fix : suffix with random read/write causing 0 initial offset When using the : suffix with random reads or writes, the initial offset would be set to 0 for the first nr-1 operations. This happened because td->ddir_seq_nr was initialized to the specified option value, when it needs to always be initialized to 1, so that the first call to get_next_offset leads to choosing a new random offset for the first nr operations. Signed-off-by: Nick Neumann nick@pcpartpicker.com --- init.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/init.c b/init.c index 139351527b..81c30f8c54 100644 --- a/init.c +++ b/init.c @@ -1576,7 +1576,14 @@ static int add_job(struct thread_data *td, const char *jobname, int job_add_num, td->ts.sig_figs = o->sig_figs; init_thread_stat_min_vals(&td->ts); - td->ddir_seq_nr = o->ddir_seq_nr; + + /* + * td->>ddir_seq_nr needs to be initialized to 1, NOT o->ddir_seq_nr, + * so that get_next_offset gets a new random offset the first time it + * is called, instead of keeping an initial offset of 0 for the first + * nr-1 calls + */ + td->ddir_seq_nr = 1; if ((o->stonewall || o->new_group) && prev_group_jobs) { prev_group_jobs = 0; From 6a16e9e9531a5f746c4e2fe43873de1db434b4fc Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Tue, 15 Feb 2022 17:11:06 -0700 Subject: [PATCH 0097/1097] diskutil: include limits.h for PATH_MAX On OmniOS, compilation fails because of a missing PATH_MAX definition: $ gmake CC cconv.o In file included from stat.h:6:0, from thread_options.h:7, from cconv.c:4: diskutil.h:52:12: error: 'PATH_MAX' undeclared here (not in a function); did you mean 'INT8_MAX'? char path[PATH_MAX]; ^~~~~~~~ INT8_MAX gmake: *** [Makefile:505: cconv.o] Error 1 Add limits.h to fix that. Link: https://github.com/axboe/fio/issues/1344 Signed-off-by: Jens Axboe --- diskutil.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/diskutil.h b/diskutil.h index 83bcbf895e..7d7ef802bf 100644 --- a/diskutil.h +++ b/diskutil.h @@ -2,6 +2,8 @@ #define FIO_DISKUTIL_H #define FIO_DU_NAME_SZ 64 +#include + #include "helper_thread.h" #include "fio_sem.h" From 55845033e922fed762d4725a768c0b24488deca7 Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Thu, 17 Feb 2022 10:16:19 -0700 Subject: [PATCH 0098/1097] t/io_uring: allow non-power-of-2 queue depths Signed-off-by: Jens Axboe --- t/io_uring.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/t/io_uring.c b/t/io_uring.c index 4520de436e..400216475e 100644 --- a/t/io_uring.c +++ b/t/io_uring.c @@ -364,7 +364,7 @@ static int io_uring_register_buffers(struct submitter *s) return 0; return syscall(__NR_io_uring_register, s->ring_fd, - IORING_REGISTER_BUFFERS, s->iovecs, depth); + IORING_REGISTER_BUFFERS, s->iovecs, roundup_pow2(depth)); } static int io_uring_register_files(struct submitter *s) @@ -962,7 +962,7 @@ static int setup_aio(struct submitter *s) fixedbufs = register_files = 0; } - return io_queue_init(depth, &s->aio_ctx); + return io_queue_init(roundup_pow2(depth), &s->aio_ctx); #else fprintf(stderr, "Legacy AIO not available on this system/build\n"); errno = EINVAL; @@ -1249,7 +1249,7 @@ int main(int argc, char *argv[]) dma_map = 0; submitter = calloc(nthreads, sizeof(*submitter) + - depth * sizeof(struct iovec)); + roundup_pow2(depth) * sizeof(struct iovec)); for (j = 0; j < nthreads; j++) { s = get_submitter(j); s->index = j; @@ -1321,7 +1321,7 @@ int main(int argc, char *argv[]) for (j = 0; j < nthreads; j++) { s = get_submitter(j); - for (i = 0; i < depth; i++) { + for (i = 0; i < roundup_pow2(depth); i++) { void *buf; if (posix_memalign(&buf, bs, bs)) { From a0639afe121870a2a9b69cadc07619ba82959e3e Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Thu, 17 Feb 2022 10:18:49 -0700 Subject: [PATCH 0099/1097] t/io_uring: align buffers correctly on non-4k page sizes Signed-off-by: Jens Axboe --- t/io_uring.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/t/io_uring.c b/t/io_uring.c index 400216475e..f513d7dcd8 100644 --- a/t/io_uring.c +++ b/t/io_uring.c @@ -1156,6 +1156,7 @@ int main(int argc, char *argv[]) struct submitter *s; unsigned long done, calls, reap; int err, i, j, flags, fd, opt, threads_per_f, threads_rem = 0, nfiles; + long page_size; struct file f; char *fdepths; void *ret; @@ -1319,12 +1320,16 @@ int main(int argc, char *argv[]) arm_sig_int(); + page_size = sysconf(_SC_PAGESIZE); + if (page_size < 0) + page_size = 4096; + for (j = 0; j < nthreads; j++) { s = get_submitter(j); for (i = 0; i < roundup_pow2(depth); i++) { void *buf; - if (posix_memalign(&buf, bs, bs)) { + if (posix_memalign(&buf, page_size, bs)) { printf("failed alloc\n"); return 1; } From a04e0665cb5d3a545ab1dbe2d2b7c150b404735d Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Thu, 17 Feb 2022 12:08:41 -0700 Subject: [PATCH 0100/1097] Use fcntl(..., F_FULLSYNC) if available Some operating systems don't perform a data integrity flush when fsync() is done, but provide fcntl(fd, F_FULLSYNC) to provide that kind of guarantee. To ensure that comparisons between operating systems is fair, use fcntl() to do a proper sync if available. Signed-off-by: Jens Axboe --- configure | 22 ++++++++++++++++++++++ io_u.c | 4 ++++ 2 files changed, 26 insertions(+) diff --git a/configure b/configure index 0efde7d6a8..fd1d435b4d 100755 --- a/configure +++ b/configure @@ -645,6 +645,25 @@ if compile_prog "" "-lz" "zlib" ; then fi print_config "zlib" "$zlib" +########################################## +# fcntl(F_FULLFSYNC) support +if test "$fcntl_sync" != "yes" ; then + fcntl_sync="no" +fi +cat > $TMPC << EOF +#include +#include + +int main(int argc, char **argv) +{ + return fcntl(0, F_FULLSYNC); +} +EOF +if compile_prog "" "" "fcntl(F_FULLSYNC)" ; then + fcntl_sync="yes" +fi +print_config "fcntl(F_FULLSYNC)" "$fcntl_sync" + ########################################## # linux-aio probe if test "$libaio" != "yes" ; then @@ -3174,6 +3193,9 @@ fi if test "$pdb" = yes; then output_sym "CONFIG_PDB" fi +if test "$fcntl_sync" = "yes" ; then + output_sym "CONFIG_FCNTL_SYNC" +fi print_config "Lib-based ioengines dynamic" "$dynamic_engines" cat > $TMPC << EOF diff --git a/io_u.c b/io_u.c index 059637e592..9d977d34e0 100644 --- a/io_u.c +++ b/io_u.c @@ -2297,7 +2297,11 @@ int do_io_u_sync(const struct thread_data *td, struct io_u *io_u) int ret; if (io_u->ddir == DDIR_SYNC) { +#ifdef CONFIG_FCNTL_SYNC + ret = fcntl(io_u->file->fd, F_FULLSYNC); +#else ret = fsync(io_u->file->fd); +#endif } else if (io_u->ddir == DDIR_DATASYNC) { #ifdef CONFIG_FDATASYNC ret = fdatasync(io_u->file->fd); From c99c81adb3510a8dc34d47fd40b19ef657e32192 Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Thu, 17 Feb 2022 12:53:59 -0700 Subject: [PATCH 0101/1097] Correct F_FULLSYNC -> F_FULLFSYNC Apparently used a mix of the two, inconsistently. Fixes: a04e0665cb5d ("Use fcntl(..., F_FULLSYNC) if available") Signed-off-by: Jens Axboe --- configure | 6 +++--- io_u.c | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/configure b/configure index fd1d435b4d..6160d84d73 100755 --- a/configure +++ b/configure @@ -656,13 +656,13 @@ cat > $TMPC << EOF int main(int argc, char **argv) { - return fcntl(0, F_FULLSYNC); + return fcntl(0, F_FULLFSYNC); } EOF -if compile_prog "" "" "fcntl(F_FULLSYNC)" ; then +if compile_prog "" "" "fcntl(F_FULLFSYNC)" ; then fcntl_sync="yes" fi -print_config "fcntl(F_FULLSYNC)" "$fcntl_sync" +print_config "fcntl(F_FULLFSYNC)" "$fcntl_sync" ########################################## # linux-aio probe diff --git a/io_u.c b/io_u.c index 9d977d34e0..806ceb7776 100644 --- a/io_u.c +++ b/io_u.c @@ -2298,7 +2298,7 @@ int do_io_u_sync(const struct thread_data *td, struct io_u *io_u) if (io_u->ddir == DDIR_SYNC) { #ifdef CONFIG_FCNTL_SYNC - ret = fcntl(io_u->file->fd, F_FULLSYNC); + ret = fcntl(io_u->file->fd, F_FULLFSYNC); #else ret = fsync(io_u->file->fd); #endif From d479658a965ac17ff213d7ba506116f822cb3219 Mon Sep 17 00:00:00 2001 From: Lukasz Dorau Date: Fri, 18 Feb 2022 14:57:18 +0100 Subject: [PATCH 0102/1097] rpma: RPMA engines require librpma>=v0.11.0 with rpma_cq_get_wc() Signed-off-by: Lukasz Dorau --- configure | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/configure b/configure index 0efde7d6a8..a0cdd9a238 100755 --- a/configure +++ b/configure @@ -955,7 +955,7 @@ print_config "rdmacm" "$rdmacm" ########################################## # librpma probe -# The librpma engine requires librpma>=v0.10.0 with rpma_mr_advise(). +# The librpma engines require librpma>=v0.11.0 with rpma_cq_get_wc(). if test "$librpma" != "yes" ; then librpma="no" fi @@ -963,7 +963,7 @@ cat > $TMPC << EOF #include int main(void) { - void *ptr = rpma_mr_advise; + void *ptr = rpma_cq_get_wc; (void) ptr; /* unused */ return 0; } From 4ef7dd21b8a960855aa9d9c1e6417509bbfa05a9 Mon Sep 17 00:00:00 2001 From: Oksana Salyk Date: Fri, 4 Feb 2022 14:00:36 -0500 Subject: [PATCH 0103/1097] rpma: update RPMA engines with new librpma completions API The API of librpma has been changed between v0.10.0 and v0.12.0 and fio has to be updated. Signed-off-by: Oksana Salyk --- engines/librpma_apm.c | 8 +++---- engines/librpma_fio.c | 46 ++++++++++++++++++++++++++--------------- engines/librpma_fio.h | 16 +++++++------- engines/librpma_gpspm.c | 39 ++++++++++++++++------------------ 4 files changed, 59 insertions(+), 50 deletions(-) diff --git a/engines/librpma_apm.c b/engines/librpma_apm.c index ffa3769d33..d1166ad839 100644 --- a/engines/librpma_apm.c +++ b/engines/librpma_apm.c @@ -22,8 +22,7 @@ static inline int client_io_flush(struct thread_data *td, struct io_u *first_io_u, struct io_u *last_io_u, unsigned long long int len); -static int client_get_io_u_index(struct rpma_completion *cmpl, - unsigned int *io_u_index); +static int client_get_io_u_index(struct ibv_wc *wc, unsigned int *io_u_index); static int client_init(struct thread_data *td) { @@ -188,10 +187,9 @@ static inline int client_io_flush(struct thread_data *td, return 0; } -static int client_get_io_u_index(struct rpma_completion *cmpl, - unsigned int *io_u_index) +static int client_get_io_u_index(struct ibv_wc *wc, unsigned int *io_u_index) { - memcpy(io_u_index, &cmpl->op_context, sizeof(*io_u_index)); + memcpy(io_u_index, &wc->wr_id, sizeof(*io_u_index)); return 1; } diff --git a/engines/librpma_fio.c b/engines/librpma_fio.c index 9d6ebf38ee..dfd8218006 100644 --- a/engines/librpma_fio.c +++ b/engines/librpma_fio.c @@ -302,6 +302,12 @@ int librpma_fio_client_init(struct thread_data *td, if (ccd->conn == NULL) goto err_peer_delete; + /* get the connection's main CQ */ + if ((ret = rpma_conn_get_cq(ccd->conn, &ccd->cq))) { + librpma_td_verror(td, ret, "rpma_conn_get_cq"); + goto err_conn_delete; + } + /* get the connection's private data sent from the server */ if ((ret = rpma_conn_get_private_data(ccd->conn, &pdata))) { librpma_td_verror(td, ret, "rpma_conn_get_private_data"); @@ -455,7 +461,7 @@ static enum fio_q_status client_queue_sync(struct thread_data *td, struct io_u *io_u) { struct librpma_fio_client_data *ccd = td->io_ops_data; - struct rpma_completion cmpl; + struct ibv_wc wc; unsigned io_u_index; int ret; @@ -478,31 +484,31 @@ static enum fio_q_status client_queue_sync(struct thread_data *td, do { /* get a completion */ - ret = rpma_conn_completion_get(ccd->conn, &cmpl); + ret = rpma_cq_get_wc(ccd->cq, 1, &wc, NULL); if (ret == RPMA_E_NO_COMPLETION) { /* lack of completion is not an error */ continue; } else if (ret != 0) { /* an error occurred */ - librpma_td_verror(td, ret, "rpma_conn_completion_get"); + librpma_td_verror(td, ret, "rpma_cq_get_wc"); goto err; } /* if io_us has completed with an error */ - if (cmpl.op_status != IBV_WC_SUCCESS) + if (wc.status != IBV_WC_SUCCESS) goto err; - if (cmpl.op == RPMA_OP_SEND) + if (wc.opcode == IBV_WC_SEND) ++ccd->op_send_completed; else { - if (cmpl.op == RPMA_OP_RECV) + if (wc.opcode == IBV_WC_RECV) ++ccd->op_recv_completed; break; } } while (1); - if (ccd->get_io_u_index(&cmpl, &io_u_index) != 1) + if (ccd->get_io_u_index(&wc, &io_u_index) != 1) goto err; if (io_u->index != io_u_index) { @@ -654,8 +660,8 @@ int librpma_fio_client_commit(struct thread_data *td) static int client_getevent_process(struct thread_data *td) { struct librpma_fio_client_data *ccd = td->io_ops_data; - struct rpma_completion cmpl; - /* io_u->index of completed io_u (cmpl.op_context) */ + struct ibv_wc wc; + /* io_u->index of completed io_u (wc.wr_id) */ unsigned int io_u_index; /* # of completed io_us */ int cmpl_num = 0; @@ -665,7 +671,7 @@ static int client_getevent_process(struct thread_data *td) int ret; /* get a completion */ - if ((ret = rpma_conn_completion_get(ccd->conn, &cmpl))) { + if ((ret = rpma_cq_get_wc(ccd->cq, 1, &wc, NULL))) { /* lack of completion is not an error */ if (ret == RPMA_E_NO_COMPLETION) { /* lack of completion is not an error */ @@ -673,22 +679,22 @@ static int client_getevent_process(struct thread_data *td) } /* an error occurred */ - librpma_td_verror(td, ret, "rpma_conn_completion_get"); + librpma_td_verror(td, ret, "rpma_cq_get_wc"); return -1; } /* if io_us has completed with an error */ - if (cmpl.op_status != IBV_WC_SUCCESS) { - td->error = cmpl.op_status; + if (wc.status != IBV_WC_SUCCESS) { + td->error = wc.status; return -1; } - if (cmpl.op == RPMA_OP_SEND) + if (wc.opcode == IBV_WC_SEND) ++ccd->op_send_completed; - else if (cmpl.op == RPMA_OP_RECV) + else if (wc.opcode == IBV_WC_RECV) ++ccd->op_recv_completed; - if ((ret = ccd->get_io_u_index(&cmpl, &io_u_index)) != 1) + if ((ret = ccd->get_io_u_index(&wc, &io_u_index)) != 1) return ret; /* look for an io_u being completed */ @@ -750,7 +756,7 @@ int librpma_fio_client_getevents(struct thread_data *td, unsigned int min, /* * To reduce CPU consumption one can use - * the rpma_conn_completion_wait() function. + * the rpma_cq_wait() function. * Note this greatly increase the latency * and make the results less stable. * The bandwidth stays more or less the same. @@ -1029,6 +1035,12 @@ int librpma_fio_server_open_file(struct thread_data *td, struct fio_file *f, csd->ws_ptr = ws_ptr; csd->conn = conn; + /* get the connection's main CQ */ + if ((ret = rpma_conn_get_cq(csd->conn, &csd->cq))) { + librpma_td_verror(td, ret, "rpma_conn_get_cq"); + goto err_conn_delete; + } + return 0; err_conn_delete: diff --git a/engines/librpma_fio.h b/engines/librpma_fio.h index 2c507e9c5c..912902357d 100644 --- a/engines/librpma_fio.h +++ b/engines/librpma_fio.h @@ -94,12 +94,13 @@ typedef int (*librpma_fio_flush_t)(struct thread_data *td, * - ( 0) - skip * - (-1) - on error */ -typedef int (*librpma_fio_get_io_u_index_t)(struct rpma_completion *cmpl, +typedef int (*librpma_fio_get_io_u_index_t)(struct ibv_wc *wc, unsigned int *io_u_index); struct librpma_fio_client_data { struct rpma_peer *peer; struct rpma_conn *conn; + struct rpma_cq *cq; /* aligned td->orig_buffer */ char *orig_buffer_aligned; @@ -199,29 +200,29 @@ static inline int librpma_fio_client_io_complete_all_sends( struct thread_data *td) { struct librpma_fio_client_data *ccd = td->io_ops_data; - struct rpma_completion cmpl; + struct ibv_wc wc; int ret; while (ccd->op_send_posted != ccd->op_send_completed) { /* get a completion */ - ret = rpma_conn_completion_get(ccd->conn, &cmpl); + ret = rpma_cq_get_wc(ccd->cq, 1, &wc, NULL); if (ret == RPMA_E_NO_COMPLETION) { /* lack of completion is not an error */ continue; } else if (ret != 0) { /* an error occurred */ - librpma_td_verror(td, ret, "rpma_conn_completion_get"); + librpma_td_verror(td, ret, "rpma_cq_get_wc"); break; } - if (cmpl.op_status != IBV_WC_SUCCESS) + if (wc.status != IBV_WC_SUCCESS) return -1; - if (cmpl.op == RPMA_OP_SEND) + if (wc.opcode == IBV_WC_SEND) ++ccd->op_send_completed; else { log_err( - "A completion other than RPMA_OP_SEND got during cleaning up the CQ from SENDs\n"); + "A completion other than IBV_WC_SEND got during cleaning up the CQ from SENDs\n"); return -1; } } @@ -251,6 +252,7 @@ struct librpma_fio_server_data { /* resources of an incoming connection */ struct rpma_conn *conn; + struct rpma_cq *cq; char *ws_ptr; struct rpma_mr_local *ws_mr; diff --git a/engines/librpma_gpspm.c b/engines/librpma_gpspm.c index 7414770971..14626e7fce 100644 --- a/engines/librpma_gpspm.c +++ b/engines/librpma_gpspm.c @@ -60,8 +60,7 @@ static inline int client_io_flush(struct thread_data *td, struct io_u *first_io_u, struct io_u *last_io_u, unsigned long long int len); -static int client_get_io_u_index(struct rpma_completion *cmpl, - unsigned int *io_u_index); +static int client_get_io_u_index(struct ibv_wc *wc, unsigned int *io_u_index); static int client_init(struct thread_data *td) { @@ -317,17 +316,16 @@ static inline int client_io_flush(struct thread_data *td, return 0; } -static int client_get_io_u_index(struct rpma_completion *cmpl, - unsigned int *io_u_index) +static int client_get_io_u_index(struct ibv_wc *wc, unsigned int *io_u_index) { GPSPMFlushResponse *flush_resp; - if (cmpl->op != RPMA_OP_RECV) + if (wc->opcode != IBV_WC_RECV) return 0; /* unpack a response from the received buffer */ flush_resp = gpspm_flush_response__unpack(NULL, - cmpl->byte_len, cmpl->op_context); + wc->byte_len, (void *)wc->wr_id); if (flush_resp == NULL) { log_err("Cannot unpack the flush response buffer\n"); return -1; @@ -373,7 +371,7 @@ struct server_data { uint32_t msg_sqe_available; /* # of free SQ slots */ /* in-memory queues */ - struct rpma_completion *msgs_queued; + struct ibv_wc *msgs_queued; uint32_t msg_queued_nr; }; @@ -562,8 +560,7 @@ static int server_open_file(struct thread_data *td, struct fio_file *f) return ret; } -static int server_qe_process(struct thread_data *td, - struct rpma_completion *cmpl) +static int server_qe_process(struct thread_data *td, struct ibv_wc *wc) { struct librpma_fio_server_data *csd = td->io_ops_data; struct server_data *sd = csd->server_data; @@ -580,7 +577,7 @@ static int server_qe_process(struct thread_data *td, int ret; /* calculate SEND/RECV pair parameters */ - msg_index = (int)(uintptr_t)cmpl->op_context; + msg_index = (int)(uintptr_t)wc->wr_id; io_u_buff_offset = IO_U_BUFF_OFF_SERVER(msg_index); send_buff_offset = io_u_buff_offset + SEND_OFFSET; recv_buff_offset = io_u_buff_offset + RECV_OFFSET; @@ -588,7 +585,7 @@ static int server_qe_process(struct thread_data *td, recv_buff_ptr = sd->orig_buffer_aligned + recv_buff_offset; /* unpack a flush request from the received buffer */ - flush_req = gpspm_flush_request__unpack(NULL, cmpl->byte_len, + flush_req = gpspm_flush_request__unpack(NULL, wc->byte_len, recv_buff_ptr); if (flush_req == NULL) { log_err("cannot unpack the flush request buffer\n"); @@ -682,28 +679,28 @@ static int server_cmpl_process(struct thread_data *td) { struct librpma_fio_server_data *csd = td->io_ops_data; struct server_data *sd = csd->server_data; - struct rpma_completion *cmpl = &sd->msgs_queued[sd->msg_queued_nr]; + struct ibv_wc *wc = &sd->msgs_queued[sd->msg_queued_nr]; struct librpma_fio_options_values *o = td->eo; int ret; - ret = rpma_conn_completion_get(csd->conn, cmpl); + ret = rpma_cq_get_wc(csd->cq, 1, wc, NULL); if (ret == RPMA_E_NO_COMPLETION) { if (o->busy_wait_polling == 0) { - ret = rpma_conn_completion_wait(csd->conn); + ret = rpma_cq_wait(csd->cq); if (ret == RPMA_E_NO_COMPLETION) { /* lack of completion is not an error */ return 0; } else if (ret != 0) { - librpma_td_verror(td, ret, "rpma_conn_completion_wait"); + librpma_td_verror(td, ret, "rpma_cq_wait"); goto err_terminate; } - ret = rpma_conn_completion_get(csd->conn, cmpl); + ret = rpma_cq_get_wc(csd->cq, 1, wc, NULL); if (ret == RPMA_E_NO_COMPLETION) { /* lack of completion is not an error */ return 0; } else if (ret != 0) { - librpma_td_verror(td, ret, "rpma_conn_completion_get"); + librpma_td_verror(td, ret, "rpma_cq_get_wc"); goto err_terminate; } } else { @@ -711,17 +708,17 @@ static int server_cmpl_process(struct thread_data *td) return 0; } } else if (ret != 0) { - librpma_td_verror(td, ret, "rpma_conn_completion_get"); + librpma_td_verror(td, ret, "rpma_cq_get_wc"); goto err_terminate; } /* validate the completion */ - if (cmpl->op_status != IBV_WC_SUCCESS) + if (wc->status != IBV_WC_SUCCESS) goto err_terminate; - if (cmpl->op == RPMA_OP_RECV) + if (wc->opcode == IBV_WC_RECV) ++sd->msg_queued_nr; - else if (cmpl->op == RPMA_OP_SEND) + else if (wc->opcode == IBV_WC_SEND) ++sd->msg_sqe_available; return 0; From 31952e1d0c86a56a3969f05bb53a20fe018fe189 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Tue, 2 Nov 2021 23:41:00 +0200 Subject: [PATCH 0104/1097] genfio: fix temporary file handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit As a side effect, the template temp file is no longer left behind, as a unique filename is used for it on each run. Use the same method of figuring out the temp dir as in check_status_file(). Signed-off-by: Ville Skyttä --- tools/genfio | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tools/genfio b/tools/genfio index 8518bbccf3..fc7e6e4677 100755 --- a/tools/genfio +++ b/tools/genfio @@ -22,7 +22,8 @@ BLK_SIZE= BLOCK_SIZE=4k SEQ=-1 -TEMPLATE=/tmp/template.fio +TEMPLATE=$(mktemp "${TMPDIR:-${TEMP:-/tmp}}/template.fio.XXXXXX") || exit $? +trap 'rm -f "$TEMPLATE"' EXIT OUTFILE= DISKS= PRINTABLE_DISKS= From e28b6d0a881080ff08434dcc1eab00c1954c182d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Thu, 4 Nov 2021 09:30:28 +0200 Subject: [PATCH 0105/1097] ci, t, tools: use `command` and `type` instead of `which` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `which` is not POSIX, and cannot be assumed to installed everywhere. `command -v` is available in POSIX and its predecessors at least since 1994: https://pubs.opengroup.org/onlinepubs/7908799/ It can be used as a replacement for `which` in a number of occurrences in fio. For bash scripts, `type -P` is available as a builtin replacement for `which` and its $PATH search semantics. Signed-off-by: Ville Skyttä --- ci/travis-install-pmdk.sh | 2 +- t/one-core-peak.sh | 4 ++-- tools/fio_generate_plots | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ci/travis-install-pmdk.sh b/ci/travis-install-pmdk.sh index 803438f8f8..3b0b5bbc56 100755 --- a/ci/travis-install-pmdk.sh +++ b/ci/travis-install-pmdk.sh @@ -12,7 +12,7 @@ WORKDIR=$(pwd) # /bin/sh: 1: clang: not found # if CC is not set to the full path of clang. # -export CC=$(which $CC) +export CC=$(type -P $CC) # Install PMDK libraries, because PMDK's libpmem # is a dependency of the librpma fio engine. diff --git a/t/one-core-peak.sh b/t/one-core-peak.sh index 9da8304e7d..466dbdd45b 100755 --- a/t/one-core-peak.sh +++ b/t/one-core-peak.sh @@ -33,7 +33,7 @@ check_binary() { # Ensure the binaries are present and executable for bin in "$@"; do if [ ! -x ${bin} ]; then - which ${bin} >/dev/null + command -v ${bin} >/dev/null [ $? -eq 0 ] || fatal "${bin} doesn't exists or is not executable" fi done @@ -197,7 +197,7 @@ show_nvme() { fw=$(cat ${device_dir}/firmware_rev | xargs) #xargs for trimming spaces serial=$(cat ${device_dir}/serial | xargs) #xargs for trimming spaces info ${device_name} "MODEL=${model} FW=${fw} serial=${serial} PCI=${pci_addr}@${link_speed} IRQ=${irq} NUMA=${numa} CPUS=${cpus} " - which nvme &> /dev/null + command -v nvme > /dev/null if [ $? -eq 0 ]; then status="" NCQA=$(nvme get-feature -H -f 0x7 ${device} 2>&1 |grep NCQA |cut -d ':' -f 2 | xargs) diff --git a/tools/fio_generate_plots b/tools/fio_generate_plots index e455878815..468cf27a6c 100755 --- a/tools/fio_generate_plots +++ b/tools/fio_generate_plots @@ -21,7 +21,7 @@ if [ -z "$1" ]; then exit 1 fi -GNUPLOT=$(which gnuplot) +GNUPLOT=$(command -v gnuplot) if [ ! -x "$GNUPLOT" ] then echo You need gnuplot installed to generate graphs From fc002f14a773b7fab28c92ff4d6eeba25c2951ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Thu, 4 Nov 2021 09:39:32 +0200 Subject: [PATCH 0106/1097] Spelling and grammar fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Ville Skyttä --- HOWTO.rst | 4 ++-- crc/xxhash.c | 4 ++-- engines/exec.c | 4 ++-- engines/http.c | 4 ++-- engines/ime.c | 2 +- engines/libhdfs.c | 2 +- engines/librpma_fio.c | 2 +- engines/librpma_gpspm.c | 2 +- engines/nbd.c | 2 +- engines/rados.c | 2 +- engines/rbd.c | 4 ++-- engines/rdma.c | 2 +- examples/enospc-pressure.fio | 4 ++-- examples/falloc.fio | 2 +- examples/librpma_apm-server.fio | 2 +- examples/librpma_gpspm-server.fio | 2 +- examples/rand-zones.fio | 2 +- filesetup.c | 2 +- fio.1 | 4 ++-- graph.c | 2 +- lib/pattern.c | 6 +++--- options.c | 4 ++-- os/os-android.h | 2 +- os/os-netbsd.h | 2 +- os/windows/posix.c | 2 +- oslib/libmtd.h | 6 +++--- stat.c | 2 +- stat.h | 2 +- t/latency_percentiles.py | 2 +- t/one-core-peak.sh | 2 +- t/readonly.py | 2 +- t/sgunmap-test.py | 2 +- t/steadystate_tests.py | 2 +- t/time-test.c | 2 +- tools/fio_jsonplus_clat2csv | 4 ++-- tools/fiograph/fiograph.py | 2 +- tools/genfio | 2 +- tools/hist/fio-histo-log-pctiles.py | 2 +- tools/plot/fio2gnuplot | 4 ++-- tools/plot/fio2gnuplot.1 | 2 +- tools/plot/fio2gnuplot.manpage | 2 +- 41 files changed, 55 insertions(+), 55 deletions(-) diff --git a/HOWTO.rst b/HOWTO.rst index ac1f347870..0978879ce6 100644 --- a/HOWTO.rst +++ b/HOWTO.rst @@ -1443,7 +1443,7 @@ I/O type range of possible random values. Defaults are: random for **pareto** and **zipf**, and 0.5 for **normal**. If you wanted to use **zipf** with a `theta` of 1.2 centered on 1/4 of allowed value range, - you would use ``random_distibution=zipf:1.2:0.25``. + you would use ``random_distribution=zipf:1.2:0.25``. For a **zoned** distribution, fio supports specifying percentages of I/O access that should fall within what range of the file or device. For @@ -3370,7 +3370,7 @@ Verification To avoid false verification errors, do not use the norandommap option when verifying data with async I/O engines and I/O depths > 1. Or use the norandommap and the lfsr random generator together to avoid writing to the - same offset with muliple outstanding I/Os. + same offset with multiple outstanding I/Os. .. option:: verify_offset=int diff --git a/crc/xxhash.c b/crc/xxhash.c index 4736c528fc..0119564be3 100644 --- a/crc/xxhash.c +++ b/crc/xxhash.c @@ -50,10 +50,10 @@ You can contact the author at : //#define XXH_ACCEPT_NULL_INPUT_POINTER 1 // XXH_FORCE_NATIVE_FORMAT : -// By default, xxHash library provides endian-independant Hash values, based on little-endian convention. +// By default, xxHash library provides endian-independent Hash values, based on little-endian convention. // Results are therefore identical for little-endian and big-endian CPU. // This comes at a performance cost for big-endian CPU, since some swapping is required to emulate little-endian format. -// Should endian-independance be of no importance for your application, you may set the #define below to 1. +// Should endian-independence be of no importance for your application, you may set the #define below to 1. // It will improve speed for Big-endian CPU. // This option has no impact on Little_Endian CPU. #define XXH_FORCE_NATIVE_FORMAT 0 diff --git a/engines/exec.c b/engines/exec.c index ab3639c502..20e50e007e 100644 --- a/engines/exec.c +++ b/engines/exec.c @@ -67,8 +67,8 @@ char *str_replace(char *orig, const char *rep, const char *with) /* * Replace a substring by another. * - * Returns the new string if occurences were found - * Returns orig if no occurence is found + * Returns the new string if occurrences were found + * Returns orig if no occurrence is found */ char *result, *insert, *tmp; int len_rep, len_with, len_front, count; diff --git a/engines/http.c b/engines/http.c index 35c44871da..57d4967def 100644 --- a/engines/http.c +++ b/engines/http.c @@ -388,7 +388,7 @@ static void _add_aws_auth_header(CURL *curl, struct curl_slist *slist, struct ht signature = _conv_hex(md, SHA256_DIGEST_LENGTH); - /* Surpress automatic Accept: header */ + /* Suppress automatic Accept: header */ slist = curl_slist_append(slist, "Accept:"); snprintf(s, sizeof(s), "x-amz-content-sha256: %s", dsha); @@ -419,7 +419,7 @@ static void _add_swift_header(CURL *curl, struct curl_slist *slist, struct http_ if (op == DDIR_WRITE) { dsha = _gen_hex_md5(buf, len); } - /* Surpress automatic Accept: header */ + /* Suppress automatic Accept: header */ slist = curl_slist_append(slist, "Accept:"); snprintf(s, sizeof(s), "etag: %s", dsha); diff --git a/engines/ime.c b/engines/ime.c index 440cc29e8e..f6690cc16c 100644 --- a/engines/ime.c +++ b/engines/ime.c @@ -83,7 +83,7 @@ struct ime_data { }; struct iovec *iovecs; /* array of queued iovecs */ struct io_u **io_us; /* array of queued io_u pointers */ - struct io_u **event_io_us; /* array of the events retieved afer get_events*/ + struct io_u **event_io_us; /* array of the events retrieved after get_events*/ unsigned int queued; /* iovecs/io_us in the queue */ unsigned int events; /* number of committed iovecs/io_us */ diff --git a/engines/libhdfs.c b/engines/libhdfs.c index eb55c3c549..f20e45cac1 100644 --- a/engines/libhdfs.c +++ b/engines/libhdfs.c @@ -27,7 +27,7 @@ struct hdfsio_data { }; struct hdfsio_options { - void *pad; /* needed because offset can't be 0 for a option defined used offsetof */ + void *pad; /* needed because offset can't be 0 for an option defined used offsetof */ char *host; char *directory; unsigned int port; diff --git a/engines/librpma_fio.c b/engines/librpma_fio.c index dfd8218006..34818904d0 100644 --- a/engines/librpma_fio.c +++ b/engines/librpma_fio.c @@ -426,7 +426,7 @@ int librpma_fio_client_post_init(struct thread_data *td) /* * td->orig_buffer is not aligned. The engine requires aligned io_us - * so FIO alignes up the address using the formula below. + * so FIO aligns up the address using the formula below. */ ccd->orig_buffer_aligned = PTR_ALIGN(td->orig_buffer, page_mask) + td->o.mem_align; diff --git a/engines/librpma_gpspm.c b/engines/librpma_gpspm.c index 14626e7fce..5cf974722d 100644 --- a/engines/librpma_gpspm.c +++ b/engines/librpma_gpspm.c @@ -431,7 +431,7 @@ static int server_post_init(struct thread_data *td) /* * td->orig_buffer is not aligned. The engine requires aligned io_us - * so FIO alignes up the address using the formula below. + * so FIO aligns up the address using the formula below. */ sd->orig_buffer_aligned = PTR_ALIGN(td->orig_buffer, page_mask) + td->o.mem_align; diff --git a/engines/nbd.c b/engines/nbd.c index b0ba75e694..7c2d5f4ba6 100644 --- a/engines/nbd.c +++ b/engines/nbd.c @@ -52,7 +52,7 @@ static struct fio_option options[] = { }, }; -/* Alocates nbd_data. */ +/* Allocates nbd_data. */ static int nbd_setup(struct thread_data *td) { struct nbd_data *nbd_data; diff --git a/engines/rados.c b/engines/rados.c index 23e62c4c45..976f9229b0 100644 --- a/engines/rados.c +++ b/engines/rados.c @@ -151,7 +151,7 @@ static int _fio_rados_connect(struct thread_data *td) char *client_name = NULL; /* - * If we specify cluser name, the rados_create2 + * If we specify cluster name, the rados_create2 * will not assume 'client.'. name is considered * as a full type.id namestr */ diff --git a/engines/rbd.c b/engines/rbd.c index c6203d4c2a..2f25889ac8 100644 --- a/engines/rbd.c +++ b/engines/rbd.c @@ -173,7 +173,7 @@ static int _fio_rbd_connect(struct thread_data *td) char *client_name = NULL; /* - * If we specify cluser name, the rados_create2 + * If we specify cluster name, the rados_create2 * will not assume 'client.'. name is considered * as a full type.id namestr */ @@ -633,7 +633,7 @@ static int fio_rbd_setup(struct thread_data *td) /* taken from "net" engine. Pretend we deal with files, * even if we do not have any ideas about files. - * The size of the RBD is set instead of a artificial file. + * The size of the RBD is set instead of an artificial file. */ if (!td->files_index) { add_file(td, td->o.filename ? : "rbd", 0, 0); diff --git a/engines/rdma.c b/engines/rdma.c index f447186981..4eb86652f4 100644 --- a/engines/rdma.c +++ b/engines/rdma.c @@ -1194,7 +1194,7 @@ static int check_set_rlimits(struct thread_data *td) static int compat_options(struct thread_data *td) { - // The original RDMA engine had an ugly / seperator + // The original RDMA engine had an ugly / separator // on the filename for it's options. This function // retains backwards compatibility with it. Note we do not // support setting the bindname option is this legacy mode. diff --git a/examples/enospc-pressure.fio b/examples/enospc-pressure.fio index ca9d8f7a7a..fa404fd505 100644 --- a/examples/enospc-pressure.fio +++ b/examples/enospc-pressure.fio @@ -35,8 +35,8 @@ bs=4k rw=randtrim filename=raicer -# Verifier thread continiously write to newly allcated blocks -# and veryfy written content +# Verifier thread continuously writes to newly allcated blocks +# and verifies written content [aio-dio-verifier] create_on_open=1 verify=crc32c-intel diff --git a/examples/falloc.fio b/examples/falloc.fio index fadf132169..5a3e88b81e 100644 --- a/examples/falloc.fio +++ b/examples/falloc.fio @@ -29,7 +29,7 @@ rw=randtrim numjobs=2 filename=fragmented_file -## Mesure IO performance on fragmented file +## Measure IO performance on fragmented file [sequential aio-dio write] stonewall ioengine=libaio diff --git a/examples/librpma_apm-server.fio b/examples/librpma_apm-server.fio index 062b5215d2..dc1ddba294 100644 --- a/examples/librpma_apm-server.fio +++ b/examples/librpma_apm-server.fio @@ -20,7 +20,7 @@ thread # (https://pmem.io/rpma/documentation/basic-direct-write-to-pmem.html) direct_write_to_pmem=0 -numjobs=1 # number of expected incomming connections +numjobs=1 # number of expected incoming connections size=100MiB # size of workspace for a single connection filename=malloc # device dax or an existing fsdax file or "malloc" for allocation from DRAM # filename=/dev/dax1.0 diff --git a/examples/librpma_gpspm-server.fio b/examples/librpma_gpspm-server.fio index 67e92a28ad..4555314f8a 100644 --- a/examples/librpma_gpspm-server.fio +++ b/examples/librpma_gpspm-server.fio @@ -22,7 +22,7 @@ thread direct_write_to_pmem=0 # set to 0 (false) to wait for completion instead of busy-wait polling completion. busy_wait_polling=1 -numjobs=1 # number of expected incomming connections +numjobs=1 # number of expected incoming connections iodepth=2 # number of parallel GPSPM requests size=100MiB # size of workspace for a single connection filename=malloc # device dax or an existing fsdax file or "malloc" for allocation from DRAM diff --git a/examples/rand-zones.fio b/examples/rand-zones.fio index 169137d493..10e717278f 100644 --- a/examples/rand-zones.fio +++ b/examples/rand-zones.fio @@ -21,6 +21,6 @@ random_distribution=zoned:50/5:30/15:20/ # The above applies to all of reads/writes/trims. If we wanted to do # something differently for writes, let's say 50% for the first 10% # and 50% for the remaining 90%, we could do it by adding a new section -# after a a comma. +# after a comma. # random_distribution=zoned:50/5:30/15:20/,50/10:50/90 diff --git a/filesetup.c b/filesetup.c index fb556d8444..7c32d0af43 100644 --- a/filesetup.c +++ b/filesetup.c @@ -1486,7 +1486,7 @@ static bool init_rand_distribution(struct thread_data *td) /* * Check if the number of blocks exceeds the randomness capability of - * the selected generator. Tausworthe is 32-bit, the others are fullly + * the selected generator. Tausworthe is 32-bit, the others are fully * 64-bit capable. */ static int check_rand_gen_limits(struct thread_data *td, struct fio_file *f, diff --git a/fio.1 b/fio.1 index e23d4092cc..984106558e 100644 --- a/fio.1 +++ b/fio.1 @@ -1221,7 +1221,7 @@ more control over most probable outcome. This value is in range [0-1] which maps range of possible random values. Defaults are: random for \fBpareto\fR and \fBzipf\fR, and 0.5 for \fBnormal\fR. If you wanted to use \fBzipf\fR with a `theta` of 1.2 centered on 1/4 of allowed value range, -you would use `random_distibution=zipf:1.2:0.25`. +you would use `random_distribution=zipf:1.2:0.25`. .P For a \fBzoned\fR distribution, fio supports specifying percentages of I/O access that should fall within what range of the file or device. For @@ -3082,7 +3082,7 @@ the verify will be of the newly written data. To avoid false verification errors, do not use the norandommap option when verifying data with async I/O engines and I/O depths > 1. Or use the norandommap and the lfsr random generator together to avoid writing to the -same offset with muliple outstanding I/Os. +same offset with multiple outstanding I/Os. .RE .TP .BI verify_offset \fR=\fPint diff --git a/graph.c b/graph.c index 7a174170c7..c49cdae14f 100644 --- a/graph.c +++ b/graph.c @@ -999,7 +999,7 @@ const char *graph_find_tooltip(struct graph *g, int ix, int iy) ydiff = fabs(yval - y); /* - * zero delta, or within or match critera, break + * zero delta, or within or match criteria, break */ if (ydiff < best_delta) { best_delta = ydiff; diff --git a/lib/pattern.c b/lib/pattern.c index 680a12be7e..d8203630d3 100644 --- a/lib/pattern.c +++ b/lib/pattern.c @@ -211,7 +211,7 @@ static const char *parse_number(const char *beg, char *out, * This function tries to find formats, e.g.: * %o - offset of the block * - * In case of successfull parsing it fills the format param + * In case of successful parsing it fills the format param * with proper offset and the size of the expected value, which * should be pasted into buffer using the format 'func' callback. * @@ -267,7 +267,7 @@ static const char *parse_format(const char *in, char *out, unsigned int parsed, * @fmt_desc - array of pattern format descriptors [input] * @fmt - array of pattern formats [output] * @fmt_sz - pointer where the size of pattern formats array stored [input], - * after successfull parsing this pointer will contain the number + * after successful parsing this pointer will contain the number * of parsed formats if any [output]. * * strings: @@ -275,7 +275,7 @@ static const char *parse_format(const char *in, char *out, unsigned int parsed, * NOTE: there is no way to escape quote, so "123\"abc" does not work. * * numbers: - * hexidecimal - sequence of hex bytes starting from 0x or 0X prefix, + * hexadecimal - sequence of hex bytes starting from 0x or 0X prefix, * e.g. 0xff12ceff1100ff * decimal - decimal number in range [INT_MIN, INT_MAX] * diff --git a/options.c b/options.c index 6cdbd2686c..e06d9b66ad 100644 --- a/options.c +++ b/options.c @@ -1366,7 +1366,7 @@ int get_max_str_idx(char *input) } /* - * Returns the directory at the index, indexes > entires will be + * Returns the directory at the index, indexes > entries will be * assigned via modulo division of the index */ int set_name_idx(char *target, size_t tlen, char *input, int index, @@ -1560,7 +1560,7 @@ static int str_gtod_reduce_cb(void *data, int *il) int val = *il; /* - * Only modfiy options if gtod_reduce==1 + * Only modify options if gtod_reduce==1 * Otherwise leave settings alone. */ if (val) { diff --git a/os/os-android.h b/os/os-android.h index 10c51b8318..2f73d249d5 100644 --- a/os/os-android.h +++ b/os/os-android.h @@ -66,7 +66,7 @@ #ifndef CONFIG_NO_SHM /* - * Bionic doesn't support SysV shared memeory, so implement it using ashmem + * Bionic doesn't support SysV shared memory, so implement it using ashmem */ #include #include diff --git a/os/os-netbsd.h b/os/os-netbsd.h index 624c7fa509..b553a4300b 100644 --- a/os/os-netbsd.h +++ b/os/os-netbsd.h @@ -13,7 +13,7 @@ #include #include -/* XXX hack to avoid confilcts between rbtree.h and */ +/* XXX hack to avoid conflicts between rbtree.h and */ #undef rb_node #undef rb_left #undef rb_right diff --git a/os/windows/posix.c b/os/windows/posix.c index 0d415e1e0d..a3a6c89fd0 100644 --- a/os/windows/posix.c +++ b/os/windows/posix.c @@ -1165,7 +1165,7 @@ HANDLE windows_handle_connection(HANDLE hjob, int sk) ret = pi.hProcess; /* duplicate socket and write the protocol_info to pipe so child can - * duplicate the communciation socket */ + * duplicate the communication socket */ if (WSADuplicateSocket(sk, GetProcessId(pi.hProcess), &protocol_info)) { log_err("WSADuplicateSocket failed (%lu).\n", GetLastError()); ret = INVALID_HANDLE_VALUE; diff --git a/oslib/libmtd.h b/oslib/libmtd.h index a0c90dcb9d..668e77981f 100644 --- a/oslib/libmtd.h +++ b/oslib/libmtd.h @@ -256,7 +256,7 @@ int mtd_mark_bad(const struct mtd_dev_info *mtd, int fd, int eb); * @mtd: MTD device description object * @fd: MTD device node file descriptor * @eb: eraseblock to read from - * @offs: offset withing the eraseblock to read from + * @offs: offset within the eraseblock to read from * @buf: buffer to read data to * @len: how many bytes to read * @@ -273,7 +273,7 @@ int mtd_read(const struct mtd_dev_info *mtd, int fd, int eb, int offs, * @mtd: MTD device description object * @fd: MTD device node file descriptor * @eb: eraseblock to write to - * @offs: offset withing the eraseblock to write to + * @offs: offset within the eraseblock to write to * @data: data buffer to write * @len: how many data bytes to write * @oob: OOB buffer to write @@ -329,7 +329,7 @@ int mtd_write_oob(libmtd_t desc, const struct mtd_dev_info *mtd, int fd, * @mtd: MTD device description object * @fd: MTD device node file descriptor * @eb: eraseblock to write to - * @offs: offset withing the eraseblock to write to + * @offs: offset within the eraseblock to write to * @img_name: the file to write * * This function writes an image @img_name the MTD device defined by @mtd. @eb diff --git a/stat.c b/stat.c index 1764eebc69..7947edb42f 100644 --- a/stat.c +++ b/stat.c @@ -377,7 +377,7 @@ void show_group_stats(struct group_run_stats *rs, struct buf_output *out) free(maxalt); } - /* Need to aggregate statisitics to show mixed values */ + /* Need to aggregate statistics to show mixed values */ if (rs->unified_rw_rep == UNIFIED_BOTH) show_mixed_group_stats(rs, out); } diff --git a/stat.h b/stat.h index dce0bb0dc9..eb7845afec 100644 --- a/stat.h +++ b/stat.h @@ -68,7 +68,7 @@ struct group_run_stats { * than one. This method has low accuracy when the value is small. For * example, let the buckets be {[0,99],[100,199],...,[900,999]}, and * the represented value of each bucket be the mean of the range. Then - * a value 0 has an round-off error of 49.5. To improve on this, we + * a value 0 has a round-off error of 49.5. To improve on this, we * use buckets with non-uniform ranges, while bounding the error of * each bucket within a ratio of the sample value. A simple example * would be when error_bound = 0.005, buckets are { diff --git a/t/latency_percentiles.py b/t/latency_percentiles.py index 9e37d9fee5..81704700d4 100755 --- a/t/latency_percentiles.py +++ b/t/latency_percentiles.py @@ -270,7 +270,7 @@ def check_latencies(self, jsondata, ddir, slat=True, clat=True, tlat=True, plus= # # Check only for the presence/absence of json+ # latency bins. Future work can check the - # accurracy of the bin values and counts. + # accuracy of the bin values and counts. # # Because the latency percentiles are based on # the bins, we can be confident that the bin diff --git a/t/one-core-peak.sh b/t/one-core-peak.sh index 9da8304e7d..63ca2093d9 100755 --- a/t/one-core-peak.sh +++ b/t/one-core-peak.sh @@ -34,7 +34,7 @@ check_binary() { for bin in "$@"; do if [ ! -x ${bin} ]; then which ${bin} >/dev/null - [ $? -eq 0 ] || fatal "${bin} doesn't exists or is not executable" + [ $? -eq 0 ] || fatal "${bin} doesn't exist or is not executable" fi done } diff --git a/t/readonly.py b/t/readonly.py index 464847c603..80fac6393d 100755 --- a/t/readonly.py +++ b/t/readonly.py @@ -6,7 +6,7 @@ # # readonly.py # -# Do some basic tests of the --readonly paramter +# Do some basic tests of the --readonly parameter # # USAGE # python readonly.py [-f fio-executable] diff --git a/t/sgunmap-test.py b/t/sgunmap-test.py index 4960a040ea..6687494f30 100755 --- a/t/sgunmap-test.py +++ b/t/sgunmap-test.py @@ -3,7 +3,7 @@ # # sgunmap-test.py # -# Limited functonality test for trim workloads using fio's sg ioengine +# Limited functionality test for trim workloads using fio's sg ioengine # This checks only the three sets of reported iodepths # # !!!WARNING!!! diff --git a/t/steadystate_tests.py b/t/steadystate_tests.py index e8bd768c51..d6ffd177e0 100755 --- a/t/steadystate_tests.py +++ b/t/steadystate_tests.py @@ -2,7 +2,7 @@ # # steadystate_tests.py # -# Test option parsing and functonality for fio's steady state detection feature. +# Test option parsing and functionality for fio's steady state detection feature. # # steadystate_tests.py --read file-for-read-testing --write file-for-write-testing ./fio # diff --git a/t/time-test.c b/t/time-test.c index a74d9206f2..3c87d4d4c3 100644 --- a/t/time-test.c +++ b/t/time-test.c @@ -67,7 +67,7 @@ * accuracy because the (ticks * clock_mult) product used for final * fractional chunk * - * iv) 64-bit arithmetic with the clock ticks to nsec conversion occuring in + * iv) 64-bit arithmetic with the clock ticks to nsec conversion occurring in * two stages. This is carried out using locks to update the number of * large time chunks (MAX_CLOCK_SEC_2STAGE) that have elapsed. * diff --git a/tools/fio_jsonplus_clat2csv b/tools/fio_jsonplus_clat2csv index 7f310fcc47..8fdd014d95 100755 --- a/tools/fio_jsonplus_clat2csv +++ b/tools/fio_jsonplus_clat2csv @@ -135,7 +135,7 @@ def more_bins(indices, bins): Returns: True if the indices do not yet point to the end of each bin in bins. - False if the indices point beyond their repsective bins. + False if the indices point beyond their respective bins. """ for key, value in six.iteritems(indices): @@ -160,7 +160,7 @@ def debug_print(debug, *args): def get_csvfile(dest, jobnum): """Generate CSV filename from command-line arguments and job numbers. - Paramaters: + Parameters: dest file specification for CSV filename. jobnum job number. diff --git a/tools/fiograph/fiograph.py b/tools/fiograph/fiograph.py index b5669a2dab..384decda18 100755 --- a/tools/fiograph/fiograph.py +++ b/tools/fiograph/fiograph.py @@ -218,7 +218,7 @@ def fio_to_graphviz(filename, format): # The first job will be a new execution group new_execution_group = True - # Let's interate on all sections to create links between them + # Let's iterate on all sections to create links between them for section_name in fio_file.sections(): # The current section section = fio_file[section_name] diff --git a/tools/genfio b/tools/genfio index 8518bbccf3..8a2c73e8ca 100755 --- a/tools/genfio +++ b/tools/genfio @@ -48,7 +48,7 @@ show_help() { one test after another then one disk after another Disabled by default -p : Run parallel test - one test after anoter but all disks at the same time + one test after another but all disks at the same time Enabled by default -D iodepth : Run with the specified iodepth Default is $IODEPTH diff --git a/tools/hist/fio-histo-log-pctiles.py b/tools/hist/fio-histo-log-pctiles.py index 08e7722d04..b5d167de22 100755 --- a/tools/hist/fio-histo-log-pctiles.py +++ b/tools/hist/fio-histo-log-pctiles.py @@ -748,7 +748,7 @@ def test_e1_get_pctiles_flat_histo(self): def test_e2_get_pctiles_highest_pct(self): fio_v3_bucket_count = 29 * 64 with open(self.fn, 'w') as f: - # make a empty fio v3 histogram + # make an empty fio v3 histogram buckets = [ 0 for j in range(0, fio_v3_bucket_count) ] # add one I/O request to last bucket buckets[-1] = 1 diff --git a/tools/plot/fio2gnuplot b/tools/plot/fio2gnuplot index d2dc81df9b..ce3ca2cc9f 100755 --- a/tools/plot/fio2gnuplot +++ b/tools/plot/fio2gnuplot @@ -492,8 +492,8 @@ def main(argv): #We need to adjust the output filename regarding the pattern required by the user if (pattern_set_by_user == True): gnuplot_output_filename=pattern - # As we do have some glob in the pattern, let's make this simpliest - # We do remove the simpliest parts of the expression to get a clear file name + # As we do have some glob in the pattern, let's make this simplest + # We do remove the simplest parts of the expression to get a clear file name gnuplot_output_filename=gnuplot_output_filename.replace('-*-','-') gnuplot_output_filename=gnuplot_output_filename.replace('*','-') gnuplot_output_filename=gnuplot_output_filename.replace('--','-') diff --git a/tools/plot/fio2gnuplot.1 b/tools/plot/fio2gnuplot.1 index 6fb1283f50..bfa10d26ef 100644 --- a/tools/plot/fio2gnuplot.1 +++ b/tools/plot/fio2gnuplot.1 @@ -35,7 +35,7 @@ The resulting graph helps at understanding trends. .TP .B Grouped 2D graph -All files are plotted in a single image to ease the comparaison. The same rendering options as per the individual 2D graph are used : +All files are plotted in a single image to ease the comparison. The same rendering options as per the individual 2D graph are used : .RS .IP \(bu 3 raw diff --git a/tools/plot/fio2gnuplot.manpage b/tools/plot/fio2gnuplot.manpage index 6a12cf8196..be3f13c202 100644 --- a/tools/plot/fio2gnuplot.manpage +++ b/tools/plot/fio2gnuplot.manpage @@ -20,7 +20,7 @@ DESCRIPTION The resulting graph helps at understanding trends. Grouped 2D graph - All files are plotted in a single image to ease the comparaison. The same rendering options as per the individual 2D graph are used : + All files are plotted in a single image to ease the comparison. The same rendering options as per the individual 2D graph are used : - raw - smooth - trend From e9d0f70a57d00e96e8dd80d53eee38b8cc829164 Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Mon, 21 Feb 2022 09:41:53 -0700 Subject: [PATCH 0107/1097] aarch64: add system call definitions Avoid a libc function call, just define our own syscall wrappers for this architecture. Lifted from liburing. Signed-off-by: Jens Axboe --- arch/arch-aarch64.h | 77 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/arch/arch-aarch64.h b/arch/arch-aarch64.h index 94571709eb..951d1718cf 100644 --- a/arch/arch-aarch64.h +++ b/arch/arch-aarch64.h @@ -44,4 +44,81 @@ static inline int arch_init(char *envp[]) return 0; } +#define __do_syscallN(...) ({ \ + __asm__ volatile ( \ + "svc 0" \ + : "=r"(x0) \ + : __VA_ARGS__ \ + : "memory", "cc"); \ + (long) x0; \ +}) + +#define __do_syscall0(__n) ({ \ + register long x8 __asm__("x8") = __n; \ + register long x0 __asm__("x0"); \ + \ + __do_syscallN("r" (x8)); \ +}) + +#define __do_syscall1(__n, __a) ({ \ + register long x8 __asm__("x8") = __n; \ + register __typeof__(__a) x0 __asm__("x0") = __a; \ + \ + __do_syscallN("r" (x8), "0" (x0)); \ +}) + +#define __do_syscall2(__n, __a, __b) ({ \ + register long x8 __asm__("x8") = __n; \ + register __typeof__(__a) x0 __asm__("x0") = __a; \ + register __typeof__(__b) x1 __asm__("x1") = __b; \ + \ + __do_syscallN("r" (x8), "0" (x0), "r" (x1)); \ +}) + +#define __do_syscall3(__n, __a, __b, __c) ({ \ + register long x8 __asm__("x8") = __n; \ + register __typeof__(__a) x0 __asm__("x0") = __a; \ + register __typeof__(__b) x1 __asm__("x1") = __b; \ + register __typeof__(__c) x2 __asm__("x2") = __c; \ + \ + __do_syscallN("r" (x8), "0" (x0), "r" (x1), "r" (x2)); \ +}) + +#define __do_syscall4(__n, __a, __b, __c, __d) ({ \ + register long x8 __asm__("x8") = __n; \ + register __typeof__(__a) x0 __asm__("x0") = __a; \ + register __typeof__(__b) x1 __asm__("x1") = __b; \ + register __typeof__(__c) x2 __asm__("x2") = __c; \ + register __typeof__(__d) x3 __asm__("x3") = __d; \ + \ + __do_syscallN("r" (x8), "0" (x0), "r" (x1), "r" (x2), "r" (x3));\ +}) + +#define __do_syscall5(__n, __a, __b, __c, __d, __e) ({ \ + register long x8 __asm__("x8") = __n; \ + register __typeof__(__a) x0 __asm__("x0") = __a; \ + register __typeof__(__b) x1 __asm__("x1") = __b; \ + register __typeof__(__c) x2 __asm__("x2") = __c; \ + register __typeof__(__d) x3 __asm__("x3") = __d; \ + register __typeof__(__e) x4 __asm__("x4") = __e; \ + \ + __do_syscallN("r" (x8), "0" (x0), "r" (x1), "r" (x2), "r" (x3), \ + "r"(x4)); \ +}) + +#define __do_syscall6(__n, __a, __b, __c, __d, __e, __f) ({ \ + register long x8 __asm__("x8") = __n; \ + register __typeof__(__a) x0 __asm__("x0") = __a; \ + register __typeof__(__b) x1 __asm__("x1") = __b; \ + register __typeof__(__c) x2 __asm__("x2") = __c; \ + register __typeof__(__d) x3 __asm__("x3") = __d; \ + register __typeof__(__e) x4 __asm__("x4") = __e; \ + register __typeof__(__f) x5 __asm__("x5") = __f; \ + \ + __do_syscallN("r" (x8), "0" (x0), "r" (x1), "r" (x2), "r" (x3), \ + "r" (x4), "r"(x5)); \ +}) + +#define FIO_ARCH_HAS_SYSCALL + #endif From cc7ab24619ae25540d7d98df842fe64dd10cbe48 Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Mon, 21 Feb 2022 09:43:15 -0700 Subject: [PATCH 0108/1097] x86-64: add system call definitions Avoid a libc function call, just define our own syscall wrappers for this architecture. Lifted from liburing. Signed-off-by: Jens Axboe --- arch/arch-x86_64.h | 113 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) diff --git a/arch/arch-x86_64.h b/arch/arch-x86_64.h index 25850f90e7..86ce1b7ed7 100644 --- a/arch/arch-x86_64.h +++ b/arch/arch-x86_64.h @@ -68,4 +68,117 @@ static inline int arch_rand_seed(unsigned long *seed) return 0; } +#define __do_syscall0(NUM) ({ \ + intptr_t rax; \ + \ + __asm__ volatile( \ + "syscall" \ + : "=a"(rax) /* %rax */ \ + : "a"(NUM) /* %rax */ \ + : "rcx", "r11", "memory" \ + ); \ + rax; \ +}) + +#define __do_syscall1(NUM, ARG1) ({ \ + intptr_t rax; \ + \ + __asm__ volatile( \ + "syscall" \ + : "=a"(rax) /* %rax */ \ + : "a"((NUM)), /* %rax */ \ + "D"((ARG1)) /* %rdi */ \ + : "rcx", "r11", "memory" \ + ); \ + rax; \ +}) + +#define __do_syscall2(NUM, ARG1, ARG2) ({ \ + intptr_t rax; \ + \ + __asm__ volatile( \ + "syscall" \ + : "=a"(rax) /* %rax */ \ + : "a"((NUM)), /* %rax */ \ + "D"((ARG1)), /* %rdi */ \ + "S"((ARG2)) /* %rsi */ \ + : "rcx", "r11", "memory" \ + ); \ + rax; \ +}) + +#define __do_syscall3(NUM, ARG1, ARG2, ARG3) ({ \ + intptr_t rax; \ + \ + __asm__ volatile( \ + "syscall" \ + : "=a"(rax) /* %rax */ \ + : "a"((NUM)), /* %rax */ \ + "D"((ARG1)), /* %rdi */ \ + "S"((ARG2)), /* %rsi */ \ + "d"((ARG3)) /* %rdx */ \ + : "rcx", "r11", "memory" \ + ); \ + rax; \ +}) + +#define __do_syscall4(NUM, ARG1, ARG2, ARG3, ARG4) ({ \ + intptr_t rax; \ + register __typeof__(ARG4) __r10 __asm__("r10") = (ARG4); \ + \ + __asm__ volatile( \ + "syscall" \ + : "=a"(rax) /* %rax */ \ + : "a"((NUM)), /* %rax */ \ + "D"((ARG1)), /* %rdi */ \ + "S"((ARG2)), /* %rsi */ \ + "d"((ARG3)), /* %rdx */ \ + "r"(__r10) /* %r10 */ \ + : "rcx", "r11", "memory" \ + ); \ + rax; \ +}) + +#define __do_syscall5(NUM, ARG1, ARG2, ARG3, ARG4, ARG5) ({ \ + intptr_t rax; \ + register __typeof__(ARG4) __r10 __asm__("r10") = (ARG4); \ + register __typeof__(ARG5) __r8 __asm__("r8") = (ARG5); \ + \ + __asm__ volatile( \ + "syscall" \ + : "=a"(rax) /* %rax */ \ + : "a"((NUM)), /* %rax */ \ + "D"((ARG1)), /* %rdi */ \ + "S"((ARG2)), /* %rsi */ \ + "d"((ARG3)), /* %rdx */ \ + "r"(__r10), /* %r10 */ \ + "r"(__r8) /* %r8 */ \ + : "rcx", "r11", "memory" \ + ); \ + rax; \ +}) + +#define __do_syscall6(NUM, ARG1, ARG2, ARG3, ARG4, ARG5, ARG6) ({ \ + intptr_t rax; \ + register __typeof__(ARG4) __r10 __asm__("r10") = (ARG4); \ + register __typeof__(ARG5) __r8 __asm__("r8") = (ARG5); \ + register __typeof__(ARG6) __r9 __asm__("r9") = (ARG6); \ + \ + __asm__ volatile( \ + "syscall" \ + : "=a"(rax) /* %rax */ \ + : "a"((NUM)), /* %rax */ \ + "D"((ARG1)), /* %rdi */ \ + "S"((ARG2)), /* %rsi */ \ + "d"((ARG3)), /* %rdx */ \ + "r"(__r10), /* %r10 */ \ + "r"(__r8), /* %r8 */ \ + "r"(__r9) /* %r9 */ \ + : "rcx", "r11", "memory" \ + ); \ + rax; \ +}) + +#define FIO_ARCH_HAS_SYSCALL + #endif From c377f4f85943e5b155b3daaab1ce5213077531d8 Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Mon, 21 Feb 2022 09:43:48 -0700 Subject: [PATCH 0109/1097] io_uring: use syscall helpers for the hot path The only real hot system call here is the io_uring_enter(2) call, as that'll happen during the IO submission/completion parts. The rest are just setup function calls, we don't really care about those. Signed-off-by: Jens Axboe --- engines/io_uring.c | 5 +++++ t/io_uring.c | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/engines/io_uring.c b/engines/io_uring.c index a2533c88e6..1e15647ede 100644 --- a/engines/io_uring.c +++ b/engines/io_uring.c @@ -278,8 +278,13 @@ static struct fio_option options[] = { static int io_uring_enter(struct ioring_data *ld, unsigned int to_submit, unsigned int min_complete, unsigned int flags) { +#ifdef FIO_ARCH_HAS_SYSCALL + return __do_syscall6(__NR_io_uring_enter, ld->ring_fd, to_submit, + min_complete, flags, NULL, 0); +#else return syscall(__NR_io_uring_enter, ld->ring_fd, to_submit, min_complete, flags, NULL, 0); +#endif } static int fio_ioring_prep(struct thread_data *td, struct io_u *io_u) diff --git a/t/io_uring.c b/t/io_uring.c index f513d7dcd8..b8fcffe88c 100644 --- a/t/io_uring.c +++ b/t/io_uring.c @@ -422,8 +422,13 @@ static void io_uring_probe(int fd) static int io_uring_enter(struct submitter *s, unsigned int to_submit, unsigned int min_complete, unsigned int flags) { +#ifdef FIO_ARCH_HAS_SYSCALL + return __do_syscall6(__NR_io_uring_enter, s->ring_fd, to_submit, + min_complete, flags, NULL, 0); +#else return syscall(__NR_io_uring_enter, s->ring_fd, to_submit, min_complete, flags, NULL, 0); +#endif } #ifndef CONFIG_HAVE_GETTID From f54b69c35edf6c4f4bd4a1cf4509b314c298adb2 Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Thu, 24 Feb 2022 11:05:41 -0800 Subject: [PATCH 0110/1097] Fix three compiler warnings Fix three occurrences of the following clang compiler warning: warning: suggest braces around initialization of subobject [-Wmissing-braces] Signed-off-by: Bart Van Assche --- engines/cmdprio.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/engines/cmdprio.c b/engines/cmdprio.c index dd358754de..979a81b6c3 100644 --- a/engines/cmdprio.c +++ b/engines/cmdprio.c @@ -319,7 +319,7 @@ static int fio_cmdprio_gen_perc(struct thread_data *td, struct cmdprio *cmdprio) { struct cmdprio_options *options = cmdprio->options; struct cmdprio_prio *prio; - struct cmdprio_values values[CMDPRIO_RWDIR_CNT] = {0}; + struct cmdprio_values values[CMDPRIO_RWDIR_CNT] = {}; struct thread_stat *ts = &td->ts; enum fio_ddir ddir; int ret; @@ -368,8 +368,8 @@ static int fio_cmdprio_parse_and_gen_bssplit(struct thread_data *td, struct cmdprio *cmdprio) { struct cmdprio_options *options = cmdprio->options; - struct cmdprio_parse_result parse_res[CMDPRIO_RWDIR_CNT] = {0}; - struct cmdprio_values values[CMDPRIO_RWDIR_CNT] = {0}; + struct cmdprio_parse_result parse_res[CMDPRIO_RWDIR_CNT] = {}; + struct cmdprio_values values[CMDPRIO_RWDIR_CNT] = {}; struct thread_stat *ts = &td->ts; int ret, implicit_cmdprio; enum fio_ddir ddir; From 78c0d7a0120f7a117493811f6abf6c2a48a06453 Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Sat, 26 Feb 2022 10:42:01 -0700 Subject: [PATCH 0111/1097] Add TD_F_SYNCS thread flag It's not enough to just track writes, some operating systems require a file to be opened for write to issue a file sync. Which does kind of make sense... Add such a flag and set it for iolog/blktrace replay, if we see a sync in there. This does mean we need to bump the IO engine version, as the engine flags need to get shifted. Link: https://github.com/axboe/fio/issues/1352 Signed-off-by: Jens Axboe --- blktrace.c | 4 ++++ fio.h | 6 ++++-- ioengines.h | 2 +- iolog.c | 9 +++++++-- 4 files changed, 16 insertions(+), 5 deletions(-) diff --git a/blktrace.c b/blktrace.c index e180476589..ead6013047 100644 --- a/blktrace.c +++ b/blktrace.c @@ -297,6 +297,10 @@ static bool handle_trace_flush(struct thread_data *td, struct blk_io_trace *t, ios[DDIR_SYNC]++; dprint(FD_BLKTRACE, "store flush delay=%lu\n", ipo->delay); + + if (!(td->flags & TD_F_SYNCS)) + td->flags |= TD_F_SYNCS; + queue_io_piece(td, ipo); return true; } diff --git a/fio.h b/fio.h index 88df117de4..c314f0a844 100644 --- a/fio.h +++ b/fio.h @@ -97,6 +97,7 @@ enum { __TD_F_MMAP_KEEP, __TD_F_DIRS_CREATED, __TD_F_CHECK_RATE, + __TD_F_SYNCS, __TD_F_LAST, /* not a real bit, keep last */ }; @@ -118,6 +119,7 @@ enum { TD_F_MMAP_KEEP = 1U << __TD_F_MMAP_KEEP, TD_F_DIRS_CREATED = 1U << __TD_F_DIRS_CREATED, TD_F_CHECK_RATE = 1U << __TD_F_CHECK_RATE, + TD_F_SYNCS = 1U << __TD_F_SYNCS, }; enum { @@ -678,8 +680,8 @@ enum { TD_NR, }; -#define TD_ENG_FLAG_SHIFT 17 -#define TD_ENG_FLAG_MASK ((1U << 17) - 1) +#define TD_ENG_FLAG_SHIFT 18 +#define TD_ENG_FLAG_MASK ((1U << 18) - 1) static inline void td_set_ioengine_flags(struct thread_data *td) { diff --git a/ioengines.h b/ioengines.h index b3f755b477..acdb0071f5 100644 --- a/ioengines.h +++ b/ioengines.h @@ -8,7 +8,7 @@ #include "io_u.h" #include "zbd_types.h" -#define FIO_IOOPS_VERSION 30 +#define FIO_IOOPS_VERSION 31 #ifndef CONFIG_DYNAMIC_ENGINES #define FIO_STATIC static diff --git a/iolog.c b/iolog.c index a2cf0c1ccd..724ec1fe16 100644 --- a/iolog.c +++ b/iolog.c @@ -402,6 +402,7 @@ static bool read_iolog2(struct thread_data *td) enum fio_ddir rw; bool realloc = false; int64_t items_to_fetch = 0; + int syncs; if (td->o.read_iolog_chunked) { items_to_fetch = iolog_items_to_fetch(td); @@ -417,7 +418,7 @@ static bool read_iolog2(struct thread_data *td) rfname = fname = malloc(256+16); act = malloc(256+16); - reads = writes = waits = 0; + syncs = reads = writes = waits = 0; while ((p = fgets(str, 4096, td->io_log_rfile)) != NULL) { struct io_piece *ipo; int r; @@ -492,7 +493,9 @@ static bool read_iolog2(struct thread_data *td) continue; waits++; } else if (rw == DDIR_INVAL) { - } else if (!ddir_sync(rw)) { + } else if (ddir_sync(rw)) { + syncs++; + } else { log_err("bad ddir: %d\n", rw); continue; } @@ -547,6 +550,8 @@ static bool read_iolog2(struct thread_data *td) " read-only\n", td->o.name, writes); writes = 0; } + if (syncs) + td->flags |= TD_F_SYNCS; if (td->o.read_iolog_chunked) { if (td->io_log_current == 0) { From c3773c171dffb79f771d213d94249cefc4b9b6de Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Sat, 26 Feb 2022 10:43:20 -0700 Subject: [PATCH 0112/1097] windowsaio: open file for write if we have syncs Windows wants the file opened for write if we do a file sync, so ensure we do that if we have syncs. Fixes: https://github.com/axboe/fio/issues/1352 Signed-off-by: Jens Axboe --- engines/windowsaio.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engines/windowsaio.c b/engines/windowsaio.c index d82c805361..6681f8bbab 100644 --- a/engines/windowsaio.c +++ b/engines/windowsaio.c @@ -248,7 +248,7 @@ static int fio_windowsaio_open_file(struct thread_data *td, struct fio_file *f) log_err("fio: unknown fadvise type %d\n", td->o.fadvise_hint); } - if (!td_write(td) || read_only) + if ((!td_write(td) && !(td->flags & TD_F_SYNCS)) || read_only) access = GENERIC_READ; else access = (GENERIC_READ | GENERIC_WRITE); From 8538256c7e516d07784aaaba74ba3187a3611569 Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Mon, 7 Mar 2022 08:59:22 -0700 Subject: [PATCH 0113/1097] t/io_uring: change map buffers registration opcode Signed-off-by: Jens Axboe --- t/io_uring.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/t/io_uring.c b/t/io_uring.c index b8fcffe88c..8cf338584c 100644 --- a/t/io_uring.c +++ b/t/io_uring.c @@ -139,7 +139,7 @@ static float plist[] = { 1.0, 5.0, 10.0, 20.0, 30.0, 40.0, 50.0, 60.0, 70.0, static int plist_len = 17; #ifndef IORING_REGISTER_MAP_BUFFERS -#define IORING_REGISTER_MAP_BUFFERS 20 +#define IORING_REGISTER_MAP_BUFFERS 22 struct io_uring_map_buffers { __s32 fd; __u32 buf_start; From d49885aa4e24e8578d8dcdb7b48fb716e56d9ff2 Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Mon, 7 Mar 2022 09:00:14 -0700 Subject: [PATCH 0114/1097] t/io_uring: change fatal map buffers condition with multiple files It _may_ not work with multiple files/devices, but for most common cases it will. Just allow it for now with a warning. Signed-off-by: Jens Axboe --- t/io_uring.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/t/io_uring.c b/t/io_uring.c index 8cf338584c..ed405fa146 100644 --- a/t/io_uring.c +++ b/t/io_uring.c @@ -349,10 +349,8 @@ static int io_uring_map_buffers(struct submitter *s) if (do_nop) return 0; - if (s->nr_files > 1) { - fprintf(stderr, "Can't map buffers with multiple files\n"); - return -1; - } + if (s->nr_files > 1) + fprintf(stdout, "Mapping buffers may not work with multiple files\n"); return syscall(__NR_io_uring_register, s->ring_fd, IORING_REGISTER_MAP_BUFFERS, &map, 1); From 788f19d4047adcaf54fdd7cb1197865990a7350a Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Mon, 7 Mar 2022 09:01:49 -0700 Subject: [PATCH 0115/1097] io_uring.h: sync with 5.18 kernel bits Signed-off-by: Jens Axboe --- os/linux/io_uring.h | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/os/linux/io_uring.h b/os/linux/io_uring.h index c45b5e9a93..42b2fe84db 100644 --- a/os/linux/io_uring.h +++ b/os/linux/io_uring.h @@ -70,6 +70,7 @@ enum { IOSQE_IO_HARDLINK_BIT, IOSQE_ASYNC_BIT, IOSQE_BUFFER_SELECT_BIT, + IOSQE_CQE_SKIP_SUCCESS_BIT, }; /* @@ -87,6 +88,8 @@ enum { #define IOSQE_ASYNC (1U << IOSQE_ASYNC_BIT) /* select buffer from sqe->buf_group */ #define IOSQE_BUFFER_SELECT (1U << IOSQE_BUFFER_SELECT_BIT) +/* don't post CQE if request succeeded */ +#define IOSQE_CQE_SKIP_SUCCESS (1U << IOSQE_CQE_SKIP_SUCCESS_BIT) /* * io_uring_setup() flags @@ -254,10 +257,11 @@ struct io_cqring_offsets { /* * io_uring_enter(2) flags */ -#define IORING_ENTER_GETEVENTS (1U << 0) -#define IORING_ENTER_SQ_WAKEUP (1U << 1) -#define IORING_ENTER_SQ_WAIT (1U << 2) -#define IORING_ENTER_EXT_ARG (1U << 3) +#define IORING_ENTER_GETEVENTS (1U << 0) +#define IORING_ENTER_SQ_WAKEUP (1U << 1) +#define IORING_ENTER_SQ_WAIT (1U << 2) +#define IORING_ENTER_EXT_ARG (1U << 3) +#define IORING_ENTER_REGISTERED_RING (1U << 4) /* * Passed in for io_uring_setup(2). Copied back with updated info on success @@ -289,6 +293,7 @@ struct io_uring_params { #define IORING_FEAT_EXT_ARG (1U << 8) #define IORING_FEAT_NATIVE_WORKERS (1U << 9) #define IORING_FEAT_RSRC_TAGS (1U << 10) +#define IORING_FEAT_CQE_SKIP (1U << 11) /* * io_uring_register(2) opcodes and arguments @@ -321,6 +326,10 @@ enum { /* set/get max number of io-wq workers */ IORING_REGISTER_IOWQ_MAX_WORKERS = 19, + /* register/unregister io_uring fd with the ring */ + IORING_REGISTER_RING_FDS = 20, + IORING_UNREGISTER_RING_FDS = 21, + /* this goes last */ IORING_REGISTER_LAST }; From ca8c91c5455b434ad87ede8a4e766d2f859d8bd8 Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Mon, 7 Mar 2022 09:07:06 -0700 Subject: [PATCH 0116/1097] t/io_uring: add support for registering the ring fd Signed-off-by: Jens Axboe --- t/io_uring.c | 57 +++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 50 insertions(+), 7 deletions(-) diff --git a/t/io_uring.c b/t/io_uring.c index ed405fa146..b160f5d4a1 100644 --- a/t/io_uring.c +++ b/t/io_uring.c @@ -76,6 +76,7 @@ struct file { struct submitter { pthread_t thread; int ring_fd; + int enter_ring_fd; int index; struct io_sq_ring sq_ring; struct io_uring_sqe *sqes; @@ -127,6 +128,7 @@ static int stats = 0; /* generate IO stats */ static int aio = 0; /* use libaio */ static int runtime = 0; /* runtime */ static int random_io = 1; /* random or sequential IO */ +static int register_ring = 1; /* register ring */ static unsigned long tsc_rate; @@ -420,12 +422,14 @@ static void io_uring_probe(int fd) static int io_uring_enter(struct submitter *s, unsigned int to_submit, unsigned int min_complete, unsigned int flags) { + if (register_ring) + flags |= IORING_ENTER_REGISTERED_RING; #ifdef FIO_ARCH_HAS_SYSCALL - return __do_syscall6(__NR_io_uring_enter, s->ring_fd, to_submit, + return __do_syscall6(__NR_io_uring_enter, s->enter_ring_fd, to_submit, min_complete, flags, NULL, 0); #else - return syscall(__NR_io_uring_enter, s->ring_fd, to_submit, min_complete, - flags, NULL, 0); + return syscall(__NR_io_uring_enter, s->enter_ring_fd, to_submit, + min_complete, flags, NULL, 0); #endif } @@ -793,6 +797,34 @@ static void *submitter_aio_fn(void *data) } #endif +static void io_uring_unregister_ring(struct submitter *s) +{ + struct io_uring_rsrc_update up = { + .offset = s->enter_ring_fd, + }; + + syscall(__NR_io_uring_register, s->ring_fd, IORING_UNREGISTER_RING_FDS, + &up, 1); +} + +static int io_uring_register_ring(struct submitter *s) +{ + struct io_uring_rsrc_update up = { + .data = s->ring_fd, + .offset = -1U, + }; + int ret; + + ret = syscall(__NR_io_uring_register, s->ring_fd, + IORING_REGISTER_RING_FDS, &up, 1); + if (ret == 1) { + s->enter_ring_fd = up.offset; + return 0; + } + register_ring = 0; + return -1; +} + static void *submitter_uring_fn(void *data) { struct submitter *s = data; @@ -804,6 +836,9 @@ static void *submitter_uring_fn(void *data) submitter_init(s); #endif + if (register_ring) + io_uring_register_ring(s); + prepped = 0; do { int to_wait, to_submit, this_reap, to_prep; @@ -896,6 +931,9 @@ static void *submitter_uring_fn(void *data) } } while (!s->finish); + if (register_ring) + io_uring_unregister_ring(s); + finish = 1; return NULL; } @@ -998,7 +1036,7 @@ static int setup_ring(struct submitter *s) perror("io_uring_setup"); return 1; } - s->ring_fd = fd; + s->ring_fd = s->enter_ring_fd = fd; io_uring_probe(fd); @@ -1103,10 +1141,12 @@ static void usage(char *argv, int status) " -T : TSC rate in HZ\n" " -r : Runtime in seconds, default %s\n" " -R : Use random IO, default %d\n" - " -a : Use legacy aio, default %d\n", + " -a : Use legacy aio, default %d\n" + " -X : Use registered ring %d\n", argv, DEPTH, BATCH_SUBMIT, BATCH_COMPLETE, BS, polled, fixedbufs, dma_map, register_files, nthreads, !buffered, do_nop, - stats, runtime == 0 ? "unlimited" : runtime_str, random_io, aio); + stats, runtime == 0 ? "unlimited" : runtime_str, random_io, aio, + register_ring); exit(status); } @@ -1167,7 +1207,7 @@ int main(int argc, char *argv[]) if (!do_nop && argc < 2) usage(argv[0], 1); - while ((opt = getopt(argc, argv, "d:s:c:b:p:B:F:n:N:O:t:T:a:r:D:R:h?")) != -1) { + while ((opt = getopt(argc, argv, "d:s:c:b:p:B:F:n:N:O:t:T:a:r:D:R:X:h?")) != -1) { switch (opt) { case 'a': aio = !!atoi(optarg); @@ -1234,6 +1274,9 @@ int main(int argc, char *argv[]) case 'R': random_io = !!atoi(optarg); break; + case 'X': + register_ring = !!atoi(optarg); + break; case 'h': case '?': default: From 379406bc5802524fb5618b07a2f28dd746c271bf Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Mon, 7 Mar 2022 09:10:36 -0700 Subject: [PATCH 0117/1097] t/io_uring: support using preadv2 Just for comparison for sync workloads, similarly to how we have support for aio. Signed-off-by: Jens Axboe --- t/io_uring.c | 87 +++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 82 insertions(+), 5 deletions(-) diff --git a/t/io_uring.c b/t/io_uring.c index b160f5d4a1..5c8af903b3 100644 --- a/t/io_uring.c +++ b/t/io_uring.c @@ -129,6 +129,7 @@ static int aio = 0; /* use libaio */ static int runtime = 0; /* runtime */ static int random_io = 1; /* random or sequential IO */ static int register_ring = 1; /* register ring */ +static int use_sync = 0; /* use preadv2 */ static unsigned long tsc_rate; @@ -938,6 +939,72 @@ static void *submitter_uring_fn(void *data) return NULL; } +static void *submitter_sync_fn(void *data) +{ + struct submitter *s = data; + int ret; + + submitter_init(s); + + do { + uint64_t offset; + struct file *f; + long r; + + if (s->nr_files == 1) { + f = &s->files[0]; + } else { + f = &s->files[s->cur_file]; + if (f->pending_ios >= file_depth(s)) { + s->cur_file++; + if (s->cur_file == s->nr_files) + s->cur_file = 0; + f = &s->files[s->cur_file]; + } + } + f->pending_ios++; + + if (random_io) { + r = __rand64(&s->rand_state); + offset = (r % (f->max_blocks - 1)) * bs; + } else { + offset = f->cur_off; + f->cur_off += bs; + if (f->cur_off + bs > f->max_size) + f->cur_off = 0; + } + +#ifdef ARCH_HAVE_CPU_CLOCK + if (stats) + s->clock_batch[s->clock_index] = get_cpu_clock(); +#endif + + s->inflight++; + s->calls++; + + if (polled) + ret = preadv2(f->real_fd, &s->iovecs[0], 1, offset, RWF_HIPRI); + else + ret = preadv2(f->real_fd, &s->iovecs[0], 1, offset, 0); + + if (ret < 0) { + perror("preadv2"); + break; + } else if (ret != bs) { + break; + } + + s->done++; + s->inflight--; + f->pending_ios--; + if (stats) + add_stat(s, s->clock_index, 1); + } while (!s->finish); + + finish = 1; + return NULL; +} + static struct submitter *get_submitter(int offset) { void *ret; @@ -1142,11 +1209,12 @@ static void usage(char *argv, int status) " -r : Runtime in seconds, default %s\n" " -R : Use random IO, default %d\n" " -a : Use legacy aio, default %d\n" + " -S : Use sync IO (preadv2), default %d" " -X : Use registered ring %d\n", argv, DEPTH, BATCH_SUBMIT, BATCH_COMPLETE, BS, polled, fixedbufs, dma_map, register_files, nthreads, !buffered, do_nop, stats, runtime == 0 ? "unlimited" : runtime_str, random_io, aio, - register_ring); + use_sync, register_ring); exit(status); } @@ -1207,7 +1275,7 @@ int main(int argc, char *argv[]) if (!do_nop && argc < 2) usage(argv[0], 1); - while ((opt = getopt(argc, argv, "d:s:c:b:p:B:F:n:N:O:t:T:a:r:D:R:X:h?")) != -1) { + while ((opt = getopt(argc, argv, "d:s:c:b:p:B:F:n:N:O:t:T:a:r:D:R:X:S:h?")) != -1) { switch (opt) { case 'a': aio = !!atoi(optarg); @@ -1277,6 +1345,9 @@ int main(int argc, char *argv[]) case 'X': register_ring = !!atoi(optarg); break; + case 'S': + use_sync = !!atoi(optarg); + break; case 'h': case '?': default: @@ -1387,7 +1458,9 @@ int main(int argc, char *argv[]) for (j = 0; j < nthreads; j++) { s = get_submitter(j); - if (!aio) + if (use_sync) + continue; + else if (!aio) err = setup_ring(s); else err = setup_aio(s); @@ -1398,14 +1471,18 @@ int main(int argc, char *argv[]) } s = get_submitter(0); printf("polled=%d, fixedbufs=%d/%d, register_files=%d, buffered=%d, QD=%d\n", polled, fixedbufs, dma_map, register_files, buffered, depth); - if (!aio) + if (use_sync) + printf("Engine=preadv2\n"); + else if (!aio) printf("Engine=io_uring, sq_ring=%d, cq_ring=%d\n", *s->sq_ring.ring_entries, *s->cq_ring.ring_entries); else printf("Engine=aio\n"); for (j = 0; j < nthreads; j++) { s = get_submitter(j); - if (!aio) + if (use_sync) + pthread_create(&s->thread, NULL, submitter_sync_fn, s); + else if (!aio) pthread_create(&s->thread, NULL, submitter_uring_fn, s); #ifdef CONFIG_LIBAIO else From 3be2f0ca0cb19a96cf6c75999dee6d0b9f737182 Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Mon, 7 Mar 2022 09:11:40 -0700 Subject: [PATCH 0118/1097] t/io_uring: add missing CR Signed-off-by: Jens Axboe --- t/io_uring.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/t/io_uring.c b/t/io_uring.c index 5c8af903b3..157eea9e51 100644 --- a/t/io_uring.c +++ b/t/io_uring.c @@ -1209,7 +1209,7 @@ static void usage(char *argv, int status) " -r : Runtime in seconds, default %s\n" " -R : Use random IO, default %d\n" " -a : Use legacy aio, default %d\n" - " -S : Use sync IO (preadv2), default %d" + " -S : Use sync IO (preadv2), default %d\n" " -X : Use registered ring %d\n", argv, DEPTH, BATCH_SUBMIT, BATCH_COMPLETE, BS, polled, fixedbufs, dma_map, register_files, nthreads, !buffered, do_nop, From dc44588f2e445edd7a4ca7dc9bf05bb3b4b2789e Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Mon, 7 Mar 2022 09:16:39 -0700 Subject: [PATCH 0119/1097] Makefile: get rid of fortify source Haven't seen anything useful come out of it. Signed-off-by: Jens Axboe --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 0ab4f82c32..6ffd3d1307 100644 --- a/Makefile +++ b/Makefile @@ -28,7 +28,7 @@ PROGS = fio SCRIPTS = $(addprefix $(SRCDIR)/,tools/fio_generate_plots tools/plot/fio2gnuplot tools/genfio tools/fiologparser.py tools/hist/fiologparser_hist.py tools/hist/fio-histo-log-pctiles.py tools/fio_jsonplus_clat2csv) ifndef CONFIG_FIO_NO_OPT - FIO_CFLAGS += -O3 -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=2 + FIO_CFLAGS += -O3 endif ifdef CONFIG_BUILD_NATIVE FIO_CFLAGS += -march=native From 9ed87094fb97011626d274e96172c398f4841b9d Mon Sep 17 00:00:00 2001 From: Denis Pronin Date: Tue, 8 Mar 2022 20:34:25 +0300 Subject: [PATCH 0120/1097] - fixed typo in configure script Signed-off-by: Denis Pronin --- configure | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure b/configure index be4605f9ad..67e5d53557 100755 --- a/configure +++ b/configure @@ -2098,7 +2098,7 @@ if test "$libhdfs" = "yes" ; then hdfs_conf_error=1 fi if test "$FIO_LIBHDFS_INCLUDE" = "" ; then - echo "configure: FIO_LIBHDFS_INCLUDE should be defined to libhdfs inlude path" + echo "configure: FIO_LIBHDFS_INCLUDE should be defined to libhdfs include path" hdfs_conf_error=1 fi if test "$FIO_LIBHDFS_LIB" = "" ; then From 593737d9d772d9f49ebf0cf26f869c073ebc4b6e Mon Sep 17 00:00:00 2001 From: Denis Pronin Date: Wed, 9 Mar 2022 13:03:36 +0300 Subject: [PATCH 0121/1097] - freeing job_sections array of strings upon freeing each its item in init.c Signed-off-by: Denis Pronin --- init.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/init.c b/init.c index 81c30f8c54..b7f866e659 100644 --- a/init.c +++ b/init.c @@ -2185,6 +2185,10 @@ static int __parse_jobs_ini(struct thread_data *td, i++; } + free(job_sections); + job_sections = NULL; + nr_job_sections = 0; + free(opts); out: free(string); From ce27f93caad960a93a00a75e989b040fac613bdb Mon Sep 17 00:00:00 2001 From: Denis Pronin Date: Wed, 9 Mar 2022 14:53:43 +0300 Subject: [PATCH 0122/1097] - fixed memory leak, which is happening when parsing options, claimed by ASAN Signed-off-by: Denis Pronin --- parse.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/parse.c b/parse.c index d086ee488f..e0bee0049b 100644 --- a/parse.c +++ b/parse.c @@ -817,6 +817,8 @@ static int __handle_option(const struct fio_option *o, const char *ptr, if (o->off1) { cp = td_var(data, o, o->off1); + if (*cp) + free(*cp); *cp = strdup(ptr); if (strlen(ptr) > o->maxlen - 1) { log_err("value exceeds max length of %d\n", From 36aac34e8db7cd58bb2dc522875285c46c732440 Mon Sep 17 00:00:00 2001 From: Denis Pronin Date: Wed, 9 Mar 2022 15:37:01 +0300 Subject: [PATCH 0123/1097] - fixed memory leak in parent process detected by ASAN when forking and not freeing memory in the parent process allocated for fork_data Signed-off-by: Denis Pronin --- backend.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/backend.c b/backend.c index a21dfef637..cd7f4e5f62 100644 --- a/backend.c +++ b/backend.c @@ -2441,6 +2441,8 @@ static void run_threads(struct sk_out *sk_out) _exit(ret); } else if (i == fio_debug_jobno) *fio_debug_jobp = pid; + free(fd); + fd = NULL; } dprint(FD_MUTEX, "wait on startup_sem\n"); if (fio_sem_down_timeout(startup_sem, 10000)) { From 66634ad8d8e5fddbea9ff8ec2dda5649cd9a3f95 Mon Sep 17 00:00:00 2001 From: Denis Pronin Date: Thu, 10 Mar 2022 13:16:41 +0300 Subject: [PATCH 0124/1097] configure script refactoring tabs are replaced by whitespaces in help page Signed-off-by: Denis Pronin --- configure | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/configure b/configure index 67e5d53557..65aee715ff 100755 --- a/configure +++ b/configure @@ -290,9 +290,9 @@ if test "$show_help" = "yes" ; then echo "--enable-libiscsi Enable iscsi support" echo "--enable-libnbd Enable libnbd (NBD engine) support" echo "--disable-libzbc Disable libzbc even if found" - echo "--disable-tcmalloc Disable tcmalloc support" - echo "--dynamic-libengines Lib-based ioengines as dynamic libraries" - echo "--disable-dfs Disable DAOS File System support even if found" + echo "--disable-tcmalloc Disable tcmalloc support" + echo "--dynamic-libengines Lib-based ioengines as dynamic libraries" + echo "--disable-dfs Disable DAOS File System support even if found" exit $exit_val fi From 5d7de92eaff9ac2514e5992320c273c82d5d282d Mon Sep 17 00:00:00 2001 From: Denis Pronin Date: Thu, 10 Mar 2022 13:34:53 +0300 Subject: [PATCH 0125/1097] improvements in dup_files function cleared allocation of td->files when duplicating files, call 'assert', before iterating over original thread_data, that prevents possible segmentation fault when duplicating files Signed-off-by: Denis Pronin --- filesetup.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/filesetup.c b/filesetup.c index 7c32d0af43..ab6c488bb7 100644 --- a/filesetup.c +++ b/filesetup.c @@ -2031,11 +2031,12 @@ void dup_files(struct thread_data *td, struct thread_data *org) if (!org->files) return; - td->files = malloc(org->files_index * sizeof(f)); + td->files = calloc(org->files_index, sizeof(f)); if (td->o.file_lock_mode != FILE_LOCK_NONE) td->file_locks = malloc(org->files_index); + assert(org->files_index >= org->o.nr_files); for_each_file(org, f, i) { struct fio_file *__f; From 807473c36e106855765579b59d5258d34fa4e58b Mon Sep 17 00:00:00 2001 From: Denis Pronin Date: Wed, 9 Mar 2022 19:41:45 +0300 Subject: [PATCH 0126/1097] fixed memory leak detected by ASAN release memory occupied for td->files for each thread_data in the parent process Signed-off-by: Denis Pronin --- backend.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/backend.c b/backend.c index cd7f4e5f62..001b2b9647 100644 --- a/backend.c +++ b/backend.c @@ -2432,7 +2432,10 @@ static void run_threads(struct sk_out *sk_out) strerror(ret)); } else { pid_t pid; + struct fio_file **files; dprint(FD_PROCESS, "will fork\n"); + files = td->files; + read_barrier(); pid = fork(); if (!pid) { int ret; @@ -2441,6 +2444,9 @@ static void run_threads(struct sk_out *sk_out) _exit(ret); } else if (i == fio_debug_jobno) *fio_debug_jobp = pid; + // freeing previously allocated memory for files + // this memory freed MUST NOT be shared between processes, only the pointer itself may be shared within TD + free(files); free(fd); fd = NULL; } From 39c83923fffaf8746ed290c3bfb7e0cbca6ab689 Mon Sep 17 00:00:00 2001 From: Denis Pronin Date: Thu, 10 Mar 2022 14:15:40 +0300 Subject: [PATCH 0127/1097] ASAN enabling when configuring introduced opportunity for a user to enable ASAN for the compiler when calling 'configure' script using '--enable-asan' option Signed-off-by: Denis Pronin --- configure | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/configure b/configure index 67e5d53557..6aa6ac9a24 100755 --- a/configure +++ b/configure @@ -248,6 +248,8 @@ for opt do ;; --disable-dfs) dfs="no" ;; + --enable-asan) asan="yes" + ;; --help) show_help="yes" ;; @@ -293,6 +295,7 @@ if test "$show_help" = "yes" ; then echo "--disable-tcmalloc Disable tcmalloc support" echo "--dynamic-libengines Lib-based ioengines as dynamic libraries" echo "--disable-dfs Disable DAOS File System support even if found" + echo "--enable-asan Enable address sanitizer" exit $exit_val fi @@ -3196,7 +3199,10 @@ fi if test "$fcntl_sync" = "yes" ; then output_sym "CONFIG_FCNTL_SYNC" fi - +if test "$asan" = "yes"; then + CFLAGS="$CFLAGS -fsanitize=address" + LDFLAGS="$LDFLAGS -fsanitize=address" +fi print_config "Lib-based ioengines dynamic" "$dynamic_engines" cat > $TMPC << EOF int main(int argc, char **argv) From a77e8ab428a842327af069083ee96cba73caf1d8 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Thu, 10 Mar 2022 19:45:46 -0500 Subject: [PATCH 0128/1097] fuzz: avoid building t/fuzz/parse_ini by default With a vanilla build t/fuzz/parse_ini will segfault because the symbol FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION is not defined. If the symbol FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION is defined, fio won't tear down shared memory on program termination. Not tearing down shared memory is necessary for t/fuzz/parse_ini to work correctly. Don't build t/fuzz/parse_ini unless CFLAGS contains -DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION. Signed-off-by: Vincent Fu --- Makefile | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 6ffd3d1307..e670c1f202 100644 --- a/Makefile +++ b/Makefile @@ -385,14 +385,16 @@ T_MEMLOCK_PROGS = t/memlock T_TT_OBJS = t/time-test.o T_TT_PROGS = t/time-test +ifneq (,$(findstring -DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION,$(CFLAGS))) T_FUZZ_OBJS = t/fuzz/fuzz_parseini.o T_FUZZ_OBJS += $(OBJS) ifdef CONFIG_ARITHMETIC T_FUZZ_OBJS += lex.yy.o y.tab.o endif +# For proper fio code teardown CFLAGS needs to include -DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION # in case there is no fuzz driver defined by environment variable LIB_FUZZING_ENGINE, use a simple one # For instance, with compiler clang, address sanitizer and libFuzzer as a fuzzing engine, you should define -# export CFLAGS="-fsanitize=address,fuzzer-no-link" +# export CFLAGS="-fsanitize=address,fuzzer-no-link -DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION" # export LIB_FUZZING_ENGINE="-fsanitize=address" # export CC=clang # before running configure && make @@ -401,6 +403,10 @@ ifndef LIB_FUZZING_ENGINE T_FUZZ_OBJS += t/fuzz/onefile.o endif T_FUZZ_PROGS = t/fuzz/fuzz_parseini +else # CFLAGS includes -DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION +T_FUZZ_OBJS = +T_FUZZ_PROGS = +endif T_OBJS = $(T_SMALLOC_OBJS) T_OBJS += $(T_IEEE_OBJS) From a7648136bd2ee4cdb38489c62016f5cd86d9fce7 Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Thu, 10 Mar 2022 19:09:56 -0700 Subject: [PATCH 0129/1097] t/io_uring: only enable sync if we have preadv2 Signed-off-by: Jens Axboe --- t/io_uring.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/t/io_uring.c b/t/io_uring.c index 157eea9e51..100359129c 100644 --- a/t/io_uring.c +++ b/t/io_uring.c @@ -939,6 +939,7 @@ static void *submitter_uring_fn(void *data) return NULL; } +#ifdef CONFIG_PWRITEV2 static void *submitter_sync_fn(void *data) { struct submitter *s = data; @@ -1004,6 +1005,13 @@ static void *submitter_sync_fn(void *data) finish = 1; return NULL; } +#else +static void *submitter_sync_fn(void *data) +{ + finish = 1; + return NULL; +} +#endif static struct submitter *get_submitter(int offset) { @@ -1346,7 +1354,12 @@ int main(int argc, char *argv[]) register_ring = !!atoi(optarg); break; case 'S': +#ifdef CONFIG_PWRITEV2 use_sync = !!atoi(optarg); +#else + fprintf(stderr, "preadv2 not supported\n"); + exit(1); +#endif break; case 'h': case '?': From 16b1e24562347d371d6d62e0bb9a03ad4e2a8a96 Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Fri, 11 Mar 2022 05:09:20 -0700 Subject: [PATCH 0130/1097] t/dedupe: handle errors more gracefully Don't assert for a deflate error, properly check for it and pass it back up the stack so we can abort the thread. Signed-off-by: Jens Axboe --- t/dedupe.c | 57 +++++++++++++++++++++++++++++++++--------------------- 1 file changed, 35 insertions(+), 22 deletions(-) diff --git a/t/dedupe.c b/t/dedupe.c index 109ea1af49..561aa08d3a 100644 --- a/t/dedupe.c +++ b/t/dedupe.c @@ -143,15 +143,15 @@ static int read_block(int fd, void *buf, off_t offset) return __read_block(fd, buf, offset, blocksize); } -static void account_unique_capacity(uint64_t offset, uint64_t *unique_capacity, - struct zlib_ctrl *zc) +static int account_unique_capacity(uint64_t offset, uint64_t *unique_capacity, + struct zlib_ctrl *zc) { z_stream *stream = &zc->stream; unsigned int compressed_len; int ret; if (read_block(file.fd, zc->buf_in, offset)) - return; + return 1; stream->next_in = zc->buf_in; stream->avail_in = blocksize; @@ -159,7 +159,8 @@ static void account_unique_capacity(uint64_t offset, uint64_t *unique_capacity, stream->next_out = zc->buf_out; ret = deflate(stream, Z_FINISH); - assert(ret != Z_STREAM_ERROR); + if (ret == Z_STREAM_ERROR) + return 1; compressed_len = blocksize - stream->avail_out; if (dump_output) @@ -169,6 +170,7 @@ static void account_unique_capacity(uint64_t offset, uint64_t *unique_capacity, *unique_capacity += compressed_len; deflateReset(stream); + return 0; } static void add_item(struct chunk *c, struct item *i) @@ -225,12 +227,12 @@ static struct chunk *alloc_chunk(void) return c; } -static void insert_chunk(struct item *i, uint64_t *unique_capacity, - struct zlib_ctrl *zc) +static int insert_chunk(struct item *i, uint64_t *unique_capacity, + struct zlib_ctrl *zc) { struct fio_rb_node **p, *parent; struct chunk *c; - int diff; + int ret, diff; p = &rb_root.rb_node; parent = NULL; @@ -244,8 +246,6 @@ static void insert_chunk(struct item *i, uint64_t *unique_capacity, } else if (diff > 0) { p = &(*p)->rb_right; } else { - int ret; - if (!collision_check) goto add; @@ -266,17 +266,21 @@ static void insert_chunk(struct item *i, uint64_t *unique_capacity, memcpy(c->hash, i->hash, sizeof(i->hash)); rb_link_node(&c->rb_node, parent, p); rb_insert_color(&c->rb_node, &rb_root); - if (compression) - account_unique_capacity(i->offset, unique_capacity, zc); + if (compression) { + ret = account_unique_capacity(i->offset, unique_capacity, zc); + if (ret) + return ret; + } add: add_item(c, i); + return 0; } -static void insert_chunks(struct item *items, unsigned int nitems, - uint64_t *ndupes, uint64_t *unique_capacity, - struct zlib_ctrl *zc) +static int insert_chunks(struct item *items, unsigned int nitems, + uint64_t *ndupes, uint64_t *unique_capacity, + struct zlib_ctrl *zc) { - int i; + int i, ret; fio_sem_down(rb_lock); @@ -288,11 +292,15 @@ static void insert_chunks(struct item *items, unsigned int nitems, s = sizeof(items[i].hash) / sizeof(uint32_t); r = bloom_set(bloom, items[i].hash, s); *ndupes += r; - } else - insert_chunk(&items[i], unique_capacity, zc); + } else { + ret = insert_chunk(&items[i], unique_capacity, zc); + if (ret) + break; + } } fio_sem_up(rb_lock); + return ret; } static void crc_buf(void *buf, uint32_t *hash) @@ -320,6 +328,7 @@ static int do_work(struct worker_thread *thread, void *buf) uint64_t ndupes = 0; uint64_t unique_capacity = 0; struct item *items; + int ret; offset = thread->cur_offset; @@ -339,13 +348,17 @@ static int do_work(struct worker_thread *thread, void *buf) nitems++; } - insert_chunks(items, nitems, &ndupes, &unique_capacity, &thread->zc); + ret = insert_chunks(items, nitems, &ndupes, &unique_capacity, &thread->zc); free(items); - thread->items += nitems; - thread->dupes += ndupes; - thread->unique_capacity += unique_capacity; - return 0; + if (!ret) { + thread->items += nitems; + thread->dupes += ndupes; + thread->unique_capacity += unique_capacity; + return 0; + } + + return ret; } static void thread_init_zlib_control(struct worker_thread *thread) From 1fe261a24794f60bf374cd1852e09ec56997a20a Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Fri, 11 Mar 2022 06:15:53 -0700 Subject: [PATCH 0131/1097] t/dedupe: ensure that 'ret' is initialized Fixes: 16b1e2456234 ("t/dedupe: handle errors more gracefully") Signed-off-by: Jens Axboe --- t/dedupe.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/t/dedupe.c b/t/dedupe.c index 561aa08d3a..d21e96f4d9 100644 --- a/t/dedupe.c +++ b/t/dedupe.c @@ -280,7 +280,7 @@ static int insert_chunks(struct item *items, unsigned int nitems, uint64_t *ndupes, uint64_t *unique_capacity, struct zlib_ctrl *zc) { - int i, ret; + int i, ret = 0; fio_sem_down(rb_lock); From cf1f424af7d098e8dce9064c8b0faef0aae9cb34 Mon Sep 17 00:00:00 2001 From: Alberto Faria Date: Tue, 15 Mar 2022 21:06:39 +0000 Subject: [PATCH 0132/1097] Properly encode engine flags in thread_data::flags We have 16 engine flags and an 18-bit shift, so cast engine flags to unsigned long long before shifting to avoid dropping FIO_ASYNCIO_SYNC_TRIM and FIO_NO_OFFLOAD. Also make thread_data::flags unsigned long long to ensure it fits all flags even when longs are 32 bit, and fix TD_ENG_FLAG_MASK. Signed-off-by: Alberto Faria --- fio.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/fio.h b/fio.h index c314f0a844..776fb51f74 100644 --- a/fio.h +++ b/fio.h @@ -184,7 +184,7 @@ struct zone_split_index { */ struct thread_data { struct flist_head opt_list; - unsigned long flags; + unsigned long long flags; struct thread_options o; void *eo; pthread_t thread; @@ -681,12 +681,12 @@ enum { }; #define TD_ENG_FLAG_SHIFT 18 -#define TD_ENG_FLAG_MASK ((1U << 18) - 1) +#define TD_ENG_FLAG_MASK ((1ULL << 18) - 1) static inline void td_set_ioengine_flags(struct thread_data *td) { td->flags = (~(TD_ENG_FLAG_MASK << TD_ENG_FLAG_SHIFT) & td->flags) | - (td->io_ops->flags << TD_ENG_FLAG_SHIFT); + ((unsigned long long)td->io_ops->flags << TD_ENG_FLAG_SHIFT); } static inline bool td_ioengine_flagged(struct thread_data *td, From cef0a8357b3fef9b12f3922cf84e7c723a1c3b37 Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Sun, 20 Mar 2022 07:22:33 -0600 Subject: [PATCH 0133/1097] engines/null: update external engine compilation Everything needs to include config-host.h, and make sure that the C++ side uses the right type for the queue op. Fixes: https://github.com/axboe/fio/issues/1371 Signed-off-by: Jens Axboe --- engines/null.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/engines/null.c b/engines/null.c index 4cc0102b99..409b2b822d 100644 --- a/engines/null.c +++ b/engines/null.c @@ -6,7 +6,8 @@ * * It also can act as external C++ engine - compiled with: * - * g++ -O2 -g -shared -rdynamic -fPIC -o cpp_null null.c -DFIO_EXTERNAL_ENGINE + * g++ -O2 -g -shared -rdynamic -fPIC -o cpp_null null.c \ + * -i ../config-host.h -DFIO_EXTERNAL_ENGINE * * to test it execute: * @@ -201,7 +202,7 @@ struct NullData { return null_commit(td, impl_); } - int fio_null_queue(struct thread_data *td, struct io_u *io_u) + fio_q_status fio_null_queue(struct thread_data *td, struct io_u *io_u) { return null_queue(td, impl_, io_u); } @@ -233,7 +234,7 @@ static int fio_null_commit(struct thread_data *td) return NullData::get(td)->fio_null_commit(td); } -static int fio_null_queue(struct thread_data *td, struct io_u *io_u) +static fio_q_status fio_null_queue(struct thread_data *td, struct io_u *io_u) { return NullData::get(td)->fio_null_queue(td, io_u); } From 64b23fb1b351e9a3ea2c61b0a10e4a462b11297d Mon Sep 17 00:00:00 2001 From: Jonathon Carter <30672425+jnoc@users.noreply.github.com> Date: Sun, 20 Mar 2022 02:56:50 +0000 Subject: [PATCH 0134/1097] Added citation.cff for easy APA/BibTeX citation directly from the Github repository Signed-off-by: Jonathon Carter --- CITATION.cff | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 CITATION.cff diff --git a/CITATION.cff b/CITATION.cff new file mode 100644 index 0000000000..3df315e5bc --- /dev/null +++ b/CITATION.cff @@ -0,0 +1,11 @@ +cff-version: 1.2.0 +preferred-citation: + type: software + authors: + - family-names: "Axboe" + given-names: "Jens" + email: axboe@kernel.dk + title: "Flexible I/O Tester" + year: 2022 + url: "https://github.com/axboe/fio" +licence: GNU GPL v2.0 From c822572d68e326384ce179b9484de0e4abf3d514 Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Sun, 20 Mar 2022 09:31:20 -0600 Subject: [PATCH 0135/1097] engines/null: use correct -include Fixes: cef0a8357b3f ("engines/null: update external engine compilation") Signed-off-by: Jens Axboe --- engines/null.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engines/null.c b/engines/null.c index 409b2b822d..8dcd1b21cf 100644 --- a/engines/null.c +++ b/engines/null.c @@ -7,7 +7,7 @@ * It also can act as external C++ engine - compiled with: * * g++ -O2 -g -shared -rdynamic -fPIC -o cpp_null null.c \ - * -i ../config-host.h -DFIO_EXTERNAL_ENGINE + * -include ../config-host.h -DFIO_EXTERNAL_ENGINE * * to test it execute: * From 46ec82705a9775bbe4abd2dec0b531810887b9f2 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Wed, 23 Mar 2022 18:22:37 -0400 Subject: [PATCH 0136/1097] io_u: produce bad offsets for some time_based jobs Allow get_next_seq_offset to produce bad offsets for time_based jobs when fio is accessing more than one file. Otherwise fio will not skip to the next file once the current one is done. Fixes: https://github.com/axboe/fio/issues/1372 Signed-off-by: Vincent Fu --- io_u.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/io_u.c b/io_u.c index 806ceb7776..50197a4bfa 100644 --- a/io_u.c +++ b/io_u.c @@ -355,7 +355,7 @@ static int get_next_seq_offset(struct thread_data *td, struct fio_file *f, * and invalidate the cache, if we need to. */ if (f->last_pos[ddir] >= f->io_size + get_start_offset(td, f) && - o->time_based) { + o->time_based && o->nr_files == 1) { f->last_pos[ddir] = f->file_offset; loop_cache_invalidate(td, f); } From 31e65735b99dcc7288fe2ede9dc647f53fe8d6b2 Mon Sep 17 00:00:00 2001 From: Chung-Chiang Cheng Date: Mon, 28 Mar 2022 14:47:25 +0800 Subject: [PATCH 0137/1097] Fix compile error of GCC 4 gcc-4.9.3 doesn't recognize __has_attribute(__fallthrough__) and reports the following error. CC crc/crc32c-arm64.o In file included from crc/../os/../file.h:5:0, from crc/../os/os-linux.h:32, from crc/../os/os.h:39, from crc/crc32c-arm64.c:2: crc/../os/../compiler/compiler.h:74:20: error: missing binary operator before token "(" Makefile:501: recipe for target 'crc/crc32c-arm64.o' failed make: *** [crc/crc32c-arm64.o] Error 1 Signed-off-by: Chung-Chiang Cheng --- compiler/compiler.h | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler/compiler.h b/compiler/compiler.h index 44fa87b90c..3fd0822f3b 100644 --- a/compiler/compiler.h +++ b/compiler/compiler.h @@ -67,6 +67,7 @@ #endif #ifndef __has_attribute +#define __has_attribute(x) __GCC4_has_attribute_##x #define __GCC4_has_attribute___fallthrough__ 0 #endif From 61850e56907c0d3d41be27bd59f573984c8cf00d Mon Sep 17 00:00:00 2001 From: Kozlowski Mateusz Date: Tue, 29 Mar 2022 11:27:03 +0200 Subject: [PATCH 0138/1097] Handle finished jobs when using status-interval stat: When printing job stats with status-interval, don't keep adding values to the total runtime if the jobs are already finished. This should fix the printing of the intermediate runtime/average BW etc. Signed-off-by: Kozlowski Mateusz --- stat.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/stat.c b/stat.c index 7947edb42f..356083e25f 100644 --- a/stat.c +++ b/stat.c @@ -2731,6 +2731,9 @@ int __show_running_run_stats(void) fio_gettime(&ts, NULL); for_each_td(td, i) { + if (td->runstate >= TD_EXITED) + continue; + td->update_rusage = 1; for_each_rw_ddir(ddir) { td->ts.io_bytes[ddir] = td->io_bytes[ddir]; @@ -2759,6 +2762,9 @@ int __show_running_run_stats(void) __show_run_stats(); for_each_td(td, i) { + if (td->runstate >= TD_EXITED) + continue; + if (td_read(td) && td->ts.io_bytes[DDIR_READ]) td->ts.runtime[DDIR_READ] -= rt[i]; if (td_write(td) && td->ts.io_bytes[DDIR_WRITE]) From 87933e32e356b15b85c6d9775d5e840994080a4f Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Wed, 30 Mar 2022 17:31:36 -0600 Subject: [PATCH 0139/1097] Rename 'fallthrough' attribute to 'fio_fallthrough' fallthrough is reserved in C++, so this causes issues with C++ programs pulling in the fio.h -> compiler.h header. Rename it to something fio specific instead. Signed-off-by: Jens Axboe --- compiler/compiler.h | 4 ++-- crc/murmur3.c | 4 ++-- engines/http.c | 2 +- hash.h | 24 ++++++++++++------------ init.c | 2 +- io_u.c | 10 +++++----- lib/lfsr.c | 32 ++++++++++++++++---------------- parse.c | 4 ++-- t/lfsr-test.c | 6 +++--- 9 files changed, 44 insertions(+), 44 deletions(-) diff --git a/compiler/compiler.h b/compiler/compiler.h index 3fd0822f3b..fefadeaa89 100644 --- a/compiler/compiler.h +++ b/compiler/compiler.h @@ -72,9 +72,9 @@ #endif #if __has_attribute(__fallthrough__) -#define fallthrough __attribute__((__fallthrough__)) +#define fio_fallthrough __attribute__((__fallthrough__)) #else -#define fallthrough do {} while (0) /* fallthrough */ +#define fio_fallthrough do {} while (0) /* fallthrough */ #endif #endif diff --git a/crc/murmur3.c b/crc/murmur3.c index ba408a9e80..08660bc8cb 100644 --- a/crc/murmur3.c +++ b/crc/murmur3.c @@ -30,10 +30,10 @@ static uint32_t murmur3_tail(const uint8_t *data, const int nblocks, switch (len & 3) { case 3: k1 ^= tail[2] << 16; - fallthrough; + fio_fallthrough; case 2: k1 ^= tail[1] << 8; - fallthrough; + fio_fallthrough; case 1: k1 ^= tail[0]; k1 *= c1; diff --git a/engines/http.c b/engines/http.c index 57d4967def..696febe151 100644 --- a/engines/http.c +++ b/engines/http.c @@ -297,7 +297,7 @@ static int _curl_trace(CURL *handle, curl_infotype type, switch (type) { case CURLINFO_TEXT: fprintf(stderr, "== Info: %s", data); - fallthrough; + fio_fallthrough; default: case CURLINFO_SSL_DATA_OUT: case CURLINFO_SSL_DATA_IN: diff --git a/hash.h b/hash.h index 2c04bc2969..f7596a5636 100644 --- a/hash.h +++ b/hash.h @@ -142,20 +142,20 @@ static inline uint32_t jhash(const void *key, uint32_t length, uint32_t initval) /* Last block: affect all 32 bits of (c) */ /* All the case statements fall through */ switch (length) { - case 12: c += (uint32_t) k[11] << 24; fallthrough; - case 11: c += (uint32_t) k[10] << 16; fallthrough; - case 10: c += (uint32_t) k[9] << 8; fallthrough; - case 9: c += k[8]; fallthrough; - case 8: b += (uint32_t) k[7] << 24; fallthrough; - case 7: b += (uint32_t) k[6] << 16; fallthrough; - case 6: b += (uint32_t) k[5] << 8; fallthrough; - case 5: b += k[4]; fallthrough; - case 4: a += (uint32_t) k[3] << 24; fallthrough; - case 3: a += (uint32_t) k[2] << 16; fallthrough; - case 2: a += (uint32_t) k[1] << 8; fallthrough; + case 12: c += (uint32_t) k[11] << 24; fio_fallthrough; + case 11: c += (uint32_t) k[10] << 16; fio_fallthrough; + case 10: c += (uint32_t) k[9] << 8; fio_fallthrough; + case 9: c += k[8]; fio_fallthrough; + case 8: b += (uint32_t) k[7] << 24; fio_fallthrough; + case 7: b += (uint32_t) k[6] << 16; fio_fallthrough; + case 6: b += (uint32_t) k[5] << 8; fio_fallthrough; + case 5: b += k[4]; fio_fallthrough; + case 4: a += (uint32_t) k[3] << 24; fio_fallthrough; + case 3: a += (uint32_t) k[2] << 16; fio_fallthrough; + case 2: a += (uint32_t) k[1] << 8; fio_fallthrough; case 1: a += k[0]; __jhash_final(a, b, c); - fallthrough; + fio_fallthrough; case 0: /* Nothing left to add */ break; } diff --git a/init.c b/init.c index b7f866e659..6f1860518f 100644 --- a/init.c +++ b/init.c @@ -2990,7 +2990,7 @@ int parse_cmd_line(int argc, char *argv[], int client_type) log_err("%s: unrecognized option '%s'\n", argv[0], argv[optind - 1]); show_closest_option(argv[optind - 1]); - fallthrough; + fio_fallthrough; default: do_exit++; exit_val = 1; diff --git a/io_u.c b/io_u.c index 50197a4bfa..eec378ddc0 100644 --- a/io_u.c +++ b/io_u.c @@ -993,7 +993,7 @@ static void __io_u_mark_map(uint64_t *map, unsigned int nr) break; case 1 ... 4: idx = 1; - fallthrough; + fio_fallthrough; case 0: break; } @@ -1035,7 +1035,7 @@ void io_u_mark_depth(struct thread_data *td, unsigned int nr) break; case 2 ... 3: idx = 1; - fallthrough; + fio_fallthrough; case 1: break; } @@ -1076,7 +1076,7 @@ static void io_u_mark_lat_nsec(struct thread_data *td, unsigned long long nsec) break; case 2 ... 3: idx = 1; - fallthrough; + fio_fallthrough; case 0 ... 1: break; } @@ -1118,7 +1118,7 @@ static void io_u_mark_lat_usec(struct thread_data *td, unsigned long long usec) break; case 2 ... 3: idx = 1; - fallthrough; + fio_fallthrough; case 0 ... 1: break; } @@ -1166,7 +1166,7 @@ static void io_u_mark_lat_msec(struct thread_data *td, unsigned long long msec) break; case 2 ... 3: idx = 1; - fallthrough; + fio_fallthrough; case 0 ... 1: break; } diff --git a/lib/lfsr.c b/lib/lfsr.c index a32e850a70..e86086c4af 100644 --- a/lib/lfsr.c +++ b/lib/lfsr.c @@ -88,37 +88,37 @@ static inline void __lfsr_next(struct fio_lfsr *fl, unsigned int spin) */ switch (spin) { case 15: __LFSR_NEXT(fl, fl->last_val); - fallthrough; + fio_fallthrough; case 14: __LFSR_NEXT(fl, fl->last_val); - fallthrough; + fio_fallthrough; case 13: __LFSR_NEXT(fl, fl->last_val); - fallthrough; + fio_fallthrough; case 12: __LFSR_NEXT(fl, fl->last_val); - fallthrough; + fio_fallthrough; case 11: __LFSR_NEXT(fl, fl->last_val); - fallthrough; + fio_fallthrough; case 10: __LFSR_NEXT(fl, fl->last_val); - fallthrough; + fio_fallthrough; case 9: __LFSR_NEXT(fl, fl->last_val); - fallthrough; + fio_fallthrough; case 8: __LFSR_NEXT(fl, fl->last_val); - fallthrough; + fio_fallthrough; case 7: __LFSR_NEXT(fl, fl->last_val); - fallthrough; + fio_fallthrough; case 6: __LFSR_NEXT(fl, fl->last_val); - fallthrough; + fio_fallthrough; case 5: __LFSR_NEXT(fl, fl->last_val); - fallthrough; + fio_fallthrough; case 4: __LFSR_NEXT(fl, fl->last_val); - fallthrough; + fio_fallthrough; case 3: __LFSR_NEXT(fl, fl->last_val); - fallthrough; + fio_fallthrough; case 2: __LFSR_NEXT(fl, fl->last_val); - fallthrough; + fio_fallthrough; case 1: __LFSR_NEXT(fl, fl->last_val); - fallthrough; + fio_fallthrough; case 0: __LFSR_NEXT(fl, fl->last_val); - fallthrough; + fio_fallthrough; default: break; } } diff --git a/parse.c b/parse.c index e0bee0049b..656a50250b 100644 --- a/parse.c +++ b/parse.c @@ -601,7 +601,7 @@ static int __handle_option(const struct fio_option *o, const char *ptr, } case FIO_OPT_STR_VAL_TIME: is_time = 1; - fallthrough; + fio_fallthrough; case FIO_OPT_ULL: case FIO_OPT_INT: case FIO_OPT_STR_VAL: @@ -980,7 +980,7 @@ static int __handle_option(const struct fio_option *o, const char *ptr, } case FIO_OPT_DEPRECATED: ret = 1; - fallthrough; + fio_fallthrough; case FIO_OPT_SOFT_DEPRECATED: log_info("Option %s is deprecated\n", o->name); break; diff --git a/t/lfsr-test.c b/t/lfsr-test.c index 279e07f0ec..4b255e19bb 100644 --- a/t/lfsr-test.c +++ b/t/lfsr-test.c @@ -41,11 +41,11 @@ int main(int argc, char *argv[]) switch (argc) { case 5: if (strncmp(argv[4], "verify", 7) == 0) verify = 1; - fallthrough; + fio_fallthrough; case 4: spin = atoi(argv[3]); - fallthrough; + fio_fallthrough; case 3: seed = atol(argv[2]); - fallthrough; + fio_fallthrough; case 2: numbers = strtol(argv[1], NULL, 16); break; default: usage(); From 06bbdc1cb857a11e6d1b7c089126397daca904fe Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Tue, 5 Apr 2022 17:32:49 +0000 Subject: [PATCH 0140/1097] smalloc: fix ptr address in redzone error message sfree_check_redzone is passed a pointer to the address of the *header* of an allocated block. This does not match the address of any of the buffers returned by smalloc. Adjust the value printed out to refer to the address returned by smalloc associated with the header in question. This makes debugging easier because it allows us to more easily identify the buffer where over-/under-run occurred. Signed-off-by: Vincent Fu Signed-off-by: Jens Axboe --- smalloc.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/smalloc.c b/smalloc.c index fa00f0ee33..23243054ec 100644 --- a/smalloc.c +++ b/smalloc.c @@ -283,13 +283,13 @@ static void sfree_check_redzone(struct block_hdr *hdr) if (hdr->prered != SMALLOC_PRE_RED) { log_err("smalloc pre redzone destroyed!\n" " ptr=%p, prered=%x, expected %x\n", - hdr, hdr->prered, SMALLOC_PRE_RED); + hdr+1, hdr->prered, SMALLOC_PRE_RED); assert(0); } if (*postred != SMALLOC_POST_RED) { log_err("smalloc post redzone destroyed!\n" " ptr=%p, postred=%x, expected %x\n", - hdr, *postred, SMALLOC_POST_RED); + hdr+1, *postred, SMALLOC_POST_RED); assert(0); } } From a3e48f483db27d20e02cbd81e3a8f18c6c5c50f5 Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Wed, 6 Apr 2022 17:10:00 -0600 Subject: [PATCH 0141/1097] Fio 3.30 Signed-off-by: Jens Axboe --- FIO-VERSION-GEN | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FIO-VERSION-GEN b/FIO-VERSION-GEN index 60f7bb21c0..fa64f50f69 100755 --- a/FIO-VERSION-GEN +++ b/FIO-VERSION-GEN @@ -1,7 +1,7 @@ #!/bin/sh GVF=FIO-VERSION-FILE -DEF_VER=fio-3.29 +DEF_VER=fio-3.30 LF=' ' From 315bbf01119ea783554d274add04db98c0a3b433 Mon Sep 17 00:00:00 2001 From: Mohamad Gebai Date: Thu, 7 Apr 2022 10:40:29 -0700 Subject: [PATCH 0142/1097] iolog: add version 3 to support timestamp-based replay Version 3 format looks as follows: timestamp filename action offset length All file and IO actions must have timestamps, including 'add'. The 'wait' action is not allowed with version 3 so that we can leave all timing functionality to timestamps. Signed-off-by: Mohamad Gebai Link: https://lore.kernel.org/r/20220407174031.599117-2-mogeb@fb.com Signed-off-by: Jens Axboe --- blktrace.c | 17 ++--------- fio.h | 3 +- iolog.c | 89 +++++++++++++++++++++++++++++++++++++++++++----------- iolog.h | 8 ++--- 4 files changed, 80 insertions(+), 37 deletions(-) diff --git a/blktrace.c b/blktrace.c index ead6013047..619121c704 100644 --- a/blktrace.c +++ b/blktrace.c @@ -313,25 +313,14 @@ static bool queue_trace(struct thread_data *td, struct blk_io_trace *t, unsigned long *ios, unsigned long long *bs, struct file_cache *cache) { - unsigned long long *last_ttime = &td->io_log_blktrace_last_ttime; + unsigned long long *last_ttime = &td->io_log_last_ttime; unsigned long long delay = 0; if ((t->action & 0xffff) != __BLK_TA_QUEUE) return false; if (!(t->action & BLK_TC_ACT(BLK_TC_NOTIFY))) { - if (!*last_ttime || td->o.no_stall || t->time < *last_ttime) - delay = 0; - else if (td->o.replay_time_scale == 100) - delay = t->time - *last_ttime; - else { - double tmp = t->time - *last_ttime; - double scale; - - scale = (double) 100.0 / (double) td->o.replay_time_scale; - tmp *= scale; - delay = tmp; - } + delay = delay_since_ttime(td, t->time); *last_ttime = t->time; } @@ -422,7 +411,7 @@ bool init_blktrace_read(struct thread_data *td, const char *filename, int need_s goto err; } td->io_log_blktrace_swap = need_swap; - td->io_log_blktrace_last_ttime = 0; + td->io_log_last_ttime = 0; td->o.size = 0; free_release_files(td); diff --git a/fio.h b/fio.h index 776fb51f74..8830ff34ad 100644 --- a/fio.h +++ b/fio.h @@ -431,10 +431,11 @@ struct thread_data { FILE *io_log_rfile; unsigned int io_log_blktrace; unsigned int io_log_blktrace_swap; - unsigned long long io_log_blktrace_last_ttime; + unsigned long long io_log_last_ttime; unsigned int io_log_current; unsigned int io_log_checkmark; unsigned int io_log_highmark; + unsigned int io_log_version; struct timespec io_log_highmark_time; /* diff --git a/iolog.c b/iolog.c index 724ec1fe16..f6023ee2ce 100644 --- a/iolog.c +++ b/iolog.c @@ -31,6 +31,7 @@ static int iolog_flush(struct io_log *log); static const char iolog_ver2[] = "fio version 2 iolog"; +static const char iolog_ver3[] = "fio version 3 iolog"; void queue_io_piece(struct thread_data *td, struct io_piece *ipo) { @@ -116,6 +117,10 @@ static int ipo_special(struct thread_data *td, struct io_piece *ipo) f = td->files[ipo->fileno]; + if (ipo->delay) + iolog_delay(td, ipo->delay); + if (fio_fill_issue_time(td)) + fio_gettime(&td->last_issue, NULL); switch (ipo->file_action) { case FIO_LOG_OPEN_FILE: if (td->o.replay_redirect && fio_file_open(f)) { @@ -134,6 +139,11 @@ static int ipo_special(struct thread_data *td, struct io_piece *ipo) case FIO_LOG_UNLINK_FILE: td_io_unlink_file(td, f); break; + case FIO_LOG_ADD_FILE: + /* + * Nothing to do + */ + break; default: log_err("fio: bad file action %d\n", ipo->file_action); break; @@ -142,7 +152,25 @@ static int ipo_special(struct thread_data *td, struct io_piece *ipo) return 1; } -static bool read_iolog2(struct thread_data *td); +static bool read_iolog(struct thread_data *td); + +unsigned long long delay_since_ttime(const struct thread_data *td, + unsigned long long time) +{ + double tmp; + double scale; + const unsigned long long *last_ttime = &td->io_log_last_ttime; + + if (!*last_ttime || td->o.no_stall || time < *last_ttime) + return 0; + else if (td->o.replay_time_scale == 100) + return time - *last_ttime; + + + scale = (double) 100.0 / (double) td->o.replay_time_scale; + tmp = time - *last_ttime; + return tmp * scale; +} int read_iolog_get(struct thread_data *td, struct io_u *io_u) { @@ -158,7 +186,7 @@ int read_iolog_get(struct thread_data *td, struct io_u *io_u) if (!read_blktrace(td)) return 1; } else { - if (!read_iolog2(td)) + if (!read_iolog(td)) return 1; } } @@ -388,14 +416,20 @@ int64_t iolog_items_to_fetch(struct thread_data *td) return items_to_fetch; } +#define io_act(_td, _r) (((_td)->io_log_version == 3 && (r) == 5) || \ + ((_td)->io_log_version == 2 && (r) == 4)) +#define file_act(_td, _r) (((_td)->io_log_version == 3 && (r) == 3) || \ + ((_td)->io_log_version == 2 && (r) == 2)) + /* - * Read version 2 iolog data. It is enhanced to include per-file logging, + * Read version 2 and 3 iolog data. It is enhanced to include per-file logging, * syncs, etc. */ -static bool read_iolog2(struct thread_data *td) +static bool read_iolog(struct thread_data *td) { unsigned long long offset; unsigned int bytes; + unsigned long long delay = 0; int reads, writes, waits, fileno = 0, file_action = 0; /* stupid gcc */ char *rfname, *fname, *act; char *str, *p; @@ -422,14 +456,28 @@ static bool read_iolog2(struct thread_data *td) while ((p = fgets(str, 4096, td->io_log_rfile)) != NULL) { struct io_piece *ipo; int r; + unsigned long long ttime; - r = sscanf(p, "%256s %256s %llu %u", rfname, act, &offset, - &bytes); + if (td->io_log_version == 3) { + r = sscanf(p, "%llu %256s %256s %llu %u", &ttime, rfname, act, + &offset, &bytes); + delay = delay_since_ttime(td, ttime); + td->io_log_last_ttime = ttime; + /* + * "wait" is not allowed with version 3 + */ + if (!strcmp(act, "wait")) { + log_err("iolog: ignoring wait command with" + " version 3 for file %s\n", fname); + continue; + } + } else /* version 2 */ + r = sscanf(p, "%256s %256s %llu %u", rfname, act, &offset, &bytes); if (td->o.replay_redirect) fname = td->o.replay_redirect; - if (r == 4) { + if (io_act(td, r)) { /* * Check action first */ @@ -451,7 +499,7 @@ static bool read_iolog2(struct thread_data *td) continue; } fileno = get_fileno(td, fname); - } else if (r == 2) { + } else if (file_act(td, r)) { rw = DDIR_INVAL; if (!strcmp(act, "add")) { if (td->o.replay_redirect && @@ -462,7 +510,6 @@ static bool read_iolog2(struct thread_data *td) fileno = add_file(td, fname, td->subjob_number, 1); file_action = FIO_LOG_ADD_FILE; } - continue; } else if (!strcmp(act, "open")) { fileno = get_fileno(td, fname); file_action = FIO_LOG_OPEN_FILE; @@ -475,7 +522,7 @@ static bool read_iolog2(struct thread_data *td) continue; } } else { - log_err("bad iolog2: %s\n", p); + log_err("bad iolog%d: %s\n", td->io_log_version, p); continue; } @@ -506,6 +553,8 @@ static bool read_iolog2(struct thread_data *td) ipo = calloc(1, sizeof(*ipo)); init_ipo(ipo); ipo->ddir = rw; + if (td->io_log_version == 3) + ipo->delay = delay; if (rw == DDIR_WAIT) { ipo->delay = offset; } else { @@ -650,18 +699,22 @@ static bool init_iolog_read(struct thread_data *td, char *fname) } /* - * version 2 of the iolog stores a specific string as the + * versions 2 and 3 of the iolog store a specific string as the * first line, check for that */ - if (!strncmp(iolog_ver2, buffer, strlen(iolog_ver2))) { - free_release_files(td); - td->io_log_rfile = f; - return read_iolog2(td); + if (!strncmp(iolog_ver2, buffer, strlen(iolog_ver2))) + td->io_log_version = 2; + else if (!strncmp(iolog_ver3, buffer, strlen(iolog_ver3))) + td->io_log_version = 3; + else { + log_err("fio: iolog version 1 is no longer supported\n"); + fclose(f); + return false; } - log_err("fio: iolog version 1 is no longer supported\n"); - fclose(f); - return false; + free_release_files(td); + td->io_log_rfile = f; + return read_iolog(td); } /* diff --git a/iolog.h b/iolog.h index a39863095a..62cbd1b02f 100644 --- a/iolog.h +++ b/iolog.h @@ -227,10 +227,8 @@ struct io_piece { unsigned long len; unsigned int flags; enum fio_ddir ddir; - union { - unsigned long delay; - unsigned int file_action; - }; + unsigned long delay; + unsigned int file_action; }; /* @@ -259,6 +257,8 @@ extern int iolog_compress_init(struct thread_data *, struct sk_out *); extern void iolog_compress_exit(struct thread_data *); extern size_t log_chunk_sizes(struct io_log *); extern int init_io_u_buffers(struct thread_data *); +extern unsigned long long delay_since_ttime(const struct thread_data *, + unsigned long long); #ifdef CONFIG_ZLIB extern int iolog_file_inflate(const char *); From e8cf24e570e0bda05dd8f9c390911ebc280956ea Mon Sep 17 00:00:00 2001 From: Mohamad Gebai Date: Thu, 7 Apr 2022 10:40:30 -0700 Subject: [PATCH 0143/1097] iolog: add iolog_write for version 3 Add timestamps to all actions for iolog version 3. Fio now generates iolog files using version 3 by default, and only supports writing using that version. Reading iolog v2 still works as expected. Signed-off-by: Mohamad Gebai Link: https://lore.kernel.org/r/20220407174031.599117-3-mogeb@fb.com Signed-off-by: Jens Axboe --- fio.h | 1 + iolog.c | 15 ++++++++++++--- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/fio.h b/fio.h index 8830ff34ad..de7eca79cb 100644 --- a/fio.h +++ b/fio.h @@ -432,6 +432,7 @@ struct thread_data { unsigned int io_log_blktrace; unsigned int io_log_blktrace_swap; unsigned long long io_log_last_ttime; + struct timespec io_log_start_time; unsigned int io_log_current; unsigned int io_log_checkmark; unsigned int io_log_highmark; diff --git a/iolog.c b/iolog.c index f6023ee2ce..51aecd4325 100644 --- a/iolog.c +++ b/iolog.c @@ -41,18 +41,24 @@ void queue_io_piece(struct thread_data *td, struct io_piece *ipo) void log_io_u(const struct thread_data *td, const struct io_u *io_u) { + struct timespec now; + if (!td->o.write_iolog_file) return; - fprintf(td->iolog_f, "%s %s %llu %llu\n", io_u->file->file_name, + fio_gettime(&now, NULL); + fprintf(td->iolog_f, "%lu %s %s %llu %llu\n", utime_since_now(&td->io_log_start_time), + io_u->file->file_name, io_ddir_name(io_u->ddir), io_u->offset, io_u->buflen); + } void log_file(struct thread_data *td, struct fio_file *f, enum file_log_act what) { const char *act[] = { "add", "open", "close" }; + struct timespec now; assert(what < 3); @@ -66,7 +72,9 @@ void log_file(struct thread_data *td, struct fio_file *f, if (!td->iolog_f) return; - fprintf(td->iolog_f, "%s %s\n", f->file_name, act[what]); + fio_gettime(&now, NULL); + fprintf(td->iolog_f, "%lu %s %s\n", utime_since_now(&td->io_log_start_time), + f->file_name, act[what]); } static void iolog_delay(struct thread_data *td, unsigned long delay) @@ -738,11 +746,12 @@ static bool init_iolog_write(struct thread_data *td) td->iolog_f = f; td->iolog_buf = malloc(8192); setvbuf(f, td->iolog_buf, _IOFBF, 8192); + fio_gettime(&td->io_log_start_time, NULL); /* * write our version line */ - if (fprintf(f, "%s\n", iolog_ver2) < 0) { + if (fprintf(f, "%s\n", iolog_ver3) < 0) { perror("iolog init\n"); return false; } From 5c2c0db4c5d9911f381546ae7d0d07d15b3ccc64 Mon Sep 17 00:00:00 2001 From: Mohamad Gebai Date: Thu, 7 Apr 2022 10:40:31 -0700 Subject: [PATCH 0144/1097] iolog: update man page for version 3 Add documentation for iolog version 3, mainly the differences between versions 2 and 3. Signed-off-by: Mohamad Gebai Link: https://lore.kernel.org/r/20220407174031.599117-4-mogeb@fb.com Signed-off-by: Jens Axboe --- HOWTO.rst | 29 ++++++++++++++++++++++++++++- fio.1 | 35 ++++++++++++++++++++++++++++++++++- 2 files changed, 62 insertions(+), 2 deletions(-) diff --git a/HOWTO.rst b/HOWTO.rst index 0978879ce6..a5fa432e4b 100644 --- a/HOWTO.rst +++ b/HOWTO.rst @@ -4398,7 +4398,9 @@ given in bytes. The `action` can be one of these: **wait** Wait for `offset` microseconds. Everything below 100 is discarded. - The time is relative to the previous `wait` statement. + The time is relative to the previous `wait` statement. Note that + action `wait` is not allowed as of version 3, as the same behavior + can be achieved using timestamps. **read** Read `length` bytes beginning from `offset`. **write** @@ -4411,6 +4413,31 @@ given in bytes. The `action` can be one of these: Trim the given file from the given `offset` for `length` bytes. +Trace file format v3 +~~~~~~~~~~~~~~~~~~~~ + +The third version of the trace file format was added in fio version 3.31. It +forces each action to have a timestamp associated with it. + +The first line of the trace file has to be:: + + fio version 3 iolog + +Following this can be lines in two different formats, which are described below. + +The file management format:: + + timestamp filename action + +The file I/O action format:: + + timestamp filename action offset length + +The `timestamp` is relative to the beginning of the run (ie starts at 0). The +`filename`, `action`, `offset` and `length` are identical to version 2, except +that version 3 does not allow the `wait` action. + + I/O Replay - Merging Traces --------------------------- diff --git a/fio.1 b/fio.1 index 984106558e..a2ec836ff3 100644 --- a/fio.1 +++ b/fio.1 @@ -4117,7 +4117,9 @@ given in bytes. The `action' can be one of these: .TP .B wait Wait for `offset' microseconds. Everything below 100 is discarded. -The time is relative to the previous `wait' statement. +The time is relative to the previous `wait' statement. Note that action `wait` +is not allowed as of version 3, as the same behavior can be achieved using +timestamps. .TP .B read Read `length' bytes beginning from `offset'. @@ -4135,6 +4137,37 @@ Write `length' bytes beginning from `offset'. Trim the given file from the given `offset' for `length' bytes. .RE .RE +.RE +.TP +.B Trace file format v3 +The third version of the trace file format was added in fio version 3.31. It +forces each action to have a timestamp associated with it. +.RS +.P +The first line of the trace file has to be: +.RS +.P +"fio version 3 iolog" +.RE +.P +Following this can be lines in two different formats, which are described below. +.P +.B +The file management format: +.RS +timestamp filename action +.P +.RE +.B +The file I/O action format: +.RS +timestamp filename action offset length +.P +The `timestamp` is relative to the beginning of the run (ie starts at 0). The +`filename`, `action`, `offset` and `length` are identical to version 2, except +that version 3 does not allow the `wait` action. +.RE +.RE .SH I/O REPLAY \- MERGING TRACES Colocation is a common practice used to get the most out of a machine. Knowing which workloads play nicely with each other and which ones don't is From 11cb686eeda83b9ff15619f01a8ad52f4be017cd Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Fri, 8 Apr 2022 12:32:12 -0600 Subject: [PATCH 0145/1097] iolog: fix warning for 32-bit compilation Cast the 64-bit value, we can't print it directly as %lu. Fixes: e8cf24e570e0 ("iolog: add iolog_write for version 3") Signed-off-by: Jens Axboe --- iolog.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/iolog.c b/iolog.c index 51aecd4325..42d2b0e909 100644 --- a/iolog.c +++ b/iolog.c @@ -47,10 +47,10 @@ void log_io_u(const struct thread_data *td, const struct io_u *io_u) return; fio_gettime(&now, NULL); - fprintf(td->iolog_f, "%lu %s %s %llu %llu\n", utime_since_now(&td->io_log_start_time), - io_u->file->file_name, - io_ddir_name(io_u->ddir), - io_u->offset, io_u->buflen); + fprintf(td->iolog_f, "%lu %s %s %llu %llu\n", + (unsigned long) utime_since_now(&td->io_log_start_time), + io_u->file->file_name, io_ddir_name(io_u->ddir), + io_u->offset, io_u->buflen); } @@ -73,8 +73,9 @@ void log_file(struct thread_data *td, struct fio_file *f, return; fio_gettime(&now, NULL); - fprintf(td->iolog_f, "%lu %s %s\n", utime_since_now(&td->io_log_start_time), - f->file_name, act[what]); + fprintf(td->iolog_f, "%lu %s %s\n", + (unsigned long) utime_since_now(&td->io_log_start_time), + f->file_name, act[what]); } static void iolog_delay(struct thread_data *td, unsigned long delay) From 6d01ac19170fadaf46a6db6b4cc347f1b389f422 Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Fri, 8 Apr 2022 12:46:44 -0600 Subject: [PATCH 0146/1097] iolog: Use %llu for 64-bit The previous fix was a bit dump, we need %llu on 32-bit. Fi that and the cast. Fixes: 11cb686eeda8 ("iolog: fix warning for 32-bit compilation") Signed-off-by: Jens Axboe --- iolog.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/iolog.c b/iolog.c index 42d2b0e909..37e799a13c 100644 --- a/iolog.c +++ b/iolog.c @@ -47,10 +47,10 @@ void log_io_u(const struct thread_data *td, const struct io_u *io_u) return; fio_gettime(&now, NULL); - fprintf(td->iolog_f, "%lu %s %s %llu %llu\n", - (unsigned long) utime_since_now(&td->io_log_start_time), - io_u->file->file_name, io_ddir_name(io_u->ddir), - io_u->offset, io_u->buflen); + fprintf(td->iolog_f, "%llu %s %s %llu %llu\n", + (unsigned long long) utime_since_now(&td->io_log_start_time), + io_u->file->file_name, io_ddir_name(io_u->ddir), io_u->offset, + io_u->buflen); } @@ -73,9 +73,9 @@ void log_file(struct thread_data *td, struct fio_file *f, return; fio_gettime(&now, NULL); - fprintf(td->iolog_f, "%lu %s %s\n", - (unsigned long) utime_since_now(&td->io_log_start_time), - f->file_name, act[what]); + fprintf(td->iolog_f, "%llu %s %s\n", + (unsigned long long) utime_since_now(&td->io_log_start_time), + f->file_name, act[what]); } static void iolog_delay(struct thread_data *td, unsigned long delay) From 6203763e2079901c3eae1ebd9e094d6a2afca996 Mon Sep 17 00:00:00 2001 From: Denis Pronin Date: Fri, 11 Mar 2022 15:13:46 +0300 Subject: [PATCH 0147/1097] fixed possible and actual memory leaks backend.c: release memory occupied by the parent process for options ioengines.c: free_ioengine entry point is protected by 'assert' call upon td and td->io_ops Signed-off-by: Denis Pronin --- backend.c | 3 +++ ioengines.c | 2 ++ 2 files changed, 5 insertions(+) diff --git a/backend.c b/backend.c index 001b2b9647..317e4f6c0d 100644 --- a/backend.c +++ b/backend.c @@ -2433,8 +2433,10 @@ static void run_threads(struct sk_out *sk_out) } else { pid_t pid; struct fio_file **files; + void *eo; dprint(FD_PROCESS, "will fork\n"); files = td->files; + eo = td->eo; read_barrier(); pid = fork(); if (!pid) { @@ -2447,6 +2449,7 @@ static void run_threads(struct sk_out *sk_out) // freeing previously allocated memory for files // this memory freed MUST NOT be shared between processes, only the pointer itself may be shared within TD free(files); + free(eo); free(fd); fd = NULL; } diff --git a/ioengines.c b/ioengines.c index d08a511a06..68f307e541 100644 --- a/ioengines.c +++ b/ioengines.c @@ -223,6 +223,8 @@ struct ioengine_ops *load_ioengine(struct thread_data *td) */ void free_ioengine(struct thread_data *td) { + assert(td != NULL && td->io_ops != NULL); + dprint(FD_IO, "free ioengine %s\n", td->io_ops->name); if (td->eo && td->io_ops->options) { From 7c799ab224ef4042967a9acca690cd33dbb34545 Mon Sep 17 00:00:00 2001 From: Denis Pronin Date: Sat, 2 Apr 2022 11:46:47 +0300 Subject: [PATCH 0148/1097] actions-full-test.sh, removed sudo from the script the script might be called with 'sudo' from the call site, if required Signed-off-by: Denis Pronin --- ci/actions-full-test.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ci/actions-full-test.sh b/ci/actions-full-test.sh index 9179066456..8282002f76 100755 --- a/ci/actions-full-test.sh +++ b/ci/actions-full-test.sh @@ -6,9 +6,9 @@ main() { echo "Running long running tests..." export PYTHONUNBUFFERED="TRUE" if [[ "${CI_TARGET_ARCH}" == "arm64" ]]; then - sudo python3 t/run-fio-tests.py --skip 6 1007 1008 --debug -p 1010:"--skip 15 16 17 18 19 20" + python3 t/run-fio-tests.py --skip 6 1007 1008 --debug -p 1010:"--skip 15 16 17 18 19 20" else - sudo python3 t/run-fio-tests.py --skip 6 1007 1008 --debug + python3 t/run-fio-tests.py --skip 6 1007 1008 --debug fi make -C doc html } From 0fc3cb4cd767bca150601a9d07fcdd57e4c50373 Mon Sep 17 00:00:00 2001 From: Denis Pronin Date: Sun, 10 Apr 2022 15:21:06 +0300 Subject: [PATCH 0149/1097] fixed memory leak of not freed jobs_eta in several cases used 'free' function in 'print_thread_status' and in 'show_thread_status_json' functions to free jobs_eta previously allocated Signed-off-by: Denis Pronin --- eta.c | 7 ++++--- stat.c | 2 ++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/eta.c b/eta.c index 17970c78db..6017ca3102 100644 --- a/eta.c +++ b/eta.c @@ -3,6 +3,7 @@ */ #include #include +#include #ifdef CONFIG_VALGRIND_DEV #include #else @@ -707,10 +708,10 @@ void print_thread_status(void) size_t size; je = get_jobs_eta(false, &size); - if (je) + if (je) { display_thread_status(je); - - free(je); + free(je); + } } void print_status_init(int thr_number) diff --git a/stat.c b/stat.c index 356083e25f..949af5edd4 100644 --- a/stat.c +++ b/stat.c @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -1698,6 +1699,7 @@ static struct json_object *show_thread_status_json(struct thread_stat *ts, if (je) { json_object_add_value_int(root, "eta", je->eta_sec); json_object_add_value_int(root, "elapsed", je->elapsed_sec); + free(je); } if (opt_list) From 3b61b44db397580134a669ca32e9d67b4126d92b Mon Sep 17 00:00:00 2001 From: Denis Pronin Date: Sun, 10 Apr 2022 15:22:46 +0300 Subject: [PATCH 0150/1097] use flist_first_entry instead of flist_entry applied to 'next' list item use flist_first_entry in 'handle_xmits' function Signed-off-by: Denis Pronin --- server.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server.c b/server.c index 914a8c74cd..4c71bd4494 100644 --- a/server.c +++ b/server.c @@ -1323,7 +1323,7 @@ static int handle_xmits(struct sk_out *sk_out) sk_unlock(sk_out); while (!flist_empty(&list)) { - entry = flist_entry(list.next, struct sk_entry, list); + entry = flist_first_entry(&list, struct sk_entry, list); flist_del(&entry->list); ret += handle_sk_entry(sk_out, entry); } From e0825685ecf76988683488390096890114478aab Mon Sep 17 00:00:00 2001 From: Denis Pronin Date: Sun, 10 Apr 2022 15:41:47 +0300 Subject: [PATCH 0151/1097] fixed bunch of memory leaks in json constructor fixed memory leak produced by not freed string given by 'strdup' in 'json_object_add_value_string' function Signed-off-by: Denis Pronin --- json.h | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/json.h b/json.h index d98242638d..66bb06b1f1 100644 --- a/json.h +++ b/json.h @@ -81,8 +81,13 @@ static inline int json_object_add_value_string(struct json_object *obj, struct json_value arg = { .type = JSON_TYPE_STRING, }; + union { + const char *a; + char *b; + } string; - arg.string = strdup(val ? : ""); + string.a = val ? val : ""; + arg.string = string.b; return json_object_add_value_type(obj, name, &arg); } From 7ddc4ed1ce94639e3306f980a7c651da3dd81c1d Mon Sep 17 00:00:00 2001 From: Denis Pronin Date: Sun, 17 Apr 2022 23:08:15 +0300 Subject: [PATCH 0152/1097] updated logging of iops1, iops2, ratio in FioJobTest_iops_rate log iops1 and iops2 upon reading json_data from results log ratio as iops2 / iops1 Signed-off-by: Denis Pronin --- t/run-fio-tests.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/t/run-fio-tests.py b/t/run-fio-tests.py index 612e50ca6a..ecceb67e93 100755 --- a/t/run-fio-tests.py +++ b/t/run-fio-tests.py @@ -546,9 +546,10 @@ def check_result(self): return iops1 = self.json_data['jobs'][0]['read']['iops'] + logging.debug("Test %d: iops1: %f", self.testnum, iops1) iops2 = self.json_data['jobs'][1]['read']['iops'] + logging.debug("Test %d: iops2: %f", self.testnum, iops2) ratio = iops2 / iops1 - logging.debug("Test %d: iops1: %f", self.testnum, iops1) logging.debug("Test %d: ratio: %f", self.testnum, ratio) if iops1 < 950 or iops1 > 1050: From 83276370ce4d6ce7fc43fb9faf06454403877da1 Mon Sep 17 00:00:00 2001 From: Denis Pronin Date: Fri, 11 Mar 2022 13:59:07 +0300 Subject: [PATCH 0153/1097] fixed compiler warnings if NDEBUG enabled in core code if NDEBUG is defined we check several return codes fairly rather than ignore them in turning into nothing assert call in RELEASE mode Signed-off-by: Denis Pronin --- backend.c | 18 ++++++++++++++---- helper_thread.c | 8 +++++++- io_u.c | 7 ++++--- ioengines.c | 10 ++++++++-- rate-submit.c | 18 +++++++++++++++--- server.c | 8 ++++++-- zbd.c | 18 ++++++++---------- 7 files changed, 62 insertions(+), 25 deletions(-) diff --git a/backend.c b/backend.c index 317e4f6c0d..c196672ea3 100644 --- a/backend.c +++ b/backend.c @@ -1600,7 +1600,7 @@ static void *thread_main(void *data) uint64_t bytes_done[DDIR_RWDIR_CNT]; int deadlock_loop_cnt; bool clear_state; - int res, ret; + int ret; sk_out_assign(sk_out); free(fd); @@ -1931,13 +1931,23 @@ static void *thread_main(void *data) * another thread is checking its io_u's for overlap */ if (td_offload_overlap(td)) { - int res = pthread_mutex_lock(&overlap_check); - assert(res == 0); + int res; + + res = pthread_mutex_lock(&overlap_check); + if (res) { + td->error = errno; + goto err; + } } td_set_runstate(td, TD_FINISHING); if (td_offload_overlap(td)) { + int res; + res = pthread_mutex_unlock(&overlap_check); - assert(res == 0); + if (res) { + td->error = errno; + goto err; + } } update_rusage_stat(td); diff --git a/helper_thread.c b/helper_thread.c index b9b83db305..26857773bc 100644 --- a/helper_thread.c +++ b/helper_thread.c @@ -1,4 +1,7 @@ +#include #include +#include +#include #include #ifdef CONFIG_HAVE_TIMERFD_CREATE #include @@ -122,7 +125,10 @@ static void submit_action(enum action a) return; ret = write_to_pipe(helper_data->pipe[1], &data, sizeof(data)); - assert(ret == 1); + if (ret != 1) { + log_err("failed to write action into pipe, err %i:%s", errno, strerror(errno)); + assert(0); + } } void helper_reset(void) diff --git a/io_u.c b/io_u.c index eec378ddc0..8da790027f 100644 --- a/io_u.c +++ b/io_u.c @@ -1570,7 +1570,6 @@ struct io_u *__get_io_u(struct thread_data *td) { const bool needs_lock = td_async_processing(td); struct io_u *io_u = NULL; - int ret; if (td->stop_io) return NULL; @@ -1604,14 +1603,16 @@ struct io_u *__get_io_u(struct thread_data *td) io_u_set(td, io_u, IO_U_F_IN_CUR_DEPTH); io_u->ipo = NULL; } else if (td_async_processing(td)) { + int ret; /* * We ran out, wait for async verify threads to finish and * return one */ assert(!(td->flags & TD_F_CHILD)); ret = pthread_cond_wait(&td->free_cond, &td->io_u_lock); - assert(ret == 0); - if (!td->error) + if (fio_unlikely(ret != 0)) { + td->error = errno; + } else if (!td->error) goto again; } diff --git a/ioengines.c b/ioengines.c index 68f307e541..63234e8afc 100644 --- a/ioengines.c +++ b/ioengines.c @@ -17,6 +17,7 @@ #include #include #include +#include #include "fio.h" #include "diskutil.h" @@ -335,8 +336,13 @@ enum fio_q_status td_io_queue(struct thread_data *td, struct io_u *io_u) * flag is now set */ if (td_offload_overlap(td)) { - int res = pthread_mutex_unlock(&overlap_check); - assert(res == 0); + int res; + + res = pthread_mutex_unlock(&overlap_check); + if (fio_unlikely(res != 0)) { + log_err("failed to unlock overlap check mutex, err: %i:%s", errno, strerror(errno)); + abort(); + } } assert(fio_file_open(io_u->file)); diff --git a/rate-submit.c b/rate-submit.c index 268356d17a..bc076e869f 100644 --- a/rate-submit.c +++ b/rate-submit.c @@ -5,6 +5,9 @@ * */ #include +#include +#include + #include "fio.h" #include "ioengines.h" #include "lib/getrusage.h" @@ -28,7 +31,10 @@ static void check_overlap(struct io_u *io_u) * threads as they assess overlap. */ res = pthread_mutex_lock(&overlap_check); - assert(res == 0); + if (fio_unlikely(res != 0)) { + log_err("failed to lock overlap check mutex, err: %i:%s", errno, strerror(errno)); + abort(); + } retry: for_each_td(td, i) { @@ -42,9 +48,15 @@ static void check_overlap(struct io_u *io_u) continue; res = pthread_mutex_unlock(&overlap_check); - assert(res == 0); + if (fio_unlikely(res != 0)) { + log_err("failed to unlock overlap check mutex, err: %i:%s", errno, strerror(errno)); + abort(); + } res = pthread_mutex_lock(&overlap_check); - assert(res == 0); + if (fio_unlikely(res != 0)) { + log_err("failed to lock overlap check mutex, err: %i:%s", errno, strerror(errno)); + abort(); + } goto retry; } } diff --git a/server.c b/server.c index 4c71bd4494..0bb82281c8 100644 --- a/server.c +++ b/server.c @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -2336,8 +2337,11 @@ void fio_server_send_add_job(struct thread_data *td) void fio_server_send_start(struct thread_data *td) { struct sk_out *sk_out = pthread_getspecific(sk_out_key); - - assert(sk_out->sk != -1); + if (!sk_out || sk_out->sk == -1) { + log_err("pthread getting specific for key failed, sk_out %p, sk %i, err: %i:%s", + sk_out, sk_out->sk, errno, strerror(errno)); + abort(); + } fio_net_queue_cmd(FIO_NET_CMD_SERVER_START, NULL, 0, NULL, SK_F_SIMPLE); } diff --git a/zbd.c b/zbd.c index b1fd6b4bb0..2f2d054e94 100644 --- a/zbd.c +++ b/zbd.c @@ -11,6 +11,7 @@ #include #include +#include "compiler/compiler.h" #include "os/os.h" #include "file.h" #include "fio.h" @@ -90,13 +91,13 @@ static bool zbd_zone_full(const struct fio_file *f, struct fio_zone_info *z, static void zone_lock(struct thread_data *td, const struct fio_file *f, struct fio_zone_info *z) { +#ifndef NDEBUG struct zoned_block_device_info *zbd = f->zbd_info; - uint32_t nz = z - zbd->zone_info; - + uint32_t const nz = z - zbd->zone_info; /* A thread should never lock zones outside its working area. */ assert(f->min_zone <= nz && nz < f->max_zone); - assert(z->has_wp); +#endif /* * Lock the io_u target zone. The zone will be unlocked if io_u offset @@ -116,11 +117,8 @@ static void zone_lock(struct thread_data *td, const struct fio_file *f, static inline void zone_unlock(struct fio_zone_info *z) { - int ret; - assert(z->has_wp); - ret = pthread_mutex_unlock(&z->mutex); - assert(!ret); + pthread_mutex_unlock(&z->mutex); } static inline struct fio_zone_info *zbd_get_zone(const struct fio_file *f, @@ -339,7 +337,8 @@ static int zbd_reset_zones(struct thread_data *td, struct fio_file *f, const uint64_t min_bs = td->o.min_bs[DDIR_WRITE]; int res = 0; - assert(min_bs); + if (fio_unlikely(0 == min_bs)) + return 1; dprint(FD_ZBD, "%s: examining zones %u .. %u\n", f->file_name, zbd_zone_idx(f, zb), zbd_zone_idx(f, ze)); @@ -1651,10 +1650,9 @@ static void zbd_queue_io(struct thread_data *td, struct io_u *io_u, int q, static void zbd_put_io(struct thread_data *td, const struct io_u *io_u) { const struct fio_file *f = io_u->file; - struct zoned_block_device_info *zbd_info = f->zbd_info; struct fio_zone_info *z; - assert(zbd_info); + assert(f->zbd_info); z = zbd_offset_to_zone(f, io_u->offset); assert(z->has_wp); From 1f081ec0411a67cef0e4d80a0d51a052a5937056 Mon Sep 17 00:00:00 2001 From: Denis Pronin Date: Fri, 22 Apr 2022 22:24:35 +0300 Subject: [PATCH 0154/1097] fixed compiler warnings if NDEBUG enabled in test code if NDEBUG is defined we check several return codes fairly rather than ignore them in turning into nothing assert call in RELEASE mode Signed-off-by: Denis Pronin --- t/read-to-pipe-async.c | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/t/read-to-pipe-async.c b/t/read-to-pipe-async.c index 586e3c95bf..76c64ab9c4 100644 --- a/t/read-to-pipe-async.c +++ b/t/read-to-pipe-async.c @@ -36,6 +36,8 @@ #include "../flist.h" +#include "compiler/compiler.h" + static int bs = 4096; static int max_us = 10000; static char *file; @@ -47,6 +49,18 @@ static int separate_writer = 1; #define PLAT_NR (PLAT_GROUP_NR * PLAT_VAL) #define PLAT_LIST_MAX 20 +#ifndef NDEBUG +#define CHECK_ZERO_OR_ABORT(code) assert(code) +#else +#define CHECK_ZERO_OR_ABORT(code) \ + do { \ + if (fio_unlikely((code) != 0)) { \ + log_err("failed checking code %i != 0", (code)); \ + abort(); \ + } \ + } while (0) +#endif + struct stats { unsigned int plat[PLAT_NR]; unsigned int nr_samples; @@ -121,7 +135,7 @@ uint64_t utime_since(const struct timespec *s, const struct timespec *e) return ret; } -static struct work_item *find_seq(struct writer_thread *w, unsigned int seq) +static struct work_item *find_seq(struct writer_thread *w, int seq) { struct work_item *work; struct flist_head *entry; @@ -224,6 +238,8 @@ static int write_work(struct work_item *work) clock_gettime(CLOCK_MONOTONIC, &s); ret = write(STDOUT_FILENO, work->buf, work->buf_size); + if (ret < 0) + return (int)ret; clock_gettime(CLOCK_MONOTONIC, &e); assert(ret == work->buf_size); @@ -241,10 +257,10 @@ static void *writer_fn(void *data) { struct writer_thread *wt = data; struct work_item *work; - unsigned int seq = 1; + int seq = 1; work = NULL; - while (!wt->thread.exit || !flist_empty(&wt->list)) { + while (!(seq < 0) && (!wt->thread.exit || !flist_empty(&wt->list))) { pthread_mutex_lock(&wt->thread.lock); if (work) { @@ -469,10 +485,10 @@ static void init_thread(struct thread_data *thread) int ret; ret = pthread_condattr_init(&cattr); - assert(ret == 0); + CHECK_ZERO_OR_ABORT(ret); #ifdef CONFIG_PTHREAD_CONDATTR_SETCLOCK ret = pthread_condattr_setclock(&cattr, CLOCK_MONOTONIC); - assert(ret == 0); + CHECK_ZERO_OR_ABORT(ret); #endif pthread_cond_init(&thread->cond, &cattr); pthread_cond_init(&thread->done_cond, &cattr); @@ -626,10 +642,10 @@ int main(int argc, char *argv[]) bytes = 0; ret = pthread_condattr_init(&cattr); - assert(ret == 0); + CHECK_ZERO_OR_ABORT(ret); #ifdef CONFIG_PTHREAD_CONDATTR_SETCLOCK ret = pthread_condattr_setclock(&cattr, CLOCK_MONOTONIC); - assert(ret == 0); + CHECK_ZERO_OR_ABORT(ret); #endif clock_gettime(CLOCK_MONOTONIC, &s); From c49cfc76551bab92d7fd0cd936ffcc901e59fb61 Mon Sep 17 00:00:00 2001 From: Bar David Date: Sun, 24 Apr 2022 12:25:57 +0300 Subject: [PATCH 0155/1097] Introducing support for generation of dedup buffers across jobs. The dedup buffers are spread evenly between the jobs that enabled the dedupe_global option Note only dedupe_mode=working_set is supported. Note compression is supported with the global dedup enabled Signed-off-by: Bar David --- HOWTO.rst | 6 ++++++ backend.c | 5 +++++ cconv.c | 2 ++ dedupe.c | 46 ++++++++++++++++++++++++++++++++++++++++++---- dedupe.h | 3 ++- fio.1 | 9 +++++++++ init.c | 2 +- options.c | 10 ++++++++++ server.h | 2 +- thread_options.h | 3 +++ 10 files changed, 81 insertions(+), 7 deletions(-) diff --git a/HOWTO.rst b/HOWTO.rst index a5fa432e4b..6a3e09f519 100644 --- a/HOWTO.rst +++ b/HOWTO.rst @@ -1749,6 +1749,12 @@ Buffers and memory Note that size needs to be explicitly provided and only 1 file per job is supported +.. option:: dedupe_global=bool + + This controls whether the deduplication buffers will be shared amongst + all jobs that have this option set. The buffers are spread evenly between + participating jobs. + .. option:: invalidate=bool Invalidate the buffer/page cache parts of the files to be used prior to diff --git a/backend.c b/backend.c index 317e4f6c0d..ffbb7e2a07 100644 --- a/backend.c +++ b/backend.c @@ -2570,6 +2570,11 @@ int fio_backend(struct sk_out *sk_out) setup_log(&agg_io_log[DDIR_TRIM], &p, "agg-trim_bw.log"); } + if (init_global_dedupe_working_set_seeds()) { + log_err("fio: failed to initialize global dedupe working set\n"); + return 1; + } + startup_sem = fio_sem_init(FIO_SEM_LOCKED); if (!sk_out) is_local_backend = true; diff --git a/cconv.c b/cconv.c index 62d02e366e..6c36afb72d 100644 --- a/cconv.c +++ b/cconv.c @@ -305,6 +305,7 @@ void convert_thread_options_to_cpu(struct thread_options *o, o->dedupe_percentage = le32_to_cpu(top->dedupe_percentage); o->dedupe_mode = le32_to_cpu(top->dedupe_mode); o->dedupe_working_set_percentage = le32_to_cpu(top->dedupe_working_set_percentage); + o->dedupe_global = le32_to_cpu(top->dedupe_global); o->block_error_hist = le32_to_cpu(top->block_error_hist); o->replay_align = le32_to_cpu(top->replay_align); o->replay_scale = le32_to_cpu(top->replay_scale); @@ -513,6 +514,7 @@ void convert_thread_options_to_net(struct thread_options_pack *top, top->dedupe_percentage = cpu_to_le32(o->dedupe_percentage); top->dedupe_mode = cpu_to_le32(o->dedupe_mode); top->dedupe_working_set_percentage = cpu_to_le32(o->dedupe_working_set_percentage); + top->dedupe_global = cpu_to_le32(o->dedupe_global); top->block_error_hist = cpu_to_le32(o->block_error_hist); top->replay_align = cpu_to_le32(o->replay_align); top->replay_scale = cpu_to_le32(o->replay_scale); diff --git a/dedupe.c b/dedupe.c index fd116dfba4..8214a786b0 100644 --- a/dedupe.c +++ b/dedupe.c @@ -1,13 +1,37 @@ #include "fio.h" -int init_dedupe_working_set_seeds(struct thread_data *td) +/** + * initializes the global dedup workset. + * this needs to be called after all jobs' seeds + * have been initialized + */ +int init_global_dedupe_working_set_seeds(void) { - unsigned long long i, j, num_seed_advancements; + int i; + struct thread_data *td; + + for_each_td(td, i) { + if (!td->o.dedupe_global) + continue; + + if (init_dedupe_working_set_seeds(td, 1)) + return 1; + } + + return 0; +} + +int init_dedupe_working_set_seeds(struct thread_data *td, bool global_dedup) +{ + int tindex; + struct thread_data *td_seed; + unsigned long long i, j, num_seed_advancements, pages_per_seed; struct frand_state dedupe_working_set_state = {0}; if (!td->o.dedupe_percentage || !(td->o.dedupe_mode == DEDUPE_MODE_WORKING_SET)) return 0; + tindex = td->thread_number - 1; num_seed_advancements = td->o.min_bs[DDIR_WRITE] / min_not_zero(td->o.min_bs[DDIR_WRITE], (unsigned long long) td->o.compress_chunk); /* @@ -20,9 +44,11 @@ int init_dedupe_working_set_seeds(struct thread_data *td) log_err("fio: could not allocate dedupe working set\n"); return 1; } + frand_copy(&dedupe_working_set_state, &td->buf_state); - for (i = 0; i < td->num_unique_pages; i++) { - frand_copy(&td->dedupe_working_set_states[i], &dedupe_working_set_state); + frand_copy(&td->dedupe_working_set_states[0], &dedupe_working_set_state); + pages_per_seed = max(td->num_unique_pages / thread_number, 1ull); + for (i = 1; i < td->num_unique_pages; i++) { /* * When compression is used the seed is advanced multiple times to * generate the buffer. We want to regenerate the same buffer when @@ -30,6 +56,18 @@ int init_dedupe_working_set_seeds(struct thread_data *td) */ for (j = 0; j < num_seed_advancements; j++) __get_next_seed(&dedupe_working_set_state); + + /* + * When global dedup is used, we rotate the seeds to allow + * generating same buffers across different jobs. Deduplication buffers + * are spread evenly across jobs participating in global dedupe + */ + if (global_dedup && i % pages_per_seed == 0) { + td_seed = tnumber_to_td(++tindex % thread_number); + frand_copy(&dedupe_working_set_state, &td_seed->buf_state); + } + + frand_copy(&td->dedupe_working_set_states[i], &dedupe_working_set_state); } return 0; diff --git a/dedupe.h b/dedupe.h index d4c4dc3779..bd1f9c0c0b 100644 --- a/dedupe.h +++ b/dedupe.h @@ -1,6 +1,7 @@ #ifndef DEDUPE_H #define DEDUPE_H -int init_dedupe_working_set_seeds(struct thread_data *td); +int init_dedupe_working_set_seeds(struct thread_data *td, bool global_dedupe); +int init_global_dedupe_working_set_seeds(void); #endif diff --git a/fio.1 b/fio.1 index a2ec836ff3..609947dc41 100644 --- a/fio.1 +++ b/fio.1 @@ -1553,6 +1553,15 @@ Note that \fBsize\fR needs to be explicitly provided and only 1 file per job is supported .RE .TP +.BI dedupe_global \fR=\fPbool +This controls whether the deduplication buffers will be shared amongst +all jobs that have this option set. The buffers are spread evenly between +participating jobs. +.P +.RS +Note that \fBdedupe_mode\fR must be set to \fBworking_set\fR for this to work. +Can be used in combination with compression +.TP .BI invalidate \fR=\fPbool Invalidate the buffer/page cache parts of the files to be used prior to starting I/O if the platform and file type support it. Defaults to true. diff --git a/init.c b/init.c index 6f1860518f..f7d702f849 100644 --- a/init.c +++ b/init.c @@ -1541,7 +1541,7 @@ static int add_job(struct thread_data *td, const char *jobname, int job_add_num, if (fixup_options(td)) goto err; - if (init_dedupe_working_set_seeds(td)) + if (!td->o.dedupe_global && init_dedupe_working_set_seeds(td, 0)) goto err; /* diff --git a/options.c b/options.c index e06d9b66ad..3b83573bd8 100644 --- a/options.c +++ b/options.c @@ -4665,6 +4665,16 @@ struct fio_option fio_options[FIO_MAX_OPTS] = { .category = FIO_OPT_C_IO, .group = FIO_OPT_G_IO_BUF, }, + { + .name = "dedupe_global", + .lname = "Global deduplication", + .type = FIO_OPT_BOOL, + .off1 = offsetof(struct thread_options, dedupe_global), + .help = "Share deduplication buffers across jobs", + .def = "0", + .category = FIO_OPT_C_IO, + .group = FIO_OPT_G_IO_BUF, + }, { .name = "dedupe_mode", .lname = "Dedupe mode", diff --git a/server.h b/server.h index 0e62b6dfe8..b0c5e2dfaf 100644 --- a/server.h +++ b/server.h @@ -51,7 +51,7 @@ struct fio_net_cmd_reply { }; enum { - FIO_SERVER_VER = 96, + FIO_SERVER_VER = 97, FIO_SERVER_MAX_FRAGMENT_PDU = 1024, FIO_SERVER_MAX_CMD_MB = 2048, diff --git a/thread_options.h b/thread_options.h index 4162c42faf..634070af00 100644 --- a/thread_options.h +++ b/thread_options.h @@ -263,6 +263,7 @@ struct thread_options { unsigned int dedupe_percentage; unsigned int dedupe_mode; unsigned int dedupe_working_set_percentage; + unsigned int dedupe_global; unsigned int time_based; unsigned int disable_lat; unsigned int disable_clat; @@ -578,6 +579,7 @@ struct thread_options_pack { uint32_t dedupe_percentage; uint32_t dedupe_mode; uint32_t dedupe_working_set_percentage; + uint32_t dedupe_global; uint32_t time_based; uint32_t disable_lat; uint32_t disable_clat; @@ -596,6 +598,7 @@ struct thread_options_pack { uint32_t lat_percentiles; uint32_t slat_percentiles; uint32_t percentile_precision; + uint32_t pad5; fio_fp64_t percentile_list[FIO_IO_U_LIST_MAX_LEN]; uint8_t read_iolog_file[FIO_TOP_STR_MAX]; From b6aa7b8785edfbb99d40a7df92b4342bc84f6eb2 Mon Sep 17 00:00:00 2001 From: Bar David Date: Thu, 28 Apr 2022 16:30:47 +0300 Subject: [PATCH 0156/1097] adding an example for dedupe_global usage and DRR testing Signed-off-by: Bar David --- examples/dedupe-global.fio | 57 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 examples/dedupe-global.fio diff --git a/examples/dedupe-global.fio b/examples/dedupe-global.fio new file mode 100644 index 0000000000..edaaad55b7 --- /dev/null +++ b/examples/dedupe-global.fio @@ -0,0 +1,57 @@ +# Writing to 2 files that share the duplicate blocks. +# The dedupe working set is spread uniformly such that when +# each of the jobs choose to perform a dedup operation they will +# regenerate a buffer from the global space. +# If you test the dedup ratio on either file by itself the result +# is likely lower than if you test the ratio of the two files combined. +# +# Use `./t/fio-dedupe -C 1 -c 1 -b 4096` to test the total +# data reduction ratio. +# +# +# Full example of test: +# $ ./fio ./examples/dedupe-global.fio +# +# Checking ratio on a and b individually: +# $ ./t/fio-dedupe a.0.0 -C 1 -c 1 -b 4096 +# +# $ Extents=25600, Unique extents=16817 Duplicated extents=5735 +# $ De-dupe ratio: 1:0.52 +# $ De-dupe working set at least: 22.40% +# $ Fio setting: dedupe_percentage=34 +# $ Unique capacity 33MB +# +# ./t/fio-dedupe b.0.0 -C 1 -c 1 -b 4096 +# $ Extents=25600, Unique extents=17009 Duplicated extents=5636 +# $ De-dupe ratio: 1:0.51 +# $ De-dupe working set at least: 22.02% +# $ Fio setting: dedupe_percentage=34 +# $ Unique capacity 34MB +# +# Combining files: +# $ cat a.0.0 > c.0.0 +# $ cat b.0.0 >> c.0.0 +# +# Checking data reduction ratio on combined file: +# $ ./t/fio-dedupe c.0.0 -C 1 -c 1 -b 4096 +# $ Extents=51200, Unique extents=25747 Duplicated extents=11028 +# $ De-dupe ratio: 1:0.99 +# $ De-dupe working set at least: 21.54% +# $ Fio setting: dedupe_percentage=50 +# $ Unique capacity 51MB +# +[global] +ioengine=libaio +iodepth=256 +size=100m +dedupe_mode=working_set +dedupe_global=1 +dedupe_percentage=50 +blocksize=4k +rw=write +buffer_compress_percentage=50 +dedupe_working_set_percentage=50 + +[a] + +[b] From 981ad73acd3f530330b1efa92fe189357bed21d5 Mon Sep 17 00:00:00 2001 From: Frank Dana Date: Sun, 1 May 2022 04:07:34 -0400 Subject: [PATCH 0157/1097] README: Update Fedora pkg URL --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index d566fae3de..527f33abcb 100644 --- a/README.rst +++ b/README.rst @@ -107,7 +107,7 @@ Ubuntu: Red Hat, Fedora, CentOS & Co: Starting with Fedora 9/Extra Packages for Enterprise Linux 4, fio packages are part of the Fedora/EPEL repositories. - https://apps.fedoraproject.org/packages/fio . + https://packages.fedoraproject.org/pkgs/fio/ . Mandriva: Mandriva has integrated fio into their package repository, so installing From a3ff873ed4e7eed863927d749f39560068d40940 Mon Sep 17 00:00:00 2001 From: Ankit Kumar Date: Wed, 11 May 2022 22:00:17 +0530 Subject: [PATCH 0158/1097] engines/xnvme: add xnvme engine This patch introduces a new fio engine to work with xNVMe >= 0.2.0. xNVMe provides a user space library (libxnvme) to work with NVMe devices. The NVMe driver being used by libxnvme is re-targetable and can be any one of the GNU/Linux Kernel NVMe driver via libaio, IOCTLs, io_uring, the SPDK NVMe driver, or your own custom NVMe driver. For more info visit https://xnvme.io https://github.com/OpenMPDK/xNVMe Co-Authored-By: Ankit Kumar Co-Authored-By: Simon A. F. Lund Co-Authored-By: Mads Ynddal Co-Authored-By: Michael Bang Co-Authored-By: Karl Bonde Torp Co-Authored-By: Gurmeet Singh Co-Authored-By: Pierre Labat Link: https://lore.kernel.org/r/20220511163019.5608-2-ankit.kumar@samsung.com Signed-off-by: Jens Axboe --- Makefile | 7 +- configure | 22 ++ engines/xnvme.c | 981 ++++++++++++++++++++++++++++++++++++++++++++++++ optgroup.h | 2 + options.c | 5 + 5 files changed, 1016 insertions(+), 1 deletion(-) create mode 100644 engines/xnvme.c diff --git a/Makefile b/Makefile index e670c1f202..8495e727ba 100644 --- a/Makefile +++ b/Makefile @@ -223,7 +223,12 @@ ifdef CONFIG_LIBZBC libzbc_LIBS = -lzbc ENGINES += libzbc endif - +ifdef CONFIG_LIBXNVME + xnvme_SRCS = engines/xnvme.c + xnvme_LIBS = $(LIBXNVME_LIBS) + xnvme_CFLAGS = $(LIBXNVME_CFLAGS) + ENGINES += xnvme +endif ifeq ($(CONFIG_TARGET_OS), Linux) SOURCE += diskutil.c fifo.c blktrace.c cgroup.c trim.c engines/sg.c \ oslib/linux-dev-lookup.c engines/io_uring.c diff --git a/configure b/configure index d327d2ca77..95b60bb70a 100755 --- a/configure +++ b/configure @@ -171,6 +171,7 @@ march_set="no" libiscsi="no" libnbd="no" libnfs="no" +xnvme="no" libzbc="" dfs="" dynamic_engines="no" @@ -240,6 +241,8 @@ for opt do ;; --disable-libzbc) libzbc="no" ;; + --enable-xnvme) xnvme="yes" + ;; --disable-tcmalloc) disable_tcmalloc="yes" ;; --disable-nfs) disable_nfs="yes" @@ -291,6 +294,7 @@ if test "$show_help" = "yes" ; then echo "--with-ime= Install path for DDN's Infinite Memory Engine" echo "--enable-libiscsi Enable iscsi support" echo "--enable-libnbd Enable libnbd (NBD engine) support" + echo "--enable-xnvme Enable xnvme support" echo "--disable-libzbc Disable libzbc even if found" echo "--disable-tcmalloc Disable tcmalloc support" echo "--dynamic-libengines Lib-based ioengines as dynamic libraries" @@ -2583,6 +2587,19 @@ if test "$libzbc" != "no" ; then fi print_config "libzbc engine" "$libzbc" +########################################## +# Check if we have xnvme +if test "$xnvme" != "yes" ; then + if check_min_lib_version xnvme 0.2.0; then + xnvme="yes" + xnvme_cflags=$(pkg-config --cflags xnvme) + xnvme_libs=$(pkg-config --libs xnvme) + else + xnvme="no" + fi +fi +print_config "xnvme engine" "$xnvme" + ########################################## # check march=armv8-a+crc+crypto if test "$march_armv8_a_crc_crypto" != "yes" ; then @@ -3190,6 +3207,11 @@ if test "$libnfs" = "yes" ; then echo "LIBNFS_CFLAGS=$libnfs_cflags" >> $config_host_mak echo "LIBNFS_LIBS=$libnfs_libs" >> $config_host_mak fi +if test "$xnvme" = "yes" ; then + output_sym "CONFIG_LIBXNVME" + echo "LIBXNVME_CFLAGS=$xnvme_cflags" >> $config_host_mak + echo "LIBXNVME_LIBS=$xnvme_libs" >> $config_host_mak +fi if test "$dynamic_engines" = "yes" ; then output_sym "CONFIG_DYNAMIC_ENGINES" fi diff --git a/engines/xnvme.c b/engines/xnvme.c new file mode 100644 index 0000000000..c11b33a805 --- /dev/null +++ b/engines/xnvme.c @@ -0,0 +1,981 @@ +/* + * fio xNVMe IO Engine + * + * IO engine using the xNVMe C API. + * + * See: http://xnvme.io/ + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include +#include +#include +#include +#include +#include "fio.h" +#include "zbd_types.h" +#include "optgroup.h" + +static pthread_mutex_t g_serialize = PTHREAD_MUTEX_INITIALIZER; + +struct xnvme_fioe_fwrap { + /* fio file representation */ + struct fio_file *fio_file; + + /* xNVMe device handle */ + struct xnvme_dev *dev; + /* xNVMe device geometry */ + const struct xnvme_geo *geo; + + struct xnvme_queue *queue; + + uint32_t ssw; + uint32_t lba_nbytes; + + uint8_t _pad[24]; +}; +XNVME_STATIC_ASSERT(sizeof(struct xnvme_fioe_fwrap) == 64, "Incorrect size") + +struct xnvme_fioe_data { + /* I/O completion queue */ + struct io_u **iocq; + + /* # of iocq entries; incremented via getevents()/cb_pool() */ + uint64_t completed; + + /* + * # of errors; incremented when observed on completion via + * getevents()/cb_pool() + */ + uint64_t ecount; + + /* Controller which device/file to select */ + int32_t prev; + int32_t cur; + + /* Number of devices/files for which open() has been called */ + int64_t nopen; + /* Number of devices/files allocated in files[] */ + uint64_t nallocated; + + struct iovec *iovec; + + uint8_t _pad[8]; + + struct xnvme_fioe_fwrap files[]; +}; +XNVME_STATIC_ASSERT(sizeof(struct xnvme_fioe_data) == 64, "Incorrect size") + +struct xnvme_fioe_options { + void *padding; + unsigned int hipri; + unsigned int sqpoll_thread; + unsigned int xnvme_dev_nsid; + unsigned int xnvme_iovec; + char *xnvme_be; + char *xnvme_async; + char *xnvme_sync; + char *xnvme_admin; +}; + +static struct fio_option options[] = { + { + .name = "hipri", + .lname = "High Priority", + .type = FIO_OPT_STR_SET, + .off1 = offsetof(struct xnvme_fioe_options, hipri), + .help = "Use polled IO completions", + .category = FIO_OPT_C_ENGINE, + .group = FIO_OPT_G_XNVME, + }, + { + .name = "sqthread_poll", + .lname = "Kernel SQ thread polling", + .type = FIO_OPT_STR_SET, + .off1 = offsetof(struct xnvme_fioe_options, sqpoll_thread), + .help = "Offload submission/completion to kernel thread", + .category = FIO_OPT_C_ENGINE, + .group = FIO_OPT_G_XNVME, + }, + { + .name = "xnvme_be", + .lname = "xNVMe Backend", + .type = FIO_OPT_STR_STORE, + .off1 = offsetof(struct xnvme_fioe_options, xnvme_be), + .help = "Select xNVMe backend [spdk,linux,fbsd]", + .category = FIO_OPT_C_ENGINE, + .group = FIO_OPT_G_XNVME, + }, + { + .name = "xnvme_async", + .lname = "xNVMe Asynchronous command-interface", + .type = FIO_OPT_STR_STORE, + .off1 = offsetof(struct xnvme_fioe_options, xnvme_async), + .help = "Select xNVMe async. interface: [emu,thrpool,io_uring,libaio,posix,nil]", + .category = FIO_OPT_C_ENGINE, + .group = FIO_OPT_G_XNVME, + }, + { + .name = "xnvme_sync", + .lname = "xNVMe Synchronous. command-interface", + .type = FIO_OPT_STR_STORE, + .off1 = offsetof(struct xnvme_fioe_options, xnvme_sync), + .help = "Select xNVMe sync. interface: [nvme,psync]", + .category = FIO_OPT_C_ENGINE, + .group = FIO_OPT_G_XNVME, + }, + { + .name = "xnvme_admin", + .lname = "xNVMe Admin command-interface", + .type = FIO_OPT_STR_STORE, + .off1 = offsetof(struct xnvme_fioe_options, xnvme_admin), + .help = "Select xNVMe admin. cmd-interface: [nvme,block,file_as_ns]", + .category = FIO_OPT_C_ENGINE, + .group = FIO_OPT_G_XNVME, + }, + { + .name = "xnvme_dev_nsid", + .lname = "xNVMe Namespace-Identifier, for user-space NVMe driver", + .type = FIO_OPT_INT, + .off1 = offsetof(struct xnvme_fioe_options, xnvme_dev_nsid), + .help = "xNVMe Namespace-Identifier, for user-space NVMe driver", + .category = FIO_OPT_C_ENGINE, + .group = FIO_OPT_G_XNVME, + }, + { + .name = "xnvme_iovec", + .lname = "Vectored IOs", + .type = FIO_OPT_STR_SET, + .off1 = offsetof(struct xnvme_fioe_options, xnvme_iovec), + .help = "Send vectored IOs", + .category = FIO_OPT_C_ENGINE, + .group = FIO_OPT_G_XNVME, + }, + + { + .name = NULL, + }, +}; + +static void cb_pool(struct xnvme_cmd_ctx *ctx, void *cb_arg) +{ + struct io_u *io_u = cb_arg; + struct xnvme_fioe_data *xd = io_u->mmap_data; + + if (xnvme_cmd_ctx_cpl_status(ctx)) { + xnvme_cmd_ctx_pr(ctx, XNVME_PR_DEF); + xd->ecount += 1; + io_u->error = EIO; + } + + xd->iocq[xd->completed++] = io_u; + xnvme_queue_put_cmd_ctx(ctx->async.queue, ctx); +} + +static struct xnvme_opts xnvme_opts_from_fioe(struct thread_data *td) +{ + struct xnvme_fioe_options *o = td->eo; + struct xnvme_opts opts = xnvme_opts_default(); + + opts.nsid = o->xnvme_dev_nsid; + opts.be = o->xnvme_be; + opts.async = o->xnvme_async; + opts.sync = o->xnvme_sync; + opts.admin = o->xnvme_admin; + + opts.poll_io = o->hipri; + opts.poll_sq = o->sqpoll_thread; + + opts.direct = td->o.odirect; + + return opts; +} + +static void _dev_close(struct thread_data *td, struct xnvme_fioe_fwrap *fwrap) +{ + if (fwrap->dev) + xnvme_queue_term(fwrap->queue); + + xnvme_dev_close(fwrap->dev); + + memset(fwrap, 0, sizeof(*fwrap)); +} + +static void xnvme_fioe_cleanup(struct thread_data *td) +{ + struct xnvme_fioe_data *xd = td->io_ops_data; + int err; + + err = pthread_mutex_lock(&g_serialize); + if (err) + log_err("ioeng->cleanup(): pthread_mutex_lock(), err(%d)\n", err); + /* NOTE: not returning here */ + + for (uint64_t i = 0; i < xd->nallocated; ++i) + _dev_close(td, &xd->files[i]); + + if (!err) { + err = pthread_mutex_unlock(&g_serialize); + if (err) + log_err("ioeng->cleanup(): pthread_mutex_unlock(), err(%d)\n", err); + } + + free(xd->iocq); + free(xd->iovec); + free(xd); + td->io_ops_data = NULL; +} + +/** + * Helper function setting up device handles as addressed by the naming + * convention of the given `fio_file` filename. + * + * Checks thread-options for explicit control of asynchronous implementation via + * the ``--xnvme_async={thrpool,emu,posix,io_uring,libaio,nil}``. + */ +static int _dev_open(struct thread_data *td, struct fio_file *f) +{ + struct xnvme_opts opts = xnvme_opts_from_fioe(td); + struct xnvme_fioe_data *xd = td->io_ops_data; + struct xnvme_fioe_fwrap *fwrap; + int flags = 0; + int err; + + if (f->fileno > (int)xd->nallocated) { + log_err("ioeng->_dev_open(%s): invalid assumption\n", f->file_name); + return 1; + } + + fwrap = &xd->files[f->fileno]; + + err = pthread_mutex_lock(&g_serialize); + if (err) { + log_err("ioeng->_dev_open(%s): pthread_mutex_lock(), err(%d)\n", f->file_name, + err); + return -err; + } + + fwrap->dev = xnvme_dev_open(f->file_name, &opts); + if (!fwrap->dev) { + log_err("ioeng->_dev_open(%s): xnvme_dev_open(), err(%d)\n", f->file_name, errno); + goto failure; + } + fwrap->geo = xnvme_dev_get_geo(fwrap->dev); + + if (xnvme_queue_init(fwrap->dev, td->o.iodepth, flags, &(fwrap->queue))) { + log_err("ioeng->_dev_open(%s): xnvme_queue_init(), err(?)\n", f->file_name); + goto failure; + } + xnvme_queue_set_cb(fwrap->queue, cb_pool, NULL); + + fwrap->ssw = xnvme_dev_get_ssw(fwrap->dev); + fwrap->lba_nbytes = fwrap->geo->lba_nbytes; + + fwrap->fio_file = f; + fwrap->fio_file->filetype = FIO_TYPE_BLOCK; + fwrap->fio_file->real_file_size = fwrap->geo->tbytes; + fio_file_set_size_known(fwrap->fio_file); + + err = pthread_mutex_unlock(&g_serialize); + if (err) + log_err("ioeng->_dev_open(%s): pthread_mutex_unlock(), err(%d)\n", f->file_name, + err); + + return 0; + +failure: + xnvme_queue_term(fwrap->queue); + xnvme_dev_close(fwrap->dev); + + err = pthread_mutex_unlock(&g_serialize); + if (err) + log_err("ioeng->_dev_open(%s): pthread_mutex_unlock(), err(%d)\n", f->file_name, + err); + + return 1; +} + +static int xnvme_fioe_init(struct thread_data *td) +{ + struct xnvme_fioe_data *xd = NULL; + struct fio_file *f; + unsigned int i; + + if (!td->o.use_thread) { + log_err("ioeng->init(): --thread=1 is required\n"); + return 1; + } + + /* Allocate xd and iocq */ + xd = calloc(1, sizeof(*xd) + sizeof(*xd->files) * td->o.nr_files); + if (!xd) { + log_err("ioeng->init(): !calloc(), err(%d)\n", errno); + return 1; + } + + xd->iocq = calloc(td->o.iodepth, sizeof(struct io_u *)); + if (!xd->iocq) { + log_err("ioeng->init(): !calloc(), err(%d)\n", errno); + return 1; + } + + xd->iovec = calloc(td->o.iodepth, sizeof(*xd->iovec)); + if (!xd->iovec) { + log_err("ioeng->init(): !calloc(xd->iovec), err(%d)\n", errno); + return 1; + } + + xd->prev = -1; + td->io_ops_data = xd; + + for_each_file(td, f, i) + { + if (_dev_open(td, f)) { + log_err("ioeng->init(): failed; _dev_open(%s)\n", f->file_name); + return 1; + } + + ++(xd->nallocated); + } + + if (xd->nallocated != td->o.nr_files) { + log_err("ioeng->init(): failed; nallocated != td->o.nr_files\n"); + return 1; + } + + return 0; +} + +/* NOTE: using the first device for buffer-allocators) */ +static int xnvme_fioe_iomem_alloc(struct thread_data *td, size_t total_mem) +{ + struct xnvme_fioe_data *xd = td->io_ops_data; + struct xnvme_fioe_fwrap *fwrap = &xd->files[0]; + + if (!fwrap->dev) { + log_err("ioeng->iomem_alloc(): failed; no dev-handle\n"); + return 1; + } + + td->orig_buffer = xnvme_buf_alloc(fwrap->dev, total_mem); + + return td->orig_buffer == NULL; +} + +/* NOTE: using the first device for buffer-allocators) */ +static void xnvme_fioe_iomem_free(struct thread_data *td) +{ + struct xnvme_fioe_data *xd = td->io_ops_data; + struct xnvme_fioe_fwrap *fwrap = &xd->files[0]; + + if (!fwrap->dev) { + log_err("ioeng->iomem_free(): failed no dev-handle\n"); + return; + } + + xnvme_buf_free(fwrap->dev, td->orig_buffer); +} + +static int xnvme_fioe_io_u_init(struct thread_data *td, struct io_u *io_u) +{ + io_u->mmap_data = td->io_ops_data; + + return 0; +} + +static void xnvme_fioe_io_u_free(struct thread_data *td, struct io_u *io_u) +{ + io_u->mmap_data = NULL; +} + +static struct io_u *xnvme_fioe_event(struct thread_data *td, int event) +{ + struct xnvme_fioe_data *xd = td->io_ops_data; + + assert(event >= 0); + assert((unsigned)event < xd->completed); + + return xd->iocq[event]; +} + +static int xnvme_fioe_getevents(struct thread_data *td, unsigned int min, unsigned int max, + const struct timespec *t) +{ + struct xnvme_fioe_data *xd = td->io_ops_data; + struct xnvme_fioe_fwrap *fwrap = NULL; + int nfiles = xd->nallocated; + int err = 0; + + if (xd->prev != -1 && ++xd->prev < nfiles) { + fwrap = &xd->files[xd->prev]; + xd->cur = xd->prev; + } + + xd->completed = 0; + for (;;) { + if (fwrap == NULL || xd->cur == nfiles) { + fwrap = &xd->files[0]; + xd->cur = 0; + } + + while (fwrap != NULL && xd->cur < nfiles && err >= 0) { + err = xnvme_queue_poke(fwrap->queue, max - xd->completed); + if (err < 0) { + switch (err) { + case -EBUSY: + case -EAGAIN: + usleep(1); + break; + + default: + log_err("ioeng->getevents(): unhandled IO error\n"); + assert(false); + return 0; + } + } + if (xd->completed >= min) { + xd->prev = xd->cur; + return xd->completed; + } + xd->cur++; + fwrap = &xd->files[xd->cur]; + + if (err < 0) { + switch (err) { + case -EBUSY: + case -EAGAIN: + usleep(1); + break; + } + } + } + } + + xd->cur = 0; + + return xd->completed; +} + +static enum fio_q_status xnvme_fioe_queue(struct thread_data *td, struct io_u *io_u) +{ + struct xnvme_fioe_data *xd = td->io_ops_data; + struct xnvme_fioe_fwrap *fwrap; + struct xnvme_cmd_ctx *ctx; + uint32_t nsid; + uint64_t slba; + uint16_t nlb; + int err; + bool vectored_io = ((struct xnvme_fioe_options *)td->eo)->xnvme_iovec; + + fio_ro_check(td, io_u); + + fwrap = &xd->files[io_u->file->fileno]; + nsid = xnvme_dev_get_nsid(fwrap->dev); + + slba = io_u->offset >> fwrap->ssw; + nlb = (io_u->xfer_buflen >> fwrap->ssw) - 1; + + ctx = xnvme_queue_get_cmd_ctx(fwrap->queue); + ctx->async.cb_arg = io_u; + + ctx->cmd.common.nsid = nsid; + ctx->cmd.nvm.slba = slba; + ctx->cmd.nvm.nlb = nlb; + + switch (io_u->ddir) { + case DDIR_READ: + ctx->cmd.common.opcode = XNVME_SPEC_NVM_OPC_READ; + break; + + case DDIR_WRITE: + ctx->cmd.common.opcode = XNVME_SPEC_NVM_OPC_WRITE; + break; + + default: + log_err("ioeng->queue(): ENOSYS: %u\n", io_u->ddir); + err = -1; + assert(false); + break; + } + + if (vectored_io) { + xd->iovec[io_u->index].iov_base = io_u->xfer_buf; + xd->iovec[io_u->index].iov_len = io_u->xfer_buflen; + + err = xnvme_cmd_passv(ctx, &xd->iovec[io_u->index], 1, io_u->xfer_buflen, NULL, 0, + 0); + } else { + err = xnvme_cmd_pass(ctx, io_u->xfer_buf, io_u->xfer_buflen, NULL, 0); + } + switch (err) { + case 0: + return FIO_Q_QUEUED; + + case -EBUSY: + case -EAGAIN: + xnvme_queue_put_cmd_ctx(ctx->async.queue, ctx); + return FIO_Q_BUSY; + + default: + log_err("ioeng->queue(): err: '%d'\n", err); + + xnvme_queue_put_cmd_ctx(ctx->async.queue, ctx); + + io_u->error = abs(err); + assert(false); + return FIO_Q_COMPLETED; + } +} + +static int xnvme_fioe_close(struct thread_data *td, struct fio_file *f) +{ + struct xnvme_fioe_data *xd = td->io_ops_data; + + dprint(FD_FILE, "xnvme close %s -- nopen: %ld\n", f->file_name, xd->nopen); + + --(xd->nopen); + + return 0; +} + +static int xnvme_fioe_open(struct thread_data *td, struct fio_file *f) +{ + struct xnvme_fioe_data *xd = td->io_ops_data; + + dprint(FD_FILE, "xnvme open %s -- nopen: %ld\n", f->file_name, xd->nopen); + + if (f->fileno > (int)xd->nallocated) { + log_err("ioeng->open(): f->fileno > xd->nallocated; invalid assumption\n"); + return 1; + } + if (xd->files[f->fileno].fio_file != f) { + log_err("ioeng->open(): fio_file != f; invalid assumption\n"); + return 1; + } + + ++(xd->nopen); + + return 0; +} + +static int xnvme_fioe_invalidate(struct thread_data *td, struct fio_file *f) +{ + /* Consider only doing this with be:spdk */ + return 0; +} + +static int xnvme_fioe_get_max_open_zones(struct thread_data *td, struct fio_file *f, + unsigned int *max_open_zones) +{ + struct xnvme_opts opts = xnvme_opts_from_fioe(td); + struct xnvme_dev *dev; + const struct xnvme_spec_znd_idfy_ns *zns; + int err = 0, err_lock; + + if (f->filetype != FIO_TYPE_FILE && f->filetype != FIO_TYPE_BLOCK && + f->filetype != FIO_TYPE_CHAR) { + log_info("ioeng->get_max_open_zoned(): ignoring filetype: %d\n", f->filetype); + return 0; + } + err_lock = pthread_mutex_lock(&g_serialize); + if (err_lock) { + log_err("ioeng->get_max_open_zones(): pthread_mutex_lock(), err(%d)\n", err_lock); + return -err_lock; + } + + dev = xnvme_dev_open(f->file_name, &opts); + if (!dev) { + log_err("ioeng->get_max_open_zones(): xnvme_dev_open(), err(%d)\n", err_lock); + err = -errno; + goto exit; + } + if (xnvme_dev_get_geo(dev)->type != XNVME_GEO_ZONED) { + errno = EINVAL; + err = -errno; + goto exit; + } + + zns = (void *)xnvme_dev_get_ns_css(dev); + if (!zns) { + log_err("ioeng->get_max_open_zones(): xnvme_dev_get_ns_css(), err(%d)\n", errno); + err = -errno; + goto exit; + } + + /* + * intentional overflow as the value is zero-based and NVMe + * defines 0xFFFFFFFF as unlimited thus overflowing to 0 which + * is how fio indicates unlimited and otherwise just converting + * to one-based. + */ + *max_open_zones = zns->mor + 1; + +exit: + xnvme_dev_close(dev); + err_lock = pthread_mutex_unlock(&g_serialize); + if (err_lock) + log_err("ioeng->get_max_open_zones(): pthread_mutex_unlock(), err(%d)\n", + err_lock); + + return err; +} + +/** + * Currently, this function is called before of I/O engine initialization, so, + * we cannot consult the file-wrapping done when 'fioe' initializes. + * Instead we just open based on the given filename. + * + * TODO: unify the different setup methods, consider keeping the handle around, + * and consider how to support the --be option in this usecase + */ +static int xnvme_fioe_get_zoned_model(struct thread_data *td, struct fio_file *f, + enum zbd_zoned_model *model) +{ + struct xnvme_opts opts = xnvme_opts_from_fioe(td); + struct xnvme_dev *dev; + int err = 0, err_lock; + + if (f->filetype != FIO_TYPE_FILE && f->filetype != FIO_TYPE_BLOCK && + f->filetype != FIO_TYPE_CHAR) { + log_info("ioeng->get_zoned_model(): ignoring filetype: %d\n", f->filetype); + return -EINVAL; + } + + err = pthread_mutex_lock(&g_serialize); + if (err) { + log_err("ioeng->get_zoned_model(): pthread_mutex_lock(), err(%d)\n", err); + return -err; + } + + dev = xnvme_dev_open(f->file_name, &opts); + if (!dev) { + log_err("ioeng->get_zoned_model(): xnvme_dev_open(%s) failed, errno: %d\n", + f->file_name, errno); + err = -errno; + goto exit; + } + + switch (xnvme_dev_get_geo(dev)->type) { + case XNVME_GEO_UNKNOWN: + dprint(FD_ZBD, "%s: got 'unknown', assigning ZBD_NONE\n", f->file_name); + *model = ZBD_NONE; + break; + + case XNVME_GEO_CONVENTIONAL: + dprint(FD_ZBD, "%s: got 'conventional', assigning ZBD_NONE\n", f->file_name); + *model = ZBD_NONE; + break; + + case XNVME_GEO_ZONED: + dprint(FD_ZBD, "%s: got 'zoned', assigning ZBD_HOST_MANAGED\n", f->file_name); + *model = ZBD_HOST_MANAGED; + break; + + default: + dprint(FD_ZBD, "%s: hit-default, assigning ZBD_NONE\n", f->file_name); + *model = ZBD_NONE; + errno = EINVAL; + err = -errno; + break; + } + +exit: + xnvme_dev_close(dev); + + err_lock = pthread_mutex_unlock(&g_serialize); + if (err_lock) + log_err("ioeng->get_zoned_model(): pthread_mutex_unlock(), err(%d)\n", err_lock); + + return err; +} + +/** + * Fills the given ``zbdz`` with at most ``nr_zones`` zone-descriptors. + * + * The implementation converts the NVMe Zoned Command Set log-pages for Zone + * descriptors into the Linux Kernel Zoned Block Report format. + * + * NOTE: This function is called before I/O engine initialization, that is, + * before ``_dev_open`` has been called and file-wrapping is setup. Thus is has + * to do the ``_dev_open`` itself, and shut it down again once it is done + * retrieving the log-pages and converting them to the report format. + * + * TODO: unify the different setup methods, consider keeping the handle around, + * and consider how to support the --async option in this usecase + */ +static int xnvme_fioe_report_zones(struct thread_data *td, struct fio_file *f, uint64_t offset, + struct zbd_zone *zbdz, unsigned int nr_zones) +{ + struct xnvme_opts opts = xnvme_opts_from_fioe(td); + const struct xnvme_spec_znd_idfy_lbafe *lbafe = NULL; + struct xnvme_dev *dev = NULL; + const struct xnvme_geo *geo = NULL; + struct xnvme_znd_report *rprt = NULL; + uint32_t ssw; + uint64_t slba; + unsigned int limit = 0; + int err = 0, err_lock; + + dprint(FD_ZBD, "%s: report_zones() offset: %zu, nr_zones: %u\n", f->file_name, offset, + nr_zones); + + err = pthread_mutex_lock(&g_serialize); + if (err) { + log_err("ioeng->report_zones(%s): pthread_mutex_lock(), err(%d)\n", f->file_name, + err); + return -err; + } + + dev = xnvme_dev_open(f->file_name, &opts); + if (!dev) { + log_err("ioeng->report_zones(%s): xnvme_dev_open(), err(%d)\n", f->file_name, + errno); + goto exit; + } + + geo = xnvme_dev_get_geo(dev); + ssw = xnvme_dev_get_ssw(dev); + lbafe = xnvme_znd_dev_get_lbafe(dev); + + limit = nr_zones > geo->nzone ? geo->nzone : nr_zones; + + dprint(FD_ZBD, "%s: limit: %u\n", f->file_name, limit); + + slba = ((offset >> ssw) / geo->nsect) * geo->nsect; + + rprt = xnvme_znd_report_from_dev(dev, slba, limit, 0); + if (!rprt) { + log_err("ioeng->report_zones(%s): xnvme_znd_report_from_dev(), err(%d)\n", + f->file_name, errno); + err = -errno; + goto exit; + } + if (rprt->nentries != limit) { + log_err("ioeng->report_zones(%s): nentries != nr_zones\n", f->file_name); + err = 1; + goto exit; + } + if (offset > geo->tbytes) { + log_err("ioeng->report_zones(%s): out-of-bounds\n", f->file_name); + goto exit; + } + + /* Transform the zone-report */ + for (uint32_t idx = 0; idx < rprt->nentries; ++idx) { + struct xnvme_spec_znd_descr *descr = XNVME_ZND_REPORT_DESCR(rprt, idx); + + zbdz[idx].start = descr->zslba << ssw; + zbdz[idx].len = lbafe->zsze << ssw; + zbdz[idx].capacity = descr->zcap << ssw; + zbdz[idx].wp = descr->wp << ssw; + + switch (descr->zt) { + case XNVME_SPEC_ZND_TYPE_SEQWR: + zbdz[idx].type = ZBD_ZONE_TYPE_SWR; + break; + + default: + log_err("ioeng->report_zones(%s): invalid type for zone at offset(%zu)\n", + f->file_name, zbdz[idx].start); + err = -EIO; + goto exit; + } + + switch (descr->zs) { + case XNVME_SPEC_ZND_STATE_EMPTY: + zbdz[idx].cond = ZBD_ZONE_COND_EMPTY; + break; + case XNVME_SPEC_ZND_STATE_IOPEN: + zbdz[idx].cond = ZBD_ZONE_COND_IMP_OPEN; + break; + case XNVME_SPEC_ZND_STATE_EOPEN: + zbdz[idx].cond = ZBD_ZONE_COND_EXP_OPEN; + break; + case XNVME_SPEC_ZND_STATE_CLOSED: + zbdz[idx].cond = ZBD_ZONE_COND_CLOSED; + break; + case XNVME_SPEC_ZND_STATE_FULL: + zbdz[idx].cond = ZBD_ZONE_COND_FULL; + break; + + case XNVME_SPEC_ZND_STATE_RONLY: + case XNVME_SPEC_ZND_STATE_OFFLINE: + default: + zbdz[idx].cond = ZBD_ZONE_COND_OFFLINE; + break; + } + } + +exit: + xnvme_buf_virt_free(rprt); + + xnvme_dev_close(dev); + + err_lock = pthread_mutex_unlock(&g_serialize); + if (err_lock) + log_err("ioeng->report_zones(): pthread_mutex_unlock(), err: %d\n", err_lock); + + dprint(FD_ZBD, "err: %d, nr_zones: %d\n", err, (int)nr_zones); + + return err ? err : (int)limit; +} + +/** + * NOTE: This function may get called before I/O engine initialization, that is, + * before ``_dev_open`` has been called and file-wrapping is setup. In such + * case it has to do ``_dev_open`` itself, and shut it down again once it is + * done resetting write pointer of zones. + */ +static int xnvme_fioe_reset_wp(struct thread_data *td, struct fio_file *f, uint64_t offset, + uint64_t length) +{ + struct xnvme_opts opts = xnvme_opts_from_fioe(td); + struct xnvme_fioe_data *xd = NULL; + struct xnvme_fioe_fwrap *fwrap = NULL; + struct xnvme_dev *dev = NULL; + const struct xnvme_geo *geo = NULL; + uint64_t first, last; + uint32_t ssw; + uint32_t nsid; + int err = 0, err_lock; + + if (td->io_ops_data) { + xd = td->io_ops_data; + fwrap = &xd->files[f->fileno]; + + assert(fwrap->dev); + assert(fwrap->geo); + + dev = fwrap->dev; + geo = fwrap->geo; + ssw = fwrap->ssw; + } else { + err = pthread_mutex_lock(&g_serialize); + if (err) { + log_err("ioeng->reset_wp(): pthread_mutex_lock(), err(%d)\n", err); + return -err; + } + + dev = xnvme_dev_open(f->file_name, &opts); + if (!dev) { + log_err("ioeng->reset_wp(): xnvme_dev_open(%s) failed, errno(%d)\n", + f->file_name, errno); + goto exit; + } + geo = xnvme_dev_get_geo(dev); + ssw = xnvme_dev_get_ssw(dev); + } + + nsid = xnvme_dev_get_nsid(dev); + + first = ((offset >> ssw) / geo->nsect) * geo->nsect; + last = (((offset + length) >> ssw) / geo->nsect) * geo->nsect; + dprint(FD_ZBD, "first: 0x%lx, last: 0x%lx\n", first, last); + + for (uint64_t zslba = first; zslba < last; zslba += geo->nsect) { + struct xnvme_cmd_ctx ctx = xnvme_cmd_ctx_from_dev(dev); + + if (zslba >= (geo->nsect * geo->nzone)) { + log_err("ioeng->reset_wp(): out-of-bounds\n"); + err = 0; + break; + } + + err = xnvme_znd_mgmt_send(&ctx, nsid, zslba, false, + XNVME_SPEC_ZND_CMD_MGMT_SEND_RESET, 0x0, NULL); + if (err || xnvme_cmd_ctx_cpl_status(&ctx)) { + err = err ? err : -EIO; + log_err("ioeng->reset_wp(): err(%d), sc(%d)", err, ctx.cpl.status.sc); + goto exit; + } + } + +exit: + if (!td->io_ops_data) { + xnvme_dev_close(dev); + + err_lock = pthread_mutex_unlock(&g_serialize); + if (err_lock) + log_err("ioeng->reset_wp(): pthread_mutex_unlock(), err(%d)\n", err_lock); + } + + return err; +} + +static int xnvme_fioe_get_file_size(struct thread_data *td, struct fio_file *f) +{ + struct xnvme_opts opts = xnvme_opts_from_fioe(td); + struct xnvme_dev *dev; + int ret = 0, err; + + if (fio_file_size_known(f)) + return 0; + + ret = pthread_mutex_lock(&g_serialize); + if (ret) { + log_err("ioeng->reset_wp(): pthread_mutex_lock(), err(%d)\n", ret); + return -ret; + } + + dev = xnvme_dev_open(f->file_name, &opts); + if (!dev) { + log_err("%s: failed retrieving device handle, errno: %d\n", f->file_name, errno); + ret = -errno; + goto exit; + } + + f->real_file_size = xnvme_dev_get_geo(dev)->tbytes; + fio_file_set_size_known(f); + f->filetype = FIO_TYPE_BLOCK; + +exit: + xnvme_dev_close(dev); + err = pthread_mutex_unlock(&g_serialize); + if (err) + log_err("ioeng->reset_wp(): pthread_mutex_unlock(), err(%d)\n", err); + + return ret; +} + +FIO_STATIC struct ioengine_ops ioengine = { + .name = "xnvme", + .version = FIO_IOOPS_VERSION, + .options = options, + .option_struct_size = sizeof(struct xnvme_fioe_options), + .flags = FIO_DISKLESSIO | FIO_NODISKUTIL | FIO_NOEXTEND | FIO_MEMALIGN | FIO_RAWIO, + + .cleanup = xnvme_fioe_cleanup, + .init = xnvme_fioe_init, + + .iomem_free = xnvme_fioe_iomem_free, + .iomem_alloc = xnvme_fioe_iomem_alloc, + + .io_u_free = xnvme_fioe_io_u_free, + .io_u_init = xnvme_fioe_io_u_init, + + .event = xnvme_fioe_event, + .getevents = xnvme_fioe_getevents, + .queue = xnvme_fioe_queue, + + .close_file = xnvme_fioe_close, + .open_file = xnvme_fioe_open, + .get_file_size = xnvme_fioe_get_file_size, + + .invalidate = xnvme_fioe_invalidate, + .get_max_open_zones = xnvme_fioe_get_max_open_zones, + .get_zoned_model = xnvme_fioe_get_zoned_model, + .report_zones = xnvme_fioe_report_zones, + .reset_wp = xnvme_fioe_reset_wp, +}; + +static void fio_init fio_xnvme_register(void) +{ + register_ioengine(&ioengine); +} + +static void fio_exit fio_xnvme_unregister(void) +{ + unregister_ioengine(&ioengine); +} diff --git a/optgroup.h b/optgroup.h index 3ac8f62a81..dc73c8f3ea 100644 --- a/optgroup.h +++ b/optgroup.h @@ -72,6 +72,7 @@ enum opt_category_group { __FIO_OPT_G_DFS, __FIO_OPT_G_NFS, __FIO_OPT_G_WINDOWSAIO, + __FIO_OPT_G_XNVME, FIO_OPT_G_RATE = (1ULL << __FIO_OPT_G_RATE), FIO_OPT_G_ZONE = (1ULL << __FIO_OPT_G_ZONE), @@ -118,6 +119,7 @@ enum opt_category_group { FIO_OPT_G_LIBCUFILE = (1ULL << __FIO_OPT_G_LIBCUFILE), FIO_OPT_G_DFS = (1ULL << __FIO_OPT_G_DFS), FIO_OPT_G_WINDOWSAIO = (1ULL << __FIO_OPT_G_WINDOWSAIO), + FIO_OPT_G_XNVME = (1ULL << __FIO_OPT_G_XNVME), }; extern const struct opt_group *opt_group_from_mask(uint64_t *mask); diff --git a/options.c b/options.c index 3b83573bd8..2b183c60d0 100644 --- a/options.c +++ b/options.c @@ -2144,6 +2144,11 @@ struct fio_option fio_options[FIO_MAX_OPTS] = { { .ival = "nfs", .help = "NFS IO engine", }, +#endif +#ifdef CONFIG_LIBXNVME + { .ival = "xnvme", + .help = "XNVME IO engine", + }, #endif }, }, From 454154e66e93b3dc314955c197a21eeacbe69c78 Mon Sep 17 00:00:00 2001 From: Ankit Kumar Date: Wed, 11 May 2022 22:00:18 +0530 Subject: [PATCH 0159/1097] docs: documentation for xnvme ioengine Signed-off-by: Ankit Kumar Link: https://lore.kernel.org/r/20220511163019.5608-3-ankit.kumar@samsung.com Signed-off-by: Jens Axboe --- HOWTO.rst | 55 +++++++++++++++++++++++++++++++++++++++++-- fio.1 | 70 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 121 insertions(+), 4 deletions(-) diff --git a/HOWTO.rst b/HOWTO.rst index 6a3e09f519..84bea5c5ba 100644 --- a/HOWTO.rst +++ b/HOWTO.rst @@ -2171,6 +2171,12 @@ I/O engine **exec** Execute 3rd party tools. Could be used to perform monitoring during jobs runtime. + **xnvme** + I/O engine using the xNVMe C API, for NVMe devices. The xnvme engine provides + flexibility to access GNU/Linux Kernel NVMe driver via libaio, IOCTLs, io_uring, + the SPDK NVMe driver, or your own custom NVMe driver. The xnvme engine includes + engine specific options. (See https://xnvme.io). + I/O engine specific parameters ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -2260,7 +2266,7 @@ with the caveat that when used on the command line, they must come after the making the submission and completion part more lightweight. Required for the below :option:`sqthread_poll` option. -.. option:: sqthread_poll : [io_uring] +.. option:: sqthread_poll : [io_uring] [xnvme] Normally fio will submit IO by issuing a system call to notify the kernel of available items in the SQ ring. If this option is set, the @@ -2275,7 +2281,7 @@ with the caveat that when used on the command line, they must come after the .. option:: hipri - [io_uring] + [io_uring], [xnvme] If this option is set, fio will attempt to use polled IO completions. Normal IO completions generate interrupts to signal the completion of @@ -2725,6 +2731,51 @@ with the caveat that when used on the command line, they must come after the If set, stdout and stderr streams are redirected to files named from the job name. Default is true. +.. option:: xnvme_async=str : [xnvme] + + Select the xnvme async command interface. This can take these values. + + **emu** + This is default and used to emulate asynchronous I/O. + **thrpool** + Use thread pool for Asynchronous I/O. + **io_uring** + Use Linux io_uring/liburing for Asynchronous I/O. + **libaio** + Use Linux aio for Asynchronous I/O. + **posix** + Use POSIX aio for Asynchronous I/O. + **nil** + Use nil-io; For introspective perf. evaluation + +.. option:: xnvme_sync=str : [xnvme] + + Select the xnvme synchronous command interface. This can take these values. + + **nvme** + This is default and uses Linux NVMe Driver ioctl() for synchronous I/O. + **psync** + Use pread()/write() for synchronous I/O. + +.. option:: xnvme_admin=str : [xnvme] + + Select the xnvme admin command interface. This can take these values. + + **nvme** + This is default and uses linux NVMe Driver ioctl() for admin commands. + **block** + Use Linux Block Layer ioctl() and sysfs for admin commands. + **file_as_ns** + Use file-stat to construct NVMe idfy responses. + +.. option:: xnvme_dev_nsid=int : [xnvme] + + xnvme namespace identifier, for userspace NVMe driver. + +.. option:: xnvme_iovec=int : [xnvme] + + If this option is set. xnvme will use vectored read/write commands. + I/O depth ~~~~~~~~~ diff --git a/fio.1 b/fio.1 index 609947dc41..ded7bbfc99 100644 --- a/fio.1 +++ b/fio.1 @@ -1965,6 +1965,12 @@ via kernel NFS. .TP .B exec Execute 3rd party tools. Could be used to perform monitoring during jobs runtime. +.TP +.B xnvme +I/O engine using the xNVMe C API, for NVMe devices. The xnvme engine provides +flexibility to access GNU/Linux Kernel NVMe driver via libaio, IOCTLs, io_uring, +the SPDK NVMe driver, or your own custom NVMe driver. The xnvme engine includes +engine specific options. (See \fIhttps://xnvme.io/\fR). .SS "I/O engine specific parameters" In addition, there are some parameters which are only valid when a specific \fBioengine\fR is in use. These are used identically to normal parameters, @@ -2039,7 +2045,7 @@ release them when IO is done. If this option is set, the pages are pre-mapped before IO is started. This eliminates the need to map and release for each IO. This is more efficient, and reduces the IO latency as well. .TP -.BI (io_uring)hipri +.BI (io_uring,xnvme)hipri If this option is set, fio will attempt to use polled IO completions. Normal IO completions generate interrupts to signal the completion of IO, polled completions do not. Hence they are require active reaping by the application. @@ -2052,7 +2058,7 @@ This avoids the overhead of managing file counts in the kernel, making the submission and completion part more lightweight. Required for the below sqthread_poll option. .TP -.BI (io_uring)sqthread_poll +.BI (io_uring,xnvme)sqthread_poll Normally fio will submit IO by issuing a system call to notify the kernel of available items in the SQ ring. If this option is set, the act of submitting IO will be done by a polling thread in the kernel. This frees up cycles for fio, at @@ -2480,6 +2486,66 @@ Defines the time between the SIGTERM and SIGKILL signals. Default is 1 second. .TP .BI (exec)std_redirect\fR=\fbool If set, stdout and stderr streams are redirected to files named from the job name. Default is true. +.TP +.BI (xnvme)xnvme_async\fR=\fPstr +Select the xnvme async command interface. This can take these values. +.RS +.RS +.TP +.B emu +This is default and used to emulate asynchronous I/O +.TP +.BI thrpool +Use thread pool for Asynchronous I/O +.TP +.BI io_uring +Use Linux io_uring/liburing for Asynchronous I/O +.TP +.BI libaio +Use Linux aio for Asynchronous I/O +.TP +.BI posix +Use POSIX aio for Asynchronous I/O +.TP +.BI nil +Use nil-io; For introspective perf. evaluation +.RE +.RE +.TP +.BI (xnvme)xnvme_sync\fR=\fPstr +Select the xnvme synchronous command interface. This can take these values. +.RS +.RS +.TP +.B nvme +This is default and uses Linux NVMe Driver ioctl() for synchronous I/O +.TP +.BI psync +Use pread()/write() for synchronous I/O +.RE +.RE +.TP +.BI (xnvme)xnvme_admin\fR=\fPstr +Select the xnvme admin command interface. This can take these values. +.RS +.RS +.TP +.B nvme +This is default and uses Linux NVMe Driver ioctl() for admin commands +.TP +.BI block +Use Linux Block Layer ioctl() and sysfs for admin commands +.TP +.BI file_as_ns +Use file-stat as to construct NVMe idfy responses +.RE +.RE +.TP +.BI (xnvme)xnvme_dev_nsid\fR=\fPint +xnvme namespace identifier, for userspace NVMe driver. +.TP +.BI (xnvme)xnvme_iovec +If this option is set, xnvme will use vectored read/write commands. .SS "I/O depth" .TP .BI iodepth \fR=\fPint From d338e8760f3f442b3e4498598854130e55745eb9 Mon Sep 17 00:00:00 2001 From: Ankit Kumar Date: Wed, 11 May 2022 22:00:19 +0530 Subject: [PATCH 0160/1097] examples: add example job file for xnvme engine usage Co-Authored-By: Ankit Kumar Co-Authored-By: Simon A. F. Lund Link: https://lore.kernel.org/r/20220511163019.5608-4-ankit.kumar@samsung.com Signed-off-by: Jens Axboe --- examples/xnvme-compare.fio | 72 +++++++++++++++++++++++++++++++ examples/xnvme-zoned.fio | 87 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 159 insertions(+) create mode 100644 examples/xnvme-compare.fio create mode 100644 examples/xnvme-zoned.fio diff --git a/examples/xnvme-compare.fio b/examples/xnvme-compare.fio new file mode 100644 index 0000000000..b89dfdf4db --- /dev/null +++ b/examples/xnvme-compare.fio @@ -0,0 +1,72 @@ +; Compare fio IO engines with a random-read workload using BS=4k at QD=1 +; +; README +; +; This job-file is intended to be used as: +; +; # Use the built-in io_uring engine to get baseline numbers +; fio examples/xnvme-compare.fio \ +; --section=default \ +; --ioengine=io_uring \ +; --sqthread_poll=1 \ +; --filename=/dev/nvme0n1 +; +; # Use the xNVMe io-engine engine with Linux backend and io_uring async. impl. +; fio examples/xnvme-compare.fio \ +; --section=default \ +; --ioengine=xnvme \ +; --sqthread_poll=1 \ +; --xnvme_async=io_uring \ +; --filename=/dev/nvme0n1 +; +; # Use the xNVMe io-engine engine with Linux backend and libaio async. impl. +; fio examples/xnvme-compare.fio \ +; --section=default \ +; --ioengine=xnvme \ +; --xnvme_async=libaio \ +; --filename=/dev/nvme0n1 +; +; # Use the xNVMe io-engine engine with SPDK backend, note that you have to set the Namespace-id +; fio examples/xnvme-compare.fio \ +; --section=default \ +; --ioengine=xnvme \ +; --xnvme_dev_nsid=1 \ +; --filename=0000\\:01\\:00.0 +; +; NOTE: The URI encoded in the filename above, the ":" must be escaped. +; +; On the command-line using two "\\": +; +; --filename=0000\\:01\\:00.0 +; +; Within a fio-script using a single "\": +; +; filename=0000\:01\:00.0 +; +; NOTE: If you want to override the default bs, iodepth, and workload, then +; invoke it as: +; +; FIO_BS="512" FIO_RW="verify" FIO_IODEPTH=16 fio examples/xnvme-compare.fio \ +; --section=override +; +[global] +rw=randread +size=12G +iodepth=1 +bs=4K +direct=1 +thread=1 +time_based=1 +runtime=7 +ramp_time=3 +norandommap=1 + +; Avoid accidentally creating device files; e.g. "/dev/nvme0n1", "/dev/nullb0" +allow_file_create=0 + +[default] + +[override] +rw=${FIO_RW} +iodepth=${FIO_IODEPTH} +bs=${FIO_BS} diff --git a/examples/xnvme-zoned.fio b/examples/xnvme-zoned.fio new file mode 100644 index 0000000000..1344f9a1c8 --- /dev/null +++ b/examples/xnvme-zoned.fio @@ -0,0 +1,87 @@ +; Running xNVMe/fio on a Zoned Device +; +; Writes 1GB at QD1 using 4K BS and verifies it. +; +; README +; +; This job-file is intended to be used as: +; +; # Use the built-in io_uring engine to get baseline numbers +; fio examples/xnvme-zoned.fio \ +; --section=default \ +; --ioengine=io_uring \ +; --sqthread_poll=1 \ +; --filename=/dev/nvme0n1 +; +; # Use the xNVMe io-engine engine with Linux backend and io_uring async. impl. +; fio examples/xnvme-zoned.fio \ +; --section=default \ +; --ioengine=xnvme \ +; --sqthread_poll=1 \ +; --xnvme_async=io_uring \ +; --filename=/dev/nvme0n1 +; +; # Use the xNVMe io-engine engine with Linux backend and libaio async. impl. +; fio examples/xnvme-zoned.fio \ +; --section=default \ +; --ioengine=xnvme \ +; --xnvme_async=libaio \ +; --filename=/dev/nvme0n1 +; +; # Use the xNVMe io-engine engine with SPDK backend, note that you have to set the Namespace-id +; fio examples/xnvme-zoned.fio \ +; --section=default \ +; --ioengine=xnvme \ +; --xnvme_dev_nsid=1 \ +; --filename=0000\\:01\\:00.0 +; +; NOTE: The URI encoded in the filename above, the ":" must be escaped. +; +; On the command-line using two "\\": +; +; --filename=0000\\:01\\:00.0 +; +; Within a fio-script using a single "\": +; +; filename=0000\:01\:00.0 +; +; NOTE: If you want to override the default bs, iodepth, and workload, then +; invoke it as: +; +; FIO_BS="512" FIO_RW="verify" FIO_IODEPTH=16 fio examples/xnvme-zoned.fio \ +; --section=override +; +; To reset all zones on the device to EMPTY state aka. wipe the entire device. +; +; # zoned mgmt-reset /dev/nvme0n2 --slba 0x0 --all +; +[global] +zonemode=zbd +rw=write +size=1G +iodepth=1 +bs=4K +direct=1 +thread=1 +ramp_time=1 +norandommap=1 +verify=crc32c +; Avoid accidentally creating device files; e.g. "/dev/nvme0n1", "/dev/nullb0" +allow_file_create=0 +; +; NOTE: If fio complains about zone-size, then run: +; +; # zoned info /dev/nvme0n1 +; +; The command will provide the values you need, then in the fio-script define: +; +; zonesize=nsect * nbytes +; +;zonesize= + +[default] + +[override] +rw=${FIO_RW} +iodepth=${FIO_IODEPTH} +bs=${FIO_BS} From 74ee19043ebb12dd6b0aa243f8cdb7ccd63af857 Mon Sep 17 00:00:00 2001 From: Ammar Faizi Date: Thu, 12 May 2022 23:43:31 +0700 Subject: [PATCH 0161/1097] backend: Fix indentation Reviewed-by: Niklas Cassel Signed-off-by: Ammar Faizi Link: https://lore.kernel.org/r/20220512164333.46516-2-ammarfaizi2@gnuweeb.org Signed-off-by: Jens Axboe --- backend.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend.c b/backend.c index ffbb7e2a07..e5bb4e2590 100644 --- a/backend.c +++ b/backend.c @@ -2021,7 +2021,7 @@ static void reap_threads(unsigned int *nr_running, uint64_t *t_rate, for_each_td(td, i) { int flags = 0; - if (!strcmp(td->o.ioengine, "cpuio")) + if (!strcmp(td->o.ioengine, "cpuio")) cputhreads++; else realthreads++; From 6f1a24593c227a4f392f454698aca20e95f0006c Mon Sep 17 00:00:00 2001 From: Ammar Faizi Date: Thu, 12 May 2022 23:43:33 +0700 Subject: [PATCH 0162/1097] Makefile: Suppress `-Wimplicit-fallthrough` when compiling `lex.yy` lex.yy.c is an auto generated C file. When compiling with clang-15, we got the following warning: ``` CC lex.yy.o lex.yy.c:1444:5: warning: unannotated fall-through between switch labels [-Wimplicit-fallthrough] case EOB_ACT_END_OF_FILE: ^ lex.yy.c:1444:5: note: insert '__attribute__((fallthrough));' to silence this warning case EOB_ACT_END_OF_FILE: ^ __attribute__((fallthrough)); lex.yy.c:1444:5: note: insert 'break;' to avoid fall-through case EOB_ACT_END_OF_FILE: ^ break; 1 warning generated. ``` There is nothing we can do to fix lex.yy.c since it's an auto generated file. Fix this by appending `-Wno-implicit-fallthrough` when compiling this file if we have `-Wimplicit-fallthrough` flag enabled. Reviewed-by: Niklas Cassel Signed-off-by: Ammar Faizi Link: https://lore.kernel.org/r/20220512164333.46516-4-ammarfaizi2@gnuweeb.org Signed-off-by: Jens Axboe --- Makefile | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 8495e727ba..ed66305a20 100644 --- a/Makefile +++ b/Makefile @@ -535,8 +535,12 @@ else $(QUIET_LEX)$(LEX) $< endif +ifneq (,$(findstring -Wimplicit-fallthrough,$(CFLAGS))) +LEX_YY_CFLAGS := -Wno-implicit-fallthrough +endif + lex.yy.o: lex.yy.c y.tab.h - $(QUIET_CC)$(CC) -o $@ $(CFLAGS) $(CPPFLAGS) -c $< + $(QUIET_CC)$(CC) -o $@ $(CFLAGS) $(CPPFLAGS) $(LEX_YY_CFLAGS) -c $< y.tab.o: y.tab.c y.tab.h $(QUIET_CC)$(CC) -o $@ $(CFLAGS) $(CPPFLAGS) -c $< From b60ccee9a1f0ce878c7270938143a29e6d1eb108 Mon Sep 17 00:00:00 2001 From: "dennis.wu" Date: Sat, 21 May 2022 23:27:35 +0800 Subject: [PATCH 0163/1097] pmemblk.c: fix one logic bug - read always with write logic issue,if read success and return 0, then pmemblk_write called as well: if (io_u->ddir == DDIR_READ && 0 != pmemblk_read(pmb->pmb_pool, buf, off)) { io_u->error = errno; break; } else if (0 != pmemblk_write(pmb->pmb_pool, buf, off)) { io_u->error = errno; break; } Signed-off-by: dennis.wu --- engines/pmemblk.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/engines/pmemblk.c b/engines/pmemblk.c index fc6358e8e1..849d8a15a0 100644 --- a/engines/pmemblk.c +++ b/engines/pmemblk.c @@ -375,10 +375,11 @@ static enum fio_q_status fio_pmemblk_queue(struct thread_data *td, off /= pmb->pmb_bsize; len /= pmb->pmb_bsize; while (0 < len) { - if (io_u->ddir == DDIR_READ && - 0 != pmemblk_read(pmb->pmb_pool, buf, off)) { - io_u->error = errno; - break; + if (io_u->ddir == DDIR_READ) { + if (0 != pmemblk_read(pmb->pmb_pool, buf, off)) { + io_u->error = errno; + break; + } } else if (0 != pmemblk_write(pmb->pmb_pool, buf, off)) { io_u->error = errno; break; From b29472cbdbce66ad9905b58f04ac5ed9e9be0090 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Tue, 24 May 2022 14:23:24 +0000 Subject: [PATCH 0164/1097] steadystate: delete incorrect comment Fio actually does not begin collecting steady state data until the steady state ramp time has expired. Remove the comment that said steady state data is collected from the start of the job. Signed-off-by: Vincent Fu Link: https://lore.kernel.org/r/20220524142229.135808-2-vincent.fu@samsung.com Signed-off-by: Jens Axboe --- steadystate.c | 7 ------- 1 file changed, 7 deletions(-) diff --git a/steadystate.c b/steadystate.c index 2e3da1db0c..ad19318c2a 100644 --- a/steadystate.c +++ b/steadystate.c @@ -250,13 +250,6 @@ int steadystate_check(void) rate_time = mtime_since(&ss->prev_time, &now); memcpy(&ss->prev_time, &now, sizeof(now)); - /* - * Begin monitoring when job starts but don't actually use - * data in checking stopping criterion until ss->ramp_time is - * over. This ensures that we will have a sane value in - * prev_iops/bw the first time through after ss->ramp_time - * is done. - */ if (ss->state & FIO_SS_RAMP_OVER) { group_bw += 1000 * (td_bytes - ss->prev_bytes) / rate_time; group_iops += 1000 * (td_iops - ss->prev_iops) / rate_time; From 95f791d3ad7d0cf3b024d51713d4861663ba8ce5 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Tue, 24 May 2022 14:23:24 +0000 Subject: [PATCH 0165/1097] configure: refer to zlib1g-dev package for zlib support Recent Debian-based distributions provide zlib support in the zlib1g-dev package. Signed-off-by: Vincent Fu Link: https://lore.kernel.org/r/20220524142229.135808-3-vincent.fu@samsung.com Signed-off-by: Jens Axboe --- configure | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure b/configure index 95b60bb70a..4ee536a03b 100755 --- a/configure +++ b/configure @@ -3142,7 +3142,7 @@ if test "$libzbc" = "yes" ; then output_sym "CONFIG_LIBZBC" fi if test "$zlib" = "no" ; then - echo "Consider installing zlib-dev (zlib-devel, some fio features depend on it." + echo "Consider installing zlib1g-dev (zlib-devel) as some fio features depend on it." if test "$build_static" = "yes"; then echo "Note that some distros have separate packages for static libraries." fi From a3a6f1058aa41db20794bee1df0bec4019c14d42 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Tue, 24 May 2022 14:23:24 +0000 Subject: [PATCH 0166/1097] HOWTO: add blank line for prettier formatting There needs to be a blank line between the name of an option and its description in order for the formatting to look nice at https://fio.readthedocs.io/en/latest/fio_doc.html#cmdoption-arg-ignore-zone-limits, Signed-off-by: Vincent Fu Link: https://lore.kernel.org/r/20220524142229.135808-4-vincent.fu@samsung.com Signed-off-by: Jens Axboe --- HOWTO.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/HOWTO.rst b/HOWTO.rst index 84bea5c5ba..eee386c146 100644 --- a/HOWTO.rst +++ b/HOWTO.rst @@ -1064,6 +1064,7 @@ Target file/device thread/process. .. option:: ignore_zone_limits=bool + If this option is used, fio will ignore the maximum number of open zones limit of the zoned block device in use, thus allowing the option :option:`max_open_zones` value to be larger than the device From db2637ec09786c5610006414f28c2b21a3c3c165 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Tue, 24 May 2022 14:23:24 +0000 Subject: [PATCH 0167/1097] t/run-fio-tests: improve json data decoding Instead of skipping up to five lines, just skip everything until the opening curly brace when trying to decode JSON data. Signed-off-by: Vincent Fu Link: https://lore.kernel.org/r/20220524142229.135808-5-vincent.fu@samsung.com Signed-off-by: Jens Axboe --- t/run-fio-tests.py | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/t/run-fio-tests.py b/t/run-fio-tests.py index ecceb67e93..32cdbc1992 100755 --- a/t/run-fio-tests.py +++ b/t/run-fio-tests.py @@ -311,21 +311,15 @@ def check_result(self): # # Sometimes fio informational messages are included at the top of the # JSON output, especially under Windows. Try to decode output as JSON - # data, lopping off up to the first four lines + # data, skipping everything until the first { # lines = file_data.splitlines() - for i in range(5): - file_data = '\n'.join(lines[i:]) - try: - self.json_data = json.loads(file_data) - except json.JSONDecodeError: - continue - else: - logging.debug("Test %d: skipped %d lines decoding JSON data", self.testnum, i) - return - - self.failure_reason = "{0} unable to decode JSON data,".format(self.failure_reason) - self.passed = False + file_data = '\n'.join(lines[lines.index("{"):]) + try: + self.json_data = json.loads(file_data) + except json.JSONDecodeError: + self.failure_reason = "{0} unable to decode JSON data,".format(self.failure_reason) + self.passed = False class FioJobTest_t0005(FioJobTest): From cb8dcafa69ef6fddd22d41c60083cb68bce92daa Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Tue, 24 May 2022 14:23:24 +0000 Subject: [PATCH 0168/1097] docs: update discussion of huge page sizes Note that the default huge page size is either 2 or 4 MiB, depending on the platform. Also mention /sys/kernel/mm/hugepages/ as another place to see the supported huge page sizes. Signed-off-by: Vincent Fu Link: https://lore.kernel.org/r/20220524142229.135808-6-vincent.fu@samsung.com Signed-off-by: Jens Axboe --- HOWTO.rst | 25 ++++++++++++++----------- fio.1 | 19 ++++++++++--------- 2 files changed, 24 insertions(+), 20 deletions(-) diff --git a/HOWTO.rst b/HOWTO.rst index eee386c146..58d02fa24c 100644 --- a/HOWTO.rst +++ b/HOWTO.rst @@ -1823,13 +1823,14 @@ Buffers and memory **mmaphuge** to work, the system must have free huge pages allocated. This can normally be checked and set by reading/writing :file:`/proc/sys/vm/nr_hugepages` on a Linux system. Fio assumes a huge page - is 4MiB in size. So to calculate the number of huge pages you need for a - given job file, add up the I/O depth of all jobs (normally one unless - :option:`iodepth` is used) and multiply by the maximum bs set. Then divide - that number by the huge page size. You can see the size of the huge pages in - :file:`/proc/meminfo`. If no huge pages are allocated by having a non-zero - number in `nr_hugepages`, using **mmaphuge** or **shmhuge** will fail. Also - see :option:`hugepage-size`. + is 2 or 4MiB in size depending on the platform. So to calculate the + number of huge pages you need for a given job file, add up the I/O + depth of all jobs (normally one unless :option:`iodepth` is used) and + multiply by the maximum bs set. Then divide that number by the huge + page size. You can see the size of the huge pages in + :file:`/proc/meminfo`. If no huge pages are allocated by having a + non-zero number in `nr_hugepages`, using **mmaphuge** or **shmhuge** + will fail. Also see :option:`hugepage-size`. **mmaphuge** also needs to have hugetlbfs mounted and the file location should point there. So if it's mounted in :file:`/huge`, you would use @@ -1848,10 +1849,12 @@ Buffers and memory .. option:: hugepage-size=int - Defines the size of a huge page. Must at least be equal to the system - setting, see :file:`/proc/meminfo`. Defaults to 4MiB. Should probably - always be a multiple of megabytes, so using ``hugepage-size=Xm`` is the - preferred way to set this to avoid setting a non-pow-2 bad value. + Defines the size of a huge page. Must at least be equal to the system + setting, see :file:`/proc/meminfo` and + :file:`/sys/kernel/mm/hugepages/`. Defaults to 2 or 4MiB depending on + the platform. Should probably always be a multiple of megabytes, so + using ``hugepage-size=Xm`` is the preferred way to set this to avoid + setting a non-pow-2 bad value. .. option:: lockmem=int diff --git a/fio.1 b/fio.1 index ded7bbfc99..5f0575742c 100644 --- a/fio.1 +++ b/fio.1 @@ -1631,11 +1631,11 @@ multiplied by the I/O depth given. Note that for \fBshmhuge\fR and \fBmmaphuge\fR to work, the system must have free huge pages allocated. This can normally be checked and set by reading/writing `/proc/sys/vm/nr_hugepages' on a Linux system. Fio assumes a huge page -is 4MiB in size. So to calculate the number of huge pages you need for a -given job file, add up the I/O depth of all jobs (normally one unless -\fBiodepth\fR is used) and multiply by the maximum bs set. Then divide -that number by the huge page size. You can see the size of the huge pages in -`/proc/meminfo'. If no huge pages are allocated by having a non-zero +is 2 or 4MiB in size depending on the platform. So to calculate the number of +huge pages you need for a given job file, add up the I/O depth of all jobs +(normally one unless \fBiodepth\fR is used) and multiply by the maximum bs set. +Then divide that number by the huge page size. You can see the size of the huge +pages in `/proc/meminfo'. If no huge pages are allocated by having a non-zero number in `nr_hugepages', using \fBmmaphuge\fR or \fBshmhuge\fR will fail. Also see \fBhugepage\-size\fR. .P @@ -1655,10 +1655,11 @@ of subsequent I/O memory buffers is the sum of the \fBiomem_align\fR and \fBbs\fR used. .TP .BI hugepage\-size \fR=\fPint -Defines the size of a huge page. Must at least be equal to the system -setting, see `/proc/meminfo'. Defaults to 4MiB. Should probably -always be a multiple of megabytes, so using `hugepage\-size=Xm' is the -preferred way to set this to avoid setting a non-pow-2 bad value. +Defines the size of a huge page. Must at least be equal to the system setting, +see `/proc/meminfo' and `/sys/kernel/mm/hugepages/'. Defaults to 2 or 4MiB +depending on the platform. Should probably always be a multiple of megabytes, +so using `hugepage\-size=Xm' is the preferred way to set this to avoid setting +a non-pow-2 bad value. .TP .BI lockmem \fR=\fPint Pin the specified amount of memory with \fBmlock\fR\|(2). Can be used to From 873db854772d9e2870546bbb919f4baa10f8b0db Mon Sep 17 00:00:00 2001 From: liangmingyuan Date: Fri, 27 May 2022 02:49:16 +0800 Subject: [PATCH 0169/1097] engines/ceph: add option for setting config file path Specifies the configuration path of ceph cluster on rados test, so conf file does not have to be /etc/ceph/ceph.conf. To set the option, adding the next line to global section of fio: conf=path_to_ceph_config_file Signed-off-by: Mingyuan Liang --- HOWTO.rst | 5 +++++ engines/rados.c | 13 ++++++++++++- examples/rados.fio | 1 + fio.1 | 4 ++++ 4 files changed, 22 insertions(+), 1 deletion(-) diff --git a/HOWTO.rst b/HOWTO.rst index 84bea5c5ba..f7b9cc1742 100644 --- a/HOWTO.rst +++ b/HOWTO.rst @@ -2491,6 +2491,11 @@ with the caveat that when used on the command line, they must come after the the full *type.id* string. If no type. prefix is given, fio will add 'client.' by default. +.. option:: conf=str : [rados] + + Specifies the configuration path of ceph cluster, so conf file does not + have to be /etc/ceph/ceph.conf. + .. option:: busy_poll=bool : [rbd,rados] Poll store instead of waiting for completion. Usually this provides better diff --git a/engines/rados.c b/engines/rados.c index 976f9229b0..d0d15c5b54 100644 --- a/engines/rados.c +++ b/engines/rados.c @@ -37,6 +37,7 @@ struct rados_options { char *cluster_name; char *pool_name; char *client_name; + char *conf; int busy_poll; int touch_objects; }; @@ -69,6 +70,16 @@ static struct fio_option options[] = { .category = FIO_OPT_C_ENGINE, .group = FIO_OPT_G_RBD, }, + { + .name = "conf", + .lname = "ceph configuration file path", + .type = FIO_OPT_STR_STORE, + .help = "Path of the ceph configuration file", + .off1 = offsetof(struct rados_options, conf), + .def = "/etc/ceph/ceph.conf", + .category = FIO_OPT_C_ENGINE, + .group = FIO_OPT_G_RBD, + }, { .name = "busy_poll", .lname = "busy poll mode", @@ -184,7 +195,7 @@ static int _fio_rados_connect(struct thread_data *td) goto failed_early; } - r = rados_conf_read_file(rados->cluster, NULL); + r = rados_conf_read_file(rados->cluster, o->conf); if (r < 0) { log_err("rados_conf_read_file failed.\n"); goto failed_early; diff --git a/examples/rados.fio b/examples/rados.fio index 035cbff4ab..dd86f354c8 100644 --- a/examples/rados.fio +++ b/examples/rados.fio @@ -14,6 +14,7 @@ ioengine=rados clientname=admin pool=rados +conf=/etc/ceph/ceph.conf busy_poll=0 rw=randwrite bs=4k diff --git a/fio.1 b/fio.1 index ded7bbfc99..83d2f2dd64 100644 --- a/fio.1 +++ b/fio.1 @@ -2243,6 +2243,10 @@ Ceph cluster. If the \fBclustername\fR is specified, the \fBclientname\fR shall the full *type.id* string. If no type. prefix is given, fio will add 'client.' by default. .TP +.BI (rados)conf \fR=\fPstr +Specifies the configuration path of ceph cluster, so conf file does not +have to be /etc/ceph/ceph.conf. +.TP .BI (rbd,rados)busy_poll \fR=\fPbool Poll store instead of waiting for completion. Usually this provides better throughput at cost of higher(up to 100%) CPU utilization. From 5ceed0be62f3ce8903d5747674f9f70f44e736d6 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Tue, 31 May 2022 21:49:11 +0000 Subject: [PATCH 0170/1097] docs: update language setting for Sphinx build Sphinx 5.0.0 no longer accepts None for language. Remove it and Sphinx will use English as the default and no longer emit a warning. For details see https://github.com/sphinx-doc/sphinx/issues/10474 Sphinx warnings cause our documentation build on GitHub Actions to fail. Failures were observed for recent macOS builds which upgraded to Sphinx 5.0.0. Signed-off-by: Vincent Fu Link: https://lore.kernel.org/r/20220531214857.169864-1-vincent.fu@samsung.com Signed-off-by: Jens Axboe --- doc/conf.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/doc/conf.py b/doc/conf.py index 10b72ecb91..844f951ab7 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -85,13 +85,6 @@ def fio_version(): version, release = fio_version() -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -# -# This is also used if you do content translation via gettext catalogs. -# Usually you set "language" from the command line for these cases. -language = None - # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: # From 1bca8ad1e18249f13ef532947e0ce6a88105290c Mon Sep 17 00:00:00 2001 From: Anuj Gupta Date: Tue, 31 May 2022 19:01:47 +0530 Subject: [PATCH 0171/1097] io_uring.h: add IORING_SETUP_SQE128 and IORING_SETUP_CQE32 This asks the kernel to setup a ring with 128-byte SQE and 32-byte CQE entries. It may fail with -EINVAL if the kernel doesn't support this feature. If the kernel does support this feature, then the ring will support big-sqe/big-cqe entries which some commands may require. Signed-off-by: Anuj Gupta Link: https://lore.kernel.org/r/20220531133155.17493-2-ankit.kumar@samsung.com Signed-off-by: Jens Axboe --- os/linux/io_uring.h | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/os/linux/io_uring.h b/os/linux/io_uring.h index 42b2fe84db..2fa661350b 100644 --- a/os/linux/io_uring.h +++ b/os/linux/io_uring.h @@ -60,7 +60,17 @@ struct io_uring_sqe { __s32 splice_fd_in; __u32 file_index; }; - __u64 __pad2[2]; + union { + struct { + __u64 addr3; + __u64 __pad2[1]; + }; + /* + * If the ring is initialized with IORING_SETUP_SQE128, then + * this field is used for 80 bytes of arbitrary command data + */ + __u8 cmd[0]; + }; }; enum { @@ -101,6 +111,24 @@ enum { #define IORING_SETUP_CLAMP (1U << 4) /* clamp SQ/CQ ring sizes */ #define IORING_SETUP_ATTACH_WQ (1U << 5) /* attach to existing wq */ #define IORING_SETUP_R_DISABLED (1U << 6) /* start with ring disabled */ +#define IORING_SETUP_SUBMIT_ALL (1U << 7) /* continue submit on error */ +/* + * Cooperative task running. When requests complete, they often require + * forcing the submitter to transition to the kernel to complete. If this + * flag is set, work will be done when the task transitions anyway, rather + * than force an inter-processor interrupt reschedule. This avoids interrupting + * a task running in userspace, and saves an IPI. + */ +#define IORING_SETUP_COOP_TASKRUN (1U << 8) +/* + * If COOP_TASKRUN is set, get notified if task work is available for + * running and a kernel transition would be needed to run it. This sets + * IORING_SQ_TASKRUN in the sq ring flags. Not valid with COOP_TASKRUN. + */ +#define IORING_SETUP_TASKRUN_FLAG (1U << 9) + +#define IORING_SETUP_SQE128 (1U << 10) /* SQEs are 128 byte */ +#define IORING_SETUP_CQE32 (1U << 11) /* CQEs are 32 byte */ enum { IORING_OP_NOP, @@ -192,6 +220,12 @@ struct io_uring_cqe { __u64 user_data; /* sqe->data submission passed back */ __s32 res; /* result code for this event */ __u32 flags; + + /* + * If the ring is initialized with IORING_SETUP_CQE32, then this field + * contains 16-bytes of padding, doubling the size of the CQE. + */ + __u64 big_cqe[]; }; /* From 76a490bb98dcfe03be9b4453b8fe401cc2a77f7f Mon Sep 17 00:00:00 2001 From: Ankit Kumar Date: Tue, 31 May 2022 19:01:48 +0530 Subject: [PATCH 0172/1097] configure: check nvme uring command support Modify configure to check availability of nvme_uring_cmd, but only when the target OS is Linux. This way in the follow up patch we can define the missing structure to prevent compilation errors. Signed-off-by: Ankit Kumar Link: https://lore.kernel.org/r/20220531133155.17493-3-ankit.kumar@samsung.com Signed-off-by: Jens Axboe --- configure | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/configure b/configure index 4ee536a03b..8182322b21 100755 --- a/configure +++ b/configure @@ -2587,6 +2587,27 @@ if test "$libzbc" != "no" ; then fi print_config "libzbc engine" "$libzbc" +if test "$targetos" = "Linux" ; then +########################################## +# Check NVME_URING_CMD support +cat > $TMPC << EOF +#include +int main(void) +{ + struct nvme_uring_cmd *cmd; + + return sizeof(struct nvme_uring_cmd); +} +EOF +if compile_prog "" "" "nvme uring cmd"; then + output_sym "CONFIG_NVME_URING_CMD" + nvme_uring_cmd="yes" +else + nvme_uring_cmd="no" +fi +print_config "NVMe uring command support" "$nvme_uring_cmd" +fi + ########################################## # Check if we have xnvme if test "$xnvme" != "yes" ; then From f43aab595c1b8832d5f2641606f5bd5a7cdd22ea Mon Sep 17 00:00:00 2001 From: Anuj Gupta Date: Tue, 31 May 2022 19:01:49 +0530 Subject: [PATCH 0173/1097] init: return error incase an invalid value is passed as option Currently, fio exits incase an invalid value is passed as fio option, but continues even if an invalid value is passed for I/O engine specific options. Exit in that scenario. Signed-off-by: Anuj Gupta Link: https://lore.kernel.org/r/20220531133155.17493-4-ankit.kumar@samsung.com Signed-off-by: Jens Axboe --- init.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/init.c b/init.c index f7d702f849..da8007760a 100644 --- a/init.c +++ b/init.c @@ -2810,6 +2810,15 @@ int parse_cmd_line(int argc, char *argv[], int client_type) break; ret = fio_cmd_ioengine_option_parse(td, opt, val); + + if (ret) { + if (td) { + put_job(td); + td = NULL; + } + do_exit++; + exit_val = 1; + } break; } case 'w': From b3d5e3fd80e3834097578e92d1b788065b8346e1 Mon Sep 17 00:00:00 2001 From: Ankit Kumar Date: Tue, 31 May 2022 19:01:50 +0530 Subject: [PATCH 0174/1097] nvme: add nvme opcodes, structures and helper functions Add NVMe specification opcodes, data structures and helper functions to get identify namespace and form nvme uring command. This will help the follow up patches to get nvme-ns generic character device size, lba size. Signed-off-by: Ankit Kumar Co-authored-by: Anuj Gupta Link: https://lore.kernel.org/r/20220531133155.17493-5-ankit.kumar@samsung.com Signed-off-by: Jens Axboe --- Makefile | 4 +- engines/nvme.c | 103 +++++++++++++++++++++++++++++++++++++ engines/nvme.h | 136 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 241 insertions(+), 2 deletions(-) create mode 100644 engines/nvme.c create mode 100644 engines/nvme.h diff --git a/Makefile b/Makefile index ed66305a20..188a74d7b4 100644 --- a/Makefile +++ b/Makefile @@ -231,7 +231,7 @@ ifdef CONFIG_LIBXNVME endif ifeq ($(CONFIG_TARGET_OS), Linux) SOURCE += diskutil.c fifo.c blktrace.c cgroup.c trim.c engines/sg.c \ - oslib/linux-dev-lookup.c engines/io_uring.c + oslib/linux-dev-lookup.c engines/io_uring.c engines/nvme.c cmdprio_SRCS = engines/cmdprio.c ifdef CONFIG_HAS_BLKZONED SOURCE += oslib/linux-blkzoned.c @@ -241,7 +241,7 @@ endif endif ifeq ($(CONFIG_TARGET_OS), Android) SOURCE += diskutil.c fifo.c blktrace.c cgroup.c trim.c profiles/tiobench.c \ - oslib/linux-dev-lookup.c engines/io_uring.c + oslib/linux-dev-lookup.c engines/io_uring.c engines/nvme.c cmdprio_SRCS = engines/cmdprio.c ifdef CONFIG_HAS_BLKZONED SOURCE += oslib/linux-blkzoned.c diff --git a/engines/nvme.c b/engines/nvme.c new file mode 100644 index 0000000000..6fecf0ba78 --- /dev/null +++ b/engines/nvme.c @@ -0,0 +1,103 @@ +/* + * nvme structure declarations and helper functions for the + * io_uring_cmd engine. + */ + +#include "nvme.h" + +int fio_nvme_uring_cmd_prep(struct nvme_uring_cmd *cmd, struct io_u *io_u, + struct iovec *iov) +{ + struct nvme_data *data = FILE_ENG_DATA(io_u->file); + __u64 slba; + __u32 nlb; + + memset(cmd, 0, sizeof(struct nvme_uring_cmd)); + + if (io_u->ddir == DDIR_READ) + cmd->opcode = nvme_cmd_read; + else if (io_u->ddir == DDIR_WRITE) + cmd->opcode = nvme_cmd_write; + else + return -ENOTSUP; + + slba = io_u->offset >> data->lba_shift; + nlb = (io_u->xfer_buflen >> data->lba_shift) - 1; + + /* cdw10 and cdw11 represent starting lba */ + cmd->cdw10 = slba & 0xffffffff; + cmd->cdw11 = slba >> 32; + /* cdw12 represent number of lba's for read/write */ + cmd->cdw12 = nlb; + if (iov) { + iov->iov_base = io_u->xfer_buf; + iov->iov_len = io_u->xfer_buflen; + cmd->addr = (__u64)(uintptr_t)iov; + cmd->data_len = 1; + } else { + cmd->addr = (__u64)(uintptr_t)io_u->xfer_buf; + cmd->data_len = io_u->xfer_buflen; + } + cmd->nsid = data->nsid; + return 0; +} + +static int nvme_identify(int fd, __u32 nsid, enum nvme_identify_cns cns, + enum nvme_csi csi, void *data) +{ + struct nvme_passthru_cmd cmd = { + .opcode = nvme_admin_identify, + .nsid = nsid, + .addr = (__u64)(uintptr_t)data, + .data_len = NVME_IDENTIFY_DATA_SIZE, + .cdw10 = cns, + .cdw11 = csi << NVME_IDENTIFY_CSI_SHIFT, + .timeout_ms = NVME_DEFAULT_IOCTL_TIMEOUT, + }; + + return ioctl(fd, NVME_IOCTL_ADMIN_CMD, &cmd); +} + +int fio_nvme_get_info(struct fio_file *f, __u32 *nsid, __u32 *lba_sz, + __u64 *nlba) +{ + struct nvme_id_ns ns; + unsigned int namespace_id; + int fd, err; + + if (f->filetype != FIO_TYPE_CHAR) { + log_err("ioengine io_uring_cmd only works with nvme ns " + "generic char devices (/dev/ngXnY)\n"); + return 1; + } + + fd = open(f->file_name, O_RDONLY); + if (fd < 0) + return -errno; + + namespace_id = ioctl(fd, NVME_IOCTL_ID); + if (namespace_id < 0) { + log_err("failed to fetch namespace-id"); + close(fd); + return -errno; + } + + /* + * Identify namespace to get namespace-id, namespace size in LBA's + * and LBA data size. + */ + err = nvme_identify(fd, namespace_id, NVME_IDENTIFY_CNS_NS, + NVME_CSI_NVM, &ns); + if (err) { + log_err("failed to fetch identify namespace\n"); + close(fd); + return err; + } + + *nsid = namespace_id; + *lba_sz = 1 << ns.lbaf[(ns.flbas & 0x0f)].ds; + *nlba = ns.nsze; + + close(fd); + return 0; +} diff --git a/engines/nvme.h b/engines/nvme.h new file mode 100644 index 0000000000..8e626bb237 --- /dev/null +++ b/engines/nvme.h @@ -0,0 +1,136 @@ +/* + * nvme structure declarations and helper functions for the + * io_uring_cmd engine. + */ + +#ifndef FIO_NVME_H +#define FIO_NVME_H + +#include +#include "../fio.h" + +/* + * If the uapi headers installed on the system lacks nvme uring command + * support, use the local version to prevent compilation issues. + */ +#ifndef CONFIG_NVME_URING_CMD +struct nvme_uring_cmd { + __u8 opcode; + __u8 flags; + __u16 rsvd1; + __u32 nsid; + __u32 cdw2; + __u32 cdw3; + __u64 metadata; + __u64 addr; + __u32 metadata_len; + __u32 data_len; + __u32 cdw10; + __u32 cdw11; + __u32 cdw12; + __u32 cdw13; + __u32 cdw14; + __u32 cdw15; + __u32 timeout_ms; + __u32 rsvd2; +}; + +#define NVME_URING_CMD_IO _IOWR('N', 0x80, struct nvme_uring_cmd) +#define NVME_URING_CMD_IO_VEC _IOWR('N', 0x81, struct nvme_uring_cmd) +#endif /* CONFIG_NVME_URING_CMD */ + +#define NVME_DEFAULT_IOCTL_TIMEOUT 0 +#define NVME_IDENTIFY_DATA_SIZE 4096 +#define NVME_IDENTIFY_CSI_SHIFT 24 + +enum nvme_identify_cns { + NVME_IDENTIFY_CNS_NS = 0x00, +}; + +enum nvme_csi { + NVME_CSI_NVM = 0, + NVME_CSI_KV = 1, + NVME_CSI_ZNS = 2, +}; + +enum nvme_admin_opcode { + nvme_admin_identify = 0x06, +}; + +enum nvme_io_opcode { + nvme_cmd_write = 0x01, + nvme_cmd_read = 0x02, +}; + +struct nvme_data { + __u32 nsid; + __u32 lba_shift; +}; + +struct nvme_lbaf { + __le16 ms; + __u8 ds; + __u8 rp; +}; + +struct nvme_id_ns { + __le64 nsze; + __le64 ncap; + __le64 nuse; + __u8 nsfeat; + __u8 nlbaf; + __u8 flbas; + __u8 mc; + __u8 dpc; + __u8 dps; + __u8 nmic; + __u8 rescap; + __u8 fpi; + __u8 dlfeat; + __le16 nawun; + __le16 nawupf; + __le16 nacwu; + __le16 nabsn; + __le16 nabo; + __le16 nabspf; + __le16 noiob; + __u8 nvmcap[16]; + __le16 npwg; + __le16 npwa; + __le16 npdg; + __le16 npda; + __le16 nows; + __le16 mssrl; + __le32 mcl; + __u8 msrc; + __u8 rsvd81[11]; + __le32 anagrpid; + __u8 rsvd96[3]; + __u8 nsattr; + __le16 nvmsetid; + __le16 endgid; + __u8 nguid[16]; + __u8 eui64[8]; + struct nvme_lbaf lbaf[16]; + __u8 rsvd192[192]; + __u8 vs[3712]; +}; + +static inline int ilog2(uint32_t i) +{ + int log = -1; + + while (i) { + i >>= 1; + log++; + } + return log; +} + +int fio_nvme_get_info(struct fio_file *f, __u32 *nsid, __u32 *lba_sz, + __u64 *nlba); + +int fio_nvme_uring_cmd_prep(struct nvme_uring_cmd *cmd, struct io_u *io_u, + struct iovec *iov); + +#endif From 855dc4d44e000b68c01b0e33eae3389b49eb7f7f Mon Sep 17 00:00:00 2001 From: Anuj Gupta Date: Tue, 31 May 2022 19:01:51 +0530 Subject: [PATCH 0175/1097] engines/io_uring: add new I/O engine for uring passthrough support Add a new I/O engine (io_uring_cmd) for sending uring passthrough commands. It will also use most of the existing helpers from the I/O engine io_uring. The I/O preparation, completion, file open, file close and post init paths are going to differ and hence io_uring_cmd will have its own helper for them. Add a new io_uring_cmd engine specific option to support nvme passthrough commands. Filename name for this specific option must specify nvme-ns generic character device (dev/ngXnY). This provides io_uring_cmd I/O engine a bandwidth to support various passthrough commands in future. The engine_pos and engine_data fields in struct fio_file are separated now. This will help I/O engine io_uring_cmd to store specific data as well as keep track of register files. Added a new option cmd_type. This specifies the type of uring command to submit. Currently it only supports nvme uring command Signed-off-by: Anuj Gupta Co-authored-by: Ankit Kumar Link: https://lore.kernel.org/r/20220531133155.17493-6-ankit.kumar@samsung.com Signed-off-by: Jens Axboe --- engines/io_uring.c | 318 +++++++++++++++++++++++++++++++++++++++++++- file.h | 12 +- os/linux/io_uring.h | 9 ++ 3 files changed, 328 insertions(+), 11 deletions(-) diff --git a/engines/io_uring.c b/engines/io_uring.c index 1e15647ede..a7b7b1663c 100644 --- a/engines/io_uring.c +++ b/engines/io_uring.c @@ -24,6 +24,13 @@ #include "../lib/types.h" #include "../os/linux/io_uring.h" #include "cmdprio.h" +#include "nvme.h" + +#include + +enum uring_cmd_type { + FIO_URING_CMD_NVME = 1, +}; struct io_sq_ring { unsigned *head; @@ -85,6 +92,7 @@ struct ioring_options { unsigned int uncached; unsigned int nowait; unsigned int force_async; + enum uring_cmd_type cmd_type; }; static const int ddir_to_op[2][2] = { @@ -270,6 +278,22 @@ static struct fio_option options[] = { .category = FIO_OPT_C_ENGINE, .group = FIO_OPT_G_IOURING, }, + { + .name = "cmd_type", + .lname = "Uring cmd type", + .type = FIO_OPT_STR, + .off1 = offsetof(struct ioring_options, cmd_type), + .help = "Specify uring-cmd type", + .def = "nvme", + .posval = { + { .ival = "nvme", + .oval = FIO_URING_CMD_NVME, + .help = "Issue nvme-uring-cmd", + }, + }, + .category = FIO_OPT_C_ENGINE, + .group = FIO_OPT_G_IOURING, + }, { .name = NULL, }, @@ -373,6 +397,52 @@ static int fio_ioring_prep(struct thread_data *td, struct io_u *io_u) return 0; } +static int fio_ioring_cmd_prep(struct thread_data *td, struct io_u *io_u) +{ + struct ioring_data *ld = td->io_ops_data; + struct ioring_options *o = td->eo; + struct fio_file *f = io_u->file; + struct io_uring_sqe *sqe; + int ret; + + /* nvme_uring_cmd case */ + if (o->cmd_type == FIO_URING_CMD_NVME) { + struct nvme_uring_cmd *cmd; + + sqe = &ld->sqes[(io_u->index) << 1]; + + if (o->registerfiles) { + sqe->fd = f->engine_pos; + sqe->flags = IOSQE_FIXED_FILE; + } else { + sqe->fd = f->fd; + } + sqe->rw_flags = 0; + if (!td->o.odirect && o->uncached) + sqe->rw_flags |= RWF_UNCACHED; + if (o->nowait) + sqe->rw_flags |= RWF_NOWAIT; + + sqe->opcode = IORING_OP_URING_CMD; + sqe->user_data = (unsigned long) io_u; + if (o->nonvectored) + sqe->cmd_op = NVME_URING_CMD_IO; + else + sqe->cmd_op = NVME_URING_CMD_IO_VEC; + if (o->force_async && ++ld->prepped == o->force_async) { + ld->prepped = 0; + sqe->flags |= IOSQE_ASYNC; + } + + cmd = (struct nvme_uring_cmd *)sqe->cmd; + ret = fio_nvme_uring_cmd_prep(cmd, io_u, + o->nonvectored ? NULL : &ld->iovecs[io_u->index]); + + return ret; + } + return -EINVAL; +} + static struct io_u *fio_ioring_event(struct thread_data *td, int event) { struct ioring_data *ld = td->io_ops_data; @@ -396,6 +466,29 @@ static struct io_u *fio_ioring_event(struct thread_data *td, int event) return io_u; } +static struct io_u *fio_ioring_cmd_event(struct thread_data *td, int event) +{ + struct ioring_data *ld = td->io_ops_data; + struct ioring_options *o = td->eo; + struct io_uring_cqe *cqe; + struct io_u *io_u; + unsigned index; + + index = (event + ld->cq_ring_off) & ld->cq_ring_mask; + if (o->cmd_type == FIO_URING_CMD_NVME) + index <<= 1; + + cqe = &ld->cq_ring.cqes[index]; + io_u = (struct io_u *) (uintptr_t) cqe->user_data; + + if (cqe->res != 0) + io_u->error = -cqe->res; + else + io_u->error = 0; + + return io_u; +} + static int fio_ioring_cqring_reap(struct thread_data *td, unsigned int events, unsigned int max) { @@ -622,14 +715,22 @@ static int fio_ioring_mmap(struct ioring_data *ld, struct io_uring_params *p) sring->array = ptr + p->sq_off.array; ld->sq_ring_mask = *sring->ring_mask; - ld->mmap[1].len = p->sq_entries * sizeof(struct io_uring_sqe); + if (p->flags & IORING_SETUP_SQE128) + ld->mmap[1].len = 2 * p->sq_entries * sizeof(struct io_uring_sqe); + else + ld->mmap[1].len = p->sq_entries * sizeof(struct io_uring_sqe); ld->sqes = mmap(0, ld->mmap[1].len, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_POPULATE, ld->ring_fd, IORING_OFF_SQES); ld->mmap[1].ptr = ld->sqes; - ld->mmap[2].len = p->cq_off.cqes + - p->cq_entries * sizeof(struct io_uring_cqe); + if (p->flags & IORING_SETUP_CQE32) { + ld->mmap[2].len = p->cq_off.cqes + + 2 * p->cq_entries * sizeof(struct io_uring_cqe); + } else { + ld->mmap[2].len = p->cq_off.cqes + + p->cq_entries * sizeof(struct io_uring_cqe); + } ptr = mmap(0, ld->mmap[2].len, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_POPULATE, ld->ring_fd, IORING_OFF_CQ_RING); @@ -728,6 +829,61 @@ static int fio_ioring_queue_init(struct thread_data *td) return fio_ioring_mmap(ld, &p); } +static int fio_ioring_cmd_queue_init(struct thread_data *td) +{ + struct ioring_data *ld = td->io_ops_data; + struct ioring_options *o = td->eo; + int depth = td->o.iodepth; + struct io_uring_params p; + int ret; + + memset(&p, 0, sizeof(p)); + + if (o->hipri) + p.flags |= IORING_SETUP_IOPOLL; + if (o->sqpoll_thread) { + p.flags |= IORING_SETUP_SQPOLL; + if (o->sqpoll_set) { + p.flags |= IORING_SETUP_SQ_AFF; + p.sq_thread_cpu = o->sqpoll_cpu; + } + } + if (o->cmd_type == FIO_URING_CMD_NVME) { + p.flags |= IORING_SETUP_SQE128; + p.flags |= IORING_SETUP_CQE32; + } + + /* + * Clamp CQ ring size at our SQ ring size, we don't need more entries + * than that. + */ + p.flags |= IORING_SETUP_CQSIZE; + p.cq_entries = depth; + +retry: + ret = syscall(__NR_io_uring_setup, depth, &p); + if (ret < 0) { + if (errno == EINVAL && p.flags & IORING_SETUP_CQSIZE) { + p.flags &= ~IORING_SETUP_CQSIZE; + goto retry; + } + return ret; + } + + ld->ring_fd = ret; + + fio_ioring_probe(td); + + if (o->fixedbufs) { + ret = syscall(__NR_io_uring_register, ld->ring_fd, + IORING_REGISTER_BUFFERS, ld->iovecs, depth); + if (ret < 0) + return ret; + } + + return fio_ioring_mmap(ld, &p); +} + static int fio_ioring_register_files(struct thread_data *td) { struct ioring_data *ld = td->io_ops_data; @@ -811,6 +967,52 @@ static int fio_ioring_post_init(struct thread_data *td) return 0; } +static int fio_ioring_cmd_post_init(struct thread_data *td) +{ + struct ioring_data *ld = td->io_ops_data; + struct ioring_options *o = td->eo; + struct io_u *io_u; + int err, i; + + for (i = 0; i < td->o.iodepth; i++) { + struct iovec *iov = &ld->iovecs[i]; + + io_u = ld->io_u_index[i]; + iov->iov_base = io_u->buf; + iov->iov_len = td_max_bs(td); + } + + err = fio_ioring_cmd_queue_init(td); + if (err) { + int init_err = errno; + + td_verror(td, init_err, "io_queue_init"); + return 1; + } + + for (i = 0; i < td->o.iodepth; i++) { + struct io_uring_sqe *sqe; + + if (o->cmd_type == FIO_URING_CMD_NVME) { + sqe = &ld->sqes[i << 1]; + memset(sqe, 0, 2 * sizeof(*sqe)); + } else { + sqe = &ld->sqes[i]; + memset(sqe, 0, sizeof(*sqe)); + } + } + + if (o->registerfiles) { + err = fio_ioring_register_files(td); + if (err) { + td_verror(td, errno, "ioring_register_files"); + return 1; + } + } + + return 0; +} + static int fio_ioring_init(struct thread_data *td) { struct ioring_options *o = td->eo; @@ -868,6 +1070,38 @@ static int fio_ioring_open_file(struct thread_data *td, struct fio_file *f) return 0; } +static int fio_ioring_cmd_open_file(struct thread_data *td, struct fio_file *f) +{ + struct ioring_data *ld = td->io_ops_data; + struct ioring_options *o = td->eo; + + if (o->cmd_type == FIO_URING_CMD_NVME) { + struct nvme_data *data = NULL; + unsigned int nsid, lba_size = 0; + unsigned long long nlba = 0; + int ret; + + /* Store the namespace-id and lba size. */ + data = FILE_ENG_DATA(f); + if (data == NULL) { + ret = fio_nvme_get_info(f, &nsid, &lba_size, &nlba); + if (ret) + return ret; + + data = calloc(1, sizeof(struct nvme_data)); + data->nsid = nsid; + data->lba_shift = ilog2(lba_size); + + FILE_SET_ENG_DATA(f, data); + } + } + if (!ld || !o->registerfiles) + return generic_open_file(td, f); + + f->fd = ld->fds[f->engine_pos]; + return 0; +} + static int fio_ioring_close_file(struct thread_data *td, struct fio_file *f) { struct ioring_data *ld = td->io_ops_data; @@ -880,7 +1114,57 @@ static int fio_ioring_close_file(struct thread_data *td, struct fio_file *f) return 0; } -static struct ioengine_ops ioengine = { +static int fio_ioring_cmd_close_file(struct thread_data *td, + struct fio_file *f) +{ + struct ioring_data *ld = td->io_ops_data; + struct ioring_options *o = td->eo; + + if (o->cmd_type == FIO_URING_CMD_NVME) { + struct nvme_data *data = FILE_ENG_DATA(f); + + FILE_SET_ENG_DATA(f, NULL); + free(data); + } + if (!ld || !o->registerfiles) + return generic_close_file(td, f); + + f->fd = -1; + return 0; +} + +static int fio_ioring_cmd_get_file_size(struct thread_data *td, + struct fio_file *f) +{ + struct ioring_options *o = td->eo; + + if (fio_file_size_known(f)) + return 0; + + if (o->cmd_type == FIO_URING_CMD_NVME) { + struct nvme_data *data = NULL; + unsigned int nsid, lba_size = 0; + unsigned long long nlba = 0; + int ret; + + ret = fio_nvme_get_info(f, &nsid, &lba_size, &nlba); + if (ret) + return ret; + + data = calloc(1, sizeof(struct nvme_data)); + data->nsid = nsid; + data->lba_shift = ilog2(lba_size); + + f->real_file_size = lba_size * nlba; + fio_file_set_size_known(f); + + FILE_SET_ENG_DATA(f, data); + return 0; + } + return generic_get_file_size(td, f); +} + +static struct ioengine_ops ioengine_uring = { .name = "io_uring", .version = FIO_IOOPS_VERSION, .flags = FIO_ASYNCIO_SYNC_TRIM | FIO_NO_OFFLOAD, @@ -900,13 +1184,35 @@ static struct ioengine_ops ioengine = { .option_struct_size = sizeof(struct ioring_options), }; +static struct ioengine_ops ioengine_uring_cmd = { + .name = "io_uring_cmd", + .version = FIO_IOOPS_VERSION, + .flags = FIO_ASYNCIO_SYNC_TRIM | FIO_NO_OFFLOAD | FIO_MEMALIGN | FIO_RAWIO, + .init = fio_ioring_init, + .post_init = fio_ioring_cmd_post_init, + .io_u_init = fio_ioring_io_u_init, + .prep = fio_ioring_cmd_prep, + .queue = fio_ioring_queue, + .commit = fio_ioring_commit, + .getevents = fio_ioring_getevents, + .event = fio_ioring_cmd_event, + .cleanup = fio_ioring_cleanup, + .open_file = fio_ioring_cmd_open_file, + .close_file = fio_ioring_cmd_close_file, + .get_file_size = fio_ioring_cmd_get_file_size, + .options = options, + .option_struct_size = sizeof(struct ioring_options), +}; + static void fio_init fio_ioring_register(void) { - register_ioengine(&ioengine); + register_ioengine(&ioengine_uring); + register_ioengine(&ioengine_uring_cmd); } static void fio_exit fio_ioring_unregister(void) { - unregister_ioengine(&ioengine); + unregister_ioengine(&ioengine_uring); + unregister_ioengine(&ioengine_uring_cmd); } #endif diff --git a/file.h b/file.h index faf65a2a01..da1b894706 100644 --- a/file.h +++ b/file.h @@ -126,12 +126,14 @@ struct fio_file { unsigned int last_write_idx; /* - * For use by the io engine for offset or private data storage + * For use by the io engine to store offset */ - union { - uint64_t engine_pos; - void *engine_data; - }; + uint64_t engine_pos; + + /* + * For use by the io engine for private data storage + */ + void *engine_data; /* * if io is protected by a semaphore, this is set diff --git a/os/linux/io_uring.h b/os/linux/io_uring.h index 2fa661350b..929997f827 100644 --- a/os/linux/io_uring.h +++ b/os/linux/io_uring.h @@ -22,6 +22,7 @@ struct io_uring_sqe { union { __u64 off; /* offset into file */ __u64 addr2; + __u32 cmd_op; }; union { __u64 addr; /* pointer to buffer or iovecs */ @@ -171,6 +172,14 @@ enum { IORING_OP_MKDIRAT, IORING_OP_SYMLINKAT, IORING_OP_LINKAT, + IORING_OP_MSG_RING, + IORING_OP_FSETXATTR, + IORING_OP_SETXATTR, + IORING_OP_FGETXATTR, + IORING_OP_GETXATTR, + IORING_OP_SOCKET, + IORING_OP_URING_CMD, + /* this goes last, obviously */ IORING_OP_LAST, From 3716f9f1bf6db8d4f6500d756912d5ac8cf0a670 Mon Sep 17 00:00:00 2001 From: Ankit Kumar Date: Tue, 31 May 2022 19:01:52 +0530 Subject: [PATCH 0176/1097] docs: document options for io_uring_cmd I/O engine Update documentation with io_uring_cmd I/O engine specific options. Add missing io_uring I/O engine entry from fio man page. Update docs with missing io_uring engine specific options. Signed-off-by: Ankit Kumar Link: https://lore.kernel.org/r/20220531133155.17493-7-ankit.kumar@samsung.com Signed-off-by: Jens Axboe --- HOWTO.rst | 41 +++++++++++++++++++++++++++++++---------- fio.1 | 33 ++++++++++++++++++++++++++++----- 2 files changed, 59 insertions(+), 15 deletions(-) diff --git a/HOWTO.rst b/HOWTO.rst index 8ab3ac4bf7..28ac2b7c91 100644 --- a/HOWTO.rst +++ b/HOWTO.rst @@ -1952,6 +1952,10 @@ I/O engine for both direct and buffered IO. This engine defines engine specific options. + **io_uring_cmd** + Fast Linux native asynchronous I/O for pass through commands. + This engine defines engine specific options. + **libaio** Linux native asynchronous I/O. Note that Linux may only support queued behavior with non-buffered I/O (set ``direct=1`` or @@ -2255,22 +2259,34 @@ with the caveat that when used on the command line, they must come after the values for trim IOs are ignored. This option is mutually exclusive with the :option:`cmdprio_percentage` option. -.. option:: fixedbufs : [io_uring] +.. option:: fixedbufs : [io_uring] [io_uring_cmd] + + If fio is asked to do direct IO, then Linux will map pages for each + IO call, and release them when IO is done. If this option is set, the + pages are pre-mapped before IO is started. This eliminates the need to + map and release for each IO. This is more efficient, and reduces the + IO latency as well. + +.. option:: nonvectored : [io_uring] [io_uring_cmd] - If fio is asked to do direct IO, then Linux will map pages for each - IO call, and release them when IO is done. If this option is set, the - pages are pre-mapped before IO is started. This eliminates the need to - map and release for each IO. This is more efficient, and reduces the - IO latency as well. + With this option, fio will use non-vectored read/write commands, where + address must contain the address directly. Default is -1. -.. option:: registerfiles : [io_uring] +.. option:: force_async=int : [io_uring] [io_uring_cmd] + + Normal operation for io_uring is to try and issue an sqe as + non-blocking first, and if that fails, execute it in an async manner. + With this option set to N, then every N request fio will ask sqe to + be issued in an async manner. Default is 0. + +.. option:: registerfiles : [io_uring] [io_uring_cmd] With this option, fio registers the set of files being used with the kernel. This avoids the overhead of managing file counts in the kernel, making the submission and completion part more lightweight. Required for the below :option:`sqthread_poll` option. -.. option:: sqthread_poll : [io_uring] [xnvme] +.. option:: sqthread_poll : [io_uring] [io_uring_cmd] [xnvme] Normally fio will submit IO by issuing a system call to notify the kernel of available items in the SQ ring. If this option is set, the @@ -2278,14 +2294,19 @@ with the caveat that when used on the command line, they must come after the This frees up cycles for fio, at the cost of using more CPU in the system. -.. option:: sqthread_poll_cpu : [io_uring] +.. option:: sqthread_poll_cpu : [io_uring] [io_uring_cmd] When :option:`sqthread_poll` is set, this option provides a way to define which CPU should be used for the polling thread. +.. option:: cmd_type=str : [io_uring_cmd] + + Specifies the type of uring passthrough command to be used. Supported + value is nvme. Default is nvme. + .. option:: hipri - [io_uring], [xnvme] + [io_uring] [io_uring_cmd] [xnvme] If this option is set, fio will attempt to use polled IO completions. Normal IO completions generate interrupts to signal the completion of diff --git a/fio.1 b/fio.1 index bdba314277..948c01f9c3 100644 --- a/fio.1 +++ b/fio.1 @@ -1739,6 +1739,15 @@ Basic \fBpreadv\fR\|(2) or \fBpwritev\fR\|(2) I/O. .B pvsync2 Basic \fBpreadv2\fR\|(2) or \fBpwritev2\fR\|(2) I/O. .TP +.B io_uring +Fast Linux native asynchronous I/O. Supports async IO +for both direct and buffered IO. +This engine defines engine specific options. +.TP +.B io_uring_cmd +Fast Linux native asynchronous I/O for passthrough commands. +This engine defines engine specific options. +.TP .B libaio Linux native asynchronous I/O. Note that Linux may only support queued behavior with non-buffered I/O (set `direct=1' or @@ -2040,35 +2049,49 @@ for trim IOs are ignored. This option is mutually exclusive with the \fBcmdprio_percentage\fR option. .RE .TP -.BI (io_uring)fixedbufs +.BI (io_uring,io_uring_cmd)fixedbufs If fio is asked to do direct IO, then Linux will map pages for each IO call, and release them when IO is done. If this option is set, the pages are pre-mapped before IO is started. This eliminates the need to map and release for each IO. This is more efficient, and reduces the IO latency as well. .TP -.BI (io_uring,xnvme)hipri +.BI (io_uring,io_uring_cmd)nonvectored +With this option, fio will use non-vectored read/write commands, where address +must contain the address directly. Default is -1. +.TP +.BI (io_uring,io_uring_cmd)force_async +Normal operation for io_uring is to try and issue an sqe as non-blocking first, +and if that fails, execute it in an async manner. With this option set to N, +then every N request fio will ask sqe to be issued in an async manner. Default +is 0. +.TP +.BI (io_uring,io_uring_cmd,xnvme)hipri If this option is set, fio will attempt to use polled IO completions. Normal IO completions generate interrupts to signal the completion of IO, polled completions do not. Hence they are require active reaping by the application. The benefits are more efficient IO for high IOPS scenarios, and lower latencies for low queue depth IO. .TP -.BI (io_uring)registerfiles +.BI (io_uring,io_uring_cmd)registerfiles With this option, fio registers the set of files being used with the kernel. This avoids the overhead of managing file counts in the kernel, making the submission and completion part more lightweight. Required for the below sqthread_poll option. .TP -.BI (io_uring,xnvme)sqthread_poll +.BI (io_uring,io_uring_cmd,xnvme)sqthread_poll Normally fio will submit IO by issuing a system call to notify the kernel of available items in the SQ ring. If this option is set, the act of submitting IO will be done by a polling thread in the kernel. This frees up cycles for fio, at the cost of using more CPU in the system. .TP -.BI (io_uring)sqthread_poll_cpu +.BI (io_uring,io_uring_cmd)sqthread_poll_cpu When `sqthread_poll` is set, this option provides a way to define which CPU should be used for the polling thread. .TP +.BI (io_uring_cmd)cmd_type \fR=\fPstr +Specifies the type of uring passthrough command to be used. Supported +value is nvme. Default is nvme. +.TP .BI (libaio)userspace_reap Normally, with the libaio engine in use, fio will use the \fBio_getevents\fR\|(3) system call to reap newly returned events. With From 59c3200d579004d1d9c2dd3a36c162c780d520e4 Mon Sep 17 00:00:00 2001 From: Ankit Kumar Date: Tue, 31 May 2022 19:01:53 +0530 Subject: [PATCH 0177/1097] zbd: Check for direct flag only if its block device nvme-ns generic character devices currently do not support O_DIRECT flag. Check for fio option for direct flag only if filetype is a block device. t/zbd skip test case #1 for character devices as they don't require direct I/O. Tested-by: Vincent Fu Signed-off-by: Ankit Kumar Reviewed-by: Shin'ichiro Kawasaki Link: https://lore.kernel.org/r/20220531133155.17493-8-ankit.kumar@samsung.com Signed-off-by: Jens Axboe --- t/zbd/test-zbd-support | 11 ++++++++++- zbd.c | 4 ++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/t/zbd/test-zbd-support b/t/zbd/test-zbd-support index 7e2fff00da..5190ae8493 100755 --- a/t/zbd/test-zbd-support +++ b/t/zbd/test-zbd-support @@ -229,6 +229,14 @@ require_regular_block_dev() { return 0 } +require_block_dev() { + if [[ -b "$realdev" ]]; then + return 0 + fi + SKIP_REASON="$dev is not a block device" + return 1 +} + require_seq_zones() { local req_seq_zones=${1} local seq_bytes=$((disk_size - first_sequential_zone_sector * 512)) @@ -251,8 +259,9 @@ require_conv_zones() { return 0 } -# Check whether buffered writes are refused. +# Check whether buffered writes are refused for block devices. test1() { + require_block_dev || return $SKIP_TESTCASE run_fio --name=job1 --filename="$dev" --rw=write --direct=0 --bs=4K \ "$(ioengine "psync")" --size="${zone_size}" --thread=1 \ --zonemode=zbd --zonesize="${zone_size}" 2>&1 | diff --git a/zbd.c b/zbd.c index b1fd6b4bb0..627fb968ec 100644 --- a/zbd.c +++ b/zbd.c @@ -466,7 +466,7 @@ static bool zbd_open_zone(struct thread_data *td, const struct fio_file *f, return res; } -/* Verify whether direct I/O is used for all host-managed zoned drives. */ +/* Verify whether direct I/O is used for all host-managed zoned block drives. */ static bool zbd_using_direct_io(void) { struct thread_data *td; @@ -477,7 +477,7 @@ static bool zbd_using_direct_io(void) if (td->o.odirect || !(td->o.td_ddir & TD_DDIR_WRITE)) continue; for_each_file(td, f, j) { - if (f->zbd_info && + if (f->zbd_info && f->filetype == FIO_TYPE_BLOCK && f->zbd_info->model == ZBD_HOST_MANAGED) return false; } From 3d05e0ffcbcc48d2876eb0207cb31b48ef857b5f Mon Sep 17 00:00:00 2001 From: Ankit Kumar Date: Tue, 31 May 2022 19:01:54 +0530 Subject: [PATCH 0178/1097] engines/io_uring: Enable zone device support for io_uring_cmd I/O engine Add zone device specific ioengine_ops for io_uring_cmd. * get_zoned_model * report_zones * reset_wp * get_max_open_zones Add the necessary NVMe ZNS specfication opcodes and structures. Add helper functions to submit admin and I/O passthrough commands for these new NVMe ZNS specific commands. For write workload iodepth must be set to 1 as there is no IO scheduler Tested-by: Vincent Fu Signed-off-by: Ankit Kumar Link: https://lore.kernel.org/r/20220531133155.17493-9-ankit.kumar@samsung.com Signed-off-by: Jens Axboe --- engines/io_uring.c | 32 ++++++ engines/nvme.c | 242 +++++++++++++++++++++++++++++++++++++++++++++ engines/nvme.h | 80 ++++++++++++++- 3 files changed, 353 insertions(+), 1 deletion(-) diff --git a/engines/io_uring.c b/engines/io_uring.c index a7b7b1663c..5a5406d4bf 100644 --- a/engines/io_uring.c +++ b/engines/io_uring.c @@ -1164,6 +1164,34 @@ static int fio_ioring_cmd_get_file_size(struct thread_data *td, return generic_get_file_size(td, f); } +static int fio_ioring_cmd_get_zoned_model(struct thread_data *td, + struct fio_file *f, + enum zbd_zoned_model *model) +{ + return fio_nvme_get_zoned_model(td, f, model); +} + +static int fio_ioring_cmd_report_zones(struct thread_data *td, + struct fio_file *f, uint64_t offset, + struct zbd_zone *zbdz, + unsigned int nr_zones) +{ + return fio_nvme_report_zones(td, f, offset, zbdz, nr_zones); +} + +static int fio_ioring_cmd_reset_wp(struct thread_data *td, struct fio_file *f, + uint64_t offset, uint64_t length) +{ + return fio_nvme_reset_wp(td, f, offset, length); +} + +static int fio_ioring_cmd_get_max_open_zones(struct thread_data *td, + struct fio_file *f, + unsigned int *max_open_zones) +{ + return fio_nvme_get_max_open_zones(td, f, max_open_zones); +} + static struct ioengine_ops ioengine_uring = { .name = "io_uring", .version = FIO_IOOPS_VERSION, @@ -1200,6 +1228,10 @@ static struct ioengine_ops ioengine_uring_cmd = { .open_file = fio_ioring_cmd_open_file, .close_file = fio_ioring_cmd_close_file, .get_file_size = fio_ioring_cmd_get_file_size, + .get_zoned_model = fio_ioring_cmd_get_zoned_model, + .report_zones = fio_ioring_cmd_report_zones, + .reset_wp = fio_ioring_cmd_reset_wp, + .get_max_open_zones = fio_ioring_cmd_get_max_open_zones, .options = options, .option_struct_size = sizeof(struct ioring_options), }; diff --git a/engines/nvme.c b/engines/nvme.c index 6fecf0ba78..59550deff7 100644 --- a/engines/nvme.c +++ b/engines/nvme.c @@ -101,3 +101,245 @@ int fio_nvme_get_info(struct fio_file *f, __u32 *nsid, __u32 *lba_sz, close(fd); return 0; } + +int fio_nvme_get_zoned_model(struct thread_data *td, struct fio_file *f, + enum zbd_zoned_model *model) +{ + struct nvme_data *data = FILE_ENG_DATA(f); + struct nvme_id_ns ns; + struct nvme_passthru_cmd cmd; + int fd, ret = 0; + + if (f->filetype != FIO_TYPE_CHAR) + return -EINVAL; + + /* File is not yet opened */ + fd = open(f->file_name, O_RDONLY | O_LARGEFILE); + if (fd < 0) + return -errno; + + /* Using nvme_id_ns for data as sizes are same */ + ret = nvme_identify(fd, data->nsid, NVME_IDENTIFY_CNS_CSI_CTRL, + NVME_CSI_ZNS, &ns); + if (ret) { + *model = ZBD_NONE; + goto out; + } + + memset(&cmd, 0, sizeof(struct nvme_passthru_cmd)); + + /* Using nvme_id_ns for data as sizes are same */ + ret = nvme_identify(fd, data->nsid, NVME_IDENTIFY_CNS_CSI_NS, + NVME_CSI_ZNS, &ns); + if (ret) { + *model = ZBD_NONE; + goto out; + } + + *model = ZBD_HOST_MANAGED; +out: + close(fd); + return 0; +} + +static int nvme_report_zones(int fd, __u32 nsid, __u64 slba, __u32 zras_feat, + __u32 data_len, void *data) +{ + struct nvme_passthru_cmd cmd = { + .opcode = nvme_zns_cmd_mgmt_recv, + .nsid = nsid, + .addr = (__u64)(uintptr_t)data, + .data_len = data_len, + .cdw10 = slba & 0xffffffff, + .cdw11 = slba >> 32, + .cdw12 = (data_len >> 2) - 1, + .cdw13 = NVME_ZNS_ZRA_REPORT_ZONES | zras_feat, + .timeout_ms = NVME_DEFAULT_IOCTL_TIMEOUT, + }; + + return ioctl(fd, NVME_IOCTL_IO_CMD, &cmd); +} + +int fio_nvme_report_zones(struct thread_data *td, struct fio_file *f, + uint64_t offset, struct zbd_zone *zbdz, + unsigned int nr_zones) +{ + struct nvme_data *data = FILE_ENG_DATA(f); + struct nvme_zone_report *zr; + struct nvme_zns_id_ns zns_ns; + struct nvme_id_ns ns; + unsigned int i = 0, j, zones_fetched = 0; + unsigned int max_zones, zones_chunks = 1024; + int fd, ret = 0; + __u32 zr_len; + __u64 zlen; + + /* File is not yet opened */ + fd = open(f->file_name, O_RDONLY | O_LARGEFILE); + if (fd < 0) + return -errno; + + zones_fetched = 0; + zr_len = sizeof(*zr) + (zones_chunks * sizeof(struct nvme_zns_desc)); + zr = calloc(1, zr_len); + if (!zr) + return -ENOMEM; + + ret = nvme_identify(fd, data->nsid, NVME_IDENTIFY_CNS_NS, + NVME_CSI_NVM, &ns); + if (ret) { + log_err("%s: nvme_identify_ns failed, err=%d\n", f->file_name, + ret); + goto out; + } + + ret = nvme_identify(fd, data->nsid, NVME_IDENTIFY_CNS_CSI_NS, + NVME_CSI_ZNS, &zns_ns); + if (ret) { + log_err("%s: nvme_zns_identify_ns failed, err=%d\n", + f->file_name, ret); + goto out; + } + zlen = zns_ns.lbafe[ns.flbas & 0x0f].zsze << data->lba_shift; + + max_zones = (f->real_file_size - offset) / zlen; + if (max_zones < nr_zones) + nr_zones = max_zones; + + if (nr_zones < zones_chunks) + zones_chunks = nr_zones; + + while (zones_fetched < nr_zones) { + if (zones_fetched + zones_chunks >= nr_zones) { + zones_chunks = nr_zones - zones_fetched; + zr_len = sizeof(*zr) + (zones_chunks * sizeof(struct nvme_zns_desc)); + } + ret = nvme_report_zones(fd, data->nsid, offset >> data->lba_shift, + NVME_ZNS_ZRAS_FEAT_ERZ, zr_len, (void *)zr); + if (ret) { + log_err("%s: nvme_zns_report_zones failed, err=%d\n", + f->file_name, ret); + goto out; + } + + /* Transform the zone-report */ + for (j = 0; j < zr->nr_zones; j++, i++) { + struct nvme_zns_desc *desc = (struct nvme_zns_desc *)&(zr->entries[j]); + + zbdz[i].start = desc->zslba << data->lba_shift; + zbdz[i].len = zlen; + zbdz[i].wp = desc->wp << data->lba_shift; + zbdz[i].capacity = desc->zcap << data->lba_shift; + + /* Zone Type is stored in first 4 bits. */ + switch (desc->zt & 0x0f) { + case NVME_ZONE_TYPE_SEQWRITE_REQ: + zbdz[i].type = ZBD_ZONE_TYPE_SWR; + break; + default: + log_err("%s: invalid type for zone at offset %llu.\n", + f->file_name, desc->zslba); + ret = -EIO; + goto out; + } + + /* Zone State is stored in last 4 bits. */ + switch (desc->zs >> 4) { + case NVME_ZNS_ZS_EMPTY: + zbdz[i].cond = ZBD_ZONE_COND_EMPTY; + break; + case NVME_ZNS_ZS_IMPL_OPEN: + zbdz[i].cond = ZBD_ZONE_COND_IMP_OPEN; + break; + case NVME_ZNS_ZS_EXPL_OPEN: + zbdz[i].cond = ZBD_ZONE_COND_EXP_OPEN; + break; + case NVME_ZNS_ZS_CLOSED: + zbdz[i].cond = ZBD_ZONE_COND_CLOSED; + break; + case NVME_ZNS_ZS_FULL: + zbdz[i].cond = ZBD_ZONE_COND_FULL; + break; + case NVME_ZNS_ZS_READ_ONLY: + case NVME_ZNS_ZS_OFFLINE: + default: + /* Treat all these conditions as offline (don't use!) */ + zbdz[i].cond = ZBD_ZONE_COND_OFFLINE; + zbdz[i].wp = zbdz[i].start; + } + } + zones_fetched += zr->nr_zones; + offset += zr->nr_zones * zlen; + } + + ret = zones_fetched; +out: + free(zr); + close(fd); + + return ret; +} + +int fio_nvme_reset_wp(struct thread_data *td, struct fio_file *f, + uint64_t offset, uint64_t length) +{ + struct nvme_data *data = FILE_ENG_DATA(f); + unsigned int nr_zones; + unsigned long long zslba; + int i, fd, ret = 0; + + /* If the file is not yet opened, open it for this function. */ + fd = f->fd; + if (fd < 0) { + fd = open(f->file_name, O_RDWR | O_LARGEFILE); + if (fd < 0) + return -errno; + } + + zslba = offset >> data->lba_shift; + nr_zones = (length + td->o.zone_size - 1) / td->o.zone_size; + + for (i = 0; i < nr_zones; i++, zslba += (td->o.zone_size >> data->lba_shift)) { + struct nvme_passthru_cmd cmd = { + .opcode = nvme_zns_cmd_mgmt_send, + .nsid = data->nsid, + .cdw10 = zslba & 0xffffffff, + .cdw11 = zslba >> 32, + .cdw13 = NVME_ZNS_ZSA_RESET, + .addr = (__u64)(uintptr_t)NULL, + .data_len = 0, + .timeout_ms = NVME_DEFAULT_IOCTL_TIMEOUT, + }; + + ret = ioctl(fd, NVME_IOCTL_IO_CMD, &cmd); + } + + if (f->fd < 0) + close(fd); + return -ret; +} + +int fio_nvme_get_max_open_zones(struct thread_data *td, struct fio_file *f, + unsigned int *max_open_zones) +{ + struct nvme_data *data = FILE_ENG_DATA(f); + struct nvme_zns_id_ns zns_ns; + int fd, ret = 0; + + fd = open(f->file_name, O_RDONLY | O_LARGEFILE); + if (fd < 0) + return -errno; + + ret = nvme_identify(fd, data->nsid, NVME_IDENTIFY_CNS_CSI_NS, + NVME_CSI_ZNS, &zns_ns); + if (ret) { + log_err("%s: nvme_zns_identify_ns failed, err=%d\n", + f->file_name, ret); + goto out; + } + + *max_open_zones = zns_ns.mor + 1; +out: + close(fd); + return ret; +} diff --git a/engines/nvme.h b/engines/nvme.h index 8e626bb237..70a89b7406 100644 --- a/engines/nvme.h +++ b/engines/nvme.h @@ -43,8 +43,15 @@ struct nvme_uring_cmd { #define NVME_IDENTIFY_DATA_SIZE 4096 #define NVME_IDENTIFY_CSI_SHIFT 24 +#define NVME_ZNS_ZRA_REPORT_ZONES 0 +#define NVME_ZNS_ZRAS_FEAT_ERZ (1 << 16) +#define NVME_ZNS_ZSA_RESET 0x4 +#define NVME_ZONE_TYPE_SEQWRITE_REQ 0x2 + enum nvme_identify_cns { - NVME_IDENTIFY_CNS_NS = 0x00, + NVME_IDENTIFY_CNS_NS = 0x00, + NVME_IDENTIFY_CNS_CSI_NS = 0x05, + NVME_IDENTIFY_CNS_CSI_CTRL = 0x06, }; enum nvme_csi { @@ -60,6 +67,18 @@ enum nvme_admin_opcode { enum nvme_io_opcode { nvme_cmd_write = 0x01, nvme_cmd_read = 0x02, + nvme_zns_cmd_mgmt_send = 0x79, + nvme_zns_cmd_mgmt_recv = 0x7a, +}; + +enum nvme_zns_zs { + NVME_ZNS_ZS_EMPTY = 0x1, + NVME_ZNS_ZS_IMPL_OPEN = 0x2, + NVME_ZNS_ZS_EXPL_OPEN = 0x3, + NVME_ZNS_ZS_CLOSED = 0x4, + NVME_ZNS_ZS_READ_ONLY = 0xd, + NVME_ZNS_ZS_FULL = 0xe, + NVME_ZNS_ZS_OFFLINE = 0xf, }; struct nvme_data { @@ -127,10 +146,69 @@ static inline int ilog2(uint32_t i) return log; } +struct nvme_zns_lbafe { + __le64 zsze; + __u8 zdes; + __u8 rsvd9[7]; +}; + +struct nvme_zns_id_ns { + __le16 zoc; + __le16 ozcs; + __le32 mar; + __le32 mor; + __le32 rrl; + __le32 frl; + __le32 rrl1; + __le32 rrl2; + __le32 rrl3; + __le32 frl1; + __le32 frl2; + __le32 frl3; + __le32 numzrwa; + __le16 zrwafg; + __le16 zrwasz; + __u8 zrwacap; + __u8 rsvd53[2763]; + struct nvme_zns_lbafe lbafe[64]; + __u8 vs[256]; +}; + +struct nvme_zns_desc { + __u8 zt; + __u8 zs; + __u8 za; + __u8 zai; + __u8 rsvd4[4]; + __le64 zcap; + __le64 zslba; + __le64 wp; + __u8 rsvd32[32]; +}; + +struct nvme_zone_report { + __le64 nr_zones; + __u8 rsvd8[56]; + struct nvme_zns_desc entries[]; +}; + int fio_nvme_get_info(struct fio_file *f, __u32 *nsid, __u32 *lba_sz, __u64 *nlba); int fio_nvme_uring_cmd_prep(struct nvme_uring_cmd *cmd, struct io_u *io_u, struct iovec *iov); +int fio_nvme_get_zoned_model(struct thread_data *td, struct fio_file *f, + enum zbd_zoned_model *model); + +int fio_nvme_report_zones(struct thread_data *td, struct fio_file *f, + uint64_t offset, struct zbd_zone *zbdz, + unsigned int nr_zones); + +int fio_nvme_reset_wp(struct thread_data *td, struct fio_file *f, + uint64_t offset, uint64_t length); + +int fio_nvme_get_max_open_zones(struct thread_data *td, struct fio_file *f, + unsigned int *max_open_zones); + #endif From dc3259dada4eb016560809850974a627b8d60a92 Mon Sep 17 00:00:00 2001 From: Anuj Gupta Date: Tue, 31 May 2022 19:01:55 +0530 Subject: [PATCH 0179/1097] examples: add 2 example job file for io_uring_cmd engine examples/uring-cmd-ng.fio has usage for conventional nvme-ns char device examples/uring-cmd-zoned.fio has usage for ZNS nvme-ns char device Signed-off-by: Anuj Gupta Co-authored-by: Ankit Kumar Link: https://lore.kernel.org/r/20220531133155.17493-10-ankit.kumar@samsung.com Signed-off-by: Jens Axboe --- examples/uring-cmd-ng.fio | 25 +++++++++++++++++++++++++ examples/uring-cmd-zoned.fio | 31 +++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+) create mode 100644 examples/uring-cmd-ng.fio create mode 100644 examples/uring-cmd-zoned.fio diff --git a/examples/uring-cmd-ng.fio b/examples/uring-cmd-ng.fio new file mode 100644 index 0000000000..b2888a0035 --- /dev/null +++ b/examples/uring-cmd-ng.fio @@ -0,0 +1,25 @@ +# io_uring_cmd I/O engine for nvme-ns generic character device + +[global] +filename=/dev/ng0n1 +ioengine=io_uring_cmd +cmd_type=nvme +size=1G +iodepth=32 +bs=4K +thread=1 +stonewall=1 + +[rand-write] +rw=randwrite +sqthread_poll=1 + +[rand-read] +rw=randread + +[write-opts] +rw=write +sqthread_poll=1 +sqthread_poll_cpu=0 +nonvectored=1 +registerfiles=1 diff --git a/examples/uring-cmd-zoned.fio b/examples/uring-cmd-zoned.fio new file mode 100644 index 0000000000..58e8f79ec5 --- /dev/null +++ b/examples/uring-cmd-zoned.fio @@ -0,0 +1,31 @@ +# io_uring_cmd I/O engine for nvme-ns generic zoned character device +# +# NOTE: with write workload iodepth must be set to 1 as there is no IO +# scheduler. + +[global] +filename=/dev/ng0n1 +ioengine=io_uring_cmd +cmd_type=nvme +zonemode=zbd +size=1G +iodepth=1 +bs=256K +verify=crc32c +stonewall=1 + +[rand-write] +rw=randwrite + +[write-opts] +rw=write +registerfiles=1 +sqthread_poll=1 +sqthread_poll_cpu=0 + +[randwrite-opts] +rw=randwrite +sqthread_poll=1 +sqthread_poll_cpu=0 +nonvectored=1 +registerfiles=1 From 3ce6a3de08240b87a8c1b2a8b0d22a81b51f714f Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Thu, 2 Jun 2022 02:22:36 -0600 Subject: [PATCH 0180/1097] engines/io_uring: cleanup supported case Signed-off-by: Jens Axboe --- engines/io_uring.c | 62 ++++++++++++++++++++++------------------------ 1 file changed, 29 insertions(+), 33 deletions(-) diff --git a/engines/io_uring.c b/engines/io_uring.c index 5a5406d4bf..cceafe6924 100644 --- a/engines/io_uring.c +++ b/engines/io_uring.c @@ -402,45 +402,41 @@ static int fio_ioring_cmd_prep(struct thread_data *td, struct io_u *io_u) struct ioring_data *ld = td->io_ops_data; struct ioring_options *o = td->eo; struct fio_file *f = io_u->file; + struct nvme_uring_cmd *cmd; struct io_uring_sqe *sqe; - int ret; - - /* nvme_uring_cmd case */ - if (o->cmd_type == FIO_URING_CMD_NVME) { - struct nvme_uring_cmd *cmd; - sqe = &ld->sqes[(io_u->index) << 1]; + /* only supports nvme_uring_cmd */ + if (o->cmd_type != FIO_URING_CMD_NVME) + return -EINVAL; - if (o->registerfiles) { - sqe->fd = f->engine_pos; - sqe->flags = IOSQE_FIXED_FILE; - } else { - sqe->fd = f->fd; - } - sqe->rw_flags = 0; - if (!td->o.odirect && o->uncached) - sqe->rw_flags |= RWF_UNCACHED; - if (o->nowait) - sqe->rw_flags |= RWF_NOWAIT; - - sqe->opcode = IORING_OP_URING_CMD; - sqe->user_data = (unsigned long) io_u; - if (o->nonvectored) - sqe->cmd_op = NVME_URING_CMD_IO; - else - sqe->cmd_op = NVME_URING_CMD_IO_VEC; - if (o->force_async && ++ld->prepped == o->force_async) { - ld->prepped = 0; - sqe->flags |= IOSQE_ASYNC; - } + sqe = &ld->sqes[(io_u->index) << 1]; - cmd = (struct nvme_uring_cmd *)sqe->cmd; - ret = fio_nvme_uring_cmd_prep(cmd, io_u, - o->nonvectored ? NULL : &ld->iovecs[io_u->index]); + if (o->registerfiles) { + sqe->fd = f->engine_pos; + sqe->flags = IOSQE_FIXED_FILE; + } else { + sqe->fd = f->fd; + } + sqe->rw_flags = 0; + if (!td->o.odirect && o->uncached) + sqe->rw_flags |= RWF_UNCACHED; + if (o->nowait) + sqe->rw_flags |= RWF_NOWAIT; - return ret; + sqe->opcode = IORING_OP_URING_CMD; + sqe->user_data = (unsigned long) io_u; + if (o->nonvectored) + sqe->cmd_op = NVME_URING_CMD_IO; + else + sqe->cmd_op = NVME_URING_CMD_IO_VEC; + if (o->force_async && ++ld->prepped == o->force_async) { + ld->prepped = 0; + sqe->flags |= IOSQE_ASYNC; } - return -EINVAL; + + cmd = (struct nvme_uring_cmd *)sqe->cmd; + return fio_nvme_uring_cmd_prep(cmd, io_u, + o->nonvectored ? NULL : &ld->iovecs[io_u->index]); } static struct io_u *fio_ioring_event(struct thread_data *td, int event) From 3efcb23f09257ecb6db3a895ebd16e133f6432f7 Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Thu, 2 Jun 2022 03:56:51 -0600 Subject: [PATCH 0181/1097] engines/nvme: fix 'fd' leak in error handling Signed-off-by: Jens Axboe --- engines/nvme.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/engines/nvme.c b/engines/nvme.c index 59550deff7..fe33e167f3 100644 --- a/engines/nvme.c +++ b/engines/nvme.c @@ -182,8 +182,10 @@ int fio_nvme_report_zones(struct thread_data *td, struct fio_file *f, zones_fetched = 0; zr_len = sizeof(*zr) + (zones_chunks * sizeof(struct nvme_zns_desc)); zr = calloc(1, zr_len); - if (!zr) + if (!zr) { + close(fd); return -ENOMEM; + } ret = nvme_identify(fd, data->nsid, NVME_IDENTIFY_CNS_NS, NVME_CSI_NVM, &ns); From 37a0881ffc6b55f61bfb23e9a5dbe737a4c455a3 Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Thu, 2 Jun 2022 03:57:22 -0600 Subject: [PATCH 0182/1097] engines/nvme: ioctl return value is an int Fix comparing an unsigned int to less than zero, it can never be true. Signed-off-by: Jens Axboe --- engines/nvme.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engines/nvme.c b/engines/nvme.c index fe33e167f3..9ffc5303d2 100644 --- a/engines/nvme.c +++ b/engines/nvme.c @@ -62,7 +62,7 @@ int fio_nvme_get_info(struct fio_file *f, __u32 *nsid, __u32 *lba_sz, __u64 *nlba) { struct nvme_id_ns ns; - unsigned int namespace_id; + int namespace_id; int fd, err; if (f->filetype != FIO_TYPE_CHAR) { From 26faead0f3c6e7608b89a51373f1455b91377fcb Mon Sep 17 00:00:00 2001 From: Shin'ichiro Kawasaki Date: Thu, 2 Jun 2022 18:13:10 +0900 Subject: [PATCH 0183/1097] t/zbd: skip test case #13 when max_open_zones is too small Test case #13 of t/zbd/test-zbd-support fails when the test target device has max_open_zones smaller than 4. To avoid the failure, add a helper function require_max_open_zones and use it to skip the test case when the device does not have required minimum max_open_zones. Reported-by: Vincent Fu Link: https://lore.kernel.org/fio/20220602015316.6ismlexb22fwd5ko@shindev/ Signed-off-by: Shin'ichiro Kawasaki Link: https://lore.kernel.org/r/20220602091310.97189-1-shinichiro.kawasaki@wdc.com Signed-off-by: Jens Axboe --- t/zbd/test-zbd-support | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/t/zbd/test-zbd-support b/t/zbd/test-zbd-support index 5190ae8493..d4aaa81322 100755 --- a/t/zbd/test-zbd-support +++ b/t/zbd/test-zbd-support @@ -259,6 +259,16 @@ require_conv_zones() { return 0 } +require_max_open_zones() { + local min=${1} + + if ((max_open_zones !=0 && max_open_zones < min)); then + SKIP_REASON="max_open_zones of $dev is smaller than $min" + return 1 + fi + return 0 +} + # Check whether buffered writes are refused for block devices. test1() { require_block_dev || return $SKIP_TESTCASE @@ -462,6 +472,8 @@ test12() { test13() { local size off capacity + require_max_open_zones 4 || return $SKIP_TESTCASE + prep_write size=$((8 * zone_size)) off=$((first_sequential_zone_sector * 512)) From e9aab3c9c90222cc478dfa86860322690af74309 Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Mon, 13 Jun 2022 16:18:09 -0700 Subject: [PATCH 0184/1097] configure: Support gcc 12 Fix the following classes of errors reported by gcc 12: * ${variable} may be used uninitialized. * ${variable} is set but not used. * argument 2 is null but the corresponding size argument 3 value is 1 [-Werror=nonnull] Signed-off-by: Bart Van Assche --- configure | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/configure b/configure index 8182322b21..110b4f30c7 100755 --- a/configure +++ b/configure @@ -1128,7 +1128,8 @@ cat > $TMPC << EOF #include int main(int argc, char **argv) { - cpu_set_t mask; + cpu_set_t mask = { }; + return sched_setaffinity(0, sizeof(mask), &mask); } EOF @@ -1139,7 +1140,8 @@ else #include int main(int argc, char **argv) { - cpu_set_t mask; + cpu_set_t mask = { }; + return sched_setaffinity(0, &mask); } EOF @@ -1621,7 +1623,8 @@ cat > $TMPC << EOF #include int main(int argc, char **argv) { - struct sched_param p; + struct sched_param p = { }; + return sched_setscheduler(0, SCHED_IDLE, &p); } EOF @@ -1743,7 +1746,9 @@ cat > $TMPC << EOF #include int main(int argc, char **argv) { - return pwritev(0, NULL, 1, 0) + preadv(0, NULL, 1, 0); + struct iovec iov[1] = { }; + + return pwritev(0, iov, 1, 0) + preadv(0, iov, 1, 0); } EOF if compile_prog "" "" "pwritev"; then @@ -1761,7 +1766,9 @@ cat > $TMPC << EOF #include int main(int argc, char **argv) { - return pwritev2(0, NULL, 1, 0, 0) + preadv2(0, NULL, 1, 0, 0); + struct iovec iov[1] = { }; + + return pwritev2(0, iov, 1, 0, 0) + preadv2(0, iov, 1, 0, 0); } EOF if compile_prog "" "" "pwritev2"; then @@ -1787,14 +1794,14 @@ cat > $TMPC << EOF #include int main(int argc, char **argv) { - struct addrinfo hints; - struct in6_addr addr; + struct addrinfo hints = { }; + struct in6_addr addr = in6addr_any; int ret; ret = getaddrinfo(NULL, NULL, &hints, NULL); freeaddrinfo(NULL); - printf("%s\n", gai_strerror(ret)); - addr = in6addr_any; + printf("%s %d\n", gai_strerror(ret), addr.s6_addr[0]); + return 0; } EOF @@ -2155,9 +2162,7 @@ cat > $TMPC << EOF #include int main(int argc, char **argv) { - int rc; - rc = pmem_is_pmem(NULL, 0); - return 0; + return pmem_is_pmem(NULL, 0); } EOF if compile_prog "" "-lpmem" "libpmem"; then @@ -2176,7 +2181,7 @@ if test "$libpmem" = "yes"; then #include int main(int argc, char **argv) { - pmem_memcpy(NULL, NULL, NULL, NULL); + pmem_memcpy(NULL, NULL, 0, 0); return 0; } EOF @@ -2392,7 +2397,7 @@ int main(int argc, char **argv) FILE *mtab = setmntent(NULL, "r"); struct mntent *mnt = getmntent(mtab); endmntent(mtab); - return 0; + return mnt != NULL; } EOF if compile_prog "" "" "getmntent"; then From 67ff4de3950daabcb0c4e14d273bb51075487285 Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Mon, 13 Jun 2022 16:27:08 -0700 Subject: [PATCH 0185/1097] configure: Fix libzbc detection on SUSE Linux The path of the libzbc header file is /usr/include/libzbc/libzbc/zbc.h instead of /usr/include/libzbc/zbc.h on SUSE Linux systems. Add support for the SUSE libzbc include path. Signed-off-by: Bart Van Assche --- configure | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/configure b/configure index 110b4f30c7..510af42448 100755 --- a/configure +++ b/configure @@ -2578,6 +2578,10 @@ int main(int argc, char **argv) } EOF if test "$libzbc" != "no" ; then + if [ -e /usr/include/libzbc/libzbc ]; then + # SUSE Linux. + CFLAGS="$CFLAGS -I/usr/include/libzbc" + fi if compile_prog "" "-lzbc" "libzbc"; then libzbc="yes" if ! check_min_lib_version libzbc 5; then From d46b4565dfdaddbfdd91976a57b2564aa7a52f62 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Tue, 14 Jun 2022 15:58:29 +0000 Subject: [PATCH 0186/1097] ioengines: add helper for trims with async ioengines Async ioengines support trim commands but trims are synchronous operations. We need to provide special handling when measuring latency for these commands. Create a helper function to help us identify when an async ioengine is issuing a sync trim command. This makes the code more readable. Signed-off-by: Vincent Fu Link: https://lore.kernel.org/r/20220614155822.307771-2-vincent.fu@samsung.com Signed-off-by: Jens Axboe --- ioengines.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/ioengines.c b/ioengines.c index 68f307e541..280da3c826 100644 --- a/ioengines.c +++ b/ioengines.c @@ -24,6 +24,13 @@ static FLIST_HEAD(engine_list); +static inline bool async_ioengine_sync_trim(struct thread_data *td, + struct io_u *io_u) +{ + return td_ioengine_flagged(td, FIO_ASYNCIO_SYNC_TRIM) && + io_u->ddir == DDIR_TRIM; +} + static bool check_engine_ops(struct thread_data *td, struct ioengine_ops *ops) { if (ops->version != FIO_IOOPS_VERSION) { @@ -350,8 +357,7 @@ enum fio_q_status td_io_queue(struct thread_data *td, struct io_u *io_u) io_u->resid = 0; if (td_ioengine_flagged(td, FIO_SYNCIO) || - (td_ioengine_flagged(td, FIO_ASYNCIO_SYNC_TRIM) && - io_u->ddir == DDIR_TRIM)) { + async_ioengine_sync_trim(td, io_u)) { if (fio_fill_issue_time(td)) fio_gettime(&io_u->issue_time, NULL); @@ -435,8 +441,7 @@ enum fio_q_status td_io_queue(struct thread_data *td, struct io_u *io_u) } if (!td_ioengine_flagged(td, FIO_SYNCIO) && - (!td_ioengine_flagged(td, FIO_ASYNCIO_SYNC_TRIM) || - io_u->ddir != DDIR_TRIM)) { + !async_ioengine_sync_trim(td, io_u)) { if (fio_fill_issue_time(td)) fio_gettime(&io_u->issue_time, NULL); From 4e7e78980ff32627e12b48d72496b701dd200a42 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Tue, 14 Jun 2022 15:58:29 +0000 Subject: [PATCH 0187/1097] ioengines: don't record issue_time if ioengines already do it io_uring, io_uring_cmd, and libaio record issue_time inside the ioengine code when their commit functions are called. So we don't need to record issue_time again for these ioengines in td_io_queue. If we do fill issue_time twice, then mean(slat) + mean(clat) != mean(lat): user@ubuntu:~/fio-dev$ fio-canonical/fio --name=test --ioengine=io_uring --number_ios=1 --rw=randread --size=1M test: (g=0): rw=randread, bs=(R) 4096B-4096B, (W) 4096B-4096B, (T) 4096B-4096B, ioengine=io_uring, iodepth=1 fio-3.30-48-g26fa Starting 1 process test: (groupid=0, jobs=1): err= 0: pid=172145: Mon Jun 6 16:12:42 2022 read: IOPS=1000, BW=4000KiB/s (4096kB/s)(4096B/1msec) slat (nsec): min=61424, max=61424, avg=61424.00, stdev= 0.00 clat (nsec): min=242709, max=242709, avg=242709.00, stdev= 0.00 lat (nsec): min=308346, max=308346, avg=308346.00, stdev= 0.00 61424 + 242709 = 304133 != 308346 If we fill issue_time only once, then the equality will hold (as it should): user@ubuntu:~/fio-dev$ fio-latency/fio --name=test --ioengine=io_uring --number_ios=1 --rw=randread --size=1M test: (g=0): rw=randread, bs=(R) 4096B-4096B, (W) 4096B-4096B, (T) 4096B-4096B, ioengine=io_uring, iodepth=1 fio-3.30-48-g26fa-dirty Starting 1 process test: (groupid=0, jobs=1): err= 0: pid=172220: Mon Jun 6 16:12:47 2022 read: IOPS=1000, BW=4000KiB/s (4096kB/s)(4096B/1msec) slat (nsec): min=53701, max=53701, avg=53701.00, stdev= 0.00 clat (nsec): min=259566, max=259566, avg=259566.00, stdev= 0.00 lat (nsec): min=313267, max=313267, avg=313267.00, stdev= 0.00 53701 + 259566 = 313267 Signed-off-by: Vincent Fu Link: https://lore.kernel.org/r/20220614155822.307771-3-vincent.fu@samsung.com Signed-off-by: Jens Axboe --- engines/io_uring.c | 7 +++++-- engines/libaio.c | 3 ++- ioengines.c | 3 ++- ioengines.h | 2 ++ 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/engines/io_uring.c b/engines/io_uring.c index cceafe6924..474d215c27 100644 --- a/engines/io_uring.c +++ b/engines/io_uring.c @@ -1191,7 +1191,8 @@ static int fio_ioring_cmd_get_max_open_zones(struct thread_data *td, static struct ioengine_ops ioengine_uring = { .name = "io_uring", .version = FIO_IOOPS_VERSION, - .flags = FIO_ASYNCIO_SYNC_TRIM | FIO_NO_OFFLOAD, + .flags = FIO_ASYNCIO_SYNC_TRIM | FIO_NO_OFFLOAD | + FIO_ASYNCIO_SETS_ISSUE_TIME, .init = fio_ioring_init, .post_init = fio_ioring_post_init, .io_u_init = fio_ioring_io_u_init, @@ -1211,7 +1212,9 @@ static struct ioengine_ops ioengine_uring = { static struct ioengine_ops ioengine_uring_cmd = { .name = "io_uring_cmd", .version = FIO_IOOPS_VERSION, - .flags = FIO_ASYNCIO_SYNC_TRIM | FIO_NO_OFFLOAD | FIO_MEMALIGN | FIO_RAWIO, + .flags = FIO_ASYNCIO_SYNC_TRIM | FIO_NO_OFFLOAD | + FIO_MEMALIGN | FIO_RAWIO | + FIO_ASYNCIO_SETS_ISSUE_TIME, .init = fio_ioring_init, .post_init = fio_ioring_cmd_post_init, .io_u_init = fio_ioring_io_u_init, diff --git a/engines/libaio.c b/engines/libaio.c index 9c278d060b..da5279f485 100644 --- a/engines/libaio.c +++ b/engines/libaio.c @@ -511,7 +511,8 @@ static int fio_libaio_init(struct thread_data *td) FIO_STATIC struct ioengine_ops ioengine = { .name = "libaio", .version = FIO_IOOPS_VERSION, - .flags = FIO_ASYNCIO_SYNC_TRIM, + .flags = FIO_ASYNCIO_SYNC_TRIM | + FIO_ASYNCIO_SETS_ISSUE_TIME, .init = fio_libaio_init, .post_init = fio_libaio_post_init, .prep = fio_libaio_prep, diff --git a/ioengines.c b/ioengines.c index 280da3c826..e4ad698c67 100644 --- a/ioengines.c +++ b/ioengines.c @@ -442,7 +442,8 @@ enum fio_q_status td_io_queue(struct thread_data *td, struct io_u *io_u) if (!td_ioengine_flagged(td, FIO_SYNCIO) && !async_ioengine_sync_trim(td, io_u)) { - if (fio_fill_issue_time(td)) + if (fio_fill_issue_time(td) && + !td_ioengine_flagged(td, FIO_ASYNCIO_SETS_ISSUE_TIME)) fio_gettime(&io_u->issue_time, NULL); /* diff --git a/ioengines.h b/ioengines.h index acdb0071f5..fafa1e4818 100644 --- a/ioengines.h +++ b/ioengines.h @@ -83,6 +83,8 @@ enum fio_ioengine_flags { FIO_ASYNCIO_SYNC_TRIM = 1 << 14, /* io engine has async ->queue except for trim */ FIO_NO_OFFLOAD = 1 << 15, /* no async offload */ + FIO_ASYNCIO_SETS_ISSUE_TIME + = 1 << 16, /* async ioengine with commit function that sets issue_time */ }; /* From 13ddd98b2a70c55657f952096785ccc64479f0eb Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Tue, 14 Jun 2022 15:58:29 +0000 Subject: [PATCH 0188/1097] HOWTO: improve description of latency measures - clarify how submission latency is calculated for async ioengines - it is slat (not clat) that is near zero for sync ioengines - Note that submission latency + completion latency = total latency Signed-off-by: Vincent Fu Link: https://lore.kernel.org/r/20220614155822.307771-4-vincent.fu@samsung.com Signed-off-by: Jens Axboe --- HOWTO.rst | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/HOWTO.rst b/HOWTO.rst index 28ac2b7c91..470777e2eb 100644 --- a/HOWTO.rst +++ b/HOWTO.rst @@ -4165,24 +4165,31 @@ writes in the example above). In the order listed, they denote: **slat** Submission latency (**min** being the minimum, **max** being the maximum, **avg** being the average, **stdev** being the standard - deviation). This is the time it took to submit the I/O. For - sync I/O this row is not displayed as the slat is really the - completion latency (since queue/complete is one operation there). - This value can be in nanoseconds, microseconds or milliseconds --- - fio will choose the most appropriate base and print that (in the - example above nanoseconds was the best scale). Note: in :option:`--minimal` mode - latencies are always expressed in microseconds. + deviation). This is the time from when fio initialized the I/O + to submission. For synchronous ioengines this includes the time + up until just before the ioengine's queue function is called. + For asynchronous ioengines this includes the time up through the + completion of the ioengine's queue function (and commit function + if it is defined). For sync I/O this row is not displayed as the + slat is negligible. This value can be in nanoseconds, + microseconds or milliseconds --- fio will choose the most + appropriate base and print that (in the example above + nanoseconds was the best scale). Note: in :option:`--minimal` + mode latencies are always expressed in microseconds. **clat** Completion latency. Same names as slat, this denotes the time from - submission to completion of the I/O pieces. For sync I/O, clat will - usually be equal (or very close) to 0, as the time from submit to - complete is basically just CPU time (I/O has already been done, see slat - explanation). + submission to completion of the I/O pieces. For sync I/O, this + represents the time from when the I/O was submitted to the + operating system to when it was completed. For asynchronous + ioengines this is the time from when the ioengine's queue (and + commit if available) functions were completed to when the I/O's + completion was reaped by fio. **lat** Total latency. Same names as slat and clat, this denotes the time from when fio created the I/O unit to completion of the I/O operation. + It is the sum of submission and completion latency. **bw** Bandwidth statistics based on samples. Same names as the xlat stats, From 39f56400f63e43fbe33e391d52f24eb6c79d0098 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Tue, 14 Jun 2022 15:58:29 +0000 Subject: [PATCH 0189/1097] ioengines: update last_issue if we set issue_time If we're not updating issue_time it doesn't make sense to update last_issue. We should also be updating last_issue in libaio and io_uring when we record issue_time. Signed-off-by: Vincent Fu Link: https://lore.kernel.org/r/20220614155822.307771-5-vincent.fu@samsung.com Signed-off-by: Jens Axboe --- engines/io_uring.c | 6 ++++++ engines/libaio.c | 6 ++++++ ioengines.c | 30 ++++++++++++++++-------------- 3 files changed, 28 insertions(+), 14 deletions(-) diff --git a/engines/io_uring.c b/engines/io_uring.c index 474d215c27..cffc73710d 100644 --- a/engines/io_uring.c +++ b/engines/io_uring.c @@ -608,6 +608,12 @@ static void fio_ioring_queued(struct thread_data *td, int start, int nr) start++; } + + /* + * only used for iolog + */ + if (td->o.read_iolog_file) + memcpy(&td->last_issue, &now, sizeof(now)); } static int fio_ioring_commit(struct thread_data *td) diff --git a/engines/libaio.c b/engines/libaio.c index da5279f485..33b8c12f96 100644 --- a/engines/libaio.c +++ b/engines/libaio.c @@ -368,6 +368,12 @@ static void fio_libaio_queued(struct thread_data *td, struct io_u **io_us, memcpy(&io_u->issue_time, &now, sizeof(now)); io_u_queued(td, io_u); } + + /* + * only used for iolog + */ + if (td->o.read_iolog_file) + memcpy(&td->last_issue, &now, sizeof(now)); } static int fio_libaio_commit(struct thread_data *td) diff --git a/ioengines.c b/ioengines.c index e4ad698c67..e2316ee4e3 100644 --- a/ioengines.c +++ b/ioengines.c @@ -358,15 +358,16 @@ enum fio_q_status td_io_queue(struct thread_data *td, struct io_u *io_u) if (td_ioengine_flagged(td, FIO_SYNCIO) || async_ioengine_sync_trim(td, io_u)) { - if (fio_fill_issue_time(td)) + if (fio_fill_issue_time(td)) { fio_gettime(&io_u->issue_time, NULL); - /* - * only used for iolog - */ - if (td->o.read_iolog_file) - memcpy(&td->last_issue, &io_u->issue_time, - sizeof(io_u->issue_time)); + /* + * only used for iolog + */ + if (td->o.read_iolog_file) + memcpy(&td->last_issue, &io_u->issue_time, + sizeof(io_u->issue_time)); + } } @@ -443,15 +444,16 @@ enum fio_q_status td_io_queue(struct thread_data *td, struct io_u *io_u) if (!td_ioengine_flagged(td, FIO_SYNCIO) && !async_ioengine_sync_trim(td, io_u)) { if (fio_fill_issue_time(td) && - !td_ioengine_flagged(td, FIO_ASYNCIO_SETS_ISSUE_TIME)) + !td_ioengine_flagged(td, FIO_ASYNCIO_SETS_ISSUE_TIME)) { fio_gettime(&io_u->issue_time, NULL); - /* - * only used for iolog - */ - if (td->o.read_iolog_file) - memcpy(&td->last_issue, &io_u->issue_time, - sizeof(io_u->issue_time)); + /* + * only used for iolog + */ + if (td->o.read_iolog_file) + memcpy(&td->last_issue, &io_u->issue_time, + sizeof(io_u->issue_time)); + } } return ret; From 2b82135e42fc5872338ee16e16d317b16abfe308 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Tue, 14 Jun 2022 15:58:29 +0000 Subject: [PATCH 0190/1097] ioengines: clean up latency accounting for 3 ioengines The librpma_apm_client, librpma_gpspm_client, and rdma ioengines have commit functions that record submission latency. In order to avoid setting issue_time twice add the FIO_ASYNCIO_SETS_ISSUE_TIME flag. Also add code to update the iolog issue time when needed. I don't have the means to test this patch. Signed-off-by: Vincent Fu Link: https://lore.kernel.org/r/20220614155822.307771-6-vincent.fu@samsung.com Signed-off-by: Jens Axboe --- engines/librpma_apm.c | 2 +- engines/librpma_fio.c | 9 ++++++++- engines/librpma_gpspm.c | 2 +- engines/rdma.c | 9 ++++++++- 4 files changed, 18 insertions(+), 4 deletions(-) diff --git a/engines/librpma_apm.c b/engines/librpma_apm.c index d1166ad839..896240dd2a 100644 --- a/engines/librpma_apm.c +++ b/engines/librpma_apm.c @@ -208,7 +208,7 @@ FIO_STATIC struct ioengine_ops ioengine_client = { .errdetails = librpma_fio_client_errdetails, .close_file = librpma_fio_file_nop, .cleanup = client_cleanup, - .flags = FIO_DISKLESSIO, + .flags = FIO_DISKLESSIO | FIO_ASYNCIO_SETS_ISSUE_TIME, .options = librpma_fio_options, .option_struct_size = sizeof(struct librpma_fio_options_values), }; diff --git a/engines/librpma_fio.c b/engines/librpma_fio.c index 34818904d0..a78a1e5767 100644 --- a/engines/librpma_fio.c +++ b/engines/librpma_fio.c @@ -621,9 +621,16 @@ int librpma_fio_client_commit(struct thread_data *td) } } - if ((fill_time = fio_fill_issue_time(td))) + if ((fill_time = fio_fill_issue_time(td))) { fio_gettime(&now, NULL); + /* + * only used for iolog + */ + if (td->o.read_iolog_file) + memcpy(&td->last_issue, &now, sizeof(now)); + + } /* move executed io_us from queued[] to flight[] */ for (i = 0; i < ccd->io_u_queued_nr; i++) { struct io_u *io_u = ccd->io_us_queued[i]; diff --git a/engines/librpma_gpspm.c b/engines/librpma_gpspm.c index 5cf974722d..f00717a731 100644 --- a/engines/librpma_gpspm.c +++ b/engines/librpma_gpspm.c @@ -352,7 +352,7 @@ FIO_STATIC struct ioengine_ops ioengine_client = { .errdetails = librpma_fio_client_errdetails, .close_file = librpma_fio_file_nop, .cleanup = client_cleanup, - .flags = FIO_DISKLESSIO, + .flags = FIO_DISKLESSIO | FIO_ASYNCIO_SETS_ISSUE_TIME, .options = librpma_fio_options, .option_struct_size = sizeof(struct librpma_fio_options_values), }; diff --git a/engines/rdma.c b/engines/rdma.c index 4eb86652f4..e3bb2567e0 100644 --- a/engines/rdma.c +++ b/engines/rdma.c @@ -832,6 +832,12 @@ static void fio_rdmaio_queued(struct thread_data *td, struct io_u **io_us, memcpy(&io_u->issue_time, &now, sizeof(now)); io_u_queued(td, io_u); } + + /* + * only used for iolog + */ + if (td->o.read_iolog_file) + memcpy(&td->last_issue, &now, sizeof(now)); } static int fio_rdmaio_commit(struct thread_data *td) @@ -1404,7 +1410,8 @@ FIO_STATIC struct ioengine_ops ioengine = { .cleanup = fio_rdmaio_cleanup, .open_file = fio_rdmaio_open_file, .close_file = fio_rdmaio_close_file, - .flags = FIO_DISKLESSIO | FIO_UNIDIR | FIO_PIPEIO, + .flags = FIO_DISKLESSIO | FIO_UNIDIR | FIO_PIPEIO | + FIO_ASYNCIO_SETS_ISSUE_TIME, .options = options, .option_struct_size = sizeof(struct rdmaio_options), }; From 2fe71558c9718e88f6a1f243f2de0560e0c44aa0 Mon Sep 17 00:00:00 2001 From: Luis Useche Date: Wed, 15 Jun 2022 15:44:54 -0700 Subject: [PATCH 0191/1097] Init file_cache to invalid (maj, min) In the very unlikely case that the trace was taken from a device with major and minor zeroes, the cache will wrongly hit the first time. We are hitting this problem when trying to replay generated traces with major and minor zeroes. Initializing with ~0U fixes this problem. Signed-off-by: Luis Useche --- blktrace.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/blktrace.c b/blktrace.c index 619121c704..00e5f9a9b7 100644 --- a/blktrace.c +++ b/blktrace.c @@ -442,7 +442,10 @@ bool init_blktrace_read(struct thread_data *td, const char *filename, int need_s bool read_blktrace(struct thread_data* td) { struct blk_io_trace t; - struct file_cache cache = { }; + struct file_cache cache = { + .maj = ~0U, + .min = ~0U, + }; unsigned long ios[DDIR_RWDIR_SYNC_CNT] = { }; unsigned long long rw_bs[DDIR_RWDIR_CNT] = { }; unsigned long skipped_writes; From d4bf5e6193b97c5e5490fdb93b069d149a38777c Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Sun, 19 Jun 2022 12:04:19 -0600 Subject: [PATCH 0192/1097] gettime: fix whitespace damage Signed-off-by: Jens Axboe --- gettime.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/gettime.c b/gettime.c index 099e9d9f6c..1446242021 100644 --- a/gettime.c +++ b/gettime.c @@ -431,22 +431,22 @@ void fio_clock_init(void) uint64_t ntime_since(const struct timespec *s, const struct timespec *e) { - int64_t sec, nsec; + int64_t sec, nsec; - sec = e->tv_sec - s->tv_sec; - nsec = e->tv_nsec - s->tv_nsec; - if (sec > 0 && nsec < 0) { - sec--; - nsec += 1000000000LL; - } + sec = e->tv_sec - s->tv_sec; + nsec = e->tv_nsec - s->tv_nsec; + if (sec > 0 && nsec < 0) { + sec--; + nsec += 1000000000LL; + } /* * time warp bug on some kernels? */ - if (sec < 0 || (sec == 0 && nsec < 0)) - return 0; + if (sec < 0 || (sec == 0 && nsec < 0)) + return 0; - return nsec + (sec * 1000000000LL); + return nsec + (sec * 1000000000LL); } uint64_t ntime_since_now(const struct timespec *s) From 6aaebfbe7269f95164ac83a04505869f96f5f83a Mon Sep 17 00:00:00 2001 From: Ankit Kumar Date: Wed, 22 Jun 2022 16:55:46 +0530 Subject: [PATCH 0193/1097] configure: add option to disable xnvme build Add option to disable xnvme build even if found. Remove enable xnvme build option, as xnvme support is already being probed. Signed-off-by: Ankit Kumar Link: https://lore.kernel.org/r/20220622112546.13503-2-ankit.kumar@samsung.com Signed-off-by: Jens Axboe --- configure | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/configure b/configure index 510af42448..04a1d0e280 100755 --- a/configure +++ b/configure @@ -171,7 +171,7 @@ march_set="no" libiscsi="no" libnbd="no" libnfs="no" -xnvme="no" +xnvme="" libzbc="" dfs="" dynamic_engines="no" @@ -241,7 +241,7 @@ for opt do ;; --disable-libzbc) libzbc="no" ;; - --enable-xnvme) xnvme="yes" + --disable-xnvme) xnvme="no" ;; --disable-tcmalloc) disable_tcmalloc="yes" ;; @@ -294,7 +294,7 @@ if test "$show_help" = "yes" ; then echo "--with-ime= Install path for DDN's Infinite Memory Engine" echo "--enable-libiscsi Enable iscsi support" echo "--enable-libnbd Enable libnbd (NBD engine) support" - echo "--enable-xnvme Enable xnvme support" + echo "--disable-xnvme Disable xnvme support even if found" echo "--disable-libzbc Disable libzbc even if found" echo "--disable-tcmalloc Disable tcmalloc support" echo "--dynamic-libengines Lib-based ioengines as dynamic libraries" @@ -2619,7 +2619,7 @@ fi ########################################## # Check if we have xnvme -if test "$xnvme" != "yes" ; then +if test "$xnvme" != "no" ; then if check_min_lib_version xnvme 0.2.0; then xnvme="yes" xnvme_cflags=$(pkg-config --cflags xnvme) From d17f5c8bf9457b32cd0e77681bb7a08aa83d3cb9 Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Thu, 23 Jun 2022 06:26:34 -0700 Subject: [PATCH 0194/1097] ci/travis-*: Fix shellcheck warnings Although these scripts are no longer used, fix the shellcheck warnings in these scripts. This patch does not change any functionality. Signed-off-by: Bart Van Assche --- ci/travis-install-librpma.sh | 6 +++--- ci/travis-install-pmdk.sh | 9 +++++---- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/ci/travis-install-librpma.sh b/ci/travis-install-librpma.sh index b127f3f569..4e5ed21d4d 100755 --- a/ci/travis-install-librpma.sh +++ b/ci/travis-install-librpma.sh @@ -16,7 +16,7 @@ cmake .. -DCMAKE_BUILD_TYPE=Release \ -DBUILD_DOC=OFF \ -DBUILD_EXAMPLES=OFF \ -DBUILD_TESTS=OFF -make -j$(nproc) -sudo make -j$(nproc) install -cd $WORKDIR +make -j"$(nproc)" +sudo make -j"$(nproc)" install +cd "$WORKDIR" rm -rf $ZIP_FILE rpma-${LIBRPMA_VERSION} diff --git a/ci/travis-install-pmdk.sh b/ci/travis-install-pmdk.sh index 3b0b5bbc56..7bde9fd09a 100755 --- a/ci/travis-install-pmdk.sh +++ b/ci/travis-install-pmdk.sh @@ -12,7 +12,8 @@ WORKDIR=$(pwd) # /bin/sh: 1: clang: not found # if CC is not set to the full path of clang. # -export CC=$(type -P $CC) +CC=$(type -P "$CC") +export CC # Install PMDK libraries, because PMDK's libpmem # is a dependency of the librpma fio engine. @@ -22,7 +23,7 @@ export CC=$(type -P $CC) wget https://github.com/pmem/pmdk/releases/download/${PMDK_VERSION}/pmdk-${PMDK_VERSION}.tar.gz tar -xzf pmdk-${PMDK_VERSION}.tar.gz cd pmdk-${PMDK_VERSION} -make -j$(nproc) NDCTL_ENABLE=n -sudo make -j$(nproc) install prefix=/usr NDCTL_ENABLE=n -cd $WORKDIR +make -j"$(nproc)" NDCTL_ENABLE=n +sudo make -j"$(nproc)" install prefix=/usr NDCTL_ENABLE=n +cd "$WORKDIR" rm -rf pmdk-${PMDK_VERSION} From 787c02a6aceba5cf9e819ce677a04556deaef847 Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Wed, 22 Jun 2022 15:37:29 -0700 Subject: [PATCH 0195/1097] ci: Verify the Android build Let the continuous integration infrastructure verify the fio Android build in order to detect regressions in the Android build quickly. Signed-off-by: Bart Van Assche --- .github/workflows/ci.yml | 5 +++++ ci/actions-build.sh | 19 +++++++++++++++++-- ci/actions-full-test.sh | 2 ++ ci/actions-install.sh | 7 +++++++ ci/actions-smoke-test.sh | 2 ++ 5 files changed, 33 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cd8ce1429c..650366b23d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,6 +15,7 @@ jobs: - linux-clang - macos - linux-i686-gcc + - android include: - build: linux-gcc os: ubuntu-20.04 @@ -27,8 +28,12 @@ jobs: - build: linux-i686-gcc os: ubuntu-20.04 arch: i686 + - build: android + os: ubuntu-20.04 + arch: aarch64-linux-android32 env: + CI_TARGET_BUILD: ${{ matrix.build }} CI_TARGET_ARCH: ${{ matrix.arch }} CC: ${{ matrix.cc }} diff --git a/ci/actions-build.sh b/ci/actions-build.sh index 74a6fdcbf0..2b3de8e3da 100755 --- a/ci/actions-build.sh +++ b/ci/actions-build.sh @@ -11,8 +11,23 @@ main() { local configure_flags=() set_ci_target_os - case "${CI_TARGET_OS}" in - "linux") + case "${CI_TARGET_BUILD}/${CI_TARGET_OS}" in + android/*) + export UNAME=Android + if [ -z "${CI_TARGET_ARCH}" ]; then + echo "Error: CI_TARGET_ARCH has not been set" + return 1 + fi + NDK=$PWD/android-ndk-r24/toolchains/llvm/prebuilt/linux-x86_64/bin + export PATH="${NDK}:${PATH}" + export LIBS="-landroid" + CC=${NDK}/${CI_TARGET_ARCH}-clang + if [ ! -e "${CC}" ]; then + echo "Error: could not find ${CC}" + return 1 + fi + ;; + */linux) case "${CI_TARGET_ARCH}" in "i686") extra_cflags="${extra_cflags} -m32" diff --git a/ci/actions-full-test.sh b/ci/actions-full-test.sh index 8282002f76..d1675f6eb2 100755 --- a/ci/actions-full-test.sh +++ b/ci/actions-full-test.sh @@ -3,6 +3,8 @@ set -eu main() { + [ "${CI_TARGET_BUILD}" = android ] && return 0 + echo "Running long running tests..." export PYTHONUNBUFFERED="TRUE" if [[ "${CI_TARGET_ARCH}" == "arm64" ]]; then diff --git a/ci/actions-install.sh b/ci/actions-install.sh index 0e472717d2..ff5149265e 100755 --- a/ci/actions-install.sh +++ b/ci/actions-install.sh @@ -83,6 +83,13 @@ install_macos() { } main() { + if [ "${CI_TARGET_BUILD}" = "android" ]; then + echo "Installing Android NDK..." + wget --quiet https://dl.google.com/android/repository/android-ndk-r24-linux.zip + unzip -q android-ndk-r24-linux.zip + return 0 + fi + set_ci_target_os install_function="install_${CI_TARGET_OS}" diff --git a/ci/actions-smoke-test.sh b/ci/actions-smoke-test.sh index c129c89fad..3196f6a1ba 100755 --- a/ci/actions-smoke-test.sh +++ b/ci/actions-smoke-test.sh @@ -3,6 +3,8 @@ set -eu main() { + [ "${CI_TARGET_BUILD}" = "android" ] && return 0 + echo "Running smoke tests..." make test } From 5366025a15bd794052d3e74be450c5a70e12bf0c Mon Sep 17 00:00:00 2001 From: Georg Sauthoff Date: Thu, 30 Jun 2022 23:30:46 +0200 Subject: [PATCH 0196/1097] Simplify and optimize __fill_random_buf This reduces the number of source lines and the code size. For example, when compiling with GCC 12.1 (-O3 -march=skylake), the resulting assembly shrinks from 33 to 27 instructions and the number of jump instructions is reduced from 4 to 3. NB: GCC is able to eliminate the memcpy() call. NB: Even if a compiler doesn't eliminate the memcpy() call, it's very unlikely to ever get called since the buffer sizes are expected to be powers of two (>= 8), usually. Signed-off-by: Georg Sauthoff --- lib/rand.c | 27 ++++++++------------------- 1 file changed, 8 insertions(+), 19 deletions(-) diff --git a/lib/rand.c b/lib/rand.c index 6e893e80ba..2243c2b434 100644 --- a/lib/rand.c +++ b/lib/rand.c @@ -97,29 +97,18 @@ void init_rand_seed(struct frand_state *state, uint64_t seed, bool use64) void __fill_random_buf(void *buf, unsigned int len, uint64_t seed) { - void *ptr = buf; + uint64_t *b = buf; + uint64_t *e = b + len / sizeof(*b); + unsigned int rest = len % sizeof(*b); - while (len) { - int this_len; - - if (len >= sizeof(int64_t)) { - *((int64_t *) ptr) = seed; - this_len = sizeof(int64_t); - } else if (len >= sizeof(int32_t)) { - *((int32_t *) ptr) = seed; - this_len = sizeof(int32_t); - } else if (len >= sizeof(int16_t)) { - *((int16_t *) ptr) = seed; - this_len = sizeof(int16_t); - } else { - *((int8_t *) ptr) = seed; - this_len = sizeof(int8_t); - } - ptr += this_len; - len -= this_len; + for (; b != e; ++b) { + *b = seed; seed *= GOLDEN_RATIO_PRIME; seed >>= 3; } + + if (fio_unlikely(rest)) + __builtin_memcpy(e, &seed, rest); } uint64_t fill_random_buf(struct frand_state *fs, void *buf, From aa75fc9db6b98ad317911b35b4a968139a648bb8 Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Fri, 1 Jul 2022 15:02:06 -0600 Subject: [PATCH 0197/1097] lib/rand: improve __fill_random_buf() This won't be equivalent to what we have, but I _think_ the randomness is good enough for this purpose. This improves performance by about 30% for me, tested on both aarch64 and x86-64. Signed-off-by: Jens Axboe --- lib/rand.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/rand.c b/lib/rand.c index 2243c2b434..1df1a7a049 100644 --- a/lib/rand.c +++ b/lib/rand.c @@ -103,8 +103,7 @@ void __fill_random_buf(void *buf, unsigned int len, uint64_t seed) for (; b != e; ++b) { *b = seed; - seed *= GOLDEN_RATIO_PRIME; - seed >>= 3; + seed *= GOLDEN_RATIO_64; } if (fio_unlikely(rest)) From dc4729e3ef6a9116d7cd30e96e4f5863883e5bd7 Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Fri, 1 Jul 2022 15:03:39 -0600 Subject: [PATCH 0198/1097] hash: cleanups - Use __hash_u64() for __fill_random_buffer() - Convert rdma to use GOLDEN_RATIO_64 That's the last user of GOLDEN_RATIO_PRIME, which due to bit sparseness isn't really useful for our purposes. Signed-off-by: Jens Axboe --- engines/rdma.c | 2 +- hash.h | 26 -------------------------- lib/rand.c | 2 +- 3 files changed, 2 insertions(+), 28 deletions(-) diff --git a/engines/rdma.c b/engines/rdma.c index e3bb2567e0..fcb4106889 100644 --- a/engines/rdma.c +++ b/engines/rdma.c @@ -1389,7 +1389,7 @@ static int fio_rdmaio_setup(struct thread_data *td) rd = malloc(sizeof(*rd)); memset(rd, 0, sizeof(*rd)); - init_rand_seed(&rd->rand_state, (unsigned int) GOLDEN_RATIO_PRIME, 0); + init_rand_seed(&rd->rand_state, (unsigned int) GOLDEN_RATIO_64, 0); td->io_ops_data = rd; } diff --git a/hash.h b/hash.h index f7596a5636..51f0706e2c 100644 --- a/hash.h +++ b/hash.h @@ -9,32 +9,6 @@ (C) 2002 William Lee Irwin III, IBM */ /* - * Knuth recommends primes in approximately golden ratio to the maximum - * integer representable by a machine word for multiplicative hashing. - * Chuck Lever verified the effectiveness of this technique: - * http://www.citi.umich.edu/techreports/reports/citi-tr-00-1.pdf - * - * These primes are chosen to be bit-sparse, that is operations on - * them can use shifts and additions instead of multiplications for - * machines where multiplications are slow. - */ - -#if BITS_PER_LONG == 32 -/* 2^31 + 2^29 - 2^25 + 2^22 - 2^19 - 2^16 + 1 */ -#define GOLDEN_RATIO_PRIME 0x9e370001UL -#elif BITS_PER_LONG == 64 -/* 2^63 + 2^61 - 2^57 + 2^54 - 2^51 - 2^18 + 1 */ -#define GOLDEN_RATIO_PRIME 0x9e37fffffffc0001UL -#else -#error Define GOLDEN_RATIO_PRIME for your wordsize. -#endif - -/* - * The above primes are actively bad for hashing, since they are - * too sparse. The 32-bit one is mostly ok, the 64-bit one causes - * real problems. Besides, the "prime" part is pointless for the - * multiplicative hash. - * * Although a random odd number will do, it turns out that the golden * ratio phi = (sqrt(5)-1)/2, or its negative, has particularly nice * properties. diff --git a/lib/rand.c b/lib/rand.c index 1df1a7a049..1e669116f3 100644 --- a/lib/rand.c +++ b/lib/rand.c @@ -103,7 +103,7 @@ void __fill_random_buf(void *buf, unsigned int len, uint64_t seed) for (; b != e; ++b) { *b = seed; - seed *= GOLDEN_RATIO_64; + seed = __hash_u64(seed); } if (fio_unlikely(rest)) From 1eb5ca76ee17ff80dd06a0c2d22498ab720ec76f Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Tue, 5 Jul 2022 07:18:54 -0600 Subject: [PATCH 0199/1097] configure: revert NFS configure change It's broken and nobody seems to be stepping up to fix it, revert the known problematic change. Fixes: 165b8a70f919 ("NFS configure fixes") Link: https://github.com/axboe/fio/pull/1216 Signed-off-by: Jens Axboe --- configure | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/configure b/configure index 04a1d0e280..7965f0b079 100755 --- a/configure +++ b/configure @@ -245,7 +245,7 @@ for opt do ;; --disable-tcmalloc) disable_tcmalloc="yes" ;; - --disable-nfs) disable_nfs="yes" + --enable-libnfs) libnfs="yes" ;; --dynamic-libengines) dynamic_engines="yes" ;; @@ -279,7 +279,6 @@ if test "$show_help" = "yes" ; then echo "--disable-rados Disable Rados support even if found" echo "--disable-rbd Disable Rados Block Device even if found" echo "--disable-http Disable HTTP support even if found" - echo "--disable-nfs Disable userspace NFS support even if found" echo "--disable-gfapi Disable gfapi" echo "--enable-libhdfs Enable hdfs support" echo "--enable-libnfs Enable nfs support" @@ -2314,15 +2313,17 @@ print_config "DAOS File System (dfs) Engine" "$dfs" ########################################## # Check if we have libnfs (for userspace nfs support). -if test "$disable_nfs" != "yes"; then +if test "$libnfs" = "yes" ; then if $(pkg-config libnfs > /dev/null 2>&1); then libnfs="yes" libnfs_cflags=$(pkg-config --cflags libnfs) - libnfs_libs=$(pkg-config --libs libnfs) + # libnfs_libs=$(pkg-config --libs libnfs) + libnfs_libs=/usr/local/lib/libnfs.a else if test "$libnfs" = "yes" ; then echo "libnfs" "Install libnfs" fi + libnfs="no" fi fi print_config "NFS engine" "$libnfs" From 64f11975d06a91059aee9bfface33591b3627feb Mon Sep 17 00:00:00 2001 From: Tuan Hoang Date: Thu, 7 Jul 2022 00:35:09 +0200 Subject: [PATCH 0200/1097] server: only do cpu_to_le64() on io_sample_data member if iolog is histogram In the case of histogram iolog, the union io_sample_data member is a pointer of struct io_u_plat_entry, while in the case of normal iolog, it is an uint64_t. Thus only need to do the byteswap in case it is an uint64_t. Signed-off-by: Ulrich Weigand Signed-off-by: Tuan Hoang --- server.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server.c b/server.c index 4c71bd4494..b453be5fc3 100644 --- a/server.c +++ b/server.c @@ -2284,7 +2284,8 @@ int fio_send_iolog(struct thread_data *td, struct io_log *log, const char *name) struct io_sample *s = get_sample(log, cur_log, i); s->time = cpu_to_le64(s->time); - s->data.val = cpu_to_le64(s->data.val); + if (log->log_type != IO_LOG_TYPE_HIST) + s->data.val = cpu_to_le64(s->data.val); s->__ddir = __cpu_to_le32(s->__ddir); s->bs = cpu_to_le64(s->bs); From 3b189ee6fae2cd1e1f78cc13b896b70eca9cc09a Mon Sep 17 00:00:00 2001 From: Tuan Hoang Date: Thu, 7 Jul 2022 09:53:52 +0200 Subject: [PATCH 0201/1097] client: only do le64_to_cpu() on io_sample_data member if iolog is histogram In the case of histogram iolog, the union io_sample_data member is a pointer of struct io_u_plat_entry, while in the case of normal iolog, it is an uint64_t. Thus only need to do the byteswap in case it is an uint64_t. This has been done similarly in server code. Signed-off-by: Ulrich Weigand Signed-off-by: Tuan Hoang --- client.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client.c b/client.c index 605a3ce573..37da74bca5 100644 --- a/client.c +++ b/client.c @@ -1702,7 +1702,8 @@ static struct cmd_iolog_pdu *convert_iolog(struct fio_net_cmd *cmd, s = (struct io_sample *)((char *)s + sizeof(struct io_u_plat_entry) * i); s->time = le64_to_cpu(s->time); - s->data.val = le64_to_cpu(s->data.val); + if (ret->log_type != IO_LOG_TYPE_HIST) + s->data.val = le64_to_cpu(s->data.val); s->__ddir = __le32_to_cpu(s->__ddir); s->bs = le64_to_cpu(s->bs); s->priority = le16_to_cpu(s->priority); From d6225c1550827077c0c0f9e1b8816b4f35cd5304 Mon Sep 17 00:00:00 2001 From: Rebecca Cran Date: Sun, 10 Jul 2022 20:55:47 -0600 Subject: [PATCH 0202/1097] Update README.rst to specify secure protocols where possible Change git clone commands to specify https instead of git or http. GitHub has removed support for the git protocol (https://github.blog/changelog/2022-03-15-removed-unencrypted-git-protocol-and-certain-ssh-keys/) and http accesses will normally redirect to https. Update links to use https where possible: all the sites with updated links automatically redirect http accesses to https. Signed-off-by: Rebecca Cran Link: https://lore.kernel.org/r/20220711025547.11917-1-rebecca@bsdio.com [axboe: fix git.kernel.dk location] Signed-off-by: Jens Axboe --- README.rst | 27 ++++++++------------------- 1 file changed, 8 insertions(+), 19 deletions(-) diff --git a/README.rst b/README.rst index 527f33abcb..4d736eafc7 100644 --- a/README.rst +++ b/README.rst @@ -27,31 +27,20 @@ Source Fio resides in a git repo, the canonical place is: - git://git.kernel.dk/fio.git - -When inside a corporate firewall, git:// URL sometimes does not work. -If git:// does not work, use the http protocol instead: - - http://git.kernel.dk/fio.git + https://git.kernel.dk/cgit/fio/ Snapshots are frequently generated and :file:`fio-git-*.tar.gz` include the git meta data as well. Other tarballs are archives of official fio releases. Snapshots can download from: - http://brick.kernel.dk/snaps/ + https://brick.kernel.dk/snaps/ There are also two official mirrors. Both of these are automatically synced with the main repository, when changes are pushed. If the main repo is down for some reason, either one of these is safe to use as a backup: - git://git.kernel.org/pub/scm/linux/kernel/git/axboe/fio.git - https://git.kernel.org/pub/scm/linux/kernel/git/axboe/fio.git -or - - git://github.com/axboe/fio.git - https://github.com/axboe/fio.git @@ -70,7 +59,7 @@ email to majordomo@vger.kernel.org with in the body of the email. Archives can be found here: - http://www.spinics.net/lists/fio/ + https://www.spinics.net/lists/fio/ or here: @@ -97,12 +86,12 @@ Binary packages Debian: Starting with Debian "Squeeze", fio packages are part of the official - Debian repository. http://packages.debian.org/search?keywords=fio . + Debian repository. https://packages.debian.org/search?keywords=fio . Ubuntu: Starting with Ubuntu 10.04 LTS (aka "Lucid Lynx"), fio packages are part of the Ubuntu "universe" repository. - http://packages.ubuntu.com/search?keywords=fio . + https://packages.ubuntu.com/search?keywords=fio . Red Hat, Fedora, CentOS & Co: Starting with Fedora 9/Extra Packages for Enterprise Linux 4, fio @@ -176,7 +165,7 @@ directory. How to compile fio on 64-bit Windows: - 1. Install Cygwin (http://www.cygwin.com/). Install **make** and all + 1. Install Cygwin (https://www.cygwin.com/). Install **make** and all packages starting with **mingw64-x86_64**. Ensure **mingw64-x86_64-zlib** are installed if you wish to enable fio's log compression functionality. @@ -205,8 +194,8 @@ browser to :file:`./doc/output/html/index.html`. To build manual page run ``make -C doc man`` and then ``man doc/output/man/fio.1``. To see what other output formats are supported run ``make -C doc help``. -.. _reStructuredText: http://www.sphinx-doc.org/rest.html -.. _Sphinx: http://www.sphinx-doc.org +.. _reStructuredText: https://www.sphinx-doc.org/rest.html +.. _Sphinx: https://www.sphinx-doc.org Platforms From 4a4c21bfbad3cf84133fa57bd58995f887dd81c4 Mon Sep 17 00:00:00 2001 From: Giuseppe Baccini Date: Tue, 19 Jul 2022 09:39:46 +0200 Subject: [PATCH 0203/1097] Fixed misplaced goto in http.c - In http.c:fio_http_queue function, when the user specifies rw=write and if curl_easy_perform fails then the control reaches line 565 and incorrectly prints: "WARNING: Only DDIR_READ/DDIR_WRITE/DDIR_TRIM are supported!". Fix to this consists in moving statement: "goto err" at the end of the block as already has been done for trim/read blocks. Signed-off-by: Giuseppe Baccini --- engines/http.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engines/http.c b/engines/http.c index 696febe151..b6df70f8d8 100644 --- a/engines/http.c +++ b/engines/http.c @@ -526,8 +526,8 @@ static enum fio_q_status fio_http_queue(struct thread_data *td, if (status == 100 || (status >= 200 && status <= 204)) goto out; log_err("DDIR_WRITE failed with HTTP status code %ld\n", status); - goto err; } + goto err; } else if (io_u->ddir == DDIR_READ) { curl_easy_setopt(http->curl, CURLOPT_READDATA, NULL); curl_easy_setopt(http->curl, CURLOPT_WRITEDATA, &_curl_stream); From 9c1c1a8d6a4f30eba9595da951d18db1685c03d8 Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Tue, 19 Jul 2022 13:21:19 -0600 Subject: [PATCH 0204/1097] engines/http: silence openssl 3.0 deprecation warnings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Maybe someone will fix these one day, for now shut up the annoying warning about certain functions being deprecated. Example: engines/http.c: In function ‘_gen_hex_md5’: engines/http.c:261:9: warning: ‘MD5’ is deprecated: Since OpenSSL 3.0 [-Wdeprecated-declarations] 261 | MD5((unsigned char*)p, len, hash); | ^~~ In file included from engines/http.c:28: /usr/include/openssl/md5.h:52:38: note: declared here 52 | OSSL_DEPRECATEDIN_3_0 unsigned char *MD5(const unsigned char *d, size_t n, | ^~~ engines/http.c: In function ‘_hmac’: engines/http.c:273:9: warning: ‘HMAC_CTX_new’ is deprecated: Since OpenSSL 3.0 [-Wdeprecated-declarations] 273 | ctx = HMAC_CTX_new(); | ^~~ Signed-off-by: Jens Axboe --- engines/http.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/engines/http.c b/engines/http.c index b6df70f8d8..1de9e66c75 100644 --- a/engines/http.c +++ b/engines/http.c @@ -29,6 +29,10 @@ #include "fio.h" #include "../optgroup.h" +/* + * Silence OpenSSL 3.0 deprecated function warnings + */ +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" enum { FIO_HTTP_WEBDAV = 0, From 02a36caa69f5675f7144fbeddb7a32e1d35ce0c7 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Fri, 15 Jul 2022 20:20:55 +0000 Subject: [PATCH 0205/1097] docs: clarify write_iolog description Note that the log file is opened in append mode to avoid confusion when the same log file is used repeatedly. Signed-off-by: Vincent Fu --- HOWTO.rst | 3 ++- fio.1 | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/HOWTO.rst b/HOWTO.rst index 470777e2eb..104cce2d21 100644 --- a/HOWTO.rst +++ b/HOWTO.rst @@ -3049,7 +3049,8 @@ I/O replay Write the issued I/O patterns to the specified file. See :option:`read_iolog`. Specify a separate file for each job, otherwise the - iologs will be interspersed and the file may be corrupt. + iologs will be interspersed and the file may be corrupt. This file will + be opened in append mode. .. option:: read_iolog=str diff --git a/fio.1 b/fio.1 index 948c01f9c3..ce9bf3ef4d 100644 --- a/fio.1 +++ b/fio.1 @@ -2793,7 +2793,8 @@ of milliseconds. Defaults to 1000. .BI write_iolog \fR=\fPstr Write the issued I/O patterns to the specified file. See \fBread_iolog\fR. Specify a separate file for each job, otherwise the -iologs will be interspersed and the file may be corrupt. +iologs will be interspersed and the file may be corrupt. This file will be +opened in append mode. .TP .BI read_iolog \fR=\fPstr Open an iolog with the specified filename and replay the I/O patterns it From 98ab12629c6a98fb53e28a4a70f53ce594cca858 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Mon, 11 Jul 2022 15:51:13 +0000 Subject: [PATCH 0206/1097] configure: cleanups for nfs ioengine By default, build the nfs ioengine if configure can find libnfs. Add a --disable-libnfs to avoid building the nfs ionegine even if support could be found on the system. Change the --enable-libnfs option to abort the build if libnfs cannot be found. Also use pkg-config to find the libnfs path instead of hard coding the path. Finally, drop CONFIG_NFS and use CONFIG_LIBNFS everywhere. There's no reason to have two separate symbols. Signed-off-by: Vincent Fu --- configure | 16 +++++++--------- options.c | 2 +- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/configure b/configure index 7965f0b079..36450df85b 100755 --- a/configure +++ b/configure @@ -170,7 +170,7 @@ disable_native="no" march_set="no" libiscsi="no" libnbd="no" -libnfs="no" +libnfs="" xnvme="" libzbc="" dfs="" @@ -245,6 +245,8 @@ for opt do ;; --disable-tcmalloc) disable_tcmalloc="yes" ;; + --disable-libnfs) libnfs="no" + ;; --enable-libnfs) libnfs="yes" ;; --dynamic-libengines) dynamic_engines="yes" @@ -282,6 +284,7 @@ if test "$show_help" = "yes" ; then echo "--disable-gfapi Disable gfapi" echo "--enable-libhdfs Enable hdfs support" echo "--enable-libnfs Enable nfs support" + echo "--disable-libnfs Disable nfs support" echo "--disable-lex Disable use of lex/yacc for math" echo "--disable-pmem Disable pmem based engines even if found" echo "--enable-lex Enable use of lex/yacc for math" @@ -2313,15 +2316,14 @@ print_config "DAOS File System (dfs) Engine" "$dfs" ########################################## # Check if we have libnfs (for userspace nfs support). -if test "$libnfs" = "yes" ; then +if test "$libnfs" != "no" ; then if $(pkg-config libnfs > /dev/null 2>&1); then libnfs="yes" libnfs_cflags=$(pkg-config --cflags libnfs) - # libnfs_libs=$(pkg-config --libs libnfs) - libnfs_libs=/usr/local/lib/libnfs.a + libnfs_libs=$(pkg-config --libs libnfs) else if test "$libnfs" = "yes" ; then - echo "libnfs" "Install libnfs" + feature_not_found "libnfs" "libnfs" fi libnfs="no" fi @@ -3190,9 +3192,6 @@ fi if test "$dfs" = "yes" ; then output_sym "CONFIG_DFS" fi -if test "$libnfs" = "yes" ; then - output_sym "CONFIG_NFS" -fi if test "$march_set" = "no" && test "$build_native" = "yes" ; then output_sym "CONFIG_BUILD_NATIVE" fi @@ -3234,7 +3233,6 @@ if test "$libnbd" = "yes" ; then fi if test "$libnfs" = "yes" ; then output_sym "CONFIG_LIBNFS" - echo "CONFIG_LIBNFS=m" >> $config_host_mak echo "LIBNFS_CFLAGS=$libnfs_cflags" >> $config_host_mak echo "LIBNFS_LIBS=$libnfs_libs" >> $config_host_mak fi diff --git a/options.c b/options.c index 2b183c60d0..5d3daedf34 100644 --- a/options.c +++ b/options.c @@ -2140,7 +2140,7 @@ struct fio_option fio_options[FIO_MAX_OPTS] = { .help = "DAOS File System (dfs) IO engine", }, #endif -#ifdef CONFIG_NFS +#ifdef CONFIG_LIBNFS { .ival = "nfs", .help = "NFS IO engine", }, From fbd4ab3846b65d0a2455a24ca5c1613d0ec3158a Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Tue, 12 Jul 2022 15:09:37 +0000 Subject: [PATCH 0207/1097] engines/nfs: remove commit hook The nfs ioengine does not need a commit hook because requests are already committed when the queue hook is called. The queue hook calls nfs_{pread/pwrite}_async and there is no separate commit step necessary for the IO to be fully submitted. This change also allows submission latencies to be reported because the commit hook was not setting the io_u's issue_time. Signed-off-by: Vincent Fu --- engines/nfs.c | 9 --------- 1 file changed, 9 deletions(-) diff --git a/engines/nfs.c b/engines/nfs.c index 21be88334d..7031769d21 100644 --- a/engines/nfs.c +++ b/engines/nfs.c @@ -279,14 +279,6 @@ static int fio_libnfs_close(struct thread_data *td, struct fio_file *f) return ret; } -/* - * Hook for writing out outstanding data. - */ -static int fio_libnfs_commit(struct thread_data *td) { - nfs_event_loop(td, true); - return 0; -} - struct ioengine_ops ioengine = { .name = "nfs", .version = FIO_IOOPS_VERSION, @@ -297,7 +289,6 @@ struct ioengine_ops ioengine = { .cleanup = fio_libnfs_cleanup, .open_file = fio_libnfs_open, .close_file = fio_libnfs_close, - .commit = fio_libnfs_commit, .flags = FIO_DISKLESSIO | FIO_NOEXTEND | FIO_NODISKUTIL, .options = options, .option_struct_size = sizeof(struct fio_libnfs_options), From dff32ddb97f2257975b6047474d665a5de7f7bbc Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Tue, 12 Jul 2022 15:30:33 +0000 Subject: [PATCH 0208/1097] ci: install libnfs for linux and macos builds We should try to build as many ioengines as possible in our automated builds in order to detect breakage sooner. Signed-off-by: Vincent Fu --- ci/actions-install.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ci/actions-install.sh b/ci/actions-install.sh index ff5149265e..b5c4198f93 100755 --- a/ci/actions-install.sh +++ b/ci/actions-install.sh @@ -26,6 +26,7 @@ DPKGCFG libibverbs-dev libnuma-dev librdmacm-dev + libnfs-dev valgrind ) case "${CI_TARGET_ARCH}" in @@ -78,7 +79,7 @@ install_macos() { #echo "Updating homebrew..." #brew update >/dev/null 2>&1 echo "Installing packages..." - HOMEBREW_NO_AUTO_UPDATE=1 brew install cunit + HOMEBREW_NO_AUTO_UPDATE=1 brew install cunit libnfs pip3 install scipy six sphinx } From acbda87c34c743ff2d9e125d9539bcfbbf49eb75 Mon Sep 17 00:00:00 2001 From: Chris Weber Date: Fri, 22 Jul 2022 03:34:02 +0000 Subject: [PATCH 0209/1097] Fix multithread issues when operating on a single shared file When nrfiles=1, numjobs>1 and create_serialize=0, multiple threads try to create the single shared file in parallel. If the file was pre-existing, but an incorrect size, then multiple threads are deleting and creating at the same time. When all of this happens in parallel, there is a chance that the file can end up the incorrect size (the chance increases as numjobs increases). These changes handle the corner case described above by having a single thread create/extend the file prior to running all of the threads in parallel. By doing this step early, when setup_files() is called later, it should no longer need to create or extend the file, avoiding the race condition. The user still needs to set a fallocate option other than 'none' or the file will end up 0 bytes in size and the race condition will still occur. It would be simple to add a ftruncate() to the code to force this, but that would override the user's choice of fallocate options. Signed-off-by: Chris Weber --- backend.c | 19 ++++++++++++++++++- file.h | 1 + filesetup.c | 46 ++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 63 insertions(+), 3 deletions(-) diff --git a/backend.c b/backend.c index e5bb4e2590..3a99850db1 100644 --- a/backend.c +++ b/backend.c @@ -2314,8 +2314,25 @@ static void run_threads(struct sk_out *sk_out) for_each_td(td, i) { print_status_init(td->thread_number - 1); - if (!td->o.create_serialize) + if (!td->o.create_serialize) { + /* + * When operating on a single rile in parallel, + * perform single-threaded early setup so that + * when setup_files() does not run into issues + * later. + */ + if (!i && td->o.nr_files==1) { + if (setup_shared_file(td)) { + exit_value++; + if (td->error) + log_err("fio: pid=%d, err=%d/%s\n", + (int) td->pid, td->error, td->verror); + td_set_runstate(td, TD_REAPED); + todo--; + } + } continue; + } if (fio_verify_load_state(td)) goto reap; diff --git a/file.h b/file.h index da1b894706..e646cf22f6 100644 --- a/file.h +++ b/file.h @@ -201,6 +201,7 @@ struct thread_data; extern void close_files(struct thread_data *); extern void close_and_free_files(struct thread_data *); extern uint64_t get_start_offset(struct thread_data *, struct fio_file *); +extern int __must_check setup_shared_file(struct thread_data *); extern int __must_check setup_files(struct thread_data *); extern int __must_check file_invalidate_cache(struct thread_data *, struct fio_file *); #ifdef __cplusplus diff --git a/filesetup.c b/filesetup.c index ab6c488bb7..b30a2932db 100644 --- a/filesetup.c +++ b/filesetup.c @@ -143,7 +143,7 @@ static int extend_file(struct thread_data *td, struct fio_file *f) if (unlink_file || new_layout) { int ret; - dprint(FD_FILE, "layout unlink %s\n", f->file_name); + dprint(FD_FILE, "layout %d unlink %d %s\n", new_layout, unlink_file, f->file_name); ret = td_io_unlink_file(td, f); if (ret != 0 && ret != ENOENT) { @@ -198,6 +198,9 @@ static int extend_file(struct thread_data *td, struct fio_file *f) } } + + dprint(FD_FILE, "fill file %s, size %llu\n", f->file_name, (unsigned long long) f->real_file_size); + left = f->real_file_size; bs = td->o.max_bs[DDIR_WRITE]; if (bs > left) @@ -1078,6 +1081,45 @@ static bool create_work_dirs(struct thread_data *td, const char *fname) return true; } +int setup_shared_file(struct thread_data *td) +{ + struct fio_file *f; + uint64_t file_size; + int err = 0; + + if (td->o.nr_files > 1) { + log_err("fio: shared file setup called for multiple files\n"); + return -1; + } + + get_file_sizes(td); + + f = td->files[0]; + + if (f == NULL) { + log_err("fio: NULL shared file\n"); + return -1; + } + + file_size = thread_number * td->o.size; + dprint(FD_FILE, "shared setup %s real_file_size=%llu, desired=%llu\n", + f->file_name, (unsigned long long)f->real_file_size, (unsigned long long)file_size); + + if (f->real_file_size < file_size) { + dprint(FD_FILE, "fio: extending shared file\n"); + f->real_file_size = file_size; + err = extend_file(td, f); + if (!err) { + err = __file_invalidate_cache(td, f, 0, f->real_file_size); + } + get_file_sizes(td); + dprint(FD_FILE, "shared setup new real_file_size=%llu\n", + (unsigned long long)f->real_file_size); + } + + return err; +} + /* * Open the files and setup files sizes, creating files if necessary. */ @@ -1092,7 +1134,7 @@ int setup_files(struct thread_data *td) const unsigned long long bs = td_min_bs(td); uint64_t fs = 0; - dprint(FD_FILE, "setup files\n"); + dprint(FD_FILE, "setup files (thread_number=%d, subjob_number=%d)\n", td->thread_number, td->subjob_number); old_state = td_bump_runstate(td, TD_SETTING_UP); From 48f8268e88629d408ffd09b1601ad13366bd4ce1 Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Wed, 27 Jul 2022 20:56:32 -0600 Subject: [PATCH 0210/1097] Minor style fixups Missing space for a comparison, and braces that aren't really needed. Fixes: acbda87c34c7 ("Fix multithread issues when operating on a single shared file") Signed-off-by: Jens Axboe --- backend.c | 2 +- filesetup.c | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/backend.c b/backend.c index 3a99850db1..5159b60ddf 100644 --- a/backend.c +++ b/backend.c @@ -2321,7 +2321,7 @@ static void run_threads(struct sk_out *sk_out) * when setup_files() does not run into issues * later. */ - if (!i && td->o.nr_files==1) { + if (!i && td->o.nr_files == 1) { if (setup_shared_file(td)) { exit_value++; if (td->error) diff --git a/filesetup.c b/filesetup.c index b30a2932db..e0592209a1 100644 --- a/filesetup.c +++ b/filesetup.c @@ -1109,9 +1109,8 @@ int setup_shared_file(struct thread_data *td) dprint(FD_FILE, "fio: extending shared file\n"); f->real_file_size = file_size; err = extend_file(td, f); - if (!err) { + if (!err) err = __file_invalidate_cache(td, f, 0, f->real_file_size); - } get_file_sizes(td); dprint(FD_FILE, "shared setup new real_file_size=%llu\n", (unsigned long long)f->real_file_size); From 5b99196735a245224ec9321f796a9da30654ae6c Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Wed, 27 Jul 2022 21:04:31 -0600 Subject: [PATCH 0211/1097] README: add maintainer section Note that Vincent is also maintaining fio - and while in there, also make a note not to email any of us directly. The public resources should be used instead. Signed-off-by: Jens Axboe --- README.rst | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/README.rst b/README.rst index 4d736eafc7..67420903b1 100644 --- a/README.rst +++ b/README.rst @@ -81,6 +81,17 @@ benchmark/test tools out there weren't flexible enough to do what he wanted. Jens Axboe 20060905 +Maintainers +----------- + +Fio is maintained by Jens Axboe - however, for reporting bugs please use +the fio reflector or the GitHub page rather than email any of them +directly. By using the public resources, others will be able to learn from +the responses too. Chances are also good that other members will be able to +help with your inquiry as well. + + Binary packages --------------- From 3e1d3f2fc4a5f09174f0d6d70d036285d69f17c2 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Wed, 27 Jul 2022 15:43:53 +0000 Subject: [PATCH 0212/1097] .github: add pull request template We frequently remind contributors about expectations for commit messages. Add a template so that guidelines show up when contributors open a pull request on GitHub. Signed-off-by: Vincent Fu --- .github/PULL_REQUEST_TEMPLATE.md | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .github/PULL_REQUEST_TEMPLATE.md diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000000..4d98a6946c --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,8 @@ +Please confirm that your commit message(s) follow these guidelines: + +1. First line is a commit title, a descriptive one-liner for the change +2. Empty second line +3. Commit message body that explains why the change is useful. Break lines that + aren't something like a URL at 72-74 chars. +4. Empty line +5. Signed-off-by: Real Name From 55037c4839c65612fa388ae937e63661d8192ed9 Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Sun, 31 Jul 2022 12:06:12 -0600 Subject: [PATCH 0213/1097] t/io_uring: switch to GiB/sec if numbers get large Easier to read if we're above 2GiB/sec in bandwidth. Signed-off-by: Jens Axboe --- t/io_uring.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/t/io_uring.c b/t/io_uring.c index 100359129c..335a06ed31 100644 --- a/t/io_uring.c +++ b/t/io_uring.c @@ -1546,8 +1546,15 @@ int main(int argc, char *argv[]) else printf("IOPS=%lu, ", iops); max_iops = max(max_iops, iops); - if (!do_nop) - printf("BW=%luMiB/s, ", bw); + if (!do_nop) { + if (bw > 2000) { + double bw_g = (double) bw / 1000.0; + + printf("BW=%.2fGiB/s, ", bw_g); + } else { + printf("BW=%luMiB/s, ", bw); + } + } printf("IOS/call=%ld/%ld, inflight=(%s)\n", rpc, ipc, fdepths); done = this_done; calls = this_call; From 4b9e13dc27fb240c0cca67d37056f08401c51abe Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Tue, 2 Aug 2022 09:47:12 -0600 Subject: [PATCH 0214/1097] t/io_uring: support NUMA placement A few other changes in here as well that I didn't bother splitting out: - Use M for millions of IOPS - Cleanup the init prints a bit - Don't track file depths, becomes prohibitive on large number of files or devices. Signed-off-by: Jens Axboe --- t/io_uring.c | 446 +++++++++++++++++++++++++++++---------------------- 1 file changed, 252 insertions(+), 194 deletions(-) diff --git a/t/io_uring.c b/t/io_uring.c index 335a06ed31..35bf195644 100644 --- a/t/io_uring.c +++ b/t/io_uring.c @@ -11,6 +11,10 @@ #include #endif +#ifdef CONFIG_LIBNUMA +#include +#endif + #include #include #include @@ -100,6 +104,9 @@ struct submitter { io_context_t aio_ctx; #endif + int numa_node; + const char *filename; + struct file files[MAX_FDS]; unsigned nr_files; unsigned cur_file; @@ -110,6 +117,7 @@ static struct submitter *submitter; static volatile int finish; static int stats_running; static unsigned long max_iops; +static long page_size; static int depth = DEPTH; static int batch_submit = BATCH_SUBMIT; @@ -130,6 +138,7 @@ static int runtime = 0; /* runtime */ static int random_io = 1; /* random or sequential IO */ static int register_ring = 1; /* register ring */ static int use_sync = 0; /* use preadv2 */ +static int numa_placement = 0; /* set to node of device */ static unsigned long tsc_rate; @@ -611,12 +620,191 @@ static int reap_events_uring(struct submitter *s) return reaped; } +static void set_affinity(struct submitter *s) +{ +#ifdef CONFIG_LIBNUMA + struct bitmask *mask; + + if (s->numa_node == -1) + return; + + numa_set_preferred(s->numa_node); + + mask = numa_allocate_cpumask(); + numa_node_to_cpus(s->numa_node, mask); + numa_sched_setaffinity(s->tid, mask); +#endif +} + +static int detect_node(struct submitter *s, const char *name) +{ +#ifdef CONFIG_LIBNUMA + const char *base = basename(name); + char str[128]; + int ret, fd, node; + + sprintf(str, "/sys/block/%s/device/numa_node", base); + fd = open(str, O_RDONLY); + if (fd < 0) + return -1; + + ret = read(fd, str, sizeof(str)); + if (ret < 0) { + close(fd); + return -1; + } + node = atoi(str); + s->numa_node = node; + close(fd); +#else + s->numa_node = -1; +#endif + return 0; +} + +static int setup_aio(struct submitter *s) +{ +#ifdef CONFIG_LIBAIO + if (polled) { + fprintf(stderr, "aio does not support polled IO\n"); + polled = 0; + } + if (sq_thread_poll) { + fprintf(stderr, "aio does not support SQPOLL IO\n"); + sq_thread_poll = 0; + } + if (do_nop) { + fprintf(stderr, "aio does not support polled IO\n"); + do_nop = 0; + } + if (fixedbufs || register_files) { + fprintf(stderr, "aio does not support registered files or buffers\n"); + fixedbufs = register_files = 0; + } + + return io_queue_init(roundup_pow2(depth), &s->aio_ctx); +#else + fprintf(stderr, "Legacy AIO not available on this system/build\n"); + errno = EINVAL; + return -1; +#endif +} + +static int setup_ring(struct submitter *s) +{ + struct io_sq_ring *sring = &s->sq_ring; + struct io_cq_ring *cring = &s->cq_ring; + struct io_uring_params p; + int ret, fd; + void *ptr; + + memset(&p, 0, sizeof(p)); + + if (polled && !do_nop) + p.flags |= IORING_SETUP_IOPOLL; + if (sq_thread_poll) { + p.flags |= IORING_SETUP_SQPOLL; + if (sq_thread_cpu != -1) { + p.flags |= IORING_SETUP_SQ_AFF; + p.sq_thread_cpu = sq_thread_cpu; + } + } + + fd = io_uring_setup(depth, &p); + if (fd < 0) { + perror("io_uring_setup"); + return 1; + } + s->ring_fd = s->enter_ring_fd = fd; + + io_uring_probe(fd); + + if (fixedbufs) { + struct rlimit rlim; + + rlim.rlim_cur = RLIM_INFINITY; + rlim.rlim_max = RLIM_INFINITY; + /* ignore potential error, not needed on newer kernels */ + setrlimit(RLIMIT_MEMLOCK, &rlim); + + ret = io_uring_register_buffers(s); + if (ret < 0) { + perror("io_uring_register_buffers"); + return 1; + } + + if (dma_map) { + ret = io_uring_map_buffers(s); + if (ret < 0) { + perror("io_uring_map_buffers"); + return 1; + } + } + } + + if (register_files) { + ret = io_uring_register_files(s); + if (ret < 0) { + perror("io_uring_register_files"); + return 1; + } + } + + ptr = mmap(0, p.sq_off.array + p.sq_entries * sizeof(__u32), + PROT_READ | PROT_WRITE, MAP_SHARED | MAP_POPULATE, fd, + IORING_OFF_SQ_RING); + sring->head = ptr + p.sq_off.head; + sring->tail = ptr + p.sq_off.tail; + sring->ring_mask = ptr + p.sq_off.ring_mask; + sring->ring_entries = ptr + p.sq_off.ring_entries; + sring->flags = ptr + p.sq_off.flags; + sring->array = ptr + p.sq_off.array; + sq_ring_mask = *sring->ring_mask; + + s->sqes = mmap(0, p.sq_entries * sizeof(struct io_uring_sqe), + PROT_READ | PROT_WRITE, MAP_SHARED | MAP_POPULATE, fd, + IORING_OFF_SQES); + + ptr = mmap(0, p.cq_off.cqes + p.cq_entries * sizeof(struct io_uring_cqe), + PROT_READ | PROT_WRITE, MAP_SHARED | MAP_POPULATE, fd, + IORING_OFF_CQ_RING); + cring->head = ptr + p.cq_off.head; + cring->tail = ptr + p.cq_off.tail; + cring->ring_mask = ptr + p.cq_off.ring_mask; + cring->ring_entries = ptr + p.cq_off.ring_entries; + cring->cqes = ptr + p.cq_off.cqes; + cq_ring_mask = *cring->ring_mask; + return 0; +} + +static void *allocate_mem(struct submitter *s, int size) +{ + void *buf; + +#ifdef CONFIG_LIBNUMA + if (s->numa_node != -1) + return numa_alloc_onnode(size, s->numa_node); +#endif + + if (posix_memalign(&buf, page_size, bs)) { + printf("failed alloc\n"); + return NULL; + } + + return buf; +} + static int submitter_init(struct submitter *s) { - int i, nr_batch; + int i, nr_batch, err; + static int init_printed; + char buf[80]; s->tid = gettid(); - printf("submitter=%d, tid=%d\n", s->index, s->tid); + printf("submitter=%d, tid=%d, file=%s, node=%d\n", s->index, s->tid, + s->filename, s->numa_node); + + set_affinity(s); __init_rand64(&s->rand_state, pthread_self()); srand48(pthread_self()); @@ -624,6 +812,37 @@ static int submitter_init(struct submitter *s) for (i = 0; i < MAX_FDS; i++) s->files[i].fileno = i; + for (i = 0; i < roundup_pow2(depth); i++) { + void *buf; + + buf = allocate_mem(s, bs); + if (!buf) + return 1; + s->iovecs[i].iov_base = buf; + s->iovecs[i].iov_len = bs; + } + + if (use_sync) { + sprintf(buf, "Engine=preadv2\n"); + err = 0; + } else if (!aio) { + err = setup_ring(s); + sprintf(buf, "Engine=io_uring, sq_ring=%d, cq_ring=%d\n", *s->sq_ring.ring_entries, *s->cq_ring.ring_entries); + } else { + sprintf(buf, "Engine=aio\n"); + err = setup_aio(s); + } + if (err) { + printf("queue setup failed: %s, %d\n", strerror(errno), err); + return 1; + } + + if (!init_printed) { + printf("polled=%d, fixedbufs=%d/%d, register_files=%d, buffered=%d, QD=%d\n", polled, fixedbufs, dma_map, register_files, buffered, depth); + printf("%s", buf); + init_printed = 1; + } + if (stats) { nr_batch = roundup_pow2(depth / batch_submit); if (nr_batch < 2) @@ -1026,15 +1245,21 @@ static struct submitter *get_submitter(int offset) static void do_finish(const char *reason) { int j; + printf("Exiting on %s\n", reason); for (j = 0; j < nthreads; j++) { struct submitter *s = get_submitter(j); s->finish = 1; } - if (max_iops > 100000) - printf("Maximum IOPS=%luK\n", max_iops / 1000); - else if (max_iops) + if (max_iops > 1000000) { + double miops = (double) max_iops / 1000000.0; + printf("Maximum IOPS=%.2fM\n", miops); + } else if (max_iops > 100000) { + double kiops = (double) max_iops / 1000.0; + printf("Maximum IOPS=%.2fK\n", kiops); + } else { printf("Maximum IOPS=%lu\n", max_iops); + } finish = 1; } @@ -1058,144 +1283,6 @@ static void arm_sig_int(void) #endif } -static int setup_aio(struct submitter *s) -{ -#ifdef CONFIG_LIBAIO - if (polled) { - fprintf(stderr, "aio does not support polled IO\n"); - polled = 0; - } - if (sq_thread_poll) { - fprintf(stderr, "aio does not support SQPOLL IO\n"); - sq_thread_poll = 0; - } - if (do_nop) { - fprintf(stderr, "aio does not support polled IO\n"); - do_nop = 0; - } - if (fixedbufs || register_files) { - fprintf(stderr, "aio does not support registered files or buffers\n"); - fixedbufs = register_files = 0; - } - - return io_queue_init(roundup_pow2(depth), &s->aio_ctx); -#else - fprintf(stderr, "Legacy AIO not available on this system/build\n"); - errno = EINVAL; - return -1; -#endif -} - -static int setup_ring(struct submitter *s) -{ - struct io_sq_ring *sring = &s->sq_ring; - struct io_cq_ring *cring = &s->cq_ring; - struct io_uring_params p; - int ret, fd; - void *ptr; - - memset(&p, 0, sizeof(p)); - - if (polled && !do_nop) - p.flags |= IORING_SETUP_IOPOLL; - if (sq_thread_poll) { - p.flags |= IORING_SETUP_SQPOLL; - if (sq_thread_cpu != -1) { - p.flags |= IORING_SETUP_SQ_AFF; - p.sq_thread_cpu = sq_thread_cpu; - } - } - - fd = io_uring_setup(depth, &p); - if (fd < 0) { - perror("io_uring_setup"); - return 1; - } - s->ring_fd = s->enter_ring_fd = fd; - - io_uring_probe(fd); - - if (fixedbufs) { - struct rlimit rlim; - - rlim.rlim_cur = RLIM_INFINITY; - rlim.rlim_max = RLIM_INFINITY; - /* ignore potential error, not needed on newer kernels */ - setrlimit(RLIMIT_MEMLOCK, &rlim); - - ret = io_uring_register_buffers(s); - if (ret < 0) { - perror("io_uring_register_buffers"); - return 1; - } - - if (dma_map) { - ret = io_uring_map_buffers(s); - if (ret < 0) { - perror("io_uring_map_buffers"); - return 1; - } - } - } - - if (register_files) { - ret = io_uring_register_files(s); - if (ret < 0) { - perror("io_uring_register_files"); - return 1; - } - } - - ptr = mmap(0, p.sq_off.array + p.sq_entries * sizeof(__u32), - PROT_READ | PROT_WRITE, MAP_SHARED | MAP_POPULATE, fd, - IORING_OFF_SQ_RING); - sring->head = ptr + p.sq_off.head; - sring->tail = ptr + p.sq_off.tail; - sring->ring_mask = ptr + p.sq_off.ring_mask; - sring->ring_entries = ptr + p.sq_off.ring_entries; - sring->flags = ptr + p.sq_off.flags; - sring->array = ptr + p.sq_off.array; - sq_ring_mask = *sring->ring_mask; - - s->sqes = mmap(0, p.sq_entries * sizeof(struct io_uring_sqe), - PROT_READ | PROT_WRITE, MAP_SHARED | MAP_POPULATE, fd, - IORING_OFF_SQES); - - ptr = mmap(0, p.cq_off.cqes + p.cq_entries * sizeof(struct io_uring_cqe), - PROT_READ | PROT_WRITE, MAP_SHARED | MAP_POPULATE, fd, - IORING_OFF_CQ_RING); - cring->head = ptr + p.cq_off.head; - cring->tail = ptr + p.cq_off.tail; - cring->ring_mask = ptr + p.cq_off.ring_mask; - cring->ring_entries = ptr + p.cq_off.ring_entries; - cring->cqes = ptr + p.cq_off.cqes; - cq_ring_mask = *cring->ring_mask; - return 0; -} - -static void file_depths(char *buf) -{ - bool prev = false; - char *p; - int i, j; - - buf[0] = '\0'; - p = buf; - for (j = 0; j < nthreads; j++) { - struct submitter *s = get_submitter(j); - - for (i = 0; i < s->nr_files; i++) { - struct file *f = &s->files[i]; - - if (prev) - p += sprintf(p, " %d", f->pending_ios); - else - p += sprintf(p, "%d", f->pending_ios); - prev = true; - } - } -} - static void usage(char *argv, int status) { char runtime_str[16]; @@ -1218,11 +1305,12 @@ static void usage(char *argv, int status) " -R : Use random IO, default %d\n" " -a : Use legacy aio, default %d\n" " -S : Use sync IO (preadv2), default %d\n" - " -X : Use registered ring %d\n", + " -X : Use registered ring %d\n" + " -P : Automatically place on device home node %d\n", argv, DEPTH, BATCH_SUBMIT, BATCH_COMPLETE, BS, polled, fixedbufs, dma_map, register_files, nthreads, !buffered, do_nop, stats, runtime == 0 ? "unlimited" : runtime_str, random_io, aio, - use_sync, register_ring); + use_sync, register_ring, numa_placement); exit(status); } @@ -1274,16 +1362,14 @@ int main(int argc, char *argv[]) { struct submitter *s; unsigned long done, calls, reap; - int err, i, j, flags, fd, opt, threads_per_f, threads_rem = 0, nfiles; - long page_size; + int i, j, flags, fd, opt, threads_per_f, threads_rem = 0, nfiles; struct file f; - char *fdepths; void *ret; if (!do_nop && argc < 2) usage(argv[0], 1); - while ((opt = getopt(argc, argv, "d:s:c:b:p:B:F:n:N:O:t:T:a:r:D:R:X:S:h?")) != -1) { + while ((opt = getopt(argc, argv, "d:s:c:b:p:B:F:n:N:O:t:T:a:r:D:R:X:S:P:h?")) != -1) { switch (opt) { case 'a': aio = !!atoi(optarg); @@ -1361,6 +1447,9 @@ int main(int argc, char *argv[]) exit(1); #endif break; + case 'P': + numa_placement = !!atoi(optarg); + break; case 'h': case '?': default: @@ -1383,6 +1472,7 @@ int main(int argc, char *argv[]) roundup_pow2(depth) * sizeof(struct iovec)); for (j = 0; j < nthreads; j++) { s = get_submitter(j); + s->numa_node = -1; s->index = j; s->done = s->calls = s->reaps = 0; } @@ -1440,7 +1530,10 @@ int main(int argc, char *argv[]) memcpy(&s->files[s->nr_files], &f, sizeof(f)); - printf("Added file %s (submitter %d)\n", argv[i], s->index); + if (numa_placement) + detect_node(s, argv[i]); + + s->filename = argv[i]; s->nr_files++; } threads_rem--; @@ -1454,43 +1547,6 @@ int main(int argc, char *argv[]) if (page_size < 0) page_size = 4096; - for (j = 0; j < nthreads; j++) { - s = get_submitter(j); - for (i = 0; i < roundup_pow2(depth); i++) { - void *buf; - - if (posix_memalign(&buf, page_size, bs)) { - printf("failed alloc\n"); - return 1; - } - s->iovecs[i].iov_base = buf; - s->iovecs[i].iov_len = bs; - } - } - - for (j = 0; j < nthreads; j++) { - s = get_submitter(j); - - if (use_sync) - continue; - else if (!aio) - err = setup_ring(s); - else - err = setup_aio(s); - if (err) { - printf("ring setup failed: %s, %d\n", strerror(errno), err); - return 1; - } - } - s = get_submitter(0); - printf("polled=%d, fixedbufs=%d/%d, register_files=%d, buffered=%d, QD=%d\n", polled, fixedbufs, dma_map, register_files, buffered, depth); - if (use_sync) - printf("Engine=preadv2\n"); - else if (!aio) - printf("Engine=io_uring, sq_ring=%d, cq_ring=%d\n", *s->sq_ring.ring_entries, *s->cq_ring.ring_entries); - else - printf("Engine=aio\n"); - for (j = 0; j < nthreads; j++) { s = get_submitter(j); if (use_sync) @@ -1503,7 +1559,6 @@ int main(int argc, char *argv[]) #endif } - fdepths = malloc(8 * s->nr_files * nthreads); reap = calls = done = 0; do { unsigned long this_done = 0; @@ -1535,16 +1590,20 @@ int main(int argc, char *argv[]) ipc = (this_reap - reap) / (this_call - calls); } else rpc = ipc = -1; - file_depths(fdepths); iops = this_done - done; if (bs > 1048576) bw = iops * (bs / 1048576); else bw = iops / (1048576 / bs); - if (iops > 100000) - printf("IOPS=%luK, ", iops / 1000); - else + if (iops > 1000000) { + double miops = (double) iops / 1000000.0; + printf("IOPS=%.2fM, ", miops); + } else if (iops > 100000) { + double kiops = (double) iops / 1000.0; + printf("IOPS=%.2fK, ", kiops); + } else { printf("IOPS=%lu, ", iops); + } max_iops = max(max_iops, iops); if (!do_nop) { if (bw > 2000) { @@ -1555,7 +1614,7 @@ int main(int argc, char *argv[]) printf("BW=%luMiB/s, ", bw); } } - printf("IOS/call=%ld/%ld, inflight=(%s)\n", rpc, ipc, fdepths); + printf("IOS/call=%ld/%ld\n", rpc, ipc); done = this_done; calls = this_call; reap = this_reap; @@ -1578,7 +1637,6 @@ int main(int argc, char *argv[]) } } - free(fdepths); free(submitter); return 0; } From b565834aa5fa119c47b69473b8efe107c7fbbfd5 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Wed, 3 Aug 2022 13:07:36 -0400 Subject: [PATCH 0215/1097] examples: fix ioengine in zbd-rand-write.fio Make the comment and actual ioengine setting consistent to reduce possible confusion. Fixes: https://github.com/axboe/fio/issues/1433 Reported-by: thunderex@gmail.com Signed-off-by: Vincent Fu --- examples/zbd-rand-write.fio | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/zbd-rand-write.fio b/examples/zbd-rand-write.fio index 46cddd0609..9494a583dd 100644 --- a/examples/zbd-rand-write.fio +++ b/examples/zbd-rand-write.fio @@ -1,4 +1,4 @@ -; Using the libaio ioengine, random write to a (zoned) block device, +; Using the psync ioengine, random write to a (zoned) block device, ; writing at most 32 zones at a time. Target zones are chosen randomly ; and writes directed at the write pointer of the chosen zones From 56b6bc259faf70fa5b9ef9de41909df9f91e72c6 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Wed, 3 Aug 2022 12:20:56 -0700 Subject: [PATCH 0216/1097] engines/null: fill issue_time during commit For iodepth > 1, the null ioengine is an asynchronous ioengine, yet it does not report submission latency. This patch adds code for setting issue_time in order to calculate submission latency. The extra work does reduce performance for iodepth > 1 but this reduction can be mitigated with options like gtod_reduce. - there are no differeces at qd 1 (5837K vs 5864K) - default options at qd 256 shows an 8% IOPS reduction (3583K vs 3303K). - gtod_reduce=1 at qd 256 shows a 3% IOPS reduction (5119K vs 4966K). $ ../fio-canonical/fio --name=test --ioengine=null --size=1T --norandommap --ramp_time=5s --runtime=15s --cpus_allowed=1 test: (g=0): rw=read, bs=(R) 4096B-4096B, (W) 4096B-4096B, (T) 4096B-4096B, ioengine=null, iodepth=1 fio-3.30-73-gd622 read: IOPS=5837k, BW=22.3GiB/s (23.9GB/s)(334GiB/15001msec) $ ./fio --name=test --ioengine=null --size=1T --norandommap --ramp_time=5s --runtime=15s --cpus_allowed=1 test: (g=0): rw=read, bs=(R) 4096B-4096B, (W) 4096B-4096B, (T) 4096B-4096B, ioengine=null, iodepth=1 fio-3.30-74-gcfbc2 read: IOPS=5864k, BW=22.4GiB/s (24.0GB/s)(336GiB/15001msec) $ ../fio-canonical/fio --name=test --ioengine=null --size=1T --norandommap --iodepth=256 --ramp_time=5s --runtime=15s --cpus_allowed=1 test: (g=0): rw=read, bs=(R) 4096B-4096B, (W) 4096B-4096B, (T) 4096B-4096B, ioengine=null, iodepth=256 fio-3.30-73-gd622 read: IOPS=3583k, BW=13.7GiB/s (14.7GB/s)(205GiB/15001msec) $ ./fio --name=test --ioengine=null --size=1T --norandommap --iodepth=256 --ramp_time=5s --runtime=15s --cpus_allowed=1 test: (g=0): rw=read, bs=(R) 4096B-4096B, (W) 4096B-4096B, (T) 4096B-4096B, ioengine=null, iodepth=256 fio-3.30-74-gcfbc2 read: IOPS=3303k, BW=12.6GiB/s (13.5GB/s)(189GiB/15001msec) $ ../fio-canonical/fio --name=test --ioengine=null --size=1T --norandommap --iodepth=256 --ramp_time=5s --runtime=15s --cpus_allowed=1 --gtod_reduce=1 test: (g=0): rw=read, bs=(R) 4096B-4096B, (W) 4096B-4096B, (T) 4096B-4096B, ioengine=null, iodepth=256 fio-3.30-73-gd622 read: IOPS=5119k, BW=19.5GiB/s (21.0GB/s)(293GiB/15001msec) $ ./fio --name=test --ioengine=null --size=1T --norandommap --iodepth=256 --ramp_time=5s --runtime=15s --cpus_allowed=1 --gtod_reduce=1 test: (g=0): rw=read, bs=(R) 4096B-4096B, (W) 4096B-4096B, (T) 4096B-4096B, ioengine=null, iodepth=256 fio-3.30-74-gcfbc2 read: IOPS=4966k, BW=18.9GiB/s (20.3GB/s)(284GiB/15001msec) Signed-off-by: Vincent Fu --- engines/null.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/engines/null.c b/engines/null.c index 8dcd1b21cf..2df567187d 100644 --- a/engines/null.c +++ b/engines/null.c @@ -44,9 +44,28 @@ static int null_getevents(struct null_data *nd, unsigned int min_events, return ret; } +static void null_queued(struct thread_data *td, struct null_data *nd) +{ + struct timespec now; + + if (!fio_fill_issue_time(td)) + return; + + fio_gettime(&now, NULL); + + for (int i = 0; i < nd->queued; i++) { + struct io_u *io_u = nd->io_us[i]; + + memcpy(&io_u->issue_time, &now, sizeof(now)); + io_u_queued(td, io_u); + } +} + static int null_commit(struct thread_data *td, struct null_data *nd) { if (!nd->events) { + null_queued(td, nd); + #ifndef FIO_EXTERNAL_ENGINE io_u_mark_submit(td, nd->queued); #endif From c08f9533042e909d4b4b12fdb8d14f1bc8e23dff Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Wed, 3 Aug 2022 12:21:11 -0700 Subject: [PATCH 0217/1097] filesetup: use correct random seed for non-uniform distributions The index in the random seed array for generating offsets is FIO_RAND_BLOCK_OFF. So this is the index that should be used to find the random seed when fio generates offsets following the Zipf, Pareto, and Gaussian distributions. The previous index 4 actually corresponds to FIO_RAND_MIX_OFF. This change means that the default sequences of non-uniform random offsets generated before and after this patch will differ. So users relying on the repeatability of I/O patterns will have new repeatable patterns after this change. Signed-off-by: Vincent Fu --- filesetup.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/filesetup.c b/filesetup.c index e0592209a1..3e2ccf9b9b 100644 --- a/filesetup.c +++ b/filesetup.c @@ -1495,7 +1495,7 @@ static void __init_rand_distribution(struct thread_data *td, struct fio_file *f) seed = jhash(f->file_name, strlen(f->file_name), 0) * td->thread_number; if (!td->o.rand_repeatable) - seed = td->rand_seeds[4]; + seed = td->rand_seeds[FIO_RAND_BLOCK_OFF]; if (td->o.random_distribution == FIO_RAND_DIST_ZIPF) zipf_init(&f->zipf, nranges, td->o.zipf_theta.u.f, td->o.random_center.u.f, seed); From 60ebb9394d7828f2ac42b821e823ba5a83d9f7df Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Fri, 5 Aug 2022 13:07:21 -0700 Subject: [PATCH 0218/1097] testing: add test for slat + clat = tlat The sum of submission and completion latency should equal total latency. Add a test case for this for the libaio ioengine. Signed-off-by: Vincent Fu --- t/jobs/t0015-e78980ff.fio | 7 +++++++ t/run-fio-tests.py | 31 +++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 t/jobs/t0015-e78980ff.fio diff --git a/t/jobs/t0015-e78980ff.fio b/t/jobs/t0015-e78980ff.fio new file mode 100644 index 0000000000..c650c0b2e1 --- /dev/null +++ b/t/jobs/t0015-e78980ff.fio @@ -0,0 +1,7 @@ +# Expected result: mean(slat) + mean(clat) = mean(lat) +# Buggy result: equality does not hold + +[test] +ioengine=libaio +size=1M +iodepth=16 diff --git a/t/run-fio-tests.py b/t/run-fio-tests.py index 32cdbc1992..7a2e1f41fd 100755 --- a/t/run-fio-tests.py +++ b/t/run-fio-tests.py @@ -527,6 +527,27 @@ def check_result(self): return +class FioJobTest_t0015(FioJobTest): + """Test consists of fio test job t0015 + Confirm that mean(slat) + mean(clat) = mean(tlat)""" + + def check_result(self): + super(FioJobTest_t0015, self).check_result() + + if not self.passed: + return + + slat = self.json_data['jobs'][0]['read']['slat_ns']['mean'] + clat = self.json_data['jobs'][0]['read']['clat_ns']['mean'] + tlat = self.json_data['jobs'][0]['read']['lat_ns']['mean'] + logging.debug('Test %d: slat %f, clat %f, tlat %f', self.testnum, slat, clat, tlat) + + if abs(slat + clat - tlat) > 1: + self.failure_reason = "{0} slat {1} + clat {2} = {3} != tlat {4},".format( + self.failure_reason, slat, clat, slat+clat, tlat) + self.passed = False + + class FioJobTest_iops_rate(FioJobTest): """Test consists of fio test job t0009 Confirm that job0 iops == 1000 @@ -816,6 +837,16 @@ def cpucount4(cls): 'output_format': 'json', 'requirements': [], }, + { + 'test_id': 15, + 'test_class': FioJobTest_t0015, + 'job': 't0015-e78980ff.fio', + 'success': SUCCESS_DEFAULT, + 'pre_job': None, + 'pre_success': None, + 'output_format': 'json', + 'requirements': [Requirements.linux, Requirements.libaio], + }, { 'test_id': 1000, 'test_class': FioExeTest, From d54ae2234253f8c0b1bf6fec19f974861a692825 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Fri, 5 Aug 2022 13:31:11 -0700 Subject: [PATCH 0219/1097] engines/null: add FIO_ASYNCIO_SETS_ISSUE_TIME flag Add a flag to prevent td_io_queue from setting issue_time since this ioengine already does it in its commit function when iodepth > 1. We also need to make sure the changes to the ioengine flags are propogated to td->flags. Fixes: 56b6bc25 ("engines/null: fill issue_time during commit") Signed-off-by: Vincent Fu --- engines/null.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/engines/null.c b/engines/null.c index 2df567187d..68759c26f1 100644 --- a/engines/null.c +++ b/engines/null.c @@ -113,9 +113,11 @@ static struct null_data *null_init(struct thread_data *td) if (td->o.iodepth != 1) { nd->io_us = (struct io_u **) malloc(td->o.iodepth * sizeof(struct io_u *)); memset(nd->io_us, 0, td->o.iodepth * sizeof(struct io_u *)); + td->io_ops->flags |= FIO_ASYNCIO_SETS_ISSUE_TIME; } else td->io_ops->flags |= FIO_SYNCIO; + td_set_ioengine_flags(td); return nd; } From de31fe9ab3dd6115cd0d5c77354f67f06595570d Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Fri, 5 Aug 2022 13:35:30 -0700 Subject: [PATCH 0220/1097] testing: add test for slat + clat = tlat Add this test for the null ioengine which can run on more platforms. Signed-off-by: Vincent Fu --- t/jobs/t0016-259ebc00.fio | 7 +++++++ t/run-fio-tests.py | 12 +++++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 t/jobs/t0016-259ebc00.fio diff --git a/t/jobs/t0016-259ebc00.fio b/t/jobs/t0016-259ebc00.fio new file mode 100644 index 0000000000..1b418e7c81 --- /dev/null +++ b/t/jobs/t0016-259ebc00.fio @@ -0,0 +1,7 @@ +# Expected result: mean(slat) + mean(clat) = mean(lat) +# Buggy result: equality does not hold + +[test] +ioengine=null +size=1M +iodepth=16 diff --git a/t/run-fio-tests.py b/t/run-fio-tests.py index 7a2e1f41fd..d77f20e00f 100755 --- a/t/run-fio-tests.py +++ b/t/run-fio-tests.py @@ -528,7 +528,7 @@ def check_result(self): class FioJobTest_t0015(FioJobTest): - """Test consists of fio test job t0015 + """Test consists of fio test jobs t0015 and t0016 Confirm that mean(slat) + mean(clat) = mean(tlat)""" def check_result(self): @@ -847,6 +847,16 @@ def cpucount4(cls): 'output_format': 'json', 'requirements': [Requirements.linux, Requirements.libaio], }, + { + 'test_id': 16, + 'test_class': FioJobTest_t0015, + 'job': 't0016-259ebc00.fio', + 'success': SUCCESS_DEFAULT, + 'pre_job': None, + 'pre_success': None, + 'output_format': 'json', + 'requirements': [], + }, { 'test_id': 1000, 'test_class': FioExeTest, From 13ceeb098527ee99d0cabe248eba70e9d2fa2aa8 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Mon, 8 Aug 2022 17:51:51 +0000 Subject: [PATCH 0221/1097] ci: upload tagged AppVeyor installers as GitHub releases Deploy Windows installers built by AppVeyor as releases on GitHub. This process is triggered only when tags are uploaded to the repository. Patch was created using this guide: https://www.appveyor.com/docs/deployment/github/ - Generate a GitHub authentication token with public_repo scope: https://github.com/settings/tokens - Login to AppVeyor and encrypt GitHub token: Account -> Encrypt YAML https://ci.appveyor.com/tools/encrypt - Insert encrypted token in appveyor.yml on the secure: line under auth_token: With APPVEYOR_REPO_TAG set to true only tags pushed to GitHub will have Cygwin MSI installers uploaded as releases to GitHub. Signed-off-by: Vincent Fu Link: https://lore.kernel.org/r/20220808175133.37920-2-vincent.fu@samsung.com [axboe: replace token with right one] Signed-off-by: Jens Axboe --- .appveyor.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/.appveyor.yml b/.appveyor.yml index b94eefe318..e55fc3d13f 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -50,5 +50,18 @@ after_build: test_script: - python.exe t/run-fio-tests.py --artifact-root test-artifacts --debug +deploy: + - provider: GitHub + description: fio Windows installer + auth_token: # encrypted token from GitHub + secure: Tjj+xRQEV25P6dQgboUblTCKx/LtUOUav2bvzSCtwMhHMAxrrn2adod6nlTf0ItV + artifact: fio.msi # upload installer to release assets + draft: false + prerelease: false + on: + branch: master # release from this branch only + APPVEYOR_REPO_TAG: true # deploy on tag push only + DISTRO: cygwin + on_finish: - 'bash.exe -lc "cd \"${APPVEYOR_BUILD_FOLDER}\" && [ -d test-artifacts ] && 7z a -t7z test-artifacts.7z test-artifacts -xr!foo.0.0 -xr!latency.?.0 -xr!fio_jsonplus_clat2csv.test && appveyor PushArtifact test-artifacts.7z' From 6e44f31b9241cdc56d0857fb10ddb2ec40faa541 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Tue, 9 Aug 2022 10:45:53 -0400 Subject: [PATCH 0222/1097] ci: drop master branch requirement for AppVeyor releases Even if we tag a commit on the master branch AppVeyor won't upload the installers as GitHub release artifacts. https://ci.appveyor.com/project/axboe/fio/builds/44416420/job/t1elejqo2yk3r72b So just drop the requirement for AppVeyor to only upload release artifacts from the master branch. This seems to get things working on my fork: https://ci.appveyor.com/project/vincentkfu/fio/builds/44417490/job/e6xtrwc6xlccrn0j It's good to have the flexibility to store installers from other branches in any case. Signed-off-by: Vincent Fu --- .appveyor.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.appveyor.yml b/.appveyor.yml index e55fc3d13f..92301ca9e0 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -59,7 +59,6 @@ deploy: draft: false prerelease: false on: - branch: master # release from this branch only APPVEYOR_REPO_TAG: true # deploy on tag push only DISTRO: cygwin From 6cafe8445fd1e04e5f7d67bbc73029a538d1b253 Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Tue, 9 Aug 2022 14:41:25 -0600 Subject: [PATCH 0223/1097] Fio 3.31 Signed-off-by: Jens Axboe --- FIO-VERSION-GEN | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FIO-VERSION-GEN b/FIO-VERSION-GEN index fa64f50f69..72630dd0a2 100755 --- a/FIO-VERSION-GEN +++ b/FIO-VERSION-GEN @@ -1,7 +1,7 @@ #!/bin/sh GVF=FIO-VERSION-FILE -DEF_VER=fio-3.30 +DEF_VER=fio-3.31 LF=' ' From 7ff204c83595cd7d6b7b6a813d9fda97afd25198 Mon Sep 17 00:00:00 2001 From: Sungup Moon Date: Mon, 8 Aug 2022 17:21:46 +0900 Subject: [PATCH 0224/1097] lib/rand: Enhance __fill_random_buf using the multi random seed The __fill_random_buf fills a buffer using the random 8byte integer to write. But, this mechanism is depend on the CPU performance and could not reach the max performance on the PCIe Gen5 devices. I have tested 128KB single worker sequential write on PCIe Gen5 NVMe, but it cannot reach write throughput 6.0GB/s. So, I have reviewed the __fill_random_buf and focused the multiplier dependency to generate the random number. So, I have changed __fill_random_buf using the multiple-random-seed to reduce the dependencies in the small data filling loop. I'll attach detail analysis result in the PR of this branch. Signed-off-by: Sungup Moon --- configure | 17 +++++++++++++++++ lib/rand.c | 37 ++++++++++++++++++++++++++++++++++++- 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/configure b/configure index 36450df85b..a2b9bd4cc8 100755 --- a/configure +++ b/configure @@ -116,6 +116,10 @@ has() { type "$1" >/dev/null 2>&1 } +num() { + echo "$1" | grep -P -q "^[0-9]+$" +} + check_define() { cat > $TMPC <> $config_host_h +print_config "seed_buckets" "$seed_buckets" echo "LIBS+=$LIBS" >> $config_host_mak echo "GFIO_LIBS+=$GFIO_LIBS" >> $config_host_mak diff --git a/lib/rand.c b/lib/rand.c index 1e669116f3..1ce4a84930 100644 --- a/lib/rand.c +++ b/lib/rand.c @@ -95,7 +95,7 @@ void init_rand_seed(struct frand_state *state, uint64_t seed, bool use64) __init_rand64(&state->state64, seed); } -void __fill_random_buf(void *buf, unsigned int len, uint64_t seed) +void __fill_random_buf_small(void *buf, unsigned int len, uint64_t seed) { uint64_t *b = buf; uint64_t *e = b + len / sizeof(*b); @@ -110,6 +110,41 @@ void __fill_random_buf(void *buf, unsigned int len, uint64_t seed) __builtin_memcpy(e, &seed, rest); } +void __fill_random_buf(void *buf, unsigned int len, uint64_t seed) +{ +#define MAX_SEED_BUCKETS 16 + static uint64_t prime[MAX_SEED_BUCKETS] = {1, 2, 3, 5, + 7, 11, 13, 17, + 19, 23, 29, 31, + 37, 41, 43, 47}; + + uint64_t *b, *e, s[CONFIG_SEED_BUCKETS]; + unsigned int rest; + int p; + + /* + * Calculate the max index which is multiples of the seed buckets. + */ + rest = (len / sizeof(*b) / CONFIG_SEED_BUCKETS) * CONFIG_SEED_BUCKETS; + + b = buf; + e = b + rest; + + rest = len - (rest * sizeof(*b)); + + for (p = 0; p < CONFIG_SEED_BUCKETS; p++) + s[p] = seed * prime[p]; + + for (; b != e; b += CONFIG_SEED_BUCKETS) { + for (p = 0; p < CONFIG_SEED_BUCKETS; ++p) { + b[p] = s[p]; + s[p] = __hash_u64(s[p]); + } + } + + __fill_random_buf_small(b, rest, s[0]); +} + uint64_t fill_random_buf(struct frand_state *fs, void *buf, unsigned int len) { From 9dc528b1638b625b5e167983a74de4e85c5859ea Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Wed, 10 Aug 2022 09:51:49 -0600 Subject: [PATCH 0225/1097] lib/rand: get rid of unused MAX_SEED_BUCKETS It's only used to size the array, we don't need it. Signed-off-by: Jens Axboe --- lib/rand.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/lib/rand.c b/lib/rand.c index 1ce4a84930..0e787a62ba 100644 --- a/lib/rand.c +++ b/lib/rand.c @@ -112,12 +112,8 @@ void __fill_random_buf_small(void *buf, unsigned int len, uint64_t seed) void __fill_random_buf(void *buf, unsigned int len, uint64_t seed) { -#define MAX_SEED_BUCKETS 16 - static uint64_t prime[MAX_SEED_BUCKETS] = {1, 2, 3, 5, - 7, 11, 13, 17, - 19, 23, 29, 31, - 37, 41, 43, 47}; - + static uint64_t prime[] = {1, 2, 3, 5, 7, 11, 13, 17, + 19, 23, 29, 31, 37, 41, 43, 47}; uint64_t *b, *e, s[CONFIG_SEED_BUCKETS]; unsigned int rest; int p; From f2d79184f45e33bf03d5858f2b14d47c7be933f5 Mon Sep 17 00:00:00 2001 From: "Feng, Hualong" Date: Wed, 20 Jul 2022 12:01:35 +0800 Subject: [PATCH 0226/1097] engines/http: Add storage class option for s3 Amazon S3 offers a range of storage classes that you can choose from based on the data access, resiliency, and cost requirements of your workloads. (https://aws.amazon.com/s3/storage-classes/) For example, we have **STANDARD** storage class to test normal workload, and have **COLD** storage class to test the workload with gzip compression. It is convenient to select which storage class to access for different kinds data. Signed-off-by: Feng, Hualong --- engines/http.c | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/engines/http.c b/engines/http.c index 1de9e66c75..dbcde28785 100644 --- a/engines/http.c +++ b/engines/http.c @@ -57,6 +57,7 @@ struct http_options { char *s3_key; char *s3_keyid; char *s3_region; + char *s3_storage_class; char *swift_auth_token; int verbose; unsigned int mode; @@ -161,6 +162,16 @@ static struct fio_option options[] = { .category = FIO_OPT_C_ENGINE, .group = FIO_OPT_G_HTTP, }, + { + .name = "http_s3_storage_class", + .lname = "S3 Storage class", + .type = FIO_OPT_STR_STORE, + .help = "S3 Storage Class", + .off1 = offsetof(struct http_options, s3_storage_class), + .def = "STANDARD", + .category = FIO_OPT_C_ENGINE, + .group = FIO_OPT_G_HTTP, + }, { .name = "http_mode", .lname = "Request mode to use", @@ -335,8 +346,8 @@ static void _add_aws_auth_header(CURL *curl, struct curl_slist *slist, struct ht char date_iso[32]; char method[8]; char dkey[128]; - char creq[512]; - char sts[256]; + char creq[4096]; + char sts[512]; char s[512]; char *uri_encoded = NULL; char *dsha = NULL; @@ -373,11 +384,12 @@ static void _add_aws_auth_header(CURL *curl, struct curl_slist *slist, struct ht "host:%s\n" "x-amz-content-sha256:%s\n" "x-amz-date:%s\n" + "x-amz-storage-class:%s\n" "\n" - "host;x-amz-content-sha256;x-amz-date\n" + "host;x-amz-content-sha256;x-amz-date;x-amz-storage-class\n" "%s" , method - , uri_encoded, o->host, dsha, date_iso, dsha); + , uri_encoded, o->host, dsha, date_iso, o->s3_storage_class, dsha); csha = _gen_hex_sha256(creq, strlen(creq)); snprintf(sts, sizeof(sts), "AWS4-HMAC-SHA256\n%s\n%s/%s/%s/%s\n%s", @@ -400,9 +412,10 @@ static void _add_aws_auth_header(CURL *curl, struct curl_slist *slist, struct ht snprintf(s, sizeof(s), "x-amz-date: %s", date_iso); slist = curl_slist_append(slist, s); - + snprintf(s, sizeof(s), "x-amz-storage-class: %s", o->s3_storage_class); + slist = curl_slist_append(slist, s); snprintf(s, sizeof(s), "Authorization: AWS4-HMAC-SHA256 Credential=%s/%s/%s/s3/aws4_request," - "SignedHeaders=host;x-amz-content-sha256;x-amz-date,Signature=%s", + "SignedHeaders=host;x-amz-content-sha256;x-amz-date;x-amz-storage-class,Signature=%s", o->s3_keyid, date_short, o->s3_region, signature); slist = curl_slist_append(slist, s); From 91af79c4654dcdfa33e6a98cb9540da4f7ffff34 Mon Sep 17 00:00:00 2001 From: "Feng, Hualong" Date: Wed, 20 Jul 2022 09:41:35 +0800 Subject: [PATCH 0227/1097] engines/http: Add s3 crypto options for s3 Server-side encryption is about protecting data at rest. (https://docs.aws.amazon.com/AmazonS3/latest/userguide/ServerSideEncryptionCustomerKeys.html) When we want to test server-side encryption, we need to specify server-side encryption with customer-provided encryption keys (SSE-C). The two option **http_s3_sse_customer_key** and **http_s3_sse_customer_algorithm** is for server-side encryption. Signed-off-by: Feng, Hualong --- engines/http.c | 163 +++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 146 insertions(+), 17 deletions(-) diff --git a/engines/http.c b/engines/http.c index dbcde28785..56dc7d1b14 100644 --- a/engines/http.c +++ b/engines/http.c @@ -57,6 +57,8 @@ struct http_options { char *s3_key; char *s3_keyid; char *s3_region; + char *s3_sse_customer_key; + char *s3_sse_customer_algorithm; char *s3_storage_class; char *swift_auth_token; int verbose; @@ -162,6 +164,26 @@ static struct fio_option options[] = { .category = FIO_OPT_C_ENGINE, .group = FIO_OPT_G_HTTP, }, + { + .name = "http_s3_sse_customer_key", + .lname = "SSE Customer Key", + .type = FIO_OPT_STR_STORE, + .help = "S3 SSE Customer Key", + .off1 = offsetof(struct http_options, s3_sse_customer_key), + .def = "", + .category = FIO_OPT_C_ENGINE, + .group = FIO_OPT_G_HTTP, + }, + { + .name = "http_s3_sse_customer_algorithm", + .lname = "SSE Customer Algorithm", + .type = FIO_OPT_STR_STORE, + .help = "S3 SSE Customer Algorithm", + .off1 = offsetof(struct http_options, s3_sse_customer_algorithm), + .def = "AES256", + .category = FIO_OPT_C_ENGINE, + .group = FIO_OPT_G_HTTP, + }, { .name = "http_s3_storage_class", .lname = "S3 Storage class", @@ -277,6 +299,54 @@ static char *_gen_hex_md5(const char *p, size_t len) return _conv_hex(hash, MD5_DIGEST_LENGTH); } +static char *_conv_base64_encode(const unsigned char *p, size_t len) +{ + char *r, *ret; + int i; + static const char sEncodingTable[] = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', + 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', + 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', + 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', + '4', '5', '6', '7', '8', '9', '+', '/' + }; + + size_t out_len = 4 * ((len + 2) / 3); + ret = r = malloc(out_len + 1); + + for (i = 0; i < len - 2; i += 3) { + *r++ = sEncodingTable[(p[i] >> 2) & 0x3F]; + *r++ = sEncodingTable[((p[i] & 0x3) << 4) | ((int) (p[i + 1] & 0xF0) >> 4)]; + *r++ = sEncodingTable[((p[i + 1] & 0xF) << 2) | ((int) (p[i + 2] & 0xC0) >> 6)]; + *r++ = sEncodingTable[p[i + 2] & 0x3F]; + } + + if (i < len) { + *r++ = sEncodingTable[(p[i] >> 2) & 0x3F]; + if (i == (len - 1)) { + *r++ = sEncodingTable[((p[i] & 0x3) << 4)]; + *r++ = '='; + } else { + *r++ = sEncodingTable[((p[i] & 0x3) << 4) | ((int) (p[i + 1] & 0xF0) >> 4)]; + *r++ = sEncodingTable[((p[i + 1] & 0xF) << 2)]; + } + *r++ = '='; + } + + ret[out_len]=0; + return ret; +} + +static char *_gen_base64_md5(const unsigned char *p, size_t len) +{ + unsigned char hash[MD5_DIGEST_LENGTH]; + MD5((unsigned char*)p, len, hash); + return _conv_base64_encode(hash, MD5_DIGEST_LENGTH); +} + static void _hmac(unsigned char *md, void *key, int key_len, char *data) { #ifndef CONFIG_HAVE_OPAQUE_HMAC_CTX HMAC_CTX _ctx; @@ -356,6 +426,9 @@ static void _add_aws_auth_header(CURL *curl, struct curl_slist *slist, struct ht const char *service = "s3"; const char *aws = "aws4_request"; unsigned char md[SHA256_DIGEST_LENGTH]; + unsigned char sse_key[33] = {0}; + char *sse_key_base64 = NULL; + char *sse_key_md5_base64 = NULL; time_t t = time(NULL); struct tm *gtm = gmtime(&t); @@ -364,6 +437,9 @@ static void _add_aws_auth_header(CURL *curl, struct curl_slist *slist, struct ht strftime (date_iso, sizeof(date_iso), "%Y%m%dT%H%M%SZ", gtm); uri_encoded = _aws_uriencode(uri); + if (o->s3_sse_customer_key != NULL) + strncpy((char*)sse_key, o->s3_sse_customer_key, sizeof(sse_key) - 1); + if (op == DDIR_WRITE) { dsha = _gen_hex_sha256(buf, len); sprintf(method, "PUT"); @@ -377,23 +453,50 @@ static void _add_aws_auth_header(CURL *curl, struct curl_slist *slist, struct ht } /* Create the canonical request first */ - snprintf(creq, sizeof(creq), - "%s\n" - "%s\n" - "\n" - "host:%s\n" - "x-amz-content-sha256:%s\n" - "x-amz-date:%s\n" - "x-amz-storage-class:%s\n" - "\n" - "host;x-amz-content-sha256;x-amz-date;x-amz-storage-class\n" - "%s" - , method - , uri_encoded, o->host, dsha, date_iso, o->s3_storage_class, dsha); + if (sse_key[0] != '\0') { + sse_key_base64 = _conv_base64_encode(sse_key, sizeof(sse_key) - 1); + sse_key_md5_base64 = _gen_base64_md5(sse_key, sizeof(sse_key) - 1); + snprintf(creq, sizeof(creq), + "%s\n" + "%s\n" + "\n" + "host:%s\n" + "x-amz-content-sha256:%s\n" + "x-amz-date:%s\n" + "x-amz-server-side-encryption-customer-algorithm:%s\n" + "x-amz-server-side-encryption-customer-key:%s\n" + "x-amz-server-side-encryption-customer-key-md5:%s\n" + "x-amz-storage-class:%s\n" + "\n" + "host;x-amz-content-sha256;x-amz-date;" + "x-amz-server-side-encryption-customer-algorithm;" + "x-amz-server-side-encryption-customer-key;" + "x-amz-server-side-encryption-customer-key-md5;" + "x-amz-storage-class\n" + "%s" + , method + , uri_encoded, o->host, dsha, date_iso + , o->s3_sse_customer_algorithm, sse_key_base64 + , sse_key_md5_base64, o->s3_storage_class, dsha); + } else { + snprintf(creq, sizeof(creq), + "%s\n" + "%s\n" + "\n" + "host:%s\n" + "x-amz-content-sha256:%s\n" + "x-amz-date:%s\n" + "x-amz-storage-class:%s\n" + "\n" + "host;x-amz-content-sha256;x-amz-date;x-amz-storage-class\n" + "%s" + , method + , uri_encoded, o->host, dsha, date_iso, o->s3_storage_class, dsha); + } csha = _gen_hex_sha256(creq, strlen(creq)); snprintf(sts, sizeof(sts), "AWS4-HMAC-SHA256\n%s\n%s/%s/%s/%s\n%s", - date_iso, date_short, o->s3_region, service, aws, csha); + date_iso, date_short, o->s3_region, service, aws, csha); snprintf((char *)dkey, sizeof(dkey), "AWS4%s", o->s3_key); _hmac(md, dkey, strlen(dkey), date_short); @@ -412,11 +515,33 @@ static void _add_aws_auth_header(CURL *curl, struct curl_slist *slist, struct ht snprintf(s, sizeof(s), "x-amz-date: %s", date_iso); slist = curl_slist_append(slist, s); + + if (sse_key[0] != '\0') { + snprintf(s, sizeof(s), "x-amz-server-side-encryption-customer-algorithm: %s", o->s3_sse_customer_algorithm); + slist = curl_slist_append(slist, s); + snprintf(s, sizeof(s), "x-amz-server-side-encryption-customer-key: %s", sse_key_base64); + slist = curl_slist_append(slist, s); + snprintf(s, sizeof(s), "x-amz-server-side-encryption-customer-key-md5: %s", sse_key_md5_base64); + slist = curl_slist_append(slist, s); + } + snprintf(s, sizeof(s), "x-amz-storage-class: %s", o->s3_storage_class); slist = curl_slist_append(slist, s); - snprintf(s, sizeof(s), "Authorization: AWS4-HMAC-SHA256 Credential=%s/%s/%s/s3/aws4_request," - "SignedHeaders=host;x-amz-content-sha256;x-amz-date;x-amz-storage-class,Signature=%s", - o->s3_keyid, date_short, o->s3_region, signature); + + if (sse_key[0] != '\0') { + snprintf(s, sizeof(s), "Authorization: AWS4-HMAC-SHA256 Credential=%s/%s/%s/s3/aws4_request," + "SignedHeaders=host;x-amz-content-sha256;" + "x-amz-date;x-amz-server-side-encryption-customer-algorithm;" + "x-amz-server-side-encryption-customer-key;" + "x-amz-server-side-encryption-customer-key-md5;" + "x-amz-storage-class," + "Signature=%s", + o->s3_keyid, date_short, o->s3_region, signature); + } else { + snprintf(s, sizeof(s), "Authorization: AWS4-HMAC-SHA256 Credential=%s/%s/%s/s3/aws4_request," + "SignedHeaders=host;x-amz-content-sha256;x-amz-date;x-amz-storage-class,Signature=%s", + o->s3_keyid, date_short, o->s3_region, signature); + } slist = curl_slist_append(slist, s); curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist); @@ -425,6 +550,10 @@ static void _add_aws_auth_header(CURL *curl, struct curl_slist *slist, struct ht free(csha); free(dsha); free(signature); + if (sse_key_base64 != NULL) { + free(sse_key_base64); + free(sse_key_md5_base64); + } } static void _add_swift_header(CURL *curl, struct curl_slist *slist, struct http_options *o, From a2084df02f33890e6c5c055a93a18ea8334e1ed1 Mon Sep 17 00:00:00 2001 From: "Feng, Hualong" Date: Thu, 28 Jul 2022 01:47:48 +0000 Subject: [PATCH 0228/1097] doc: Add usage and example about s3 storage class and crypto There add option usage about s3 storage class `http_s3_storage_class` and s3 SSE server side encryption `http_s3_sse_customer_key` and `http_s3_sse_customer_algorithm` And add example file in example folder. Signed-off-by: Feng, Hualong --- HOWTO.rst | 14 +++++++++++ examples/http-s3-crypto.fio | 38 ++++++++++++++++++++++++++++++ examples/http-s3-storage-class.fio | 37 +++++++++++++++++++++++++++++ fio.1 | 9 +++++++ 4 files changed, 98 insertions(+) create mode 100644 examples/http-s3-crypto.fio create mode 100644 examples/http-s3-storage-class.fio diff --git a/HOWTO.rst b/HOWTO.rst index 104cce2d21..05fc117f2f 100644 --- a/HOWTO.rst +++ b/HOWTO.rst @@ -2692,6 +2692,20 @@ with the caveat that when used on the command line, they must come after the The S3 key/access id. +.. option:: http_s3_sse_customer_key=str : [http] + + The encryption customer key in SSE server side. + +.. option:: http_s3_sse_customer_algorithm=str : [http] + + The encryption customer algorithm in SSE server side. + Default is **AES256** + +.. option:: http_s3_storage_class=str : [http] + + Which storage class to access. User-customizable settings. + Default is **STANDARD** + .. option:: http_swift_auth_token=str : [http] The Swift auth token. See the example configuration file on how diff --git a/examples/http-s3-crypto.fio b/examples/http-s3-crypto.fio new file mode 100644 index 0000000000..2403746edc --- /dev/null +++ b/examples/http-s3-crypto.fio @@ -0,0 +1,38 @@ +# Example test for the HTTP engine's S3 support against Amazon AWS. +# Obviously, you have to adjust the S3 credentials; for this example, +# they're passed in via the environment. +# And you can set the SSE Customer Key and Algorithm to test Server +# Side Encryption. +# + +[global] +ioengine=http +name=test +direct=1 +filename=/larsmb-fio-test/object +http_verbose=0 +https=on +http_mode=s3 +http_s3_key=${S3_KEY} +http_s3_keyid=${S3_ID} +http_host=s3.eu-central-1.amazonaws.com +http_s3_region=eu-central-1 +http_s3_sse_customer_key=${SSE_KEY} +http_s3_sse_customer_algorithm=AES256 +group_reporting + +# With verify, this both writes and reads the object +[create] +rw=write +bs=4k +size=64k +io_size=4k +verify=sha256 + +[trim] +stonewall +rw=trim +bs=4k +size=64k +io_size=4k + diff --git a/examples/http-s3-storage-class.fio b/examples/http-s3-storage-class.fio new file mode 100644 index 0000000000..9ee23837df --- /dev/null +++ b/examples/http-s3-storage-class.fio @@ -0,0 +1,37 @@ +# Example test for the HTTP engine's S3 support against Amazon AWS. +# Obviously, you have to adjust the S3 credentials; for this example, +# they're passed in via the environment. +# And here add storage class parameter, you can set normal test for +# STANDARD and compression test for another storage class. +# + +[global] +ioengine=http +name=test +direct=1 +filename=/larsmb-fio-test/object +http_verbose=0 +https=on +http_mode=s3 +http_s3_key=${S3_KEY} +http_s3_keyid=${S3_ID} +http_host=s3.eu-central-1.amazonaws.com +http_s3_region=eu-central-1 +http_s3_storage_class=${STORAGE_CLASS} +group_reporting + +# With verify, this both writes and reads the object +[create] +rw=write +bs=4k +size=64k +io_size=4k +verify=sha256 + +[trim] +stonewall +rw=trim +bs=4k +size=64k +io_size=4k + diff --git a/fio.1 b/fio.1 index ce9bf3ef4d..6630525f4c 100644 --- a/fio.1 +++ b/fio.1 @@ -2308,6 +2308,15 @@ The S3 secret key. .BI (http)http_s3_keyid \fR=\fPstr The S3 key/access id. .TP +.BI (http)http_s3_sse_customer_key \fR=\fPstr +The encryption customer key in SSE server side. +.TP +.BI (http)http_s3_sse_customer_algorithm \fR=\fPstr +The encryption customer algorithm in SSE server side. Default is \fBAES256\fR +.TP +.BI (http)http_s3_storage_class \fR=\fPstr +Which storage class to access. User-customizable settings. Default is \fBSTANDARD\fR +.TP .BI (http)http_swift_auth_token \fR=\fPstr The Swift auth token. See the example configuration file on how to retrieve this. From 910c72df9b6bceac924265d85ebee761f7c4695c Mon Sep 17 00:00:00 2001 From: "Friendy.Su@sony.com" Date: Mon, 8 Aug 2022 08:35:50 +0000 Subject: [PATCH 0229/1097] ioengines: merge filecreate, filestat, filedelete engines to fileoperations.c file operation engines have similar structure, implement them in one file. Signed-off-by: friendy-su --- Makefile | 2 +- engines/filecreate.c | 118 --------------- engines/filedelete.c | 115 -------------- engines/fileoperations.c | 318 +++++++++++++++++++++++++++++++++++++++ engines/filestat.c | 190 ----------------------- 5 files changed, 319 insertions(+), 424 deletions(-) delete mode 100644 engines/filecreate.c delete mode 100644 engines/filedelete.c create mode 100644 engines/fileoperations.c delete mode 100644 engines/filestat.c diff --git a/Makefile b/Makefile index 188a74d7b4..634d2c9313 100644 --- a/Makefile +++ b/Makefile @@ -56,7 +56,7 @@ SOURCE := $(sort $(patsubst $(SRCDIR)/%,%,$(wildcard $(SRCDIR)/crc/*.c)) \ pshared.c options.c \ smalloc.c filehash.c profile.c debug.c engines/cpu.c \ engines/mmap.c engines/sync.c engines/null.c engines/net.c \ - engines/ftruncate.c engines/filecreate.c engines/filestat.c engines/filedelete.c \ + engines/ftruncate.c engines/fileoperations.c \ engines/exec.c \ server.c client.c iolog.c backend.c libfio.c flow.c cconv.c \ gettime-thread.c helpers.c json.c idletime.c td_error.c \ diff --git a/engines/filecreate.c b/engines/filecreate.c deleted file mode 100644 index 7884752d31..0000000000 --- a/engines/filecreate.c +++ /dev/null @@ -1,118 +0,0 @@ -/* - * filecreate engine - * - * IO engine that doesn't do any IO, just creates files and tracks the latency - * of the file creation. - */ -#include -#include -#include - -#include "../fio.h" - -struct fc_data { - enum fio_ddir stat_ddir; -}; - -static int open_file(struct thread_data *td, struct fio_file *f) -{ - struct timespec start; - int do_lat = !td->o.disable_lat; - - dprint(FD_FILE, "fd open %s\n", f->file_name); - - if (f->filetype != FIO_TYPE_FILE) { - log_err("fio: only files are supported\n"); - return 1; - } - if (!strcmp(f->file_name, "-")) { - log_err("fio: can't read/write to stdin/out\n"); - return 1; - } - - if (do_lat) - fio_gettime(&start, NULL); - - f->fd = open(f->file_name, O_CREAT|O_RDWR, 0600); - - if (f->fd == -1) { - char buf[FIO_VERROR_SIZE]; - int e = errno; - - snprintf(buf, sizeof(buf), "open(%s)", f->file_name); - td_verror(td, e, buf); - return 1; - } - - if (do_lat) { - struct fc_data *data = td->io_ops_data; - uint64_t nsec; - - nsec = ntime_since_now(&start); - add_clat_sample(td, data->stat_ddir, nsec, 0, 0, 0, 0); - } - - return 0; -} - -static enum fio_q_status queue_io(struct thread_data *td, - struct io_u fio_unused *io_u) -{ - return FIO_Q_COMPLETED; -} - -/* - * Ensure that we at least have a block size worth of IO to do for each - * file. If the job file has td->o.size < nr_files * block_size, then - * fio won't do anything. - */ -static int get_file_size(struct thread_data *td, struct fio_file *f) -{ - f->real_file_size = td_min_bs(td); - return 0; -} - -static int init(struct thread_data *td) -{ - struct fc_data *data; - - data = calloc(1, sizeof(*data)); - - if (td_read(td)) - data->stat_ddir = DDIR_READ; - else if (td_write(td)) - data->stat_ddir = DDIR_WRITE; - - td->io_ops_data = data; - return 0; -} - -static void cleanup(struct thread_data *td) -{ - struct fc_data *data = td->io_ops_data; - - free(data); -} - -static struct ioengine_ops ioengine = { - .name = "filecreate", - .version = FIO_IOOPS_VERSION, - .init = init, - .cleanup = cleanup, - .queue = queue_io, - .get_file_size = get_file_size, - .open_file = open_file, - .close_file = generic_close_file, - .flags = FIO_DISKLESSIO | FIO_SYNCIO | FIO_FAKEIO | - FIO_NOSTATS | FIO_NOFILEHASH, -}; - -static void fio_init fio_filecreate_register(void) -{ - register_ioengine(&ioengine); -} - -static void fio_exit fio_filecreate_unregister(void) -{ - unregister_ioengine(&ioengine); -} diff --git a/engines/filedelete.c b/engines/filedelete.c deleted file mode 100644 index df388ac920..0000000000 --- a/engines/filedelete.c +++ /dev/null @@ -1,115 +0,0 @@ -/* - * file delete engine - * - * IO engine that doesn't do any IO, just delete files and track the latency - * of the file deletion. - */ -#include -#include -#include -#include -#include -#include "../fio.h" - -struct fc_data { - enum fio_ddir stat_ddir; -}; - -static int delete_file(struct thread_data *td, struct fio_file *f) -{ - struct timespec start; - int do_lat = !td->o.disable_lat; - int ret; - - dprint(FD_FILE, "fd delete %s\n", f->file_name); - - if (f->filetype != FIO_TYPE_FILE) { - log_err("fio: only files are supported\n"); - return 1; - } - if (!strcmp(f->file_name, "-")) { - log_err("fio: can't read/write to stdin/out\n"); - return 1; - } - - if (do_lat) - fio_gettime(&start, NULL); - - ret = unlink(f->file_name); - - if (ret == -1) { - char buf[FIO_VERROR_SIZE]; - int e = errno; - - snprintf(buf, sizeof(buf), "delete(%s)", f->file_name); - td_verror(td, e, buf); - return 1; - } - - if (do_lat) { - struct fc_data *data = td->io_ops_data; - uint64_t nsec; - - nsec = ntime_since_now(&start); - add_clat_sample(td, data->stat_ddir, nsec, 0, 0, 0, 0); - } - - return 0; -} - - -static enum fio_q_status queue_io(struct thread_data *td, struct io_u fio_unused *io_u) -{ - return FIO_Q_COMPLETED; -} - -static int init(struct thread_data *td) -{ - struct fc_data *data; - - data = calloc(1, sizeof(*data)); - - if (td_read(td)) - data->stat_ddir = DDIR_READ; - else if (td_write(td)) - data->stat_ddir = DDIR_WRITE; - - td->io_ops_data = data; - return 0; -} - -static int delete_invalidate(struct thread_data *td, struct fio_file *f) -{ - /* do nothing because file not opened */ - return 0; -} - -static void cleanup(struct thread_data *td) -{ - struct fc_data *data = td->io_ops_data; - - free(data); -} - -static struct ioengine_ops ioengine = { - .name = "filedelete", - .version = FIO_IOOPS_VERSION, - .init = init, - .invalidate = delete_invalidate, - .cleanup = cleanup, - .queue = queue_io, - .get_file_size = generic_get_file_size, - .open_file = delete_file, - .flags = FIO_SYNCIO | FIO_FAKEIO | - FIO_NOSTATS | FIO_NOFILEHASH, -}; - -static void fio_init fio_filedelete_register(void) -{ - register_ioengine(&ioengine); -} - -static void fio_exit fio_filedelete_unregister(void) -{ - unregister_ioengine(&ioengine); -} diff --git a/engines/fileoperations.c b/engines/fileoperations.c new file mode 100644 index 0000000000..1db60da181 --- /dev/null +++ b/engines/fileoperations.c @@ -0,0 +1,318 @@ +/* + * fileoperations engine + * + * IO engine that doesn't do any IO, just operates files and tracks the latency + * of the file operation. + */ +#include +#include +#include +#include +#include +#include +#include +#include "../fio.h" +#include "../optgroup.h" +#include "../oslib/statx.h" + + +struct fc_data { + enum fio_ddir stat_ddir; +}; + +struct filestat_options { + void *pad; + unsigned int stat_type; +}; + +enum { + FIO_FILESTAT_STAT = 1, + FIO_FILESTAT_LSTAT = 2, + FIO_FILESTAT_STATX = 3, +}; + +static struct fio_option options[] = { + { + .name = "stat_type", + .lname = "stat_type", + .type = FIO_OPT_STR, + .off1 = offsetof(struct filestat_options, stat_type), + .help = "Specify stat system call type to measure lookup/getattr performance", + .def = "stat", + .posval = { + { .ival = "stat", + .oval = FIO_FILESTAT_STAT, + .help = "Use stat(2)", + }, + { .ival = "lstat", + .oval = FIO_FILESTAT_LSTAT, + .help = "Use lstat(2)", + }, + { .ival = "statx", + .oval = FIO_FILESTAT_STATX, + .help = "Use statx(2) if exists", + }, + }, + .category = FIO_OPT_C_ENGINE, + .group = FIO_OPT_G_FILESTAT, + }, + { + .name = NULL, + }, +}; + + +static int open_file(struct thread_data *td, struct fio_file *f) +{ + struct timespec start; + int do_lat = !td->o.disable_lat; + + dprint(FD_FILE, "fd open %s\n", f->file_name); + + if (f->filetype != FIO_TYPE_FILE) { + log_err("fio: only files are supported\n"); + return 1; + } + if (!strcmp(f->file_name, "-")) { + log_err("fio: can't read/write to stdin/out\n"); + return 1; + } + + if (do_lat) + fio_gettime(&start, NULL); + + f->fd = open(f->file_name, O_CREAT|O_RDWR, 0600); + + if (f->fd == -1) { + char buf[FIO_VERROR_SIZE]; + int e = errno; + + snprintf(buf, sizeof(buf), "open(%s)", f->file_name); + td_verror(td, e, buf); + return 1; + } + + if (do_lat) { + struct fc_data *data = td->io_ops_data; + uint64_t nsec; + + nsec = ntime_since_now(&start); + add_clat_sample(td, data->stat_ddir, nsec, 0, 0, 0, 0); + } + + return 0; +} + +static int stat_file(struct thread_data *td, struct fio_file *f) +{ + struct filestat_options *o = td->eo; + struct timespec start; + int do_lat = !td->o.disable_lat; + struct stat statbuf; +#ifndef WIN32 + struct statx statxbuf; + char *abspath; +#endif + int ret; + + dprint(FD_FILE, "fd stat %s\n", f->file_name); + + if (f->filetype != FIO_TYPE_FILE) { + log_err("fio: only files are supported\n"); + return 1; + } + if (!strcmp(f->file_name, "-")) { + log_err("fio: can't read/write to stdin/out\n"); + return 1; + } + + if (do_lat) + fio_gettime(&start, NULL); + + switch (o->stat_type) { + case FIO_FILESTAT_STAT: + ret = stat(f->file_name, &statbuf); + break; + case FIO_FILESTAT_LSTAT: + ret = lstat(f->file_name, &statbuf); + break; + case FIO_FILESTAT_STATX: +#ifndef WIN32 + abspath = realpath(f->file_name, NULL); + if (abspath) { + ret = statx(-1, abspath, 0, STATX_ALL, &statxbuf); + free(abspath); + } else + ret = -1; +#else + ret = -1; +#endif + break; + default: + ret = -1; + break; + } + + if (ret == -1) { + char buf[FIO_VERROR_SIZE]; + int e = errno; + + snprintf(buf, sizeof(buf), "stat(%s) type=%u", f->file_name, + o->stat_type); + td_verror(td, e, buf); + return 1; + } + + if (do_lat) { + struct fc_data *data = td->io_ops_data; + uint64_t nsec; + + nsec = ntime_since_now(&start); + add_clat_sample(td, data->stat_ddir, nsec, 0, 0, 0, 0); + } + + return 0; +} + + +static int delete_file(struct thread_data *td, struct fio_file *f) +{ + struct timespec start; + int do_lat = !td->o.disable_lat; + int ret; + + dprint(FD_FILE, "fd delete %s\n", f->file_name); + + if (f->filetype != FIO_TYPE_FILE) { + log_err("fio: only files are supported\n"); + return 1; + } + if (!strcmp(f->file_name, "-")) { + log_err("fio: can't read/write to stdin/out\n"); + return 1; + } + + if (do_lat) + fio_gettime(&start, NULL); + + ret = unlink(f->file_name); + + if (ret == -1) { + char buf[FIO_VERROR_SIZE]; + int e = errno; + + snprintf(buf, sizeof(buf), "delete(%s)", f->file_name); + td_verror(td, e, buf); + return 1; + } + + if (do_lat) { + struct fc_data *data = td->io_ops_data; + uint64_t nsec; + + nsec = ntime_since_now(&start); + add_clat_sample(td, data->stat_ddir, nsec, 0, 0, 0, 0); + } + + return 0; +} + +static int invalidate_do_nothing(struct thread_data *td, struct fio_file *f) +{ + /* do nothing because file not opened */ + return 0; +} + +static enum fio_q_status queue_io(struct thread_data *td, struct io_u *io_u) +{ + return FIO_Q_COMPLETED; +} + +/* + * Ensure that we at least have a block size worth of IO to do for each + * file. If the job file has td->o.size < nr_files * block_size, then + * fio won't do anything. + */ +static int get_file_size(struct thread_data *td, struct fio_file *f) +{ + f->real_file_size = td_min_bs(td); + return 0; +} + +static int init(struct thread_data *td) +{ + struct fc_data *data; + + data = calloc(1, sizeof(*data)); + + if (td_read(td)) + data->stat_ddir = DDIR_READ; + else if (td_write(td)) + data->stat_ddir = DDIR_WRITE; + + td->io_ops_data = data; + return 0; +} + +static void cleanup(struct thread_data *td) +{ + struct fc_data *data = td->io_ops_data; + + free(data); +} + +static struct ioengine_ops ioengine_filecreate = { + .name = "filecreate", + .version = FIO_IOOPS_VERSION, + .init = init, + .cleanup = cleanup, + .queue = queue_io, + .get_file_size = get_file_size, + .open_file = open_file, + .close_file = generic_close_file, + .flags = FIO_DISKLESSIO | FIO_SYNCIO | FIO_FAKEIO | + FIO_NOSTATS | FIO_NOFILEHASH, +}; + +static struct ioengine_ops ioengine_filestat = { + .name = "filestat", + .version = FIO_IOOPS_VERSION, + .init = init, + .cleanup = cleanup, + .queue = queue_io, + .invalidate = invalidate_do_nothing, + .get_file_size = generic_get_file_size, + .open_file = stat_file, + .flags = FIO_SYNCIO | FIO_FAKEIO | + FIO_NOSTATS | FIO_NOFILEHASH, + .options = options, + .option_struct_size = sizeof(struct filestat_options), +}; + +static struct ioengine_ops ioengine_filedelete = { + .name = "filedelete", + .version = FIO_IOOPS_VERSION, + .init = init, + .invalidate = invalidate_do_nothing, + .cleanup = cleanup, + .queue = queue_io, + .get_file_size = generic_get_file_size, + .open_file = delete_file, + .flags = FIO_SYNCIO | FIO_FAKEIO | + FIO_NOSTATS | FIO_NOFILEHASH, +}; + + +static void fio_init fio_fileoperations_register(void) +{ + register_ioengine(&ioengine_filecreate); + register_ioengine(&ioengine_filestat); + register_ioengine(&ioengine_filedelete); +} + +static void fio_exit fio_fileoperations_unregister(void) +{ + unregister_ioengine(&ioengine_filecreate); + unregister_ioengine(&ioengine_filestat); + unregister_ioengine(&ioengine_filedelete); +} diff --git a/engines/filestat.c b/engines/filestat.c deleted file mode 100644 index e587eb542d..0000000000 --- a/engines/filestat.c +++ /dev/null @@ -1,190 +0,0 @@ -/* - * filestat engine - * - * IO engine that doesn't do any IO, just stat files and tracks the latency - * of the file stat. - */ -#include -#include -#include -#include -#include -#include -#include -#include "../fio.h" -#include "../optgroup.h" -#include "../oslib/statx.h" - -struct fc_data { - enum fio_ddir stat_ddir; -}; - -struct filestat_options { - void *pad; - unsigned int stat_type; -}; - -enum { - FIO_FILESTAT_STAT = 1, - FIO_FILESTAT_LSTAT = 2, - FIO_FILESTAT_STATX = 3, -}; - -static struct fio_option options[] = { - { - .name = "stat_type", - .lname = "stat_type", - .type = FIO_OPT_STR, - .off1 = offsetof(struct filestat_options, stat_type), - .help = "Specify stat system call type to measure lookup/getattr performance", - .def = "stat", - .posval = { - { .ival = "stat", - .oval = FIO_FILESTAT_STAT, - .help = "Use stat(2)", - }, - { .ival = "lstat", - .oval = FIO_FILESTAT_LSTAT, - .help = "Use lstat(2)", - }, - { .ival = "statx", - .oval = FIO_FILESTAT_STATX, - .help = "Use statx(2) if exists", - }, - }, - .category = FIO_OPT_C_ENGINE, - .group = FIO_OPT_G_FILESTAT, - }, - { - .name = NULL, - }, -}; - -static int stat_file(struct thread_data *td, struct fio_file *f) -{ - struct filestat_options *o = td->eo; - struct timespec start; - int do_lat = !td->o.disable_lat; - struct stat statbuf; -#ifndef WIN32 - struct statx statxbuf; - char *abspath; -#endif - int ret; - - dprint(FD_FILE, "fd stat %s\n", f->file_name); - - if (f->filetype != FIO_TYPE_FILE) { - log_err("fio: only files are supported\n"); - return 1; - } - if (!strcmp(f->file_name, "-")) { - log_err("fio: can't read/write to stdin/out\n"); - return 1; - } - - if (do_lat) - fio_gettime(&start, NULL); - - switch (o->stat_type){ - case FIO_FILESTAT_STAT: - ret = stat(f->file_name, &statbuf); - break; - case FIO_FILESTAT_LSTAT: - ret = lstat(f->file_name, &statbuf); - break; - case FIO_FILESTAT_STATX: -#ifndef WIN32 - abspath = realpath(f->file_name, NULL); - if (abspath) { - ret = statx(-1, abspath, 0, STATX_ALL, &statxbuf); - free(abspath); - } else - ret = -1; -#else - ret = -1; -#endif - break; - default: - ret = -1; - break; - } - - if (ret == -1) { - char buf[FIO_VERROR_SIZE]; - int e = errno; - - snprintf(buf, sizeof(buf), "stat(%s) type=%u", f->file_name, - o->stat_type); - td_verror(td, e, buf); - return 1; - } - - if (do_lat) { - struct fc_data *data = td->io_ops_data; - uint64_t nsec; - - nsec = ntime_since_now(&start); - add_clat_sample(td, data->stat_ddir, nsec, 0, 0, 0, 0); - } - - return 0; -} - -static enum fio_q_status queue_io(struct thread_data *td, struct io_u fio_unused *io_u) -{ - return FIO_Q_COMPLETED; -} - -static int init(struct thread_data *td) -{ - struct fc_data *data; - - data = calloc(1, sizeof(*data)); - - if (td_read(td)) - data->stat_ddir = DDIR_READ; - else if (td_write(td)) - data->stat_ddir = DDIR_WRITE; - - td->io_ops_data = data; - return 0; -} - -static void cleanup(struct thread_data *td) -{ - struct fc_data *data = td->io_ops_data; - - free(data); -} - -static int stat_invalidate(struct thread_data *td, struct fio_file *f) -{ - /* do nothing because file not opened */ - return 0; -} - -static struct ioengine_ops ioengine = { - .name = "filestat", - .version = FIO_IOOPS_VERSION, - .init = init, - .cleanup = cleanup, - .queue = queue_io, - .invalidate = stat_invalidate, - .get_file_size = generic_get_file_size, - .open_file = stat_file, - .flags = FIO_SYNCIO | FIO_FAKEIO | - FIO_NOSTATS | FIO_NOFILEHASH, - .options = options, - .option_struct_size = sizeof(struct filestat_options), -}; - -static void fio_init fio_filestat_register(void) -{ - register_ioengine(&ioengine); -} - -static void fio_exit fio_filestat_unregister(void) -{ - unregister_ioengine(&ioengine); -} From eeb302f9bfa4bbe121cae2a12a679c888164fc93 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Mon, 15 Aug 2022 10:37:57 -0400 Subject: [PATCH 0230/1097] README: link to GitHub releases for Windows Note that Windows installers are now available as releases on GitHub. Signed-off-by: Vincent Fu --- README.rst | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/README.rst b/README.rst index 67420903b1..79582deac2 100644 --- a/README.rst +++ b/README.rst @@ -123,10 +123,12 @@ Solaris: ``pkgutil -i fio``. Windows: - Rebecca Cran has fio packages for Windows at - https://bsdio.com/fio/ . The latest builds for Windows can also - be grabbed from https://ci.appveyor.com/project/axboe/fio by clicking - the latest x86 or x64 build, then selecting the ARTIFACTS tab. + Beginning with fio 3.31 Windows installers are available on GitHub at + https://github.com/axboe/fio/releases. Rebecca Cran + has fio packages for Windows at + https://bsdio.com/fio/ . The latest builds for Windows can also be + grabbed from https://ci.appveyor.com/project/axboe/fio by clicking the + latest x86 or x64 build and then selecting the Artifacts tab. BSDs: Packages for BSDs may be available from their binary package repositories. From fdac9c68425a7bd4008614476a27e536b0b0bf8b Mon Sep 17 00:00:00 2001 From: Ankit Kumar Date: Tue, 16 Aug 2022 11:08:20 +0530 Subject: [PATCH 0231/1097] engines/xnvme: fix segfault issue with xnvme ioengine fix segfault when xnvme ioengine is called without thread=1. The segfault happens because td->io_ops_data is accessed at two locations xnvme_fioe_cleanup and xnvme_fioe_iomem_free, during the error handling call. Signed-off-by: Ankit Kumar Link: https://lore.kernel.org/r/20220816053821.440-2-ankit.kumar@samsung.com Signed-off-by: Jens Axboe --- engines/xnvme.c | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/engines/xnvme.c b/engines/xnvme.c index c11b33a805..d86474814a 100644 --- a/engines/xnvme.c +++ b/engines/xnvme.c @@ -205,9 +205,14 @@ static void _dev_close(struct thread_data *td, struct xnvme_fioe_fwrap *fwrap) static void xnvme_fioe_cleanup(struct thread_data *td) { - struct xnvme_fioe_data *xd = td->io_ops_data; + struct xnvme_fioe_data *xd = NULL; int err; + if (!td->io_ops_data) + return; + + xd = td->io_ops_data; + err = pthread_mutex_lock(&g_serialize); if (err) log_err("ioeng->cleanup(): pthread_mutex_lock(), err(%d)\n", err); @@ -367,8 +372,14 @@ static int xnvme_fioe_iomem_alloc(struct thread_data *td, size_t total_mem) /* NOTE: using the first device for buffer-allocators) */ static void xnvme_fioe_iomem_free(struct thread_data *td) { - struct xnvme_fioe_data *xd = td->io_ops_data; - struct xnvme_fioe_fwrap *fwrap = &xd->files[0]; + struct xnvme_fioe_data *xd = NULL; + struct xnvme_fioe_fwrap *fwrap = NULL; + + if (!td->io_ops_data) + return; + + xd = td->io_ops_data; + fwrap = &xd->files[0]; if (!fwrap->dev) { log_err("ioeng->iomem_free(): failed no dev-handle\n"); From 4deb92f9e1a83682143661eb3b04422bdfff5ace Mon Sep 17 00:00:00 2001 From: Ankit Kumar Date: Tue, 16 Aug 2022 11:08:21 +0530 Subject: [PATCH 0232/1097] doc: update fio doc for xnvme engine - Elaborate about the various sync, async and admin interfaces. - add missing io_uring_cmd async backend entry. - xnvme ioengine doesn't support file stat. Signed-off-by: Ankit Kumar Link: https://lore.kernel.org/r/20220816053821.440-3-ankit.kumar@samsung.com Signed-off-by: Jens Axboe --- HOWTO.rst | 37 ++++++++++++++++++++++++++----------- fio.1 | 34 +++++++++++++++++++++------------- 2 files changed, 47 insertions(+), 24 deletions(-) diff --git a/HOWTO.rst b/HOWTO.rst index 05fc117f2f..b2750b566c 100644 --- a/HOWTO.rst +++ b/HOWTO.rst @@ -2780,41 +2780,56 @@ with the caveat that when used on the command line, they must come after the Select the xnvme async command interface. This can take these values. **emu** - This is default and used to emulate asynchronous I/O. + This is default and use to emulate asynchronous I/O by using a + single thread to create a queue pair on top of a synchronous + I/O interface using the NVMe driver IOCTL. **thrpool** - Use thread pool for Asynchronous I/O. + Emulate an asynchronous I/O interface with a pool of userspace + threads on top of a synchronous I/O interface using the NVMe + driver IOCTL. By default four threads are used. **io_uring** - Use Linux io_uring/liburing for Asynchronous I/O. + Linux native asynchronous I/O interface which supports both + direct and buffered I/O. + **io_uring_cmd** + Fast Linux native asynchronous I/O interface for NVMe pass + through commands. This only works with NVMe character device + (/dev/ngXnY). **libaio** Use Linux aio for Asynchronous I/O. **posix** - Use POSIX aio for Asynchronous I/O. + Use the posix asynchronous I/O interface to perform one or + more I/O operations asynchronously. **nil** - Use nil-io; For introspective perf. evaluation + Do not transfer any data; just pretend to. This is mainly used + for introspective performance evaluation. .. option:: xnvme_sync=str : [xnvme] Select the xnvme synchronous command interface. This can take these values. **nvme** - This is default and uses Linux NVMe Driver ioctl() for synchronous I/O. + This is default and uses Linux NVMe Driver ioctl() for + synchronous I/O. **psync** - Use pread()/write() for synchronous I/O. + This supports regular as well as vectored pread() and pwrite() + commands. + **block** + This is the same as psync except that it also supports zone + management commands using Linux block layer IOCTLs. .. option:: xnvme_admin=str : [xnvme] Select the xnvme admin command interface. This can take these values. **nvme** - This is default and uses linux NVMe Driver ioctl() for admin commands. + This is default and uses linux NVMe Driver ioctl() for admin + commands. **block** Use Linux Block Layer ioctl() and sysfs for admin commands. - **file_as_ns** - Use file-stat to construct NVMe idfy responses. .. option:: xnvme_dev_nsid=int : [xnvme] - xnvme namespace identifier, for userspace NVMe driver. + xnvme namespace identifier for userspace NVMe driver, such as SPDK. .. option:: xnvme_iovec=int : [xnvme] diff --git a/fio.1 b/fio.1 index 6630525f4c..f3f3dc5d5a 100644 --- a/fio.1 +++ b/fio.1 @@ -2530,22 +2530,29 @@ Select the xnvme async command interface. This can take these values. .RS .TP .B emu -This is default and used to emulate asynchronous I/O +This is default and use to emulate asynchronous I/O by using a single thread to +create a queue pair on top of a synchronous I/O interface using the NVMe driver +IOCTL. .TP .BI thrpool -Use thread pool for Asynchronous I/O +Emulate an asynchronous I/O interface with a pool of userspace threads on top +of a synchronous I/O interface using the NVMe driver IOCTL. By default four +threads are used. .TP .BI io_uring -Use Linux io_uring/liburing for Asynchronous I/O +Linux native asynchronous I/O interface which supports both direct and buffered +I/O. .TP .BI libaio Use Linux aio for Asynchronous I/O .TP .BI posix -Use POSIX aio for Asynchronous I/O +Use the posix asynchronous I/O interface to perform one or more I/O operations +asynchronously. .TP .BI nil -Use nil-io; For introspective perf. evaluation +Do not transfer any data; just pretend to. This is mainly used for +introspective performance evaluation. .RE .RE .TP @@ -2555,10 +2562,14 @@ Select the xnvme synchronous command interface. This can take these values. .RS .TP .B nvme -This is default and uses Linux NVMe Driver ioctl() for synchronous I/O +This is default and uses Linux NVMe Driver ioctl() for synchronous I/O. .TP .BI psync -Use pread()/write() for synchronous I/O +This supports regular as well as vectored pread() and pwrite() commands. +.TP +.BI block +This is the same as psync except that it also supports zone management +commands using Linux block layer IOCTLs. .RE .RE .TP @@ -2568,18 +2579,15 @@ Select the xnvme admin command interface. This can take these values. .RS .TP .B nvme -This is default and uses Linux NVMe Driver ioctl() for admin commands +This is default and uses Linux NVMe Driver ioctl() for admin commands. .TP .BI block -Use Linux Block Layer ioctl() and sysfs for admin commands -.TP -.BI file_as_ns -Use file-stat as to construct NVMe idfy responses +Use Linux Block Layer ioctl() and sysfs for admin commands. .RE .RE .TP .BI (xnvme)xnvme_dev_nsid\fR=\fPint -xnvme namespace identifier, for userspace NVMe driver. +xnvme namespace identifier for userspace NVMe driver such as SPDK. .TP .BI (xnvme)xnvme_iovec If this option is set, xnvme will use vectored read/write commands. From adcc0730174af34e6ae1b415f3e84a1d06ceb1a0 Mon Sep 17 00:00:00 2001 From: Konstantin Kharlamov Date: Tue, 16 Aug 2022 19:14:13 +0300 Subject: [PATCH 0233/1097] doc: get rid of trailing whitespace Signed-off-by: Konstantin Kharlamov --- HOWTO.rst | 4 ++-- fio.1 | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/HOWTO.rst b/HOWTO.rst index b2750b566c..c94238ed5b 100644 --- a/HOWTO.rst +++ b/HOWTO.rst @@ -1301,7 +1301,7 @@ I/O type effectively caps the file size at `real_size - offset`. Can be combined with :option:`size` to constrain the start and end range of the I/O workload. A percentage can be specified by a number between 1 and 100 followed by '%', - for example, ``offset=20%`` to specify 20%. In ZBD mode, value can be set as + for example, ``offset=20%`` to specify 20%. In ZBD mode, value can be set as number of zones using 'z'. .. option:: offset_align=int @@ -1877,7 +1877,7 @@ I/O size If this option is not specified, fio will use the full size of the given files or devices. If the files do not exist, size must be given. It is also possible to give size as a percentage between 1 and 100. If ``size=20%`` is - given, fio will use 20% of the full size of the given files or devices. + given, fio will use 20% of the full size of the given files or devices. In ZBD mode, value can also be set as number of zones using 'z'. Can be combined with :option:`offset` to constrain the start and end range that I/O will be done within. diff --git a/fio.1 b/fio.1 index f3f3dc5d5a..d40b42479a 100644 --- a/fio.1 +++ b/fio.1 @@ -292,7 +292,7 @@ For Zone Block Device Mode: .RS .P .PD 0 -z means Zone +z means Zone .P .PD .RE @@ -1083,7 +1083,7 @@ provided. Data before the given offset will not be touched. This effectively caps the file size at `real_size \- offset'. Can be combined with \fBsize\fR to constrain the start and end range of the I/O workload. A percentage can be specified by a number between 1 and 100 followed by '%', -for example, `offset=20%' to specify 20%. In ZBD mode, value can be set as +for example, `offset=20%' to specify 20%. In ZBD mode, value can be set as number of zones using 'z'. .TP .BI offset_align \fR=\fPint @@ -1099,7 +1099,7 @@ specified). This option is useful if there are several jobs which are intended to operate on a file in parallel disjoint segments, with even spacing between the starting points. Percentages can be used for this option. If a percentage is given, the generated offset will be aligned to the minimum -\fBblocksize\fR or to the value of \fBoffset_align\fR if provided.In ZBD mode, value +\fBblocksize\fR or to the value of \fBoffset_align\fR if provided.In ZBD mode, value can be set as number of zones using 'z'. .TP .BI number_ios \fR=\fPint @@ -1678,7 +1678,7 @@ If this option is not specified, fio will use the full size of the given files or devices. If the files do not exist, size must be given. It is also possible to give size as a percentage between 1 and 100. If `size=20%' is given, fio will use 20% of the full size of the given files or devices. In ZBD mode, -size can be given in units of number of zones using 'z'. Can be combined with \fBoffset\fR to +size can be given in units of number of zones using 'z'. Can be combined with \fBoffset\fR to constrain the start and end range that I/O will be done within. .TP .BI io_size \fR=\fPint[%|z] "\fR,\fB io_limit" \fR=\fPint[%|z] @@ -1697,7 +1697,7 @@ also be set as number of zones using 'z'. .BI filesize \fR=\fPirange(int) Individual file sizes. May be a range, in which case fio will select sizes for files at random within the given range. If not given, each created file -is the same size. This option overrides \fBsize\fR in terms of file size, +is the same size. This option overrides \fBsize\fR in terms of file size, i.e. \fBsize\fR becomes merely the default for \fBio_size\fR (and has no effect it all if \fBio_size\fR is set explicitly). .TP From 31a58cbacdbbac5783ee0255de3532ff5294480b Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Mon, 15 Aug 2022 11:34:43 -0400 Subject: [PATCH 0234/1097] test: add latency test using posixaio ioengine Make sure that mean(slat) + mean(clat) = mean(total lat). Tests 15 and 16 use the libaio and null ioengines, respectively. Both of those ioengines have commit hooks. Add this new test using the posixaio ioengine which does not have a commit hook so that we can better cover the possible ways that latency is calcualted. Signed-off-by: Vincent Fu --- t/jobs/t0017.fio | 9 +++++++++ t/run-fio-tests.py | 10 ++++++++++ 2 files changed, 19 insertions(+) create mode 100644 t/jobs/t0017.fio diff --git a/t/jobs/t0017.fio b/t/jobs/t0017.fio new file mode 100644 index 0000000000..14486d98cd --- /dev/null +++ b/t/jobs/t0017.fio @@ -0,0 +1,9 @@ +# Expected result: mean(slat) + mean(clat) = mean(lat) +# Buggy result: equality does not hold +# This is similar to t0015 and t0016 except that is uses posixaio which is +# available on more platforms and does not have a commit hook + +[test] +ioengine=posixaio +size=1M +iodepth=16 diff --git a/t/run-fio-tests.py b/t/run-fio-tests.py index d77f20e00f..2bd02a2a31 100755 --- a/t/run-fio-tests.py +++ b/t/run-fio-tests.py @@ -857,6 +857,16 @@ def cpucount4(cls): 'output_format': 'json', 'requirements': [], }, + { + 'test_id': 17, + 'test_class': FioJobTest_t0015, + 'job': 't0017.fio', + 'success': SUCCESS_DEFAULT, + 'pre_job': None, + 'pre_success': None, + 'output_format': 'json', + 'requirements': [Requirements.not_windows], + }, { 'test_id': 1000, 'test_class': FioExeTest, From f045d5f85f80c226f12586df90482fe804fc5c19 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Mon, 15 Aug 2022 11:40:58 -0400 Subject: [PATCH 0235/1097] test: fix hash for t0016 I used the wrong hash for t0016 in the original commit. Fix it to refer to the hash that fixed the issue in this tree. Fixes: de31fe9a ("testing: add test for slat + clat = tlat") Signed-off-by: Vincent Fu --- t/jobs/{t0016-259ebc00.fio => t0016-d54ae22.fio} | 0 t/run-fio-tests.py | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename t/jobs/{t0016-259ebc00.fio => t0016-d54ae22.fio} (100%) diff --git a/t/jobs/t0016-259ebc00.fio b/t/jobs/t0016-d54ae22.fio similarity index 100% rename from t/jobs/t0016-259ebc00.fio rename to t/jobs/t0016-d54ae22.fio diff --git a/t/run-fio-tests.py b/t/run-fio-tests.py index 2bd02a2a31..504b7cdb38 100755 --- a/t/run-fio-tests.py +++ b/t/run-fio-tests.py @@ -850,7 +850,7 @@ def cpucount4(cls): { 'test_id': 16, 'test_class': FioJobTest_t0015, - 'job': 't0016-259ebc00.fio', + 'job': 't0016-d54ae22.fio', 'success': SUCCESS_DEFAULT, 'pre_job': None, 'pre_success': None, From dc3059899f9fae921466413e1a25ef3279a24b3b Mon Sep 17 00:00:00 2001 From: Konstantin Kharlamov Date: Tue, 16 Aug 2022 19:10:38 +0300 Subject: [PATCH 0236/1097] doc: clarify that I/O errors may go unnoticed without direct=1 Fixes: https://github.com/axboe/fio/issues/1443 Reported-by: Konstantin Kharlamov Signed-off-by: Konstantin Kharlamov --- HOWTO.rst | 7 +++++++ fio.1 | 10 ++++++++++ 2 files changed, 17 insertions(+) diff --git a/HOWTO.rst b/HOWTO.rst index c94238ed5b..08be687c90 100644 --- a/HOWTO.rst +++ b/HOWTO.rst @@ -3927,6 +3927,13 @@ Error handling appended, the total error count and the first error. The error field given in the stats is the first error that was hit during the run. + Note: a write error from the device may go unnoticed by fio when using + buffered IO, as the write() (or similar) system call merely dirties the + kernel pages, unless :option:`sync` or :option:`direct` is used. Device IO + errors occur when the dirty data is actually written out to disk. If fully + sync writes aren't desirable, :option:`fsync` or :option:`fdatasync` can be + used as well. This is specific to writes, as reads are always synchronous. + The allowed values are: **none** diff --git a/fio.1 b/fio.1 index d40b42479a..27454b0b89 100644 --- a/fio.1 +++ b/fio.1 @@ -3606,6 +3606,16 @@ EILSEQ) until the runtime is exceeded or the I/O size specified is completed. If this option is used, there are two more stats that are appended, the total error count and the first error. The error field given in the stats is the first error that was hit during the run. +.RS +.P +Note: a write error from the device may go unnoticed by fio when using buffered +IO, as the write() (or similar) system call merely dirties the kernel pages, +unless `sync' or `direct' is used. Device IO errors occur when the dirty data is +actually written out to disk. If fully sync writes aren't desirable, `fsync' or +`fdatasync' can be used as well. This is specific to writes, as reads are always +synchronous. +.RS +.P The allowed values are: .RS .RS From 0dea1f55e9b821a6bcb12e8faaebb8ac91940ad8 Mon Sep 17 00:00:00 2001 From: Brandon Paupore Date: Fri, 5 Aug 2022 12:57:27 -0500 Subject: [PATCH 0237/1097] Add wait for handling SIGBREAK When closing a command prompt window or terminating it using something like the taskkill command, each child process (such as a running FIO workload) is sent a SIGBREAK signal. Once those child processes have responded to that signal, Windows terminates them if they're still executing. This change has the main thread to wait for others to exit when handling a SIGBREAK signal, such that each job will still have time to wrap-up and give stats before the entire program terminates. Signed-off-by: Brandon Paupore --- backend.c | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/backend.c b/backend.c index 5159b60ddf..4a6a61b835 100644 --- a/backend.c +++ b/backend.c @@ -90,6 +90,25 @@ static void sig_int(int sig) } } +#ifdef WIN32 +static void sig_break(int sig) +{ + struct thread_data *td; + int i; + + sig_int(sig); + + /** + * Windows terminates all job processes on SIGBREAK after the handler + * returns, so give them time to wrap-up and give stats + */ + for_each_td(td, i) { + while (td->runstate < TD_EXITED) + sleep(1); + } +} +#endif + void sig_show_status(int sig) { show_running_run_stats(); @@ -112,7 +131,7 @@ static void set_sig_handlers(void) /* Windows uses SIGBREAK as a quit signal from other applications */ #ifdef WIN32 memset(&act, 0, sizeof(act)); - act.sa_handler = sig_int; + act.sa_handler = sig_break; act.sa_flags = SA_RESTART; sigaction(SIGBREAK, &act, NULL); #endif From f1282f7669669ce9b2c9f64d5d55af3d85586394 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Tue, 23 Aug 2022 12:38:20 -0400 Subject: [PATCH 0238/1097] Revert "Minor style fixups" This reverts commit 48f8268e88629d408ffd09b1601ad13366bd4ce1. Signed-off-by: Vincent Fu --- backend.c | 2 +- filesetup.c | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/backend.c b/backend.c index 5159b60ddf..3a99850db1 100644 --- a/backend.c +++ b/backend.c @@ -2321,7 +2321,7 @@ static void run_threads(struct sk_out *sk_out) * when setup_files() does not run into issues * later. */ - if (!i && td->o.nr_files == 1) { + if (!i && td->o.nr_files==1) { if (setup_shared_file(td)) { exit_value++; if (td->error) diff --git a/filesetup.c b/filesetup.c index 3e2ccf9b9b..144a057241 100644 --- a/filesetup.c +++ b/filesetup.c @@ -1109,8 +1109,9 @@ int setup_shared_file(struct thread_data *td) dprint(FD_FILE, "fio: extending shared file\n"); f->real_file_size = file_size; err = extend_file(td, f); - if (!err) + if (!err) { err = __file_invalidate_cache(td, f, 0, f->real_file_size); + } get_file_sizes(td); dprint(FD_FILE, "shared setup new real_file_size=%llu\n", (unsigned long long)f->real_file_size); From d5e3f7cbaf9f20160a1b0f1a35885c7f63be960f Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Tue, 23 Aug 2022 12:38:51 -0400 Subject: [PATCH 0239/1097] Revert "Fix multithread issues when operating on a single shared file" This reverts commit acbda87c34c743ff2d9e125d9539bcfbbf49eb75. This commit introduced a lot of unintended consequences for create_serialize=0. The aim of the commit can be accomplished with a combination of filesize and io_size. Fixes: https://github.com/axboe/fio/issues/1442 Signed-off-by: Vincent Fu --- backend.c | 19 +------------------ file.h | 1 - filesetup.c | 46 ++-------------------------------------------- 3 files changed, 3 insertions(+), 63 deletions(-) diff --git a/backend.c b/backend.c index 3a99850db1..e5bb4e2590 100644 --- a/backend.c +++ b/backend.c @@ -2314,25 +2314,8 @@ static void run_threads(struct sk_out *sk_out) for_each_td(td, i) { print_status_init(td->thread_number - 1); - if (!td->o.create_serialize) { - /* - * When operating on a single rile in parallel, - * perform single-threaded early setup so that - * when setup_files() does not run into issues - * later. - */ - if (!i && td->o.nr_files==1) { - if (setup_shared_file(td)) { - exit_value++; - if (td->error) - log_err("fio: pid=%d, err=%d/%s\n", - (int) td->pid, td->error, td->verror); - td_set_runstate(td, TD_REAPED); - todo--; - } - } + if (!td->o.create_serialize) continue; - } if (fio_verify_load_state(td)) goto reap; diff --git a/file.h b/file.h index e646cf22f6..da1b894706 100644 --- a/file.h +++ b/file.h @@ -201,7 +201,6 @@ struct thread_data; extern void close_files(struct thread_data *); extern void close_and_free_files(struct thread_data *); extern uint64_t get_start_offset(struct thread_data *, struct fio_file *); -extern int __must_check setup_shared_file(struct thread_data *); extern int __must_check setup_files(struct thread_data *); extern int __must_check file_invalidate_cache(struct thread_data *, struct fio_file *); #ifdef __cplusplus diff --git a/filesetup.c b/filesetup.c index 144a057241..1d3cc5ad9e 100644 --- a/filesetup.c +++ b/filesetup.c @@ -143,7 +143,7 @@ static int extend_file(struct thread_data *td, struct fio_file *f) if (unlink_file || new_layout) { int ret; - dprint(FD_FILE, "layout %d unlink %d %s\n", new_layout, unlink_file, f->file_name); + dprint(FD_FILE, "layout unlink %s\n", f->file_name); ret = td_io_unlink_file(td, f); if (ret != 0 && ret != ENOENT) { @@ -198,9 +198,6 @@ static int extend_file(struct thread_data *td, struct fio_file *f) } } - - dprint(FD_FILE, "fill file %s, size %llu\n", f->file_name, (unsigned long long) f->real_file_size); - left = f->real_file_size; bs = td->o.max_bs[DDIR_WRITE]; if (bs > left) @@ -1081,45 +1078,6 @@ static bool create_work_dirs(struct thread_data *td, const char *fname) return true; } -int setup_shared_file(struct thread_data *td) -{ - struct fio_file *f; - uint64_t file_size; - int err = 0; - - if (td->o.nr_files > 1) { - log_err("fio: shared file setup called for multiple files\n"); - return -1; - } - - get_file_sizes(td); - - f = td->files[0]; - - if (f == NULL) { - log_err("fio: NULL shared file\n"); - return -1; - } - - file_size = thread_number * td->o.size; - dprint(FD_FILE, "shared setup %s real_file_size=%llu, desired=%llu\n", - f->file_name, (unsigned long long)f->real_file_size, (unsigned long long)file_size); - - if (f->real_file_size < file_size) { - dprint(FD_FILE, "fio: extending shared file\n"); - f->real_file_size = file_size; - err = extend_file(td, f); - if (!err) { - err = __file_invalidate_cache(td, f, 0, f->real_file_size); - } - get_file_sizes(td); - dprint(FD_FILE, "shared setup new real_file_size=%llu\n", - (unsigned long long)f->real_file_size); - } - - return err; -} - /* * Open the files and setup files sizes, creating files if necessary. */ @@ -1134,7 +1092,7 @@ int setup_files(struct thread_data *td) const unsigned long long bs = td_min_bs(td); uint64_t fs = 0; - dprint(FD_FILE, "setup files (thread_number=%d, subjob_number=%d)\n", td->thread_number, td->subjob_number); + dprint(FD_FILE, "setup files\n"); old_state = td_bump_runstate(td, TD_SETTING_UP); From 1816895b788e4437c8a5d1cdc00a59bc39f52ebf Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Wed, 24 Aug 2022 12:01:39 -0600 Subject: [PATCH 0240/1097] engines/io_uring: pass back correct error value when interrupted Running with an io_uring engine and using a USR1 signal to show current status will end up terminating the job with: fio: pid=91726, err=-4/file:ioengines.c:320, func=get_events, error=Unknown error -4 sfx: (groupid=0, jobs=1): err=-4 (file:ioengines.c:320, func=get_events, error=Unknown error -4): pid=91726: Wed Aug 24 11:59:51 2022 Ensure that the return value is set correctly based on the errno. Signed-off-by: Jens Axboe --- engines/io_uring.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/engines/io_uring.c b/engines/io_uring.c index cffc73710d..89d64b06fc 100644 --- a/engines/io_uring.c +++ b/engines/io_uring.c @@ -445,12 +445,18 @@ static struct io_u *fio_ioring_event(struct thread_data *td, int event) struct io_uring_cqe *cqe; struct io_u *io_u; unsigned index; + static int eio; index = (event + ld->cq_ring_off) & ld->cq_ring_mask; cqe = &ld->cq_ring.cqes[index]; io_u = (struct io_u *) (uintptr_t) cqe->user_data; + if (eio++ == 5) { + printf("mark EIO\n"); + cqe->res = -EIO; + } + if (cqe->res != io_u->xfer_buflen) { if (cqe->res > io_u->xfer_buflen) io_u->error = -cqe->res; @@ -532,6 +538,7 @@ static int fio_ioring_getevents(struct thread_data *td, unsigned int min, if (r < 0) { if (errno == EAGAIN || errno == EINTR) continue; + r = -errno; td_verror(td, errno, "io_uring_enter"); break; } @@ -665,6 +672,7 @@ static int fio_ioring_commit(struct thread_data *td) usleep(1); continue; } + ret = -errno; td_verror(td, errno, "io_uring_enter submit"); break; } From 5446b11aa39996920e0949e77397410e599b2162 Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Wed, 24 Aug 2022 13:42:29 -0700 Subject: [PATCH 0241/1097] Enable CPU affinity support on Android This patch enables the --cpumask=, --cpus_allowed= and --cpus_allowed_policy= fio options. Signed-off-by: Bart Van Assche --- os/os-android.h | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/os/os-android.h b/os/os-android.h index 2f73d249d5..34534239fd 100644 --- a/os/os-android.h +++ b/os/os-android.h @@ -24,6 +24,7 @@ #define __has_builtin(x) 0 // Compatibility with non-clang compilers. #endif +#define FIO_HAVE_CPU_AFFINITY #define FIO_HAVE_DISK_UTIL #define FIO_HAVE_IOSCHED_SWITCH #define FIO_HAVE_IOPRIO @@ -44,6 +45,13 @@ #define OS_MAP_ANON MAP_ANONYMOUS +typedef cpu_set_t os_cpu_mask_t; + +#define fio_setaffinity(pid, cpumask) \ + sched_setaffinity((pid), sizeof(cpumask), &(cpumask)) +#define fio_getaffinity(pid, ptr) \ + sched_getaffinity((pid), sizeof(cpu_set_t), (ptr)) + #ifndef POSIX_MADV_DONTNEED #define posix_madvise madvise #define POSIX_MADV_DONTNEED MADV_DONTNEED @@ -64,6 +72,24 @@ pthread_getaffinity_np(pthread_self(), sizeof(mask), &(mask)) #endif +#define fio_cpu_clear(mask, cpu) CPU_CLR((cpu), (mask)) +#define fio_cpu_set(mask, cpu) CPU_SET((cpu), (mask)) +#define fio_cpu_isset(mask, cpu) (CPU_ISSET((cpu), (mask)) != 0) +#define fio_cpu_count(mask) CPU_COUNT((mask)) + +static inline int fio_cpuset_init(os_cpu_mask_t *mask) +{ + CPU_ZERO(mask); + return 0; +} + +static inline int fio_cpuset_exit(os_cpu_mask_t *mask) +{ + return 0; +} + +#define FIO_MAX_CPUS CPU_SETSIZE + #ifndef CONFIG_NO_SHM /* * Bionic doesn't support SysV shared memory, so implement it using ashmem From 6243766bb1660ad8880fffc373121bcc4d61620d Mon Sep 17 00:00:00 2001 From: Khem Raj Date: Wed, 24 Aug 2022 18:08:53 -0700 Subject: [PATCH 0242/1097] io_uring: Replace pthread_self with s->tid __init_rand64 takes 64bit value and srand48 takes unsigned 32bit value, pthread_t is opaque type and some libcs ( e.g. musl ) do not define them in plain old data types and ends up with errors | t/io_uring.c:809:32: error: incompatible pointer to integer conversion passing 'pthread_t' (aka 'struct __pthread *') to parameter of type 'uint64_t' (aka 'unsigned long') [-Wint-conver sion] | __init_rand64(&s->rand_state, pthread_self()); | ^~~~~~~~~~~~~~ Signed-off-by: Khem Raj --- t/io_uring.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/t/io_uring.c b/t/io_uring.c index 35bf195644..f34a3554fe 100644 --- a/t/io_uring.c +++ b/t/io_uring.c @@ -799,15 +799,14 @@ static int submitter_init(struct submitter *s) int i, nr_batch, err; static int init_printed; char buf[80]; - s->tid = gettid(); printf("submitter=%d, tid=%d, file=%s, node=%d\n", s->index, s->tid, s->filename, s->numa_node); set_affinity(s); - __init_rand64(&s->rand_state, pthread_self()); - srand48(pthread_self()); + __init_rand64(&s->rand_state, s->tid); + srand48(s->tid); for (i = 0; i < MAX_FDS; i++) s->files[i].fileno = i; From c27ae7ae6c3d9108bba80ff71cf36bf7fc8b34c9 Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Thu, 25 Aug 2022 11:19:34 -0600 Subject: [PATCH 0243/1097] engines/io_uring: delete debug code This was inadvertently introduced by a previous commit, get rid of it. Fixes: 1816895b788e ("engines/io_uring: pass back correct error value when interrupted") Signed-off-by: Jens Axboe --- engines/io_uring.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/engines/io_uring.c b/engines/io_uring.c index 89d64b06fc..94376efa7f 100644 --- a/engines/io_uring.c +++ b/engines/io_uring.c @@ -445,18 +445,12 @@ static struct io_u *fio_ioring_event(struct thread_data *td, int event) struct io_uring_cqe *cqe; struct io_u *io_u; unsigned index; - static int eio; index = (event + ld->cq_ring_off) & ld->cq_ring_mask; cqe = &ld->cq_ring.cqes[index]; io_u = (struct io_u *) (uintptr_t) cqe->user_data; - if (eio++ == 5) { - printf("mark EIO\n"); - cqe->res = -EIO; - } - if (cqe->res != io_u->xfer_buflen) { if (cqe->res > io_u->xfer_buflen) io_u->error = -cqe->res; From c409e4c2a549ccc0334f2c084a76e80314d42c42 Mon Sep 17 00:00:00 2001 From: Anuj Gupta Date: Fri, 26 Aug 2022 17:03:05 +0530 Subject: [PATCH 0244/1097] t/io_uring: prep for including engines/nvme.h in t/io_uring Change page_size and cal_clat_percentiles name to something different as these are indirectly picked from engines/nvme.h (fio.h and stat.h) Signed-off-by: Anuj Gupta Link: https://lore.kernel.org/r/20220826113306.4139-2-anuj20.g@samsung.com Signed-off-by: Jens Axboe --- t/io_uring.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/t/io_uring.c b/t/io_uring.c index f34a3554fe..6e4737e446 100644 --- a/t/io_uring.c +++ b/t/io_uring.c @@ -117,7 +117,7 @@ static struct submitter *submitter; static volatile int finish; static int stats_running; static unsigned long max_iops; -static long page_size; +static long t_io_uring_page_size; static int depth = DEPTH; static int batch_submit = BATCH_SUBMIT; @@ -195,9 +195,9 @@ static unsigned long plat_idx_to_val(unsigned int idx) return cycles_to_nsec(base + ((k + 0.5) * (1 << error_bits))); } -unsigned int calc_clat_percentiles(unsigned long *io_u_plat, unsigned long nr, - unsigned long **output, - unsigned long *maxv, unsigned long *minv) +unsigned int calculate_clat_percentiles(unsigned long *io_u_plat, + unsigned long nr, unsigned long **output, + unsigned long *maxv, unsigned long *minv) { unsigned long sum = 0; unsigned int len = plist_len, i, j = 0; @@ -251,7 +251,7 @@ static void show_clat_percentiles(unsigned long *io_u_plat, unsigned long nr, bool is_last; char fmt[32]; - len = calc_clat_percentiles(io_u_plat, nr, &ovals, &maxv, &minv); + len = calculate_clat_percentiles(io_u_plat, nr, &ovals, &maxv, &minv); if (!len || !ovals) goto out; @@ -786,7 +786,7 @@ static void *allocate_mem(struct submitter *s, int size) return numa_alloc_onnode(size, s->numa_node); #endif - if (posix_memalign(&buf, page_size, bs)) { + if (posix_memalign(&buf, t_io_uring_page_size, bs)) { printf("failed alloc\n"); return NULL; } @@ -1542,9 +1542,9 @@ int main(int argc, char *argv[]) arm_sig_int(); - page_size = sysconf(_SC_PAGESIZE); - if (page_size < 0) - page_size = 4096; + t_io_uring_page_size = sysconf(_SC_PAGESIZE); + if (t_io_uring_page_size < 0) + t_io_uring_page_size = 4096; for (j = 0; j < nthreads; j++) { s = get_submitter(j); From 7d04588a766308d5903f6cfe34ed72f6c7612d19 Mon Sep 17 00:00:00 2001 From: Anuj Gupta Date: Fri, 26 Aug 2022 17:03:06 +0530 Subject: [PATCH 0245/1097] t/io_uring: add support for async-passthru This patch adds support for async-passthru in t/io_uring. User needs to specify -u1 option in the command Example commandline: t/io_uring -b512 -d128 -c32 -s32 -p0 -F1 -B0 -O0 -n1 -u1 /dev/ng0n1 Signed-off-by: Anuj Gupta Link: https://lore.kernel.org/r/20220826113306.4139-3-anuj20.g@samsung.com Signed-off-by: Jens Axboe --- t/io_uring.c | 238 +++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 230 insertions(+), 8 deletions(-) diff --git a/t/io_uring.c b/t/io_uring.c index 6e4737e446..0a90f85c55 100644 --- a/t/io_uring.c +++ b/t/io_uring.c @@ -35,6 +35,7 @@ #include "../lib/rand.h" #include "../minmax.h" #include "../os/linux/io_uring.h" +#include "../engines/nvme.h" struct io_sq_ring { unsigned *head; @@ -67,6 +68,8 @@ struct file { unsigned long max_size; unsigned long cur_off; unsigned pending_ios; + unsigned int nsid; /* nsid field required for nvme-passthrough */ + unsigned int lba_shift; /* lba_shift field required for nvme-passthrough */ int real_fd; int fixed_fd; int fileno; @@ -139,6 +142,7 @@ static int random_io = 1; /* random or sequential IO */ static int register_ring = 1; /* register ring */ static int use_sync = 0; /* use preadv2 */ static int numa_placement = 0; /* set to node of device */ +static int pt = 0; /* passthrough I/O or not */ static unsigned long tsc_rate; @@ -161,6 +165,54 @@ struct io_uring_map_buffers { }; #endif +static int nvme_identify(int fd, __u32 nsid, enum nvme_identify_cns cns, + enum nvme_csi csi, void *data) +{ + struct nvme_passthru_cmd cmd = { + .opcode = nvme_admin_identify, + .nsid = nsid, + .addr = (__u64)(uintptr_t)data, + .data_len = NVME_IDENTIFY_DATA_SIZE, + .cdw10 = cns, + .cdw11 = csi << NVME_IDENTIFY_CSI_SHIFT, + .timeout_ms = NVME_DEFAULT_IOCTL_TIMEOUT, + }; + + return ioctl(fd, NVME_IOCTL_ADMIN_CMD, &cmd); +} + +static int nvme_get_info(int fd, __u32 *nsid, __u32 *lba_sz, __u64 *nlba) +{ + struct nvme_id_ns ns; + int namespace_id; + int err; + + namespace_id = ioctl(fd, NVME_IOCTL_ID); + if (namespace_id < 0) { + fprintf(stderr, "error failed to fetch namespace-id\n"); + close(fd); + return -errno; + } + + /* + * Identify namespace to get namespace-id, namespace size in LBA's + * and LBA data size. + */ + err = nvme_identify(fd, namespace_id, NVME_IDENTIFY_CNS_NS, + NVME_CSI_NVM, &ns); + if (err) { + fprintf(stderr, "error failed to fetch identify namespace\n"); + close(fd); + return err; + } + + *nsid = namespace_id; + *lba_sz = 1 << ns.lbaf[(ns.flbas & 0x0f)].ds; + *nlba = ns.nsze; + + return 0; +} + static unsigned long cycles_to_nsec(unsigned long cycles) { uint64_t val; @@ -520,6 +572,65 @@ static void init_io(struct submitter *s, unsigned index) sqe->user_data |= ((uint64_t)s->clock_index << 32); } +static void init_io_pt(struct submitter *s, unsigned index) +{ + struct io_uring_sqe *sqe = &s->sqes[index << 1]; + unsigned long offset; + struct file *f; + struct nvme_uring_cmd *cmd; + unsigned long long slba; + unsigned long long nlb; + long r; + + if (s->nr_files == 1) { + f = &s->files[0]; + } else { + f = &s->files[s->cur_file]; + if (f->pending_ios >= file_depth(s)) { + s->cur_file++; + if (s->cur_file == s->nr_files) + s->cur_file = 0; + f = &s->files[s->cur_file]; + } + } + f->pending_ios++; + + if (random_io) { + r = __rand64(&s->rand_state); + offset = (r % (f->max_blocks - 1)) * bs; + } else { + offset = f->cur_off; + f->cur_off += bs; + if (f->cur_off + bs > f->max_size) + f->cur_off = 0; + } + + if (register_files) { + sqe->fd = f->fixed_fd; + sqe->flags = IOSQE_FIXED_FILE; + } else { + sqe->fd = f->real_fd; + sqe->flags = 0; + } + sqe->opcode = IORING_OP_URING_CMD; + sqe->user_data = (unsigned long) f->fileno; + if (stats) + sqe->user_data |= ((unsigned long)s->clock_index << 32); + sqe->cmd_op = NVME_URING_CMD_IO; + slba = offset >> f->lba_shift; + nlb = (bs >> f->lba_shift) - 1; + cmd = (struct nvme_uring_cmd *)&sqe->cmd; + /* cdw10 and cdw11 represent starting slba*/ + cmd->cdw10 = slba & 0xffffffff; + cmd->cdw11 = slba >> 32; + /* cdw12 represent number of lba to be read*/ + cmd->cdw12 = nlb; + cmd->addr = (unsigned long) s->iovecs[index].iov_base; + cmd->data_len = bs; + cmd->nsid = f->nsid; + cmd->opcode = 2; +} + static int prep_more_ios_uring(struct submitter *s, int max_ios) { struct io_sq_ring *ring = &s->sq_ring; @@ -532,7 +643,10 @@ static int prep_more_ios_uring(struct submitter *s, int max_ios) break; index = tail & sq_ring_mask; - init_io(s, index); + if (pt) + init_io_pt(s, index); + else + init_io(s, index); ring->array[index] = index; prepped++; tail = next_tail; @@ -549,7 +663,29 @@ static int get_file_size(struct file *f) if (fstat(f->real_fd, &st) < 0) return -1; - if (S_ISBLK(st.st_mode)) { + if (pt) { + __u64 nlba; + __u32 lbs; + int ret; + + if (!S_ISCHR(st.st_mode)) { + fprintf(stderr, "passthrough works with only nvme-ns " + "generic devices (/dev/ngXnY)\n"); + return -1; + } + ret = nvme_get_info(f->real_fd, &f->nsid, &lbs, &nlba); + if (ret) + return -1; + if ((bs % lbs) != 0) { + printf("error: bs:%d should be a multiple logical_block_size:%d\n", + bs, lbs); + return -1; + } + f->max_blocks = nlba / bs; + f->max_size = nlba; + f->lba_shift = ilog2(lbs); + return 0; + } else if (S_ISBLK(st.st_mode)) { unsigned long long bytes; if (ioctl(f->real_fd, BLKGETSIZE64, &bytes) != 0) @@ -620,6 +756,60 @@ static int reap_events_uring(struct submitter *s) return reaped; } +static int reap_events_uring_pt(struct submitter *s) +{ + struct io_cq_ring *ring = &s->cq_ring; + struct io_uring_cqe *cqe; + unsigned head, reaped = 0; + int last_idx = -1, stat_nr = 0; + unsigned index; + int fileno; + + head = *ring->head; + do { + struct file *f; + + read_barrier(); + if (head == atomic_load_acquire(ring->tail)) + break; + index = head & cq_ring_mask; + cqe = &ring->cqes[index << 1]; + fileno = cqe->user_data & 0xffffffff; + f = &s->files[fileno]; + f->pending_ios--; + + if (cqe->res != 0) { + printf("io: unexpected ret=%d\n", cqe->res); + if (polled && cqe->res == -EINVAL) + printf("passthrough doesn't support polled IO\n"); + return -1; + } + if (stats) { + int clock_index = cqe->user_data >> 32; + + if (last_idx != clock_index) { + if (last_idx != -1) { + add_stat(s, last_idx, stat_nr); + stat_nr = 0; + } + last_idx = clock_index; + } + stat_nr++; + } + reaped++; + head++; + } while (1); + + if (stat_nr) + add_stat(s, last_idx, stat_nr); + + if (reaped) { + s->inflight -= reaped; + atomic_store_release(ring->head, head); + } + return reaped; +} + static void set_affinity(struct submitter *s) { #ifdef CONFIG_LIBNUMA @@ -697,6 +887,7 @@ static int setup_ring(struct submitter *s) struct io_uring_params p; int ret, fd; void *ptr; + size_t len; memset(&p, 0, sizeof(p)); @@ -709,6 +900,10 @@ static int setup_ring(struct submitter *s) p.sq_thread_cpu = sq_thread_cpu; } } + if (pt) { + p.flags |= IORING_SETUP_SQE128; + p.flags |= IORING_SETUP_CQE32; + } fd = io_uring_setup(depth, &p); if (fd < 0) { @@ -761,11 +956,22 @@ static int setup_ring(struct submitter *s) sring->array = ptr + p.sq_off.array; sq_ring_mask = *sring->ring_mask; - s->sqes = mmap(0, p.sq_entries * sizeof(struct io_uring_sqe), + if (p.flags & IORING_SETUP_SQE128) + len = 2 * p.sq_entries * sizeof(struct io_uring_sqe); + else + len = p.sq_entries * sizeof(struct io_uring_sqe); + s->sqes = mmap(0, len, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_POPULATE, fd, IORING_OFF_SQES); - ptr = mmap(0, p.cq_off.cqes + p.cq_entries * sizeof(struct io_uring_cqe), + if (p.flags & IORING_SETUP_CQE32) { + len = p.cq_off.cqes + + 2 * p.cq_entries * sizeof(struct io_uring_cqe); + } else { + len = p.cq_off.cqes + + p.cq_entries * sizeof(struct io_uring_cqe); + } + ptr = mmap(0, len, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_POPULATE, fd, IORING_OFF_CQ_RING); cring->head = ptr + p.cq_off.head; @@ -855,7 +1061,16 @@ static int submitter_init(struct submitter *s) s->plat = NULL; nr_batch = 0; } + /* perform the expensive command initialization part for passthrough here + * rather than in the fast path + */ + if (pt) { + for (i = 0; i < roundup_pow2(depth); i++) { + struct io_uring_sqe *sqe = &s->sqes[i << 1]; + memset(&sqe->cmd, 0, sizeof(struct nvme_uring_cmd)); + } + } return nr_batch; } @@ -1111,7 +1326,10 @@ static void *submitter_uring_fn(void *data) do { int r; - r = reap_events_uring(s); + if (pt) + r = reap_events_uring_pt(s); + else + r = reap_events_uring(s); if (r == -1) { s->finish = 1; break; @@ -1305,11 +1523,12 @@ static void usage(char *argv, int status) " -a : Use legacy aio, default %d\n" " -S : Use sync IO (preadv2), default %d\n" " -X : Use registered ring %d\n" - " -P : Automatically place on device home node %d\n", + " -P : Automatically place on device home node %d\n" + " -u : Use nvme-passthrough I/O, default %d\n", argv, DEPTH, BATCH_SUBMIT, BATCH_COMPLETE, BS, polled, fixedbufs, dma_map, register_files, nthreads, !buffered, do_nop, stats, runtime == 0 ? "unlimited" : runtime_str, random_io, aio, - use_sync, register_ring, numa_placement); + use_sync, register_ring, numa_placement, pt); exit(status); } @@ -1368,7 +1587,7 @@ int main(int argc, char *argv[]) if (!do_nop && argc < 2) usage(argv[0], 1); - while ((opt = getopt(argc, argv, "d:s:c:b:p:B:F:n:N:O:t:T:a:r:D:R:X:S:P:h?")) != -1) { + while ((opt = getopt(argc, argv, "d:s:c:b:p:B:F:n:N:O:t:T:a:r:D:R:X:S:P:u:h?")) != -1) { switch (opt) { case 'a': aio = !!atoi(optarg); @@ -1449,6 +1668,9 @@ int main(int argc, char *argv[]) case 'P': numa_placement = !!atoi(optarg); break; + case 'u': + pt = !!atoi(optarg); + break; case 'h': case '?': default: From 9ce84fbd2c8eece4618c46312449210efaf8463c Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Fri, 26 Aug 2022 07:52:54 -0600 Subject: [PATCH 0246/1097] t/io_uring: fix 64-bit cast on 32-bit archs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit gcc complains that: t/io_uring.c: In function ‘init_io_pt’: t/io_uring.c:618:52: error: left shift count >= width of type [-Werror=shift-count-overflow] 618 | sqe->user_data |= ((unsigned long)s->clock_index << 32); | ^~ we're shifting more than the size of the type. Cast to a 64-bit value so that it'll work on 32-bit as well. Fixes: 7d04588a7663 ("t/io_uring: add support for async-passthru") Signed-off-by: Jens Axboe --- t/io_uring.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/t/io_uring.c b/t/io_uring.c index 0a90f85c55..b90bcf789b 100644 --- a/t/io_uring.c +++ b/t/io_uring.c @@ -615,7 +615,7 @@ static void init_io_pt(struct submitter *s, unsigned index) sqe->opcode = IORING_OP_URING_CMD; sqe->user_data = (unsigned long) f->fileno; if (stats) - sqe->user_data |= ((unsigned long)s->clock_index << 32); + sqe->user_data |= ((__u64) s->clock_index << 32ULL); sqe->cmd_op = NVME_URING_CMD_IO; slba = offset >> f->lba_shift; nlb = (bs >> f->lba_shift) - 1; From a2947c330d299e67bb532d622e2eccdcf27afc46 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Thu, 25 Aug 2022 12:08:33 -0700 Subject: [PATCH 0247/1097] test: add basic test for io_uring ioengine We should have a quick smoke test for the io_uring ioengine to automatically detect breakage. Signed-off-by: Vincent Fu --- t/jobs/t0018.fio | 9 +++++++++ t/run-fio-tests.py | 22 ++++++++++++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 t/jobs/t0018.fio diff --git a/t/jobs/t0018.fio b/t/jobs/t0018.fio new file mode 100644 index 0000000000..e2298b1f97 --- /dev/null +++ b/t/jobs/t0018.fio @@ -0,0 +1,9 @@ +# Expected result: job completes without error +# Buggy result: job fails + +[test] +ioengine=io_uring +filesize=256K +time_based +runtime=3s +rw=randrw diff --git a/t/run-fio-tests.py b/t/run-fio-tests.py index 504b7cdb38..1e5e9f249f 100755 --- a/t/run-fio-tests.py +++ b/t/run-fio-tests.py @@ -582,6 +582,7 @@ class Requirements(object): _linux = False _libaio = False + _io_uring = False _zbd = False _root = False _zoned_nullb = False @@ -605,6 +606,12 @@ def __init__(self, fio_root): Requirements._zbd = "CONFIG_HAS_BLKZONED" in contents Requirements._libaio = "CONFIG_LIBAIO" in contents + contents, success = FioJobTest.get_file("/proc/kallsyms") + if not success: + print("Unable to open '/proc/kallsyms' to probe for io_uring support") + else: + Requirements._io_uring = "io_uring_setup" in contents + Requirements._root = (os.geteuid() == 0) if Requirements._zbd and Requirements._root: try: @@ -627,6 +634,7 @@ def __init__(self, fio_root): req_list = [Requirements.linux, Requirements.libaio, + Requirements.io_uring, Requirements.zbd, Requirements.root, Requirements.zoned_nullb, @@ -648,6 +656,11 @@ def libaio(cls): """Is libaio available?""" return Requirements._libaio, "libaio required" + @classmethod + def io_uring(cls): + """Is io_uring available?""" + return Requirements._io_uring, "io_uring required" + @classmethod def zbd(cls): """Is ZBD support available?""" @@ -867,6 +880,15 @@ def cpucount4(cls): 'output_format': 'json', 'requirements': [Requirements.not_windows], }, + { + 'test_id': 18, + 'test_class': FioJobTest, + 'job': 't0018.fio', + 'success': SUCCESS_DEFAULT, + 'pre_job': None, + 'pre_success': None, + 'requirements': [Requirements.linux, Requirements.io_uring], + }, { 'test_id': 1000, 'test_class': FioExeTest, From a5a2429ece9b2a7e35e2b8a0248e7b1de6d075c3 Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Fri, 26 Aug 2022 14:14:44 -0600 Subject: [PATCH 0248/1097] t/io_uring: remove duplicate definition of gettid() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit With a recent change, we now include os.h through nvme.h, and this can cause a duplicate gettid() definition: t/io_uring.c:499:12: error: redefinition of ‘gettid’ static int gettid(void) ^~~~~~ In file included from t/../engines/../os/os.h:39, from t/../engines/../thread_options.h:5, from t/../engines/../fio.h:18, from t/../engines/nvme.h:10, from t/io_uring.c:38: t/../engines/../os/os-linux.h:147:19: note: previous definition of ‘gettid’ was here static inline int gettid(void) ^~~~~~ Include os.h directly to make it clear that we use it, and remove the gettid() definition from io_uring.c. Reported-by: Yi Zhang Signed-off-by: Jens Axboe --- t/io_uring.c | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/t/io_uring.c b/t/io_uring.c index b90bcf789b..e8e41796e3 100644 --- a/t/io_uring.c +++ b/t/io_uring.c @@ -30,6 +30,7 @@ #include #include "../arch/arch.h" +#include "../os/os.h" #include "../lib/types.h" #include "../lib/roundup.h" #include "../lib/rand.h" @@ -495,13 +496,6 @@ static int io_uring_enter(struct submitter *s, unsigned int to_submit, #endif } -#ifndef CONFIG_HAVE_GETTID -static int gettid(void) -{ - return syscall(__NR_gettid); -} -#endif - static unsigned file_depth(struct submitter *s) { return (depth + s->nr_files - 1) / s->nr_files; From ef54f290e8d585a267bd3588ad92d1aedcb4246e Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Mon, 29 Aug 2022 11:30:30 -0400 Subject: [PATCH 0249/1097] test: add some tests for seq and rand offsets t/jobs/t0019.fio is a seq read test t/jobs/t0020.fio is a rand read test We don't have any automated tests which make sure that sequential access patterns are actually sequential and that random access patterns are not sequential. Add these two tests to help detect the possibility that these features could break. Signed-off-by: Vincent Fu --- t/jobs/t0019.fio | 10 ++++++ t/jobs/t0020.fio | 11 ++++++ t/run-fio-tests.py | 84 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 105 insertions(+) create mode 100644 t/jobs/t0019.fio create mode 100644 t/jobs/t0020.fio diff --git a/t/jobs/t0019.fio b/t/jobs/t0019.fio new file mode 100644 index 0000000000..b60d27d2ea --- /dev/null +++ b/t/jobs/t0019.fio @@ -0,0 +1,10 @@ +# Expected result: offsets are accessed sequentially and all offsets are read +# Buggy result: offsets are not accessed sequentially and one or more offsets are missed +# run with --debug=io or logging to see which offsets are accessed + +[test] +ioengine=null +filesize=1M +write_bw_log=test +per_job_logs=0 +log_offset=1 diff --git a/t/jobs/t0020.fio b/t/jobs/t0020.fio new file mode 100644 index 0000000000..1c1c5166fc --- /dev/null +++ b/t/jobs/t0020.fio @@ -0,0 +1,11 @@ +# Expected result: offsets are not accessed sequentially and all offsets are touched +# Buggy result: offsets are accessed sequentially and one or more offsets are missed +# run with --debug=io or logging to see which offsets are read + +[test] +ioengine=null +filesize=1M +rw=randread +write_bw_log=test +per_job_logs=0 +log_offset=1 diff --git a/t/run-fio-tests.py b/t/run-fio-tests.py index 1e5e9f249f..78f435211c 100755 --- a/t/run-fio-tests.py +++ b/t/run-fio-tests.py @@ -548,6 +548,72 @@ def check_result(self): self.passed = False +class FioJobTest_t0019(FioJobTest): + """Test consists of fio test job t0019 + Confirm that all offsets were touched sequentially""" + + def check_result(self): + super(FioJobTest_t0019, self).check_result() + + bw_log_filename = os.path.join(self.test_dir, "test_bw.log") + file_data, success = self.get_file(bw_log_filename) + log_lines = file_data.split('\n') + + prev = -4096 + for line in log_lines: + if len(line.strip()) == 0: + continue + cur = int(line.split(',')[4]) + if cur - prev != 4096: + self.passed = False + self.failure_reason = "offsets {0}, {1} not sequential".format(prev, cur) + return + prev = cur + + if cur/4096 != 255: + self.passed = False + self.failure_reason = "unexpected last offset {0}".format(cur) + + +class FioJobTest_t0020(FioJobTest): + """Test consists of fio test job t0020 + Confirm that almost all offsets were touched non-sequentially""" + + def check_result(self): + super(FioJobTest_t0020, self).check_result() + + bw_log_filename = os.path.join(self.test_dir, "test_bw.log") + file_data, success = self.get_file(bw_log_filename) + log_lines = file_data.split('\n') + + seq_count = 0 + offsets = set() + + prev = int(log_lines[0].split(',')[4]) + for line in log_lines[1:]: + offsets.add(prev/4096) + if len(line.strip()) == 0: + continue + cur = int(line.split(',')[4]) + if cur - prev == 4096: + seq_count += 1 + prev = cur + + # 10 is an arbitrary threshold + if seq_count > 10: + self.passed = False + self.failure_reason = "too many ({0}) consecutive offsets".format(seq_count) + + if len(offsets) != 256: + self.passed = False + self.failure_reason += " number of offsets is {0} instead of 256".format(len(offsets)) + + for i in range(256): + if not i in offsets: + self.passed = False + self.failure_reason += " missing offset {0}".format(i*4096) + + class FioJobTest_iops_rate(FioJobTest): """Test consists of fio test job t0009 Confirm that job0 iops == 1000 @@ -889,6 +955,24 @@ def cpucount4(cls): 'pre_success': None, 'requirements': [Requirements.linux, Requirements.io_uring], }, + { + 'test_id': 19, + 'test_class': FioJobTest_t0019, + 'job': 't0019.fio', + 'success': SUCCESS_DEFAULT, + 'pre_job': None, + 'pre_success': None, + 'requirements': [], + }, + { + 'test_id': 20, + 'test_class': FioJobTest_t0020, + 'job': 't0020.fio', + 'success': SUCCESS_DEFAULT, + 'pre_job': None, + 'pre_success': None, + 'requirements': [], + }, { 'test_id': 1000, 'test_class': FioExeTest, From 726a95856b8f8415d222efbd1821d9f47c264e78 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Mon, 29 Aug 2022 14:24:16 -0400 Subject: [PATCH 0250/1097] test: use Ubuntu 22.04 for 64-bit tests On 22.04 there was a conflict among libunwind-14-dev, libunwind-dev, and libunwind8 that was resolved by removing libunwind-14-dev. The 32-bit Ubuntu setup steps require more attention to get them to work on 22.04. Stay on 20.04 for now and figure it out later. Starting pkgProblemResolver with broken count: 1 Starting 2 pkgProblemResolver with broken count: 1 Investigating (0) libunwind-14-dev:amd64 < 1:14.0.0-1ubuntu1 @ii K Ib > Broken libunwind-14-dev:amd64 Breaks on libunwind-dev:amd64 < none -> 1.3.2-2build2 @un puN > Considering libunwind-dev:amd64 -1 as a solution to libunwind-14-dev:amd64 2 Done Some packages could not be installed. This may mean that you have requested an impossible situation or if you are using the unstable distribution that some required packages have not yet been created or been moved out of Incoming. The following information may help to resolve the situation: The following packages have unmet dependencies: libunwind-14-dev : Breaks: libunwind-dev but 1.3.2-2build2 is to be installed E: Error, pkgProblemResolver::Resolve generated breaks, this may be caused by held packages. Signed-off-by: Vincent Fu --- .github/workflows/ci.yml | 6 +++--- ci/actions-install.sh | 2 ++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 650366b23d..85104e5aad 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,10 +18,10 @@ jobs: - android include: - build: linux-gcc - os: ubuntu-20.04 + os: ubuntu-22.04 cc: gcc - build: linux-clang - os: ubuntu-20.04 + os: ubuntu-22.04 cc: clang - build: macos os: macos-11 @@ -29,7 +29,7 @@ jobs: os: ubuntu-20.04 arch: i686 - build: android - os: ubuntu-20.04 + os: ubuntu-22.04 arch: aarch64-linux-android32 env: diff --git a/ci/actions-install.sh b/ci/actions-install.sh index b5c4198f93..7017de2a6a 100755 --- a/ci/actions-install.sh +++ b/ci/actions-install.sh @@ -54,6 +54,8 @@ DPKGCFG libtcmalloc-minimal4 nvidia-cuda-dev ) + echo "Removing libunwind-14-dev because of conflicts with libunwind-dev" + sudo apt remove -y libunwind-14-dev ;; esac From b68ba328173f5a4714d888f6ce80fd24a4e4c504 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Mon, 29 Aug 2022 15:15:56 -0400 Subject: [PATCH 0251/1097] test: get 32-bit Ubuntu 22.04 build working Ubuntu 22.04 no longer has i386 builds for the packages libibverbs and librdmacm. So stop trying to install those packages for the 32-bit build. Signed-off-by: Vincent Fu --- .github/workflows/ci.yml | 2 +- ci/actions-install.sh | 11 ++++------- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 85104e5aad..bdc4db85d9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,7 +26,7 @@ jobs: - build: macos os: macos-11 - build: linux-i686-gcc - os: ubuntu-20.04 + os: ubuntu-22.04 arch: i686 - build: android os: ubuntu-22.04 diff --git a/ci/actions-install.sh b/ci/actions-install.sh index 7017de2a6a..c209a08960 100755 --- a/ci/actions-install.sh +++ b/ci/actions-install.sh @@ -23,26 +23,21 @@ DPKGCFG libcunit1-dev libcurl4-openssl-dev libfl-dev - libibverbs-dev libnuma-dev - librdmacm-dev libnfs-dev valgrind ) case "${CI_TARGET_ARCH}" in "i686") sudo dpkg --add-architecture i386 - opts="--allow-downgrades" pkgs=("${pkgs[@]/%/:i386}") pkgs+=( gcc-multilib pkg-config:i386 zlib1g-dev:i386 - libpcre2-8-0=10.34-7 ) ;; "x86_64") - opts="" pkgs+=( libglusterfs-dev libgoogle-perftools-dev @@ -53,6 +48,8 @@ DPKGCFG librbd-dev libtcmalloc-minimal4 nvidia-cuda-dev + libibverbs-dev + librdmacm-dev ) echo "Removing libunwind-14-dev because of conflicts with libunwind-dev" sudo apt remove -y libunwind-14-dev @@ -68,8 +65,8 @@ DPKGCFG echo "Updating APT..." sudo apt-get -qq update - echo "Installing packages..." - sudo apt-get install "$opts" -o APT::Immediate-Configure=false --no-install-recommends -qq -y "${pkgs[@]}" + echo "Installing packages... ${pkgs[@]}" + sudo apt-get install -o APT::Immediate-Configure=false --no-install-recommends -qq -y "${pkgs[@]}" } install_linux() { From eb40b275dc70ad8bc2003f8f466651dcc2aa0b09 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Tue, 30 Aug 2022 09:59:55 -0400 Subject: [PATCH 0252/1097] test: add tests for lfsr and norandommap t0021 checks whether the lfsr random generator actually touches every offset. t0022 checks whether fio touches offsets more than once when norandommap=1. We should have automated tests for basic functionality to detect problems early. Signed-off-by: Vincent Fu --- t/jobs/t0021.fio | 15 +++++++++++++ t/jobs/t0022.fio | 13 +++++++++++ t/run-fio-tests.py | 55 +++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 t/jobs/t0021.fio create mode 100644 t/jobs/t0022.fio diff --git a/t/jobs/t0021.fio b/t/jobs/t0021.fio new file mode 100644 index 0000000000..47fbae71eb --- /dev/null +++ b/t/jobs/t0021.fio @@ -0,0 +1,15 @@ +# make sure the lfsr random generator actually does touch all the offsets +# +# Expected result: offsets are not accessed sequentially and all offsets are touched +# Buggy result: offsets are accessed sequentially and one or more offsets are missed +# run with --debug=io or logging to see which offsets are read + +[test] +ioengine=null +filesize=1M +rw=randread +write_bw_log=test +per_job_logs=0 +log_offset=1 +norandommap=1 +random_generator=lfsr diff --git a/t/jobs/t0022.fio b/t/jobs/t0022.fio new file mode 100644 index 0000000000..2324571e9a --- /dev/null +++ b/t/jobs/t0022.fio @@ -0,0 +1,13 @@ +# make sure that when we enable norandommap we touch some offsets more than once +# +# Expected result: at least one offset is touched more than once +# Buggy result: each offset is touched only once + +[test] +ioengine=null +filesize=1M +rw=randread +write_bw_log=test +per_job_logs=0 +log_offset=1 +norandommap=1 diff --git a/t/run-fio-tests.py b/t/run-fio-tests.py index 78f435211c..4782376136 100755 --- a/t/run-fio-tests.py +++ b/t/run-fio-tests.py @@ -576,7 +576,7 @@ def check_result(self): class FioJobTest_t0020(FioJobTest): - """Test consists of fio test job t0020 + """Test consists of fio test jobs t0020 and t0021 Confirm that almost all offsets were touched non-sequentially""" def check_result(self): @@ -614,6 +614,41 @@ def check_result(self): self.failure_reason += " missing offset {0}".format(i*4096) +class FioJobTest_t0022(FioJobTest): + """Test consists of fio test job t0022""" + + def check_result(self): + super(FioJobTest_t0022, self).check_result() + + bw_log_filename = os.path.join(self.test_dir, "test_bw.log") + file_data, success = self.get_file(bw_log_filename) + log_lines = file_data.split('\n') + + filesize = 1024*1024 + bs = 4096 + seq_count = 0 + offsets = set() + + prev = int(log_lines[0].split(',')[4]) + for line in log_lines[1:]: + offsets.add(prev/bs) + if len(line.strip()) == 0: + continue + cur = int(line.split(',')[4]) + if cur - prev == bs: + seq_count += 1 + prev = cur + + # 10 is an arbitrary threshold + if seq_count > 10: + self.passed = False + self.failure_reason = "too many ({0}) consecutive offsets".format(seq_count) + + if len(offsets) == filesize/bs: + self.passed = False + self.failure_reason += " no duplicate offsets found with norandommap=1".format(len(offsets)) + + class FioJobTest_iops_rate(FioJobTest): """Test consists of fio test job t0009 Confirm that job0 iops == 1000 @@ -973,6 +1008,24 @@ def cpucount4(cls): 'pre_success': None, 'requirements': [], }, + { + 'test_id': 21, + 'test_class': FioJobTest_t0020, + 'job': 't0021.fio', + 'success': SUCCESS_DEFAULT, + 'pre_job': None, + 'pre_success': None, + 'requirements': [], + }, + { + 'test_id': 22, + 'test_class': FioJobTest_t0022, + 'job': 't0022.fio', + 'success': SUCCESS_DEFAULT, + 'pre_job': None, + 'pre_success': None, + 'requirements': [], + }, { 'test_id': 1000, 'test_class': FioExeTest, From 6ce17ec60a51c55a8da2002d14f960a194553fe7 Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Tue, 30 Aug 2022 10:48:18 -0600 Subject: [PATCH 0253/1097] backend: revert bad memory leak fix This essentially reverts the commit mentioned in the fixes line, as it causes crashes with using a trigger timeout + command. Fixes: 807473c36e10 ("fixed memory leak detected by ASAN") Signed-off-by: Jens Axboe --- backend.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/backend.c b/backend.c index 375a23e420..fe614f6e1c 100644 --- a/backend.c +++ b/backend.c @@ -2451,10 +2451,8 @@ static void run_threads(struct sk_out *sk_out) strerror(ret)); } else { pid_t pid; - struct fio_file **files; void *eo; dprint(FD_PROCESS, "will fork\n"); - files = td->files; eo = td->eo; read_barrier(); pid = fork(); @@ -2465,9 +2463,6 @@ static void run_threads(struct sk_out *sk_out) _exit(ret); } else if (i == fio_debug_jobno) *fio_debug_jobp = pid; - // freeing previously allocated memory for files - // this memory freed MUST NOT be shared between processes, only the pointer itself may be shared within TD - free(files); free(eo); free(fd); fd = NULL; From db7fc8d864dc4fb607a0379333a0db60431bd649 Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Tue, 30 Aug 2022 10:51:13 -0600 Subject: [PATCH 0254/1097] Fio 3.32 Signed-off-by: Jens Axboe --- FIO-VERSION-GEN | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FIO-VERSION-GEN b/FIO-VERSION-GEN index 72630dd0a2..db0738182a 100755 --- a/FIO-VERSION-GEN +++ b/FIO-VERSION-GEN @@ -1,7 +1,7 @@ #!/bin/sh GVF=FIO-VERSION-FILE -DEF_VER=fio-3.31 +DEF_VER=fio-3.32 LF=' ' From 6e0cefdcad82d030eab1cff08fc9a34dc0375223 Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Wed, 24 Aug 2022 14:54:39 -0700 Subject: [PATCH 0255/1097] Remove two casts from os-linux.h This patch prepares for merging the os-linux.h and os-android.h header files. While CPU_CLR() and CPU_SET() are defined as statement expressions in the Linux header files, these macros have been defined as statements in the Android header files. Remove the '(void)' casts to prevent the introduction of a build error when merging the Linux and Android header files. Signed-off-by: Bart Van Assche --- os/os-linux.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/os/os-linux.h b/os/os-linux.h index 3001140ca4..4a05a77517 100644 --- a/os/os-linux.h +++ b/os/os-linux.h @@ -81,8 +81,8 @@ typedef cpu_set_t os_cpu_mask_t; pthread_getaffinity_np(pthread_self(), sizeof(mask), &(mask)) #endif -#define fio_cpu_clear(mask, cpu) (void) CPU_CLR((cpu), (mask)) -#define fio_cpu_set(mask, cpu) (void) CPU_SET((cpu), (mask)) +#define fio_cpu_clear(mask, cpu) CPU_CLR((cpu), (mask)) +#define fio_cpu_set(mask, cpu) CPU_SET((cpu), (mask)) #define fio_cpu_isset(mask, cpu) (CPU_ISSET((cpu), (mask)) != 0) #define fio_cpu_count(mask) CPU_COUNT((mask)) From e69d8385f127899e2bee6d816e0187072813ed8c Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Wed, 24 Aug 2022 15:05:32 -0700 Subject: [PATCH 0256/1097] Linux: Use the byte order functions from Prepare for the unification of the Linux and Android header files by switching to the functions for Linux. Signed-off-by: Bart Van Assche --- os/os-linux.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/os/os-linux.h b/os/os-linux.h index 4a05a77517..84d1e33167 100644 --- a/os/os-linux.h +++ b/os/os-linux.h @@ -17,6 +17,7 @@ #include #include #include +#include #ifdef ARCH_HAVE_CRC_CRYPTO #include @@ -50,6 +51,7 @@ #define FIO_HAVE_TRIM #define FIO_HAVE_GETTID #define FIO_USE_GENERIC_INIT_RANDOM_STATE +#define FIO_HAVE_BYTEORDER_FUNCS #define FIO_HAVE_PWRITEV2 #define FIO_HAVE_SHM_ATTACH_REMOVED From 646d19a4fbc5185ef614bc45d59edf5819ba22a7 Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Wed, 24 Aug 2022 14:55:51 -0700 Subject: [PATCH 0257/1097] Split os-android.h Move the shared memory emulation into a new header file in preparation of merging the Linux and Android operating system header files. Signed-off-by: Bart Van Assche --- os/os-android.h | 85 +------------------------------------------------ os/os-ashmem.h | 84 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+), 84 deletions(-) create mode 100644 os/os-ashmem.h diff --git a/os/os-android.h b/os/os-android.h index 34534239fd..146f5138cd 100644 --- a/os/os-android.h +++ b/os/os-android.h @@ -90,90 +90,7 @@ static inline int fio_cpuset_exit(os_cpu_mask_t *mask) #define FIO_MAX_CPUS CPU_SETSIZE -#ifndef CONFIG_NO_SHM -/* - * Bionic doesn't support SysV shared memory, so implement it using ashmem - */ -#include -#include -#include -#include -#if __ANDROID_API__ >= __ANDROID_API_O__ -#include -#else -#define ASHMEM_DEVICE "/dev/ashmem" -#endif -#define shmid_ds shmid64_ds -#define SHM_HUGETLB 04000 - -static inline int shmctl(int __shmid, int __cmd, struct shmid_ds *__buf) -{ - int ret=0; - if (__cmd == IPC_RMID) - { - int length = ioctl(__shmid, ASHMEM_GET_SIZE, NULL); - struct ashmem_pin pin = {0 , length}; - ret = ioctl(__shmid, ASHMEM_UNPIN, &pin); - close(__shmid); - } - return ret; -} - -#if __ANDROID_API__ >= __ANDROID_API_O__ -static inline int shmget(key_t __key, size_t __size, int __shmflg) -{ - char keybuf[11]; - - sprintf(keybuf, "%d", __key); - - return ASharedMemory_create(keybuf, __size + sizeof(uint64_t)); -} -#else -static inline int shmget(key_t __key, size_t __size, int __shmflg) -{ - int fd,ret; - char keybuf[11]; - - fd = open(ASHMEM_DEVICE, O_RDWR); - if (fd < 0) - return fd; - - sprintf(keybuf,"%d",__key); - ret = ioctl(fd, ASHMEM_SET_NAME, keybuf); - if (ret < 0) - goto error; - - /* Stores size in first 8 bytes, allocate extra space */ - ret = ioctl(fd, ASHMEM_SET_SIZE, __size + sizeof(uint64_t)); - if (ret < 0) - goto error; - - return fd; - -error: - close(fd); - return ret; -} -#endif - -static inline void *shmat(int __shmid, const void *__shmaddr, int __shmflg) -{ - size_t size = ioctl(__shmid, ASHMEM_GET_SIZE, NULL); - /* Needs to be 8-byte aligned to prevent SIGBUS on 32-bit ARM */ - uint64_t *ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, __shmid, 0); - /* Save size at beginning of buffer, for use with munmap */ - *ptr = size; - return ptr + 1; -} - -static inline int shmdt (const void *__shmaddr) -{ - /* Find mmap size which we stored at the beginning of the buffer */ - uint64_t *ptr = (uint64_t *)__shmaddr - 1; - size_t size = *ptr; - return munmap(ptr, size); -} -#endif +#include "os-ashmem.h" #define SPLICE_DEF_SIZE (64*1024) diff --git a/os/os-ashmem.h b/os/os-ashmem.h new file mode 100644 index 0000000000..c34ff656cc --- /dev/null +++ b/os/os-ashmem.h @@ -0,0 +1,84 @@ +#ifndef CONFIG_NO_SHM +/* + * Bionic doesn't support SysV shared memory, so implement it using ashmem + */ +#include +#include +#include +#include +#if __ANDROID_API__ >= __ANDROID_API_O__ +#include +#else +#define ASHMEM_DEVICE "/dev/ashmem" +#endif +#define shmid_ds shmid64_ds +#define SHM_HUGETLB 04000 + +static inline int shmctl(int __shmid, int __cmd, struct shmid_ds *__buf) +{ + int ret=0; + if (__cmd == IPC_RMID) + { + int length = ioctl(__shmid, ASHMEM_GET_SIZE, NULL); + struct ashmem_pin pin = {0 , length}; + ret = ioctl(__shmid, ASHMEM_UNPIN, &pin); + close(__shmid); + } + return ret; +} + +#if __ANDROID_API__ >= __ANDROID_API_O__ +static inline int shmget(key_t __key, size_t __size, int __shmflg) +{ + char keybuf[11]; + + sprintf(keybuf, "%d", __key); + + return ASharedMemory_create(keybuf, __size + sizeof(uint64_t)); +} +#else +static inline int shmget(key_t __key, size_t __size, int __shmflg) +{ + int fd,ret; + char keybuf[11]; + + fd = open(ASHMEM_DEVICE, O_RDWR); + if (fd < 0) + return fd; + + sprintf(keybuf,"%d",__key); + ret = ioctl(fd, ASHMEM_SET_NAME, keybuf); + if (ret < 0) + goto error; + + /* Stores size in first 8 bytes, allocate extra space */ + ret = ioctl(fd, ASHMEM_SET_SIZE, __size + sizeof(uint64_t)); + if (ret < 0) + goto error; + + return fd; + +error: + close(fd); + return ret; +} +#endif + +static inline void *shmat(int __shmid, const void *__shmaddr, int __shmflg) +{ + size_t size = ioctl(__shmid, ASHMEM_GET_SIZE, NULL); + /* Needs to be 8-byte aligned to prevent SIGBUS on 32-bit ARM */ + uint64_t *ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, __shmid, 0); + /* Save size at beginning of buffer, for use with munmap */ + *ptr = size; + return ptr + 1; +} + +static inline int shmdt (const void *__shmaddr) +{ + /* Find mmap size which we stored at the beginning of the buffer */ + uint64_t *ptr = (uint64_t *)__shmaddr - 1; + size_t size = *ptr; + return munmap(ptr, size); +} +#endif From 910f378affca272e65cb2a72a82f27b1506fc0ec Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Wed, 24 Aug 2022 15:05:42 -0700 Subject: [PATCH 0258/1097] Merge os-android.h into os-linux.h Reduce code duplication by merging the os-android.h and os-linux.h header files. The only functional change in this patch is that FIO_HAVE_SGIO is now defined in the Android build and hence that the sg I/O engine is enabled for Android. Signed-off-by: Bart Van Assche --- os/os-android.h | 259 ------------------------------------------------ os/os-linux.h | 8 ++ os/os.h | 4 +- 3 files changed, 9 insertions(+), 262 deletions(-) delete mode 100644 os/os-android.h diff --git a/os/os-android.h b/os/os-android.h deleted file mode 100644 index 146f5138cd..0000000000 --- a/os/os-android.h +++ /dev/null @@ -1,259 +0,0 @@ -#ifndef FIO_OS_ANDROID_H -#define FIO_OS_ANDROID_H - -#define FIO_OS os_android - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "./os-linux-syscall.h" -#include "../file.h" - -#ifndef __has_builtin // Optional of course. - #define __has_builtin(x) 0 // Compatibility with non-clang compilers. -#endif - -#define FIO_HAVE_CPU_AFFINITY -#define FIO_HAVE_DISK_UTIL -#define FIO_HAVE_IOSCHED_SWITCH -#define FIO_HAVE_IOPRIO -#define FIO_HAVE_IOPRIO_CLASS -#define FIO_HAVE_ODIRECT -#define FIO_HAVE_HUGETLB -#define FIO_HAVE_BLKTRACE -#define FIO_HAVE_CL_SIZE -#define FIO_HAVE_CGROUPS -#define FIO_HAVE_FS_STAT -#define FIO_HAVE_TRIM -#define FIO_HAVE_GETTID -#define FIO_USE_GENERIC_INIT_RANDOM_STATE -#define FIO_HAVE_E4_ENG -#define FIO_HAVE_BYTEORDER_FUNCS -#define FIO_HAVE_MMAP_HUGE -#define FIO_NO_HAVE_SHM_H - -#define OS_MAP_ANON MAP_ANONYMOUS - -typedef cpu_set_t os_cpu_mask_t; - -#define fio_setaffinity(pid, cpumask) \ - sched_setaffinity((pid), sizeof(cpumask), &(cpumask)) -#define fio_getaffinity(pid, ptr) \ - sched_getaffinity((pid), sizeof(cpu_set_t), (ptr)) - -#ifndef POSIX_MADV_DONTNEED -#define posix_madvise madvise -#define POSIX_MADV_DONTNEED MADV_DONTNEED -#define POSIX_MADV_SEQUENTIAL MADV_SEQUENTIAL -#define POSIX_MADV_RANDOM MADV_RANDOM -#endif - -#ifdef MADV_REMOVE -#define FIO_MADV_FREE MADV_REMOVE -#endif -#ifndef MAP_HUGETLB -#define MAP_HUGETLB 0x40000 /* arch specific */ -#endif - -#ifdef CONFIG_PTHREAD_GETAFFINITY -#define FIO_HAVE_GET_THREAD_AFFINITY -#define fio_get_thread_affinity(mask) \ - pthread_getaffinity_np(pthread_self(), sizeof(mask), &(mask)) -#endif - -#define fio_cpu_clear(mask, cpu) CPU_CLR((cpu), (mask)) -#define fio_cpu_set(mask, cpu) CPU_SET((cpu), (mask)) -#define fio_cpu_isset(mask, cpu) (CPU_ISSET((cpu), (mask)) != 0) -#define fio_cpu_count(mask) CPU_COUNT((mask)) - -static inline int fio_cpuset_init(os_cpu_mask_t *mask) -{ - CPU_ZERO(mask); - return 0; -} - -static inline int fio_cpuset_exit(os_cpu_mask_t *mask) -{ - return 0; -} - -#define FIO_MAX_CPUS CPU_SETSIZE - -#include "os-ashmem.h" - -#define SPLICE_DEF_SIZE (64*1024) - -enum { - IOPRIO_CLASS_NONE, - IOPRIO_CLASS_RT, - IOPRIO_CLASS_BE, - IOPRIO_CLASS_IDLE, -}; - -enum { - IOPRIO_WHO_PROCESS = 1, - IOPRIO_WHO_PGRP, - IOPRIO_WHO_USER, -}; - -#define IOPRIO_BITS 16 -#define IOPRIO_CLASS_SHIFT 13 - -#define IOPRIO_MIN_PRIO 0 /* highest priority */ -#define IOPRIO_MAX_PRIO 7 /* lowest priority */ - -#define IOPRIO_MIN_PRIO_CLASS 0 -#define IOPRIO_MAX_PRIO_CLASS 3 - -static inline int ioprio_value(int ioprio_class, int ioprio) -{ - /* - * If no class is set, assume BE - */ - if (!ioprio_class) - ioprio_class = IOPRIO_CLASS_BE; - - return (ioprio_class << IOPRIO_CLASS_SHIFT) | ioprio; -} - -static inline bool ioprio_value_is_class_rt(unsigned int priority) -{ - return (priority >> IOPRIO_CLASS_SHIFT) == IOPRIO_CLASS_RT; -} - -static inline int ioprio_set(int which, int who, int ioprio_class, int ioprio) -{ - return syscall(__NR_ioprio_set, which, who, - ioprio_value(ioprio_class, ioprio)); -} - -#ifndef BLKGETSIZE64 -#define BLKGETSIZE64 _IOR(0x12,114,size_t) -#endif - -#ifndef BLKFLSBUF -#define BLKFLSBUF _IO(0x12,97) -#endif - -#ifndef BLKDISCARD -#define BLKDISCARD _IO(0x12,119) -#endif - -static inline int blockdev_invalidate_cache(struct fio_file *f) -{ - return ioctl(f->fd, BLKFLSBUF); -} - -static inline int blockdev_size(struct fio_file *f, unsigned long long *bytes) -{ - if (!ioctl(f->fd, BLKGETSIZE64, bytes)) - return 0; - - return errno; -} - -static inline unsigned long long os_phys_mem(void) -{ - long pagesize, pages; - - pagesize = sysconf(_SC_PAGESIZE); - pages = sysconf(_SC_PHYS_PAGES); - if (pages == -1 || pagesize == -1) - return 0; - - return (unsigned long long) pages * (unsigned long long) pagesize; -} - -#ifdef O_NOATIME -#define FIO_O_NOATIME O_NOATIME -#else -#define FIO_O_NOATIME 0 -#endif - -/* Check for GCC or Clang byte swap intrinsics */ -#if (__has_builtin(__builtin_bswap16) && __has_builtin(__builtin_bswap32) \ - && __has_builtin(__builtin_bswap64)) || (__GNUC__ > 4 \ - || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8)) /* fio_swapN */ -#define fio_swap16(x) __builtin_bswap16(x) -#define fio_swap32(x) __builtin_bswap32(x) -#define fio_swap64(x) __builtin_bswap64(x) -#else -#include -#define fio_swap16(x) bswap_16(x) -#define fio_swap32(x) bswap_32(x) -#define fio_swap64(x) bswap_64(x) -#endif /* fio_swapN */ - -#define CACHE_LINE_FILE \ - "/sys/devices/system/cpu/cpu0/cache/index0/coherency_line_size" - -static inline int arch_cache_line_size(void) -{ - char size[32]; - int fd, ret; - - fd = open(CACHE_LINE_FILE, O_RDONLY); - if (fd < 0) - return -1; - - ret = read(fd, size, sizeof(size)); - - close(fd); - - if (ret <= 0) - return -1; - else - return atoi(size); -} - -static inline unsigned long long get_fs_free_size(const char *path) -{ - unsigned long long ret; - struct statfs s; - - if (statfs(path, &s) < 0) - return -1ULL; - - ret = s.f_bsize; - ret *= (unsigned long long) s.f_bfree; - return ret; -} - -static inline int os_trim(struct fio_file *f, unsigned long long start, - unsigned long long len) -{ - uint64_t range[2]; - - range[0] = start; - range[1] = len; - - if (!ioctl(f->fd, BLKDISCARD, range)) - return 0; - - return errno; -} - -#ifdef CONFIG_SCHED_IDLE -static inline int fio_set_sched_idle(void) -{ - struct sched_param p = { .sched_priority = 0, }; - return sched_setscheduler(gettid(), SCHED_IDLE, &p); -} -#endif - -#ifndef RWF_UNCACHED -#define RWF_UNCACHED 0x00000040 -#endif - -#endif diff --git a/os/os-linux.h b/os/os-linux.h index 84d1e33167..831f0ad047 100644 --- a/os/os-linux.h +++ b/os/os-linux.h @@ -1,7 +1,11 @@ #ifndef FIO_OS_LINUX_H #define FIO_OS_LINUX_H +#ifdef __ANDROID__ +#define FIO_OS os_android +#else #define FIO_OS os_linux +#endif #include #include @@ -18,6 +22,10 @@ #include #include #include +#ifdef __ANDROID__ +#include "os-ashmem.h" +#define FIO_NO_HAVE_SHM_H +#endif #ifdef ARCH_HAVE_CRC_CRYPTO #include diff --git a/os/os.h b/os/os.h index 810e616685..aba6813f23 100644 --- a/os/os.h +++ b/os/os.h @@ -33,9 +33,7 @@ typedef enum { } cpu_features; /* IWYU pragma: begin_exports */ -#if defined(__ANDROID__) -#include "os-android.h" -#elif defined(__linux__) +#if defined(__linux__) #include "os-linux.h" #elif defined(__FreeBSD__) #include "os-freebsd.h" From 4d22c1037dafdac3d9bf30a3fb39d88f4391e41a Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Wed, 31 Aug 2022 13:45:32 -0600 Subject: [PATCH 0259/1097] engines/io_uring: set COOP_TASKRUN for ring setup If available, this will generally improve performance quite a bit. Fio can easily use it, we just need to set the flag. IORING_SETUP_COOP_TASKRUN tells the kernel that the application doesn't need to get rescheduled to handle task_work, it's fine to defer this to when we transition anyway. For a QD=8 random read workload, this increases performance from 680K to 870K IOPS. Signed-off-by: Jens Axboe --- engines/io_uring.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/engines/io_uring.c b/engines/io_uring.c index 94376efa7f..a51468f5b0 100644 --- a/engines/io_uring.c +++ b/engines/io_uring.c @@ -809,9 +809,19 @@ static int fio_ioring_queue_init(struct thread_data *td) p.flags |= IORING_SETUP_CQSIZE; p.cq_entries = depth; + /* + * Setup COOP_TASKRUN as we don't need to get IPI interrupted for + * completing IO operations. + */ + p.flags |= IORING_SETUP_COOP_TASKRUN; + retry: ret = syscall(__NR_io_uring_setup, depth, &p); if (ret < 0) { + if (errno == EINVAL && p.flags & IORING_SETUP_COOP_TASKRUN) { + p.flags &= ~IORING_SETUP_COOP_TASKRUN; + goto retry; + } if (errno == EINVAL && p.flags & IORING_SETUP_CQSIZE) { p.flags &= ~IORING_SETUP_CQSIZE; goto retry; From e453f369ecf4db4913ef6916603037f2d7141035 Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Wed, 31 Aug 2022 14:14:29 -0600 Subject: [PATCH 0260/1097] engines/io_uring: set single issuer and defer taskrun If available, set these two flags as well. SINGLE_ISSUER tells the kernel that it can expect that it's just a single task issuing requests, and DEFER_TASKRUN tells the kernel that we're fine with deferring task_work runs until we reap events. Signed-off-by: Jens Axboe --- engines/io_uring.c | 11 +++++++++++ os/linux/io_uring.h | 12 ++++++++++++ 2 files changed, 23 insertions(+) diff --git a/engines/io_uring.c b/engines/io_uring.c index a51468f5b0..d0fc61dcac 100644 --- a/engines/io_uring.c +++ b/engines/io_uring.c @@ -815,9 +815,20 @@ static int fio_ioring_queue_init(struct thread_data *td) */ p.flags |= IORING_SETUP_COOP_TASKRUN; + /* + * io_uring is always a single issuer, and we can defer task_work + * runs until we reap events. + */ + p.flags |= IORING_SETUP_SINGLE_ISSUER | IORING_SETUP_DEFER_TASKRUN; + retry: ret = syscall(__NR_io_uring_setup, depth, &p); if (ret < 0) { + if (errno == EINVAL && p.flags & IORING_SETUP_DEFER_TASKRUN) { + p.flags &= ~IORING_SETUP_DEFER_TASKRUN; + p.flags &= ~IORING_SETUP_SINGLE_ISSUER; + goto retry; + } if (errno == EINVAL && p.flags & IORING_SETUP_COOP_TASKRUN) { p.flags &= ~IORING_SETUP_COOP_TASKRUN; goto retry; diff --git a/os/linux/io_uring.h b/os/linux/io_uring.h index 929997f827..6604e73607 100644 --- a/os/linux/io_uring.h +++ b/os/linux/io_uring.h @@ -131,6 +131,18 @@ enum { #define IORING_SETUP_SQE128 (1U << 10) /* SQEs are 128 byte */ #define IORING_SETUP_CQE32 (1U << 11) /* CQEs are 32 byte */ +/* + * Only one task is allowed to submit requests + */ +#define IORING_SETUP_SINGLE_ISSUER (1U << 12) + +/* + * Defer running task work to get events. + * Rather than running bits of task work whenever the task transitions + * try to do it just before it is needed. + */ +#define IORING_SETUP_DEFER_TASKRUN (1U << 13) + enum { IORING_OP_NOP, IORING_OP_READV, From 501565a1eebdcbca5943152e924133279f56cfbe Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Wed, 31 Aug 2022 18:40:14 -0600 Subject: [PATCH 0261/1097] t/io_uring: unify getting of the offset Move it into a helper, and use it from all three methods. This also fixes an issue with the aio method not honoring random IO or not. Signed-off-by: Jens Axboe --- t/io_uring.c | 50 +++++++++++++++++++++----------------------------- 1 file changed, 21 insertions(+), 29 deletions(-) diff --git a/t/io_uring.c b/t/io_uring.c index e8e41796e3..1746e59a11 100644 --- a/t/io_uring.c +++ b/t/io_uring.c @@ -501,12 +501,28 @@ static unsigned file_depth(struct submitter *s) return (depth + s->nr_files - 1) / s->nr_files; } +static unsigned long long get_offset(struct submitter *s, struct file *f) +{ + unsigned long long offset; + long r; + + if (random_io) { + r = __rand64(&s->rand_state); + offset = (r % (f->max_blocks - 1)) * bs; + } else { + offset = f->cur_off; + f->cur_off += bs; + if (f->cur_off + bs > f->max_size) + f->cur_off = 0; + } + + return offset; +} + static void init_io(struct submitter *s, unsigned index) { struct io_uring_sqe *sqe = &s->sqes[index]; - unsigned long offset; struct file *f; - long r; if (do_nop) { sqe->opcode = IORING_OP_NOP; @@ -526,16 +542,6 @@ static void init_io(struct submitter *s, unsigned index) } f->pending_ios++; - if (random_io) { - r = __rand64(&s->rand_state); - offset = (r % (f->max_blocks - 1)) * bs; - } else { - offset = f->cur_off; - f->cur_off += bs; - if (f->cur_off + bs > f->max_size) - f->cur_off = 0; - } - if (register_files) { sqe->flags = IOSQE_FIXED_FILE; sqe->fd = f->fixed_fd; @@ -560,7 +566,7 @@ static void init_io(struct submitter *s, unsigned index) sqe->buf_index = 0; } sqe->ioprio = 0; - sqe->off = offset; + sqe->off = get_offset(s, f); sqe->user_data = (unsigned long) f->fileno; if (stats && stats_running) sqe->user_data |= ((uint64_t)s->clock_index << 32); @@ -1072,10 +1078,8 @@ static int submitter_init(struct submitter *s) static int prep_more_ios_aio(struct submitter *s, int max_ios, struct iocb *iocbs) { uint64_t data; - long long offset; struct file *f; unsigned index; - long r; index = 0; while (index < max_ios) { @@ -1094,10 +1098,8 @@ static int prep_more_ios_aio(struct submitter *s, int max_ios, struct iocb *iocb } f->pending_ios++; - r = lrand48(); - offset = (r % (f->max_blocks - 1)) * bs; io_prep_pread(iocb, f->real_fd, s->iovecs[index].iov_base, - s->iovecs[index].iov_len, offset); + s->iovecs[index].iov_len, get_offset(s, f)); data = f->fileno; if (stats && stats_running) @@ -1380,7 +1382,6 @@ static void *submitter_sync_fn(void *data) do { uint64_t offset; struct file *f; - long r; if (s->nr_files == 1) { f = &s->files[0]; @@ -1395,16 +1396,6 @@ static void *submitter_sync_fn(void *data) } f->pending_ios++; - if (random_io) { - r = __rand64(&s->rand_state); - offset = (r % (f->max_blocks - 1)) * bs; - } else { - offset = f->cur_off; - f->cur_off += bs; - if (f->cur_off + bs > f->max_size) - f->cur_off = 0; - } - #ifdef ARCH_HAVE_CPU_CLOCK if (stats) s->clock_batch[s->clock_index] = get_cpu_clock(); @@ -1413,6 +1404,7 @@ static void *submitter_sync_fn(void *data) s->inflight++; s->calls++; + offset = get_offset(s, f); if (polled) ret = preadv2(f->real_fd, &s->iovecs[0], 1, offset, RWF_HIPRI); else From 2be18f6b266f3fcba89719b354672090f49d53d9 Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Wed, 31 Aug 2022 18:44:52 -0600 Subject: [PATCH 0262/1097] t/io_uring: take advantage of new io_uring setup flags Set COOP_TASKRUN, SINGLE_ISSUER, and DEFER_TASKRUN if they are available on the host. Signed-off-by: Jens Axboe --- t/io_uring.c | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/t/io_uring.c b/t/io_uring.c index 1746e59a11..5b46015aa3 100644 --- a/t/io_uring.c +++ b/t/io_uring.c @@ -449,6 +449,8 @@ static int io_uring_register_files(struct submitter *s) static int io_uring_setup(unsigned entries, struct io_uring_params *p) { + int ret; + /* * Clamp CQ ring size at our SQ ring size, we don't need more entries * than that. @@ -456,7 +458,28 @@ static int io_uring_setup(unsigned entries, struct io_uring_params *p) p->flags |= IORING_SETUP_CQSIZE; p->cq_entries = entries; - return syscall(__NR_io_uring_setup, entries, p); + p->flags |= IORING_SETUP_COOP_TASKRUN; + p->flags |= IORING_SETUP_SINGLE_ISSUER; + p->flags |= IORING_SETUP_DEFER_TASKRUN; +retry: + ret = syscall(__NR_io_uring_setup, entries, p); + if (!ret) + return 0; + + if (errno == EINVAL && p->flags & IORING_SETUP_COOP_TASKRUN) { + p->flags &= ~IORING_SETUP_COOP_TASKRUN; + goto retry; + } + if (errno == EINVAL && p->flags & IORING_SETUP_SINGLE_ISSUER) { + p->flags &= ~IORING_SETUP_SINGLE_ISSUER; + goto retry; + } + if (errno == EINVAL && p->flags & IORING_SETUP_DEFER_TASKRUN) { + p->flags &= ~IORING_SETUP_DEFER_TASKRUN; + goto retry; + } + + return ret; } static void io_uring_probe(int fd) From 6b6f52b9b74219d5d7c93e61d21250bb47ecfe66 Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Thu, 1 Sep 2022 08:34:32 -0600 Subject: [PATCH 0263/1097] t/io_uring: minor optimizations to IO init fast path 1) Only read SQ ring head at the start of prep, we don't need to read it at every iteration. 2) Initialize SQ ring index at init time, rather than in the fast path. Signed-off-by: Jens Axboe --- t/io_uring.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/t/io_uring.c b/t/io_uring.c index 5b46015aa3..9d580b5af1 100644 --- a/t/io_uring.c +++ b/t/io_uring.c @@ -658,11 +658,12 @@ static int prep_more_ios_uring(struct submitter *s, int max_ios) { struct io_sq_ring *ring = &s->sq_ring; unsigned index, tail, next_tail, prepped = 0; + unsigned int head = atomic_load_acquire(ring->head); next_tail = tail = *ring->tail; do { next_tail++; - if (next_tail == atomic_load_acquire(ring->head)) + if (next_tail == head) break; index = tail & sq_ring_mask; @@ -670,7 +671,6 @@ static int prep_more_ios_uring(struct submitter *s, int max_ios) init_io_pt(s, index); else init_io(s, index); - ring->array[index] = index; prepped++; tail = next_tail; } while (prepped < max_ios); @@ -908,7 +908,7 @@ static int setup_ring(struct submitter *s) struct io_sq_ring *sring = &s->sq_ring; struct io_cq_ring *cring = &s->cq_ring; struct io_uring_params p; - int ret, fd; + int ret, fd, i; void *ptr; size_t len; @@ -1003,6 +1003,10 @@ static int setup_ring(struct submitter *s) cring->ring_entries = ptr + p.cq_off.ring_entries; cring->cqes = ptr + p.cq_off.cqes; cq_ring_mask = *cring->ring_mask; + + for (i = 0; i < p.sq_entries; i++) + sring->array[i] = i; + return 0; } From 03900b0bf8af625bb43b10f0627b3c5947c3ff79 Mon Sep 17 00:00:00 2001 From: aggieNick02 Date: Thu, 1 Sep 2022 12:26:45 -0500 Subject: [PATCH 0264/1097] Fix fio silently dropping log entries when using log_compression This fixes issue 1457: https://github.com/axboe/fio/issues/1457 The fix is to just add a workqueue_flush to iolog_flush so that any log chunks being compressed in the workqueue get flushed before we compress and write the entries in the current active logs. Without this, log entries in the workqueue can be omitted or be written out of order. We also add a new test that ensures the log file has the right number of entries in the correct order when using log_compression, both with and without the store_log_compressed option. There is still some cleanup to do with respect to the current documentation and comments for the log_compression option, as it does not affect the size of chunks, but instead is effectively a boolean that controls whether chunks are compressed and removed. I'll leave that for a separate PR. Signed-off-by: Nick Neumann --- iolog.c | 6 +-- t/log_compression.py | 121 +++++++++++++++++++++++++++++++++++++++++++ t/run-fio-tests.py | 8 +++ 3 files changed, 132 insertions(+), 3 deletions(-) create mode 100755 t/log_compression.py diff --git a/iolog.c b/iolog.c index 37e799a13c..41d3e47350 100644 --- a/iolog.c +++ b/iolog.c @@ -1574,14 +1574,14 @@ void iolog_compress_exit(struct thread_data *td) * Queue work item to compress the existing log entries. We reset the * current log to a small size, and reference the existing log in the * data that we queue for compression. Once compression has been done, - * this old log is freed. If called with finish == true, will not return - * until the log compression has completed, and will flush all previous - * logs too + * this old log is freed. Will not return until the log compression + * has completed, and will flush all previous logs too */ static int iolog_flush(struct io_log *log) { struct iolog_flush_data *data; + workqueue_flush(&log->td->log_compress_wq); data = malloc(sizeof(*data)); if (!data) return 1; diff --git a/t/log_compression.py b/t/log_compression.py new file mode 100755 index 0000000000..94c92db797 --- /dev/null +++ b/t/log_compression.py @@ -0,0 +1,121 @@ +#!/usr/bin/env python3 +# +# log_compression.py +# +# Test log_compression and log_store_compressed. Uses null ioengine. +# Previous bugs have caused output in per I/O log files to be missing +# and/or out of order +# +# Expected result: 8000 log entries, offset starting at 0 and increasing by bs +# Buggy result: Log entries out of order (usually without log_store_compressed) +# and/or missing log entries (usually with log_store_compressed) +# +# USAGE +# python log_compression.py [-f fio-executable] +# +# EXAMPLES +# python t/log_compression.py +# python t/log_compression.py -f ./fio +# +# REQUIREMENTS +# Python 3.5+ +# +# ===TEST MATRIX=== +# +# With log_compression=10K +# With log_store_compressed=1 and log_compression=10K + +import os +import sys +import platform +import argparse +import subprocess + + +def parse_args(): + """Parse command-line arguments.""" + parser = argparse.ArgumentParser() + parser.add_argument('-f', '--fio', + help='path to fio executable (e.g., ./fio)') + return parser.parse_args() + + +def run_fio(fio,log_store_compressed): + fio_args = [ + '--name=job', + '--ioengine=null', + '--filesize=1000M', + '--bs=128K', + '--rw=write', + '--iodepth=1', + '--write_bw_log=test', + '--per_job_logs=0', + '--log_offset=1', + '--log_compression=10K', + ] + if log_store_compressed: + fio_args.append('--log_store_compressed=1') + + subprocess.check_output([fio] + fio_args) + + if log_store_compressed: + fio_inflate_args = [ + '--inflate-log=test_bw.log.fz' + ] + with open('test_bw.from_fz.log','wt') as f: + subprocess.check_call([fio]+fio_inflate_args,stdout=f) + +def check_log_file(log_store_compressed): + filename = 'test_bw.from_fz.log' if log_store_compressed else 'test_bw.log' + with open(filename,'rt') as f: + file_data = f.read() + log_lines = [x for x in file_data.split('\n') if len(x.strip())!=0] + log_ios = len(log_lines) + + filesize = 1000*1024*1024 + bs = 128*1024 + ios = filesize//bs + if log_ios!=ios: + print('wrong number of ios ({}) in log; should be {}'.format(log_ios,ios)) + return False + + expected_offset = 0 + for line_number,line in enumerate(log_lines): + log_offset = int(line.split(',')[4]) + if log_offset != expected_offset: + print('wrong offset ({}) for io number {} in log; should be {}'.format( + log_offset, line_number, expected_offset)) + return False + expected_offset += bs + return True + +def main(): + """Entry point for this script.""" + args = parse_args() + if args.fio: + fio_path = args.fio + else: + fio_path = os.path.join(os.path.dirname(__file__), '../fio') + if not os.path.exists(fio_path): + fio_path = 'fio' + print("fio path is", fio_path) + + passed_count = 0 + failed_count = 0 + for log_store_compressed in [False, True]: + run_fio(fio_path, log_store_compressed) + passed = check_log_file(log_store_compressed) + print('Test with log_store_compressed={} {}'.format(log_store_compressed, + 'PASSED' if passed else 'FAILED')) + if passed: + passed_count+=1 + else: + failed_count+=1 + + print('{} tests passed, {} failed'.format(passed_count, failed_count)) + + sys.exit(failed_count) + +if __name__ == '__main__': + main() + diff --git a/t/run-fio-tests.py b/t/run-fio-tests.py index 4782376136..e72fa2a01c 100755 --- a/t/run-fio-tests.py +++ b/t/run-fio-tests.py @@ -1124,6 +1124,14 @@ def cpucount4(cls): 'success': SUCCESS_DEFAULT, 'requirements': [], }, + { + 'test_id': 1012, + 'test_class': FioExeTest, + 'exe': 't/log_compression.py', + 'parameters': ['-f', '{fio_path}'], + 'success': SUCCESS_DEFAULT, + 'requirements': [], + }, ] From a7c386edc19f3ef121b037b60b02852ffbcd6d65 Mon Sep 17 00:00:00 2001 From: aggieNick02 Date: Fri, 2 Sep 2022 18:19:43 -0500 Subject: [PATCH 0265/1097] Fix log compression storage on windows Set the file open mode to be binary instead of text when dealing with compressed log files. This fixes log compression storage not working on windows, and lets the test added in PR https://github.com/axboe/fio/pull/1458 pass on windows. Signed-off-by: Nick Neumann --- iolog.c | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/iolog.c b/iolog.c index 41d3e47350..aa9c3bb1e4 100644 --- a/iolog.c +++ b/iolog.c @@ -1218,7 +1218,7 @@ int iolog_file_inflate(const char *file) void *buf; FILE *f; - f = fopen(file, "r"); + f = fopen(file, "rb"); if (!f) { perror("fopen"); return 1; @@ -1300,10 +1300,21 @@ void flush_log(struct io_log *log, bool do_append) void *buf; FILE *f; + /* + * If log_gz_store is true, we are writing a binary file. + * Set the mode appropriately (on all platforms) to avoid issues + * on windows (line-ending conversions, etc.) + */ if (!do_append) - f = fopen(log->filename, "w"); + if (log->log_gz_store) + f = fopen(log->filename, "wb"); + else + f = fopen(log->filename, "w"); else - f = fopen(log->filename, "a"); + if (log->log_gz_store) + f = fopen(log->filename, "ab"); + else + f = fopen(log->filename, "a"); if (!f) { perror("fopen log"); return; From 52c9872ff1ba0129046d2a8b0718861cd1af97c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Sat, 3 Sep 2022 08:49:16 +0300 Subject: [PATCH 0266/1097] init: include 5 in --terse-version help MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Thanks-to: Koichi Murase Signed-off-by: Ville Skyttä --- init.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/init.c b/init.c index da8007760a..f6a8056a2d 100644 --- a/init.c +++ b/init.c @@ -2269,7 +2269,7 @@ static void usage(const char *name) printf(" --minimal\t\tMinimal (terse) output\n"); printf(" --output-format=type\tOutput format (terse,json,json+,normal)\n"); printf(" --terse-version=type\tSet terse version output format" - " (default 3, or 2 or 4)\n"); + " (default 3, or 2 or 4 or 5)\n"); printf(" --version\t\tPrint version info and exit\n"); printf(" --help\t\tPrint this page\n"); printf(" --cpuclock-test\tPerform test/validation of CPU clock\n"); From ffe1d11f8401a7fe63819fd1b2f24972b3d3a19b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Sat, 3 Sep 2022 08:54:03 +0300 Subject: [PATCH 0267/1097] HOWTO: spelling fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Ville Skyttä --- HOWTO.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/HOWTO.rst b/HOWTO.rst index 08be687c90..4a4d6b529a 100644 --- a/HOWTO.rst +++ b/HOWTO.rst @@ -2550,7 +2550,7 @@ with the caveat that when used on the command line, they must come after the [dfs] - Specificy a different chunk size (in bytes) for the dfs file. + Specify a different chunk size (in bytes) for the dfs file. Use DAOS container's chunk size by default. [libhdfs] @@ -2559,7 +2559,7 @@ with the caveat that when used on the command line, they must come after the .. option:: object_class=str : [dfs] - Specificy a different object class for the dfs file. + Specify a different object class for the dfs file. Use DAOS container's object class by default. .. option:: skip_bad=bool : [mtd] From 57fd9225ba2f842bf0b466a266329b44cac86222 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Sat, 3 Sep 2022 09:12:55 +0300 Subject: [PATCH 0268/1097] doc: fix --showcmd usage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It does not take an argument, but operates on the job files and other arguments given on the command line. Thanks-to: Koichi Murase Signed-off-by: Ville Skyttä --- HOWTO.rst | 4 ++-- fio.1 | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/HOWTO.rst b/HOWTO.rst index 08be687c90..a7176dae03 100644 --- a/HOWTO.rst +++ b/HOWTO.rst @@ -167,9 +167,9 @@ Command line options defined by `ioengine`. If no `ioengine` is given, list all available ioengines. -.. option:: --showcmd=jobfile +.. option:: --showcmd - Convert `jobfile` to a set of command-line options. + Convert given job files to a set of command-line options. .. option:: --readonly diff --git a/fio.1 b/fio.1 index 27454b0b89..67d7c710fa 100644 --- a/fio.1 +++ b/fio.1 @@ -67,8 +67,8 @@ List all commands defined by \fIioengine\fR, or print help for \fIcommand\fR defined by \fIioengine\fR. If no \fIioengine\fR is given, list all available ioengines. .TP -.BI \-\-showcmd \fR=\fPjobfile -Convert \fIjobfile\fR to a set of command\-line options. +.BI \-\-showcmd +Convert given \fIjobfile\fRs to a set of command\-line options. .TP .BI \-\-readonly Turn on safety read\-only checks, preventing writes and trims. The \fB\-\-readonly\fR From cc791a99030064d5deebf72fef08306bc6adab69 Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Sat, 3 Sep 2022 11:01:19 -0600 Subject: [PATCH 0269/1097] t/io_uring: properly detect numa nodes for passthrough mode We need to use a different path for the char nodes. With this, my setup correctly detects the nodes for all devices: submitter=0, tid=4746, file=/dev/ng0n1, node=6 submitter=1, tid=4747, file=/dev/ng1n1, node=6 submitter=2, tid=4748, file=/dev/ng2n1, node=6 submitter=3, tid=4749, file=/dev/ng3n1, node=6 submitter=4, tid=4750, file=/dev/ng4n1, node=4 submitter=5, tid=4751, file=/dev/ng5n1, node=4 submitter=6, tid=4752, file=/dev/ng6n1, node=4 submitter=7, tid=4753, file=/dev/ng7n1, node=4 submitter=8, tid=4754, file=/dev/ng8n1, node=4 submitter=9, tid=4755, file=/dev/ng9n1, node=4 submitter=10, tid=4756, file=/dev/ng10n1, node=4 submitter=12, tid=4758, file=/dev/ng12n1, node=10 submitter=11, tid=4757, file=/dev/ng11n1, node=4 submitter=13, tid=4759, file=/dev/ng13n1, node=10 [...] Signed-off-by: Jens Axboe --- t/io_uring.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/t/io_uring.c b/t/io_uring.c index 9d580b5af1..3bce9e7e98 100644 --- a/t/io_uring.c +++ b/t/io_uring.c @@ -856,7 +856,10 @@ static int detect_node(struct submitter *s, const char *name) char str[128]; int ret, fd, node; - sprintf(str, "/sys/block/%s/device/numa_node", base); + if (pt) + sprintf(str, "/sys/class/nvme-generic/%s/device/numa_node", base); + else + sprintf(str, "/sys/block/%s/device/numa_node", base); fd = open(str, O_RDONLY); if (fd < 0) return -1; From 021ce718f5ae4bfd5f4e42290993578adb7c7bd5 Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Sat, 3 Sep 2022 11:02:10 -0600 Subject: [PATCH 0270/1097] t/io_uring: enable support for registered buffers for passthrough Signed-off-by: Jens Axboe --- os/linux/io_uring.h | 8 ++++++++ t/io_uring.c | 4 ++++ 2 files changed, 12 insertions(+) diff --git a/os/linux/io_uring.h b/os/linux/io_uring.h index 6604e73607..c7a24ad88d 100644 --- a/os/linux/io_uring.h +++ b/os/linux/io_uring.h @@ -46,6 +46,7 @@ struct io_uring_sqe { __u32 rename_flags; __u32 unlink_flags; __u32 hardlink_flags; + __u32 uring_cmd_flags; }; __u64 user_data; /* data to be passed back at completion time */ /* pack this to avoid bogus arm OABI complaints */ @@ -197,6 +198,13 @@ enum { IORING_OP_LAST, }; +/* + * sqe->uring_cmd_flags + * IORING_URING_CMD_FIXED use registered buffer; pass thig flag + * along with setting sqe->buf_index. + */ +#define IORING_URING_CMD_FIXED (1U << 0) + /* * sqe->fsync_flags */ diff --git a/t/io_uring.c b/t/io_uring.c index 3bce9e7e98..b9353ac867 100644 --- a/t/io_uring.c +++ b/t/io_uring.c @@ -650,6 +650,10 @@ static void init_io_pt(struct submitter *s, unsigned index) cmd->cdw12 = nlb; cmd->addr = (unsigned long) s->iovecs[index].iov_base; cmd->data_len = bs; + if (fixedbufs) { + sqe->uring_cmd_flags = IORING_URING_CMD_FIXED; + sqe->buf_index = index; + } cmd->nsid = f->nsid; cmd->opcode = 2; } From d3061c18e84c91a417f8832b1a7cc09b1d26d1ee Mon Sep 17 00:00:00 2001 From: Lukasz Dorau Date: Mon, 28 Feb 2022 15:25:53 +0100 Subject: [PATCH 0271/1097] rpma: simplify server_cmpl_process() Simplify the code of the server_cmpl_process() function. Signed-off-by: Lukasz Dorau --- engines/librpma_gpspm.c | 40 ++++++++++++++++++---------------------- 1 file changed, 18 insertions(+), 22 deletions(-) diff --git a/engines/librpma_gpspm.c b/engines/librpma_gpspm.c index f00717a731..64962dda4c 100644 --- a/engines/librpma_gpspm.c +++ b/engines/librpma_gpspm.c @@ -685,29 +685,25 @@ static int server_cmpl_process(struct thread_data *td) ret = rpma_cq_get_wc(csd->cq, 1, wc, NULL); if (ret == RPMA_E_NO_COMPLETION) { - if (o->busy_wait_polling == 0) { - ret = rpma_cq_wait(csd->cq); - if (ret == RPMA_E_NO_COMPLETION) { - /* lack of completion is not an error */ - return 0; - } else if (ret != 0) { - librpma_td_verror(td, ret, "rpma_cq_wait"); - goto err_terminate; - } - - ret = rpma_cq_get_wc(csd->cq, 1, wc, NULL); - if (ret == RPMA_E_NO_COMPLETION) { - /* lack of completion is not an error */ - return 0; - } else if (ret != 0) { - librpma_td_verror(td, ret, "rpma_cq_get_wc"); - goto err_terminate; - } - } else { - /* lack of completion is not an error */ - return 0; + if (o->busy_wait_polling) + return 0; /* lack of completion is not an error */ + + ret = rpma_cq_wait(csd->cq); + if (ret == RPMA_E_NO_COMPLETION) + return 0; /* lack of completion is not an error */ + if (ret) { + librpma_td_verror(td, ret, "rpma_cq_wait"); + goto err_terminate; + } + + ret = rpma_cq_get_wc(csd->cq, 1, wc, NULL); + if (ret == RPMA_E_NO_COMPLETION) + return 0; /* lack of completion is not an error */ + if (ret) { + librpma_td_verror(td, ret, "rpma_cq_get_wc"); + goto err_terminate; } - } else if (ret != 0) { + } else if (ret) { librpma_td_verror(td, ret, "rpma_cq_get_wc"); goto err_terminate; } From 8fabefc1ceb2c9374c256b5e819a695bc94aedff Mon Sep 17 00:00:00 2001 From: Kacper Stefanski Date: Mon, 14 Feb 2022 00:02:23 +0100 Subject: [PATCH 0272/1097] rpma: add support for libpmem2 to librpma engine in APM mode Add support for libpmem2 to librpma fio engine in the APM mode. Signed-off-by: Kacper Stefanski --- Makefile | 12 ++++- configure | 29 +++++++++++- engines/librpma_fio.c | 52 ++++++--------------- engines/librpma_fio.h | 7 ++- engines/librpma_fio_pmem.h | 67 +++++++++++++++++++++++++++ engines/librpma_fio_pmem2.h | 91 +++++++++++++++++++++++++++++++++++++ 6 files changed, 215 insertions(+), 43 deletions(-) create mode 100644 engines/librpma_fio_pmem.h create mode 100644 engines/librpma_fio_pmem2.h diff --git a/Makefile b/Makefile index 634d2c9313..f947f11c71 100644 --- a/Makefile +++ b/Makefile @@ -111,13 +111,21 @@ endif ifdef CONFIG_LIBRPMA_APM librpma_apm_SRCS = engines/librpma_apm.c librpma_fio_SRCS = engines/librpma_fio.c - librpma_apm_LIBS = -lrpma -lpmem + ifdef CONFIG_LIBPMEM2_INSTALLED + librpma_apm_LIBS = -lrpma -lpmem2 + else + librpma_apm_LIBS = -lrpma -lpmem + endif ENGINES += librpma_apm endif ifdef CONFIG_LIBRPMA_GPSPM librpma_gpspm_SRCS = engines/librpma_gpspm.c engines/librpma_gpspm_flush.pb-c.c librpma_fio_SRCS = engines/librpma_fio.c - librpma_gpspm_LIBS = -lrpma -lpmem -lprotobuf-c + ifdef CONFIG_LIBPMEM2_INSTALLED + librpma_gpspm_LIBS = -lrpma -lpmem2 -lprotobuf-c + else + librpma_gpspm_LIBS = -lrpma -lpmem -lprotobuf-c + endif ENGINES += librpma_gpspm endif ifdef librpma_fio_SRCS diff --git a/configure b/configure index a2b9bd4cc8..7741ef4fd8 100755 --- a/configure +++ b/configure @@ -2201,6 +2201,26 @@ EOF fi print_config "libpmem1_5" "$libpmem1_5" +########################################## +# Check whether we have libpmem2 +if test "$libpmem2" != "yes" ; then + libpmem2="no" +fi +cat > $TMPC << EOF +#include +int main(int argc, char **argv) +{ + struct pmem2_config *cfg; + pmem2_config_new(&cfg); + pmem2_config_delete(&cfg); + return 0; +} +EOF +if compile_prog "" "-lpmem2" "libpmem2"; then + libpmem2="yes" +fi +print_config "libpmem2" "$libpmem2" + ########################################## # Check whether we have libpmemblk # libpmem is a prerequisite @@ -2990,11 +3010,13 @@ if test "$libverbs" = "yes" -a "$rdmacm" = "yes" ; then fi # librpma is supported on the 'x86_64' architecture for now if test "$cpu" = "x86_64" -a "$libverbs" = "yes" -a "$rdmacm" = "yes" \ - -a "$librpma" = "yes" -a "$libpmem" = "yes" ; then + -a "$librpma" = "yes" \ + && test "$libpmem" = "yes" -o "$libpmem2" = "yes" ; then output_sym "CONFIG_LIBRPMA_APM" fi if test "$cpu" = "x86_64" -a "$libverbs" = "yes" -a "$rdmacm" = "yes" \ - -a "$librpma" = "yes" -a "$libpmem" = "yes" -a "$libprotobuf_c" = "yes" ; then + -a "$librpma" = "yes" -a "$libprotobuf_c" = "yes" \ + && test "$libpmem" = "yes" -o "$libpmem2" = "yes" ; then output_sym "CONFIG_LIBRPMA_GPSPM" fi if test "$clock_gettime" = "yes" ; then @@ -3138,6 +3160,9 @@ fi if test "$pmem" = "yes" ; then output_sym "CONFIG_LIBPMEM" fi +if test "$libpmem2" = "yes" ; then + output_sym "CONFIG_LIBPMEM2_INSTALLED" +fi if test "$libime" = "yes" ; then output_sym "CONFIG_IME" fi diff --git a/engines/librpma_fio.c b/engines/librpma_fio.c index a78a1e5767..42d6163ea1 100644 --- a/engines/librpma_fio.c +++ b/engines/librpma_fio.c @@ -1,7 +1,7 @@ /* * librpma_fio: librpma_apm and librpma_gpspm engines' common part. * - * Copyright 2021, Intel Corporation + * Copyright 2021-2022, Intel Corporation * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License, @@ -13,9 +13,11 @@ * GNU General Public License for more details. */ -#include "librpma_fio.h" - -#include +#ifdef CONFIG_LIBPMEM2_INSTALLED +#include "librpma_fio_pmem2.h" +#else +#include "librpma_fio_pmem.h" +#endif /* CONFIG_LIBPMEM2_INSTALLED */ struct fio_option librpma_fio_options[] = { { @@ -111,10 +113,8 @@ char *librpma_fio_allocate_dram(struct thread_data *td, size_t size, char *librpma_fio_allocate_pmem(struct thread_data *td, struct fio_file *f, size_t size, struct librpma_fio_mem *mem) { - size_t size_mmap = 0; - char *mem_ptr = NULL; - int is_pmem = 0; size_t ws_offset; + mem->mem_ptr = NULL; if (size % page_size) { log_err("fio: size (%zu) is not aligned to page size (%zu)\n", @@ -135,48 +135,24 @@ char *librpma_fio_allocate_pmem(struct thread_data *td, struct fio_file *f, return NULL; } - /* map the file */ - mem_ptr = pmem_map_file(f->file_name, 0 /* len */, 0 /* flags */, - 0 /* mode */, &size_mmap, &is_pmem); - if (mem_ptr == NULL) { - log_err("fio: pmem_map_file(%s) failed\n", f->file_name); - /* pmem_map_file() sets errno on failure */ - td_verror(td, errno, "pmem_map_file"); - return NULL; - } - - /* pmem is expected */ - if (!is_pmem) { - log_err("fio: %s is not located in persistent memory\n", + if (librpma_fio_pmem_map_file(f, size, mem, ws_offset)) { + log_err("fio: librpma_fio_pmem_map_file(%s) failed\n", f->file_name); - goto err_unmap; - } - - /* check size of allocated persistent memory */ - if (size_mmap < ws_offset + size) { - log_err( - "fio: %s is too small to handle so many threads (%zu < %zu)\n", - f->file_name, size_mmap, ws_offset + size); - goto err_unmap; + return NULL; } log_info("fio: size of memory mapped from the file %s: %zu\n", - f->file_name, size_mmap); - - mem->mem_ptr = mem_ptr; - mem->size_mmap = size_mmap; + f->file_name, mem->size_mmap); - return mem_ptr + ws_offset; + log_info("fio: library used to map PMem from file: %s\n", RPMA_PMEM_USED); -err_unmap: - (void) pmem_unmap(mem_ptr, size_mmap); - return NULL; + return mem->mem_ptr ? mem->mem_ptr + ws_offset : NULL; } void librpma_fio_free(struct librpma_fio_mem *mem) { if (mem->size_mmap) - (void) pmem_unmap(mem->mem_ptr, mem->size_mmap); + librpma_fio_unmap(mem); else free(mem->mem_ptr); } diff --git a/engines/librpma_fio.h b/engines/librpma_fio.h index 912902357d..480ded1bde 100644 --- a/engines/librpma_fio.h +++ b/engines/librpma_fio.h @@ -1,7 +1,7 @@ /* * librpma_fio: librpma_apm and librpma_gpspm engines' common header. * - * Copyright 2021, Intel Corporation + * Copyright 2021-2022, Intel Corporation * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License, @@ -72,6 +72,11 @@ struct librpma_fio_mem { /* size of the mapped persistent memory */ size_t size_mmap; + +#ifdef CONFIG_LIBPMEM2_INSTALLED + /* libpmem2 structure used for mapping PMem */ + struct pmem2_map *map; +#endif }; char *librpma_fio_allocate_dram(struct thread_data *td, size_t size, diff --git a/engines/librpma_fio_pmem.h b/engines/librpma_fio_pmem.h new file mode 100644 index 0000000000..4854292c4a --- /dev/null +++ b/engines/librpma_fio_pmem.h @@ -0,0 +1,67 @@ +/* + * librpma_fio_pmem: allocates pmem using libpmem. + * + * Copyright 2022, Intel Corporation + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License, + * version 2 as published by the Free Software Foundation.. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include "librpma_fio.h" + +#define RPMA_PMEM_USED "libpmem" + +static int librpma_fio_pmem_map_file(struct fio_file *f, size_t size, + struct librpma_fio_mem *mem, size_t ws_offset) +{ + int is_pmem = 0; + size_t size_mmap = 0; + + /* map the file */ + mem->mem_ptr = pmem_map_file(f->file_name, 0 /* len */, 0 /* flags */, + 0 /* mode */, &size_mmap, &is_pmem); + if (mem->mem_ptr == NULL) { + /* pmem_map_file() sets errno on failure */ + log_err("fio: pmem_map_file(%s) failed: %s (errno %i)\n", + f->file_name, strerror(errno), errno); + return -1; + } + + /* pmem is expected */ + if (!is_pmem) { + log_err("fio: %s is not located in persistent memory\n", + f->file_name); + goto err_unmap; + } + + /* check size of allocated persistent memory */ + if (size_mmap < ws_offset + size) { + log_err( + "fio: %s is too small to handle so many threads (%zu < %zu)\n", + f->file_name, size_mmap, ws_offset + size); + goto err_unmap; + } + + log_info("fio: size of memory mapped from the file %s: %zu\n", + f->file_name, size_mmap); + + mem->size_mmap = size_mmap; + + return 0; + +err_unmap: + (void) pmem_unmap(mem->mem_ptr, size_mmap); + return -1; +} + +static inline void librpma_fio_unmap(struct librpma_fio_mem *mem) +{ + (void) pmem_unmap(mem->mem_ptr, mem->size_mmap); +} diff --git a/engines/librpma_fio_pmem2.h b/engines/librpma_fio_pmem2.h new file mode 100644 index 0000000000..09a51f5f40 --- /dev/null +++ b/engines/librpma_fio_pmem2.h @@ -0,0 +1,91 @@ +/* + * librpma_fio_pmem2: allocates pmem using libpmem2. + * + * Copyright 2022, Intel Corporation + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License, + * version 2 as published by the Free Software Foundation.. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include "librpma_fio.h" + +#define RPMA_PMEM_USED "libpmem2" + +static int librpma_fio_pmem_map_file(struct fio_file *f, size_t size, + struct librpma_fio_mem *mem, size_t ws_offset) +{ + int fd; + struct pmem2_config *cfg = NULL; + struct pmem2_map *map = NULL; + struct pmem2_source *src = NULL; + + size_t size_mmap; + + if((fd = open(f->file_name, O_RDWR)) < 0) { + log_err("fio: cannot open fio file\n"); + return -1; + } + + if (pmem2_source_from_fd(&src, fd) != 0) { + log_err("fio: pmem2_source_from_fd() failed\n"); + goto err_close; + } + + if (pmem2_config_new(&cfg) != 0) { + log_err("fio: pmem2_config_new() failed\n"); + goto err_source_delete; + } + + if (pmem2_config_set_required_store_granularity(cfg, + PMEM2_GRANULARITY_CACHE_LINE) != 0) { + log_err("fio: pmem2_config_set_required_store_granularity() failed: %s\n", pmem2_errormsg()); + goto err_config_delete; + } + + if (pmem2_map_new(&map, cfg, src) != 0) { + log_err("fio: pmem2_map_new(%s) failed: %s\n", f->file_name, pmem2_errormsg()); + goto err_config_delete; + } + + size_mmap = pmem2_map_get_size(map); + + /* check size of allocated persistent memory */ + if (size_mmap < ws_offset + size) { + log_err( + "fio: %s is too small to handle so many threads (%zu < %zu)\n", + f->file_name, size_mmap, ws_offset + size); + goto err_map_delete; + } + + mem->mem_ptr = pmem2_map_get_address(map); + mem->size_mmap = size_mmap; + mem->map = map; + pmem2_config_delete(&cfg); + pmem2_source_delete(&src); + close(fd); + + return 0; + +err_map_delete: + pmem2_map_delete(&map); +err_config_delete: + pmem2_config_delete(&cfg); +err_source_delete: + pmem2_source_delete(&src); +err_close: + close(fd); + + return -1; +} + +static inline void librpma_fio_unmap(struct librpma_fio_mem *mem) +{ + (void) pmem2_map_delete(&mem->map); +} From fb04caf1942208b4e23ecf9dfc5c8e6ee5e71135 Mon Sep 17 00:00:00 2001 From: Kacper Stefanski Date: Thu, 24 Feb 2022 15:59:03 +0100 Subject: [PATCH 0273/1097] rpma: add support for libpmem2 to librpma engine in GPSPM mode Add support for libpmem2 to librpma fio engine in the GPSPM mode. Signed-off-by: Kacper Stefanski --- engines/librpma_gpspm.c | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/engines/librpma_gpspm.c b/engines/librpma_gpspm.c index 64962dda4c..70116d0d6e 100644 --- a/engines/librpma_gpspm.c +++ b/engines/librpma_gpspm.c @@ -2,7 +2,7 @@ * librpma_gpspm: IO engine that uses PMDK librpma to write data, * based on General Purpose Server Persistency Method * - * Copyright 2020-2021, Intel Corporation + * Copyright 2020-2022, Intel Corporation * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License, @@ -16,7 +16,11 @@ #include "librpma_fio.h" +#ifdef CONFIG_LIBPMEM2_INSTALLED +#include +#else #include +#endif /* Generated by the protocol buffer compiler from: librpma_gpspm_flush.proto */ #include "librpma_gpspm_flush.pb-c.h" @@ -361,6 +365,8 @@ FIO_STATIC struct ioengine_ops ioengine_client = { #define IO_U_BUFF_OFF_SERVER(i) (i * IO_U_BUF_LEN) +typedef void (*librpma_fio_persist_fn)(const void *ptr, size_t size); + struct server_data { /* aligned td->orig_buffer */ char *orig_buffer_aligned; @@ -373,6 +379,8 @@ struct server_data { /* in-memory queues */ struct ibv_wc *msgs_queued; uint32_t msg_queued_nr; + + librpma_fio_persist_fn persist; }; static int server_init(struct thread_data *td) @@ -400,6 +408,13 @@ static int server_init(struct thread_data *td) goto err_free_sd; } +#ifdef CONFIG_LIBPMEM2_INSTALLED + /* get libpmem2 persist function from pmem2_map */ + sd->persist = pmem2_get_persist_fn(csd->mem.map); +#else + sd->persist = pmem_persist; +#endif + /* * Assure a single io_u buffer can store both SEND and RECV messages and * an io_us buffer allocation is page-size-aligned which is required @@ -594,7 +609,7 @@ static int server_qe_process(struct thread_data *td, struct ibv_wc *wc) if (IS_NOT_THE_LAST_MESSAGE(flush_req)) { op_ptr = csd->ws_ptr + flush_req->offset; - pmem_persist(op_ptr, flush_req->length); + sd->persist(op_ptr, flush_req->length); } else { /* * This is the last message - the client is done. From 4e2bd713356cfc89ea6c898985c492af93b34a5d Mon Sep 17 00:00:00 2001 From: Lukasz Dorau Date: Tue, 6 Sep 2022 14:11:43 +0200 Subject: [PATCH 0274/1097] ci: build the librpma fio engine Install the librpma library and two required packges (libpmem2-dev and libprotobuf-c-dev) to have the librpma fio engine built. Signed-off-by: Lukasz Dorau --- ...travis-install-librpma.sh => actions-install-librpma.sh} | 3 +-- ci/actions-install.sh | 6 ++++++ 2 files changed, 7 insertions(+), 2 deletions(-) rename ci/{travis-install-librpma.sh => actions-install-librpma.sh} (74%) diff --git a/ci/travis-install-librpma.sh b/ci/actions-install-librpma.sh similarity index 74% rename from ci/travis-install-librpma.sh rename to ci/actions-install-librpma.sh index 4e5ed21d4d..31f9f71259 100755 --- a/ci/travis-install-librpma.sh +++ b/ci/actions-install-librpma.sh @@ -1,7 +1,6 @@ #!/bin/bash -e -# 11.02.2021 Merge pull request #866 from ldorau/rpma-mmap-memory-for-rpma_mr_reg-in-rpma_flush_apm_new -LIBRPMA_VERSION=fbac593917e98f3f26abf14f4fad5a832b330f5c +LIBRPMA_VERSION="1.0.0" ZIP_FILE=rpma.zip WORKDIR=$(pwd) diff --git a/ci/actions-install.sh b/ci/actions-install.sh index c209a08960..82e14d2ac4 100755 --- a/ci/actions-install.sh +++ b/ci/actions-install.sh @@ -44,7 +44,9 @@ DPKGCFG libiscsi-dev libnbd-dev libpmem-dev + libpmem2-dev libpmemblk-dev + libprotobuf-c-dev librbd-dev libtcmalloc-minimal4 nvidia-cuda-dev @@ -67,6 +69,10 @@ DPKGCFG sudo apt-get -qq update echo "Installing packages... ${pkgs[@]}" sudo apt-get install -o APT::Immediate-Configure=false --no-install-recommends -qq -y "${pkgs[@]}" + if [ "${CI_TARGET_ARCH}" == "x86_64" ]; then + # install librpma from sources + ci/actions-install-librpma.sh + fi } install_linux() { From c24ffaf5b03d2908147d2f0cf4823dc89b43920f Mon Sep 17 00:00:00 2001 From: Lukasz Dorau Date: Tue, 6 Sep 2022 14:17:40 +0200 Subject: [PATCH 0275/1097] ci: remove the unused travis-install-pmdk.sh file Remove the unused travis-install-pmdk.sh file. The libpmem and libpmem2 libraries are installed from packages. Signed-off-by: Lukasz Dorau --- ci/travis-install-pmdk.sh | 29 ----------------------------- 1 file changed, 29 deletions(-) delete mode 100755 ci/travis-install-pmdk.sh diff --git a/ci/travis-install-pmdk.sh b/ci/travis-install-pmdk.sh deleted file mode 100755 index 7bde9fd09a..0000000000 --- a/ci/travis-install-pmdk.sh +++ /dev/null @@ -1,29 +0,0 @@ -#!/bin/bash -e - -# pmdk v1.9.1 release -PMDK_VERSION=1.9.1 - -WORKDIR=$(pwd) - -# -# The '/bin/sh' shell used by PMDK's 'make install' -# does not know the exact localization of clang -# and fails with: -# /bin/sh: 1: clang: not found -# if CC is not set to the full path of clang. -# -CC=$(type -P "$CC") -export CC - -# Install PMDK libraries, because PMDK's libpmem -# is a dependency of the librpma fio engine. -# Install it from a release package -# with already generated documentation, -# in order to not install 'pandoc'. -wget https://github.com/pmem/pmdk/releases/download/${PMDK_VERSION}/pmdk-${PMDK_VERSION}.tar.gz -tar -xzf pmdk-${PMDK_VERSION}.tar.gz -cd pmdk-${PMDK_VERSION} -make -j"$(nproc)" NDCTL_ENABLE=n -sudo make -j"$(nproc)" install prefix=/usr NDCTL_ENABLE=n -cd "$WORKDIR" -rm -rf pmdk-${PMDK_VERSION} From 9f2eaaf58dd20e8bbad8eb33d36c91b9cff1ab43 Mon Sep 17 00:00:00 2001 From: Christophe Vu-Brugier Date: Mon, 12 Sep 2022 14:13:04 +0200 Subject: [PATCH 0276/1097] examples: set zonemode to strided in disk-zone-profile.fio This fixes the following error: # fio disk-zone-profile.fio fio: --zonemode=none and --zonesize are not compatible. Signed-off-by: Christophe Vu-Brugier --- examples/disk-zone-profile.fio | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/disk-zone-profile.fio b/examples/disk-zone-profile.fio index 96e5669556..010252f087 100644 --- a/examples/disk-zone-profile.fio +++ b/examples/disk-zone-profile.fio @@ -1,4 +1,4 @@ -; Read disk in zones of 128m/2g, generating a plot of that afterwards +; Read disk in zones of 256m/2g. Generating a plot of that afterwards ; should give a nice picture of the zoning of this drive [global] @@ -7,6 +7,7 @@ direct=1 rw=read ioengine=libaio iodepth=2 +zonemode=strided zonesize=256m zoneskip=2g write_bw_log From edb785dc131730ab388962f29c4bc97e4b50fa06 Mon Sep 17 00:00:00 2001 From: Christophe Vu-Brugier Date: Mon, 12 Sep 2022 14:17:49 +0200 Subject: [PATCH 0277/1097] examples: fix bandwidth logs generation in disk-zone-profile.fio Because the job name is set to "/dev/sdb", the bandwidth logs are written to /dev/sdb_bw.1.log which is unexpected. This commit changes the job name to "disk-zone-profile" so that the bandwidth logs are written to "disk-zone-profile_bw.1.log". Moreover, the `log_offset` option is enabled to log the I/O offset. This is required to plot the HDD zones. Signed-off-by: Christophe Vu-Brugier --- examples/disk-zone-profile.fio | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/examples/disk-zone-profile.fio b/examples/disk-zone-profile.fio index 010252f087..577820ebe7 100644 --- a/examples/disk-zone-profile.fio +++ b/examples/disk-zone-profile.fio @@ -10,6 +10,8 @@ iodepth=2 zonemode=strided zonesize=256m zoneskip=2g -write_bw_log -[/dev/sdb] +[disk-zone-profile] +filename=/dev/sdb +write_bw_log +log_offset=1 From 53c82bb879532b994451c6abc7be80c94241d03b Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Mon, 12 Sep 2022 10:43:17 -0400 Subject: [PATCH 0278/1097] stat: fix comment about memory consumption We changed from unsigned int to uint64_t for the latency frequency counters. Reflect this in the comment describing how much memory the latency frequency counters require. Signed-off-by: Vincent Fu --- stat.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stat.h b/stat.h index eb7845afec..4c3bf71f3b 100644 --- a/stat.h +++ b/stat.h @@ -51,7 +51,7 @@ struct group_run_stats { * * FIO_IO_U_PLAT_GROUP_NR and FIO_IO_U_PLAT_BITS determine the memory * requirement of storing those aggregate counts. The memory used will - * be (FIO_IO_U_PLAT_GROUP_NR * 2^FIO_IO_U_PLAT_BITS) * sizeof(int) + * be (FIO_IO_U_PLAT_GROUP_NR * 2^FIO_IO_U_PLAT_BITS) * sizeof(uint64_t) * bytes. * * FIO_IO_U_PLAT_NR is the total number of buckets. From baa7cecaa4d4e93e300214b26b543d8b096ddacf Mon Sep 17 00:00:00 2001 From: lilinjie Date: Tue, 13 Sep 2022 18:35:20 +0800 Subject: [PATCH 0279/1097] fix spelling error Signed-off-by: lilinjie --- fio.1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fio.1 b/fio.1 index 67d7c710fa..61de666211 100644 --- a/fio.1 +++ b/fio.1 @@ -2488,11 +2488,11 @@ Specify the label or UUID of the DAOS pool to connect to. Specify the label or UUID of the DAOS container to open. .TP .BI (dfs)chunk_size -Specificy a different chunk size (in bytes) for the dfs file. +Specify a different chunk size (in bytes) for the dfs file. Use DAOS container's chunk size by default. .TP .BI (dfs)object_class -Specificy a different object class for the dfs file. +Specify a different object class for the dfs file. Use DAOS container's object class by default. .TP .BI (nfs)nfs_url From 08996af41b2566565cbcdee71766030a2c8ba377 Mon Sep 17 00:00:00 2001 From: Ankit Kumar Date: Tue, 13 Sep 2022 16:15:27 +0530 Subject: [PATCH 0280/1097] backend: number of ios not as expected for trimwrite number_ios should be twice for trimwrite, just like size or io_size. Update the documentation for "rw=trimwrite" to explain the changes. Signed-off-by: Ankit Kumar Reviewed-by: Vincent Fu Link: https://lore.kernel.org/r/20220913104527.18734-2-ankit.kumar@samsung.com Signed-off-by: Jens Axboe --- HOWTO.rst | 6 +++++- backend.c | 6 ++++-- fio.1 | 5 ++++- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/HOWTO.rst b/HOWTO.rst index 2c6c6dbe5c..924f5ed956 100644 --- a/HOWTO.rst +++ b/HOWTO.rst @@ -1129,7 +1129,11 @@ I/O type Random mixed reads and writes. **trimwrite** Sequential trim+write sequences. Blocks will be trimmed first, - then the same blocks will be written to. + then the same blocks will be written to. So if ``io_size=64K`` + is specified, Fio will trim a total of 64K bytes and also + write 64K bytes on the same trimmed blocks. This behaviour + will be consistent with ``number_ios`` or other Fio options + limiting the total bytes or number of I/O's. Fio defaults to read if the option is not specified. For the mixed I/O types, the default is to split them 50/50. For certain types of I/O the diff --git a/backend.c b/backend.c index fe614f6e1c..ec535bcc47 100644 --- a/backend.c +++ b/backend.c @@ -971,9 +971,11 @@ static void do_io(struct thread_data *td, uint64_t *bytes_done) total_bytes += td->o.size; /* In trimwrite mode, each byte is trimmed and then written, so - * allow total_bytes to be twice as big */ - if (td_trimwrite(td)) + * allow total_bytes or number of ios to be twice as big */ + if (td_trimwrite(td)) { total_bytes += td->total_io_size; + td->o.number_ios *= 2; + } while ((td->o.read_iolog_file && !flist_empty(&td->io_log_list)) || (!flist_empty(&td->trim_list)) || !io_issue_bytes_exceeded(td) || diff --git a/fio.1 b/fio.1 index 67d7c710fa..c67bd46420 100644 --- a/fio.1 +++ b/fio.1 @@ -900,7 +900,10 @@ Random mixed reads and writes. .TP .B trimwrite Sequential trim+write sequences. Blocks will be trimmed first, -then the same blocks will be written to. +then the same blocks will be written to. So if `io_size=64K' is specified, +Fio will trim a total of 64K bytes and also write 64K bytes on the same +trimmed blocks. This behaviour will be consistent with `number_ios' or +other Fio options limiting the total bytes or number of I/O's. .RE .P Fio defaults to read if the option is not specified. For the mixed I/O From 9e0ad34413bb1a4f4e02a62ed81d665130879bf1 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Wed, 14 Sep 2022 10:50:52 -0700 Subject: [PATCH 0281/1097] gettime: cleanups Fix a comment and get rid of the extra new lines in the debug print. Signed-off-by: Vincent Fu --- gettime.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gettime.c b/gettime.c index 1446242021..8993be1688 100644 --- a/gettime.c +++ b/gettime.c @@ -313,7 +313,7 @@ static int calibrate_cpu_clock(void) max_ticks = MAX_CLOCK_SEC * cycles_per_msec * 1000ULL; max_mult = ULLONG_MAX / max_ticks; - dprint(FD_TIME, "\n\nmax_ticks=%llu, __builtin_clzll=%d, " + dprint(FD_TIME, "max_ticks=%llu, __builtin_clzll=%d, " "max_mult=%llu\n", max_ticks, __builtin_clzll(max_ticks), max_mult); @@ -335,7 +335,7 @@ static int calibrate_cpu_clock(void) /* * Find the greatest power of 2 clock ticks that is less than the - * ticks in MAX_CLOCK_SEC_2STAGE + * ticks in MAX_CLOCK_SEC */ max_cycles_shift = max_cycles_mask = 0; tmp = MAX_CLOCK_SEC * 1000ULL * cycles_per_msec; From d14687025c0c61d047e4252036d1b024d62cb0a6 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Mon, 19 Sep 2022 09:34:13 -0400 Subject: [PATCH 0282/1097] configure: change grep -P to grep -E grep -P is not supported on FreeBSD. So use grep -E when we check whether the value provided for --seed-buckets is a number or not. Reported-by: Rebecca Cran Link: https://lore.kernel.org/fio/26076f36-54d5-ca6e-0b96-a7371b1c5e49@bsdio.com/T/#t Signed-off-by: Vincent Fu --- configure | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure b/configure index 7741ef4fd8..546541a295 100755 --- a/configure +++ b/configure @@ -117,7 +117,7 @@ has() { } num() { - echo "$1" | grep -P -q "^[0-9]+$" + echo "$1" | grep -E -q "^[0-9]+$" } check_define() { From 0574e8c3b2b47e1e2564c2f50ea0b6f2629f2e48 Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Thu, 22 Sep 2022 10:03:51 -0600 Subject: [PATCH 0283/1097] arm64: ensure CPU clock retrieval issues isb() This prevents counter value speculation, which will otherwise cause issues with the CPU clock. Link: https://github.com/axboe/fio/issues/1472 Signed-off-by: Jens Axboe --- arch/arch-aarch64.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/arch/arch-aarch64.h b/arch/arch-aarch64.h index 951d1718cf..919e579676 100644 --- a/arch/arch-aarch64.h +++ b/arch/arch-aarch64.h @@ -27,10 +27,13 @@ static inline int arch_ffz(unsigned long bitmask) #define ARCH_HAVE_FFZ +#define isb() asm volatile("isb" : : : "memory") + static inline unsigned long long get_cpu_clock(void) { unsigned long val; + isb(); asm volatile("mrs %0, cntvct_el0" : "=r" (val)); return val; } From 6112c0f5a86c6b437e7158ab40a6e9384ce95e85 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Tue, 27 Sep 2022 07:43:49 -0700 Subject: [PATCH 0284/1097] doc: build manpage from fio_doc.rst instead of fio_man.rst Sphinx prints warnings when it encounters duplicate labels. In HOWTO.rst are labels for int, irange, and bool. We include HOWTO.rst in both fio_doc.rst and fio_man.rst. Since labels must be unique across all files, Sphinx prints warnings for these labels. For an unknown reason, Sphinx previously did not issue warnings for the duplicate labels mentioned above until 5.2.0. But Sphinx 5.2.1 is now installed for the macOS 11 image in GitHub Actions. So now we see Sphinx warnings when building documentation in GitHub Actions. Our CI treats Sphinx warnings as test failures. So our macOS builds are marked as failures. Resolve this problem by eliminating the separate fio_man.rst file and just building the manpage from the largely equivalent fio_doc.rst. Successful build with 5.1.1: https://github.com/axboe/fio/actions/runs/3106980788/jobs/5034529793 Failed build with 5.2.1: https://github.com/axboe/fio/actions/runs/3129974184/jobs/5079696775 Link: https://github.com/sphinx-doc/sphinx/pull/10781 Link: https://github.com/sphinx-doc/sphinx/issues/10870 Signed-off-by: Vincent Fu --- doc/conf.py | 2 +- doc/fio_doc.rst | 42 +++++++++++++++++++++--------------------- doc/fio_man.rst | 12 ------------ 3 files changed, 22 insertions(+), 34 deletions(-) delete mode 100644 doc/fio_man.rst diff --git a/doc/conf.py b/doc/conf.py index 844f951ab7..18a8dccce3 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -318,7 +318,7 @@ def fio_version(): # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - ('fio_man', 'fio', 'flexible I/O tester', + ('fio_doc', 'fio', 'flexible I/O tester', [author], 1) ] diff --git a/doc/fio_doc.rst b/doc/fio_doc.rst index 34e7fde988..cad84c7c7e 100644 --- a/doc/fio_doc.rst +++ b/doc/fio_doc.rst @@ -7,45 +7,45 @@ fio - Flexible I/O tester rev. |version| .. include:: ../HOWTO.rst +.. only:: not man + Examples + ======== -Examples -======== + .. include:: fio_examples.rst -.. include:: fio_examples.rst + TODO + ==== -TODO -==== + GFIO TODO + --------- -GFIO TODO ---------- + .. include:: ../GFIO-TODO -.. include:: ../GFIO-TODO + Server TODO + ----------- -Server TODO ------------ + .. include:: ../SERVER-TODO -.. include:: ../SERVER-TODO + Steady State TODO + ----------------- -Steady State TODO ------------------ + .. include:: ../STEADYSTATE-TODO -.. include:: ../STEADYSTATE-TODO + Moral License + ============= -Moral License -============= + .. include:: ../MORAL-LICENSE -.. include:: ../MORAL-LICENSE + License + ======= -License -======= - -.. literalinclude:: ../COPYING + .. literalinclude:: ../COPYING diff --git a/doc/fio_man.rst b/doc/fio_man.rst deleted file mode 100644 index dc1d1c0ddc..0000000000 --- a/doc/fio_man.rst +++ /dev/null @@ -1,12 +0,0 @@ -:orphan: - -Fio Manpage -=========== - -(rev. |release|) - - -.. include:: ../README.rst - - -.. include:: ../HOWTO.rst From c5f0a205e9bf205d71ed015aa2be0b9e24b0e756 Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Wed, 28 Sep 2022 07:28:58 -0600 Subject: [PATCH 0285/1097] t/io_uring: get rid of useless read barriers We don't need a read barrier when we have acquire semantics on reading the CQ ring tail. We also don't need acquire semantics on the SQ ring head, unless we're using SQPOLL. A syscall transition will have happened for !SQPOLL. Signed-off-by: Jens Axboe --- t/io_uring.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/t/io_uring.c b/t/io_uring.c index b9353ac867..edbacee3fc 100644 --- a/t/io_uring.c +++ b/t/io_uring.c @@ -661,8 +661,12 @@ static void init_io_pt(struct submitter *s, unsigned index) static int prep_more_ios_uring(struct submitter *s, int max_ios) { struct io_sq_ring *ring = &s->sq_ring; - unsigned index, tail, next_tail, prepped = 0; - unsigned int head = atomic_load_acquire(ring->head); + unsigned head, index, tail, next_tail, prepped = 0; + + if (sq_thread_poll) + head = atomic_load_acquire(ring->head); + else + head = *ring->head; next_tail = tail = *ring->tail; do { @@ -741,7 +745,6 @@ static int reap_events_uring(struct submitter *s) do { struct file *f; - read_barrier(); if (head == atomic_load_acquire(ring->tail)) break; cqe = &ring->cqes[head & cq_ring_mask]; @@ -796,7 +799,6 @@ static int reap_events_uring_pt(struct submitter *s) do { struct file *f; - read_barrier(); if (head == atomic_load_acquire(ring->tail)) break; index = head & cq_ring_mask; From c16dc793a3c45780f67ce65244b6e91323dee014 Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Wed, 28 Sep 2022 09:11:41 -0600 Subject: [PATCH 0286/1097] Add randtrimwrite data direction This works like trimwrite, except we use random offsets for the trim location rather than sequential ones. Suggested-by: Josef Bacik Signed-off-by: Jens Axboe --- HOWTO.rst | 3 +++ fio.1 | 5 +++++ io_ddir.h | 4 +++- options.c | 4 ++++ 4 files changed, 15 insertions(+), 1 deletion(-) diff --git a/HOWTO.rst b/HOWTO.rst index 924f5ed956..e89d05f047 100644 --- a/HOWTO.rst +++ b/HOWTO.rst @@ -1134,6 +1134,9 @@ I/O type write 64K bytes on the same trimmed blocks. This behaviour will be consistent with ``number_ios`` or other Fio options limiting the total bytes or number of I/O's. + **randtrimwrite** + Like trimwrite, but uses random offsets rather + than sequential writes. Fio defaults to read if the option is not specified. For the mixed I/O types, the default is to split them 50/50. For certain types of I/O the diff --git a/fio.1 b/fio.1 index 39d6b4f490..4324a97509 100644 --- a/fio.1 +++ b/fio.1 @@ -904,6 +904,11 @@ then the same blocks will be written to. So if `io_size=64K' is specified, Fio will trim a total of 64K bytes and also write 64K bytes on the same trimmed blocks. This behaviour will be consistent with `number_ios' or other Fio options limiting the total bytes or number of I/O's. +.TP +.B randtrimwrite +Like +.B trimwrite , +but uses random offsets rather than sequential writes. .RE .P Fio defaults to read if the option is not specified. For the mixed I/O diff --git a/io_ddir.h b/io_ddir.h index 296a9d04ac..7227e9ee7f 100644 --- a/io_ddir.h +++ b/io_ddir.h @@ -41,6 +41,7 @@ enum td_ddir { TD_DDIR_RANDRW = TD_DDIR_RW | TD_DDIR_RAND, TD_DDIR_RANDTRIM = TD_DDIR_TRIM | TD_DDIR_RAND, TD_DDIR_TRIMWRITE = TD_DDIR_TRIM | TD_DDIR_WRITE, + TD_DDIR_RANDTRIMWRITE = TD_DDIR_RANDTRIM | TD_DDIR_WRITE, }; #define td_read(td) ((td)->o.td_ddir & TD_DDIR_READ) @@ -67,7 +68,8 @@ static inline const char *ddir_str(enum td_ddir ddir) { static const char *__str[] = { NULL, "read", "write", "rw", "rand", "randread", "randwrite", "randrw", - "trim", NULL, "trimwrite", NULL, "randtrim" }; + "trim", NULL, "trimwrite", NULL, "randtrim", + NULL, "randtrimwrite" }; return __str[ddir]; } diff --git a/options.c b/options.c index 5d3daedf34..a668b0e475 100644 --- a/options.c +++ b/options.c @@ -1947,6 +1947,10 @@ struct fio_option fio_options[FIO_MAX_OPTS] = { .oval = TD_DDIR_TRIMWRITE, .help = "Trim and write mix, trims preceding writes" }, + { .ival = "randtrimwrite", + .oval = TD_DDIR_RANDTRIMWRITE, + .help = "Randomly trim and write mix, trims preceding writes" + }, }, }, { From 0ebd3bf6186852408503f428c7a74fedd34214cb Mon Sep 17 00:00:00 2001 From: Anuj Gupta Date: Mon, 3 Oct 2022 09:01:52 +0530 Subject: [PATCH 0287/1097] engines/io_uring: add fixedbufs support for io_uring_cmd This patch enables fixedbufs support for io_uring_cmd. This has already been done in t/io_uring, just do it here as well. Signed-off-by: Anuj Gupta Link: https://lore.kernel.org/r/20221003033152.314763-1-anuj20.g@samsung.com Signed-off-by: Jens Axboe --- engines/io_uring.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/engines/io_uring.c b/engines/io_uring.c index d0fc61dcac..c679177fb4 100644 --- a/engines/io_uring.c +++ b/engines/io_uring.c @@ -433,6 +433,10 @@ static int fio_ioring_cmd_prep(struct thread_data *td, struct io_u *io_u) ld->prepped = 0; sqe->flags |= IOSQE_ASYNC; } + if (o->fixedbufs) { + sqe->uring_cmd_flags = IORING_URING_CMD_FIXED; + sqe->buf_index = io_u->index; + } cmd = (struct nvme_uring_cmd *)sqe->cmd; return fio_nvme_uring_cmd_prep(cmd, io_u, From f9f1e137bbf6afec54102ff7720c1c0c01940199 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Thu, 29 Sep 2022 16:58:17 -0700 Subject: [PATCH 0288/1097] randtrimwrite: write at same offset as trim We need to do a little bit more to make sure that the writes land on the offsets that were trimmed. We only have a single random seed for offsets. So we need to just use the offset from trim commands when issuing writes. When we have variable block sizes we need to make sure that the trim and write commands are the same size. When randommap is enabled we have to let it adjust the block size for trim commands to make sure that the trim command does not touch any blocks that have already been touched. For sizes of write commands just use the size of the trim command. Fixes: c16dc793a3c45780f67ce65244b6e91323dee014 "Add randtrimwrite data direction" Signed-off-by: Vincent Fu --- io_ddir.h | 2 ++ io_u.c | 14 +++++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/io_ddir.h b/io_ddir.h index 7227e9ee7f..217eb62862 100644 --- a/io_ddir.h +++ b/io_ddir.h @@ -52,6 +52,8 @@ enum td_ddir { #define file_randommap(td, f) (!(td)->o.norandommap && fio_file_axmap((f))) #define td_trimwrite(td) (((td)->o.td_ddir & TD_DDIR_TRIMWRITE) \ == TD_DDIR_TRIMWRITE) +#define td_randtrimwrite(td) (((td)->o.td_ddir & TD_DDIR_RANDTRIMWRITE) \ + == TD_DDIR_RANDTRIMWRITE) static inline int ddir_sync(enum fio_ddir ddir) { diff --git a/io_u.c b/io_u.c index eec378ddc0..3f6c60ee88 100644 --- a/io_u.c +++ b/io_u.c @@ -417,7 +417,13 @@ static int get_next_block(struct thread_data *td, struct io_u *io_u, b = offset = -1ULL; - if (rw_seq) { + if (td_randtrimwrite(td) && ddir == DDIR_WRITE) { + /* don't mark randommap for these writes */ + io_u_set(td, io_u, IO_U_F_BUSY_OK); + offset = f->last_start[DDIR_TRIM]; + *is_random = true; + ret = 0; + } else if (rw_seq) { if (td_random(td)) { if (should_do_random(td, ddir)) { ret = get_next_rand_block(td, f, ddir, &b); @@ -530,6 +536,12 @@ static unsigned long long get_next_buflen(struct thread_data *td, struct io_u *i assert(ddir_rw(ddir)); + if (td_randtrimwrite(td) && ddir == DDIR_WRITE) { + struct fio_file *f = io_u->file; + + return f->last_pos[DDIR_TRIM] - f->last_start[DDIR_TRIM]; + } + if (td->o.bs_is_seq_rand) ddir = is_random ? DDIR_WRITE : DDIR_READ; From c37183f8a161df252b7b05e9be9c4e0665106146 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Fri, 30 Sep 2022 12:48:56 -0700 Subject: [PATCH 0289/1097] test: test job for randtrimwrite This test exposes a problem with randtrimwrite when norandommap is enabled. Currently we check to see if the last write and last trim ended at the same position. If so, we decide that the next operation should be a trim. This is ok in most cases but if norandommap is enabled, it could happen by chance that we just finished a trim + write on an offset and randomly chose that same offset again. This would fool fio into thinking that it already finished a trim + write pair when it actually issued only a trim operation. Signed-off-by: Vincent Fu --- t/jobs/t0023.fio | 75 ++++++++++++++++++++++++++++++++++++++++++++++ t/run-fio-tests.py | 64 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 139 insertions(+) create mode 100644 t/jobs/t0023.fio diff --git a/t/jobs/t0023.fio b/t/jobs/t0023.fio new file mode 100644 index 0000000000..0250ee1a10 --- /dev/null +++ b/t/jobs/t0023.fio @@ -0,0 +1,75 @@ +# randtrimwrite data direction tests +[global] +filesize=1M +ioengine=null +rw=randtrimwrite +log_offset=1 +per_job_logs=0 +randrepeat=0 +stonewall + +# Expected result: trim issued to random offset followed by write to same offset +# all offsets touched +# block sizes match +# Buggy result: something else +[basic] +write_bw_log + +# Expected result: trim issued to random offset followed by write to same offset +# all offsets trimmed +# block sizes 8k for both write and trim +# Buggy result: something else +[bs] +write_bw_log +bs=4k,4k,8k + +# Expected result: trim issued to random offset followed by write to same offset +# all offsets trimmed +# block sizes match +# Buggy result: something else +[bsrange] +write_bw_log +bsrange=512-4k + +# Expected result: trim issued to random offset followed by write to same offset +# all offsets trimmed +# block sizes match +# Buggy result: something else +[bssplit] +write_bw_log +bsrange=512/25:1k:25:2k:25:4k/25 + +# Expected result: trim issued to random offset followed by write to same offset +# all offsets touched +# block sizes match +# Buggy result: something else +[basic_no_rm] +write_bw_log +norandommap=1 + +# Expected result: trim issued to random offset followed by write to same offset +# all offsets trimmed +# block sizes 8k for both write and trim +# Buggy result: something else +[bs_no_rm] +write_bw_log +bs=4k,4k,8k +norandommap=1 + +# Expected result: trim issued to random offset followed by write to same offset +# all offsets trimmed +# block sizes match +# Buggy result: something else +[bsrange_no_rm] +write_bw_log +bsrange=512-4k +norandommap=1 + +# Expected result: trim issued to random offset followed by write to same offset +# all offsets trimmed +# block sizes match +# Buggy result: something else +[bssplit_no_rm] +write_bw_log +bsrange=512/25:1k:25:2k:25:4k/25 +norandommap=1 diff --git a/t/run-fio-tests.py b/t/run-fio-tests.py index e72fa2a01c..a2b036d9c8 100755 --- a/t/run-fio-tests.py +++ b/t/run-fio-tests.py @@ -649,6 +649,61 @@ def check_result(self): self.failure_reason += " no duplicate offsets found with norandommap=1".format(len(offsets)) +class FioJobTest_t0023(FioJobTest): + """Test consists of fio test job t0023""" + + def check_seq(self, filename): + bw_log_filename = os.path.join(self.test_dir, filename) + file_data, success = self.get_file(bw_log_filename) + log_lines = file_data.split('\n') + + prev_ddir = 1 + for line in log_lines: + if len(line.strip()) == 0: + continue + vals = line.split(',') + ddir = int(vals[2]) + bs = int(vals[3]) + offset = int(vals[4]) + if prev_ddir == 1: + if ddir != 2: + self.passed = False + self.failure_reason += " {0}: write not preceeded by trim: {1}".format(bw_log_filename, line) + break + else: + if ddir != 1: + self.passed = False + self.failure_reason += " {0}: trim not preceeded by write: {1}".format(bw_log_filename, line) + break + else: + if prev_bs != bs: + self.passed = False + self.failure_reason += " {0}: block size does not match: {1}".format(bw_log_filename, line) + break + if prev_offset != offset: + self.passed = False + self.failure_reason += " {0}: offset does not match: {1}".format(bw_log_filename, line) + break + prev_ddir = ddir + prev_bs = bs + prev_offset = offset + + + def check_result(self): + super(FioJobTest_t0023, self).check_result() + + self.check_seq("basic_bw.log") + self.check_seq("bs_bw.log") + self.check_seq("bsrange_bw.log") + self.check_seq("bssplit_bw.log") + self.check_seq("basic_no_rm_bw.log") + self.check_seq("bs_no_rm_bw.log") + self.check_seq("bsrange_no_rm_bw.log") + self.check_seq("bssplit_no_rm_bw.log") + + # TODO make sure all offsets were touched + + class FioJobTest_iops_rate(FioJobTest): """Test consists of fio test job t0009 Confirm that job0 iops == 1000 @@ -1026,6 +1081,15 @@ def cpucount4(cls): 'pre_success': None, 'requirements': [], }, + { + 'test_id': 23, + 'test_class': FioJobTest_t0023, + 'job': 't0023.fio', + 'success': SUCCESS_DEFAULT, + 'pre_job': None, + 'pre_success': None, + 'requirements': [], + }, { 'test_id': 1000, 'test_class': FioExeTest, From 5e0863daaad1826a6b429e961d9ee83edcaf77ba Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Fri, 30 Sep 2022 16:54:57 -0700 Subject: [PATCH 0290/1097] randtrimwrite: fix offsets for corner case For randtrimwrite, we decide whether to issue a trim or a write based on whether the end offsets for the most recent trim and write commands match. If they don't match that means we just issued a new trim and the next operation should be a write. If they *do* match that means we just completed a trim + write pair and the next command should be a trim. This works fine for sequential workloads but for random workloads it's possible to complete a trim + write pair and then have the randomly generated offset for the next trim command match the previous offset. If that happens we need to alter the offset for the last write operation in order to ensure that we issue a write operation the next time through. It feels dirty to change the meaning of last_pos[DDIR_WRITE] in this way but hopefully the long comment in the code will be sufficient warning. Signed-off-by: Vincent Fu --- io_u.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/io_u.c b/io_u.c index 3f6c60ee88..2c37720f05 100644 --- a/io_u.c +++ b/io_u.c @@ -513,6 +513,24 @@ static int get_next_offset(struct thread_data *td, struct io_u *io_u, return 1; } + /* + * For randtrimwrite, we decide whether to issue a trim or a write + * based on whether the offsets for the most recent trim and write + * operations match. If they don't match that means we just issued a + * new trim and the next operation should be a write. If they *do* + * match that means we just completed a trim+write pair and the next + * command should be a trim. + * + * This works fine for sequential workloads but for random workloads + * it's possible to complete a trim+write pair and then have the next + * randomly generated offset match the previous offset. If that happens + * we need to alter the offset for the last write operation in order + * to ensure that we issue a write operation the next time through. + */ + if (td_randtrimwrite(td) && ddir == DDIR_TRIM && + f->last_start[DDIR_TRIM] == io_u->offset) + f->last_pos[DDIR_WRITE]--; + io_u->verify_offset = io_u->offset; return 0; } From 793b868671d14f9a3e4fa76ac129545987084a8d Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Mon, 3 Oct 2022 10:42:41 -0700 Subject: [PATCH 0291/1097] randtrimwrite: fix corner case with variable block sizes When we have variable block sizes it's possible to finish a trim + write pair and then have the next (smaller length) trim operation have a different start offset but the same end offset as the previous pair of trim and write operations. This would fool fio into believing that it had already completed a trim + write pair when it actually completed only the trim. Resolve this problem by comparing start offsets instead of end offsets. Signed-off-by: Vincent Fu --- io_u.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/io_u.c b/io_u.c index 2c37720f05..91f1a3584d 100644 --- a/io_u.c +++ b/io_u.c @@ -529,7 +529,7 @@ static int get_next_offset(struct thread_data *td, struct io_u *io_u, */ if (td_randtrimwrite(td) && ddir == DDIR_TRIM && f->last_start[DDIR_TRIM] == io_u->offset) - f->last_pos[DDIR_WRITE]--; + f->last_start[DDIR_WRITE]--; io_u->verify_offset = io_u->offset; return 0; @@ -798,7 +798,7 @@ static void set_rw_ddir(struct thread_data *td, struct io_u *io_u) if (td_trimwrite(td)) { struct fio_file *f = io_u->file; - if (f->last_pos[DDIR_WRITE] == f->last_pos[DDIR_TRIM]) + if (f->last_start[DDIR_WRITE] == f->last_start[DDIR_TRIM]) ddir = DDIR_TRIM; else ddir = DDIR_WRITE; From ac0a7e39f048e68aa102b4543a8b411b430ca007 Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Tue, 4 Oct 2022 12:22:00 -0700 Subject: [PATCH 0292/1097] Android: Fix the build of the 'sg' engine Fix the following compiler error: engines/sg.c:1337:46: error: argument type 'void' is incomplete snprintf(msgchunk, MAXMSGCHUNK, " %02x", hdr->sbp[i]); Signed-off-by: Bart Van Assche --- engines/sg.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/engines/sg.c b/engines/sg.c index 72ee07ba45..24783374cb 100644 --- a/engines/sg.c +++ b/engines/sg.c @@ -1331,10 +1331,12 @@ static char *fio_sgio_errdetails(struct io_u *io_u) strlcat(msg, ". ", MAXERRDETAIL); } if (hdr->sb_len_wr) { + const uint8_t *const sbp = hdr->sbp; + snprintf(msgchunk, MAXMSGCHUNK, "Sense Data (%d bytes):", hdr->sb_len_wr); strlcat(msg, msgchunk, MAXERRDETAIL); for (i = 0; i < hdr->sb_len_wr; i++) { - snprintf(msgchunk, MAXMSGCHUNK, " %02x", hdr->sbp[i]); + snprintf(msgchunk, MAXMSGCHUNK, " %02x", sbp[i]); strlcat(msg, msgchunk, MAXERRDETAIL); } strlcat(msg, ". ", MAXERRDETAIL); From 6a2ecfdb4befef491e13106598517889f9793e4c Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Tue, 4 Oct 2022 12:22:00 -0700 Subject: [PATCH 0293/1097] Android: Enable the 'sg' engine Make it possible to use the 'sg' engine on Android. This engine has been verified on Android by running fio --verify=md5 on top of a scsi_debug instance. Signed-off-by: Bart Van Assche --- Makefile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index f947f11c71..7bd572d7df 100644 --- a/Makefile +++ b/Makefile @@ -249,7 +249,8 @@ endif endif ifeq ($(CONFIG_TARGET_OS), Android) SOURCE += diskutil.c fifo.c blktrace.c cgroup.c trim.c profiles/tiobench.c \ - oslib/linux-dev-lookup.c engines/io_uring.c engines/nvme.c + oslib/linux-dev-lookup.c engines/io_uring.c engines/nvme.c \ + engines/sg.c cmdprio_SRCS = engines/cmdprio.c ifdef CONFIG_HAS_BLKZONED SOURCE += oslib/linux-blkzoned.c From 1fb782940ec411f39e0a25b2ce4757a25c269bf4 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Tue, 4 Oct 2022 14:10:17 -0700 Subject: [PATCH 0294/1097] test: clean up randtrimwrite test Simplify the job file by putting write_bw_log in the [global] section. Rename check_seq to the clearer check_trimwrite since it makes sure that trims are followed by writes. Signed-off-by: Vincent Fu --- t/jobs/t0023.fio | 13 +------------ t/run-fio-tests.py | 20 +++++++++++--------- 2 files changed, 12 insertions(+), 21 deletions(-) diff --git a/t/jobs/t0023.fio b/t/jobs/t0023.fio index 0250ee1a10..4d76b4a35f 100644 --- a/t/jobs/t0023.fio +++ b/t/jobs/t0023.fio @@ -7,20 +7,19 @@ log_offset=1 per_job_logs=0 randrepeat=0 stonewall +write_bw_log # Expected result: trim issued to random offset followed by write to same offset # all offsets touched # block sizes match # Buggy result: something else [basic] -write_bw_log # Expected result: trim issued to random offset followed by write to same offset # all offsets trimmed # block sizes 8k for both write and trim # Buggy result: something else [bs] -write_bw_log bs=4k,4k,8k # Expected result: trim issued to random offset followed by write to same offset @@ -28,7 +27,6 @@ bs=4k,4k,8k # block sizes match # Buggy result: something else [bsrange] -write_bw_log bsrange=512-4k # Expected result: trim issued to random offset followed by write to same offset @@ -36,40 +34,31 @@ bsrange=512-4k # block sizes match # Buggy result: something else [bssplit] -write_bw_log bsrange=512/25:1k:25:2k:25:4k/25 # Expected result: trim issued to random offset followed by write to same offset -# all offsets touched # block sizes match # Buggy result: something else [basic_no_rm] -write_bw_log norandommap=1 # Expected result: trim issued to random offset followed by write to same offset -# all offsets trimmed # block sizes 8k for both write and trim # Buggy result: something else [bs_no_rm] -write_bw_log bs=4k,4k,8k norandommap=1 # Expected result: trim issued to random offset followed by write to same offset -# all offsets trimmed # block sizes match # Buggy result: something else [bsrange_no_rm] -write_bw_log bsrange=512-4k norandommap=1 # Expected result: trim issued to random offset followed by write to same offset -# all offsets trimmed # block sizes match # Buggy result: something else [bssplit_no_rm] -write_bw_log bsrange=512/25:1k:25:2k:25:4k/25 norandommap=1 diff --git a/t/run-fio-tests.py b/t/run-fio-tests.py index a2b036d9c8..07dafc0c44 100755 --- a/t/run-fio-tests.py +++ b/t/run-fio-tests.py @@ -652,7 +652,9 @@ def check_result(self): class FioJobTest_t0023(FioJobTest): """Test consists of fio test job t0023""" - def check_seq(self, filename): + def check_trimwrite(self, filename): + """Make sure that trims are followed by writes of the same size at the same offset.""" + bw_log_filename = os.path.join(self.test_dir, filename) file_data, success = self.get_file(bw_log_filename) log_lines = file_data.split('\n') @@ -692,14 +694,14 @@ def check_seq(self, filename): def check_result(self): super(FioJobTest_t0023, self).check_result() - self.check_seq("basic_bw.log") - self.check_seq("bs_bw.log") - self.check_seq("bsrange_bw.log") - self.check_seq("bssplit_bw.log") - self.check_seq("basic_no_rm_bw.log") - self.check_seq("bs_no_rm_bw.log") - self.check_seq("bsrange_no_rm_bw.log") - self.check_seq("bssplit_no_rm_bw.log") + self.check_trimwrite("basic_bw.log") + self.check_trimwrite("bs_bw.log") + self.check_trimwrite("bsrange_bw.log") + self.check_trimwrite("bssplit_bw.log") + self.check_trimwrite("basic_no_rm_bw.log") + self.check_trimwrite("bs_no_rm_bw.log") + self.check_trimwrite("bsrange_no_rm_bw.log") + self.check_trimwrite("bssplit_no_rm_bw.log") # TODO make sure all offsets were touched From 9e83aa16efc28e1d20e37f3da87411aeb7644254 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Tue, 4 Oct 2022 17:20:09 -0700 Subject: [PATCH 0295/1097] test: check all offsets touched for randtrimwrite Make sure that all of the offsets are touched for the randtrimwrite test when randommap is enabled. This exposes a problem where mixed ddir workloads that use a random map may not have all blocks touched when the different data directions use different block sizes. Resolve this for now by setting the same block size for all data directions for the job named bs. Signed-off-by: Vincent Fu --- t/jobs/t0023.fio | 3 +-- t/run-fio-tests.py | 42 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/t/jobs/t0023.fio b/t/jobs/t0023.fio index 4d76b4a35f..4f0bef8902 100644 --- a/t/jobs/t0023.fio +++ b/t/jobs/t0023.fio @@ -6,7 +6,6 @@ rw=randtrimwrite log_offset=1 per_job_logs=0 randrepeat=0 -stonewall write_bw_log # Expected result: trim issued to random offset followed by write to same offset @@ -20,7 +19,7 @@ write_bw_log # block sizes 8k for both write and trim # Buggy result: something else [bs] -bs=4k,4k,8k +bs=8k,8k,8k # Expected result: trim issued to random offset followed by write to same offset # all offsets trimmed diff --git a/t/run-fio-tests.py b/t/run-fio-tests.py index 07dafc0c44..f183668aa8 100755 --- a/t/run-fio-tests.py +++ b/t/run-fio-tests.py @@ -691,9 +691,46 @@ def check_trimwrite(self, filename): prev_offset = offset + def check_all_offsets(self, filename, sectorsize, filesize): + file_data, success = self.get_file(os.path.join(self.test_dir, filename)) + if not success: + self.passed = False + self.failure_reason = " could not open {0}".format(filename) + return + + log_lines = file_data.split('\n') + + offsets = set() + + for line in log_lines: + if len(line.strip()) == 0: + continue + vals = line.split(',') + bs = int(vals[3]) + offset = int(vals[4]) + if offset % sectorsize != 0: + self.passed = False + self.failure_reason += " {0}: offset {1} not a multiple of sector size {2}".format(filename, offset, sectorsize) + break; + if bs % sectorsize != 0: + self.passed = False + self.failure_reason += " {0}: block size {1} not a multiple of sector size {2}".format(filename, bs, sectorsize) + break; + for i in range(int(bs/sectorsize)): + offsets.add(offset/sectorsize + i) + + if len(offsets) != filesize/sectorsize: + self.passed = False + self.failure_reason += " {0}: only {1} offsets touched; expected {2}".format(filename, len(offsets), filesize/sectorsize) + else: + logging.debug("{0}: {1} sectors touched".format(filename, len(offsets))) + + def check_result(self): super(FioJobTest_t0023, self).check_result() + filesize = 1024*1024 + self.check_trimwrite("basic_bw.log") self.check_trimwrite("bs_bw.log") self.check_trimwrite("bsrange_bw.log") @@ -703,7 +740,10 @@ def check_result(self): self.check_trimwrite("bsrange_no_rm_bw.log") self.check_trimwrite("bssplit_no_rm_bw.log") - # TODO make sure all offsets were touched + self.check_all_offsets("basic_bw.log", 4096, filesize) + self.check_all_offsets("bs_bw.log", 8192, filesize) + self.check_all_offsets("bsrange_bw.log", 512, filesize) + self.check_all_offsets("bssplit_bw.log", 512, filesize) class FioJobTest_iops_rate(FioJobTest): From 21a202292ad846d49e61ca9ffb8de1eab5eb3c06 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Wed, 5 Oct 2022 12:13:43 -0700 Subject: [PATCH 0296/1097] test: fix style issues in run-fio-tests.py Fix some style issues identified by pylint. Signed-off-by: Vincent Fu --- t/run-fio-tests.py | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/t/run-fio-tests.py b/t/run-fio-tests.py index f183668aa8..6833cea6e2 100755 --- a/t/run-fio-tests.py +++ b/t/run-fio-tests.py @@ -650,7 +650,7 @@ def check_result(self): class FioJobTest_t0023(FioJobTest): - """Test consists of fio test job t0023""" + """Test consists of fio test job t0023 randtrimwrite test.""" def check_trimwrite(self, filename): """Make sure that trims are followed by writes of the same size at the same offset.""" @@ -670,21 +670,25 @@ def check_trimwrite(self, filename): if prev_ddir == 1: if ddir != 2: self.passed = False - self.failure_reason += " {0}: write not preceeded by trim: {1}".format(bw_log_filename, line) + self.failure_reason += " {0}: write not preceeded by trim: {1}".format( + bw_log_filename, line) break else: if ddir != 1: self.passed = False - self.failure_reason += " {0}: trim not preceeded by write: {1}".format(bw_log_filename, line) + self.failure_reason += " {0}: trim not preceeded by write: {1}".format( + bw_log_filename, line) break else: if prev_bs != bs: self.passed = False - self.failure_reason += " {0}: block size does not match: {1}".format(bw_log_filename, line) + self.failure_reason += " {0}: block size does not match: {1}".format( + bw_log_filename, line) break if prev_offset != offset: self.passed = False - self.failure_reason += " {0}: offset does not match: {1}".format(bw_log_filename, line) + self.failure_reason += " {0}: offset does not match: {1}".format( + bw_log_filename, line) break prev_ddir = ddir prev_bs = bs @@ -692,6 +696,8 @@ def check_trimwrite(self, filename): def check_all_offsets(self, filename, sectorsize, filesize): + """Make sure all offsets were touched.""" + file_data, success = self.get_file(os.path.join(self.test_dir, filename)) if not success: self.passed = False @@ -710,20 +716,23 @@ def check_all_offsets(self, filename, sectorsize, filesize): offset = int(vals[4]) if offset % sectorsize != 0: self.passed = False - self.failure_reason += " {0}: offset {1} not a multiple of sector size {2}".format(filename, offset, sectorsize) - break; + self.failure_reason += " {0}: offset {1} not a multiple of sector size {2}".format( + filename, offset, sectorsize) + break if bs % sectorsize != 0: self.passed = False - self.failure_reason += " {0}: block size {1} not a multiple of sector size {2}".format(filename, bs, sectorsize) - break; + self.failure_reason += " {0}: block size {1} not a multiple of sector size " \ + "{2}".format(filename, bs, sectorsize) + break for i in range(int(bs/sectorsize)): offsets.add(offset/sectorsize + i) if len(offsets) != filesize/sectorsize: self.passed = False - self.failure_reason += " {0}: only {1} offsets touched; expected {2}".format(filename, len(offsets), filesize/sectorsize) + self.failure_reason += " {0}: only {1} offsets touched; expected {2}".format( + filename, len(offsets), filesize/sectorsize) else: - logging.debug("{0}: {1} sectors touched".format(filename, len(offsets))) + logging.debug("%s: %d sectors touched", filename, len(offsets)) def check_result(self): From 2d5c460058072401b1860c3a410f6ca30d95c79c Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Wed, 5 Oct 2022 12:14:25 -0700 Subject: [PATCH 0297/1097] test: add basic tests for trimwrite workloads We have some tests for randtrimwrite; it's easy enough to reuse the code to test trimwrite workloads. Signed-off-by: Vincent Fu --- t/jobs/t0024.fio | 36 ++++++++++++++++++++++++++++++++++++ t/run-fio-tests.py | 29 +++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+) create mode 100644 t/jobs/t0024.fio diff --git a/t/jobs/t0024.fio b/t/jobs/t0024.fio new file mode 100644 index 0000000000..393a2b70e1 --- /dev/null +++ b/t/jobs/t0024.fio @@ -0,0 +1,36 @@ +# trimwrite data direction tests +[global] +filesize=1M +ioengine=null +rw=trimwrite +log_offset=1 +per_job_logs=0 +randrepeat=0 +write_bw_log + +# Expected result: trim issued to sequential offsets followed by write to same offset +# all offsets touched +# block sizes match +# Buggy result: something else +[basic] + +# Expected result: trim issued to sequential offsets followed by write to same offset +# all offsets trimmed +# block sizes 8k for both write and trim +# Buggy result: something else +[bs] +bs=8k,8k,8k + +# Expected result: trim issued to sequential offsets followed by write to same offset +# all offsets trimmed +# block sizes match +# Buggy result: something else +[bsrange] +bsrange=512-4k + +# Expected result: trim issued to sequential offsets followed by write to same offset +# all offsets trimmed +# block sizes match +# Buggy result: something else +[bssplit] +bsrange=512/25:1k:25:2k:25:4k/25 diff --git a/t/run-fio-tests.py b/t/run-fio-tests.py index 6833cea6e2..a285a25cd0 100755 --- a/t/run-fio-tests.py +++ b/t/run-fio-tests.py @@ -755,6 +755,26 @@ def check_result(self): self.check_all_offsets("bssplit_bw.log", 512, filesize) +class FioJobTest_t0024(FioJobTest_t0023): + """Test consists of fio test job t0024 trimwrite test.""" + + def check_result(self): + # call FioJobTest_t0023's parent to skip checks done by t0023 + super(FioJobTest_t0023, self).check_result() + + filesize = 1024*1024 + + self.check_trimwrite("basic_bw.log") + self.check_trimwrite("bs_bw.log") + self.check_trimwrite("bsrange_bw.log") + self.check_trimwrite("bssplit_bw.log") + + self.check_all_offsets("basic_bw.log", 4096, filesize) + self.check_all_offsets("bs_bw.log", 8192, filesize) + self.check_all_offsets("bsrange_bw.log", 512, filesize) + self.check_all_offsets("bssplit_bw.log", 512, filesize) + + class FioJobTest_iops_rate(FioJobTest): """Test consists of fio test job t0009 Confirm that job0 iops == 1000 @@ -1141,6 +1161,15 @@ def cpucount4(cls): 'pre_success': None, 'requirements': [], }, + { + 'test_id': 24, + 'test_class': FioJobTest_t0024, + 'job': 't0024.fio', + 'success': SUCCESS_DEFAULT, + 'pre_job': None, + 'pre_success': None, + 'requirements': [], + }, { 'test_id': 1000, 'test_class': FioExeTest, From 114eadb2d1df6a86afdcc42a21310d8c5559e72d Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Wed, 5 Oct 2022 13:46:40 -0700 Subject: [PATCH 0298/1097] test: fix t/run-fio-tests.py style issues identified by pylint Signed-off-by: Vincent Fu --- t/run-fio-tests.py | 72 +++++++++++++++++++++++++++++----------------- 1 file changed, 45 insertions(+), 27 deletions(-) diff --git a/t/run-fio-tests.py b/t/run-fio-tests.py index a285a25cd0..d7baa13997 100755 --- a/t/run-fio-tests.py +++ b/t/run-fio-tests.py @@ -55,7 +55,7 @@ from pathlib import Path -class FioTest(object): +class FioTest(): """Base for all fio tests.""" def __init__(self, exe_path, parameters, success): @@ -427,8 +427,10 @@ def check_result(self): return iops_files = [] - for i in range(1,4): - file_data, success = self.get_file(os.path.join(self.test_dir, "{0}_iops.{1}.log".format(os.path.basename(self.fio_job), i))) + for i in range(1, 4): + filename = os.path.join(self.test_dir, "{0}_iops.{1}.log".format(os.path.basename( + self.fio_job), i)) + file_data, success = self.get_file(filename) if not success: self.failure_reason = "{0} unable to open output file,".format(self.failure_reason) @@ -448,17 +450,15 @@ def check_result(self): ratio1 = iops3/iops2 ratio2 = iops3/iops1 - logging.debug( - "sample {0}: job1 iops={1} job2 iops={2} job3 iops={3} job3/job2={4:.3f} job3/job1={5:.3f}".format( - i, iops1, iops2, iops3, ratio1, ratio2 - ) - ) + logging.debug("sample {0}: job1 iops={1} job2 iops={2} job3 iops={3} " \ + "job3/job2={4:.3f} job3/job1={5:.3f}".format(i, iops1, iops2, iops3, ratio1, + ratio2)) # test job1 and job2 succeeded to recalibrate if ratio1 < 1 or ratio1 > 3 or ratio2 < 7 or ratio2 > 13: - self.failure_reason = "{0} iops ratio mismatch iops1={1} iops2={2} iops3={3} expected r1~2 r2~10 got r1={4:.3f} r2={5:.3f},".format( - self.failure_reason, iops1, iops2, iops3, ratio1, ratio2 - ) + self.failure_reason += " iops ratio mismatch iops1={0} iops2={1} iops3={2} " \ + "expected r1~2 r2~10 got r1={3:.3f} r2={4:.3f},".format(iops1, iops2, iops3, + ratio1, ratio2) self.passed = False return @@ -478,8 +478,10 @@ def check_result(self): return iops_files = [] - for i in range(1,4): - file_data, success = self.get_file(os.path.join(self.test_dir, "{0}_iops.{1}.log".format(os.path.basename(self.fio_job), i))) + for i in range(1, 4): + filename = os.path.join(self.test_dir, "{0}_iops.{1}.log".format(os.path.basename( + self.fio_job), i)) + file_data, success = self.get_file(filename) if not success: self.failure_reason = "{0} unable to open output file,".format(self.failure_reason) @@ -501,10 +503,9 @@ def check_result(self): if ratio1 < 0.43 or ratio1 > 0.57 or ratio2 < 0.21 or ratio2 > 0.45: - self.failure_reason = "{0} iops ratio mismatch iops1={1} iops2={2} iops3={3}\ - expected r1~0.5 r2~0.33 got r1={4:.3f} r2={5:.3f},".format( - self.failure_reason, iops1, iops2, iops3, ratio1, ratio2 - ) + self.failure_reason += " iops ratio mismatch iops1={0} iops2={1} iops3={2} " \ + "expected r1~0.5 r2~0.33 got r1={3:.3f} r2={4:.3f},".format( + iops1, iops2, iops3, ratio1, ratio2) self.passed = False iops1 = iops1 + float(iops_files[0][i].split(',')[1]) @@ -512,17 +513,14 @@ def check_result(self): ratio1 = iops1/iops2 ratio2 = iops1/iops3 - logging.debug( - "sample {0}: job1 iops={1} job2 iops={2} job3 iops={3} job1/job2={4:.3f} job1/job3={5:.3f}".format( - i, iops1, iops2, iops3, ratio1, ratio2 - ) - ) + logging.debug("sample {0}: job1 iops={1} job2 iops={2} job3 iops={3} " \ + "job1/job2={4:.3f} job1/job3={5:.3f}".format(i, iops1, iops2, iops3, + ratio1, ratio2)) # test job1 and job2 succeeded to recalibrate if ratio1 < 0.43 or ratio1 > 0.57: - self.failure_reason = "{0} iops ratio mismatch iops1={1} iops2={2} expected ratio~0.5 got ratio={3:.3f},".format( - self.failure_reason, iops1, iops2, ratio1 - ) + self.failure_reason += " iops ratio mismatch iops1={0} iops2={1} expected ratio~0.5 " \ + "got ratio={2:.3f},".format(iops1, iops2, ratio1) self.passed = False return @@ -557,6 +555,11 @@ def check_result(self): bw_log_filename = os.path.join(self.test_dir, "test_bw.log") file_data, success = self.get_file(bw_log_filename) + if not success: + self.failure_reason += " unable to open output file {0}".format(bw_log_filename) + self.passed = False + return + log_lines = file_data.split('\n') prev = -4096 @@ -584,6 +587,11 @@ def check_result(self): bw_log_filename = os.path.join(self.test_dir, "test_bw.log") file_data, success = self.get_file(bw_log_filename) + if not success: + self.failure_reason += " unable to open output file {0}".format(bw_log_filename) + self.passed = False + return + log_lines = file_data.split('\n') seq_count = 0 @@ -622,6 +630,11 @@ def check_result(self): bw_log_filename = os.path.join(self.test_dir, "test_bw.log") file_data, success = self.get_file(bw_log_filename) + if not success: + self.failure_reason += " unable to open output file {0}".format(bw_log_filename) + self.passed = False + return + log_lines = file_data.split('\n') filesize = 1024*1024 @@ -646,7 +659,7 @@ def check_result(self): if len(offsets) == filesize/bs: self.passed = False - self.failure_reason += " no duplicate offsets found with norandommap=1".format(len(offsets)) + self.failure_reason += " no duplicate offsets found with norandommap=1" class FioJobTest_t0023(FioJobTest): @@ -657,6 +670,11 @@ def check_trimwrite(self, filename): bw_log_filename = os.path.join(self.test_dir, filename) file_data, success = self.get_file(bw_log_filename) + if not success: + self.failure_reason += " unable to open output file {0}".format(bw_log_filename) + self.passed = False + return + log_lines = file_data.split('\n') prev_ddir = 1 @@ -803,7 +821,7 @@ def check_result(self): self.passed = False -class Requirements(object): +class Requirements(): """Requirements consists of multiple run environment characteristics. These are to determine if a particular test can be run""" From 0f5234b4d75c4415d87903d26cdd79ee380c7f49 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Wed, 5 Oct 2022 13:53:20 -0700 Subject: [PATCH 0299/1097] test: improve run-fio-tests.py file open method We have several instances where we try to open a file and fail the test if accessing the file fails. Encapsulate all of this within a single method to reduce code duplication. Signed-off-by: Vincent Fu --- t/run-fio-tests.py | 63 +++++++++++++++++++++------------------------- 1 file changed, 29 insertions(+), 34 deletions(-) diff --git a/t/run-fio-tests.py b/t/run-fio-tests.py index d7baa13997..df87ae721d 100755 --- a/t/run-fio-tests.py +++ b/t/run-fio-tests.py @@ -286,6 +286,19 @@ def get_file(cls, filename): return file_data, success + def get_file_fail(self, filename): + """Safely read a file and fail the test upon error.""" + file_data = None + + try: + with open(filename, "r") as output_file: + file_data = output_file.read() + except OSError: + self.failure_reason += " unable to read file {0}".format(filename) + self.passed = False + + return file_data + def check_result(self): """Check fio job results.""" @@ -302,10 +315,8 @@ def check_result(self): if 'json' not in self.output_format: return - file_data, success = self.get_file(os.path.join(self.test_dir, self.fio_output)) - if not success: - self.failure_reason = "{0} unable to open output file,".format(self.failure_reason) - self.passed = False + file_data = self.get_file_fail(os.path.join(self.test_dir, self.fio_output)) + if not file_data: return # @@ -430,11 +441,8 @@ def check_result(self): for i in range(1, 4): filename = os.path.join(self.test_dir, "{0}_iops.{1}.log".format(os.path.basename( self.fio_job), i)) - file_data, success = self.get_file(filename) - - if not success: - self.failure_reason = "{0} unable to open output file,".format(self.failure_reason) - self.passed = False + file_data = self.get_file_fail(filename) + if not file_data: return iops_files.append(file_data.splitlines()) @@ -481,11 +489,8 @@ def check_result(self): for i in range(1, 4): filename = os.path.join(self.test_dir, "{0}_iops.{1}.log".format(os.path.basename( self.fio_job), i)) - file_data, success = self.get_file(filename) - - if not success: - self.failure_reason = "{0} unable to open output file,".format(self.failure_reason) - self.passed = False + file_data = self.get_file_fail(filename) + if not file_data: return iops_files.append(file_data.splitlines()) @@ -554,10 +559,8 @@ def check_result(self): super(FioJobTest_t0019, self).check_result() bw_log_filename = os.path.join(self.test_dir, "test_bw.log") - file_data, success = self.get_file(bw_log_filename) - if not success: - self.failure_reason += " unable to open output file {0}".format(bw_log_filename) - self.passed = False + file_data = self.get_file_fail(bw_log_filename) + if not file_data: return log_lines = file_data.split('\n') @@ -586,10 +589,8 @@ def check_result(self): super(FioJobTest_t0020, self).check_result() bw_log_filename = os.path.join(self.test_dir, "test_bw.log") - file_data, success = self.get_file(bw_log_filename) - if not success: - self.failure_reason += " unable to open output file {0}".format(bw_log_filename) - self.passed = False + file_data = self.get_file_fail(bw_log_filename) + if not file_data: return log_lines = file_data.split('\n') @@ -629,10 +630,8 @@ def check_result(self): super(FioJobTest_t0022, self).check_result() bw_log_filename = os.path.join(self.test_dir, "test_bw.log") - file_data, success = self.get_file(bw_log_filename) - if not success: - self.failure_reason += " unable to open output file {0}".format(bw_log_filename) - self.passed = False + file_data = self.get_file_fail(bw_log_filename) + if not file_data: return log_lines = file_data.split('\n') @@ -669,10 +668,8 @@ def check_trimwrite(self, filename): """Make sure that trims are followed by writes of the same size at the same offset.""" bw_log_filename = os.path.join(self.test_dir, filename) - file_data, success = self.get_file(bw_log_filename) - if not success: - self.failure_reason += " unable to open output file {0}".format(bw_log_filename) - self.passed = False + file_data = self.get_file_fail(bw_log_filename) + if not file_data: return log_lines = file_data.split('\n') @@ -716,10 +713,8 @@ def check_trimwrite(self, filename): def check_all_offsets(self, filename, sectorsize, filesize): """Make sure all offsets were touched.""" - file_data, success = self.get_file(os.path.join(self.test_dir, filename)) - if not success: - self.passed = False - self.failure_reason = " could not open {0}".format(filename) + file_data = self.get_file_fail(os.path.join(self.test_dir, filename)) + if not file_data: return log_lines = file_data.split('\n') From 7aeb498947d6f2d6c96b571520f12b80365fa8a1 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Wed, 5 Oct 2022 18:34:41 -0400 Subject: [PATCH 0300/1097] test: make t0014.fio time_based Make the job time_based to ensure we run for long enough to obtain the expected log lines. If the device is too fast fio will consume 100G and write fewer than the expected log lines. Signed-off-by: Vincent Fu --- t/jobs/t0014.fio | 1 + 1 file changed, 1 insertion(+) diff --git a/t/jobs/t0014.fio b/t/jobs/t0014.fio index d9b456516e..eb13478ba5 100644 --- a/t/jobs/t0014.fio +++ b/t/jobs/t0014.fio @@ -17,6 +17,7 @@ flow_id=1 thread log_avg_msec=1000 write_iops_log=t0014.fio +time_based [flow1] flow=1 From b19c5ee1357ffb74f4de57b1617364bbbaacf1a0 Mon Sep 17 00:00:00 2001 From: Pankaj Raghav Date: Fri, 7 Oct 2022 14:05:28 +0200 Subject: [PATCH 0301/1097] examples: uring-cmd-zoned: expand the reasoning behind QD1 Expand the reasoning behind using QD1 for zoned devices with io_uring_cmd engine. Signed-off-by: Pankaj Raghav Signed-off-by: Vincent Fu --- examples/uring-cmd-zoned.fio | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/examples/uring-cmd-zoned.fio b/examples/uring-cmd-zoned.fio index 58e8f79ec5..89be61beae 100644 --- a/examples/uring-cmd-zoned.fio +++ b/examples/uring-cmd-zoned.fio @@ -1,7 +1,11 @@ # io_uring_cmd I/O engine for nvme-ns generic zoned character device # -# NOTE: with write workload iodepth must be set to 1 as there is no IO -# scheduler. +# NOTE: +# Regular writes against a zone should be limited to QD1, as the device can +# reorder the requests. +# +# As the passthrough path do not use an IO scheduler (such as mq-deadline), +# the queue depth should be limited to 1 to avoid zone invalid writes. [global] filename=/dev/ng0n1 From 07f78c37833730594778fb5684ac6ec40d0289f8 Mon Sep 17 00:00:00 2001 From: Ankit Kumar Date: Tue, 11 Oct 2022 16:29:35 +0530 Subject: [PATCH 0302/1097] engines/io_uring: set coop taskrun, single issuer and defer taskrun Add missing changes to io_uring_cmd I/O engine for COOP_TASKRUN This was introduced for io_uring in commit 4d22c103 Add missing changes to io_uring_cmd I/O engine to set single issuer and defer taskrun. This was introduced for io_uring in commit e453f369 Signed-off-by: Ankit Kumar Link: https://lore.kernel.org/r/20221011105935.10455-2-ankit.kumar@samsung.com Signed-off-by: Jens Axboe --- engines/io_uring.c | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/engines/io_uring.c b/engines/io_uring.c index c679177fb4..6906e0a473 100644 --- a/engines/io_uring.c +++ b/engines/io_uring.c @@ -889,9 +889,30 @@ static int fio_ioring_cmd_queue_init(struct thread_data *td) p.flags |= IORING_SETUP_CQSIZE; p.cq_entries = depth; + /* + * Setup COOP_TASKRUN as we don't need to get IPI interrupted for + * completing IO operations. + */ + p.flags |= IORING_SETUP_COOP_TASKRUN; + + /* + * io_uring is always a single issuer, and we can defer task_work + * runs until we reap events. + */ + p.flags |= IORING_SETUP_SINGLE_ISSUER | IORING_SETUP_DEFER_TASKRUN; + retry: ret = syscall(__NR_io_uring_setup, depth, &p); if (ret < 0) { + if (errno == EINVAL && p.flags & IORING_SETUP_DEFER_TASKRUN) { + p.flags &= ~IORING_SETUP_DEFER_TASKRUN; + p.flags &= ~IORING_SETUP_SINGLE_ISSUER; + goto retry; + } + if (errno == EINVAL && p.flags & IORING_SETUP_COOP_TASKRUN) { + p.flags &= ~IORING_SETUP_COOP_TASKRUN; + goto retry; + } if (errno == EINVAL && p.flags & IORING_SETUP_CQSIZE) { p.flags &= ~IORING_SETUP_CQSIZE; goto retry; From 705c378006b864826f248c69f38c69d890ec87c4 Mon Sep 17 00:00:00 2001 From: Nicholas Roma Date: Sat, 15 Oct 2022 20:41:52 +0900 Subject: [PATCH 0303/1097] Update to README Typo and grammatical fix for README Signed-off-by: Nick Roma --- README.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.rst b/README.rst index 79582deac2..bcd08ec9ea 100644 --- a/README.rst +++ b/README.rst @@ -150,7 +150,7 @@ GNU make isn't the default, type ``gmake`` instead of ``make``. Configure will print the enabled options. Note that on Linux based platforms, the libaio development packages must be installed to use the libaio -engine. Depending on distro, it is usually called libaio-devel or libaio-dev. +engine. Depending on the distro, it is usually called libaio-devel or libaio-dev. For gfio, gtk 2.18 (or newer), associated glib threads, and cairo are required to be installed. gfio isn't built automatically and can be enabled with a @@ -170,7 +170,7 @@ configure. Windows ~~~~~~~ -The minimum versions of Windows for building/runing fio are Windows 7/Windows +The minimum versions of Windows for building/running fio are Windows 7/Windows Server 2008 R2. On Windows, Cygwin (https://www.cygwin.com/) is required in order to build fio. To create an MSI installer package install WiX from https://wixtoolset.org and run :file:`dobuild.cmd` from the :file:`os/windows` @@ -224,7 +224,7 @@ implemented, I'd be happy to take patches for that. An example of that is disk utility statistics and (I think) huge page support, support for that does exist in FreeBSD/Solaris. -Fio uses pthread mutexes for signalling and locking and some platforms do not +Fio uses pthread mutexes for signaling and locking and some platforms do not support process shared pthread mutexes. As a result, on such platforms only threads are supported. This could be fixed with sysv ipc locking or other locking alternatives. From 349d5f6c30cc7be60da8878d0b0078697f356897 Mon Sep 17 00:00:00 2001 From: Alexey Dobriyan Date: Sat, 15 Oct 2022 22:41:08 +0300 Subject: [PATCH 0304/1097] fio: warn about "ioengine=psync" and "iodepth >= 1" Some users are using both ioengine=psync and iodepth=2+ without blinking in front of the millions: https://youtu.be/coShLkCriXc?t=840 https://youtu.be/coShLkCriXc?t=890 Help them with helpful message. Signed-off-by: Alexey Dobriyan Link: https://lore.kernel.org/r/Y0sM1PJKEzfv47ZB@localhost.localdomain [axboe: style fixup] Signed-off-by: Jens Axboe --- backend.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/backend.c b/backend.c index ec535bcc47..d8f4f2a508 100644 --- a/backend.c +++ b/backend.c @@ -1791,6 +1791,11 @@ static void *thread_main(void *data) if (td_io_init(td)) goto err; + if (td_ioengine_flagged(td, FIO_SYNCIO) && td->o.iodepth > 1) { + log_info("note: both iodepth >= 1 and synchronous I/O engine " + "are selected, queue depth will be capped at 1\n"); + } + if (init_io_u(td)) goto err; From 842fb796d249ef4c2b97a54442aa8055668888a0 Mon Sep 17 00:00:00 2001 From: Dmitry Fomichev Date: Sun, 16 Oct 2022 10:43:08 +0900 Subject: [PATCH 0305/1097] t/zbd: fix max_open_zones determination in tests The test script erroneously falls back to using libzbc for finding out the max_open_zones setting if the device being tested is not a SCSI device. This causes false positives if libzbc is not installed in the test system. To fix, unless the option to use libzbc is explicitly set, set this value to 0 for non-SCSI devices to let fio find the actual max_open_zones value using the system block interface. Signed-off-by: Dmitry Fomichev Link: https://lore.kernel.org/r/20221016014309.53682-1-dmitry.fomichev@wdc.com Signed-off-by: Jens Axboe --- t/zbd/functions | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/t/zbd/functions b/t/zbd/functions index 7cff18fd18..812320f529 100644 --- a/t/zbd/functions +++ b/t/zbd/functions @@ -230,9 +230,11 @@ max_open_zones() { echo ${max_nr_open_zones} } fi - else + elif [ -n "${use_libzbc}" ]; then ${zbc_report_zones} "$dev" | sed -n 's/^[[:blank:]]*Maximum number of open sequential write required zones:[[:blank:]]*//p' + else + echo 0 fi } From 0360d61fbfcc1f07bcdc16672f5040f8cf49681f Mon Sep 17 00:00:00 2001 From: Dmitry Fomichev Date: Sun, 16 Oct 2022 10:43:09 +0900 Subject: [PATCH 0306/1097] t/zbd: add a CLI option to force io_uring Many modern Linux distros don't include libaio-dev or libaio-devel package by default and this leads to libaio ioengine being unavailable in fio instances built at such systems. Approximately one third of the test cases in the fio test script for zoned block devices, t/zbd/test-zbd-support, use libaio ioengine. In order to allow users to run the entire set of ZBD tests without libaio, introduce a new command line option, -u, to the test script. When this option is added to the script command line, all tests that normally use libaio will be modified to use io_uring ioengine. Signed-off-by: Dmitry Fomichev Link: https://lore.kernel.org/r/20221016014309.53682-2-dmitry.fomichev@wdc.com Signed-off-by: Jens Axboe --- t/zbd/test-zbd-support | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/t/zbd/test-zbd-support b/t/zbd/test-zbd-support index d4aaa81322..cdc03f281b 100755 --- a/t/zbd/test-zbd-support +++ b/t/zbd/test-zbd-support @@ -17,6 +17,7 @@ usage() { echo -e "\t-t Run only a single test case with specified number" echo -e "\t-q Quit the test run after any failed test" echo -e "\t-z Run fio with debug=zbd option" + echo -e "\t-u Use io_uring ioengine in place of libaio" } max() { @@ -38,6 +39,8 @@ min() { ioengine() { if [ -n "$use_libzbc" ]; then echo -n "--ioengine=libzbc" + elif [ "$1" = "libaio" -a -n "$force_io_uring" ]; then + echo -n "--ioengine=io_uring" else echo -n "--ioengine=$1" fi @@ -1275,6 +1278,7 @@ use_libzbc= zbd_debug= max_open_zones_opt= quit_on_err= +force_io_uring= while [ "${1#-}" != "$1" ]; do case "$1" in @@ -1292,6 +1296,7 @@ while [ "${1#-}" != "$1" ]; do shift;; -q) quit_on_err=1; shift;; -z) zbd_debug=1; shift;; + -u) force_io_uring=1; shift;; --) shift; break;; *) usage; exit 1;; esac @@ -1302,6 +1307,11 @@ if [ $# != 1 ]; then exit 1 fi +if [ -n "$use_libzbc" -a -n "$force_io_uring" ]; then + echo "Please specify only one of -l and -u options" + exit 1 +fi + # shellcheck source=functions source "$(dirname "$0")/functions" || exit $? From 2f160e0c8848bab566427a11eee116d8e834bcf0 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Tue, 18 Oct 2022 10:43:54 -0400 Subject: [PATCH 0307/1097] test: change GitHub actions checkout from v2 to v3 GitHub actions is reporting a warning that actions/checkout@v2 includes the deprecated Node.js 12. So switch to actions/checkout@v3 which uses Node.js 16. As far as I can tell this has no impact on our workflow. For details see: https://github.blog/changelog/2022-09-22-github-actions-all-actions-will-begin-running-on-node16-instead-of-node12/ https://github.com/actions/checkout Signed-off-by: Vincent Fu --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bdc4db85d9..1b8c070166 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -39,7 +39,7 @@ jobs: steps: - name: Checkout repo - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Install dependencies run: ./ci/actions-install.sh - name: Build From 57a5412fa68e5a83ebc103566f037e220bc43808 Mon Sep 17 00:00:00 2001 From: "Brian T. Smith" Date: Mon, 26 Sep 2022 12:59:18 -0500 Subject: [PATCH 0308/1097] fix configure probe for libcufile The libcufile probe now requires -ldl passsed to the linker. Signed-off-by: Brian T. Smith --- configure | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/configure b/configure index 546541a295..24c599a8d6 100755 --- a/configure +++ b/configure @@ -2723,9 +2723,9 @@ int main(int argc, char* argv[]) { return 0; } EOF - if compile_prog "" "-lcuda -lcudart -lcufile" "libcufile"; then + if compile_prog "" "-lcuda -lcudart -lcufile -ldl" "libcufile"; then libcufile="yes" - LIBS="-lcuda -lcudart -lcufile $LIBS" + LIBS="-lcuda -lcudart -lcufile -ldl $LIBS" else if test "$libcufile" = "yes" ; then feature_not_found "libcufile" "" From f1c6bd61ccd71f2298038557f94fe30dc9236c7d Mon Sep 17 00:00:00 2001 From: "Brian T. Smith" Date: Tue, 18 Oct 2022 13:14:51 -0500 Subject: [PATCH 0309/1097] libcufile: use generic_get_file_size In libcufile ioengine_ops, set get_file_size to generic_get_file_size. Fixes #1213. Signed-off-by: Brian T. Smith --- engines/libcufile.c | 1 + 1 file changed, 1 insertion(+) diff --git a/engines/libcufile.c b/engines/libcufile.c index e575b7864d..2bedf26136 100644 --- a/engines/libcufile.c +++ b/engines/libcufile.c @@ -606,6 +606,7 @@ FIO_STATIC struct ioengine_ops ioengine = { .version = FIO_IOOPS_VERSION, .init = fio_libcufile_init, .queue = fio_libcufile_queue, + .get_file_size = generic_get_file_size, .open_file = fio_libcufile_open_file, .close_file = fio_libcufile_close_file, .iomem_alloc = fio_libcufile_iomem_alloc, From d72244761b2230fbb2d6eaec59cdedd3ea651d4f Mon Sep 17 00:00:00 2001 From: Ankit Kumar Date: Fri, 21 Oct 2022 12:19:00 +0530 Subject: [PATCH 0310/1097] stat: fix segfault with fio option --bandwidth-log The log_params for aggregate read, write and trim only specify log type. As a result the io_log doesn't have the relevant thread_data structure. With fio option --bandwidth-log this results in segmentation fault. Add a check and use DEF_LOG_ENTRIES for such case. Fixes: 0a852a50 ("fio: Introduce the log_entries option") Signed-off-by: Ankit Kumar [vkf: added Fixes tag, lightly edited commit message] Signed-off-by: Vincent Fu --- stat.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/stat.c b/stat.c index 949af5edd4..b963973a58 100644 --- a/stat.c +++ b/stat.c @@ -2870,7 +2870,10 @@ static struct io_logs *get_new_log(struct io_log *iolog) * forever */ if (!iolog->cur_log_max) { - new_samples = iolog->td->o.log_entries; + if (iolog->td) + new_samples = iolog->td->o.log_entries; + else + new_samples = DEF_LOG_ENTRIES; } else { new_samples = iolog->cur_log_max * 2; if (new_samples > MAX_LOG_ENTRIES) From 40f61ec7193682a332587095f76ff8f1293efb2a Mon Sep 17 00:00:00 2001 From: mayuanpeng Date: Fri, 21 Oct 2022 20:18:13 +0800 Subject: [PATCH 0311/1097] cpus_allowed: use __NRPROCESSORS_CONF instead of __SC_NPROCESSORS_ONLN for non-sequential CPU ids When disabling SMT on some systems, the ID of some available CPU may be larger than the value of sysconf(_SC_NPROCESSORS_ONLN). Without this patch, fio complains that the expected CPU ID is invalid. Here's an example from my server: $ ./fio --cpus_allowed=14 --ioengine=libaio --direct=1 --name=test --numjobs=1 --blocksize=128k --iodepth=1 --rw=read --filename=/dev/nvme0n1 fio: CPU 14 too large (max=11) fio: failed parsing cpus_allowed=14 System information: $ lscpu Architecture: x86_64 CPU op-mode(s): 32-bit, 64-bit Byte Order: Little Endian CPU(s): 20 On-line CPU(s) list: 0,2,4,6,8,10,12,14,16-19 Off-line CPU(s) list: 1,3,5,7,9,11,13,15 ... Model name: 12th Gen Intel(R) Core(TM) i7-12700 BIOS Model name: 12th Gen Intel(R) Core(TM) i7-12700 ... $ uname -a Linux localhost.localdomain 4.18.0-348.el8.x86_64 #1 SMP Tue Oct 19 15:14:17 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux $ cat /etc/redhat-release CentOS Linux release 8.5.2111 $ cat /proc/cmdline BOOT_IMAGE=(hd0,gpt2)/vmlinuz-4.18.0-348.el8.x86_64 root=/dev/mapper/cl-root ro nosmt isolcpus=0,2,4,6,8,10,12,14 crashkernel=auto resume=/dev/mapper/cl-swap rd.lvm.lv=cl/root rd.lvm.lv=cl/swap rhgb quiet $ cat test.c #include #include int main(int argc, char *argv[]) { printf("_SC_NPROCESSORS_ONLN=%ld _SC_NPROCESSORS_CONF=%ld\n", sysconf(_SC_NPROCESSORS_ONLN), sysconf(_SC_NPROCESSORS_CONF)); } $ gcc test.c $ ./a.out _SC_NPROCESSORS_ONLN=12 _SC_NPROCESSORS_CONF=20 Signed-off-by: mayuanpeng --- gettime.c | 2 +- idletime.c | 2 +- options.c | 8 ++++---- os/os-hpux.h | 4 ++-- os/os-linux.h | 8 -------- os/os-solaris.h | 2 +- os/os-windows.h | 5 +---- os/os.h | 8 ++++---- os/windows/cpu-affinity.c | 6 ------ os/windows/posix.c | 16 ++++++++++++---- server.c | 2 +- t/dedupe.c | 2 +- 12 files changed, 28 insertions(+), 37 deletions(-) diff --git a/gettime.c b/gettime.c index 8993be1688..bc66a3ac9f 100644 --- a/gettime.c +++ b/gettime.c @@ -671,7 +671,7 @@ static int clock_cmp(const void *p1, const void *p2) int fio_monotonic_clocktest(int debug) { struct clock_thread *cthreads; - unsigned int seen_cpus, nr_cpus = cpus_online(); + unsigned int seen_cpus, nr_cpus = cpus_configured(); struct clock_entry *entries; unsigned long nr_entries, tentries, failed = 0; struct clock_entry *prev, *this; diff --git a/idletime.c b/idletime.c index fc1df8e9d0..90ed77ea6e 100644 --- a/idletime.c +++ b/idletime.c @@ -189,7 +189,7 @@ void fio_idle_prof_init(void) pthread_condattr_t cattr; struct idle_prof_thread *ipt; - ipc.nr_cpus = cpus_online(); + ipc.nr_cpus = cpus_configured(); ipc.status = IDLE_PROF_STATUS_OK; if (ipc.opt == IDLE_PROF_OPT_NONE) diff --git a/options.c b/options.c index a668b0e475..9e4d8cd1a9 100644 --- a/options.c +++ b/options.c @@ -627,7 +627,7 @@ static int str_exitall_cb(void) int fio_cpus_split(os_cpu_mask_t *mask, unsigned int cpu_index) { unsigned int i, index, cpus_in_mask; - const long max_cpu = cpus_online(); + const long max_cpu = cpus_configured(); cpus_in_mask = fio_cpu_count(mask); if (!cpus_in_mask) @@ -666,7 +666,7 @@ static int str_cpumask_cb(void *data, unsigned long long *val) return 1; } - max_cpu = cpus_online(); + max_cpu = cpus_configured(); for (i = 0; i < sizeof(int) * 8; i++) { if ((1 << i) & *val) { @@ -702,7 +702,7 @@ static int set_cpus_allowed(struct thread_data *td, os_cpu_mask_t *mask, strip_blank_front(&str); strip_blank_end(str); - max_cpu = cpus_online(); + max_cpu = cpus_configured(); while ((cpu = strsep(&str, ",")) != NULL) { char *str2, *cpu2; @@ -5305,7 +5305,7 @@ void fio_keywords_init(void) sprintf(buf, "%llu", mb_memory); fio_keywords[1].replace = strdup(buf); - l = cpus_online(); + l = cpus_configured(); sprintf(buf, "%lu", l); fio_keywords[2].replace = strdup(buf); } diff --git a/os/os-hpux.h b/os/os-hpux.h index a80cb2bc47..9f3d76f507 100644 --- a/os/os-hpux.h +++ b/os/os-hpux.h @@ -88,9 +88,9 @@ static inline unsigned long long os_phys_mem(void) return ret; } -#define FIO_HAVE_CPU_ONLINE_SYSCONF +#define FIO_HAVE_CPU_CONF_SYSCONF -static inline unsigned int cpus_online(void) +static inline unsigned int cpus_configured(void) { return mpctl(MPC_GETNUMSPUS, 0, NULL); } diff --git a/os/os-linux.h b/os/os-linux.h index 831f0ad047..bbb1f27c82 100644 --- a/os/os-linux.h +++ b/os/os-linux.h @@ -251,14 +251,6 @@ static inline int arch_cache_line_size(void) return atoi(size); } -#ifdef __powerpc64__ -#define FIO_HAVE_CPU_ONLINE_SYSCONF -static inline unsigned int cpus_online(void) -{ - return sysconf(_SC_NPROCESSORS_CONF); -} -#endif - static inline unsigned long long get_fs_free_size(const char *path) { unsigned long long ret; diff --git a/os/os-solaris.h b/os/os-solaris.h index ea1f081c89..60d4c1eca4 100644 --- a/os/os-solaris.h +++ b/os/os-solaris.h @@ -119,7 +119,7 @@ static inline int fio_set_odirect(struct fio_file *f) static inline bool fio_cpu_isset(os_cpu_mask_t *mask, int cpu) { - const unsigned int max_cpus = sysconf(_SC_NPROCESSORS_ONLN); + const unsigned int max_cpus = sysconf(_SC_NPROCESSORS_CONF); unsigned int num_cpus; processorid_t *cpus; bool ret; diff --git a/os/os-windows.h b/os/os-windows.h index 510b8143db..12f3348611 100644 --- a/os/os-windows.h +++ b/os/os-windows.h @@ -44,7 +44,7 @@ #define fio_swap64(x) _byteswap_uint64(x) #define _SC_PAGESIZE 0x1 -#define _SC_NPROCESSORS_ONLN 0x2 +#define _SC_NPROCESSORS_CONF 0x2 #define _SC_PHYS_PAGES 0x4 #define SA_RESTART 0 @@ -219,9 +219,6 @@ static inline int fio_mkdir(const char *path, mode_t mode) { return 0; } -#define FIO_HAVE_CPU_ONLINE_SYSCONF -unsigned int cpus_online(void); - int first_set_cpu(os_cpu_mask_t *cpumask); int fio_setaffinity(int pid, os_cpu_mask_t cpumask); int fio_cpuset_init(os_cpu_mask_t *mask); diff --git a/os/os.h b/os/os.h index aba6813f23..a6fde1fd27 100644 --- a/os/os.h +++ b/os/os.h @@ -352,10 +352,10 @@ static inline unsigned long long get_fs_free_size(const char *path) } #endif -#ifndef FIO_HAVE_CPU_ONLINE_SYSCONF -static inline unsigned int cpus_online(void) +#ifndef FIO_HAVE_CPU_CONF_SYSCONF +static inline unsigned int cpus_configured(void) { - return sysconf(_SC_NPROCESSORS_ONLN); + return sysconf(_SC_NPROCESSORS_CONF); } #endif @@ -363,7 +363,7 @@ static inline unsigned int cpus_online(void) #ifdef FIO_HAVE_CPU_AFFINITY static inline int CPU_COUNT(os_cpu_mask_t *mask) { - int max_cpus = cpus_online(); + int max_cpus = cpus_configured(); int nr_cpus, i; for (i = 0, nr_cpus = 0; i < max_cpus; i++) diff --git a/os/windows/cpu-affinity.c b/os/windows/cpu-affinity.c index 7601970fc7..8f3d6a76b4 100644 --- a/os/windows/cpu-affinity.c +++ b/os/windows/cpu-affinity.c @@ -2,12 +2,6 @@ #include -/* Return all processors regardless of processor group */ -unsigned int cpus_online(void) -{ - return GetActiveProcessorCount(ALL_PROCESSOR_GROUPS); -} - static void print_mask(os_cpu_mask_t *cpumask) { for (int i = 0; i < FIO_CPU_MASK_ROWS; i++) diff --git a/os/windows/posix.c b/os/windows/posix.c index a3a6c89fd0..a47223daaf 100644 --- a/os/windows/posix.c +++ b/os/windows/posix.c @@ -216,10 +216,18 @@ long sysconf(int name) MEMORYSTATUSEX status; switch (name) { - case _SC_NPROCESSORS_ONLN: - val = GetNumLogicalProcessors(); + case _SC_NPROCESSORS_CONF: + /* + * Using GetMaximumProcessorCount introduces a problem in + * gettime.c because Windows does not have + * fio_get_thread_affinity. Log sample (see #1479): + * + * CPU mask contains processor beyond last active processor index (2) + * clock setaffinity failed: No error + */ + val = GetActiveProcessorCount(ALL_PROCESSOR_GROUPS); if (val == -1) - log_err("sysconf(_SC_NPROCESSORS_ONLN) failed\n"); + log_err("sysconf(_SC_NPROCESSORS_CONF) failed\n"); break; @@ -1201,4 +1209,4 @@ HANDLE windows_handle_connection(HANDLE hjob, int sk) DisconnectNamedPipe(hpipe); CloseHandle(hpipe); return ret; -} \ No newline at end of file +} diff --git a/server.c b/server.c index b453be5fc3..b869d38727 100644 --- a/server.c +++ b/server.c @@ -999,7 +999,7 @@ static int handle_probe_cmd(struct fio_net_cmd *cmd) .os = FIO_OS, .arch = FIO_ARCH, .bpp = sizeof(void *), - .cpus = __cpu_to_le32(cpus_online()), + .cpus = __cpu_to_le32(cpus_configured()), }; dprint(FD_NET, "server: sending probe reply\n"); diff --git a/t/dedupe.c b/t/dedupe.c index d21e96f4d9..02e52b742e 100644 --- a/t/dedupe.c +++ b/t/dedupe.c @@ -688,7 +688,7 @@ int main(int argc, char *argv[]) use_bloom = 0; if (!num_threads) - num_threads = cpus_online(); + num_threads = cpus_configured(); if (argc == optind) return usage(argv); From 191d6634e8a692bef15143715c88920987ecaa89 Mon Sep 17 00:00:00 2001 From: Shin'ichiro Kawasaki Date: Thu, 20 Oct 2022 15:38:51 +0900 Subject: [PATCH 0312/1097] verify: fix bytes_done accounting of experimental verify The commit 55312f9f5572 ("Add ->bytes_done[] to struct thread_data") moved bytes_done[] on stack to struct thread_data. However, this unified two bytes_done[] in do_io() and do_verify() stacks into single td->bytes_done[]. This caused wrong condition check in do_verify() in experimental verify path since td->bytes_done[] holds values for do_io() not for do_verify(). This caused unexpected loop break in do_verify() and verify read skip when experimental_verify=1 option is specified. To fix this, add bytes_verified to struct thread_data for do_verify() in same manner as bytes_done[] for do_io(). Introduce a helper function io_u_update_bytes_done() to factor out same code for bytes_done[] and bytes_verified[]. Fixes: 55312f9f5572 ("Add ->bytes_done[] to struct thread_data") Signed-off-by: Shin'ichiro Kawasaki Signed-off-by: Vincent Fu --- backend.c | 2 +- fio.h | 1 + io_u.c | 23 +++++++++++++++++------ libfio.c | 1 + rate-submit.c | 2 ++ 5 files changed, 22 insertions(+), 7 deletions(-) diff --git a/backend.c b/backend.c index d8f4f2a508..15c6e0b3c0 100644 --- a/backend.c +++ b/backend.c @@ -682,7 +682,7 @@ static void do_verify(struct thread_data *td, uint64_t verify_bytes) break; } } else { - if (ddir_rw_sum(td->bytes_done) + td->o.rw_min_bs > verify_bytes) + if (td->bytes_verified + td->o.rw_min_bs > verify_bytes) break; while ((io_u = get_io_u(td)) != NULL) { diff --git a/fio.h b/fio.h index de7eca79cb..0592a4c3dc 100644 --- a/fio.h +++ b/fio.h @@ -370,6 +370,7 @@ struct thread_data { uint64_t zone_bytes; struct fio_sem *sem; uint64_t bytes_done[DDIR_RWDIR_CNT]; + uint64_t bytes_verified; uint64_t *thinktime_blocks_counter; struct timespec last_thinktime; diff --git a/io_u.c b/io_u.c index 91f1a3584d..8035f4b725 100644 --- a/io_u.c +++ b/io_u.c @@ -2121,13 +2121,26 @@ static void ios_completed(struct thread_data *td, } } +static void io_u_update_bytes_done(struct thread_data *td, + struct io_completion_data *icd) +{ + int ddir; + + if (td->runstate == TD_VERIFYING) { + td->bytes_verified += icd->bytes_done[DDIR_READ]; + return; + } + + for (ddir = 0; ddir < DDIR_RWDIR_CNT; ddir++) + td->bytes_done[ddir] += icd->bytes_done[ddir]; +} + /* * Complete a single io_u for the sync engines. */ int io_u_sync_complete(struct thread_data *td, struct io_u *io_u) { struct io_completion_data icd; - int ddir; init_icd(td, &icd, 1); io_completed(td, &io_u, &icd); @@ -2140,8 +2153,7 @@ int io_u_sync_complete(struct thread_data *td, struct io_u *io_u) return -1; } - for (ddir = 0; ddir < DDIR_RWDIR_CNT; ddir++) - td->bytes_done[ddir] += icd.bytes_done[ddir]; + io_u_update_bytes_done(td, &icd); return 0; } @@ -2153,7 +2165,7 @@ int io_u_queued_complete(struct thread_data *td, int min_evts) { struct io_completion_data icd; struct timespec *tvp = NULL; - int ret, ddir; + int ret; struct timespec ts = { .tv_sec = 0, .tv_nsec = 0, }; dprint(FD_IO, "io_u_queued_complete: min=%d\n", min_evts); @@ -2179,8 +2191,7 @@ int io_u_queued_complete(struct thread_data *td, int min_evts) return -1; } - for (ddir = 0; ddir < DDIR_RWDIR_CNT; ddir++) - td->bytes_done[ddir] += icd.bytes_done[ddir]; + io_u_update_bytes_done(td, &icd); return ret; } diff --git a/libfio.c b/libfio.c index 1a8917768b..ac5219744e 100644 --- a/libfio.c +++ b/libfio.c @@ -94,6 +94,7 @@ static void reset_io_counters(struct thread_data *td, int all) td->rate_next_io_time[ddir] = 0; td->last_usec[ddir] = 0; } + td->bytes_verified = 0; } td->zone_bytes = 0; diff --git a/rate-submit.c b/rate-submit.c index 268356d17a..2fe768c0be 100644 --- a/rate-submit.c +++ b/rate-submit.c @@ -263,6 +263,8 @@ static void sum_ddir(struct thread_data *dst, struct thread_data *src, sum_val(&dst->this_io_blocks[ddir], &src->this_io_blocks[ddir]); sum_val(&dst->this_io_bytes[ddir], &src->this_io_bytes[ddir]); sum_val(&dst->bytes_done[ddir], &src->bytes_done[ddir]); + if (ddir == DDIR_READ) + sum_val(&dst->bytes_verified, &src->bytes_verified); pthread_double_unlock(&dst->io_wq.stat_lock, &src->io_wq.stat_lock); } From 28921bc06ceb731efd6b363d3f54c2d76bd962e9 Mon Sep 17 00:00:00 2001 From: Shin'ichiro Kawasaki Date: Thu, 20 Oct 2022 15:38:52 +0900 Subject: [PATCH 0313/1097] verify: fix numberio accounting of experimental verify As for non-experimental verify, numberio is compared between the numbers saved in metadata and written data header. As for experimental verify, the metadata is not available. Instead of numberio in metadata, it refers td->io_issues[] as the numberio value for the comparison. However, td->io_issues[] is used not only for verify reads but also for normal I/Os. It results in comparison with wrong numberio value and verification failure. Fix this issue by adding a new field td->verify_read_issues which counts up number of verify reads. Substitute td->verify_read_issues to io_u->numberio to refer it for the comparison in experimental verify path. Also move td->io_issues[] substitution to io_u->numberio out of populate_verify_io_u() to keep same behavior in non-experimental verify path. Signed-off-by: Shin'ichiro Kawasaki Signed-off-by: Vincent Fu --- backend.c | 6 +++++- fio.h | 1 + verify.c | 2 -- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/backend.c b/backend.c index 15c6e0b3c0..ba954a6b64 100644 --- a/backend.c +++ b/backend.c @@ -711,6 +711,8 @@ static void do_verify(struct thread_data *td, uint64_t verify_bytes) break; } else if (io_u->ddir == DDIR_WRITE) { io_u->ddir = DDIR_READ; + io_u->numberio = td->verify_read_issues; + td->verify_read_issues++; populate_verify_io_u(td, io_u); break; } else { @@ -1030,8 +1032,10 @@ static void do_io(struct thread_data *td, uint64_t *bytes_done) break; } - if (io_u->ddir == DDIR_WRITE && td->flags & TD_F_DO_VERIFY) + if (io_u->ddir == DDIR_WRITE && td->flags & TD_F_DO_VERIFY) { + io_u->numberio = td->io_issues[io_u->ddir]; populate_verify_io_u(td, io_u); + } ddir = io_u->ddir; diff --git a/fio.h b/fio.h index 0592a4c3dc..8da776403e 100644 --- a/fio.h +++ b/fio.h @@ -356,6 +356,7 @@ struct thread_data { * Issue side */ uint64_t io_issues[DDIR_RWDIR_CNT]; + uint64_t verify_read_issues; uint64_t io_issue_bytes[DDIR_RWDIR_CNT]; uint64_t loops; diff --git a/verify.c b/verify.c index 0e1e463934..d6a229cac3 100644 --- a/verify.c +++ b/verify.c @@ -1287,8 +1287,6 @@ void populate_verify_io_u(struct thread_data *td, struct io_u *io_u) if (td->o.verify == VERIFY_NULL) return; - io_u->numberio = td->io_issues[io_u->ddir]; - fill_pattern_headers(td, io_u, 0, 0); } From d661fb18c39bc361399a0f9c254ac89f9c6e45fd Mon Sep 17 00:00:00 2001 From: Shin'ichiro Kawasaki Date: Thu, 20 Oct 2022 15:38:53 +0900 Subject: [PATCH 0314/1097] test: add test for verify read back of experimental verify Add a test case to confirm fix of bytes_done accounting issue of experimental verify. The test job runs a simple workload with experimental verify and confirm verify read amount is the file size. Signed-off-by: Shin'ichiro Kawasaki Signed-off-by: Vincent Fu --- t/jobs/t0025.fio | 7 +++++++ t/run-fio-tests.py | 22 ++++++++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 t/jobs/t0025.fio diff --git a/t/jobs/t0025.fio b/t/jobs/t0025.fio new file mode 100644 index 0000000000..29b5fe80d8 --- /dev/null +++ b/t/jobs/t0025.fio @@ -0,0 +1,7 @@ +[job] +filename=t0025file +size=128k +readwrite=write +do_verify=1 +verify=md5 +experimental_verify=1 diff --git a/t/run-fio-tests.py b/t/run-fio-tests.py index df87ae721d..439991a1a3 100755 --- a/t/run-fio-tests.py +++ b/t/run-fio-tests.py @@ -788,6 +788,18 @@ def check_result(self): self.check_all_offsets("bssplit_bw.log", 512, filesize) +class FioJobTest_t0025(FioJobTest): + """Test experimental verify read backs written data pattern.""" + def check_result(self): + super(FioJobTest_t0025, self).check_result() + + if not self.passed: + return + + if self.json_data['jobs'][0]['read']['io_kbytes'] != 128: + self.passed = False + + class FioJobTest_iops_rate(FioJobTest): """Test consists of fio test job t0009 Confirm that job0 iops == 1000 @@ -1183,6 +1195,16 @@ def cpucount4(cls): 'pre_success': None, 'requirements': [], }, + { + 'test_id': 25, + 'test_class': FioJobTest_t0025, + 'job': 't0025.fio', + 'success': SUCCESS_DEFAULT, + 'pre_job': None, + 'pre_success': None, + 'output_format': 'json', + 'requirements': [], + }, { 'test_id': 1000, 'test_class': FioExeTest, From c4704c081a54160621227b42238f6e439c28fba3 Mon Sep 17 00:00:00 2001 From: Shin'ichiro Kawasaki Date: Thu, 20 Oct 2022 15:38:54 +0900 Subject: [PATCH 0315/1097] test: add test for experimental verify with loops and time_based options Add a test case to confirm fix of numberio accounting issue of experimental verify using loops and time_based options. Of note is that this case fails on Windows with error "bad header rand_seed". Until it gets fixed, require to non-Windows OS for this test case so that CI does not report the known failure. Signed-off-by: Shin'ichiro Kawasaki Signed-off-by: Vincent Fu --- t/jobs/t0026.fio | 19 +++++++++++++++++++ t/run-fio-tests.py | 9 +++++++++ 2 files changed, 28 insertions(+) create mode 100644 t/jobs/t0026.fio diff --git a/t/jobs/t0026.fio b/t/jobs/t0026.fio new file mode 100644 index 0000000000..ee89b14057 --- /dev/null +++ b/t/jobs/t0026.fio @@ -0,0 +1,19 @@ +[job1] +filename=t0026file +size=1M +readwrite=randwrite +loops=8 +do_verify=1 +verify=md5 +experimental_verify=1 + +[job2] +stonewall=1 +filename=t0026file +size=1M +readwrite=randrw +time_based +runtime=5 +do_verify=1 +verify=md5 +experimental_verify=1 diff --git a/t/run-fio-tests.py b/t/run-fio-tests.py index 439991a1a3..e5b307ac0d 100755 --- a/t/run-fio-tests.py +++ b/t/run-fio-tests.py @@ -1205,6 +1205,15 @@ def cpucount4(cls): 'output_format': 'json', 'requirements': [], }, + { + 'test_id': 26, + 'test_class': FioJobTest, + 'job': 't0026.fio', + 'success': SUCCESS_DEFAULT, + 'pre_job': None, + 'pre_success': None, + 'requirements': [Requirements.not_windows], + }, { 'test_id': 1000, 'test_class': FioExeTest, From 73f168ea2c9a66145559c2217fc5a70c992cb80e Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Tue, 1 Nov 2022 17:24:34 -0400 Subject: [PATCH 0316/1097] HOWTO: update description for flow option d4e74fda98b60dc175b4114492fcc7c21c617ddd updated the description for `flow` in fio.1 but did not update the HOWTO. Make the HOWTO description match what is in fio.1 Signed-off-by: Vincent Fu --- HOWTO.rst | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/HOWTO.rst b/HOWTO.rst index e89d05f047..53ae8c1779 100644 --- a/HOWTO.rst +++ b/HOWTO.rst @@ -3329,13 +3329,13 @@ Threads, processes and job synchronization .. option:: flow=int - Weight in token-based flow control. If this value is used, then there is a - 'flow counter' which is used to regulate the proportion of activity between - two or more jobs. Fio attempts to keep this flow counter near zero. The - ``flow`` parameter stands for how much should be added or subtracted to the - flow counter on each iteration of the main I/O loop. That is, if one job has - ``flow=8`` and another job has ``flow=-1``, then there will be a roughly 1:8 - ratio in how much one runs vs the other. + Weight in token-based flow control. If this value is used, then fio + regulates the activity between two or more jobs sharing the same + flow_id. Fio attempts to keep each job activity proportional to other + jobs' activities in the same flow_id group, with respect to requested + weight per job. That is, if one job has `flow=3', another job has + `flow=2' and another with `flow=1`, then there will be a roughly 3:2:1 + ratio in how much one runs vs the others. .. option:: flow_sleep=int From 7fc3a553beadd15cac09b1514547c4d382d292d9 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Wed, 2 Nov 2022 10:26:36 -0400 Subject: [PATCH 0317/1097] HOWTO: clean up exit_what description Signed-off-by: Vincent Fu --- HOWTO.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/HOWTO.rst b/HOWTO.rst index 53ae8c1779..0fb5593e82 100644 --- a/HOWTO.rst +++ b/HOWTO.rst @@ -3356,10 +3356,10 @@ Threads, processes and job synchronization make fio terminate all jobs in the same group, as soon as one job of that group finishes. -.. option:: exit_what +.. option:: exit_what=str By default, fio will continue running all other jobs when one job finishes. - Sometimes this is not the desired action. Setting ``exit_all`` will + Sometimes this is not the desired action. Setting ``exitall`` will instead make fio terminate all jobs in the same group. The option ``exit_what`` allows to control which jobs get terminated when ``exitall`` is enabled. The default is ``group`` and does not change the behaviour of From d6f936d1172a56c28b225d8a6858283d60d57f92 Mon Sep 17 00:00:00 2001 From: Ankit Kumar Date: Fri, 21 Oct 2022 17:02:36 +0530 Subject: [PATCH 0318/1097] io_uring: update documentation and small fix for sqthread_poll type for sqthread_poll should be FIO_OPT_STR_SET, as this only requires it to be set. This also fixes the issue: https://github.com/axboe/fio/issues/927 Update the HOWTO and man page to specify the missing types for all the remaining io_uring options. Signed-off-by: Ankit Kumar Signed-off-by: Vincent Fu --- HOWTO.rst | 6 +++--- engines/io_uring.c | 2 +- fio.1 | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/HOWTO.rst b/HOWTO.rst index 0fb5593e82..e796f96113 100644 --- a/HOWTO.rst +++ b/HOWTO.rst @@ -2274,7 +2274,7 @@ with the caveat that when used on the command line, they must come after the map and release for each IO. This is more efficient, and reduces the IO latency as well. -.. option:: nonvectored : [io_uring] [io_uring_cmd] +.. option:: nonvectored=int : [io_uring] [io_uring_cmd] With this option, fio will use non-vectored read/write commands, where address must contain the address directly. Default is -1. @@ -2301,7 +2301,7 @@ with the caveat that when used on the command line, they must come after the This frees up cycles for fio, at the cost of using more CPU in the system. -.. option:: sqthread_poll_cpu : [io_uring] [io_uring_cmd] +.. option:: sqthread_poll_cpu=int : [io_uring] [io_uring_cmd] When :option:`sqthread_poll` is set, this option provides a way to define which CPU should be used for the polling thread. @@ -2351,7 +2351,7 @@ with the caveat that when used on the command line, they must come after the When hipri is set this determines the probability of a pvsync2 I/O being high priority. The default is 100%. -.. option:: nowait : [pvsync2] [libaio] [io_uring] +.. option:: nowait=bool : [pvsync2] [libaio] [io_uring] [io_uring_cmd] By default if a request cannot be executed immediately (e.g. resource starvation, waiting on locks) it is queued and the initiating process will be blocked until diff --git a/engines/io_uring.c b/engines/io_uring.c index 6906e0a473..3c656b773a 100644 --- a/engines/io_uring.c +++ b/engines/io_uring.c @@ -226,7 +226,7 @@ static struct fio_option options[] = { { .name = "sqthread_poll", .lname = "Kernel SQ thread polling", - .type = FIO_OPT_INT, + .type = FIO_OPT_STR_SET, .off1 = offsetof(struct ioring_options, sqpoll_thread), .help = "Offload submission/completion to kernel thread", .category = FIO_OPT_C_ENGINE, diff --git a/fio.1 b/fio.1 index 4324a97509..9e33c9e189 100644 --- a/fio.1 +++ b/fio.1 @@ -2063,7 +2063,7 @@ release them when IO is done. If this option is set, the pages are pre-mapped before IO is started. This eliminates the need to map and release for each IO. This is more efficient, and reduces the IO latency as well. .TP -.BI (io_uring,io_uring_cmd)nonvectored +.BI (io_uring,io_uring_cmd)nonvectored \fR=\fPint With this option, fio will use non-vectored read/write commands, where address must contain the address directly. Default is -1. .TP @@ -2092,7 +2092,7 @@ available items in the SQ ring. If this option is set, the act of submitting IO will be done by a polling thread in the kernel. This frees up cycles for fio, at the cost of using more CPU in the system. .TP -.BI (io_uring,io_uring_cmd)sqthread_poll_cpu +.BI (io_uring,io_uring_cmd)sqthread_poll_cpu \fR=\fPint When `sqthread_poll` is set, this option provides a way to define which CPU should be used for the polling thread. .TP @@ -2115,7 +2115,7 @@ than normal. When hipri is set this determines the probability of a pvsync2 I/O being high priority. The default is 100%. .TP -.BI (pvsync2,libaio,io_uring)nowait +.BI (pvsync2,libaio,io_uring,io_uring_cmd)nowait \fR=\fPbool By default if a request cannot be executed immediately (e.g. resource starvation, waiting on locks) it is queued and the initiating process will be blocked until the required resource becomes free. From 70d9a988dfd0e08ac299044a2cb8eff3d8b51949 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Fri, 4 Nov 2022 13:04:34 -0400 Subject: [PATCH 0319/1097] test: change GitHub Actions macOS platform to macOS 12 I am seeing some documentation build failures on macOS 11. See if the issue is fixed in macOS 12. https://github.com/vincentkfu/fio/actions/runs/3395509649/jobs/5645674083 This change ultimately did not resolve the issue but we might as well switch to macOS 12 since it has been out for a year already. Signed-off-by: Vincent Fu --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1b8c070166..4bc91d3ed7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,7 +24,7 @@ jobs: os: ubuntu-22.04 cc: clang - build: macos - os: macos-11 + os: macos-12 - build: linux-i686-gcc os: ubuntu-22.04 arch: i686 From 02ee8a1ba7ea798f03fb029f589382b6f799be24 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Fri, 4 Nov 2022 13:15:10 -0400 Subject: [PATCH 0320/1097] test: use homebrew to install sphinx instead of pip on macOS With the current GitHub Actions macOS image, pip3 install sphinx does not appear to place sphinx-doc in the path. This results in documentation build failures. Resolve this by using homebrew to install sphinx-doc and add it to the search path. https://www.sphinx-doc.org/en/master/usage/installation.html https://github.com/vincentkfu/fio/actions/runs/3395703049/jobs/5645918799 Signed-off-by: Vincent Fu --- ci/actions-install.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ci/actions-install.sh b/ci/actions-install.sh index 82e14d2ac4..c16dff162e 100755 --- a/ci/actions-install.sh +++ b/ci/actions-install.sh @@ -84,8 +84,9 @@ install_macos() { #echo "Updating homebrew..." #brew update >/dev/null 2>&1 echo "Installing packages..." - HOMEBREW_NO_AUTO_UPDATE=1 brew install cunit libnfs - pip3 install scipy six sphinx + HOMEBREW_NO_AUTO_UPDATE=1 brew install cunit libnfs sphinx-doc + brew link sphinx-doc --force + pip3 install scipy six } main() { From 72bcaffd7d56d4c2ebad6d0a1e465e0e9db8be40 Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Sun, 6 Nov 2022 13:55:41 -0700 Subject: [PATCH 0321/1097] Fio 3.33 Signed-off-by: Jens Axboe --- FIO-VERSION-GEN | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FIO-VERSION-GEN b/FIO-VERSION-GEN index db0738182a..5a0822c94d 100755 --- a/FIO-VERSION-GEN +++ b/FIO-VERSION-GEN @@ -1,7 +1,7 @@ #!/bin/sh GVF=FIO-VERSION-FILE -DEF_VER=fio-3.32 +DEF_VER=fio-3.33 LF=' ' From 4b387ecdec69f7b5be4d10b69cc0da6cd818665b Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Mon, 7 Nov 2022 12:37:16 -0800 Subject: [PATCH 0322/1097] Windows: Fix the build Fix the build errors about passing arguments without a prototype. Fixes: 93bcfd20e37c ("Move Windows port to MinGW") Signed-off-by: Bart Van Assche --- os/windows/dlls.c | 16 +++++++++++----- os/windows/posix/include/syslog.h | 2 +- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/os/windows/dlls.c b/os/windows/dlls.c index 774b1c612f..ffedfa1e8f 100644 --- a/os/windows/dlls.c +++ b/os/windows/dlls.c @@ -11,12 +11,18 @@ void os_clk_tck(long *clk_tck) */ unsigned long minRes, maxRes, curRes; HMODULE lib; - FARPROC queryTimer; - FARPROC setTimer; + NTSTATUS NTAPI (*queryTimer) + (OUT PULONG MinimumResolution, + OUT PULONG MaximumResolution, + OUT PULONG CurrentResolution); + NTSTATUS NTAPI (*setTimer) + (IN ULONG DesiredResolution, + IN BOOLEAN SetResolution, + OUT PULONG CurrentResolution); if (!(lib = LoadLibrary(TEXT("ntdll.dll"))) || - !(queryTimer = GetProcAddress(lib, "NtQueryTimerResolution")) || - !(setTimer = GetProcAddress(lib, "NtSetTimerResolution"))) { + !(queryTimer = (void *)GetProcAddress(lib, "NtQueryTimerResolution")) || + !(setTimer = (void *)GetProcAddress(lib, "NtSetTimerResolution"))) { dprint(FD_HELPERTHREAD, "Failed to load ntdll library, set to lower bound 64 Hz\n"); *clk_tck = 64; @@ -30,4 +36,4 @@ void os_clk_tck(long *clk_tck) setTimer(maxRes, 1, &curRes); *clk_tck = (long) (10000000L / maxRes); } -} \ No newline at end of file +} diff --git a/os/windows/posix/include/syslog.h b/os/windows/posix/include/syslog.h index b8582e9540..03a04f69f8 100644 --- a/os/windows/posix/include/syslog.h +++ b/os/windows/posix/include/syslog.h @@ -1,7 +1,7 @@ #ifndef SYSLOG_H #define SYSLOG_H -int syslog(); +int syslog(int priority, const char *format, ...); #define LOG_INFO 0x1 #define LOG_ERROR 0x2 From 13a9a800a6198d747082ad3e993ee3ff3043eec3 Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Mon, 7 Nov 2022 11:14:34 -0800 Subject: [PATCH 0323/1097] Android: Enable zoned block device support Enable support for --zonemode=zbd on Android. Signed-off-by: Bart Van Assche --- configure | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure b/configure index 24c599a8d6..30bf5acbb0 100755 --- a/configure +++ b/configure @@ -2561,7 +2561,7 @@ if compile_prog "" "" "valgrind_dev"; then fi print_config "Valgrind headers" "$valgrind_dev" -if test "$targetos" = "Linux" ; then +if test "$targetos" = "Linux" || test "$targetos" = "Android"; then ########################################## # probe if test "$linux_blkzoned" != "yes" ; then From 73816e1adb25ad4cdfbd4f7dbd8aa65f73581174 Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Sun, 13 Nov 2022 15:09:22 -0800 Subject: [PATCH 0324/1097] configure: Fix clock_gettime() detection Prevent that the clock_gettime() and CLOCK_MONOTONIC tests fail as follows: error: argument 2 null where non-null expected [-Werror=nonnull] Signed-off-by: Bart Van Assche --- configure | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/configure b/configure index 30bf5acbb0..60b58cd264 100755 --- a/configure +++ b/configure @@ -1172,7 +1172,9 @@ cat > $TMPC << EOF #include int main(int argc, char **argv) { - return clock_gettime(0, NULL); + struct timespec ts; + + return clock_gettime(0, &ts); } EOF if compile_prog "" "" "clock_gettime"; then @@ -1194,7 +1196,9 @@ if test "$clock_gettime" = "yes" ; then #include int main(int argc, char **argv) { - return clock_gettime(CLOCK_MONOTONIC, NULL); + struct timespec ts; + + return clock_gettime(CLOCK_MONOTONIC, &ts); } EOF if compile_prog "" "$LIBS" "clock monotonic"; then From 1edc3916ff631575532bbe9814846e287195300f Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Sun, 13 Nov 2022 15:11:54 -0800 Subject: [PATCH 0325/1097] configure: Fix the struct nvme_uring_cmd detection Prevent that struct nvme_uring_cmd detection fails as follows: error: unused variable 'cmd' [-Werror=unused-variable] Signed-off-by: Bart Van Assche --- configure | 2 -- 1 file changed, 2 deletions(-) diff --git a/configure b/configure index 60b58cd264..1b12d26899 100755 --- a/configure +++ b/configure @@ -2638,8 +2638,6 @@ cat > $TMPC << EOF #include int main(void) { - struct nvme_uring_cmd *cmd; - return sizeof(struct nvme_uring_cmd); } EOF From 192cdbe2e4fc521aa6c90a6ff4ac62145be60dbb Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Sun, 13 Nov 2022 15:10:10 -0800 Subject: [PATCH 0326/1097] os/os.h: Improve cpus_configured() Fix the following Coverity complaint: 1. negative_return: Calling sysconf, which might return a negative value. 2. return_negative_fn: Returning the return value of sysconf, which might be negative. Signed-off-by: Bart Van Assche --- os/os.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/os/os.h b/os/os.h index a6fde1fd27..c428260ca4 100644 --- a/os/os.h +++ b/os/os.h @@ -355,7 +355,9 @@ static inline unsigned long long get_fs_free_size(const char *path) #ifndef FIO_HAVE_CPU_CONF_SYSCONF static inline unsigned int cpus_configured(void) { - return sysconf(_SC_NPROCESSORS_CONF); + int nr_cpus = sysconf(_SC_NPROCESSORS_CONF); + + return nr_cpus >= 1 ? nr_cpus : 1; } #endif From f8ec93e47b97fed10951f9ef2aaa6a84750b713a Mon Sep 17 00:00:00 2001 From: Shin'ichiro Kawasaki Date: Mon, 14 Nov 2022 11:12:56 +0900 Subject: [PATCH 0327/1097] oslib: blkzoned: add blkzoned_finish_zone() helper function Add the helper function blkzoned_finish_zone() to support zone finish operation to zoned block devices through ioctl. This feature will be used to change status of zones which is not yet full but does not have enough size to write next block, so that such zones do not exceed max active zones limit. This function does zone finish only when kernel supports the ioctl BLKFINISHZONE. Otherwise, it does nothing. This should be fine since the kernel without BLKFINISHZONE does not report max active zone limit through sysfs to user space. Signed-off-by: Shin'ichiro Kawasaki Tested-by: Dmitry Fomichev Reviewed-by: Dmitry Fomichev Signed-off-by: Vincent Fu --- oslib/blkzoned.h | 8 ++++++++ oslib/linux-blkzoned.c | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/oslib/blkzoned.h b/oslib/blkzoned.h index 719b041d12..29fb034f58 100644 --- a/oslib/blkzoned.h +++ b/oslib/blkzoned.h @@ -18,6 +18,8 @@ extern int blkzoned_reset_wp(struct thread_data *td, struct fio_file *f, uint64_t offset, uint64_t length); extern int blkzoned_get_max_open_zones(struct thread_data *td, struct fio_file *f, unsigned int *max_open_zones); +extern int blkzoned_finish_zone(struct thread_data *td, struct fio_file *f, + uint64_t offset, uint64_t length); #else /* * Define stubs for systems that do not have zoned block device support. @@ -51,6 +53,12 @@ static inline int blkzoned_get_max_open_zones(struct thread_data *td, struct fio { return -EIO; } +static inline int blkzoned_finish_zone(struct thread_data *td, + struct fio_file *f, + uint64_t offset, uint64_t length) +{ + return -EIO; +} #endif #endif /* FIO_BLKZONED_H */ diff --git a/oslib/linux-blkzoned.c b/oslib/linux-blkzoned.c index 185bd5011b..c3130d0e2a 100644 --- a/oslib/linux-blkzoned.c +++ b/oslib/linux-blkzoned.c @@ -308,3 +308,40 @@ int blkzoned_reset_wp(struct thread_data *td, struct fio_file *f, return ret; } + +int blkzoned_finish_zone(struct thread_data *td, struct fio_file *f, + uint64_t offset, uint64_t length) +{ +#ifdef BLKFINISHZONE + struct blk_zone_range zr = { + .sector = offset >> 9, + .nr_sectors = length >> 9, + }; + int fd, ret = 0; + + /* If the file is not yet opened, open it for this function. */ + fd = f->fd; + if (fd < 0) { + fd = open(f->file_name, O_RDWR | O_LARGEFILE); + if (fd < 0) + return -errno; + } + + if (ioctl(fd, BLKFINISHZONE, &zr) < 0) + ret = -errno; + + if (f->fd < 0) + close(fd); + + return ret; +#else + /* + * Kernel versions older than 5.5 does not support BLKFINISHZONE. These + * old kernels assumed zones are closed automatically at max_open_zones + * limit. Also they did not support max_active_zones limit. Then there + * was no need to finish zones to avoid errors caused by max_open_zones + * or max_active_zones. For those old versions, just do nothing. + */ + return 0; +#endif +} From a7f1b5cdea3c4757a0c0226762fed12017b9b90f Mon Sep 17 00:00:00 2001 From: Shin'ichiro Kawasaki Date: Mon, 14 Nov 2022 11:12:57 +0900 Subject: [PATCH 0328/1097] engines/libzbc: add libzbc_finish_zone() helper function To support zone finish operation to ZBC drives through libzbc, add finish_zone() callback to struct ioengine_ops, and implement in libzbc IO engine. This feature is used to keep the same zone handling by zonemode=zbd for libzbc engine as other engines. Signed-off-by: Shin'ichiro Kawasaki Tested-by: Dmitry Fomichev Reviewed-by: Dmitry Fomichev Signed-off-by: Vincent Fu --- engines/libzbc.c | 34 ++++++++++++++++++++++++++++++++++ ioengines.h | 2 ++ 2 files changed, 36 insertions(+) diff --git a/engines/libzbc.c b/engines/libzbc.c index 2bc2c7e0e4..2b63ef1aca 100644 --- a/engines/libzbc.c +++ b/engines/libzbc.c @@ -332,6 +332,39 @@ static int libzbc_reset_wp(struct thread_data *td, struct fio_file *f, return -ret; } +static int libzbc_finish_zone(struct thread_data *td, struct fio_file *f, + uint64_t offset, uint64_t length) +{ + struct libzbc_data *ld = td->io_ops_data; + uint64_t sector = offset >> 9; + unsigned int nr_zones; + struct zbc_errno err; + int i, ret; + + assert(ld); + assert(ld->zdev); + + nr_zones = (length + td->o.zone_size - 1) / td->o.zone_size; + assert(nr_zones > 0); + + for (i = 0; i < nr_zones; i++, sector += td->o.zone_size >> 9) { + ret = zbc_finish_zone(ld->zdev, sector, 0); + if (ret) + goto err; + } + + return 0; + +err: + zbc_errno(ld->zdev, &err); + td_verror(td, errno, "zbc_finish_zone failed"); + if (err.sk) + log_err("%s: finish zone failed %s:%s\n", + f->file_name, + zbc_sk_str(err.sk), zbc_asc_ascq_str(err.asc_ascq)); + return -ret; +} + static int libzbc_get_max_open_zones(struct thread_data *td, struct fio_file *f, unsigned int *max_open_zones) { @@ -434,6 +467,7 @@ FIO_STATIC struct ioengine_ops ioengine = { .report_zones = libzbc_report_zones, .reset_wp = libzbc_reset_wp, .get_max_open_zones = libzbc_get_max_open_zones, + .finish_zone = libzbc_finish_zone, .queue = libzbc_queue, .flags = FIO_SYNCIO | FIO_NOEXTEND | FIO_RAWIO, }; diff --git a/ioengines.h b/ioengines.h index fafa1e4818..11d2115ce7 100644 --- a/ioengines.h +++ b/ioengines.h @@ -61,6 +61,8 @@ struct ioengine_ops { uint64_t, uint64_t); int (*get_max_open_zones)(struct thread_data *, struct fio_file *, unsigned int *); + int (*finish_zone)(struct thread_data *, struct fio_file *, + uint64_t, uint64_t); int option_struct_size; struct fio_option *options; }; From df67bf1e5d3186f3b8776faf0934491295d9464d Mon Sep 17 00:00:00 2001 From: Shin'ichiro Kawasaki Date: Mon, 14 Nov 2022 11:12:58 +0900 Subject: [PATCH 0329/1097] zbd: add zbd_zone_remainder() helper function Add the helper function zbd_zone_remainder(), which returns the number of bytes that are still available for writing before the zone gets full. Use this function to improve readability. It will also be used in the following patch. Signed-off-by: Shin'ichiro Kawasaki Tested-by: Dmitry Fomichev Reviewed-by: Dmitry Fomichev Signed-off-by: Vincent Fu --- zbd.c | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/zbd.c b/zbd.c index 627fb968ec..26a6404def 100644 --- a/zbd.c +++ b/zbd.c @@ -70,6 +70,19 @@ static inline uint64_t zbd_zone_capacity_end(const struct fio_zone_info *z) return z->start + z->capacity; } +/** + * zbd_zone_remainder - Return the number of bytes that are still available for + * writing before the zone gets full + * @z: zone info pointer. + */ +static inline uint64_t zbd_zone_remainder(struct fio_zone_info *z) +{ + if (z->wp >= zbd_zone_capacity_end(z)) + return 0; + + return zbd_zone_capacity_end(z) - z->wp; +} + /** * zbd_zone_full - verify whether a minimum number of bytes remain in a zone * @f: file pointer. @@ -83,8 +96,7 @@ static bool zbd_zone_full(const struct fio_file *f, struct fio_zone_info *z, { assert((required & 511) == 0); - return z->has_wp && - z->wp + required > zbd_zone_capacity_end(z); + return z->has_wp && required > zbd_zone_remainder(z); } static void zone_lock(struct thread_data *td, const struct fio_file *f, @@ -440,7 +452,7 @@ static bool zbd_open_zone(struct thread_data *td, const struct fio_file *f, * already in-flight, handle it as a full zone instead of an * open zone. */ - if (z->wp >= zbd_zone_capacity_end(z)) + if (!zbd_zone_remainder(z)) res = false; goto out; } @@ -1368,7 +1380,7 @@ static struct fio_zone_info *zbd_convert_to_open_zone(struct thread_data *td, /* Both z->mutex and zbdi->mutex are held. */ examine_zone: - if (z->wp + min_bs <= zbd_zone_capacity_end(z)) { + if (zbd_zone_remainder(z) >= min_bs) { pthread_mutex_unlock(&zbdi->mutex); goto out; } @@ -1433,7 +1445,7 @@ static struct fio_zone_info *zbd_convert_to_open_zone(struct thread_data *td, z = zbd_get_zone(f, zone_idx); zone_lock(td, f, z); - if (z->wp + min_bs <= zbd_zone_capacity_end(z)) + if (zbd_zone_remainder(z) >= min_bs) goto out; pthread_mutex_lock(&zbdi->mutex); } From e1a1b59b0b9b283f75846f4a1efbe498b4236cc5 Mon Sep 17 00:00:00 2001 From: Shin'ichiro Kawasaki Date: Mon, 14 Nov 2022 11:12:59 +0900 Subject: [PATCH 0330/1097] zbd: finish zones with remainder smaller than minimum write block size When zonemode is zbd and block size is not divisor of zone size, write target zone selection does not work as expected. When the write is random write and the device has max open zone limit, the random write is repeated to the zones selected up to the max open zone limit. All writes are repeated only to the zones. When the write is sequential write, write is done only for the first zone. The cause of such unexpected zone selection is current write target zone selection logic. It selects write target zones within open zones. When block size is not divisor of zone size, the selected open zone has only remainder of writable blocks smaller than the block size. Fio resets such zone after zone selection and continues writing to it. This zone reset is required not to exceed the limit of max_open_zones option or max_active_zone limit of the zoned device, but it does not simulate the workload. To avoid the zone reset and unexpected write to same zone, fix write target zone handling of zones with remainder smaller than write block size. Do not reset but finish such zone not to exceed the max_open_zones option and max_active_zone limit. Then choose the zone next to the finished zone as write target. To implement this, add the helper function zbd_finish_zone(). Signed-off-by: Shin'ichiro Kawasaki Tested-by: Dmitry Fomichev Reviewed-by: Dmitry Fomichev Signed-off-by: Vincent Fu --- zbd.c | 69 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/zbd.c b/zbd.c index 26a6404def..9ab78e2ed5 100644 --- a/zbd.c +++ b/zbd.c @@ -334,6 +334,44 @@ static void zbd_close_zone(struct thread_data *td, const struct fio_file *f, z->open = 0; } +/** + * zbd_finish_zone - finish the specified zone + * @td: FIO thread data. + * @f: FIO file for which to finish a zone + * @z: Zone to finish. + * + * Finish the zone at @offset with open or close status. + */ +static int zbd_finish_zone(struct thread_data *td, struct fio_file *f, + struct fio_zone_info *z) +{ + uint64_t offset = z->start; + uint64_t length = f->zbd_info->zone_size; + int ret = 0; + + switch (f->zbd_info->model) { + case ZBD_HOST_AWARE: + case ZBD_HOST_MANAGED: + if (td->io_ops && td->io_ops->finish_zone) + ret = td->io_ops->finish_zone(td, f, offset, length); + else + ret = blkzoned_finish_zone(td, f, offset, length); + break; + default: + break; + } + + if (ret < 0) { + td_verror(td, errno, "finish zone failed"); + log_err("%s: finish zone at sector %"PRIu64" failed (%d).\n", + f->file_name, offset >> 9, errno); + } else { + z->wp = (z+1)->start; + } + + return ret; +} + /** * zbd_reset_zones - Reset a range of zones. * @td: fio thread data. @@ -1953,6 +1991,33 @@ enum io_u_action zbd_adjust_block(struct thread_data *td, struct io_u *io_u) goto eof; } +retry: + if (zbd_zone_remainder(zb) > 0 && + zbd_zone_remainder(zb) < min_bs) { + pthread_mutex_lock(&f->zbd_info->mutex); + zbd_close_zone(td, f, zb); + pthread_mutex_unlock(&f->zbd_info->mutex); + dprint(FD_ZBD, + "%s: finish zone %d\n", + f->file_name, zbd_zone_idx(f, zb)); + io_u_quiesce(td); + zbd_finish_zone(td, f, zb); + if (zbd_zone_idx(f, zb) + 1 >= f->max_zone) { + if (!td_random(td)) + goto eof; + } + zone_unlock(zb); + + /* Find the next write pointer zone */ + do { + zb++; + if (zbd_zone_idx(f, zb) >= f->max_zone) + zb = zbd_get_zone(f, f->min_zone); + } while (!zb->has_wp); + + zone_lock(td, f, zb); + } + if (!zbd_open_zone(td, f, zb)) { zone_unlock(zb); zb = zbd_convert_to_open_zone(td, io_u); @@ -1963,6 +2028,10 @@ enum io_u_action zbd_adjust_block(struct thread_data *td, struct io_u *io_u) } } + if (zbd_zone_remainder(zb) > 0 && + zbd_zone_remainder(zb) < min_bs) + goto retry; + /* Check whether the zone reset threshold has been exceeded */ if (td->o.zrf.u.f) { if (zbdi->wp_sectors_with_data >= f->io_size * td->o.zrt.u.f && From e7b44887cb0ee99d9b62b0177d17efcafbb93dc1 Mon Sep 17 00:00:00 2001 From: Shin'ichiro Kawasaki Date: Mon, 14 Nov 2022 11:13:00 +0900 Subject: [PATCH 0331/1097] zbd: allow block size not divisor of zone size Current implementation checks that block size is divisor of zone size when verify work load is specified. After the recent fix of block size unaligned to zone, this check is no longer valid. Remove the check. The check had been valid since such block size left unwritten area at each zone end and keeps the zones in open/active status until verify read is done. It easily hit max open/active zones limitation. After the fix, the zones with unwritten area are finished then they do not hit the limitation. Signed-off-by: Shin'ichiro Kawasaki Tested-by: Dmitry Fomichev Reviewed-by: Dmitry Fomichev Signed-off-by: Vincent Fu --- zbd.c | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/zbd.c b/zbd.c index 9ab78e2ed5..6f4e5ab24b 100644 --- a/zbd.c +++ b/zbd.c @@ -652,7 +652,7 @@ static bool zbd_verify_bs(void) { struct thread_data *td; struct fio_file *f; - int i, j, k; + int i, j; for_each_td(td, i) { if (td_trim(td) && @@ -674,15 +674,6 @@ static bool zbd_verify_bs(void) zone_size); return false; } - for (k = 0; k < FIO_ARRAY_SIZE(td->o.bs); k++) { - if (td->o.verify != VERIFY_NONE && - zone_size % td->o.bs[k] != 0) { - log_info("%s: block size %llu is not a divisor of the zone size %"PRIu64"\n", - f->file_name, td->o.bs[k], - zone_size); - return false; - } - } } } return true; From cef8006cf110c30ec64de9a82c10f00d1f2f84f9 Mon Sep 17 00:00:00 2001 From: Shin'ichiro Kawasaki Date: Mon, 14 Nov 2022 11:13:01 +0900 Subject: [PATCH 0332/1097] zbd, verify: verify before zone reset for zone_reset_threshold/frequency When zone_reset_threshold and zone_reset_frequency options are specified with zonemode=zbd, it resets zones not full. When verify option is specified on top of that, the zone reset of non-full zones erases data for verify and causes verify failure. Current implementation avoids this scenario by assert. To allow zone_reset_threshold/frequency options together with verify, do verify before the zone reset. When zone reset is required to an open zone with verify data, call get_next_verify() to get io_u for verify read and return it from zbd_adjust_block(). When io_u->file is set, get_next_verify() assumes the io_u is requeued and does nothing. Unset io_u->file to tell get_next_verify() is not requeued. Also modify verify_io_u() to skip rand_seed check when the option zone_reset_frequency is set. When the option is set, random seed is not reset for verify reads in same manner as verify_backlog option, then this check is not valid. Signed-off-by: Shin'ichiro Kawasaki Signed-off-by: Vincent Fu --- verify.c | 6 ++++-- zbd.c | 14 +++++++++++++- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/verify.c b/verify.c index d6a229cac3..ddfadcc873 100644 --- a/verify.c +++ b/verify.c @@ -917,9 +917,11 @@ int verify_io_u(struct thread_data *td, struct io_u **io_u_ptr) hdr = p; /* - * Make rand_seed check pass when have verify_backlog. + * Make rand_seed check pass when have verify_backlog or + * zone reset frequency for zonemode=zbd. */ - if (!td_rw(td) || (td->flags & TD_F_VER_BACKLOG)) + if (!td_rw(td) || (td->flags & TD_F_VER_BACKLOG) || + td->o.zrf.u.f) io_u->rand_seed = hdr->rand_seed; if (td->o.verify != VERIFY_PATTERN_NO_HDR) { diff --git a/zbd.c b/zbd.c index 6f4e5ab24b..fadeb45841 100644 --- a/zbd.c +++ b/zbd.c @@ -2032,7 +2032,19 @@ enum io_u_action zbd_adjust_block(struct thread_data *td, struct io_u *io_u) /* Reset the zone pointer if necessary */ if (zb->reset_zone || zbd_zone_full(f, zb, min_bs)) { - assert(td->o.verify == VERIFY_NONE); + if (td->o.verify != VERIFY_NONE) { + /* + * Unset io-u->file to tell get_next_verify() + * that this IO is not requeue. + */ + io_u->file = NULL; + if (!get_next_verify(td, io_u)) { + zone_unlock(zb); + return io_u_accept; + } + io_u->file = f; + } + /* * Since previous write requests may have been submitted * asynchronously and since we will submit the zone From c5c8b92be5a2a7951cf6be0d040a511e23aa91bf Mon Sep 17 00:00:00 2001 From: Shin'ichiro Kawasaki Date: Mon, 14 Nov 2022 11:13:02 +0900 Subject: [PATCH 0333/1097] zbd: fix zone reset condition for verify When data verification is requested, zbd_file_reset() resets zones only when td->runstate is not TD_VERIFYING so that the data to read back for verify is not wiped out. However, even when verify data to read is left, td->runstate is not always TD_VERIFYING. When verify_backlog option is set, or when block size is not divisor of zone size, zbd_file_reset() can be called while td->runstate is TD_RUNNING. This causes verify failures. To avoid the failures, improve the check condition to reset zones in zbd_file_reset(). On top of td->runstate, refer td->io_hist_len, td->verify_batch and td->o.verify_backlog values to avoid zone reset. This is same check as check_get_verify(). Signed-off-by: Shin'ichiro Kawasaki Signed-off-by: Vincent Fu --- zbd.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/zbd.c b/zbd.c index fadeb45841..97faa0e53b 100644 --- a/zbd.c +++ b/zbd.c @@ -1249,6 +1249,7 @@ void zbd_file_reset(struct thread_data *td, struct fio_file *f) { struct fio_zone_info *zb, *ze; uint64_t swd; + bool verify_data_left = false; if (!f->zbd_info || !td_write(td)) return; @@ -1265,8 +1266,16 @@ void zbd_file_reset(struct thread_data *td, struct fio_file *f) * writing any data to avoid that a zone reset has to be issued while * writing data, which causes data loss. */ - if (td->o.verify != VERIFY_NONE && td->runstate != TD_VERIFYING) - zbd_reset_zones(td, f, zb, ze); + if (td->o.verify != VERIFY_NONE) { + verify_data_left = td->runstate == TD_VERIFYING || + td->io_hist_len || td->verify_batch; + if (td->io_hist_len && td->o.verify_backlog) + verify_data_left = + td->io_hist_len % td->o.verify_backlog; + if (!verify_data_left) + zbd_reset_zones(td, f, zb, ze); + } + zbd_reset_write_cnt(td, f); } From 6e2da06ae86f4ef32a75ddcfe1087fc41c56d2f6 Mon Sep 17 00:00:00 2001 From: Shin'ichiro Kawasaki Date: Mon, 14 Nov 2022 11:13:03 +0900 Subject: [PATCH 0334/1097] zbd: prevent experimental verify with zonemode=zbd When the experimental_verify option is specified, fio does not record normal I/O metadata to create verify read unit. Instead, fio resets file status after normal I/O and before verify start, then replay I/Os to regenerate write unit and adjust it to verify read. This I/O replay does not work for zonemode=zbd since zone status needs to be restored at verify start to the status before the normal I/O. However, such status restore moves write pointers and erases data pattern for verify. Check that the options experimental_verify and zonemode=zbd are not specified together and error out in case they are both specified. Also remove the helper function zbd_replay_write_order() which is called from zbd_adjust_block(). This function adjusts verify read unit to meet zone write restrictions for experimental verify, but such adjustment does not work because zone status is not restored before verify start. Signed-off-by: Shin'ichiro Kawasaki Tested-by: Dmitry Fomichev Reviewed-by: Dmitry Fomichev Signed-off-by: Vincent Fu --- zbd.c | 46 ++++++---------------------------------------- zbd.h | 2 -- 2 files changed, 6 insertions(+), 42 deletions(-) diff --git a/zbd.c b/zbd.c index 97faa0e53b..d1e469f665 100644 --- a/zbd.c +++ b/zbd.c @@ -291,7 +291,6 @@ static int zbd_reset_zone(struct thread_data *td, struct fio_file *f, pthread_mutex_unlock(&f->zbd_info->mutex); z->wp = z->start; - z->verify_block = 0; td->ts.nr_zone_resets++; @@ -1085,6 +1084,11 @@ int zbd_setup_files(struct thread_data *td) if (!zbd_verify_bs()) return 1; + if (td->o.experimental_verify) { + log_err("zonemode=zbd does not support experimental verify\n"); + return 1; + } + for_each_file(td, f, i) { struct zoned_block_device_info *zbd = f->zbd_info; struct fio_zone_info *z; @@ -1526,42 +1530,6 @@ static struct fio_zone_info *zbd_convert_to_open_zone(struct thread_data *td, return z; } -/* The caller must hold z->mutex. */ -static struct fio_zone_info *zbd_replay_write_order(struct thread_data *td, - struct io_u *io_u, - struct fio_zone_info *z) -{ - const struct fio_file *f = io_u->file; - const uint64_t min_bs = td->o.min_bs[DDIR_WRITE]; - - if (!zbd_open_zone(td, f, z)) { - zone_unlock(z); - z = zbd_convert_to_open_zone(td, io_u); - assert(z); - } - - if (z->verify_block * min_bs >= z->capacity) { - log_err("%s: %d * %"PRIu64" >= %"PRIu64"\n", - f->file_name, z->verify_block, min_bs, z->capacity); - /* - * If the assertion below fails during a test run, adding - * "--experimental_verify=1" to the command line may help. - */ - assert(false); - } - - io_u->offset = z->start + z->verify_block * min_bs; - if (io_u->offset + io_u->buflen >= zbd_zone_capacity_end(z)) { - log_err("%s: %llu + %llu >= %"PRIu64"\n", - f->file_name, io_u->offset, io_u->buflen, - zbd_zone_capacity_end(z)); - assert(false); - } - z->verify_block += io_u->buflen / min_bs; - - return z; -} - /* * Find another zone which has @min_bytes of readable data. Search in zones * @zb + 1 .. @zl. For random workload, also search in zones @zb - 1 .. @zf. @@ -1912,10 +1880,8 @@ enum io_u_action zbd_adjust_block(struct thread_data *td, struct io_u *io_u) switch (io_u->ddir) { case DDIR_READ: - if (td->runstate == TD_VERIFYING && td_write(td)) { - zb = zbd_replay_write_order(td, io_u, zb); + if (td->runstate == TD_VERIFYING && td_write(td)) goto accept; - } /* * Check that there is enough written data in the zone to do an diff --git a/zbd.h b/zbd.h index 0a73b41dd9..d425707e82 100644 --- a/zbd.h +++ b/zbd.h @@ -25,7 +25,6 @@ enum io_u_action { * @start: zone start location (bytes) * @wp: zone write pointer location (bytes) * @capacity: maximum size usable from the start of a zone (bytes) - * @verify_block: number of blocks that have been verified for this zone * @mutex: protects the modifiable members in this structure * @type: zone type (BLK_ZONE_TYPE_*) * @cond: zone state (BLK_ZONE_COND_*) @@ -39,7 +38,6 @@ struct fio_zone_info { uint64_t start; uint64_t wp; uint64_t capacity; - uint32_t verify_block; enum zbd_zone_type type:2; enum zbd_zone_cond cond:4; unsigned int has_wp:1; From 268b19c666a477fcca7088ff5564c7a23fc2180b Mon Sep 17 00:00:00 2001 From: Shin'ichiro Kawasaki Date: Mon, 14 Nov 2022 11:13:04 +0900 Subject: [PATCH 0335/1097] t/zbd: fix test case #33 for block size unaligned to zone size Recent fix of zonemode=zbd support for block size unaligned to zone size unveiled that the test case #33 has two issues. First issue is test preparation. Before the fix, write was done to only to the first zone due to a bug in zone selection which happens when block size is not a divisor of zone size. Then, status of second zone did not affect. After the fix, write count to the zones may vary if the second zone has almost full status since the zone is skipped from write target. Fix this by resetting the write target zones. Second issue is expected written data size. The test case checks that the written data size is larger than the io_size option value. This expectation was fine before the fix because data write was repeated in do_io() and the limit was checked with io_issue_bytes_exceeded(), which triggers loop break when written data is larger than io_size. However, after the fix, the limit is checked with keep_running() in thread_main(). According to code and block comment in keep_running(), fio job terminates even when written data size is smaller than io_size if the gap is smaller than maximum IO size. Then the expected written data size is the largest multiple of block size smaller than or equal to the io_size. This io_size check change resulted in the test case failure. Avoid the failure by fixing the expected written data size calculation. Signed-off-by: Shin'ichiro Kawasaki Tested-by: Dmitry Fomichev Reviewed-by: Dmitry Fomichev Signed-off-by: Vincent Fu --- t/zbd/test-zbd-support | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/t/zbd/test-zbd-support b/t/zbd/test-zbd-support index cdc03f281b..debe37632d 100755 --- a/t/zbd/test-zbd-support +++ b/t/zbd/test-zbd-support @@ -813,7 +813,8 @@ test33() { local bs io_size size local off capacity=0; - prep_write + [ -n "$is_zbd" ] && reset_zone "$dev" -1 + off=$((first_sequential_zone_sector * 512)) capacity=$(total_zone_capacity 1 $off $dev) size=$((2 * zone_size)) @@ -822,7 +823,7 @@ test33() { run_fio_on_seq "$(ioengine "psync")" --iodepth=1 --rw=write \ --size=$size --io_size=$io_size --bs=$bs \ >> "${logfile}.${test_number}" 2>&1 || return $? - check_written $(((io_size + bs - 1) / bs * bs)) || return $? + check_written $((io_size / bs * bs)) || return $? } # Write to sequential zones with a block size that is not a divisor of the From 7778964025e60b8972b2aced42a2f5d676b7b0dd Mon Sep 17 00:00:00 2001 From: Shin'ichiro Kawasaki Date: Mon, 14 Nov 2022 11:13:05 +0900 Subject: [PATCH 0336/1097] t/zbd: modify test case #34 for block size unaligned to zone size The test case #34 confirmed that the block size unaligned to zone size is handled as an error. After recent fix, now fio is able to handle such block sizes, then the check for the error is no longer required. Instead of removing this unnecessary test case, change it to cover verify with complex workload. It runs random write workload with high queue depth with verify. Use two types of block sizes unaligned to zone size. This test workload is same as test case #57 except the verify option and block sizes. Signed-off-by: Shin'ichiro Kawasaki Signed-off-by: Vincent Fu --- t/zbd/test-zbd-support | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/t/zbd/test-zbd-support b/t/zbd/test-zbd-support index debe37632d..37af67247f 100755 --- a/t/zbd/test-zbd-support +++ b/t/zbd/test-zbd-support @@ -826,17 +826,27 @@ test33() { check_written $((io_size / bs * bs)) || return $? } -# Write to sequential zones with a block size that is not a divisor of the -# zone size and with data verification enabled. +# Test repeated async write job with verify using two unaligned block sizes. test34() { - local size + local bs off zone_capacity + local -a block_sizes - prep_write - size=$((2 * zone_size)) - run_fio_on_seq "$(ioengine "psync")" --iodepth=1 --rw=write --size=$size \ - --do_verify=1 --verify=md5 --bs=$((3 * zone_size / 4)) \ - >> "${logfile}.${test_number}" 2>&1 && return 1 - grep -q 'not a divisor of' "${logfile}.${test_number}" + require_zbd || return $SKIP_TESTCASE + prep_write + + off=$((first_sequential_zone_sector * 512)) + zone_capacity=$(total_zone_capacity 1 $off $dev) + block_sizes=($((4096 * 7)) $(($(min ${zone_capacity} 4194304) - 4096))) + + for bs in ${block_sizes[@]}; do + run_fio --name=job --filename="${dev}" --rw=randwrite \ + --bs="${bs}" --offset="${off}" \ + --size=$((4 * zone_size)) --iodepth=256 \ + "$(ioengine "libaio")" --time_based=1 --runtime=15s \ + --zonemode=zbd --direct=1 --zonesize="${zone_size}" \ + --verify=crc32c --do_verify=1 ${job_var_opts[@]} \ + >> "${logfile}.${test_number}" 2>&1 || return $? + done } # Test 1/4 for the I/O boundary rounding code: $size < $zone_size. From 7b6db71265c223963fb1a85334794106a946f625 Mon Sep 17 00:00:00 2001 From: Shin'ichiro Kawasaki Date: Mon, 14 Nov 2022 11:13:06 +0900 Subject: [PATCH 0337/1097] t/zbd: add test case to check zone_reset_threshold/frequency with verify Recent commit fixed assertion failure observed with zone_reset_threshold and zone_reset_frequency options together with verify. Add a test case to confirm the fix. Run four types of workloads and confirm no error. Signed-off-by: Shin'ichiro Kawasaki Signed-off-by: Vincent Fu --- t/zbd/test-zbd-support | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/t/zbd/test-zbd-support b/t/zbd/test-zbd-support index 37af67247f..fec93099b4 100755 --- a/t/zbd/test-zbd-support +++ b/t/zbd/test-zbd-support @@ -1280,6 +1280,24 @@ test58() { >>"${logfile}.${test_number}" 2>&1 } +# Test zone_reset_threshold with verify. +test59() { + local off bs loops=2 size=$((zone_size)) w + local -a workloads=(write randwrite rw randrw) + + prep_write + off=$((first_sequential_zone_sector * 512)) + + bs=$(min $((256*1024)) "$zone_size") + for w in "${workloads[@]}"; do + run_fio_on_seq "$(ioengine "psync")" --rw=${w} --bs="$bs" \ + --size=$size --loops=$loops --do_verify=1 \ + --verify=md5 --zone_reset_frequency=.9 \ + --zone_reset_threshold=.1 \ + >> "${logfile}.${test_number}" 2>&1 || return $? + done +} + SECONDS=0 tests=() dynamic_analyzer=() From f336af02d3b0144c8e3fdc95300af4959a9aa398 Mon Sep 17 00:00:00 2001 From: Shin'ichiro Kawasaki Date: Mon, 14 Nov 2022 11:13:07 +0900 Subject: [PATCH 0338/1097] t/zbd: remove experimental_verify option from test case #54 The option experimental_verify does not work with zonemode=zbd. Remove it from the test case #54. Signed-off-by: Shin'ichiro Kawasaki Tested-by: Dmitry Fomichev Reviewed-by: Dmitry Fomichev Signed-off-by: Vincent Fu --- t/zbd/test-zbd-support | 1 - 1 file changed, 1 deletion(-) diff --git a/t/zbd/test-zbd-support b/t/zbd/test-zbd-support index fec93099b4..5124296c89 100755 --- a/t/zbd/test-zbd-support +++ b/t/zbd/test-zbd-support @@ -1182,7 +1182,6 @@ test54() { --rw=randrw:2 --rwmixwrite=25 --bsrange=4k-${zone_size} \ --zonemode=zbd --zonesize=${zone_size} \ --verify=crc32c --do_verify=1 --verify_backlog=2 \ - --experimental_verify=1 \ --alloc-size=65536 --random_generator=tausworthe64 \ ${job_var_opts[@]} --debug=zbd \ >> "${logfile}.${test_number}" 2>&1 || return $? From 78054a76da3c29e1a97ca01acbec85170f6db1e6 Mon Sep 17 00:00:00 2001 From: Shin'ichiro Kawasaki Date: Mon, 14 Nov 2022 11:13:08 +0900 Subject: [PATCH 0339/1097] t/zbd: add test case to check experimental_verify option The option experimental_verify does not work with zonemode=zbd. Add a test case to check that fio errors out when the both options are specified. Signed-off-by: Shin'ichiro Kawasaki Tested-by: Dmitry Fomichev Reviewed-by: Dmitry Fomichev Signed-off-by: Vincent Fu --- t/zbd/test-zbd-support | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/t/zbd/test-zbd-support b/t/zbd/test-zbd-support index 5124296c89..4091d9ac95 100755 --- a/t/zbd/test-zbd-support +++ b/t/zbd/test-zbd-support @@ -1297,6 +1297,14 @@ test59() { done } +# Test fio errors out experimental_verify option with zonemode=zbd. +test60() { + run_fio_on_seq "$(ioengine "psync")" --rw=write --size=$zone_size \ + --do_verify=1 --verify=md5 --experimental_verify=1 \ + >> "${logfile}.${test_number}" 2>&1 && return 1 + grep -q 'not support experimental verify' "${logfile}.${test_number}" +} + SECONDS=0 tests=() dynamic_analyzer=() From 7f812e878210490e7fe68b3312fafbd864137165 Mon Sep 17 00:00:00 2001 From: Logan Gunthorpe Date: Fri, 18 Nov 2022 16:15:56 -0700 Subject: [PATCH 0340/1097] cconv: Support pattern buffers of arbitrary size Change the thread_options_pack structure to support pattern buffers of arbitrary size by using a flexible array at the end of the the structure to store both the verify_pattern and the buffer_pattern in that order. In this way, only the actual bytes of each pattern will be sent over the wire and patterns of an arbitrary size can be used with the packed structure. In order to determine the required size of the structure the function thread_options_pack_size() is introduced which returns the total number of bytes required for a given thread_options instance. The two callsites of convert_thread_options_to_net() are then converted to dynamically allocate a pdu of the appropriate size and the two callsites of convert_thread_options_to_cpu() are modified to take the size of the received data to prevent buffer overruns. Also add specific testing of this feature in fio_test_cconv(). Seeing this changes the client/server protocol, the FIO_SERVER_VER is bumped. Signed-off-by: Logan Gunthorpe Signed-off-by: Vincent Fu --- cconv.c | 75 +++++++++++++++++++++++++++++++++--------------- client.c | 17 +++++++---- gclient.c | 12 ++++++-- server.c | 23 +++++++++------ server.h | 2 +- thread_options.h | 11 +++++-- 6 files changed, 96 insertions(+), 44 deletions(-) diff --git a/cconv.c b/cconv.c index 6c36afb72d..bb1af4970e 100644 --- a/cconv.c +++ b/cconv.c @@ -54,8 +54,15 @@ static void free_thread_options_to_cpu(struct thread_options *o) } } -void convert_thread_options_to_cpu(struct thread_options *o, - struct thread_options_pack *top) +size_t thread_options_pack_size(struct thread_options *o) +{ + return sizeof(struct thread_options_pack) + o->verify_pattern_bytes + + o->buffer_pattern_bytes; +} + +int convert_thread_options_to_cpu(struct thread_options *o, + struct thread_options_pack *top, + size_t top_sz) { int i, j; @@ -171,10 +178,17 @@ void convert_thread_options_to_cpu(struct thread_options *o, o->verify_interval = le32_to_cpu(top->verify_interval); o->verify_offset = le32_to_cpu(top->verify_offset); - memcpy(o->verify_pattern, top->verify_pattern, MAX_PATTERN_SIZE); - memcpy(o->buffer_pattern, top->buffer_pattern, MAX_PATTERN_SIZE); - o->verify_pattern_bytes = le32_to_cpu(top->verify_pattern_bytes); + o->buffer_pattern_bytes = le32_to_cpu(top->buffer_pattern_bytes); + if (o->verify_pattern_bytes >= MAX_PATTERN_SIZE || + o->buffer_pattern_bytes >= MAX_PATTERN_SIZE || + thread_options_pack_size(o) > top_sz) + return -EINVAL; + + memcpy(o->verify_pattern, top->patterns, o->verify_pattern_bytes); + memcpy(o->buffer_pattern, &top->patterns[o->verify_pattern_bytes], + o->buffer_pattern_bytes); + o->verify_fatal = le32_to_cpu(top->verify_fatal); o->verify_dump = le32_to_cpu(top->verify_dump); o->verify_async = le32_to_cpu(top->verify_async); @@ -268,7 +282,6 @@ void convert_thread_options_to_cpu(struct thread_options *o, o->zero_buffers = le32_to_cpu(top->zero_buffers); o->refill_buffers = le32_to_cpu(top->refill_buffers); o->scramble_buffers = le32_to_cpu(top->scramble_buffers); - o->buffer_pattern_bytes = le32_to_cpu(top->buffer_pattern_bytes); o->time_based = le32_to_cpu(top->time_based); o->disable_lat = le32_to_cpu(top->disable_lat); o->disable_clat = le32_to_cpu(top->disable_clat); @@ -334,6 +347,8 @@ void convert_thread_options_to_cpu(struct thread_options *o, uint8_t verify_cpumask[FIO_TOP_STR_MAX]; uint8_t log_gz_cpumask[FIO_TOP_STR_MAX]; #endif + + return 0; } void convert_thread_options_to_net(struct thread_options_pack *top, @@ -572,8 +587,9 @@ void convert_thread_options_to_net(struct thread_options_pack *top, top->max_latency[i] = __cpu_to_le64(o->max_latency[i]); } - memcpy(top->verify_pattern, o->verify_pattern, MAX_PATTERN_SIZE); - memcpy(top->buffer_pattern, o->buffer_pattern, MAX_PATTERN_SIZE); + memcpy(top->patterns, o->verify_pattern, o->verify_pattern_bytes); + memcpy(&top->patterns[o->verify_pattern_bytes], o->buffer_pattern, + o->buffer_pattern_bytes); top->size = __cpu_to_le64(o->size); top->io_size = __cpu_to_le64(o->io_size); @@ -620,7 +636,6 @@ void convert_thread_options_to_net(struct thread_options_pack *top, uint8_t verify_cpumask[FIO_TOP_STR_MAX]; uint8_t log_gz_cpumask[FIO_TOP_STR_MAX]; #endif - } /* @@ -630,18 +645,32 @@ void convert_thread_options_to_net(struct thread_options_pack *top, */ int fio_test_cconv(struct thread_options *__o) { - struct thread_options o; - struct thread_options_pack top1, top2; - - memset(&top1, 0, sizeof(top1)); - memset(&top2, 0, sizeof(top2)); - - convert_thread_options_to_net(&top1, __o); - memset(&o, 0, sizeof(o)); - convert_thread_options_to_cpu(&o, &top1); - convert_thread_options_to_net(&top2, &o); - - free_thread_options_to_cpu(&o); - - return memcmp(&top1, &top2, sizeof(top1)); + struct thread_options o1 = *__o, o2; + struct thread_options_pack *top1, *top2; + size_t top_sz; + int ret; + + o1.verify_pattern_bytes = 61; + memset(o1.verify_pattern, 'V', o1.verify_pattern_bytes); + o1.buffer_pattern_bytes = 15; + memset(o1.buffer_pattern, 'B', o1.buffer_pattern_bytes); + + top_sz = thread_options_pack_size(&o1); + top1 = calloc(1, top_sz); + top2 = calloc(1, top_sz); + + convert_thread_options_to_net(top1, &o1); + memset(&o2, 0, sizeof(o2)); + ret = convert_thread_options_to_cpu(&o2, top1, top_sz); + if (ret) + goto out; + + convert_thread_options_to_net(top2, &o2); + ret = memcmp(top1, top2, top_sz); + +out: + free_thread_options_to_cpu(&o2); + free(top2); + free(top1); + return ret; } diff --git a/client.c b/client.c index 37da74bca5..51496c7702 100644 --- a/client.c +++ b/client.c @@ -922,13 +922,20 @@ int fio_clients_send_ini(const char *filename) int fio_client_update_options(struct fio_client *client, struct thread_options *o, uint64_t *tag) { - struct cmd_add_job_pdu pdu; + size_t cmd_sz = offsetof(struct cmd_add_job_pdu, top) + + thread_options_pack_size(o); + struct cmd_add_job_pdu *pdu; + int ret; - pdu.thread_number = cpu_to_le32(client->thread_number); - pdu.groupid = cpu_to_le32(client->groupid); - convert_thread_options_to_net(&pdu.top, o); + pdu = malloc(cmd_sz); + pdu->thread_number = cpu_to_le32(client->thread_number); + pdu->groupid = cpu_to_le32(client->groupid); + convert_thread_options_to_net(&pdu->top, o); - return fio_net_send_cmd(client->fd, FIO_NET_CMD_UPDATE_JOB, &pdu, sizeof(pdu), tag, &client->cmd_list); + ret = fio_net_send_cmd(client->fd, FIO_NET_CMD_UPDATE_JOB, pdu, + cmd_sz, tag, &client->cmd_list); + free(pdu); + return ret; } static void convert_io_stat(struct io_stat *dst, struct io_stat *src) diff --git a/gclient.c b/gclient.c index c59bcfe2f6..73f64b3b87 100644 --- a/gclient.c +++ b/gclient.c @@ -553,12 +553,15 @@ static void gfio_quit_op(struct fio_client *client, struct fio_net_cmd *cmd) } static struct thread_options *gfio_client_add_job(struct gfio_client *gc, - struct thread_options_pack *top) + struct thread_options_pack *top, size_t top_sz) { struct gfio_client_options *gco; gco = calloc(1, sizeof(*gco)); - convert_thread_options_to_cpu(&gco->o, top); + if (convert_thread_options_to_cpu(&gco->o, top, top_sz)) { + dprint(FD_NET, "client: failed parsing add_job command\n"); + return NULL; + } INIT_FLIST_HEAD(&gco->list); flist_add_tail(&gco->list, &gc->o_list); gc->o_list_nr = 1; @@ -577,7 +580,10 @@ static void gfio_add_job_op(struct fio_client *client, struct fio_net_cmd *cmd) p->thread_number = le32_to_cpu(p->thread_number); p->groupid = le32_to_cpu(p->groupid); - o = gfio_client_add_job(gc, &p->top); + o = gfio_client_add_job(gc, &p->top, + cmd->pdu_len - offsetof(struct cmd_add_job_pdu, top)); + if (o == NULL) + return; gdk_threads_enter(); diff --git a/server.c b/server.c index b869d38727..a6347efd82 100644 --- a/server.c +++ b/server.c @@ -1082,6 +1082,7 @@ static int handle_update_job_cmd(struct fio_net_cmd *cmd) struct cmd_add_job_pdu *pdu = (struct cmd_add_job_pdu *) cmd->payload; struct thread_data *td; uint32_t tnumber; + int ret; tnumber = le32_to_cpu(pdu->thread_number); @@ -1093,8 +1094,9 @@ static int handle_update_job_cmd(struct fio_net_cmd *cmd) } td = tnumber_to_td(tnumber); - convert_thread_options_to_cpu(&td->o, &pdu->top); - send_update_job_reply(cmd->tag, 0); + ret = convert_thread_options_to_cpu(&td->o, &pdu->top, + cmd->pdu_len - offsetof(struct cmd_add_job_pdu, top)); + send_update_job_reply(cmd->tag, ret); return 0; } @@ -2323,15 +2325,18 @@ int fio_send_iolog(struct thread_data *td, struct io_log *log, const char *name) void fio_server_send_add_job(struct thread_data *td) { - struct cmd_add_job_pdu pdu = { - .thread_number = cpu_to_le32(td->thread_number), - .groupid = cpu_to_le32(td->groupid), - }; + struct cmd_add_job_pdu *pdu; + size_t cmd_sz = offsetof(struct cmd_add_job_pdu, top) + + thread_options_pack_size(&td->o); - convert_thread_options_to_net(&pdu.top, &td->o); + pdu = malloc(cmd_sz); + pdu->thread_number = cpu_to_le32(td->thread_number); + pdu->groupid = cpu_to_le32(td->groupid); - fio_net_queue_cmd(FIO_NET_CMD_ADD_JOB, &pdu, sizeof(pdu), NULL, - SK_F_COPY); + convert_thread_options_to_net(&pdu->top, &td->o); + + fio_net_queue_cmd(FIO_NET_CMD_ADD_JOB, pdu, cmd_sz, NULL, SK_F_COPY); + free(pdu); } void fio_server_send_start(struct thread_data *td) diff --git a/server.h b/server.h index b0c5e2dfaf..2813302028 100644 --- a/server.h +++ b/server.h @@ -51,7 +51,7 @@ struct fio_net_cmd_reply { }; enum { - FIO_SERVER_VER = 97, + FIO_SERVER_VER = 98, FIO_SERVER_MAX_FRAGMENT_PDU = 1024, FIO_SERVER_MAX_CMD_MB = 2048, diff --git a/thread_options.h b/thread_options.h index 634070af00..d2897ac274 100644 --- a/thread_options.h +++ b/thread_options.h @@ -464,7 +464,6 @@ struct thread_options_pack { uint32_t do_verify; uint32_t verify_interval; uint32_t verify_offset; - uint8_t verify_pattern[MAX_PATTERN_SIZE]; uint32_t verify_pattern_bytes; uint32_t verify_fatal; uint32_t verify_dump; @@ -572,7 +571,6 @@ struct thread_options_pack { uint32_t zero_buffers; uint32_t refill_buffers; uint32_t scramble_buffers; - uint8_t buffer_pattern[MAX_PATTERN_SIZE]; uint32_t buffer_pattern_bytes; uint32_t compress_percentage; uint32_t compress_chunk; @@ -699,9 +697,16 @@ struct thread_options_pack { uint32_t log_entries; uint32_t log_prio; + + /* + * verify_pattern followed by buffer_pattern from the unpacked struct + */ + uint8_t patterns[]; } __attribute__((packed)); -extern void convert_thread_options_to_cpu(struct thread_options *o, struct thread_options_pack *top); +extern int convert_thread_options_to_cpu(struct thread_options *o, + struct thread_options_pack *top, size_t top_sz); +extern size_t thread_options_pack_size(struct thread_options *o); extern void convert_thread_options_to_net(struct thread_options_pack *top, struct thread_options *); extern int fio_test_cconv(struct thread_options *); extern void options_default_fill(struct thread_options *o); From 6c9397396eb83a6ce64a998795e7a50552e4337e Mon Sep 17 00:00:00 2001 From: Logan Gunthorpe Date: Fri, 18 Nov 2022 16:15:57 -0700 Subject: [PATCH 0341/1097] lib/pattern: Support NULL output buffer in parse_and_fill_pattern() Support passing a NULL output buffer to parse_and_fill_pattern(). Each formatting function simply needs to avoid accessing the buffer when it is NULL. This allows calculating the required size of the buffer before one might be allocated. This will be useful in a subsequent patch for dynamically allocating the pattern buffers. Signed-off-by: Logan Gunthorpe Signed-off-by: Vincent Fu --- lib/pattern.c | 35 +++++++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/lib/pattern.c b/lib/pattern.c index d8203630d3..70d0313d23 100644 --- a/lib/pattern.c +++ b/lib/pattern.c @@ -51,9 +51,17 @@ static const char *parse_file(const char *beg, char *out, if (fd < 0) goto err_free_out; - count = read(fd, out, out_len); - if (count == -1) - goto err_free_close_out; + if (out) { + count = read(fd, out, out_len); + if (count == -1) + goto err_free_close_out; + } else { + count = lseek(fd, 0, SEEK_END); + if (count == -1) + goto err_free_close_out; + if (count >= out_len) + count = out_len; + } *filled = count; close(fd); @@ -100,7 +108,8 @@ static const char *parse_string(const char *beg, char *out, if (end - beg > out_len) return NULL; - memcpy(out, beg, end - beg); + if (out) + memcpy(out, beg, end - beg); *filled = end - beg; /* Catch up quote */ @@ -156,12 +165,14 @@ static const char *parse_number(const char *beg, char *out, i = 0; if (!lval) { num = 0; - out[i] = 0x00; + if (out) + out[i] = 0x00; i = 1; } else { val = (unsigned int)lval; for (; val && out_len; out_len--, i++, val >>= 8) - out[i] = val & 0xff; + if (out) + out[i] = val & 0xff; if (val) return NULL; } @@ -183,7 +194,8 @@ static const char *parse_number(const char *beg, char *out, const char *fmt; fmt = (num & 1 ? "%1hhx" : "%2hhx"); - sscanf(beg, fmt, &out[i]); + if (out) + sscanf(beg, fmt, &out[i]); if (num & 1) { num++; beg--; @@ -251,7 +263,8 @@ static const char *parse_format(const char *in, char *out, unsigned int parsed, if (f->desc->len > out_len) return NULL; - memset(out, '\0', f->desc->len); + if (out) + memset(out, '\0', f->desc->len); *filled = f->desc->len; return in + len; @@ -262,7 +275,9 @@ static const char *parse_format(const char *in, char *out, unsigned int parsed, * numbers and pattern formats. * @in - string input * @in_len - size of the input string - * @out - output buffer where parsed result will be put + * @out - output buffer where parsed result will be put, may be NULL + * in which case this function just calculates the required + * length of the buffer * @out_len - lengths of the output buffer * @fmt_desc - array of pattern format descriptors [input] * @fmt - array of pattern formats [output] @@ -314,7 +329,7 @@ int parse_and_fill_pattern(const char *in, unsigned int in_len, const char *beg, *end, *out_beg = out; unsigned int total = 0, fmt_rem = 0; - if (!in || !in_len || !out || !out_len) + if (!in || !in_len || !out_len) return -EINVAL; if (fmt_sz_out) fmt_rem = *fmt_sz_out; From accccb191e6c20242e3f7ad7b5f705dcba118a11 Mon Sep 17 00:00:00 2001 From: Logan Gunthorpe Date: Fri, 18 Nov 2022 16:15:58 -0700 Subject: [PATCH 0342/1097] lib/pattern: Support short repeated read calls when loading from file Once a pattern file can be much larger, it will be possible that kernel will return a short read while loading the file and thus may randomly only load part of the file. Fix this by putting the read in a loop so the entire file will be read. Signed-off-by: Logan Gunthorpe Signed-off-by: Vincent Fu --- lib/pattern.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/lib/pattern.c b/lib/pattern.c index 70d0313d23..d324263c2b 100644 --- a/lib/pattern.c +++ b/lib/pattern.c @@ -32,7 +32,7 @@ static const char *parse_file(const char *beg, char *out, const char *end; char *file; int fd; - ssize_t count; + ssize_t rc, count = 0; if (!out_len) goto err_out; @@ -52,9 +52,16 @@ static const char *parse_file(const char *beg, char *out, goto err_free_out; if (out) { - count = read(fd, out, out_len); - if (count == -1) - goto err_free_close_out; + while (1) { + rc = read(fd, out, out_len - count); + if (rc == 0) + break; + if (rc == -1) + goto err_free_close_out; + + count += rc; + out += rc; + } } else { count = lseek(fd, 0, SEEK_END); if (count == -1) From 1dc47d6bccb7c64ee8cd3923e5ec9e8d3bb8a96e Mon Sep 17 00:00:00 2001 From: Logan Gunthorpe Date: Fri, 18 Nov 2022 16:15:59 -0700 Subject: [PATCH 0343/1097] options: Support arbitrarily long pattern buffers Dynamically allocate the pattern buffer to remove the 512B length restriction. To accomplish this, store a pointer instead of a fixed block of memory for the buffers in the thread_options structure. Then introduce and use the function parse_and_fill_pattern_alloc() which will calculate the approprite size of the buffer and allocate it before filling it. The buffers will be freed, along with a number of string buffers in free_thread_options_to_cpu(). They will also be reallocated (if necessary) when receiving them over the wire with convert_thread_options_to_cpu(). This allows for specifying real world compressible data (eg. The Calgary Corpus) for the buffer_pattern option. Signed-off-by: Logan Gunthorpe Signed-off-by: Vincent Fu --- cconv.c | 11 ++++++++++ lib/pattern.c | 52 +++++++++++++++++++++++++++++++++++++++++++----- lib/pattern.h | 21 ++++++++++++++----- options.c | 10 +++++----- stat.h | 1 - thread_options.h | 4 ++-- 6 files changed, 81 insertions(+), 18 deletions(-) diff --git a/cconv.c b/cconv.c index bb1af4970e..d755844f51 100644 --- a/cconv.c +++ b/cconv.c @@ -48,6 +48,9 @@ static void free_thread_options_to_cpu(struct thread_options *o) free(o->profile); free(o->cgroup); + free(o->verify_pattern); + free(o->buffer_pattern); + for (i = 0; i < DDIR_RWDIR_CNT; i++) { free(o->bssplit[i]); free(o->zone_split[i]); @@ -185,6 +188,10 @@ int convert_thread_options_to_cpu(struct thread_options *o, thread_options_pack_size(o) > top_sz) return -EINVAL; + o->verify_pattern = realloc(o->verify_pattern, + o->verify_pattern_bytes); + o->buffer_pattern = realloc(o->buffer_pattern, + o->buffer_pattern_bytes); memcpy(o->verify_pattern, top->patterns, o->verify_pattern_bytes); memcpy(o->buffer_pattern, &top->patterns[o->verify_pattern_bytes], o->buffer_pattern_bytes); @@ -651,8 +658,10 @@ int fio_test_cconv(struct thread_options *__o) int ret; o1.verify_pattern_bytes = 61; + o1.verify_pattern = malloc(o1.verify_pattern_bytes); memset(o1.verify_pattern, 'V', o1.verify_pattern_bytes); o1.buffer_pattern_bytes = 15; + o1.buffer_pattern = malloc(o1.buffer_pattern_bytes); memset(o1.buffer_pattern, 'B', o1.buffer_pattern_bytes); top_sz = thread_options_pack_size(&o1); @@ -672,5 +681,7 @@ int fio_test_cconv(struct thread_options *__o) free_thread_options_to_cpu(&o2); free(top2); free(top1); + free(o1.buffer_pattern); + free(o1.verify_pattern); return ret; } diff --git a/lib/pattern.c b/lib/pattern.c index d324263c2b..1ae05758f7 100644 --- a/lib/pattern.c +++ b/lib/pattern.c @@ -327,11 +327,11 @@ static const char *parse_format(const char *in, char *out, unsigned int parsed, * * Returns number of bytes filled or err < 0 in case of failure. */ -int parse_and_fill_pattern(const char *in, unsigned int in_len, - char *out, unsigned int out_len, - const struct pattern_fmt_desc *fmt_desc, - struct pattern_fmt *fmt, - unsigned int *fmt_sz_out) +static int parse_and_fill_pattern(const char *in, unsigned int in_len, + char *out, unsigned int out_len, + const struct pattern_fmt_desc *fmt_desc, + struct pattern_fmt *fmt, + unsigned int *fmt_sz_out) { const char *beg, *end, *out_beg = out; unsigned int total = 0, fmt_rem = 0; @@ -392,6 +392,48 @@ int parse_and_fill_pattern(const char *in, unsigned int in_len, return total; } +/** + * parse_and_fill_pattern_alloc() - Parses combined input, which consists of + * strings, numbers and pattern formats and + * allocates a buffer for the result. + * + * @in - string input + * @in_len - size of the input string + * @out - pointer to the output buffer pointer, this will be set to the newly + * allocated pattern buffer which must be freed by the caller + * @fmt_desc - array of pattern format descriptors [input] + * @fmt - array of pattern formats [output] + * @fmt_sz - pointer where the size of pattern formats array stored [input], + * after successful parsing this pointer will contain the number + * of parsed formats if any [output]. + * + * See documentation on parse_and_fill_pattern() above for a description + * of the functionality. + * + * Returns number of bytes filled or err < 0 in case of failure. + */ +int parse_and_fill_pattern_alloc(const char *in, unsigned int in_len, + char **out, const struct pattern_fmt_desc *fmt_desc, + struct pattern_fmt *fmt, unsigned int *fmt_sz_out) +{ + int count; + + count = parse_and_fill_pattern(in, in_len, NULL, MAX_PATTERN_SIZE, + fmt_desc, fmt, fmt_sz_out); + if (count < 0) + return count; + + *out = malloc(count); + count = parse_and_fill_pattern(in, in_len, *out, count, fmt_desc, + fmt, fmt_sz_out); + if (count < 0) { + free(*out); + *out = NULL; + } + + return count; +} + /** * dup_pattern() - Duplicates part of the pattern all over the buffer. * diff --git a/lib/pattern.h b/lib/pattern.h index a6d9d6b427..7123b42d67 100644 --- a/lib/pattern.h +++ b/lib/pattern.h @@ -1,6 +1,19 @@ #ifndef FIO_PARSE_PATTERN_H #define FIO_PARSE_PATTERN_H +/* + * The pattern is dynamically allocated, but that doesn't mean there + * are not limits. The network protocol has a limit of + * FIO_SERVER_MAX_CMD_MB and potentially two patterns must fit in there. + * There's also a need to verify the incoming data from the network and + * this provides a sensible check. + * + * 128MiB is an arbitrary limit that meets these criteria. The patterns + * tend to be truncated at the IO size anyway and IO sizes that large + * aren't terribly practical. + */ +#define MAX_PATTERN_SIZE (128 << 20) + /** * Pattern format description. The input for 'parse_pattern'. * Describes format with its name and callback, which should @@ -21,11 +34,9 @@ struct pattern_fmt { const struct pattern_fmt_desc *desc; }; -int parse_and_fill_pattern(const char *in, unsigned int in_len, - char *out, unsigned int out_len, - const struct pattern_fmt_desc *fmt_desc, - struct pattern_fmt *fmt, - unsigned int *fmt_sz_out); +int parse_and_fill_pattern_alloc(const char *in, unsigned int in_len, + char **out, const struct pattern_fmt_desc *fmt_desc, + struct pattern_fmt *fmt, unsigned int *fmt_sz_out); int paste_format_inplace(char *pattern, unsigned int pattern_len, struct pattern_fmt *fmt, unsigned int fmt_sz, diff --git a/options.c b/options.c index 9e4d8cd1a9..49612345b3 100644 --- a/options.c +++ b/options.c @@ -1488,8 +1488,8 @@ static int str_buffer_pattern_cb(void *data, const char *input) int ret; /* FIXME: for now buffer pattern does not support formats */ - ret = parse_and_fill_pattern(input, strlen(input), td->o.buffer_pattern, - MAX_PATTERN_SIZE, NULL, NULL, NULL); + ret = parse_and_fill_pattern_alloc(input, strlen(input), + &td->o.buffer_pattern, NULL, NULL, NULL); if (ret < 0) return 1; @@ -1537,9 +1537,9 @@ static int str_verify_pattern_cb(void *data, const char *input) int ret; td->o.verify_fmt_sz = FIO_ARRAY_SIZE(td->o.verify_fmt); - ret = parse_and_fill_pattern(input, strlen(input), td->o.verify_pattern, - MAX_PATTERN_SIZE, fmt_desc, - td->o.verify_fmt, &td->o.verify_fmt_sz); + ret = parse_and_fill_pattern_alloc(input, strlen(input), + &td->o.verify_pattern, fmt_desc, td->o.verify_fmt, + &td->o.verify_fmt_sz); if (ret < 0) return 1; diff --git a/stat.h b/stat.h index 4c3bf71f3b..8ceabc48c7 100644 --- a/stat.h +++ b/stat.h @@ -142,7 +142,6 @@ enum block_info_state { BLOCK_STATE_COUNT, }; -#define MAX_PATTERN_SIZE 512 #define FIO_JOBNAME_SIZE 128 #define FIO_JOBDESC_SIZE 256 #define FIO_VERROR_SIZE 128 diff --git a/thread_options.h b/thread_options.h index d2897ac274..74e7ea4586 100644 --- a/thread_options.h +++ b/thread_options.h @@ -144,7 +144,7 @@ struct thread_options { unsigned int do_verify; unsigned int verify_interval; unsigned int verify_offset; - char verify_pattern[MAX_PATTERN_SIZE]; + char *verify_pattern; unsigned int verify_pattern_bytes; struct pattern_fmt verify_fmt[8]; unsigned int verify_fmt_sz; @@ -256,7 +256,7 @@ struct thread_options { unsigned int zero_buffers; unsigned int refill_buffers; unsigned int scramble_buffers; - char buffer_pattern[MAX_PATTERN_SIZE]; + char *buffer_pattern; unsigned int buffer_pattern_bytes; unsigned int compress_percentage; unsigned int compress_chunk; From 1fb215e991d260a128e35d761f6850e8d9e4c333 Mon Sep 17 00:00:00 2001 From: Logan Gunthorpe Date: Fri, 18 Nov 2022 16:16:00 -0700 Subject: [PATCH 0344/1097] lib/pattern: Support binary pattern buffers on windows On windows, binary files used as pattern buffers may be mangled or truncated seeing the files are openned in text mode. Fix this by passing O_BINARY on windows when openning the file. Suggested-by: Vincent Fu Signed-off-by: Logan Gunthorpe Signed-off-by: Vincent Fu --- lib/pattern.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/pattern.c b/lib/pattern.c index 1ae05758f7..9be29af6bc 100644 --- a/lib/pattern.c +++ b/lib/pattern.c @@ -47,7 +47,11 @@ static const char *parse_file(const char *beg, char *out, if (file == NULL) goto err_out; +#ifdef _WIN32 + fd = open(file, O_RDONLY | O_BINARY); +#else fd = open(file, O_RDONLY); +#endif if (fd < 0) goto err_free_out; From ede04c27b618842e32b2a3349672f6b59a1697e1 Mon Sep 17 00:00:00 2001 From: Logan Gunthorpe Date: Fri, 18 Nov 2022 16:16:01 -0700 Subject: [PATCH 0345/1097] test: add large pattern test Add a test which writes a file with a 16KB buffer pattern, then verify the file with the same pattern. The test writes a single 16KB block and thus should be the same as the patttern file. Verify that this is the case after the test is run. Signed-off-by: Logan Gunthorpe Signed-off-by: Vincent Fu --- t/jobs/t0027.fio | 14 ++++++++++++++ t/run-fio-tests.py | 29 +++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) create mode 100644 t/jobs/t0027.fio diff --git a/t/jobs/t0027.fio b/t/jobs/t0027.fio new file mode 100644 index 0000000000..b5b97a30d3 --- /dev/null +++ b/t/jobs/t0027.fio @@ -0,0 +1,14 @@ +[global] +filename=t0027file +size=16k +bs=16k + +[write_job] +readwrite=write +buffer_pattern='t0027.pattern' + +[read_job] +stonewall=1 +readwrite=read +verify=pattern +verify_pattern='t0027.pattern' diff --git a/t/run-fio-tests.py b/t/run-fio-tests.py index e5b307ac0d..a06f812683 100755 --- a/t/run-fio-tests.py +++ b/t/run-fio-tests.py @@ -799,6 +799,26 @@ def check_result(self): if self.json_data['jobs'][0]['read']['io_kbytes'] != 128: self.passed = False +class FioJobTest_t0027(FioJobTest): + def setup(self, *args, **kws): + super(FioJobTest_t0027, self).setup(*args, **kws) + self.pattern_file = os.path.join(self.test_dir, "t0027.pattern") + self.output_file = os.path.join(self.test_dir, "t0027file") + self.pattern = os.urandom(16 << 10) + with open(self.pattern_file, "wb") as f: + f.write(self.pattern) + + def check_result(self): + super(FioJobTest_t0027, self).check_result() + + if not self.passed: + return + + with open(self.output_file, "rb") as f: + data = f.read() + + if data != self.pattern: + self.passed = False class FioJobTest_iops_rate(FioJobTest): """Test consists of fio test job t0009 @@ -1214,6 +1234,15 @@ def cpucount4(cls): 'pre_success': None, 'requirements': [Requirements.not_windows], }, + { + 'test_id': 27, + 'test_class': FioJobTest_t0027, + 'job': 't0027.fio', + 'success': SUCCESS_DEFAULT, + 'pre_job': None, + 'pre_success': None, + 'requirements': [], + }, { 'test_id': 1000, 'test_class': FioExeTest, From 6fb31a012cf383cac698ac046eda31c787cc9f2f Mon Sep 17 00:00:00 2001 From: chienfuchen32 Date: Wed, 23 Nov 2022 22:33:29 +0800 Subject: [PATCH 0346/1097] update documentation typo Signed-off-by: chienfuchen32 --- HOWTO.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HOWTO.rst b/HOWTO.rst index e796f96113..bcc3da3a9c 100644 --- a/HOWTO.rst +++ b/HOWTO.rst @@ -4501,7 +4501,7 @@ Trace file format v2 ~~~~~~~~~~~~~~~~~~~~ The second version of the trace file format was added in fio version 1.17. It -allows to access more then one file per trace and has a bigger set of possible +allows to access more than one file per trace and has a bigger set of possible file actions. The first line of the trace file has to be:: From c011bf1292f14ec2bb05fa37a492509dc8640fdd Mon Sep 17 00:00:00 2001 From: Ankit Kumar Date: Wed, 23 Nov 2022 16:57:37 +0530 Subject: [PATCH 0347/1097] engines:io_uring: fix clat calculation for sqthread poll When sqthread_poll is specified for io_uring and io_uring_cmd I/O engines, fio reports garbage value for completion latencies. This is because the issue time was not recorded. Added a change for that. On the other hand submission latency for sqthread poll is really just the time it takes to fill in the SQ ring entries and any syscall required to wake up the idle kernel thread. So there is really no need to report those. This fixes the issue: https://github.com/axboe/fio/issues/1484 Signed-off-by: Ankit Kumar Signed-off-by: Vincent Fu --- engines/io_uring.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/engines/io_uring.c b/engines/io_uring.c index 3c656b773a..a9abd11dfc 100644 --- a/engines/io_uring.c +++ b/engines/io_uring.c @@ -637,12 +637,16 @@ static int fio_ioring_commit(struct thread_data *td) */ if (o->sqpoll_thread) { struct io_sq_ring *ring = &ld->sq_ring; + unsigned start = *ld->sq_ring.head; unsigned flags; flags = atomic_load_acquire(ring->flags); if (flags & IORING_SQ_NEED_WAKEUP) io_uring_enter(ld, ld->queued, 0, IORING_ENTER_SQ_WAKEUP); + fio_ioring_queued(td, start, ld->queued); + io_u_mark_submit(td, ld->queued); + ld->queued = 0; return 0; } @@ -804,6 +808,14 @@ static int fio_ioring_queue_init(struct thread_data *td) p.flags |= IORING_SETUP_SQ_AFF; p.sq_thread_cpu = o->sqpoll_cpu; } + + /* + * Submission latency for sqpoll_thread is just the time it + * takes to fill in the SQ ring entries, and any syscall if + * IORING_SQ_NEED_WAKEUP is set, we don't need to log that time + * separately. + */ + td->o.disable_slat = 1; } /* @@ -876,6 +888,14 @@ static int fio_ioring_cmd_queue_init(struct thread_data *td) p.flags |= IORING_SETUP_SQ_AFF; p.sq_thread_cpu = o->sqpoll_cpu; } + + /* + * Submission latency for sqpoll_thread is just the time it + * takes to fill in the SQ ring entries, and any syscall if + * IORING_SQ_NEED_WAKEUP is set, we don't need to log that time + * separately. + */ + td->o.disable_slat = 1; } if (o->cmd_type == FIO_URING_CMD_NVME) { p.flags |= IORING_SETUP_SQE128; From 72044c66ac7055a98c9b3021c298c81849e3c990 Mon Sep 17 00:00:00 2001 From: Ankit Kumar Date: Wed, 23 Nov 2022 16:57:38 +0530 Subject: [PATCH 0348/1097] doc: update about sqthread_poll Update that when sqthread_poll is enabled fio will not report submission latency. Signed-off-by: Ankit Kumar Signed-off-by: Vincent Fu --- HOWTO.rst | 4 +++- fio.1 | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/HOWTO.rst b/HOWTO.rst index bcc3da3a9c..4419ee1b08 100644 --- a/HOWTO.rst +++ b/HOWTO.rst @@ -2299,7 +2299,9 @@ with the caveat that when used on the command line, they must come after the kernel of available items in the SQ ring. If this option is set, the act of submitting IO will be done by a polling thread in the kernel. This frees up cycles for fio, at the cost of using more CPU in the - system. + system. As submission is just the time it takes to fill in the sqe + entries and any syscall required to wake up the idle kernel thread, + fio will not report submission latencies. .. option:: sqthread_poll_cpu=int : [io_uring] [io_uring_cmd] diff --git a/fio.1 b/fio.1 index 9e33c9e189..a156bf5d4e 100644 --- a/fio.1 +++ b/fio.1 @@ -2090,7 +2090,9 @@ sqthread_poll option. Normally fio will submit IO by issuing a system call to notify the kernel of available items in the SQ ring. If this option is set, the act of submitting IO will be done by a polling thread in the kernel. This frees up cycles for fio, at -the cost of using more CPU in the system. +the cost of using more CPU in the system. As submission is just the time it +takes to fill in the sqe entries and any syscall required to wake up the idle +kernel thread, fio will not report submission latencies. .TP .BI (io_uring,io_uring_cmd)sqthread_poll_cpu \fR=\fPint When `sqthread_poll` is set, this option provides a way to define which CPU From 91014e45431dfdccb834868db178571d323cf88d Mon Sep 17 00:00:00 2001 From: Sven Hoexter Date: Mon, 28 Nov 2022 19:47:48 +0100 Subject: [PATCH 0349/1097] Spelling: Fix allows to -> allows one to in man 1 fio Reported by lintian, a Debian package linter. Signed-off-by: Sven Hoexter --- fio.1 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/fio.1 b/fio.1 index a156bf5d4e..7f29bdbe7c 100644 --- a/fio.1 +++ b/fio.1 @@ -830,7 +830,7 @@ so. Default: false. .BI max_open_zones \fR=\fPint When running a random write test across an entire drive many more zones will be open than in a typical application workload. Hence this command line option -that allows to limit the number of open zones. The number of open zones is +that allows one to limit the number of open zones. The number of open zones is defined as the number of zones to which write commands are issued by all threads/processes. .TP @@ -1224,7 +1224,7 @@ map. For the \fBnormal\fR distribution, a normal (Gaussian) deviation is supplied as a value between 0 and 100. .P The second, optional float is allowed for \fBpareto\fR, \fBzipf\fR and \fBnormal\fR -distributions. It allows to set base of distribution in non-default place, giving +distributions. It allows one to set base of distribution in non-default place, giving more control over most probable outcome. This value is in range [0-1] which maps linearly to range of possible random values. Defaults are: random for \fBpareto\fR and \fBzipf\fR, and 0.5 for \fBnormal\fR. @@ -4213,7 +4213,7 @@ This format is not supported in fio versions >= 1.20\-rc3. .TP .B Trace file format v2 The second version of the trace file format was added in fio version 1.17. It -allows to access more then one file per trace and has a bigger set of possible +allows one to access more then one file per trace and has a bigger set of possible file actions. .RS .P From 80ba3068c537673dc289d17e819289632a9b7df3 Mon Sep 17 00:00:00 2001 From: Sven Hoexter Date: Mon, 28 Nov 2022 20:06:36 +0100 Subject: [PATCH 0350/1097] Use correct backslash escape in man 1 fio The usage of '\\' to create a literal backslash enclosed in apostrophe is sometimes wrongly rendered as an acute accent. Reported by lintian, a Debian package linter. Signed-off-by: Sven Hoexter --- fio.1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fio.1 b/fio.1 index 7f29bdbe7c..a28ec03206 100644 --- a/fio.1 +++ b/fio.1 @@ -569,7 +569,7 @@ by this option will be \fBsize\fR divided by number of files unless an explicit size is specified by \fBfilesize\fR. .RS .P -Each colon in the wanted path must be escaped with a '\\' +Each colon in the wanted path must be escaped with a '\e' character. For instance, if the path is `/dev/dsk/foo@3,0:c' then you would use `filename=/dev/dsk/foo@3,0\\:c' and if the path is `F:\\filename' then you would use `filename=F\\:\\filename'. From 12efafa35d63b9d75e7810a6e8d3c7d7345f76e8 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Tue, 29 Nov 2022 13:05:51 -0500 Subject: [PATCH 0351/1097] docs: synchronize fio.1 and HOWTO changes A couple recent patches changed only one of fio.1 or HOWTO. This patch synchronizes the changes between the two documents. Signed-off-by: Vincent Fu --- HOWTO.rst | 6 +++--- fio.1 | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/HOWTO.rst b/HOWTO.rst index 4419ee1b08..6f5c964ae3 100644 --- a/HOWTO.rst +++ b/HOWTO.rst @@ -1054,7 +1054,7 @@ Target file/device When running a random write test across an entire drive many more zones will be open than in a typical application workload. Hence this - command line option that allows to limit the number of open zones. The + command line option that allows one to limit the number of open zones. The number of open zones is defined as the number of zones to which write commands are issued. @@ -1446,7 +1446,7 @@ I/O type supplied as a value between 0 and 100. The second, optional float is allowed for **pareto**, **zipf** and **normal** distributions. - It allows to set base of distribution in non-default place, giving more control + It allows one to set base of distribution in non-default place, giving more control over most probable outcome. This value is in range [0-1] which maps linearly to range of possible random values. Defaults are: random for **pareto** and **zipf**, and 0.5 for **normal**. @@ -4503,7 +4503,7 @@ Trace file format v2 ~~~~~~~~~~~~~~~~~~~~ The second version of the trace file format was added in fio version 1.17. It -allows to access more than one file per trace and has a bigger set of possible +allows one to access more than one file per trace and has a bigger set of possible file actions. The first line of the trace file has to be:: diff --git a/fio.1 b/fio.1 index a28ec03206..e87c29a76d 100644 --- a/fio.1 +++ b/fio.1 @@ -4213,7 +4213,7 @@ This format is not supported in fio versions >= 1.20\-rc3. .TP .B Trace file format v2 The second version of the trace file format was added in fio version 1.17. It -allows one to access more then one file per trace and has a bigger set of possible +allows one to access more than one file per trace and has a bigger set of possible file actions. .RS .P From 967c5441fa3d3932ec50ea5623411cc6e8589463 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Tue, 29 Nov 2022 17:09:41 -0500 Subject: [PATCH 0352/1097] docs: description for experimental_verify Explain how experimental_verify differs from standard verify. Signed-off-by: Vincent Fu --- HOWTO.rst | 5 ++++- fio.1 | 4 +++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/HOWTO.rst b/HOWTO.rst index 6f5c964ae3..2ea8455874 100644 --- a/HOWTO.rst +++ b/HOWTO.rst @@ -3612,7 +3612,10 @@ Verification .. option:: experimental_verify=bool - Enable experimental verification. + Enable experimental verification. Standard verify records I/O metadata + for later use during the verification phase. Experimental verify + instead resets the file after the write phase and then replays I/Os for + the verification phase. Steady state ~~~~~~~~~~~~ diff --git a/fio.1 b/fio.1 index e87c29a76d..746c447282 100644 --- a/fio.1 +++ b/fio.1 @@ -3324,7 +3324,9 @@ Verify that trim/discarded blocks are returned as zeros. Trim this number of I/O blocks. .TP .BI experimental_verify \fR=\fPbool -Enable experimental verification. +Enable experimental verification. Standard verify records I/O metadata for +later use during the verification phase. Experimental verify instead resets the +file after the write phase and then replays I/Os for the verification phase. .SS "Steady state" .TP .BI steadystate \fR=\fPstr:float "\fR,\fP ss" \fR=\fPstr:float From 6d8fe6e847bb43cf7db5eee4cf58fd490f12be47 Mon Sep 17 00:00:00 2001 From: Shin'ichiro Kawasaki Date: Thu, 1 Dec 2022 11:44:25 +0900 Subject: [PATCH 0353/1097] backend: respect return value of init_io_u_buffers When workloads require large buffer for I/O, fio fails to allocate I/O buffer but does not report meaningful error message. It just accesses to null pointer and fail with signal 11. This symptom is observed with the command line below: $ fio --name=job --filename=/tmp/fio --rw=write --bs=1g --size=1g \ --iodepth=128 --ioengine=libaio The I/O buffer allocation is done in function init_io_u_buffers. The allocation failure is not reported because return value of the function is ignored. Check the return value and report to the higher layer. Fixes: 71e6e5a2fd5c ("iolog replay: Realloc io_u buffers to adapt to operation size.") Signed-off-by: Shin'ichiro Kawasaki Link: https://lore.kernel.org/r/20221201024425.2340442-1-shinichiro.kawasaki@wdc.com Signed-off-by: Jens Axboe --- backend.c | 3 ++- blktrace.c | 3 ++- iolog.c | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/backend.c b/backend.c index ba954a6b64..928e524a37 100644 --- a/backend.c +++ b/backend.c @@ -1301,7 +1301,8 @@ static int init_io_u(struct thread_data *td) } } - init_io_u_buffers(td); + if (init_io_u_buffers(td)) + return 1; if (init_file_completion_logging(td, max_units)) return 1; diff --git a/blktrace.c b/blktrace.c index 00e5f9a9b7..d5c8aee70b 100644 --- a/blktrace.c +++ b/blktrace.c @@ -545,7 +545,8 @@ bool read_blktrace(struct thread_data* td) td->o.max_bs[DDIR_TRIM] = max(td->o.max_bs[DDIR_TRIM], rw_bs[DDIR_TRIM]); io_u_quiesce(td); free_io_mem(td); - init_io_u_buffers(td); + if (init_io_u_buffers(td)) + return false; } return true; } diff --git a/iolog.c b/iolog.c index aa9c3bb1e4..62f2f524c7 100644 --- a/iolog.c +++ b/iolog.c @@ -620,7 +620,8 @@ static bool read_iolog(struct thread_data *td) { io_u_quiesce(td); free_io_mem(td); - init_io_u_buffers(td); + if (init_io_u_buffers(td)) + return false; } return true; } From 942d66c85ee8f007ea5f1097d097cf9a44b662a0 Mon Sep 17 00:00:00 2001 From: Ankit Kumar Date: Thu, 1 Dec 2022 10:38:32 +0530 Subject: [PATCH 0354/1097] doc: update about size In few cases with fio option size the number of bytes of data transferred is actually less than what we specified. This can happen if there are gaps or holes while doing I/O's or if we are running a mix of sequential and random workload. Update the documentation for that. Fixes: https://github.com/axboe/fio/issues/1486 Signed-off-by: Ankit Kumar Signed-off-by: Vincent Fu --- HOWTO.rst | 7 +++++-- fio.1 | 7 +++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/HOWTO.rst b/HOWTO.rst index 2ea8455874..0aaf033a70 100644 --- a/HOWTO.rst +++ b/HOWTO.rst @@ -1875,8 +1875,11 @@ I/O size .. option:: size=int The total size of file I/O for each thread of this job. Fio will run until - this many bytes has been transferred, unless runtime is limited by other options - (such as :option:`runtime`, for instance, or increased/decreased by :option:`io_size`). + this many bytes has been transferred, unless runtime is altered by other means + such as (1) :option:`runtime`, (2) :option:`io_size` (3) :option:`number_ios`, + (4) gaps/holes while doing I/O's such as ``rw=read:16K``, or (5) sequential + I/O reaching end of the file which is possible when :option:`percentage_random` + is less than 100. Fio will divide this size between the available files determined by options such as :option:`nrfiles`, :option:`filename`, unless :option:`filesize` is specified by the job. If the result of division happens to be 0, the size is diff --git a/fio.1 b/fio.1 index 746c447282..62af0bd2e6 100644 --- a/fio.1 +++ b/fio.1 @@ -1676,8 +1676,11 @@ simulate a smaller amount of memory. The amount specified is per worker. .TP .BI size \fR=\fPint[%|z] The total size of file I/O for each thread of this job. Fio will run until -this many bytes has been transferred, unless runtime is limited by other options -(such as \fBruntime\fR, for instance, or increased/decreased by \fBio_size\fR). +this many bytes has been transferred, unless runtime is altered by other means +such as (1) \fBruntime\fR, (2) \fBio_size\fR, (3) \fBnumber_ios\fR, (4) +gaps/holes while doing I/O's such as `rw=read:16K', or (5) sequential I/O +reaching end of the file which is possible when \fBpercentage_random\fR is +less than 100. Fio will divide this size between the available files determined by options such as \fBnrfiles\fR, \fBfilename\fR, unless \fBfilesize\fR is specified by the job. If the result of division happens to be 0, the size is From a601337a4d7b6dfc36677600d8d38948f2928d03 Mon Sep 17 00:00:00 2001 From: Alberto Faria Date: Thu, 1 Dec 2022 22:07:54 +0000 Subject: [PATCH 0355/1097] Add a libblkio engine The libblkio library provides a unified API for efficiently accessing block devices using modern high-performance block I/O interfaces like io_uring and vhost-user-blk. Using libblkio reduces the amount of code needed for interfacing with storage devices and allows developers to focus on their applcations. Add a libblkio engine that uses libblkio to perform I/O. This is useful to benchmark the library itself, and also adds support for storage interfaces and devices otherwise not supported by fio, such as virtio-blk PCI, vhost-user, and vhost-vDPA devices. See the libblkio documentation [2] or KVM Forum 2022 [3] presentation for more information on the library itself. [1] https://gitlab.com/libblkio/libblkio [2] https://libblkio.gitlab.io/libblkio/index.html [3] https://static.sched.com/hosted_files/kvmforum2022/8c/libblkio-kvm-forum-2022.pdf Signed-off-by: Alberto Faria Signed-off-by: Vincent Fu --- HOWTO.rst | 33 ++ Makefile | 6 + configure | 25 ++ engines/libblkio.c | 433 ++++++++++++++++++++++ examples/libblkio-io_uring.fio | 18 + examples/libblkio-virtio-blk-vfio-pci.fio | 19 + fio.1 | 25 ++ optgroup.h | 2 + 8 files changed, 561 insertions(+) create mode 100644 engines/libblkio.c create mode 100644 examples/libblkio-io_uring.fio create mode 100644 examples/libblkio-virtio-blk-vfio-pci.fio diff --git a/HOWTO.rst b/HOWTO.rst index 0aaf033a70..08140165e7 100644 --- a/HOWTO.rst +++ b/HOWTO.rst @@ -2195,6 +2195,12 @@ I/O engine the SPDK NVMe driver, or your own custom NVMe driver. The xnvme engine includes engine specific options. (See https://xnvme.io). + **libblkio** + Use the libblkio library + (https://gitlab.com/libblkio/libblkio). The specific + *driver* to use must be set using + :option:`libblkio_driver`. + I/O engine specific parameters ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -2847,6 +2853,33 @@ with the caveat that when used on the command line, they must come after the If this option is set. xnvme will use vectored read/write commands. +.. option:: libblkio_driver=str : [libblkio] + + The libblkio *driver* to use. Different drivers access devices through + different underlying interfaces. Available drivers depend on the + libblkio version in use and are listed at + https://libblkio.gitlab.io/libblkio/blkio.html#drivers + +.. option:: libblkio_pre_connect_props=str : [libblkio] + + A colon-separated list of libblkio properties to be set after creating + but before connecting the libblkio instance. Each property must have the + format ``=``. Colons can be escaped as ``\:``. These are + set after the engine sets any other properties, so those can be + overriden. Available properties depend on the libblkio version in use + and are listed at + https://libblkio.gitlab.io/libblkio/blkio.html#properties + +.. option:: libblkio_pre_start_props=str : [libblkio] + + A colon-separated list of libblkio properties to be set after connecting + but before starting the libblkio instance. Each property must have the + format ``=``. Colons can be escaped as ``\:``. These are + set after the engine sets any other properties, so those can be + overriden. Available properties depend on the libblkio version in use + and are listed at + https://libblkio.gitlab.io/libblkio/blkio.html#properties + I/O depth ~~~~~~~~~ diff --git a/Makefile b/Makefile index 7bd572d7df..9fd8f59b85 100644 --- a/Makefile +++ b/Makefile @@ -237,6 +237,12 @@ ifdef CONFIG_LIBXNVME xnvme_CFLAGS = $(LIBXNVME_CFLAGS) ENGINES += xnvme endif +ifdef CONFIG_LIBBLKIO + libblkio_SRCS = engines/libblkio.c + libblkio_LIBS = $(LIBBLKIO_LIBS) + libblkio_CFLAGS = $(LIBBLKIO_CFLAGS) + ENGINES += libblkio +endif ifeq ($(CONFIG_TARGET_OS), Linux) SOURCE += diskutil.c fifo.c blktrace.c cgroup.c trim.c engines/sg.c \ oslib/linux-dev-lookup.c engines/io_uring.c engines/nvme.c diff --git a/configure b/configure index 1b12d26899..6d8e3a8712 100755 --- a/configure +++ b/configure @@ -176,6 +176,7 @@ libiscsi="no" libnbd="no" libnfs="" xnvme="" +libblkio="" libzbc="" dfs="" seed_buckets="" @@ -248,6 +249,8 @@ for opt do ;; --disable-xnvme) xnvme="no" ;; + --disable-libblkio) libblkio="no" + ;; --disable-tcmalloc) disable_tcmalloc="yes" ;; --disable-libnfs) libnfs="no" @@ -304,6 +307,7 @@ if test "$show_help" = "yes" ; then echo "--enable-libiscsi Enable iscsi support" echo "--enable-libnbd Enable libnbd (NBD engine) support" echo "--disable-xnvme Disable xnvme support even if found" + echo "--disable-libblkio Disable libblkio support even if found" echo "--disable-libzbc Disable libzbc even if found" echo "--disable-tcmalloc Disable tcmalloc support" echo "--dynamic-libengines Lib-based ioengines as dynamic libraries" @@ -2663,6 +2667,22 @@ if test "$xnvme" != "no" ; then fi print_config "xnvme engine" "$xnvme" +########################################## +# Check if we have libblkio +if test "$libblkio" != "no" ; then + if check_min_lib_version blkio 1.0.0; then + libblkio="yes" + libblkio_cflags=$(pkg-config --cflags blkio) + libblkio_libs=$(pkg-config --libs blkio) + else + if test "$libblkio" = "yes" ; then + feature_not_found "libblkio" "libblkio-dev or libblkio-devel" + fi + libblkio="no" + fi +fi +print_config "libblkio engine" "$libblkio" + ########################################## # check march=armv8-a+crc+crypto if test "$march_armv8_a_crc_crypto" != "yes" ; then @@ -3276,6 +3296,11 @@ if test "$xnvme" = "yes" ; then echo "LIBXNVME_CFLAGS=$xnvme_cflags" >> $config_host_mak echo "LIBXNVME_LIBS=$xnvme_libs" >> $config_host_mak fi +if test "$libblkio" = "yes" ; then + output_sym "CONFIG_LIBBLKIO" + echo "LIBBLKIO_CFLAGS=$libblkio_cflags" >> $config_host_mak + echo "LIBBLKIO_LIBS=$libblkio_libs" >> $config_host_mak +fi if test "$dynamic_engines" = "yes" ; then output_sym "CONFIG_DYNAMIC_ENGINES" fi diff --git a/engines/libblkio.c b/engines/libblkio.c new file mode 100644 index 0000000000..7a5d427180 --- /dev/null +++ b/engines/libblkio.c @@ -0,0 +1,433 @@ +/* + * libblkio engine + * + * IO engine using libblkio to access various block I/O interfaces: + * https://gitlab.com/libblkio/libblkio + */ + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "../fio.h" +#include "../optgroup.h" +#include "../options.h" +#include "../parse.h" + +/* per-thread state */ +struct fio_blkio_data { + struct blkio *b; + struct blkioq *q; + + bool has_mem_region; /* whether mem_region is valid */ + struct blkio_mem_region mem_region; + + struct blkio_completion *completions; +}; + +struct fio_blkio_options { + void *pad; /* option fields must not have offset 0 */ + + char *driver; + char *pre_connect_props; + char *pre_start_props; +}; + +static struct fio_option options[] = { + { + .name = "libblkio_driver", + .lname = "libblkio driver name", + .type = FIO_OPT_STR_STORE, + .off1 = offsetof(struct fio_blkio_options, driver), + .help = "Name of the driver to be used by libblkio", + .category = FIO_OPT_C_ENGINE, + .group = FIO_OPT_G_LIBBLKIO, + }, + { + .name = "libblkio_pre_connect_props", + .lname = "Properties to be set before blkio_connect()", + .type = FIO_OPT_STR_STORE, + .off1 = offsetof(struct fio_blkio_options, pre_connect_props), + .help = "", + .category = FIO_OPT_C_ENGINE, + .group = FIO_OPT_G_LIBBLKIO, + }, + { + .name = "libblkio_pre_start_props", + .lname = "Properties to be set before blkio_start()", + .type = FIO_OPT_STR_STORE, + .off1 = offsetof(struct fio_blkio_options, pre_start_props), + .help = "", + .category = FIO_OPT_C_ENGINE, + .group = FIO_OPT_G_LIBBLKIO, + }, + { + .name = NULL, + }, +}; + +static int fio_blkio_set_props_from_str(struct blkio *b, const char *opt_name, + const char *str) { + int ret = 0; + char *new_str, *name, *value; + + if (!str) + return 0; + + /* iteration can mutate string, so copy it */ + new_str = strdup(str); + if (!new_str) { + log_err("fio: strdup() failed\n"); + return 1; + } + + /* iterate over property name-value pairs */ + while ((name = get_next_str(&new_str))) { + /* split into property name and value */ + value = strchr(name, '='); + if (!value) { + log_err("fio: missing '=' in option %s\n", opt_name); + ret = 1; + break; + } + + *value = '\0'; + ++value; + + /* strip whitespace from property name */ + strip_blank_front(&name); + strip_blank_end(name); + + if (name[0] == '\0') { + log_err("fio: empty property name in option %s\n", + opt_name); + ret = 1; + break; + } + + /* strip whitespace from property value */ + strip_blank_front(&value); + strip_blank_end(value); + + /* set property */ + if (blkio_set_str(b, name, value) != 0) { + log_err("fio: error setting property '%s' to '%s': %s\n", + name, value, blkio_get_error_msg()); + ret = 1; + break; + } + } + + free(new_str); + return ret; +} + +/* + * Log the failure of a libblkio function. + * + * `(void)func` is to ensure `func` exists and prevent typos + */ +#define fio_blkio_log_err(func) \ + ({ \ + (void)func; \ + log_err("fio: %s() failed: %s\n", #func, \ + blkio_get_error_msg()); \ + }) + +static int fio_blkio_create_and_connect(struct thread_data *td, + struct blkio **out_blkio) +{ + const struct fio_blkio_options *options = td->eo; + struct blkio *b; + int ret; + + if (!options->driver) { + log_err("fio: engine libblkio requires option libblkio_driver to be set\n"); + return 1; + } + + if (blkio_create(options->driver, &b) != 0) { + fio_blkio_log_err(blkio_create); + return 1; + } + + /* don't fail if driver doesn't have a "direct" property */ + ret = blkio_set_bool(b, "direct", td->o.odirect); + if (ret != 0 && ret != -ENOENT) { + fio_blkio_log_err(blkio_set_bool); + goto err_blkio_destroy; + } + + if (blkio_set_bool(b, "read-only", read_only) != 0) { + fio_blkio_log_err(blkio_set_bool); + goto err_blkio_destroy; + } + + if (fio_blkio_set_props_from_str(b, "libblkio_pre_connect_props", + options->pre_connect_props) != 0) + goto err_blkio_destroy; + + if (blkio_connect(b) != 0) { + fio_blkio_log_err(blkio_connect); + goto err_blkio_destroy; + } + + if (fio_blkio_set_props_from_str(b, "libblkio_pre_start_props", + options->pre_start_props) != 0) + goto err_blkio_destroy; + + *out_blkio = b; + return 0; + +err_blkio_destroy: + blkio_destroy(&b); + return 1; +} + +/* + * This callback determines the device/file size, so it creates and connects a + * blkio instance. But it is invoked from the main thread in the original fio + * process, not from the processes in which jobs will actually run. It thus + * subsequently destroys the blkio, which is recreated in the init() callback. + */ +static int fio_blkio_setup(struct thread_data *td) +{ + struct blkio *b; + int ret = 0; + uint64_t capacity; + + assert(td->files_index == 1); + + if (fio_blkio_create_and_connect(td, &b) != 0) + return 1; + + if (blkio_get_uint64(b, "capacity", &capacity) != 0) { + fio_blkio_log_err(blkio_get_uint64); + ret = 1; + goto out_blkio_destroy; + } + + td->files[0]->real_file_size = capacity; + fio_file_set_size_known(td->files[0]); + +out_blkio_destroy: + blkio_destroy(&b); + return ret; +} + +static int fio_blkio_init(struct thread_data *td) +{ + struct fio_blkio_data *data; + + /* + * Request enqueueing is fast, and it's not possible to know exactly + * when a request is submitted, so never report submission latencies. + */ + td->o.disable_slat = 1; + + data = calloc(1, sizeof(*data)); + if (!data) { + log_err("fio: calloc() failed\n"); + return 1; + } + + data->completions = calloc(td->o.iodepth, sizeof(data->completions[0])); + if (!data->completions) { + log_err("fio: calloc() failed\n"); + goto err_free; + } + + if (fio_blkio_create_and_connect(td, &data->b) != 0) + goto err_free; + + if (blkio_set_int(data->b, "num-queues", 1) != 0) { + fio_blkio_log_err(blkio_set_int); + goto err_blkio_destroy; + } + + if (blkio_start(data->b) != 0) { + fio_blkio_log_err(blkio_start); + goto err_blkio_destroy; + } + + data->q = blkio_get_queue(data->b, 0); + + /* Set data last so cleanup() does nothing if init() fails. */ + td->io_ops_data = data; + + return 0; + +err_blkio_destroy: + blkio_destroy(&data->b); +err_free: + free(data->completions); + free(data); + return 1; +} + +static void fio_blkio_cleanup(struct thread_data *td) +{ + struct fio_blkio_data *data = td->io_ops_data; + + if (data) { + blkio_destroy(&data->b); + free(data->completions); + free(data); + } +} + +#define align_up(x, y) ((((x) + (y) - 1) / (y)) * (y)) + +static int fio_blkio_iomem_alloc(struct thread_data *td, size_t size) +{ + struct fio_blkio_data *data = td->io_ops_data; + int ret; + uint64_t mem_region_alignment; + + if (blkio_get_uint64(data->b, "mem-region-alignment", + &mem_region_alignment) != 0) { + fio_blkio_log_err(blkio_get_uint64); + return 1; + } + + /* round up size to satisfy mem-region-alignment */ + size = align_up(size, (size_t)mem_region_alignment); + + if (blkio_alloc_mem_region(data->b, &data->mem_region, size) != 0) { + fio_blkio_log_err(blkio_alloc_mem_region); + ret = 1; + goto out; + } + + if (blkio_map_mem_region(data->b, &data->mem_region) != 0) { + fio_blkio_log_err(blkio_map_mem_region); + ret = 1; + goto out_free; + } + + td->orig_buffer = data->mem_region.addr; + data->has_mem_region = true; + + ret = 0; + goto out; + +out_free: + blkio_free_mem_region(data->b, &data->mem_region); +out: + return ret; +} + +static void fio_blkio_iomem_free(struct thread_data *td) +{ + struct fio_blkio_data *data = td->io_ops_data; + + if (data && data->has_mem_region) { + blkio_unmap_mem_region(data->b, &data->mem_region); + blkio_free_mem_region(data->b, &data->mem_region); + + data->has_mem_region = false; + } +} + +static int fio_blkio_open_file(struct thread_data *td, struct fio_file *f) +{ + return 0; +} + +static enum fio_q_status fio_blkio_queue(struct thread_data *td, + struct io_u *io_u) +{ + struct fio_blkio_data *data = td->io_ops_data; + + fio_ro_check(td, io_u); + + switch (io_u->ddir) { + case DDIR_READ: + blkioq_read(data->q, io_u->offset, io_u->xfer_buf, + (size_t)io_u->xfer_buflen, io_u, 0); + break; + case DDIR_WRITE: + blkioq_write(data->q, io_u->offset, io_u->xfer_buf, + (size_t)io_u->xfer_buflen, io_u, 0); + break; + case DDIR_TRIM: + blkioq_discard(data->q, io_u->offset, io_u->xfer_buflen, + io_u, 0); + break; + case DDIR_SYNC: + case DDIR_DATASYNC: + blkioq_flush(data->q, io_u, 0); + break; + default: + io_u->error = ENOTSUP; + io_u_log_error(td, io_u); + return FIO_Q_COMPLETED; + } + + return FIO_Q_QUEUED; +} + +static int fio_blkio_getevents(struct thread_data *td, unsigned int min, + unsigned int max, const struct timespec *t) +{ + struct fio_blkio_data *data = td->io_ops_data; + int n; + + n = blkioq_do_io(data->q, data->completions, (int)min, (int)max, NULL); + if (n < 0) { + fio_blkio_log_err(blkioq_do_io); + return -1; + } + + return n; +} + +static struct io_u *fio_blkio_event(struct thread_data *td, int event) +{ + struct fio_blkio_data *data = td->io_ops_data; + struct blkio_completion *completion = &data->completions[event]; + struct io_u *io_u = completion->user_data; + + io_u->error = -completion->ret; + + return io_u; +} + +FIO_STATIC struct ioengine_ops ioengine = { + .name = "libblkio", + .version = FIO_IOOPS_VERSION, + .flags = FIO_DISKLESSIO | FIO_NOEXTEND | + FIO_NO_OFFLOAD, + + .setup = fio_blkio_setup, + .init = fio_blkio_init, + .cleanup = fio_blkio_cleanup, + + .iomem_alloc = fio_blkio_iomem_alloc, + .iomem_free = fio_blkio_iomem_free, + + .open_file = fio_blkio_open_file, + + .queue = fio_blkio_queue, + .getevents = fio_blkio_getevents, + .event = fio_blkio_event, + + .options = options, + .option_struct_size = sizeof(struct fio_blkio_options), +}; + +static void fio_init fio_blkio_register(void) +{ + register_ioengine(&ioengine); +} + +static void fio_exit fio_blkio_unregister(void) +{ + unregister_ioengine(&ioengine); +} diff --git a/examples/libblkio-io_uring.fio b/examples/libblkio-io_uring.fio new file mode 100644 index 0000000000..655a0b5089 --- /dev/null +++ b/examples/libblkio-io_uring.fio @@ -0,0 +1,18 @@ +; Benchmark accessing a regular file or block device using libblkio. +; +; Replace "/dev/nvme0n1" below with the path to your file or device, or override +; it by passing the '--libblkio_pre_connect_props=path=...' flag to fio. +; +; For information on libblkio, see: https://gitlab.com/libblkio/libblkio + +[global] +ioengine=libblkio +libblkio_driver=io_uring +libblkio_pre_connect_props=path=/dev/nvme0n1 ; REPLACE THIS WITH THE RIGHT PATH +rw=randread +blocksize=4k +direct=1 +time_based=1 +runtime=10s + +[job] diff --git a/examples/libblkio-virtio-blk-vfio-pci.fio b/examples/libblkio-virtio-blk-vfio-pci.fio new file mode 100644 index 0000000000..425df4a6bf --- /dev/null +++ b/examples/libblkio-virtio-blk-vfio-pci.fio @@ -0,0 +1,19 @@ +; Benchmark accessing a PCI virtio-blk device using libblkio. +; +; Replace "/sys/bus/pci/devices/0000\:00\:01.0" below with the path to your +; device's sysfs directory, or override it by passing the +; '--libblkio_pre_connect_props=path=...' flag to fio. Note that colons in the +; path must be escaped with a backslash. +; +; For information on libblkio, see: https://gitlab.com/libblkio/libblkio + +[global] +ioengine=libblkio +libblkio_driver=virtio-blk-vfio-pci +libblkio_pre_connect_props=path=/sys/bus/pci/devices/0000\:00\:01.0 ; REPLACE THIS WITH THE RIGHT PATH +rw=randread +blocksize=4k +time_based=1 +runtime=10s + +[job] diff --git a/fio.1 b/fio.1 index 62af0bd2e6..3615f25815 100644 --- a/fio.1 +++ b/fio.1 @@ -1992,6 +1992,10 @@ I/O engine using the xNVMe C API, for NVMe devices. The xnvme engine provides flexibility to access GNU/Linux Kernel NVMe driver via libaio, IOCTLs, io_uring, the SPDK NVMe driver, or your own custom NVMe driver. The xnvme engine includes engine specific options. (See \fIhttps://xnvme.io/\fR). +.TP +.B libblkio +Use the libblkio library (\fIhttps://gitlab.com/libblkio/libblkio\fR). The +specific driver to use must be set using \fBlibblkio_driver\fR. .SS "I/O engine specific parameters" In addition, there are some parameters which are only valid when a specific \fBioengine\fR is in use. These are used identically to normal parameters, @@ -2604,6 +2608,27 @@ xnvme namespace identifier for userspace NVMe driver such as SPDK. .TP .BI (xnvme)xnvme_iovec If this option is set, xnvme will use vectored read/write commands. +.TP +.BI (libblkio)libblkio_driver \fR=\fPstr +The libblkio driver to use. Different drivers access devices through different +underlying interfaces. Available drivers depend on the libblkio version in use +and are listed at \fIhttps://libblkio.gitlab.io/libblkio/blkio.html#drivers\fR +.TP +.BI (libblkio)libblkio_pre_connect_props \fR=\fPstr +A colon-separated list of libblkio properties to be set after creating but +before connecting the libblkio instance. Each property must have the format +\fB=\fR. Colons can be escaped as \fB\\:\fR. These are set after +the engine sets any other properties, so those can be overriden. Available +properties depend on the libblkio version in use and are listed at +\fIhttps://libblkio.gitlab.io/libblkio/blkio.html#properties\fR +.TP +.BI (libblkio)libblkio_pre_start_props \fR=\fPstr +A colon-separated list of libblkio properties to be set after connecting but +before starting the libblkio instance. Each property must have the format +\fB=\fR. Colons can be escaped as \fB\\:\fR. These are set after +the engine sets any other properties, so those can be overriden. Available +properties depend on the libblkio version in use and are listed at +\fIhttps://libblkio.gitlab.io/libblkio/blkio.html#properties\fR .SS "I/O depth" .TP .BI iodepth \fR=\fPint diff --git a/optgroup.h b/optgroup.h index dc73c8f3ea..024b902f63 100644 --- a/optgroup.h +++ b/optgroup.h @@ -73,6 +73,7 @@ enum opt_category_group { __FIO_OPT_G_NFS, __FIO_OPT_G_WINDOWSAIO, __FIO_OPT_G_XNVME, + __FIO_OPT_G_LIBBLKIO, FIO_OPT_G_RATE = (1ULL << __FIO_OPT_G_RATE), FIO_OPT_G_ZONE = (1ULL << __FIO_OPT_G_ZONE), @@ -120,6 +121,7 @@ enum opt_category_group { FIO_OPT_G_DFS = (1ULL << __FIO_OPT_G_DFS), FIO_OPT_G_WINDOWSAIO = (1ULL << __FIO_OPT_G_WINDOWSAIO), FIO_OPT_G_XNVME = (1ULL << __FIO_OPT_G_XNVME), + FIO_OPT_G_LIBBLKIO = (1ULL << __FIO_OPT_G_LIBBLKIO), }; extern const struct opt_group *opt_group_from_mask(uint64_t *mask); From f2bcd554791ca002b90a9c202838ab04af0054e9 Mon Sep 17 00:00:00 2001 From: Alberto Faria Date: Thu, 1 Dec 2022 22:07:55 +0000 Subject: [PATCH 0356/1097] Add engine flag FIO_SKIPPABLE_IOMEM_ALLOC It makes it valid to set option mem/iomem even when the engine specifies iomem_alloc and iomem_free callbacks, allowing users to optionally use fio's customizable memory allocation logic instead of the engine's. This is in preparation for giving libblkio engine users the choice between controlling memory allocation or delegating it to the libblkio library. Signed-off-by: Alberto Faria Signed-off-by: Vincent Fu --- ioengines.h | 2 ++ memory.c | 22 ++++++++++++---------- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/ioengines.h b/ioengines.h index 11d2115ce7..d43540d0f6 100644 --- a/ioengines.h +++ b/ioengines.h @@ -87,6 +87,8 @@ enum fio_ioengine_flags { FIO_NO_OFFLOAD = 1 << 15, /* no async offload */ FIO_ASYNCIO_SETS_ISSUE_TIME = 1 << 16, /* async ioengine with commit function that sets issue_time */ + FIO_SKIPPABLE_IOMEM_ALLOC + = 1 << 17, /* skip iomem_alloc & iomem_free if job sets mem/iomem */ }; /* diff --git a/memory.c b/memory.c index 6cf7333375..577d3dd5af 100644 --- a/memory.c +++ b/memory.c @@ -305,16 +305,18 @@ int allocate_io_mem(struct thread_data *td) dprint(FD_MEM, "Alloc %llu for buffers\n", (unsigned long long) total_mem); /* - * If the IO engine has hooks to allocate/free memory, use those. But - * error out if the user explicitly asked for something else. + * If the IO engine has hooks to allocate/free memory and the user + * doesn't explicitly ask for something else, use those. But fail if the + * user asks for something else with an engine that doesn't allow that. */ - if (td->io_ops->iomem_alloc) { - if (fio_option_is_set(&td->o, mem_type)) { - log_err("fio: option 'mem/iomem' conflicts with specified IO engine\n"); - ret = 1; - } else - ret = td->io_ops->iomem_alloc(td, total_mem); - } else if (td->o.mem_type == MEM_MALLOC) + if (td->io_ops->iomem_alloc && fio_option_is_set(&td->o, mem_type) && + !td_ioengine_flagged(td, FIO_SKIPPABLE_IOMEM_ALLOC)) { + log_err("fio: option 'mem/iomem' conflicts with specified IO engine\n"); + ret = 1; + } else if (td->io_ops->iomem_alloc && + !fio_option_is_set(&td->o, mem_type)) + ret = td->io_ops->iomem_alloc(td, total_mem); + else if (td->o.mem_type == MEM_MALLOC) ret = alloc_mem_malloc(td, total_mem); else if (td->o.mem_type == MEM_SHM || td->o.mem_type == MEM_SHMHUGE) ret = alloc_mem_shm(td, total_mem); @@ -342,7 +344,7 @@ void free_io_mem(struct thread_data *td) if (td->o.odirect || td->o.oatomic) total_mem += page_mask; - if (td->io_ops->iomem_alloc) { + if (td->io_ops->iomem_alloc && !fio_option_is_set(&td->o, mem_type)) { if (td->io_ops->iomem_free) td->io_ops->iomem_free(td); } else if (td->o.mem_type == MEM_MALLOC) From ef9b6f2fa7a285527ae2413affb7112b74e27f77 Mon Sep 17 00:00:00 2001 From: Alberto Faria Date: Thu, 1 Dec 2022 22:07:56 +0000 Subject: [PATCH 0357/1097] engines/libblkio: Allow setting option mem/iomem This allows users to customize data buffer memory using fio's existing options. Users become responsible for ensuring that the allocated memory satisfies all constraints imposed by the libblkio driver under use. Signed-off-by: Alberto Faria Signed-off-by: Vincent Fu --- HOWTO.rst | 5 ++++- engines/libblkio.c | 46 ++++++++++++++++++++++++++++++++++++++++++++-- fio.1 | 4 +++- 3 files changed, 51 insertions(+), 4 deletions(-) diff --git a/HOWTO.rst b/HOWTO.rst index 08140165e7..69ca1e7902 100644 --- a/HOWTO.rst +++ b/HOWTO.rst @@ -2199,7 +2199,10 @@ I/O engine Use the libblkio library (https://gitlab.com/libblkio/libblkio). The specific *driver* to use must be set using - :option:`libblkio_driver`. + :option:`libblkio_driver`. If + :option:`mem`/:option:`iomem` is not specified, memory + allocation is delegated to libblkio (and so is + guaranteed to work with the selected *driver*). I/O engine specific parameters ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/engines/libblkio.c b/engines/libblkio.c index 7a5d427180..11f45e2a59 100644 --- a/engines/libblkio.c +++ b/engines/libblkio.c @@ -26,7 +26,7 @@ struct fio_blkio_data { struct blkioq *q; bool has_mem_region; /* whether mem_region is valid */ - struct blkio_mem_region mem_region; + struct blkio_mem_region mem_region; /* only if allocated by libblkio */ struct blkio_completion *completions; }; @@ -271,6 +271,47 @@ static int fio_blkio_init(struct thread_data *td) return 1; } +static int fio_blkio_post_init(struct thread_data *td) +{ + struct fio_blkio_data *data = td->io_ops_data; + + if (!data->has_mem_region) { + /* + * Memory was allocated by the fio core and not iomem_alloc(), + * so we need to register it as a memory region here. + * + * `td->orig_buffer_size` is computed like `len` below, but then + * fio can add some padding to it to make sure it is + * sufficiently aligned to the page size and the mem_align + * option. However, this can make it become unaligned to the + * "mem-region-alignment" property in ways that the user can't + * control, so we essentially recompute `td->orig_buffer_size` + * here but without adding that padding. + */ + + unsigned long long max_block_size; + struct blkio_mem_region region; + + max_block_size = max(td->o.max_bs[DDIR_READ], + max(td->o.max_bs[DDIR_WRITE], + td->o.max_bs[DDIR_TRIM])); + + region = (struct blkio_mem_region) { + .addr = td->orig_buffer, + .len = (size_t)max_block_size * + (size_t)td->o.iodepth, + .fd = -1, + }; + + if (blkio_map_mem_region(data->b, ®ion) != 0) { + fio_blkio_log_err(blkio_map_mem_region); + return 1; + } + } + + return 0; +} + static void fio_blkio_cleanup(struct thread_data *td) { struct fio_blkio_data *data = td->io_ops_data; @@ -403,10 +444,11 @@ FIO_STATIC struct ioengine_ops ioengine = { .name = "libblkio", .version = FIO_IOOPS_VERSION, .flags = FIO_DISKLESSIO | FIO_NOEXTEND | - FIO_NO_OFFLOAD, + FIO_NO_OFFLOAD | FIO_SKIPPABLE_IOMEM_ALLOC, .setup = fio_blkio_setup, .init = fio_blkio_init, + .post_init = fio_blkio_post_init, .cleanup = fio_blkio_cleanup, .iomem_alloc = fio_blkio_iomem_alloc, diff --git a/fio.1 b/fio.1 index 3615f25815..1842877402 100644 --- a/fio.1 +++ b/fio.1 @@ -1995,7 +1995,9 @@ engine specific options. (See \fIhttps://xnvme.io/\fR). .TP .B libblkio Use the libblkio library (\fIhttps://gitlab.com/libblkio/libblkio\fR). The -specific driver to use must be set using \fBlibblkio_driver\fR. +specific driver to use must be set using \fBlibblkio_driver\fR. If +\fBmem\fR/\fBiomem\fR is not specified, memory allocation is delegated to +libblkio (and so is guaranteed to work with the selected driver). .SS "I/O engine specific parameters" In addition, there are some parameters which are only valid when a specific \fBioengine\fR is in use. These are used identically to normal parameters, From a870d6ff25d7d453891763a83f5f297df653ac38 Mon Sep 17 00:00:00 2001 From: Alberto Faria Date: Thu, 1 Dec 2022 22:07:57 +0000 Subject: [PATCH 0358/1097] engines/libblkio: Add support for poll queues Configure a poll queue instead of a "regular" queue when option hipri is set. Signed-off-by: Alberto Faria Signed-off-by: Vincent Fu --- HOWTO.rst | 4 ++++ engines/libblkio.c | 25 +++++++++++++++++++++++-- fio.1 | 3 +++ 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/HOWTO.rst b/HOWTO.rst index 69ca1e7902..2b3fe9c23c 100644 --- a/HOWTO.rst +++ b/HOWTO.rst @@ -2335,6 +2335,10 @@ with the caveat that when used on the command line, they must come after the by the application. The benefits are more efficient IO for high IOPS scenarios, and lower latencies for low queue depth IO. + [libblkio] + + Use poll queues. + [pvsync2] Set RWF_HIPRI on I/O, indicating to the kernel that it's of higher priority diff --git a/engines/libblkio.c b/engines/libblkio.c index 11f45e2a59..6decfd39e4 100644 --- a/engines/libblkio.c +++ b/engines/libblkio.c @@ -37,6 +37,8 @@ struct fio_blkio_options { char *driver; char *pre_connect_props; char *pre_start_props; + + unsigned int hipri; }; static struct fio_option options[] = { @@ -67,6 +69,15 @@ static struct fio_option options[] = { .category = FIO_OPT_C_ENGINE, .group = FIO_OPT_G_LIBBLKIO, }, + { + .name = "hipri", + .lname = "Use poll queues", + .type = FIO_OPT_STR_SET, + .off1 = offsetof(struct fio_blkio_options, hipri), + .help = "Use poll queues", + .category = FIO_OPT_C_ENGINE, + .group = FIO_OPT_G_LIBBLKIO, + }, { .name = NULL, }, @@ -223,6 +234,7 @@ static int fio_blkio_setup(struct thread_data *td) static int fio_blkio_init(struct thread_data *td) { + const struct fio_blkio_options *options = td->eo; struct fio_blkio_data *data; /* @@ -246,7 +258,13 @@ static int fio_blkio_init(struct thread_data *td) if (fio_blkio_create_and_connect(td, &data->b) != 0) goto err_free; - if (blkio_set_int(data->b, "num-queues", 1) != 0) { + if (blkio_set_int(data->b, "num-queues", options->hipri ? 0 : 1) != 0) { + fio_blkio_log_err(blkio_set_int); + goto err_blkio_destroy; + } + + if (blkio_set_int(data->b, "num-poll-queues", + options->hipri ? 1 : 0) != 0) { fio_blkio_log_err(blkio_set_int); goto err_blkio_destroy; } @@ -256,7 +274,10 @@ static int fio_blkio_init(struct thread_data *td) goto err_blkio_destroy; } - data->q = blkio_get_queue(data->b, 0); + if (options->hipri) + data->q = blkio_get_poll_queue(data->b, 0); + else + data->q = blkio_get_queue(data->b, 0); /* Set data last so cleanup() does nothing if init() fails. */ td->io_ops_data = data; diff --git a/fio.1 b/fio.1 index 1842877402..7f97e94056 100644 --- a/fio.1 +++ b/fio.1 @@ -2631,6 +2631,9 @@ before starting the libblkio instance. Each property must have the format the engine sets any other properties, so those can be overriden. Available properties depend on the libblkio version in use and are listed at \fIhttps://libblkio.gitlab.io/libblkio/blkio.html#properties\fR +.TP +.BI (libblkio)hipri +Use poll queues. .SS "I/O depth" .TP .BI iodepth \fR=\fPint From 6dd4291cea1f2502f783d1f4a7cca48466b16f49 Mon Sep 17 00:00:00 2001 From: Alberto Faria Date: Thu, 1 Dec 2022 22:07:58 +0000 Subject: [PATCH 0359/1097] engines/libblkio: Add option libblkio_vectored When enabled, read and write requests are submitted as vectored requests using blkioq_{readv,writev}(), instead of using blkioq_{read,write}(). Signed-off-by: Alberto Faria Signed-off-by: Vincent Fu --- HOWTO.rst | 4 ++++ engines/libblkio.c | 46 +++++++++++++++++++++++++++++++++++++++++----- fio.1 | 3 +++ 3 files changed, 48 insertions(+), 5 deletions(-) diff --git a/HOWTO.rst b/HOWTO.rst index 2b3fe9c23c..f77d76ccf8 100644 --- a/HOWTO.rst +++ b/HOWTO.rst @@ -2887,6 +2887,10 @@ with the caveat that when used on the command line, they must come after the and are listed at https://libblkio.gitlab.io/libblkio/blkio.html#properties +.. option:: libblkio_vectored : [libblkio] + + Submit vectored read and write requests. + I/O depth ~~~~~~~~~ diff --git a/engines/libblkio.c b/engines/libblkio.c index 6decfd39e4..219bb0dc4b 100644 --- a/engines/libblkio.c +++ b/engines/libblkio.c @@ -28,6 +28,7 @@ struct fio_blkio_data { bool has_mem_region; /* whether mem_region is valid */ struct blkio_mem_region mem_region; /* only if allocated by libblkio */ + struct iovec *iovecs; /* for vectored requests */ struct blkio_completion *completions; }; @@ -39,6 +40,7 @@ struct fio_blkio_options { char *pre_start_props; unsigned int hipri; + unsigned int vectored; }; static struct fio_option options[] = { @@ -78,6 +80,15 @@ static struct fio_option options[] = { .category = FIO_OPT_C_ENGINE, .group = FIO_OPT_G_LIBBLKIO, }, + { + .name = "libblkio_vectored", + .lname = "Use blkioq_{readv,writev}()", + .type = FIO_OPT_STR_SET, + .off1 = offsetof(struct fio_blkio_options, vectored), + .help = "Use blkioq_{readv,writev}() instead of blkioq_{read,write}()", + .category = FIO_OPT_C_ENGINE, + .group = FIO_OPT_G_LIBBLKIO, + }, { .name = NULL, }, @@ -249,8 +260,9 @@ static int fio_blkio_init(struct thread_data *td) return 1; } + data->iovecs = calloc(td->o.iodepth, sizeof(data->iovecs[0])); data->completions = calloc(td->o.iodepth, sizeof(data->completions[0])); - if (!data->completions) { + if (!data->iovecs || !data->completions) { log_err("fio: calloc() failed\n"); goto err_free; } @@ -288,6 +300,7 @@ static int fio_blkio_init(struct thread_data *td) blkio_destroy(&data->b); err_free: free(data->completions); + free(data->iovecs); free(data); return 1; } @@ -340,6 +353,7 @@ static void fio_blkio_cleanup(struct thread_data *td) if (data) { blkio_destroy(&data->b); free(data->completions); + free(data->iovecs); free(data); } } @@ -405,18 +419,40 @@ static int fio_blkio_open_file(struct thread_data *td, struct fio_file *f) static enum fio_q_status fio_blkio_queue(struct thread_data *td, struct io_u *io_u) { + const struct fio_blkio_options *options = td->eo; struct fio_blkio_data *data = td->io_ops_data; fio_ro_check(td, io_u); switch (io_u->ddir) { case DDIR_READ: - blkioq_read(data->q, io_u->offset, io_u->xfer_buf, - (size_t)io_u->xfer_buflen, io_u, 0); + if (options->vectored) { + struct iovec *iov = &data->iovecs[io_u->index]; + iov->iov_base = io_u->xfer_buf; + iov->iov_len = (size_t)io_u->xfer_buflen; + + blkioq_readv(data->q, io_u->offset, iov, 1, + io_u, 0); + } else { + blkioq_read(data->q, io_u->offset, + io_u->xfer_buf, + (size_t)io_u->xfer_buflen, io_u, 0); + } break; case DDIR_WRITE: - blkioq_write(data->q, io_u->offset, io_u->xfer_buf, - (size_t)io_u->xfer_buflen, io_u, 0); + if (options->vectored) { + struct iovec *iov = &data->iovecs[io_u->index]; + iov->iov_base = io_u->xfer_buf; + iov->iov_len = (size_t)io_u->xfer_buflen; + + blkioq_writev(data->q, io_u->offset, iov, 1, + io_u, 0); + } else { + blkioq_write(data->q, io_u->offset, + io_u->xfer_buf, + (size_t)io_u->xfer_buflen, io_u, + 0); + } break; case DDIR_TRIM: blkioq_discard(data->q, io_u->offset, io_u->xfer_buflen, diff --git a/fio.1 b/fio.1 index 7f97e94056..9b8995b57e 100644 --- a/fio.1 +++ b/fio.1 @@ -2634,6 +2634,9 @@ properties depend on the libblkio version in use and are listed at .TP .BI (libblkio)hipri Use poll queues. +.TP +.BI (libblkio)libblkio_vectored +Submit vectored read and write requests. .SS "I/O depth" .TP .BI iodepth \fR=\fPint From 464981ffb495b46abe0532a733f47b9ca2e0d127 Mon Sep 17 00:00:00 2001 From: Alberto Faria Date: Thu, 1 Dec 2022 22:07:59 +0000 Subject: [PATCH 0360/1097] engines/libblkio: Add option libblkio_write_zeroes_on_trim When set, trim IOs will be submitted as blkioq_write_zeroes() requests instead of blkioq_discard() requests. Signed-off-by: Alberto Faria Signed-off-by: Vincent Fu --- HOWTO.rst | 4 ++++ engines/libblkio.c | 20 ++++++++++++++++++-- fio.1 | 3 +++ 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/HOWTO.rst b/HOWTO.rst index f77d76ccf8..273b7a68bd 100644 --- a/HOWTO.rst +++ b/HOWTO.rst @@ -2891,6 +2891,10 @@ with the caveat that when used on the command line, they must come after the Submit vectored read and write requests. +.. option:: libblkio_write_zeroes_on_trim : [libblkio] + + Submit trims as "write zeroes" requests instead of discard requests. + I/O depth ~~~~~~~~~ diff --git a/engines/libblkio.c b/engines/libblkio.c index 219bb0dc4b..66a9bd31f8 100644 --- a/engines/libblkio.c +++ b/engines/libblkio.c @@ -41,6 +41,7 @@ struct fio_blkio_options { unsigned int hipri; unsigned int vectored; + unsigned int write_zeroes_on_trim; }; static struct fio_option options[] = { @@ -89,6 +90,16 @@ static struct fio_option options[] = { .category = FIO_OPT_C_ENGINE, .group = FIO_OPT_G_LIBBLKIO, }, + { + .name = "libblkio_write_zeroes_on_trim", + .lname = "Use blkioq_write_zeroes() for TRIM", + .type = FIO_OPT_STR_SET, + .off1 = offsetof(struct fio_blkio_options, + write_zeroes_on_trim), + .help = "Use blkioq_write_zeroes() for TRIM instead of blkioq_discard()", + .category = FIO_OPT_C_ENGINE, + .group = FIO_OPT_G_LIBBLKIO, + }, { .name = NULL, }, @@ -455,8 +466,13 @@ static enum fio_q_status fio_blkio_queue(struct thread_data *td, } break; case DDIR_TRIM: - blkioq_discard(data->q, io_u->offset, io_u->xfer_buflen, - io_u, 0); + if (options->write_zeroes_on_trim) { + blkioq_write_zeroes(data->q, io_u->offset, + io_u->xfer_buflen, io_u, 0); + } else { + blkioq_discard(data->q, io_u->offset, + io_u->xfer_buflen, io_u, 0); + } break; case DDIR_SYNC: case DDIR_DATASYNC: diff --git a/fio.1 b/fio.1 index 9b8995b57e..ad061caa0d 100644 --- a/fio.1 +++ b/fio.1 @@ -2637,6 +2637,9 @@ Use poll queues. .TP .BI (libblkio)libblkio_vectored Submit vectored read and write requests. +.TP +.BI (libblkio)libblkio_write_zeroes_on_trim +Submit trims as "write zeroes" requests instead of discard requests. .SS "I/O depth" .TP .BI iodepth \fR=\fPint From b158577d787beaa6d098be3f180f947f7ad80b22 Mon Sep 17 00:00:00 2001 From: Alberto Faria Date: Thu, 1 Dec 2022 22:08:00 +0000 Subject: [PATCH 0361/1097] engines/libblkio: Add option libblkio_wait_mode It allows configuring how the engine waits for request completions, instead of always using a blocking blkioq_do_io() call. Signed-off-by: Alberto Faria Signed-off-by: Vincent Fu --- HOWTO.rst | 14 +++++- engines/libblkio.c | 117 ++++++++++++++++++++++++++++++++++++++++++--- fio.1 | 16 ++++++- 3 files changed, 139 insertions(+), 8 deletions(-) diff --git a/HOWTO.rst b/HOWTO.rst index 273b7a68bd..e9602f59f6 100644 --- a/HOWTO.rst +++ b/HOWTO.rst @@ -2337,7 +2337,8 @@ with the caveat that when used on the command line, they must come after the [libblkio] - Use poll queues. + Use poll queues. This is incompatible with + :option:`libblkio_wait_mode=eventfd `. [pvsync2] @@ -2895,6 +2896,17 @@ with the caveat that when used on the command line, they must come after the Submit trims as "write zeroes" requests instead of discard requests. +.. option:: libblkio_wait_mode=str : [libblkio] + + How to wait for completions: + + **block** (default) + Use a blocking call to ``blkioq_do_io()``. + **eventfd** + Use a blocking call to ``read()`` on the completion eventfd. + **loop** + Use a busy loop with a non-blocking call to ``blkioq_do_io()``. + I/O depth ~~~~~~~~~ diff --git a/engines/libblkio.c b/engines/libblkio.c index 66a9bd31f8..8a4828c394 100644 --- a/engines/libblkio.c +++ b/engines/libblkio.c @@ -24,6 +24,7 @@ struct fio_blkio_data { struct blkio *b; struct blkioq *q; + int completion_fd; /* -1 if not FIO_BLKIO_WAIT_MODE_EVENTFD */ bool has_mem_region; /* whether mem_region is valid */ struct blkio_mem_region mem_region; /* only if allocated by libblkio */ @@ -32,6 +33,12 @@ struct fio_blkio_data { struct blkio_completion *completions; }; +enum fio_blkio_wait_mode { + FIO_BLKIO_WAIT_MODE_BLOCK, + FIO_BLKIO_WAIT_MODE_EVENTFD, + FIO_BLKIO_WAIT_MODE_LOOP, +}; + struct fio_blkio_options { void *pad; /* option fields must not have offset 0 */ @@ -42,6 +49,7 @@ struct fio_blkio_options { unsigned int hipri; unsigned int vectored; unsigned int write_zeroes_on_trim; + enum fio_blkio_wait_mode wait_mode; }; static struct fio_option options[] = { @@ -100,6 +108,30 @@ static struct fio_option options[] = { .category = FIO_OPT_C_ENGINE, .group = FIO_OPT_G_LIBBLKIO, }, + { + .name = "libblkio_wait_mode", + .lname = "How to wait for completions", + .type = FIO_OPT_STR, + .off1 = offsetof(struct fio_blkio_options, wait_mode), + .help = "How to wait for completions", + .def = "block", + .posval = { + { .ival = "block", + .oval = FIO_BLKIO_WAIT_MODE_BLOCK, + .help = "Blocking blkioq_do_io()", + }, + { .ival = "eventfd", + .oval = FIO_BLKIO_WAIT_MODE_EVENTFD, + .help = "Blocking read() on the completion eventfd", + }, + { .ival = "loop", + .oval = FIO_BLKIO_WAIT_MODE_LOOP, + .help = "Busy loop with non-blocking blkioq_do_io()", + }, + }, + .category = FIO_OPT_C_ENGINE, + .group = FIO_OPT_G_LIBBLKIO, + }, { .name = NULL, }, @@ -231,12 +263,19 @@ static int fio_blkio_create_and_connect(struct thread_data *td, */ static int fio_blkio_setup(struct thread_data *td) { + const struct fio_blkio_options *options = td->eo; struct blkio *b; int ret = 0; uint64_t capacity; assert(td->files_index == 1); + if (options->hipri && + options->wait_mode == FIO_BLKIO_WAIT_MODE_EVENTFD) { + log_err("fio: option hipri is incompatible with option libblkio_wait_mode=eventfd\n"); + return 1; + } + if (fio_blkio_create_and_connect(td, &b) != 0) return 1; @@ -258,6 +297,7 @@ static int fio_blkio_init(struct thread_data *td) { const struct fio_blkio_options *options = td->eo; struct fio_blkio_data *data; + int flags; /* * Request enqueueing is fast, and it's not possible to know exactly @@ -302,6 +342,28 @@ static int fio_blkio_init(struct thread_data *td) else data->q = blkio_get_queue(data->b, 0); + if (options->wait_mode == FIO_BLKIO_WAIT_MODE_EVENTFD) { + /* enable completion fd and make it blocking */ + blkioq_set_completion_fd_enabled(data->q, true); + data->completion_fd = blkioq_get_completion_fd(data->q); + + flags = fcntl(data->completion_fd, F_GETFL); + if (flags < 0) { + log_err("fio: fcntl(F_GETFL) failed: %s\n", + strerror(errno)); + goto err_blkio_destroy; + } + + if (fcntl(data->completion_fd, F_SETFL, + flags & ~O_NONBLOCK) != 0) { + log_err("fio: fcntl(F_SETFL) failed: %s\n", + strerror(errno)); + goto err_blkio_destroy; + } + } else { + data->completion_fd = -1; + } + /* Set data last so cleanup() does nothing if init() fails. */ td->io_ops_data = data; @@ -490,16 +552,59 @@ static enum fio_q_status fio_blkio_queue(struct thread_data *td, static int fio_blkio_getevents(struct thread_data *td, unsigned int min, unsigned int max, const struct timespec *t) { + const struct fio_blkio_options *options = td->eo; struct fio_blkio_data *data = td->io_ops_data; - int n; + int ret, n; + uint64_t event; + + switch (options->wait_mode) { + case FIO_BLKIO_WAIT_MODE_BLOCK: + n = blkioq_do_io(data->q, data->completions, (int)min, (int)max, + NULL); + if (n < 0) { + fio_blkio_log_err(blkioq_do_io); + return -1; + } + return n; + case FIO_BLKIO_WAIT_MODE_EVENTFD: + n = blkioq_do_io(data->q, data->completions, 0, (int)max, NULL); + if (n < 0) { + fio_blkio_log_err(blkioq_do_io); + return -1; + } + while (n < (int)min) { + ret = read(data->completion_fd, &event, sizeof(event)); + if (ret != sizeof(event)) { + log_err("fio: read() on the completion fd returned %d\n", + ret); + return -1; + } - n = blkioq_do_io(data->q, data->completions, (int)min, (int)max, NULL); - if (n < 0) { - fio_blkio_log_err(blkioq_do_io); + ret = blkioq_do_io(data->q, data->completions + n, 0, + (int)max - n, NULL); + if (ret < 0) { + fio_blkio_log_err(blkioq_do_io); + return -1; + } + + n += ret; + } + return n; + case FIO_BLKIO_WAIT_MODE_LOOP: + for (n = 0; n < (int)min; ) { + ret = blkioq_do_io(data->q, data->completions + n, 0, + (int)max - n, NULL); + if (ret < 0) { + fio_blkio_log_err(blkioq_do_io); + return -1; + } + + n += ret; + } + return n; + default: return -1; } - - return n; } static struct io_u *fio_blkio_event(struct thread_data *td, int event) diff --git a/fio.1 b/fio.1 index ad061caa0d..1a81021428 100644 --- a/fio.1 +++ b/fio.1 @@ -2633,13 +2633,27 @@ properties depend on the libblkio version in use and are listed at \fIhttps://libblkio.gitlab.io/libblkio/blkio.html#properties\fR .TP .BI (libblkio)hipri -Use poll queues. +Use poll queues. This is incompatible with \fBlibblkio_wait_mode=eventfd\fR. .TP .BI (libblkio)libblkio_vectored Submit vectored read and write requests. .TP .BI (libblkio)libblkio_write_zeroes_on_trim Submit trims as "write zeroes" requests instead of discard requests. +.TP +.BI (libblkio)libblkio_wait_mode \fR=\fPstr +How to wait for completions: +.RS +.RS +.TP +.B block \fR(default) +Use a blocking call to \fBblkioq_do_io()\fR. +.TP +.B eventfd +Use a blocking call to \fBread()\fR on the completion eventfd. +.TP +.B loop +Use a busy loop with a non-blocking call to \fBblkioq_do_io()\fR. .SS "I/O depth" .TP .BI iodepth \fR=\fPint From b1bd09b593ddd4043c6217321c7135c0c580edc0 Mon Sep 17 00:00:00 2001 From: Alberto Faria Date: Thu, 1 Dec 2022 22:08:01 +0000 Subject: [PATCH 0362/1097] engines/libblkio: Add option libblkio_force_enable_completion_eventfd When set, the queue's completion fd is enabled even when it isn't used, i.e., even if option libblkio_wait_mode is _not_ set to "eventfd". Depending on the libblkio driver, this can have an impact on performance. This option allows evaluating that overhead. Signed-off-by: Alberto Faria Signed-off-by: Vincent Fu --- HOWTO.rst | 9 ++++++++- engines/libblkio.c | 21 +++++++++++++++++++-- fio.1 | 10 +++++++++- 3 files changed, 36 insertions(+), 4 deletions(-) diff --git a/HOWTO.rst b/HOWTO.rst index e9602f59f6..1cea16c4be 100644 --- a/HOWTO.rst +++ b/HOWTO.rst @@ -2338,7 +2338,8 @@ with the caveat that when used on the command line, they must come after the [libblkio] Use poll queues. This is incompatible with - :option:`libblkio_wait_mode=eventfd `. + :option:`libblkio_wait_mode=eventfd ` and + :option:`libblkio_force_enable_completion_eventfd`. [pvsync2] @@ -2907,6 +2908,12 @@ with the caveat that when used on the command line, they must come after the **loop** Use a busy loop with a non-blocking call to ``blkioq_do_io()``. +.. option:: libblkio_force_enable_completion_eventfd : [libblkio] + + Enable the queue's completion eventfd even when unused. This may impact + performance. The default is to enable it only if + :option:`libblkio_wait_mode=eventfd `. + I/O depth ~~~~~~~~~ diff --git a/engines/libblkio.c b/engines/libblkio.c index 8a4828c394..7f5dcf36bb 100644 --- a/engines/libblkio.c +++ b/engines/libblkio.c @@ -24,7 +24,7 @@ struct fio_blkio_data { struct blkio *b; struct blkioq *q; - int completion_fd; /* -1 if not FIO_BLKIO_WAIT_MODE_EVENTFD */ + int completion_fd; /* may be -1 if not FIO_BLKIO_WAIT_MODE_EVENTFD */ bool has_mem_region; /* whether mem_region is valid */ struct blkio_mem_region mem_region; /* only if allocated by libblkio */ @@ -50,6 +50,7 @@ struct fio_blkio_options { unsigned int vectored; unsigned int write_zeroes_on_trim; enum fio_blkio_wait_mode wait_mode; + unsigned int force_enable_completion_eventfd; }; static struct fio_option options[] = { @@ -132,6 +133,16 @@ static struct fio_option options[] = { .category = FIO_OPT_C_ENGINE, .group = FIO_OPT_G_LIBBLKIO, }, + { + .name = "libblkio_force_enable_completion_eventfd", + .lname = "Force enable the completion eventfd, even if unused", + .type = FIO_OPT_STR_SET, + .off1 = offsetof(struct fio_blkio_options, + force_enable_completion_eventfd), + .help = "This can impact performance", + .category = FIO_OPT_C_ENGINE, + .group = FIO_OPT_G_LIBBLKIO, + }, { .name = NULL, }, @@ -276,6 +287,11 @@ static int fio_blkio_setup(struct thread_data *td) return 1; } + if (options->hipri && options->force_enable_completion_eventfd) { + log_err("fio: option hipri is incompatible with option libblkio_force_enable_completion_eventfd\n"); + return 1; + } + if (fio_blkio_create_and_connect(td, &b) != 0) return 1; @@ -342,7 +358,8 @@ static int fio_blkio_init(struct thread_data *td) else data->q = blkio_get_queue(data->b, 0); - if (options->wait_mode == FIO_BLKIO_WAIT_MODE_EVENTFD) { + if (options->wait_mode == FIO_BLKIO_WAIT_MODE_EVENTFD || + options->force_enable_completion_eventfd) { /* enable completion fd and make it blocking */ blkioq_set_completion_fd_enabled(data->q, true); data->completion_fd = blkioq_get_completion_fd(data->q); diff --git a/fio.1 b/fio.1 index 1a81021428..da5483037b 100644 --- a/fio.1 +++ b/fio.1 @@ -2633,7 +2633,8 @@ properties depend on the libblkio version in use and are listed at \fIhttps://libblkio.gitlab.io/libblkio/blkio.html#properties\fR .TP .BI (libblkio)hipri -Use poll queues. This is incompatible with \fBlibblkio_wait_mode=eventfd\fR. +Use poll queues. This is incompatible with \fBlibblkio_wait_mode=eventfd\fR and +\fBlibblkio_force_enable_completion_eventfd\fR. .TP .BI (libblkio)libblkio_vectored Submit vectored read and write requests. @@ -2654,6 +2655,13 @@ Use a blocking call to \fBread()\fR on the completion eventfd. .TP .B loop Use a busy loop with a non-blocking call to \fBblkioq_do_io()\fR. +.RE +.RE +.TP +.BI (libblkio)libblkio_force_enable_completion_eventfd +Enable the queue's completion eventfd even when unused. This may impact +performance. The default is to enable it only if +\fBlibblkio_wait_mode=eventfd\fR. .SS "I/O depth" .TP .BI iodepth \fR=\fPint From 13fffdfbe3de66c368fe9d6bfaf61950f7f08857 Mon Sep 17 00:00:00 2001 From: Alberto Faria Date: Thu, 1 Dec 2022 22:08:02 +0000 Subject: [PATCH 0363/1097] engines/libblkio: Add options for some driver-specific properties The properties are either common to several drivers or particularly relevant for benchmarking, so this should help write cleaner workload files. Signed-off-by: Alberto Faria Signed-off-by: Vincent Fu --- HOWTO.rst | 42 +++++++++++---- engines/libblkio.c | 62 ++++++++++++++++++++++- examples/libblkio-io_uring.fio | 4 +- examples/libblkio-virtio-blk-vfio-pci.fio | 9 ++-- fio.1 | 36 +++++++++---- 5 files changed, 124 insertions(+), 29 deletions(-) diff --git a/HOWTO.rst b/HOWTO.rst index 1cea16c4be..10e6bc7943 100644 --- a/HOWTO.rst +++ b/HOWTO.rst @@ -2869,23 +2869,45 @@ with the caveat that when used on the command line, they must come after the libblkio version in use and are listed at https://libblkio.gitlab.io/libblkio/blkio.html#drivers +.. option:: libblkio_path=str : [libblkio] + + Sets the value of the driver-specific "path" property before connecting + the libblkio instance, which identifies the target device or file on + which to perform I/O. Its exact semantics are driver-dependent and not + all drivers may support it; see + https://libblkio.gitlab.io/libblkio/blkio.html#drivers + .. option:: libblkio_pre_connect_props=str : [libblkio] - A colon-separated list of libblkio properties to be set after creating - but before connecting the libblkio instance. Each property must have the - format ``=``. Colons can be escaped as ``\:``. These are - set after the engine sets any other properties, so those can be - overriden. Available properties depend on the libblkio version in use + A colon-separated list of additional libblkio properties to be set after + creating but before connecting the libblkio instance. Each property must + have the format ``=``. Colons can be escaped as ``\:``. + These are set after the engine sets any other properties, so those can + be overriden. Available properties depend on the libblkio version in use and are listed at https://libblkio.gitlab.io/libblkio/blkio.html#properties +.. option:: libblkio_num_entries=int : [libblkio] + + Sets the value of the driver-specific "num-entries" property before + starting the libblkio instance. Its exact semantics are driver-dependent + and not all drivers may support it; see + https://libblkio.gitlab.io/libblkio/blkio.html#drivers + +.. option:: libblkio_queue_size=int : [libblkio] + + Sets the value of the driver-specific "queue-size" property before + starting the libblkio instance. Its exact semantics are driver-dependent + and not all drivers may support it; see + https://libblkio.gitlab.io/libblkio/blkio.html#drivers + .. option:: libblkio_pre_start_props=str : [libblkio] - A colon-separated list of libblkio properties to be set after connecting - but before starting the libblkio instance. Each property must have the - format ``=``. Colons can be escaped as ``\:``. These are - set after the engine sets any other properties, so those can be - overriden. Available properties depend on the libblkio version in use + A colon-separated list of additional libblkio properties to be set after + connecting but before starting the libblkio instance. Each property must + have the format ``=``. Colons can be escaped as ``\:``. + These are set after the engine sets any other properties, so those can + be overriden. Available properties depend on the libblkio version in use and are listed at https://libblkio.gitlab.io/libblkio/blkio.html#properties diff --git a/engines/libblkio.c b/engines/libblkio.c index 7f5dcf36bb..fc4e3f8a07 100644 --- a/engines/libblkio.c +++ b/engines/libblkio.c @@ -43,7 +43,12 @@ struct fio_blkio_options { void *pad; /* option fields must not have offset 0 */ char *driver; + + char *path; char *pre_connect_props; + + int num_entries; + int queue_size; char *pre_start_props; unsigned int hipri; @@ -63,18 +68,49 @@ static struct fio_option options[] = { .category = FIO_OPT_C_ENGINE, .group = FIO_OPT_G_LIBBLKIO, }, + { + .name = "libblkio_path", + .lname = "libblkio \"path\" property", + .type = FIO_OPT_STR_STORE, + .off1 = offsetof(struct fio_blkio_options, path), + .help = "Value to set the \"path\" property to", + .category = FIO_OPT_C_ENGINE, + .group = FIO_OPT_G_LIBBLKIO, + }, { .name = "libblkio_pre_connect_props", - .lname = "Properties to be set before blkio_connect()", + .lname = "Additional properties to be set before blkio_connect()", .type = FIO_OPT_STR_STORE, .off1 = offsetof(struct fio_blkio_options, pre_connect_props), .help = "", .category = FIO_OPT_C_ENGINE, .group = FIO_OPT_G_LIBBLKIO, }, + { + .name = "libblkio_num_entries", + .lname = "libblkio \"num-entries\" property", + .type = FIO_OPT_INT, + .off1 = offsetof(struct fio_blkio_options, num_entries), + .help = "Value to set the \"num-entries\" property to", + .minval = 1, + .interval = 1, + .category = FIO_OPT_C_ENGINE, + .group = FIO_OPT_G_LIBBLKIO, + }, + { + .name = "libblkio_queue_size", + .lname = "libblkio \"queue-size\" property", + .type = FIO_OPT_INT, + .off1 = offsetof(struct fio_blkio_options, queue_size), + .help = "Value to set the \"queue-size\" property to", + .minval = 1, + .interval = 1, + .category = FIO_OPT_C_ENGINE, + .group = FIO_OPT_G_LIBBLKIO, + }, { .name = "libblkio_pre_start_props", - .lname = "Properties to be set before blkio_start()", + .lname = "Additional properties to be set before blkio_start()", .type = FIO_OPT_STR_STORE, .off1 = offsetof(struct fio_blkio_options, pre_start_props), .help = "", @@ -245,6 +281,13 @@ static int fio_blkio_create_and_connect(struct thread_data *td, goto err_blkio_destroy; } + if (options->path) { + if (blkio_set_str(b, "path", options->path) != 0) { + fio_blkio_log_err(blkio_set_str); + goto err_blkio_destroy; + } + } + if (fio_blkio_set_props_from_str(b, "libblkio_pre_connect_props", options->pre_connect_props) != 0) goto err_blkio_destroy; @@ -254,6 +297,21 @@ static int fio_blkio_create_and_connect(struct thread_data *td, goto err_blkio_destroy; } + if (options->num_entries != 0) { + if (blkio_set_int(b, "num-entries", + options->num_entries) != 0) { + fio_blkio_log_err(blkio_set_int); + goto err_blkio_destroy; + } + } + + if (options->queue_size != 0) { + if (blkio_set_int(b, "queue-size", options->queue_size) != 0) { + fio_blkio_log_err(blkio_set_int); + goto err_blkio_destroy; + } + } + if (fio_blkio_set_props_from_str(b, "libblkio_pre_start_props", options->pre_start_props) != 0) goto err_blkio_destroy; diff --git a/examples/libblkio-io_uring.fio b/examples/libblkio-io_uring.fio index 655a0b5089..3485b97eab 100644 --- a/examples/libblkio-io_uring.fio +++ b/examples/libblkio-io_uring.fio @@ -1,14 +1,14 @@ ; Benchmark accessing a regular file or block device using libblkio. ; ; Replace "/dev/nvme0n1" below with the path to your file or device, or override -; it by passing the '--libblkio_pre_connect_props=path=...' flag to fio. +; it by passing the '--libblkio_path=...' flag to fio. ; ; For information on libblkio, see: https://gitlab.com/libblkio/libblkio [global] ioengine=libblkio libblkio_driver=io_uring -libblkio_pre_connect_props=path=/dev/nvme0n1 ; REPLACE THIS WITH THE RIGHT PATH +libblkio_path=/dev/nvme0n1 ; REPLACE THIS WITH THE RIGHT PATH rw=randread blocksize=4k direct=1 diff --git a/examples/libblkio-virtio-blk-vfio-pci.fio b/examples/libblkio-virtio-blk-vfio-pci.fio index 425df4a6bf..6bed664be4 100644 --- a/examples/libblkio-virtio-blk-vfio-pci.fio +++ b/examples/libblkio-virtio-blk-vfio-pci.fio @@ -1,16 +1,15 @@ ; Benchmark accessing a PCI virtio-blk device using libblkio. ; -; Replace "/sys/bus/pci/devices/0000\:00\:01.0" below with the path to your -; device's sysfs directory, or override it by passing the -; '--libblkio_pre_connect_props=path=...' flag to fio. Note that colons in the -; path must be escaped with a backslash. +; Replace "/sys/bus/pci/devices/0000:00:01.0" below with the path to your +; device's sysfs directory, or override it by passing the '--libblkio_path=...' +; flag to fio. ; ; For information on libblkio, see: https://gitlab.com/libblkio/libblkio [global] ioengine=libblkio libblkio_driver=virtio-blk-vfio-pci -libblkio_pre_connect_props=path=/sys/bus/pci/devices/0000\:00\:01.0 ; REPLACE THIS WITH THE RIGHT PATH +libblkio_path=/sys/bus/pci/devices/0000:00:01.0 ; REPLACE THIS WITH THE RIGHT PATH rw=randread blocksize=4k time_based=1 diff --git a/fio.1 b/fio.1 index da5483037b..6f7a608d9d 100644 --- a/fio.1 +++ b/fio.1 @@ -2616,20 +2616,36 @@ The libblkio driver to use. Different drivers access devices through different underlying interfaces. Available drivers depend on the libblkio version in use and are listed at \fIhttps://libblkio.gitlab.io/libblkio/blkio.html#drivers\fR .TP +.BI (libblkio)libblkio_path \fR=\fPstr +Sets the value of the driver-specific "path" property before connecting the +libblkio instance, which identifies the target device or file on which to +perform I/O. Its exact semantics are driver-dependent and not all drivers may +support it; see \fIhttps://libblkio.gitlab.io/libblkio/blkio.html#drivers\fR +.TP .BI (libblkio)libblkio_pre_connect_props \fR=\fPstr -A colon-separated list of libblkio properties to be set after creating but -before connecting the libblkio instance. Each property must have the format -\fB=\fR. Colons can be escaped as \fB\\:\fR. These are set after -the engine sets any other properties, so those can be overriden. Available -properties depend on the libblkio version in use and are listed at +A colon-separated list of additional libblkio properties to be set after +creating but before connecting the libblkio instance. Each property must have +the format \fB=\fR. Colons can be escaped as \fB\\:\fR. These are +set after the engine sets any other properties, so those can be overriden. +Available properties depend on the libblkio version in use and are listed at \fIhttps://libblkio.gitlab.io/libblkio/blkio.html#properties\fR .TP +.BI (libblkio)libblkio_num_entries \fR=\fPint +Sets the value of the driver-specific "num-entries" property before starting the +libblkio instance. Its exact semantics are driver-dependent and not all drivers +may support it; see \fIhttps://libblkio.gitlab.io/libblkio/blkio.html#drivers\fR +.TP +.BI (libblkio)libblkio_queue_size \fR=\fPint +Sets the value of the driver-specific "queue-size" property before starting the +libblkio instance. Its exact semantics are driver-dependent and not all drivers +may support it; see \fIhttps://libblkio.gitlab.io/libblkio/blkio.html#drivers\fR +.TP .BI (libblkio)libblkio_pre_start_props \fR=\fPstr -A colon-separated list of libblkio properties to be set after connecting but -before starting the libblkio instance. Each property must have the format -\fB=\fR. Colons can be escaped as \fB\\:\fR. These are set after -the engine sets any other properties, so those can be overriden. Available -properties depend on the libblkio version in use and are listed at +A colon-separated list of additional libblkio properties to be set after +connecting but before starting the libblkio instance. Each property must have +the format \fB=\fR. Colons can be escaped as \fB\\:\fR. These are +set after the engine sets any other properties, so those can be overriden. +Available properties depend on the libblkio version in use and are listed at \fIhttps://libblkio.gitlab.io/libblkio/blkio.html#properties\fR .TP .BI (libblkio)hipri From 3afc2d8ac30c58372a1b7ccabaea0f3eae4ddaba Mon Sep 17 00:00:00 2001 From: Alberto Faria Date: Thu, 1 Dec 2022 22:08:03 +0000 Subject: [PATCH 0364/1097] engines/libblkio: Share a single blkio instance among threads in same process fio groups all subjobs that set option 'thread' into a single process. Have them all share a single `struct blkio` instance, with one `struct blkioq` per thread/subjob. This allows benchmarking multi-queue setups. Note that `struct blkio` instances cannot be shared across different processes. Signed-off-by: Alberto Faria Signed-off-by: Vincent Fu --- HOWTO.rst | 8 +- engines/libblkio.c | 242 +++++++++++++++++++--- examples/libblkio-io_uring.fio | 13 +- examples/libblkio-virtio-blk-vfio-pci.fio | 13 +- fio.1 | 6 +- 5 files changed, 250 insertions(+), 32 deletions(-) diff --git a/HOWTO.rst b/HOWTO.rst index 10e6bc7943..5a5263c349 100644 --- a/HOWTO.rst +++ b/HOWTO.rst @@ -2202,7 +2202,13 @@ I/O engine :option:`libblkio_driver`. If :option:`mem`/:option:`iomem` is not specified, memory allocation is delegated to libblkio (and so is - guaranteed to work with the selected *driver*). + guaranteed to work with the selected *driver*). One + libblkio instance is used per process, so all jobs + setting option :option:`thread` will share a single + instance (with one queue per thread) and must specify + compatible options. Note that some drivers don't allow + several instances to access the same device or file + simultaneously, but allow it for threads. I/O engine specific parameters ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/engines/libblkio.c b/engines/libblkio.c index fc4e3f8a07..054aa8001a 100644 --- a/engines/libblkio.c +++ b/engines/libblkio.c @@ -20,9 +20,28 @@ #include "../options.h" #include "../parse.h" +/* per-process state */ +static struct { + pthread_mutex_t mutex; + int initted_threads; + int initted_hipri_threads; + struct blkio *b; +} proc_state = { PTHREAD_MUTEX_INITIALIZER, 0, 0, NULL }; + +static void fio_blkio_proc_lock(void) { + int ret; + ret = pthread_mutex_lock(&proc_state.mutex); + assert(ret == 0); +} + +static void fio_blkio_proc_unlock(void) { + int ret; + ret = pthread_mutex_unlock(&proc_state.mutex); + assert(ret == 0); +} + /* per-thread state */ struct fio_blkio_data { - struct blkio *b; struct blkioq *q; int completion_fd; /* may be -1 if not FIO_BLKIO_WAIT_MODE_EVENTFD */ @@ -252,6 +271,106 @@ static int fio_blkio_set_props_from_str(struct blkio *b, const char *opt_name, blkio_get_error_msg()); \ }) +static bool possibly_null_strs_equal(const char *a, const char *b) +{ + return (!a && !b) || (a && b && strcmp(a, b) == 0); +} + +/* + * Returns the total number of subjobs using the 'libblkio' ioengine and setting + * the 'thread' option in the entire workload that have the given value for the + * 'hipri' option. + */ +static int total_threaded_subjobs(bool hipri) +{ + struct thread_data *td; + unsigned int i; + int count = 0; + + for_each_td(td, i) { + const struct fio_blkio_options *options = td->eo; + if (strcmp(td->o.ioengine, "libblkio") == 0 && + td->o.use_thread && (bool)options->hipri == hipri) + ++count; + } + + return count; +} + +static struct { + bool set_up; + bool direct; + struct fio_blkio_options opts; +} first_threaded_subjob = { 0 }; + +static void fio_blkio_log_opt_compat_err(const char *option_name) +{ + log_err("fio: jobs using engine libblkio and sharing a process must agree on the %s option\n", + option_name); +} + +/* + * If td represents a subjob with option 'thread', check if its options are + * compatible with those of other threaded subjobs that were already set up. + */ +static int fio_blkio_check_opt_compat(struct thread_data *td) +{ + const struct fio_blkio_options *options = td->eo, *prev_options; + + if (!td->o.use_thread) + return 0; /* subjob doesn't use 'thread' */ + + if (!first_threaded_subjob.set_up) { + /* first subjob using 'thread', store options for later */ + first_threaded_subjob.set_up = true; + first_threaded_subjob.direct = td->o.odirect; + first_threaded_subjob.opts = *options; + return 0; + } + + /* not first subjob using 'thread', check option compatibility */ + prev_options = &first_threaded_subjob.opts; + + if (td->o.odirect != first_threaded_subjob.direct) { + fio_blkio_log_opt_compat_err("direct/buffered"); + return 1; + } + + if (strcmp(options->driver, prev_options->driver) != 0) { + fio_blkio_log_opt_compat_err("libblkio_driver"); + return 1; + } + + if (!possibly_null_strs_equal(options->path, prev_options->path)) { + fio_blkio_log_opt_compat_err("libblkio_path"); + return 1; + } + + if (!possibly_null_strs_equal(options->pre_connect_props, + prev_options->pre_connect_props)) { + fio_blkio_log_opt_compat_err("libblkio_pre_connect_props"); + return 1; + } + + if (options->num_entries != prev_options->num_entries) { + fio_blkio_log_opt_compat_err("libblkio_num_entries"); + return 1; + } + + if (options->queue_size != prev_options->queue_size) { + fio_blkio_log_opt_compat_err("libblkio_queue_size"); + return 1; + } + + if (!possibly_null_strs_equal(options->pre_start_props, + prev_options->pre_start_props)) { + fio_blkio_log_opt_compat_err("libblkio_pre_start_props"); + return 1; + } + + return 0; +} + static int fio_blkio_create_and_connect(struct thread_data *td, struct blkio **out_blkio) { @@ -324,6 +443,8 @@ static int fio_blkio_create_and_connect(struct thread_data *td, return 1; } +static bool incompatible_threaded_subjob_options = false; + /* * This callback determines the device/file size, so it creates and connects a * blkio instance. But it is invoked from the main thread in the original fio @@ -339,6 +460,11 @@ static int fio_blkio_setup(struct thread_data *td) assert(td->files_index == 1); + if (fio_blkio_check_opt_compat(td) != 0) { + incompatible_threaded_subjob_options = true; + return 1; + } + if (options->hipri && options->wait_mode == FIO_BLKIO_WAIT_MODE_EVENTFD) { log_err("fio: option hipri is incompatible with option libblkio_wait_mode=eventfd\n"); @@ -373,6 +499,15 @@ static int fio_blkio_init(struct thread_data *td) struct fio_blkio_data *data; int flags; + if (td->o.use_thread && incompatible_threaded_subjob_options) { + /* + * Different subjobs using option 'thread' specified + * incompatible options. We don't know which configuration + * should win, so we just fail all such subjobs. + */ + return 1; + } + /* * Request enqueueing is fast, and it's not possible to know exactly * when a request is submitted, so never report submission latencies. @@ -392,29 +527,49 @@ static int fio_blkio_init(struct thread_data *td) goto err_free; } - if (fio_blkio_create_and_connect(td, &data->b) != 0) - goto err_free; + fio_blkio_proc_lock(); - if (blkio_set_int(data->b, "num-queues", options->hipri ? 0 : 1) != 0) { - fio_blkio_log_err(blkio_set_int); - goto err_blkio_destroy; - } + if (proc_state.initted_threads == 0) { + /* initialize per-process blkio */ + int num_queues, num_poll_queues; - if (blkio_set_int(data->b, "num-poll-queues", - options->hipri ? 1 : 0) != 0) { - fio_blkio_log_err(blkio_set_int); - goto err_blkio_destroy; - } + if (td->o.use_thread) { + num_queues = total_threaded_subjobs(false); + num_poll_queues = total_threaded_subjobs(true); + } else { + num_queues = options->hipri ? 0 : 1; + num_poll_queues = options->hipri ? 1 : 0; + } - if (blkio_start(data->b) != 0) { - fio_blkio_log_err(blkio_start); - goto err_blkio_destroy; + if (fio_blkio_create_and_connect(td, &proc_state.b) != 0) + goto err_unlock; + + if (blkio_set_int(proc_state.b, "num-queues", + num_queues) != 0) { + fio_blkio_log_err(blkio_set_int); + goto err_blkio_destroy; + } + + if (blkio_set_int(proc_state.b, "num-poll-queues", + num_poll_queues) != 0) { + fio_blkio_log_err(blkio_set_int); + goto err_blkio_destroy; + } + + if (blkio_start(proc_state.b) != 0) { + fio_blkio_log_err(blkio_start); + goto err_blkio_destroy; + } } - if (options->hipri) - data->q = blkio_get_poll_queue(data->b, 0); - else - data->q = blkio_get_queue(data->b, 0); + if (options->hipri) { + int i = proc_state.initted_hipri_threads; + data->q = blkio_get_poll_queue(proc_state.b, i); + } else { + int i = proc_state.initted_threads - + proc_state.initted_hipri_threads; + data->q = blkio_get_queue(proc_state.b, i); + } if (options->wait_mode == FIO_BLKIO_WAIT_MODE_EVENTFD || options->force_enable_completion_eventfd) { @@ -439,13 +594,24 @@ static int fio_blkio_init(struct thread_data *td) data->completion_fd = -1; } + ++proc_state.initted_threads; + if (options->hipri) + ++proc_state.initted_hipri_threads; + /* Set data last so cleanup() does nothing if init() fails. */ td->io_ops_data = data; + fio_blkio_proc_unlock(); + return 0; err_blkio_destroy: - blkio_destroy(&data->b); + if (proc_state.initted_threads == 0) + blkio_destroy(&proc_state.b); +err_unlock: + if (proc_state.initted_threads == 0) + proc_state.b = NULL; + fio_blkio_proc_unlock(); err_free: free(data->completions); free(data->iovecs); @@ -485,7 +651,7 @@ static int fio_blkio_post_init(struct thread_data *td) .fd = -1, }; - if (blkio_map_mem_region(data->b, ®ion) != 0) { + if (blkio_map_mem_region(proc_state.b, ®ion) != 0) { fio_blkio_log_err(blkio_map_mem_region); return 1; } @@ -498,11 +664,25 @@ static void fio_blkio_cleanup(struct thread_data *td) { struct fio_blkio_data *data = td->io_ops_data; + /* + * Subjobs from different jobs can be terminated at different times, so + * this callback may be invoked for one subjob while another is still + * doing I/O. Those subjobs may share the process, so we must wait until + * the last subjob in the process wants to clean up to actually destroy + * the blkio. + */ + if (data) { - blkio_destroy(&data->b); free(data->completions); free(data->iovecs); free(data); + + fio_blkio_proc_lock(); + if (--proc_state.initted_threads == 0) { + blkio_destroy(&proc_state.b); + proc_state.b = NULL; + } + fio_blkio_proc_unlock(); } } @@ -514,7 +694,7 @@ static int fio_blkio_iomem_alloc(struct thread_data *td, size_t size) int ret; uint64_t mem_region_alignment; - if (blkio_get_uint64(data->b, "mem-region-alignment", + if (blkio_get_uint64(proc_state.b, "mem-region-alignment", &mem_region_alignment) != 0) { fio_blkio_log_err(blkio_get_uint64); return 1; @@ -523,13 +703,16 @@ static int fio_blkio_iomem_alloc(struct thread_data *td, size_t size) /* round up size to satisfy mem-region-alignment */ size = align_up(size, (size_t)mem_region_alignment); - if (blkio_alloc_mem_region(data->b, &data->mem_region, size) != 0) { + fio_blkio_proc_lock(); + + if (blkio_alloc_mem_region(proc_state.b, &data->mem_region, + size) != 0) { fio_blkio_log_err(blkio_alloc_mem_region); ret = 1; goto out; } - if (blkio_map_mem_region(data->b, &data->mem_region) != 0) { + if (blkio_map_mem_region(proc_state.b, &data->mem_region) != 0) { fio_blkio_log_err(blkio_map_mem_region); ret = 1; goto out_free; @@ -542,8 +725,9 @@ static int fio_blkio_iomem_alloc(struct thread_data *td, size_t size) goto out; out_free: - blkio_free_mem_region(data->b, &data->mem_region); + blkio_free_mem_region(proc_state.b, &data->mem_region); out: + fio_blkio_proc_unlock(); return ret; } @@ -552,8 +736,10 @@ static void fio_blkio_iomem_free(struct thread_data *td) struct fio_blkio_data *data = td->io_ops_data; if (data && data->has_mem_region) { - blkio_unmap_mem_region(data->b, &data->mem_region); - blkio_free_mem_region(data->b, &data->mem_region); + fio_blkio_proc_lock(); + blkio_unmap_mem_region(proc_state.b, &data->mem_region); + blkio_free_mem_region(proc_state.b, &data->mem_region); + fio_blkio_proc_unlock(); data->has_mem_region = false; } diff --git a/examples/libblkio-io_uring.fio b/examples/libblkio-io_uring.fio index 3485b97eab..40f625cfa6 100644 --- a/examples/libblkio-io_uring.fio +++ b/examples/libblkio-io_uring.fio @@ -3,6 +3,10 @@ ; Replace "/dev/nvme0n1" below with the path to your file or device, or override ; it by passing the '--libblkio_path=...' flag to fio. ; +; In the example below, the two subjobs of "job-B" *and* the single subjob of +; "job-C" will share a single libblkio instance, and "job-A" will use a separate +; libblkio instance. +; ; For information on libblkio, see: https://gitlab.com/libblkio/libblkio [global] @@ -15,4 +19,11 @@ direct=1 time_based=1 runtime=10s -[job] +[job-A] + +[job-B] +numjobs=2 ; run two copies of this job simultaneously +thread=1 ; have each copy run as a separate thread in the *same* process + +[job-C] +thread=1 ; have the job run as a thread in the *same* process as "job-B" diff --git a/examples/libblkio-virtio-blk-vfio-pci.fio b/examples/libblkio-virtio-blk-vfio-pci.fio index 6bed664be4..024224a6ad 100644 --- a/examples/libblkio-virtio-blk-vfio-pci.fio +++ b/examples/libblkio-virtio-blk-vfio-pci.fio @@ -4,6 +4,10 @@ ; device's sysfs directory, or override it by passing the '--libblkio_path=...' ; flag to fio. ; +; In the example below, the two subjobs of "job-B" *and* the single subjob of +; "job-C" will share a single libblkio instance, and "job-A" will use a separate +; libblkio instance. +; ; For information on libblkio, see: https://gitlab.com/libblkio/libblkio [global] @@ -15,4 +19,11 @@ blocksize=4k time_based=1 runtime=10s -[job] +[job-A] + +[job-B] +numjobs=2 ; run two copies of this job simultaneously +thread=1 ; have each copy run as a separate thread in the *same* process + +[job-C] +thread=1 ; have the job run as a thread in the *same* process as "job-B" diff --git a/fio.1 b/fio.1 index 6f7a608d9d..7a153731c3 100644 --- a/fio.1 +++ b/fio.1 @@ -1997,7 +1997,11 @@ engine specific options. (See \fIhttps://xnvme.io/\fR). Use the libblkio library (\fIhttps://gitlab.com/libblkio/libblkio\fR). The specific driver to use must be set using \fBlibblkio_driver\fR. If \fBmem\fR/\fBiomem\fR is not specified, memory allocation is delegated to -libblkio (and so is guaranteed to work with the selected driver). +libblkio (and so is guaranteed to work with the selected driver). One libblkio +instance is used per process, so all jobs setting option \fBthread\fR will share +a single instance (with one queue per thread) and must specify compatible +options. Note that some drivers don't allow several instances to access the same +device or file simultaneously, but allow it for threads. .SS "I/O engine specific parameters" In addition, there are some parameters which are only valid when a specific \fBioengine\fR is in use. These are used identically to normal parameters, From 70eb71e682b90078db6f361936933b88f71ad5fd Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Mon, 12 Dec 2022 16:57:52 -0700 Subject: [PATCH 0365/1097] t/io_uring: adjust IORING_REGISTER_MAP_BUFFERS value This isn't upstream yet, but current test patches have it at 26. Signed-off-by: Jens Axboe --- t/io_uring.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/t/io_uring.c b/t/io_uring.c index edbacee3fc..1ea0a9da29 100644 --- a/t/io_uring.c +++ b/t/io_uring.c @@ -156,7 +156,7 @@ static float plist[] = { 1.0, 5.0, 10.0, 20.0, 30.0, 40.0, 50.0, 60.0, 70.0, static int plist_len = 17; #ifndef IORING_REGISTER_MAP_BUFFERS -#define IORING_REGISTER_MAP_BUFFERS 22 +#define IORING_REGISTER_MAP_BUFFERS 26 struct io_uring_map_buffers { __s32 fd; __u32 buf_start; From a0274f429672c10bb113e8d9214369143f71af1a Mon Sep 17 00:00:00 2001 From: Shin'ichiro Kawasaki Date: Thu, 15 Dec 2022 10:56:03 +0900 Subject: [PATCH 0366/1097] man: fix troff warning 'make doc' reports a warning: troff: :2554: warning: can't find font 'b' To avoid it, add missing 'P' for troff built-in command '\f'. Signed-off-by: Shin'ichiro Kawasaki Signed-off-by: Jens Axboe --- fio.1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fio.1 b/fio.1 index 7a153731c3..a0d765e59a 100644 --- a/fio.1 +++ b/fio.1 @@ -2544,7 +2544,7 @@ replaced by the name of the job .BI (exec)grace_time\fR=\fPint Defines the time between the SIGTERM and SIGKILL signals. Default is 1 second. .TP -.BI (exec)std_redirect\fR=\fbool +.BI (exec)std_redirect\fR=\fPbool If set, stdout and stderr streams are redirected to files named from the job name. Default is true. .TP .BI (xnvme)xnvme_async\fR=\fPstr From 1c97d909f748d12de73245d4464b47bf56a5726b Mon Sep 17 00:00:00 2001 From: Shin'ichiro Kawasaki Date: Thu, 15 Dec 2022 10:56:04 +0900 Subject: [PATCH 0367/1097] HOWTO/man: improve descriptions of max open zones options The options max_open_zones and job_max_open_zones control the number of open zones for zonemode=zbd. However, descriptions in HOWTO and man about these options are not clear enough since it is not well described what an open zone is. Improve the description by adding explanation of the open zone state. Also explain the default values for these options. Signed-off-by: Shin'ichiro Kawasaki Link: https://lore.kernel.org/r/20221215015606.2767187-3-shinichiro.kawasaki@wdc.com [axboe: apply wording suggestion from Damien] Signed-off-by: Jens Axboe --- HOWTO.rst | 27 ++++++++++++++++++++------- fio.1 | 23 +++++++++++++++++------ 2 files changed, 37 insertions(+), 13 deletions(-) diff --git a/HOWTO.rst b/HOWTO.rst index 5a5263c349..97fe53504d 100644 --- a/HOWTO.rst +++ b/HOWTO.rst @@ -1052,16 +1052,29 @@ Target file/device .. option:: max_open_zones=int - When running a random write test across an entire drive many more - zones will be open than in a typical application workload. Hence this - command line option that allows one to limit the number of open zones. The - number of open zones is defined as the number of zones to which write - commands are issued. + A zone of a zoned block device is in the open state when it is partially + written (i.e. not all sectors of the zone have been written). Zoned + block devices may have a limit on the total number of zones that can + be simultaneously in the open state, that is, the number of zones that + can be written to simultaneously. The :option:`max_open_zones` parameter + limits the number of zones to which write commands are issued by all fio + jobs, that is, limits the number of zones that will be in the open + state. This parameter is relevant only if the :option:`zonemode` =zbd is + used. The default value is always equal to maximum number of open zones + of the target zoned block device and a value higher than this limit + cannot be specified by users unless the option + :option:`ignore_zone_limits` is specified. When + :option:`ignore_zone_limits` is specified or the target device has no + limit on the number of zones that can be in an open state, + :option:`max_open_zones` can specify 0 to disable any limit on the + number of zones that can be simultaneously written to by all jobs. .. option:: job_max_open_zones=int - Limit on the number of simultaneously opened zones per single - thread/process. + In the same manner as :option:`max_open_zones`, limit the number of open + zones per fio job, that is, the number of zones that a single job can + simultaneously write to. A value of zero indicates no limit. + Default: zero. .. option:: ignore_zone_limits=bool diff --git a/fio.1 b/fio.1 index a0d765e59a..1074b52a38 100644 --- a/fio.1 +++ b/fio.1 @@ -828,14 +828,25 @@ numbers fio only reads beyond the write pointer if explicitly told to do so. Default: false. .TP .BI max_open_zones \fR=\fPint -When running a random write test across an entire drive many more zones will be -open than in a typical application workload. Hence this command line option -that allows one to limit the number of open zones. The number of open zones is -defined as the number of zones to which write commands are issued by all -threads/processes. +A zone of a zoned block device is in the open state when it is partially written +(i.e. not all sectors of the zone have been written). Zoned block devices may +have limit a on the total number of zones that can be simultaneously in the +open state, that is, the number of zones that can be written to simultaneously. +The \fBmax_open_zones\fR parameter limits the number of zones to which write +commands are issued by all fio jobs, that is, limits the number of zones that +will be in the open state. This parameter is relevant only if the +\fBzonemode=zbd\fR is used. The default value is always equal to maximum number +of open zones of the target zoned block device and a value higher than this +limit cannot be specified by users unless the option \fBignore_zone_limits\fR is +specified. When \fBignore_zone_limits\fR is specified or the target device has +no limit on the number of zones that can be in an open state, +\fBmax_open_zones\fR can specify 0 to disable any limit on the number of zones +that can be simultaneously written to by all jobs. .TP .BI job_max_open_zones \fR=\fPint -Limit on the number of simultaneously opened zones per single thread/process. +In the same manner as \fBmax_open_zones\fR, limit the number of open zones per +fio job, that is, the number of zones that a single job can simultaneously write +to. A value of zero indicates no limit. Default: zero. .TP .BI ignore_zone_limits \fR=\fPbool If this option is used, fio will ignore the maximum number of open zones limit From f65f4f2d6dbdc9785fefdc5789e5b1ba4fa09dce Mon Sep 17 00:00:00 2001 From: Shin'ichiro Kawasaki Date: Thu, 15 Dec 2022 10:56:05 +0900 Subject: [PATCH 0368/1097] example: add a zoned block device write example with GC by zone resets Add an example job file which shows how to simulate garbage collection of zoned block devices using options zone_reset_threshold and zone_reset_frequency. Signed-off-by: Shin'ichiro Kawasaki Signed-off-by: Jens Axboe --- examples/zbd-rand-write-zone-reset-gc.fio | 27 ++++++++++++++++++++++ examples/zbd-rand-write-zone-reset-gc.png | Bin 0 -> 59186 bytes 2 files changed, 27 insertions(+) create mode 100644 examples/zbd-rand-write-zone-reset-gc.fio create mode 100644 examples/zbd-rand-write-zone-reset-gc.png diff --git a/examples/zbd-rand-write-zone-reset-gc.fio b/examples/zbd-rand-write-zone-reset-gc.fio new file mode 100644 index 0000000000..8f77baf392 --- /dev/null +++ b/examples/zbd-rand-write-zone-reset-gc.fio @@ -0,0 +1,27 @@ +; Using the psync ioengine, random write to a (zoned) block device. Write +; target zones are chosen randomly among the first 8 zones starting from device +; offset corresponding to the 524th zone of the device (524 x 256 MB). Simulate +; garbage collection operation using zone_reset_threshold and +; zone_reset_frequency options. The zone resets happen when total written data +; bytes is beyond 70% of 8 zones, and 8 = 1 / 0.125 blocks are written. This +; example does not specify max_open_zones. The limit of maximum open zones is +; obtained from the target block device. + +[global] +name=zbd-rand-write-gc +group_reporting +rw=randwrite +zonemode=zbd +zonesize=256M +bs=32M +direct=1 +time_based +runtime=40 + +[dev1] +filename=/dev/sdb +size=8z +offset=524z +ioengine=psync +zone_reset_threshold=0.7 +zone_reset_frequency=0.125 diff --git a/examples/zbd-rand-write-zone-reset-gc.png b/examples/zbd-rand-write-zone-reset-gc.png new file mode 100644 index 0000000000000000000000000000000000000000..b10acc807f096e401070652b6d669c02c9baf3d7 GIT binary patch literal 59186 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 z z2}K2hKx=4ZWYoJY*KxHUR{{@Es;!;fKkje~3ybf*zVomxk4>7B^SvVBX(&c?9#B1D zM@2DbHILw76=sc?(%B1MLJEv*bp)NkVrPlooqWrt@mwp*Rmu-RP-aV?{ zZ)1@)8J>kmyR^)nv=!VFu%We)!2VTLqr+ho8jy5MfJm2#<|9NFG`Hh#0nP?=ZxTRS zN~m#Q$d{_g5RW=#(a3@DM-L%3H)36$Eft^(oV74dptxvn5zb0Gr?Aw~WJFyR0NK)6(eQE)6k6kd+}H8O290;XXULkA4WkT*XpF9lG!>Uomv))kFrOuLgwz$Zy^J`LHs@^a zZVBAY_@Ka|sPwn`tG!_xPBgWhhYs?}jaD%dCNt5A^q{w&vJ;vAKDs-}{#W;+!T`1b zd2#uY4`zwBUHhBU#Rf|TG!a{s&&W`>Czjg#jrHC2QEc~9*dN8<`a^Bh_HlR?XL(p0 z2}OM-^Z~GV7dSjy-G&FZb#ABcK*;y+fq_uuMhL0#a62pP*Kq;sRH|k@gy8E1h(QJ) z$jSLmJ5stRNs3c2ZU#aRs6?!#0JC>rERGsBU~!>A{=9j2a$$;z&FMX^F6F}}MyRH?7S zC(2CvKSRJG1oZd93o0Bo9zu~bSS7>sw2G(d#iJVUnhQRRJfgPLHOUmi^an1_!*{Sf zz6D90A_AqT^t->aFD)(i?VNkW);q;_O-XG(pP^hEeJnG}-QCJBz!=6zKwC`%0F4Tc zZXiH4myyS-Q?+&mD@L}=XHPGG9w$dg(%*=|_?hMD;>bg775}acNBAWx+v2x*!HmP= zmu2rxqUW_dKg12?D3!&{w!{jq&Y7&Y0NnH%C0Vaq(+7{!6T6>@GGVYCA>%)6?p z!zlxve=di=_()vg;$+XyiwT113;|*AGx-Ja!*8g<3f-$qg!K#znnWqw(ZzJrR(laR zV!Y=2zZsT8wB07P{0Q4aST=c@ojWfhwBPKIZJUCKisH_mNB(q zu9{B<^-jw~S2X9tdZEBO?7MM0k`hLkt%7_()p7VgOlD>eegysu361(ASbia}5GoEDdu6?o_q3Sr z$Ix;FoY~-O6Fbe19qLOs(f#{(IKI1$fvbR55cj_G3~%Gq@Q`Wk@0yvxV6i=2r`-eO zftrxS%*xu>(()COomBx~iHs$DYS=gk`OVaI3D^9;7&zhKOdf7>bYY zvMneq6d;UxUn2B!NJ?esS#(8~Xh~-FJ^T@~m#5=oI`7|fU?5@AGo_`SQMl3rFqH&C zluH>kclYIMK<*+Uahp*#^8ZmO zav?xhoJY8tGW7{Cc@N(D0oBwmW_=LH2>?YM%j4h&AEHp};sC>fw8#J6(GhagnQ9Nc zloXSaea1~0wMAHV(C`z-L^++{izZf{#XtDfQM{C|bN*PylUN;0nVefrvUHBy7=C*# zlbla%D&M#&saz=h_{ZYfS{v{V^qN==l0A3l4`Heaq%i_1S+o$Z4zn~eRrASVZ%MM6 zu8PQAknDs&et5De`S@ZXrxl#K3sXmg<=u+r>r_wh~Jw?js9k$Z>=z{=r11Ciu~)TeYML5Q9eovziQiH%*`ISepTAD)j@04(%p>(#~yh7 zlFOvEv|zT*VFEPNZl}wUupd5ha&mgeqywCca@xSbVf)_kaqH}CebI1+tyBz0AUrC{ zWyDNmXD(;=tXX5{Ex*DVQ4S)*b5ttBL^51cv@c$yBougk{(K{XC)kIat;n*eC6R<) zh_%5#$j9d<=(C_VQdYxWA3)Bif(b>O(39=%?a61(mRnOm^d)#-KU>$Grs1;X5J5E( z1p_HKI5?j3%me-vK*AXWhj5|YstkNg

Z?ZWnv4Ts%DcD>2prvZVwy_y32-!@;5a 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 Vd|4WK)|>$dJYD@<);T3K0RXp-dz1hG literal 0 HcmV?d00001 From a5000c864aa8e7b5525f51fa6fecec75c518b013 Mon Sep 17 00:00:00 2001 From: Shin'ichiro Kawasaki Date: Thu, 15 Dec 2022 10:56:06 +0900 Subject: [PATCH 0369/1097] example: add a zoned block device write example with GC by trim workload Add an example job file which shows how to simulate garbage collection of zoned block devices using a job with trim workload and flow options. Signed-off-by: Shin'ichiro Kawasaki Signed-off-by: Jens Axboe --- examples/zbd-rand-write-trim-gc.fio | 43 ++++++++++++++++++++++++++++ examples/zbd-rand-write-trim-gc.png | Bin 0 -> 104661 bytes 2 files changed, 43 insertions(+) create mode 100644 examples/zbd-rand-write-trim-gc.fio create mode 100644 examples/zbd-rand-write-trim-gc.png diff --git a/examples/zbd-rand-write-trim-gc.fio b/examples/zbd-rand-write-trim-gc.fio new file mode 100644 index 0000000000..139d2c43fc --- /dev/null +++ b/examples/zbd-rand-write-trim-gc.fio @@ -0,0 +1,43 @@ +; Using the libaio ioengine, random write to a (zoned) block device. Write +; target zones are chosen randomly among the first 128 zones starting from +; device offset corresponding to the 524th zone of the device (524 x 256 MB). +; For first 3 seconds, run only random write. After that, run random write job +; and garbage collection simulation job in parallel. The garbage collection +; simulation job runs trim workload to reset the 128 zones randomly. Use flow +; option to make the zone resets happen every 128 blocks writes by the other +; job. This example does not specify max_open_zones. The limit of maximum +; open zones is obtained from the target block device. + +[global] +group_reporting +zonemode=zbd +zonesize=256M +direct=1 +time_based +runtime=30 + +filename=/dev/sdb +offset=524z + +[warmup] +rw=randwrite +bs=2M +size=128z +ioengine=libaio +runtime=3 + +[wjob] +wait_for=warmup +rw=randwrite +bs=2M +size=128z +ioengine=libaio +flow=128 + +[trimjob] +wait_for=warmup +rw=randtrim +bs=256M +size=128z +ioengine=psync +flow=1 diff --git a/examples/zbd-rand-write-trim-gc.png b/examples/zbd-rand-write-trim-gc.png new file mode 100644 index 0000000000000000000000000000000000000000..f58dd412f873f400d058c3747e55fc30ab60afc3 GIT binary patch literal 104661 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 literal 0 HcmV?d00001 From 26da15807e2d7116e1be668eac198f26f5416f09 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Fri, 16 Dec 2022 13:44:30 -0500 Subject: [PATCH 0370/1097] tools/fiograph: update config file Add 'specific_options' for some new ioengines. Also add a missing ioengine option for the sg ioengine. We should have some sort of script to fill in these ioengine options. Signed-off-by: Vincent Fu --- tools/fiograph/fiograph.conf | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/tools/fiograph/fiograph.conf b/tools/fiograph/fiograph.conf index cfd2fd8eb3..91c5fcfeed 100644 --- a/tools/fiograph/fiograph.conf +++ b/tools/fiograph/fiograph.conf @@ -45,7 +45,7 @@ specific_options=stat_type specific_options=volume brick [ioengine_http] -specific_options=https http_host http_user http_pass http_s3_key http_s3_keyid http_swift_auth_token http_s3_region http_mode http_verbose +specific_options=https http_host http_user http_pass http_s3_key http_s3_keyid http_swift_auth_token http_s3_region http_mode http_verbose http_s3_storage_class http_s3_sse_customer_key http_s3_sse_customer_algorithm [ioengine_ime_aio] specific_options=ime_psync ime_psyncv @@ -53,9 +53,15 @@ specific_options=ime_psync ime_psyncv [ioengine_io_uring] specific_options=hipri cmdprio_percentage cmdprio_class cmdprio cmdprio_bssplit fixedbufs registerfiles sqthread_poll sqthread_poll_cpu nonvectored uncached nowait force_async +[ioengine_io_uring_cmd] +specific_options=hipri cmdprio_percentage cmdprio_class cmdprio cmdprio_bssplit fixedbufs registerfiles sqthread_poll sqthread_poll_cpu nonvectored uncached nowait force_async cmd_type + [ioengine_libaio] specific_options=userspace_reap cmdprio_percentage cmdprio_class cmdprio cmdprio_bssplit nowait +[ioengine_libblkio] +specific_options=libblkio_driver libblkio_path libblkio_pre_connect_props libblkio_num_entries libblkio_queue_size libblkio_pre_start_props hipri libblkio_vectored libblkio_write_zeroes_on_trim libblkio_wait_mode libblkio_force_enable_completion_eventfd + [ioengine_libcufile] specific_options=gpu_dev_ids cuda_io @@ -99,7 +105,10 @@ specific_options=clustername rbdname pool clientname busy_poll specific_options=hostname bindname port verb [ioengine_sg] -specific_options=hipri readfua writefua sg_write_mode sg +specific_options=hipri readfua writefua sg_write_mode stream_id [ioengine_pvsync2] specific_options=hipri hipri_percentage uncached nowait sync psync vsync pvsync + +[ioengine_xnvme] +specific_options=hipri sqthread_poll xnvme_be xnvme_async xnvme_sync xnvme_admin xnvme_dev_nsid xnvme_iovec From 4e0fa717441c5d79ebeb0ef6a96a085c554ecc4f Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Fri, 16 Dec 2022 13:20:08 -0500 Subject: [PATCH 0371/1097] examples: add missing fiograph diagrams We have added multiple example job files recently without including fiograph diagrams for them. This patch adds fiograph diagrams for example job files where they were missing. Signed-off-by: Vincent Fu --- examples/dedupe-global.png | Bin 0 -> 55479 bytes examples/http-s3-crypto.png | Bin 0 -> 131254 bytes examples/http-s3-storage-class.png | Bin 0 -> 123771 bytes examples/libblkio-io_uring.png | Bin 0 -> 57227 bytes examples/libblkio-virtio-blk-vfio-pci.png | Bin 0 -> 62398 bytes examples/sg_verify-fail.png | Bin 0 -> 107581 bytes examples/sg_verify.png | Bin 0 -> 161969 bytes examples/uring-cmd-ng.png | Bin 0 -> 83761 bytes examples/uring-cmd-zoned.png | Bin 0 -> 98231 bytes examples/xnvme-compare.png | Bin 0 -> 44742 bytes examples/xnvme-zoned.png | Bin 0 -> 48340 bytes 11 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 examples/dedupe-global.png create mode 100644 examples/http-s3-crypto.png create mode 100644 examples/http-s3-storage-class.png create mode 100644 examples/libblkio-io_uring.png create mode 100644 examples/libblkio-virtio-blk-vfio-pci.png create mode 100644 examples/sg_verify-fail.png create mode 100644 examples/sg_verify.png create mode 100644 examples/uring-cmd-ng.png create mode 100644 examples/uring-cmd-zoned.png create mode 100644 examples/xnvme-compare.png create mode 100644 examples/xnvme-zoned.png diff --git a/examples/dedupe-global.png b/examples/dedupe-global.png new file mode 100644 index 0000000000000000000000000000000000000000..fd4602e31572ccab5da2cf1250e8d5b1904d3c01 GIT binary patch literal 55479 zcmd43cT|(x*DW0NC`VARfCy47bOe+tU8PCyAYJLb2c%;I0qMQ>8j94=5s^-al+cTG z2oQP;B-|a(`Q7{8JMMV@`tBHC&QU{1p1t?lYpuQ3obySLijvHAVrpUt1ae*Wm82R3 zav2MOT(J533V0>x@EHgA=Zdksj3nfo@Lxt#el!GfA0jLHLgQV^#d4Zyt?*jaqrZ`G*0#+>!)k7Y2hx;{e7vYbw@tpQ*BcoNDEA->D_<_U=n{GDs1E9 zLjU;zqT3#GjR`m^We3usBKlmz$4x-8NpIoSycA ziS)*CM3!+P%%XW2l)d&q@L|Gl0; zopbx{7!rvLhy<@g+{yk}ET)#;?@SekJ%~6Da`vKq{hB_<_} zOHNMK*Vor*@%8!q>C@_9uDttj=cYZ&WP`f{Y?qW)XzH*wmR-BZZnDPN#U;9-p<$xR zDx1${WNPQ#t_y63-}2XYw&p9>smTkPe*Tnf!eiI1QAM$hO-)Oys|DY`KR>`s>!tf* z*X+Wl>!CKyEBE>MG-29hTBD@~n$^~tf2qKIeA@1Mn)oPZgbPl`)Jy|15EYn z)!R&WqaOQZW@g5jgwcswYZR!oMwn_8ss z?bK^>TkfOkwNcMgD35_V%+6Sg2!?xYw-SYtP;%b2K&J5}2{_oZ`kgGrhlr##I4NCk zdondW%~57I_N`UT7d_0Hty^s!z|b}{G&ELi6CW87@un}4r&G=E_xJbG=H}*+$krd9 zNv_LwL@~;L1s0%==QQ9p^us~rr{!YVsK8HbDvfX#mDpkR?S-M#eSxZdZM30XI2|&pXE- zwZt9PyKS;LI5;S#iy4TCiACioCi7dJ>@IoG8AZ6+N zNG&&*ft86#I$6Mh({-bLPN|a+)iJz(f|cdzN`LAQh{MN7o6%A`j}7p_@87@ccS=f2 zqqpbU;#R<})rd4llE>wMNMh&UD0$^Wo}YZ$aN-24P-8c#wYu{vDkMa{bkiI?T==Na za%JBwD=VwYVOFen?;h?|RBCD}@H#UOUl@n&t}sy)uX*2H=;nkbhi+9we0==I#)c_( zA~_Y6RiRM~$5)Jo(^o>KVX^v?KvayKne1IKp%@G%Z9O3&;Vx{2-~x!NW36AOI>w+@R2EPYzDb(q9YR%1+?|7T$uLjxMJI0|Op4k+XskIn6$&)`uJ8 z*n?3cRGE=@dq+pllWQPg`qW0Be0|w&NE2*w1_pmF`zQ7J-&)w(ayU5apja2k$;nw1 z$}?^c)FG24Q&BPA~QsFJ8dy zxI{&9aZrb@Et(V|sGQdkraA^#l56xC@uNqNBz&;@ksz_m_?_b(!XHr}kAXA!NBxs+ z8S|t=sX-p75efhD<(r6-oZMi02&D_HdWkL{pTo@5?%7zmNgdi17!&L}n=`!{CdW=q zEoy0JZ7APCFfYX(v$fm_VztThfNlRBgNw7^N<2ism<|2Tfsuu!t7t2EvbVp#1{>S@ z?QPqx?V}c#h&?%6a#>W1^O9WVd~6QLzv%U{7Oj^rKQJk!6s(VyQPzjv`#+3SZ9C50 zd)-*TeyYId^pN$zgBN9uyTMBV6@n$Y)pCZ0hEab~E$}Z*W8*Ai6B9m@j%&1H-mmvo zkS*C_jg5`Nx$=~~d)M#rvoJDB8Tp>%x^0Z9{@y{PiyK8oMhf}i8}D!#+Om>ey-LWK zA-rj4L0d^h2xbZ7fLONZ{=NdgdeH-wa0)vK{@;9v) zvwd;c0ES*8FgQnNim2DfJ8<#PdgwYeCnskB$kI-;K1Wo_q6aZI@7&RP>;r;_x-H;3 z51VF@=`$fAY7|UcfJq_Y&DVPZ{ph>09S$ux^Sc3WX~cb}iq4vjW_Wbr_I9oxuR;Zlje_n-^xb&w8W+(C4OmJq<6ls+db#=Xx9RwyCvnSL; zEFZ_}3F(q$sm69Zt4I^JJW;a@j^*Is;3$T=y1GJ{aJrhm35n^u!9UK=a_QltM||J} zCS=LAwKXBn{Q^3_GbgHm2Vj{c`gLj-E?lS~z5VAS&;L{>ELk9t>Do;`lC8Ez4T8Ot z=M9@_@>++LHLE#0SAZjhQCwWS8sw~V+?F4o^)L(WY!6BO)L1g8gK%bNEPj*uFO`k< z|Fg0Pj0H^b|FG`*zYHKa2FkzZ1Co-GV?u<*f(Q8}P-$Jha^=p+yMKxm!i)c3lzoKj z{5R`!`340^&P)5{hg79h=Rp(<9BK&Dbw>z<-hH z`6=Y|4rMXqrGc0Hcl)MA_3(I|BK4J|l$2|FftSb$Rj7X~cvscikNIv}Zlj4jVKt;g z5XbPH!+x#KutMy*Q8cQ~C3s`gj`pSsZ%~liL{+!?WKG?Ae!6~4Y`ef~i2r#xTwu7u ztlslLZs7ZS+1CBa3!d);YwpB&2@+#6KCWh*)zqKrGR)OdYq_E%Kr6IZghui3`FJ+gyDOJNzdV1R zo10i=9b2YVvKyJ2nwci54hn!GYQ7$oTsa+Qcjm}TkR=_t;8bJdjf2yzek?{yDee%BCYG??p)-IXV+c1SN8ujDF}4pq<#ygW5hV~0r+9yQV~n^-$pDYHD@ zhA|vofWTv;jPs%Eja9~<%NiRi_*|A5576yWq%6LK;ZoSbB>lV_n>xN0=6v~^x#@V` z>^i-#=HX!@Xnlw=T{kHaX`5vQEHWH=ce07-6)K^xo^u8~;4s|Ck7f5cSk0Ctz8VzY z;ulxab`cWuFrIVVW+00gyTPLzO6MaL&1|%MQIMP+lqo7o(^gsM!>r7q)D#R}UJ_d^ z^d%G3b;P$JD!f!wOp2+xsc)T@kPv(yQNIDGRVNFsKN-*IUgrJ`>%26;&bu z#xPFz!xU2STpVulD!Ri_&q7J524kUfPg;eucf5u`awT1>KH|5|*)4ITS5apW zf8qHJ+tp1t7rJic7O#uBfAiU3+sK0pQGG}8e_y>i-1@Opw}C&HK^*csj4;kfY3Lw$ zjri(S4SwP8b9662X6i|-;*C!8YmO2Tt=z|-4(#sCS*(LATX4&AH9G1ji>GE3s&|Qv~to5e_MX z`kaYBD_Ne4EQyGC=YuVThta!NK%cP$&iKu0gMdupWt|zxgg|;~;|+tg)uK|hO6D&T zjol&4i@C;jXLxNWEJ-YttnvG1U#hS+6g^yyZE&3X0K7*o_5G#j;aaDFxW{j2bz_Qu zN(z@4Ze}|zlol9EVWj?o)HbkbbPc*74F-xd#pDziP{fmO4=+3yEVoaM_dHZk1(8xi zdbu_qks_$B2e+?=-hEyOb_XdEogwNqMR9xK4Z)7K6N{|E!V+zPM%7Rjh`+Nj2$PCR zo+^*Z{1S;5f`oIc=Y8!nFWhcO$Q4qBnm=#Ku|Sg7)tcI@DA}t7MpfD+H-G^n8@y^t zBrX6GVKC$9ah}h~yFc;>J1Xe78UfqbxK?e`IRl(8YGKt96d}C0?T;|R5Rs8@2a)0p z1?tQrBH*{Ea+4xo{J7L;sgt_l*cF1Mh?G(s2YrsrdQhluf5y*y+ji}9po@fI%vTVk z1}*k#+0vu7w8W5%6sxJ@q*5aR)`vd^Mlv%}j4Ktu=jZ2X$jM!&N8UnmM?kq;<+LFE zIWVx-l)KP=y593_rxOHj_=FJe?=(vCnB1|~UV4G=o##9BO{OoAE9mMbHJ|Ou`|Pw+ z3V9tCa~jk$P*PHA7OF{+i5+K1htU**c4}NmAw#1;qx)>;Hswmj?>Ehk(%lQegt-q6 zDmX*U(seTmKxvZWKI@|mHbWDU#0TA9RaH#nmLj?H_#4#nyC@RDp;uVwz45&Jl{^oU zZ$$KY>)BrV`SAYQ5USEb88pO-fuyv91*+Mupzklysg%A%L`or^+iWkmyc&zbcV*UaKf?A0NBB zHzB{3{-7XTohN%M9O1}RVG9pi?!@6t2_n5uplEQEaW*XU+bg?s`}S>l5YFFe9}*y< zh;Tw(O#pB-G-AY#e_xV60aF`K2YpYB>=(jf4v4#A^+3i z*>k&N^hNi7NobjYJsJib4PghRBqa-EqnVP#{hIR>5{4h2S-AU0f8Jvf&S2Xxi7Jc0*v)--h3lh#)F= z2w2?|UtysRdd4SNIuzSFl1Uy?!Jn2Q9aan*$|qubHYPl3;}ME;I|z}`5ub(lS7aAA zS;SWFEdKm+0l_J&OH9#)&*mZi-0S6?bVC8x@7dA@DBcao+_2p<(oq@dP`_Jq*ULi; ze0DJRpU;wCv7v)Jw}N&C`N}3ebIO{iw$Y|hyzo5n7B#i?`VHHj>FE~@?&7BFPl6S7 z?kT?=Gu~J_@xBC+i?29L(65$Gzpin*%)JtP#&>sq>V6HNesmjV8T3lYvSVrLXH!7hA-!EOz zvNLE-$z1Aft9G%d;xtr<&d<-*g&&R{?_7WEmAky*fJ`&g{r1~^bCQzX%PU&9YI~?y zM^Uq>zCm;H=1p1M+HH4Qk-Pcpqt{Wem0YIDZ}zgq)IGz9X`@Ei_bzPuVtcAy)Id=&4BtIsq?c=K7QNmSeF zw5l<)_x^&+o&?Qv1j7sU~{QMx_oA35MCrg(RNhl4Ouzi$8Iy4dnYf5`(+=4$S zbBU`jFJGg6<`~|L$9^zJ+UJ3?2b6I_Y!Bv==)7#pd{35~({PB?)8sVY`Y=+O00^(K}kt4#)orx~;vmXqpe43pCqX zElcl$2OVB?#-wj=Ul8)~eDfY3jD0Uq_H>z%k59g=nL4lBWPR=X`@n;4X4=?9c zJza08TVGXut?k#w_*;*|r|%rES6j|8YioN|I~b`N)c=KE9bm6_UYbyJIxB@~^YGbd zsY6#)1_s?ME!lH(%_yvFFK@zGOzPtq8O_)jaw2tWith^uc=1`y!`egiSb2E#T$X97 zizaJIT0k|-?BKwBf)!fpX10MDwG`=AE>}fGm#s`{|JmM}pGkpyWP})!eE__i#c8re zN;azAt;AFl158Uqe@(^HGwyJ+fu;M*#avi8x*l5D+{1eRyA*WYb8W2rulAogOXzGX zb#d6rix;I)IU$ti+D8preLk*eQ`*4v^h!(vGMhz$U8O~ecBrXueB2O`bfO!JD%;8p zrZ8|ldj~L>Ax7Eh`pawS=Nk#@$G_h=fVyCHxDa-7sF`qb3F6ODzWf#u4g3)DefkF- zj->+NtOTe9?k&p0qfSb?a{W3Az{7UrHys`7+&J`PWfPs5ifq7ShhOdSPxHK_~xSN%=+*XJf)0W4-|xVFM{J} z1~xMjc9~w|!6^qi!I`ZPXHD{>vs0Bx9kWYJkwS4Vx%Bw>nr888R!)wB&vvVo-Q=Cz zgalrLrX(v(@fH{{36CEH@{N%YKd<)y4y|_5`y*>iqeNFT;O**=vN#GLU5}Se4>#38 zfna+#(GI#cC>8Lw03g`_ZUuGlchS!7Zl$!eFgdv%4Y{5JeR_R>1^d0e`nO|bbaZNC zBTa0Sv%&d^vfudR%1$Agbl%b3T?H&4H_v7Pd8ow;7hjBoKLgGS9587{c*G`cDrpTNnlHWs$*09C#5+a0(m1(nEbb8 zP#-eXV8GwChD~hpQEppd6)$nUsGSy|~|H{t9_ zA}3#HXg7I5c*;gKHObmdV%a%44PaWSgR$(RF==VJABh&K?57hi9{m?TZX=v^Y6{Z6 zEtI`w7oueWiaGkb^X;=ju?-guY6V*{#CDzQc zLM}`?BzI_r$i#oLvda^b9uCTL@r^_=o(~LUUj;_+h0`K$D-Uz3wOpoBm(^>+#w;Vn z%{33FTn5nD9@f#qdJm=}-wdLWUUwu)Ipoi2P9E{R=&V7EKDrT^K z#F0^8`*fDzoNp1Aeg_Cat=CiCIni^0TQg<3DsCR{-myISw%RiMa3EY*D@M`}6ohv; zbaNb*0b-Q0Q6#vS9b<2wrr)5H4;FutO{WF=JCwib0`lv%flk{r!=w z_^`FisHIsc<(X=Mm~#i0EWk}`z~Mz{H`{Emv$IpAUsrL3jIMaR(xMlI!b+=N4hog@ zJJC$LjmtGCGZZE`#8$Ic7w9UV7lQ7LG3Z3=9XMArjw4n(Q)`DL#r2Lde&P-%9jqs+ z3SUY{#Ky$r82RBe0lA=UZk`7~=i)dHJvMIcK{db5Fk1Al%#Q^4a%01GbE-~VCX7ba z)HFvkN}QK>IBJngf&PCok?^YOcRR+42~S?k&CP)o#RI%HAJAFWfRNELVYRe{U=)Gf zt;cuSLFUD!9(^WZ6>{fHgyjH?E|E~(`B#KXipx#$AVso|-|Zn1=)5+bf>K(UfNVxQ zkYJDnF!m^bW;qVPWC_HRJnnxdoyzwkhd>pMsxXuH@bJ(HizM(jdMI`|Fg8a}&?v9V zGFNbzlDh5$^z&zfkxy4;0BKaW$!D>tIQK+6DrYSA}_mNOFzU1fta_7KA zHLdG`bzZ>r$^6r;bGd98dJRMp279*0S)_$Pj647Y@6=P!)k0!?NNY$TOY#Z|Ur~Fk z>UowuF|2iHGgH%5K-tyZ+5wQe7SdtHKEJ?+*LLj5sEy!h-Q2RCu7161;ji)wwMQby zZ~W=JBhq2uAgc%kc?)88PkTRGQnUE=0Qw|F(8ctwbebOs^ggKiOiIvsOiYY0Cc7Z` z*1bQ~C%{DoZsAyN0=_IRJUkp=)p^T^WWAnnTH!dDJ)lNWh3d?*Y5*=Lr=hX+;CT4( z$L=#$R$0*h^g+Gfp0X5W{Uku%ghxBUepBEQi%Uz4vH(&-R=I8JGQzBOfBr`cz{JGH z&#%Q}-uG*+Es)O-?=6or>y6W~w`~BO_vR#A`RdiHo=dUXj|F}-4T%L8v6TN)BtxD% zfgpQGKtL3jqCw$qt~pa~B9o}V1Z#PJiAdMb0Z_ewVl7%p_cJ7T&FVn5CfLAgn^8gs zCgk`V7-{>3JPtG6U~`Vgp@;Ir0T&0LF?aB8IY9eq0ov&CfdZ3yB$mM1n{g)=*Gy3H zT1LEl`4V`K0&I+jn;UR{(QWgr?LB_-9ZbI!eZ@uDp))4v~yd3Fm|yFwttjeHJbqoSihpmCj?oD>2a0lVdQ z4pYsRUIS(v0o^9}ls}CYy>5xn-g-g*nUHl$qfhxLSNB*1+(JMM241yne@~&clwt&V z?(rijf5QX;AhheR_7~Dte*exOP$+=-F9$g-b9h)IH#e76L_~k9<-D0d!mQu8u=v(= zGEzwNpz6b=k6Us$VJA3N4xylV6g$mly-;9mcVt+#Hpa9m)WnV)mD`=Y1&(JEgnCUm zp;)*>XsB730^YJrc4>iBTOv^hGmbXlbMMr@ZYlDe0Iosqy#e$2-qhJT0^CSNt5&$q z<|tvhhIFAc8IN$q<+d6P*X!EYiPek)kP%ee9fUOCMMh|k{Vz`5sCkjknL1cyKSV%~ z09W(pY@KkF5{G0+O8%5tMHo1syFNdvPlFmD*aU`hl%j_#ta~rgrbonOP!MTZb{5hG zH&b&6FbnXNP3a?Re6+}h-~En*nnW2duAuWL@YSdmRlkF)m`9XE@gk?k^G95~C%xTT zX5wE@U(A%wcpiOkz+!)n2Frbos{B}~t7TXC01;Tw>tkT*U{SWo47b@>G2!UM(d{;* z(55iTa=g@IVSU7k6jNl1J`{y3$BMAaMsemFj_hv5+pMgm)+e~CruC;;85fokL5#C5`Tl&cBJ)9AA+@BSjM2T(Ug z^FQU<*hPT1d^dM-k)n3Tw3 zlz8dLYH0QWa_(GX5^aa7G*nJa^VBsFyO&Ly@3v@ty1JO{?BZG}VuZ&zbBx0!qlFb~ zynlx(hVga!ic0L!rgDA1FgzUC;Z>N@fcY@tbWNX<$(Jm5Vb z!Q{K~O;D^I>!lh2AeWd>g)u zfEa(uJi9=-X^=w_M9Ev(sQ`ryDbn@R6)kDYXZl^&^!M^NoW<93=IU1z3Bx)c6Ml_R z!tA;qjqZ_&l_?!T>&@u3hsvxM10s9)D-K#S$cn`xXrFmKhvL?YBuYq~og5=u8d~z) zQj2tbtAky|n#Fdi<1j`=GRY!EPd7VWhw^~c4K*lg$4?k6F0ba*u*v?&2ByMaIWsVupZhat3Jy%EFRAzp$VQ zw>QuWeCr~{DFy^Ubv^90nku%=#q;X(ngiL!X`P0>^?5m883cu&t<=dB0pm05AxHOM z$?-5MZN9_3@}fl@S+Dak7?yWo- z%`)xx*vLM5IvEF=lKaT}O{ZNA&QCJQja=o#+DZ77(-wl>r z(VpKSAH`OyUAt7Sbf~oJdZ?i$9i>*9yNG?_wXm{&f$qY|2qL5@wP}P@k6qb9`MR#~ ztD%;}Qs7&|yak21Vcn*K{vDTiGj<>b$mY_IE2P#3;jn|L6{MBt(Hd57y=7@qxu=>u zbmEEJXl0oVDnoPE>7biWiHnje*EDBUoaYz)U2(Rf^UIT2<7*Y#fgL>!{p1e^wm3_MV!|Qu>H0j@wfV^?slbNTuL>v|rJxnlSelTE6Il`ydz@l_wd5&OUA zm~cY!T|uuFfskc?ETjMuCYA#$pon*_pxLD5gLM{rhm)<*WEIV563#?$h|05$s<|%j zQZ*fKQBuU;%GVLK(DCi}&BipQ?p2-c+vn=bt175{HJm;ynpd0OohmyF03WWNkEEzi z@zg>3;8RM?3@RJ>*C%y^ZhE~Otte#`<1^f%_cj8MfqWdJy zPC_WKpk5iOcw!xVh%eWcrIM9UvQ9<5K>Dd0mZOw z15!>Wnd=D(x-Gt<+Mt#IWf!m0ykzNWAY!UE%MXZ6+GpGVxBq@UjNYNS-g3Mmq@Av_ zq*$yp>iBTtY5me)*L>C?czhRHm1pux<~6mxOaFNDT7eLVQq~a8UAhlk*{FwKB!U&1DJ@@ zC$Ep?>xq|Ku(>jIsE@-%w!kc!(&ENuMYl_9(kDt+VFSC)J8Es;1E^7MF%zSIHDAo_UK*i{Br@88@F&r-$290XE&ii zE9A;Dxbxt_15lX)LV|s{%U`dnFD8jUHu#wZ#jS@_0Y6g!)kAm?f0aBJo>7Cab(|40=7JZEX|G05`U zTSmm)8%|PSN(BO0f)o+-IcypQ8G!F&1MQ5OA7-j9u3lg(_IwjQtJ{Oa;T*>CbuKHs zrd>BYJUxqEekS&u4`Oy*>Y*T9fuLf|&(Htx;R7$w)G#tK>P(DRn1yHx1KEz&!k6&y z(Hz-mjRrS6f~t3PRNL>Q*Qg2z;q=0A05~Kx9t271@^W6OK?C}9s|8*Z9T}OaZRjBd zT8RiCuL5GFVIbGToz9+Xfk!0Ls}7afc5csU!!7$rI-%EV{~M?Bt6Jzi(BY1P2~l%P z?(RggX+&TTW9^eFlZ21_pbj(a=aUZS+P*_62ozY%@}a0GVpDwQlj~A&-K;OJZ|TJg zKo|}xx@hUuyoN`c^s|W>Ch>ySR?pcP@MF0xXHxZu6jab{wREv4IDB!L3A)L)lu+5z z6#-^y{R@aDkw_XqRd77D_@_lvA&qH}JomqF2}CdUXp8F)T|&^&0c})?QHx=N+vXqv zjy$mFPho9)vWUUhqm;I0n#}G7=+@YJOdc{TCgsE%`RV|bvTlWGkQn~hgwVziSQkKP zWv{MUHXTmb=r(y)8~Pk*$_oSeUA6U!Ie5GEbba**kPm?73Jr9_LawV3Q&Urf=B~uB zNoe#bQ15zT>Q;Dtu&z=->DjQ=Jhapsk1Ej<*qm+{RdblCb@E(Eb*}=_zV^<}b-*xM z|H^m|7EuPqpqTp_?{a!1--2hWYe;r@(%_t1cLpoVsj9#-2ER;9K+0u*I~gSlWugP9V`KfRAI%Qen&N^xmkN=ahV zA|qSP(VRwc_0VWA619Mm=L)UWIQL%s)z0wbXguyzzE9^*6hLAwEpZN{|G`H}4TS3& z8el+wMsP%ca})!0IfeWwQe^Kd*acR1cX!d9pSJ;Q71?~Ud=H4BWdV>99~c-Y9Zm-X zj@D~HAjzqcJd~$61h~d3)9zabo0Hl=_E@A{mPXivjEwtD+doL^-|b|fo(2U4c`k$s z3^)6D0kEt9Bnj;CLO9UF@&UA{Rm}mQWQ3tS4>wq~wY7mb%Noe8!IcclQ^K7Kb$Hg= z$a23u0n`GbdwU=oaTSv(K{7kj6{*(Xwk+uzPPfcHLq^Zfj0|bxK_;|Zb95IlLd+IK z?jr=2xgbdb@ggF9JEKL66psDY6TEAERC|;}KBqvWsn+DV{L@b*V*dw;T9_d0r{r3w zK+fSN!l%aC3_mzG!MBzt2ul@4JOvZ0tCxHKe(=ZVWgrnEJp7$ArkJsSs<%661O|!9 zDJUo~wrT-u(?YUqm!?>F#Ik7;Pp#xBCNsza!D~4iP*7_D<$P>hTpnPAb(@yw=d*y8 zST~l>YEY5@J-mLMkd>XynCMsN`1|_>fSXqez1z+(Ye?JPb_9rh?(>}oWFN1{@|eF$ z_p_f-Viue6hI*?mCRAcw`il~li=F!vae@{uvD$@d+hXS_l|4@_mNsnhKOS)?5}wW* z<56vhB;Kc;x9^za&EA{hbAPLcjZj6u{e0B}O-M0EiX-X|b7)sZ4N7)HCC zvoAw0>n3%U2DGQBbQi65UW-_h{+@oN3b=qnr3;VO>4$^F$hXZxv+eDTEq+RV{W>5m z6{)l~j`}H4uqU;=`4Y-^PW`5xc#n%l4BK^<`yT~<2hSg=X>U!BOBYn^{=o13rI=O}TbjQDf_(g@uKOmlr!wqoS9a1g6F+^3`(Xw3a~h zc+^ndvoZWjtN68Q6sUssf8JKM1lX+Ht5?4OOb1dYoyX$MCIITT3&!kPAn9CwG|1lG zo7sE&)*UV`EWF1rAQ-!y5?fh$(i^nzx}JPA<7m>GM&qId|KmJpq#%2AcVqi6VEA&o z8QU4UwMZF;Q9Q&J%Gpq-4*p z0$e6T1pGC1`o|y?Y_sJLxuo;o^Mg4Tih3jZ+n=DHnikEq4axt&^c2Ghw%FMkE!0jv4A4F)aMKOA zi$YFGNlQ}l6G5w8cdMD9>0~(>fVXjkGn+%Nh6SL!%*tuQK(0eUOItz!E^D^yT7d>= zp(AuCQz9@1NX#F}gjxRla?{z_85~sdCFn3J9%*p6Xi7^50O@-*P?G~p0Z6H07oYTo zEt*<=hwOz~-s6w49*N<%=VdOY_fqKTYZS&EqRlWSC(bU{Zr%i@pLo*`2zNlar#>4v zGm}w^a^210^{(|O!{y4K!#^XhV=#lYPT){flM8lT{K9Me$pe-JoZ6)0l}vb2%d;<` zv3DLnF+w3zI4HBC`;Kh3op#DOTdX;cpnKIZpd24he7Al{MTGSH=$n#(Q0~lum!8A^AoDY`3O@nK>b&6Jd$&&f#i4ei^lhMi(XWAWp-; z<(dvg^rCVA^;6(5+Z?yB+@A`Id?bSo6_`;0Nsl0%TFZ?$1Th^;PEXGTt^gQ=nF<^* zUKi+Efx4=vrzZ+D(LfGW?RV}At_bQ9ZB|xR05y1SR*%{X2LL768rTVHe%l1YcXO9D z3shd%O;oAM$pwSE2QpQ&`^1Ozt1Ke{IR4|ukHv+BZ1}9N9yq1d!Jt5F3mAGVNWKQ> zsk+MC%_gs-B0vFC3%M46ViN%S#ei$l0#f`zASnykoXwoNE70G_g&W9TWgjvP?GEceGshsDgiYfG8( zL$iW~_vtPE>`V?%6*?H%q!%>ioUKQBf7FYXO%gYnXqi2Gw`J&ZamF-iH?gt^$mG^P zzirE*G<>qvGMg8Aa!f0n1FnCEgBwnH$Glk$l<2uuLw%j@XoeH4T!Wh|h`!Bfo8~0H zRh4d}GBIgfGfoTKhhf|wN1`*X1LqK64fhg-Jb!vTH$@f|vl8p5$i`P*bP3`f45$)4 zKZd2kLoJAT9G7pY`^ z0Nfb`HMM82iNwp7&pA1x3%l9hVw45#o<2H@_q8UzVv8!Ew z?a|BtB&@?%Y-G?kTPk#S<0uukwF>^cioy$?r2`bL!%3ux{rM-y%0&B|#N2bHltxrd_&ZTiE=GyS3o`mFeDWlN1l(|_;!#D=O-Y8&+ z>sDft2AK7SUc#p@QN7Ayk#+sg=_HRUk_}101%G41xFRWKS z&|mqTPa<9jXpN{oZPU*!G@P$V(Pzr zFT`B`2WS6-Q2KKue4R8)BpLMY&H*=q+HC)BZKqJL8>w|Nu^X?D0!LsB!!)?53aEO5 z`%~t``?8)SDuBjm-mc|FE3Ng^SR*YG)KW*YTn2T<&0H6FEq~pY9RxI5aMR|OFJCT> z(u&VC@(os)QHF?yo$a4~OXpH<)%ctb+(CVg|KlrSI_3@-cwwC_CoPv z5_&N=n)>Yg%KGo)kC(B(?!}*3r0Y;6je4a`XgR>D`glvo^vZs1z2wV2y(T)1wUTcF zV6*1}mqP)VX0UwFP*zh&)#f5UVqH}EV=`_FcPMR3hVpx5G8w7~u)1nKZ`jM4PX&8E zsY{ky1y^aA3j!Rj`#5co5P%nDxUVixJ+kY-!S!%ghp%AlIRQ5R7<}CoJGiEYzqjPB z#9hfv;vJUn$7Aq+AtKNcFb>(^AH(bgKfVg`3)Z||={qQ1znnw{q;)dWbkw--mV}EE z0Plf&GBCsEqCR-PbiLG%lbhNN>-(Id4LdrYT}cPN-D!)lGhOdJ_b#jxUPe3E;qwX& z@^#T^4bDoJ#Hm@diL{$gD;(2C2fk8F($<);Iw3kQ{cfsmEK>fwe3)yaR&F99Jg^{1 znU7$j=@s|%tTO5KY5Ez|vPtrRX)p7E98&u+G+p;R<}iJ{42j?3;Ulp&NA=gSuMGQ` zx$_a*=5VFqvvLBRluxiWWKG*IGnCRfk5BKYXw;-Q{4ZzC_b&5bC{#+F04;Th=kc7W zUf>6{uC}&Eqi!3YRWUUf*6VjT^$QjkO$i9cNLdTM=&4D^m=MG7!#kY3vqZmV&*OY? zs74Dh$7q0TgTPB&CJcL`@;m=N06a7%gxOVv!fNgl+N)?C``S+Nw29{rX+ zLSvY{`1Rb!QK!m1vNX>KRh(8ioT)`5pF&`qbj@{3-9m&)qbhuVdUBu}UWHz~*Nqy` zJkqRH4#9G&RrVmN4)d!)gtDs$3)p|K;x3Q9qLTIxe-g-tObvtO00mbM9YFgMfcX9S zd3uvDSx6C<{u!RmE#R%HDGx8@u;XX_sYe-r#Qo=c0aR~Oyi#Y={bqZLXG1W$@2umY zMlRXI+#J0MJP2SOrxy~6-0@SFrKFy#{55_vVV3^q;uDrO2j zJ4LfUrO7Go=l;*YJH4eO4Np`Peoaf9njw_fn5j|LKMOypGvqL?ZfJ|(D~>mRI6#j1 zw^1jXy<$eY_LJ__Jy4r5McCKRg_mj9p$x)Y$(Q2ER)vUR_wJEK7~;liaJk3B|-8mcg_hv51;tCP#Y zbUfHFB8l+H!IIn&cL8Am|I@V7DA%Laez=1-ta5s2Ji76N2=!FbL2pf99^#LYjc zCyBRU{CPg9#H5+6X!8DFZ;slhlk95ZY9OY|=I9u%`rq)%3a`Hhu0ALt?)G3o$8nJ<_#v35&8UkhgkRp%Y~VM<{2N4 znw`p|U2GgXiO+d8dp~{*Ule8+=62?Owl~X@z_#y|sN)fCWn(3;t3(Z^aMbjRwM2hlVO@zWUoMD; zQTTGTZV8W|>Y$)!M8#qG%gp-)9yeYfK6bHpEo{fO6$@1awUSYoS3 zP>7oMaMQ7Q6|>czFQDjcvHo2)kqU?DeS3=O`2o=Dw-zr%03 zXddAqpuY0sXoloxu)X$x{61Y=R>P$&T;;D!d!jh$$4eSNSPiZ5QCcVJSMwp;9)l}$ zmvwSuMdp}(W=j*SfcayCYm9=*2ieU?ab9B8anm)r2cK@la_-qPSvaJiCfvl5vg=T4zj@oJj=8ar_S_AJ7|9P^aWd>#nm&*DZ0CR=SWpY8t zcyS#vVYTK+ZX*=>R`pwW(wlavh(CX5QawNX*u2#9a+Y6vKViUKMC0&vH3+t(H^j#x z{##*wGQKb7o~CLjo#i9^3JQx06Yq9)_YM^1uR5J&b&iKHKwIibR6>(lx-<{ zBSTz|lPvTDe4dWio^ z$G!4c81Eo`E<)|1?!t7cJi<0v=WB6m==RC-s(|SXV$r%s)BF?SyK3R=v}W$ro$HyU z5(XW3?znCFxtyh($f(#KSd(=f>hhSK=}ztARLKX;4{9eH2+nXQy~+4U;Nz#Nzw*xb zKZ7F_fz6RxG7mtQDi70fJM+}0cqc}Rhcd7}3hKTP%@Cz36*;$*%xCbqOttiZh7KsZ+Co9%WWk1 zXszcdo85X<`|!`0L+E65Oxn*LaV=-zb^dXWahhi51Zd~TT1~#q-=rg?ePetdXSvRm zmZuds84TWR9F{ZAgj#(S|7hlf?Xb$*Ncj;vsSWmOZm>lDlP$>{@YxwFT-b=vPbT8q z%ZEww)^2PjepK>mp0`+aSQV!EViOkcnu`nEljo7ouFkbyYX_pRcL4GqtFcQi|9CTN z@K3f|6q&$*q_-#%p3anzsB@EsrmtA1va4s%N44@stW>5J?7*n8IER>=nBR8nIk+{R zjg9F6+t-(wZve*e*syVIEb~jJhNI&X$UOo3;_dqE?Cb+hVOPt!kRGuU$#Y8e*wG?! zt*7o=`Vf5$PH${ae&4J>dbATWs|w|>!4k%7ADN+vrs@%2ki7cFc>;X~PHUrm0+&k> ziK_yU-NMW^bR@LD-a-tko9XQe8NIbtU%6mEU;@aF5e4z z)h+k-q;+YdzBc4ocxOy!d|)`26*qa5uim5t0Vhj8%*GH^C~mDE^8!b3mx_c{@}@no`$DX*YSZNk2Bhcl0q7yjPFf3J-HBi@!guuri_(@w}hp7 z{`Iml9|N1yZ~bj|M?CIAkY&DUi(gGg_6^$lz9pVPaa_A3yU>1B9js)!*p+8q3y;^s z$`-m%kA6-EwGH+~fnf&nHR(U-U9rIw4Z>5J_w8JCCe%mpAZYJ8Pqoq#@f7v4h%9?r z+HEZlgvOSY{`DJdy~5PfNRXu*zq!51Kt7+@_r2{usRP%k^1FGObrczs_UU8wrRMA2 zPHKWXL(?3v-a)6Sbxr}F=ST!SaV!A6t}2=izKQ($ zi%KROwmGSJ?cVZ_fowm**CjNcVuS&K(fyT*#}T-O+q?Oj>bx^qS^LQ79(iXvyQsZa z{pyWkgk#qu<=s18243FxeSKvK0uFRQp69(kAoI@4>z**L>h%H*Ht+>F*Jg~EuiuOX@5~bZl?*SadfhBk--K@?4MUblRB>A3c0{SHPYI zNM9en{^qZg`c+`y0i9?^!YU=IbK&^td|}MoBqBwId}rITtCM~{XFNK_rrgG z;J1Z4R?Ioq7*|bl+IPurU|=vo)l@(Hj+0ym*&n_N>C!~Jm|KJ)>__g{fffC$id1Wn z4V#_y7WuY+gnF-~c}X=7wuY0(c@pyS$z1~uxd4bNhzqKR^d)YK>8ldq^v_b|iFlvD zF-+G`EZo&im8AjUG3VC)zTWSzo_7CVl`GPW`cgIwr?sYO zymVr!Y4#2EbFwvFdXJ~z(4(>6B12~tTLgc9P~98?aBf|{L-N}=ueu51J2H_hj(Qv+ zyhq$J0}r_aEnxj>O)>0CGZ`uL0O{;b6R~-|LC-@_cKSUCw{KAVEnJH&gucbax=U%J zBvQM7wm4LiP*9)2JaFc$s1H@@H=XK$raR9SAx@=a8ibjNZWrfO5dP$Rwl@Ss_U~b> zo8;?q6Bhwtv0{pNEe^sC!*JP*255oDYJ%7Q4t=bETp@q>I``i~kwWNj?S#-%Kn%@6 z%wh7Ug*sst0M9=lTW-4Z68U3tT~u^5D{!um>(U4#0lCM14}#&{e~%SjQMaf_iw_AX zlmOe$0Qo)-x97iamd7OF`U(jbh+z_JkcO-88y*S4oPh6ycPf2?zMW1vJJDfh9!c(a z4Y5{ zMS;9g?lE8~O>}uxbzs8v4hZ0Y_uce{<$4a}S|(eADEc86SLD`Ab4X;Q z!Rq^euaZYd2rik+?p?vNU0lJ7)1rdllNj-W(|E2Ub_Hbzd=|hM;lgbJQM(e>Zy&5K%u0kNwVFb=#V=4_KgAtiYQ#w z{QUf%EiFEvcZh<>FXL92*CIuE&=pWoc@AkfA>lS84<6ivw2Q4y>*vb?Zs&Z!CI9N_ zkt;F@fYr(%1I#WFP(&3J7dK2yJaBV!t2th#D=aJ&Un_DtD9g`JgctA~ zj9aY&rJ$hq>(jO98OTnPAn zk}C_5qXucI)y)NTwR`So%ub=7!-``D0zlw7Rr{Mv#O<2zImWSzOIM_%g!b^URzI-{ z)|Zd>xcsfB=jNEKPDlA?=7;Zf)WEB>%^EjHeTUu?&VQIe7rQKYlY%GHoyG9!!~$+- zpS}ngi5QnrX%0`mc$Vb;g!Tf{*}aq+GLib5w+l$#`J52ioe~`F4E)ObU6IusS*7m= z-_lR0@p(ZgL1&}fscE(oIj=wqYE~+oS`XLZI|U-#uU?j2oACDb?S7mP6!J`1(Oplo z=#m~~>B%$G3QbARcmi_ay<+y`Ob{wZ4M3&>*fBNC2Ia^IfJhym-^CrwOMDY_)O${ zF$4l8?nY0>ERSpur)v}2CK zi>k=HT_Bh(cq?kAK55ad$=?opIm}37_c(nG<^R_G0zKEwI_~xrj{JpH(qmT*6UqCp z4nCM)Xa2GBHn*d;Y3imI3p>leW2-#({;Q*%Ms3HBR&KiRhsJy{x3u1ttKe8_t;|B{ zir7Ea*-khcIx9fV8u$FEVN!#65LwopQ#L9m_e71%G^IG@Z|_o>Pb;U|F{5AB)m`1c zuoDE#)LL^mZu8cZ+MFr9>Edk9%{_5;IKFM=*|1@==g66(lYh_SuAXP^*8e9BLqW&u zp*u|qtdUk{$2Lf2-l{CDovnZ-crU|1&;XBKw8)oxM7(%%{)#?6=MW3{H8@zvz<>rq zouKH09H}Olg1;f(?XjTiseSJ2^15V^FG#Re+9~8hc)|Et>`tO2P1me-Jv=;otE2O| zwpI{WJStY!V8ppoxn7{t+zZz9P;p9d(NHrn1;S08DC>Z=WdR&0?w+1FgoNHXIS?1e zC>j042MF=-Q5X_Ts)gb^NTDuroG+fP zEH^)+>!D-GV(R0Ii5nMwF?$C@Azi!m=}A}|;T(rf$9pcQq0;;`u;jVc{zZSQB&Fy2H z%j(y)^-aZeBAT*SYVUA5aeC&EHTm;4HocR+-iCUeyY!i3Z#2IYrDnNfNp$|^n%~PS z$O(MGPChOK_}&&>2>lA8d%Ak#Mv?Q8C1_mykw>(mA@X#o29Zj%w>NqK3AgsDtT4MA zrAAYwPV*f!$KA!Gk_lMF)8id|`_g5MO;Lrqx2UM7;K6NVYG-4Us#>H+!tWRg0vM>6 zOI*#tGCghaVmt zZ9&jmzSV;Cq{G644s)1`_6`n)VNfw%19#IrEJLua%*@X#r`d*bQgv^@wB(gOW21_} zCk`o<=9@dlOP}%eZ_!Z)j1zmfaQH`oU8?Gu9!a%j=nAID#^QHQc+md1W?cA0#+6eM`NqVFMpdttLB` z#6g`K_m2QODd&mhf3I)1=5CIG!05qUuRKz!+J6%C7(39H?pva^TcTR%{a>}NXH1O0 zsQj3d$^gKDuE9c*J&$po-FNeewmXbETjz~v#kS@ho@E?p`2clJo}BZekz_wOv^q@q zu>PfAz-3MW`E2TGY1u@^M5Q%lzhze6!S$Tm0oBA=hU$8oI_K{nMM>^>aM& z*vXEBPYkdDC3pvO3Pu$%lq^pWaHv@ph7`GYZ<-V_$P|CV+-&39pdpnJO<3$|X(4A| zxJ^e#r$`&txq+C+9w$UcV|=KtK66>A20>ltJLj$Kd-v|4K@k^PX+}(ZHdIN+6A%`L z56^MOjr5IknBGsPWjZNS7T$;Y+uvt8|pqPC5KcD|_*KTef|5NSFrqTB3)MpY7qi2S>sjqc9Y>{VP&XN_FzaMk4<+0PvgxFt?@~2IW-|lS8=2}(Kq+D+_`Col)o+S@ToMm$D;y*hAon9(9cciDQ0P(= z{NsGd-0gs)$gwk7CQCr=1Yo##t>y}ZN9`~8-thU<5WUfksZXgVs zWmkb6Hnw`b;IH97f0)henwh`syf|=A2ER(Wb)&(rOy^7cp>T(BRv2ALoQ$0c zH}pcnO@Btx>s}$r=f`E=q|l>SES4|f}7p$+M?m16s0_FQ+OL)$TM z$zE{E9}p;pakc!-BaiWp+BUy@qU(*hfx)9g8Oo3D)#ks-i_k%-%a|C^hn!OPOr-GA zO5pDq59)8-`eIRNp?#dYG~V8=5l+iM?T?MZR;w%%HO#FtkGV8DWZLyK;p4FSh(dkE z^^IazuZ0+aE|nn7u)_;kfcMcs_QC;yx^rOtX}TkV{uH@EfCdOD-qF<5jJKb@Ihdzu zj;#4_cYRzB?T(_8DV=?xKzct_cP(z4srHl=AQvzqES_?hPx37f=43fIn5Zi0O&mCa z@C9uc(vdpNCnqQUNVw(m&LFXuAZHC^;j5F?#Ky+Paj9RSiudvJ>mS`eK)K-(5Qu>W zr+9r{g*HrWC7#2u*S$;dqN+~k`*+{)yvJ&F=`VzgE)$rAGE*hEMga@-Fj<_ClXqUu z&g9KD1|~^J*sFtTjqP)N$@4dLk+JI?zi+1^Psoe84ebR%Ca(#7I~`NAoFY8={RxwY zI!cGUrM#K328vmC({ZGOzJ|Y*o$xF~n69hU|HH2-TYY>JKD=j9yFUPs<1?wV zjoH6snzeDp8P=zJY4@zNhk{nCOzylENZgA_jiSsMS2HxF%j}rouh>~5MlF-{{V&~R z7Ylv%Jy06+fuJfn^^pfN$GCrC2*VmCRM07Okp=pl1?I!Zr2 zb2IY$_YtNps#O!hM>WJBt4sI=Zo7Dw6V*pb>AWwo8&F->?QF9`7bNR+(yVUowhCH0^>fZeSy~slDMRi>i;!3dw>H*CS3pVcpDRi5bRkda0~VCnnPhGektP zt&ykt7k5$qPjxfv$_kjaGPnFteDR0<_R3Y&LPf8o@4^B=b;|XTh^aF%)k&hWDPis` z57k`P{;7Ki76R&odD&g^TuRMH`?mBE!HipyDe_Iv5xGklbogzzM+a zf0+}wJ|buM$U9CE|{SMGK5&`E(fM*a4#zP5;bm+P);r=uQJZOVNRnUx+!q5Y7%L`2UUqip0ML;+i z8z{uIAmjj(h}9kRyi)r!w(jkd;Ec)TC#8PD61X%Di`gNx>qHLqya9Uqu9dnSz3X{_je+U^%)&!u1q_ z%gKiI57q@C3IOz#fHPPxc7FsBY=zxMZc(6k>U4;VfEL)`Ncil(0z3sZdoz^!*5_gf z01AeN!1Q!#PzwPDjVB``Q$4nu7>UWvgr)vG-IL_Ln|Fbr-`qf#1|^dqRvwQ=yMx0W z1=naHd9?x-!E4n#Zpz)@f8=INGP0 znw8Cf%J=-B*rkHAT76${tlM<%JMq)%?RXXklUqy-ZZEUiW7bg7vT17n)dIXTtu6U6 zT8YEW5*$_A{%-L&sbMl8HT?U%8h;mV=6y69<8W50jS)A17~l^`gYf+G9e00>loJI? zzn2pD3A<e1C%V^1mB)n z-)E$a^X^rRRC}wV^&Ez5lpUK5PS#f$gil2_+N@!e_qBE_P=Qm)~cI0 zt>hW|qM2WYzwG zf$0z^&ogE`nB4*q@2WrEPr0lhInO>TZdVQ_H166E+{D0ed`kpb^^o_`4w=McuVh5l zHy{YTNLcLs590zls#C5X?krLFWf+uh>hz{j-8!LvrY-8{fr^`DeT)KAoN0)@a0ojaf+YGm6hNeF1EifI`k*59%|I@mg! zHtl5kyGekHD)A;h8^15dYhx$JXmJkk0@~0#GrQ|e_&yHUF$jl&?&8C65|3xKhXJ*QPQ0*L-u*) z#fuj|0fqqNBLV>3sl~>W<#(d#}iCxqSTCGnY(UQ5!D^_mSZr3Y|i}sx&7Hwl%5( zGX7B=RxU2W4*P( z=ijzJLjy*WEq#b2ar5r(+>MB$m1)N5jRqZkT``4-v|V#l0x&&cS?k5IPv%#n>T4EV zK&SHDYG__^H|FM-1d2gjHnfS|s)i|PE<8EXv2g>(MWXB@YfvvHH-VphKNyF!bjHE$a8wHSp( zmM#v$YY@T@p?shmVwW)VZC2L}DElw}E`*L1=;?gAbsJ)*^ST|aEG;3PT@MoNuU@;R zWH6DuESsq~=U8hP7P)w1xY~&mb|RU9;=G};5%`eXr)OsfV*&$(US%S%=-RD&%&@&e zj;YAXl;9oAICzVTEoa+4%ZCJjwL-8W|?y>_4TWy!yz zo-mBP2g{?)unc$s!2cBOCzLv7q7Optl5553C$9WiSZD@APr36EEA$5l1PjSIQL{|x zU%z7a8@l$d~OgE6C?Rd2Le%1QGhP>)Vf`W`(;nRu67Iq z<=zLlsPDlsE8mvDW%23HpSLC^Cd1`c)R6216rLC7r2S`<+Xa|ZJTeTB$C0}$^2~s+ zr`F=YS^haX%?ZV*f2lSDi4JMq-hh`4C?Dz-vwMKjFgvfjH_0&}>NWH(sK*8FP=N-| z-rgP?EMf-&0DOP?bQuE!gO;Vxw+Qm2;$4poOU)*H{rx+xwB5tQV>TP-0-O@z%{x0g zA+q)}?EA1Y8zRFBkSQQ`w;7p)bwPat=*kd04q0V(B_?<5=lJ!S?mUI#%rNrJyI5u! zN~s%E*^69}Gm6rR^J2-~VIHii@hox6N!=)ODGzeJ;zZ}fGqBPyO>j@4IZ3))=dO^q z*fS{0pyVH_tn&B24OZTq25;Iyh*rukEbPrPdH0S2@_A?V6goBR7i0Z&D|t1zga zcg6GHrez}KN3N9c9srCoaBZcwx3`(u`pm^uw&R=WHiG_MuH;C-G+Rsgz(& zoG%&DNP$uoO65Z1Ax211_%?C?rjS=^YM1}4tw}+0gikZjpCnxG(V&Y$1C~Iw!uoNg z-Nt;AKk@MBXfF^Po_8MjfF9&a+3xgr2gQ{_P_3G816;L^!9|-pL_C^Z0@E{xq5N+^?Wr3|1*fYP7XLF zcXxNd{XdnLb3rAKO zTGyzpwbd8G*zS<>y+{%DUmP!I19v!bWdW?0gI$w8FHh9C@WN(8Roks}z@Ez%xd7V- zkJ&g_aJv5`2O!7mA=LgQY8`M*Xulici{~g`@vvJ`5y83#$w7bY1(8*pjb8c}GKlm}vkI&k3F>0KbBug{*IyFLQ0R`7j3u`8$q@?5`&{2c7;7YyE4gy_;bSM)t5(mTl0WU*?wT!1$ zS6BDaTfjWuA35P`q6QlquYG_u)fhqI=U{X?HZmdC@uVm}9}R>0m~tbiDxCtq&{_tT zd0;;*L=-87J-~T>6VwRE#v+K=3-&ZP{RDJrK|Mm)ld1yR21AtDW?2zT{3BHkYzTsc z22QLbaDMTDao2H(fPkPGMj1>!9q`yi#>5!Awsmw2@a?b7&Do%Mw6(RL1Xc;Qml%O+ z0$q0cnq5z+)5b2H@sU%eB5lqFhR0RKoRZR*$VgdP*+g?(gmt$dgZcV&s?RU@ z;KiguFhBU_ zV_-a_q49>k5RBjLVD%&9vuDPolKcakvh-!;Lg6105z!3l6N~rnyN@U8>MjF-2pkZX z)jVceT3YJh5%bXZweF-V0GG_c)1Yd%S3<281UVRJz+irE1Fjvr&K?rTKGv1|=kgYc zBC-@jN-)~m-rmsEbRBkc0Wz0<2Cy#I*VliB6hjD8B%jZm+XdxO-9-{4rT&q(25M*O(= zRQaqxyw58x+?LgrONh@BP90CVP5lD-=92;!7#ycZ z3kmS;?`MDC1x>@|=H?$r(9G?w$y^3GhQ>FUHW?_dXjOo6|1isc@U==z-6OjPndj)_ zw4Q1?X;0s5zN)S&Y^=NY#Ky1{s3byQVQ;^wu)YbU8Z2#5;61t0cKzyAs6v#c%@4*j za6ozime`-Lu~;i!7C9g*8fn95(Hy3&NZeo2Ty<4d>$sh+zdHJsJ(L^Xh2_>uI8c$m z!%uwD3aiEGoD%o!@0eHe_V>8$bR)bbHw`;k77`viZw84 z4SBr59OZkl!|yE1wX@%;qq!?U-%h`2`ls#U^0^pD<@@ZU7C`?#{A#p2M~jo{SgN6^ zPBg0qx?E^rM28!hp`l7CmYr4_`+_y<_SB1-m#{w_e`KC&C+4f>*F2RGup4mk?QULa z!#vgcM~k5T32K9X0XqEcPP>iGqpXBJ;0w@zq1hft&@^mKAaf#B@W|9SQ$BrtHh95# zkL*=@5}g-_X)pa_X-~nPZM@h}zPGCk%4{{`v0c)>BUI;wVNJpvZvWJ%)J}{2+oR2= z)jQnpkI>Sn(tx_?05GTDCL>D;RlUH*BpBEv?GFVmy5`vtJ{-hl>~YpePzvzn%X~p3 zr>B1`OB2SVUmKACM2e7`#u|A&CC^kOrOsXMMhNK4ObQx{=>$%*3|&D}z*PV$sY-*z zr+EyrHqc=3Gh0#!AxdVMC5nY3&`!d@D11yq^9PK>$ioL#enl-Tmt@TxWd+&I#q3}E z86^wPKw}59?-UYed1{r0Txt9&H+N|4`$u>Fp;e;M(C9opchZbQk*2$5Ea#5b2yJry zdKIHu^u*w?Or>B)NvJWRRL&~PTMm_xl%Y6`d26|C898RmxVBt6Y(QBO}r&HlZ<{$>pk56s$>7(B3BUUby-?i+R;T{;U#ai9q& zr=+_E(s@x^qLvW$_;)LrIg*pyM8vmm2hlR!q!->3G&}BO*A1Ti23jiJbJ$I%* zYa5e#%3Z3vh6CxnT|=K0cN;tO^PHQ?nrn1hJ0H_Ky541a;`D51Y_LzA{eq!lF>Y4S z2h{949h#{8`MtUb`K2-Q#;$>H4?+mf+uzS0#CLhb5G;ZryZlRJmd*kcYq`EXZVEdNL_hW#YeS|Bf719?R! zWqf^I|0gz`&d^Nr^mC3d&91Xn>SqbvY0|{KR=uh=@9?j1-2OAhS%aW1AI^1B7Q3Re zRp>BnpdA2bxOYfMNWt^Ff!qi$g7^@C%@Wq-fu}c^6aXkr+O3-|*z1m1FDNOYtB&v6!@5XyAljHy+GFqX<~}xD6^RkJ^8%Cp*T1}pLP%#aDY4c zDn`ApF6Y{<1kK+7q_(4<9L$L!fCeBaed8tEgY$z|*|C!4azp{6mG-SU?5*A2d)sBp3t16l(ZDSB5e7HJM?Nfdir3=t>0F5QYVze#n% zvVVCdx4N`>k4al+8x`>UGK2QP69jf4K`}{3Yt0IR+wwbZLx+qKwG+l4y3l|q34hRV zX5=Wq($Q*N^$;dKav2adG%O0g#KedtcZ_R!s9%Gwm^SeCmVuAA_s{0$=3y;#a>R*n zy{x?I@6F;Pg~A@%nMVLbaoEx%V}!6HVpqjzz)7J(51MFGoy!iPcs}*$7kD5#-iV0+ zM)~CXfHw%_0pnrLFng{VqrSoyFwu~Cvy)c8bCN5ooRD5(Rd=9H>LizsK%L0ErCSI4hW#eD`Q3p7|)4U{VJ&H2yn zftzEMuNIfax&0zA%dI=*C056|?oydkiKVfUkx7Kvo9Qt2ytm2BT5z9o#A^2j{%d4I z%h_?O-BDFg4Y|&KTYaprEQ!hO&BYg=TIpH)af^=aE0qGnspUntzwZxY2L^zytuvlq z0>V!GANXPi?~GIL7Y4_sul}El1%w`kM;(G8t|m$EnvWD-^AJipdZ({%eCGOpgpvPO z0-1GkZ~y-#!bBa|Ls3!%8B<(bTz?i9U%Y+$58#13HvbP{XI>_x0J|QT1|~HRq)Vmt zq+=1#ze@D!n3#xzEE5P;$S)}o$yDrxm?;odTDQWMG}w3VCXEcWw`bRNW+k~Old6{r zC&|)xI~Nx1_%_BhX2>}i4!@_ockfGv+zf8K)B275b=dQl?eXIft@?f0yvc=EujuPq z42hU(tHNfQFRF>Z9d2oB%R8)zVCj8NVl~N&-h1?Em;W{~vkWn937_-@CZ5~f zEJ^+WA89j~?@?ExnY0m)if zr;pOQbd0oHW%OMyza1Mfwcc4wGqh7Z&iOID@a+dLw;ecXuyaduVObgCQ`S@lQ=}S- zL6QMYHf6AQ>e)iyWQB=cAAZz*=CErYZ~X{&^e-E#^&iq^nw;3o)eBBs91c5Lc;@{O zU>^~X@q$OP_tq^rpTENp#UatDU<=jUCA;-;a?s)dutp5^Wg~w-`|;;bpJcNJ0M{(A z$=bh|1x{dbxZn{qs@ZBKf2riJUw=aXyS1}3v(qJrNc#|hC@_Z4ZEZOaQ9Kl>Q_ze# zA5M9J=r14AeVm=0tphkg7K*TvARTT2e}?^(2f^Z4852;EJq>s9@jYNxfJV$@wD=}8 zWr&mza-6vU+W;|{8tw>XN%Pm4<*~FIJ!M$XvB0KozuMZqWMt3)Y}pAop5aI#C8CHp zJ3ytwDW3vXatP5OttfN7J&s|lAYu6kSmA$a>iam1FQ()LWYYtfX-Cou9U5OtG z`#U#p68VwUk_pys9G&iv?yjiVm%Vg$zAhkug3WQ8BQ*EFNks6xE)Qr% zzx%5>y|i;n)Ni@MO_t_V!P6TN+)o>Kd;GmG4X%KHD$2jXlbN7zoNPj&*aW-<&}S;`Gyd&sa389{0*aJn-V{Jhs~AQ)%FH zVlF1z4Nz{H*ecgdXPtkcH6~kvvEWJ;ttCPkj@y~Rge;kjZXkAC_A8>Pr$A~$69tn$pD(&z; z1;Z8%5YxmM9zq%obmB_7;p;?#u6)p`=)sPxpla0C*B68IA60PbfZH0Am;?5BH8W_{ zD7W;0=B)>!g`n6(%p4#)ehyUt6dgE$Js^GWps4x3iq(rZ=4POcfNh4c@81`H`t(EE zI<%y)`>mXIb!yMT56{w;ZUOq>prBtMkYnE}0sIcMjsPCDfeYZak-39TZ&?>@-{IDc zAI{uiloA!Vgu~dpytnRi=KiX%`S_zelaRxN6V!L-3bukOx#Lm_AXEK6hh$?w3L_R)EM3CfWnj=h7N?sApu1 zOnDQm5cL$du6m;44HrGI( zb;XVFRgy71K{@J3Qe38DBO$2Q{$JxQuB{%Go^>uNs!QVt(?jUjWQNi7H29mBvMNA@@s7)npGT(+S==`5t8VS} z5&=46b9=jYbP)Vj5Qx+P!VDy`GsOd+7v4^;!DQyW3L7g*a1Mm`oU_#gwW~5X{Y>LHkw_Ag{ zx8-Gvg3Q2^Z-$N!hPpW>vglo2!BLYiG3n=+GN|<@+nVB2OHGanW#4Wp*&&g&9x z5vuU~^LFj0-y62Wv+WH~AyO>s;ib6VSDH>K0pTHZn{C6IO*pQPV}V!%#lkB$6Ml(D z;jA?$_8oDox;ey&P^eR+QEzwjnEwWPdmuCmI;|uJifRgY`q7Wqe$^QY46@<;Qaf{@ zx7(G%!pW{*c=7JDwp*l-^Yiy*c1Sa_FT6$$E@H6v5*_uu2xbkABw<$Fd1b2`&!ltH z=-$L^JBl8)7nJ`tJ6klq0Re~}y6SWrAP^D7D_Ay5 z@h1@eNzTrW53*VlQ&Xr`EFL>wl7Y<;)95#UHd`}kRCxowJ6JkB$3TxUWJ^aw)7=mh z9_|UETH3Jv(GO_sMvFDiG{}xg;Qrx*8GW+t*RQ08V=yiHeEIT-h2<_MXBkdgXcMBZ z708n0=05tCHvd*v*E1)_qjQ6i(Ib$JisGF?>*e7wH)j+Ne4&43r7hX;nDLOQmfT^4 z+p~{qxMFH*jLOP-8H)2fHiH>js-DVWYp@DtzEC~h)VOnpvaDr9_2ui=JU%{AKgySK zA7o-Z*ZI_YA~~-4#;d}_+3C^FL5KeDaB|Qr_Y7yUJmj+J_}hEL7HD>B+A0G*DAvixM;2w$H_3D z`N6^fd!A7Zr+T*^7<_TNX+A{MoFCM60PYH-xHz-Vb7-h0x5VYuf)?fqA108^>gVRS z5al-rIw5`It*M#6iYXKDbD}FksjOWQ1pqKeRo~wOS9&T)cB9vn^^|u_dPB(0&!4Sm z|L}(Buio;@-uDkSdbR#zH;dkYqmEqUyt_eN*<|3?w+SBsMJw^QQo0fap${VWA_J_~qH`4Dr3zYed z)P=LDM;7Q@FWZ=C0(-J(fd8xQIvmmB2T3{li^Gu{ya^6Y3tOiPT{DT+eZ5j|H?=@O^rBPe@6Sg@X-qYTpFrLwV9RSxp6DyNtFES|ZEh~( z=*R2`{-m6_ue{6&2!Ry^X@AcP6lg4v(n^$Hz;EipEcVd#|nCBb;AZc^iJu z#q}yUDoVz-R8UV3|KO`1Yz|^;d+*)5*`TPXJkF7X&~LSkk|Gbvs$5uSNdzEh`9{fo zQc|Pnnu?0^>WZdrf{qAwDmhpGxcfUT5($f82V1KOCN4KpuY9cec=hsx5mgEV$(XMMwpH2b!J#7_QecU^5QzB`aWYa3^lNK*^%xzFYxaVhdEUHQ94`N z7tFQYV9-BGoTGHjd$O=5Hzcl;KlJvx({0B6qGp$}CpKgQqA!Qoxao+*Qm1YaR*hG^ z>UWdNT)xlB5zWOp6{Awi!bK{_>jK`|cY1oPkX?>=gzmIBU{Zr^F&KenLdXI^WMY5F_Jrh8|81UWu8 z57Z$44*M{h2wa?60deP5WYwbn72)vtf#7Bz5F>LD1~nFxc#!~i4G4!tEouS@4Zukg z3Y7qxqC(;LOi>X}r^$~TWVzklvTMprh{PAvM3S*wLHthpU*qGYjf`l4f7OA+VK8V! zIHX3W++ex{dMu+qYyrOq26>Vv=zAEPkC=IZuq?>W z|L4gQ#q{-}E&&PnR!;NDS@<=g5FzC!J(G_wY*56fe>3{I$$xF@An@%%Cm}2AwzqFZ zTOdd}IMRgtR*hMmHnWyIR<^6l*SJxPBV)C^Y=#;gzwl4H%UTq4P@bK_>oRs8Z zvn+6uO)Mv8SMI>}S*O{|YE6xY-;qygisdG`uuXV;NGE{WKD7igwyqpOWsjm@(Se79 zCG&&BoNmaz-zaTeeYhAx_NaY z5dDCmCjxPY#15EESRVw#j&T@y)%*9|PPcAIOH2Pp_N(~YhGC_|k;FsjBQ!JMopekv zMUi%FPE`cv)*OE)h~dOKuKiJT6N3ke(4K}y-M6Nu(hHrWRXfsR$NQ!kJJtRDB=?vE zI``L9;67Zraf8m;`E?X~))iM*nF~#_nWf&j6|dvrsKmr|8oXavdj&Un?e0|@P|_|l z7U`2wJe+32!00bFUa0F$<#*VqegtFrsq&zo*gp8Y_1mn+A->ZBliO=;)O5Bp&ZMqR zbaPPsgxhhK(#|y(~T2LgHm=l7p-B68cu5}P?<9>f4e!cGQsf`P)wjX$5h6iTqX)<l z%Y3bXA_VTS0jOjLNC~H<;gRc8?6=QiKxVlIBtv}P+%;)k9`L~=iBz5Ez_@kG0aO<) zot^WrlXIxgxmv^EVCm~y$q9?XZZ<=(y%s#E$}MI*3!Z0UNBf-r_`#*)C2vT|-lI8D zVsfNeSab_?K{oI^@+Y`w78dL)O>h3v!Tq-QEL-1|vyfgFfE+Er9Jg^aer5p5SUkK8~QNVi%|CKQxFt@USiR@l|GM%n?S{D2`kW=(r!NZ!1u z%Eg^SE~sfIcse|6t*za=tK5iibiFoOeYod4wnmx%lo-mMX;9_IH{cqMl2dv6mun>bNsS#pwho`)aUc@^wc!G+g zrlzLpPD=09t5<*f%d=0fjA3h{qANvYo<6;C`7#e7FDCN2ge`y^3>O>XUL19~S+@=t zu_LY!1_pn4TxbJ`5sUUWQiArWmKHfEf{-u?fPWXA_W_jK8Bo#O0`XsOm4nqI5t{G3 ze@DdMPxTN_)dn3P=mPEl%m#|5?`KeP>AHJ)6~X#sSsg0*SM@-PC;%WLfelX1`FJTE zE*F~X#hD%KpB}|!@etyvSuI18c|vlW;82lwh!(h>IWoTf@d2U$n3=!ASzpR`+%tka zqMzU^K_ZR(NK%oP3LhbgJ&*^Y0k}N;!{QMdz$aDb$18wye6gE!XpSWJz5i4h2|z$z z9{&TPwUOu*m_;9x2hPl^wyx(@ZDSxY4zS(rY$Ji@6U)%QhcK7o@JSA2K_X+g;CR79 zq~Z)}?f@&oiU+!|``q$!6D|LZ@R;_I&s3~}{Nqt#jSnC?g9Bg!)t)$D=OEYM(02d( zk@z#rHKt?UZZi8`b{F6=Z9~fMYWLxN@vpZ((3~`}q8VYWPLaO*so#R+3NQ$G`-HSsyN@1nyEDDbeDC!n3}!^Nxgs z#3K=Eqx&N%njlufWgj+X?y*W<8qF{+F;e$lre_rhz8fP7Zh?BHytBGrk8c>hMSWn= z44f)kficV{*9KcM`oLIQmfAKp*k7rIZ9j1XB)xqh7z|q`9#%72c*?v5x5#M3`n>}O zNYnw@${xI=UlG4B`2ul&z<|Q<3B|@oN{JZ&>6mQ^lf%VdzDxq831qi;8)bET=P0%` zZX7I7_kSQBw$oSZn(QwY(>zG0NWiK;M6 Knto?1og?!zYvmcihH*nU%nbsg>_58 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 VBl5igt&IGegs8Mgy3n&X{|lSwr$+z) literal 0 HcmV?d00001 diff --git a/examples/http-s3-crypto.png b/examples/http-s3-crypto.png new file mode 100644 index 0000000000000000000000000000000000000000..b452cf454164ddb700a6e525b9e57cfbbf581720 GIT binary patch literal 131254 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� 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 z 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 ZU%TwsQw!egD2=1rf%77G@_&hB{0o>2xL^PP literal 0 HcmV?d00001 diff --git a/examples/libblkio-io_uring.png b/examples/libblkio-io_uring.png new file mode 100644 index 0000000000000000000000000000000000000000..1bc6cc98742ceca3a95c400e903b3beec3006988 GIT binary patch literal 57227 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

Epl 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 NdAjjtSc{Mw+;)W6(Z(1TsE;l=dGlxTn7-+IJ-4 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

XoeyBVbB>C5& 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

j27040@77GeA8I$MhE6nx(qcOy@EF40%4R=~5K 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 Hl+XkK9M4}9 literal 0 HcmV?d00001 diff --git a/examples/uring-cmd-ng.png b/examples/uring-cmd-ng.png new file mode 100644 index 0000000000000000000000000000000000000000..cd2ff1624939ca61af50e5b468b95029656e3306 GIT binary patch literal 83761 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

WyOGdHb`T-DI}stb21;`M8l zG4SK_jfVQ;*`N9&X>t@|il5(+l|H9&D5EET(CDAX!lUj{@(oIeg)I`29~b!(Q)mS3 zlq3snY}D-He`0y^YZt1QbT6{yD^iW?OGpI4mzATCm6&($cAT7);JS838y4zyta^EQ z`w;H7b>%|o&W3XOqC&~TlcM4i5TGY2s`~nM^_vSX?if&eT_bat>W?A1DH|147{z0z zJZIR_(+|~pzT!234=dckUA%?LsIdGcDaPBl}hhp>od)_D}IG>P?5EKR(JUm+Gxfo?A>*L1N-P zs68+28PsptP0un37CFoJE+}vk_z)04<0CxSt3A-irmHLAva2MGZDn=w*7}&ybdz#p z<3w2Oc<_Vjk%PpdBbN)POP?t&dZ36|Q=TBH?V>s&tHsgi zXmBe=!E%<7VH9W{%I6WH5_NKlSfQ!3gMN`^%50`Qxkp`mN3`LKnDLe%Nl>F@oWres zsn4tK5elbUge^YdoPl9F`E|?nlfx;vEmK;*k9I*^(Y8>p_^iuCQPKU1Pd^Jr~-FX+8BJ#9UZWw~@s z!Ik$YEi8x0dbH%&&22oLOVPBirHd&3GCC!W1ZBuO-f_=O4f(tBuv<=9Tr;aOi4e?H zL)ov%ipNG%t(IuKss692J!`m|zUi{lBRBgy7Ihoc-37=lQYnaBS|3;Zj*E{7RJ&OVRdI+I>ysiKJw2>B-2=-KD`Y zt#@~eWr=nB?r#H_Tx{~$bN#i6(_b^9aS#xdhdxf#3S-8ZhrO4h2wu&WcYF$sgjLaZ zA@X6{$LszC1QZlcIF|bT)5HLmiihI8m39Hz6z_K->sELOdSBJ+GShoch)BDEZcmWU z52{mk#Kp-g)1w?7o;UcshXgxWg@drs{ZnIv=;%La3Fu^Ux)r88v*h$vY4)Ulc&rDL z=GAd(ieRyV24cYhozIm%Hbw1}ME!-SjESjS+O641R>@_(GJ<~Z4_^7-PqQ(oqXuqv0Ct#|c=Vx%!8s4JxJdP*~&XU9&n zc;i3P+Yk5F#Y=Cj3-!d4qm&VcluQc%%t5FbdaqOgBK?{ZhJVT(v(?37%AI2k zq4n#ZY+kxY@~(a>>P|>#d(Na__fSfGFV1C46zuA%9Jjf7ctDG=4KQcvsGcWRp{3+u zbsD-k>N$@$aF8XPv^^YMh&;#nxZ{6f@auUiE{0L|JYeTu#-KvN!ykiiF@PPR0|}G3 zO%US-?756>i9NR7k4qj(E%a?Z;(k?!J9T@EE3TNQ!4D3H(4MPuJ3Rt(H84zrj-bu*fJFb|#1*(N zPhI;s267}-YF&OPFJCO6cL{V^821$no5($vvHsItK&BCSIljXwA zcw=`l&Md;{K9ocas8icI0jhEg<@6J9UXHm^9=3C zjxMHjCZw0cu){cARq@(R#gnczDebpoj$?#O2E{)MGxVifq<>C-9TtC=b9-lTKwo`> z7uV;8E$lI&;3lLKcNrZpM2^HdOF<(Y{P_i zO%nr`0t+G^$&mNvp43>at~pU}H`99aBE7EVlRfIUT?((!RvOZT40>-n@bd0kL(G(x z;wIG(K9%^-vABq=I!C&d_U;Z+&mJxEQuZ!xLfqvtWfDO+f$Hbylo1i(=b?p z-t_+dzQ3Oz1-nsjhG04p0!}>dU%MB}LkmV834FNT^Z{QW@#jU%zc6sIOb9rWdBjGo zUAx}i#UAiedopUew`?~Q>ZsI?jesy-I09<9x|p@R{`cJZXpAg^AW1^byze%FN4( z1P>xMYQ@fAp9S}ao_^W$6rz%z$@%6tKhf*DKQpdGqNIM$&Gy&c1-Yg-_5uKg1&XFU zcmx5^RZd@;o^8w$fmr>}H4n_((rz$b*K5M9sdazhIuW{ZBc=9uqcS`=ys~TU(>*PH z?D-Hi`s8Ip-R(lW2`-1z&0T%DE;e>H=i7T%?XKWj;VyaP2W}Mp_BNvoG`;=P`A5xz zaE$Ia0WGZ8w(4m6TXUxjGpgMxxxXPM4H8tf1 zuAqSG27S$mx;lOsMS$hn@Gut)BB-f_(l^nK4+P>waI(e~v|wssCIXV#C1XMLzoYqb zr$`-OL{nT;q_prQEUXmfc^58Wh)2=X){rBqsi|k0DBWe{AHzHlCI$+wTP?J3XW(VN zmSMyg27PRhsuyM;bccCZhg(K!sy*-_0J3~f=8DYk&iR{L$Ypcjt?`VED5{)hY*29F4|* zrKUzfN$I{e#>E?#o0+*hH&^1YsRb#-P~X|vIUub;pOu(2HJSa~qr&Bq3fE%}TYmbxcc($c)!y0~1|Wqy zGE8AFQhT8zki-bgDsZC#Ri8c*g;otcy=PeIX=z`K`7EdY@^1oB^|V007j=KuJDX>N z7O)Lm;8udr^>aV61bJV|M>@@Y=`vtQ+q+c)V(r*huH@(C4V7Ed2DK!S!00mVfyyOJ zTo>CQ5cW6n${(LM1j>Z2p58>Q`y^c9X7aTUa3TPYlkSkN_nUe<~>ozMv*x zRIOlRW?qK?5z@J!RAg{HtnHrHXYwcV)C8c2+GbV&8|C`lXGJdLnT#eTCeW_~dLD47 z_^OW53&K=|{|wDK%B<)85)XE z*5$zEgK?)kxPyb3Fg_MAH1NQkgOaiqco&2@R>kAM-vXt45Xq^8{{te32~vs*3Lr?n z4Nf)S6a_-;v)QTdXvHr z7$CfL3&!tUC=|>~2SIAQUm94i^BqyNgJpn+`1MPpyb%Hq0bsg1!+!PWYB@Vy>eJ^JwNk&Rzd zG#7>S#l6eHgTa{``e7DpbzGN9>b}GZ&WtoY`AoU~(5;b~)r_}%IH1YY=kwRk{&+6j zBW63p<@%65O~h7;F7@tVtdmbTA1JTRTh30S?};3bv)b`U6LDq`(12v934F=8dVc)S z0T1Yoy@Y|@Kx3C;VKgRT1SUc`s-wL8R{+}PIL-gy^myO@^JkOXX*w+Q)RV@oeA_2p zZKFRt7h{+Bhh6~}Lnr1buZVi+*&O5VTcUV@Ri;(9+3xwx*|b|W5Ns~-DJ&Z<3j6zT z^s}(*keZQI{;Y`FjqEih5yCMq&Md>W^mMw0;$431eTe>aI3Ij=Xo`!l6 z=8f*&2=O~!Kqkc*^2uQ?Zf&)0ZMAK?uDbO9D&veDbJ#ekJKkG2Has~s;Kqnynb35R zmXm(Oe3)%_YmxFcimhlmQ!)g2B?iER0`P$>xez&e-pu}!+KkF8kC*}do`Dq9`NUZP zuFo>+EOU7)#IP(24w`iIX{~7&pMMG(Ffz&op4_8HR$$DL;UpCwS@)+&Q&v89`1~w3 zbd;~8b}aRLe#f0I=`={-)&@waidt`i}V;`u9AJjF#p|9Pm|Z(zLwUvWMDZ+3U+jaXfPe^0|BQbB2_E+ZIZ zl#4H4W@>M&Sq~L<{3-vg#IE$2^qTzIJLao{ajJZr z2iL%g(S>Dk&0)9fs@t~J@KyDjW-muh6k5~^_Jyx50VCTqw8TUssno<&zKfd>I^d3PA zM$SpS87vc3=dywblPEHOcB>vv3+FI2TZqP9r0DA{{>v8LAZeA$n_|2rph zSV!j&lbJwtp7cSZbuj)FjQ{AHi$_s|%AiU`S83}&O}C+bURr(UO@xIk zuuc`X6?e8@W-JwLllI;vnKPJkpxzcq&I?@1J04ks)0vKbSR1)Q#yj(~ev|Q{%H73* z=XTbr5~>0srh?ffcjbH+4HqA{G1#IrFm(2bz%J0IL`JR3;W_~U7##uSkjF~rx#Uk7 zb2u}5*vopRy&+K|As!s7H*3Szq1~5q&wJ39nKwwE<&|);r&8V5UAyyZ1xt>r&yN|68PR3&MCeoiD zKkK8WZAf&RI~#_h)D5$H3|lI}>ap(AExy?ol-2E3UgsXqsfS6#WiuZSG&aA-r&5R_ zRJe6RU}mX-&@Y(-DA@o@!Xtb96f2>35l$U|d-MA8`SWLBFLD5+c(Qg9G+_E!`f=d& z=I}h9n1P3}I`Swo%U!p4cHq>pwySb;=2@A!gNL>Uth;B4tCji8Qz}BrXCkF2Wzu;= zRi-kPL0MW={WlF7mWt7l?;}SVm4@Fwb21d$Oq(WeYD#HblbqyRuXc3KU5|NW@o8nu zy?mN(%67Qbs8y%3T7Op!0&DwJyXYkvQBqbP)+xt`NaH3OjlN zpPsmssD*GB)J(70$aq>hAB3YAq%u7{Ycud~-8>FqMIWqE7UsHA7NfYIesr%2^8`A^6`|`3}*@#UQ#pk-kFz6z07Pb1ND5X_L(hiA zl%Eazjqo_aMQe?|GkxIrAh|yo!{kyfuNn=CW>Wn$799k>DQMROj(&`bnM{gP^V4|; zZL3ADnM5!DB*qu-nO{G05p5d|MxGiT1$#ZnsTgH@B3S*%vA2!$SnPOG zx`xDOasCsoru!|!w7Owi?th|t!LX24fME>=6|M6agBu_yvlrhj zYUK|>hp*sz?so&~e_#RHk|e|BPZV3KW*lp4w}_|lO8%;B=4(`()rouP3Pm+^p#}#A zT0>snM8!~$l0E^wHr%eE;_ChzDgWL(Sfx60*)g|wuPm8S!sN3^arJ)!UEGSs3T$kq zs`AXEKzSv=>w*8^EuRJ|>Fv9n;Cd{ao z#y+}Jjr6jZx11bH47;h+6fJ-8SZ0=#vo9VI7&Fd-Dj@z>j_{U6iNM7WA4+Z_-bUY9N#8 zGk29;vE;#V%0NZZeQcNlvn{N0v_=&ijX{e9hz%T3*#M=54ld|z!e0Q?2(RSi2E9VC zFxdP>o-FX^&HMAz>fi5@q%7Tx!H#(u_Ylebpam>TxaIUO2t1Vh13h#^5B~r47JSHy z3kWRa41@vQrm*V*ir3@^=3lq+l5vJTId67Z+C9xXjwFd}<^38gL{%>5uAt^y%Z zQ7$breY8?}-7u$@=JCsTiXj0kN9p~kt`$DXQ(L;IIMqQ}{T$=9o^jorV-@K8;ZU!`I-1xz1!J-vS_jnoAHcKT?F*B^vh6X%2E(`%8NuHk(8_&=zUqZeO0~Mxa0p+!DAtvb5;}PI3gCqUH5<6tEale zY%&NkT(7jTf6ZdmpwU)W;0j4(^w}`LBW#{^)}C34rWd8K{kNK54tw61Qk$8;M`=L= zU8_g$%`y9Z0k&jxPxMfq;tfC1m4hKdr`V(An@F9yyvv_f2oXsJ07vb=4MTv9!xLy2 z5u)IHxiVC@zi*x{x$Jeu$+pNJSkyz8&4e>TDLW`zI_t@}#xwrHSWH_+`-b(6yA%Fc zc2{V;xHBBs7E?t{aO`%<7WhLwV`X_iiu*C#XTG1#6fohe_)3+ah%c=sowt>*b-cBD zL-_Ym;WF!|M+7M}WqTIMM}Mtg-@nfc&)ht_Ys4+()S(MKg@9Sf@nT}!m~(}UYjg>U zE0T*M)~#%~9fCV@{oM}hs{Qah20=d|{+Mq(kS)>&K=zLnHT%#)6AP>(c6&>iS9qv%Qao zj>Z@(l#y8mx$6m+vAr4F&$y3Q#tJ!}aopIG_)tqWX;KOc4*LAuS==1Hayy?9PtjAU zwA3GCLITbAqojKX2|d}f2J?V^3;FK~l88d>j@RcE)>k9YUoV1bNx_rfb5@IN75>@& zMGZntKcJQ_d|ap~#bBz#I^5?#)iCj6Lbi!5DvnX1wA!!mUExk~gx_tX7xOQAr&!Z^ zork8MO*s6 zK@Dl8NH%Pz?Z<*lDyk^r@0044N=#2k@8}C%)%d~E%G?z;eg&zcG4@RXPg+V%Bggn< z^+0c5&~cy0Xj)Q5QdUa4Y}1afq3(P_uc3{=2d^Tw+(hzuL(#*bBkDPt*!h_KmDvZT zH${bf{Fol~vK%{|3|=E?W$+k4hkTJ+b@CEfQ(wM151Z zAP!LA3k{X8{Cyka-jkw|lk?7TrZ-A(Rwl$3uOK?mE%UTgP};*6=$x-s(aGfRvq=8+ zDfIoohl#sb$iM46{0D#6d98}a`BWe8pBh$}Oy?A--nH|?7gbY}%P`NVRkl(1f9E6o zPW(O{I-H+K0CT{1aB4LlW`}AU_#p15;;5CZqP;3b9e8nn z0=Z1M!ccM3AP24KfS!@FqGPaZ*pHOP(1BnxH+NR{_r!DLcC$Z2B*r9A3^C|ETkSw- z$s`b!F5ydDGuD!5oO!0Q`*%-i1D$IpK!4Q%$%0$DJx{XB0Lw_zKPzbcc)sd#Pd81xIqvQap-UcNdRC z#8Qs9K(qV$seMy2xflL?b|>T76~qK0lN$joz7{&Xf@K(H(Co#Pv1`yDPC6-#shH)* zhj-}Ut?*h*IB&mK!VB4JXYTSB{aAjqU=);=o+~=p5bLu)K4$-K{e%xnwAHT^%ePx! zu3a*dmJ5gtjZKW0R`D+{2#WZ-8FtRSI}g1q^IH=HABEv0NM9}gYS~blB9uqPUQboe zGUdYP9UFs%rX0#iM?3wkuApRc!oN#OqdLac`=ip8wCiR~=As!umbd9#lo?KiM$%!w z3r);$Q(6}^k-BW6(vuT}cO#CE$F{5H2FK1_8`p_>R%Y^AJqxP)>F{6>8PAh$!NVw* zK$qR-GqXN&N$S==zydJqzWg5;4Dlg*Dx=z@x=|fna|+w0i6*70Ln%u_i0;K-6TlIX zkDO-F_iG}POGS9R#Q8I#_*V%Lbxr_CSepCK<;J=O@J}wG->0;uFg^UW~ICt27o*@aT8^d>V3Av)ZBCI8iefu+4qoGePv?P3!Id_V7 zI!|op20yD?i&?`s6FM&%VZUyB>USI3l$*-TrT&QaFAsMQ;Ma%zg0qiP`Y11u;@*Ex z%s0C$JWv(h}Z3YufmZRd;4nC5=xHYdR#LqpUZIDWPcc-6``X8 zgPoi+)n6b#n%$bXddQOm3;u2#MFQ%7O~2K<>$bN^gnjvsJ(r@E5>FDxvd6I1Z|3k- z@+RKDt7-Js7*9b!477k6SG^!!wN$`tc1%3daql3&-2ssMw+BtOtzJM z5m6-rrM8EXWxhp#F^dMN!>EHgRK!ZEVpyV1UuK}n|E(^I!{LseenIP;+p2ga4?YOT-Wpg4x8l z6MgubKW(KxMY{i(RI@~yORpj}UZQ)tQuHP?$Ni1aUa+(-r!A0dw~KOR*d9u{5Cj3t z7e%@Ie1H<&=cO%_^eZ4w?&veqECqyqzZExswZ{5x^~ds$5KuEJ=nO0Kr0);PD99)r zMK)+$7oZjrz%s;V|GQb!8uRvg)BRfRbKl$01%K;w;qSEigeaIOd^xg|Y|Hf0zulK3 zQeDEe~i1Co1DO2HMsHOa+p}g3%{`^1wF>PZO+Yy3VwkF9mlQ{ zzOo{hwG(%1F+JXIQ<*mVXBpZY2I1OOpv9YFPY*$Z$~}lQrss{;I_IlJR^dghi|?Wcc?9d|;BJ_Db~;bd|;4 zLe0FBx(KK3INZacC_7F#)M2HYXSk1^I_z#zos)h~l*OXISsTkX;FbjnvlB_8!yzTc zKRT~deH01!VhTI75#8^5g3NQjgPgHw)Ld-0hc+=8;k-rTZ|D-V=E!#k5Zk=>4tZ{9 zySQdt2`dg(12n#ICwLD^j<0OvvTBf;Oqo>#yQi1rhwew0PWj(r^!Gl~w$6P>e<>5T zu$XH&T^f<>ReHQ}-ka`RZoXY47`_Q2%^VmADeb>OaANX~W!o#AGjX(SC4hKRTb-+H zE9LL|g5nFQpc6bR9jlMv(l_xn9cAfKZpg%!H>u2HyvLkeRMF>Q`m4XGO{i(+ICJub z07f|8fl<3c!6*}PiAF>7trBVGURWo^&Rb@H$9HrS7i)*W8YKi3%n{R`O4a&Y4gF;W zEF`pLWiVbE9@1yrlwYeV2s-vqhJ#9K^1Q*^<3a5l*l)rD6j6>Vz89!Y?~cy4{UxmN zUjg)pr~JvuCW?QKukkW}PSO9LRJnhhXII?NF+1{X_|$cy@5mzcg|l*3Z%%W?y^aF69o=0b@l>vSk=cLGbjE z@O4!?uDXPaiReI|GL60)X7)lOCgbJLk3UpDkoIYJZ@zBg68Fm}L{wF>$&$mNJeaD7 z@6`*z7yS+)^? z_S7TlPKnQz#P+*C`^7ZxA!izUNZ*^(Z;fmnv~A8i%!BzxPD+llWNAY^(UWkLRMZFR zudMDdhwRgX(LH4R)!#~c|)Ays}_?d$W0>koxrw(>Tr zQ4{+6HZn;Sdz9ecykMO=_O7A$|lmXC1P zIs3O+$BFrX%YwUL5CyfX<(yCQ-O$F*J}s`Xq*Z+3_^O;AX!FgcCj%x=x(h)B*Lqfq6I<`a`YhwdkGvJmEww_2pADJeWG2tdZY9Obs)|d zPER%*jRd^kFBvbA7T~~~>-sO^jcH#>(Dq7~gu`m)KRdVeSnA^<=-$C7Y2wPCF@PRQ z4?PB89&Bp(E}R3|`OnCJw}jG8kql9bg3ZfaX5XkA*w;|4xP3dQ+#$T7~Fh$ zV|ht^T$A7NO?ggvyjtG=(Zr9jA6opDa;hOaGMb5BMg%1UeN*12aL6dzDc|*zh7I5L zhD8$lDbJsIBmdo^y>X;ZUC%#aE}>_mK}lED;Ei12RN_zD^N0T)v^eQy~~^OB?4HX>E9F4$trjF(9&{0;cNgy zuF72-<6~YJtYuxxl0Pk{+ttj7SCe{>#cgve1$}sSUSnc5jQ^?oe4dZF{t(92G;1j@ z4#SK{jUBrj>CY6AoZ(mqqt7$}&0VqxgaJEmoI2?433#8K9t#NtV_;kzIqk-4qsVzu z2uI#t4^w;;+m802|N7s3-yU4-_l$}xR+o!^5$Xo7rU9b0br0zUFgN0$VyN3UJgE&8;2@82Rd&u&DMS)V)lz@GgeX(-Ldb1|#IOnDc z((5lSOyQ&FCC3W^~vG94Zox<7Fn zwslb|r@T`A``2AD$65nfoRYE^Jq8ZD+^_Ae_1C%_20*!%U7AsZ?tziF4wSHPh9#qA z8yGmlbd5*T(832$tam4DHE0B8uddNWiTgg;sO`n2iVS?6l1BPI_atb7ThY z#rW8@iGG(O+4`o`XZ`%Y%+co*!Sb@c;bC@a>V-@cmWLC{;%Tk$T|p9DiQvfgRup$TO#U*^ zxmh$ccAR@7G1b$`!$sQDD9+*KLB8h^ z{pslCb&L36yj#PkEk@ za-Bs-WPGk8IivGp2RaAR)$54at_e^A@*$~Wb-+*H?XiBfpqw{J7x z8?+u-`Y7vrIpbZ3=XqeY{yP(wqR;0Oz}5(|U(0alU6WV&%c1ax&`X^p>DRZlcb5tb zdoyIo=f~dy#KohvD5r&Fd=(+vzG@dG^_t68HzY?DhhDUK0J9B&FE=25cw(3x#Eho*jg>=WZRJZ!&mp?0!xdT#1?Rl4nY&0KG4I}!7O zmSv@or+jP6VIaPid98Fm|Ae6p>Syl_U>Fb8x}U%5Yy3;l^}s>`W)zln_r1R{YyU?nfPcNd`9F&g0C)W( zAl?yAX$b%(Wj2n=R}`-{H8P^|0_h|v^F@Q(`i_@$zyeOkPIdVsOz+MS}^j zRuiJSzNL4|HcbaMmd60J0%S2zrKR#{VufBby^7M&W9xE8085frSS zoQwkzSJd^VgJ`-ZLXG~;op{jt2mh=)cb=j*KXn!mAY%fA1Y&u0c^$t>%QbxKeJv-# zus3T_ab19p-c>SAuPUGK{uN)EzxRNS;jl2fgk-eh{L*_s)9Cs}rTDem27KAh-**z9x9=|dGOG{ZVx_576pg_<-X4y(P2;=4cI~w-4ZKOI z`MTYdmtU&>>U*3nv~-cb@stu;y67okXx_W*skbZlo2h=i{Ulo?mp*iLK*_ZDYT2(( zxmT^mpWc+t5H`M*X&)kJ=aH(!JZuON{+j_1UrcSW#XU^UJk zm%CF3vFf|A8n#)_M@PSL*F7TU)+9k}Ftv21xE!TRm@@wEOV6lw9^T+gtgZWPA*Hv= z>4{moHr~c-n~_lxy0MtbF+6-zg7zXgdLzb*>UT;JF%?np)g10mWtOo|% zeOG&~#_r~_{r%kld<#1{W)*ompnMJ58+&{N! ztAC$eM(-|??(V0GRYRckGPk>I%pP+YVVb8YxH^>I@gh`_o$cEQ-YGVEed36SveZrg zoTw&Kth?Z$hCC;tyVoGp`}!N{FFq@TN6p>>I2sr(RoE6|V7!vh2QD)pKob*13o43d;eFrFa^bk^Zx#y-&R; z?Sh+08ZW^|YC1Zw4PfNvR-q;^MBy$xc}#XR7xlhf{4|o}ph4OF0xZFJN!98M$pjHv ziMP)`r2PcV7bf)@27D|?TUW8M^L;^*XYbtUU?V~AMvdILuYXlF$57L@W%uzqw#UoUdyL(J^dlR6pUrAr1HM&%)i#C z;Ggg~JUyAY5uT8&20TS$ughcG zCD{hXC{FWO_$5oXyT3ng@i>aZ^yp|g_qqUBAOQW`6%Q>LrK>x;UVGlDl@G8~v}*az zg7jf3n3qwy?nr}I($uq+4{Riiw6vI`V7cT+#$^RUNf{Y*>9Qz|Zyw*ieQRzOxe?SE zcJ0@u0~%ls_Ll&!dV1#}DAm~7o~{&jrAWs4;eYb?=W{zLtJ>UK9d$oHwa4&$Q~w*V zprD0wJggsw^a`e(u_^~t_{s5lEsT?O9}Rn4xD*`|Ln|4J28>b#pd=1z@=HQ39_MAq zyI+8Vkq7U+n^U_x8Veij0Nw&i4o%HB)xIA;MsS)_2rK}igA5lFX|uQTdgbe-T<~bU zgAf(smrb0S(t7fw^2ZM$nxGb)M-PG(>IDHs3JQK)?q{IG!!2L{h{I2DS{!C$4$A|K zhodml2U8!%2=FgRc^Tsblm~zw`vCN%+$tG_ejp};;JHTOaO*ao+$0N6SJwAG4@`p-`%&uvcwjrn+Z46Ru~{R(`$!2Ib!#I?Fy z+)TsJbR00|>z=3KMrm1Bgr z;G=_D)>91hevfyIb31ylHwCw1e|>U((*bNqiVF+tIke;k0hf^~9-*b7(XBTL7`9e* z@N^&S=*WKI4ao}>tA>mZv)O_1uyQ%(CDGM)SV-WWI9~=-3>FgCJ18p3rd(w)Bw|tlw_89d1BJ?@Hn+UoQ4+Uf z)@n94C@?TjzzE#dhX?~ic1{kh<@OprAwRpU1#5Y7bljX*=sZY3FK=r?fKM`@>}rd5(fu|yk!Ls;uL=j@`8t(CTxm|3Qz}!B?4~( z;A?QbIDb6_k4R8JV1vhjsucVJPKp&7o?(GzoIadaK+_E9A47W_%kKcdZQmKqHM9)= z4=Haye+Kt&Q1$Zn#~b(s&N)Na>9TaSIE0|WPxHQNIEYp+(}~bV-2N!3G)|rP~2=&j}#kY$Q0yq{k}vZ&X-%yJ8-LkY-lWxGkgQYi`1%7_?$}0%6GjW zs-FNS5Qr*(C5Mf29mxiJd&NHZ6M)Nq=oLbQnl(l-05J;3fmk$QOxeygo z7=0WWhha}rtjZ0<5#l9Att5O*3XZ7pzP_ZSH`Uc3YqF9q6H@GGuB)TtLf%oxM09l$=4Rz)Aq30DMXSw_klHK;jEG ztxB^V!=yb2ws>>E-BPo00E{?r$39C0!vH45yl=z9<-J4P6zfCz+CxrC0twv^uxnxo z2hh(u_<>AbBXBX#C(lpS8+MC;S#WPXBhd>U4yyhYCt(QYMy`BQ02Ju~ehItiIW*-@0LXxKt85YH=Rn64Z~$B1Sx!b^$> zy^rA4hfY@rgb|P%m?=?0SeO6$D@&#Pm8T{_UlQJh+>Pmi12;m3{e7J zOX%zgFOQ19JFSR#roRzGh_pY2!~a9wTLx6Qc59>9C@lg?DJ>u%-60?#Eh#PCAl;w> zA|-;-Eg;e$-2&3x4bmXp4QEVT>)rc}y}$3Ae}^CI$8s`x=6vqwzQ?%675|5qAlyIT zKE&?qEMzFnk$Sxsc2yZhm1?|Xa$_H^Yb2pmWoybu8JD3dYw3YycYJ9ze>yx2KBvZ(z0Sn)1W85*L}84=LZS~%LJ zjxFx>I*Z8)bjK;w^1hTX`s?De!S(;(r9=9`9VBf?rxM~>Nl$m`B~1D`uxxdyELzw;Ik&SIKz1EuTiKCM}8y?9Wd@hd1U1d^i z{VqzkbI7EnJ;cac-i;cu$VqqfO;iS0+WkTzqewv|nO!xvpKo*Y>Ix)S@kYr9OQD9J zenf9Ygq`s%q`0Z&+U5+R;(Yd2I{X zGjgM$$4QHe+AYjT=5IeWzeFZP>M|WNvxnsIkn4MU_Q(7AsnUuAUtLJaYyhiimbgl< zd>228;*L06&dx6Dg0**ZbBc1d0u{NVw>O#yvK2>dS65~jZgWShW>wX^qT&ezMWdiB z%fxK#@31JY*z}Muzeg4|G)y`<8F~MnP*n8yaQ>wio>Vdi%!Zn&vdHE3PA*Q7ii(>v zGydz79>aZo=?D4W*@hLCR6-qpclEb^i-{h+rmU=+*;DJ~rS5T7aWA9bPSE1Agj#7& z{rLF7ln|sYD@k`KU}NjBXGi-@iizfKn;QpHk~Q19w*L@iXR?d=KfI zLhjoct&<2xRm#lOV`}a1<`h5%xgHEoFV-bBXX(n}_PkW|*p(|;XsEVk=4f(qu5Inj zqeVDXj@@rD#ii-9(oH`2TA?84_Vki*aLDejZU;B(sTJcEEzvwO%S}zC_7=R$JwW>4 z$Mz&0Y?xru@$&s0*uNk6askMp1S$PN{2yv8 zjG9YJE2T}1pNSx&-9K|y7;Nz)?c5#^!^PE8Q&S{+Wj>Oks%F)%sE0>_Wx#l9ar>LOkrE?mEll zq-{k}a9y)nqPGqg`~T`4qE!>!B)t1tpMz_rx9f3mq%xEjtV?T4 zI2`Z;y;$$Eh|#rX3OoPau}4k^ zxmKq~u8J0W-$JSvHk~QpgMW*uuD<3&b}|RFjrNdKnZwayyvRtqD&@}0`-=oM9qc5D z2k?pcz?F<_sG{^^D-QjOF7L@G>Y$%`TLYQR=FAbu1$Q&IkdPLo@KxrXSfx;Sbp_nJ zvcGcASCcN_XF_j=smawlejj{%o;jAxEOzJMy)`kyB;sj;e^GgKDOB9njujA9?UffO zUb0V~oMx&-ZiPvNb7iT_m{@v9SLsg+XOM}Cka^M2M1_QiTTIT2lQ-3SmsyN&SXR@~ z(wID?M|U?Za@t-|SW$V#W4E}cukg0fR7&zuN1GlyTM=095E3lo;wj6@^}NRPd;bdc z){(%b+yk$Own!rH=ci@V=OSDwj-b#b*U}nESELtucemhNl=NYf^L*Q&+Lt5^(GSUP zXNPEqVK}tvN|~>e0=(!pad75bSMJL1QdE`QQgV_lrq9|!)NKju#;Bt15(+{mD0AjkxU9Fm(?D~_|vx$-0TQxBDR{sn%gp5$q|`(4Yw2riu>5G157 zG6pR_HuJJZotsxLTWM>Lw}d*Nq1lBqI1Jck@8h|WC1|}-B!&j#7#2DI`HIK%&lye| zi|@mi$;ig@bm?HPLMQx|x@6?`#41XGV2fJZEwk4$`M$yR5d=Pfv>42&+twK1BqRED z7JKLzXKqsLybZPB?jNU+|GT0Cah?BEm;6A`hY~{mtq4J?Ah;+GzUlp`HeAZZsF4y} z5Lh#kos~`bW0yTNsZ@aV5b}h7dCLGtq}O3auv9oUe*IQdk_znV`)p)r2sRr(jC;*2 zCT1T&nNir>67}6w`5B}t1dByHuG!*0w_4a#EjFj^Xr5YJUY3qAx<{~_opXM~A22sp zFxG{Vz#tq6c+RQV3K0KDlP4;sOXt9SIV*1C~A8~ z8qCRg`o{clG3gR9G3C&Qu&~+n>idN*4#wKru?l`70gqMk8~R5_C97OCG$c%DW=2?X zqv;;A^zCjYK7GnrUUHZE+1%`ok1(lx&ubB~xmiiKWl>u$3HN)<%%CX`ov3Ig!F&}L z_tna3&GY&atXO+zdu9XFl|6^7@PHbw5$xv32|LU5n=ldx(c1p_w zBw>@Wk89+V@v5y;T7T7ChbA%5pB74LdbRN~aB8WWnD2Ay%6*x3HG zw1Ig zbWCiZj?NhTH=+&HK^+-#R#0k?vRRWCJM2sW0{!d=Hh@ z&n9n`2YjMb5f$d(;wG3|7!(nn<=~R*j6?f@|42Q;Z5cQ9bhtg<5|7XU_lcsk$tKg*X;CSV6cvfX9votr@jnzHdynI2 zYHP3S)--%5L`KeiL<3TMJnj2*syku%y7}Jnspv+%&y=fC(Bc@7coA@&00A@>P!v3# z3hepac)#%_d%^73nB;5@9bGeW;#8d$20t9WLo0*ceSrjKUR?*9N?UXn$G0c3$nJje zE9)815UkSOB;vPEtB4N;{E3N^=$z^r;tre)dB*2c{FJS zo9bqCjyq^+qcbxfTJl=R%o|fPvADW*zZbqNC}u_6!am87so|pRZeLnj=&~1JHVMnd ze=U{)=@bl_kX}0w2ft@$>6_bBJ#@87tq=R{_Ir7G<8XT3z*i@aqFOQOmip*1M-D&{ zMxAlrwcMQuiPs!=7L#Q7iS+b@&3eI^U6Ily2^xA_uaA-Ue8qmsr3OGId#5 z>FJI4*j2=vM|Vi(k7qz?xhSfxcXww)S$gwk{b0@@d=XLjHS_J>>e;Vq-maZT27Mql zFq0gss@xc$leWqCYM#wf`7~vdj)Qd+ol`eKN@mp@LnmOGYbKyeGdbJak zDY}iSsKl9*?pi`ZnWX+ zorVR~uB)4IhEgf&JsnwDS$8D08T(1(WbJ$>!)1ArM6 zyefV09utVZz!}^YiYMdX1ckB?o;1I3hNrz>hi$7ckbY@g2PQU>06TSuoQV)$`8Qs! z*tsPqEVa3cQ!|UhE7*Bwc}udDICqh{_7Ug!YYQDFLwmdM+}s*%ZO>4a7X?k)-K0Lm zUJxkAfFLEn&;L6}hwAYnHwrFKJ4yL^Yfh8TU!tNV`KDfac)Ar)JJ0C z;*sIuONZOoySr0MuLQVj_=n+wStuF1{Us@lz#-M-DgsAAGD7{VBCuTZPY_2(TTIMn z1?O!_AAuiE&QT5Zr5O0h43@)Cg}?e$)1UDOS0W)bb!JK%pLp%D$>&mEjdmm)b`< z#<9Z2@TTBHhMa+kGeW`l0zVk7+vU#D+x zo)Z5XfJj$&0yk0e+mRvHS@5Z;4AW^eE+u5WVv79k@eJ;k#Q%&*NJZ%pVNETrn8DH} zzzUvO6ytSaLo)p_cS}ep-%GIh15q3R5$f5GIZJn1LVtT*&4G161HN9`ds%8wiFn>Y zDFMg=PMo)%=9q$nA0D%?WTZR=33Yf*j*}t|%oG3!Ji?|r0jI(_Mpf11n>UHw-RCdA znQe<~`TY6&(2L@0C@Y9j51zV4U}wy|97p?fpZ(pGEcK=7HH(uC60onKr7|=z%}{Vk z{uiKw*QG&5@=G-!*NrEh4M z>a2UQJBPw&fWqX?Eof5wUn8}C`EqpRjJ5Y17S=ZScCeM+xUNcYcX4P0KRNjzBKi|l zpRSuC%Q!04(hQ^2go+2Y9JUubGCUtMS zYm+Ezy-lfBFWnHp=wQdn<+68=jm-y>%X+pO3dgIL(Oo1`^EVvTUnUe*+_16v?s=s! zIXUsG$b%rp{{|J<3GZ%zHFv>qajzJ=MJ|@$Ok?%hSnXRgQ_}=;3?zZ42o+?4A}X5J zxnZCf`2(+9>f5BjpBqgnf|1RYuoJqQkV)v3zll>tK;`wKd~eMcI|y2x>~BUb?OEgb zhU9x#_ILh33d2xPN=iMm3`|rHua$r3wRcPt^z#p%FhRf_g(w@i?#+6GDT?hac+7ve zyb=3Jfd3l`57(*}d73r2Z7l3m858|GOKgCmfzKZY&)6muZ~2B8!46{N(`gDC(B&$z z3Au-71Qs{QdYKk$$<$`teg-vTCmuVy#2tUSLn&^Gm#R2Ov2UTY{Bf+aHH(2M2q$Dy z@q6K(6*Kxs$-C^?pN!@FkgtvnYK;yK6cO^aNXmGe^($Mp1;#SzzZNEKZOu>|JE+6J zXbe2YEqPP*3{f=XL`n;iRlH@8ehH`@BUT?#1L;mIw`oUum7J;ONmx?3+kt$ z<>>gy)$VSZO1RODrhs(Daw!H!wG5SXqm`$4)kEP?w9_{@x<=S3_U? zr=j=D67`Pc=mtQ)d$B)60gl45uOb48i;j*FUOwC(h|MQbq#pyGO31nOke9b>_;5E} zA>w~HHD%2n1ZkUyZ^4tVItNZo8TS^0?r-bpqKJ$C4h-}S4wklA7%7^RaB}p*{KT0r z0n6hr{J^iCNmSHC;q!K$ZX4V)!G|oiuXMgBr%np)>*?jz*4d1hjpfbk_TVHC{L5iG z2#4v*gdREeKjdhn1g+N>#3UpEjfbjY4=z5(D7zQw* zC_z9Ev8EPbt_M@0_5PNoY>?Y`t*{#yW_#??Cf_u1}~sOyavwz#CySvSuRbICN!w)~RBE zPE7=&I^-U5!6tU!gXaN*rRq9y3Py7BXD6q~IDY=;TKC^6y$Ih8M?Q&NsWxaT>h;pCx34+J8#JBO=4np5C$Dhi;5MW{QqHupgQ4UyhKHQ z`w|KH?PnOodg^5giW=k4n^aV`8*~!i#(eqg!LM27-t8?Ds6|M;kgvwL_7sOa7MIp* zM9+zca9v5#uA?s~y&Uh(^^m%`JZU-UWG6I6Oq7TVPz0%lb>u*imgdOx$&d73k10mZ(t`AKPRa(l z2S3E2dHy9ZUfd~4`N@1-_5Er{j5>_v@$nYc)-B(#iaonWjVbo|{zLBQJJ*St}c>yecw z6F?p>sS!aAyl+Wi`O-ujpR3@LiEi!HjFXeHp&^%R*LH_w@neaTXZb$*X@8Cp+qZT8 zxfTnlqfZe8znq8%2GNMFH25M~VTBurlQ|m8*O5js zyb~`xw0+UYuehoTJ)XhBBJ3fqT45nwi%Y|ltXR{Jk00OYhqgnYG_i966w3)y>_4rtGuB@=~OxW`_{+ z(&Dd^Q#y1aeDAu%;9N5%sgBTMR zG#1)ShBQ@hhW$q@*~!z-?`v{$$Y^ot?VHwCiP>uGC!?0Fet!6V{+`{@tw6>$XuDft z*x7WvuXT20S|7{Jke+dk{I+`&=4tNZ(XOtVttWMjK~8yj*C;4fLT{=oHs|39sI!n`m5_{*P*tqvx|8(p#I_lD$P$$K z>b*}2!eV1%e+MEQ4tFR66;yvecG^;eYHa4+eEg3q0?MGivCv2_cnRvejj9=I?}>@? zdSY8or?Z&z*=N`R3DEala3KZ$XP}rKKo<-pC-CuH|39F_2A-{HsEPxeazT2@qu7AIiT7vYSG4%+Q5%@iNobD>WeEHJW zb_b{otg#EHfAY8z+`fT%jvY7=gCnS)zkjM!G`Qa>YG`CFDXf$I8Jw?z!JUwh5MnA1 zyfZ#<;Q;0s5Vh#&VtIIpcIf}S%KTm3)Z4dj={3p(cjo<~BF1kL<*pBZ2@t)?SJpHo7flTr_?u@W?=C^Z| z<%fu{J!I}_=mfY&zF0&?vVNlMj*U56jIJ|gL_zUCI5+@4Dx~x|K%OYPYm{{I=|$&@ ze(jnHb0jmB)b|0~kAq)jieW|W&2eS;<8nTZWQIxhXS3(=u)L?Hia&j7)vUnWtC^a> z2AGAGDII{Epd#Xp4Gr%+~q+*=^WgARVuYh`s!tEaLK&yx={nf4=h0->h#)@uKwwb z&rn((*+p|ZWLH6o#_n>np0$F)=dUwH7v9<<0^}jYJWVCGY3XHUk;0^oF466AyyOpl zgb4i5MuR$`+#!5^_SFS4Vs~hrI@8#ipI?fc2+xVV{W~CTJn(`6*k5NNp(oZCM`U{C zh=1#jgAj~0n-suto z(%I!VzxdA&gS@@#A!UM zBs;S=`B_-}1xb>>9zogjXTW}+$B6v)Cd?yqiJ>ukWmEp^w~6a~+d(lgDPI)_X|2^D z{rcOtWfT(DN?9$4w)&LYl#hvN;WtnMq^{8_myuIA@wzslptK79=gGfedkdFU?PPE7 z9g@$Xiuu<17PdRNr^jx|a<-e(`3DCG6c5hRyqTh#=NH5EgQZ>rMf%n+Ptjq?UcF(c zpu}cAsd)!`W`4!&ryhbSxZcWOdAig4LQ4x@@O>)|K>RnoLx{L}+}zqr)wQjymnxi! zZ{Mx}^m>2R#aKS;t-C1@qRiM_TEpn1rKQ1-9TvCZOA*la6X+S*5D}unK)p%ta-hga zu69XQ)|->w+1Z|kh5?vndU&Al^1k)F!b+i^EYMY`NC5BZf`HMzx|7;rrta?tpzKrg z5n*ADqr+}Ev#`h{?*>2ym5#Nwv7(}lWPqB$YaIJUw_{g5CWt)r-l{w~F$xa;AR;1+ zjXiyGXkSyq))>Sb9re=^ZT)W?U*i@7rdW~r_K&Ud(#Vxz=I@1yWo|Cr)jkhlYT(pM zx&K4J^vV??H8s!AckbKRfVHtVgXRzC>2-ggMjad<16Az=EZd?wz9=G|VI$9**qK|~ zh$uOnJAJx!inh*}_ChEmwtwdE6`>WCY1GDxvYy5E{a6kT@`t7p65z+(ZlSgoa96v> z0427gcE;+>o18)4|2EdYik$dEVEGeOBlQ~aJjroeiFr*{9#+6GIq!qQHE%&#_0KiU zz9!mFsyxmqe8_~6{~`@+?vlJ0J^_voOe5!|?8DKoPG~stxIlpLw=5CEdm}ks zp3sp!z4kUS!VkGcMfF1wO$a7|7hCJYE+aiJ%<$19#mtv+1LgJLCJcUS!NI}6MZL>y zp9$L)!n_`71jv>!v=I`#?pOakhR{NFBq>GW+}GDit)weX?C0`7(ECa`wNZO@;BQy@ z{}(9#&eg)5U}}o9w3K0bdd$<)TvAeugOJNUAo8Q^qfgDb#1r_$(N;NS@7^gzt^{KL zl%8x05GJ37F)VyaKCR3bX43O|@?WFl#N&ni=pAH_*V)1`zlXt1t~N%&yDb-QB3yXMhLTXNyU9F;9~CS&svL zwsPbl0{~g@oybxOC5cw6CO?27X&$51@m{T3F$eGid@u+Gb2=?OiVcPy&-nRK;!3<) z*BtLnb-jN--~PtZ_eG<@C!Ra-lGYZTZO1G$v@i0hG|-0;u$N&Wb6mazO$HP+q91tD zbMRmzi6LCesaC2Ot|OR=l$?lSEj7Q6&8=_rt=v#%7zPNJ-z1lmxUY_s14Rh-2PZKC zg35z~F9E2y@eBY+s8(`?x8eZm+G~^?r*(D1!phbb_13K;VEnQY2}}IaTcyH>zS|*h zc;zdl{|)hL&<3c9C2cG&*=S!+#d>?Jltz>Z6NM z#8+3w+@Hb>b9hu797);Pa|`jOK-+@727ui1^5&7k)xln!uBCof*2}k^HrCaV^NeB9 zD$e;^H#1xgO&>LAg==eJwpA0kPRqlS+TK9K?Ob-^WncbxX<9MLJ3Bw$5y`v&C*9iG zTIF3yQ}Fc$nibSf0+u}rDg9{M3zH`D#>TXnJx7pt0_6Xl;5cPf{#o%017T#c%Ck`wNK}R$xd6tXHp(V5b%s*#7N#667eaPfx#_4N0Q%7Nxk+gKD@@qT9jXQgP`0ybk!_>-Z z(=guc>(ZlGSxo~HFX?zbx=?W-cdyFhq$!vf8>iu0SN=sPozMZRYOpQG_7w*GK?U&C z?d({=6M1hBn8Ee}0!VKJ5_*>;!XDM&hlhoQh>?4JOiiTS}DH0yzm-cTNm7H$NkNzV|{7KQQc5?;c*7I3#7?AW3$7|7R3~ zgfyY^>&KgG*RDZ`bSc#AkgLEB11+dy3knL}zkd%m`snZw{+%(h4JgnV$~iP^FBBAj zA?#BrEhTljH);v?(f*+-gy>W(ECnek`mlnks;U$f6%qOmR?`9C&O>5Lp2p`B$6S{Z zb$UT~n{T~gzmb@y5&g>sF8nR92L!4hIyyRN*YuboMUo&O;$NP~@_ zqyjEFBpt*elpsd4O~DZH9LeMKeaj7a+pvBhfF6UG=UYaGzxf?s;h{V9kPom|4!+|$ zWC4|YGOdk`smaO71qCdib=$3U{)=Ah=nvrp(0~!5J6!*Iu*PR(V+*sSy6p=OQ0GE;Xebsk5np-!;DRx*q!u8_O5WKS5@#h8 zwUDcd5yUz8LCpD|rzt>$!`7Cy*GE{()z#;TLO!oJVnSF8K%vtTO557n3VyK<;=2o7 z53RxL6EliQA9&}@t*!E7bBG85c8i#;;}^UTI)I0T1;~98w3@}|3gfNW7rH^D*TtaK z0eU=gBBV*cl7BD!1z7zEYP}xl<3t?_lwrFtFfb4ltW{gEekv-8fzM)edbA7L03PBT z&>Di-KlGgh>=p{v;Ta1`IhvJ@o71zivw;NcVV^%EU%i|t+w}4=LQDvo0rUm)^IyLT z)nR{ymU)TDxwe32(RLt&BIm2;=P-9c(zH#6H-57yW7xh2RZcwiP@%+fOJE9yh3Ty@ z_OBKj6$9|FHY^W=dD>txNDx%cw%y!g>$QET?>u+27da9B22&*Ay?cktIn@*JXsOAV z!UGLF8DOn*5K3F|LYWqDj~RkiFmK=98kj&_S3<%AFIkW|g6d?+mdn5Xi(DGS8Ni(* z=C(I0Mxcy9%r71;fuI&p&#)1I$OI8q1f{?$4nkjH9dP}(wB!ddn+)IK&xWrk8TTBP zZub5eHHRsxM*LwDohJ9xw#9oauZ@pXr4{@&@%FP*Jf>4k%guFP8;id0ZbU?|{58X0 zMw%YR(Nv;AA2#=kyKD>4ca>6DwQIc~i|Cu>p{IlLUpxQn|7v{&_(?-s*vy(zB@^oPeiUuwGFe<*y6mwTTF

{w_o{l8(#LmeHM(bKWv%BGec=p@1qe%%dV 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 R+ERKDJuSm4MM&#k{|*_8j3@vA literal 0 HcmV?d00001 diff --git a/examples/uring-cmd-zoned.png b/examples/uring-cmd-zoned.png new file mode 100644 index 0000000000000000000000000000000000000000..a3dd199dc55b7f0d2a940ce0712cf85f5113adce GIT binary patch literal 98231 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 TTbwBiL2mVQ^>bP0l+XkKx;we- literal 0 HcmV?d00001 diff --git a/examples/xnvme-compare.png b/examples/xnvme-compare.png new file mode 100644 index 0000000000000000000000000000000000000000..2af92f6299bac38d8b1e78f80e4997115b9ec330 GIT binary patch literal 44742 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 Date: Tue, 13 Dec 2022 21:34:20 +0000 Subject: [PATCH 0372/1097] t/run-fio-tests: relax acceptance criteria for t0008 t0008 runs a 50/50 seq read/write job on a 32M file. Since a random number generator determines whether fio issues a read or a write command, the read/write ratio will not be exactly 50/50. This patch relaxes the acceptance criteria to avoid false positives when we do not use the default random number seeds. Signed-off-by: Vincent Fu --- t/run-fio-tests.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/t/run-fio-tests.py b/t/run-fio-tests.py index a06f812683..70ff437114 100755 --- a/t/run-fio-tests.py +++ b/t/run-fio-tests.py @@ -387,10 +387,13 @@ def check_result(self): class FioJobTest_t0008(FioJobTest): """Test consists of fio test job t0008 Confirm that read['io_kbytes'] = 32768 and that - write['io_kbytes'] ~ 16568 + write['io_kbytes'] ~ 16384 - I did runs with fio-ae2fafc8 and saw write['io_kbytes'] values of - 16585, 16588. With two runs of fio-3.16 I obtained 16568""" + This is a 50/50 seq read/write workload. Since fio flips a coin to + determine whether to issue a read or a write, total bytes written will not + be exactly 16384K. But total bytes read will be exactly 32768K because + reads will include the initial phase as well as the verify phase where all + the blocks originally written will be read.""" def check_result(self): super(FioJobTest_t0008, self).check_result() @@ -398,10 +401,10 @@ def check_result(self): if not self.passed: return - ratio = self.json_data['jobs'][0]['write']['io_kbytes'] / 16568 + ratio = self.json_data['jobs'][0]['write']['io_kbytes'] / 16384 logging.debug("Test %d: ratio: %f", self.testnum, ratio) - if ratio < 0.99 or ratio > 1.01: + if ratio < 0.97 or ratio > 1.03: self.failure_reason = "{0} bytes written mismatch,".format(self.failure_reason) self.passed = False if self.json_data['jobs'][0]['read']['io_kbytes'] != 32768: From eb3570b597b2004a1ac573f52d965f833cf7a6a4 Mon Sep 17 00:00:00 2001 From: Ankit Kumar Date: Thu, 22 Dec 2022 10:09:48 +0530 Subject: [PATCH 0373/1097] engines/xnvme: fixes for xnvme ioengine 1. fix error-handling in xnvme_fioe_queue() 2. fix resource-leak in xnvme_fioe_init() Signed-off-by: Simon A. F. Lund Signed-off-by: Ankit Kumar Signed-off-by: Vincent Fu --- engines/xnvme.c | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/engines/xnvme.c b/engines/xnvme.c index d86474814a..dcc54998b0 100644 --- a/engines/xnvme.c +++ b/engines/xnvme.c @@ -322,12 +322,15 @@ static int xnvme_fioe_init(struct thread_data *td) xd->iocq = calloc(td->o.iodepth, sizeof(struct io_u *)); if (!xd->iocq) { - log_err("ioeng->init(): !calloc(), err(%d)\n", errno); + free(xd); + log_err("ioeng->init(): !calloc(xd->iocq), err(%d)\n", errno); return 1; } xd->iovec = calloc(td->o.iodepth, sizeof(*xd->iovec)); if (!xd->iovec) { + free(xd->iocq); + free(xd); log_err("ioeng->init(): !calloc(xd->iovec), err(%d)\n", errno); return 1; } @@ -338,6 +341,10 @@ static int xnvme_fioe_init(struct thread_data *td) for_each_file(td, f, i) { if (_dev_open(td, f)) { + /* + * Note: We are not freeing xd, iocq and iovec. This + * will be done as part of cleanup routine. + */ log_err("ioeng->init(): failed; _dev_open(%s)\n", f->file_name); return 1; } @@ -506,9 +513,11 @@ static enum fio_q_status xnvme_fioe_queue(struct thread_data *td, struct io_u *i default: log_err("ioeng->queue(): ENOSYS: %u\n", io_u->ddir); - err = -1; + xnvme_queue_put_cmd_ctx(ctx->async.queue, ctx); + + io_u->error = ENOSYS; assert(false); - break; + return FIO_Q_COMPLETED; } if (vectored_io) { From 203a4c7c93d396c8808402dbaa36d2764244d27d Mon Sep 17 00:00:00 2001 From: Ankit Kumar Date: Thu, 22 Dec 2022 10:09:49 +0530 Subject: [PATCH 0374/1097] engines/xnvme: user space vfio based backend Add an option to use user-space VFIO-based backend, implemented using libvfn. Update xnvme engine options for missing backends. Signed-off-by: Ankit Kumar Signed-off-by: Vincent Fu --- HOWTO.rst | 5 ++++- engines/xnvme.c | 7 ++++--- fio.1 | 6 +++++- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/HOWTO.rst b/HOWTO.rst index 97fe53504d..aba6c9b3b9 100644 --- a/HOWTO.rst +++ b/HOWTO.rst @@ -2845,6 +2845,9 @@ with the caveat that when used on the command line, they must come after the **posix** Use the posix asynchronous I/O interface to perform one or more I/O operations asynchronously. + **vfio** + Use the user-space VFIO-based backend, implemented using + libvfn instead of SPDK. **nil** Do not transfer any data; just pretend to. This is mainly used for introspective performance evaluation. @@ -2875,7 +2878,7 @@ with the caveat that when used on the command line, they must come after the .. option:: xnvme_dev_nsid=int : [xnvme] - xnvme namespace identifier for userspace NVMe driver, such as SPDK. + xnvme namespace identifier for userspace NVMe driver, SPDK or vfio. .. option:: xnvme_iovec=int : [xnvme] diff --git a/engines/xnvme.c b/engines/xnvme.c index dcc54998b0..ee6b67c122 100644 --- a/engines/xnvme.c +++ b/engines/xnvme.c @@ -113,7 +113,8 @@ static struct fio_option options[] = { .lname = "xNVMe Asynchronous command-interface", .type = FIO_OPT_STR_STORE, .off1 = offsetof(struct xnvme_fioe_options, xnvme_async), - .help = "Select xNVMe async. interface: [emu,thrpool,io_uring,libaio,posix,nil]", + .help = "Select xNVMe async. interface: " + "[emu,thrpool,io_uring,io_uring_cmd,libaio,posix,vfio,nil]", .category = FIO_OPT_C_ENGINE, .group = FIO_OPT_G_XNVME, }, @@ -122,7 +123,7 @@ static struct fio_option options[] = { .lname = "xNVMe Synchronous. command-interface", .type = FIO_OPT_STR_STORE, .off1 = offsetof(struct xnvme_fioe_options, xnvme_sync), - .help = "Select xNVMe sync. interface: [nvme,psync]", + .help = "Select xNVMe sync. interface: [nvme,psync,block]", .category = FIO_OPT_C_ENGINE, .group = FIO_OPT_G_XNVME, }, @@ -131,7 +132,7 @@ static struct fio_option options[] = { .lname = "xNVMe Admin command-interface", .type = FIO_OPT_STR_STORE, .off1 = offsetof(struct xnvme_fioe_options, xnvme_admin), - .help = "Select xNVMe admin. cmd-interface: [nvme,block,file_as_ns]", + .help = "Select xNVMe admin. cmd-interface: [nvme,block]", .category = FIO_OPT_C_ENGINE, .group = FIO_OPT_G_XNVME, }, diff --git a/fio.1 b/fio.1 index 1074b52a38..004d3ba0ea 100644 --- a/fio.1 +++ b/fio.1 @@ -2584,6 +2584,10 @@ Use Linux aio for Asynchronous I/O Use the posix asynchronous I/O interface to perform one or more I/O operations asynchronously. .TP +.BI vfio +Use the user-space VFIO-based backend, implemented using libvfn instead of +SPDK. +.TP .BI nil Do not transfer any data; just pretend to. This is mainly used for introspective performance evaluation. @@ -2621,7 +2625,7 @@ Use Linux Block Layer ioctl() and sysfs for admin commands. .RE .TP .BI (xnvme)xnvme_dev_nsid\fR=\fPint -xnvme namespace identifier for userspace NVMe driver such as SPDK. +xnvme namespace identifier for userspace NVMe driver SPDK or vfio. .TP .BI (xnvme)xnvme_iovec If this option is set, xnvme will use vectored read/write commands. From efbafe2a0934924f139a2fb4b07eda4560cdf7d0 Mon Sep 17 00:00:00 2001 From: Ankit Kumar Date: Thu, 22 Dec 2022 10:09:50 +0530 Subject: [PATCH 0375/1097] engines/xnvme: add subnqn to fio-options For fio to utilize a fabrics target with multiple systems, it needs a way for the user to specify which subsystem to use. This is done by providing 'subnqn' as fio-option. Signed-off-by: Simon A. F. Lund Signed-off-by: Ankit Kumar Signed-off-by: Vincent Fu --- HOWTO.rst | 5 +++++ engines/xnvme.c | 11 +++++++++++ fio.1 | 4 ++++ 3 files changed, 20 insertions(+) diff --git a/HOWTO.rst b/HOWTO.rst index aba6c9b3b9..0fec38a765 100644 --- a/HOWTO.rst +++ b/HOWTO.rst @@ -2880,6 +2880,11 @@ with the caveat that when used on the command line, they must come after the xnvme namespace identifier for userspace NVMe driver, SPDK or vfio. +.. option:: xnvme_dev_subnqn=str : [xnvme] + + Sets the subsystem NQN for fabrics. This is for xNVMe to utilize a + fabrics target with multiple systems. + .. option:: xnvme_iovec=int : [xnvme] If this option is set. xnvme will use vectored read/write commands. diff --git a/engines/xnvme.c b/engines/xnvme.c index ee6b67c122..208d917d63 100644 --- a/engines/xnvme.c +++ b/engines/xnvme.c @@ -78,6 +78,7 @@ struct xnvme_fioe_options { char *xnvme_async; char *xnvme_sync; char *xnvme_admin; + char *xnvme_dev_subnqn; }; static struct fio_option options[] = { @@ -145,6 +146,15 @@ static struct fio_option options[] = { .category = FIO_OPT_C_ENGINE, .group = FIO_OPT_G_XNVME, }, + { + .name = "xnvme_dev_subnqn", + .lname = "Subsystem nqn for Fabrics", + .type = FIO_OPT_STR_STORE, + .off1 = offsetof(struct xnvme_fioe_options, xnvme_dev_subnqn), + .help = "Subsystem NQN for Fabrics", + .category = FIO_OPT_C_ENGINE, + .group = FIO_OPT_G_XNVME, + }, { .name = "xnvme_iovec", .lname = "Vectored IOs", @@ -181,6 +191,7 @@ static struct xnvme_opts xnvme_opts_from_fioe(struct thread_data *td) struct xnvme_opts opts = xnvme_opts_default(); opts.nsid = o->xnvme_dev_nsid; + opts.subnqn = o->xnvme_dev_subnqn; opts.be = o->xnvme_be; opts.async = o->xnvme_async; opts.sync = o->xnvme_sync; diff --git a/fio.1 b/fio.1 index 004d3ba0ea..134aed541e 100644 --- a/fio.1 +++ b/fio.1 @@ -2627,6 +2627,10 @@ Use Linux Block Layer ioctl() and sysfs for admin commands. .BI (xnvme)xnvme_dev_nsid\fR=\fPint xnvme namespace identifier for userspace NVMe driver SPDK or vfio. .TP +.BI (xnvme)xnvme_dev_subnqn\fR=\fPstr +Sets the subsystem NQN for fabrics. This is for xNVMe to utilize a fabrics +target with multiple systems. +.TP .BI (xnvme)xnvme_iovec If this option is set, xnvme will use vectored read/write commands. .TP From c945074c0336fb1720acead38e578d4dd7f29921 Mon Sep 17 00:00:00 2001 From: Ankit Kumar Date: Thu, 22 Dec 2022 10:09:51 +0530 Subject: [PATCH 0376/1097] engines/xnvme: add support for picking mem backend Add option to the xnvme fio engine for picking a memory backend. Update the fio document. Signed-off-by: Mads Ynddal Signed-off-by: Ankit Kumar Signed-off-by: Vincent Fu --- HOWTO.rst | 17 +++++++++++++++++ engines/xnvme.c | 11 +++++++++++ fio.1 | 22 ++++++++++++++++++++++ 3 files changed, 50 insertions(+) diff --git a/HOWTO.rst b/HOWTO.rst index 0fec38a765..0a48a45377 100644 --- a/HOWTO.rst +++ b/HOWTO.rst @@ -2885,6 +2885,23 @@ with the caveat that when used on the command line, they must come after the Sets the subsystem NQN for fabrics. This is for xNVMe to utilize a fabrics target with multiple systems. +.. option:: xnvme_mem=str : [xnvme] + + Select the xnvme memory backend. This can take these values. + + **posix** + This is the default posix memory backend for linux NVMe driver. + **hugepage** + Use hugepages, instead of existing posix memory backend. The + memory backend uses hugetlbfs. This require users to allocate + hugepages, mount hugetlbfs and set an enviornment variable for + XNVME_HUGETLB_PATH. + **spdk** + Uses SPDK's memory allocator. + **vfio** + Uses libvfn's memory allocator. This also specifies the use + of libvfn backend instead of SPDK. + .. option:: xnvme_iovec=int : [xnvme] If this option is set. xnvme will use vectored read/write commands. diff --git a/engines/xnvme.c b/engines/xnvme.c index 208d917d63..bb92a121dc 100644 --- a/engines/xnvme.c +++ b/engines/xnvme.c @@ -75,6 +75,7 @@ struct xnvme_fioe_options { unsigned int xnvme_dev_nsid; unsigned int xnvme_iovec; char *xnvme_be; + char *xnvme_mem; char *xnvme_async; char *xnvme_sync; char *xnvme_admin; @@ -109,6 +110,15 @@ static struct fio_option options[] = { .category = FIO_OPT_C_ENGINE, .group = FIO_OPT_G_XNVME, }, + { + .name = "xnvme_mem", + .lname = "xNVMe Memory Backend", + .type = FIO_OPT_STR_STORE, + .off1 = offsetof(struct xnvme_fioe_options, xnvme_mem), + .help = "Select xNVMe memory backend", + .category = FIO_OPT_C_ENGINE, + .group = FIO_OPT_G_XNVME, + }, { .name = "xnvme_async", .lname = "xNVMe Asynchronous command-interface", @@ -193,6 +203,7 @@ static struct xnvme_opts xnvme_opts_from_fioe(struct thread_data *td) opts.nsid = o->xnvme_dev_nsid; opts.subnqn = o->xnvme_dev_subnqn; opts.be = o->xnvme_be; + opts.mem = o->xnvme_mem; opts.async = o->xnvme_async; opts.sync = o->xnvme_sync; opts.admin = o->xnvme_admin; diff --git a/fio.1 b/fio.1 index 134aed541e..eb87533fc7 100644 --- a/fio.1 +++ b/fio.1 @@ -2631,6 +2631,28 @@ xnvme namespace identifier for userspace NVMe driver SPDK or vfio. Sets the subsystem NQN for fabrics. This is for xNVMe to utilize a fabrics target with multiple systems. .TP +.BI (xnvme)xnvme_mem\fR=\fPstr +Select the xnvme memory backend. This can take these values. +.RS +.RS +.TP +.B posix +This is the default posix memory backend for linux NVMe driver. +.TP +.BI hugepage +Use hugepages, instead of existing posix memory backend. The memory backend +uses hugetlbfs. This require users to allocate hugepages, mount hugetlbfs and +set an enviornment variable for XNVME_HUGETLB_PATH. +.TP +.BI spdk +Uses SPDK's memory allocator. +.TP +.BI vfio +Uses libvfn's memory allocator. This also specifies the use of libvfn backend +instead of SPDK. +.RE +.RE +.TP .BI (xnvme)xnvme_iovec If this option is set, xnvme will use vectored read/write commands. .TP From 537e0d23f774288f07bd6412a4c116d01b40f247 Mon Sep 17 00:00:00 2001 From: Ankit Kumar Date: Wed, 11 Jan 2023 12:18:05 +0530 Subject: [PATCH 0377/1097] doc: clarify the usage of rw_sequencer Update man page clarifying the usage of rw_sequencer=sequential Added few examples explaining the offset generation for rw_sequencer. Fixes: https://github.com/axboe/fio/issues/1223 Signed-off-by: Ankit Kumar Signed-off-by: Vincent Fu --- HOWTO.rst | 35 ++++++++++++++++++++++++++++------- fio.1 | 47 +++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 69 insertions(+), 13 deletions(-) diff --git a/HOWTO.rst b/HOWTO.rst index 0a48a45377..17caaf5d38 100644 --- a/HOWTO.rst +++ b/HOWTO.rst @@ -1176,13 +1176,34 @@ I/O type Generate the same offset. ``sequential`` is only useful for random I/O, where fio would normally - generate a new random offset for every I/O. If you append e.g. 8 to randread, - you would get a new random offset for every 8 I/Os. The result would be a - seek for only every 8 I/Os, instead of for every I/O. Use ``rw=randread:8`` - to specify that. As sequential I/O is already sequential, setting - ``sequential`` for that would not result in any differences. ``identical`` - behaves in a similar fashion, except it sends the same offset 8 number of - times before generating a new offset. + generate a new random offset for every I/O. If you append e.g. 8 to + randread, i.e. ``rw=randread:8`` you would get a new random offset for + every 8 I/Os. The result would be a sequence of 8 sequential offsets + with a random starting point. However this behavior may change if a + sequential I/O reaches end of the file. As sequential I/O is already + sequential, setting ``sequential`` for that would not result in any + difference. ``identical`` behaves in a similar fashion, except it sends + the same offset 8 number of times before generating a new offset. + + Example #1:: + + rw=randread:8 + rw_sequencer=sequential + bs=4k + + The generated sequence of offsets will look like this: + 4k, 8k, 12k, 16k, 20k, 24k, 28k, 32k, 92k, 96k, 100k, 104k, 108k, + 112k, 116k, 120k, 48k, 52k ... + + Example #2:: + + rw=randread:8 + rw_sequencer=identical + bs=4k + + The generated sequence of offsets will look like this: + 4k, 4k, 4k, 4k, 4k, 4k, 4k, 4k, 92k, 92k, 92k, 92k, 92k, 92k, 92k, 92k, + 48k, 48k, 48k ... .. option:: unified_rw_reporting=str diff --git a/fio.1 b/fio.1 index eb87533fc7..527b3d4605 100644 --- a/fio.1 +++ b/fio.1 @@ -952,12 +952,47 @@ Generate the same offset. .P \fBsequential\fR is only useful for random I/O, where fio would normally generate a new random offset for every I/O. If you append e.g. 8 to randread, -you would get a new random offset for every 8 I/Os. The result would be a -seek for only every 8 I/Os, instead of for every I/O. Use `rw=randread:8' -to specify that. As sequential I/O is already sequential, setting -\fBsequential\fR for that would not result in any differences. \fBidentical\fR -behaves in a similar fashion, except it sends the same offset 8 number of -times before generating a new offset. +i.e. `rw=randread:8' you would get a new random offset for every 8 I/Os. The +result would be a sequence of 8 sequential offsets with a random starting +point. However this behavior may change if a sequential I/O reaches end of the +file. As sequential I/O is already sequential, setting \fBsequential\fR for +that would not result in any difference. \fBidentical\fR behaves in a similar +fashion, except it sends the same offset 8 number of times before generating a +new offset. +.P +.P +Example #1: +.RS +.P +.PD 0 +rw=randread:8 +.P +rw_sequencer=sequential +.P +bs=4k +.PD +.RE +.P +The generated sequence of offsets will look like this: +4k, 8k, 12k, 16k, 20k, 24k, 28k, 32k, 92k, 96k, 100k, 104k, 108k, 112k, 116k, +120k, 48k, 52k ... +.P +.P +Example #2: +.RS +.P +.PD 0 +rw=randread:8 +.P +rw_sequencer=identical +.P +bs=4k +.PD +.RE +.P +The generated sequence of offsets will look like this: +4k, 4k, 4k, 4k, 4k, 4k, 4k, 4k, 92k, 92k, 92k, 92k, 92k, 92k, 92k, 92k, 48k, +48k, 48k ... .RE .TP .BI unified_rw_reporting \fR=\fPstr From 44834c57f944074684c1b58604cc44199cf5e633 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Wed, 11 Jan 2023 15:22:40 -0500 Subject: [PATCH 0378/1097] examples: add missing fiograph diagram for sg_write_same_ndob.fio This fiograph diagram was missed in the earlier patch that added missing fiograph diagrams for the example job files. Now each example job file should have a fiograph diagram. Signed-off-by: Vincent Fu --- examples/sg_write_same_ndob.png | Bin 0 -> 97793 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 examples/sg_write_same_ndob.png diff --git a/examples/sg_write_same_ndob.png b/examples/sg_write_same_ndob.png new file mode 100644 index 0000000000000000000000000000000000000000..8b76fc6c76da373ba20832a85dc3a01c4b97b16a GIT binary patch literal 97793 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 ENN%w7O4M=8<;{U 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 literal 0 HcmV?d00001 From 0d672d86a391ac5079518c2dea3f611cd048f842 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Wed, 18 Jan 2023 10:43:18 -0500 Subject: [PATCH 0379/1097] tools/fiograph: add link to file formats To the fiograph help text add a link to a list of the supported image file output formats. Signed-off-by: Vincent Fu --- tools/fiograph/fiograph.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/fiograph/fiograph.py b/tools/fiograph/fiograph.py index 384decda18..8fbd1909e2 100755 --- a/tools/fiograph/fiograph.py +++ b/tools/fiograph/fiograph.py @@ -274,7 +274,7 @@ def setup_commandline(): parser.add_argument('--format', action='store', type=str, default='png', - help='the output format') + help='the output format (see https://graphviz.org/docs/outputs/)') parser.add_argument('--view', action='store_true', default=False, help='view the graph') From 24ff9393bc3e85c8fb04ca4c2d47adaf5f688e58 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Wed, 18 Jan 2023 10:52:31 -0500 Subject: [PATCH 0380/1097] tools/fiograph: improve default output file name Instead of removing all occurrences of '.fio' in the job filename, only remove '.fio' when it is at the end of the job filename. Signed-off-by: Vincent Fu --- tools/fiograph/fiograph.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tools/fiograph/fiograph.py b/tools/fiograph/fiograph.py index 8fbd1909e2..03c2cacd2d 100755 --- a/tools/fiograph/fiograph.py +++ b/tools/fiograph/fiograph.py @@ -294,7 +294,8 @@ def main(): args = setup_commandline() if args.output is None: output_file = args.file - output_file = output_file.replace('.fio', '') + if output_file.endswith('.fio'): + output_file = output_file[:-4] else: output_file = args.output config_file = configparser.RawConfigParser(allow_no_value=True) From 69bb4b00b20047e7b5af9a6e35cc872cae605071 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Wed, 18 Jan 2023 11:14:14 -0500 Subject: [PATCH 0381/1097] tools/fiograph: improve default config file search When a config file is not explicitly specified the current default is to search for fiograph.conf only in the current directory. Change this to try to use fiograph.conf in the directory where fiograph.py is located when fiograph.conf is not found in the current directory. Signed-off-by: Vincent Fu --- tools/fiograph/fiograph.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/tools/fiograph/fiograph.py b/tools/fiograph/fiograph.py index 03c2cacd2d..86ed40a810 100755 --- a/tools/fiograph/fiograph.py +++ b/tools/fiograph/fiograph.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +import errno from graphviz import Digraph import argparse import configparser @@ -283,7 +284,6 @@ def setup_commandline(): help='keep the graphviz script file') parser.add_argument('--config', action='store', type=str, - default='fiograph.conf', help='the configuration filename') args = parser.parse_args() return args @@ -292,14 +292,26 @@ def setup_commandline(): def main(): global config_file args = setup_commandline() + if args.output is None: output_file = args.file if output_file.endswith('.fio'): output_file = output_file[:-4] else: output_file = args.output + + if args.config is None: + if os.path.exists('fiograph.conf'): + config_filename = 'fiograph.conf' + else: + config_filename = os.path.join(os.path.dirname(__file__), 'fiograph.conf') + if not os.path.exists(config_filename): + raise FileNotFoundError("Cannot locate configuration file") + else: + config_filename = args.config config_file = configparser.RawConfigParser(allow_no_value=True) - config_file.read(args.config) + config_file.read(config_filename) + fio_to_graphviz(args.file, args.format).render(output_file, view=args.view) if not args.keep: os.remove(output_file) From cab9bedf70f34142b70cf97bf4d8c8df57a6f82f Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Thu, 19 Jan 2023 13:10:22 -0500 Subject: [PATCH 0382/1097] examples: remove test.png test.png doesn't correspond to any example job files. It seems to have been inadvertantly added to the repository. Signed-off-by: Vincent Fu --- examples/test.png | Bin 30141 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 examples/test.png diff --git a/examples/test.png b/examples/test.png deleted file mode 100644 index 6be500293c45e67385cb212e13d488827130ea8e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 30141 zcmbrmbx>Db^genpC`l0zkP;(o0R<6}6a=KZyGy0JOX-wOiMx2e z^Zn!g?%bI>bI;4X2%p0_XYalCisyOO309Doz{VuSL=Xg9N>Wq_L9R6+2nvFJ4Sq8n z##;d2P+!VOh$5F)|0UIBL?8$iA|)!M;vBy*?ZT_NN``DN&rLXu#(6|P`2LPQGBrWk zIDKGOq_fn5JTfa?PC4%Nz$B0l}eZuo3P!N8v z;ADW~10N*+ZM=8w>QAP9xNcniLG^Vzbode70c{Bne#BeDp^!)O##<|hkzGoYiGDL% zudAdqFg1mR)HrUQE=KDIIb9yl<6ggh{Ud4+-kL7lW(aqx!RvBV-|Hr#r#G{`z0E(+ z_70n@wysW7W~R=Kn4DbBQe9Q`!1)t;GXG7u-Ko#OfYRJp*Uz7$64l4YJM+mRfk9Rr z92H!4OK)v#+B-T9m7L*RbU0VR$=JlCf7Sk_+vY^s#KZ*B*Vl(fNGNt^E&$6Pl@~_4 zww{}tD=i}vy!qpYI3pwD%gL4&qmC~Ot&WzK8)X)w1A~L>YikP&3vgO|Lc+<(NxL`< zE|iSp?^IPpbhJdqpsI=r78X{zd_s11c84i(?ow~!<=QagNOZ0TzE*dj^Y-s1IGy#` zv-S0LbGEpwtkp1q3wj!w!Rb0*ytV5w=*jh)G6J<3@(C|lW1l~N?&#=fHLI$w?sc$6 zI3DEZCo;)>2Q%PR>OAwrK!5-0zEfw4L|9PA@bGX1t=wyEAxTMkDypQTBTC`Sir+3U zLDiX=ng5Qqwx+5GkW7_FTz|qjKe1?)u1u6!AW2C{zgJhYGBceo&Q2N|8#gvK+9T*P zQd7|}Fw)Y~QBhH^d8ei>_*_T3Lr8e*)-C-8Pmhz`MW?M9hY2z5<=(^>va&k$?p!;& z1q~OUBOw56o%4+lMWDh1uJd|{} z#0`sjKlCkkun@Y#U%0}3PlJ==V~x(i!NK;#MKoLrynycRZun!-KRfe3V`WX<-FbIx z#|jN$vjc8cUWmNUs+YPYVWG1Hj=>Oboh7L;?0|ShxHL_qU4|; z$w{jbO!VZ7o4sKFVfap}c=`Gb%#R*TWc#M3J}7!>Iac`V*DtWw($doS_;?ueH+~ON zC>kRYY~6`{Hd+2-H^!q{$2QunW<@9T-@zRO-)TJMk@bPVqRPy z1pz90dPz$*Hnva8cag@9j`y{EyFCd!rlzK9OsT1b>B~QbVzGy#f{CBS*RNs(IC8D7rdOD zG4z%^Xav+l`rsxM1PqLgTiXs+GgS5Uy}Dwas;Q}&n3!CtfAGxjOC>z#|be> zNpwvmb#)09)4h7E=J}st5@8f_@+UVC8=D(Xr8_5quQOgkpnAwc6PSAYpbZJh>eX+O-=1s z?R@l?yz0^7N`Ff5AR$<}l$4a{A6$Vm!3UIZit!smW8(+3v_B@CO-vSHQP$Si(^6AQ z%gWjZDbSNWG5^>9UXXkLe{<0Pzdii_+Xc4mvmAAFbcBS2SOUPWS;3ltnZ}q*h&kbzRcV%GD7+yuV^E-N-|3|>HAh|B_ z`t@rVnUoX;CFShzCcmE_$vYwXWXr>nfM=hn_sGf4=Jq(XM~(-E{6A{D9sQl|Pmu`T zH0g|jH?>3uvz-Eo(m##Pm=}7xt_WC>XluI)R&hZg7YUQC$FlkeE;FY=ElZ-8k*qOE-o&I zhlirqlEI>E7o|Ns&cT!07d%(HMf4aPnMT>`NN}&n(8x%2P0i@&Xhgzo4FC7lq*B*Kga(TxN-yzPFq6*9pUEYh7tSv`XWgM1r1h{?A!_Uj++w@3J~wGuwL+# zh#)*Q?6lq8U8KMUQYCS|NcEVgzqUS!JnlfXz?*_Adj_5GgWb#Of7OLZ5$lZ z6zGwpv@|gyIB>|hzmzH8_d&dG-Xb!<*-6OBp2Nhz$G|0aJq$^d|Jic$G$iJ3%fRmZ z{_1n1A49fwcHE>x)Lqo4b+B0YWG}#5?Jh`7Vzw$K#)k{i;vrOYbfa}68Zy5~zJTjC zruKtOuvi)FZ95C&?r2St|MsLh>J(AGH3qe-+en z7b`P+uBS7FmTpf%9J9Kroq>_VYFvrm)14_pL`2`8OSC~ErN5)218Hn-etCAhW5tLU z41vY?%^POqOH|Zn>+^qG4bv8(gif9Mm|xhJDcj{`q@{lek05F*Q+BO?v7Sv-$Ze$6c5fx0a1Jzo0Nb;hR_BY_9&o#t%Z88pHk3Gbx`w;G~3 zdDLB8`b~RO&gR3sy1Umo$?v?zh83TOY-XY489E!ORoi>Quk2RUVA$YprKR8J=l?A= zA8c)Hg$?^PJUsl=lFR@SsDCOk4y?A~w9qH)r%;3XVR z_g5jfuC2X>;kmi}i;usz%dD`UkXewCtItA4!cb|gzuFZ`$w`j01_{zyfu2W=c|_ND zv0a#;d*nG>*q2+wb>$1k{@?x^W8Zr$bH3cQ5 zI8K}UMR~co_wL=>SXt3fR1A-Zushy1eEz)5Zn;M;=7~?HV3(;u$#eFs>}=GVH^GtA zd0p~@jj!zQE_OymMNLm)WX>8sF*tV}r+aauCy|BqdET-;XXfRfd83O9x8S(A1(pWM zTbQirayftOMOOr3MD`uv2`&e;*+@gj6x7wjLPENJ{R;Z<0hViSex8i&)u;0E^6+qc z3l9s6bq?zp|99_h%ZT^HJf-31|K{%0mn5`NA)eCztMM1t=PTn*E-z;`+XMB9$i;K1x!K61S3I>ZE@8d{2(&bp+zwZ_oKlaKhNO}@0 zduYMK$%zOW7#I|^`ME0U=)~98*JozZ?-Umm*-Vt0LpFgVWn@Hu{@mQZqEN7&l{gED zLmX-O1yQY=`l~ifu3CQ|*BgE)fB)KEp0#)_%llMv%4e-Ik&=>bsQ+-#t*3ztf-;2K@U5L4BtbEQ{mDM*`5%kjIIKo;ap6fRn%ZtQTf@DgQB>-BeLk!qJJc#dw?>*!P;7L%@hRtfOpf zvquIk7VJ6E?Si)2KR=Z5JYCIzj2#M$xHwV<1_l}$8Ulid@^x;i{mAU>O8G=0{in(n*YzcX(20o$$4RzYMfgI5ecC=Sj^>7+AL&zYRhh~M?$x!N;GrjfBc@UJz1YV?eFg=;deit zsmiK7>-GsfmTAMH` zQirvCbyE#<1`gy`=X}{~C>p7@{5yXA8msr<@oS3Vu%JBL2L0g{}u{g@sRE@;rNH_wR5M!k~7!O#KR{2^RM9@_K0m`K9PJYI=I-xz=EaamWAuefaRg6!_ir7^(}qkVs2TD^&xnG73I+PU$mdat)=gJcyX#FBmV$5JD2kZw-xs#* zTit*6?p=lT>}LjFA0LzxeA}9=ECx73)j~^4tCc-c`e8*$Nr~Gg8P!m5K2)&~#taRq znWg*{1ky4yf34!x(?FPCuf2VPMD-3B);2V#^HUbU)sXd1~}F2`#3% zSy(>Mn?G48tHiXs+Iik?dD?IR%Bn;f!+FM_!knhK7ce zl!=gP6)#nU+@n@=wx$zCw`u$lN(Ln%p@cvMu&#>U#qNW)6;veyfScGq2?j`o`Moc z;Zf#jrcN~^VSv+r^W(>lw6wHPpUvA-t$ek!zd=CIm@I)#O%x+y?*k6_^XDta6Q7iN zgL5C9dcVq(ozgen@Kan|HAFmZ?MX+S)H(ycltu=m|_3Tm^psQ&s{Yz}A-AW!Kol!(*~AA|hgX zYRb~eikM034go>SDN#gwt;fIeoSgM949ZYLd!8SRe*gZxU_!z}Uz22PqIqLnLH$VI zPzXgf1`#;F(cQi2_5SK9F&Wjo^OzoMAc?qq8#CByYTGA$UXLEIFn-$P^}_l4_lf!K zTpbK15nO8O-y2xv<-b=?G~B=4|S+)E5{^!4-OX}ki`2CHnz}k3;Y|Sn#QTT%+qd4S51hA=32iO3B$Ibd-CqkG z7(exj*1ttz)_)h395|#B^zmJ9;Ds`EY#iR^4K%+DIy^tyWgMNVYr(;9ed{cF| z!o&Mm_w$xThDLnzHqE%27CYbW(~F8CoqZx2cQ?jRV0O)Vn<>2nJNsJZ+F$`%tQ&-+ zF|mGwnPERZ-{8GrIR7IQl96a;T}*6j-C|>mi1w?hh+|@Be?R`Y#YiNhI@oq@ULGX2 z8=ISubk#vI*4L*XARs_U7+G0asa^4=?B5Q+9*_`9%E-96xy@EPti#5HVvj|?-rmW{ zEUz4poL~Yvy=r>~PR_C6;Yi`+T+I?%K0b1!qqFn;{JgEL?Pzn-X?-LwDT#`Og$0Tf zRzA|giz5MzO1z$LH|JIni;>2`3>bOLSEpUT0h>mMn00;o6f}x&#K^9U^uYvm#obiT z7gWI0&=!%x7@KTa9V|4t<^Rq%kuoz=Q0Jbz;fi4=QBW|7x%u|tJMD_!V^0;7{i0d^ z>?H?oP8!aMR@!6u`ZWy`(%;c2K^+)GfoE%)rEG08oaL`{xaI5H*m)~8&9D)dPxuwG zYPL*`4%^4bgH;Zyb9;Mxb907u{mTsvusaxPkQG>$i^~a23V?&-C1wNxP^baU=z3Mg zNJO%8aQr(t+1cCs+1mQb!h#?GoB>3v(R^J-1_sEPA7TQGNqX;|tmWk7WM^lm8(iz( zzpe3-Kj zT&;H$>2iKuvS3jIN-J}HEzJR7HU>u5cXvOGsm(kA7 z@CdGh`F#AClyhq|Uq}iAEKW876Ml7g2xsf*{ocZLJ47NeWof&liXQd12MEe%-VGq8fo{m+?Hk1)aF6Df1`aXGAo;YJ80r8}-j7#=D8sR<4> z14H}i=jdo+WX0YUjne!`7l6KzXq88*qN4r>iX1AB2oWv0JSsdpI&CrGsKTDyJH*7+ z+R6DQ+{sY2Z%$SK@>=il4`A-0p&@|g0hmRbHTj*SvvQRDOQ&h<&St)|7Ye zddHDEyR|s6?(=0=*Plvug`-(he0u2h*fy-U$cnx_&J%lu3BkI~OF z^z}Md?%H%QGcu<1zcT)lqh8=zP7J9W;(WM4;@9+h?v#a4Mp6<%Zr;3E`~YU4x!Kn} zHYNrg@k7PFFC!jC!RNaF3W111%&Y_E4*xCngx3`dz? z?G?^5(|jB^F==Go?;8}s1?&Rp{f6R)@>m2H0|PzEl&kv)h9t#+5hlQ&bc? zJG;D$%w(xKfz5Z{c4G=I+XWy`Y;A2@#qgT*b!&oZQ4rY3K?{vPexM>iY5?k~pr$5X z+u7X>WhNE^NjV>xm42z~^9K(aw;Ea^8K0P9fh)$x*Pl#(hl(`*X-PCCWaDTlx8h3_ zk(BW(dYYQnlrEPjuL6;60ez|l&DIu%UT8rKJ8ogoLmJ1@5`jDYX+(KN;`)0D)x-qx z-ZETqn`PZIDS3DJ@)`;vVHEbR7ISr&89mI7(k$96M>_N6n)hiEN{mPs#5X~~-%v^b zjz}Z%@k>O+WP_K$x=>xl#%x34hY#zGKG$JLPjivTD z0A;c7ECHxlgU$TA^mJ3eSAe*Xk_z_qZ3d)psr&0st?r*c&3=TCtgfzJp6$IbGMa~x zwlp^faoq!7O~!5s2Gi5q8}{LY%!?Np{jVm=tsolehDl5Y)_P*Ud6Sg$Z!G1fJbC>CJmOlCb8eBbdzZ2;EQ{#eOU<|*iaJ@|)JSnkp){#M zcFMgkIu&eDkUz{mQl8hyP3hZ{N*T;$`|N-gsb%o8`!gN@n?G(P@#1UU>Cte28SzEP zzh}oLZ%=+Y(k`b*rJ_E#86U)B!?WpVqWciJqe@I3%++kbx;wiJmk~Z=%ss8%KkDpK z?H*2h+m1IRaZqxCm!F>xNdy$_R<{7t|Mj`bZh2yIvcdi2b6}u^zJ6kKw7Q&}71Zc} zWRaK7udh$Oj+BD+01RT6S?=Zi2R6ravj0JR?U1Six&^r}%^;3K>m$4-)2$v4*G2tI6g6W~lcP#La)Q^rzXY?NTbBE*lQpc3g67d< zilWNw`qy$Rah0{4yUN#8d^QQndnry=FJ_A4iXAvc+m?QOCiHg>IFuWgAovbzaSaJj z4QNG0LR2WS=+np8CqAiD-;BIT?v>)wvMhyOdgMis%wLeWhzI1K74ch3c+SLGsQ%ou zGOmu%9f^0ilr!1>(rH?nn&B+LC!b;W=VhC}eqAmtF>%NW6w8Rqmt6Wg?~99BE#5qB zG`H|_RDX~){{d%hZS(Q(u{YEL0)<$7E2}W}ipy~Js|Zzyb)IqZ+3Fny+jH#<8Y?#v zHuU-@`+~{d#jNfEBA3Ij$to&w}i6iO$aE&-EDiw1F zGj#AC9m}K3^0m$RshPe&$XWwSvHKuPwU#$@$Uj6*n0ho0Xpa!?pQ3wtK@R4xPN9DQ#vJ3Jzb!v}~QG zmh$T?Qy1GqsYe`y$XeXm()sg(IU}*3l#eepJl{?kPsZErmd(LI z%%ON(e7v}^SJF!xe-Qh}j`2Y4wNy5d-1_%DF$N)q@4{9_R-PvHzM0L^A0mIg3}cX? zFDPAEpe6lBeMtMBmV7k)j-_4V6XEk4OL&&;0D*LMeTj_Z1F~>^7?W+=#2B9NS4Z>7 zp0`M^xm17@vR%^FD+&<-3~6zx^U~?Si`+{=PAL@fD{l=Gp-+#T=h`t|auLx+wBJal zZ&>M^i$=B|Y+)dl_iDnglCU6k$rs`U4uj;Us2pDRTLG6UGy%@PGn!?zzuzzc%cuU9 z#c1>HVp7U=&V3I-RS!a>G|naI!OV%#xavbyP5s-;Ow0|HXKAOA2a(A3K?I2n{|(Zy zCxHeZ9BW3m`j3#ndKUb7;_Kec*}^1$F28*mSQR2gDjB8{%V?-j5dqS3`iBpOvuOoz z1(2Y<57z}9UCN7()w|W2PVinCv;YD#WPO*B6s->Hlx7JR_SC{O(Z(}n_FL}wl4b+y zAzDphCtE~EuzTik+Yp2Fmqf=ee*2)IVG&}rU*~;F>024!)Y&aFwKo1V9@htz{fdS= z|DjTs#UJKQvB(?eZdx1=chj%kd3#AnS6O#C@oef#lkyqC`K9N@QA)hTQudOPuhQ_r zaMhOG{N6lqmo2W5$_SjNq&?Sr}Vk_ z?+@;WHV>IGM`itDH)keK4)&<^qH8~Ph1~G&a*9PhX~alQDR8TJ(pQyp2*>*Z6p}fM zH%GXeu((dT5T_g-p&Wi4VWXa~j_|a@wcS$UnY#1$nO%=%ZP||J$ay2Nk8xFQ;fBIV zGSb-6HqY_X`ru%A=jKjwfk?1Zz==OvCk|~naopo6XSex-(b3>&eJ#ql^}36r=T(?D z5ne|3!`y8Wq>!KT2DSVZ(lwNL_ZOLHrMRf6IFTxG`}<#77MjmuyCR7ggN+0XV^3IZ zn{VK9^n&GDnMp{aOUB20?8=>dZEbFSA4d4u_jC5`Y_Q>lyl-@eP{JY=EaD|GVl2Gd< zubvKS%4m_BM8`2btf*p`BF~*mqE1VuPU{!aN8Lq*ug&^2n!FU6ycV@qeIb1{eSNit z3OAulx9TVnyv_&o3=W=jglk*!5M>V5ROKanT$Nm0s-mKZ0AfMMzIS~)Ry$Jy@7C6p zTt|PPY!44OW;lF&d=k{f&Pjkzv$Nx|U%>;Ymao03$!Cz7${+O(NCW<#Y;p3%N;{+v z0Htt||8T)@` z^Y!haY<%a6w}gjBMpk3=^CKWR@@NBEiP*Vb!5#!PH1-m2T$nSio9FeSzEcojwO{Ge zt#O1(dULD@h=iGm30Ejk)6&x7Kd!rh&Zzar4_6l#n?r?j020Z092b_CONb5w8d1H+ zay1Ib^s2UOx$d^Kgaq;kJ1wZY^VfS&J@H3HMM3caGSfdWaBI4TgPq;6*&hSIeh`<$ zf3)}w+p(*=8_>^uknA+wrQmm8SzH``6u5HB2OC+6Tbj(UQcugWeey0a7Psz=b6E%V zsjvY$_JgUZDIiQChC4VoK=rWF75fZe8ZPo7B(S#=vHS0km+1e@~8&XqK1DG(Z5TMW?gEH%ifjV$Jo5qn8pP0D8VO>pJ{0B^_m8IoNBcqm< z7U0M=F6RpJ^FIa!Iqxlnf>fxes3A98sIIK!RQv-{pQEi=G(D<1LxT*D;QwKLsti;#HZ_S)U_w z*0X7pXlB0D;V%%fsj;&Uu2{Nv?%h|SkM?IPb#)3&+~<2{U)10to=-jT8Ss%LX=R6}PX9X$4{OpDY;aH$0~5wl!a~9x${q!d z+*<`MuB@)wKde`W^%N?uXt^6V;yGD1xG*tx$dgw#V^iDh8BJDxW@o zejgG7LWlE>eo+Ja26WOdk%QQ0W`HY#eon01$j}gej z_#!P4V6UqxNth2c&IXb8e61?1C|+C2{r4)}O^uPQ}uHd3Ys4_cd>uOU5&0+fV= zHxaYmZx3h4pP6EXiH>#aMbovIOZc4(Zl8(2rOyFBC$ z)zNs!arX|v62aeL=DAyQ40ogp9DV9<6BCaXn{;AfW2@(BEzZr^&b4BI+DabndEdS( zj?MA*ALR^raH^Bz<5@X5keN~nc>t=B_$<*K?es0K=cLr#9o6ZuzQJ^b%1rmLNQHDnR0B|7)l!+yp$8T_+44Ly*&T#>~zlVgG;4zI#$AWx`NTZp|>oHGbvpX zx6X*BwAxwgiKm<^opmH?F!wNKT|DU0=uO(v2*cR11M6BE!77 zl<%45kD~!wy*GNJ{rP7q7ePZoU%ub@XLM-JGnne1w)C~z>&2o|xebh3Es#Vm*X|w!>Ge|MQ1Je0Ya_(RH#9L3 z`=Zm}Io;Fqv8*SGN&Cjln?TwC(e(Daf4gzdH$I@HIhmM3d36htlWn$U_<)i?#!Ae3 z`;tYjtU*_o>CvMOPZ|Ik09FA#q&}uIK*Y#fV2uEU4i5l(f=IGCGF#)sg22_sCnpIW z0!Cd{TDrfpa{@Y9pbUeA0XqdY89y07R~~L|R5Y}I|Na#~r4GtXIDkO=K*V&vN7h|V zf_y8D!s*Ew6>$LLODliaGm5t7Ck{16iDA~ih=lZ-0?8r^ns%Yt^q6Q4|l!6u&5h90n2nx!gT|XL-}xNPt4yw2if1 z^Xb$VmJsVPJp^5g)Yy8cc+&2h*58u-;O!HW4i;f|rg=u%%U3Nl_lHcyOHvAq-YOM( zNE2f!))0-4J&yaxJtH$4Of1HdUt76bewtUCS(}nfBNsH1eQn)6 zh}Lz`w}Ci@J~p>h!{y{;v9h-E$YHCui>sx#MPF6l;UF=m-8#a8)WSHxgl`K`*etpni6L{2&qmL}^6Klg#B|y$z7t!FI>;CilXkS8pP8 z7FSNL8>)(4P;~;A3p5*@m-hF9(HbsMP`!dqBQz8{RuBRB8MMG)>ge3rX zz%{}lOB><5#@~QLIq;&_90M)z!uJ z{JDu774lmq@DTKa07fXLh=>1lK<;wcruzA{fYKUrgvU%w?Z4YoIB{`%L0AhIJSczW zL9JL+LRT}?AANWme4dn^i=LuMcxe6qJfq<=Syed#TkRhe*_>(gVj@~J8>$*(j8VzS zZ&3tazPWn5S2f|BY}XJL+=8-i4yO&PZK`b$E;l&lp7y8s-T1!kVQ6Zeo|VmD0mYj> zcPXqgaXrhuG_Lo&`PUmI%ffMJaiYKJNq4*`9(p~#Sx$EKfW*B8&pff^F+JlCoD;C! z<|iXR)|W<)t-eAkPkV=%47KmdqVBLBn`~G2=Q**iyQ8B<;OpbDbkWrUk_vdk_rlnj zc%9E1uEPf&({8)ULM(pwu*eT(PwhvRHUE^PHOVmYtGT=Kpk4cXjXiXl&9blX*w-1! z%rBs253)kgWo~J^8Cen31=EN0eB_UI?irl0o7r~JfA1gIOhZj&vwD&iKtq%*o%y-R zKi!|d$Ai~uyOd=!aQ42J+rd`O$D_z2HtU;;0Uy`h=|D;b;c;tWp%kRf`uejhx-sIq z!!=y3AUjO1d$tGa17jRTP(vWrGqpSWmUPpm315;@Q(I4?K!Je3$~t7>Gco1iTw`Wo zK|{#N6L8iF@7E_)SG(ref&df&WeAlFpza!scr3wTN0t)g8)+0r7xQk2LBefGg|w{C zT%zNFtcbJn;g3BnH<1=r1|g~4yrtmGf9<+zZQUqyWNWm_nMy4Brp zqDK(#_gYaqX&(IbY|mef3-C=UqPeZQDX8d{ZvJIQjd(g}umts>Ju2U0rj4V(2B*`e zz;Rrt#zw<53+Qrw_O`q*>znqQVq9n3d^ZMwcJR)JMfIhBbjN7*=xoaDU-Jd(it`7U ze&xac6=wRH`h@gUF06(pw?npZu_^6S9j?V+*Xqt1is)y|V%Vu%?H9E!9xf^yE=#jj zOesmZeu*(fQe;;-x7}3iHyJAG{iv|fxGD4t8HyB&_D}1F*Ks54v@Z;n#$O>FhJe#*s!>5mCsi*7t9{RHED2`L?;D z{Tc5I;$6%yIEFI<8aTJmX`y&Sf!Qz3^@1lkSLVMRXks zGD3FWc4o0qzJZF?;kF?x<7`#mXmaTOkAU=B?Jb*k>KMr0SfP88Ozoh>HrD>O_o>Z& z`*ii?$+%$K?y}oCzoDoU(F;%+QJr`-c`wl=KKHlf!ddbkKlH?3 zl8fPYFvVmAEmL^AOPMuvf4Dc?2CG`PFZ z`f78ftW_#KwL%JzsLt-_y&d-*HQ#O{4s{L{uT|Y^-~3;hfFvGJV)*(Byi{;71}Klv zT{!|3z*Q+I1VO!UyiM)$brEK)JpvB_`vx(&s*Vn?+u;TP)}Iz-X`k`(#>Y?f6;YVq zK%lh%Lf!4#x1p?EU0aim67W15o0_WKWV3{FMog>?Wc7jX-pMEOC(z5v%0d@GHb^T# z6QXGcikrc7IU42^HVzI@^kX9s(8X#ydwX#c6XjPNC1yXr|MK3qQW!E#C2xJZLaaxO z>_QKJs4HnI3h&sA%=Zx|4`OROy1khjHdDWudF@BX<9Ro;S+*AQR+BHL;(O)mlZe3= z)6V1F?Uv;Xg6iphC+51x9`=SPm;}fqju9CVZ9xEUq}n5$oT!rMBCnZ86)c z*T%o@av*{_v>L3oQaO&I$Mw!>kMr2Dk$9{Gja$p4-3P%tQcNe-X(?*qctlwGg@->T zzGiU*7Z82A17OKs%1#)$drEmIl_Mt?mzcQto0-}Z(5|$y=YA5U5Uw2?%JQOCcAxzwTpw z(en`t-h9^(0nVA8U-F^pQJQfir@SiSqUrl03^%oYI2~TZdiAi)nDfdz#plN}3q%T3 z@N)3e@h|JjPw#NQVLtp(K}U+>t$SE=&x3rLxiTS`OJpiKJm%}&f3)T86~Tc6Gsp51 zrE>$AHAargf%3zVgWZh1x1?~)zjIiXPD}RK-SD2ve#!TuF6*80iSzM&Wg30rx^#>| zoi5!Vr{^YYC)U#YH@&{Xqdu#Hi+n#zl)J#2-#UB8$A!-|5=}`aaINvf z9I=(f?(X(yaw+seH(CsYG!FPuo!=$qmoLv=LRXJ-&a@5Yhxo9$CfoOgPZG`ijTxRGm{q+3jl({9gee%&QK7G z>c{z&fEF7vi|c4OS87IByS`$}7AQ+KbIAEz?Lo~8=t-;0OAqw90SshpVgk}-&W8^d zfraZU<`+HUKK(Hws}XT(ovj}-J8Z@@Z&8+8na%lt%b$05oMimwV~pdIg2b)*dbcBo z&OU`oTI|@NoisExz5#Tk>uIdAqV^yB75qd3%EUp|daWpZt$Qy|#ufv%ZsAVxy`66h zC1cTYtZAD|a7+v<*y}&=S@%fM{-GX2RLtvNX@3nV7qLWR#FA%_=6ZXCl>Uz?*|7?< zBHPsq&)36`7aLf3&JA)RWOu*sBEL=M)1(G1RU-mFpdkxl3n)}YREly+*b{9x=4#u# ziZD8nMuE1=Ov%i`A3uy)O~i~F?qG~(m4xs8kqAsA=JWb|lo~#>@^WophokufZ<>~o zvBr64j(|=cHdEl)!;tEXQ9JZEZ~nq=d8}Rjn46mvdHndXc*x!4loXMaevMw5?gXCE zo*rh9T^gT^XKp1zY7cY040F( z7^(-L^g#2|9KAVS^0T`;REOX8N|4qmaT^*#l$DgAHwO9}P_ao{tK}ae04dNtcraD% z0PSr#$;m$G=~f2hPOVFNYno7cnGwZnnwCB0rh#Wz(xr&m_Qw|k+A%m=F_b-%}T{(uytNXszlnVi)Ef>-XP(` zpHUnc%uUey@LSErX*J|zCN?JDKlP-}c2-K{?wI76Y$mQWY&G$_D7xG#dr`6Zph1o~ zhpD8$90j4~PX-)+#6(OyC#*ikey7pzI-oh+1Kh|qkTQoglff0-*yj2DQ}abXS$Jyz z;(kSM{fd6Jz1onIa?awhX7K``Ca}{O)$Fz^wVH4;)88#$Fof<5_nM=$04Ew zAt%hY(w<+tTG~>W)8x(i>}HqOB-c~Fq@Ed{4dQ$y7k%#Mhl*;83W)X%1iKsibmnwK zYlO};!NBUZ-PjVK@JP~wd{Kh$s-x<}YJ}`;9F^z4mkbwCkeq^o`dC*2#Ms|tL2U6u z_6Oj@qGDOJd0d&0v;EN*FLuAt2R*{NO(u%+e)+}=UGr%duU@y$NB&2q<+F<-y{&T7H;DN2}O7dzTcO^|q#>R(C$PSL4+QwyU6xP9Ewr=5$AQW>ogB( z9&tQ>$^tRl2+^<#o&VrU!9jeKHJ-uy#zoXb%;XDb5YplnU2_he_4KT~R<5yfvgj|B z7vR%X&{R;+L=crXYsS;<%4jbhKDrn0sURoaEzs0ZnqFF(eg~1gBS*U?`(WmvaQ7{) zElW1@qip7D-fO3?F$3m!I-Sf~dp<=|x3OJ1v)_%1*J_ck$}!1I49QgTv?s^PgoxxSggB6cF) zcF21C^^s*0g{mx+9uy!tY!xesVwtsG6+bCp1^zD}HEDsWU#K-j3LV%ORiYfV5Uub7 z=A8N}JtpHXZ3rwGowBC;tt`&Bp+d3#c>|*XUMMT7O0vz5i zM&CDY8~+s>6VX4({&9Nc&C(3z3*miq=tuw+JH(*k2oY$ok<`^akJjfrd@wdJFaWg? zq!WNj$|dpxIGQ#6+MfIT;`|@L%^+t0>xOf{HG)75a(Ls*AHK{dM3>6{>Ea0z1-$gs z)Y6lZpm~Fah2;|abd#o@4cDkKNQXE!=Ie3oZx<+sZra=g{LrOn%iO);3GVfSN1BG3 zntxtMU8lsQT<6B#KG|cfq}R!tF%e5vPnDy^+A}r} z>`^^>&-rxjFr&3N5(%whSF`429JpIsT3SM9&=6oatF`|CsyRKK@wjb?=D0PpxwZ8| zp7#kW>+6aM5LJ%k>&ozthwnq%5y)6T_*=;F*W_Hh_k#WD(~~#E`-LDsC@){Kzf;#K zI@~IH;6|>`XJJu?!e5`Mo)(qdw0GxImd*UfR7mfpA583TL;H@Jhj z2pPfV5}qvWU`tC1v}=y!YQhq~anNc50qyy}qq@>0qN`T$Rw=;3!SKGHeb-{kvwlW! z4IjWt=$!)y5=jDq`;#Y6XlPQ>)5R4P`yuU^n26Kq@^j$#uc&YWta5UMh((Xr^UV2W zO?LL$+Heja=zxL(kpV&W9>*J<$*{t#!LNJJz4PqZGZ@kYE#8g8#GF3W<4Zzne@>|{ z`lWs@w=>ll9zB9KE>mOUtH^xtaS_T0V1>>OC#_p$fP;l{Z(-Lj7dkNLUea)Jjf2GV zApAY>D$s}McIfW1}!>hz&0w{Y5(ib^vD1OXLe3bu)u6xU0wU4UqXC*cFv?es*1#N!&jTQ zuU|nr%MB=Up0)s=S@}iT5yR1&TMG-2PzF$nma=oz&-sI#C-z2-wG-!3-))=52&H{ z7x*&hy}-u6fCjs=k`gjjP=aH0|H}U-39@P~CzZ4n@-6=?|e+ zE)O1;EQS|a^XFw}WsJ7;>O53R1RE|Q>WSxyaF!MUH8be#-O44DMs0%tC<9A^M#3ZL z3)pf7x->(5L!n_XjUW{r+gasNSymQAPUwEP54;IG`v`QOK_dgS$bpo-l^}Am&EN@C zBR_k48{CfGj4Wq*oE_sMdbO{%!V8&}FZnH1J3Z^!c+NBY;MvO8OeT0MaO%1>>dPt@Rz6S1<>qt)mOl3jQ)@E%$1W8 zIy(BC)?lc|8o*ULIVza__tJ`el99j+ zTuY!_0EP$KGkJ0s!b?_Gmgoue?^IZh->0JLJEQ@P79x1BO;ax6bTzmDeA&prz*Q?R z_y8dx6a>1$p`kE>usS<8H{@VK0;a{r<|zWw=ZF9(tO2LM#l+k_I(i`^L(>qHo4Wz* zEx4{J84CxaE8j57l5oyV6Dje{iMw}IkP|{!%gBf$B!=(`hl}GKBRxHeh<0fGwJs~9 zS>I!oPv92Qi!^{2F^F5jyDXrC>D8a+0N7yQ!~x>H60Wi^+xtP+grK0{q^;(7FGZek zvVtje%|mqvX#_DL;d6F&u(6}VJdGbI*w`=bc_9UbZ5eeEjl}4t zr9B#kJMRIY1Q)9whyejNf!V2BT3Gn|5mCjPfSkQY5E=TsBffkQ%^2kI_)WAX*t?7e zTMU}J_+~an*Gqhgvcg(f9&Z?#IK&PrmZtQg{z$)|xa4RtV3p_NGugDra}u=P2rkM< z{KlO7XlN>@ipCvHF*&(P$gUvW?bE->>T+U&*%JXiJgzMd3JaRtfSUV`Wv{CWv)`I;E2W5t!-=}~CW^169`S@b;c2)F z)vl|m6HpRNpI57SX>C?DWTYpj=Oj38Z%%BU8Xk&t*Tn&%$S3tY7WqzTIJmGxx z``+)VwUV^wX^`krcsf@$3HhCP^;#1OX3P(uiHVBBVuQHdhWnEzR)NXB7%V$X0Q6Hp zTi*dD#Wz1SHEDc&J5F-RhEhKFfBNlS_>&-Pb4xj_XLgw`*HZNuj=yCm{aQLG8Vl9P zdT3_w#fu0+C21wkx+9C-$pw+YnsOXzRgcK|n-ax~0J0L?y3KNFulXV>ul>;K`ai~# z0;@lhL1|bhNrllx2tT%Xt!BgRe|Rw7I5spp zf61Bb{l$#0*`Sw$#06VhUv9i0j-N5YPak^^m zNpTTs#4)iprc{;HKipk@qx+JLmHFoPweKRhxYJS% z^A5f1TZtE@d-J(IgepYN(|flU38vW`BO>2L#&~O7{BsAOvaqz!E_ivWdun|3OSkoC zwgvKQ;!E37e8;ixF}^g7yXo<({!*CL^66=!uVilL2M)vp#48<@`{X-2I{;Ebgk7+w zIw$M7YG5RDdiM94cVOht$9|e~*XG!$rPdQArZ+Cu~||8qf<8_I(=QZ2#;;Z$@#1%pjr1vXJ<}&db#Jht1Wf`WPF$bFU`!<0eyA)3ok8@f{*~zl3Gsq zv%4Vx?|?b)?d+Jpdgbiqw!E;QK>vl1QLU8h?$>U|dy~=j+aB$n@M0BUiy=n@>j&YF z=ph*a0T8m#JOD4FNNWT5yt>+G{L)u<_^|jq;1+|aDa!r({Jy?{p`pE(I}*43nvC=C z$aSAsX`zQYhD0AZJug#U9~H>Ve`6V35-Yq@{(rUhm0?kKQP+bYjFdx2NXQ`4f`F0& zGIR?_iG+Z3Nh7JW(yf$;lpx)Wv;vX>(hbrE9q;COzd!H)Z!WKg>yh!E`#y8dKKrb_ z*4kxtAS#D_NLju+6N3@^@k867p4G(mWs4ELVAT81-a(PuQ9&JS_S*p|DeU;|p6#!@ zT(M2c#v@FQV~88M$L&>A;u}uyJ>M0VlJa6!#%DYZ5A{&Ya35L&#j+QEd-B2ltb~NN z8|Sg1gI~9A`c*`NRYF1FX9H5)Z^Q}YaZHBGT8o#Op}M4?vlRd#H2)L54sesQ3X<&SOJmjw9&Wtnd0@|k59!`T+A#C{`jQt3ke1+OfQ}UI>yGvtuIK?7P_HvBU^6lFgocMJD=1))@vxzf6-%h70KiuYBy$kb zR#%TrO*Ir3?*K<2A20Mmmm^o zKGWXrKZfjp1OECnRvn&DR?pUi97D@WKxyr_qavd-NK`&TT(YjMi6) zAg30}DYVl!U+I{izqYm25g1rROm`Hu_{?#+70RESmYLmhUl@_57Gsl|#C%F9lyY z8(Ds-JCk6l6z!cT&-C{MFmkd6c<7IUG9>6WYQB&fJ(nss%}C40WZ`#$$%GWxB1(FN zksi^9ybtd@K%SN?Mh@C$iJmQar?VJkdU5vNU?e7Cd0e&D*(tz|EnE?#9fPN6g0kaMWQ$Tsl{Pc+(jpnhNtR5TF z0U_(wcMMdo78gwc#)XXcc!kW}bGxacAa^&1+4+TZbb#x2+VZx2#wXz* z4BErvZBX!c8Dx8VJVQWVn)l>XudvSv$Bp#RPoGSfn2-w{Y;7IFd7mQY&fvTfr=;Rq zQK=OlL(0b+BkpWLfyvEXy;doJo)f~b3Ld=KZcGLhW8MeGrFH8{Ys&MQ84qm^Bb(D- z+(AkdiVBXYywbKkR6Jy43N{6?p;rPGyd^SS2!9Z8$l-}AI)$w;1YmeET;jEpN0k*d znTK9;hd0M%57G`Qx<`>2#RbJluaY_h!fjJ+wdoC8=tWY@d9R|G*qHOUGShUUsm~eG z&LXCV2b>#sPR_zk8}fgxZs~YFw;lNQp=!$h+qDNbacrS>HU|Y~mJqWsG%ZdY6HPZNGlk!^PUAiGFBRVs-!7K}A&+ z6h%CNkR@5}(`La}zRz2{^yP&TX@sqD9XR8VU=-nC40=!3IfCQm@H0>SK5(0pkqIix zD=Laay*NF(SE;L|6@>c5Q|}6gP}sv3>FBquE{A=o7 zoyalNHZk!NOG^SwS9kYp)6>*9r+oZYs1{?DfYpzm8fa*H|0&7q%-&64Z3t(w}} zF-4LC(eBo)dACxpP09E?fixw*;ZzS>ioLM-lKg9`m!nU##pXnjZKnbxU<+^NJs$y@u0Nt)vD(9^5*n98&hMv& zto>bko_yStZhumLdbCGh_d`XW!zlCB-z}4DE7gXh#a{&`+nwrd?j2(m6Mw5HF-_d9 zVZCPd>uSYLw8u>5$O^vD3rxHyXYBecI6Z<- zrDuO>nu6{i)GLy5(S=5kUd_k^@wP|o@y4&Coh(`P-{MWdI)S2J$bX$KXM_oP*eoCWABhzHbzAqmg9X01Yzo$E)9H-njEq+|E zOZ}#IP`lqPd9~8!H*X)-H_k*CXO}jLjzNQ=^2Ks3ouZ@+kxVyYv|^0t`R3vK^7kq4 zXer*@({=i-%IC|gxizB@Hc63un_rDT^lRwXyl%@ai)=YH=OvewKaq# z-<67ayDE1*A|=Gyv?w@&yP8pi?&jRI)ng$YBYrx$WdVZlVk+B)5}HO-U=hJYP+ zTK|@HaceA^>!~|}CqMJ7Ynn38V~z+g76pUH_Xux=kY-IBHPS2zw~V;FTJN;#m~&_x z@)FYEH99I#&{H`MmAP7hZxxG4mGnsOt5TzHSQscL)n%K!S1*g+e?je?Hc=O~ zeaxEzK;Vxe!*noyNtJ2q?7VT`-2o)XU^ha47A2-FDMJ5cYKjg`PDMop2cOIV*e-$M zT+-qXkgN<60CloZDNyu690m-bu#oF|xJ;J1fXx_+mX_0qgMgAt3TZpjc=}!Z+{Vt% z&cZ_Q`TRxhjqs;r|!axr^d*bA#{QE2^tay89R} z{s`cxzq7NRmnusgGDt8M$9p~E=UxCckN5XKpKtSj|K1musvcFcyU87-vhC{H7#FJn z5*K~-!HqSKijN=1e`xtT-So$xuTcsl#8>ROBP*MT!c{0=bJ5m$78K+a#180AJ*Im{ zeO&{^C*u{Cde-F~<8YTsXj>5~mH@Q^)evTnAh0|_A-etD-{0TcqhEFxvzPRw`!YD_ z2v`;1_xpd~b$n;03k+=FiBW(Y$pBdqjxG8%j?W^UfbYs~!~u}x|9UHt?{h=`EBoZh z~(nf&rh0Us;MtlH@(9( zR_C6QQff0YGkcM8O4)cFzbPpj;^eTdC^_6YA$ZFf6duynlal9s<`tSTclFzsc$tPENGNUYE@nCrkS91o z-Jv4nPxb~U8$&}wVE=-z2IP&O_3QW<7>oq{sfmKT?n}wZb$yhN1;h@t$8Dy(K)mqw z@~Y19c??c#Z{LoC>n6C04)yl~fyUI-bP@VC7%FDerh_e`oi2S2a z$MUgpjEwjQ!Ki)PZv+fT#a_r%+?ZySit8`@v$CFH+aUcS*G@&W3=A5f z*bmAFr>g;-a}H1zf#r}co z7`aH(U8lmyQ4s})SqE4C;gNXn! zH&IA9?nONyO#RLjY+-9V1#*DB&2x|DBOqXy@*bMx_^HZSTd(YGzMZH#H^+ovD z-&`GVLiowmdmVz}y9^2e`wKSU&jjc%V6&q9BE(eA>GH%SP=fyP;|F-$f(_24mqt&Y zmVkRIxU_@_bKMctDoPvvdBi1DQ(j*m{-9Y`C)ehuX@!x5I9D|mLQD}BJOovUl4oOo zcHHla-M@bVchpKd#=84<5e0D?U{q{a)49=dAEWDudqZw+Y->;D|^dmy;@OfBPGy8iPJ4IasU83W*5s|zg4si|JJ zFLMsy0-MrazT;jGD=;wda-}O4JW#g_65g8$1x60c8_1A^Zkh7xN=izCg$(>1$aNI| zd+FL!M~m`7=S<7Qh`RuHXY?_oD?-jas(G9+Gf4l1vwXlVqM7CiT-`6) z`1J3;ucpuX%-mh8!z+m*D`c=xpU;+17wT+V+uOv!G#@OQyGVc{eEq9AkMU0ToB=*{ z*n{=#&t&j0e(RO*TPkCZmb7VNrdsy&p$i<_U0;A=~%(9gkP=0tvW;mEL_8J-szXcKKVY{ z`QSG#t&Pt2vfRmf75C-gUjoGRQKBU4f3L8`tfc1m7k6lJ9rH$-4oUT77eD;~kI27U zP)YRugvH*UpcUEDNTWcGoPwUb?!&qb`@}0;V|cCxw)A^7A`&i_DxFO!%hnTz4QZukCi5qiHb-PZ(9g=pD#TASZ8O>O z#}BffoBZym({vO1toT}f>(DB4f=QsoKpKHd6b4_2modHp`A1RJvY|3QO`cmM^2Rtk zdioQ?=6UCjPW`=&G&pkAjn!j}W8PkSt0QlxY{!y0q_v%?k!tY5nXQgD*{$x6@`^8K zFpAM{x?#5Pp}vf0Gd}|-hmxd>$z0q1o2%AcR(EuJMkuw#3p{h#@-1`hSrlVFTjeQu zrd2K+5lc!-I=MJW7(77W5-g$+eyU0uvO{N=k5fBEoD_<`n^|4HcHG=o(3SFuPxCS6 z-D8P%;%1-OXvu07hl;`S`6({x*S|~jF*yBol#|7@^KyG6wjP^^353ewznJx_>-1by zRSa!x0e-&XGz+b@!FJ}8ADl9;3f-8Wx{Sc3{O=RP=%tG~Ws4%}BN9?5Dq>!vtUM-u zFOHB^$=jJecT|5q_-!xo7w@lO{7<~P<8lM5CN9J~<9O&xt5XCLrkZBxfRAk-BTk(N z^}K?6&B{phf|&jkZ_PcZMfjmE7L=EyAKOB@1o#Y%()M&wk+T*d#(M-b1sBGu_IV%umt(t{L7V+A#$^l zA>pxnZ&B}P)JT8kDmtKpJ-oM|D~>^-c-)3wfVbbTQxZw?AiZH$QA?cyz4&Y{>rVE{ zfevXjlT;vrur0R_4Z$LUUKEy?l^s|AgO3*#SjcZ6PD7S0Nk@ZtZy3?b6%J z-1r+r8zo<;Yx%fDYnfC~oy4=xj}V=bg!lc#Ldn|nW-1*Mm4?zN49UNn@|~I=EIhx# zeBJK7z;Hw1y8S7Fupd4Te1rhzd90^*c;hu#sn^%vzj@PNCFiQ;BLo876=lktt0c#* zylv(>u{QBE(DjAuK+V9YLg8E1WY*|-_Y|?68U{S72k9`7fNKcokAJ+C6WBRNsq#e7 zck*P&Bj-D1+IN^Eg(CVA3KtC;t?qBYv?hj=u52P`wptg-&d;3#&*>H=$ShfGK){A`|TdZv36VlZlt*22r z&E9Ygo>53Wu;9`a94eno;oxS9BuW=@1VV!3N*l~M@5`rR`@)fK9Sb|ZV}6%1=@tY? zF>Rh%?Q%enl|L1gN35lMh7B-rRo6Yyjrj)LR zu7RzAlPsU~>kk}7)>)SA9>ZjQPx(GMg-kD?!EPPm9zF zhYv3_H2}guefh?X8+v*MCKU*Tf1uw@2i1grf0*R^)3=rtg9LMZ@R~Cz8%Dp~(c$$1gO867o3su8af?4*3I@h%i>yR>3me|-H-K;q*%uZOfy7PW zyO3!ysSUS`ib~-=7MNGi{YGJ3>|i+X7T_wNFi>MOFVUG?g1Raa4Wq6i54J}8SWu!w zqKSyY=R!!~l2kkl!S1TCUkwhHLM98eaVU8s(U2a4d=7}KfRq7kqc0kA1D$C<6cixw zk;n%~Az|UyOkf&=c<{(bX$cRQ7s;8K3)@ZS^wiXrMn(_y^_4RRiX9nuTM0l2YYF5J zI0K;3YLMfNj68$W%iJ8gwi)ghLE^WLx&hVZCpC`k{r$x?H9`Uci87fW*gHR5NxzWV ziERK73+dT~9qZ1lgtm4f2x)+ud{Okc#t)ZZAk91y#^FcB9547@ptQ@q9Jg<`FX!Fm z;c1z3NNKSCQScU=Y6AnMjg99a1p=}KSk3_dQW_0rQ5sSdcn!}X5CNmT8+&29_Z?RJaM^8ztQ7JSfP{HO@7H0RvPVaEIIgOCYPdnf6j zYH=|m5$Lc%+DnL?ZjYZkcX!MfV%xnQopx`SEcmN%DPQ0fO3 z@OHZQaklEF$h_rTo>}sRO?Tw>#R;Hba7>zva!u`wFXYqevw^@51l&IjbGvgrk~~-= z)Myh%u@M9Q7dL0=e!AtNqD+j*Z78EV#yX}xe%WsJS@DTm{$Gm9L$~o6f%NJ6Rd2O0 zhM+S#K8XZ|q7c33Rlm4g3H`fS-(M@Jsm@-qCCG^n2)?pqw$)#uvCif;VDAZecs-4#wXg9g-Zwui~h$_zZFBD+PV za_J?C-AgwFrQn_LPCx!4v%uK2b@;Gavr8g*#(ToMY4|vJJq7lUrmQtBM{O-zoCmQuR&=gi1qrNm;xE3-MWw2yEIWgQWru==>(>44c*kZE&93a zr|I&RBDKOOjI@h%gEbk;>ze_E|miL8S9k6Js5j*%i)sQaJ3$!#R@N1?m4Xy!g94`frjBMp=dkO}pntE2=2TCI6A<{Iyg8$ zzs3zi3DWj~jT3~P55zcx%wRXm`oTN32D6v=^|M9%M~ggTtgx{hz=39^|= zgj7x*xBp49G5b~ai1|$@Dke5g61guJ z`T-BS^JB27?_`sPQ8>`SvSOpo;oVCApOsew64w zc6|KWgnU9|!g|7LhG+V7YOQPInwO{d@l9RXy~#G zGN^3o`P|)`*FD#B6`cZbv#$uPD{E}L^hQ6J|iIxK6aTd(s0RkRDraPWlE**Zc4WiPujiv9M`ys|rp!KLi>z>mb=vPJBwrJ2#{whptum2)kR9@ESeM54O&)#J#BJF2%WR;FJiWLv2zGb6 z%FC&c8DABiJN@a)$W8aVNyS0!l<1&J`LbnUXEIQf-?Mqn0TB5yzo2QRcBam}umu~d zZM20NRTTjYn16}C1^f;Ky}GL4W2QYn#LzrFTYPGB?VYZCrUC5CzB%>Nl3U6 zEvW<>FwmC(CSk|TEWkL2Za&~*9TRipKtRDUS!qiXDkThNmY?;iP#2cQU;v6l>+4T#Hy(3|h(N(K zdB%I6Za*so@&I7H{+GZ4wE{fm1?vDg%6;?+u)O;E6Ywhq(iSwyfgVF_WP z{^=Ef+On4k?gI;AVtX4~5uZcpx%qZVQqrAx62e#W#-U~A&tAQ{KG)JmVtmIbBsVt~ zki*I<-=yE|pi1H4fhSS>kD|9ZRhI}go3Uc7iS?BgV3)a^96aZIIs06=lA4;j25=(q zc<;l40tWmqz(1f(0d3gKOjD<ULV?eU3{qNF*2J^v*RkgB+ zf^c`RSv8Y%-WLg*lUCsqY7!DORL@W_7RUca4R7DR4LI^+b@f+tneX36rKQ=KneCNm z2elg?9vuNR7^5){1n8Whi=I*{JvzP6pXXpLC!L+09fc7;I{^FKo7?UOt|=OSKne|% zKXpybK)OumeFP{i7!gM{r6UN3ryIfvD6Q-!vb|;zn@x(Eh<^Jb1Cr4~%~atEr-2Vx z%AB~k1Minmz@!1O2NP*1ShEP+yXU>Xr4L6uGqYvL&;e#dVSug(+3`%dWh{JQaIREM z6$aW~1;CeJNQ-oQ0ctDoZbo4MYl9cgr8$^#kJ#hejpuexWLIe@yp1BA5{#Z6 z4SINmDg@~7ICyvq4uUK!ETE`^=?K#EbYU0hXuuQ-^_iyJrYiHa&bcu{QU67SER5R!+I1?bkJ;I97Gn$mYa(!y2IsMn>1i%# zxMf}eP;KYJF04|p@zIfoCL`d1xP23-<*?WQoGnr2w4JAkWPqa%^y?}?QWFM2)dCWH zh&ckbuP!&Xpx%j{+8P-x6zVHD3)`*#U~V}xz&g0Pf#3Fq68;!=bv5gUk(fF-hCoCE z+@6Mt3aljH0J(0%PotMYS%(3;%HXLlqd$I#0Y3-&o{5_G(dz1s18;W2Bx!pMz+5=( z!UUPTf`hSe;BME})`mAjmj(YnFT+t6tq0@}mIk6hFO#8j02#DuJQsoF#p~}iW;OIZ!0YeMHD*>~+NMqokr|g7C&2u-M#DGnD zeEel094adaw(9Bywi9t29PPxk?$XSy=7`ncdE+bajO}l@%PZ=n>4*;^Te{IOcTgDM z--6oEX}e+aCKVW$gQGAkKd1AB5dWoapaTwC9Ng5BTTj6e@h zp3Er}fRWWc*4T@$Q&ZmOjwT~}jV>SZ^Cv`tK|w*vNH4Lvw)Stj?H$4>_qNW1(P7g9 zy_{dGxeXppvH9!PRP|xiv2Cee~A^%jhQlnzOJuf*F(B`3QAs?*mxq z$a?7NQe)(zSh(FEO?Y0<7h|dX5|oHfA2~}0wZqx_9j|O1;ohipbezxrY5!~SkK3Gx zH!(KVE(Fps;~E7=Ru?JUCVe)3>02bBm!QnB3jV8)nas;o%;zZBkJ=TTZqw{sPYiCstS@ zP#DGEpNGOxE?C)QWlhyM1^+3B8lbkPVE_uoUpV3r!Z0PLj^7mP(>qkaiIG9}Ch-6g ziNyfTi?m=N3}$U+WWP)`A@qM)<8Y zb858eGW`N=H?FPH5(q0Q9p-@W@J+YO3~YI$c>w+f?4v*f z`L(q*UF%}qgnMVN7^>`GngozSb#*n&$_pz)At6v~9KvM<(-NHzJ8sH#@&F(L^TGYa~efwtusl9a+HHLuQ6Bjz+pbyjj*dTN*fxQnlF_>?}E<-Q_FH#<0eu6XI<@=X| zTj|8S+~N2q2^*RjOioi?k6GU)NFf13R*(Zs4%hRq>Vr)0-p^0@AOQen$73BGV2^;1 z=V>dw@4+0NTIXeea*$UXL!R=2xg6Ahr=X$cL|jx9G<#fY$fbhc?&SH=8Yg|s$yr%i zy7r$K1402%06yjA5vdN{ps~Dw(+F`EQLUXY5MRGq9ZaNNXbr&8y-6Q_l`i1UOang= zU;N;h@_&Br!g=Zpx-6lfhUG_s1!&2G^Jc;v1VX9FDFi$UfoDM|&1xhCj5v6UQPjxj zsFN!HRRjWic)O-hC|?HZi$VZe-q99C7#08JhPnd0nwy=dFMgtMp^)+a^Jm97p3>Dd Um(7;1ju+RJmsXJ~#2CH$AJDCj#sB~S From 13bc47b65d32df6fd212c4687c7fd29b4ab7c09d Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Wed, 18 Jan 2023 19:58:08 -0500 Subject: [PATCH 0383/1097] tools/fiograph: accommodate job files not ending in .fio For job files not ending in .fio, fiograph will overwrite the job file with a graphviz script and then delete it if --keep is not specified. Fix this by creating temporary files to contain the graphviz script and image file. Then rename or delete the script and image file as directed by the user specified options. Signed-off-by: Vincent Fu --- tools/fiograph/fiograph.py | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/tools/fiograph/fiograph.py b/tools/fiograph/fiograph.py index 86ed40a810..cfb9b04187 100755 --- a/tools/fiograph/fiograph.py +++ b/tools/fiograph/fiograph.py @@ -1,4 +1,6 @@ #!/usr/bin/env python3 +import uuid +import time import errno from graphviz import Digraph import argparse @@ -293,13 +295,6 @@ def main(): global config_file args = setup_commandline() - if args.output is None: - output_file = args.file - if output_file.endswith('.fio'): - output_file = output_file[:-4] - else: - output_file = args.output - if args.config is None: if os.path.exists('fiograph.conf'): config_filename = 'fiograph.conf' @@ -312,9 +307,25 @@ def main(): config_file = configparser.RawConfigParser(allow_no_value=True) config_file.read(config_filename) - fio_to_graphviz(args.file, args.format).render(output_file, view=args.view) + temp_filename = uuid.uuid4().hex + image_filename = fio_to_graphviz(args.file, args.format).render(temp_filename, view=args.view) + + output_filename_stub = args.file + if args.output: + output_filename = args.output + else: + if output_filename_stub.endswith('.fio'): + output_filename_stub = output_filename_stub[:-4] + output_filename = image_filename.replace(temp_filename, output_filename_stub) + if args.view: + time.sleep(1) + # allow time for the file to be opened before renaming it + os.rename(image_filename, output_filename) + if not args.keep: - os.remove(output_file) + os.remove(temp_filename) + else: + os.rename(temp_filename, output_filename_stub + '.gv') main() From 5ca54c1ba2db849dfaef5fe3aec60329b3df0bd1 Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Tue, 24 Jan 2023 20:54:48 -0700 Subject: [PATCH 0384/1097] Makefile: add -Wno-stringop-truncation for y.tab.o MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This file is auto-generated, and it currently spews the following warning for me: In function ‘setup_to_parse_string’, inlined from ‘evaluate_arithmetic_expression’ at y.tab.c:1571:2: y.tab.c:1559:9: warning: ‘strncpy’ specified bound depends on the length of the source argument [-Wstringop-truncation] 1559 | strncpy(lexer_input_buffer, string, len); | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ y.tab.c:1556:19: note: length computed here 1556 | if (len > strlen(string)) | ^~~~~~~~~~~~~~ when compiled with: gcc (Debian 12.2.0-14) 12.2.0 Just set -Wno-stringop-truncation unconditionally in the Makefile for this file, don't think there's any point in checking if this warning has been enabled manually. Signed-off-by: Jens Axboe --- Makefile | 6 +++++- configure | 19 +++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 9fd8f59b85..5f4e65620f 100644 --- a/Makefile +++ b/Makefile @@ -554,11 +554,15 @@ ifneq (,$(findstring -Wimplicit-fallthrough,$(CFLAGS))) LEX_YY_CFLAGS := -Wno-implicit-fallthrough endif +ifdef CONFIG_HAVE_NO_STRINGOP +YTAB_YY_CFLAGS := -Wno-stringop-truncation +endif + lex.yy.o: lex.yy.c y.tab.h $(QUIET_CC)$(CC) -o $@ $(CFLAGS) $(CPPFLAGS) $(LEX_YY_CFLAGS) -c $< y.tab.o: y.tab.c y.tab.h - $(QUIET_CC)$(CC) -o $@ $(CFLAGS) $(CPPFLAGS) -c $< + $(QUIET_CC)$(CC) -o $@ $(CFLAGS) $(CPPFLAGS) $(YTAB_YY_CFLAGS) -c $< y.tab.c: exp/expression-parser.y $(QUIET_YACC)$(YACC) -o $@ -l -d -b y $< diff --git a/configure b/configure index 6d8e3a8712..a17d1cda10 100755 --- a/configure +++ b/configure @@ -2826,6 +2826,22 @@ if compile_prog "-Wimplicit-fallthrough=2" "" "-Wimplicit-fallthrough=2"; then fi print_config "-Wimplicit-fallthrough=2" "$fallthrough" +########################################## +# check if the compiler has -Wno-stringop-concatenation +no_stringop="no" +cat > $TMPC << EOF +#include + +int main(int argc, char **argv) +{ + return printf("%s\n", argv[0]); +} +EOF +if compile_prog "-Wno-stringop-truncation -Werror" "" "no_stringop"; then + no_stringop="yes" +fi +print_config "-Wno-stringop-truncation" "$no_stringop" + ########################################## # check for MADV_HUGEPAGE support if test "$thp" != "yes" ; then @@ -3271,6 +3287,9 @@ fi if test "$fallthrough" = "yes"; then CFLAGS="$CFLAGS -Wimplicit-fallthrough" fi +if test "$no_stringop" = "yes"; then + output_sym "CONFIG_HAVE_NO_STRINGOP" +fi if test "$thp" = "yes" ; then output_sym "CONFIG_HAVE_THP" fi From 1e8ec88fd5f3ab4b7bbd0119708d94fd64a4e7ad Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Wed, 25 Jan 2023 08:01:30 -0700 Subject: [PATCH 0385/1097] Enable crc32c accelleration for arm64 on OSX Before: jensaxboe@Jenss-MacBook-Pro fio % ./fio --crctest=crc32c crc32c: 440.18 MiB/sec After: ensaxboe@Jenss-MacBook-Pro fio % ./fio --crctest=crc32c crc32c: 23923.00 MiB/sec We know we have it on osx on arm hardware, enabling it is pretty straightforward with that assumption. Signed-off-by: Jens Axboe --- configure | 8 +++++--- os/os-mac.h | 10 ++++++++++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/configure b/configure index a17d1cda10..182cd3c3c9 100755 --- a/configure +++ b/configure @@ -2685,20 +2685,22 @@ print_config "libblkio engine" "$libblkio" ########################################## # check march=armv8-a+crc+crypto -if test "$march_armv8_a_crc_crypto" != "yes" ; then - march_armv8_a_crc_crypto="no" -fi +march_armv8_a_crc_crypto="no" if test "$cpu" = "arm64" ; then cat > $TMPC < #include #include +#endif int main(void) { /* Can we also do a runtime probe? */ #if __linux__ return getauxval(AT_HWCAP); +#elif defined(__APPLE__) + return 0; #else # error "Don't know how to do runtime probe for ARM CRC32c" #endif diff --git a/os/os-mac.h b/os/os-mac.h index ec2cc1e555..c9103c45ac 100644 --- a/os/os-mac.h +++ b/os/os-mac.h @@ -14,12 +14,14 @@ #include #include +#include "../arch/arch.h" #include "../file.h" #define FIO_USE_GENERIC_INIT_RANDOM_STATE #define FIO_HAVE_GETTID #define FIO_HAVE_CHARDEV_SIZE #define FIO_HAVE_NATIVE_FALLOCATE +#define FIO_HAVE_CPU_HAS #define OS_MAP_ANON MAP_ANON @@ -106,4 +108,12 @@ static inline bool fio_fallocate(struct fio_file *f, uint64_t offset, uint64_t l return false; } +static inline bool os_cpu_has(cpu_features feature) +{ + /* just check for arm on OSX for now, we know that has it */ + if (feature != CPU_ARM64_CRC32C) + return false; + return FIO_ARCH == arch_aarch64; +} + #endif From c6cade164bc7e35e95ba88f816be4f44475e4e23 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Mon, 30 Jan 2023 10:37:48 -0500 Subject: [PATCH 0386/1097] lib/pattern: Fix seg fault when calculating pattern length When --buffer_pattern or --verify_pattern has multiple elements (0x110x22 or 0xdeadface"abcd"-12'filename') calculating the length produces a segmentation fault in parse_and_fill_pattern() because it increments out when out is passed to the parse_* routines it calls. This patch uses the fix provided in the GitHub issue. Fixes: https://github.com/axboe/fio/issues/1500 Fixes: 6c9397396eb83a6ce64a998795e7a50552e4337e "lib/pattern: Support NULL output buffer in parse_and_fill_pattern()" Signed-off-by: Vincent Fu --- lib/pattern.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/pattern.c b/lib/pattern.c index 9be29af6bc..e31d473471 100644 --- a/lib/pattern.c +++ b/lib/pattern.c @@ -386,7 +386,8 @@ static int parse_and_fill_pattern(const char *in, unsigned int in_len, assert(filled); assert(filled <= out_len); out_len -= filled; - out += filled; + if (out) + out += filled; total += filled; } while (in_len); From 23fb164250c29d184940ddcb2c26c02ea51aea4b Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Tue, 31 Jan 2023 10:43:13 -0500 Subject: [PATCH 0387/1097] test: add test for lib/pattern segfault issue Add t/jobs/t0028-c6cade16.fio to test the ability for the routines in lib/pattern.c to parse multi-part buffer patterns. Signed-off-by: Vincent Fu --- t/jobs/t0028-c6cade16.fio | 5 +++++ t/run-fio-tests.py | 9 +++++++++ 2 files changed, 14 insertions(+) create mode 100644 t/jobs/t0028-c6cade16.fio diff --git a/t/jobs/t0028-c6cade16.fio b/t/jobs/t0028-c6cade16.fio new file mode 100644 index 0000000000..a0096d8026 --- /dev/null +++ b/t/jobs/t0028-c6cade16.fio @@ -0,0 +1,5 @@ +[test] +size=16k +readwrite=write +buffer_pattern="abcd"-120xdeadface +ioengine=null diff --git a/t/run-fio-tests.py b/t/run-fio-tests.py index 70ff437114..c3091b6854 100755 --- a/t/run-fio-tests.py +++ b/t/run-fio-tests.py @@ -1246,6 +1246,15 @@ def cpucount4(cls): 'pre_success': None, 'requirements': [], }, + { + 'test_id': 28, + 'test_class': FioJobTest, + 'job': 't0028-c6cade16.fio', + 'success': SUCCESS_DEFAULT, + 'pre_job': None, + 'pre_success': None, + 'requirements': [], + }, { 'test_id': 1000, 'test_class': FioExeTest, From 7d7a704638a1e957c845c04eeac82bdeda0c674c Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Tue, 31 Jan 2023 10:44:54 -0500 Subject: [PATCH 0388/1097] lib/pattern: fix formatting The fix I committed was not formatted nicely. Make the code look nicer. Signed-off-by: Vincent Fu --- lib/pattern.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/pattern.c b/lib/pattern.c index e31d473471..9fca643e32 100644 --- a/lib/pattern.c +++ b/lib/pattern.c @@ -386,9 +386,9 @@ static int parse_and_fill_pattern(const char *in, unsigned int in_len, assert(filled); assert(filled <= out_len); out_len -= filled; - if (out) - out += filled; total += filled; + if (out) + out += filled; } while (in_len); From ff6131afb6af08707ee36cb0397dc607795eaa53 Mon Sep 17 00:00:00 2001 From: Horshack Date: Thu, 2 Feb 2023 11:37:01 -0500 Subject: [PATCH 0389/1097] Add -replay_skip support for fio-generated I/O logs -replay_skip is an existing option to specify classes of I/Os to skip (read, write, etc..) when replaying I/Os via the -read_iolog option. The code currently only implements -replay_skip for blktrace I/O logs. This pull request adds -replay_skip support for logs generated by fio via the -write_iolog feature. Signed-off-by: Adam Horshack (horshack@live.com) --- iolog.c | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/iolog.c b/iolog.c index 62f2f524c7..3b296cd7d0 100644 --- a/iolog.c +++ b/iolog.c @@ -492,17 +492,25 @@ static bool read_iolog(struct thread_data *td) */ if (!strcmp(act, "wait")) rw = DDIR_WAIT; - else if (!strcmp(act, "read")) + else if (!strcmp(act, "read")) { + if (td->o.replay_skip & (1u << DDIR_READ)) + continue; rw = DDIR_READ; - else if (!strcmp(act, "write")) + } else if (!strcmp(act, "write")) { + if (td->o.replay_skip & (1u << DDIR_WRITE)) + continue; rw = DDIR_WRITE; - else if (!strcmp(act, "sync")) + } else if (!strcmp(act, "sync")) { + if (td->o.replay_skip & (1u << DDIR_SYNC)) + continue; rw = DDIR_SYNC; - else if (!strcmp(act, "datasync")) + } else if (!strcmp(act, "datasync")) rw = DDIR_DATASYNC; - else if (!strcmp(act, "trim")) + else if (!strcmp(act, "trim")) { + if (td->o.replay_skip & (1u << DDIR_TRIM)) + continue; rw = DDIR_TRIM; - else { + } else { log_err("fio: bad iolog file action: %s\n", act); continue; From d72b10e3ca2f7e3b7ef4e54ea98e4e964a67192d Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Fri, 3 Feb 2023 09:54:50 -0500 Subject: [PATCH 0390/1097] fio: add FIO_RO_NEEDS_RW_OPEN ioengine flag Some oddball cases like sg/bsg require devices to be opened for writing in order to do read commands. So fio has been opening character devices in rw mode for read workloads. However, nvme generic character devices do not need (and may refuse) a writeable open for read workloads. So instead of always opening character devices in rw mode, open devices in rw mode for read workloads only if the ioengine has the FIO_RO_NEEDS_RW_OPEN flag. Link: https://lore.kernel.org/fio/20230203123421.126720-1-joshi.k@samsung.com/ Signed-off-by: Vincent Fu --- engines/sg.c | 2 +- filesetup.c | 2 +- ioengines.h | 2 ++ 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/engines/sg.c b/engines/sg.c index 24783374cb..0bb5be4a9d 100644 --- a/engines/sg.c +++ b/engines/sg.c @@ -1428,7 +1428,7 @@ static struct ioengine_ops ioengine = { .open_file = fio_sgio_open, .close_file = fio_sgio_close, .get_file_size = fio_sgio_get_file_size, - .flags = FIO_SYNCIO | FIO_RAWIO, + .flags = FIO_SYNCIO | FIO_RAWIO | FIO_RO_NEEDS_RW_OPEN, .options = options, .option_struct_size = sizeof(struct sg_options) }; diff --git a/filesetup.c b/filesetup.c index 1d3cc5ad9e..cb7047c5af 100644 --- a/filesetup.c +++ b/filesetup.c @@ -768,7 +768,7 @@ int generic_open_file(struct thread_data *td, struct fio_file *f) else from_hash = file_lookup_open(f, flags); } else if (td_read(td)) { - if (f->filetype == FIO_TYPE_CHAR && !read_only) + if (td_ioengine_flagged(td, FIO_RO_NEEDS_RW_OPEN) && !read_only) flags |= O_RDWR; else flags |= O_RDONLY; diff --git a/ioengines.h b/ioengines.h index d43540d0f6..2cb9743e8e 100644 --- a/ioengines.h +++ b/ioengines.h @@ -89,6 +89,8 @@ enum fio_ioengine_flags { = 1 << 16, /* async ioengine with commit function that sets issue_time */ FIO_SKIPPABLE_IOMEM_ALLOC = 1 << 17, /* skip iomem_alloc & iomem_free if job sets mem/iomem */ + FIO_RO_NEEDS_RW_OPEN + = 1 << 18, /* open files in rw mode even if we have a read job */ }; /* From 2f5102a53152c9e278b9dbaa58ad9cdb36673043 Mon Sep 17 00:00:00 2001 From: Horshack Date: Sun, 5 Feb 2023 21:17:31 -0500 Subject: [PATCH 0391/1097] Improve IOPs 50% by avoiding clock sampling when rate options not used Profiling revealed thread_main() is spending 50% of its time in calls to utime_since_now() from rate_ddir(). This call is only necessary if the user specified a rate option for the job. A conditional was added to avoid the call if !should_check_rate(). See this link for details and profiling data: https://github.com/axboe/fio/issues/1501#issuecomment-1418327049 Signed-off-by: Adam Horshack (horshack@live.com) --- io_u.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/io_u.c b/io_u.c index 8035f4b725..eb617e649c 100644 --- a/io_u.c +++ b/io_u.c @@ -785,7 +785,15 @@ static enum fio_ddir get_rw_ddir(struct thread_data *td) else ddir = DDIR_INVAL; - td->rwmix_ddir = rate_ddir(td, ddir); + if (!should_check_rate(td)) { + /* + * avoid time-consuming call to utime_since_now() if rate checking + * isn't being used. this imrpoves IOPs 50%. See: + * https://github.com/axboe/fio/issues/1501#issuecomment-1418327049 + */ + td->rwmix_ddir = ddir; + } else + td->rwmix_ddir = rate_ddir(td, ddir); return td->rwmix_ddir; } From 6d7f8d9a31f9ecdeab0eed8f23c63b9a94ec61f6 Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Mon, 6 Feb 2023 12:36:37 -0700 Subject: [PATCH 0392/1097] engines/libzbc: set FIO_RO_NEEDS_RW_OPEN engine flag The libzbc engine also needs a writeable open, even for a read-only workload. Fixes: d72b10e3ca2f ("fio: add FIO_RO_NEEDS_RW_OPEN ioengine flag") Reported-by: Kanchan Joshi Signed-off-by: Jens Axboe --- engines/libzbc.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/engines/libzbc.c b/engines/libzbc.c index 2b63ef1aca..dae4fe16e9 100644 --- a/engines/libzbc.c +++ b/engines/libzbc.c @@ -469,7 +469,8 @@ FIO_STATIC struct ioengine_ops ioengine = { .get_max_open_zones = libzbc_get_max_open_zones, .finish_zone = libzbc_finish_zone, .queue = libzbc_queue, - .flags = FIO_SYNCIO | FIO_NOEXTEND | FIO_RAWIO, + .flags = FIO_SYNCIO | FIO_NOEXTEND | FIO_RAWIO | + FIO_RO_NEEDS_RW_OPEN, }; static void fio_init fio_libzbc_register(void) From 06ec57ef41e1530d578f22bb8cc57c5ecd07e5d9 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Tue, 7 Feb 2023 09:33:55 -0500 Subject: [PATCH 0393/1097] Revert "engines/libzbc: set FIO_RO_NEEDS_RW_OPEN engine flag" This reverts commit 6d7f8d9a31f9ecdeab0eed8f23c63b9a94ec61f6. The FIO_RO_NEEDS_RW_OPEN file affects only generic_open_file but the libzbc ioengine has its own file open routine. So the flag has no effect and its presence may be misleading. Signed-off-by: Vincent Fu --- engines/libzbc.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/engines/libzbc.c b/engines/libzbc.c index dae4fe16e9..2b63ef1aca 100644 --- a/engines/libzbc.c +++ b/engines/libzbc.c @@ -469,8 +469,7 @@ FIO_STATIC struct ioengine_ops ioengine = { .get_max_open_zones = libzbc_get_max_open_zones, .finish_zone = libzbc_finish_zone, .queue = libzbc_queue, - .flags = FIO_SYNCIO | FIO_NOEXTEND | FIO_RAWIO | - FIO_RO_NEEDS_RW_OPEN, + .flags = FIO_SYNCIO | FIO_NOEXTEND | FIO_RAWIO, }; static void fio_init fio_libzbc_register(void) From 57cbfced8c837b3b95746359ac6ba34514d68e3c Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Tue, 7 Feb 2023 09:37:29 -0500 Subject: [PATCH 0394/1097] engines/libzbc: for read workloads always open devices with O_RDONLY flag libzbc uses the SG_IO ioctl to send commands to devices (instead of using write() to send commands to character devices). So we don't need to open character devices with the O_RDWR flag. Signed-off-by: Vincent Fu --- engines/libzbc.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/engines/libzbc.c b/engines/libzbc.c index 2b63ef1aca..cb3e9ca545 100644 --- a/engines/libzbc.c +++ b/engines/libzbc.c @@ -68,9 +68,6 @@ static int libzbc_open_dev(struct thread_data *td, struct fio_file *f, if (!read_only) flags |= O_RDWR; } else if (td_read(td)) { - if (f->filetype == FIO_TYPE_CHAR && !read_only) - flags |= O_RDWR; - else flags |= O_RDONLY; } From f0c8ab1c36369d8d6aa214fba572dacefa3a8677 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Tue, 7 Feb 2023 10:44:00 -0500 Subject: [PATCH 0395/1097] ioengines: clarify FIO_RO_NEEDS_RW_OPEN flag This flag is only checked in generic_open_file(). So ioengines with the own open file routines that do not call generic_open_file() will be unaffected. Signed-off-by: Vincent Fu --- ioengines.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ioengines.h b/ioengines.h index 2cb9743e8e..ea79918019 100644 --- a/ioengines.h +++ b/ioengines.h @@ -90,7 +90,8 @@ enum fio_ioengine_flags { FIO_SKIPPABLE_IOMEM_ALLOC = 1 << 17, /* skip iomem_alloc & iomem_free if job sets mem/iomem */ FIO_RO_NEEDS_RW_OPEN - = 1 << 18, /* open files in rw mode even if we have a read job */ + = 1 << 18, /* open files in rw mode even if we have a read job; only + affects ioengines using generic_open_file */ }; /* From c83579f08ac3b3bf87c71ebdca607487c0d77d97 Mon Sep 17 00:00:00 2001 From: Horshack Date: Thu, 9 Feb 2023 11:03:12 -0500 Subject: [PATCH 0396/1097] SIGSEGV / Exit 139 when write_iolog used with io_submit_mode=offload Segmentation fault when log_io_u() attempts to write an entry to a user-specified write_iolog file, if the I/O is issued from an offload thread created by io_submit_mode=offload. Call path: rate-submit.c::io_workqueue_fn() -> td_io_queue() -> log_io_u(td, io_u) The log file handle in thread_data->iolog_f opened by init_iolog() is not being copied to the offload thread's private copy of thread_data, causing a NULL deference when fprintf() is called to write to the log file. Fix is to copy the main thread's td->iolog_f to the offload thread's td at creation time. Seems a bit disjointed to be copying individual fields between these two structures on an as-needed basis rather than having a mechanism to replicate the entire structure, or at least replicating the I/O submission specific fields by moving them into a nested structure that's copied wholesale in io_workqueue_init_worker_fn() - that way future code changes to the I/O submission path wont cause the same bug for fields needed by both the inline and offline submission paths. Signed-off-by: Adam Horshack (horshack@live.com) --- rate-submit.c | 1 + 1 file changed, 1 insertion(+) diff --git a/rate-submit.c b/rate-submit.c index 2fe768c0be..3cc17eaa56 100644 --- a/rate-submit.c +++ b/rate-submit.c @@ -154,6 +154,7 @@ static int io_workqueue_init_worker_fn(struct submit_worker *sw) dup_files(td, parent); td->eo = parent->eo; fio_options_mem_dupe(td); + td->iolog_f = parent->iolog_f; if (ioengine_load(td)) goto err; From b8d07a36d0391fa4530349f92fd74d0e13d540ba Mon Sep 17 00:00:00 2001 From: Horshack Date: Thu, 9 Feb 2023 11:31:55 -0500 Subject: [PATCH 0397/1097] Suppress sync engine QD > 1 warning if io_submit_mode is offload The user is warned if iodepth > 1 when using a synchronous I/O engine, since the engine can only submit one I/O at a time. This warning is not accounting for the case of the user enabling I/O submission offload threads via io_submit_mode=offload. Modified the warning conditional to suppress the warning when this is the case. Signed-off-by: Adam Horshack (horshack@live.com) --- backend.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend.c b/backend.c index 928e524a37..ffd34b36ff 100644 --- a/backend.c +++ b/backend.c @@ -1796,7 +1796,7 @@ static void *thread_main(void *data) if (td_io_init(td)) goto err; - if (td_ioengine_flagged(td, FIO_SYNCIO) && td->o.iodepth > 1) { + if (td_ioengine_flagged(td, FIO_SYNCIO) && td->o.iodepth > 1 && td->o.io_submit_mode != IO_MODE_OFFLOAD) { log_info("note: both iodepth >= 1 and synchronous I/O engine " "are selected, queue depth will be capped at 1\n"); } From 615c794cbf851c994e94fffe8b8f565e64f137a5 Mon Sep 17 00:00:00 2001 From: Horshack Date: Thu, 9 Feb 2023 21:47:38 -0500 Subject: [PATCH 0398/1097] Read stats for backlog verifies not reported for time-expired workloads When verify_backlog is used on a write-only workload with a runtime= value and the runtime expires before the workload has written its full dataset, the read stats for the backlog verifies are not reported, resulting in a stat result showing only the workload writes (ie, the "read:" results section is completely missing from fio's stats output) The logic in thread_main() fails to call update_runtime() for DDIR_READ because the existing call to update_runtime() for DDIR_READ on write-only workloads is currently only done after do_verify() is complete, which wont be called in this scenario because td->terminate is true due to the expiration of the runtime. Link: https://github.com/axboe/fio/issues/1515 Signed-off-by: Adam Horshack (horshack@live.com) --- backend.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/backend.c b/backend.c index 928e524a37..c6b97dd9c8 100644 --- a/backend.c +++ b/backend.c @@ -1919,7 +1919,8 @@ static void *thread_main(void *data) } } while (1); - if (td_read(td) && td->io_bytes[DDIR_READ]) + if (td->io_bytes[DDIR_READ] && (td_read(td) || + ((td->flags & TD_F_VER_BACKLOG) && td_write(td)))) update_runtime(td, elapsed_us, DDIR_READ); if (td_write(td) && td->io_bytes[DDIR_WRITE]) update_runtime(td, elapsed_us, DDIR_WRITE); From 440f63b11300ca0881a77004393e22cce9525b4e Mon Sep 17 00:00:00 2001 From: Shin'ichiro Kawasaki Date: Thu, 9 Feb 2023 16:09:00 +0900 Subject: [PATCH 0399/1097] zbd: refer file->last_start[] instead of sectors with data accounting To decide the first IO direction of randrw workload, the function zbd_adjust_ddir() refers to the zbd_info->sectors_with_data value which indicates the number of bytes written to the zoned block devices being accessed. However, this accounting has two issues. The first issue is wrong accounting for multiple jobs with different write ranges. The second issue is job start up failure due to zone lock contention. Avoid using zbd_info->sectors_with_data and simply refer to file-> last_start[DDIR_WRITE] instead. It is initialized with -1ULL for each job. After any write operation is done by the job, it keeps valid offset. If it has valid offset, written data is expected and the first IO direction can be read. Also remove zbd_info->sectors_with_data, which is no longer used. Keep the field zbd_info->wp_sectors_with_data since it is still used for zones with write pointers. Signed-off-by: Shin'ichiro Kawasaki Reviewed-by: Niklas Cassel Signed-off-by: Vincent Fu --- zbd.c | 14 +++----------- zbd.h | 2 -- 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/zbd.c b/zbd.c index d1e469f665..f5e76c4056 100644 --- a/zbd.c +++ b/zbd.c @@ -286,7 +286,6 @@ static int zbd_reset_zone(struct thread_data *td, struct fio_file *f, } pthread_mutex_lock(&f->zbd_info->mutex); - f->zbd_info->sectors_with_data -= data_in_zone; f->zbd_info->wp_sectors_with_data -= data_in_zone; pthread_mutex_unlock(&f->zbd_info->mutex); @@ -1201,7 +1200,6 @@ static uint64_t zbd_process_swd(struct thread_data *td, const struct fio_file *f, enum swd_action a) { struct fio_zone_info *zb, *ze, *z; - uint64_t swd = 0; uint64_t wp_swd = 0; zb = zbd_get_zone(f, f->min_zone); @@ -1211,17 +1209,14 @@ static uint64_t zbd_process_swd(struct thread_data *td, zone_lock(td, f, z); wp_swd += z->wp - z->start; } - swd += z->wp - z->start; } pthread_mutex_lock(&f->zbd_info->mutex); switch (a) { case CHECK_SWD: - assert(f->zbd_info->sectors_with_data == swd); assert(f->zbd_info->wp_sectors_with_data == wp_swd); break; case SET_SWD: - f->zbd_info->sectors_with_data = swd; f->zbd_info->wp_sectors_with_data = wp_swd; break; } @@ -1231,7 +1226,7 @@ static uint64_t zbd_process_swd(struct thread_data *td, if (z->has_wp) zone_unlock(z); - return swd; + return wp_swd; } /* @@ -1640,10 +1635,8 @@ static void zbd_queue_io(struct thread_data *td, struct io_u *io_u, int q, * have occurred. */ pthread_mutex_lock(&zbd_info->mutex); - if (z->wp <= zone_end) { - zbd_info->sectors_with_data += zone_end - z->wp; + if (z->wp <= zone_end) zbd_info->wp_sectors_with_data += zone_end - z->wp; - } pthread_mutex_unlock(&zbd_info->mutex); z->wp = zone_end; break; @@ -1801,8 +1794,7 @@ enum fio_ddir zbd_adjust_ddir(struct thread_data *td, struct io_u *io_u, if (ddir != DDIR_READ || !td_rw(td)) return ddir; - if (io_u->file->zbd_info->sectors_with_data || - td->o.read_beyond_wp) + if (io_u->file->last_start[DDIR_WRITE] != -1ULL || td->o.read_beyond_wp) return DDIR_READ; return DDIR_WRITE; diff --git a/zbd.h b/zbd.h index d425707e82..9ab25c47e9 100644 --- a/zbd.h +++ b/zbd.h @@ -54,7 +54,6 @@ struct fio_zone_info { * @mutex: Protects the modifiable members in this structure (refcount and * num_open_zones). * @zone_size: size of a single zone in bytes. - * @sectors_with_data: total size of data in all zones in units of 512 bytes * @wp_sectors_with_data: total size of data in zones with write pointers in * units of 512 bytes * @zone_size_log2: log2 of the zone size in bytes if it is a power of 2 or 0 @@ -76,7 +75,6 @@ struct zoned_block_device_info { uint32_t max_open_zones; pthread_mutex_t mutex; uint64_t zone_size; - uint64_t sectors_with_data; uint64_t wp_sectors_with_data; uint32_t zone_size_log2; uint32_t nr_zones; From 0c998b7d4c1d406d3f8561722a08fbad346b6f12 Mon Sep 17 00:00:00 2001 From: Shin'ichiro Kawasaki Date: Thu, 9 Feb 2023 16:09:01 +0900 Subject: [PATCH 0400/1097] zbd: remove CHECK_SWD feature The 'sectors with data' accounting had been used for CHECK_SWD debug feature. It compared expected written data size and actually written data size for zonemode=zbd. However, this feature has been disabled for a while and not actively used. Also, the sector with data accounting has two issues. The first issue is wrong accounting for multiple jobs with different write ranges. The second issue is job start up failure due to zone lock contention. Avoid using the accounting by removing the CHECK_SWD feature and related code. Also rename the function zbd_process_swd() to zbd_set_swd() to clarify that it no longer works for CHECK_SWD. Signed-off-by: Shin'ichiro Kawasaki Reviewed-by: Niklas Cassel Signed-off-by: Vincent Fu --- zbd.c | 38 +++----------------------------------- 1 file changed, 3 insertions(+), 35 deletions(-) diff --git a/zbd.c b/zbd.c index f5e76c4056..b6cf2a93be 100644 --- a/zbd.c +++ b/zbd.c @@ -1190,14 +1190,7 @@ static bool zbd_dec_and_reset_write_cnt(const struct thread_data *td, return write_cnt == 0; } -enum swd_action { - CHECK_SWD, - SET_SWD, -}; - -/* Calculate the number of sectors with data (swd) and perform action 'a' */ -static uint64_t zbd_process_swd(struct thread_data *td, - const struct fio_file *f, enum swd_action a) +static uint64_t zbd_set_swd(struct thread_data *td, const struct fio_file *f) { struct fio_zone_info *zb, *ze, *z; uint64_t wp_swd = 0; @@ -1212,14 +1205,7 @@ static uint64_t zbd_process_swd(struct thread_data *td, } pthread_mutex_lock(&f->zbd_info->mutex); - switch (a) { - case CHECK_SWD: - assert(f->zbd_info->wp_sectors_with_data == wp_swd); - break; - case SET_SWD: - f->zbd_info->wp_sectors_with_data = wp_swd; - break; - } + f->zbd_info->wp_sectors_with_data = wp_swd; pthread_mutex_unlock(&f->zbd_info->mutex); for (z = zb; z < ze; z++) @@ -1229,21 +1215,6 @@ static uint64_t zbd_process_swd(struct thread_data *td, return wp_swd; } -/* - * The swd check is useful for debugging but takes too much time to leave - * it enabled all the time. Hence it is disabled by default. - */ -static const bool enable_check_swd = false; - -/* Check whether the values of zbd_info.*sectors_with_data are correct. */ -static void zbd_check_swd(struct thread_data *td, const struct fio_file *f) -{ - if (!enable_check_swd) - return; - - zbd_process_swd(td, f, CHECK_SWD); -} - void zbd_file_reset(struct thread_data *td, struct fio_file *f) { struct fio_zone_info *zb, *ze; @@ -1255,7 +1226,7 @@ void zbd_file_reset(struct thread_data *td, struct fio_file *f) zb = zbd_get_zone(f, f->min_zone); ze = zbd_get_zone(f, f->max_zone); - swd = zbd_process_swd(td, f, SET_SWD); + swd = zbd_set_swd(td, f); dprint(FD_ZBD, "%s(%s): swd = %" PRIu64 "\n", __func__, f->file_name, swd); @@ -1677,7 +1648,6 @@ static void zbd_put_io(struct thread_data *td, const struct io_u *io_u) zbd_end_zone_io(td, io_u, z); zone_unlock(z); - zbd_check_swd(td, f); } /* @@ -1866,8 +1836,6 @@ enum io_u_action zbd_adjust_block(struct thread_data *td, struct io_u *io_u) io_u->ddir == DDIR_READ && td->o.read_beyond_wp) return io_u_accept; - zbd_check_swd(td, f); - zone_lock(td, f, zb); switch (io_u->ddir) { From d56a6df36315dc8cdd7e63d695b378bc286108d1 Mon Sep 17 00:00:00 2001 From: Shin'ichiro Kawasaki Date: Thu, 9 Feb 2023 16:09:02 +0900 Subject: [PATCH 0401/1097] zbd: rename the accounting 'sectors with data' to 'valid data bytes' The 'sectors with data' accounting was designed to have 'sector' as its unit. Then related variables have the word 'sector' in their names. Also related code comments use the words 'sector' or 'logical blocks'. However, actually it was implemented to have 'byte' as unit. Rename related variables and comments to indicate the byte unit. Also replace the abbreviation swd to vdb. Fixes: a7c2b6fc2959 ("Add support for resetting zones periodically") Signed-off-by: Shin'ichiro Kawasaki Reviewed-by: Niklas Cassel Signed-off-by: Vincent Fu --- t/zbd/test-zbd-support | 4 ++-- zbd.c | 25 +++++++++++++------------ zbd.h | 5 ++--- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/t/zbd/test-zbd-support b/t/zbd/test-zbd-support index 4091d9ac95..c32953c48b 100755 --- a/t/zbd/test-zbd-support +++ b/t/zbd/test-zbd-support @@ -1110,8 +1110,8 @@ test51() { run_fio "${opts[@]}" >> "${logfile}.${test_number}" 2>&1 || return $? } -# Verify that zone_reset_threshold only takes logical blocks from seq -# zones into account, and logical blocks of conv zones are not counted. +# Verify that zone_reset_threshold only accounts written bytes in seq +# zones, and written data bytes of conv zones are not counted. test52() { local off io_size diff --git a/zbd.c b/zbd.c index b6cf2a93be..455dad5311 100644 --- a/zbd.c +++ b/zbd.c @@ -286,7 +286,7 @@ static int zbd_reset_zone(struct thread_data *td, struct fio_file *f, } pthread_mutex_lock(&f->zbd_info->mutex); - f->zbd_info->wp_sectors_with_data -= data_in_zone; + f->zbd_info->wp_valid_data_bytes -= data_in_zone; pthread_mutex_unlock(&f->zbd_info->mutex); z->wp = z->start; @@ -1190,35 +1190,35 @@ static bool zbd_dec_and_reset_write_cnt(const struct thread_data *td, return write_cnt == 0; } -static uint64_t zbd_set_swd(struct thread_data *td, const struct fio_file *f) +static uint64_t zbd_set_vdb(struct thread_data *td, const struct fio_file *f) { struct fio_zone_info *zb, *ze, *z; - uint64_t wp_swd = 0; + uint64_t wp_vdb = 0; zb = zbd_get_zone(f, f->min_zone); ze = zbd_get_zone(f, f->max_zone); for (z = zb; z < ze; z++) { if (z->has_wp) { zone_lock(td, f, z); - wp_swd += z->wp - z->start; + wp_vdb += z->wp - z->start; } } pthread_mutex_lock(&f->zbd_info->mutex); - f->zbd_info->wp_sectors_with_data = wp_swd; + f->zbd_info->wp_valid_data_bytes = wp_vdb; pthread_mutex_unlock(&f->zbd_info->mutex); for (z = zb; z < ze; z++) if (z->has_wp) zone_unlock(z); - return wp_swd; + return wp_vdb; } void zbd_file_reset(struct thread_data *td, struct fio_file *f) { struct fio_zone_info *zb, *ze; - uint64_t swd; + uint64_t vdb; bool verify_data_left = false; if (!f->zbd_info || !td_write(td)) @@ -1226,10 +1226,10 @@ void zbd_file_reset(struct thread_data *td, struct fio_file *f) zb = zbd_get_zone(f, f->min_zone); ze = zbd_get_zone(f, f->max_zone); - swd = zbd_set_swd(td, f); + vdb = zbd_set_vdb(td, f); - dprint(FD_ZBD, "%s(%s): swd = %" PRIu64 "\n", - __func__, f->file_name, swd); + dprint(FD_ZBD, "%s(%s): valid data bytes = %" PRIu64 "\n", + __func__, f->file_name, vdb); /* * If data verification is enabled reset the affected zones before @@ -1607,7 +1607,7 @@ static void zbd_queue_io(struct thread_data *td, struct io_u *io_u, int q, */ pthread_mutex_lock(&zbd_info->mutex); if (z->wp <= zone_end) - zbd_info->wp_sectors_with_data += zone_end - z->wp; + zbd_info->wp_valid_data_bytes += zone_end - z->wp; pthread_mutex_unlock(&zbd_info->mutex); z->wp = zone_end; break; @@ -1960,7 +1960,8 @@ enum io_u_action zbd_adjust_block(struct thread_data *td, struct io_u *io_u) /* Check whether the zone reset threshold has been exceeded */ if (td->o.zrf.u.f) { - if (zbdi->wp_sectors_with_data >= f->io_size * td->o.zrt.u.f && + if (zbdi->wp_valid_data_bytes >= + f->io_size * td->o.zrt.u.f && zbd_dec_and_reset_write_cnt(td, f)) zb->reset_zone = 1; } diff --git a/zbd.h b/zbd.h index 9ab25c47e9..20b2fe17dd 100644 --- a/zbd.h +++ b/zbd.h @@ -54,8 +54,7 @@ struct fio_zone_info { * @mutex: Protects the modifiable members in this structure (refcount and * num_open_zones). * @zone_size: size of a single zone in bytes. - * @wp_sectors_with_data: total size of data in zones with write pointers in - * units of 512 bytes + * @wp_valid_data_bytes: total size of data in zones with write pointers * @zone_size_log2: log2 of the zone size in bytes if it is a power of 2 or 0 * if the zone size is not a power of 2. * @nr_zones: number of zones @@ -75,7 +74,7 @@ struct zoned_block_device_info { uint32_t max_open_zones; pthread_mutex_t mutex; uint64_t zone_size; - uint64_t wp_sectors_with_data; + uint64_t wp_valid_data_bytes; uint32_t zone_size_log2; uint32_t nr_zones; uint32_t refcount; From d65625eb041b273b9908636142184550469e3245 Mon Sep 17 00:00:00 2001 From: Shin'ichiro Kawasaki Date: Thu, 9 Feb 2023 16:09:03 +0900 Subject: [PATCH 0402/1097] doc: fix unit of zone_reset_threshold and relation to other option The zone_reset_threshold option uses the 'sectors with data' accounting then it was described to have 'logical block' as its unit. However, the accounting was implemented with 'byte' unit. Fix the description of the option. Also, the zone_reset_threshold option works together with the zone_reset_frequency option. Describe this relation also. Signed-off-by: Shin'ichiro Kawasaki Reviewed-by: Niklas Cassel Signed-off-by: Vincent Fu --- HOWTO.rst | 7 ++++--- fio.1 | 7 ++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/HOWTO.rst b/HOWTO.rst index 17caaf5d38..a5f8cc2df3 100644 --- a/HOWTO.rst +++ b/HOWTO.rst @@ -1085,9 +1085,10 @@ Target file/device .. option:: zone_reset_threshold=float - A number between zero and one that indicates the ratio of logical - blocks with data to the total number of logical blocks in the test - above which zones should be reset periodically. + A number between zero and one that indicates the ratio of written bytes + in the zones with write pointers in the IO range to the size of the IO + range. When current ratio is above this ratio, zones are reset + periodically as :option:`zone_reset_frequency` specifies. .. option:: zone_reset_frequency=float diff --git a/fio.1 b/fio.1 index 527b3d4605..3556ad358f 100644 --- a/fio.1 +++ b/fio.1 @@ -854,9 +854,10 @@ of the zoned block device in use, thus allowing the option \fBmax_open_zones\fR value to be larger than the device reported limit. Default: false. .TP .BI zone_reset_threshold \fR=\fPfloat -A number between zero and one that indicates the ratio of logical blocks with -data to the total number of logical blocks in the test above which zones -should be reset periodically. +A number between zero and one that indicates the ratio of written bytes in the +zones with write pointers in the IO range to the size of the IO range. When +current ratio is above this ratio, zones are reset periodically as +\fBzone_reset_frequency\fR specifies. .TP .BI zone_reset_frequency \fR=\fPfloat A number between zero and one that indicates how often a zone reset should be From 2fb29f27e04d94cd30f8c59554302ace5d69972e Mon Sep 17 00:00:00 2001 From: Shin'ichiro Kawasaki Date: Thu, 9 Feb 2023 16:09:04 +0900 Subject: [PATCH 0403/1097] zbd: account valid data bytes only for zone_reset_threshold option The valid data bytes accounting is used only for zone_reset_threshold option. Avoid the accounting when the option is not specified. Signed-off-by: Shin'ichiro Kawasaki Reviewed-by: Niklas Cassel Signed-off-by: Vincent Fu --- zbd.c | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/zbd.c b/zbd.c index 455dad5311..6783acf987 100644 --- a/zbd.c +++ b/zbd.c @@ -147,6 +147,11 @@ zbd_offset_to_zone(const struct fio_file *f, uint64_t offset) return zbd_get_zone(f, zbd_offset_to_zone_idx(f, offset)); } +static bool accounting_vdb(struct thread_data *td, const struct fio_file *f) +{ + return td->o.zrt.u.f && td_write(td); +} + /** * zbd_get_zoned_model - Get a device zoned model * @td: FIO thread data @@ -285,9 +290,11 @@ static int zbd_reset_zone(struct thread_data *td, struct fio_file *f, break; } - pthread_mutex_lock(&f->zbd_info->mutex); - f->zbd_info->wp_valid_data_bytes -= data_in_zone; - pthread_mutex_unlock(&f->zbd_info->mutex); + if (accounting_vdb(td, f)) { + pthread_mutex_lock(&f->zbd_info->mutex); + f->zbd_info->wp_valid_data_bytes -= data_in_zone; + pthread_mutex_unlock(&f->zbd_info->mutex); + } z->wp = z->start; @@ -1195,6 +1202,9 @@ static uint64_t zbd_set_vdb(struct thread_data *td, const struct fio_file *f) struct fio_zone_info *zb, *ze, *z; uint64_t wp_vdb = 0; + if (!accounting_vdb(td, f)) + return 0; + zb = zbd_get_zone(f, f->min_zone); ze = zbd_get_zone(f, f->max_zone); for (z = zb; z < ze; z++) { @@ -1605,10 +1615,11 @@ static void zbd_queue_io(struct thread_data *td, struct io_u *io_u, int q, * z->wp > zone_end means that one or more I/O errors * have occurred. */ - pthread_mutex_lock(&zbd_info->mutex); - if (z->wp <= zone_end) + if (accounting_vdb(td, f) && z->wp <= zone_end) { + pthread_mutex_lock(&zbd_info->mutex); zbd_info->wp_valid_data_bytes += zone_end - z->wp; - pthread_mutex_unlock(&zbd_info->mutex); + pthread_mutex_unlock(&zbd_info->mutex); + } z->wp = zone_end; break; default: From b3e9bd036a8ce3a81dd03293852de130848beb38 Mon Sep 17 00:00:00 2001 From: Shin'ichiro Kawasaki Date: Thu, 9 Feb 2023 16:09:05 +0900 Subject: [PATCH 0404/1097] zbd: check write ranges for zone_reset_threshold option The valid data bytes accounting is used for zone_reset_threshold option. This accounting usage has two issues. The first issue is unexpected zone reset due to different IO ranges. The valid data bytes accounting is done for all IO ranges per device, and shared by all jobs. On the other hand, the zone_reset_threshold option is defined as the ratio to each job's IO range. When a job refers to the accounting value, it includes writes to IO ranges out of the job's IO range. Then zone reset is triggered earlier than expected. The second issue is accounting value initialization. The initialization of the accounting field is repeated for each job, then the value initialized by the first job is overwritten by other jobs. This works as expected for single job or multiple jobs with same write range. However, when multiple jobs have different write ranges, the overwritten value is wrong except for the last job. To ensure that the accounting works as expected for the option, check that write ranges of all jobs are same. If jobs have different write ranges, report it as an error. Initialize the accounting field only once for the first job. All jobs have same write range, then one time initialization is enough. Update man page to clarify this limitation of the option. Signed-off-by: Shin'ichiro Kawasaki Reviewed-by: Niklas Cassel Signed-off-by: Vincent Fu --- HOWTO.rst | 4 +++- fio.1 | 3 ++- zbd.c | 31 +++++++++++++++++++++++++++---- zbd.h | 4 ++++ 4 files changed, 36 insertions(+), 6 deletions(-) diff --git a/HOWTO.rst b/HOWTO.rst index a5f8cc2df3..158c5d897e 100644 --- a/HOWTO.rst +++ b/HOWTO.rst @@ -1088,7 +1088,9 @@ Target file/device A number between zero and one that indicates the ratio of written bytes in the zones with write pointers in the IO range to the size of the IO range. When current ratio is above this ratio, zones are reset - periodically as :option:`zone_reset_frequency` specifies. + periodically as :option:`zone_reset_frequency` specifies. If there are + multiple jobs when using this option, the IO range for all write jobs + has to be the same. .. option:: zone_reset_frequency=float diff --git a/fio.1 b/fio.1 index 3556ad358f..00a0935390 100644 --- a/fio.1 +++ b/fio.1 @@ -857,7 +857,8 @@ value to be larger than the device reported limit. Default: false. A number between zero and one that indicates the ratio of written bytes in the zones with write pointers in the IO range to the size of the IO range. When current ratio is above this ratio, zones are reset periodically as -\fBzone_reset_frequency\fR specifies. +\fBzone_reset_frequency\fR specifies. If there are multiple jobs when using this +option, the IO range for all write jobs has to be the same. .TP .BI zone_reset_frequency \fR=\fPfloat A number between zero and one that indicates how often a zone reset should be diff --git a/zbd.c b/zbd.c index 6783acf987..893794315b 100644 --- a/zbd.c +++ b/zbd.c @@ -542,7 +542,7 @@ static bool zbd_using_direct_io(void) } /* Whether or not the I/O range for f includes one or more sequential zones */ -static bool zbd_is_seq_job(struct fio_file *f) +static bool zbd_is_seq_job(const struct fio_file *f) { uint32_t zone_idx, zone_idx_b, zone_idx_e; @@ -1201,10 +1201,33 @@ static uint64_t zbd_set_vdb(struct thread_data *td, const struct fio_file *f) { struct fio_zone_info *zb, *ze, *z; uint64_t wp_vdb = 0; + struct zoned_block_device_info *zbdi = f->zbd_info; if (!accounting_vdb(td, f)) return 0; + /* + * Ensure that the I/O range includes one or more sequential zones so + * that f->min_zone and f->max_zone have different values. + */ + if (!zbd_is_seq_job(f)) + return 0; + + if (zbdi->write_min_zone != zbdi->write_max_zone) { + if (zbdi->write_min_zone != f->min_zone || + zbdi->write_max_zone != f->max_zone) { + td_verror(td, EINVAL, + "multi-jobs with different write ranges are " + "not supported with zone_reset_threshold"); + log_err("multi-jobs with different write ranges are " + "not supported with zone_reset_threshold\n"); + } + return 0; + } + + zbdi->write_min_zone = f->min_zone; + zbdi->write_max_zone = f->max_zone; + zb = zbd_get_zone(f, f->min_zone); ze = zbd_get_zone(f, f->max_zone); for (z = zb; z < ze; z++) { @@ -1214,9 +1237,9 @@ static uint64_t zbd_set_vdb(struct thread_data *td, const struct fio_file *f) } } - pthread_mutex_lock(&f->zbd_info->mutex); - f->zbd_info->wp_valid_data_bytes = wp_vdb; - pthread_mutex_unlock(&f->zbd_info->mutex); + pthread_mutex_lock(&zbdi->mutex); + zbdi->wp_valid_data_bytes = wp_vdb; + pthread_mutex_unlock(&zbdi->mutex); for (z = zb; z < ze; z++) if (z->has_wp) diff --git a/zbd.h b/zbd.h index 20b2fe17dd..05189555e0 100644 --- a/zbd.h +++ b/zbd.h @@ -55,6 +55,8 @@ struct fio_zone_info { * num_open_zones). * @zone_size: size of a single zone in bytes. * @wp_valid_data_bytes: total size of data in zones with write pointers + * @write_min_zone: Minimum zone index of all job's write ranges. Inclusive. + * @write_max_zone: Maximum zone index of all job's write ranges. Exclusive. * @zone_size_log2: log2 of the zone size in bytes if it is a power of 2 or 0 * if the zone size is not a power of 2. * @nr_zones: number of zones @@ -75,6 +77,8 @@ struct zoned_block_device_info { pthread_mutex_t mutex; uint64_t zone_size; uint64_t wp_valid_data_bytes; + uint32_t write_min_zone; + uint32_t write_max_zone; uint32_t zone_size_log2; uint32_t nr_zones; uint32_t refcount; From 9fb714dae71993dda948c4757fdad6ee580099b8 Mon Sep 17 00:00:00 2001 From: Shin'ichiro Kawasaki Date: Thu, 9 Feb 2023 16:09:06 +0900 Subject: [PATCH 0405/1097] zbd: initialize valid data bytes accounting at file setup The valid data bytes accounting field is initialized at file reset, after each job started. Each job locks zones to check write pointer positions of its write target zones. This can cause zone lock contention with write by other jobs. To avoid the zone lock contention, move the initialization from file reset to file setup before job start. It allows to access the write pointers and the accounting field without locks. Remove the lock and unlock codes which are no longer required. Ensure the locks are not required by checking run-state in the struct thread_data. Also rename the function zbd_set_vdb() to zbd_verify_and_set_vdb() to be consistent with other functions called in zbd_setup_files(). Signed-off-by: Shin'ichiro Kawasaki Reviewed-by: Niklas Cassel Signed-off-by: Vincent Fu --- zbd.c | 108 ++++++++++++++++++++++++++++------------------------------ 1 file changed, 52 insertions(+), 56 deletions(-) diff --git a/zbd.c b/zbd.c index 893794315b..ba2c0401a9 100644 --- a/zbd.c +++ b/zbd.c @@ -1074,6 +1074,52 @@ void zbd_recalc_options_with_zone_granularity(struct thread_data *td) } } +static uint64_t zbd_verify_and_set_vdb(struct thread_data *td, + const struct fio_file *f) +{ + struct fio_zone_info *zb, *ze, *z; + uint64_t wp_vdb = 0; + struct zoned_block_device_info *zbdi = f->zbd_info; + + assert(td->runstate < TD_RUNNING); + assert(zbdi); + + if (!accounting_vdb(td, f)) + return 0; + + /* + * Ensure that the I/O range includes one or more sequential zones so + * that f->min_zone and f->max_zone have different values. + */ + if (!zbd_is_seq_job(f)) + return 0; + + if (zbdi->write_min_zone != zbdi->write_max_zone) { + if (zbdi->write_min_zone != f->min_zone || + zbdi->write_max_zone != f->max_zone) { + td_verror(td, EINVAL, + "multi-jobs with different write ranges are " + "not supported with zone_reset_threshold"); + log_err("multi-jobs with different write ranges are " + "not supported with zone_reset_threshold\n"); + } + return 0; + } + + zbdi->write_min_zone = f->min_zone; + zbdi->write_max_zone = f->max_zone; + + zb = zbd_get_zone(f, f->min_zone); + ze = zbd_get_zone(f, f->max_zone); + for (z = zb; z < ze; z++) + if (z->has_wp) + wp_vdb += z->wp - z->start; + + zbdi->wp_valid_data_bytes = wp_vdb; + + return wp_vdb; +} + int zbd_setup_files(struct thread_data *td) { struct fio_file *f; @@ -1099,6 +1145,7 @@ int zbd_setup_files(struct thread_data *td) struct zoned_block_device_info *zbd = f->zbd_info; struct fio_zone_info *z; int zi; + uint64_t vdb; assert(zbd); @@ -1106,6 +1153,11 @@ int zbd_setup_files(struct thread_data *td) f->max_zone = zbd_offset_to_zone_idx(f, f->file_offset + f->io_size); + vdb = zbd_verify_and_set_vdb(td, f); + + dprint(FD_ZBD, "%s(%s): valid data bytes = %" PRIu64 "\n", + __func__, f->file_name, vdb); + /* * When all zones in the I/O range are conventional, io_size * can be smaller than zone size, making min_zone the same @@ -1197,61 +1249,9 @@ static bool zbd_dec_and_reset_write_cnt(const struct thread_data *td, return write_cnt == 0; } -static uint64_t zbd_set_vdb(struct thread_data *td, const struct fio_file *f) -{ - struct fio_zone_info *zb, *ze, *z; - uint64_t wp_vdb = 0; - struct zoned_block_device_info *zbdi = f->zbd_info; - - if (!accounting_vdb(td, f)) - return 0; - - /* - * Ensure that the I/O range includes one or more sequential zones so - * that f->min_zone and f->max_zone have different values. - */ - if (!zbd_is_seq_job(f)) - return 0; - - if (zbdi->write_min_zone != zbdi->write_max_zone) { - if (zbdi->write_min_zone != f->min_zone || - zbdi->write_max_zone != f->max_zone) { - td_verror(td, EINVAL, - "multi-jobs with different write ranges are " - "not supported with zone_reset_threshold"); - log_err("multi-jobs with different write ranges are " - "not supported with zone_reset_threshold\n"); - } - return 0; - } - - zbdi->write_min_zone = f->min_zone; - zbdi->write_max_zone = f->max_zone; - - zb = zbd_get_zone(f, f->min_zone); - ze = zbd_get_zone(f, f->max_zone); - for (z = zb; z < ze; z++) { - if (z->has_wp) { - zone_lock(td, f, z); - wp_vdb += z->wp - z->start; - } - } - - pthread_mutex_lock(&zbdi->mutex); - zbdi->wp_valid_data_bytes = wp_vdb; - pthread_mutex_unlock(&zbdi->mutex); - - for (z = zb; z < ze; z++) - if (z->has_wp) - zone_unlock(z); - - return wp_vdb; -} - void zbd_file_reset(struct thread_data *td, struct fio_file *f) { struct fio_zone_info *zb, *ze; - uint64_t vdb; bool verify_data_left = false; if (!f->zbd_info || !td_write(td)) @@ -1259,10 +1259,6 @@ void zbd_file_reset(struct thread_data *td, struct fio_file *f) zb = zbd_get_zone(f, f->min_zone); ze = zbd_get_zone(f, f->max_zone); - vdb = zbd_set_vdb(td, f); - - dprint(FD_ZBD, "%s(%s): valid data bytes = %" PRIu64 "\n", - __func__, f->file_name, vdb); /* * If data verification is enabled reset the affected zones before From e31c83cbd03f89231c36f346bf19ee95f29c2a51 Mon Sep 17 00:00:00 2001 From: Shin'ichiro Kawasaki Date: Thu, 9 Feb 2023 16:09:07 +0900 Subject: [PATCH 0406/1097] t/zbd: add test cases for zone_reset_threshold option The zone_reset_threshold option works for multiple jobs only when the jobs have same write range. Add three test cases to confirm that the option works for multiple jobs as expected. The first test case checks that different write ranges are reported as an error. The second test case checks that multiple write jobs work when they have same write range. The third test case checks that a read job and a write job work when they have different IO ranges. Signed-off-by: Shin'ichiro Kawasaki Reviewed-by: Niklas Cassel Signed-off-by: Vincent Fu --- t/zbd/test-zbd-support | 56 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/t/zbd/test-zbd-support b/t/zbd/test-zbd-support index c32953c48b..893aff3ce5 100755 --- a/t/zbd/test-zbd-support +++ b/t/zbd/test-zbd-support @@ -1305,6 +1305,62 @@ test60() { grep -q 'not support experimental verify' "${logfile}.${test_number}" } +# Test fio errors out zone_reset_threshold option for multiple jobs with +# different write ranges. +test61() { + run_fio_on_seq "$(ioengine "psync")" --rw=write --size="$zone_size" \ + --numjobs=2 --offset_increment="$zone_size" \ + --zone_reset_threshold=0.1 --zone_reset_frequency=1 \ + --exitall_on_error=1 \ + >> "${logfile}.${test_number}" 2>&1 && return 1 + grep -q 'different write ranges' "${logfile}.${test_number}" +} + +# Test zone_reset_threshold option works for multiple jobs with same write +# range. +test62() { + local bs loops=2 size=$((zone_size)) + + [ -n "$is_zbd" ] && reset_zone "$dev" -1 + + # Two jobs write to single zone twice. Reset zone happens at next write + # after half of the zone gets filled. So 2 * 2 * 2 - 1 = 7 times zone + # resets are expected. + bs=$(min $((256*1024)) $((zone_size / 4))) + run_fio_on_seq "$(ioengine "psync")" --rw=write --bs="$bs" \ + --size=$size --loops=$loops --numjobs=2 \ + --zone_reset_frequency=1 --zone_reset_threshold=.5 \ + --group_reporting=1 \ + >> "${logfile}.${test_number}" 2>&1 || return $? + check_written $((size * loops * 2)) || return $? + check_reset_count -eq 7 || return $? +} + +# Test zone_reset_threshold option works for a read job and a write job with +# different IO range. +test63() { + local bs loops=2 size=$((zone_size)) off1 off2 + + [ -n "$is_zbd" ] && reset_zone "$dev" -1 + + off1=$((first_sequential_zone_sector * 512)) + off2=$((off1 + zone_size)) + bs=$(min $((256*1024)) $((zone_size / 4))) + + # One job writes to single zone twice. Reset zone happens at next write + # after half of the zone gets filled. So 2 * 2 - 1 = 3 times zone resets + # are expected. + run_fio "$(ioengine "psync")" --bs="$bs" --size=$size --loops=$loops \ + --filename="$dev" --group_reporting=1 \ + --zonemode=zbd --zonesize="$zone_size" --direct=1 \ + --zone_reset_frequency=1 --zone_reset_threshold=.5 \ + --name=r --rw=read --offset=$off1 "${job_var_opts[@]}" \ + --name=w --rw=write --offset=$off2 "${job_var_opts[@]}" \ + >> "${logfile}.${test_number}" 2>&1 || return $? + check_written $((size * loops)) || return $? + check_reset_count -eq 3 || return $? +} + SECONDS=0 tests=() dynamic_analyzer=() From 46124fb10c1be7bdbd54ff59a76516e35ad8a71f Mon Sep 17 00:00:00 2001 From: "Richard W.M. Jones" Date: Mon, 13 Feb 2023 13:23:27 +0000 Subject: [PATCH 0407/1097] examples: Small updates to nbd.fio Improve the documentation, describing how to use nbdkit with a local file. Move the suggested test file to /var/tmp since /tmp might be a tmpfs. Use indenting to make it easier to read. Use ${uri} instead of ${unixsocket} since nbdkit 1.14 was released nearly 4 years ago. Signed-off-by: Richard W.M. Jones Signed-off-by: Vincent Fu --- examples/nbd.fio | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/examples/nbd.fio b/examples/nbd.fio index 6900ebe7f0..31629fad70 100644 --- a/examples/nbd.fio +++ b/examples/nbd.fio @@ -1,21 +1,25 @@ -# To use fio to test nbdkit: +# To use fio to test nbdkit + RAM disk: # -# nbdkit -U - memory size=256M --run 'export unixsocket; fio examples/nbd.fio' +# nbdkit -U - memory size=256M --run 'export uri; fio examples/nbd.fio' # -# To use fio to test qemu-nbd: +# To use fio to test nbdkit + local file: # -# rm -f /tmp/disk.img /tmp/socket -# truncate -s 256M /tmp/disk.img -# export unixsocket=/tmp/socket -# qemu-nbd -t -k $unixsocket -f raw /tmp/disk.img & -# fio examples/nbd.fio -# killall qemu-nbd +# rm -f /var/tmp/disk.img +# truncate -s 256M /var/tmp/disk.img +# nbdkit -U - file /var/tmp/disk.img --run 'export uri; fio examples/nbd.fio' +# +# To use fio to test qemu-nbd + local file: +# +# rm -f /var/tmp/disk.img /var/tmp/socket +# truncate -s 256M /var/tmp/disk.img +# export uri='nbd+unix:///?socket=/var/tmp/socket' +# qemu-nbd -t -k /var/tmp/socket -f raw /var/tmp/disk.img & +# fio examples/nbd.fio +# killall qemu-nbd [global] ioengine=nbd -uri=nbd+unix:///?socket=${unixsocket} -# Starting from nbdkit 1.14 the following will work: -#uri=${uri} +uri=${uri} rw=randrw time_based runtime=60 From 1bd16cf9c113fcf9d49cae07da50e8a5c7a784ee Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Tue, 14 Feb 2023 10:47:50 -0500 Subject: [PATCH 0408/1097] examples: update nbd.fio fiograph diagram Signed-off-by: Vincent Fu --- examples/nbd.png | Bin 88667 -> 43251 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/examples/nbd.png b/examples/nbd.png index e3bcf6105882ea639b89e9774d1c5df2c72c588b..3a933c9ba0d2aab84447844e6fbcee6c2d4cb8e9 100644 GIT binary patch literal 43251 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 JR3Gs2{sexZJx~Au literal 88667 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 M(m0v&m(i{N0nqk1-~a#s From 04c1cdc4c108c6537681ab7c50daaed6d2fb4c93 Mon Sep 17 00:00:00 2001 From: osalyk Date: Fri, 17 Feb 2023 02:56:51 -0500 Subject: [PATCH 0409/1097] pmemblk: remove pmemblk engine No further support or maintenance of the libpmemblk library is planned. https://pmem.io/blog/2022/11/update-on-pmdk-and-our-long-term-support-strategy/ https://github.com/pmem/pmdk/pull/5538 Signed-off-by: osalyk --- HOWTO.rst | 5 - Makefile | 5 - ci/actions-install.sh | 1 - configure | 41 ---- engines/pmemblk.c | 449 ---------------------------------------- examples/pmemblk.fio | 71 ------- examples/pmemblk.png | Bin 107529 -> 0 bytes fio.1 | 5 - options.c | 6 - os/windows/examples.wxs | 4 - 10 files changed, 587 deletions(-) delete mode 100644 engines/pmemblk.c delete mode 100644 examples/pmemblk.fio delete mode 100644 examples/pmemblk.png diff --git a/HOWTO.rst b/HOWTO.rst index 158c5d897e..1adbe2ae48 100644 --- a/HOWTO.rst +++ b/HOWTO.rst @@ -2147,11 +2147,6 @@ I/O engine before overwriting. The `trimwrite` mode works well for this constraint. - **pmemblk** - Read and write using filesystem DAX to a file on a filesystem - mounted with DAX on a persistent memory device through the PMDK - libpmemblk library. - **dev-dax** Read and write using device DAX to a persistent memory device (e.g., /dev/dax0.0) through the PMDK libpmem library. diff --git a/Makefile b/Makefile index 5f4e65620f..e4cde4bac8 100644 --- a/Makefile +++ b/Makefile @@ -208,11 +208,6 @@ ifdef CONFIG_MTD SOURCE += oslib/libmtd.c SOURCE += oslib/libmtd_legacy.c endif -ifdef CONFIG_PMEMBLK - pmemblk_SRCS = engines/pmemblk.c - pmemblk_LIBS = -lpmemblk - ENGINES += pmemblk -endif ifdef CONFIG_LINUX_DEVDAX dev-dax_SRCS = engines/dev-dax.c dev-dax_LIBS = -lpmem diff --git a/ci/actions-install.sh b/ci/actions-install.sh index c16dff162e..5057fca39c 100755 --- a/ci/actions-install.sh +++ b/ci/actions-install.sh @@ -45,7 +45,6 @@ DPKGCFG libnbd-dev libpmem-dev libpmem2-dev - libpmemblk-dev libprotobuf-c-dev librbd-dev libtcmalloc-minimal4 diff --git a/configure b/configure index 182cd3c3c9..0d02bce81e 100755 --- a/configure +++ b/configure @@ -163,7 +163,6 @@ show_help="no" exit_val=0 gfio_check="no" libhdfs="no" -pmemblk="no" devdax="no" pmem="no" cuda="no" @@ -2229,43 +2228,6 @@ if compile_prog "" "-lpmem2" "libpmem2"; then fi print_config "libpmem2" "$libpmem2" -########################################## -# Check whether we have libpmemblk -# libpmem is a prerequisite -if test "$libpmemblk" != "yes" ; then - libpmemblk="no" -fi -if test "$libpmem" = "yes"; then - cat > $TMPC << EOF -#include -int main(int argc, char **argv) -{ - PMEMblkpool *pbp; - pbp = pmemblk_open("", 0); - return 0; -} -EOF - if compile_prog "" "-lpmemblk" "libpmemblk"; then - libpmemblk="yes" - fi -fi -print_config "libpmemblk" "$libpmemblk" - -# Choose libpmem-based ioengines -if test "$libpmem" = "yes" && test "$disable_pmem" = "no"; then - devdax="yes" - if test "$libpmem1_5" = "yes"; then - pmem="yes" - fi - if test "$libpmemblk" = "yes"; then - pmemblk="yes" - fi -fi - -########################################## -# Report whether pmemblk engine is enabled -print_config "PMDK pmemblk engine" "$pmemblk" - ########################################## # Report whether dev-dax engine is enabled print_config "PMDK dev-dax engine" "$devdax" @@ -3191,9 +3153,6 @@ fi if test "$mtd" = "yes" ; then output_sym "CONFIG_MTD" fi -if test "$pmemblk" = "yes" ; then - output_sym "CONFIG_PMEMBLK" -fi if test "$devdax" = "yes" ; then output_sym "CONFIG_LINUX_DEVDAX" fi diff --git a/engines/pmemblk.c b/engines/pmemblk.c deleted file mode 100644 index 849d8a15a0..0000000000 --- a/engines/pmemblk.c +++ /dev/null @@ -1,449 +0,0 @@ -/* - * pmemblk: IO engine that uses PMDK libpmemblk to read and write data - * - * Copyright (C) 2016 Hewlett Packard Enterprise Development LP - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License, - * version 2 as published by the Free Software Foundation.. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public - * License along with this program; if not, write to the Free - * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ - -/* - * pmemblk engine - * - * IO engine that uses libpmemblk to read and write data - * - * To use: - * ioengine=pmemblk - * - * Other relevant settings: - * thread=1 REQUIRED - * iodepth=1 - * direct=1 - * unlink=1 - * filename=/mnt/pmem0/fiotestfile,BSIZE,FSIZEMiB - * - * thread must be set to 1 for pmemblk as multiple processes cannot - * open the same block pool file. - * - * iodepth should be set to 1 as pmemblk is always synchronous. - * Use numjobs to scale up. - * - * direct=1 is implied as pmemblk is always direct. A warning message - * is printed if this is not specified. - * - * unlink=1 removes the block pool file after testing, and is optional. - * - * The pmem device must have a DAX-capable filesystem and be mounted - * with DAX enabled. filename must point to a file on that filesystem. - * - * Example: - * mkfs.xfs /dev/pmem0 - * mkdir /mnt/pmem0 - * mount -o dax /dev/pmem0 /mnt/pmem0 - * - * When specifying the filename, if the block pool file does not already - * exist, then the pmemblk engine creates the pool file if you specify - * the block and file sizes. BSIZE is the block size in bytes. - * FSIZEMB is the pool file size in MiB. - * - * See examples/pmemblk.fio for more. - * - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "../fio.h" - -/* - * libpmemblk - */ -typedef struct fio_pmemblk_file *fio_pmemblk_file_t; - -struct fio_pmemblk_file { - fio_pmemblk_file_t pmb_next; - char *pmb_filename; - uint64_t pmb_refcnt; - PMEMblkpool *pmb_pool; - size_t pmb_bsize; - size_t pmb_nblocks; -}; - -static fio_pmemblk_file_t Cache; - -static pthread_mutex_t CacheLock = PTHREAD_MUTEX_INITIALIZER; - -#define PMB_CREATE (0x0001) /* should create file */ - -fio_pmemblk_file_t fio_pmemblk_cache_lookup(const char *filename) -{ - fio_pmemblk_file_t i; - - for (i = Cache; i != NULL; i = i->pmb_next) - if (!strcmp(filename, i->pmb_filename)) - return i; - - return NULL; -} - -static void fio_pmemblk_cache_insert(fio_pmemblk_file_t pmb) -{ - pmb->pmb_next = Cache; - Cache = pmb; -} - -static void fio_pmemblk_cache_remove(fio_pmemblk_file_t pmb) -{ - fio_pmemblk_file_t i; - - if (pmb == Cache) { - Cache = Cache->pmb_next; - pmb->pmb_next = NULL; - return; - } - - for (i = Cache; i != NULL; i = i->pmb_next) - if (pmb == i->pmb_next) { - i->pmb_next = i->pmb_next->pmb_next; - pmb->pmb_next = NULL; - return; - } -} - -/* - * to control block size and gross file size at the libpmemblk - * level, we allow the block size and file size to be appended - * to the file name: - * - * path[,bsize,fsizemib] - * - * note that we do not use the fio option "filesize" to dictate - * the file size because we can only give libpmemblk the gross - * file size, which is different from the net or usable file - * size (which is probably what fio wants). - * - * the final path without the parameters is returned in ppath. - * the block size and file size are returned in pbsize and fsize. - * - * note that the user specifies the file size in MiB, but - * we return bytes from here. - */ -static void pmb_parse_path(const char *pathspec, char **ppath, uint64_t *pbsize, - uint64_t *pfsize) -{ - char *path; - char *s; - uint64_t bsize; - uint64_t fsizemib; - - path = strdup(pathspec); - if (!path) { - *ppath = NULL; - return; - } - - /* extract sizes, if given */ - s = strrchr(path, ','); - if (s && (fsizemib = strtoull(s + 1, NULL, 10))) { - *s = 0; - s = strrchr(path, ','); - if (s && (bsize = strtoull(s + 1, NULL, 10))) { - *s = 0; - *ppath = path; - *pbsize = bsize; - *pfsize = fsizemib << 20; - return; - } - } - - /* size specs not found */ - strcpy(path, pathspec); - *ppath = path; - *pbsize = 0; - *pfsize = 0; -} - -static fio_pmemblk_file_t pmb_open(const char *pathspec, int flags) -{ - fio_pmemblk_file_t pmb; - char *path = NULL; - uint64_t bsize = 0; - uint64_t fsize = 0; - - pmb_parse_path(pathspec, &path, &bsize, &fsize); - if (!path) - return NULL; - - pthread_mutex_lock(&CacheLock); - - pmb = fio_pmemblk_cache_lookup(path); - if (!pmb) { - pmb = malloc(sizeof(*pmb)); - if (!pmb) - goto error; - - /* try opening existing first, create it if needed */ - pmb->pmb_pool = pmemblk_open(path, bsize); - if (!pmb->pmb_pool && (errno == ENOENT) && - (flags & PMB_CREATE) && (0 < fsize) && (0 < bsize)) { - pmb->pmb_pool = - pmemblk_create(path, bsize, fsize, 0644); - } - if (!pmb->pmb_pool) { - log_err("pmemblk: unable to open pmemblk pool file %s (%s)\n", - path, strerror(errno)); - goto error; - } - - pmb->pmb_filename = path; - pmb->pmb_next = NULL; - pmb->pmb_refcnt = 0; - pmb->pmb_bsize = pmemblk_bsize(pmb->pmb_pool); - pmb->pmb_nblocks = pmemblk_nblock(pmb->pmb_pool); - - fio_pmemblk_cache_insert(pmb); - } else { - free(path); - } - - pmb->pmb_refcnt += 1; - - pthread_mutex_unlock(&CacheLock); - - return pmb; - -error: - if (pmb) { - if (pmb->pmb_pool) - pmemblk_close(pmb->pmb_pool); - pmb->pmb_pool = NULL; - pmb->pmb_filename = NULL; - free(pmb); - } - if (path) - free(path); - - pthread_mutex_unlock(&CacheLock); - return NULL; -} - -static void pmb_close(fio_pmemblk_file_t pmb, const bool keep) -{ - pthread_mutex_lock(&CacheLock); - - pmb->pmb_refcnt--; - - if (!keep && !pmb->pmb_refcnt) { - pmemblk_close(pmb->pmb_pool); - pmb->pmb_pool = NULL; - free(pmb->pmb_filename); - pmb->pmb_filename = NULL; - fio_pmemblk_cache_remove(pmb); - free(pmb); - } - - pthread_mutex_unlock(&CacheLock); -} - -static int pmb_get_flags(struct thread_data *td, uint64_t *pflags) -{ - static int thread_warned = 0; - static int odirect_warned = 0; - - uint64_t flags = 0; - - if (!td->o.use_thread) { - if (!thread_warned) { - thread_warned = 1; - log_err("pmemblk: must set thread=1 for pmemblk engine\n"); - } - return 1; - } - - if (!td->o.odirect && !odirect_warned) { - odirect_warned = 1; - log_info("pmemblk: direct == 0, but pmemblk is always direct\n"); - } - - if (td->o.allow_create) - flags |= PMB_CREATE; - - (*pflags) = flags; - return 0; -} - -static int fio_pmemblk_open_file(struct thread_data *td, struct fio_file *f) -{ - uint64_t flags = 0; - fio_pmemblk_file_t pmb; - - if (pmb_get_flags(td, &flags)) - return 1; - - pmb = pmb_open(f->file_name, flags); - if (!pmb) - return 1; - - FILE_SET_ENG_DATA(f, pmb); - return 0; -} - -static int fio_pmemblk_close_file(struct thread_data fio_unused *td, - struct fio_file *f) -{ - fio_pmemblk_file_t pmb = FILE_ENG_DATA(f); - - if (pmb) - pmb_close(pmb, false); - - FILE_SET_ENG_DATA(f, NULL); - return 0; -} - -static int fio_pmemblk_get_file_size(struct thread_data *td, struct fio_file *f) -{ - uint64_t flags = 0; - fio_pmemblk_file_t pmb = FILE_ENG_DATA(f); - - if (fio_file_size_known(f)) - return 0; - - if (!pmb) { - if (pmb_get_flags(td, &flags)) - return 1; - pmb = pmb_open(f->file_name, flags); - if (!pmb) - return 1; - } - - f->real_file_size = pmb->pmb_bsize * pmb->pmb_nblocks; - - fio_file_set_size_known(f); - - if (!FILE_ENG_DATA(f)) - pmb_close(pmb, true); - - return 0; -} - -static enum fio_q_status fio_pmemblk_queue(struct thread_data *td, - struct io_u *io_u) -{ - struct fio_file *f = io_u->file; - fio_pmemblk_file_t pmb = FILE_ENG_DATA(f); - - unsigned long long off; - unsigned long len; - void *buf; - - fio_ro_check(td, io_u); - - switch (io_u->ddir) { - case DDIR_READ: - case DDIR_WRITE: - off = io_u->offset; - len = io_u->xfer_buflen; - - io_u->error = EINVAL; - if (off % pmb->pmb_bsize) - break; - if (len % pmb->pmb_bsize) - break; - if ((off + len) / pmb->pmb_bsize > pmb->pmb_nblocks) - break; - - io_u->error = 0; - buf = io_u->xfer_buf; - off /= pmb->pmb_bsize; - len /= pmb->pmb_bsize; - while (0 < len) { - if (io_u->ddir == DDIR_READ) { - if (0 != pmemblk_read(pmb->pmb_pool, buf, off)) { - io_u->error = errno; - break; - } - } else if (0 != pmemblk_write(pmb->pmb_pool, buf, off)) { - io_u->error = errno; - break; - } - buf += pmb->pmb_bsize; - off++; - len--; - } - off *= pmb->pmb_bsize; - len *= pmb->pmb_bsize; - io_u->resid = io_u->xfer_buflen - (off - io_u->offset); - break; - case DDIR_SYNC: - case DDIR_DATASYNC: - case DDIR_SYNC_FILE_RANGE: - /* we're always sync'd */ - io_u->error = 0; - break; - default: - io_u->error = EINVAL; - break; - } - - return FIO_Q_COMPLETED; -} - -static int fio_pmemblk_unlink_file(struct thread_data *td, struct fio_file *f) -{ - char *path = NULL; - uint64_t bsize = 0; - uint64_t fsize = 0; - - /* - * we need our own unlink in case the user has specified - * the block and file sizes in the path name. we parse - * the file_name to determine the file name we actually used. - */ - - pmb_parse_path(f->file_name, &path, &bsize, &fsize); - if (!path) - return ENOENT; - - unlink(path); - free(path); - return 0; -} - -FIO_STATIC struct ioengine_ops ioengine = { - .name = "pmemblk", - .version = FIO_IOOPS_VERSION, - .queue = fio_pmemblk_queue, - .open_file = fio_pmemblk_open_file, - .close_file = fio_pmemblk_close_file, - .get_file_size = fio_pmemblk_get_file_size, - .unlink_file = fio_pmemblk_unlink_file, - .flags = FIO_SYNCIO | FIO_DISKLESSIO | FIO_NOEXTEND | FIO_NODISKUTIL, -}; - -static void fio_init fio_pmemblk_register(void) -{ - register_ioengine(&ioengine); -} - -static void fio_exit fio_pmemblk_unregister(void) -{ - unregister_ioengine(&ioengine); -} diff --git a/examples/pmemblk.fio b/examples/pmemblk.fio deleted file mode 100644 index 59bb2a8a5a..0000000000 --- a/examples/pmemblk.fio +++ /dev/null @@ -1,71 +0,0 @@ -[global] -bs=1m -ioengine=pmemblk -norandommap -time_based -runtime=30 -group_reporting -disable_lat=1 -disable_slat=1 -disable_clat=1 -clat_percentiles=0 -cpus_allowed_policy=split - -# For the pmemblk engine: -# -# IOs always complete immediately -# IOs are always direct -# Must use threads -# -iodepth=1 -direct=1 -thread -numjobs=16 -# -# Unlink can be used to remove the files when done, but if you are -# using serial runs with stonewall, and you want the files to be created -# only once and unlinked only at the very end, then put the unlink=1 -# in the last group. This is the method demonstrated here. -# -# Note that if you have a read-only group and if the files will be -# newly created, then all of the data will read back as zero and the -# read will be optimized, yielding performance that is different from -# that of reading non-zero blocks (or unoptimized zero blocks). -# -unlink=0 -# -# The pmemblk engine does IO to files in a DAX-mounted filesystem. -# The filesystem should be created on an NVDIMM (e.g /dev/pmem0) -# and then mounted with the '-o dax' option. Note that the engine -# accesses the underlying NVDIMM directly, bypassing the kernel block -# layer, so the usual filesystem/disk performance monitoring tools such -# as iostat will not provide useful data. -# -# Here we specify a test file on each of two NVDIMMs. The first -# number after the file name is the block size in bytes (4096 bytes -# in this example). The second number is the size of the file to -# create in MiB (1 GiB in this example); note that the actual usable -# space available to fio will be less than this as libpmemblk requires -# some space for metadata. -# -# Currently, the minimum block size is 512 bytes and the minimum file -# size is about 17 MiB (these are libpmemblk requirements). -# -# While both files in this example have the same block size and file -# size, this is not required. -# -filename=/pmem0/fio-test,4096,1024 -#filename=/pmem1/fio-test,4096,1024 - -[pmemblk-write] -rw=randwrite -stonewall - -[pmemblk-read] -rw=randread -stonewall -# -# We're done, so unlink the file: -# -unlink=1 - diff --git a/examples/pmemblk.png b/examples/pmemblk.png deleted file mode 100644 index 250e254b72a935806161c6a1d6506c05303507c5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 107529 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≀jLV<2LXv9y=JdFG`pSkQC^+P4%iPtW?V>Uh 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�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 - - - @@ -212,7 +209,6 @@ - From dd6500e9d62d016f60fc5c1abdcc1455154004c2 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Fri, 10 Feb 2023 13:57:54 -0500 Subject: [PATCH 0410/1097] iolog: handle trim commands when reading iologs Add code to process trim commands when we are reading an iolog. Fixes: https://github.com/axboe/fio/issues/769 Signed-off-by: Vincent Fu --- iolog.c | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/iolog.c b/iolog.c index 3b296cd7d0..ea7796320d 100644 --- a/iolog.c +++ b/iolog.c @@ -439,7 +439,7 @@ static bool read_iolog(struct thread_data *td) unsigned long long offset; unsigned int bytes; unsigned long long delay = 0; - int reads, writes, waits, fileno = 0, file_action = 0; /* stupid gcc */ + int reads, writes, trims, waits, fileno = 0, file_action = 0; /* stupid gcc */ char *rfname, *fname, *act; char *str, *p; enum fio_ddir rw; @@ -461,7 +461,7 @@ static bool read_iolog(struct thread_data *td) rfname = fname = malloc(256+16); act = malloc(256+16); - syncs = reads = writes = waits = 0; + syncs = reads = writes = trims = waits = 0; while ((p = fgets(str, 4096, td->io_log_rfile)) != NULL) { struct io_piece *ipo; int r; @@ -552,6 +552,13 @@ static bool read_iolog(struct thread_data *td) if (read_only) continue; writes++; + } else if (rw == DDIR_TRIM) { + /* + * Don't add a trim for ro mode + */ + if (read_only) + continue; + trims++; } else if (rw == DDIR_WAIT) { if (td->o.no_stall) continue; @@ -634,14 +641,16 @@ static bool read_iolog(struct thread_data *td) return true; } - if (!reads && !writes && !waits) + if (!reads && !writes && !waits && !trims) return false; - else if (reads && !writes) - td->o.td_ddir = TD_DDIR_READ; - else if (!reads && writes) - td->o.td_ddir = TD_DDIR_WRITE; - else - td->o.td_ddir = TD_DDIR_RW; + + td->o.td_ddir = 0; + if (reads) + td->o.td_ddir |= TD_DDIR_READ; + if (writes) + td->o.td_ddir |= TD_DDIR_WRITE; + if (trims) + td->o.td_ddir |= TD_DDIR_TRIM; return true; } From a25ba6c64fe1313716f5a593ae6bd67492b2314a Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Fri, 17 Feb 2023 11:14:47 -0700 Subject: [PATCH 0411/1097] Get rid of O_ATOMIC This feature never went upstream on the Linux kernel side, let's just get rid of it. The option is left for now, but we can deprecate that or even probably remove it as it will never had had any effect. Signed-off-by: Jens Axboe --- HOWTO.rst | 6 ------ backend.c | 4 ++-- engines/ime.c | 4 ---- engines/libzbc.c | 6 ------ filesetup.c | 7 ------- fio.1 | 5 ----- init.c | 6 ------ memory.c | 4 ++-- os/os-linux.h | 6 ------ os/os.h | 6 ------ 10 files changed, 4 insertions(+), 50 deletions(-) diff --git a/HOWTO.rst b/HOWTO.rst index 158c5d897e..26a3c039a5 100644 --- a/HOWTO.rst +++ b/HOWTO.rst @@ -1110,12 +1110,6 @@ I/O type OpenBSD and ZFS on Solaris don't support direct I/O. On Windows the synchronous ioengines don't support direct I/O. Default: false. -.. option:: atomic=bool - - If value is true, attempt to use atomic direct I/O. Atomic writes are - guaranteed to be stable once acknowledged by the operating system. Only - Linux supports O_ATOMIC right now. - .. option:: buffered=bool If value is true, use buffered I/O. This is the opposite of the diff --git a/backend.c b/backend.c index 0ccc7c2b72..9e981bf408 100644 --- a/backend.c +++ b/backend.c @@ -1333,7 +1333,7 @@ int init_io_u_buffers(struct thread_data *td) * overflow later. this adjustment may be too much if we get * lucky and the allocator gives us an aligned address. */ - if (td->o.odirect || td->o.mem_align || td->o.oatomic || + if (td->o.odirect || td->o.mem_align || td_ioengine_flagged(td, FIO_RAWIO)) td->orig_buffer_size += page_mask + td->o.mem_align; @@ -1352,7 +1352,7 @@ int init_io_u_buffers(struct thread_data *td) if (data_xfer && allocate_io_mem(td)) return 1; - if (td->o.odirect || td->o.mem_align || td->o.oatomic || + if (td->o.odirect || td->o.mem_align || td_ioengine_flagged(td, FIO_RAWIO)) p = PTR_ALIGN(td->orig_buffer, page_mask) + td->o.mem_align; else diff --git a/engines/ime.c b/engines/ime.c index f6690cc16c..037b8419e2 100644 --- a/engines/ime.c +++ b/engines/ime.c @@ -188,10 +188,6 @@ static int fio_ime_open_file(struct thread_data *td, struct fio_file *f) return 1; } - if (td->o.oatomic) { - td_verror(td, EINVAL, "IME does not support atomic IO"); - return 1; - } if (td->o.odirect) flags |= O_DIRECT; flags |= td->o.sync_io; diff --git a/engines/libzbc.c b/engines/libzbc.c index cb3e9ca545..1bf1e8c8d8 100644 --- a/engines/libzbc.c +++ b/engines/libzbc.c @@ -71,12 +71,6 @@ static int libzbc_open_dev(struct thread_data *td, struct fio_file *f, flags |= O_RDONLY; } - if (td->o.oatomic) { - td_verror(td, EINVAL, "libzbc does not support O_ATOMIC"); - log_err("%s: libzbc does not support O_ATOMIC\n", f->file_name); - return -EINVAL; - } - ld = calloc(1, sizeof(*ld)); if (!ld) return -ENOMEM; diff --git a/filesetup.c b/filesetup.c index cb7047c5af..1836d7e2a1 100644 --- a/filesetup.c +++ b/filesetup.c @@ -741,13 +741,6 @@ int generic_open_file(struct thread_data *td, struct fio_file *f) goto skip_flags; if (td->o.odirect) flags |= OS_O_DIRECT; - if (td->o.oatomic) { - if (!FIO_O_ATOMIC) { - td_verror(td, EINVAL, "OS does not support atomic IO"); - return 1; - } - flags |= OS_O_DIRECT | FIO_O_ATOMIC; - } flags |= td->o.sync_io; if (td->o.create_on_open && td->o.allow_create) flags |= O_CREAT; diff --git a/fio.1 b/fio.1 index 00a0935390..9ec90133ae 100644 --- a/fio.1 +++ b/fio.1 @@ -873,11 +873,6 @@ If value is true, use non-buffered I/O. This is usually O_DIRECT. Note that OpenBSD and ZFS on Solaris don't support direct I/O. On Windows the synchronous ioengines don't support direct I/O. Default: false. .TP -.BI atomic \fR=\fPbool -If value is true, attempt to use atomic direct I/O. Atomic writes are -guaranteed to be stable once acknowledged by the operating system. Only -Linux supports O_ATOMIC right now. -.TP .BI buffered \fR=\fPbool If value is true, use buffered I/O. This is the opposite of the \fBdirect\fR option. Defaults to true. diff --git a/init.c b/init.c index f6a8056a2d..78c6c80351 100644 --- a/init.c +++ b/init.c @@ -916,12 +916,6 @@ static int fixup_options(struct thread_data *td) ret |= 1; } - /* - * O_ATOMIC implies O_DIRECT - */ - if (o->oatomic) - o->odirect = 1; - /* * If randseed is set, that overrides randrepeat */ diff --git a/memory.c b/memory.c index 577d3dd5af..2fdca65768 100644 --- a/memory.c +++ b/memory.c @@ -295,7 +295,7 @@ int allocate_io_mem(struct thread_data *td) total_mem = td->orig_buffer_size; - if (td->o.odirect || td->o.mem_align || td->o.oatomic || + if (td->o.odirect || td->o.mem_align || td_ioengine_flagged(td, FIO_MEMALIGN)) { total_mem += page_mask; if (td->o.mem_align && td->o.mem_align > page_size) @@ -341,7 +341,7 @@ void free_io_mem(struct thread_data *td) unsigned int total_mem; total_mem = td->orig_buffer_size; - if (td->o.odirect || td->o.oatomic) + if (td->o.odirect) total_mem += page_mask; if (td->io_ops->iomem_alloc && !fio_option_is_set(&td->o, mem_type)) { diff --git a/os/os-linux.h b/os/os-linux.h index bbb1f27c82..7a78b42d4d 100644 --- a/os/os-linux.h +++ b/os/os-linux.h @@ -205,12 +205,6 @@ static inline unsigned long long os_phys_mem(void) #define FIO_O_NOATIME 0 #endif -#ifdef O_ATOMIC -#define OS_O_ATOMIC O_ATOMIC -#else -#define OS_O_ATOMIC 040000000 -#endif - #ifdef MADV_REMOVE #define FIO_MADV_FREE MADV_REMOVE #endif diff --git a/os/os.h b/os/os.h index c428260ca4..ebaf8af5e2 100644 --- a/os/os.h +++ b/os/os.h @@ -133,12 +133,6 @@ extern int fio_cpus_split(os_cpu_mask_t *mask, unsigned int cpu); #define OS_O_DIRECT O_DIRECT #endif -#ifdef OS_O_ATOMIC -#define FIO_O_ATOMIC OS_O_ATOMIC -#else -#define FIO_O_ATOMIC 0 -#endif - #ifndef FIO_HAVE_HUGETLB #define SHM_HUGETLB 0 #define MAP_HUGETLB 0 From 85c1cf35921635723435ca6594f2da28f0a659fd Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Fri, 17 Feb 2023 13:22:10 -0500 Subject: [PATCH 0412/1097] filesetup: don't skip flags for trim workloads Fio has not been setting O_DIRECT, O_SYNC, O_DSYNC, and O_CREAT for workloads that include trim commands. Stop doing this and actually set these flags when requested for workloads that include trim commands. Signed-off-by: Vincent Fu --- filesetup.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/filesetup.c b/filesetup.c index 1836d7e2a1..648f48c628 100644 --- a/filesetup.c +++ b/filesetup.c @@ -737,14 +737,11 @@ int generic_open_file(struct thread_data *td, struct fio_file *f) f_out = stderr; } - if (td_trim(td)) - goto skip_flags; if (td->o.odirect) flags |= OS_O_DIRECT; flags |= td->o.sync_io; if (td->o.create_on_open && td->o.allow_create) flags |= O_CREAT; -skip_flags: if (f->filetype != FIO_TYPE_FILE) flags |= FIO_O_NOATIME; From f9fc7a27cae5ea2dbb310c05f7b693c68ba15537 Mon Sep 17 00:00:00 2001 From: Ankit Kumar Date: Fri, 17 Feb 2023 12:33:22 +0530 Subject: [PATCH 0413/1097] backend: fix runtime when used with thinktime Runtime for fio jobs when used in conjuction with thinktime, thinktime_iotime and thinktime_spin were sometimes more than what is specified. Add a fix so that fio doesn't spin or sleep for any duration beyond runtime. For the first cycle fio starts by doing I/O for thinktime + thinktime_iotime, which should just be for thinktime_iotime. Add a fix for that. Signed-off-by: Ankit Kumar Link: https://lore.kernel.org/r/20230217070322.14163-2-ankit.kumar@samsung.com Signed-off-by: Jens Axboe --- backend.c | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/backend.c b/backend.c index 9e981bf408..cb1fbf4241 100644 --- a/backend.c +++ b/backend.c @@ -866,6 +866,7 @@ static void handle_thinktime(struct thread_data *td, enum fio_ddir ddir, struct timespec *time) { unsigned long long b; + unsigned long long runtime_left; uint64_t total; int left; struct timespec now; @@ -874,7 +875,7 @@ static void handle_thinktime(struct thread_data *td, enum fio_ddir ddir, if (td->o.thinktime_iotime) { fio_gettime(&now, NULL); if (utime_since(&td->last_thinktime, &now) - >= td->o.thinktime_iotime + td->o.thinktime) { + >= td->o.thinktime_iotime) { stall = true; } else if (!fio_option_is_set(&td->o, thinktime_blocks)) { /* @@ -897,11 +898,24 @@ static void handle_thinktime(struct thread_data *td, enum fio_ddir ddir, io_u_quiesce(td); + left = td->o.thinktime_spin; + if (td->o.timeout) { + runtime_left = td->o.timeout - utime_since_now(&td->epoch); + if (runtime_left < (unsigned long long)left) + left = runtime_left; + } + total = 0; - if (td->o.thinktime_spin) - total = usec_spin(td->o.thinktime_spin); + if (left) + total = usec_spin(left); left = td->o.thinktime - total; + if (td->o.timeout) { + runtime_left = td->o.timeout - utime_since_now(&td->epoch); + if (runtime_left < (unsigned long long)left) + left = runtime_left; + } + if (left) total += usec_sleep(td, left); @@ -930,8 +944,10 @@ static void handle_thinktime(struct thread_data *td, enum fio_ddir ddir, fio_gettime(time, NULL); td->last_thinktime_blocks = b; - if (td->o.thinktime_iotime) + if (td->o.thinktime_iotime) { + fio_gettime(&now, NULL); td->last_thinktime = now; + } } /* From d5a47449ce79001ba233fe6d0499627d0438cb69 Mon Sep 17 00:00:00 2001 From: Horshack Date: Sat, 18 Feb 2023 13:07:09 -0500 Subject: [PATCH 0414/1097] ioengines.c:346: td_io_queue: Assertion `res == 0' failed Assertion in ioengines.c::td_io_queue() fails for pthread_mutex_unlock() on overlap_check mutex when serialize_overlap=1, io_submit_mode=offload, and verify= are used together. backend.c::fio_io_sync() invokes td_io_queue(), which expects the caller to have ownership of the overlap_check mutex when serialize_overlap and offloading are configured, as part of the overlap-check interlock with IO_U_F_FLIGHT. The mutex is not acquired for this path because it's not an I/O requiring an overlap check. The fix is to refine the conditional that triggers td_io_queue() to release the overlap_check mutex. Rather than using broad config options, the conditional now uses a new io_u flag named IO_U_F_OVERLAP_LOCK, which is only set for the offload worker thread path that acquires the mutex. Link: https://github.com/axboe/fio/issues/1520 Signed-off-by: Adam Horshack (horshack@live.com) --- io_u.h | 1 + ioengines.c | 3 ++- rate-submit.c | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/io_u.h b/io_u.h index 206e24fee0..50f2ad5542 100644 --- a/io_u.h +++ b/io_u.h @@ -21,6 +21,7 @@ enum { IO_U_F_TRIMMED = 1 << 5, IO_U_F_BARRIER = 1 << 6, IO_U_F_VER_LIST = 1 << 7, + IO_U_F_OVERLAP_LOCK = 1 << 8, }; /* diff --git a/ioengines.c b/ioengines.c index e2316ee4e3..843dd46637 100644 --- a/ioengines.c +++ b/ioengines.c @@ -341,9 +341,10 @@ enum fio_q_status td_io_queue(struct thread_data *td, struct io_u *io_u) * started the overlap check because the IO_U_F_FLIGHT * flag is now set */ - if (td_offload_overlap(td)) { + if (io_u->flags & IO_U_F_OVERLAP_LOCK) { int res = pthread_mutex_unlock(&overlap_check); assert(res == 0); + io_u_clear(td, io_u, IO_U_F_OVERLAP_LOCK); } assert(fio_file_open(io_u->file)); diff --git a/rate-submit.c b/rate-submit.c index 3cc17eaa56..902a6c00b3 100644 --- a/rate-submit.c +++ b/rate-submit.c @@ -47,6 +47,7 @@ static void check_overlap(struct io_u *io_u) assert(res == 0); goto retry; } + io_u_set(td, io_u, IO_U_F_OVERLAP_LOCK); } static int io_workqueue_fn(struct submit_worker *sw, From 50eaefa5cdaeb345df1f9902fe87d55aa48dc206 Mon Sep 17 00:00:00 2001 From: Horshack Date: Sat, 18 Feb 2023 15:22:54 -0500 Subject: [PATCH 0415/1097] Bad header rand_seed with time_based or loops with randrepeat=0 verify Verify fails with "bad header rand_seed" when multiple iterations of do_io() execute (time_based=1 or loops>0), with verify enabled and randrepeat=0 The root cause is do_verify() resetting the verify seed back to the job-init value, which works for verification of the first iteration of do_io() but fails for subsequent iterations because the seed is left in its post-do_io() state after the first do_verify(), which means different rand values for the second iteration of do_io() yet the second iteration of do_verify() will revert back again to the job-init seed value. The fix is to revert the verify seed for randrepeat=0 back to ts state when do_io() last ran rather than to its job-init value. That will allow do_verify() to use the correct seed for each iteration while still retaining a per-iteration unique verify seed. Link: https://github.com/axboe/fio/issues/1517#issuecomment-1430282533 Signed-off-by: Adam Horshack (horshack@live.com) --- backend.c | 15 +++++---------- fio.h | 1 + 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/backend.c b/backend.c index 0ccc7c2b72..9ea688fb1f 100644 --- a/backend.c +++ b/backend.c @@ -637,15 +637,6 @@ static void do_verify(struct thread_data *td, uint64_t verify_bytes) if (td->error) return; - /* - * verify_state needs to be reset before verification - * proceeds so that expected random seeds match actual - * random seeds in headers. The main loop will reset - * all random number generators if randrepeat is set. - */ - if (!td->o.rand_repeatable) - td_fill_verify_state_seed(td); - td_set_runstate(td, TD_VERIFYING); io_u = NULL; @@ -1878,8 +1869,12 @@ static void *thread_main(void *data) if (td->o.verify_only && td_write(td)) verify_bytes = do_dry_run(td); else { + if (!td->o.rand_repeatable) + /* save verify rand state to replay hdr seeds later at verify */ + frand_copy(&td->verify_state_last_do_io, &td->verify_state); do_io(td, bytes_done); - + if (!td->o.rand_repeatable) + frand_copy(&td->verify_state, &td->verify_state_last_do_io); if (!ddir_rw_sum(bytes_done)) { fio_mark_td_terminate(td); verify_bytes = 0; diff --git a/fio.h b/fio.h index 8da776403e..09c441491b 100644 --- a/fio.h +++ b/fio.h @@ -258,6 +258,7 @@ struct thread_data { struct frand_state bsrange_state[DDIR_RWDIR_CNT]; struct frand_state verify_state; + struct frand_state verify_state_last_do_io; struct frand_state trim_state; struct frand_state delay_state; From 7a3a166c6c43e45de1c8085254fbdd011c572f05 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Mon, 20 Feb 2023 08:28:15 -0500 Subject: [PATCH 0416/1097] configure: restore dev-dax and libpmem The commit removing pmemblk inadvertently removed dev-dax and libpmem ioengines as well. Tested-by: Yi Zhang yi.zhang@redhat.com Fixes: 04c1cdc4c108c6537681ab7c50daaed6d2fb4c93 ("pmemblk: remove pmemblk engine") Fixes: https://github.com/axboe/fio/issues/1523 Signed-off-by: Vincent Fu --- configure | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/configure b/configure index 0d02bce81e..45d10a3109 100755 --- a/configure +++ b/configure @@ -2228,6 +2228,14 @@ if compile_prog "" "-lpmem2" "libpmem2"; then fi print_config "libpmem2" "$libpmem2" +# Choose libpmem-based ioengines +if test "$libpmem" = "yes" && test "$disable_pmem" = "no"; then + devdax="yes" + if test "$libpmem1_5" = "yes"; then + pmem="yes" + fi +fi + ########################################## # Report whether dev-dax engine is enabled print_config "PMDK dev-dax engine" "$devdax" From 5f2a90b84dd63deddb2e3019c769e21e216c1ee5 Mon Sep 17 00:00:00 2001 From: Alexander Patrakov Date: Wed, 22 Feb 2023 16:35:59 +0800 Subject: [PATCH 0417/1097] fix fiologparser.py to work with new logging format The logging format updates documented in 1a953d97 were never propagated to fiologparser.py, which since then has been failing with a ValueError exception. This commit explicitly limits fiologparser.py to only reading the first 2 columns in the log file, because they are the only columns used. This is similar to issue #928. Signed-off-by: Alexander Patrakov --- tools/fiologparser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/fiologparser.py b/tools/fiologparser.py index 054f1f6078..708c5d4920 100755 --- a/tools/fiologparser.py +++ b/tools/fiologparser.py @@ -166,7 +166,7 @@ def read_data(self, fn): f = open(fn, 'r') p_time = 0 for line in f: - (time, value, foo, bar) = line.rstrip('\r\n').rsplit(', ') + (time, value) = line.rstrip('\r\n').rsplit(', ')[:2] self.add_sample(p_time, int(time), int(value)) p_time = int(time) From 629eda50301dd471390c325891c1c1447282fbba Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Wed, 22 Feb 2023 13:40:30 -0800 Subject: [PATCH 0418/1097] io_u: Add a debug message in fill_io_u() A debug message is logged before each 'return io_u_eof' statement in fill_io_u() except one. Hence add a debug message in front of that one return statement. Signed-off-by: Bart Van Assche --- io_u.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/io_u.c b/io_u.c index eb617e649c..d50d846511 100644 --- a/io_u.c +++ b/io_u.c @@ -984,8 +984,10 @@ static int fill_io_u(struct thread_data *td, struct io_u *io_u) offset = io_u->offset; if (td->o.zone_mode == ZONE_MODE_ZBD) { ret = zbd_adjust_block(td, io_u); - if (ret == io_u_eof) + if (ret == io_u_eof) { + dprint(FD_IO, "zbd_adjust_block() returned io_u_eof\n"); return 1; + } } if (io_u->offset + io_u->buflen > io_u->file->real_file_size) { From 067c18eb3700373d029a9f2795d662453fc09cf4 Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Wed, 22 Feb 2023 13:54:18 -0800 Subject: [PATCH 0419/1097] zbd: Report the zone capacity The zone capacity is important information. Hence report the zone capacity if ZBD debugging is enabled. Signed-off-by: Bart Van Assche --- zbd.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zbd.c b/zbd.c index ba2c0401a9..1d96bf178a 100644 --- a/zbd.c +++ b/zbd.c @@ -807,8 +807,8 @@ static int parse_zone_info(struct thread_data *td, struct fio_file *f) goto out; } - dprint(FD_ZBD, "Device %s has %d zones of size %"PRIu64" KB\n", - f->file_name, nr_zones, zone_size / 1024); + dprint(FD_ZBD, "Device %s has %d zones of size %"PRIu64" KB and capacity %"PRIu64" KB\n", + f->file_name, nr_zones, zone_size / 1024, zones[0].capacity / 1024); zbd_info = scalloc(1, sizeof(*zbd_info) + (nr_zones + 1) * sizeof(zbd_info->zone_info[0])); From adfa7b7cc6db1ce9ccc7c8a9df9a6d719b8da280 Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Fri, 13 Jan 2023 14:36:26 -0800 Subject: [PATCH 0420/1097] zbd: Make an error message more detailed If zone data is invalid, report in detail why it is invalid. Signed-off-by: Bart Van Assche --- zbd.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/zbd.c b/zbd.c index 1d96bf178a..d6f8f800e3 100644 --- a/zbd.c +++ b/zbd.c @@ -848,8 +848,9 @@ static int parse_zone_info(struct thread_data *td, struct fio_file *f) p->cond = z->cond; if (j > 0 && p->start != p[-1].start + zone_size) { - log_info("%s: invalid zone data\n", - f->file_name); + log_info("%s: invalid zone data [%d:%d]: %"PRIu64" + %"PRIu64" != %"PRIu64"\n", + f->file_name, j, i, + p[-1].start, zone_size, p->start); ret = -EINVAL; goto out; } From ad21b61d27fd94b4c94a5e26842106d76ea54ba0 Mon Sep 17 00:00:00 2001 From: Horshack Date: Sun, 26 Feb 2023 10:12:05 -0500 Subject: [PATCH 0421/1097] Fix "verify bad_hdr rand_seed" for requeued I/Os On configurations that can cause I/Os to be internally requeued from FIO_Q_BUSY such as '--iodepth_batch_complete_max', and the workload has verify enabled, the subsequent verification of the data fails with a bad verify rand_seed because the pattern for the I/O is generated twice for the same I/O, causing the seed to become out of sync when the verify is later performed. The seed is generate twice because do_io() handles the I/O twice, first when it originates the I/O and again when it later gets the same I/O back from get_io_u() after it's is pulled from the requeue list, which is where the first submission landed due to the workload reaching '--iodepth_batch_complete_max'. The fix is for do_io() to track when it has generated the verify pattern for an I/O via a new io_u flag 'IO_U_F_PATTERN_DONE', avoiding a second call to populate_verify_io_u() when that flag is detected. Link: https://github.com/axboe/fio/issues/1526 Signed-off-by: Adam Horshack (horshack@live.com) --- backend.c | 7 +++++-- io_u.c | 2 +- io_u.h | 1 + 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/backend.c b/backend.c index f494c83155..975ef48938 100644 --- a/backend.c +++ b/backend.c @@ -1040,8 +1040,11 @@ static void do_io(struct thread_data *td, uint64_t *bytes_done) } if (io_u->ddir == DDIR_WRITE && td->flags & TD_F_DO_VERIFY) { - io_u->numberio = td->io_issues[io_u->ddir]; - populate_verify_io_u(td, io_u); + if (!(io_u->flags & IO_U_F_PATTERN_DONE)) { + io_u_set(td, io_u, IO_U_F_PATTERN_DONE); + io_u->numberio = td->io_issues[io_u->ddir]; + populate_verify_io_u(td, io_u); + } } ddir = io_u->ddir; diff --git a/io_u.c b/io_u.c index eb617e649c..0b2754f1e9 100644 --- a/io_u.c +++ b/io_u.c @@ -2004,7 +2004,7 @@ static void io_completed(struct thread_data *td, struct io_u **io_u_ptr, dprint_io_u(io_u, "complete"); assert(io_u->flags & IO_U_F_FLIGHT); - io_u_clear(td, io_u, IO_U_F_FLIGHT | IO_U_F_BUSY_OK); + io_u_clear(td, io_u, IO_U_F_FLIGHT | IO_U_F_BUSY_OK | IO_U_F_PATTERN_DONE); /* * Mark IO ok to verify diff --git a/io_u.h b/io_u.h index 206e24fee0..9b5d697d94 100644 --- a/io_u.h +++ b/io_u.h @@ -21,6 +21,7 @@ enum { IO_U_F_TRIMMED = 1 << 5, IO_U_F_BARRIER = 1 << 6, IO_U_F_VER_LIST = 1 << 7, + IO_U_F_PATTERN_DONE = 1 << 8, }; /* From 169b7ce1101f551c1829fbc2d0e654087a09413d Mon Sep 17 00:00:00 2001 From: Cuelive <93713079+Cuelive@users.noreply.github.com> Date: Tue, 28 Feb 2023 10:03:21 +0800 Subject: [PATCH 0422/1097] blktrace: fix compilation error on the uos system When compiling on uos, it fails with an undefined reference to 'major'. Fix this by including the correct header for it. liuyafei --- blktrace.c | 1 + 1 file changed, 1 insertion(+) diff --git a/blktrace.c b/blktrace.c index d5c8aee70b..ef9ce6bffd 100644 --- a/blktrace.c +++ b/blktrace.c @@ -5,6 +5,7 @@ #include #include #include +#include #include "flist.h" #include "fio.h" From a7e8aae0220458e3d3bfa12c04835a63bbf152e2 Mon Sep 17 00:00:00 2001 From: Keith Busch Date: Mon, 27 Feb 2023 07:51:34 -0800 Subject: [PATCH 0423/1097] fio: add fdp support for io_uring_cmd nvme engine Add support for NVMe TP4146 Flexible Data Placemen, allowing placement identifiers in write commands. The user can enabled this with the new "fdp=1" parameter for fio's io_uring_cmd ioengine. By default, the fio jobs will cycle through all the namespace's available placement identifiers for write commands. The user can limit which placement identifiers can be used with additional parameter, "fdp_pli=", which can be used to separate write intensive jobs from less intensive ones. Setting up your namespace for FDP is outside the scope of 'fio', so this assumes the namespace is already properly configured for the mode. Link: https://lore.kernel.org/fio/CAKi7+wfX-eaUD5pky5cJ824uCzsQ4sPYMZdp3AuCUZOA1TQrYw@mail.gmail.com/T/#m056018eb07229bed00d4e589f9760b2a2aa009fc Based-on-a-patch-by: Ankit Kumar Signed-off-by: Keith Busch Reviewed-by: Damien Le Moal [Vincent: fold in sfree fix from Ankit] Signed-off-by: Vincent Fu --- HOWTO.rst | 12 ++++ Makefile | 2 +- cconv.c | 10 ++++ engines/io_uring.c | 24 ++++++++ engines/nvme.c | 40 ++++++++++++- engines/nvme.h | 18 ++++++ examples/uring-cmd-fdp.fio | 37 ++++++++++++ fdp.c | 119 +++++++++++++++++++++++++++++++++++++ fdp.h | 16 +++++ file.h | 3 + filesetup.c | 9 +++ fio.1 | 9 +++ io_u.c | 3 + io_u.h | 3 + ioengines.h | 5 +- options.c | 49 +++++++++++++++ server.h | 2 +- thread_options.h | 9 +++ 18 files changed, 366 insertions(+), 4 deletions(-) create mode 100644 examples/uring-cmd-fdp.fio create mode 100644 fdp.c create mode 100644 fdp.h diff --git a/HOWTO.rst b/HOWTO.rst index 7a0535af76..4ac9ffe2a6 100644 --- a/HOWTO.rst +++ b/HOWTO.rst @@ -2423,6 +2423,18 @@ with the caveat that when used on the command line, they must come after the For direct I/O, requests will only succeed if cache invalidation isn't required, file blocks are fully allocated and the disk request could be issued immediately. +.. option:: fdp=bool : [io_uring_cmd] + + Enable Flexible Data Placement mode for write commands. + +.. option:: fdp_pli=str : [io_uring_cmd] + + Select which Placement ID Index/Indicies this job is allowed to use for + writes. By default, the job will cycle through all available Placement + IDs, so use this to isolate these identifiers to specific jobs. If you + want fio to use placement identifier only at indices 0, 2 and 5 specify + ``fdp_pli=0,2,5``. + .. option:: cpuload=int : [cpuio] Attempt to use the specified percentage of CPU cycles. This is a mandatory diff --git a/Makefile b/Makefile index e4cde4bac8..6d7fd4e2bb 100644 --- a/Makefile +++ b/Makefile @@ -62,7 +62,7 @@ SOURCE := $(sort $(patsubst $(SRCDIR)/%,%,$(wildcard $(SRCDIR)/crc/*.c)) \ gettime-thread.c helpers.c json.c idletime.c td_error.c \ profiles/tiobench.c profiles/act.c io_u_queue.c filelock.c \ workqueue.c rate-submit.c optgroup.c helper_thread.c \ - steadystate.c zone-dist.c zbd.c dedupe.c + steadystate.c zone-dist.c zbd.c dedupe.c fdp.c ifdef CONFIG_LIBHDFS HDFSFLAGS= -I $(JAVA_HOME)/include -I $(JAVA_HOME)/include/linux -I $(FIO_LIBHDFS_INCLUDE) diff --git a/cconv.c b/cconv.c index d755844f51..05ac75e329 100644 --- a/cconv.c +++ b/cconv.c @@ -349,6 +349,11 @@ int convert_thread_options_to_cpu(struct thread_options *o, for (i = 0; i < FIO_IO_U_LIST_MAX_LEN; i++) o->merge_blktrace_iters[i].u.f = fio_uint64_to_double(le64_to_cpu(top->merge_blktrace_iters[i].u.i)); + + o->fdp = le32_to_cpu(top->fdp); + o->fdp_nrpli = le32_to_cpu(top->fdp_nrpli); + for (i = 0; i < o->fdp_nrpli; i++) + o->fdp_plis[i] = le32_to_cpu(top->fdp_plis[i]); #if 0 uint8_t cpumask[FIO_TOP_STR_MAX]; uint8_t verify_cpumask[FIO_TOP_STR_MAX]; @@ -638,6 +643,11 @@ void convert_thread_options_to_net(struct thread_options_pack *top, for (i = 0; i < FIO_IO_U_LIST_MAX_LEN; i++) top->merge_blktrace_iters[i].u.i = __cpu_to_le64(fio_double_to_uint64(o->merge_blktrace_iters[i].u.f)); + + top->fdp = cpu_to_le32(o->fdp); + top->fdp_nrpli = cpu_to_le32(o->fdp_nrpli); + for (i = 0; i < o->fdp_nrpli; i++) + top->fdp_plis[i] = cpu_to_le32(o->fdp_plis[i]); #if 0 uint8_t cpumask[FIO_TOP_STR_MAX]; uint8_t verify_cpumask[FIO_TOP_STR_MAX]; diff --git a/engines/io_uring.c b/engines/io_uring.c index a9abd11dfc..5393758aa6 100644 --- a/engines/io_uring.c +++ b/engines/io_uring.c @@ -1262,6 +1262,29 @@ static int fio_ioring_cmd_get_max_open_zones(struct thread_data *td, return fio_nvme_get_max_open_zones(td, f, max_open_zones); } +static int fio_ioring_cmd_fetch_ruhs(struct thread_data *td, struct fio_file *f, + struct fio_ruhs_info *fruhs_info) +{ + struct nvme_fdp_ruh_status *ruhs; + int bytes, ret, i; + + bytes = sizeof(*ruhs) + 128 * sizeof(struct nvme_fdp_ruh_status_desc); + ruhs = scalloc(1, bytes); + if (!ruhs) + return -ENOMEM; + + ret = fio_nvme_iomgmt_ruhs(td, f, ruhs, bytes); + if (ret) + goto free; + + fruhs_info->nr_ruhs = le16_to_cpu(ruhs->nruhsd); + for (i = 0; i < fruhs_info->nr_ruhs; i++) + fruhs_info->plis[i] = le16_to_cpu(ruhs->ruhss[i].pid); +free: + sfree(ruhs); + return ret; +} + static struct ioengine_ops ioengine_uring = { .name = "io_uring", .version = FIO_IOOPS_VERSION, @@ -1307,6 +1330,7 @@ static struct ioengine_ops ioengine_uring_cmd = { .get_max_open_zones = fio_ioring_cmd_get_max_open_zones, .options = options, .option_struct_size = sizeof(struct ioring_options), + .fdp_fetch_ruhs = fio_ioring_cmd_fetch_ruhs, }; static void fio_init fio_ioring_register(void) diff --git a/engines/nvme.c b/engines/nvme.c index 9ffc5303d2..da18eba987 100644 --- a/engines/nvme.c +++ b/engines/nvme.c @@ -28,7 +28,8 @@ int fio_nvme_uring_cmd_prep(struct nvme_uring_cmd *cmd, struct io_u *io_u, cmd->cdw10 = slba & 0xffffffff; cmd->cdw11 = slba >> 32; /* cdw12 represent number of lba's for read/write */ - cmd->cdw12 = nlb; + cmd->cdw12 = nlb | (io_u->dtype << 20); + cmd->cdw13 = io_u->dspec << 16; if (iov) { iov->iov_base = io_u->xfer_buf; iov->iov_len = io_u->xfer_buflen; @@ -345,3 +346,40 @@ int fio_nvme_get_max_open_zones(struct thread_data *td, struct fio_file *f, close(fd); return ret; } + +static inline int nvme_fdp_reclaim_unit_handle_status(int fd, __u32 nsid, + __u32 data_len, void *data) +{ + struct nvme_passthru_cmd cmd = { + .opcode = nvme_cmd_io_mgmt_recv, + .nsid = nsid, + .addr = (__u64)(uintptr_t)data, + .data_len = data_len, + .cdw10 = 1, + .cdw11 = (data_len >> 2) - 1, + }; + + return ioctl(fd, NVME_IOCTL_IO_CMD, &cmd); +} + +int fio_nvme_iomgmt_ruhs(struct thread_data *td, struct fio_file *f, + struct nvme_fdp_ruh_status *ruhs, __u32 bytes) +{ + struct nvme_data *data = FILE_ENG_DATA(f); + int fd, ret; + + fd = open(f->file_name, O_RDONLY | O_LARGEFILE); + if (fd < 0) + return -errno; + + ret = nvme_fdp_reclaim_unit_handle_status(fd, data->nsid, bytes, ruhs); + if (ret) { + log_err("%s: nvme_fdp_reclaim_unit_handle_status failed, err=%d\n", + f->file_name, ret); + errno = ENOTSUP; + } else + errno = 0; + + close(fd); + return -errno; +} diff --git a/engines/nvme.h b/engines/nvme.h index 70a89b7406..1c0e526b95 100644 --- a/engines/nvme.h +++ b/engines/nvme.h @@ -67,6 +67,7 @@ enum nvme_admin_opcode { enum nvme_io_opcode { nvme_cmd_write = 0x01, nvme_cmd_read = 0x02, + nvme_cmd_io_mgmt_recv = 0x12, nvme_zns_cmd_mgmt_send = 0x79, nvme_zns_cmd_mgmt_recv = 0x7a, }; @@ -192,6 +193,23 @@ struct nvme_zone_report { struct nvme_zns_desc entries[]; }; +struct nvme_fdp_ruh_status_desc { + __u16 pid; + __u16 ruhid; + __u32 earutr; + __u64 ruamw; + __u8 rsvd16[16]; +}; + +struct nvme_fdp_ruh_status { + __u8 rsvd0[14]; + __le16 nruhsd; + struct nvme_fdp_ruh_status_desc ruhss[]; +}; + +int fio_nvme_iomgmt_ruhs(struct thread_data *td, struct fio_file *f, + struct nvme_fdp_ruh_status *ruhs, __u32 bytes); + int fio_nvme_get_info(struct fio_file *f, __u32 *nsid, __u32 *lba_sz, __u64 *nlba); diff --git a/examples/uring-cmd-fdp.fio b/examples/uring-cmd-fdp.fio new file mode 100644 index 0000000000..55d741d3f5 --- /dev/null +++ b/examples/uring-cmd-fdp.fio @@ -0,0 +1,37 @@ +# io_uring_cmd I/O engine for nvme-ns generic character device with FDP enabled +# This assumes the namespace is already configured with FDP support and has at +# least 8 available reclaim units. +# +# Each job targets different ranges of LBAs with different placement +# identifiers, and has different write intensity. + +[global] +filename=/dev/ng0n1 +ioengine=io_uring_cmd +cmd_type=nvme +iodepth=32 +bs=4K +fdp=1 +time_based=1 +runtime=1000 + +[write-heavy] +rw=randrw +rwmixwrite=90 +fdp_pli=0,1,2,3 +offset=0% +size=30% + +[write-mid] +rw=randrw +rwmixwrite=30 +fdp_pli=4,5 +offset=30% +size=30% + +[write-light] +rw=randrw +rwmixwrite=10 +fdp_pli=6 +offset=60% +size=30% diff --git a/fdp.c b/fdp.c new file mode 100644 index 0000000000..0f1aae5e4b --- /dev/null +++ b/fdp.c @@ -0,0 +1,119 @@ +/* + * Note: This is similar to a very basic setup + * of ZBD devices + * + * Specify fdp=1 (With char devices /dev/ng0n1) + */ + +#include +#include +#include +#include +#include "file.h" +#include "fio.h" + +#include "pshared.h" +#include "fdp.h" + +static int fdp_ruh_info(struct thread_data *td, struct fio_file *f, + struct fio_ruhs_info *ruhs) +{ + int ret = -EINVAL; + + if (td->io_ops && td->io_ops->fdp_fetch_ruhs) { + ret = td->io_ops->fdp_fetch_ruhs(td, f, ruhs); + if (ret < 0) { + td_verror(td, errno, "fdp fetch ruhs failed"); + log_err("%s: fdp fetch ruhs failed (%d)\n", + f->file_name, errno); + } + } else + log_err("%s: engine (%s) lacks fetch ruhs\n", + f->file_name, td->io_ops->name); + + return ret; +} + +static int init_ruh_info(struct thread_data *td, struct fio_file *f) +{ + struct fio_ruhs_info *ruhs, *tmp; + int i, ret; + + ruhs = scalloc(1, sizeof(*ruhs) + 128 * sizeof(*ruhs->plis)); + if (!ruhs) + return -ENOMEM; + + ret = fdp_ruh_info(td, f, ruhs); + if (ret) { + log_info("fio: ruh info failed for %s (%d)\n", + f->file_name, -ret); + goto out; + } + + if (ruhs->nr_ruhs > 128) + ruhs->nr_ruhs = 128; + + if (td->o.fdp_nrpli == 0) { + f->ruhs_info = ruhs; + return 0; + } + + for (i = 0; i < td->o.fdp_nrpli; i++) { + if (td->o.fdp_plis[i] > ruhs->nr_ruhs) { + ret = -EINVAL; + goto out; + } + } + + tmp = scalloc(1, sizeof(*tmp) + ruhs->nr_ruhs * sizeof(*tmp->plis)); + if (!tmp) { + ret = -ENOMEM; + goto out; + } + + tmp->nr_ruhs = td->o.fdp_nrpli; + for (i = 0; i < td->o.fdp_nrpli; i++) + tmp->plis[i] = ruhs->plis[td->o.fdp_plis[i]]; + f->ruhs_info = tmp; +out: + sfree(ruhs); + return ret; +} + +int fdp_init(struct thread_data *td) +{ + struct fio_file *f; + int i, ret = 0; + + for_each_file(td, f, i) { + ret = init_ruh_info(td, f); + if (ret) + break; + } + return ret; +} + +void fdp_free_ruhs_info(struct fio_file *f) +{ + if (!f->ruhs_info) + return; + sfree(f->ruhs_info); + f->ruhs_info = NULL; +} + +void fdp_fill_dspec_data(struct thread_data *td, struct io_u *io_u) +{ + struct fio_file *f = io_u->file; + struct fio_ruhs_info *ruhs = f->ruhs_info; + int dspec; + + if (!ruhs || io_u->ddir != DDIR_WRITE) { + io_u->dtype = 0; + io_u->dspec = 0; + return; + } + + dspec = ruhs->plis[ruhs->pli_loc++ % ruhs->nr_ruhs]; + io_u->dtype = 2; + io_u->dspec = dspec; +} diff --git a/fdp.h b/fdp.h new file mode 100644 index 0000000000..81691f62ca --- /dev/null +++ b/fdp.h @@ -0,0 +1,16 @@ +#ifndef FIO_FDP_H +#define FIO_FDP_H + +#include "io_u.h" + +struct fio_ruhs_info { + uint32_t nr_ruhs; + uint32_t pli_loc; + uint16_t plis[]; +}; + +int fdp_init(struct thread_data *td); +void fdp_free_ruhs_info(struct fio_file *f); +void fdp_fill_dspec_data(struct thread_data *td, struct io_u *io_u); + +#endif /* FIO_FDP_H */ diff --git a/file.h b/file.h index da1b894706..deb36e0291 100644 --- a/file.h +++ b/file.h @@ -12,6 +12,7 @@ /* Forward declarations */ struct zoned_block_device_info; +struct fdp_ruh_info; /* * The type of object we are working on @@ -101,6 +102,8 @@ struct fio_file { uint64_t file_offset; uint64_t io_size; + struct fio_ruhs_info *ruhs_info; + /* * Zoned block device information. See also zonemode=zbd. */ diff --git a/filesetup.c b/filesetup.c index 648f48c628..8e5059412e 100644 --- a/filesetup.c +++ b/filesetup.c @@ -1407,6 +1407,12 @@ int setup_files(struct thread_data *td) td_restore_runstate(td, old_state); + if (td->o.fdp) { + err = fdp_init(td); + if (err) + goto err_out; + } + return 0; err_offset: @@ -1584,6 +1590,8 @@ void fio_file_free(struct fio_file *f) { if (fio_file_axmap(f)) axmap_free(f->io_axmap); + if (f->ruhs_info) + sfree(f->ruhs_info); if (!fio_file_smalloc(f)) { free(f->file_name); free(f); @@ -1617,6 +1625,7 @@ void close_and_free_files(struct thread_data *td) } zbd_close_file(f); + fdp_free_ruhs_info(f); fio_file_free(f); } diff --git a/fio.1 b/fio.1 index e94fad0aba..159d6d9150 100644 --- a/fio.1 +++ b/fio.1 @@ -2184,6 +2184,15 @@ cached data. Currently the RWF_NOWAIT flag does not supported for cached write. For direct I/O, requests will only succeed if cache invalidation isn't required, file blocks are fully allocated and the disk request could be issued immediately. .TP +.BI (io_uring_cmd)fdp \fR=\fPbool +Enable Flexible Data Placement mode for write commands. +.TP +.BI (io_uring_cmd)fdp_pli \fR=\fPstr +Select which Placement ID Index/Indicies this job is allowed to use for writes. +By default, the job will cycle through all available Placement IDs, so use this +to isolate these identifiers to specific jobs. If you want fio to use placement +identifier only at indices 0, 2 and 5 specify, you would set `fdp_pli=0,2,5`. +.TP .BI (cpuio)cpuload \fR=\fPint Attempt to use the specified percentage of CPU cycles. This is a mandatory option when using cpuio I/O engine. diff --git a/io_u.c b/io_u.c index 23c4ae2d96..ca7ee68fde 100644 --- a/io_u.c +++ b/io_u.c @@ -990,6 +990,9 @@ static int fill_io_u(struct thread_data *td, struct io_u *io_u) } } + if (td->o.fdp) + fdp_fill_dspec_data(td, io_u); + if (io_u->offset + io_u->buflen > io_u->file->real_file_size) { dprint(FD_IO, "io_u %p, off=0x%llx + len=0x%llx exceeds file size=0x%llx\n", io_u, diff --git a/io_u.h b/io_u.h index ef6d0065c9..6b4c3014ba 100644 --- a/io_u.h +++ b/io_u.h @@ -119,6 +119,9 @@ struct io_u { */ int (*end_io)(struct thread_data *, struct io_u **); + uint32_t dtype; + uint32_t dspec; + union { #ifdef CONFIG_LIBAIO struct iocb iocb; diff --git a/ioengines.h b/ioengines.h index ea79918019..9484265e6f 100644 --- a/ioengines.h +++ b/ioengines.h @@ -7,8 +7,9 @@ #include "flist.h" #include "io_u.h" #include "zbd_types.h" +#include "fdp.h" -#define FIO_IOOPS_VERSION 31 +#define FIO_IOOPS_VERSION 32 #ifndef CONFIG_DYNAMIC_ENGINES #define FIO_STATIC static @@ -63,6 +64,8 @@ struct ioengine_ops { unsigned int *); int (*finish_zone)(struct thread_data *, struct fio_file *, uint64_t, uint64_t); + int (*fdp_fetch_ruhs)(struct thread_data *, struct fio_file *, + struct fio_ruhs_info *); int option_struct_size; struct fio_option *options; }; diff --git a/options.c b/options.c index 536ba91c34..91049af547 100644 --- a/options.c +++ b/options.c @@ -251,6 +251,34 @@ int str_split_parse(struct thread_data *td, char *str, return ret; } +static int fio_fdp_cmp(const void *p1, const void *p2) +{ + const uint16_t *t1 = p1; + const uint16_t *t2 = p2; + + return *t1 - *t2; +} + +static int str_fdp_pli_cb(void *data, const char *input) +{ + struct thread_data *td = cb_data_to_td(data); + char *str, *p, *v; + int i = 0; + + p = str = strdup(input); + strip_blank_front(&str); + strip_blank_end(str); + + while ((v = strsep(&str, ",")) != NULL && i < FIO_MAX_PLIS) + td->o.fdp_plis[i++] = strtoll(v, NULL, 0); + free(p); + + qsort(td->o.fdp_plis, i, sizeof(*td->o.fdp_plis), fio_fdp_cmp); + td->o.fdp_nrpli = i; + + return 0; +} + static int str_bssplit_cb(void *data, const char *input) { struct thread_data *td = cb_data_to_td(data); @@ -3643,6 +3671,27 @@ struct fio_option fio_options[FIO_MAX_OPTS] = { .category = FIO_OPT_C_IO, .group = FIO_OPT_G_ZONE, }, + { + .name = "fdp", + .lname = "Flexible data placement", + .type = FIO_OPT_BOOL, + .off1 = offsetof(struct thread_options, fdp), + .help = "Use Data placement directive (FDP)", + .def = "0", + .category = FIO_OPT_C_IO, + .group = FIO_OPT_G_INVALID, + }, + { + .name = "fdp_pli", + .lname = "FDP Placement ID indicies", + .type = FIO_OPT_STR, + .cb = str_fdp_pli_cb, + .off1 = offsetof(struct thread_options, fdp_plis), + .help = "Sets which placement ids to use (defaults to all)", + .hide = 1, + .category = FIO_OPT_C_IO, + .group = FIO_OPT_G_INVALID, + }, { .name = "lockmem", .lname = "Lock memory", diff --git a/server.h b/server.h index 2813302028..898a893dec 100644 --- a/server.h +++ b/server.h @@ -51,7 +51,7 @@ struct fio_net_cmd_reply { }; enum { - FIO_SERVER_VER = 98, + FIO_SERVER_VER = 99, FIO_SERVER_MAX_FRAGMENT_PDU = 1024, FIO_SERVER_MAX_CMD_MB = 2048, diff --git a/thread_options.h b/thread_options.h index 74e7ea4586..2520357cb0 100644 --- a/thread_options.h +++ b/thread_options.h @@ -386,6 +386,11 @@ struct thread_options { fio_fp64_t zrt; fio_fp64_t zrf; +#define FIO_MAX_PLIS 16 + unsigned int fdp; + unsigned int fdp_plis[FIO_MAX_PLIS]; + unsigned int fdp_nrpli; + unsigned int log_entries; unsigned int log_prio; }; @@ -698,6 +703,10 @@ struct thread_options_pack { uint32_t log_entries; uint32_t log_prio; + uint32_t fdp; + uint32_t fdp_plis[FIO_MAX_PLIS]; + uint32_t fdp_nrpli; + /* * verify_pattern followed by buffer_pattern from the unpacked struct */ From 5a85d6d89ee4e8bcdc1243a5e32d87cf713ebce2 Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Tue, 28 Feb 2023 08:28:51 -0700 Subject: [PATCH 0424/1097] fdp: cleanup init I don't believe we can have a NULL ->io_ops here, but let's just add an error check and make the static checkers happy as they don't like the non-NULL check and then a later deref in the other branch. Add missing braces while at it. Signed-off-by: Jens Axboe --- fdp.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/fdp.c b/fdp.c index 0f1aae5e4b..c50af1e241 100644 --- a/fdp.c +++ b/fdp.c @@ -20,16 +20,22 @@ static int fdp_ruh_info(struct thread_data *td, struct fio_file *f, { int ret = -EINVAL; - if (td->io_ops && td->io_ops->fdp_fetch_ruhs) { + if (!td->io_ops) { + log_err("fio: no ops set in fdp init?!\n"); + return ret; + } + + if (td->io_ops->fdp_fetch_ruhs) { ret = td->io_ops->fdp_fetch_ruhs(td, f, ruhs); if (ret < 0) { td_verror(td, errno, "fdp fetch ruhs failed"); log_err("%s: fdp fetch ruhs failed (%d)\n", f->file_name, errno); } - } else + } else { log_err("%s: engine (%s) lacks fetch ruhs\n", f->file_name, td->io_ops->name); + } return ret; } From 14520f23661673fcd0fc4b4c27b3bd7b5b9fe524 Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Tue, 28 Feb 2023 08:54:36 -0700 Subject: [PATCH 0425/1097] Revert "ioengines.c:346: td_io_queue: Assertion `res == 0' failed" This reverts commit d5a47449ce79001ba233fe6d0499627d0438cb69. The change to rate-submit.c is clearly bogus, as it's referencing 'td' outside of the actual 'td' loop. It's not valid at that point. Signed-off-by: Jens Axboe --- io_u.h | 1 - ioengines.c | 3 +-- rate-submit.c | 1 - 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/io_u.h b/io_u.h index 6b4c3014ba..55b4d08312 100644 --- a/io_u.h +++ b/io_u.h @@ -22,7 +22,6 @@ enum { IO_U_F_BARRIER = 1 << 6, IO_U_F_VER_LIST = 1 << 7, IO_U_F_PATTERN_DONE = 1 << 8, - IO_U_F_OVERLAP_LOCK = 1 << 9, }; /* diff --git a/ioengines.c b/ioengines.c index 843dd46637..e2316ee4e3 100644 --- a/ioengines.c +++ b/ioengines.c @@ -341,10 +341,9 @@ enum fio_q_status td_io_queue(struct thread_data *td, struct io_u *io_u) * started the overlap check because the IO_U_F_FLIGHT * flag is now set */ - if (io_u->flags & IO_U_F_OVERLAP_LOCK) { + if (td_offload_overlap(td)) { int res = pthread_mutex_unlock(&overlap_check); assert(res == 0); - io_u_clear(td, io_u, IO_U_F_OVERLAP_LOCK); } assert(fio_file_open(io_u->file)); diff --git a/rate-submit.c b/rate-submit.c index 902a6c00b3..3cc17eaa56 100644 --- a/rate-submit.c +++ b/rate-submit.c @@ -47,7 +47,6 @@ static void check_overlap(struct io_u *io_u) assert(res == 0); goto retry; } - io_u_set(td, io_u, IO_U_F_OVERLAP_LOCK); } static int io_workqueue_fn(struct submit_worker *sw, From c7927863619e86e9872b9b5d31eab4a0a23786dd Mon Sep 17 00:00:00 2001 From: Horshack Date: Fri, 10 Feb 2023 14:00:11 -0500 Subject: [PATCH 0426/1097] Clarify documentation for runtime parameter I realize this is highly subjective but I think the description of the runtime parameter could be made a bit more precise. I misinterpreted its meaning after reading the doc and only learned of my mistake by trial and error using fio. Either I'm just slow or the description could use just a little more precision :) Signed-off-by: Adam Horshack (horshack@live.com) --- HOWTO.rst | 10 ++++++---- fio.1 | 10 ++++++---- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/HOWTO.rst b/HOWTO.rst index 17caaf5d38..0ccf03e267 100644 --- a/HOWTO.rst +++ b/HOWTO.rst @@ -686,10 +686,12 @@ Time related parameters .. option:: runtime=time - Tell fio to terminate processing after the specified period of time. It - can be quite hard to determine for how long a specified job will run, so - this parameter is handy to cap the total runtime to a given time. When - the unit is omitted, the value is interpreted in seconds. + Limit runtime. The test will run until it completes the configured I/O + workload or until it has run for this specified amount of time, whichever + occurs first. It can be quite hard to determine for how long a specified + job will run, so this parameter is handy to cap the total runtime to a + given time. When the unit is omitted, the value is interpreted in + seconds. .. option:: time_based diff --git a/fio.1 b/fio.1 index 527b3d4605..614ad9024a 100644 --- a/fio.1 +++ b/fio.1 @@ -471,10 +471,12 @@ See \fB\-\-max\-jobs\fR. Default: 1. .SS "Time related parameters" .TP .BI runtime \fR=\fPtime -Tell fio to terminate processing after the specified period of time. It -can be quite hard to determine for how long a specified job will run, so -this parameter is handy to cap the total runtime to a given time. When -the unit is omitted, the value is interpreted in seconds. +Limit runtime. The test will run until it completes the configured I/O +workload or until it has run for this specified amount of time, whichever +occurs first. It can be quite hard to determine for how long a specified +job will run, so this parameter is handy to cap the total runtime to a +given time. When the unit is omitted, the value is interpreted in +seconds. .TP .BI time_based If set, fio will run for the duration of the \fBruntime\fR specified From 7d352b12ce77d8c0f6401a007761ac50b09ca61f Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Tue, 28 Feb 2023 13:01:32 -0500 Subject: [PATCH 0427/1097] fdp: change the order of includes to fix Windows build error On Windows fio.h includes some definitions needed by file.h. fio.h actually includes file.h already but we can retain the file.h include in fdp.c since it refers to some declarations that were added there. Signed-off-by: Vincent Fu --- fdp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fdp.c b/fdp.c index c50af1e241..84e04fce61 100644 --- a/fdp.c +++ b/fdp.c @@ -9,8 +9,8 @@ #include #include #include -#include "file.h" #include "fio.h" +#include "file.h" #include "pshared.h" #include "fdp.h" From 5a37211238f995657c50e5d0ea6e5e22ff3ca69e Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Tue, 28 Feb 2023 13:58:58 -0500 Subject: [PATCH 0428/1097] examples: add fiograph diagram for uring-cmd-fdp.fio Signed-off-by: Vincent Fu --- examples/uring-cmd-fdp.png | Bin 0 -> 50265 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 examples/uring-cmd-fdp.png diff --git a/examples/uring-cmd-fdp.png b/examples/uring-cmd-fdp.png new file mode 100644 index 0000000000000000000000000000000000000000..251f4fe34a215f3b87c3847caf62be826d68a452 GIT binary patch literal 50265 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 z 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 literal 0 HcmV?d00001 From 77a2306b43642fa6d776c53b74117592c468ea90 Mon Sep 17 00:00:00 2001 From: Horshack Date: Fri, 3 Mar 2023 08:14:53 -0500 Subject: [PATCH 0429/1097] Fix --bandwidth-log segmentation fault when numjobs even multiple of 8 Segmentation fault occurs when aggregate bandwidth logging is enabled (--bandwidth-log) and numjobs is an even multiple of 8. Fault occurs because logic is using the terminating value of struct thread_data *td from the most recent for_each_td(). This bug was caught by the refactoring of for_each_td(). Link: https://github.com/axboe/fio/issues/1534 Signed-off-by: Adam Horshack (horshack@live.com) --- eta.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/eta.c b/eta.c index 6017ca3102..f315e80654 100644 --- a/eta.c +++ b/eta.c @@ -383,6 +383,7 @@ bool calc_thread_status(struct jobs_eta *je, int force) { struct thread_data *td; int i, unified_rw_rep; + bool any_td_in_ramp; uint64_t rate_time, disp_time, bw_avg_time, *eta_secs; unsigned long long io_bytes[DDIR_RWDIR_CNT] = {}; unsigned long long io_iops[DDIR_RWDIR_CNT] = {}; @@ -505,7 +506,11 @@ bool calc_thread_status(struct jobs_eta *je, int force) fio_gettime(&now, NULL); rate_time = mtime_since(&rate_prev_time, &now); - if (write_bw_log && rate_time > bw_avg_time && !in_ramp_time(td)) { + any_td_in_ramp = false; + for_each_td(td, i) { + any_td_in_ramp |= in_ramp_time(td); + } + if (write_bw_log && rate_time > bw_avg_time && !any_td_in_ramp) { calc_rate(unified_rw_rep, rate_time, io_bytes, rate_io_bytes, je->rate); memcpy(&rate_prev_time, &now, sizeof(now)); From da8f124f8cf55eedab9cd2d3bd8afc0cd4971295 Mon Sep 17 00:00:00 2001 From: Horshack Date: Thu, 2 Mar 2023 15:12:54 -0500 Subject: [PATCH 0430/1097] Refactor for_each_td() to catch inappropriate td ptr reuse I recently introduced a bug caused by reusing a struct thread_data *td after the end of a for_each_td() loop construct. Link: https://github.com/axboe/fio/pull/1521#issuecomment-1448591102 To prevent others from making this same mistake, this commit refactors for_each_td() so that both the struct thread_data * and the loop index variable are placed inside their own scope for the loop. This will cause any reference to those variables outside the for_each_td() to produce an undeclared identifier error, provided the outer scope doesn't already reuse those same variable names for other code within the routine (which is fine because the scopes are separate). Because C/C++ doesn't let you declare two different variable types within the scope of a for() loop initializer, creating a scope for both struct thread_data * and the loop index required explicitly declaring a scope with a curly brace. This means for_each_td() includes an opening curly brace to create the scope, which means all uses of for_each_td() must now end with an invocation of a new macro named end_for_each() to emit an ending curly brace to match the scope brace created by for_each_td(): for_each_td(td) { while (td->runstate < TD_EXITED) sleep(1); } end_for_each(); The alternative is to end every for_each_td() construct with an inline curly brace, which is off-putting since the implementation of an extra opening curly brace is abstracted in for_each_td(): for_each_td(td) { while (td->runstate < TD_EXITED) sleep(1); }} Most fio logic only declares "struct thread_data *td" and "int i" for use in for_each_td(), which means those declarations will now cause -Wunused-variable warnings since they're not used outside the scope of the refactored for_each_td(). Those declarations have been removed. Implementing this change caught a latent bug in eta.c::calc_thread_status() that accesses the ending value of struct thread_data *td after the end of for_each_td(), now manifesting as a compile error, so working as designed :) Signed-off-by: Adam Horshack (horshack@live.com) --- backend.c | 44 +++++++++++++++++------------------------- dedupe.c | 7 ++----- engines/libblkio.c | 6 ++---- eta.c | 31 +++++++++++++++--------------- fio.h | 19 ++++++++++++++++-- init.c | 14 +++++--------- iolog.c | 6 ++---- libfio.c | 12 ++++-------- rate-submit.c | 7 +++---- stat.c | 48 ++++++++++++++++++++-------------------------- steadystate.c | 27 ++++++++++++-------------- verify.c | 17 ++++++++-------- zbd.c | 35 ++++++++++++++------------------- 13 files changed, 123 insertions(+), 150 deletions(-) diff --git a/backend.c b/backend.c index 975ef48938..f541676c1d 100644 --- a/backend.c +++ b/backend.c @@ -93,19 +93,16 @@ static void sig_int(int sig) #ifdef WIN32 static void sig_break(int sig) { - struct thread_data *td; - int i; - sig_int(sig); /** * Windows terminates all job processes on SIGBREAK after the handler * returns, so give them time to wrap-up and give stats */ - for_each_td(td, i) { + for_each_td(td) { while (td->runstate < TD_EXITED) sleep(1); - } + } end_for_each(); } #endif @@ -2056,15 +2053,14 @@ static void *thread_main(void *data) static void reap_threads(unsigned int *nr_running, uint64_t *t_rate, uint64_t *m_rate) { - struct thread_data *td; unsigned int cputhreads, realthreads, pending; - int i, status, ret; + int status, ret; /* * reap exited threads (TD_EXITED -> TD_REAPED) */ realthreads = pending = cputhreads = 0; - for_each_td(td, i) { + for_each_td(td) { int flags = 0; if (!strcmp(td->o.ioengine, "cpuio")) @@ -2157,7 +2153,7 @@ static void reap_threads(unsigned int *nr_running, uint64_t *t_rate, done_secs += mtime_since_now(&td->epoch) / 1000; profile_td_exit(td); flow_exit_job(td); - } + } end_for_each(); if (*nr_running == cputhreads && !pending && realthreads) fio_terminate_threads(TERMINATE_ALL, TERMINATE_ALL); @@ -2284,13 +2280,11 @@ static bool waitee_running(struct thread_data *me) { const char *waitee = me->o.wait_for; const char *self = me->o.name; - struct thread_data *td; - int i; if (!waitee) return false; - for_each_td(td, i) { + for_each_td(td) { if (!strcmp(td->o.name, self) || strcmp(td->o.name, waitee)) continue; @@ -2300,7 +2294,7 @@ static bool waitee_running(struct thread_data *me) runstate_to_name(td->runstate)); return true; } - } + } end_for_each(); dprint(FD_PROCESS, "%s: %s completed, can run\n", self, waitee); return false; @@ -2324,14 +2318,14 @@ static void run_threads(struct sk_out *sk_out) set_sig_handlers(); nr_thread = nr_process = 0; - for_each_td(td, i) { + for_each_td(td) { if (check_mount_writes(td)) return; if (td->o.use_thread) nr_thread++; else nr_process++; - } + } end_for_each(); if (output_format & FIO_OUTPUT_NORMAL) { struct buf_output out; @@ -2357,7 +2351,7 @@ static void run_threads(struct sk_out *sk_out) nr_started = 0; m_rate = t_rate = 0; - for_each_td(td, i) { + for_each_td(td) { print_status_init(td->thread_number - 1); if (!td->o.create_serialize) @@ -2393,7 +2387,7 @@ static void run_threads(struct sk_out *sk_out) td_io_close_file(td, f); } } - } + } end_for_each(); /* start idle threads before io threads start to run */ fio_idle_prof_start(); @@ -2409,7 +2403,7 @@ static void run_threads(struct sk_out *sk_out) /* * create threads (TD_NOT_CREATED -> TD_CREATED) */ - for_each_td(td, i) { + for_each_td(td) { if (td->runstate != TD_NOT_CREATED) continue; @@ -2488,7 +2482,7 @@ static void run_threads(struct sk_out *sk_out) ret = (int)(uintptr_t)thread_main(fd); _exit(ret); - } else if (i == fio_debug_jobno) + } else if (__td_index == fio_debug_jobno) *fio_debug_jobp = pid; free(eo); free(fd); @@ -2504,7 +2498,7 @@ static void run_threads(struct sk_out *sk_out) break; } dprint(FD_MUTEX, "done waiting on startup_sem\n"); - } + } end_for_each(); /* * Wait for the started threads to transition to @@ -2549,7 +2543,7 @@ static void run_threads(struct sk_out *sk_out) /* * start created threads (TD_INITIALIZED -> TD_RUNNING). */ - for_each_td(td, i) { + for_each_td(td) { if (td->runstate != TD_INITIALIZED) continue; @@ -2563,7 +2557,7 @@ static void run_threads(struct sk_out *sk_out) t_rate += ddir_rw_sum(td->o.rate); todo--; fio_sem_up(td->sem); - } + } end_for_each(); reap_threads(&nr_running, &t_rate, &m_rate); @@ -2589,9 +2583,7 @@ static void free_disk_util(void) int fio_backend(struct sk_out *sk_out) { - struct thread_data *td; int i; - if (exec_profile) { if (load_profile(exec_profile)) return 1; @@ -2647,7 +2639,7 @@ int fio_backend(struct sk_out *sk_out) } } - for_each_td(td, i) { + for_each_td(td) { struct thread_stat *ts = &td->ts; free_clat_prio_stats(ts); @@ -2660,7 +2652,7 @@ int fio_backend(struct sk_out *sk_out) } fio_sem_remove(td->sem); td->sem = NULL; - } + } end_for_each(); free_disk_util(); if (cgroup_list) { diff --git a/dedupe.c b/dedupe.c index 8214a786b0..6170568918 100644 --- a/dedupe.c +++ b/dedupe.c @@ -7,16 +7,13 @@ */ int init_global_dedupe_working_set_seeds(void) { - int i; - struct thread_data *td; - - for_each_td(td, i) { + for_each_td(td) { if (!td->o.dedupe_global) continue; if (init_dedupe_working_set_seeds(td, 1)) return 1; - } + } end_for_each(); return 0; } diff --git a/engines/libblkio.c b/engines/libblkio.c index 054aa8001a..ee42d11c17 100644 --- a/engines/libblkio.c +++ b/engines/libblkio.c @@ -283,16 +283,14 @@ static bool possibly_null_strs_equal(const char *a, const char *b) */ static int total_threaded_subjobs(bool hipri) { - struct thread_data *td; - unsigned int i; int count = 0; - for_each_td(td, i) { + for_each_td(td) { const struct fio_blkio_options *options = td->eo; if (strcmp(td->o.ioengine, "libblkio") == 0 && td->o.use_thread && (bool)options->hipri == hipri) ++count; - } + } end_for_each(); return count; } diff --git a/eta.c b/eta.c index 6017ca3102..b392b83c2a 100644 --- a/eta.c +++ b/eta.c @@ -381,8 +381,7 @@ bool eta_time_within_slack(unsigned int time) */ bool calc_thread_status(struct jobs_eta *je, int force) { - struct thread_data *td; - int i, unified_rw_rep; + int unified_rw_rep; uint64_t rate_time, disp_time, bw_avg_time, *eta_secs; unsigned long long io_bytes[DDIR_RWDIR_CNT] = {}; unsigned long long io_iops[DDIR_RWDIR_CNT] = {}; @@ -416,7 +415,7 @@ bool calc_thread_status(struct jobs_eta *je, int force) bw_avg_time = ULONG_MAX; unified_rw_rep = 0; - for_each_td(td, i) { + for_each_td(td) { unified_rw_rep += td->o.unified_rw_rep; if (is_power_of_2(td->o.kb_base)) je->is_pow2 = 1; @@ -458,9 +457,9 @@ bool calc_thread_status(struct jobs_eta *je, int force) je->nr_pending++; if (je->elapsed_sec >= 3) - eta_secs[i] = thread_eta(td); + eta_secs[__td_index] = thread_eta(td); else - eta_secs[i] = INT_MAX; + eta_secs[__td_index] = INT_MAX; check_str_update(td); @@ -477,26 +476,26 @@ bool calc_thread_status(struct jobs_eta *je, int force) } } } - } + } end_for_each(); if (exitall_on_terminate) { je->eta_sec = INT_MAX; - for_each_td(td, i) { - if (eta_secs[i] < je->eta_sec) - je->eta_sec = eta_secs[i]; - } + for_each_td_index() { + if (eta_secs[__td_index] < je->eta_sec) + je->eta_sec = eta_secs[__td_index]; + } end_for_each(); } else { unsigned long eta_stone = 0; je->eta_sec = 0; - for_each_td(td, i) { + for_each_td(td) { if ((td->runstate == TD_NOT_CREATED) && td->o.stonewall) - eta_stone += eta_secs[i]; + eta_stone += eta_secs[__td_index]; else { - if (eta_secs[i] > je->eta_sec) - je->eta_sec = eta_secs[i]; + if (eta_secs[__td_index] > je->eta_sec) + je->eta_sec = eta_secs[__td_index]; } - } + } end_for_each(); je->eta_sec += eta_stone; } @@ -505,7 +504,7 @@ bool calc_thread_status(struct jobs_eta *je, int force) fio_gettime(&now, NULL); rate_time = mtime_since(&rate_prev_time, &now); - if (write_bw_log && rate_time > bw_avg_time && !in_ramp_time(td)) { + if (write_bw_log && rate_time > bw_avg_time /* && !in_ramp_time(td) fixme: td isn't valid here */) { calc_rate(unified_rw_rep, rate_time, io_bytes, rate_io_bytes, je->rate); memcpy(&rate_prev_time, &now, sizeof(now)); diff --git a/fio.h b/fio.h index 09c441491b..325355174b 100644 --- a/fio.h +++ b/fio.h @@ -753,9 +753,24 @@ extern void lat_target_reset(struct thread_data *); /* * Iterates all threads/processes within all the defined jobs + * Usage: + * for_each_td(var_name_for_td) { + * << bodoy of your loop >> + * Note: internally-scoped loop index availble as __td_index + * } end_for_each_td() */ -#define for_each_td(td, i) \ - for ((i) = 0, (td) = &segments[0].threads[0]; (i) < (int) thread_number; (i)++, (td) = tnumber_to_td((i))) +#define for_each_td(td) \ +{ \ + int __td_index; \ + struct thread_data *(td); \ + for (__td_index = 0, (td) = &segments[0].threads[0];\ + __td_index < (int) thread_number; __td_index++, (td) = tnumber_to_td(__td_index)) +#define for_each_td_index() \ +{ \ + int __td_index; \ + for (__td_index = 0; __td_index < (int) thread_number; __td_index++) +#define end_for_each() } + #define for_each_file(td, f, i) \ if ((td)->files_index) \ for ((i) = 0, (f) = (td)->files[0]; \ diff --git a/init.c b/init.c index 78c6c80351..442dab4273 100644 --- a/init.c +++ b/init.c @@ -1405,15 +1405,14 @@ static void gen_log_name(char *name, size_t size, const char *logtype, static int check_waitees(char *waitee) { - struct thread_data *td; - int i, ret = 0; + int ret = 0; - for_each_td(td, i) { + for_each_td(td) { if (td->subjob_number) continue; ret += !strcmp(td->o.name, waitee); - } + } end_for_each(); return ret; } @@ -1448,10 +1447,7 @@ static bool wait_for_ok(const char *jobname, struct thread_options *o) static int verify_per_group_options(struct thread_data *td, const char *jobname) { - struct thread_data *td2; - int i; - - for_each_td(td2, i) { + for_each_td(td2) { if (td->groupid != td2->groupid) continue; @@ -1461,7 +1457,7 @@ static int verify_per_group_options(struct thread_data *td, const char *jobname) jobname); return 1; } - } + } end_for_each(); return 0; } diff --git a/iolog.c b/iolog.c index ea7796320d..cc2cbc65ef 100644 --- a/iolog.c +++ b/iolog.c @@ -1875,9 +1875,7 @@ void td_writeout_logs(struct thread_data *td, bool unit_logs) void fio_writeout_logs(bool unit_logs) { - struct thread_data *td; - int i; - - for_each_td(td, i) + for_each_td(td) { td_writeout_logs(td, unit_logs); + } end_for_each(); } diff --git a/libfio.c b/libfio.c index ac5219744e..a52014ce6b 100644 --- a/libfio.c +++ b/libfio.c @@ -240,13 +240,11 @@ void fio_mark_td_terminate(struct thread_data *td) void fio_terminate_threads(unsigned int group_id, unsigned int terminate) { - struct thread_data *td; pid_t pid = getpid(); - int i; dprint(FD_PROCESS, "terminate group_id=%d\n", group_id); - for_each_td(td, i) { + for_each_td(td) { if ((terminate == TERMINATE_GROUP && group_id == TERMINATE_ALL) || (terminate == TERMINATE_GROUP && group_id == td->groupid) || (terminate == TERMINATE_STONEWALL && td->runstate >= TD_RUNNING) || @@ -274,22 +272,20 @@ void fio_terminate_threads(unsigned int group_id, unsigned int terminate) ops->terminate(td); } } - } + } end_for_each(); } int fio_running_or_pending_io_threads(void) { - struct thread_data *td; - int i; int nr_io_threads = 0; - for_each_td(td, i) { + for_each_td(td) { if (td->io_ops_init && td_ioengine_flagged(td, FIO_NOIO)) continue; nr_io_threads++; if (td->runstate < TD_EXITED) return 1; - } + } end_for_each(); if (!nr_io_threads) return -1; /* we only had cpuio threads to begin with */ diff --git a/rate-submit.c b/rate-submit.c index 3cc17eaa56..103a80aa13 100644 --- a/rate-submit.c +++ b/rate-submit.c @@ -12,8 +12,7 @@ static void check_overlap(struct io_u *io_u) { - int i, res; - struct thread_data *td; + int res; /* * Allow only one thread to check for overlap at a time to prevent two @@ -31,7 +30,7 @@ static void check_overlap(struct io_u *io_u) assert(res == 0); retry: - for_each_td(td, i) { + for_each_td(td) { if (td->runstate <= TD_SETTING_UP || td->runstate >= TD_FINISHING || !td->o.serialize_overlap || @@ -46,7 +45,7 @@ static void check_overlap(struct io_u *io_u) res = pthread_mutex_lock(&overlap_check); assert(res == 0); goto retry; - } + } end_for_each(); } static int io_workqueue_fn(struct submit_worker *sw, diff --git a/stat.c b/stat.c index b963973a58..e0a2dcc60f 100644 --- a/stat.c +++ b/stat.c @@ -2366,7 +2366,6 @@ void init_thread_stat(struct thread_stat *ts) static void init_per_prio_stats(struct thread_stat *threadstats, int nr_ts) { - struct thread_data *td; struct thread_stat *ts; int i, j, last_ts, idx; enum fio_ddir ddir; @@ -2380,7 +2379,7 @@ static void init_per_prio_stats(struct thread_stat *threadstats, int nr_ts) * store a 1 in ts->disable_prio_stat, and then do an additional * loop at the end where we invert the ts->disable_prio_stat values. */ - for_each_td(td, i) { + for_each_td(td) { if (!td->o.stats) continue; if (idx && @@ -2407,7 +2406,7 @@ static void init_per_prio_stats(struct thread_stat *threadstats, int nr_ts) } idx++; - } + } end_for_each(); /* Loop through all dst threadstats and fixup the values. */ for (i = 0; i < nr_ts; i++) { @@ -2419,7 +2418,6 @@ static void init_per_prio_stats(struct thread_stat *threadstats, int nr_ts) void __show_run_stats(void) { struct group_run_stats *runstats, *rs; - struct thread_data *td; struct thread_stat *threadstats, *ts; int i, j, k, nr_ts, last_ts, idx; bool kb_base_warned = false; @@ -2440,7 +2438,7 @@ void __show_run_stats(void) */ nr_ts = 0; last_ts = -1; - for_each_td(td, i) { + for_each_td(td) { if (!td->o.group_reporting) { nr_ts++; continue; @@ -2452,7 +2450,7 @@ void __show_run_stats(void) last_ts = td->groupid; nr_ts++; - } + } end_for_each(); threadstats = malloc(nr_ts * sizeof(struct thread_stat)); opt_lists = malloc(nr_ts * sizeof(struct flist_head *)); @@ -2467,7 +2465,7 @@ void __show_run_stats(void) j = 0; last_ts = -1; idx = 0; - for_each_td(td, i) { + for_each_td(td) { if (!td->o.stats) continue; if (idx && (!td->o.group_reporting || @@ -2569,7 +2567,7 @@ void __show_run_stats(void) } else ts->ss_dur = ts->ss_state = 0; - } + } end_for_each(); for (i = 0; i < nr_ts; i++) { unsigned long long bw; @@ -2722,17 +2720,15 @@ void __show_run_stats(void) int __show_running_run_stats(void) { - struct thread_data *td; unsigned long long *rt; struct timespec ts; - int i; fio_sem_down(stat_sem); rt = malloc(thread_number * sizeof(unsigned long long)); fio_gettime(&ts, NULL); - for_each_td(td, i) { + for_each_td(td) { if (td->runstate >= TD_EXITED) continue; @@ -2742,16 +2738,16 @@ int __show_running_run_stats(void) } td->ts.total_run_time = mtime_since(&td->epoch, &ts); - rt[i] = mtime_since(&td->start, &ts); + rt[__td_index] = mtime_since(&td->start, &ts); if (td_read(td) && td->ts.io_bytes[DDIR_READ]) - td->ts.runtime[DDIR_READ] += rt[i]; + td->ts.runtime[DDIR_READ] += rt[__td_index]; if (td_write(td) && td->ts.io_bytes[DDIR_WRITE]) - td->ts.runtime[DDIR_WRITE] += rt[i]; + td->ts.runtime[DDIR_WRITE] += rt[__td_index]; if (td_trim(td) && td->ts.io_bytes[DDIR_TRIM]) - td->ts.runtime[DDIR_TRIM] += rt[i]; - } + td->ts.runtime[DDIR_TRIM] += rt[__td_index]; + } end_for_each(); - for_each_td(td, i) { + for_each_td(td) { if (td->runstate >= TD_EXITED) continue; if (td->rusage_sem) { @@ -2759,21 +2755,21 @@ int __show_running_run_stats(void) fio_sem_down(td->rusage_sem); } td->update_rusage = 0; - } + } end_for_each(); __show_run_stats(); - for_each_td(td, i) { + for_each_td(td) { if (td->runstate >= TD_EXITED) continue; if (td_read(td) && td->ts.io_bytes[DDIR_READ]) - td->ts.runtime[DDIR_READ] -= rt[i]; + td->ts.runtime[DDIR_READ] -= rt[__td_index]; if (td_write(td) && td->ts.io_bytes[DDIR_WRITE]) - td->ts.runtime[DDIR_WRITE] -= rt[i]; + td->ts.runtime[DDIR_WRITE] -= rt[__td_index]; if (td_trim(td) && td->ts.io_bytes[DDIR_TRIM]) - td->ts.runtime[DDIR_TRIM] -= rt[i]; - } + td->ts.runtime[DDIR_TRIM] -= rt[__td_index]; + } end_for_each(); free(rt); fio_sem_up(stat_sem); @@ -3554,15 +3550,13 @@ static int add_iops_samples(struct thread_data *td, struct timespec *t) */ int calc_log_samples(void) { - struct thread_data *td; unsigned int next = ~0U, tmp = 0, next_mod = 0, log_avg_msec_min = -1U; struct timespec now; - int i; long elapsed_time = 0; fio_gettime(&now, NULL); - for_each_td(td, i) { + for_each_td(td) { elapsed_time = mtime_since_now(&td->epoch); if (!td->o.stats) @@ -3589,7 +3583,7 @@ int calc_log_samples(void) if (tmp < next) next = tmp; - } + } end_for_each(); /* if log_avg_msec_min has not been changed, set it to 0 */ if (log_avg_msec_min == -1U) diff --git a/steadystate.c b/steadystate.c index ad19318c2a..14cdf0ed1a 100644 --- a/steadystate.c +++ b/steadystate.c @@ -23,8 +23,8 @@ static void steadystate_alloc(struct thread_data *td) void steadystate_setup(void) { - struct thread_data *td, *prev_td; - int i, prev_groupid; + struct thread_data *prev_td; + int prev_groupid; if (!steadystate_enabled) return; @@ -36,7 +36,7 @@ void steadystate_setup(void) */ prev_groupid = -1; prev_td = NULL; - for_each_td(td, i) { + for_each_td(td) { if (!td->ss.dur) continue; @@ -51,7 +51,7 @@ void steadystate_setup(void) prev_groupid = td->groupid; } prev_td = td; - } + } end_for_each(); if (prev_td && prev_td->o.group_reporting) steadystate_alloc(prev_td); @@ -198,16 +198,15 @@ static bool steadystate_deviation(uint64_t iops, uint64_t bw, int steadystate_check(void) { - int i, j, ddir, prev_groupid, group_ramp_time_over = 0; + int ddir, prev_groupid, group_ramp_time_over = 0; unsigned long rate_time; - struct thread_data *td, *td2; struct timespec now; uint64_t group_bw = 0, group_iops = 0; uint64_t td_iops, td_bytes; bool ret; prev_groupid = -1; - for_each_td(td, i) { + for_each_td(td) { const bool needs_lock = td_async_processing(td); struct steadystate_data *ss = &td->ss; @@ -271,7 +270,7 @@ int steadystate_check(void) dprint(FD_STEADYSTATE, "steadystate_check() thread: %d, " "groupid: %u, rate_msec: %ld, " "iops: %llu, bw: %llu, head: %d, tail: %d\n", - i, td->groupid, rate_time, + __td_index, td->groupid, rate_time, (unsigned long long) group_iops, (unsigned long long) group_bw, ss->head, ss->tail); @@ -283,18 +282,18 @@ int steadystate_check(void) if (ret) { if (td->o.group_reporting) { - for_each_td(td2, j) { + for_each_td(td2) { if (td2->groupid == td->groupid) { td2->ss.state |= FIO_SS_ATTAINED; fio_mark_td_terminate(td2); } - } + } end_for_each(); } else { ss->state |= FIO_SS_ATTAINED; fio_mark_td_terminate(td); } } - } + } end_for_each(); return 0; } @@ -302,8 +301,6 @@ int td_steadystate_init(struct thread_data *td) { struct steadystate_data *ss = &td->ss; struct thread_options *o = &td->o; - struct thread_data *td2; - int j; memset(ss, 0, sizeof(*ss)); @@ -325,7 +322,7 @@ int td_steadystate_init(struct thread_data *td) } /* make sure that ss options are consistent within reporting group */ - for_each_td(td2, j) { + for_each_td(td2) { if (td2->groupid == td->groupid) { struct steadystate_data *ss2 = &td2->ss; @@ -339,7 +336,7 @@ int td_steadystate_init(struct thread_data *td) return 1; } } - } + } end_for_each(); return 0; } diff --git a/verify.c b/verify.c index ddfadcc873..e7e4c69ca6 100644 --- a/verify.c +++ b/verify.c @@ -1568,10 +1568,9 @@ static int fill_file_completions(struct thread_data *td, struct all_io_list *get_all_io_list(int save_mask, size_t *sz) { struct all_io_list *rep; - struct thread_data *td; size_t depth; void *next; - int i, nr; + int nr; compiletime_assert(sizeof(struct all_io_list) == 8, "all_io_list"); @@ -1581,14 +1580,14 @@ struct all_io_list *get_all_io_list(int save_mask, size_t *sz) */ depth = 0; nr = 0; - for_each_td(td, i) { - if (save_mask != IO_LIST_ALL && (i + 1) != save_mask) + for_each_td(td) { + if (save_mask != IO_LIST_ALL && (__td_index + 1) != save_mask) continue; td->stop_io = 1; td->flags |= TD_F_VSTATE_SAVED; depth += (td->o.iodepth * td->o.nr_files); nr++; - } + } end_for_each(); if (!nr) return NULL; @@ -1602,11 +1601,11 @@ struct all_io_list *get_all_io_list(int save_mask, size_t *sz) rep->threads = cpu_to_le64((uint64_t) nr); next = &rep->state[0]; - for_each_td(td, i) { + for_each_td(td) { struct thread_io_list *s = next; unsigned int comps, index = 0; - if (save_mask != IO_LIST_ALL && (i + 1) != save_mask) + if (save_mask != IO_LIST_ALL && (__td_index + 1) != save_mask) continue; comps = fill_file_completions(td, s, &index); @@ -1615,7 +1614,7 @@ struct all_io_list *get_all_io_list(int save_mask, size_t *sz) s->depth = cpu_to_le64((uint64_t) td->o.iodepth); s->nofiles = cpu_to_le64((uint64_t) td->o.nr_files); s->numberio = cpu_to_le64((uint64_t) td->io_issues[DDIR_WRITE]); - s->index = cpu_to_le64((uint64_t) i); + s->index = cpu_to_le64((uint64_t) __td_index); if (td->random_state.use64) { s->rand.state64.s[0] = cpu_to_le64(td->random_state.state64.s1); s->rand.state64.s[1] = cpu_to_le64(td->random_state.state64.s2); @@ -1633,7 +1632,7 @@ struct all_io_list *get_all_io_list(int save_mask, size_t *sz) } snprintf((char *) s->name, sizeof(s->name), "%s", td->o.name); next = io_list_next(s); - } + } end_for_each(); return rep; } diff --git a/zbd.c b/zbd.c index d6f8f800e3..f5fb923ac0 100644 --- a/zbd.c +++ b/zbd.c @@ -524,11 +524,10 @@ static bool zbd_open_zone(struct thread_data *td, const struct fio_file *f, /* Verify whether direct I/O is used for all host-managed zoned block drives. */ static bool zbd_using_direct_io(void) { - struct thread_data *td; struct fio_file *f; - int i, j; + int j; - for_each_td(td, i) { + for_each_td(td) { if (td->o.odirect || !(td->o.td_ddir & TD_DDIR_WRITE)) continue; for_each_file(td, f, j) { @@ -536,7 +535,7 @@ static bool zbd_using_direct_io(void) f->zbd_info->model == ZBD_HOST_MANAGED) return false; } - } + } end_for_each(); return true; } @@ -639,27 +638,25 @@ static bool zbd_zone_align_file_sizes(struct thread_data *td, */ static bool zbd_verify_sizes(void) { - struct thread_data *td; struct fio_file *f; - int i, j; + int j; - for_each_td(td, i) { + for_each_td(td) { for_each_file(td, f, j) { if (!zbd_zone_align_file_sizes(td, f)) return false; } - } + } end_for_each(); return true; } static bool zbd_verify_bs(void) { - struct thread_data *td; struct fio_file *f; - int i, j; + int j; - for_each_td(td, i) { + for_each_td(td) { if (td_trim(td) && (td->o.min_bs[DDIR_TRIM] != td->o.max_bs[DDIR_TRIM] || td->o.bssplit_nr[DDIR_TRIM])) { @@ -680,7 +677,7 @@ static bool zbd_verify_bs(void) return false; } } - } + } end_for_each(); return true; } @@ -1010,11 +1007,10 @@ void zbd_free_zone_info(struct fio_file *f) */ static int zbd_init_zone_info(struct thread_data *td, struct fio_file *file) { - struct thread_data *td2; struct fio_file *f2; - int i, j, ret; + int j, ret; - for_each_td(td2, i) { + for_each_td(td2) { for_each_file(td2, f2, j) { if (td2 == td && f2 == file) continue; @@ -1025,7 +1021,7 @@ static int zbd_init_zone_info(struct thread_data *td, struct fio_file *file) file->zbd_info->refcount++; return 0; } - } + } end_for_each(); ret = zbd_create_zone_info(td, file); if (ret < 0) @@ -1289,13 +1285,10 @@ static uint32_t pick_random_zone_idx(const struct fio_file *f, static bool any_io_in_flight(void) { - struct thread_data *td; - int i; - - for_each_td(td, i) { + for_each_td(td) { if (td->io_u_in_flight) return true; - } + } end_for_each(); return false; } From 79201772c8091386077bf3bdc31f53211a0e020d Mon Sep 17 00:00:00 2001 From: Shin'ichiro Kawasaki Date: Mon, 6 Mar 2023 14:58:39 +0900 Subject: [PATCH 0431/1097] t/zbd: rename logical_block_size to min_seq_write_size The test script t/zbd/test-zbd-support assumes that the logical block size is the minimum size unit to write to sequential write required zones, then it uses a variable named 'logical_block_size' to keep the minimum size. The assumption is true for ZNS devices but not for ZBC/ZAC devices. Rename the variable from 'logical_block_size' to 'min_seq_write_size' to not imply the wrong assumption. Signed-off-by: Shin'ichiro Kawasaki Signed-off-by: Vincent Fu --- t/zbd/test-zbd-support | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/t/zbd/test-zbd-support b/t/zbd/test-zbd-support index 893aff3ce5..7b229002c5 100755 --- a/t/zbd/test-zbd-support +++ b/t/zbd/test-zbd-support @@ -166,7 +166,7 @@ write_and_run_one_fio_job() { shift 2 r=$(((RANDOM << 16) | RANDOM)) write_opts=(--name="write_job" --rw=write "$(ioengine "psync")" \ - --bs="${logical_block_size}" --zonemode=zbd \ + --bs="${min_seq_write_size}" --zonemode=zbd \ --zonesize="${zone_size}" --thread=1 --direct=1 \ --offset="${write_offset}" --size="${write_size}") write_opts+=("${job_var_opts[@]}") @@ -335,7 +335,7 @@ test4() { size=$((zone_size)) [ -n "$is_zbd" ] && reset_zone "$dev" $((off / 512)) opts+=("--name=$dev" "--filename=$dev" "--offset=$off") - opts+=(--bs="$(min $((logical_block_size * 256)) $size)") + opts+=(--bs="$(min $((min_seq_write_size * 256)) $size)") opts+=("--size=$size" "--thread=1" "--read_beyond_wp=1") opts+=("$(ioengine "psync")" "--rw=read" "--direct=1" "--disable_lat=1") opts+=("--zonemode=zbd" "--zonesize=${zone_size}") @@ -351,7 +351,7 @@ test5() { off=$((first_sequential_zone_sector * 512)) capacity=$(total_zone_capacity 4 $off $dev) size=$((4 * zone_size)) - bs=$(min "$(max $((zone_size / 64)) "$logical_block_size")" "$zone_cap_bs") + bs=$(min "$(max $((zone_size / 64)) "$min_seq_write_size")" "$zone_cap_bs") run_fio_on_seq "$(ioengine "psync")" --iodepth=1 --rw=write \ --bs="$bs" --do_verify=1 --verify=md5 \ >>"${logfile}.${test_number}" 2>&1 || return $? @@ -367,7 +367,7 @@ test6() { off=$((first_sequential_zone_sector * 512)) capacity=$(total_zone_capacity 4 $off $dev) size=$((4 * zone_size)) - bs=$(min "$(max $((zone_size / 64)) "$logical_block_size")" "$zone_cap_bs") + bs=$(min "$(max $((zone_size / 64)) "$min_seq_write_size")" "$zone_cap_bs") write_and_run_one_fio_job \ $((first_sequential_zone_sector * 512)) "${size}" \ --offset="${off}" \ @@ -748,7 +748,7 @@ test30() { prep_write off=$((first_sequential_zone_sector * 512)) run_one_fio_job "$(ioengine "libaio")" --iodepth=8 --rw=randrw \ - --bs="$(max $((zone_size / 128)) "$logical_block_size")"\ + --bs="$(max $((zone_size / 128)) "$min_seq_write_size")"\ --zonemode=zbd --zonesize="${zone_size}" --offset=$off\ --loops=2 --time_based --runtime=30s --norandommap=1\ >>"${logfile}.${test_number}" 2>&1 @@ -904,9 +904,9 @@ test38() { local bs off size prep_write - size=$((logical_block_size)) - off=$((disk_size - logical_block_size)) - bs=$((logical_block_size)) + size=$((min_seq_write_size)) + off=$((disk_size - min_seq_write_size)) + bs=$((min_seq_write_size)) run_one_fio_job --offset=$off --size=$size "$(ioengine "psync")" \ --iodepth=1 --rw=write --do_verify=1 --verify=md5 \ --bs=$bs --zonemode=zbd --zonesize="${zone_size}" \ @@ -924,7 +924,7 @@ read_one_block() { exit 1 fi off=${result[0]} - bs=$((logical_block_size)) + bs=$((min_seq_write_size)) run_one_fio_job --rw=read "$(ioengine "psync")" --offset=$off --bs=$bs \ --size=$bs "$@" 2>&1 | tee -a "${logfile}.${test_number}" @@ -934,14 +934,14 @@ read_one_block() { test39() { require_zbd || return $SKIP_TESTCASE read_one_block --zonemode=none >/dev/null || return $? - check_read $((logical_block_size)) || return $? + check_read $((min_seq_write_size)) || return $? } # Check whether fio accepts --zonemode=strided for zoned block devices. test40() { local bs - bs=$((logical_block_size)) + bs=$((min_seq_write_size)) require_zbd || return $SKIP_TESTCASE read_one_block --zonemode=strided | grep -q 'fio: --zonesize must be specified when using --zonemode=strided' || @@ -982,7 +982,7 @@ test45() { require_zbd || return $SKIP_TESTCASE prep_write - bs=$((logical_block_size)) + bs=$((min_seq_write_size)) run_one_fio_job "$(ioengine "psync")" --iodepth=1 --rw=randwrite --bs=$bs\ --offset=$((first_sequential_zone_sector * 512)) \ --size="$zone_size" --do_verify=1 --verify=md5 2>&1 | @@ -1007,7 +1007,7 @@ test47() { local bs prep_write - bs=$((logical_block_size)) + bs=$((min_seq_write_size)) run_fio_on_seq "$(ioengine "psync")" --rw=write --bs=$bs --zoneskip=1 \ >> "${logfile}.${test_number}" 2>&1 && return 1 grep -q 'zoneskip 1 is not a multiple of the device zone size' "${logfile}.${test_number}" @@ -1190,7 +1190,7 @@ test54() { # test 'z' suffix parsing only test55() { local bs - bs=$((logical_block_size)) + bs=$((min_seq_write_size)) require_zbd || return $SKIP_TESTCASE # offset=1z + offset_increment=10z + size=2z @@ -1216,7 +1216,7 @@ test55() { # test 'z' suffix parsing only test56() { local bs - bs=$((logical_block_size)) + bs=$((min_seq_write_size)) require_regular_block_dev || return $SKIP_TESTCASE require_seq_zones 10 || return $SKIP_TESTCASE @@ -1260,7 +1260,7 @@ test58() { require_seq_zones 128 || return $SKIP_TESTCASE size=$((zone_size * 128)) - bs="$(max $((zone_size / 128)) "$logical_block_size")" + bs="$(max $((zone_size / 128)) "$min_seq_write_size")" prep_write off=$((first_sequential_zone_sector * 512)) run_fio --zonemode=zbd --direct=1 --zonesize="${zone_size}" --thread=1 \ @@ -1427,7 +1427,7 @@ if [[ -b "$realdev" ]]; then realsysfs=$(readlink "/sys/dev/block/$major:$minor") basename=$(basename "${realsysfs%/*}") fi - logical_block_size=$(<"/sys/block/$basename/queue/logical_block_size") + min_seq_write_size=$(<"/sys/block/$basename/queue/logical_block_size") case "$(<"/sys/class/block/$basename/queue/zoned")" in host-managed|host-aware) is_zbd=true @@ -1452,8 +1452,8 @@ if [[ -b "$realdev" ]]; then ;; *) first_sequential_zone_sector=$(((disk_size / 2) & - (logical_block_size - 1))) - zone_size=$(max 65536 "$logical_block_size") + (min_seq_write_size - 1))) + zone_size=$(max 65536 "$min_seq_write_size") sectors_per_zone=$((zone_size / 512)) max_open_zones=128 set_io_scheduler "$basename" none || exit $? @@ -1476,7 +1476,7 @@ elif [[ -c "$realdev" ]]; then echo "Failed to determine disk size" exit 1 fi - if ! logical_block_size=($(zbc_logical_block_size "$dev")); then + if ! min_seq_write_size=($(zbc_logical_block_size "$dev")); then echo "Failed to determine logical block size" exit 1 fi From 557cfc51068921766e8cd6b242feb4c929cb45ea Mon Sep 17 00:00:00 2001 From: Shin'ichiro Kawasaki Date: Mon, 6 Mar 2023 14:58:40 +0900 Subject: [PATCH 0432/1097] t/zbd: fix minimum write size to sequential write required zones ZBC and ZAC require that writes to sequential write required zones shall be aligned to physical block size. However, the t/zbd/test-zbd-support script uses logical block size as the minimum write size. When SMR drives have the physical block size larger than the logical block size, writes with the logical block size causes unaligned write command error. To fix it, use correct value as the minimum write size. As for zoned block devices, introduce a helper function min_seq_write_size(), which checks sysfs attributes and returns the correct size. Refer the attribute zone_write_granularity when it is available, which provides the minimum write size regardless of the device type. If the attribute is not available, refer the attribute physical_block_size for SMR devices, and the logical_block_size attribute for other devices. As for SG node device, refer physical block size that zbc_info command reports. Signed-off-by: Shin'ichiro Kawasaki Signed-off-by: Vincent Fu --- t/zbd/functions | 28 +++++++++++++++++++++++++--- t/zbd/test-zbd-support | 6 +++--- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/t/zbd/functions b/t/zbd/functions index 812320f529..9a6d699910 100644 --- a/t/zbd/functions +++ b/t/zbd/functions @@ -238,18 +238,40 @@ max_open_zones() { fi } +# Get minimum block size to write to seq zones. Refer the sysfs attribute +# zone_write_granularity which shows the valid minimum size regardless of zoned +# block device type. If the sysfs attribute is not available, refer physical +# block size for rotational SMR drives. For non-rotational devices such as ZNS +# devices, refer logical block size. +min_seq_write_size() { + local sys_path="/sys/block/$1/queue" + local -i size=0 + + if [[ -r "$sys_path/zone_write_granularity" ]]; then + size=$(<"$sys_path/zone_write_granularity") + fi + + if ((size)); then + echo "$size" + elif (($(<"$sys_path/rotational"))); then + cat "$sys_path/physical_block_size" + else + cat "$sys_path/logical_block_size" + fi +} + is_zbc() { local dev=$1 [[ -z "$(${zbc_info} "$dev" | grep "is not a zoned block device")" ]] } -zbc_logical_block_size() { +zbc_physical_block_size() { local dev=$1 ${zbc_info} "$dev" | - grep "logical blocks" | - sed -n 's/^[[:blank:]]*[0-9]* logical blocks of[[:blank:]]*//p' | + grep "physical blocks" | + sed -n 's/^[[:blank:]]*[0-9]* physical blocks of[[:blank:]]*//p' | sed 's/ B//' } diff --git a/t/zbd/test-zbd-support b/t/zbd/test-zbd-support index 7b229002c5..996160e769 100755 --- a/t/zbd/test-zbd-support +++ b/t/zbd/test-zbd-support @@ -1427,7 +1427,7 @@ if [[ -b "$realdev" ]]; then realsysfs=$(readlink "/sys/dev/block/$major:$minor") basename=$(basename "${realsysfs%/*}") fi - min_seq_write_size=$(<"/sys/block/$basename/queue/logical_block_size") + min_seq_write_size=$(min_seq_write_size "$basename") case "$(<"/sys/class/block/$basename/queue/zoned")" in host-managed|host-aware) is_zbd=true @@ -1476,8 +1476,8 @@ elif [[ -c "$realdev" ]]; then echo "Failed to determine disk size" exit 1 fi - if ! min_seq_write_size=($(zbc_logical_block_size "$dev")); then - echo "Failed to determine logical block size" + if ! min_seq_write_size=($(zbc_physical_block_size "$dev")); then + echo "Failed to determine physical block size" exit 1 fi if ! result=($(first_sequential_zone "$dev")); then From ebd9b4dd690808f720712b9c1a1288ae0742f361 Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Tue, 14 Mar 2023 06:44:41 -0600 Subject: [PATCH 0433/1097] Fio 3.34 Signed-off-by: Jens Axboe --- FIO-VERSION-GEN | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FIO-VERSION-GEN b/FIO-VERSION-GEN index 5a0822c94d..f1585d342e 100755 --- a/FIO-VERSION-GEN +++ b/FIO-VERSION-GEN @@ -1,7 +1,7 @@ #!/bin/sh GVF=FIO-VERSION-FILE -DEF_VER=fio-3.33 +DEF_VER=fio-3.34 LF=' ' From 4d9521fd4d679f914ccf425f6c06864484352fea Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Tue, 14 Mar 2023 13:58:13 -0600 Subject: [PATCH 0434/1097] t/io_uring: avoid truncation of offset on 32-bit builds Signed-off-by: Jens Axboe --- t/io_uring.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/t/io_uring.c b/t/io_uring.c index 1ea0a9da29..d39d7924b4 100644 --- a/t/io_uring.c +++ b/t/io_uring.c @@ -530,8 +530,11 @@ static unsigned long long get_offset(struct submitter *s, struct file *f) long r; if (random_io) { + unsigned long long block; + r = __rand64(&s->rand_state); - offset = (r % (f->max_blocks - 1)) * bs; + block = r % f->max_blocks; + offset = block * (unsigned long long) bs; } else { offset = f->cur_off; f->cur_off += bs; From bb2d963c60d906818a44cda7838d736860b45f79 Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Tue, 14 Mar 2023 13:59:32 -0600 Subject: [PATCH 0435/1097] t/io_uring: use the get_offset() code to retrieve pass-through offset No point in duplicating this code, use the helper. Signed-off-by: Jens Axboe --- t/io_uring.c | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/t/io_uring.c b/t/io_uring.c index d39d7924b4..17d1d26356 100644 --- a/t/io_uring.c +++ b/t/io_uring.c @@ -606,7 +606,6 @@ static void init_io_pt(struct submitter *s, unsigned index) struct nvme_uring_cmd *cmd; unsigned long long slba; unsigned long long nlb; - long r; if (s->nr_files == 1) { f = &s->files[0]; @@ -621,15 +620,7 @@ static void init_io_pt(struct submitter *s, unsigned index) } f->pending_ios++; - if (random_io) { - r = __rand64(&s->rand_state); - offset = (r % (f->max_blocks - 1)) * bs; - } else { - offset = f->cur_off; - f->cur_off += bs; - if (f->cur_off + bs > f->max_size) - f->cur_off = 0; - } + offset = get_offset(s, f); if (register_files) { sqe->fd = f->fixed_fd; From 4ad09b569a2689b3b67744eaccd378d013eb82a7 Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Tue, 14 Mar 2023 14:03:32 -0600 Subject: [PATCH 0436/1097] t/io_uring: abstract out init_new_io() helper We do this in 4 different spots, put it in a helper. Signed-off-by: Jens Axboe --- t/io_uring.c | 62 ++++++++++++++++------------------------------------ 1 file changed, 19 insertions(+), 43 deletions(-) diff --git a/t/io_uring.c b/t/io_uring.c index 17d1d26356..504f8ce9f1 100644 --- a/t/io_uring.c +++ b/t/io_uring.c @@ -545,16 +545,10 @@ static unsigned long long get_offset(struct submitter *s, struct file *f) return offset; } -static void init_io(struct submitter *s, unsigned index) +static struct file *init_new_io(struct submitter *s) { - struct io_uring_sqe *sqe = &s->sqes[index]; struct file *f; - if (do_nop) { - sqe->opcode = IORING_OP_NOP; - return; - } - if (s->nr_files == 1) { f = &s->files[0]; } else { @@ -566,7 +560,22 @@ static void init_io(struct submitter *s, unsigned index) f = &s->files[s->cur_file]; } } + f->pending_ios++; + return f; +} + +static void init_io(struct submitter *s, unsigned index) +{ + struct io_uring_sqe *sqe = &s->sqes[index]; + struct file *f; + + if (do_nop) { + sqe->opcode = IORING_OP_NOP; + return; + } + + f = init_new_io(s); if (register_files) { sqe->flags = IOSQE_FIXED_FILE; @@ -607,18 +616,7 @@ static void init_io_pt(struct submitter *s, unsigned index) unsigned long long slba; unsigned long long nlb; - if (s->nr_files == 1) { - f = &s->files[0]; - } else { - f = &s->files[s->cur_file]; - if (f->pending_ios >= file_depth(s)) { - s->cur_file++; - if (s->cur_file == s->nr_files) - s->cur_file = 0; - f = &s->files[s->cur_file]; - } - } - f->pending_ios++; + f = init_new_io(s); offset = get_offset(s, f); @@ -1115,18 +1113,7 @@ static int prep_more_ios_aio(struct submitter *s, int max_ios, struct iocb *iocb while (index < max_ios) { struct iocb *iocb = &iocbs[index]; - if (s->nr_files == 1) { - f = &s->files[0]; - } else { - f = &s->files[s->cur_file]; - if (f->pending_ios >= file_depth(s)) { - s->cur_file++; - if (s->cur_file == s->nr_files) - s->cur_file = 0; - f = &s->files[s->cur_file]; - } - } - f->pending_ios++; + f = init_new_io(s); io_prep_pread(iocb, f->real_fd, s->iovecs[index].iov_base, s->iovecs[index].iov_len, get_offset(s, f)); @@ -1413,18 +1400,7 @@ static void *submitter_sync_fn(void *data) uint64_t offset; struct file *f; - if (s->nr_files == 1) { - f = &s->files[0]; - } else { - f = &s->files[s->cur_file]; - if (f->pending_ios >= file_depth(s)) { - s->cur_file++; - if (s->cur_file == s->nr_files) - s->cur_file = 0; - f = &s->files[s->cur_file]; - } - } - f->pending_ios++; + f = init_new_io(s); #ifdef ARCH_HAVE_CPU_CLOCK if (stats) From a967e54d34afe3bb10cd521d78bcaea2dd8c7cdc Mon Sep 17 00:00:00 2001 From: Damien Le Moal Date: Fri, 10 Mar 2023 10:28:39 +0900 Subject: [PATCH 0437/1097] stat: Fix ioprio print When using per-priority statistics for workloads using multiple different priority values, the statistics output displays the priority class and value (level) for each set of statistics. However, this is done using linux priority values coding, that is, assuming that the priority level is at most 7 (lower 3-bits). However, this is not always the case for all OSes. E.g. dragonfly allows IO priorities up to a value of 10. Introduce the OS dependent ioprio_class() and ioprio() macros to extract the fields from an ioprio value according to the OS capabilities. A generic definition (always returning 0) for these macros in os/os.h is added and used for all OSes that do not define these macros. The functions show_ddir_status() and add_ddir_status_json() are modified to use these new macros to fix per priority statistics output. The modification includes changes to the loops over the clat_prio array to reduce indentation levels, making the code a little cleaner. Fixes: 692dec0cfb4b ("stat: report clat stats on a per priority granularity") Signed-off-by: Damien Le Moal Reviewed-by: Niklas Cassel Signed-off-by: Vincent Fu --- os/os-dragonfly.h | 2 ++ os/os-linux.h | 3 ++ os/os.h | 2 ++ stat.c | 85 +++++++++++++++++++++++++---------------------- 4 files changed, 52 insertions(+), 40 deletions(-) diff --git a/os/os-dragonfly.h b/os/os-dragonfly.h index 5b37a37e19..bde39101b5 100644 --- a/os/os-dragonfly.h +++ b/os/os-dragonfly.h @@ -175,6 +175,8 @@ static inline int fio_getaffinity(int pid, os_cpu_mask_t *mask) #define ioprio_set(which, who, ioprio_class, ioprio) \ ioprio_set(which, who, ioprio) +#define ioprio(ioprio) (ioprio) + static inline int blockdev_size(struct fio_file *f, unsigned long long *bytes) { struct partinfo pi; diff --git a/os/os-linux.h b/os/os-linux.h index 7a78b42d4d..2f9f7e796d 100644 --- a/os/os-linux.h +++ b/os/os-linux.h @@ -153,6 +153,9 @@ static inline int ioprio_set(int which, int who, int ioprio_class, int ioprio) ioprio_value(ioprio_class, ioprio)); } +#define ioprio_class(ioprio) ((ioprio) >> IOPRIO_CLASS_SHIFT) +#define ioprio(ioprio) ((ioprio) & 7) + #ifndef CONFIG_HAVE_GETTID static inline int gettid(void) { diff --git a/os/os.h b/os/os.h index ebaf8af5e2..036fc233fe 100644 --- a/os/os.h +++ b/os/os.h @@ -116,12 +116,14 @@ extern int fio_cpus_split(os_cpu_mask_t *mask, unsigned int cpu); #endif #ifndef FIO_HAVE_IOPRIO_CLASS +#define ioprio_class(prio) 0 #define ioprio_value_is_class_rt(prio) (false) #define IOPRIO_MIN_PRIO_CLASS 0 #define IOPRIO_MAX_PRIO_CLASS 0 #endif #ifndef FIO_HAVE_IOPRIO #define ioprio_value(prioclass, prio) (0) +#define ioprio(ioprio) 0 #define ioprio_set(which, who, prioclass, prio) (0) #define IOPRIO_MIN_PRIO 0 #define IOPRIO_MAX_PRIO 0 diff --git a/stat.c b/stat.c index e0a2dcc60f..56be330b56 100644 --- a/stat.c +++ b/stat.c @@ -590,17 +590,18 @@ static void show_ddir_status(struct group_run_stats *rs, struct thread_stat *ts, /* Only print per prio stats if there are >= 2 prios with samples */ if (get_nr_prios_with_samples(ts, ddir) >= 2) { for (i = 0; i < ts->nr_clat_prio[ddir]; i++) { - if (calc_lat(&ts->clat_prio[ddir][i].clat_stat, &min, - &max, &mean, &dev)) { - char buf[64]; + char buf[64]; - snprintf(buf, sizeof(buf), - "%s prio %u/%u", - clat_type, - ts->clat_prio[ddir][i].ioprio >> 13, - ts->clat_prio[ddir][i].ioprio & 7); - display_lat(buf, min, max, mean, dev, out); - } + if (!calc_lat(&ts->clat_prio[ddir][i].clat_stat, &min, + &max, &mean, &dev)) + continue; + + snprintf(buf, sizeof(buf), + "%s prio %u/%u", + clat_type, + ioprio_class(ts->clat_prio[ddir][i].ioprio), + ioprio(ts->clat_prio[ddir][i].ioprio)); + display_lat(buf, min, max, mean, dev, out); } } @@ -632,20 +633,22 @@ static void show_ddir_status(struct group_run_stats *rs, struct thread_stat *ts, /* Only print per prio stats if there are >= 2 prios with samples */ if (get_nr_prios_with_samples(ts, ddir) >= 2) { for (i = 0; i < ts->nr_clat_prio[ddir]; i++) { - uint64_t prio_samples = ts->clat_prio[ddir][i].clat_stat.samples; - - if (prio_samples > 0) { - snprintf(prio_name, sizeof(prio_name), - "%s prio %u/%u (%.2f%% of IOs)", - clat_type, - ts->clat_prio[ddir][i].ioprio >> 13, - ts->clat_prio[ddir][i].ioprio & 7, - 100. * (double) prio_samples / (double) samples); - show_clat_percentiles(ts->clat_prio[ddir][i].io_u_plat, - prio_samples, ts->percentile_list, - ts->percentile_precision, - prio_name, out); - } + uint64_t prio_samples = + ts->clat_prio[ddir][i].clat_stat.samples; + + if (!prio_samples) + continue; + + snprintf(prio_name, sizeof(prio_name), + "%s prio %u/%u (%.2f%% of IOs)", + clat_type, + ioprio_class(ts->clat_prio[ddir][i].ioprio), + ioprio(ts->clat_prio[ddir][i].ioprio), + 100. * (double) prio_samples / (double) samples); + show_clat_percentiles(ts->clat_prio[ddir][i].io_u_plat, + prio_samples, ts->percentile_list, + ts->percentile_precision, + prio_name, out); } } } @@ -1508,22 +1511,24 @@ static void add_ddir_status_json(struct thread_stat *ts, json_object_add_value_array(dir_object, "prios", array); for (i = 0; i < ts->nr_clat_prio[ddir]; i++) { - if (ts->clat_prio[ddir][i].clat_stat.samples > 0) { - struct json_object *obj = json_create_object(); - unsigned long long class, level; - - class = ts->clat_prio[ddir][i].ioprio >> 13; - json_object_add_value_int(obj, "prioclass", class); - level = ts->clat_prio[ddir][i].ioprio & 7; - json_object_add_value_int(obj, "prio", level); - - tmp_object = add_ddir_lat_json(ts, - ts->clat_percentiles | ts->lat_percentiles, - &ts->clat_prio[ddir][i].clat_stat, - ts->clat_prio[ddir][i].io_u_plat); - json_object_add_value_object(obj, obj_name, tmp_object); - json_array_add_value_object(array, obj); - } + struct json_object *obj; + + if (!ts->clat_prio[ddir][i].clat_stat.samples) + continue; + + obj = json_create_object(); + + json_object_add_value_int(obj, "prioclass", + ioprio_class(ts->clat_prio[ddir][i].ioprio)); + json_object_add_value_int(obj, "prio", + ioprio(ts->clat_prio[ddir][i].ioprio)); + + tmp_object = add_ddir_lat_json(ts, + ts->clat_percentiles | ts->lat_percentiles, + &ts->clat_prio[ddir][i].clat_stat, + ts->clat_prio[ddir][i].io_u_plat); + json_object_add_value_object(obj, obj_name, tmp_object); + json_array_add_value_object(array, obj); } } From 90e678ba69ab9bbbfb2136f2d84a7224b72a61cb Mon Sep 17 00:00:00 2001 From: Christian Loehle Date: Tue, 7 Feb 2023 16:06:16 +0100 Subject: [PATCH 0438/1097] fio: steadystate: allow for custom check interval Allow for a different steady state check interval than 1s with a new --ss_interval parameter. Steady state is reached when the steady state condition (like slope) is true when comparing the last windows (set with --ss_dur). The actual values for this comparison is currently calculated for a 1s interval during the window. This is especially problematic for slow random devices, where the values do not converge for such a fine granularity. Letting the user set this solves this problem, although requires them figuring out an appropriate value themselves. --ss=iops:5% --ss_dur=120s should reproduce this for many (slower) devices. Then adding like --ss_interval=20s may let it converge. Signed-off-by: Christian Loehle --- HOWTO.rst | 14 +++++++--- STEADYSTATE-TODO | 2 -- cconv.c | 2 ++ fio.1 | 6 +++++ helper_thread.c | 2 +- init.c | 19 ++++++++++++++ options.c | 14 ++++++++++ stat.c | 7 ++--- steadystate.c | 58 +++++++++++++++++++++++++----------------- steadystate.h | 3 +-- t/steadystate_tests.py | 1 + thread_options.h | 2 ++ 12 files changed, 96 insertions(+), 34 deletions(-) diff --git a/HOWTO.rst b/HOWTO.rst index bbd9496eef..bc9693888c 100644 --- a/HOWTO.rst +++ b/HOWTO.rst @@ -3822,9 +3822,9 @@ Steady state .. option:: steadystate_duration=time, ss_dur=time A rolling window of this duration will be used to judge whether steady state - has been reached. Data will be collected once per second. The default is 0 - which disables steady state detection. When the unit is omitted, the - value is interpreted in seconds. + has been reached. Data will be collected every ss_check_interval. + The default is 0 which disables steady state detection. When the unit is omitted, + the value is interpreted in seconds. .. option:: steadystate_ramp_time=time, ss_ramp=time @@ -3832,6 +3832,14 @@ Steady state collection for checking the steady state job termination criterion. The default is 0. When the unit is omitted, the value is interpreted in seconds. +.. option:: steadystate_check_interval=time, ss_interval=time + + The values during the rolling window will be collected with a period + of this value. If ss_interval is 30s and ss_dur is 300s, 10 measurements will + be taken. Default is 1s but that might not converge, especially for + slower cards, so set this accordingly. When the unit is omitted, + the value is interpreted in seconds. + Measurements and reporting ~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/STEADYSTATE-TODO b/STEADYSTATE-TODO index e4b146e93c..8c10c7f51e 100644 --- a/STEADYSTATE-TODO +++ b/STEADYSTATE-TODO @@ -1,7 +1,5 @@ Known issues/TODO (for steady-state) -- Allow user to specify the frequency of measurements - - Better documentation for output - Report read, write, trim IOPS/BW separately diff --git a/cconv.c b/cconv.c index 05ac75e329..1ae38b1be6 100644 --- a/cconv.c +++ b/cconv.c @@ -252,6 +252,7 @@ int convert_thread_options_to_cpu(struct thread_options *o, o->ss_ramp_time = le64_to_cpu(top->ss_ramp_time); o->ss_state = le32_to_cpu(top->ss_state); o->ss_limit.u.f = fio_uint64_to_double(le64_to_cpu(top->ss_limit.u.i)); + o->ss_check_interval = le64_to_cpu(top->ss_check_interval); o->zone_range = le64_to_cpu(top->zone_range); o->zone_size = le64_to_cpu(top->zone_size); o->zone_capacity = le64_to_cpu(top->zone_capacity); @@ -614,6 +615,7 @@ void convert_thread_options_to_net(struct thread_options_pack *top, top->ss_ramp_time = __cpu_to_le64(top->ss_ramp_time); top->ss_state = cpu_to_le32(top->ss_state); top->ss_limit.u.i = __cpu_to_le64(fio_double_to_uint64(o->ss_limit.u.f)); + top->ss_check_interval = __cpu_to_le64(top->ss_check_interval); top->zone_range = __cpu_to_le64(o->zone_range); top->zone_size = __cpu_to_le64(o->zone_size); top->zone_capacity = __cpu_to_le64(o->zone_capacity); diff --git a/fio.1 b/fio.1 index a238331c22..af588077bb 100644 --- a/fio.1 +++ b/fio.1 @@ -3540,6 +3540,12 @@ value is interpreted in seconds. Allow the job to run for the specified duration before beginning data collection for checking the steady state job termination criterion. The default is 0. When the unit is omitted, the value is interpreted in seconds. +.TP +.BI steadystate_check_interval \fR=\fPtime "\fR,\fP ss_interval" \fR=\fPtime +The values suring the rolling window will be collected with a period of this +value. Default is 1s but that might not converge, especially for slower cards, +so set this accordingly. When the unit is omitted, the value is interpreted +in seconds. .SS "Measurements and reporting" .TP .BI per_job_logs \fR=\fPbool diff --git a/helper_thread.c b/helper_thread.c index b9b83db305..77016638f5 100644 --- a/helper_thread.c +++ b/helper_thread.c @@ -281,7 +281,7 @@ static void *helper_thread_main(void *data) }, { .name = "steadystate", - .interval_ms = steadystate_enabled ? STEADYSTATE_MSEC : + .interval_ms = steadystate_enabled ? ss_check_interval : 0, .func = steadystate_check, } diff --git a/init.c b/init.c index 442dab4273..a70f749ac3 100644 --- a/init.c +++ b/init.c @@ -981,6 +981,25 @@ static int fixup_options(struct thread_data *td) } } + for_each_td(td2) { + if (td->o.ss_check_interval != td2->o.ss_check_interval) { + log_err("fio: conflicting ss_check_interval: %llu and %llu, must be globally equal\n", + td->o.ss_check_interval, td2->o.ss_check_interval); + ret |= 1; + } + } end_for_each(); + if (td->o.ss_dur && td->o.ss_check_interval / 1000L < 1000) { + log_err("fio: ss_check_interval must be at least 1s\n"); + ret |= 1; + + } + if (td->o.ss_dur && (td->o.ss_dur % td->o.ss_check_interval != 0 || td->o.ss_dur <= td->o.ss_check_interval)) { + log_err("fio: ss_duration %lluus must be multiple of ss_check_interval %lluus\n", + td->o.ss_dur, td->o.ss_check_interval); + ret |= 1; + } + + return ret; } diff --git a/options.c b/options.c index 91049af547..18857795e3 100644 --- a/options.c +++ b/options.c @@ -5228,6 +5228,20 @@ struct fio_option fio_options[FIO_MAX_OPTS] = { .category = FIO_OPT_C_GENERAL, .group = FIO_OPT_G_RUNTIME, }, + { + .name = "steadystate_check_interval", + .lname = "Steady state check interval", + .alias = "ss_interval", + .parent = "steadystate", + .type = FIO_OPT_STR_VAL_TIME, + .off1 = offsetof(struct thread_options, ss_check_interval), + .help = "Polling interval for the steady state check (too low means steadystate will not converge)", + .def = "1", + .is_seconds = 1, + .is_time = 1, + .category = FIO_OPT_C_GENERAL, + .group = FIO_OPT_G_RUNTIME, + }, { .name = NULL, }, diff --git a/stat.c b/stat.c index 56be330b56..d779a90f32 100644 --- a/stat.c +++ b/stat.c @@ -1874,6 +1874,7 @@ static struct json_object *show_thread_status_json(struct thread_stat *ts, struct json_array *iops, *bw; int j, k, l; char ss_buf[64]; + int intervals = ts->ss_dur / (ss_check_interval / 1000L); snprintf(ss_buf, sizeof(ss_buf), "%s%s:%f%s", ts->ss_state & FIO_SS_IOPS ? "iops" : "bw", @@ -1907,9 +1908,9 @@ static struct json_object *show_thread_status_json(struct thread_stat *ts, if ((ts->ss_state & FIO_SS_ATTAINED) || !(ts->ss_state & FIO_SS_BUFFER_FULL)) j = ts->ss_head; else - j = ts->ss_head == 0 ? ts->ss_dur - 1 : ts->ss_head - 1; - for (l = 0; l < ts->ss_dur; l++) { - k = (j + l) % ts->ss_dur; + j = ts->ss_head == 0 ? intervals - 1 : ts->ss_head - 1; + for (l = 0; l < intervals; l++) { + k = (j + l) % intervals; json_array_add_value_int(bw, ts->ss_bw_data[k]); json_array_add_value_int(iops, ts->ss_iops_data[k]); } diff --git a/steadystate.c b/steadystate.c index 14cdf0ed1a..513d6869a4 100644 --- a/steadystate.c +++ b/steadystate.c @@ -4,6 +4,7 @@ #include "steadystate.h" bool steadystate_enabled = false; +unsigned int ss_check_interval = 1000; void steadystate_free(struct thread_data *td) { @@ -15,8 +16,10 @@ void steadystate_free(struct thread_data *td) static void steadystate_alloc(struct thread_data *td) { - td->ss.bw_data = calloc(td->ss.dur, sizeof(uint64_t)); - td->ss.iops_data = calloc(td->ss.dur, sizeof(uint64_t)); + int intervals = td->ss.dur / (ss_check_interval / 1000L); + + td->ss.bw_data = calloc(intervals, sizeof(uint64_t)); + td->ss.iops_data = calloc(intervals, sizeof(uint64_t)); td->ss.state |= FIO_SS_DATA; } @@ -64,6 +67,7 @@ static bool steadystate_slope(uint64_t iops, uint64_t bw, double result; struct steadystate_data *ss = &td->ss; uint64_t new_val; + int intervals = ss->dur / (ss_check_interval / 1000L); ss->bw_data[ss->tail] = bw; ss->iops_data[ss->tail] = iops; @@ -73,10 +77,10 @@ static bool steadystate_slope(uint64_t iops, uint64_t bw, else new_val = bw; - if (ss->state & FIO_SS_BUFFER_FULL || ss->tail - ss->head == ss->dur - 1) { + if (ss->state & FIO_SS_BUFFER_FULL || ss->tail - ss->head == intervals - 1) { if (!(ss->state & FIO_SS_BUFFER_FULL)) { /* first time through */ - for(i = 0, ss->sum_y = 0; i < ss->dur; i++) { + for (i = 0, ss->sum_y = 0; i < intervals; i++) { if (ss->state & FIO_SS_IOPS) ss->sum_y += ss->iops_data[i]; else @@ -123,9 +127,9 @@ static bool steadystate_slope(uint64_t iops, uint64_t bw, return true; } - ss->tail = (ss->tail + 1) % ss->dur; + ss->tail = (ss->tail + 1) % intervals; if (ss->tail <= ss->head) - ss->head = (ss->head + 1) % ss->dur; + ss->head = (ss->head + 1) % intervals; return false; } @@ -138,18 +142,20 @@ static bool steadystate_deviation(uint64_t iops, uint64_t bw, double mean; struct steadystate_data *ss = &td->ss; + int intervals = ss->dur / (ss_check_interval / 1000L); ss->bw_data[ss->tail] = bw; ss->iops_data[ss->tail] = iops; - if (ss->state & FIO_SS_BUFFER_FULL || ss->tail - ss->head == ss->dur - 1) { + if (ss->state & FIO_SS_BUFFER_FULL || ss->tail - ss->head == intervals - 1) { if (!(ss->state & FIO_SS_BUFFER_FULL)) { /* first time through */ - for(i = 0, ss->sum_y = 0; i < ss->dur; i++) + for (i = 0, ss->sum_y = 0; i < intervals; i++) { if (ss->state & FIO_SS_IOPS) ss->sum_y += ss->iops_data[i]; else ss->sum_y += ss->bw_data[i]; + } ss->state |= FIO_SS_BUFFER_FULL; } else { /* easy to update the sum */ ss->sum_y -= ss->oldest_y; @@ -164,10 +170,10 @@ static bool steadystate_deviation(uint64_t iops, uint64_t bw, else ss->oldest_y = ss->bw_data[ss->head]; - mean = (double) ss->sum_y / ss->dur; + mean = (double) ss->sum_y / intervals; ss->deviation = 0.0; - for (i = 0; i < ss->dur; i++) { + for (i = 0; i < intervals; i++) { if (ss->state & FIO_SS_IOPS) diff = ss->iops_data[i] - mean; else @@ -180,8 +186,9 @@ static bool steadystate_deviation(uint64_t iops, uint64_t bw, else ss->criterion = ss->deviation; - dprint(FD_STEADYSTATE, "sum_y: %llu, mean: %f, max diff: %f, " + dprint(FD_STEADYSTATE, "intervals: %d, sum_y: %llu, mean: %f, max diff: %f, " "objective: %f, limit: %f\n", + intervals, (unsigned long long) ss->sum_y, mean, ss->deviation, ss->criterion, ss->limit); @@ -189,9 +196,9 @@ static bool steadystate_deviation(uint64_t iops, uint64_t bw, return true; } - ss->tail = (ss->tail + 1) % ss->dur; - if (ss->tail <= ss->head) - ss->head = (ss->head + 1) % ss->dur; + ss->tail = (ss->tail + 1) % intervals; + if (ss->tail == ss->head) + ss->head = (ss->head + 1) % intervals; return false; } @@ -228,10 +235,10 @@ int steadystate_check(void) fio_gettime(&now, NULL); if (ss->ramp_time && !(ss->state & FIO_SS_RAMP_OVER)) { /* - * Begin recording data one second after ss->ramp_time + * Begin recording data one check interval after ss->ramp_time * has elapsed */ - if (utime_since(&td->epoch, &now) >= (ss->ramp_time + 1000000L)) + if (utime_since(&td->epoch, &now) >= (ss->ramp_time + ss_check_interval * 1000L)) ss->state |= FIO_SS_RAMP_OVER; } @@ -250,8 +257,10 @@ int steadystate_check(void) memcpy(&ss->prev_time, &now, sizeof(now)); if (ss->state & FIO_SS_RAMP_OVER) { - group_bw += 1000 * (td_bytes - ss->prev_bytes) / rate_time; - group_iops += 1000 * (td_iops - ss->prev_iops) / rate_time; + group_bw += rate_time * (td_bytes - ss->prev_bytes) / + (ss_check_interval * ss_check_interval / 1000L); + group_iops += rate_time * (td_iops - ss->prev_iops) / + (ss_check_interval * ss_check_interval / 1000L); ++group_ramp_time_over; } ss->prev_iops = td_iops; @@ -312,6 +321,7 @@ int td_steadystate_init(struct thread_data *td) ss->dur = o->ss_dur; ss->limit = o->ss_limit.u.f; ss->ramp_time = o->ss_ramp_time; + ss_check_interval = o->ss_check_interval / 1000L; ss->state = o->ss_state; if (!td->ss.ramp_time) @@ -345,26 +355,28 @@ uint64_t steadystate_bw_mean(struct thread_stat *ts) { int i; uint64_t sum; - + int intervals = ts->ss_dur / (ss_check_interval / 1000L); + if (!ts->ss_dur) return 0; - for (i = 0, sum = 0; i < ts->ss_dur; i++) + for (i = 0, sum = 0; i < intervals; i++) sum += ts->ss_bw_data[i]; - return sum / ts->ss_dur; + return sum / intervals; } uint64_t steadystate_iops_mean(struct thread_stat *ts) { int i; uint64_t sum; + int intervals = ts->ss_dur / (ss_check_interval / 1000L); if (!ts->ss_dur) return 0; - for (i = 0, sum = 0; i < ts->ss_dur; i++) + for (i = 0, sum = 0; i < intervals; i++) sum += ts->ss_iops_data[i]; - return sum / ts->ss_dur; + return sum / intervals; } diff --git a/steadystate.h b/steadystate.h index bbb86fbb30..f1ef2b20ba 100644 --- a/steadystate.h +++ b/steadystate.h @@ -11,6 +11,7 @@ extern uint64_t steadystate_bw_mean(struct thread_stat *); extern uint64_t steadystate_iops_mean(struct thread_stat *); extern bool steadystate_enabled; +extern unsigned int ss_check_interval; struct steadystate_data { double limit; @@ -64,6 +65,4 @@ enum { FIO_SS_BW_SLOPE = FIO_SS_BW | FIO_SS_SLOPE, }; -#define STEADYSTATE_MSEC 1000 - #endif diff --git a/t/steadystate_tests.py b/t/steadystate_tests.py index d6ffd177e0..d0fa73b28d 100755 --- a/t/steadystate_tests.py +++ b/t/steadystate_tests.py @@ -115,6 +115,7 @@ def check(data, iops, slope, pct, limit, dur, criterion): {'s': False, 'timeout': 20, 'numjobs': 2}, {'s': True, 'timeout': 100, 'numjobs': 3, 'ss_dur': 10, 'ss_ramp': 5, 'iops': False, 'slope': True, 'ss_limit': 0.1, 'pct': True}, {'s': True, 'timeout': 10, 'numjobs': 3, 'ss_dur': 10, 'ss_ramp': 500, 'iops': False, 'slope': True, 'ss_limit': 0.1, 'pct': True}, + {'s': True, 'timeout': 10, 'numjobs': 3, 'ss_dur': 10, 'ss_ramp': 500, 'iops': False, 'slope': True, 'ss_limit': 0.1, 'pct': True, 'ss_interval': 5}, ] jobnum = 0 diff --git a/thread_options.h b/thread_options.h index 2520357cb0..6670cbbfac 100644 --- a/thread_options.h +++ b/thread_options.h @@ -211,6 +211,7 @@ struct thread_options { fio_fp64_t ss_limit; unsigned long long ss_dur; unsigned long long ss_ramp_time; + unsigned long long ss_check_interval; unsigned int overwrite; unsigned int bw_avg_time; unsigned int iops_avg_time; @@ -533,6 +534,7 @@ struct thread_options_pack { uint64_t ss_ramp_time; uint32_t ss_state; fio_fp64_t ss_limit; + uint64_t ss_check_interval; uint32_t overwrite; uint32_t bw_avg_time; uint32_t iops_avg_time; From 93fdba013d15599fa85d7ea94d7c048f2f3d8f5c Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Mon, 20 Mar 2023 11:45:31 -0400 Subject: [PATCH 0439/1097] steadystate: fix slope calculation for variable check intervals There were several more spots where the calculations dependend on the assumption that measurements were taken once every second. Fix those. Fixes: 95a2f70c20c28f417f605974ab3b90c032c10024 ("fio: steadystate: allow for custom check interval") Signed-off-by: Vincent Fu --- steadystate.c | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/steadystate.c b/steadystate.c index 513d6869a4..3e3683f38a 100644 --- a/steadystate.c +++ b/steadystate.c @@ -85,7 +85,7 @@ static bool steadystate_slope(uint64_t iops, uint64_t bw, ss->sum_y += ss->iops_data[i]; else ss->sum_y += ss->bw_data[i]; - j = (ss->head + i) % ss->dur; + j = (ss->head + i) % intervals; if (ss->state & FIO_SS_IOPS) ss->sum_xy += i * ss->iops_data[j]; else @@ -95,7 +95,7 @@ static bool steadystate_slope(uint64_t iops, uint64_t bw, } else { /* easy to update the sums */ ss->sum_y -= ss->oldest_y; ss->sum_y += new_val; - ss->sum_xy = ss->sum_xy - ss->sum_y + ss->dur * new_val; + ss->sum_xy = ss->sum_xy - ss->sum_y + intervals * new_val; } if (ss->state & FIO_SS_IOPS) @@ -109,10 +109,10 @@ static bool steadystate_slope(uint64_t iops, uint64_t bw, * equally spaced when they are often off by a few milliseconds. * This assumption greatly simplifies the calculations. */ - ss->slope = (ss->sum_xy - (double) ss->sum_x * ss->sum_y / ss->dur) / - (ss->sum_x_sq - (double) ss->sum_x * ss->sum_x / ss->dur); + ss->slope = (ss->sum_xy - (double) ss->sum_x * ss->sum_y / intervals) / + (ss->sum_x_sq - (double) ss->sum_x * ss->sum_x / intervals); if (ss->state & FIO_SS_PCT) - ss->criterion = 100.0 * ss->slope / (ss->sum_y / ss->dur); + ss->criterion = 100.0 * ss->slope / (ss->sum_y / intervals); else ss->criterion = ss->slope; @@ -310,6 +310,7 @@ int td_steadystate_init(struct thread_data *td) { struct steadystate_data *ss = &td->ss; struct thread_options *o = &td->o; + int intervals; memset(ss, 0, sizeof(*ss)); @@ -327,8 +328,9 @@ int td_steadystate_init(struct thread_data *td) if (!td->ss.ramp_time) ss->state |= FIO_SS_RAMP_OVER; - ss->sum_x = o->ss_dur * (o->ss_dur - 1) / 2; - ss->sum_x_sq = (o->ss_dur - 1) * (o->ss_dur) * (2*o->ss_dur - 1) / 6; + intervals = ss->dur / (ss_check_interval / 1000L); + ss->sum_x = intervals * (intervals - 1) / 2; + ss->sum_x_sq = (intervals - 1) * (intervals) * (2*intervals - 1) / 6; } /* make sure that ss options are consistent within reporting group */ From 4ff90aba8e1201a0dc835f814ce6efc46c4b0cd8 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Mon, 20 Mar 2023 11:52:22 -0400 Subject: [PATCH 0440/1097] steadystate: add some TODO items We need a better test script that includes test cases for the new check_interval option. With the new test script we can be more comfortable doing cleanups. Signed-off-by: Vincent Fu --- STEADYSTATE-TODO | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/STEADYSTATE-TODO b/STEADYSTATE-TODO index 8c10c7f51e..2848eb5416 100644 --- a/STEADYSTATE-TODO +++ b/STEADYSTATE-TODO @@ -1,5 +1,15 @@ Known issues/TODO (for steady-state) +- Replace the test script with a better one + - Add test cases for the new check_interval option + - Parse debug=steadystate output to check calculations + +- Instead of calculating `intervals` every time, calculate it once and stash it + somewhere + +- Add the time unit to the ss_dur and check_interval variable names to reduce + possible confusion + - Better documentation for output - Report read, write, trim IOPS/BW separately From 51bbb1a120c96ae7b93d058c7ce418962b202515 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Mon, 20 Mar 2023 13:45:09 -0400 Subject: [PATCH 0441/1097] docs: clean up steadystate options Synchronize HOWTO.rst and fio.1, make a minor edit, and improve the formatting. Signed-off-by: Vincent Fu --- HOWTO.rst | 19 ++++++++++--------- fio.1 | 13 +++++++------ 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/HOWTO.rst b/HOWTO.rst index bc9693888c..5240f9da86 100644 --- a/HOWTO.rst +++ b/HOWTO.rst @@ -3821,10 +3821,11 @@ Steady state .. option:: steadystate_duration=time, ss_dur=time - A rolling window of this duration will be used to judge whether steady state - has been reached. Data will be collected every ss_check_interval. - The default is 0 which disables steady state detection. When the unit is omitted, - the value is interpreted in seconds. + A rolling window of this duration will be used to judge whether steady + state has been reached. Data will be collected every + :option:`ss_interval`. The default is 0 which disables steady state + detection. When the unit is omitted, the value is interpreted in + seconds. .. option:: steadystate_ramp_time=time, ss_ramp=time @@ -3834,11 +3835,11 @@ Steady state .. option:: steadystate_check_interval=time, ss_interval=time - The values during the rolling window will be collected with a period - of this value. If ss_interval is 30s and ss_dur is 300s, 10 measurements will - be taken. Default is 1s but that might not converge, especially for - slower cards, so set this accordingly. When the unit is omitted, - the value is interpreted in seconds. + The values during the rolling window will be collected with a period of + this value. If :option:`ss_interval` is 30s and :option:`ss_dur` is + 300s, 10 measurements will be taken. Default is 1s but that might not + converge, especially for slower devices, so set this accordingly. When + the unit is omitted, the value is interpreted in seconds. Measurements and reporting diff --git a/fio.1 b/fio.1 index af588077bb..e2db3a3fc0 100644 --- a/fio.1 +++ b/fio.1 @@ -3532,9 +3532,9 @@ slope. Stop the job if the slope falls below the specified limit. .TP .BI steadystate_duration \fR=\fPtime "\fR,\fP ss_dur" \fR=\fPtime A rolling window of this duration will be used to judge whether steady state -has been reached. Data will be collected once per second. The default is 0 -which disables steady state detection. When the unit is omitted, the -value is interpreted in seconds. +has been reached. Data will be collected every \fBss_interval\fR. The default +is 0 which disables steady state detection. When the unit is omitted, the value +is interpreted in seconds. .TP .BI steadystate_ramp_time \fR=\fPtime "\fR,\fP ss_ramp" \fR=\fPtime Allow the job to run for the specified duration before beginning data @@ -3543,9 +3543,10 @@ default is 0. When the unit is omitted, the value is interpreted in seconds. .TP .BI steadystate_check_interval \fR=\fPtime "\fR,\fP ss_interval" \fR=\fPtime The values suring the rolling window will be collected with a period of this -value. Default is 1s but that might not converge, especially for slower cards, -so set this accordingly. When the unit is omitted, the value is interpreted -in seconds. +value. If \fBss_interval\fR is 30s and \fBss_dur\fR is 300s, 10 measurements +will be taken. Default is 1s but that might not converge, especially for slower +devices, so set this accordingly. When the unit is omitted, the value is +interpreted in seconds. .SS "Measurements and reporting" .TP .BI per_job_logs \fR=\fPbool From 671aa9f5c98c183de12d18c31525b533db0c186f Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Tue, 21 Mar 2023 08:38:32 -0600 Subject: [PATCH 0442/1097] engines/io_uring: use correct type for fio_nvme_get_info() powerpc64 compiles complain about casting unsigned long long to __u64, just use the right type to begin with. Fixes: b3d5e3fd80e3 ("nvme: add nvme opcodes, structures and helper functions") Signed-off-by: Jens Axboe --- engines/io_uring.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/engines/io_uring.c b/engines/io_uring.c index 5393758aa6..54fdf7f3af 100644 --- a/engines/io_uring.c +++ b/engines/io_uring.c @@ -1148,7 +1148,7 @@ static int fio_ioring_cmd_open_file(struct thread_data *td, struct fio_file *f) if (o->cmd_type == FIO_URING_CMD_NVME) { struct nvme_data *data = NULL; unsigned int nsid, lba_size = 0; - unsigned long long nlba = 0; + __u64 nlba = 0; int ret; /* Store the namespace-id and lba size. */ @@ -1214,7 +1214,7 @@ static int fio_ioring_cmd_get_file_size(struct thread_data *td, if (o->cmd_type == FIO_URING_CMD_NVME) { struct nvme_data *data = NULL; unsigned int nsid, lba_size = 0; - unsigned long long nlba = 0; + __u64 nlba = 0; int ret; ret = fio_nvme_get_info(f, &nsid, &lba_size, &nlba); From 2fa0ab21c5726d8242a820ff688de019cc4d2fe2 Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Tue, 21 Mar 2023 08:40:14 -0600 Subject: [PATCH 0443/1097] engines/nvme: cast __u64 to unsigned long long for printing Signed-off-by: Jens Axboe --- engines/nvme.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engines/nvme.c b/engines/nvme.c index da18eba987..3f6b64a86f 100644 --- a/engines/nvme.c +++ b/engines/nvme.c @@ -241,7 +241,7 @@ int fio_nvme_report_zones(struct thread_data *td, struct fio_file *f, break; default: log_err("%s: invalid type for zone at offset %llu.\n", - f->file_name, desc->zslba); + f->file_name, (unsigned long long) desc->zslba); ret = -EIO; goto out; } From 3b3c987763a516cd0b0d9dc9d25d88ca43aeb058 Mon Sep 17 00:00:00 2001 From: Ankit Kumar Date: Tue, 14 Mar 2023 14:56:26 +0530 Subject: [PATCH 0444/1097] fdp: drop expensive modulo operation Remove the usage of expensive modulo operation as the same can be easily achieved with addition and wrapping around avilable reclaim unit handles. Signed-off-by: Ankit Kumar --- fdp.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/fdp.c b/fdp.c index 84e04fce61..d92dbc67cc 100644 --- a/fdp.c +++ b/fdp.c @@ -119,7 +119,10 @@ void fdp_fill_dspec_data(struct thread_data *td, struct io_u *io_u) return; } - dspec = ruhs->plis[ruhs->pli_loc++ % ruhs->nr_ruhs]; + if (ruhs->pli_loc >= ruhs->nr_ruhs) + ruhs->pli_loc = 0; + + dspec = ruhs->plis[ruhs->pli_loc++]; io_u->dtype = 2; io_u->dspec = dspec; } From 16be6037a37a286778bff6e6d27132b776dc8e79 Mon Sep 17 00:00:00 2001 From: Ankit Kumar Date: Tue, 7 Mar 2023 19:51:06 +0530 Subject: [PATCH 0445/1097] io_uring_cmd: suppport for trim operation Add support for trim operation to the io_uring_cmd ioengine. Print ZBD zone reset stats for trim operation. Signed-off-by: Ankit Kumar --- engines/io_uring.c | 31 ++++++++++++++++++++++++++++++- engines/nvme.c | 34 ++++++++++++++++++++++++++++++++++ engines/nvme.h | 12 ++++++++++++ stat.c | 2 +- 4 files changed, 77 insertions(+), 2 deletions(-) diff --git a/engines/io_uring.c b/engines/io_uring.c index 54fdf7f3af..f10a45933f 100644 --- a/engines/io_uring.c +++ b/engines/io_uring.c @@ -24,6 +24,7 @@ #include "../lib/types.h" #include "../os/linux/io_uring.h" #include "cmdprio.h" +#include "zbd.h" #include "nvme.h" #include @@ -409,6 +410,9 @@ static int fio_ioring_cmd_prep(struct thread_data *td, struct io_u *io_u) if (o->cmd_type != FIO_URING_CMD_NVME) return -EINVAL; + if (io_u->ddir == DDIR_TRIM) + return 0; + sqe = &ld->sqes[(io_u->index) << 1]; if (o->registerfiles) { @@ -556,6 +560,27 @@ static inline void fio_ioring_cmdprio_prep(struct thread_data *td, ld->sqes[io_u->index].ioprio = io_u->ioprio; } +static int fio_ioring_cmd_io_u_trim(const struct thread_data *td, + struct io_u *io_u) +{ + struct fio_file *f = io_u->file; + int ret; + + if (td->o.zone_mode == ZONE_MODE_ZBD) { + ret = zbd_do_io_u_trim(td, io_u); + if (ret == io_u_completed) + return io_u->xfer_buflen; + if (ret) + goto err; + } + + return fio_nvme_trim(td, f, io_u->offset, io_u->xfer_buflen); + +err: + io_u->error = ret; + return 0; +} + static enum fio_q_status fio_ioring_queue(struct thread_data *td, struct io_u *io_u) { @@ -572,7 +597,11 @@ static enum fio_q_status fio_ioring_queue(struct thread_data *td, if (ld->queued) return FIO_Q_BUSY; - do_io_u_trim(td, io_u); + if (!strcmp(td->io_ops->name, "io_uring_cmd")) + fio_ioring_cmd_io_u_trim(td, io_u); + else + do_io_u_trim(td, io_u); + io_u_mark_submit(td, 1); io_u_mark_complete(td, 1); return FIO_Q_COMPLETED; diff --git a/engines/nvme.c b/engines/nvme.c index 3f6b64a86f..ac9086876c 100644 --- a/engines/nvme.c +++ b/engines/nvme.c @@ -43,6 +43,40 @@ int fio_nvme_uring_cmd_prep(struct nvme_uring_cmd *cmd, struct io_u *io_u, return 0; } +static int nvme_trim(int fd, __u32 nsid, __u32 nr_range, __u32 data_len, + void *data) +{ + struct nvme_passthru_cmd cmd = { + .opcode = nvme_cmd_dsm, + .nsid = nsid, + .addr = (__u64)(uintptr_t)data, + .data_len = data_len, + .cdw10 = nr_range - 1, + .cdw11 = NVME_ATTRIBUTE_DEALLOCATE, + }; + + return ioctl(fd, NVME_IOCTL_IO_CMD, &cmd); +} + +int fio_nvme_trim(const struct thread_data *td, struct fio_file *f, + unsigned long long offset, unsigned long long len) +{ + struct nvme_data *data = FILE_ENG_DATA(f); + struct nvme_dsm_range dsm; + int ret; + + dsm.nlb = (len >> data->lba_shift); + dsm.slba = (offset >> data->lba_shift); + + ret = nvme_trim(f->fd, data->nsid, 1, sizeof(struct nvme_dsm_range), + &dsm); + if (ret) + log_err("%s: nvme_trim failed for offset %llu and len %llu, err=%d\n", + f->file_name, offset, len, ret); + + return ret; +} + static int nvme_identify(int fd, __u32 nsid, enum nvme_identify_cns cns, enum nvme_csi csi, void *data) { diff --git a/engines/nvme.h b/engines/nvme.h index 1c0e526b95..408594d550 100644 --- a/engines/nvme.h +++ b/engines/nvme.h @@ -48,6 +48,8 @@ struct nvme_uring_cmd { #define NVME_ZNS_ZSA_RESET 0x4 #define NVME_ZONE_TYPE_SEQWRITE_REQ 0x2 +#define NVME_ATTRIBUTE_DEALLOCATE (1 << 2) + enum nvme_identify_cns { NVME_IDENTIFY_CNS_NS = 0x00, NVME_IDENTIFY_CNS_CSI_NS = 0x05, @@ -67,6 +69,7 @@ enum nvme_admin_opcode { enum nvme_io_opcode { nvme_cmd_write = 0x01, nvme_cmd_read = 0x02, + nvme_cmd_dsm = 0x09, nvme_cmd_io_mgmt_recv = 0x12, nvme_zns_cmd_mgmt_send = 0x79, nvme_zns_cmd_mgmt_recv = 0x7a, @@ -207,6 +210,15 @@ struct nvme_fdp_ruh_status { struct nvme_fdp_ruh_status_desc ruhss[]; }; +struct nvme_dsm_range { + __le32 cattr; + __le32 nlb; + __le64 slba; +}; + +int fio_nvme_trim(const struct thread_data *td, struct fio_file *f, + unsigned long long offset, unsigned long long len); + int fio_nvme_iomgmt_ruhs(struct thread_data *td, struct fio_file *f, struct nvme_fdp_ruh_status *ruhs, __u32 bytes); diff --git a/stat.c b/stat.c index d779a90f32..015b8e280f 100644 --- a/stat.c +++ b/stat.c @@ -555,7 +555,7 @@ static void show_ddir_status(struct group_run_stats *rs, struct thread_stat *ts, iops = (1000 * (uint64_t)ts->total_io_u[ddir]) / runt; iops_p = num2str(iops, ts->sig_figs, 1, 0, N2S_NONE); - if (ddir == DDIR_WRITE) + if (ddir == DDIR_WRITE || ddir == DDIR_TRIM) post_st = zbd_write_status(ts); else if (ddir == DDIR_READ && ts->cachehit && ts->cachemiss) { uint64_t total; From cfc4ad2ec90e98cc92f8ec911f6ae04e11cf2ba4 Mon Sep 17 00:00:00 2001 From: "suho.son" Date: Fri, 31 Mar 2023 16:10:09 +0900 Subject: [PATCH 0446/1097] thinktime: Fix missing re-init thinktime when using ramptime Prevent I/O bursts after ramptime due to thinktime. Each thread generates a certain amount of I/O requests, configured by thinktime_blocks. When ramptime ends, thinktime_blocks can't control I/O. Because thinktime_blocks are not reinitialized after ramptime. I fixed it by reinitializing last_thinktime and last_thinktime_blocks when ramptime ended. Signed-off by: Suho Son --- fio.h | 2 +- libfio.c | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/fio.h b/fio.h index 325355174b..7e90175af8 100644 --- a/fio.h +++ b/fio.h @@ -376,7 +376,7 @@ struct thread_data { uint64_t *thinktime_blocks_counter; struct timespec last_thinktime; - uint64_t last_thinktime_blocks; + int64_t last_thinktime_blocks; /* * State for random io, a bitmap of blocks done vs not done diff --git a/libfio.c b/libfio.c index a52014ce6b..ddd49cd76d 100644 --- a/libfio.c +++ b/libfio.c @@ -131,10 +131,14 @@ void clear_io_state(struct thread_data *td, int all) void reset_all_stats(struct thread_data *td) { + unsigned long long b; int i; reset_io_counters(td, 1); + b = ddir_rw_sum(td->thinktime_blocks_counter); + td->last_thinktime_blocks -= b; + for (i = 0; i < DDIR_RWDIR_CNT; i++) { td->io_bytes[i] = 0; td->io_blocks[i] = 0; @@ -149,6 +153,8 @@ void reset_all_stats(struct thread_data *td) memcpy(&td->bw_sample_time, &td->epoch, sizeof(td->epoch)); memcpy(&td->ss.prev_time, &td->epoch, sizeof(td->epoch)); + td->last_thinktime = td->epoch; + lat_target_reset(td); clear_rusage_stat(td); helper_reset(); From eb314e7072a056b13c28bcb785f51b35c67fd1e6 Mon Sep 17 00:00:00 2001 From: Yuanchu Xie Date: Fri, 31 Mar 2023 11:37:02 -0700 Subject: [PATCH 0447/1097] fio: add support for POSIX_FADV_NOREUSE As of Linux kernel commit 17e810229cb3 ("mm: support POSIX_FADV_NOREUSE"), POSIX_FADV_NOREUSE hints at the LRU algorithm to ignore accesses to mapped files with this flag. Previously, it was a no-op. Add it in fio as an fadvise_hint option to test the new behavior. Signed-off-by: Yuanchu Xie Link: https://lore.kernel.org/r/20230331183703.3145788-1-yuanchu@google.com Signed-off-by: Jens Axboe --- fio.h | 1 + ioengines.c | 2 ++ options.c | 4 ++++ 3 files changed, 7 insertions(+) diff --git a/fio.h b/fio.h index 325355174b..f2acd4302b 100644 --- a/fio.h +++ b/fio.h @@ -163,6 +163,7 @@ enum { F_ADV_TYPE, F_ADV_RANDOM, F_ADV_SEQUENTIAL, + F_ADV_NOREUSE, }; /* diff --git a/ioengines.c b/ioengines.c index e2316ee4e3..56759c5325 100644 --- a/ioengines.c +++ b/ioengines.c @@ -565,6 +565,8 @@ int td_io_open_file(struct thread_data *td, struct fio_file *f) flags = POSIX_FADV_RANDOM; else if (td->o.fadvise_hint == F_ADV_SEQUENTIAL) flags = POSIX_FADV_SEQUENTIAL; + else if (td->o.fadvise_hint == F_ADV_NOREUSE) + flags = POSIX_FADV_NOREUSE; else { log_err("fio: unknown fadvise type %d\n", td->o.fadvise_hint); diff --git a/options.c b/options.c index 18857795e3..fe580ebb9d 100644 --- a/options.c +++ b/options.c @@ -2740,6 +2740,10 @@ struct fio_option fio_options[FIO_MAX_OPTS] = { .oval = F_ADV_SEQUENTIAL, .help = "Advise using FADV_SEQUENTIAL", }, + { .ival = "noreuse", + .oval = F_ADV_NOREUSE, + .help = "Advise using FADV_NOREUSE", + }, }, .help = "Use fadvise() to advise the kernel on IO pattern", .def = "1", From 109aad505302d2924396774f267eca7451f8fb14 Mon Sep 17 00:00:00 2001 From: Yuanchu Xie Date: Fri, 31 Mar 2023 11:37:03 -0700 Subject: [PATCH 0448/1097] docs: add noreuse fadvise_hint option noreuse was added as an fadvise_hint option to apply POSIX_FADV_NOREUSE, so we add it to the docs as well. Signed-off-by: Yuanchu Xie Link: https://lore.kernel.org/r/20230331183703.3145788-1-yuanchu@google.com Signed-off-by: Jens Axboe --- HOWTO.rst | 5 +++++ fio.1 | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/HOWTO.rst b/HOWTO.rst index 5240f9da86..cb0f9834f8 100644 --- a/HOWTO.rst +++ b/HOWTO.rst @@ -1308,6 +1308,11 @@ I/O type **random** Advise using **FADV_RANDOM**. + **noreuse** + Advise using **FADV_NOREUSE**. This may be a no-op on older Linux + kernels. Since Linux 6.3, it provides a hint to the LRU algorithm. + See the :manpage:`posix_fadvise(2)` man page. + .. option:: write_hint=str Use :manpage:`fcntl(2)` to advise the kernel what life time to expect diff --git a/fio.1 b/fio.1 index e2db3a3fc0..311b16d828 100644 --- a/fio.1 +++ b/fio.1 @@ -1098,6 +1098,11 @@ Advise using FADV_SEQUENTIAL. .TP .B random Advise using FADV_RANDOM. +.TP +.B noreuse +Advise using FADV_NOREUSE. This may be a no-op on older Linux +kernels. Since Linux 6.3, it provides a hint to the LRU algorithm. +See the \fBposix_fadvise\fR\|(2) man page. .RE .RE .TP From 638689b15af35bd746f9114a3e8895e7a983ed83 Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Fri, 31 Mar 2023 12:52:01 -0600 Subject: [PATCH 0449/1097] Only expose fadvise_hint=noreuse if supported At least OSX doesn't have this option or define, just make it available if it's there on the platform we're building on. Fixes: eb314e7072a0 ("fio: add support for POSIX_FADV_NOREUSE") Signed-off-by: Jens Axboe --- ioengines.c | 2 ++ options.c | 3 +++ 2 files changed, 5 insertions(+) diff --git a/ioengines.c b/ioengines.c index 56759c5325..742f97dd32 100644 --- a/ioengines.c +++ b/ioengines.c @@ -565,8 +565,10 @@ int td_io_open_file(struct thread_data *td, struct fio_file *f) flags = POSIX_FADV_RANDOM; else if (td->o.fadvise_hint == F_ADV_SEQUENTIAL) flags = POSIX_FADV_SEQUENTIAL; +#ifdef POSIX_FADV_NOREUSE else if (td->o.fadvise_hint == F_ADV_NOREUSE) flags = POSIX_FADV_NOREUSE; +#endif else { log_err("fio: unknown fadvise type %d\n", td->o.fadvise_hint); diff --git a/options.c b/options.c index fe580ebb9d..440bff37cb 100644 --- a/options.c +++ b/options.c @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -2740,10 +2741,12 @@ struct fio_option fio_options[FIO_MAX_OPTS] = { .oval = F_ADV_SEQUENTIAL, .help = "Advise using FADV_SEQUENTIAL", }, +#ifdef POSIX_FADV_NOREUSE { .ival = "noreuse", .oval = F_ADV_NOREUSE, .help = "Advise using FADV_NOREUSE", }, +#endif }, .help = "Use fadvise() to advise the kernel on IO pattern", .def = "1", From af10f514b5e8713f0821c34fadfe22c91d137720 Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Tue, 4 Apr 2023 08:51:31 -0600 Subject: [PATCH 0450/1097] engines/nvme: cache errno value It's pretty pointless to do a bunch of things in between getting a -1/errno value, and then expect errno to still be what you want. Signed-off-by: Jens Axboe --- engines/nvme.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/engines/nvme.c b/engines/nvme.c index ac9086876c..fd2161f363 100644 --- a/engines/nvme.c +++ b/engines/nvme.c @@ -112,9 +112,10 @@ int fio_nvme_get_info(struct fio_file *f, __u32 *nsid, __u32 *lba_sz, namespace_id = ioctl(fd, NVME_IOCTL_ID); if (namespace_id < 0) { + err = -errno; log_err("failed to fetch namespace-id"); close(fd); - return -errno; + return err; } /* @@ -414,6 +415,7 @@ int fio_nvme_iomgmt_ruhs(struct thread_data *td, struct fio_file *f, } else errno = 0; + ret = -errno; close(fd); - return -errno; + return ret; } From db64b7b643609e12a49a27bb84a289f12266552e Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Tue, 4 Apr 2023 09:11:19 -0600 Subject: [PATCH 0451/1097] engines/nfs: fix the most egregious style violations Not sure how this got past review... Signed-off-by: Jens Axboe --- engines/nfs.c | 147 ++++++++++++++++++++++++++++++-------------------- 1 file changed, 89 insertions(+), 58 deletions(-) diff --git a/engines/nfs.c b/engines/nfs.c index 7031769d21..336e670bf8 100644 --- a/engines/nfs.c +++ b/engines/nfs.c @@ -16,10 +16,17 @@ enum nfs_op_type { struct fio_libnfs_options { struct nfs_context *context; char *nfs_url; - unsigned int queue_depth; /* nfs_callback needs this info, but doesn't have fio td structure to pull it from */ + /* nfs_callback needs this info, but doesn't have fio td structure to + * pull it from + */ + unsigned int queue_depth; + /* the following implement a circular queue of outstanding IOs */ - int outstanding_events; /* IOs issued to libnfs, that have not returned yet */ - int prev_requested_event_index; /* event last returned via fio_libnfs_event */ + + /* IOs issued to libnfs, that have not returned yet */ + int outstanding_events; + /* event last returned via fio_libnfs_event */ + int prev_requested_event_index; int next_buffered_event; /* round robin-pointer within events[] */ int buffered_event_count; /* IOs completed by libnfs, waiting for FIO */ int free_event_buffer_index; /* next free buffer */ @@ -33,11 +40,12 @@ struct nfs_data { static struct fio_option options[] = { { - .name = "nfs_url", - .lname = "nfs_url", - .type = FIO_OPT_STR_STORE, - .help = "URL in libnfs format, eg nfs:///path[?arg=val[&arg=val]*]", - .off1 = offsetof(struct fio_libnfs_options, nfs_url), + .name = "nfs_url", + .lname = "nfs_url", + .type = FIO_OPT_STR_STORE, + .help = "URL in libnfs format, eg nfs:///path[?arg=val[&arg=val]*]", + .off1 = offsetof(struct fio_libnfs_options, nfs_url), .category = FIO_OPT_C_ENGINE, .group = __FIO_OPT_G_NFS, }, @@ -50,44 +58,53 @@ static struct io_u *fio_libnfs_event(struct thread_data *td, int event) { struct fio_libnfs_options *o = td->eo; struct io_u *io_u = o->events[o->next_buffered_event]; + assert(o->events[o->next_buffered_event]); o->events[o->next_buffered_event] = NULL; o->next_buffered_event = (o->next_buffered_event + 1) % td->o.iodepth; + /* validate our state machine */ assert(o->buffered_event_count); o->buffered_event_count--; assert(io_u); + /* assert that fio_libnfs_event is being called in sequential fashion */ assert(event == 0 || o->prev_requested_event_index + 1 == event); - if (o->buffered_event_count == 0) { + if (o->buffered_event_count == 0) o->prev_requested_event_index = -1; - } else { + else o->prev_requested_event_index = event; - } return io_u; } -static int nfs_event_loop(struct thread_data *td, bool flush) { +/* + * fio core logic seems to stop calling this event-loop if we ever return with + * 0 events + */ +#define SHOULD_WAIT(td, o, flush) \ + ((o)->outstanding_events == (td)->o.iodepth || \ + (flush && (o)->outstanding_events)) + +static int nfs_event_loop(struct thread_data *td, bool flush) +{ struct fio_libnfs_options *o = td->eo; struct pollfd pfds[1]; /* nfs:0 */ + /* we already have stuff queued for fio, no need to waste cpu on poll() */ if (o->buffered_event_count) return o->buffered_event_count; - /* fio core logic seems to stop calling this event-loop if we ever return with 0 events */ - #define SHOULD_WAIT() (o->outstanding_events == td->o.iodepth || (flush && o->outstanding_events)) do { - int timeout = SHOULD_WAIT() ? -1 : 0; + int timeout = SHOULD_WAIT(td, o, flush) ? -1 : 0; int ret = 0; + pfds[0].fd = nfs_get_fd(o->context); pfds[0].events = nfs_which_events(o->context); ret = poll(&pfds[0], 1, timeout); if (ret < 0) { - if (errno == EINTR || errno == EAGAIN) { + if (errno == EINTR || errno == EAGAIN) continue; - } - log_err("nfs: failed to poll events: %s.\n", - strerror(errno)); + log_err("nfs: failed to poll events: %s\n", strerror(errno)); break; } @@ -96,27 +113,30 @@ static int nfs_event_loop(struct thread_data *td, bool flush) { log_err("nfs: socket is in an unrecoverable error state.\n"); break; } - } while (SHOULD_WAIT()); + } while (SHOULD_WAIT(td, o, flush)); + return o->buffered_event_count; -#undef SHOULD_WAIT } static int fio_libnfs_getevents(struct thread_data *td, unsigned int min, - unsigned int max, const struct timespec *t) + unsigned int max, const struct timespec *t) { return nfs_event_loop(td, false); } static void nfs_callback(int res, struct nfs_context *nfs, void *data, - void *private_data) + void *private_data) { struct io_u *io_u = private_data; struct nfs_data *nfs_data = io_u->file->engine_data; struct fio_libnfs_options *o = nfs_data->options; if (res < 0) { - log_err("Failed NFS operation(code:%d): %s\n", res, nfs_get_error(o->context)); + log_err("Failed NFS operation(code:%d): %s\n", res, + nfs_get_error(o->context)); io_u->error = -res; - /* res is used for read math below, don't wanna pass negative there */ + /* res is used for read math below, don't want to pass negative + * there + */ res = 0; } else if (io_u->ddir == DDIR_READ) { memcpy(io_u->buf, data, res); @@ -133,42 +153,46 @@ static void nfs_callback(int res, struct nfs_context *nfs, void *data, o->buffered_event_count++; } -static int queue_write(struct fio_libnfs_options *o, struct io_u *io_u) { +static int queue_write(struct fio_libnfs_options *o, struct io_u *io_u) +{ struct nfs_data *nfs_data = io_u->engine_data; - return nfs_pwrite_async(o->context, nfs_data->nfsfh, - io_u->offset, io_u->buflen, io_u->buf, nfs_callback, - io_u); + + return nfs_pwrite_async(o->context, nfs_data->nfsfh, io_u->offset, + io_u->buflen, io_u->buf, nfs_callback, io_u); } -static int queue_read(struct fio_libnfs_options *o, struct io_u *io_u) { +static int queue_read(struct fio_libnfs_options *o, struct io_u *io_u) +{ struct nfs_data *nfs_data = io_u->engine_data; - return nfs_pread_async(o->context, nfs_data->nfsfh, io_u->offset, io_u->buflen, nfs_callback, io_u); + + return nfs_pread_async(o->context, nfs_data->nfsfh, io_u->offset, + io_u->buflen, nfs_callback, io_u); } static enum fio_q_status fio_libnfs_queue(struct thread_data *td, - struct io_u *io_u) + struct io_u *io_u) { struct nfs_data *nfs_data = io_u->file->engine_data; struct fio_libnfs_options *o = nfs_data->options; struct nfs_context *nfs = o->context; - int err; enum fio_q_status ret = FIO_Q_QUEUED; + int err; io_u->engine_data = nfs_data; - switch(io_u->ddir) { - case DDIR_WRITE: - err = queue_write(o, io_u); - break; - case DDIR_READ: - err = queue_read(o, io_u); - break; - case DDIR_TRIM: - log_err("nfs: trim is not supported"); - err = -1; - break; - default: - log_err("nfs: unhandled io %d\n", io_u->ddir); - err = -1; + switch (io_u->ddir) { + case DDIR_WRITE: + err = queue_write(o, io_u); + break; + case DDIR_READ: + err = queue_read(o, io_u); + break; + case DDIR_TRIM: + log_err("nfs: trim is not supported"); + err = -1; + break; + default: + log_err("nfs: unhandled io %d\n", io_u->ddir); + err = -1; } if (err) { log_err("nfs: Failed to queue nfs op: %s\n", nfs_get_error(nfs)); @@ -195,7 +219,7 @@ static int do_mount(struct thread_data *td, const char *url) return 0; options->context = nfs_init_context(); - if (options->context == NULL) { + if (!options->context) { log_err("nfs: failed to init nfs context\n"); return -1; } @@ -219,7 +243,9 @@ static int do_mount(struct thread_data *td, const char *url) static int fio_libnfs_setup(struct thread_data *td) { - /* Using threads with libnfs causes fio to hang on exit, lower performance */ + /* Using threads with libnfs causes fio to hang on exit, lower + * performance + */ td->o.use_thread = 0; return 0; } @@ -227,6 +253,7 @@ static int fio_libnfs_setup(struct thread_data *td) static void fio_libnfs_cleanup(struct thread_data *td) { struct fio_libnfs_options *o = td->eo; + nfs_umount(o->context); nfs_destroy_context(o->context); free(o->events); @@ -234,10 +261,10 @@ static void fio_libnfs_cleanup(struct thread_data *td) static int fio_libnfs_open(struct thread_data *td, struct fio_file *f) { - int ret; struct fio_libnfs_options *options = td->eo; struct nfs_data *nfs_data = NULL; int flags = 0; + int ret; if (!options->nfs_url) { log_err("nfs: nfs_url is a required parameter\n"); @@ -246,23 +273,25 @@ static int fio_libnfs_open(struct thread_data *td, struct fio_file *f) ret = do_mount(td, options->nfs_url); - if (ret != 0) { - log_err("nfs: Failed to mount %s with code %d: %s\n", options->nfs_url, ret, nfs_get_error(options->context)); + if (ret) { + log_err("nfs: Failed to mount %s with code %d: %s\n", + options->nfs_url, ret, nfs_get_error(options->context)); return ret; } nfs_data = malloc(sizeof(struct nfs_data)); memset(nfs_data, 0, sizeof(struct nfs_data)); nfs_data->options = options; - if (td->o.td_ddir == TD_DDIR_WRITE) { + if (td->o.td_ddir == TD_DDIR_WRITE) flags |= O_CREAT | O_RDWR; - } else { + else flags |= O_RDWR; - } + ret = nfs_open(options->context, f->file_name, flags, &nfs_data->nfsfh); - if (ret != 0) - log_err("Failed to open %s: %s\n", f->file_name, nfs_get_error(options->context)); + if (ret) + log_err("Failed to open %s: %s\n", f->file_name, + nfs_get_error(options->context)); f->engine_data = nfs_data; return ret; } @@ -272,8 +301,10 @@ static int fio_libnfs_close(struct thread_data *td, struct fio_file *f) struct nfs_data *nfs_data = f->engine_data; struct fio_libnfs_options *o = nfs_data->options; int ret = 0; + if (nfs_data->nfsfh) ret = nfs_close(o->context, nfs_data->nfsfh); + free(nfs_data); f->engine_data = NULL; return ret; @@ -289,7 +320,7 @@ struct ioengine_ops ioengine = { .cleanup = fio_libnfs_cleanup, .open_file = fio_libnfs_open, .close_file = fio_libnfs_close, - .flags = FIO_DISKLESSIO | FIO_NOEXTEND | FIO_NODISKUTIL, + .flags = FIO_DISKLESSIO | FIO_NOEXTEND | FIO_NODISKUTIL, .options = options, .option_struct_size = sizeof(struct fio_libnfs_options), }; From 2d8c2709dc067aabdeab8bc1eea1992d9d802375 Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Tue, 4 Apr 2023 09:49:19 -0600 Subject: [PATCH 0452/1097] io_u: fix bad style Fixes: 4ef1562a0135 ("io_u: Fix bad interaction with --openfiles and non-sequential file selection policy") Signed-off-by: Jens Axboe --- io_u.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/io_u.c b/io_u.c index ca7ee68fde..30265cfb4a 100644 --- a/io_u.c +++ b/io_u.c @@ -1370,8 +1370,8 @@ static struct fio_file *__get_next_file(struct thread_data *td) if (td->o.file_service_type == FIO_FSERVICE_SEQ) goto out; if (td->file_service_left) { - td->file_service_left--; - goto out; + td->file_service_left--; + goto out; } } From a8f6714e91d58f4a2eff4198b9f09c89df75f5cb Mon Sep 17 00:00:00 2001 From: Leah Rumancik Date: Fri, 7 Apr 2023 13:41:03 -0700 Subject: [PATCH 0453/1097] engines/libaio: fix io_getevents min/max events arguments If the io_getevents system call is interrupted, it can succeed while returning less than the min_nr requested events. If this occurs during the final reaping of events, libaio will call io_getevents again wtih the same minimum events argument. Since some of the events have already been reaped, this results in a hang. To fix this, update the arguments for io_getevents based on the previously seen events. Signed-off-by: Leah Rumancik --- engines/libaio.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/engines/libaio.c b/engines/libaio.c index 33b8c12f96..1b82c90b75 100644 --- a/engines/libaio.c +++ b/engines/libaio.c @@ -288,14 +288,16 @@ static int fio_libaio_getevents(struct thread_data *td, unsigned int min, && actual_min == 0 && ((struct aio_ring *)(ld->aio_ctx))->magic == AIO_RING_MAGIC) { - r = user_io_getevents(ld->aio_ctx, max, + r = user_io_getevents(ld->aio_ctx, max - events, ld->aio_events + events); } else { r = io_getevents(ld->aio_ctx, actual_min, - max, ld->aio_events + events, lt); + max - events, ld->aio_events + events, lt); } - if (r > 0) + if (r > 0) { events += r; + actual_min = actual_min > events ? actual_min - events : 0; + } else if ((min && r == 0) || r == -EAGAIN) { fio_libaio_commit(td); if (actual_min) From ae8646a1e5848994c4f4511aa190178958309c92 Mon Sep 17 00:00:00 2001 From: Leah Rumancik Date: Mon, 10 Apr 2023 11:57:15 -0700 Subject: [PATCH 0454/1097] engines/io_uring: update getevents max to reflect previously seen events To ensure we don't return more than the requested number of events, update the max events variable after reaping events before subsequent calls to io_getevents system call. Signed-off-by: Leah Rumancik --- engines/io_uring.c | 1 + 1 file changed, 1 insertion(+) diff --git a/engines/io_uring.c b/engines/io_uring.c index f10a45933f..7f743c2a88 100644 --- a/engines/io_uring.c +++ b/engines/io_uring.c @@ -529,6 +529,7 @@ static int fio_ioring_getevents(struct thread_data *td, unsigned int min, r = fio_ioring_cqring_reap(td, events, max); if (r) { events += r; + max -= r; if (actual_min != 0) actual_min -= r; continue; From e2a4a77e483e2d40a680b57b13bb08042929df1c Mon Sep 17 00:00:00 2001 From: Xiaoguang Wang Date: Thu, 13 Apr 2023 18:47:14 +0800 Subject: [PATCH 0455/1097] t/io_uring: fix max_blocks calculation in nvme passthrough mode nvme_id_ns's nsze has already been counted in logical blocks, so there is no need to divide by bs. Signed-off-by: Xiaoguang Wang Link: https://lore.kernel.org/r/20230413104714.57703-1-xiaoguang.wang@linux.alibaba.com Signed-off-by: Jens Axboe --- t/io_uring.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/t/io_uring.c b/t/io_uring.c index 504f8ce9f1..f9f4b84005 100644 --- a/t/io_uring.c +++ b/t/io_uring.c @@ -704,7 +704,7 @@ static int get_file_size(struct file *f) bs, lbs); return -1; } - f->max_blocks = nlba / bs; + f->max_blocks = nlba; f->max_size = nlba; f->lba_shift = ilog2(lbs); return 0; From 64b2ba1460687332817655ede202166386af938b Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Tue, 21 Feb 2023 22:53:00 +0000 Subject: [PATCH 0456/1097] rand: print out random seeds for debugging In order to debug how we handle random seeds let's print out the random seeds we use in the FD_RANDOM debug output. Signed-off-by: Vincent Fu --- init.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/init.c b/init.c index a70f749ac3..1c8df1a754 100644 --- a/init.c +++ b/init.c @@ -1111,6 +1111,10 @@ void td_fill_rand_seeds(struct thread_data *td) init_rand_seed(&td->buf_state, td->rand_seeds[FIO_RAND_BUF_OFF], use64); frand_copy(&td->buf_state_prev, &td->buf_state); + + dprint(FD_RANDOM, "FIO_RAND_NR_OFFS=%d\n", FIO_RAND_NR_OFFS); + for (int i = 0; i < FIO_RAND_NR_OFFS; i++) + dprint(FD_RANDOM, "rand_seeds[%d]=%" PRIu64 "\n", i, td->rand_seeds[i]); } /* From ab3c4e7868980f91439a1fe888b0f13b27f3cd42 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Fri, 24 Mar 2023 17:47:14 +0000 Subject: [PATCH 0457/1097] init: refactor random seed setting td->rand_seed was modified in three different places. Put all this code in setup_random_seeds() to make it easier to understand and more maintanable. Also put setup_random_seeds() next to the other random-seed-related functions in init.c. init_rand_seed() was called in three different places for fio's main random number generators. Also put these three sets of invocations in the same place. Always initialize all of fio's main set of random states instead of skipping some for sequential workloads. This makes debugging easier. No functional change. Signed-off-by: Vincent Fu --- fio.h | 1 - init.c | 95 ++++++++++++++++++++++++---------------------------------- 2 files changed, 39 insertions(+), 57 deletions(-) diff --git a/fio.h b/fio.h index 6b841e9c26..6fc7fb9c63 100644 --- a/fio.h +++ b/fio.h @@ -638,7 +638,6 @@ extern void fio_options_dup_and_init(struct option *); extern char *fio_option_dup_subs(const char *); extern void fio_options_mem_dupe(struct thread_data *); extern void td_fill_rand_seeds(struct thread_data *); -extern void td_fill_verify_state_seed(struct thread_data *); extern void add_job_opts(const char **, int); extern int ioengine_load(struct thread_data *); extern bool parse_dryrun(void); diff --git a/init.c b/init.c index 1c8df1a754..e020d452f9 100644 --- a/init.c +++ b/init.c @@ -1020,19 +1020,6 @@ static void init_rand_file_service(struct thread_data *td) } } -void td_fill_verify_state_seed(struct thread_data *td) -{ - bool use64; - - if (td->o.random_generator == FIO_RAND_GEN_TAUSWORTHE64) - use64 = true; - else - use64 = false; - - init_rand_seed(&td->verify_state, td->rand_seeds[FIO_RAND_VER_OFF], - use64); -} - static void td_fill_rand_seeds_internal(struct thread_data *td, bool use64) { uint64_t read_seed = td->rand_seeds[FIO_RAND_BS_OFF]; @@ -1056,7 +1043,8 @@ static void td_fill_rand_seeds_internal(struct thread_data *td, bool use64) init_rand_seed(&td->bsrange_state[DDIR_WRITE], write_seed, use64); init_rand_seed(&td->bsrange_state[DDIR_TRIM], trim_seed, use64); - td_fill_verify_state_seed(td); + init_rand_seed(&td->verify_state, td->rand_seeds[FIO_RAND_VER_OFF], + use64); init_rand_seed(&td->rwmix_state, td->rand_seeds[FIO_RAND_MIX_OFF], false); if (td->o.file_service_type == FIO_FSERVICE_RANDOM) @@ -1075,12 +1063,6 @@ static void td_fill_rand_seeds_internal(struct thread_data *td, bool use64) init_rand_seed(&td->prio_state, td->rand_seeds[FIO_RAND_PRIO_CMDS], false); init_rand_seed(&td->dedupe_working_set_index_state, td->rand_seeds[FIO_RAND_DEDUPE_WORKING_SET_IX], use64); - if (!td_random(td)) - return; - - if (td->o.rand_repeatable) - td->rand_seeds[FIO_RAND_BLOCK_OFF] = FIO_RANDSEED * td->thread_number; - init_rand_seed(&td->random_state, td->rand_seeds[FIO_RAND_BLOCK_OFF], use64); for (i = 0; i < DDIR_RWDIR_CNT; i++) { @@ -1088,20 +1070,15 @@ static void td_fill_rand_seeds_internal(struct thread_data *td, bool use64) init_rand_seed(s, td->rand_seeds[FIO_RAND_SEQ_RAND_READ_OFF], false); } + + init_rand_seed(&td->buf_state, td->rand_seeds[FIO_RAND_BUF_OFF], use64); + frand_copy(&td->buf_state_prev, &td->buf_state); } void td_fill_rand_seeds(struct thread_data *td) { bool use64; - if (td->o.allrand_repeatable) { - unsigned int i; - - for (i = 0; i < FIO_RAND_NR_OFFS; i++) - td->rand_seeds[i] = FIO_RANDSEED * td->thread_number - + i; - } - if (td->o.random_generator == FIO_RAND_GEN_TAUSWORTHE64) use64 = true; else @@ -1109,14 +1086,45 @@ void td_fill_rand_seeds(struct thread_data *td) td_fill_rand_seeds_internal(td, use64); - init_rand_seed(&td->buf_state, td->rand_seeds[FIO_RAND_BUF_OFF], use64); - frand_copy(&td->buf_state_prev, &td->buf_state); - dprint(FD_RANDOM, "FIO_RAND_NR_OFFS=%d\n", FIO_RAND_NR_OFFS); for (int i = 0; i < FIO_RAND_NR_OFFS; i++) dprint(FD_RANDOM, "rand_seeds[%d]=%" PRIu64 "\n", i, td->rand_seeds[i]); } +static int setup_random_seeds(struct thread_data *td) +{ + uint64_t seed; + unsigned int i; + + if (!td->o.rand_repeatable && !fio_option_is_set(&td->o, rand_seed)) { + int ret = init_random_seeds(td->rand_seeds, sizeof(td->rand_seeds)); + if (ret) + return ret; + } else { + seed = td->o.rand_seed; + for (i = 0; i < 4; i++) + seed *= 0x9e370001UL; + + for (i = 0; i < FIO_RAND_NR_OFFS; i++) { + td->rand_seeds[i] = seed * td->thread_number + i; + seed *= 0x9e370001UL; + } + } + + if (td->o.allrand_repeatable) { + unsigned int i; + + for (i = 0; i < FIO_RAND_NR_OFFS; i++) + td->rand_seeds[i] = FIO_RANDSEED * td->thread_number + i; + } + + if (td->o.rand_repeatable) + td->rand_seeds[FIO_RAND_BLOCK_OFF] = FIO_RANDSEED * td->thread_number; + + td_fill_rand_seeds(td); + return 0; +} + /* * Initializes the ioengine configured for a job, if it has not been done so * already. @@ -1250,31 +1258,6 @@ static void init_flags(struct thread_data *td) } } -static int setup_random_seeds(struct thread_data *td) -{ - uint64_t seed; - unsigned int i; - - if (!td->o.rand_repeatable && !fio_option_is_set(&td->o, rand_seed)) { - int ret = init_random_seeds(td->rand_seeds, sizeof(td->rand_seeds)); - if (!ret) - td_fill_rand_seeds(td); - return ret; - } - - seed = td->o.rand_seed; - for (i = 0; i < 4; i++) - seed *= 0x9e370001UL; - - for (i = 0; i < FIO_RAND_NR_OFFS; i++) { - td->rand_seeds[i] = seed * td->thread_number + i; - seed *= 0x9e370001UL; - } - - td_fill_rand_seeds(td); - return 0; -} - enum { FPRE_NONE = 0, FPRE_JOBNAME, From 9d8b492fc5593ca2d4a5eb15f477e7e672232fde Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Fri, 24 Mar 2023 18:08:25 +0000 Subject: [PATCH 0458/1097] init: get rid of td_fill_rand_seeds_internal Do all the work of td_fill_rand_seeds_internal in td_fill_rand_seeds since td_fill_rand_seeds was basically emptied by the previous patch. No functional change. Signed-off-by: Vincent Fu --- init.c | 29 ++++++++++++----------------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/init.c b/init.c index e020d452f9..0a6903b362 100644 --- a/init.c +++ b/init.c @@ -1020,12 +1020,18 @@ static void init_rand_file_service(struct thread_data *td) } } -static void td_fill_rand_seeds_internal(struct thread_data *td, bool use64) +void td_fill_rand_seeds(struct thread_data *td) { uint64_t read_seed = td->rand_seeds[FIO_RAND_BS_OFF]; uint64_t write_seed = td->rand_seeds[FIO_RAND_BS1_OFF]; uint64_t trim_seed = td->rand_seeds[FIO_RAND_BS2_OFF]; int i; + bool use64; + + if (td->o.random_generator == FIO_RAND_GEN_TAUSWORTHE64) + use64 = true; + else + use64 = false; /* * trimwrite is special in that we need to generate the same @@ -1075,22 +1081,6 @@ static void td_fill_rand_seeds_internal(struct thread_data *td, bool use64) frand_copy(&td->buf_state_prev, &td->buf_state); } -void td_fill_rand_seeds(struct thread_data *td) -{ - bool use64; - - if (td->o.random_generator == FIO_RAND_GEN_TAUSWORTHE64) - use64 = true; - else - use64 = false; - - td_fill_rand_seeds_internal(td, use64); - - dprint(FD_RANDOM, "FIO_RAND_NR_OFFS=%d\n", FIO_RAND_NR_OFFS); - for (int i = 0; i < FIO_RAND_NR_OFFS; i++) - dprint(FD_RANDOM, "rand_seeds[%d]=%" PRIu64 "\n", i, td->rand_seeds[i]); -} - static int setup_random_seeds(struct thread_data *td) { uint64_t seed; @@ -1122,6 +1112,11 @@ static int setup_random_seeds(struct thread_data *td) td->rand_seeds[FIO_RAND_BLOCK_OFF] = FIO_RANDSEED * td->thread_number; td_fill_rand_seeds(td); + + dprint(FD_RANDOM, "FIO_RAND_NR_OFFS=%d\n", FIO_RAND_NR_OFFS); + for (int i = 0; i < FIO_RAND_NR_OFFS; i++) + dprint(FD_RANDOM, "rand_seeds[%d]=%" PRIu64 "\n", i, td->rand_seeds[i]); + return 0; } From 95282650802cc290d38d24fa8febfc7425750a9e Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Fri, 24 Mar 2023 18:16:29 +0000 Subject: [PATCH 0459/1097] init: clean up random seed options - make allrandrepeat a synonym of randrepeat. allrandrepeat is superfluous because the seeds set by randrepeat already encompass random number generators beyond the one used for random offsets. - allow randseed to override [all]randrepeat: this is what the documentation implies but was not previously the case This is a breaking change for users relying on the values of fio's default random seeds. Link: https://github.com/axboe/fio/pull/1546 Fixes: https://github.com/axboe/fio/issues/1502 Signed-off-by: Vincent Fu --- cconv.c | 2 -- init.c | 11 +---------- options.c | 11 +---------- server.h | 2 +- thread_options.h | 3 --- 5 files changed, 3 insertions(+), 26 deletions(-) diff --git a/cconv.c b/cconv.c index 1ae38b1be6..9095d5195e 100644 --- a/cconv.c +++ b/cconv.c @@ -206,7 +206,6 @@ int convert_thread_options_to_cpu(struct thread_options *o, o->do_disk_util = le32_to_cpu(top->do_disk_util); o->override_sync = le32_to_cpu(top->override_sync); o->rand_repeatable = le32_to_cpu(top->rand_repeatable); - o->allrand_repeatable = le32_to_cpu(top->allrand_repeatable); o->rand_seed = le64_to_cpu(top->rand_seed); o->log_entries = le32_to_cpu(top->log_entries); o->log_avg_msec = le32_to_cpu(top->log_avg_msec); @@ -446,7 +445,6 @@ void convert_thread_options_to_net(struct thread_options_pack *top, top->do_disk_util = cpu_to_le32(o->do_disk_util); top->override_sync = cpu_to_le32(o->override_sync); top->rand_repeatable = cpu_to_le32(o->rand_repeatable); - top->allrand_repeatable = cpu_to_le32(o->allrand_repeatable); top->rand_seed = __cpu_to_le64(o->rand_seed); top->log_entries = cpu_to_le32(o->log_entries); top->log_avg_msec = cpu_to_le32(o->log_avg_msec); diff --git a/init.c b/init.c index 0a6903b362..48121f1496 100644 --- a/init.c +++ b/init.c @@ -1088,6 +1088,7 @@ static int setup_random_seeds(struct thread_data *td) if (!td->o.rand_repeatable && !fio_option_is_set(&td->o, rand_seed)) { int ret = init_random_seeds(td->rand_seeds, sizeof(td->rand_seeds)); + dprint(FD_RANDOM, "using system RNG for random seeds\n"); if (ret) return ret; } else { @@ -1101,16 +1102,6 @@ static int setup_random_seeds(struct thread_data *td) } } - if (td->o.allrand_repeatable) { - unsigned int i; - - for (i = 0; i < FIO_RAND_NR_OFFS; i++) - td->rand_seeds[i] = FIO_RANDSEED * td->thread_number + i; - } - - if (td->o.rand_repeatable) - td->rand_seeds[FIO_RAND_BLOCK_OFF] = FIO_RANDSEED * td->thread_number; - td_fill_rand_seeds(td); dprint(FD_RANDOM, "FIO_RAND_NR_OFFS=%d\n", FIO_RAND_NR_OFFS); diff --git a/options.c b/options.c index 440bff37cb..8193fb29fe 100644 --- a/options.c +++ b/options.c @@ -2465,6 +2465,7 @@ struct fio_option fio_options[FIO_MAX_OPTS] = { }, { .name = "randrepeat", + .alias = "allrandrepeat", .lname = "Random repeatable", .type = FIO_OPT_BOOL, .off1 = offsetof(struct thread_options, rand_repeatable), @@ -2594,16 +2595,6 @@ struct fio_option fio_options[FIO_MAX_OPTS] = { .category = FIO_OPT_C_IO, .group = FIO_OPT_G_RANDOM, }, - { - .name = "allrandrepeat", - .lname = "All Random Repeat", - .type = FIO_OPT_BOOL, - .off1 = offsetof(struct thread_options, allrand_repeatable), - .help = "Use repeatable random numbers for everything", - .def = "0", - .category = FIO_OPT_C_IO, - .group = FIO_OPT_G_RANDOM, - }, { .name = "nrfiles", .lname = "Number of files", diff --git a/server.h b/server.h index 898a893dec..601d334043 100644 --- a/server.h +++ b/server.h @@ -51,7 +51,7 @@ struct fio_net_cmd_reply { }; enum { - FIO_SERVER_VER = 99, + FIO_SERVER_VER = 100, FIO_SERVER_MAX_FRAGMENT_PDU = 1024, FIO_SERVER_MAX_CMD_MB = 2048, diff --git a/thread_options.h b/thread_options.h index 6670cbbfac..a24ebee69c 100644 --- a/thread_options.h +++ b/thread_options.h @@ -162,7 +162,6 @@ struct thread_options { unsigned int do_disk_util; unsigned int override_sync; unsigned int rand_repeatable; - unsigned int allrand_repeatable; unsigned long long rand_seed; unsigned int log_avg_msec; unsigned int log_hist_msec; @@ -485,8 +484,6 @@ struct thread_options_pack { uint32_t do_disk_util; uint32_t override_sync; uint32_t rand_repeatable; - uint32_t allrand_repeatable; - uint32_t pad2; uint64_t rand_seed; uint32_t log_avg_msec; uint32_t log_hist_msec; From c994fa62425fbc1e5f62115c58829067bcc1e28f Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Thu, 23 Feb 2023 22:53:34 +0000 Subject: [PATCH 0460/1097] t/random_seed: python script to test random seed options This new script tests combinations of randrepeat, allrandrepeat, and randseed. Also add this script to t/run-fio-tests.py. Signed-off-by: Vincent Fu --- t/random_seed.py | 394 +++++++++++++++++++++++++++++++++++++++++++++ t/run-fio-tests.py | 8 + 2 files changed, 402 insertions(+) create mode 100755 t/random_seed.py diff --git a/t/random_seed.py b/t/random_seed.py new file mode 100755 index 0000000000..86f2eb219b --- /dev/null +++ b/t/random_seed.py @@ -0,0 +1,394 @@ +#!/usr/bin/env python3 +""" +# random_seed.py +# +# Test fio's random seed options. +# +# - make sure that randseed overrides randrepeat and allrandrepeat +# - make sure that seeds differ across invocations when [all]randrepeat=0 and randseed is not set +# - make sure that seeds are always the same when [all]randrepeat=1 and randseed is not set +# +# USAGE +# see python3 random_seed.py --help +# +# EXAMPLES +# python3 t/random_seed.py +# python3 t/random_seed.py -f ./fio +# +# REQUIREMENTS +# Python 3.6 +# +""" +import os +import sys +import time +import locale +import argparse +import subprocess +from pathlib import Path + +class FioRandTest(): + """fio random seed test.""" + + def __init__(self, artifact_root, test_options, debug): + """ + artifact_root root directory for artifacts (subdirectory will be created under here) + test test specification + """ + self.artifact_root = artifact_root + self.test_options = test_options + self.debug = debug + self.filename_stub = None + self.filenames = {} + + self.test_dir = os.path.abspath(os.path.join(self.artifact_root, + f"{self.test_options['test_id']:03d}")) + if not os.path.exists(self.test_dir): + os.mkdir(self.test_dir) + + self.filename_stub = f"random{self.test_options['test_id']:03d}" + self.filenames['command'] = os.path.join(self.test_dir, f"{self.filename_stub}.command") + self.filenames['stdout'] = os.path.join(self.test_dir, f"{self.filename_stub}.stdout") + self.filenames['stderr'] = os.path.join(self.test_dir, f"{self.filename_stub}.stderr") + self.filenames['exitcode'] = os.path.join(self.test_dir, f"{self.filename_stub}.exitcode") + self.filenames['output'] = os.path.join(self.test_dir, f"{self.filename_stub}.output") + + def run_fio(self, fio_path): + """Run a test.""" + + fio_args = [ + "--debug=random", + "--name=random_seed", + "--ioengine=null", + "--filesize=32k", + "--rw=randread", + f"--output={self.filenames['output']}", + ] + for opt in ['randseed', 'randrepeat', 'allrandrepeat']: + if opt in self.test_options: + option = f"--{opt}={self.test_options[opt]}" + fio_args.append(option) + + command = [fio_path] + fio_args + with open(self.filenames['command'], "w+", encoding=locale.getpreferredencoding()) as command_file: + command_file.write(" ".join(command)) + + passed = True + + try: + with open(self.filenames['stdout'], "w+", encoding=locale.getpreferredencoding()) as stdout_file, \ + open(self.filenames['stderr'], "w+", encoding=locale.getpreferredencoding()) as stderr_file, \ + open(self.filenames['exitcode'], "w+", encoding=locale.getpreferredencoding()) as exitcode_file: + proc = None + # Avoid using subprocess.run() here because when a timeout occurs, + # fio will be stopped with SIGKILL. This does not give fio a + # chance to clean up and means that child processes may continue + # running and submitting IO. + proc = subprocess.Popen(command, + stdout=stdout_file, + stderr=stderr_file, + cwd=self.test_dir, + universal_newlines=True) + proc.communicate(timeout=300) + exitcode_file.write(f'{proc.returncode}\n') + passed &= (proc.returncode == 0) + except subprocess.TimeoutExpired: + proc.terminate() + proc.communicate() + assert proc.poll() + print("Timeout expired") + passed = False + except Exception: + if proc: + if not proc.poll(): + proc.terminate() + proc.communicate() + print(f"Exception: {sys.exc_info()}") + passed = False + + return passed + + def get_rand_seeds(self): + """Collect random seeds from --debug=random output.""" + with open(self.filenames['output'], "r", encoding=locale.getpreferredencoding()) as out_file: + file_data = out_file.read() + + offsets = 0 + for line in file_data.split('\n'): + if 'random' in line and 'FIO_RAND_NR_OFFS=' in line: + tokens = line.split('=') + offsets = int(tokens[len(tokens)-1]) + break + + if offsets == 0: + pass + # find an exception to throw + + seed_list = [] + for line in file_data.split('\n'): + if 'random' not in line: + continue + if 'rand_seeds[' in line: + tokens = line.split('=') + seed = int(tokens[-1]) + seed_list.append(seed) + # assume that seeds are in order + + return seed_list + + def check(self): + """Check test output.""" + + raise NotImplementedError() + + +class TestRR(FioRandTest): + """ + Test object for [all]randrepeat. If run for the first time just collect the + seeds. For later runs make sure the seeds match or do not match those + previously collected. + """ + # one set of seeds is for randrepeat=0 and the other is for randrepeat=1 + seeds = { 0: None, 1: None } + + def check(self): + """Check output for allrandrepeat=1.""" + + retval = True + opt = 'randrepeat' if 'randrepeat' in self.test_options else 'allrandrepeat' + rr = self.test_options[opt] + rand_seeds = self.get_rand_seeds() + + if not TestRR.seeds[rr]: + TestRR.seeds[rr] = rand_seeds + if self.debug: + print(f"TestRR: saving rand_seeds for [a]rr={rr}") + else: + if rr: + if TestRR.seeds[1] != rand_seeds: + retval = False + print(f"TestRR: unexpected seed mismatch for [a]rr={rr}") + else: + if self.debug: + print(f"TestRR: seeds correctly match for [a]rr={rr}") + if TestRR.seeds[0] == rand_seeds: + retval = False + print("TestRR: seeds unexpectedly match those from system RNG") + else: + if TestRR.seeds[0] == rand_seeds: + retval = False + print(f"TestRR: unexpected seed match for [a]rr={rr}") + else: + if self.debug: + print(f"TestRR: seeds correctly don't match for [a]rr={rr}") + if TestRR.seeds[1] == rand_seeds: + retval = False + print(f"TestRR: random seeds unexpectedly match those from [a]rr=1") + + return retval + + +class TestRS(FioRandTest): + """ + Test object when randseed=something controls the generated seeds. If run + for the first time for a given randseed just collect the seeds. For later + runs with the same seed make sure the seeds are the same as those + previously collected. + """ + seeds = {} + + def check(self): + """Check output for randseed=something.""" + + retval = True + rand_seeds = self.get_rand_seeds() + randseed = self.test_options['randseed'] + + if self.debug: + print("randseed = ", randseed) + + if randseed not in TestRS.seeds: + TestRS.seeds[randseed] = rand_seeds + if self.debug: + print("TestRS: saving rand_seeds") + else: + if TestRS.seeds[randseed] != rand_seeds: + retval = False + print("TestRS: seeds don't match when they should") + else: + if self.debug: + print("TestRS: seeds correctly match") + + # Now try to find seeds generated using a different randseed and make + # sure they *don't* match + for key in TestRS.seeds: + if key != randseed: + if TestRS.seeds[key] == rand_seeds: + retval = False + print("TestRS: randseeds differ but generated seeds match.") + else: + if self.debug: + print("TestRS: randseeds differ and generated seeds also differ.") + + return retval + + +def parse_args(): + """Parse command-line arguments.""" + + parser = argparse.ArgumentParser() + parser.add_argument('-f', '--fio', help='path to file executable (e.g., ./fio)') + parser.add_argument('-a', '--artifact-root', help='artifact root directory') + parser.add_argument('-d', '--debug', help='enable debug output', action='store_true') + parser.add_argument('-s', '--skip', nargs='+', type=int, + help='list of test(s) to skip') + parser.add_argument('-o', '--run-only', nargs='+', type=int, + help='list of test(s) to run, skipping all others') + args = parser.parse_args() + + return args + + +def main(): + """Run tests of fio random seed options""" + + args = parse_args() + + artifact_root = args.artifact_root if args.artifact_root else \ + f"random-seed-test-{time.strftime('%Y%m%d-%H%M%S')}" + os.mkdir(artifact_root) + print(f"Artifact directory is {artifact_root}") + + if args.fio: + fio = str(Path(args.fio).absolute()) + else: + fio = 'fio' + print(f"fio path is {fio}") + + test_list = [ + { + "test_id": 1, + "randrepeat": 0, + "test_obj": TestRR, + }, + { + "test_id": 2, + "randrepeat": 0, + "test_obj": TestRR, + }, + { + "test_id": 3, + "randrepeat": 1, + "test_obj": TestRR, + }, + { + "test_id": 4, + "randrepeat": 1, + "test_obj": TestRR, + }, + { + "test_id": 5, + "allrandrepeat": 0, + "test_obj": TestRR, + }, + { + "test_id": 6, + "allrandrepeat": 0, + "test_obj": TestRR, + }, + { + "test_id": 7, + "allrandrepeat": 1, + "test_obj": TestRR, + }, + { + "test_id": 8, + "allrandrepeat": 1, + "test_obj": TestRR, + }, + { + "test_id": 9, + "randrepeat": 0, + "randseed": "12345", + "test_obj": TestRS, + }, + { + "test_id": 10, + "randrepeat": 0, + "randseed": "12345", + "test_obj": TestRS, + }, + { + "test_id": 11, + "randrepeat": 1, + "randseed": "12345", + "test_obj": TestRS, + }, + { + "test_id": 12, + "allrandrepeat": 0, + "randseed": "12345", + "test_obj": TestRS, + }, + { + "test_id": 13, + "allrandrepeat": 1, + "randseed": "12345", + "test_obj": TestRS, + }, + { + "test_id": 14, + "randrepeat": 0, + "randseed": "67890", + "test_obj": TestRS, + }, + { + "test_id": 15, + "randrepeat": 1, + "randseed": "67890", + "test_obj": TestRS, + }, + { + "test_id": 16, + "allrandrepeat": 0, + "randseed": "67890", + "test_obj": TestRS, + }, + { + "test_id": 17, + "allrandrepeat": 1, + "randseed": "67890", + "test_obj": TestRS, + }, + ] + + passed = 0 + failed = 0 + skipped = 0 + + for test in test_list: + if (args.skip and test['test_id'] in args.skip) or \ + (args.run_only and test['test_id'] not in args.run_only): + skipped = skipped + 1 + outcome = 'SKIPPED (User request)' + else: + test_obj = test['test_obj'](artifact_root, test, args.debug) + status = test_obj.run_fio(fio) + if status: + status = test_obj.check() + if status: + passed = passed + 1 + outcome = 'PASSED' + else: + failed = failed + 1 + outcome = 'FAILED' + + print(f"**********Test {test['test_id']} {outcome}**********") + + print(f"{passed} tests passed, {failed} failed, {skipped} skipped") + + sys.exit(failed) + + +if __name__ == '__main__': + main() diff --git a/t/run-fio-tests.py b/t/run-fio-tests.py index c3091b6854..7e0df7ed2e 100755 --- a/t/run-fio-tests.py +++ b/t/run-fio-tests.py @@ -1361,6 +1361,14 @@ def cpucount4(cls): 'success': SUCCESS_DEFAULT, 'requirements': [], }, + { + 'test_id': 1013, + 'test_class': FioExeTest, + 'exe': 't/random_seed.py', + 'parameters': ['-f', '{fio_path}'], + 'success': SUCCESS_DEFAULT, + 'requirements': [], + }, ] From 888dbd62c769448c4052f795f123f0d04a56fde3 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Fri, 24 Mar 2023 14:16:14 +0000 Subject: [PATCH 0461/1097] test: improve evaluation of t0020.fio and t0021.fio We previously used an arbirtrary test based on a count of consecutive offsets to decide whether the offsets from t0020.fio and t0021.fio were random or not. Replace this with a Wald-Wolfowitz runs test. This new test calculates a statistic based on whether each offset is in the first or second half of the LBA space. So it is not particularly powerful although it has a more sound statistical justification than what we originally had. Recent changes to fio's default random number seeds produced false positives with t0020.fio and prompted this change. Consider calculating the test statistic directly instead of relying on the statsmodel import in order to reduce the dependencies required. References: https://www.statology.org/runs-test-python/ https://www.geeksforgeeks.org/runs-test-of-randomness-in-python/ https://www.investopedia.com/terms/r/runs_test.asp https://www.statsmodels.org/stable/generated/statsmodels.sandbox.stats.runs.runstest_1samp.html Signed-off-by: Vincent Fu --- ci/actions-install.sh | 3 ++- ci/appveyor-install.sh | 2 +- t/run-fio-tests.py | 18 ++++++++---------- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/ci/actions-install.sh b/ci/actions-install.sh index 5057fca39c..fb3bd141c8 100755 --- a/ci/actions-install.sh +++ b/ci/actions-install.sh @@ -62,6 +62,7 @@ DPKGCFG pkgs+=( python3-scipy python3-sphinx + python3-statsmodels ) echo "Updating APT..." @@ -85,7 +86,7 @@ install_macos() { echo "Installing packages..." HOMEBREW_NO_AUTO_UPDATE=1 brew install cunit libnfs sphinx-doc brew link sphinx-doc --force - pip3 install scipy six + pip3 install scipy six statsmodels } main() { diff --git a/ci/appveyor-install.sh b/ci/appveyor-install.sh index 3137f39ebe..1e28c45403 100755 --- a/ci/appveyor-install.sh +++ b/ci/appveyor-install.sh @@ -37,7 +37,7 @@ case "${DISTRO}" in ;; esac -python.exe -m pip install scipy six +python.exe -m pip install scipy six statsmodels echo "Python3 path: $(type -p python3 2>&1)" echo "Python3 version: $(python3 -V 2>&1)" diff --git a/t/run-fio-tests.py b/t/run-fio-tests.py index 7e0df7ed2e..4fe6fe46f6 100755 --- a/t/run-fio-tests.py +++ b/t/run-fio-tests.py @@ -53,6 +53,7 @@ import subprocess import multiprocessing from pathlib import Path +from statsmodels.sandbox.stats.runs import runstest_1samp class FioTest(): @@ -598,24 +599,16 @@ def check_result(self): log_lines = file_data.split('\n') - seq_count = 0 - offsets = set() + offsets = [] prev = int(log_lines[0].split(',')[4]) for line in log_lines[1:]: - offsets.add(prev/4096) + offsets.append(prev/4096) if len(line.strip()) == 0: continue cur = int(line.split(',')[4]) - if cur - prev == 4096: - seq_count += 1 prev = cur - # 10 is an arbitrary threshold - if seq_count > 10: - self.passed = False - self.failure_reason = "too many ({0}) consecutive offsets".format(seq_count) - if len(offsets) != 256: self.passed = False self.failure_reason += " number of offsets is {0} instead of 256".format(len(offsets)) @@ -625,6 +618,11 @@ def check_result(self): self.passed = False self.failure_reason += " missing offset {0}".format(i*4096) + (z, p) = runstest_1samp(list(offsets)) + if p < 0.05: + self.passed = False + self.failure_reason += f" runs test failed with p = {p}" + class FioJobTest_t0022(FioJobTest): """Test consists of fio test job t0022""" From 7624d58953d38612c11496551a855a1aeee7ad24 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Fri, 24 Mar 2023 18:46:07 +0000 Subject: [PATCH 0462/1097] docs: update documentation for randrepeat and allrandrepeat Note that allrandrepeat is an alias for randrepeat and that randrepeat actually sets random seeds for all of fio's main random number generators. Signed-off-by: Vincent Fu --- HOWTO.rst | 7 +++---- fio.1 | 7 +++---- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/HOWTO.rst b/HOWTO.rst index cb0f9834f8..0a6e60c77b 100644 --- a/HOWTO.rst +++ b/HOWTO.rst @@ -1232,13 +1232,12 @@ I/O type .. option:: randrepeat=bool - Seed the random number generator used for random I/O patterns in a - predictable way so the pattern is repeatable across runs. Default: true. + Seed all random number generators in a predictable way so the pattern + is repeatable across runs. Default: true. .. option:: allrandrepeat=bool - Seed all random number generators in a predictable way so results are - repeatable across runs. Default: false. + Alias for :option:`randrepeat`. Default: true. .. option:: randseed=int diff --git a/fio.1 b/fio.1 index 311b16d828..4207814b44 100644 --- a/fio.1 +++ b/fio.1 @@ -1022,12 +1022,11 @@ Alias for \fBboth\fR. .RE .TP .BI randrepeat \fR=\fPbool -Seed the random number generator used for random I/O patterns in a -predictable way so the pattern is repeatable across runs. Default: true. +Seed all random number generators in a predictable way so the pattern is +repeatable across runs. Default: true. .TP .BI allrandrepeat \fR=\fPbool -Seed all random number generators in a predictable way so results are -repeatable across runs. Default: false. +Alias for \fBrandrepeat\fR. Default: true. .TP .BI randseed \fR=\fPint Seed the random number generators based on this seed value, to be able to From 699bc19bf5706791c64e20c0b378c5194430b9d6 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Wed, 19 Apr 2023 10:40:07 -0400 Subject: [PATCH 0463/1097] ci: disable __thread support for Windows msys2 build Our Windows msys2 build is now broken after AppVeyor's recent image update from clang/lld 15.0.7-3 to 16.0.0-1. Here is the error: CC unittests/oslib/strcasestr.o CC unittests/oslib/strsep.o LINK fio ld.lld: error: undefined symbol: static_tv_valid >>> referenced by gettime.o clang: error: linker command failed with exit code 1 (use -v to see invocation) Rishabh Shukla investigated and found that clang + ld builds successfully. So the problem seems to be with lld 16.0.0-1. Until we find the root cause let's just disable __thread support on AppVeyor msys2 builds. Link: https://github.com/axboe/fio/issues/1559 Suggested-by: Rishabh Shukla Signed-off-by: Vincent Fu --- .appveyor.yml | 4 +++- configure | 7 ++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index 92301ca9e0..a63cf24f01 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -6,9 +6,11 @@ image: environment: CYG_MIRROR: http://cygwin.mirror.constant.com matrix: +# --disable-tls for the msys2 build to work around +# breakage with clang/lld 16.0.0-1 - ARCHITECTURE: x64 CC: clang - CONFIGURE_OPTIONS: --enable-pdb + CONFIGURE_OPTIONS: --enable-pdb --disable-tls DISTRO: msys2 # Skip 32 bit clang build # - ARCHITECTURE: x86 diff --git a/configure b/configure index 45d10a3109..abb6d01649 100755 --- a/configure +++ b/configure @@ -264,6 +264,8 @@ for opt do ;; --seed-buckets=*) seed_buckets="$optarg" ;; + --disable-tls) tls_check="no" + ;; --help) show_help="yes" ;; @@ -313,6 +315,7 @@ if test "$show_help" = "yes" ; then echo "--disable-dfs Disable DAOS File System support even if found" echo "--enable-asan Enable address sanitizer" echo "--seed-buckets= Number of seed buckets for the refill-buffer" + echo "--disable-tls Disable __thread local storage" exit $exit_val fi @@ -1549,7 +1552,8 @@ print_config "socklen_t" "$socklen_t" if test "$tls_thread" != "yes" ; then tls_thread="no" fi -cat > $TMPC << EOF +if test "$tls_check" != "no"; then + cat > $TMPC << EOF #include static __thread int ret; int main(int argc, char **argv) @@ -1560,6 +1564,7 @@ EOF if compile_prog "" "" "__thread"; then tls_thread="yes" fi +fi print_config "__thread" "$tls_thread" ########################################## From a7b7cb5bc0ebd935c99f06ad852ce4d1b2e88a1b Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Sat, 15 Apr 2023 00:50:58 +0000 Subject: [PATCH 0464/1097] engines: cleanup casts and move memset Drop type casting of malloc's return value. Move a memset call closer to its corresponding malloc call. This is a prep patch for a Coccinelle script that converts malloc+memset(,0,) to calloc. Signed-off-by: Vincent Fu --- engines/null.c | 4 ++-- engines/solarisaio.c | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/engines/null.c b/engines/null.c index 68759c26f1..68c9969c4f 100644 --- a/engines/null.c +++ b/engines/null.c @@ -106,12 +106,12 @@ static void null_cleanup(struct null_data *nd) static struct null_data *null_init(struct thread_data *td) { - struct null_data *nd = (struct null_data *) malloc(sizeof(*nd)); + struct null_data *nd = malloc(sizeof(*nd)); memset(nd, 0, sizeof(*nd)); if (td->o.iodepth != 1) { - nd->io_us = (struct io_u **) malloc(td->o.iodepth * sizeof(struct io_u *)); + nd->io_us = malloc(td->o.iodepth * sizeof(struct io_u *)); memset(nd->io_us, 0, td->o.iodepth * sizeof(struct io_u *)); td->io_ops->flags |= FIO_ASYNCIO_SETS_ISSUE_TIME; } else diff --git a/engines/solarisaio.c b/engines/solarisaio.c index 21e95935b2..59bae71379 100644 --- a/engines/solarisaio.c +++ b/engines/solarisaio.c @@ -185,8 +185,9 @@ static void fio_solarisaio_init_sigio(void) static int fio_solarisaio_init(struct thread_data *td) { - struct solarisaio_data *sd = malloc(sizeof(*sd)); unsigned int max_depth; + struct solarisaio_data *sd = malloc(sizeof(*sd)); + memset(sd, 0, sizeof(*sd)); max_depth = td->o.iodepth; if (max_depth > MAXASYNCHIO) { @@ -195,7 +196,6 @@ static int fio_solarisaio_init(struct thread_data *td) max_depth); } - memset(sd, 0, sizeof(*sd)); sd->aio_events = malloc(max_depth * sizeof(struct io_u *)); memset(sd->aio_events, 0, max_depth * sizeof(struct io_u *)); sd->max_depth = max_depth; From a93259c9d05c69d4d32f2d1ce459e6a960077b17 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Sat, 15 Apr 2023 00:51:44 +0000 Subject: [PATCH 0465/1097] engines: separate declaration and assignment Separate the declaraction and assignment for some variables. This is a prep patch for a Coccinelle script that converts mallloc+memset(,0,) to calloc. Part of this patch was created using the Coccinelle script below. @@ identifier x; expression y; statement s; type T; @@ -T x = malloc(y); +T x; +x = malloc(y); ( if (!x) s | if (x == NULL) s | ) memset(x, 0, y); Signed-off-by: Vincent Fu --- engines/null.c | 3 ++- engines/posixaio.c | 3 ++- engines/solarisaio.c | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/engines/null.c b/engines/null.c index 68c9969c4f..c17697bff5 100644 --- a/engines/null.c +++ b/engines/null.c @@ -106,7 +106,8 @@ static void null_cleanup(struct null_data *nd) static struct null_data *null_init(struct thread_data *td) { - struct null_data *nd = malloc(sizeof(*nd)); + struct null_data *nd; + nd = malloc(sizeof(*nd)); memset(nd, 0, sizeof(*nd)); diff --git a/engines/posixaio.c b/engines/posixaio.c index 135d088c7a..948f8a2483 100644 --- a/engines/posixaio.c +++ b/engines/posixaio.c @@ -197,7 +197,8 @@ static void fio_posixaio_cleanup(struct thread_data *td) static int fio_posixaio_init(struct thread_data *td) { - struct posixaio_data *pd = malloc(sizeof(*pd)); + struct posixaio_data *pd; + pd = malloc(sizeof(*pd)); memset(pd, 0, sizeof(*pd)); pd->aio_events = malloc(td->o.iodepth * sizeof(struct io_u *)); diff --git a/engines/solarisaio.c b/engines/solarisaio.c index 59bae71379..366b72f46d 100644 --- a/engines/solarisaio.c +++ b/engines/solarisaio.c @@ -186,7 +186,8 @@ static void fio_solarisaio_init_sigio(void) static int fio_solarisaio_init(struct thread_data *td) { unsigned int max_depth; - struct solarisaio_data *sd = malloc(sizeof(*sd)); + struct solarisaio_data *sd; + sd = malloc(sizeof(*sd)); memset(sd, 0, sizeof(*sd)); max_depth = td->o.iodepth; From 223decddb199e9af0c5cd357d28c43e48dbcea7a Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Sat, 15 Apr 2023 00:52:47 +0000 Subject: [PATCH 0466/1097] fio: replace malloc+memset with calloc Clean up the code base by replacing malloc+memset with calloc. This patch was generated from the Coccinelle script below. The script below is inspired by similar scripts used elsewhere: https://lore.kernel.org/linux-btrfs/cover.1443546000.git.silvio.fricke@gmail.com/ https://github.com/coccinelle/coccinellery/blob/master/simple_kzalloc/simple_kzalloc1.cocci @@ expression x,y; statement s; type T; @@ -x = malloc(y * sizeof(T)); +x = calloc(y, sizeof(T)); ( if (!x) s | if (x == NULL) s | ) -memset(x, 0, y * sizeof(T)); @@ expression x,y,z; statement s; @@ -x = malloc(y * sizeof(z)); +x = calloc(y, sizeof(z)); ( if (!x) s | if (x == NULL) s | ) -memset(x, 0, y * sizeof(z)); @@ expression e,x; statement s; @@ -x = malloc(e); +x = calloc(1, e); ( if (!x) s | if (x == NULL) s | ) -memset(x, 0, e); Signed-off-by: Vincent Fu --- client.c | 6 ++---- engines/e4defrag.c | 3 +-- engines/io_uring.c | 3 +-- engines/libhdfs.c | 3 +-- engines/libiscsi.c | 3 +-- engines/net.c | 4 +--- engines/nfs.c | 6 ++---- engines/null.c | 3 +-- engines/posixaio.c | 7 ++----- engines/rdma.c | 22 +++++++--------------- engines/solarisaio.c | 6 ++---- engines/sync.c | 3 +-- eta.c | 6 ++---- filesetup.c | 3 +-- gfio.c | 3 +-- graph.c | 3 +-- init.c | 3 +-- t/io_uring.c | 3 +-- t/lfsr-test.c | 3 +-- verify.c | 3 +-- 20 files changed, 31 insertions(+), 65 deletions(-) diff --git a/client.c b/client.c index 51496c7702..7cd2ba66e1 100644 --- a/client.c +++ b/client.c @@ -369,8 +369,7 @@ static struct fio_client *get_new_client(void) { struct fio_client *client; - client = malloc(sizeof(*client)); - memset(client, 0, sizeof(*client)); + client = calloc(1, sizeof(*client)); INIT_FLIST_HEAD(&client->list); INIT_FLIST_HEAD(&client->hash_list); @@ -793,8 +792,7 @@ static int __fio_client_send_remote_ini(struct fio_client *client, dprint(FD_NET, "send remote ini %s to %s\n", filename, client->hostname); p_size = sizeof(*pdu) + strlen(filename) + 1; - pdu = malloc(p_size); - memset(pdu, 0, p_size); + pdu = calloc(1, p_size); pdu->name_len = strlen(filename); strcpy((char *) pdu->file, filename); pdu->client_type = cpu_to_le16((uint16_t) client->type); diff --git a/engines/e4defrag.c b/engines/e4defrag.c index 0a0004d047..37cc2ada81 100644 --- a/engines/e4defrag.c +++ b/engines/e4defrag.c @@ -77,12 +77,11 @@ static int fio_e4defrag_init(struct thread_data *td) return 1; } - ed = malloc(sizeof(*ed)); + ed = calloc(1, sizeof(*ed)); if (!ed) { td_verror(td, ENOMEM, "io_queue_init"); return 1; } - memset(ed, 0 ,sizeof(*ed)); if (td->o.directory) len = sprintf(donor_name, "%s/", td->o.directory); diff --git a/engines/io_uring.c b/engines/io_uring.c index 7f743c2a88..f5ffe9f448 100644 --- a/engines/io_uring.c +++ b/engines/io_uring.c @@ -800,11 +800,10 @@ static void fio_ioring_probe(struct thread_data *td) /* default to off, as that's always safe */ o->nonvectored = 0; - p = malloc(sizeof(*p) + 256 * sizeof(struct io_uring_probe_op)); + p = calloc(1, sizeof(*p) + 256 * sizeof(struct io_uring_probe_op)); if (!p) return; - memset(p, 0, sizeof(*p) + 256 * sizeof(struct io_uring_probe_op)); ret = syscall(__NR_io_uring_register, ld->ring_fd, IORING_REGISTER_PROBE, p, 256); if (ret < 0) diff --git a/engines/libhdfs.c b/engines/libhdfs.c index f20e45cac1..d0a2684084 100644 --- a/engines/libhdfs.c +++ b/engines/libhdfs.c @@ -315,8 +315,7 @@ static int fio_hdfsio_setup(struct thread_data *td) uint64_t file_size, total_file_size; if (!td->io_ops_data) { - hd = malloc(sizeof(*hd)); - memset(hd, 0, sizeof(*hd)); + hd = calloc(1, sizeof(*hd)); hd->curr_file_id = -1; diff --git a/engines/libiscsi.c b/engines/libiscsi.c index c97b5709ae..37c9b55a91 100644 --- a/engines/libiscsi.c +++ b/engines/libiscsi.c @@ -68,8 +68,7 @@ static int fio_iscsi_setup_lun(struct iscsi_info *iscsi_info, struct scsi_readcapacity16 *rc16 = NULL; int ret = 0; - iscsi_lun = malloc(sizeof(struct iscsi_lun)); - memset(iscsi_lun, 0, sizeof(struct iscsi_lun)); + iscsi_lun = calloc(1, sizeof(struct iscsi_lun)); iscsi_lun->iscsi_info = iscsi_info; diff --git a/engines/net.c b/engines/net.c index c6cec5845a..fec53d7417 100644 --- a/engines/net.c +++ b/engines/net.c @@ -1370,9 +1370,7 @@ static int fio_netio_setup(struct thread_data *td) } if (!td->io_ops_data) { - nd = malloc(sizeof(*nd)); - - memset(nd, 0, sizeof(*nd)); + nd = calloc(1, sizeof(*nd)); nd->listenfd = -1; nd->pipes[0] = nd->pipes[1] = -1; td->io_ops_data = nd; diff --git a/engines/nfs.c b/engines/nfs.c index 336e670bf8..970962a3f9 100644 --- a/engines/nfs.c +++ b/engines/nfs.c @@ -224,8 +224,7 @@ static int do_mount(struct thread_data *td, const char *url) return -1; } - options->events = malloc(event_size); - memset(options->events, 0, event_size); + options->events = calloc(1, event_size); options->prev_requested_event_index = -1; options->queue_depth = td->o.iodepth; @@ -278,8 +277,7 @@ static int fio_libnfs_open(struct thread_data *td, struct fio_file *f) options->nfs_url, ret, nfs_get_error(options->context)); return ret; } - nfs_data = malloc(sizeof(struct nfs_data)); - memset(nfs_data, 0, sizeof(struct nfs_data)); + nfs_data = calloc(1, sizeof(struct nfs_data)); nfs_data->options = options; if (td->o.td_ddir == TD_DDIR_WRITE) diff --git a/engines/null.c b/engines/null.c index c17697bff5..7236ec9488 100644 --- a/engines/null.c +++ b/engines/null.c @@ -112,8 +112,7 @@ static struct null_data *null_init(struct thread_data *td) memset(nd, 0, sizeof(*nd)); if (td->o.iodepth != 1) { - nd->io_us = malloc(td->o.iodepth * sizeof(struct io_u *)); - memset(nd->io_us, 0, td->o.iodepth * sizeof(struct io_u *)); + nd->io_us = calloc(td->o.iodepth, sizeof(struct io_u *)); td->io_ops->flags |= FIO_ASYNCIO_SETS_ISSUE_TIME; } else td->io_ops->flags |= FIO_SYNCIO; diff --git a/engines/posixaio.c b/engines/posixaio.c index 948f8a2483..0f4eea68a9 100644 --- a/engines/posixaio.c +++ b/engines/posixaio.c @@ -198,11 +198,8 @@ static void fio_posixaio_cleanup(struct thread_data *td) static int fio_posixaio_init(struct thread_data *td) { struct posixaio_data *pd; - pd = malloc(sizeof(*pd)); - - memset(pd, 0, sizeof(*pd)); - pd->aio_events = malloc(td->o.iodepth * sizeof(struct io_u *)); - memset(pd->aio_events, 0, td->o.iodepth * sizeof(struct io_u *)); + pd = calloc(1, sizeof(*pd)); + pd->aio_events = calloc(td->o.iodepth, sizeof(struct io_u *)); td->io_ops_data = pd; return 0; diff --git a/engines/rdma.c b/engines/rdma.c index fcb4106889..ee2844d323 100644 --- a/engines/rdma.c +++ b/engines/rdma.c @@ -1296,23 +1296,18 @@ static int fio_rdmaio_init(struct thread_data *td) if ((rd->rdma_protocol == FIO_RDMA_MEM_WRITE) || (rd->rdma_protocol == FIO_RDMA_MEM_READ)) { - rd->rmt_us = - malloc(FIO_RDMA_MAX_IO_DEPTH * sizeof(struct remote_u)); - memset(rd->rmt_us, 0, - FIO_RDMA_MAX_IO_DEPTH * sizeof(struct remote_u)); + rd->rmt_us = calloc(FIO_RDMA_MAX_IO_DEPTH, + sizeof(struct remote_u)); rd->rmt_nr = 0; } - rd->io_us_queued = malloc(td->o.iodepth * sizeof(struct io_u *)); - memset(rd->io_us_queued, 0, td->o.iodepth * sizeof(struct io_u *)); + rd->io_us_queued = calloc(td->o.iodepth, sizeof(struct io_u *)); rd->io_u_queued_nr = 0; - rd->io_us_flight = malloc(td->o.iodepth * sizeof(struct io_u *)); - memset(rd->io_us_flight, 0, td->o.iodepth * sizeof(struct io_u *)); + rd->io_us_flight = calloc(td->o.iodepth, sizeof(struct io_u *)); rd->io_u_flight_nr = 0; - rd->io_us_completed = malloc(td->o.iodepth * sizeof(struct io_u *)); - memset(rd->io_us_completed, 0, td->o.iodepth * sizeof(struct io_u *)); + rd->io_us_completed = calloc(td->o.iodepth, sizeof(struct io_u *)); rd->io_u_completed_nr = 0; if (td_read(td)) { /* READ as the server */ @@ -1339,8 +1334,7 @@ static int fio_rdmaio_post_init(struct thread_data *td) for (i = 0; i < td->io_u_freelist.nr; i++) { struct io_u *io_u = td->io_u_freelist.io_us[i]; - io_u->engine_data = malloc(sizeof(struct rdma_io_u_data)); - memset(io_u->engine_data, 0, sizeof(struct rdma_io_u_data)); + io_u->engine_data = calloc(1, sizeof(struct rdma_io_u_data)); ((struct rdma_io_u_data *)io_u->engine_data)->wr_id = i; io_u->mr = ibv_reg_mr(rd->pd, io_u->buf, max_bs, @@ -1386,9 +1380,7 @@ static int fio_rdmaio_setup(struct thread_data *td) } if (!td->io_ops_data) { - rd = malloc(sizeof(*rd)); - - memset(rd, 0, sizeof(*rd)); + rd = calloc(1, sizeof(*rd)); init_rand_seed(&rd->rand_state, (unsigned int) GOLDEN_RATIO_64, 0); td->io_ops_data = rd; } diff --git a/engines/solarisaio.c b/engines/solarisaio.c index 366b72f46d..b2b47fede6 100644 --- a/engines/solarisaio.c +++ b/engines/solarisaio.c @@ -187,8 +187,7 @@ static int fio_solarisaio_init(struct thread_data *td) { unsigned int max_depth; struct solarisaio_data *sd; - sd = malloc(sizeof(*sd)); - memset(sd, 0, sizeof(*sd)); + sd = calloc(1, sizeof(*sd)); max_depth = td->o.iodepth; if (max_depth > MAXASYNCHIO) { @@ -197,8 +196,7 @@ static int fio_solarisaio_init(struct thread_data *td) max_depth); } - sd->aio_events = malloc(max_depth * sizeof(struct io_u *)); - memset(sd->aio_events, 0, max_depth * sizeof(struct io_u *)); + sd->aio_events = calloc(max_depth, sizeof(struct io_u *)); sd->max_depth = max_depth; #ifdef USE_SIGNAL_COMPLETIONS diff --git a/engines/sync.c b/engines/sync.c index 339ba99970..d19991222e 100644 --- a/engines/sync.c +++ b/engines/sync.c @@ -402,8 +402,7 @@ static int fio_vsyncio_init(struct thread_data *td) { struct syncio_data *sd; - sd = malloc(sizeof(*sd)); - memset(sd, 0, sizeof(*sd)); + sd = calloc(1, sizeof(*sd)); sd->last_offset = -1ULL; sd->iovecs = malloc(td->o.iodepth * sizeof(struct iovec)); sd->io_us = malloc(td->o.iodepth * sizeof(struct io_u *)); diff --git a/eta.c b/eta.c index ce1c6f2dcd..af4027e0e2 100644 --- a/eta.c +++ b/eta.c @@ -409,8 +409,7 @@ bool calc_thread_status(struct jobs_eta *je, int force) if (!ddir_rw_sum(disp_io_bytes)) fill_start_time(&disp_prev_time); - eta_secs = malloc(thread_number * sizeof(uint64_t)); - memset(eta_secs, 0, thread_number * sizeof(uint64_t)); + eta_secs = calloc(thread_number, sizeof(uint64_t)); je->elapsed_sec = (mtime_since_genesis() + 999) / 1000; @@ -692,10 +691,9 @@ struct jobs_eta *get_jobs_eta(bool force, size_t *size) return NULL; *size = sizeof(*je) + THREAD_RUNSTR_SZ + 8; - je = malloc(*size); + je = calloc(1, *size); if (!je) return NULL; - memset(je, 0, *size); if (!calc_thread_status(je, force)) { free(je); diff --git a/filesetup.c b/filesetup.c index 8e5059412e..14c7610849 100644 --- a/filesetup.c +++ b/filesetup.c @@ -303,13 +303,12 @@ static bool pre_read_file(struct thread_data *td, struct fio_file *f) if (bs > left) bs = left; - b = malloc(bs); + b = calloc(1, bs); if (!b) { td_verror(td, errno, "malloc"); ret = false; goto error; } - memset(b, 0, bs); if (lseek(f->fd, f->file_offset, SEEK_SET) < 0) { td_verror(td, errno, "lseek"); diff --git a/gfio.c b/gfio.c index 22c5314d3d..10c9b0947b 100644 --- a/gfio.c +++ b/gfio.c @@ -730,8 +730,7 @@ static struct gui_entry *alloc_new_gui_entry(struct gui *ui) { struct gui_entry *ge; - ge = malloc(sizeof(*ge)); - memset(ge, 0, sizeof(*ge)); + ge = calloc(1, sizeof(*ge)); ge->state = GE_STATE_NEW; ge->ui = ui; return ge; diff --git a/graph.c b/graph.c index c49cdae14f..3d2b6c96dd 100644 --- a/graph.c +++ b/graph.c @@ -713,8 +713,7 @@ static void graph_label_add_value(struct graph_label *i, void *value, struct graph *g = i->parent; struct graph_value *x; - x = malloc(sizeof(*x)); - memset(x, 0, sizeof(*x)); + x = calloc(1, sizeof(*x)); INIT_FLIST_HEAD(&x->alias); INIT_FLIST_HEAD(&x->list); flist_add_tail(&x->list, &i->value_list); diff --git a/init.c b/init.c index 48121f1496..437406ecae 100644 --- a/init.c +++ b/init.c @@ -1946,8 +1946,7 @@ static int __parse_jobs_ini(struct thread_data *td, * it's really 256 + small bit, 280 should suffice */ if (!nested) { - name = malloc(280); - memset(name, 0, 280); + name = calloc(1, 280); } opts = NULL; diff --git a/t/io_uring.c b/t/io_uring.c index f9f4b84005..6b0efef85c 100644 --- a/t/io_uring.c +++ b/t/io_uring.c @@ -487,11 +487,10 @@ static void io_uring_probe(int fd) struct io_uring_probe *p; int ret; - p = malloc(sizeof(*p) + 256 * sizeof(struct io_uring_probe_op)); + p = calloc(1, sizeof(*p) + 256 * sizeof(struct io_uring_probe_op)); if (!p) return; - memset(p, 0, sizeof(*p) + 256 * sizeof(struct io_uring_probe_op)); ret = syscall(__NR_io_uring_register, fd, IORING_REGISTER_PROBE, p, 256); if (ret < 0) goto out; diff --git a/t/lfsr-test.c b/t/lfsr-test.c index 4b255e19bb..632de38313 100644 --- a/t/lfsr-test.c +++ b/t/lfsr-test.c @@ -78,8 +78,7 @@ int main(int argc, char *argv[]) /* Create verification table */ if (verify) { v_size = numbers * sizeof(uint8_t); - v = malloc(v_size); - memset(v, 0, v_size); + v = calloc(1, v_size); printf("\nVerification table is %lf KiB\n", (double)(v_size) / 1024); } v_start = v; diff --git a/verify.c b/verify.c index e7e4c69ca6..2848b68612 100644 --- a/verify.c +++ b/verify.c @@ -1595,8 +1595,7 @@ struct all_io_list *get_all_io_list(int save_mask, size_t *sz) *sz = sizeof(*rep); *sz += nr * sizeof(struct thread_io_list); *sz += depth * sizeof(struct file_comp); - rep = malloc(*sz); - memset(rep, 0, *sz); + rep = calloc(1, *sz); rep->threads = cpu_to_le64((uint64_t) nr); From 073974b24aac23610e9e13e3eb56438ad108ab31 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Thu, 20 Apr 2023 15:12:22 +0000 Subject: [PATCH 0467/1097] filesetup: better handle non-uniform distributions When we have a random workload with multiple files, randrepeat=0, and a non-uniform random distribution, the offsets touched will follow the same sequence for all files: $ ./fio --name=test --nrfiles=2 --randrepeat=0 --filesize=16k \ --debug=io --rw=randread --norandommap \ --random_distribution=normal:50 | grep complete: io 23042 complete: io_u 0x55bd982f6000: off=0x2000,len=0x1000,ddir=0,file=test.0.0 io 23042 complete: io_u 0x55bd982f6000: off=0x2000,len=0x1000,ddir=0,file=test.0.1 io 23042 complete: io_u 0x55bd982f6000: off=0x3000,len=0x1000,ddir=0,file=test.0.0 io 23042 complete: io_u 0x55bd982f6000: off=0x3000,len=0x1000,ddir=0,file=test.0.1 io 23042 complete: io_u 0x55bd982f6000: off=0x2000,len=0x1000,ddir=0,file=test.0.0 io 23042 complete: io_u 0x55bd982f6000: off=0x2000,len=0x1000,ddir=0,file=test.0.1 io 23042 complete: io_u 0x55bd982f6000: off=0x3000,len=0x1000,ddir=0,file=test.0.0 io 23042 complete: io_u 0x55bd982f6000: off=0x3000,len=0x1000,ddir=0,file=test.0.1 Notice that the blocks touched for test.0.0 and test.0.1 follow the same sequence. This patch allows the sequence of offsets touched to differ between files by always involving the filename in the seed used for each file. The randrepeat setting will still be respected as it is involved in determining the value for td->rand_seeds[FIO_RAND_BLOCK_OFF]. With the patch applied the above invocation produces output like: io 23022 complete: io_u 0x55ed2cd2c000: off=0x2000,len=0x1000,ddir=0,file=test.0.0 io 23022 complete: io_u 0x55ed2cd2c000: off=0x2000,len=0x1000,ddir=0,file=test.0.1 io 23022 complete: io_u 0x55ed2cd2c000: off=0x3000,len=0x1000,ddir=0,file=test.0.0 io 23022 complete: io_u 0x55ed2cd2c000: off=0x2000,len=0x1000,ddir=0,file=test.0.1 io 23022 complete: io_u 0x55ed2cd2c000: off=0x2000,len=0x1000,ddir=0,file=test.0.0 io 23022 complete: io_u 0x55ed2cd2c000: off=0x3000,len=0x1000,ddir=0,file=test.0.1 io 23022 complete: io_u 0x55ed2cd2c000: off=0x1000,len=0x1000,ddir=0,file=test.0.0 io 23022 complete: io_u 0x55ed2cd2c000: off=0x2000,len=0x1000,ddir=0,file=test.0.1 Signed-off-by: Vincent Fu --- filesetup.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/filesetup.c b/filesetup.c index 14c7610849..816d10816e 100644 --- a/filesetup.c +++ b/filesetup.c @@ -1447,9 +1447,8 @@ static void __init_rand_distribution(struct thread_data *td, struct fio_file *f) nranges = (fsize + range_size - 1ULL) / range_size; - seed = jhash(f->file_name, strlen(f->file_name), 0) * td->thread_number; - if (!td->o.rand_repeatable) - seed = td->rand_seeds[FIO_RAND_BLOCK_OFF]; + seed = jhash(f->file_name, strlen(f->file_name), 0) * td->thread_number * + td->rand_seeds[FIO_RAND_BLOCK_OFF]; if (td->o.random_distribution == FIO_RAND_DIST_ZIPF) zipf_init(&f->zipf, nranges, td->o.zipf_theta.u.f, td->o.random_center.u.f, seed); From 9724b4f5ebf0841087c5a56c1d83efe0f4aeb6d7 Mon Sep 17 00:00:00 2001 From: Niklas Cassel Date: Thu, 27 Apr 2023 11:24:23 +0200 Subject: [PATCH 0468/1097] Revert "zbd: Report the zone capacity" This reverts commit 067c18eb3700373d029a9f2795d662453fc09cf4. In the Linux zoned block devices API (blkzoned.h) (and in the NVMe Zoned Namespace Command Set Specification), the zone capacity can be different for each zone (unlike zone size, which has to be the same for all zones). In order to not mislead the user to think that the zone capacity has to be the same for all zones, remove the zone capacity for zone0 from the per device print. The zone capacity can be printed in prints that are related to a specific zone, e.g. when encountering an error when performing I/O to a zone. Signed-off-by: Niklas Cassel Link: https://lore.kernel.org/r/20230427092423.605250-1-nks@flawful.org Signed-off-by: Jens Axboe --- zbd.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zbd.c b/zbd.c index f5fb923ac0..351b3971ff 100644 --- a/zbd.c +++ b/zbd.c @@ -804,8 +804,8 @@ static int parse_zone_info(struct thread_data *td, struct fio_file *f) goto out; } - dprint(FD_ZBD, "Device %s has %d zones of size %"PRIu64" KB and capacity %"PRIu64" KB\n", - f->file_name, nr_zones, zone_size / 1024, zones[0].capacity / 1024); + dprint(FD_ZBD, "Device %s has %d zones of size %"PRIu64" KB\n", + f->file_name, nr_zones, zone_size / 1024); zbd_info = scalloc(1, sizeof(*zbd_info) + (nr_zones + 1) * sizeof(zbd_info->zone_info[0])); From ae7ea7a4dd51ef5be79a21fc5fd65f1a6acbde17 Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Thu, 27 Apr 2023 07:20:53 -0600 Subject: [PATCH 0469/1097] t/io_uring: make submitter_init() return < 0 on error Rather than fudge around with setting some finish variables, have it error it directly and have the callers actually check for that. Signed-off-by: Jens Axboe --- t/io_uring.c | 34 +++++++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/t/io_uring.c b/t/io_uring.c index 6b0efef85c..2f8d63fbe6 100644 --- a/t/io_uring.c +++ b/t/io_uring.c @@ -1049,7 +1049,7 @@ static int submitter_init(struct submitter *s) buf = allocate_mem(s, bs); if (!buf) - return 1; + return -1; s->iovecs[i].iov_base = buf; s->iovecs[i].iov_len = bs; } @@ -1066,7 +1066,7 @@ static int submitter_init(struct submitter *s) } if (err) { printf("queue setup failed: %s, %d\n", strerror(errno), err); - return 1; + return -1; } if (!init_printed) { @@ -1172,9 +1172,15 @@ static void *submitter_aio_fn(void *data) struct iocb *iocbs; struct io_event *events; #ifdef ARCH_HAVE_CPU_CLOCK - int nr_batch = submitter_init(s); -#else - submitter_init(s); + int nr_batch; +#endif + + ret = submitter_init(s); + if (ret < 0) + goto done; + +#ifdef ARCH_HAVE_CPU_CLOCK + nr_batch = ret; #endif iocbsptr = calloc(depth, sizeof(struct iocb *)); @@ -1238,6 +1244,7 @@ static void *submitter_aio_fn(void *data) free(iocbsptr); free(iocbs); free(events); +done: finish = 1; return NULL; } @@ -1277,9 +1284,15 @@ static void *submitter_uring_fn(void *data) struct io_sq_ring *ring = &s->sq_ring; int ret, prepped; #ifdef ARCH_HAVE_CPU_CLOCK - int nr_batch = submitter_init(s); -#else - submitter_init(s); + int nr_batch; +#endif + + ret = submitter_init(s); + if (ret < 0) + goto done; + +#ifdef ARCH_HAVE_CPU_CLOCK + nr_batch = ret; #endif if (register_ring) @@ -1383,6 +1396,7 @@ static void *submitter_uring_fn(void *data) if (register_ring) io_uring_unregister_ring(s); +done: finish = 1; return NULL; } @@ -1393,7 +1407,8 @@ static void *submitter_sync_fn(void *data) struct submitter *s = data; int ret; - submitter_init(s); + if (submitter_init(s) < 0) + goto done; do { uint64_t offset; @@ -1429,6 +1444,7 @@ static void *submitter_sync_fn(void *data) add_stat(s, s->clock_index, 1); } while (!s->finish); +done: finish = 1; return NULL; } From 89d088800832668acdf9c28bca9f0818065e5abe Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Fri, 21 Apr 2023 18:56:59 +0000 Subject: [PATCH 0470/1097] ci: add Windows Cygwin and msys2 builds to GitHub Actions AppVeyor will only run builds one at a time whereas GitHub Actions will run all builds in parallel. Signed-off-by: Vincent Fu --- .github/workflows/ci.yml | 75 +++++++++++++++++++++++++++++++++++++--- ci/actions-build.sh | 12 ++++++- ci/actions-install.sh | 4 +++ ci/common.sh | 2 +- 4 files changed, 87 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4bc91d3ed7..ebe7b58a73 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,6 +16,9 @@ jobs: - macos - linux-i686-gcc - android + - windows-cygwin-64 + - windows-cygwin-32 + - windows-msys2-64 include: - build: linux-gcc os: ubuntu-22.04 @@ -31,6 +34,22 @@ jobs: - build: android os: ubuntu-22.04 arch: aarch64-linux-android32 + - build: windows-cygwin-64 + os: windows-latest + arch: x86_64 + installer_arch: x64 + shell: bash + - build: windows-cygwin-32 + os: windows-latest + arch: i686 + installer_arch: x86 + shell: bash + - build: windows-msys2-64 + os: windows-latest + cc: clang + arch: x86_64 + installer_arch: x64 + shell: msys2 env: CI_TARGET_BUILD: ${{ matrix.build }} @@ -38,13 +57,61 @@ jobs: CC: ${{ matrix.cc }} steps: + - name: git config line endings (Windows) + if: ${{ contains( matrix.build, 'windows' ) }} + run: git config --global core.autocrlf input - name: Checkout repo uses: actions/checkout@v3 + - name: Install Cygwin toolchain (Windows) + if: ${{ startsWith(matrix.build, 'windows-cygwin') }} + uses: cygwin/cygwin-install-action@master + with: + packages: > + mingw64-${{matrix.arch}}-binutils + mingw64-${{matrix.arch}}-CUnit + mingw64-${{matrix.arch}}-curl + mingw64-${{matrix.arch}}-dlfcn + mingw64-${{matrix.arch}}-gcc-core + mingw64-${{matrix.arch}}-headers + mingw64-${{matrix.arch}}-runtime + mingw64-${{matrix.arch}}-zlib + + - name: Install msys2 toolchain (Windows) + if: ${{ startsWith(matrix.build, 'windows-msys2') }} + uses: msys2/setup-msys2@v2 + with: + install: > + git + base-devel + mingw-w64-${{matrix.arch}}-clang + mingw-w64-${{matrix.arch}}-cunit + mingw-w64-${{matrix.arch}}-toolchain + mingw-w64-${{matrix.arch}}-lld + mingw-w64-${{matrix.arch}}-python-scipy + mingw-w64-${{matrix.arch}}-python-six + mingw-w64-${{matrix.arch}}-python-statsmodels + mingw-w64-${{matrix.arch}}-python-sphinx + - name: Install dependencies - run: ./ci/actions-install.sh + run: ${{matrix.shell}} ./ci/actions-install.sh + if: ${{ !contains( matrix.build, 'msys2' ) }} - name: Build - run: ./ci/actions-build.sh + run: ${{matrix.shell}} ./ci/actions-build.sh + - name: Build installer (Windows) + if: ${{ contains( matrix.build, 'windows' ) }} + shell: cmd + run: | + cd os\windows + dobuild.cmd ${{ matrix.installer_arch }} + cd ..\.. + + - name: Upload installer (Windows) + if: ${{ contains( matrix.build, 'windows' ) }} + uses: actions/upload-artifact@v3 + with: + name: ${{ matrix.build }}-installer + path: os\windows\*.msi - name: Smoke test - run: ./ci/actions-smoke-test.sh + run: ${{matrix.shell}} ./ci/actions-smoke-test.sh - name: Full test - run: ./ci/actions-full-test.sh + run: ${{matrix.shell}} ./ci/actions-full-test.sh diff --git a/ci/actions-build.sh b/ci/actions-build.sh index 2b3de8e3da..7146fb592b 100755 --- a/ci/actions-build.sh +++ b/ci/actions-build.sh @@ -41,7 +41,17 @@ main() { ) ;; esac - ;; + ;; + */windows) + configure_flags+=("--disable-native") + case "${CI_TARGET_ARCH}" in + "i686") + configure_flags+=("--build-32bit-win") + ;; + "x86_64") + ;; + esac + ;; esac configure_flags+=(--extra-cflags="${extra_cflags}") diff --git a/ci/actions-install.sh b/ci/actions-install.sh index fb3bd141c8..39395de8b7 100755 --- a/ci/actions-install.sh +++ b/ci/actions-install.sh @@ -89,6 +89,10 @@ install_macos() { pip3 install scipy six statsmodels } +install_windows() { + pip3 install scipy six statsmodels sphinx +} + main() { if [ "${CI_TARGET_BUILD}" = "android" ]; then echo "Installing Android NDK..." diff --git a/ci/common.sh b/ci/common.sh index 8861f843f0..3cf6a4169f 100644 --- a/ci/common.sh +++ b/ci/common.sh @@ -15,7 +15,7 @@ function set_ci_target_os { darwin*) CI_TARGET_OS="macos" ;; - msys*) + cygwin|msys*) CI_TARGET_OS="windows" ;; bsd*) From 8b60e320760fae4859c269e543dadd41c875e8ec Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Wed, 26 Apr 2023 22:04:34 +0000 Subject: [PATCH 0471/1097] ci: work around for GitHub Actions Cygwin sed issue gcc and other compilers create dependency files that are included in the Makefile for builds subsequent to the first build after a clean checkout. Dependency files produced by gcc look like this: parse.o: parse.c config-host.h compiler/compiler.h parse.h flist.h \ debug.h lib/types.h log.h lib/output_buffer.h options.h optgroup.h \ minmax.h lib/ieee754.h lib/pow2.h lib/types.h The Makefile rule for .o files manipulates the gcc dependencies file to look like this: parse.o: parse.c config-host.h compiler/compiler.h parse.h flist.h \ debug.h lib/types.h log.h lib/output_buffer.h options.h optgroup.h \ minmax.h lib/ieee754.h lib/pow2.h lib/types.h parse.c: config-host.h: compiler/compiler.h: parse.h: flist.h: debug.h: lib/types.h: log.h: lib/output_buffer.h: options.h: optgroup.h: minmax.h: lib/ieee754.h: lib/pow2.h: lib/types.h: For some reason the GitHub Actions Windows Cygwin sed does not execute -e 's/\\$$//' properly to remove the backslashes at the end of each line. On this platform the dependencies file after processing looks like: parse.o: parse.c config-host.h compiler/compiler.h parse.h flist.h \ debug.h lib/types.h log.h lib/output_buffer.h options.h optgroup.h \ minmax.h lib/ieee754.h lib/pow2.h lib/types.h parse.c: config-host.h: compiler/compiler.h: parse.h: flist.h: \: debug.h: lib/types.h: log.h: lib/output_buffer.h: options.h: optgroup.h: \: minmax.h: lib/ieee754.h: lib/pow2.h: lib/types.h: Once these dependency files are created subsequent invocations of make (i.e., 'make test') produce Makefile parsing errors like: parse.d:9: *** missing separator. Stop. All of this works fine on GitHub Actions msys2 and AppVeyor's Cygwin and msys2. Work around this problem by deleting the dependencies files before running the tests. For more details describing the processing of dependency files see https://scottmcpeak.com/autodepend/autodepend.html Signed-off-by: Vincent Fu --- .github/workflows/ci.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ebe7b58a73..06e03ce3e6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -111,6 +111,10 @@ jobs: with: name: ${{ matrix.build }}-installer path: os\windows\*.msi + - name: Remove dependency files to resolve Makefile Cygwin sed issue (Windows) + if: ${{ startsWith(matrix.build, 'windows-cygwin') }} + run: rm *.d */*.d */*/*.d + shell: bash - name: Smoke test run: ${{matrix.shell}} ./ci/actions-smoke-test.sh - name: Full test From bea469e86ba2bd4a5b6a5af18455b0b01da2b67b Mon Sep 17 00:00:00 2001 From: Anuj Gupta Date: Fri, 28 Apr 2023 01:14:15 +0530 Subject: [PATCH 0472/1097] t/io_uring: avoid null-ptr dereference in case setup_ring fails s->sq_ring.ring_entries and s->cq_ring.ring_entries will be NULL, incase setup_ring fails. This will cause a segmentation fault. Avoid dereferencing them in such a scenario. Signed-off-by: Anuj Gupta Link: https://lore.kernel.org/r/20230427194415.1160701-1-anuj20.g@samsung.com Signed-off-by: Jens Axboe --- t/io_uring.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/t/io_uring.c b/t/io_uring.c index 2f8d63fbe6..bf0aa26ece 100644 --- a/t/io_uring.c +++ b/t/io_uring.c @@ -1059,7 +1059,8 @@ static int submitter_init(struct submitter *s) err = 0; } else if (!aio) { err = setup_ring(s); - sprintf(buf, "Engine=io_uring, sq_ring=%d, cq_ring=%d\n", *s->sq_ring.ring_entries, *s->cq_ring.ring_entries); + if (!err) + sprintf(buf, "Engine=io_uring, sq_ring=%d, cq_ring=%d\n", *s->sq_ring.ring_entries, *s->cq_ring.ring_entries); } else { sprintf(buf, "Engine=aio\n"); err = setup_aio(s); From d97050594b6f94f20288d4f81846b4d08207273a Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Thu, 27 Apr 2023 12:57:00 -0700 Subject: [PATCH 0473/1097] Detect ASharedMemory_create() support Make linking with the android library optional. Signed-off-by: Bart Van Assche --- configure | 20 ++++++++++++++++++++ os/os-ashmem.h | 4 ++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/configure b/configure index abb6d01649..ca03350b47 100755 --- a/configure +++ b/configure @@ -1345,6 +1345,23 @@ if compile_prog "" "" "sync_file_range"; then fi print_config "sync_file_range" "$sync_file_range" +########################################## +# ASharedMemory_create() probe +if test "$ASharedMemory_create" != "yes" ; then + ASharedMemory_create="no" +fi +cat > $TMPC << EOF +#include +int main(int argc, char **argv) +{ + return ASharedMemory_create("", 0); +} +EOF +if compile_prog "" "" "ASharedMemory_create"; then + ASharedMemory_create="yes" +fi +print_config "ASharedMemory_create" "$ASharedMemory_create" + ########################################## # ext4 move extent probe if test "$ext4_me" != "yes" ; then @@ -3011,6 +3028,9 @@ fi if test "$sync_file_range" = "yes" ; then output_sym "CONFIG_SYNC_FILE_RANGE" fi +if test "$ASharedMemory_create" = "yes" ; then + output_sym "CONFIG_ASHAREDMEMORY_CREATE" +fi if test "$sfaa" = "yes" ; then output_sym "CONFIG_SFAA" fi diff --git a/os/os-ashmem.h b/os/os-ashmem.h index c34ff656cc..80eab7c4e1 100644 --- a/os/os-ashmem.h +++ b/os/os-ashmem.h @@ -6,7 +6,7 @@ #include #include #include -#if __ANDROID_API__ >= __ANDROID_API_O__ +#ifdef CONFIG_ASHAREDMEMORY_CREATE #include #else #define ASHMEM_DEVICE "/dev/ashmem" @@ -27,7 +27,7 @@ static inline int shmctl(int __shmid, int __cmd, struct shmid_ds *__buf) return ret; } -#if __ANDROID_API__ >= __ANDROID_API_O__ +#ifdef CONFIG_ASHAREDMEMORY_CREATE static inline int shmget(key_t __key, size_t __size, int __shmflg) { char keybuf[11]; From 9b3cc2ddc20d84089c65a397e3aedd952270dc2b Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Thu, 27 Apr 2023 12:57:00 -0700 Subject: [PATCH 0474/1097] ci: Also test the Android recovery environment The android library is not available in the Android recovery environment. Add a CI test for building and linking fio without the android library. Signed-off-by: Bart Van Assche --- .github/workflows/ci.yml | 3 +++ ci/actions-build.sh | 6 ++++-- ci/actions-full-test.sh | 5 ++++- ci/actions-install.sh | 14 ++++++++------ ci/actions-smoke-test.sh | 5 ++++- 5 files changed, 23 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 06e03ce3e6..dd2997f075 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,6 +34,9 @@ jobs: - build: android os: ubuntu-22.04 arch: aarch64-linux-android32 + - build: android-recovery + os: ubuntu-22.04 + arch: aarch64-linux-android32 - build: windows-cygwin-64 os: windows-latest arch: x86_64 diff --git a/ci/actions-build.sh b/ci/actions-build.sh index 7146fb592b..351b8d18aa 100755 --- a/ci/actions-build.sh +++ b/ci/actions-build.sh @@ -12,7 +12,7 @@ main() { set_ci_target_os case "${CI_TARGET_BUILD}/${CI_TARGET_OS}" in - android/*) + android*/*) export UNAME=Android if [ -z "${CI_TARGET_ARCH}" ]; then echo "Error: CI_TARGET_ARCH has not been set" @@ -20,7 +20,9 @@ main() { fi NDK=$PWD/android-ndk-r24/toolchains/llvm/prebuilt/linux-x86_64/bin export PATH="${NDK}:${PATH}" - export LIBS="-landroid" + if [ "${CI_TARGET_BUILD}" = "android" ]; then + export LIBS="-landroid" + fi CC=${NDK}/${CI_TARGET_ARCH}-clang if [ ! -e "${CC}" ]; then echo "Error: could not find ${CC}" diff --git a/ci/actions-full-test.sh b/ci/actions-full-test.sh index d1675f6eb2..d2fb4201ae 100755 --- a/ci/actions-full-test.sh +++ b/ci/actions-full-test.sh @@ -3,7 +3,10 @@ set -eu main() { - [ "${CI_TARGET_BUILD}" = android ] && return 0 + case "${CI_TARGET_BUILD}" in + android*) + return 0;; + esac echo "Running long running tests..." export PYTHONUNBUFFERED="TRUE" diff --git a/ci/actions-install.sh b/ci/actions-install.sh index 39395de8b7..0d73ac97e2 100755 --- a/ci/actions-install.sh +++ b/ci/actions-install.sh @@ -94,12 +94,14 @@ install_windows() { } main() { - if [ "${CI_TARGET_BUILD}" = "android" ]; then - echo "Installing Android NDK..." - wget --quiet https://dl.google.com/android/repository/android-ndk-r24-linux.zip - unzip -q android-ndk-r24-linux.zip - return 0 - fi + case "${CI_TARGET_BUILD}" in + android*) + echo "Installing Android NDK..." + wget --quiet https://dl.google.com/android/repository/android-ndk-r24-linux.zip + unzip -q android-ndk-r24-linux.zip + return 0 + ;; + esac set_ci_target_os diff --git a/ci/actions-smoke-test.sh b/ci/actions-smoke-test.sh index 3196f6a1ba..494462ac38 100755 --- a/ci/actions-smoke-test.sh +++ b/ci/actions-smoke-test.sh @@ -3,7 +3,10 @@ set -eu main() { - [ "${CI_TARGET_BUILD}" = "android" ] && return 0 + case "${CI_TARGET_BUILD}" in + android*) + return 0;; + esac echo "Running smoke tests..." make test From a51fe82b7ff91b144a1c87cf3221f4a66c1f54bf Mon Sep 17 00:00:00 2001 From: Denis Pronin Date: Tue, 9 May 2023 12:11:38 +0300 Subject: [PATCH 0475/1097] use 'min' macro to find out next value of actual_min in libaio Signed-off-by: Denis Pronin --- engines/libaio.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engines/libaio.c b/engines/libaio.c index 1b82c90b75..6a0745aad6 100644 --- a/engines/libaio.c +++ b/engines/libaio.c @@ -296,7 +296,7 @@ static int fio_libaio_getevents(struct thread_data *td, unsigned int min, } if (r > 0) { events += r; - actual_min = actual_min > events ? actual_min - events : 0; + actual_min -= min((unsigned int)events, actual_min); } else if ((min && r == 0) || r == -EAGAIN) { fio_libaio_commit(td); From 4eba0c6efaec809007b6a9568d9ec954200609b5 Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Wed, 10 May 2023 08:04:40 -0600 Subject: [PATCH 0476/1097] README: remove reference to the bsdio installer These are no longer being updated, and we do have images on the github page. Remove the reference. Suggested-by: Rebecca Cran Signed-off-by: Jens Axboe --- README.rst | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/README.rst b/README.rst index bcd08ec9ea..8f6208e351 100644 --- a/README.rst +++ b/README.rst @@ -123,12 +123,11 @@ Solaris: ``pkgutil -i fio``. Windows: - Beginning with fio 3.31 Windows installers are available on GitHub at - https://github.com/axboe/fio/releases. Rebecca Cran - has fio packages for Windows at - https://bsdio.com/fio/ . The latest builds for Windows can also be - grabbed from https://ci.appveyor.com/project/axboe/fio by clicking the - latest x86 or x64 build and then selecting the Artifacts tab. + Beginning with fio 3.31 Windows installers are available on GitHub at + https://github.com/axboe/fio/releases. The latest builds for Windows + can also be grabbed from https://ci.appveyor.com/project/axboe/fio by + clicking the latest x86 or x64 build and then selecting the Artifacts + tab. BSDs: Packages for BSDs may be available from their binary package repositories. From 45bf2f40455c1377dafcbe9fd06b960008041783 Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Wed, 10 May 2023 09:16:01 -0600 Subject: [PATCH 0477/1097] t/read-to-pipe-async: remove dead code 'work' will be overwritten shortly, no point in clearing it to NULL. Signed-off-by: Jens Axboe --- t/read-to-pipe-async.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/t/read-to-pipe-async.c b/t/read-to-pipe-async.c index 586e3c95bf..569fc62ae6 100644 --- a/t/read-to-pipe-async.c +++ b/t/read-to-pipe-async.c @@ -247,10 +247,8 @@ static void *writer_fn(void *data) while (!wt->thread.exit || !flist_empty(&wt->list)) { pthread_mutex_lock(&wt->thread.lock); - if (work) { + if (work) flist_add_tail(&work->list, &wt->done_list); - work = NULL; - } work = find_seq(wt, seq); if (work) From 37946bed31b688fe55e2003b6d59ff0c964165bb Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Wed, 10 May 2023 09:16:55 -0600 Subject: [PATCH 0478/1097] engines/rdma: remove dead code We can never reach this branch. Signed-off-by: Jens Axboe --- engines/rdma.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/engines/rdma.c b/engines/rdma.c index ee2844d323..ebdbcb1c80 100644 --- a/engines/rdma.c +++ b/engines/rdma.c @@ -856,8 +856,6 @@ static int fio_rdmaio_commit(struct thread_data *td) ret = fio_rdmaio_send(td, io_us, rd->io_u_queued_nr); else if (!rd->is_client) ret = fio_rdmaio_recv(td, io_us, rd->io_u_queued_nr); - else - ret = 0; /* must be a SYNC */ if (ret > 0) { fio_rdmaio_queued(td, io_us, ret); From 12b609b5637b1b104578cc740d03e205b51dc4fc Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Wed, 10 May 2023 17:29:02 -0400 Subject: [PATCH 0479/1097] t/run-fio-test: fix comment The test job that measures rates is actually t0011. Signed-off-by: Vincent Fu --- t/run-fio-tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/t/run-fio-tests.py b/t/run-fio-tests.py index 4fe6fe46f6..71e3e5a621 100755 --- a/t/run-fio-tests.py +++ b/t/run-fio-tests.py @@ -822,7 +822,7 @@ def check_result(self): self.passed = False class FioJobTest_iops_rate(FioJobTest): - """Test consists of fio test job t0009 + """Test consists of fio test job t0011 Confirm that job0 iops == 1000 and that job1_iops / job0_iops ~ 8 With two runs of fio-3.16 I observed a ratio of 8.3""" From f6f80750f75810bdaf56dd9362982055de1d7232 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Wed, 10 May 2023 20:10:56 -0400 Subject: [PATCH 0480/1097] docs: expand description for interval-based bw and iops statistics Update HOWTO.rst and fio.1 to provide more details about the descriptive statistics interval-based bw and iops measurements. Signed-off-by: Vincent Fu --- HOWTO.rst | 22 +++++++++++++++------- fio.1 | 19 ++++++++++++------- 2 files changed, 27 insertions(+), 14 deletions(-) diff --git a/HOWTO.rst b/HOWTO.rst index 0a6e60c77b..80c08f7ec1 100644 --- a/HOWTO.rst +++ b/HOWTO.rst @@ -4417,15 +4417,23 @@ writes in the example above). In the order listed, they denote: It is the sum of submission and completion latency. **bw** - Bandwidth statistics based on samples. Same names as the xlat stats, - but also includes the number of samples taken (**samples**) and an - approximate percentage of total aggregate bandwidth this thread - received in its group (**per**). This last value is only really - useful if the threads in this group are on the same disk, since they - are then competing for disk access. + Bandwidth statistics based on measurements from discrete + intervals. Fio continuously monitors bytes transferred and I/O + operations completed. By default fio calculates bandwidth in + each half-second interval (see :option:`bwavgtime`) and reports + descriptive statistics for the measurements here. Same names as + the xlat stats, but also includes the number of samples taken + (**samples**) and an approximate percentage of total aggregate + bandwidth this thread received in its group (**per**). This + last value is only really useful if the threads in this group + are on the same disk, since they are then competing for disk + access. **iops** - IOPS statistics based on samples. Same names as bw. + IOPS statistics based on measurements from discrete intervals. + For details see the description for bw above. See + :option:`iopsavgtime` to control the duration of the intervals. + Same values reported here as for bw except for percentage. **lat (nsec/usec/msec)** The distribution of I/O completion latencies. This is the time from when diff --git a/fio.1 b/fio.1 index 4207814b44..e577e2e013 100644 --- a/fio.1 +++ b/fio.1 @@ -4073,15 +4073,20 @@ Total latency. Same names as slat and clat, this denotes the time from when fio created the I/O unit to completion of the I/O operation. .TP .B bw -Bandwidth statistics based on samples. Same names as the xlat stats, -but also includes the number of samples taken (\fIsamples\fR) and an -approximate percentage of total aggregate bandwidth this thread -received in its group (\fIper\fR). This last value is only really -useful if the threads in this group are on the same disk, since they -are then competing for disk access. +Bandwidth statistics based on measurements from discrete intervals. Fio +continuosly monitors bytes transferred and I/O operations completed. By default +fio calculates bandwidth in each half-second interval (see \fBbwavgtime\fR) +and reports descriptive statistics for the measurements here. Same names as the +xlat stats, but also includes the number of samples taken (\fIsamples\fR) and an +approximate percentage of total aggregate bandwidth this thread received in its +group (\fIper\fR). This last value is only really useful if the threads in this +group are on the same disk, since they are then competing for disk access. .TP .B iops -IOPS statistics based on samples. Same names as \fBbw\fR. +IOPS statistics based on measurements from discrete intervals. +For details see the description for \fBbw\fR above. See +\fBiopsavgtime\fR to control the duration of the intervals. +Same values reported here as for \fBbw\fR except for percentage. .TP .B lat (nsec/usec/msec) The distribution of I/O completion latencies. This is the time from when From 01a7d384d4c78f23b03f79e8002e705ea5cbceb4 Mon Sep 17 00:00:00 2001 From: Ankit Kumar Date: Mon, 15 May 2023 16:33:39 +0530 Subject: [PATCH 0481/1097] engines/nvme: support for 64 LBA formats The NVM command set specification 1.0c supports 64 LBA formats. The 0-based nlbaf field specifies the number of LBA formats. The flbas field is used to calculate the current LBA format, in which bit 0-3 indicates lsb and bit 5-6 indicates msb of the format index. Signed-off-by: Ankit Kumar Signed-off-by: Vincent Fu --- engines/nvme.c | 14 +++++++++++++- engines/nvme.h | 3 +-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/engines/nvme.c b/engines/nvme.c index fd2161f363..96a5f0648f 100644 --- a/engines/nvme.c +++ b/engines/nvme.c @@ -99,6 +99,7 @@ int fio_nvme_get_info(struct fio_file *f, __u32 *nsid, __u32 *lba_sz, struct nvme_id_ns ns; int namespace_id; int fd, err; + __u32 format_idx; if (f->filetype != FIO_TYPE_CHAR) { log_err("ioengine io_uring_cmd only works with nvme ns " @@ -131,7 +132,18 @@ int fio_nvme_get_info(struct fio_file *f, __u32 *nsid, __u32 *lba_sz, } *nsid = namespace_id; - *lba_sz = 1 << ns.lbaf[(ns.flbas & 0x0f)].ds; + + /* + * 16 or 64 as maximum number of supported LBA formats. + * From flbas bit 0-3 indicates lsb and bit 5-6 indicates msb + * of the format index used to format the namespace. + */ + if (ns.nlbaf < 16) + format_idx = ns.flbas & 0xf; + else + format_idx = (ns.flbas & 0xf) + (((ns.flbas >> 5) & 0x3) << 4); + + *lba_sz = 1 << ns.lbaf[format_idx].ds; *nlba = ns.nsze; close(fd); diff --git a/engines/nvme.h b/engines/nvme.h index 408594d550..9d6288c86c 100644 --- a/engines/nvme.h +++ b/engines/nvme.h @@ -134,8 +134,7 @@ struct nvme_id_ns { __le16 endgid; __u8 nguid[16]; __u8 eui64[8]; - struct nvme_lbaf lbaf[16]; - __u8 rsvd192[192]; + struct nvme_lbaf lbaf[64]; __u8 vs[3712]; }; From 345fa8fd80e669ffb0b1b8898c2f713279d4814a Mon Sep 17 00:00:00 2001 From: Ankit Kumar Date: Mon, 15 May 2023 16:33:40 +0530 Subject: [PATCH 0482/1097] engines/io_uring_cmd: add extended LBA support The io_uring_cmd ioengine assumes that logical block size is always power of 2. But with namespace formats where metadata is transferred at the end of logical block i.e. an extended logical block this is not true. This patch calculates the correct extended logical block size and uses division operation for start and number of logical block calculation. The existing calculation for power of 2 logical block size will remain same. This also add checks to verify that block sizes are multiple of LBA size. This current implementation however doesn't support protection info and metadata transferred as separate buffer. Return error for those. Signed-off-by: Ankit Kumar Signed-off-by: Vincent Fu [Vincent: edited commit message] --- engines/io_uring.c | 30 ++++++++++++++++++++++---- engines/nvme.c | 52 +++++++++++++++++++++++++++++++++++++--------- engines/nvme.h | 3 ++- 3 files changed, 70 insertions(+), 15 deletions(-) diff --git a/engines/io_uring.c b/engines/io_uring.c index f5ffe9f448..90e5a8566a 100644 --- a/engines/io_uring.c +++ b/engines/io_uring.c @@ -1177,22 +1177,40 @@ static int fio_ioring_cmd_open_file(struct thread_data *td, struct fio_file *f) if (o->cmd_type == FIO_URING_CMD_NVME) { struct nvme_data *data = NULL; unsigned int nsid, lba_size = 0; + __u32 ms = 0; __u64 nlba = 0; int ret; /* Store the namespace-id and lba size. */ data = FILE_ENG_DATA(f); if (data == NULL) { - ret = fio_nvme_get_info(f, &nsid, &lba_size, &nlba); + ret = fio_nvme_get_info(f, &nsid, &lba_size, &ms, &nlba); if (ret) return ret; data = calloc(1, sizeof(struct nvme_data)); data->nsid = nsid; - data->lba_shift = ilog2(lba_size); + if (ms) + data->lba_ext = lba_size + ms; + else + data->lba_shift = ilog2(lba_size); FILE_SET_ENG_DATA(f, data); } + + lba_size = data->lba_ext ? data->lba_ext : (1 << data->lba_shift); + + for_each_rw_ddir(ddir) { + if (td->o.min_bs[ddir] % lba_size || + td->o.max_bs[ddir] % lba_size) { + if (data->lba_ext) + log_err("block size must be a multiple of " + "(LBA data size + Metadata size)\n"); + else + log_err("block size must be a multiple of LBA data size\n"); + return 1; + } + } } if (!ld || !o->registerfiles) return generic_open_file(td, f); @@ -1243,16 +1261,20 @@ static int fio_ioring_cmd_get_file_size(struct thread_data *td, if (o->cmd_type == FIO_URING_CMD_NVME) { struct nvme_data *data = NULL; unsigned int nsid, lba_size = 0; + __u32 ms = 0; __u64 nlba = 0; int ret; - ret = fio_nvme_get_info(f, &nsid, &lba_size, &nlba); + ret = fio_nvme_get_info(f, &nsid, &lba_size, &ms, &nlba); if (ret) return ret; data = calloc(1, sizeof(struct nvme_data)); data->nsid = nsid; - data->lba_shift = ilog2(lba_size); + if (ms) + data->lba_ext = lba_size + ms; + else + data->lba_shift = ilog2(lba_size); f->real_file_size = lba_size * nlba; fio_file_set_size_known(f); diff --git a/engines/nvme.c b/engines/nvme.c index 96a5f0648f..1047ade2b9 100644 --- a/engines/nvme.c +++ b/engines/nvme.c @@ -21,8 +21,13 @@ int fio_nvme_uring_cmd_prep(struct nvme_uring_cmd *cmd, struct io_u *io_u, else return -ENOTSUP; - slba = io_u->offset >> data->lba_shift; - nlb = (io_u->xfer_buflen >> data->lba_shift) - 1; + if (data->lba_ext) { + slba = io_u->offset / data->lba_ext; + nlb = (io_u->xfer_buflen / data->lba_ext) - 1; + } else { + slba = io_u->offset >> data->lba_shift; + nlb = (io_u->xfer_buflen >> data->lba_shift) - 1; + } /* cdw10 and cdw11 represent starting lba */ cmd->cdw10 = slba & 0xffffffff; @@ -65,8 +70,13 @@ int fio_nvme_trim(const struct thread_data *td, struct fio_file *f, struct nvme_dsm_range dsm; int ret; - dsm.nlb = (len >> data->lba_shift); - dsm.slba = (offset >> data->lba_shift); + if (data->lba_ext) { + dsm.nlb = len / data->lba_ext; + dsm.slba = offset / data->lba_ext; + } else { + dsm.nlb = len >> data->lba_shift; + dsm.slba = offset >> data->lba_shift; + } ret = nvme_trim(f->fd, data->nsid, 1, sizeof(struct nvme_dsm_range), &dsm); @@ -94,7 +104,7 @@ static int nvme_identify(int fd, __u32 nsid, enum nvme_identify_cns cns, } int fio_nvme_get_info(struct fio_file *f, __u32 *nsid, __u32 *lba_sz, - __u64 *nlba) + __u32 *ms, __u64 *nlba) { struct nvme_id_ns ns; int namespace_id; @@ -114,9 +124,8 @@ int fio_nvme_get_info(struct fio_file *f, __u32 *nsid, __u32 *lba_sz, namespace_id = ioctl(fd, NVME_IOCTL_ID); if (namespace_id < 0) { err = -errno; - log_err("failed to fetch namespace-id"); - close(fd); - return err; + log_err("%s: failed to fetch namespace-id\n", f->file_name); + goto out; } /* @@ -126,7 +135,8 @@ int fio_nvme_get_info(struct fio_file *f, __u32 *nsid, __u32 *lba_sz, err = nvme_identify(fd, namespace_id, NVME_IDENTIFY_CNS_NS, NVME_CSI_NVM, &ns); if (err) { - log_err("failed to fetch identify namespace\n"); + log_err("%s: failed to fetch identify namespace\n", + f->file_name); close(fd); return err; } @@ -144,10 +154,32 @@ int fio_nvme_get_info(struct fio_file *f, __u32 *nsid, __u32 *lba_sz, format_idx = (ns.flbas & 0xf) + (((ns.flbas >> 5) & 0x3) << 4); *lba_sz = 1 << ns.lbaf[format_idx].ds; + + /* + * Only extended LBA can be supported. + * Bit 4 for flbas indicates if metadata is transferred at the end of + * logical block creating an extended LBA. + */ + *ms = le16_to_cpu(ns.lbaf[format_idx].ms); + if (*ms && !((ns.flbas >> 4) & 0x1)) { + log_err("%s: only extended logical block can be supported\n", + f->file_name); + err = -ENOTSUP; + goto out; + } + + /* Check for end to end data protection support */ + if (ns.dps & 0x3) { + log_err("%s: end to end data protection not supported\n", + f->file_name); + err = -ENOTSUP; + goto out; + } *nlba = ns.nsze; +out: close(fd); - return 0; + return err; } int fio_nvme_get_zoned_model(struct thread_data *td, struct fio_file *f, diff --git a/engines/nvme.h b/engines/nvme.h index 9d6288c86c..f7cb820d9a 100644 --- a/engines/nvme.h +++ b/engines/nvme.h @@ -88,6 +88,7 @@ enum nvme_zns_zs { struct nvme_data { __u32 nsid; __u32 lba_shift; + __u32 lba_ext; }; struct nvme_lbaf { @@ -222,7 +223,7 @@ int fio_nvme_iomgmt_ruhs(struct thread_data *td, struct fio_file *f, struct nvme_fdp_ruh_status *ruhs, __u32 bytes); int fio_nvme_get_info(struct fio_file *f, __u32 *nsid, __u32 *lba_sz, - __u64 *nlba); + __u32 *ms, __u64 *nlba); int fio_nvme_uring_cmd_prep(struct nvme_uring_cmd *cmd, struct io_u *io_u, struct iovec *iov); From 83b2d4b78374055c3a2261136eedf03b5fbfc335 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Mon, 15 May 2023 08:51:27 -0400 Subject: [PATCH 0483/1097] ci: stop testing Linux 32-bit builds The GitHub Actions image has had a longstanding problem with the dependencies required for 32-bit builds. The current work around no longer fixes the issue. Stop testing 32-bit builds to avoid false positive failures with our CI testing. We can re-enable 32-bit builds when this issue is fixed upstream. Details: https://github.com/actions/runner-images/issues/4589 Signed-off-by: Vincent Fu --- .github/workflows/ci.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dd2997f075..8325a3d9fa 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,6 @@ jobs: - linux-gcc - linux-clang - macos - - linux-i686-gcc - android - windows-cygwin-64 - windows-cygwin-32 @@ -28,9 +27,6 @@ jobs: cc: clang - build: macos os: macos-12 - - build: linux-i686-gcc - os: ubuntu-22.04 - arch: i686 - build: android os: ubuntu-22.04 arch: aarch64-linux-android32 From be42eadd18fad2569dfc6517940db8bbe2469f6d Mon Sep 17 00:00:00 2001 From: Ankit Kumar Date: Tue, 16 May 2023 17:47:17 +0530 Subject: [PATCH 0484/1097] engines/io_uring: fix coverity issue *** CID 455020: Integer handling issues (BAD_SHIFT) /engines/io_uring.c: 1201 in fio_ioring_cmd_open_file() In expression "1 << data->lba_shift", left shifting by more than 31 bits has undefined behavior. The shift amount, "data->lba_shift", is 4294967295 Fixes: 345fa8f ("engines/io_uring_cmd: add extended LBA support") Signed-off-by: Ankit Kumar Link: https://lore.kernel.org/r/20230516121717.28508-2-ankit.kumar@samsung.com Signed-off-by: Jens Axboe --- engines/io_uring.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/engines/io_uring.c b/engines/io_uring.c index 90e5a8566a..ff64fc9fbf 100644 --- a/engines/io_uring.c +++ b/engines/io_uring.c @@ -1198,7 +1198,8 @@ static int fio_ioring_cmd_open_file(struct thread_data *td, struct fio_file *f) FILE_SET_ENG_DATA(f, data); } - lba_size = data->lba_ext ? data->lba_ext : (1 << data->lba_shift); + assert(data->lba_shift < 32); + lba_size = data->lba_ext ? data->lba_ext : (1U << data->lba_shift); for_each_rw_ddir(ddir) { if (td->o.min_bs[ddir] % lba_size || From afb34fb175d1a2de35120807feb6f5af403c581a Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Wed, 17 May 2023 14:21:59 +0000 Subject: [PATCH 0485/1097] docs: move rate_cycle description Move the description for rate_cycle from the I/O latency section to the I/O rate section. Signed-off-by: Vincent Fu --- HOWTO.rst | 10 +++++----- fio.1 | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/HOWTO.rst b/HOWTO.rst index 80c08f7ec1..8d4c6e1cca 100644 --- a/HOWTO.rst +++ b/HOWTO.rst @@ -3203,6 +3203,11 @@ I/O rate fio will ignore the thinktime and continue doing IO at the specified rate, instead of entering a catch-up mode after thinktime is done. +.. option:: rate_cycle=int + + Average bandwidth for :option:`rate` and :option:`rate_min` over this number + of milliseconds. Defaults to 1000. + I/O latency ~~~~~~~~~~~ @@ -3241,11 +3246,6 @@ I/O latency microseconds. Comma-separated values may be specified for reads, writes, and trims as described in :option:`blocksize`. -.. option:: rate_cycle=int - - Average bandwidth for :option:`rate` and :option:`rate_min` over this number - of milliseconds. Defaults to 1000. - I/O replay ~~~~~~~~~~ diff --git a/fio.1 b/fio.1 index e577e2e013..a276f1d653 100644 --- a/fio.1 +++ b/fio.1 @@ -2946,6 +2946,10 @@ By default, fio will attempt to catch up to the specified rate setting, if any kind of thinktime setting was used. If this option is set, then fio will ignore the thinktime and continue doing IO at the specified rate, instead of entering a catch-up mode after thinktime is done. +.TP +.BI rate_cycle \fR=\fPint +Average bandwidth for \fBrate\fR and \fBrate_min\fR over this number +of milliseconds. Defaults to 1000. .SS "I/O latency" .TP .BI latency_target \fR=\fPtime @@ -2975,10 +2979,6 @@ If set, fio will exit the job with an ETIMEDOUT error if it exceeds this maximum latency. When the unit is omitted, the value is interpreted in microseconds. Comma-separated values may be specified for reads, writes, and trims as described in \fBblocksize\fR. -.TP -.BI rate_cycle \fR=\fPint -Average bandwidth for \fBrate\fR and \fBrate_min\fR over this number -of milliseconds. Defaults to 1000. .SS "I/O replay" .TP .BI write_iolog \fR=\fPstr From 899e057eb8b6a85fabeafc1e12458b374db6c89e Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Wed, 17 May 2023 14:25:02 +0000 Subject: [PATCH 0486/1097] docs: move experimental_verify description Move the description for experimental_verify next to the other verify-related options. Signed-off-by: Vincent Fu --- HOWTO.rst | 14 +++++++------- fio.1 | 10 +++++----- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/HOWTO.rst b/HOWTO.rst index 8d4c6e1cca..9b69eaee4a 100644 --- a/HOWTO.rst +++ b/HOWTO.rst @@ -3761,6 +3761,13 @@ Verification verification pass, according to the settings in the job file used. Default false. +.. option:: experimental_verify=bool + + Enable experimental verification. Standard verify records I/O metadata + for later use during the verification phase. Experimental verify + instead resets the file after the write phase and then replays I/Os for + the verification phase. + .. option:: trim_percentage=int Number of verify blocks to discard/trim. @@ -3777,13 +3784,6 @@ Verification Trim this number of I/O blocks. -.. option:: experimental_verify=bool - - Enable experimental verification. Standard verify records I/O metadata - for later use during the verification phase. Experimental verify - instead resets the file after the write phase and then replays I/Os for - the verification phase. - Steady state ~~~~~~~~~~~~ diff --git a/fio.1 b/fio.1 index a276f1d653..ba0272ce2f 100644 --- a/fio.1 +++ b/fio.1 @@ -3475,6 +3475,11 @@ far it should verify. Without this information, fio will run a full verification pass, according to the settings in the job file used. Default false. .TP +.BI experimental_verify \fR=\fPbool +Enable experimental verification. Standard verify records I/O metadata for +later use during the verification phase. Experimental verify instead resets the +file after the write phase and then replays I/Os for the verification phase. +.TP .BI trim_percentage \fR=\fPint Number of verify blocks to discard/trim. .TP @@ -3486,11 +3491,6 @@ Verify that trim/discarded blocks are returned as zeros. .TP .BI trim_backlog_batch \fR=\fPint Trim this number of I/O blocks. -.TP -.BI experimental_verify \fR=\fPbool -Enable experimental verification. Standard verify records I/O metadata for -later use during the verification phase. Experimental verify instead resets the -file after the write phase and then replays I/Os for the verification phase. .SS "Steady state" .TP .BI steadystate \fR=\fPstr:float "\fR,\fP ss" \fR=\fPstr:float From a64fd9c7994b51039b2fde851579c2453ddb35c0 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Wed, 17 May 2023 14:44:49 +0000 Subject: [PATCH 0487/1097] docs: document no_completion_thread Describe in the HOWTO and man page the windowsaio ioengine no_completion_thread option. Signed-off-by: Vincent Fu --- HOWTO.rst | 4 ++++ fio.1 | 3 +++ 2 files changed, 7 insertions(+) diff --git a/HOWTO.rst b/HOWTO.rst index 9b69eaee4a..32fff5ecbd 100644 --- a/HOWTO.rst +++ b/HOWTO.rst @@ -3011,6 +3011,10 @@ with the caveat that when used on the command line, they must come after the performance. The default is to enable it only if :option:`libblkio_wait_mode=eventfd `. +.. option:: no_completion_thread : [windowsaio] + + Avoid using a separate thread for completion polling. + I/O depth ~~~~~~~~~ diff --git a/fio.1 b/fio.1 index ba0272ce2f..80bf3371a3 100644 --- a/fio.1 +++ b/fio.1 @@ -2765,6 +2765,9 @@ Use a busy loop with a non-blocking call to \fBblkioq_do_io()\fR. Enable the queue's completion eventfd even when unused. This may impact performance. The default is to enable it only if \fBlibblkio_wait_mode=eventfd\fR. +.TP +.BI (windowsaio)no_completion_thread +Avoid using a separate thread for completion polling. .SS "I/O depth" .TP .BI iodepth \fR=\fPint From e448d863c2fbf7036126e2f222ce7351ab3750ba Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Thu, 18 May 2023 13:23:49 -0400 Subject: [PATCH 0488/1097] Revert "ci: stop testing Linux 32-bit builds" This reverts commit 83b2d4b78374055c3a2261136eedf03b5fbfc335. There now seems to be a solution to the 32-bit GitHub Actions Ubuntu breakage. Signed-off-by: Vincent Fu --- .github/workflows/ci.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8325a3d9fa..dd2997f075 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,6 +14,7 @@ jobs: - linux-gcc - linux-clang - macos + - linux-i686-gcc - android - windows-cygwin-64 - windows-cygwin-32 @@ -27,6 +28,9 @@ jobs: cc: clang - build: macos os: macos-12 + - build: linux-i686-gcc + os: ubuntu-22.04 + arch: i686 - build: android os: ubuntu-22.04 arch: aarch64-linux-android32 From 5cedafafeeb9dde862455342cd24c860d84f4f07 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Thu, 18 May 2023 13:25:07 -0400 Subject: [PATCH 0489/1097] ci: fix ups for 32-bit GitHub Actions Linux builds Fix the dependency install problem for 32-bit builds by explicitly installing libc6:i386 and libgcc-s1:i386 as mentioned in the link below. https://github.com/actions/runner-images/issues/4589#issuecomment-1552409942 Deleting microsoft-prod.list and adding --allow-downgrades were not needed for our use case. Signed-off-by: Vincent Fu --- ci/actions-install.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ci/actions-install.sh b/ci/actions-install.sh index 0d73ac97e2..95241e7882 100755 --- a/ci/actions-install.sh +++ b/ci/actions-install.sh @@ -35,6 +35,8 @@ DPKGCFG gcc-multilib pkg-config:i386 zlib1g-dev:i386 + libc6:i386 + libgcc-s1:i386 ) ;; "x86_64") From 9496e3985244e054b6e9a50903a2fdc89a76d6ab Mon Sep 17 00:00:00 2001 From: Jingyun Hua Date: Fri, 19 May 2023 17:14:32 +0800 Subject: [PATCH 0490/1097] Add LoongArch64 support Signed-off-by: Jingyun Hua --- arch/arch-loongarch64.h | 10 ++++++++++ arch/arch.h | 3 +++ configure | 4 +++- libfio.c | 1 + os/os-linux-syscall.h | 16 ++++++++++++++++ 5 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 arch/arch-loongarch64.h diff --git a/arch/arch-loongarch64.h b/arch/arch-loongarch64.h new file mode 100644 index 0000000000..43ea83b436 --- /dev/null +++ b/arch/arch-loongarch64.h @@ -0,0 +1,10 @@ +#ifndef ARCH_LOONGARCH64_H +#define ARCH_LOONGARCH64_H + +#define FIO_ARCH (arch_loongarch64) + +#define read_barrier() __asm__ __volatile__("dbar 0": : :"memory") +#define write_barrier() __asm__ __volatile__("dbar 0": : :"memory") +#define nop __asm__ __volatile__("dbar 0": : :"memory") + +#endif diff --git a/arch/arch.h b/arch/arch.h index fca003beab..6e476701b5 100644 --- a/arch/arch.h +++ b/arch/arch.h @@ -23,6 +23,7 @@ enum { arch_hppa, arch_mips, arch_aarch64, + arch_loongarch64, arch_generic, @@ -97,6 +98,8 @@ extern unsigned long arch_flags; #include "arch-hppa.h" #elif defined(__aarch64__) #include "arch-aarch64.h" +#elif defined(__loongarch64) +#include "arch-loongarch64.h" #else #warning "Unknown architecture, attempting to use generic model." #include "arch-generic.h" diff --git a/configure b/configure index ca03350b47..74416fd48b 100755 --- a/configure +++ b/configure @@ -499,13 +499,15 @@ elif check_define __aarch64__ ; then cpu="aarch64" elif check_define __hppa__ ; then cpu="hppa" +elif check_define __loongarch64 ; then + cpu="loongarch64" else cpu=`uname -m` fi # Normalise host CPU name and set ARCH. case "$cpu" in - ia64|ppc|ppc64|s390|s390x|sparc64) + ia64|ppc|ppc64|s390|s390x|sparc64|loongarch64) cpu="$cpu" ;; i386|i486|i586|i686|i86pc|BePC) diff --git a/libfio.c b/libfio.c index ddd49cd76d..5e3fd30b71 100644 --- a/libfio.c +++ b/libfio.c @@ -74,6 +74,7 @@ static const char *fio_arch_strings[arch_nr] = { "hppa", "mips", "aarch64", + "loongarch64", "generic" }; diff --git a/os/os-linux-syscall.h b/os/os-linux-syscall.h index c399b2fa99..67ee4d9109 100644 --- a/os/os-linux-syscall.h +++ b/os/os-linux-syscall.h @@ -270,6 +270,22 @@ #define __NR_ioprio_get 31 #endif +/* Linux syscalls for loongarch64 */ +#elif defined(ARCH_LOONGARCH64_H) +#ifndef __NR_ioprio_set +#define __NR_ioprio_set 30 +#define __NR_ioprio_get 31 +#endif + +#ifndef __NR_fadvise64 +#define __NR_fadvise64 223 +#endif + +#ifndef __NR_sys_splice +#define __NR_sys_splice 76 +#define __NR_sys_tee 77 +#define __NR_sys_vmsplice 75 +#endif #else #warning "Unknown architecture" #endif From 362ce0375b191549b7a91b592ccd5e9002c6aea3 Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Fri, 19 May 2023 09:38:56 -0700 Subject: [PATCH 0491/1097] zbd: Make an error message more detailed Knowing which part of a zone report is invalid is useful when debugging firmware of zoned devices. Hence report the number of zones that have been parsed successfully when reporting a report zones error. Signed-off-by: Bart Van Assche --- zbd.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zbd.c b/zbd.c index 351b3971ff..5f6b9ef4c5 100644 --- a/zbd.c +++ b/zbd.c @@ -213,8 +213,8 @@ static int zbd_report_zones(struct thread_data *td, struct fio_file *f, ret = blkzoned_report_zones(td, f, offset, zones, nr_zones); if (ret < 0) { td_verror(td, errno, "report zones failed"); - log_err("%s: report zones from sector %"PRIu64" failed (%d).\n", - f->file_name, offset >> 9, errno); + log_err("%s: report zones from sector %"PRIu64" failed (nr_zones=%d; errno=%d).\n", + f->file_name, offset >> 9, nr_zones, errno); } else if (ret == 0) { td_verror(td, errno, "Empty zone report"); log_err("%s: report zones from sector %"PRIu64" is empty.\n", From 870ea00243b1290541334bec2a56428c9f68dba6 Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Fri, 19 May 2023 19:30:38 -0600 Subject: [PATCH 0492/1097] io_ur: make sure that sync errors are noticed upfront This could probably be cleaner in the error handling, but jump to the normal error handling case for ddir_sync() as well. Fixes: https://github.com/axboe/fio/issues/1577 Signed-off-by: Jens Axboe --- io_u.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/io_u.c b/io_u.c index 30265cfb4a..6f5fc94d9a 100644 --- a/io_u.c +++ b/io_u.c @@ -2027,6 +2027,8 @@ static void io_completed(struct thread_data *td, struct io_u **io_u_ptr, } if (ddir_sync(ddir)) { + if (io_u->error) + goto error; td->last_was_sync = true; if (f) { f->first_write = -1ULL; @@ -2082,6 +2084,7 @@ static void io_completed(struct thread_data *td, struct io_u **io_u_ptr, icd->error = ret; } } else if (io_u->error) { +error: icd->error = io_u->error; io_u_log_error(td, io_u); } From 04f9090b68e7350f25873387ab62738b667c9201 Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Wed, 22 Feb 2023 13:54:18 -0800 Subject: [PATCH 0493/1097] zbd: Report the zone capacity The zone capacity is important information. Hence report the zone capacity if it is identical for all zones and if ZBD debugging is enabled. Signed-off-by: Bart Van Assche --- zbd.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/zbd.c b/zbd.c index 5f6b9ef4c5..5f1a7d7f3e 100644 --- a/zbd.c +++ b/zbd.c @@ -776,7 +776,8 @@ static int parse_zone_info(struct thread_data *td, struct fio_file *f) int nr_zones, nrz; struct zbd_zone *zones, *z; struct fio_zone_info *p; - uint64_t zone_size, offset; + uint64_t zone_size, offset, capacity; + bool same_zone_cap = true; struct zoned_block_device_info *zbd_info = NULL; int i, j, ret = -ENOMEM; @@ -793,6 +794,7 @@ static int parse_zone_info(struct thread_data *td, struct fio_file *f) } zone_size = zones[0].len; + capacity = zones[0].capacity; nr_zones = (f->real_file_size + zone_size - 1) / zone_size; if (td->o.zone_size == 0) { @@ -821,6 +823,8 @@ static int parse_zone_info(struct thread_data *td, struct fio_file *f) PTHREAD_MUTEX_RECURSIVE); p->start = z->start; p->capacity = z->capacity; + if (capacity != z->capacity) + same_zone_cap = false; switch (z->cond) { case ZBD_ZONE_COND_NOT_WP: @@ -876,6 +880,11 @@ static int parse_zone_info(struct thread_data *td, struct fio_file *f) f->zbd_info->zone_size_log2 = is_power_of_2(zone_size) ? ilog2(zone_size) : 0; f->zbd_info->nr_zones = nr_zones; + + if (same_zone_cap) + dprint(FD_ZBD, "Zone capacity = %"PRIu64" KB\n", + capacity / 1024); + zbd_info = NULL; ret = 0; From 5a649e2dddc4d8ad163b0cf57f7cea00a2e94a33 Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Tue, 23 May 2023 12:33:03 -0600 Subject: [PATCH 0494/1097] Fio 3.35 Signed-off-by: Jens Axboe --- FIO-VERSION-GEN | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FIO-VERSION-GEN b/FIO-VERSION-GEN index f1585d342e..4b0d56d038 100755 --- a/FIO-VERSION-GEN +++ b/FIO-VERSION-GEN @@ -1,7 +1,7 @@ #!/bin/sh GVF=FIO-VERSION-FILE -DEF_VER=fio-3.34 +DEF_VER=fio-3.35 LF=' ' From 5bde0576127046f16bd50d591dfcc5cb79ce8a76 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Wed, 24 May 2023 09:40:55 -0400 Subject: [PATCH 0495/1097] ci: stop using AppVeyor for Windows builds Stop running Windows tests using AppVeyor since it only runs tests serially. GitHub Actions Windows tests have been running for a few weeks and seem to be working ok. Signed-off-by: Vincent Fu --- .appveyor.yml | 68 ------------------------------------------ README.rst | 11 +++---- ci/appveyor-install.sh | 43 -------------------------- 3 files changed, 6 insertions(+), 116 deletions(-) delete mode 100644 .appveyor.yml delete mode 100755 ci/appveyor-install.sh diff --git a/.appveyor.yml b/.appveyor.yml deleted file mode 100644 index a63cf24f01..0000000000 --- a/.appveyor.yml +++ /dev/null @@ -1,68 +0,0 @@ -clone_depth: 1 # NB: this stops FIO-VERSION-GEN making tag based versions - -image: - - Visual Studio 2019 - -environment: - CYG_MIRROR: http://cygwin.mirror.constant.com - matrix: -# --disable-tls for the msys2 build to work around -# breakage with clang/lld 16.0.0-1 - - ARCHITECTURE: x64 - CC: clang - CONFIGURE_OPTIONS: --enable-pdb --disable-tls - DISTRO: msys2 -# Skip 32 bit clang build -# - ARCHITECTURE: x86 -# CC: clang -# CONFIGURE_OPTIONS: --enable-pdb -# DISTRO: msys2 - - ARCHITECTURE: x64 - CONFIGURE_OPTIONS: - DISTRO: cygwin - - ARCHITECTURE: x86 - CONFIGURE_OPTIONS: --build-32bit-win - DISTRO: cygwin - -install: - - if %DISTRO%==cygwin ( - SET "PATH=C:\cygwin64\bin;C:\cygwin64;%PATH%" - ) - - if %DISTRO%==msys2 if %ARCHITECTURE%==x86 ( - SET "PATH=C:\msys64\mingw32\bin;C:\msys64\usr\bin;%PATH%" - ) - - if %DISTRO%==msys2 if %ARCHITECTURE%==x64 ( - SET "PATH=C:\msys64\mingw64\bin;C:\msys64\usr\bin;%PATH%" - ) - - SET PATH=C:\Python38-x64;%PATH% # NB: Changed env variables persist to later sections - - SET PYTHONUNBUFFERED=TRUE - - bash.exe ci\appveyor-install.sh - -build_script: - - bash.exe configure --extra-cflags=-Werror --disable-native %CONFIGURE_OPTIONS% - - make.exe -j2 - -after_build: - - file.exe fio.exe - - make.exe test - - 'cd os\windows && dobuild.cmd %ARCHITECTURE% && cd ..' - - ls.exe ./os/windows/*.msi - - ps: Get-ChildItem .\os\windows\*.msi | % { Push-AppveyorArtifact $_.FullName -FileName $_.Name -DeploymentName fio.msi } - -test_script: - - python.exe t/run-fio-tests.py --artifact-root test-artifacts --debug - -deploy: - - provider: GitHub - description: fio Windows installer - auth_token: # encrypted token from GitHub - secure: Tjj+xRQEV25P6dQgboUblTCKx/LtUOUav2bvzSCtwMhHMAxrrn2adod6nlTf0ItV - artifact: fio.msi # upload installer to release assets - draft: false - prerelease: false - on: - APPVEYOR_REPO_TAG: true # deploy on tag push only - DISTRO: cygwin - -on_finish: - - 'bash.exe -lc "cd \"${APPVEYOR_BUILD_FOLDER}\" && [ -d test-artifacts ] && 7z a -t7z test-artifacts.7z test-artifacts -xr!foo.0.0 -xr!latency.?.0 -xr!fio_jsonplus_clat2csv.test && appveyor PushArtifact test-artifacts.7z' diff --git a/README.rst b/README.rst index 8f6208e351..dd521daf9c 100644 --- a/README.rst +++ b/README.rst @@ -123,11 +123,12 @@ Solaris: ``pkgutil -i fio``. Windows: - Beginning with fio 3.31 Windows installers are available on GitHub at - https://github.com/axboe/fio/releases. The latest builds for Windows - can also be grabbed from https://ci.appveyor.com/project/axboe/fio by - clicking the latest x86 or x64 build and then selecting the Artifacts - tab. + Beginning with fio 3.31 Windows installers for tagged releases are + available on GitHub at https://github.com/axboe/fio/releases. The + latest installers for Windows can also be obtained as GitHub Actions + artifacts by selecting a build from + https://github.com/axboe/fio/actions. These require logging in to a + GitHub account. BSDs: Packages for BSDs may be available from their binary package repositories. diff --git a/ci/appveyor-install.sh b/ci/appveyor-install.sh deleted file mode 100755 index 1e28c45403..0000000000 --- a/ci/appveyor-install.sh +++ /dev/null @@ -1,43 +0,0 @@ -#!/bin/bash -# The PATH to appropriate distro commands must already be set before invoking -# this script -# The following environment variables must be set: -# PLATFORM={i686,x64} -# DISTRO={cygwin,msys2} -# The following environment can optionally be set: -# CYG_MIRROR= -set -eu - -case "${ARCHITECTURE}" in - "x64") - PACKAGE_ARCH="x86_64" - ;; - "x86") - PACKAGE_ARCH="i686" - ;; -esac - -echo "Installing packages..." -case "${DISTRO}" in - "cygwin") - CYG_MIRROR=${CYG_MIRROR:-"http://cygwin.mirror.constant.com"} - setup-x86_64.exe --quiet-mode --no-shortcuts --only-site \ - --site "${CYG_MIRROR}" --packages \ - "mingw64-${PACKAGE_ARCH}-CUnit,mingw64-${PACKAGE_ARCH}-zlib" - ;; - "msys2") - #pacman --noconfirm -Syuu # MSYS2 core update - #pacman --noconfirm -Syuu # MSYS2 normal update - pacman.exe --noconfirm -S \ - mingw-w64-${PACKAGE_ARCH}-clang \ - mingw-w64-${PACKAGE_ARCH}-cunit \ - mingw-w64-${PACKAGE_ARCH}-toolchain \ - mingw-w64-${PACKAGE_ARCH}-lld - pacman.exe -Q # List installed packages - ;; -esac - -python.exe -m pip install scipy six statsmodels - -echo "Python3 path: $(type -p python3 2>&1)" -echo "Python3 version: $(python3 -V 2>&1)" From 954b86f71b0718943796192be1a89ffb0da5a97c Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Tue, 25 Apr 2023 18:22:26 +0000 Subject: [PATCH 0496/1097] ci: upload tagged GitHub Actions Windows installers as releases Since we are no long using AppVeyor for Windows tests, we can use the installers built by GitHub Actions for Windows releases. Signed-off-by: Vincent Fu --- .github/workflows/ci.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dd2997f075..69fedf7751 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -108,12 +108,17 @@ jobs: dobuild.cmd ${{ matrix.installer_arch }} cd ..\.. - - name: Upload installer (Windows) + - name: Upload installer as artifact (Windows) if: ${{ contains( matrix.build, 'windows' ) }} uses: actions/upload-artifact@v3 with: name: ${{ matrix.build }}-installer path: os\windows\*.msi + - name: Upload installer as release for tagged builds (Windows) + uses: softprops/action-gh-release@v1 + if: ${{ startsWith(github.ref, 'refs/tags/') && startsWith(matrix.build, 'windows-cygwin') }} + with: + files: os/windows/*.msi - name: Remove dependency files to resolve Makefile Cygwin sed issue (Windows) if: ${{ startsWith(matrix.build, 'windows-cygwin') }} run: rm *.d */*.d */*/*.d From 4820d46cef75f806d8c95afaa77f86ded4e3603e Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Fri, 26 May 2023 20:09:53 -0400 Subject: [PATCH 0497/1097] ci: disable tls for msys2 builds The same tls issue that occurred with AppVeyor msys2 builds has appeared in GitHub Actions msys2 builds. Disable thread local storage for GitHub Actions msys2 builds as well. Signed-off-by: Vincent Fu --- ci/actions-build.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ci/actions-build.sh b/ci/actions-build.sh index 351b8d18aa..31d3446c07 100755 --- a/ci/actions-build.sh +++ b/ci/actions-build.sh @@ -53,6 +53,9 @@ main() { "x86_64") ;; esac + if [ "${CI_TARGET_BUILD}" = "windows-msys2-64" ]; then + configure_flags+=("--disable-tls") + fi ;; esac configure_flags+=(--extra-cflags="${extra_cflags}") From b03ed937838505c4202d298e7daefda3b93963c7 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Fri, 26 May 2023 23:05:12 +0000 Subject: [PATCH 0498/1097] t/nvmept.py: test script for io_uring_cmd NVMe pass through Test basic functionality of the io_uring_cmd ioengine with NVMe pass-through commands. This script starts with basic sequential and random read, write, trim, readwrite, and trimwrite workloads. It also runs sequential and random write workloads with verification. Finally it runs random read and write workloads with almost all of the ioengine options enabled. The only ioengine option not exercised is hipri because the NVMe driver must be loaded with poll_queues set for this to work. Since this is a destructive test the target device must be specified on the command line. Signed-off-by: Vincent Fu --- t/nvmept.py | 414 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 414 insertions(+) create mode 100755 t/nvmept.py diff --git a/t/nvmept.py b/t/nvmept.py new file mode 100755 index 0000000000..a25192f2d4 --- /dev/null +++ b/t/nvmept.py @@ -0,0 +1,414 @@ +#!/usr/bin/env python3 +""" +# nvmept.py +# +# Test fio's io_uring_cmd ioengine with NVMe pass-through commands. +# +# USAGE +# see python3 nvmept.py --help +# +# EXAMPLES +# python3 t/nvmept.py --dut /dev/ng0n1 +# python3 t/nvmept.py --dut /dev/ng1n1 -f ./fio +# +# REQUIREMENTS +# Python 3.6 +# +""" +import os +import sys +import json +import time +import locale +import argparse +import subprocess +from pathlib import Path + +class FioTest(): + """fio test.""" + + def __init__(self, artifact_root, test_opts, debug): + """ + artifact_root root directory for artifacts (subdirectory will be created under here) + test test specification + """ + self.artifact_root = artifact_root + self.test_opts = test_opts + self.debug = debug + self.filename_stub = None + self.filenames = {} + self.json_data = None + + self.test_dir = os.path.abspath(os.path.join(self.artifact_root, + f"{self.test_opts['test_id']:03d}")) + if not os.path.exists(self.test_dir): + os.mkdir(self.test_dir) + + self.filename_stub = f"pt{self.test_opts['test_id']:03d}" + self.filenames['command'] = os.path.join(self.test_dir, f"{self.filename_stub}.command") + self.filenames['stdout'] = os.path.join(self.test_dir, f"{self.filename_stub}.stdout") + self.filenames['stderr'] = os.path.join(self.test_dir, f"{self.filename_stub}.stderr") + self.filenames['exitcode'] = os.path.join(self.test_dir, f"{self.filename_stub}.exitcode") + self.filenames['output'] = os.path.join(self.test_dir, f"{self.filename_stub}.output") + + def run_fio(self, fio_path): + """Run a test.""" + + fio_args = [ + "--name=nvmept", + "--ioengine=io_uring_cmd", + "--cmd_type=nvme", + "--iodepth=8", + "--iodepth_batch=4", + "--iodepth_batch_complete=4", + f"--filename={self.test_opts['filename']}", + f"--rw={self.test_opts['rw']}", + f"--output={self.filenames['output']}", + f"--output-format={self.test_opts['output-format']}", + ] + for opt in ['fixedbufs', 'nonvectored', 'force_async', 'registerfiles', + 'sqthread_poll', 'sqthread_poll_cpu', 'hipri', 'nowait', + 'time_based', 'runtime', 'verify', 'io_size']: + if opt in self.test_opts: + option = f"--{opt}={self.test_opts[opt]}" + fio_args.append(option) + + command = [fio_path] + fio_args + with open(self.filenames['command'], "w+", + encoding=locale.getpreferredencoding()) as command_file: + command_file.write(" ".join(command)) + + passed = True + + try: + with open(self.filenames['stdout'], "w+", + encoding=locale.getpreferredencoding()) as stdout_file, \ + open(self.filenames['stderr'], "w+", + encoding=locale.getpreferredencoding()) as stderr_file, \ + open(self.filenames['exitcode'], "w+", + encoding=locale.getpreferredencoding()) as exitcode_file: + proc = None + # Avoid using subprocess.run() here because when a timeout occurs, + # fio will be stopped with SIGKILL. This does not give fio a + # chance to clean up and means that child processes may continue + # running and submitting IO. + proc = subprocess.Popen(command, + stdout=stdout_file, + stderr=stderr_file, + cwd=self.test_dir, + universal_newlines=True) + proc.communicate(timeout=300) + exitcode_file.write(f'{proc.returncode}\n') + passed &= (proc.returncode == 0) + except subprocess.TimeoutExpired: + proc.terminate() + proc.communicate() + assert proc.poll() + print("Timeout expired") + passed = False + except Exception: + if proc: + if not proc.poll(): + proc.terminate() + proc.communicate() + print(f"Exception: {sys.exc_info()}") + passed = False + + if passed: + if 'output-format' in self.test_opts and 'json' in \ + self.test_opts['output-format']: + if not self.get_json(): + print('Unable to decode JSON data') + passed = False + + return passed + + def get_json(self): + """Convert fio JSON output into a python JSON object""" + + filename = self.filenames['output'] + with open(filename, 'r', encoding=locale.getpreferredencoding()) as file: + file_data = file.read() + + # + # Sometimes fio informational messages are included at the top of the + # JSON output, especially under Windows. Try to decode output as JSON + # data, lopping off up to the first four lines + # + lines = file_data.splitlines() + for i in range(5): + file_data = '\n'.join(lines[i:]) + try: + self.json_data = json.loads(file_data) + except json.JSONDecodeError: + continue + else: + return True + + return False + + @staticmethod + def check_empty(job): + """ + Make sure JSON data is empty. + + Some data structures should be empty. This function makes sure that they are. + + job JSON object that we need to check for emptiness + """ + + return job['total_ios'] == 0 and \ + job['slat_ns']['N'] == 0 and \ + job['clat_ns']['N'] == 0 and \ + job['lat_ns']['N'] == 0 + + def check_all_ddirs(self, ddir_nonzero, job): + """ + Iterate over the data directions and check whether each is + appropriately empty or not. + """ + + retval = True + ddirlist = ['read', 'write', 'trim'] + + for ddir in ddirlist: + if ddir in ddir_nonzero: + if self.check_empty(job[ddir]): + print(f"Unexpected zero {ddir} data found in output") + retval = False + else: + if not self.check_empty(job[ddir]): + print(f"Unexpected {ddir} data found in output") + retval = False + + return retval + + def check(self): + """Check test output.""" + + raise NotImplementedError() + + +class PTTest(FioTest): + """ + NVMe pass-through test class. Check to make sure output for selected data + direction(s) is non-zero and that zero data appears for other directions. + """ + + def check(self): + if 'rw' not in self.test_opts: + return True + + job = self.json_data['jobs'][0] + retval = True + + if self.test_opts['rw'] in ['read', 'randread']: + retval = self.check_all_ddirs(['read'], job) + elif self.test_opts['rw'] in ['write', 'randwrite']: + if 'verify' not in self.test_opts: + retval = self.check_all_ddirs(['write'], job) + else: + retval = self.check_all_ddirs(['read', 'write'], job) + elif self.test_opts['rw'] in ['trim', 'randtrim']: + retval = self.check_all_ddirs(['trim'], job) + elif self.test_opts['rw'] in ['readwrite', 'randrw']: + retval = self.check_all_ddirs(['read', 'write'], job) + elif self.test_opts['rw'] in ['trimwrite', 'randtrimwrite']: + retval = self.check_all_ddirs(['trim', 'write'], job) + else: + print(f"Unhandled rw value {self.test_opts['rw']}") + retval = False + + return retval + + +def parse_args(): + """Parse command-line arguments.""" + + parser = argparse.ArgumentParser() + parser.add_argument('-f', '--fio', help='path to file executable (e.g., ./fio)') + parser.add_argument('-a', '--artifact-root', help='artifact root directory') + parser.add_argument('-d', '--debug', help='enable debug output', action='store_true') + parser.add_argument('-s', '--skip', nargs='+', type=int, + help='list of test(s) to skip') + parser.add_argument('-o', '--run-only', nargs='+', type=int, + help='list of test(s) to run, skipping all others') + parser.add_argument('--dut', help='target NVMe character device to test ' + '(e.g., /dev/ng0n1). WARNING: THIS IS A DESTRUCTIVE TEST', required=True) + args = parser.parse_args() + + return args + + +def main(): + """Run tests using fio's io_uring_cmd ioengine to send NVMe pass through commands.""" + + args = parse_args() + + artifact_root = args.artifact_root if args.artifact_root else \ + f"nvmept-test-{time.strftime('%Y%m%d-%H%M%S')}" + os.mkdir(artifact_root) + print(f"Artifact directory is {artifact_root}") + + if args.fio: + fio = str(Path(args.fio).absolute()) + else: + fio = 'fio' + print(f"fio path is {fio}") + + test_list = [ + { + "test_id": 1, + "rw": 'read', + "timebased": 1, + "runtime": 3, + "output-format": "json", + "test_obj": PTTest, + }, + { + "test_id": 2, + "rw": 'randread', + "timebased": 1, + "runtime": 3, + "output-format": "json", + "test_obj": PTTest, + }, + { + "test_id": 3, + "rw": 'write', + "timebased": 1, + "runtime": 3, + "output-format": "json", + "test_obj": PTTest, + }, + { + "test_id": 4, + "rw": 'randwrite', + "timebased": 1, + "runtime": 3, + "output-format": "json", + "test_obj": PTTest, + }, + { + "test_id": 5, + "rw": 'trim', + "timebased": 1, + "runtime": 3, + "output-format": "json", + "test_obj": PTTest, + }, + { + "test_id": 6, + "rw": 'randtrim', + "timebased": 1, + "runtime": 3, + "output-format": "json", + "test_obj": PTTest, + }, + { + "test_id": 7, + "rw": 'write', + "io_size": 1024*1024, + "verify": "crc32c", + "output-format": "json", + "test_obj": PTTest, + }, + { + "test_id": 8, + "rw": 'randwrite', + "io_size": 1024*1024, + "verify": "crc32c", + "output-format": "json", + "test_obj": PTTest, + }, + { + "test_id": 9, + "rw": 'readwrite', + "timebased": 1, + "runtime": 3, + "output-format": "json", + "test_obj": PTTest, + }, + { + "test_id": 10, + "rw": 'randrw', + "timebased": 1, + "runtime": 3, + "output-format": "json", + "test_obj": PTTest, + }, + { + "test_id": 11, + "rw": 'trimwrite', + "timebased": 1, + "runtime": 3, + "output-format": "json", + "test_obj": PTTest, + }, + { + "test_id": 12, + "rw": 'randtrimwrite', + "timebased": 1, + "runtime": 3, + "output-format": "json", + "test_obj": PTTest, + }, + { + "test_id": 13, + "rw": 'randread', + "timebased": 1, + "runtime": 3, + "fixedbufs": 1, + "nonvectored": 1, + "force_async": 1, + "registerfiles": 1, + "sqthread_poll": 1, + "output-format": "json", + "test_obj": PTTest, + }, + { + "test_id": 14, + "rw": 'randwrite', + "timebased": 1, + "runtime": 3, + "fixedbufs": 1, + "nonvectored": 1, + "force_async": 1, + "registerfiles": 1, + "sqthread_poll": 1, + "output-format": "json", + "test_obj": PTTest, + }, + ] + + passed = 0 + failed = 0 + skipped = 0 + + for test in test_list: + if (args.skip and test['test_id'] in args.skip) or \ + (args.run_only and test['test_id'] not in args.run_only): + skipped = skipped + 1 + outcome = 'SKIPPED (User request)' + else: + test['filename'] = args.dut + test_obj = test['test_obj'](artifact_root, test, args.debug) + status = test_obj.run_fio(fio) + if status: + status = test_obj.check() + if status: + passed = passed + 1 + outcome = 'PASSED' + else: + failed = failed + 1 + outcome = 'FAILED' + + print(f"**********Test {test['test_id']} {outcome}**********") + + print(f"{passed} tests passed, {failed} failed, {skipped} skipped") + + sys.exit(failed) + + +if __name__ == '__main__': + main() From 2128f93aacb41fe5990027133162579d39da7ec4 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Wed, 31 May 2023 19:55:55 +0000 Subject: [PATCH 0499/1097] t/run-fio-tests: integrate t/nvmept.py Running t/nvmept.py requires that the test runner provide an NVMe character device test target. t/nvmept.py will be skipped unless an NVMe character device test target is provided. Signed-off-by: Vincent Fu --- t/run-fio-tests.py | 49 +++++++++++++++++++++++++++++++++------------- 1 file changed, 35 insertions(+), 14 deletions(-) diff --git a/t/run-fio-tests.py b/t/run-fio-tests.py index 71e3e5a621..8382d1fdba 100755 --- a/t/run-fio-tests.py +++ b/t/run-fio-tests.py @@ -863,8 +863,9 @@ class Requirements(): _not_windows = False _unittests = False _cpucount4 = False + _nvmecdev = False - def __init__(self, fio_root): + def __init__(self, fio_root, args): Requirements._not_macos = platform.system() != "Darwin" Requirements._not_windows = platform.system() != "Windows" Requirements._linux = platform.system() == "Linux" @@ -904,17 +905,21 @@ def __init__(self, fio_root): Requirements._unittests = os.path.exists(unittest_path) Requirements._cpucount4 = multiprocessing.cpu_count() >= 4 - - req_list = [Requirements.linux, - Requirements.libaio, - Requirements.io_uring, - Requirements.zbd, - Requirements.root, - Requirements.zoned_nullb, - Requirements.not_macos, - Requirements.not_windows, - Requirements.unittests, - Requirements.cpucount4] + Requirements._nvmecdev = args.nvmecdev + + req_list = [ + Requirements.linux, + Requirements.libaio, + Requirements.io_uring, + Requirements.zbd, + Requirements.root, + Requirements.zoned_nullb, + Requirements.not_macos, + Requirements.not_windows, + Requirements.unittests, + Requirements.cpucount4, + Requirements.nvmecdev, + ] for req in req_list: value, desc = req() logging.debug("Requirements: Requirement '%s' met? %s", desc, value) @@ -969,6 +974,11 @@ def cpucount4(cls): """Do we have at least 4 CPUs?""" return Requirements._cpucount4, "4+ CPUs required" + @classmethod + def nvmecdev(cls): + """Do we have an NVMe character device to test?""" + return Requirements._nvmecdev, "NVMe character device test target required" + SUCCESS_DEFAULT = { 'zero_return': True, @@ -1367,6 +1377,14 @@ def cpucount4(cls): 'success': SUCCESS_DEFAULT, 'requirements': [], }, + { + 'test_id': 1014, + 'test_class': FioExeTest, + 'exe': 't/nvmept.py', + 'parameters': ['-f', '{fio_path}', '--dut', '{nvmecdev}'], + 'success': SUCCESS_DEFAULT, + 'requirements': [Requirements.linux, Requirements.nvmecdev], + }, ] @@ -1390,6 +1408,8 @@ def parse_args(): help='skip requirements checking') parser.add_argument('-p', '--pass-through', action='append', help='pass-through an argument to an executable test') + parser.add_argument('--nvmecdev', action='store', default=None, + help='NVMe character device for **DESTRUCTIVE** testing (e.g., /dev/ng0n1)') args = parser.parse_args() return args @@ -1439,7 +1459,7 @@ def main(): print("Artifact directory is %s" % artifact_root) if not args.skip_req: - req = Requirements(fio_root) + req = Requirements(fio_root, args) passed = 0 failed = 0 @@ -1477,7 +1497,8 @@ def main(): elif issubclass(config['test_class'], FioExeTest): exe_path = os.path.join(fio_root, config['exe']) if config['parameters']: - parameters = [p.format(fio_path=fio_path) for p in config['parameters']] + parameters = [p.format(fio_path=fio_path, nvmecdev=args.nvmecdev) + for p in config['parameters']] else: parameters = [] if Path(exe_path).suffix == '.py' and platform.system() == "Windows": From 1b4ba547cf45377fffc7a1e60728369997cc7a9b Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Thu, 1 Jun 2023 14:20:17 +0000 Subject: [PATCH 0500/1097] t/run-fio-tests: address issues identified by pylint - use f strings - use Python3 syntax for parent classes - other small cleanups Signed-off-by: Vincent Fu --- t/run-fio-tests.py | 142 +++++++++++++++++++++++---------------------- 1 file changed, 72 insertions(+), 70 deletions(-) diff --git a/t/run-fio-tests.py b/t/run-fio-tests.py index 8382d1fdba..c91deed46a 100755 --- a/t/run-fio-tests.py +++ b/t/run-fio-tests.py @@ -79,22 +79,22 @@ def setup(self, artifact_root, testnum): self.artifact_root = artifact_root self.testnum = testnum - self.test_dir = os.path.join(artifact_root, "{:04d}".format(testnum)) + self.test_dir = os.path.join(artifact_root, f"{testnum:04d}") if not os.path.exists(self.test_dir): os.mkdir(self.test_dir) self.command_file = os.path.join( self.test_dir, - "{0}.command".format(os.path.basename(self.exe_path))) + f"{os.path.basename(self.exe_path)}.command") self.stdout_file = os.path.join( self.test_dir, - "{0}.stdout".format(os.path.basename(self.exe_path))) + f"{os.path.basename(self.exe_path)}.stdout") self.stderr_file = os.path.join( self.test_dir, - "{0}.stderr".format(os.path.basename(self.exe_path))) + f"{os.path.basename(self.exe_path)}.stderr") self.exitcode_file = os.path.join( self.test_dir, - "{0}.exitcode".format(os.path.basename(self.exe_path))) + f"{os.path.basename(self.exe_path)}.exitcode") def run(self): """Run the test.""" @@ -126,7 +126,7 @@ def run(self): command = [self.exe_path] + self.parameters command_file = open(self.command_file, "w+") - command_file.write("%s\n" % command) + command_file.write(f"{command}\n") command_file.close() stdout_file = open(self.stdout_file, "w+") @@ -144,7 +144,7 @@ def run(self): cwd=self.test_dir, universal_newlines=True) proc.communicate(timeout=self.success['timeout']) - exitcode_file.write('{0}\n'.format(proc.returncode)) + exitcode_file.write(f'{proc.returncode}\n') logging.debug("Test %d: return code: %d", self.testnum, proc.returncode) self.output['proc'] = proc except subprocess.TimeoutExpired: @@ -169,7 +169,7 @@ def check_result(self): if 'proc' not in self.output: if self.output['failure'] == 'timeout': - self.failure_reason = "{0} timeout,".format(self.failure_reason) + self.failure_reason = f"{self.failure_reason} timeout," else: assert self.output['failure'] == 'exception' self.failure_reason = '{0} exception: {1}, {2}'.format( @@ -183,21 +183,21 @@ def check_result(self): if self.success['zero_return']: if self.output['proc'].returncode != 0: self.passed = False - self.failure_reason = "{0} non-zero return code,".format(self.failure_reason) + self.failure_reason = f"{self.failure_reason} non-zero return code," else: if self.output['proc'].returncode == 0: - self.failure_reason = "{0} zero return code,".format(self.failure_reason) + self.failure_reason = f"{self.failure_reason} zero return code," self.passed = False stderr_size = os.path.getsize(self.stderr_file) if 'stderr_empty' in self.success: if self.success['stderr_empty']: if stderr_size != 0: - self.failure_reason = "{0} stderr not empty,".format(self.failure_reason) + self.failure_reason = f"{self.failure_reason} stderr not empty," self.passed = False else: if stderr_size == 0: - self.failure_reason = "{0} stderr empty,".format(self.failure_reason) + self.failure_reason = f"{self.failure_reason} stderr empty," self.passed = False @@ -223,11 +223,11 @@ def __init__(self, fio_path, fio_job, success, fio_pre_job=None, self.output_format = output_format self.precon_failed = False self.json_data = None - self.fio_output = "{0}.output".format(os.path.basename(self.fio_job)) + self.fio_output = f"{os.path.basename(self.fio_job)}.output" self.fio_args = [ "--max-jobs=16", - "--output-format={0}".format(self.output_format), - "--output={0}".format(self.fio_output), + f"--output-format={self.output_format}", + f"--output={self.fio_output}", self.fio_job, ] FioExeTest.__init__(self, fio_path, self.fio_args, success) @@ -235,20 +235,20 @@ def __init__(self, fio_path, fio_job, success, fio_pre_job=None, def setup(self, artifact_root, testnum): """Setup instance variables for fio job test.""" - super(FioJobTest, self).setup(artifact_root, testnum) + super().setup(artifact_root, testnum) self.command_file = os.path.join( self.test_dir, - "{0}.command".format(os.path.basename(self.fio_job))) + f"{os.path.basename(self.fio_job)}.command") self.stdout_file = os.path.join( self.test_dir, - "{0}.stdout".format(os.path.basename(self.fio_job))) + f"{os.path.basename(self.fio_job)}.stdout") self.stderr_file = os.path.join( self.test_dir, - "{0}.stderr".format(os.path.basename(self.fio_job))) + f"{os.path.basename(self.fio_job)}.stderr") self.exitcode_file = os.path.join( self.test_dir, - "{0}.exitcode".format(os.path.basename(self.fio_job))) + f"{os.path.basename(self.fio_job)}.exitcode") def run_pre_job(self): """Run fio job precondition step.""" @@ -269,7 +269,7 @@ def run(self): self.run_pre_job() if not self.precon_failed: - super(FioJobTest, self).run() + super().run() else: logging.debug("Test %d: precondition step failed", self.testnum) @@ -295,7 +295,7 @@ def get_file_fail(self, filename): with open(filename, "r") as output_file: file_data = output_file.read() except OSError: - self.failure_reason += " unable to read file {0}".format(filename) + self.failure_reason += f" unable to read file {filename}" self.passed = False return file_data @@ -305,10 +305,10 @@ def check_result(self): if self.precon_failed: self.passed = False - self.failure_reason = "{0} precondition step failed,".format(self.failure_reason) + self.failure_reason = f"{self.failure_reason} precondition step failed," return - super(FioJobTest, self).check_result() + super().check_result() if not self.passed: return @@ -330,7 +330,7 @@ def check_result(self): try: self.json_data = json.loads(file_data) except json.JSONDecodeError: - self.failure_reason = "{0} unable to decode JSON data,".format(self.failure_reason) + self.failure_reason = f"{self.failure_reason} unable to decode JSON data," self.passed = False @@ -339,16 +339,16 @@ class FioJobTest_t0005(FioJobTest): Confirm that read['io_kbytes'] == write['io_kbytes'] == 102400""" def check_result(self): - super(FioJobTest_t0005, self).check_result() + super().check_result() if not self.passed: return if self.json_data['jobs'][0]['read']['io_kbytes'] != 102400: - self.failure_reason = "{0} bytes read mismatch,".format(self.failure_reason) + self.failure_reason = f"{self.failure_reason} bytes read mismatch," self.passed = False if self.json_data['jobs'][0]['write']['io_kbytes'] != 102400: - self.failure_reason = "{0} bytes written mismatch,".format(self.failure_reason) + self.failure_reason = f"{self.failure_reason} bytes written mismatch," self.passed = False @@ -357,7 +357,7 @@ class FioJobTest_t0006(FioJobTest): Confirm that read['io_kbytes'] ~ 2*write['io_kbytes']""" def check_result(self): - super(FioJobTest_t0006, self).check_result() + super().check_result() if not self.passed: return @@ -366,7 +366,7 @@ def check_result(self): / self.json_data['jobs'][0]['write']['io_kbytes'] logging.debug("Test %d: ratio: %f", self.testnum, ratio) if ratio < 1.99 or ratio > 2.01: - self.failure_reason = "{0} read/write ratio mismatch,".format(self.failure_reason) + self.failure_reason = f"{self.failure_reason} read/write ratio mismatch," self.passed = False @@ -375,13 +375,13 @@ class FioJobTest_t0007(FioJobTest): Confirm that read['io_kbytes'] = 87040""" def check_result(self): - super(FioJobTest_t0007, self).check_result() + super().check_result() if not self.passed: return if self.json_data['jobs'][0]['read']['io_kbytes'] != 87040: - self.failure_reason = "{0} bytes read mismatch,".format(self.failure_reason) + self.failure_reason = f"{self.failure_reason} bytes read mismatch," self.passed = False @@ -397,7 +397,7 @@ class FioJobTest_t0008(FioJobTest): the blocks originally written will be read.""" def check_result(self): - super(FioJobTest_t0008, self).check_result() + super().check_result() if not self.passed: return @@ -406,10 +406,10 @@ def check_result(self): logging.debug("Test %d: ratio: %f", self.testnum, ratio) if ratio < 0.97 or ratio > 1.03: - self.failure_reason = "{0} bytes written mismatch,".format(self.failure_reason) + self.failure_reason = f"{self.failure_reason} bytes written mismatch," self.passed = False if self.json_data['jobs'][0]['read']['io_kbytes'] != 32768: - self.failure_reason = "{0} bytes read mismatch,".format(self.failure_reason) + self.failure_reason = f"{self.failure_reason} bytes read mismatch," self.passed = False @@ -418,7 +418,7 @@ class FioJobTest_t0009(FioJobTest): Confirm that runtime >= 60s""" def check_result(self): - super(FioJobTest_t0009, self).check_result() + super().check_result() if not self.passed: return @@ -426,7 +426,7 @@ def check_result(self): logging.debug('Test %d: elapsed: %d', self.testnum, self.json_data['jobs'][0]['elapsed']) if self.json_data['jobs'][0]['elapsed'] < 60: - self.failure_reason = "{0} elapsed time mismatch,".format(self.failure_reason) + self.failure_reason = f"{self.failure_reason} elapsed time mismatch," self.passed = False @@ -436,7 +436,7 @@ class FioJobTest_t0012(FioJobTest): job1,job2,job3 respectively""" def check_result(self): - super(FioJobTest_t0012, self).check_result() + super().check_result() if not self.passed: return @@ -484,7 +484,7 @@ class FioJobTest_t0014(FioJobTest): re-calibrate the activity dynamically""" def check_result(self): - super(FioJobTest_t0014, self).check_result() + super().check_result() if not self.passed: return @@ -539,7 +539,7 @@ class FioJobTest_t0015(FioJobTest): Confirm that mean(slat) + mean(clat) = mean(tlat)""" def check_result(self): - super(FioJobTest_t0015, self).check_result() + super().check_result() if not self.passed: return @@ -560,7 +560,7 @@ class FioJobTest_t0019(FioJobTest): Confirm that all offsets were touched sequentially""" def check_result(self): - super(FioJobTest_t0019, self).check_result() + super().check_result() bw_log_filename = os.path.join(self.test_dir, "test_bw.log") file_data = self.get_file_fail(bw_log_filename) @@ -576,13 +576,13 @@ def check_result(self): cur = int(line.split(',')[4]) if cur - prev != 4096: self.passed = False - self.failure_reason = "offsets {0}, {1} not sequential".format(prev, cur) + self.failure_reason = f"offsets {prev}, {cur} not sequential" return prev = cur if cur/4096 != 255: self.passed = False - self.failure_reason = "unexpected last offset {0}".format(cur) + self.failure_reason = f"unexpected last offset {cur}" class FioJobTest_t0020(FioJobTest): @@ -590,7 +590,7 @@ class FioJobTest_t0020(FioJobTest): Confirm that almost all offsets were touched non-sequentially""" def check_result(self): - super(FioJobTest_t0020, self).check_result() + super().check_result() bw_log_filename = os.path.join(self.test_dir, "test_bw.log") file_data = self.get_file_fail(bw_log_filename) @@ -611,14 +611,14 @@ def check_result(self): if len(offsets) != 256: self.passed = False - self.failure_reason += " number of offsets is {0} instead of 256".format(len(offsets)) + self.failure_reason += f" number of offsets is {len(offsets)} instead of 256" for i in range(256): if not i in offsets: self.passed = False - self.failure_reason += " missing offset {0}".format(i*4096) + self.failure_reason += f" missing offset {i * 4096}" - (z, p) = runstest_1samp(list(offsets)) + (_, p) = runstest_1samp(list(offsets)) if p < 0.05: self.passed = False self.failure_reason += f" runs test failed with p = {p}" @@ -628,7 +628,7 @@ class FioJobTest_t0022(FioJobTest): """Test consists of fio test job t0022""" def check_result(self): - super(FioJobTest_t0022, self).check_result() + super().check_result() bw_log_filename = os.path.join(self.test_dir, "test_bw.log") file_data = self.get_file_fail(bw_log_filename) @@ -655,7 +655,7 @@ def check_result(self): # 10 is an arbitrary threshold if seq_count > 10: self.passed = False - self.failure_reason = "too many ({0}) consecutive offsets".format(seq_count) + self.failure_reason = f"too many ({seq_count}) consecutive offsets" if len(offsets) == filesize/bs: self.passed = False @@ -690,7 +690,7 @@ def check_trimwrite(self, filename): bw_log_filename, line) break else: - if ddir != 1: + if ddir != 1: # pylint: disable=no-else-break self.passed = False self.failure_reason += " {0}: trim not preceeded by write: {1}".format( bw_log_filename, line) @@ -701,11 +701,13 @@ def check_trimwrite(self, filename): self.failure_reason += " {0}: block size does not match: {1}".format( bw_log_filename, line) break + if prev_offset != offset: self.passed = False self.failure_reason += " {0}: offset does not match: {1}".format( bw_log_filename, line) break + prev_ddir = ddir prev_bs = bs prev_offset = offset @@ -750,7 +752,7 @@ def check_all_offsets(self, filename, sectorsize, filesize): def check_result(self): - super(FioJobTest_t0023, self).check_result() + super().check_result() filesize = 1024*1024 @@ -792,7 +794,7 @@ def check_result(self): class FioJobTest_t0025(FioJobTest): """Test experimental verify read backs written data pattern.""" def check_result(self): - super(FioJobTest_t0025, self).check_result() + super().check_result() if not self.passed: return @@ -802,7 +804,7 @@ def check_result(self): class FioJobTest_t0027(FioJobTest): def setup(self, *args, **kws): - super(FioJobTest_t0027, self).setup(*args, **kws) + super().setup(*args, **kws) self.pattern_file = os.path.join(self.test_dir, "t0027.pattern") self.output_file = os.path.join(self.test_dir, "t0027file") self.pattern = os.urandom(16 << 10) @@ -810,7 +812,7 @@ def setup(self, *args, **kws): f.write(self.pattern) def check_result(self): - super(FioJobTest_t0027, self).check_result() + super().check_result() if not self.passed: return @@ -828,7 +830,7 @@ class FioJobTest_iops_rate(FioJobTest): With two runs of fio-3.16 I observed a ratio of 8.3""" def check_result(self): - super(FioJobTest_iops_rate, self).check_result() + super().check_result() if not self.passed: return @@ -841,11 +843,11 @@ def check_result(self): logging.debug("Test %d: ratio: %f", self.testnum, ratio) if iops1 < 950 or iops1 > 1050: - self.failure_reason = "{0} iops value mismatch,".format(self.failure_reason) + self.failure_reason = f"{self.failure_reason} iops value mismatch," self.passed = False if ratio < 6 or ratio > 10: - self.failure_reason = "{0} iops ratio mismatch,".format(self.failure_reason) + self.failure_reason = f"{self.failure_reason} iops ratio mismatch," self.passed = False @@ -874,7 +876,7 @@ def __init__(self, fio_root, args): config_file = os.path.join(fio_root, "config-host.h") contents, success = FioJobTest.get_file(config_file) if not success: - print("Unable to open {0} to check requirements".format(config_file)) + print(f"Unable to open {config_file} to check requirements") Requirements._zbd = True else: Requirements._zbd = "CONFIG_HAS_BLKZONED" in contents @@ -886,7 +888,7 @@ def __init__(self, fio_root, args): else: Requirements._io_uring = "io_uring_setup" in contents - Requirements._root = (os.geteuid() == 0) + Requirements._root = os.geteuid() == 0 if Requirements._zbd and Requirements._root: try: subprocess.run(["modprobe", "null_blk"], @@ -1428,7 +1430,7 @@ def main(): if args.pass_through: for arg in args.pass_through: if not ':' in arg: - print("Invalid --pass-through argument '%s'" % arg) + print(f"Invalid --pass-through argument '{arg}'") print("Syntax for --pass-through is TESTNUMBER:ARGUMENT") return split = arg.split(":", 1) @@ -1439,7 +1441,7 @@ def main(): fio_root = args.fio_root else: fio_root = str(Path(__file__).absolute().parent.parent) - print("fio root is %s" % fio_root) + print(f"fio root is {fio_root}") if args.fio: fio_path = args.fio @@ -1449,14 +1451,14 @@ def main(): else: fio_exe = "fio" fio_path = os.path.join(fio_root, fio_exe) - print("fio path is %s" % fio_path) + print(f"fio path is {fio_path}") if not shutil.which(fio_path): print("Warning: fio executable not found") artifact_root = args.artifact_root if args.artifact_root else \ - "fio-test-{0}".format(time.strftime("%Y%m%d-%H%M%S")) + f"fio-test-{time.strftime('%Y%m%d-%H%M%S')}" os.mkdir(artifact_root) - print("Artifact directory is %s" % artifact_root) + print(f"Artifact directory is {artifact_root}") if not args.skip_req: req = Requirements(fio_root, args) @@ -1469,7 +1471,7 @@ def main(): if (args.skip and config['test_id'] in args.skip) or \ (args.run_only and config['test_id'] not in args.run_only): skipped = skipped + 1 - print("Test {0} SKIPPED (User request)".format(config['test_id'])) + print(f"Test {config['test_id']} SKIPPED (User request)") continue if issubclass(config['test_class'], FioJobTest): @@ -1510,7 +1512,7 @@ def main(): config['success']) desc = config['exe'] else: - print("Test {0} FAILED: unable to process test config".format(config['test_id'])) + print(f"Test {config['test_id']} FAILED: unable to process test config") failed = failed + 1 continue @@ -1523,7 +1525,7 @@ def main(): if not reqs_met: break if not reqs_met: - print("Test {0} SKIPPED ({1}) {2}".format(config['test_id'], reason, desc)) + print(f"Test {config['test_id']} SKIPPED ({reason}) {desc}") skipped = skipped + 1 continue @@ -1541,15 +1543,15 @@ def main(): result = "PASSED" passed = passed + 1 else: - result = "FAILED: {0}".format(test.failure_reason) + result = f"FAILED: {test.failure_reason}" failed = failed + 1 contents, _ = FioJobTest.get_file(test.stderr_file) logging.debug("Test %d: stderr:\n%s", config['test_id'], contents) contents, _ = FioJobTest.get_file(test.stdout_file) logging.debug("Test %d: stdout:\n%s", config['test_id'], contents) - print("Test {0} {1} {2}".format(config['test_id'], result, desc)) + print(f"Test {config['test_id']} {result} {desc}") - print("{0} test(s) passed, {1} failed, {2} skipped".format(passed, failed, skipped)) + print(f"{passed} test(s) passed, {failed} failed, {skipped} skipped") sys.exit(failed) From fb551941e7b74e484ccdee863cde84ea1ef4bcbe Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Fri, 2 Jun 2023 16:25:50 +0000 Subject: [PATCH 0501/1097] t/run-fio-tests: split source file This is the first step in creating a test library that can be used by other python test scripts to reduce code duplication. Signed-off-by: Vincent Fu --- t/fiotestcommon.py | 161 ++++++++++++++ t/fiotestlib.py | 392 +++++++++++++++++++++++++++++++++ t/run-fio-tests.py | 532 +-------------------------------------------- 3 files changed, 564 insertions(+), 521 deletions(-) create mode 100644 t/fiotestcommon.py create mode 100755 t/fiotestlib.py diff --git a/t/fiotestcommon.py b/t/fiotestcommon.py new file mode 100644 index 0000000000..71abc828b3 --- /dev/null +++ b/t/fiotestcommon.py @@ -0,0 +1,161 @@ +#!/usr/bin/env python3 +""" +fiotestcommon.py + +This contains constant definitions, helpers, and a Requirements class that can +be used to help with running fio tests. +""" + +import os +import logging +import platform +import subprocess +import multiprocessing +from fiotestlib import FioJobTest + + +SUCCESS_DEFAULT = { + 'zero_return': True, + 'stderr_empty': True, + 'timeout': 600, + } +SUCCESS_NONZERO = { + 'zero_return': False, + 'stderr_empty': False, + 'timeout': 600, + } +SUCCESS_STDERR = { + 'zero_return': True, + 'stderr_empty': False, + 'timeout': 600, + } + +class Requirements(): + """Requirements consists of multiple run environment characteristics. + These are to determine if a particular test can be run""" + + _linux = False + _libaio = False + _io_uring = False + _zbd = False + _root = False + _zoned_nullb = False + _not_macos = False + _not_windows = False + _unittests = False + _cpucount4 = False + _nvmecdev = False + + def __init__(self, fio_root, args): + Requirements._not_macos = platform.system() != "Darwin" + Requirements._not_windows = platform.system() != "Windows" + Requirements._linux = platform.system() == "Linux" + + if Requirements._linux: + config_file = os.path.join(fio_root, "config-host.h") + contents, success = FioJobTest.get_file(config_file) + if not success: + print(f"Unable to open {config_file} to check requirements") + Requirements._zbd = True + else: + Requirements._zbd = "CONFIG_HAS_BLKZONED" in contents + Requirements._libaio = "CONFIG_LIBAIO" in contents + + contents, success = FioJobTest.get_file("/proc/kallsyms") + if not success: + print("Unable to open '/proc/kallsyms' to probe for io_uring support") + else: + Requirements._io_uring = "io_uring_setup" in contents + + Requirements._root = os.geteuid() == 0 + if Requirements._zbd and Requirements._root: + try: + subprocess.run(["modprobe", "null_blk"], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + if os.path.exists("/sys/module/null_blk/parameters/zoned"): + Requirements._zoned_nullb = True + except Exception: + pass + + if platform.system() == "Windows": + utest_exe = "unittest.exe" + else: + utest_exe = "unittest" + unittest_path = os.path.join(fio_root, "unittests", utest_exe) + Requirements._unittests = os.path.exists(unittest_path) + + Requirements._cpucount4 = multiprocessing.cpu_count() >= 4 + Requirements._nvmecdev = args.nvmecdev + + req_list = [ + Requirements.linux, + Requirements.libaio, + Requirements.io_uring, + Requirements.zbd, + Requirements.root, + Requirements.zoned_nullb, + Requirements.not_macos, + Requirements.not_windows, + Requirements.unittests, + Requirements.cpucount4, + Requirements.nvmecdev, + ] + for req in req_list: + value, desc = req() + logging.debug("Requirements: Requirement '%s' met? %s", desc, value) + + @classmethod + def linux(cls): + """Are we running on Linux?""" + return Requirements._linux, "Linux required" + + @classmethod + def libaio(cls): + """Is libaio available?""" + return Requirements._libaio, "libaio required" + + @classmethod + def io_uring(cls): + """Is io_uring available?""" + return Requirements._io_uring, "io_uring required" + + @classmethod + def zbd(cls): + """Is ZBD support available?""" + return Requirements._zbd, "Zoned block device support required" + + @classmethod + def root(cls): + """Are we running as root?""" + return Requirements._root, "root required" + + @classmethod + def zoned_nullb(cls): + """Are zoned null block devices available?""" + return Requirements._zoned_nullb, "Zoned null block device support required" + + @classmethod + def not_macos(cls): + """Are we running on a platform other than macOS?""" + return Requirements._not_macos, "platform other than macOS required" + + @classmethod + def not_windows(cls): + """Are we running on a platform other than Windws?""" + return Requirements._not_windows, "platform other than Windows required" + + @classmethod + def unittests(cls): + """Were unittests built?""" + return Requirements._unittests, "Unittests support required" + + @classmethod + def cpucount4(cls): + """Do we have at least 4 CPUs?""" + return Requirements._cpucount4, "4+ CPUs required" + + @classmethod + def nvmecdev(cls): + """Do we have an NVMe character device to test?""" + return Requirements._nvmecdev, "NVMe character device test target required" diff --git a/t/fiotestlib.py b/t/fiotestlib.py new file mode 100755 index 0000000000..ff114a1b0c --- /dev/null +++ b/t/fiotestlib.py @@ -0,0 +1,392 @@ +#!/usr/bin/env python3 +""" +fiotestlib.py + +This library contains FioTest objects that provide convenient means to run +different sorts of fio tests. + +It also contains a test runner that runs an array of dictionary objects +describing fio tests. +""" + +import os +import sys +import json +import locale +import logging +import platform +import traceback +import subprocess +from pathlib import Path + + +class FioTest(): + """Base for all fio tests.""" + + def __init__(self, exe_path, parameters, success): + self.exe_path = exe_path + self.parameters = parameters + self.success = success + self.output = {} + self.artifact_root = None + self.testnum = None + self.test_dir = None + self.passed = True + self.failure_reason = '' + self.command_file = None + self.stdout_file = None + self.stderr_file = None + self.exitcode_file = None + + def setup(self, artifact_root, testnum): + """Setup instance variables for test.""" + + self.artifact_root = artifact_root + self.testnum = testnum + self.test_dir = os.path.join(artifact_root, f"{testnum:04d}") + if not os.path.exists(self.test_dir): + os.mkdir(self.test_dir) + + self.command_file = os.path.join( self.test_dir, + f"{os.path.basename(self.exe_path)}.command") + self.stdout_file = os.path.join( self.test_dir, + f"{os.path.basename(self.exe_path)}.stdout") + self.stderr_file = os.path.join( self.test_dir, + f"{os.path.basename(self.exe_path)}.stderr") + self.exitcode_file = os.path.join( self.test_dir, + f"{os.path.basename(self.exe_path)}.exitcode") + + def run(self): + """Run the test.""" + + raise NotImplementedError() + + def check_result(self): + """Check test results.""" + + raise NotImplementedError() + + +class FioExeTest(FioTest): + """Test consists of an executable binary or script""" + + def __init__(self, exe_path, parameters, success): + """Construct a FioExeTest which is a FioTest consisting of an + executable binary or script. + + exe_path: location of executable binary or script + parameters: list of parameters for executable + success: Definition of test success + """ + + FioTest.__init__(self, exe_path, parameters, success) + + def run(self): + """Execute the binary or script described by this instance.""" + + command = [self.exe_path] + self.parameters + command_file = open(self.command_file, "w+", + encoding=locale.getpreferredencoding()) + command_file.write(f"{command}\n") + command_file.close() + + stdout_file = open(self.stdout_file, "w+", + encoding=locale.getpreferredencoding()) + stderr_file = open(self.stderr_file, "w+", + encoding=locale.getpreferredencoding()) + exitcode_file = open(self.exitcode_file, "w+", + encoding=locale.getpreferredencoding()) + try: + proc = None + # Avoid using subprocess.run() here because when a timeout occurs, + # fio will be stopped with SIGKILL. This does not give fio a + # chance to clean up and means that child processes may continue + # running and submitting IO. + proc = subprocess.Popen(command, + stdout=stdout_file, + stderr=stderr_file, + cwd=self.test_dir, + universal_newlines=True) + proc.communicate(timeout=self.success['timeout']) + exitcode_file.write(f'{proc.returncode}\n') + logging.debug("Test %d: return code: %d", self.testnum, proc.returncode) + self.output['proc'] = proc + except subprocess.TimeoutExpired: + proc.terminate() + proc.communicate() + assert proc.poll() + self.output['failure'] = 'timeout' + except Exception: + if proc: + if not proc.poll(): + proc.terminate() + proc.communicate() + self.output['failure'] = 'exception' + self.output['exc_info'] = sys.exc_info() + finally: + stdout_file.close() + stderr_file.close() + exitcode_file.close() + + def check_result(self): + """Check results of test run.""" + + if 'proc' not in self.output: + if self.output['failure'] == 'timeout': + self.failure_reason = f"{self.failure_reason} timeout," + else: + assert self.output['failure'] == 'exception' + self.failure_reason = '{0} exception: {1}, {2}'.format( + self.failure_reason, self.output['exc_info'][0], + self.output['exc_info'][1]) + + self.passed = False + return + + if 'zero_return' in self.success: + if self.success['zero_return']: + if self.output['proc'].returncode != 0: + self.passed = False + self.failure_reason = f"{self.failure_reason} non-zero return code," + else: + if self.output['proc'].returncode == 0: + self.failure_reason = f"{self.failure_reason} zero return code," + self.passed = False + + stderr_size = os.path.getsize(self.stderr_file) + if 'stderr_empty' in self.success: + if self.success['stderr_empty']: + if stderr_size != 0: + self.failure_reason = f"{self.failure_reason} stderr not empty," + self.passed = False + else: + if stderr_size == 0: + self.failure_reason = f"{self.failure_reason} stderr empty," + self.passed = False + + +class FioJobTest(FioExeTest): + """Test consists of a fio job""" + + def __init__(self, fio_path, fio_job, success, fio_pre_job=None, + fio_pre_success=None, output_format="normal"): + """Construct a FioJobTest which is a FioExeTest consisting of a + single fio job file with an optional setup step. + + fio_path: location of fio executable + fio_job: location of fio job file + success: Definition of test success + fio_pre_job: fio job for preconditioning + fio_pre_success: Definition of test success for fio precon job + output_format: normal (default), json, jsonplus, or terse + """ + + self.fio_job = fio_job + self.fio_pre_job = fio_pre_job + self.fio_pre_success = fio_pre_success if fio_pre_success else success + self.output_format = output_format + self.precon_failed = False + self.json_data = None + self.fio_output = f"{os.path.basename(self.fio_job)}.output" + self.fio_args = [ + "--max-jobs=16", + f"--output-format={self.output_format}", + f"--output={self.fio_output}", + self.fio_job, + ] + FioExeTest.__init__(self, fio_path, self.fio_args, success) + + def setup(self, artifact_root, testnum): + """Setup instance variables for fio job test.""" + + super().setup(artifact_root, testnum) + + self.command_file = os.path.join(self.test_dir, + f"{os.path.basename(self.fio_job)}.command") + self.stdout_file = os.path.join(self.test_dir, + f"{os.path.basename(self.fio_job)}.stdout") + self.stderr_file = os.path.join(self.test_dir, + f"{os.path.basename(self.fio_job)}.stderr") + self.exitcode_file = os.path.join(self.test_dir, + f"{os.path.basename(self.fio_job)}.exitcode") + + def run_pre_job(self): + """Run fio job precondition step.""" + + precon = FioJobTest(self.exe_path, self.fio_pre_job, + self.fio_pre_success, + output_format=self.output_format) + precon.setup(self.artifact_root, self.testnum) + precon.run() + precon.check_result() + self.precon_failed = not precon.passed + self.failure_reason = precon.failure_reason + + def run(self): + """Run fio job test.""" + + if self.fio_pre_job: + self.run_pre_job() + + if not self.precon_failed: + super().run() + else: + logging.debug("Test %d: precondition step failed", self.testnum) + + @classmethod + def get_file(cls, filename): + """Safely read a file.""" + file_data = '' + success = True + + try: + with open(filename, "r", encoding=locale.getpreferredencoding()) as output_file: + file_data = output_file.read() + except OSError: + success = False + + return file_data, success + + def get_file_fail(self, filename): + """Safely read a file and fail the test upon error.""" + file_data = None + + try: + with open(filename, "r", encoding=locale.getpreferredencoding()) as output_file: + file_data = output_file.read() + except OSError: + self.failure_reason += f" unable to read file {filename}" + self.passed = False + + return file_data + + def check_result(self): + """Check fio job results.""" + + if self.precon_failed: + self.passed = False + self.failure_reason = f"{self.failure_reason} precondition step failed," + return + + super().check_result() + + if not self.passed: + return + + if 'json' not in self.output_format: + return + + file_data = self.get_file_fail(os.path.join(self.test_dir, self.fio_output)) + if not file_data: + return + + # + # Sometimes fio informational messages are included at the top of the + # JSON output, especially under Windows. Try to decode output as JSON + # data, skipping everything until the first { + # + lines = file_data.splitlines() + file_data = '\n'.join(lines[lines.index("{"):]) + try: + self.json_data = json.loads(file_data) + except json.JSONDecodeError: + self.failure_reason = f"{self.failure_reason} unable to decode JSON data," + self.passed = False + + +def run_fio_tests(test_list, test_env, args): + """ + Run tests as specified in test_list. + """ + + passed = 0 + failed = 0 + skipped = 0 + + for config in test_list: + if (args.skip and config['test_id'] in args.skip) or \ + (args.run_only and config['test_id'] not in args.run_only): + skipped = skipped + 1 + print(f"Test {config['test_id']} SKIPPED (User request)") + continue + + if issubclass(config['test_class'], FioJobTest): + if config['pre_job']: + fio_pre_job = os.path.join(test_env['fio_root'], 't', 'jobs', + config['pre_job']) + else: + fio_pre_job = None + if config['pre_success']: + fio_pre_success = config['pre_success'] + else: + fio_pre_success = None + if 'output_format' in config: + output_format = config['output_format'] + else: + output_format = 'normal' + test = config['test_class']( + test_env['fio_path'], + os.path.join(test_env['fio_root'], 't', 'jobs', config['job']), + config['success'], + fio_pre_job=fio_pre_job, + fio_pre_success=fio_pre_success, + output_format=output_format) + desc = config['job'] + elif issubclass(config['test_class'], FioExeTest): + exe_path = os.path.join(test_env['fio_root'], config['exe']) + if config['parameters']: + parameters = [p.format(fio_path=test_env['fio_path'], nvmecdev=args.nvmecdev) + for p in config['parameters']] + else: + parameters = [] + if Path(exe_path).suffix == '.py' and platform.system() == "Windows": + parameters.insert(0, exe_path) + exe_path = "python.exe" + if config['test_id'] in test_env['pass_through']: + parameters += test_env['pass_through'][config['test_id']].split() + test = config['test_class'](exe_path, parameters, + config['success']) + desc = config['exe'] + else: + print(f"Test {config['test_id']} FAILED: unable to process test config") + failed = failed + 1 + continue + + if not args.skip_req: + reqs_met = True + for req in config['requirements']: + reqs_met, reason = req() + logging.debug("Test %d: Requirement '%s' met? %s", config['test_id'], reason, + reqs_met) + if not reqs_met: + break + if not reqs_met: + print(f"Test {config['test_id']} SKIPPED ({reason}) {desc}") + skipped = skipped + 1 + continue + + try: + test.setup(test_env['artifact_root'], config['test_id']) + test.run() + test.check_result() + except KeyboardInterrupt: + break + except Exception as e: + test.passed = False + test.failure_reason += str(e) + logging.debug("Test %d exception:\n%s\n", config['test_id'], traceback.format_exc()) + if test.passed: + result = "PASSED" + passed = passed + 1 + else: + result = f"FAILED: {test.failure_reason}" + failed = failed + 1 + contents, _ = FioJobTest.get_file(test.stderr_file) + logging.debug("Test %d: stderr:\n%s", config['test_id'], contents) + contents, _ = FioJobTest.get_file(test.stdout_file) + logging.debug("Test %d: stdout:\n%s", config['test_id'], contents) + print(f"Test {config['test_id']} {result} {desc}") + + print(f"{passed} test(s) passed, {failed} failed, {skipped} skipped") + + return passed, failed, skipped diff --git a/t/run-fio-tests.py b/t/run-fio-tests.py index c91deed46a..4f651ae83c 100755 --- a/t/run-fio-tests.py +++ b/t/run-fio-tests.py @@ -43,295 +43,14 @@ import os import sys -import json import time import shutil import logging import argparse -import platform -import traceback -import subprocess -import multiprocessing from pathlib import Path from statsmodels.sandbox.stats.runs import runstest_1samp - - -class FioTest(): - """Base for all fio tests.""" - - def __init__(self, exe_path, parameters, success): - self.exe_path = exe_path - self.parameters = parameters - self.success = success - self.output = {} - self.artifact_root = None - self.testnum = None - self.test_dir = None - self.passed = True - self.failure_reason = '' - self.command_file = None - self.stdout_file = None - self.stderr_file = None - self.exitcode_file = None - - def setup(self, artifact_root, testnum): - """Setup instance variables for test.""" - - self.artifact_root = artifact_root - self.testnum = testnum - self.test_dir = os.path.join(artifact_root, f"{testnum:04d}") - if not os.path.exists(self.test_dir): - os.mkdir(self.test_dir) - - self.command_file = os.path.join( - self.test_dir, - f"{os.path.basename(self.exe_path)}.command") - self.stdout_file = os.path.join( - self.test_dir, - f"{os.path.basename(self.exe_path)}.stdout") - self.stderr_file = os.path.join( - self.test_dir, - f"{os.path.basename(self.exe_path)}.stderr") - self.exitcode_file = os.path.join( - self.test_dir, - f"{os.path.basename(self.exe_path)}.exitcode") - - def run(self): - """Run the test.""" - - raise NotImplementedError() - - def check_result(self): - """Check test results.""" - - raise NotImplementedError() - - -class FioExeTest(FioTest): - """Test consists of an executable binary or script""" - - def __init__(self, exe_path, parameters, success): - """Construct a FioExeTest which is a FioTest consisting of an - executable binary or script. - - exe_path: location of executable binary or script - parameters: list of parameters for executable - success: Definition of test success - """ - - FioTest.__init__(self, exe_path, parameters, success) - - def run(self): - """Execute the binary or script described by this instance.""" - - command = [self.exe_path] + self.parameters - command_file = open(self.command_file, "w+") - command_file.write(f"{command}\n") - command_file.close() - - stdout_file = open(self.stdout_file, "w+") - stderr_file = open(self.stderr_file, "w+") - exitcode_file = open(self.exitcode_file, "w+") - try: - proc = None - # Avoid using subprocess.run() here because when a timeout occurs, - # fio will be stopped with SIGKILL. This does not give fio a - # chance to clean up and means that child processes may continue - # running and submitting IO. - proc = subprocess.Popen(command, - stdout=stdout_file, - stderr=stderr_file, - cwd=self.test_dir, - universal_newlines=True) - proc.communicate(timeout=self.success['timeout']) - exitcode_file.write(f'{proc.returncode}\n') - logging.debug("Test %d: return code: %d", self.testnum, proc.returncode) - self.output['proc'] = proc - except subprocess.TimeoutExpired: - proc.terminate() - proc.communicate() - assert proc.poll() - self.output['failure'] = 'timeout' - except Exception: - if proc: - if not proc.poll(): - proc.terminate() - proc.communicate() - self.output['failure'] = 'exception' - self.output['exc_info'] = sys.exc_info() - finally: - stdout_file.close() - stderr_file.close() - exitcode_file.close() - - def check_result(self): - """Check results of test run.""" - - if 'proc' not in self.output: - if self.output['failure'] == 'timeout': - self.failure_reason = f"{self.failure_reason} timeout," - else: - assert self.output['failure'] == 'exception' - self.failure_reason = '{0} exception: {1}, {2}'.format( - self.failure_reason, self.output['exc_info'][0], - self.output['exc_info'][1]) - - self.passed = False - return - - if 'zero_return' in self.success: - if self.success['zero_return']: - if self.output['proc'].returncode != 0: - self.passed = False - self.failure_reason = f"{self.failure_reason} non-zero return code," - else: - if self.output['proc'].returncode == 0: - self.failure_reason = f"{self.failure_reason} zero return code," - self.passed = False - - stderr_size = os.path.getsize(self.stderr_file) - if 'stderr_empty' in self.success: - if self.success['stderr_empty']: - if stderr_size != 0: - self.failure_reason = f"{self.failure_reason} stderr not empty," - self.passed = False - else: - if stderr_size == 0: - self.failure_reason = f"{self.failure_reason} stderr empty," - self.passed = False - - -class FioJobTest(FioExeTest): - """Test consists of a fio job""" - - def __init__(self, fio_path, fio_job, success, fio_pre_job=None, - fio_pre_success=None, output_format="normal"): - """Construct a FioJobTest which is a FioExeTest consisting of a - single fio job file with an optional setup step. - - fio_path: location of fio executable - fio_job: location of fio job file - success: Definition of test success - fio_pre_job: fio job for preconditioning - fio_pre_success: Definition of test success for fio precon job - output_format: normal (default), json, jsonplus, or terse - """ - - self.fio_job = fio_job - self.fio_pre_job = fio_pre_job - self.fio_pre_success = fio_pre_success if fio_pre_success else success - self.output_format = output_format - self.precon_failed = False - self.json_data = None - self.fio_output = f"{os.path.basename(self.fio_job)}.output" - self.fio_args = [ - "--max-jobs=16", - f"--output-format={self.output_format}", - f"--output={self.fio_output}", - self.fio_job, - ] - FioExeTest.__init__(self, fio_path, self.fio_args, success) - - def setup(self, artifact_root, testnum): - """Setup instance variables for fio job test.""" - - super().setup(artifact_root, testnum) - - self.command_file = os.path.join( - self.test_dir, - f"{os.path.basename(self.fio_job)}.command") - self.stdout_file = os.path.join( - self.test_dir, - f"{os.path.basename(self.fio_job)}.stdout") - self.stderr_file = os.path.join( - self.test_dir, - f"{os.path.basename(self.fio_job)}.stderr") - self.exitcode_file = os.path.join( - self.test_dir, - f"{os.path.basename(self.fio_job)}.exitcode") - - def run_pre_job(self): - """Run fio job precondition step.""" - - precon = FioJobTest(self.exe_path, self.fio_pre_job, - self.fio_pre_success, - output_format=self.output_format) - precon.setup(self.artifact_root, self.testnum) - precon.run() - precon.check_result() - self.precon_failed = not precon.passed - self.failure_reason = precon.failure_reason - - def run(self): - """Run fio job test.""" - - if self.fio_pre_job: - self.run_pre_job() - - if not self.precon_failed: - super().run() - else: - logging.debug("Test %d: precondition step failed", self.testnum) - - @classmethod - def get_file(cls, filename): - """Safely read a file.""" - file_data = '' - success = True - - try: - with open(filename, "r") as output_file: - file_data = output_file.read() - except OSError: - success = False - - return file_data, success - - def get_file_fail(self, filename): - """Safely read a file and fail the test upon error.""" - file_data = None - - try: - with open(filename, "r") as output_file: - file_data = output_file.read() - except OSError: - self.failure_reason += f" unable to read file {filename}" - self.passed = False - - return file_data - - def check_result(self): - """Check fio job results.""" - - if self.precon_failed: - self.passed = False - self.failure_reason = f"{self.failure_reason} precondition step failed," - return - - super().check_result() - - if not self.passed: - return - - if 'json' not in self.output_format: - return - - file_data = self.get_file_fail(os.path.join(self.test_dir, self.fio_output)) - if not file_data: - return - - # - # Sometimes fio informational messages are included at the top of the - # JSON output, especially under Windows. Try to decode output as JSON - # data, skipping everything until the first { - # - lines = file_data.splitlines() - file_data = '\n'.join(lines[lines.index("{"):]) - try: - self.json_data = json.loads(file_data) - except json.JSONDecodeError: - self.failure_reason = f"{self.failure_reason} unable to decode JSON data," - self.passed = False +from fiotestlib import FioExeTest, FioJobTest, run_fio_tests +from fiotestcommon import * class FioJobTest_t0005(FioJobTest): @@ -851,152 +570,6 @@ def check_result(self): self.passed = False -class Requirements(): - """Requirements consists of multiple run environment characteristics. - These are to determine if a particular test can be run""" - - _linux = False - _libaio = False - _io_uring = False - _zbd = False - _root = False - _zoned_nullb = False - _not_macos = False - _not_windows = False - _unittests = False - _cpucount4 = False - _nvmecdev = False - - def __init__(self, fio_root, args): - Requirements._not_macos = platform.system() != "Darwin" - Requirements._not_windows = platform.system() != "Windows" - Requirements._linux = platform.system() == "Linux" - - if Requirements._linux: - config_file = os.path.join(fio_root, "config-host.h") - contents, success = FioJobTest.get_file(config_file) - if not success: - print(f"Unable to open {config_file} to check requirements") - Requirements._zbd = True - else: - Requirements._zbd = "CONFIG_HAS_BLKZONED" in contents - Requirements._libaio = "CONFIG_LIBAIO" in contents - - contents, success = FioJobTest.get_file("/proc/kallsyms") - if not success: - print("Unable to open '/proc/kallsyms' to probe for io_uring support") - else: - Requirements._io_uring = "io_uring_setup" in contents - - Requirements._root = os.geteuid() == 0 - if Requirements._zbd and Requirements._root: - try: - subprocess.run(["modprobe", "null_blk"], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - if os.path.exists("/sys/module/null_blk/parameters/zoned"): - Requirements._zoned_nullb = True - except Exception: - pass - - if platform.system() == "Windows": - utest_exe = "unittest.exe" - else: - utest_exe = "unittest" - unittest_path = os.path.join(fio_root, "unittests", utest_exe) - Requirements._unittests = os.path.exists(unittest_path) - - Requirements._cpucount4 = multiprocessing.cpu_count() >= 4 - Requirements._nvmecdev = args.nvmecdev - - req_list = [ - Requirements.linux, - Requirements.libaio, - Requirements.io_uring, - Requirements.zbd, - Requirements.root, - Requirements.zoned_nullb, - Requirements.not_macos, - Requirements.not_windows, - Requirements.unittests, - Requirements.cpucount4, - Requirements.nvmecdev, - ] - for req in req_list: - value, desc = req() - logging.debug("Requirements: Requirement '%s' met? %s", desc, value) - - @classmethod - def linux(cls): - """Are we running on Linux?""" - return Requirements._linux, "Linux required" - - @classmethod - def libaio(cls): - """Is libaio available?""" - return Requirements._libaio, "libaio required" - - @classmethod - def io_uring(cls): - """Is io_uring available?""" - return Requirements._io_uring, "io_uring required" - - @classmethod - def zbd(cls): - """Is ZBD support available?""" - return Requirements._zbd, "Zoned block device support required" - - @classmethod - def root(cls): - """Are we running as root?""" - return Requirements._root, "root required" - - @classmethod - def zoned_nullb(cls): - """Are zoned null block devices available?""" - return Requirements._zoned_nullb, "Zoned null block device support required" - - @classmethod - def not_macos(cls): - """Are we running on a platform other than macOS?""" - return Requirements._not_macos, "platform other than macOS required" - - @classmethod - def not_windows(cls): - """Are we running on a platform other than Windws?""" - return Requirements._not_windows, "platform other than Windows required" - - @classmethod - def unittests(cls): - """Were unittests built?""" - return Requirements._unittests, "Unittests support required" - - @classmethod - def cpucount4(cls): - """Do we have at least 4 CPUs?""" - return Requirements._cpucount4, "4+ CPUs required" - - @classmethod - def nvmecdev(cls): - """Do we have an NVMe character device to test?""" - return Requirements._nvmecdev, "NVMe character device test target required" - - -SUCCESS_DEFAULT = { - 'zero_return': True, - 'stderr_empty': True, - 'timeout': 600, - } -SUCCESS_NONZERO = { - 'zero_return': False, - 'stderr_empty': False, - 'timeout': 600, - } -SUCCESS_STDERR = { - 'zero_return': True, - 'stderr_empty': False, - 'timeout': 600, - } TEST_LIST = [ { 'test_id': 1, @@ -1461,98 +1034,15 @@ def main(): print(f"Artifact directory is {artifact_root}") if not args.skip_req: - req = Requirements(fio_root, args) - - passed = 0 - failed = 0 - skipped = 0 - - for config in TEST_LIST: - if (args.skip and config['test_id'] in args.skip) or \ - (args.run_only and config['test_id'] not in args.run_only): - skipped = skipped + 1 - print(f"Test {config['test_id']} SKIPPED (User request)") - continue - - if issubclass(config['test_class'], FioJobTest): - if config['pre_job']: - fio_pre_job = os.path.join(fio_root, 't', 'jobs', - config['pre_job']) - else: - fio_pre_job = None - if config['pre_success']: - fio_pre_success = config['pre_success'] - else: - fio_pre_success = None - if 'output_format' in config: - output_format = config['output_format'] - else: - output_format = 'normal' - test = config['test_class']( - fio_path, - os.path.join(fio_root, 't', 'jobs', config['job']), - config['success'], - fio_pre_job=fio_pre_job, - fio_pre_success=fio_pre_success, - output_format=output_format) - desc = config['job'] - elif issubclass(config['test_class'], FioExeTest): - exe_path = os.path.join(fio_root, config['exe']) - if config['parameters']: - parameters = [p.format(fio_path=fio_path, nvmecdev=args.nvmecdev) - for p in config['parameters']] - else: - parameters = [] - if Path(exe_path).suffix == '.py' and platform.system() == "Windows": - parameters.insert(0, exe_path) - exe_path = "python.exe" - if config['test_id'] in pass_through: - parameters += pass_through[config['test_id']].split() - test = config['test_class'](exe_path, parameters, - config['success']) - desc = config['exe'] - else: - print(f"Test {config['test_id']} FAILED: unable to process test config") - failed = failed + 1 - continue - - if not args.skip_req: - reqs_met = True - for req in config['requirements']: - reqs_met, reason = req() - logging.debug("Test %d: Requirement '%s' met? %s", config['test_id'], reason, - reqs_met) - if not reqs_met: - break - if not reqs_met: - print(f"Test {config['test_id']} SKIPPED ({reason}) {desc}") - skipped = skipped + 1 - continue - - try: - test.setup(artifact_root, config['test_id']) - test.run() - test.check_result() - except KeyboardInterrupt: - break - except Exception as e: - test.passed = False - test.failure_reason += str(e) - logging.debug("Test %d exception:\n%s\n", config['test_id'], traceback.format_exc()) - if test.passed: - result = "PASSED" - passed = passed + 1 - else: - result = f"FAILED: {test.failure_reason}" - failed = failed + 1 - contents, _ = FioJobTest.get_file(test.stderr_file) - logging.debug("Test %d: stderr:\n%s", config['test_id'], contents) - contents, _ = FioJobTest.get_file(test.stdout_file) - logging.debug("Test %d: stdout:\n%s", config['test_id'], contents) - print(f"Test {config['test_id']} {result} {desc}") - - print(f"{passed} test(s) passed, {failed} failed, {skipped} skipped") - + Requirements(fio_root, args) + + test_env = { + 'fio_path': fio_path, + 'fio_root': fio_root, + 'artifact_root': artifact_root, + 'pass_through': pass_through, + } + _, failed, _ = run_fio_tests(TEST_LIST, test_env, args) sys.exit(failed) From 0dc6e911832fca2c5cf9a5ac2663b468ef2c4341 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Fri, 2 Jun 2023 18:55:08 +0000 Subject: [PATCH 0502/1097] t/run-fio-tests: rename FioJobTest to FioJobFileTest We have two different types of tests based on fio jobs. The ones referred to by FioJobFileTest consist of running a fio job file. It's also possible to run a fio job test with options specified on the command line. Such a class will be defined later. Signed-off-by: Vincent Fu --- t/fiotestcommon.py | 6 +-- t/fiotestlib.py | 12 +++--- t/run-fio-tests.py | 94 +++++++++++++++++++++++----------------------- 3 files changed, 56 insertions(+), 56 deletions(-) diff --git a/t/fiotestcommon.py b/t/fiotestcommon.py index 71abc828b3..8250bf523f 100644 --- a/t/fiotestcommon.py +++ b/t/fiotestcommon.py @@ -11,7 +11,7 @@ import platform import subprocess import multiprocessing -from fiotestlib import FioJobTest +from fiotestlib import FioJobFileTest SUCCESS_DEFAULT = { @@ -53,7 +53,7 @@ def __init__(self, fio_root, args): if Requirements._linux: config_file = os.path.join(fio_root, "config-host.h") - contents, success = FioJobTest.get_file(config_file) + contents, success = FioJobFileTest.get_file(config_file) if not success: print(f"Unable to open {config_file} to check requirements") Requirements._zbd = True @@ -61,7 +61,7 @@ def __init__(self, fio_root, args): Requirements._zbd = "CONFIG_HAS_BLKZONED" in contents Requirements._libaio = "CONFIG_LIBAIO" in contents - contents, success = FioJobTest.get_file("/proc/kallsyms") + contents, success = FioJobFileTest.get_file("/proc/kallsyms") if not success: print("Unable to open '/proc/kallsyms' to probe for io_uring support") else: diff --git a/t/fiotestlib.py b/t/fiotestlib.py index ff114a1b0c..8b48470e6c 100755 --- a/t/fiotestlib.py +++ b/t/fiotestlib.py @@ -165,12 +165,12 @@ def check_result(self): self.passed = False -class FioJobTest(FioExeTest): +class FioJobFileTest(FioExeTest): """Test consists of a fio job""" def __init__(self, fio_path, fio_job, success, fio_pre_job=None, fio_pre_success=None, output_format="normal"): - """Construct a FioJobTest which is a FioExeTest consisting of a + """Construct a FioJobFileTest which is a FioExeTest consisting of a single fio job file with an optional setup step. fio_path: location of fio executable @@ -213,7 +213,7 @@ def setup(self, artifact_root, testnum): def run_pre_job(self): """Run fio job precondition step.""" - precon = FioJobTest(self.exe_path, self.fio_pre_job, + precon = FioJobFileTest(self.exe_path, self.fio_pre_job, self.fio_pre_success, output_format=self.output_format) precon.setup(self.artifact_root, self.testnum) @@ -310,7 +310,7 @@ def run_fio_tests(test_list, test_env, args): print(f"Test {config['test_id']} SKIPPED (User request)") continue - if issubclass(config['test_class'], FioJobTest): + if issubclass(config['test_class'], FioJobFileTest): if config['pre_job']: fio_pre_job = os.path.join(test_env['fio_root'], 't', 'jobs', config['pre_job']) @@ -381,9 +381,9 @@ def run_fio_tests(test_list, test_env, args): else: result = f"FAILED: {test.failure_reason}" failed = failed + 1 - contents, _ = FioJobTest.get_file(test.stderr_file) + contents, _ = FioJobFileTest.get_file(test.stderr_file) logging.debug("Test %d: stderr:\n%s", config['test_id'], contents) - contents, _ = FioJobTest.get_file(test.stdout_file) + contents, _ = FioJobFileTest.get_file(test.stdout_file) logging.debug("Test %d: stdout:\n%s", config['test_id'], contents) print(f"Test {config['test_id']} {result} {desc}") diff --git a/t/run-fio-tests.py b/t/run-fio-tests.py index 4f651ae83c..f44e35229b 100755 --- a/t/run-fio-tests.py +++ b/t/run-fio-tests.py @@ -49,11 +49,11 @@ import argparse from pathlib import Path from statsmodels.sandbox.stats.runs import runstest_1samp -from fiotestlib import FioExeTest, FioJobTest, run_fio_tests +from fiotestlib import FioExeTest, FioJobFileTest, run_fio_tests from fiotestcommon import * -class FioJobTest_t0005(FioJobTest): +class FioJobFileTest_t0005(FioJobFileTest): """Test consists of fio test job t0005 Confirm that read['io_kbytes'] == write['io_kbytes'] == 102400""" @@ -71,7 +71,7 @@ def check_result(self): self.passed = False -class FioJobTest_t0006(FioJobTest): +class FioJobFileTest_t0006(FioJobFileTest): """Test consists of fio test job t0006 Confirm that read['io_kbytes'] ~ 2*write['io_kbytes']""" @@ -89,7 +89,7 @@ def check_result(self): self.passed = False -class FioJobTest_t0007(FioJobTest): +class FioJobFileTest_t0007(FioJobFileTest): """Test consists of fio test job t0007 Confirm that read['io_kbytes'] = 87040""" @@ -104,7 +104,7 @@ def check_result(self): self.passed = False -class FioJobTest_t0008(FioJobTest): +class FioJobFileTest_t0008(FioJobFileTest): """Test consists of fio test job t0008 Confirm that read['io_kbytes'] = 32768 and that write['io_kbytes'] ~ 16384 @@ -132,7 +132,7 @@ def check_result(self): self.passed = False -class FioJobTest_t0009(FioJobTest): +class FioJobFileTest_t0009(FioJobFileTest): """Test consists of fio test job t0009 Confirm that runtime >= 60s""" @@ -149,7 +149,7 @@ def check_result(self): self.passed = False -class FioJobTest_t0012(FioJobTest): +class FioJobFileTest_t0012(FioJobFileTest): """Test consists of fio test job t0012 Confirm ratios of job iops are 1:5:10 job1,job2,job3 respectively""" @@ -194,7 +194,7 @@ def check_result(self): return -class FioJobTest_t0014(FioJobTest): +class FioJobFileTest_t0014(FioJobFileTest): """Test consists of fio test job t0014 Confirm that job1_iops / job2_iops ~ 1:2 for entire duration and that job1_iops / job3_iops ~ 1:3 for first half of duration. @@ -253,7 +253,7 @@ def check_result(self): return -class FioJobTest_t0015(FioJobTest): +class FioJobFileTest_t0015(FioJobFileTest): """Test consists of fio test jobs t0015 and t0016 Confirm that mean(slat) + mean(clat) = mean(tlat)""" @@ -274,7 +274,7 @@ def check_result(self): self.passed = False -class FioJobTest_t0019(FioJobTest): +class FioJobFileTest_t0019(FioJobFileTest): """Test consists of fio test job t0019 Confirm that all offsets were touched sequentially""" @@ -304,7 +304,7 @@ def check_result(self): self.failure_reason = f"unexpected last offset {cur}" -class FioJobTest_t0020(FioJobTest): +class FioJobFileTest_t0020(FioJobFileTest): """Test consists of fio test jobs t0020 and t0021 Confirm that almost all offsets were touched non-sequentially""" @@ -343,7 +343,7 @@ def check_result(self): self.failure_reason += f" runs test failed with p = {p}" -class FioJobTest_t0022(FioJobTest): +class FioJobFileTest_t0022(FioJobFileTest): """Test consists of fio test job t0022""" def check_result(self): @@ -381,7 +381,7 @@ def check_result(self): self.failure_reason += " no duplicate offsets found with norandommap=1" -class FioJobTest_t0023(FioJobTest): +class FioJobFileTest_t0023(FioJobFileTest): """Test consists of fio test job t0023 randtrimwrite test.""" def check_trimwrite(self, filename): @@ -490,12 +490,12 @@ def check_result(self): self.check_all_offsets("bssplit_bw.log", 512, filesize) -class FioJobTest_t0024(FioJobTest_t0023): +class FioJobFileTest_t0024(FioJobFileTest_t0023): """Test consists of fio test job t0024 trimwrite test.""" def check_result(self): - # call FioJobTest_t0023's parent to skip checks done by t0023 - super(FioJobTest_t0023, self).check_result() + # call FioJobFileTest_t0023's parent to skip checks done by t0023 + super(FioJobFileTest_t0023, self).check_result() filesize = 1024*1024 @@ -510,7 +510,7 @@ def check_result(self): self.check_all_offsets("bssplit_bw.log", 512, filesize) -class FioJobTest_t0025(FioJobTest): +class FioJobFileTest_t0025(FioJobFileTest): """Test experimental verify read backs written data pattern.""" def check_result(self): super().check_result() @@ -521,7 +521,7 @@ def check_result(self): if self.json_data['jobs'][0]['read']['io_kbytes'] != 128: self.passed = False -class FioJobTest_t0027(FioJobTest): +class FioJobFileTest_t0027(FioJobFileTest): def setup(self, *args, **kws): super().setup(*args, **kws) self.pattern_file = os.path.join(self.test_dir, "t0027.pattern") @@ -542,7 +542,7 @@ def check_result(self): if data != self.pattern: self.passed = False -class FioJobTest_iops_rate(FioJobTest): +class FioJobFileTest_iops_rate(FioJobFileTest): """Test consists of fio test job t0011 Confirm that job0 iops == 1000 and that job1_iops / job0_iops ~ 8 @@ -573,7 +573,7 @@ def check_result(self): TEST_LIST = [ { 'test_id': 1, - 'test_class': FioJobTest, + 'test_class': FioJobFileTest, 'job': 't0001-52c58027.fio', 'success': SUCCESS_DEFAULT, 'pre_job': None, @@ -582,7 +582,7 @@ def check_result(self): }, { 'test_id': 2, - 'test_class': FioJobTest, + 'test_class': FioJobFileTest, 'job': 't0002-13af05ae-post.fio', 'success': SUCCESS_DEFAULT, 'pre_job': 't0002-13af05ae-pre.fio', @@ -591,7 +591,7 @@ def check_result(self): }, { 'test_id': 3, - 'test_class': FioJobTest, + 'test_class': FioJobFileTest, 'job': 't0003-0ae2c6e1-post.fio', 'success': SUCCESS_NONZERO, 'pre_job': 't0003-0ae2c6e1-pre.fio', @@ -600,7 +600,7 @@ def check_result(self): }, { 'test_id': 4, - 'test_class': FioJobTest, + 'test_class': FioJobFileTest, 'job': 't0004-8a99fdf6.fio', 'success': SUCCESS_DEFAULT, 'pre_job': None, @@ -609,7 +609,7 @@ def check_result(self): }, { 'test_id': 5, - 'test_class': FioJobTest_t0005, + 'test_class': FioJobFileTest_t0005, 'job': 't0005-f7078f7b.fio', 'success': SUCCESS_DEFAULT, 'pre_job': None, @@ -619,7 +619,7 @@ def check_result(self): }, { 'test_id': 6, - 'test_class': FioJobTest_t0006, + 'test_class': FioJobFileTest_t0006, 'job': 't0006-82af2a7c.fio', 'success': SUCCESS_DEFAULT, 'pre_job': None, @@ -629,7 +629,7 @@ def check_result(self): }, { 'test_id': 7, - 'test_class': FioJobTest_t0007, + 'test_class': FioJobFileTest_t0007, 'job': 't0007-37cf9e3c.fio', 'success': SUCCESS_DEFAULT, 'pre_job': None, @@ -639,7 +639,7 @@ def check_result(self): }, { 'test_id': 8, - 'test_class': FioJobTest_t0008, + 'test_class': FioJobFileTest_t0008, 'job': 't0008-ae2fafc8.fio', 'success': SUCCESS_DEFAULT, 'pre_job': None, @@ -649,7 +649,7 @@ def check_result(self): }, { 'test_id': 9, - 'test_class': FioJobTest_t0009, + 'test_class': FioJobFileTest_t0009, 'job': 't0009-f8b0bd10.fio', 'success': SUCCESS_DEFAULT, 'pre_job': None, @@ -661,7 +661,7 @@ def check_result(self): }, { 'test_id': 10, - 'test_class': FioJobTest, + 'test_class': FioJobFileTest, 'job': 't0010-b7aae4ba.fio', 'success': SUCCESS_DEFAULT, 'pre_job': None, @@ -670,7 +670,7 @@ def check_result(self): }, { 'test_id': 11, - 'test_class': FioJobTest_iops_rate, + 'test_class': FioJobFileTest_iops_rate, 'job': 't0011-5d2788d5.fio', 'success': SUCCESS_DEFAULT, 'pre_job': None, @@ -680,7 +680,7 @@ def check_result(self): }, { 'test_id': 12, - 'test_class': FioJobTest_t0012, + 'test_class': FioJobFileTest_t0012, 'job': 't0012.fio', 'success': SUCCESS_DEFAULT, 'pre_job': None, @@ -690,7 +690,7 @@ def check_result(self): }, { 'test_id': 13, - 'test_class': FioJobTest, + 'test_class': FioJobFileTest, 'job': 't0013.fio', 'success': SUCCESS_DEFAULT, 'pre_job': None, @@ -700,7 +700,7 @@ def check_result(self): }, { 'test_id': 14, - 'test_class': FioJobTest_t0014, + 'test_class': FioJobFileTest_t0014, 'job': 't0014.fio', 'success': SUCCESS_DEFAULT, 'pre_job': None, @@ -710,7 +710,7 @@ def check_result(self): }, { 'test_id': 15, - 'test_class': FioJobTest_t0015, + 'test_class': FioJobFileTest_t0015, 'job': 't0015-e78980ff.fio', 'success': SUCCESS_DEFAULT, 'pre_job': None, @@ -720,7 +720,7 @@ def check_result(self): }, { 'test_id': 16, - 'test_class': FioJobTest_t0015, + 'test_class': FioJobFileTest_t0015, 'job': 't0016-d54ae22.fio', 'success': SUCCESS_DEFAULT, 'pre_job': None, @@ -730,7 +730,7 @@ def check_result(self): }, { 'test_id': 17, - 'test_class': FioJobTest_t0015, + 'test_class': FioJobFileTest_t0015, 'job': 't0017.fio', 'success': SUCCESS_DEFAULT, 'pre_job': None, @@ -740,7 +740,7 @@ def check_result(self): }, { 'test_id': 18, - 'test_class': FioJobTest, + 'test_class': FioJobFileTest, 'job': 't0018.fio', 'success': SUCCESS_DEFAULT, 'pre_job': None, @@ -749,7 +749,7 @@ def check_result(self): }, { 'test_id': 19, - 'test_class': FioJobTest_t0019, + 'test_class': FioJobFileTest_t0019, 'job': 't0019.fio', 'success': SUCCESS_DEFAULT, 'pre_job': None, @@ -758,7 +758,7 @@ def check_result(self): }, { 'test_id': 20, - 'test_class': FioJobTest_t0020, + 'test_class': FioJobFileTest_t0020, 'job': 't0020.fio', 'success': SUCCESS_DEFAULT, 'pre_job': None, @@ -767,7 +767,7 @@ def check_result(self): }, { 'test_id': 21, - 'test_class': FioJobTest_t0020, + 'test_class': FioJobFileTest_t0020, 'job': 't0021.fio', 'success': SUCCESS_DEFAULT, 'pre_job': None, @@ -776,7 +776,7 @@ def check_result(self): }, { 'test_id': 22, - 'test_class': FioJobTest_t0022, + 'test_class': FioJobFileTest_t0022, 'job': 't0022.fio', 'success': SUCCESS_DEFAULT, 'pre_job': None, @@ -785,7 +785,7 @@ def check_result(self): }, { 'test_id': 23, - 'test_class': FioJobTest_t0023, + 'test_class': FioJobFileTest_t0023, 'job': 't0023.fio', 'success': SUCCESS_DEFAULT, 'pre_job': None, @@ -794,7 +794,7 @@ def check_result(self): }, { 'test_id': 24, - 'test_class': FioJobTest_t0024, + 'test_class': FioJobFileTest_t0024, 'job': 't0024.fio', 'success': SUCCESS_DEFAULT, 'pre_job': None, @@ -803,7 +803,7 @@ def check_result(self): }, { 'test_id': 25, - 'test_class': FioJobTest_t0025, + 'test_class': FioJobFileTest_t0025, 'job': 't0025.fio', 'success': SUCCESS_DEFAULT, 'pre_job': None, @@ -813,7 +813,7 @@ def check_result(self): }, { 'test_id': 26, - 'test_class': FioJobTest, + 'test_class': FioJobFileTest, 'job': 't0026.fio', 'success': SUCCESS_DEFAULT, 'pre_job': None, @@ -822,7 +822,7 @@ def check_result(self): }, { 'test_id': 27, - 'test_class': FioJobTest_t0027, + 'test_class': FioJobFileTest_t0027, 'job': 't0027.fio', 'success': SUCCESS_DEFAULT, 'pre_job': None, @@ -831,7 +831,7 @@ def check_result(self): }, { 'test_id': 28, - 'test_class': FioJobTest, + 'test_class': FioJobFileTest, 'job': 't0028-c6cade16.fio', 'success': SUCCESS_DEFAULT, 'pre_job': None, From ecd6d30fcea14e42d933f1895b2f910cbe84506b Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Fri, 2 Jun 2023 19:04:31 +0000 Subject: [PATCH 0503/1097] t/run-fio-tests: move get_file outside of FioJobFileTest There's no reason for this helper function to be part of a class. Just make it an independent helper. Signed-off-by: Vincent Fu --- t/fiotestcommon.py | 21 ++++++++++++++++++--- t/fiotestlib.py | 19 +++---------------- 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/t/fiotestcommon.py b/t/fiotestcommon.py index 8250bf523f..f5012c82c4 100644 --- a/t/fiotestcommon.py +++ b/t/fiotestcommon.py @@ -7,11 +7,11 @@ """ import os +import locale import logging import platform import subprocess import multiprocessing -from fiotestlib import FioJobFileTest SUCCESS_DEFAULT = { @@ -30,6 +30,21 @@ 'timeout': 600, } + +def get_file(filename): + """Safely read a file.""" + file_data = '' + success = True + + try: + with open(filename, "r", encoding=locale.getpreferredencoding()) as output_file: + file_data = output_file.read() + except OSError: + success = False + + return file_data, success + + class Requirements(): """Requirements consists of multiple run environment characteristics. These are to determine if a particular test can be run""" @@ -53,7 +68,7 @@ def __init__(self, fio_root, args): if Requirements._linux: config_file = os.path.join(fio_root, "config-host.h") - contents, success = FioJobFileTest.get_file(config_file) + contents, success = get_file(config_file) if not success: print(f"Unable to open {config_file} to check requirements") Requirements._zbd = True @@ -61,7 +76,7 @@ def __init__(self, fio_root, args): Requirements._zbd = "CONFIG_HAS_BLKZONED" in contents Requirements._libaio = "CONFIG_LIBAIO" in contents - contents, success = FioJobFileTest.get_file("/proc/kallsyms") + contents, success = get_file("/proc/kallsyms") if not success: print("Unable to open '/proc/kallsyms' to probe for io_uring support") else: diff --git a/t/fiotestlib.py b/t/fiotestlib.py index 8b48470e6c..6aa46e2942 100755 --- a/t/fiotestlib.py +++ b/t/fiotestlib.py @@ -18,6 +18,7 @@ import traceback import subprocess from pathlib import Path +from fiotestcommon import get_file class FioTest(): @@ -233,20 +234,6 @@ def run(self): else: logging.debug("Test %d: precondition step failed", self.testnum) - @classmethod - def get_file(cls, filename): - """Safely read a file.""" - file_data = '' - success = True - - try: - with open(filename, "r", encoding=locale.getpreferredencoding()) as output_file: - file_data = output_file.read() - except OSError: - success = False - - return file_data, success - def get_file_fail(self, filename): """Safely read a file and fail the test upon error.""" file_data = None @@ -381,9 +368,9 @@ def run_fio_tests(test_list, test_env, args): else: result = f"FAILED: {test.failure_reason}" failed = failed + 1 - contents, _ = FioJobFileTest.get_file(test.stderr_file) + contents, _ = get_file(test.stderr_file) logging.debug("Test %d: stderr:\n%s", config['test_id'], contents) - contents, _ = FioJobFileTest.get_file(test.stdout_file) + contents, _ = get_file(test.stdout_file) logging.debug("Test %d: stdout:\n%s", config['test_id'], contents) print(f"Test {config['test_id']} {result} {desc}") From 68b3a741b1ba68fd37afd5ad3e75352fc4901e2f Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Mon, 5 Jun 2023 17:46:19 +0000 Subject: [PATCH 0504/1097] t/fiotestlib: use dictionaries for filenames and paths Instead of separate variables for all the different filenames and paths, use dictionaries for the filenames and paths. This involves changing the function signature for the constructor and setup() functions so that parameters are passed to setup() instead of the constructor. This is because the base class defines the filenames dictionary but FioJobFileTest needs to put an entry into the dictionary before calling the base class constructor. Signed-off-by: Vincent Fu --- t/fiotestlib.py | 118 ++++++++++++++++++++++----------------------- t/run-fio-tests.py | 18 +++---- 2 files changed, 67 insertions(+), 69 deletions(-) diff --git a/t/fiotestlib.py b/t/fiotestlib.py index 6aa46e2942..e5ccc13f2b 100755 --- a/t/fiotestlib.py +++ b/t/fiotestlib.py @@ -24,38 +24,34 @@ class FioTest(): """Base for all fio tests.""" - def __init__(self, exe_path, parameters, success): - self.exe_path = exe_path - self.parameters = parameters + def __init__(self, exe_path, success): + self.paths = {'exe': exe_path} self.success = success self.output = {} - self.artifact_root = None self.testnum = None - self.test_dir = None self.passed = True self.failure_reason = '' - self.command_file = None - self.stdout_file = None - self.stderr_file = None - self.exitcode_file = None + self.filenames = {} + self.parameters = None - def setup(self, artifact_root, testnum): + def setup(self, artifact_root, testnum, parameters): """Setup instance variables for test.""" - self.artifact_root = artifact_root self.testnum = testnum - self.test_dir = os.path.join(artifact_root, f"{testnum:04d}") - if not os.path.exists(self.test_dir): - os.mkdir(self.test_dir) - - self.command_file = os.path.join( self.test_dir, - f"{os.path.basename(self.exe_path)}.command") - self.stdout_file = os.path.join( self.test_dir, - f"{os.path.basename(self.exe_path)}.stdout") - self.stderr_file = os.path.join( self.test_dir, - f"{os.path.basename(self.exe_path)}.stderr") - self.exitcode_file = os.path.join( self.test_dir, - f"{os.path.basename(self.exe_path)}.exitcode") + self.parameters = parameters + self.paths['artifacts'] = artifact_root + self.paths['test_dir'] = os.path.join(artifact_root, f"{testnum:04d}") + if not os.path.exists(self.paths['test_dir']): + os.mkdir(self.paths['test_dir']) + + self.filenames['cmd'] = os.path.join(self.paths['test_dir'], + f"{os.path.basename(self.paths['exe'])}.command") + self.filenames['stdout'] = os.path.join(self.paths['test_dir'], + f"{os.path.basename(self.paths['exe'])}.stdout") + self.filenames['stderr'] = os.path.join(self.paths['test_dir'], + f"{os.path.basename(self.paths['exe'])}.stderr") + self.filenames['exitcode'] = os.path.join(self.paths['test_dir'], + f"{os.path.basename(self.paths['exe'])}.exitcode") def run(self): """Run the test.""" @@ -71,31 +67,30 @@ def check_result(self): class FioExeTest(FioTest): """Test consists of an executable binary or script""" - def __init__(self, exe_path, parameters, success): + def __init__(self, exe_path, success): """Construct a FioExeTest which is a FioTest consisting of an executable binary or script. exe_path: location of executable binary or script - parameters: list of parameters for executable success: Definition of test success """ - FioTest.__init__(self, exe_path, parameters, success) + FioTest.__init__(self, exe_path, success) def run(self): """Execute the binary or script described by this instance.""" - command = [self.exe_path] + self.parameters - command_file = open(self.command_file, "w+", + command = [self.paths['exe']] + self.parameters + command_file = open(self.filenames['cmd'], "w+", encoding=locale.getpreferredencoding()) command_file.write(f"{command}\n") command_file.close() - stdout_file = open(self.stdout_file, "w+", + stdout_file = open(self.filenames['stdout'], "w+", encoding=locale.getpreferredencoding()) - stderr_file = open(self.stderr_file, "w+", + stderr_file = open(self.filenames['stderr'], "w+", encoding=locale.getpreferredencoding()) - exitcode_file = open(self.exitcode_file, "w+", + exitcode_file = open(self.filenames['exitcode'], "w+", encoding=locale.getpreferredencoding()) try: proc = None @@ -106,7 +101,7 @@ def run(self): proc = subprocess.Popen(command, stdout=stdout_file, stderr=stderr_file, - cwd=self.test_dir, + cwd=self.paths['test_dir'], universal_newlines=True) proc.communicate(timeout=self.success['timeout']) exitcode_file.write(f'{proc.returncode}\n') @@ -154,7 +149,7 @@ def check_result(self): self.failure_reason = f"{self.failure_reason} zero return code," self.passed = False - stderr_size = os.path.getsize(self.stderr_file) + stderr_size = os.path.getsize(self.filenames['stderr']) if 'stderr_empty' in self.success: if self.success['stderr_empty']: if stderr_size != 0: @@ -167,7 +162,7 @@ def check_result(self): class FioJobFileTest(FioExeTest): - """Test consists of a fio job""" + """Test consists of a fio job with options in a job file.""" def __init__(self, fio_path, fio_job, success, fio_pre_job=None, fio_pre_success=None, output_format="normal"): @@ -188,36 +183,39 @@ def __init__(self, fio_path, fio_job, success, fio_pre_job=None, self.output_format = output_format self.precon_failed = False self.json_data = None - self.fio_output = f"{os.path.basename(self.fio_job)}.output" - self.fio_args = [ + + FioExeTest.__init__(self, fio_path, success) + + def setup(self, artifact_root, testnum, parameters=None): + """Setup instance variables for fio job test.""" + + self.filenames['fio_output'] = f"{os.path.basename(self.fio_job)}.output" + fio_args = [ "--max-jobs=16", f"--output-format={self.output_format}", - f"--output={self.fio_output}", + f"--output={self.filenames['fio_output']}", self.fio_job, ] - FioExeTest.__init__(self, fio_path, self.fio_args, success) - - def setup(self, artifact_root, testnum): - """Setup instance variables for fio job test.""" - super().setup(artifact_root, testnum) + super().setup(artifact_root, testnum, fio_args) - self.command_file = os.path.join(self.test_dir, - f"{os.path.basename(self.fio_job)}.command") - self.stdout_file = os.path.join(self.test_dir, - f"{os.path.basename(self.fio_job)}.stdout") - self.stderr_file = os.path.join(self.test_dir, - f"{os.path.basename(self.fio_job)}.stderr") - self.exitcode_file = os.path.join(self.test_dir, - f"{os.path.basename(self.fio_job)}.exitcode") + # Update the filenames from the default + self.filenames['cmd'] = os.path.join(self.paths['test_dir'], + f"{os.path.basename(self.fio_job)}.command") + self.filenames['stdout'] = os.path.join(self.paths['test_dir'], + f"{os.path.basename(self.fio_job)}.stdout") + self.filenames['stderr'] = os.path.join(self.paths['test_dir'], + f"{os.path.basename(self.fio_job)}.stderr") + self.filenames['exitcode'] = os.path.join(self.paths['test_dir'], + f"{os.path.basename(self.fio_job)}.exitcode") def run_pre_job(self): """Run fio job precondition step.""" - precon = FioJobFileTest(self.exe_path, self.fio_pre_job, + precon = FioJobFileTest(self.paths['exe'], self.fio_pre_job, self.fio_pre_success, output_format=self.output_format) - precon.setup(self.artifact_root, self.testnum) + precon.setup(self.paths['artifacts'], self.testnum) precon.run() precon.check_result() self.precon_failed = not precon.passed @@ -263,7 +261,8 @@ def check_result(self): if 'json' not in self.output_format: return - file_data = self.get_file_fail(os.path.join(self.test_dir, self.fio_output)) + file_data = self.get_file_fail(os.path.join(self.paths['test_dir'], + self.filenames['fio_output'])) if not file_data: return @@ -319,20 +318,19 @@ def run_fio_tests(test_list, test_env, args): fio_pre_success=fio_pre_success, output_format=output_format) desc = config['job'] + parameters = [] elif issubclass(config['test_class'], FioExeTest): exe_path = os.path.join(test_env['fio_root'], config['exe']) + parameters = [] if config['parameters']: parameters = [p.format(fio_path=test_env['fio_path'], nvmecdev=args.nvmecdev) for p in config['parameters']] - else: - parameters = [] if Path(exe_path).suffix == '.py' and platform.system() == "Windows": parameters.insert(0, exe_path) exe_path = "python.exe" if config['test_id'] in test_env['pass_through']: parameters += test_env['pass_through'][config['test_id']].split() - test = config['test_class'](exe_path, parameters, - config['success']) + test = config['test_class'](exe_path, config['success']) desc = config['exe'] else: print(f"Test {config['test_id']} FAILED: unable to process test config") @@ -353,7 +351,7 @@ def run_fio_tests(test_list, test_env, args): continue try: - test.setup(test_env['artifact_root'], config['test_id']) + test.setup(test_env['artifact_root'], config['test_id'], parameters) test.run() test.check_result() except KeyboardInterrupt: @@ -368,9 +366,9 @@ def run_fio_tests(test_list, test_env, args): else: result = f"FAILED: {test.failure_reason}" failed = failed + 1 - contents, _ = get_file(test.stderr_file) + contents, _ = get_file(test.filenames['stderr']) logging.debug("Test %d: stderr:\n%s", config['test_id'], contents) - contents, _ = get_file(test.stdout_file) + contents, _ = get_file(test.filenames['stdout']) logging.debug("Test %d: stdout:\n%s", config['test_id'], contents) print(f"Test {config['test_id']} {result} {desc}") diff --git a/t/run-fio-tests.py b/t/run-fio-tests.py index f44e35229b..4d63a86e2e 100755 --- a/t/run-fio-tests.py +++ b/t/run-fio-tests.py @@ -162,7 +162,7 @@ def check_result(self): iops_files = [] for i in range(1, 4): - filename = os.path.join(self.test_dir, "{0}_iops.{1}.log".format(os.path.basename( + filename = os.path.join(self.paths['test_dir'], "{0}_iops.{1}.log".format(os.path.basename( self.fio_job), i)) file_data = self.get_file_fail(filename) if not file_data: @@ -210,7 +210,7 @@ def check_result(self): iops_files = [] for i in range(1, 4): - filename = os.path.join(self.test_dir, "{0}_iops.{1}.log".format(os.path.basename( + filename = os.path.join(self.paths['test_dir'], "{0}_iops.{1}.log".format(os.path.basename( self.fio_job), i)) file_data = self.get_file_fail(filename) if not file_data: @@ -281,7 +281,7 @@ class FioJobFileTest_t0019(FioJobFileTest): def check_result(self): super().check_result() - bw_log_filename = os.path.join(self.test_dir, "test_bw.log") + bw_log_filename = os.path.join(self.paths['test_dir'], "test_bw.log") file_data = self.get_file_fail(bw_log_filename) if not file_data: return @@ -311,7 +311,7 @@ class FioJobFileTest_t0020(FioJobFileTest): def check_result(self): super().check_result() - bw_log_filename = os.path.join(self.test_dir, "test_bw.log") + bw_log_filename = os.path.join(self.paths['test_dir'], "test_bw.log") file_data = self.get_file_fail(bw_log_filename) if not file_data: return @@ -349,7 +349,7 @@ class FioJobFileTest_t0022(FioJobFileTest): def check_result(self): super().check_result() - bw_log_filename = os.path.join(self.test_dir, "test_bw.log") + bw_log_filename = os.path.join(self.paths['test_dir'], "test_bw.log") file_data = self.get_file_fail(bw_log_filename) if not file_data: return @@ -387,7 +387,7 @@ class FioJobFileTest_t0023(FioJobFileTest): def check_trimwrite(self, filename): """Make sure that trims are followed by writes of the same size at the same offset.""" - bw_log_filename = os.path.join(self.test_dir, filename) + bw_log_filename = os.path.join(self.paths['test_dir'], filename) file_data = self.get_file_fail(bw_log_filename) if not file_data: return @@ -435,7 +435,7 @@ def check_trimwrite(self, filename): def check_all_offsets(self, filename, sectorsize, filesize): """Make sure all offsets were touched.""" - file_data = self.get_file_fail(os.path.join(self.test_dir, filename)) + file_data = self.get_file_fail(os.path.join(self.paths['test_dir'], filename)) if not file_data: return @@ -524,8 +524,8 @@ def check_result(self): class FioJobFileTest_t0027(FioJobFileTest): def setup(self, *args, **kws): super().setup(*args, **kws) - self.pattern_file = os.path.join(self.test_dir, "t0027.pattern") - self.output_file = os.path.join(self.test_dir, "t0027file") + self.pattern_file = os.path.join(self.paths['test_dir'], "t0027.pattern") + self.output_file = os.path.join(self.paths['test_dir'], "t0027file") self.pattern = os.urandom(16 << 10) with open(self.pattern_file, "wb") as f: f.write(self.pattern) From 1cf0ba9d3ca7918e54860e01d13f1899a02ce758 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Mon, 5 Jun 2023 17:59:55 +0000 Subject: [PATCH 0505/1097] t/fiotestlib: use 'with' for opening files Use with when opening files to relieve us of the need to explicitly close the files. Signed-off-by: Vincent Fu --- t/fiotestlib.py | 53 ++++++++++++++++++++++--------------------------- 1 file changed, 24 insertions(+), 29 deletions(-) diff --git a/t/fiotestlib.py b/t/fiotestlib.py index e5ccc13f2b..8565f63508 100755 --- a/t/fiotestlib.py +++ b/t/fiotestlib.py @@ -81,32 +81,31 @@ def run(self): """Execute the binary or script described by this instance.""" command = [self.paths['exe']] + self.parameters - command_file = open(self.filenames['cmd'], "w+", - encoding=locale.getpreferredencoding()) - command_file.write(f"{command}\n") - command_file.close() - - stdout_file = open(self.filenames['stdout'], "w+", - encoding=locale.getpreferredencoding()) - stderr_file = open(self.filenames['stderr'], "w+", - encoding=locale.getpreferredencoding()) - exitcode_file = open(self.filenames['exitcode'], "w+", - encoding=locale.getpreferredencoding()) + with open(self.filenames['cmd'], "w+", + encoding=locale.getpreferredencoding()) as command_file: + command_file.write(f"{command}\n") + try: - proc = None - # Avoid using subprocess.run() here because when a timeout occurs, - # fio will be stopped with SIGKILL. This does not give fio a - # chance to clean up and means that child processes may continue - # running and submitting IO. - proc = subprocess.Popen(command, - stdout=stdout_file, - stderr=stderr_file, - cwd=self.paths['test_dir'], - universal_newlines=True) - proc.communicate(timeout=self.success['timeout']) - exitcode_file.write(f'{proc.returncode}\n') - logging.debug("Test %d: return code: %d", self.testnum, proc.returncode) - self.output['proc'] = proc + with open(self.filenames['stdout'], "w+", + encoding=locale.getpreferredencoding()) as stdout_file, \ + open(self.filenames['stderr'], "w+", + encoding=locale.getpreferredencoding()) as stderr_file, \ + open(self.filenames['exitcode'], "w+", + encoding=locale.getpreferredencoding()) as exitcode_file: + proc = None + # Avoid using subprocess.run() here because when a timeout occurs, + # fio will be stopped with SIGKILL. This does not give fio a + # chance to clean up and means that child processes may continue + # running and submitting IO. + proc = subprocess.Popen(command, + stdout=stdout_file, + stderr=stderr_file, + cwd=self.paths['test_dir'], + universal_newlines=True) + proc.communicate(timeout=self.success['timeout']) + exitcode_file.write(f'{proc.returncode}\n') + logging.debug("Test %d: return code: %d", self.testnum, proc.returncode) + self.output['proc'] = proc except subprocess.TimeoutExpired: proc.terminate() proc.communicate() @@ -119,10 +118,6 @@ def run(self): proc.communicate() self.output['failure'] = 'exception' self.output['exc_info'] = sys.exc_info() - finally: - stdout_file.close() - stderr_file.close() - exitcode_file.close() def check_result(self): """Check results of test run.""" From 6544f1ae3ec012d1ec68e1e19d46b2cc27e87f80 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Mon, 5 Jun 2023 18:06:17 +0000 Subject: [PATCH 0506/1097] t/fiotestlib: use f-string for formatting Take advantage of running on modern Python versions and use an f-string for formatting. Signed-off-by: Vincent Fu --- t/fiotestlib.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/t/fiotestlib.py b/t/fiotestlib.py index 8565f63508..2060c30020 100755 --- a/t/fiotestlib.py +++ b/t/fiotestlib.py @@ -127,9 +127,8 @@ def check_result(self): self.failure_reason = f"{self.failure_reason} timeout," else: assert self.output['failure'] == 'exception' - self.failure_reason = '{0} exception: {1}, {2}'.format( - self.failure_reason, self.output['exc_info'][0], - self.output['exc_info'][1]) + self.failure_reason = f'{self.failure_reason} exception: ' + \ + f'{self.output["exc_info"][0]}, {self.output["exc_info"][1]}' self.passed = False return From 111fa98ea91a8cfbd9e143f9dd7df3857bcbe00d Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Tue, 6 Jun 2023 20:38:21 +0000 Subject: [PATCH 0507/1097] t/fiotestlib: rearrange constructor and setup steps Make the constructor and setup steps more sensible. The constructor now sets variables that should be fixed throughout the execution of the test. Some of the setup steps may depend on values set by the constructor. So keeps those separate. Signed-off-by: Vincent Fu --- t/fiotestlib.py | 75 +++++++++++++++++++++++++------------------------ 1 file changed, 39 insertions(+), 36 deletions(-) diff --git a/t/fiotestlib.py b/t/fiotestlib.py index 2060c30020..73b904c52e 100755 --- a/t/fiotestlib.py +++ b/t/fiotestlib.py @@ -24,35 +24,37 @@ class FioTest(): """Base for all fio tests.""" - def __init__(self, exe_path, success): - self.paths = {'exe': exe_path} + def __init__(self, exe_path, success, testnum, artifact_root): self.success = success + self.testnum = testnum self.output = {} - self.testnum = None self.passed = True self.failure_reason = '' - self.filenames = {} self.parameters = None - - def setup(self, artifact_root, testnum, parameters): + self.paths = { + 'exe': exe_path, + 'artifacts': artifact_root, + 'test_dir': os.path.join(artifact_root, \ + f"{testnum:04d}"), + } + self.filenames = { + 'cmd': os.path.join(self.paths['test_dir'], \ + f"{os.path.basename(self.paths['exe'])}.command"), + 'stdout': os.path.join(self.paths['test_dir'], \ + f"{os.path.basename(self.paths['exe'])}.stdout"), + 'stderr': os.path.join(self.paths['test_dir'], \ + f"{os.path.basename(self.paths['exe'])}.stderr"), + 'exitcode': os.path.join(self.paths['test_dir'], \ + f"{os.path.basename(self.paths['exe'])}.exitcode"), + } + + def setup(self, parameters): """Setup instance variables for test.""" - self.testnum = testnum self.parameters = parameters - self.paths['artifacts'] = artifact_root - self.paths['test_dir'] = os.path.join(artifact_root, f"{testnum:04d}") if not os.path.exists(self.paths['test_dir']): os.mkdir(self.paths['test_dir']) - self.filenames['cmd'] = os.path.join(self.paths['test_dir'], - f"{os.path.basename(self.paths['exe'])}.command") - self.filenames['stdout'] = os.path.join(self.paths['test_dir'], - f"{os.path.basename(self.paths['exe'])}.stdout") - self.filenames['stderr'] = os.path.join(self.paths['test_dir'], - f"{os.path.basename(self.paths['exe'])}.stderr") - self.filenames['exitcode'] = os.path.join(self.paths['test_dir'], - f"{os.path.basename(self.paths['exe'])}.exitcode") - def run(self): """Run the test.""" @@ -67,16 +69,6 @@ def check_result(self): class FioExeTest(FioTest): """Test consists of an executable binary or script""" - def __init__(self, exe_path, success): - """Construct a FioExeTest which is a FioTest consisting of an - executable binary or script. - - exe_path: location of executable binary or script - success: Definition of test success - """ - - FioTest.__init__(self, exe_path, success) - def run(self): """Execute the binary or script described by this instance.""" @@ -158,14 +150,17 @@ def check_result(self): class FioJobFileTest(FioExeTest): """Test consists of a fio job with options in a job file.""" - def __init__(self, fio_path, fio_job, success, fio_pre_job=None, - fio_pre_success=None, output_format="normal"): + def __init__(self, fio_path, fio_job, success, testnum, artifact_root, + fio_pre_job=None, fio_pre_success=None, + output_format="normal"): """Construct a FioJobFileTest which is a FioExeTest consisting of a single fio job file with an optional setup step. fio_path: location of fio executable fio_job: location of fio job file success: Definition of test success + testnum: test ID + artifact_root: root directory for artifacts fio_pre_job: fio job for preconditioning fio_pre_success: Definition of test success for fio precon job output_format: normal (default), json, jsonplus, or terse @@ -178,9 +173,9 @@ def __init__(self, fio_path, fio_job, success, fio_pre_job=None, self.precon_failed = False self.json_data = None - FioExeTest.__init__(self, fio_path, success) + super().__init__(fio_path, success, testnum, artifact_root) - def setup(self, artifact_root, testnum, parameters=None): + def setup(self, parameters=None): """Setup instance variables for fio job test.""" self.filenames['fio_output'] = f"{os.path.basename(self.fio_job)}.output" @@ -191,7 +186,7 @@ def setup(self, artifact_root, testnum, parameters=None): self.fio_job, ] - super().setup(artifact_root, testnum, fio_args) + super().setup(fio_args) # Update the filenames from the default self.filenames['cmd'] = os.path.join(self.paths['test_dir'], @@ -208,8 +203,10 @@ def run_pre_job(self): precon = FioJobFileTest(self.paths['exe'], self.fio_pre_job, self.fio_pre_success, + self.testnum, + self.paths['artifacts'], output_format=self.output_format) - precon.setup(self.paths['artifacts'], self.testnum) + precon.setup() precon.run() precon.check_result() self.precon_failed = not precon.passed @@ -308,6 +305,8 @@ def run_fio_tests(test_list, test_env, args): test_env['fio_path'], os.path.join(test_env['fio_root'], 't', 'jobs', config['job']), config['success'], + config['test_id'], + test_env['artifact_root'], fio_pre_job=fio_pre_job, fio_pre_success=fio_pre_success, output_format=output_format) @@ -324,7 +323,11 @@ def run_fio_tests(test_list, test_env, args): exe_path = "python.exe" if config['test_id'] in test_env['pass_through']: parameters += test_env['pass_through'][config['test_id']].split() - test = config['test_class'](exe_path, config['success']) + test = config['test_class']( + exe_path, + config['success'], + config['test_id'], + test_env['artifact_root']) desc = config['exe'] else: print(f"Test {config['test_id']} FAILED: unable to process test config") @@ -345,7 +348,7 @@ def run_fio_tests(test_list, test_env, args): continue try: - test.setup(test_env['artifact_root'], config['test_id'], parameters) + test.setup(parameters) test.run() test.check_result() except KeyboardInterrupt: From 8cb1dbbea2a79edfc55597fb36cb910876ddb4a3 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Tue, 6 Jun 2023 20:18:34 -0400 Subject: [PATCH 0508/1097] t/fiotestlib: record test command in more useful format Instead of recording the test command as a Python object, record it as a string with spaces delimiting each of the paramters. This way we can use it to directly issue the command in a shell. Signed-off-by: Vincent Fu --- t/fiotestlib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/t/fiotestlib.py b/t/fiotestlib.py index 73b904c52e..926b02be6c 100755 --- a/t/fiotestlib.py +++ b/t/fiotestlib.py @@ -75,7 +75,7 @@ def run(self): command = [self.paths['exe']] + self.parameters with open(self.filenames['cmd'], "w+", encoding=locale.getpreferredencoding()) as command_file: - command_file.write(f"{command}\n") + command_file.write(" ".join(command)) try: with open(self.filenames['stdout'], "w+", From 5dc5c6f6af9674dd9547093a59d6fbd77bd9d9f8 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Wed, 7 Jun 2023 13:47:53 +0000 Subject: [PATCH 0509/1097] t/fiotestlib: add class for command-line fio job Add a separate class for running a fio job with all of the options provided on the command line (instead of in a job file). Signed-off-by: Vincent Fu --- t/fiotestlib.py | 105 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 103 insertions(+), 2 deletions(-) diff --git a/t/fiotestlib.py b/t/fiotestlib.py index 926b02be6c..69f630a08b 100755 --- a/t/fiotestlib.py +++ b/t/fiotestlib.py @@ -18,7 +18,7 @@ import traceback import subprocess from pathlib import Path -from fiotestcommon import get_file +from fiotestcommon import get_file, SUCCESS_DEFAULT class FioTest(): @@ -271,6 +271,96 @@ def check_result(self): self.passed = False +class FioJobCmdTest(FioExeTest): + """This runs a fio job with options specified on the command line.""" + + def __init__(self, fio_path, success, testnum, artifact_root, fio_opts, basename=None): + + self.basename = basename if basename else os.path.basename(fio_path) + self.fio_opts = fio_opts + self.json_data = None + + super().__init__(fio_path, success, testnum, artifact_root) + + filename_stub = f"{self.basename}{self.testnum:03d}" + self.filenames['cmd'] = os.path.join(self.paths['test_dir'], f"{filename_stub}.command") + self.filenames['stdout'] = os.path.join(self.paths['test_dir'], f"{filename_stub}.stdout") + self.filenames['stderr'] = os.path.join(self.paths['test_dir'], f"{filename_stub}.stderr") + self.filenames['output'] = os.path.abspath(os.path.join(self.paths['test_dir'], + f"{filename_stub}.output")) + self.filenames['exitcode'] = os.path.join(self.paths['test_dir'], + f"{filename_stub}.exitcode") + + def run(self): + super().run() + + if 'output-format' in self.fio_opts and 'json' in \ + self.fio_opts['output-format']: + if not self.get_json(): + print('Unable to decode JSON data') + self.passed = False + + def get_json(self): + """Convert fio JSON output into a python JSON object""" + + filename = self.filenames['output'] + with open(filename, 'r', encoding=locale.getpreferredencoding()) as file: + file_data = file.read() + + # + # Sometimes fio informational messages are included at the top of the + # JSON output, especially under Windows. Try to decode output as JSON + # data, lopping off up to the first four lines + # + lines = file_data.splitlines() + for i in range(5): + file_data = '\n'.join(lines[i:]) + try: + self.json_data = json.loads(file_data) + except json.JSONDecodeError: + continue + else: + return True + + return False + + @staticmethod + def check_empty(job): + """ + Make sure JSON data is empty. + + Some data structures should be empty. This function makes sure that they are. + + job JSON object that we need to check for emptiness + """ + + return job['total_ios'] == 0 and \ + job['slat_ns']['N'] == 0 and \ + job['clat_ns']['N'] == 0 and \ + job['lat_ns']['N'] == 0 + + def check_all_ddirs(self, ddir_nonzero, job): + """ + Iterate over the data directions and check whether each is + appropriately empty or not. + """ + + retval = True + ddirlist = ['read', 'write', 'trim'] + + for ddir in ddirlist: + if ddir in ddir_nonzero: + if self.check_empty(job[ddir]): + print(f"Unexpected zero {ddir} data found in output") + retval = False + else: + if not self.check_empty(job[ddir]): + print(f"Unexpected {ddir} data found in output") + retval = False + + return retval + + def run_fio_tests(test_list, test_env, args): """ Run tests as specified in test_list. @@ -312,6 +402,17 @@ def run_fio_tests(test_list, test_env, args): output_format=output_format) desc = config['job'] parameters = [] + elif issubclass(config['test_class'], FioJobCmdTest): + if not 'success' in config: + config['success'] = SUCCESS_DEFAULT + test = config['test_class'](test_env['fio_path'], + config['success'], + config['test_id'], + test_env['artifact_root'], + config['fio_opts'], + test_env['basename']) + desc = config['test_id'] + parameters = config elif issubclass(config['test_class'], FioExeTest): exe_path = os.path.join(test_env['fio_root'], config['exe']) parameters = [] @@ -334,7 +435,7 @@ def run_fio_tests(test_list, test_env, args): failed = failed + 1 continue - if not args.skip_req: + if 'requirements' in config and not args.skip_req: reqs_met = True for req in config['requirements']: reqs_met, reason = req() From 86131edc93f6739bb6a4e85c18d6e60b731d3d80 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Tue, 6 Jun 2023 22:13:15 +0000 Subject: [PATCH 0510/1097] t/random_seed: use logging module for debug prints Instead of having 'if self.debug:' lines all over the place, use the logging module for debug messages. Signed-off-by: Vincent Fu --- t/random_seed.py | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/t/random_seed.py b/t/random_seed.py index 86f2eb219b..d8e0db8153 100755 --- a/t/random_seed.py +++ b/t/random_seed.py @@ -23,6 +23,7 @@ import sys import time import locale +import logging import argparse import subprocess from pathlib import Path @@ -161,16 +162,14 @@ def check(self): if not TestRR.seeds[rr]: TestRR.seeds[rr] = rand_seeds - if self.debug: - print(f"TestRR: saving rand_seeds for [a]rr={rr}") + logging.debug(f"TestRR: saving rand_seeds for [a]rr={rr}") else: if rr: if TestRR.seeds[1] != rand_seeds: retval = False print(f"TestRR: unexpected seed mismatch for [a]rr={rr}") else: - if self.debug: - print(f"TestRR: seeds correctly match for [a]rr={rr}") + logging.debug(f"TestRR: seeds correctly match for [a]rr={rr}") if TestRR.seeds[0] == rand_seeds: retval = False print("TestRR: seeds unexpectedly match those from system RNG") @@ -179,8 +178,7 @@ def check(self): retval = False print(f"TestRR: unexpected seed match for [a]rr={rr}") else: - if self.debug: - print(f"TestRR: seeds correctly don't match for [a]rr={rr}") + logging.debug(f"TestRR: seeds correctly don't match for [a]rr={rr}") if TestRR.seeds[1] == rand_seeds: retval = False print(f"TestRR: random seeds unexpectedly match those from [a]rr=1") @@ -204,20 +202,17 @@ def check(self): rand_seeds = self.get_rand_seeds() randseed = self.test_options['randseed'] - if self.debug: - print("randseed = ", randseed) + logging.debug(f"randseed = {randseed}") if randseed not in TestRS.seeds: TestRS.seeds[randseed] = rand_seeds - if self.debug: - print("TestRS: saving rand_seeds") + logging.debug("TestRS: saving rand_seeds") else: if TestRS.seeds[randseed] != rand_seeds: retval = False print("TestRS: seeds don't match when they should") else: - if self.debug: - print("TestRS: seeds correctly match") + logging.debug("TestRS: seeds correctly match") # Now try to find seeds generated using a different randseed and make # sure they *don't* match @@ -227,10 +222,7 @@ def check(self): retval = False print("TestRS: randseeds differ but generated seeds match.") else: - if self.debug: - print("TestRS: randseeds differ and generated seeds also differ.") - - return retval + logging.debug("TestRS: randseeds differ and generated seeds also differ.") def parse_args(): @@ -254,6 +246,11 @@ def main(): args = parse_args() + if args.debug: + logging.basicConfig(level=logging.DEBUG) + else: + logging.basicConfig(level=logging.INFO) + artifact_root = args.artifact_root if args.artifact_root else \ f"random-seed-test-{time.strftime('%Y%m%d-%H%M%S')}" os.mkdir(artifact_root) From 1017a7d3548aa1b277fee1cf2cb481707536714e Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Wed, 7 Jun 2023 13:49:18 +0000 Subject: [PATCH 0511/1097] t/random_seed: use methods provided in fiotestlib to run tests Adapt random_seed.py to use the new FioJobCmdTest class. Also use the test runner from fiotestlib. This reduces code duplication. Signed-off-by: Vincent Fu --- t/random_seed.py | 261 +++++++++++++++++++---------------------------- 1 file changed, 105 insertions(+), 156 deletions(-) diff --git a/t/random_seed.py b/t/random_seed.py index d8e0db8153..9996fdf07c 100755 --- a/t/random_seed.py +++ b/t/random_seed.py @@ -27,35 +27,13 @@ import argparse import subprocess from pathlib import Path +from fiotestlib import FioJobCmdTest, run_fio_tests -class FioRandTest(): +class FioRandTest(FioJobCmdTest): """fio random seed test.""" - def __init__(self, artifact_root, test_options, debug): - """ - artifact_root root directory for artifacts (subdirectory will be created under here) - test test specification - """ - self.artifact_root = artifact_root - self.test_options = test_options - self.debug = debug - self.filename_stub = None - self.filenames = {} - - self.test_dir = os.path.abspath(os.path.join(self.artifact_root, - f"{self.test_options['test_id']:03d}")) - if not os.path.exists(self.test_dir): - os.mkdir(self.test_dir) - - self.filename_stub = f"random{self.test_options['test_id']:03d}" - self.filenames['command'] = os.path.join(self.test_dir, f"{self.filename_stub}.command") - self.filenames['stdout'] = os.path.join(self.test_dir, f"{self.filename_stub}.stdout") - self.filenames['stderr'] = os.path.join(self.test_dir, f"{self.filename_stub}.stderr") - self.filenames['exitcode'] = os.path.join(self.test_dir, f"{self.filename_stub}.exitcode") - self.filenames['output'] = os.path.join(self.test_dir, f"{self.filename_stub}.output") - - def run_fio(self, fio_path): - """Run a test.""" + def setup(self, parameters): + """Setup the test.""" fio_args = [ "--debug=random", @@ -66,48 +44,11 @@ def run_fio(self, fio_path): f"--output={self.filenames['output']}", ] for opt in ['randseed', 'randrepeat', 'allrandrepeat']: - if opt in self.test_options: - option = f"--{opt}={self.test_options[opt]}" + if opt in self.fio_opts: + option = f"--{opt}={self.fio_opts[opt]}" fio_args.append(option) - command = [fio_path] + fio_args - with open(self.filenames['command'], "w+", encoding=locale.getpreferredencoding()) as command_file: - command_file.write(" ".join(command)) - - passed = True - - try: - with open(self.filenames['stdout'], "w+", encoding=locale.getpreferredencoding()) as stdout_file, \ - open(self.filenames['stderr'], "w+", encoding=locale.getpreferredencoding()) as stderr_file, \ - open(self.filenames['exitcode'], "w+", encoding=locale.getpreferredencoding()) as exitcode_file: - proc = None - # Avoid using subprocess.run() here because when a timeout occurs, - # fio will be stopped with SIGKILL. This does not give fio a - # chance to clean up and means that child processes may continue - # running and submitting IO. - proc = subprocess.Popen(command, - stdout=stdout_file, - stderr=stderr_file, - cwd=self.test_dir, - universal_newlines=True) - proc.communicate(timeout=300) - exitcode_file.write(f'{proc.returncode}\n') - passed &= (proc.returncode == 0) - except subprocess.TimeoutExpired: - proc.terminate() - proc.communicate() - assert proc.poll() - print("Timeout expired") - passed = False - except Exception: - if proc: - if not proc.poll(): - proc.terminate() - proc.communicate() - print(f"Exception: {sys.exc_info()}") - passed = False - - return passed + super().setup(fio_args) def get_rand_seeds(self): """Collect random seeds from --debug=random output.""" @@ -137,11 +78,6 @@ def get_rand_seeds(self): return seed_list - def check(self): - """Check test output.""" - - raise NotImplementedError() - class TestRR(FioRandTest): """ @@ -152,12 +88,11 @@ class TestRR(FioRandTest): # one set of seeds is for randrepeat=0 and the other is for randrepeat=1 seeds = { 0: None, 1: None } - def check(self): + def check_result(self): """Check output for allrandrepeat=1.""" - retval = True - opt = 'randrepeat' if 'randrepeat' in self.test_options else 'allrandrepeat' - rr = self.test_options[opt] + opt = 'randrepeat' if 'randrepeat' in self.fio_opts else 'allrandrepeat' + rr = self.fio_opts[opt] rand_seeds = self.get_rand_seeds() if not TestRR.seeds[rr]: @@ -166,25 +101,23 @@ def check(self): else: if rr: if TestRR.seeds[1] != rand_seeds: - retval = False + self.passed = False print(f"TestRR: unexpected seed mismatch for [a]rr={rr}") else: logging.debug(f"TestRR: seeds correctly match for [a]rr={rr}") if TestRR.seeds[0] == rand_seeds: - retval = False + self.passed = False print("TestRR: seeds unexpectedly match those from system RNG") else: if TestRR.seeds[0] == rand_seeds: - retval = False + self.passed = False print(f"TestRR: unexpected seed match for [a]rr={rr}") else: logging.debug(f"TestRR: seeds correctly don't match for [a]rr={rr}") if TestRR.seeds[1] == rand_seeds: - retval = False + self.passed = False print(f"TestRR: random seeds unexpectedly match those from [a]rr=1") - return retval - class TestRS(FioRandTest): """ @@ -195,12 +128,11 @@ class TestRS(FioRandTest): """ seeds = {} - def check(self): + def check_result(self): """Check output for randseed=something.""" - retval = True rand_seeds = self.get_rand_seeds() - randseed = self.test_options['randseed'] + randseed = self.fio_opts['randseed'] logging.debug(f"randseed = {randseed}") @@ -209,7 +141,7 @@ def check(self): logging.debug("TestRS: saving rand_seeds") else: if TestRS.seeds[randseed] != rand_seeds: - retval = False + self.passed = False print("TestRS: seeds don't match when they should") else: logging.debug("TestRS: seeds correctly match") @@ -219,7 +151,7 @@ def check(self): for key in TestRS.seeds: if key != randseed: if TestRS.seeds[key] == rand_seeds: - retval = False + self.passed = False print("TestRS: randseeds differ but generated seeds match.") else: logging.debug("TestRS: randseeds differ and generated seeds also differ.") @@ -257,133 +189,150 @@ def main(): print(f"Artifact directory is {artifact_root}") if args.fio: - fio = str(Path(args.fio).absolute()) + fio_path = str(Path(args.fio).absolute()) else: - fio = 'fio' - print(f"fio path is {fio}") + fio_path = 'fio' + print(f"fio path is {fio_path}") test_list = [ { "test_id": 1, - "randrepeat": 0, - "test_obj": TestRR, + "fio_opts": { + "randrepeat": 0, + }, + "test_class": TestRR, }, { "test_id": 2, - "randrepeat": 0, - "test_obj": TestRR, + "fio_opts": { + "randrepeat": 0, + }, + "test_class": TestRR, }, { "test_id": 3, - "randrepeat": 1, - "test_obj": TestRR, + "fio_opts": { + "randrepeat": 1, + }, + "test_class": TestRR, }, { "test_id": 4, - "randrepeat": 1, - "test_obj": TestRR, + "fio_opts": { + "randrepeat": 1, + }, + "test_class": TestRR, }, { "test_id": 5, - "allrandrepeat": 0, - "test_obj": TestRR, + "fio_opts": { + "allrandrepeat": 0, + }, + "test_class": TestRR, }, { "test_id": 6, - "allrandrepeat": 0, - "test_obj": TestRR, + "fio_opts": { + "allrandrepeat": 0, + }, + "test_class": TestRR, }, { "test_id": 7, - "allrandrepeat": 1, - "test_obj": TestRR, + "fio_opts": { + "allrandrepeat": 1, + }, + "test_class": TestRR, }, { "test_id": 8, - "allrandrepeat": 1, - "test_obj": TestRR, + "fio_opts": { + "allrandrepeat": 1, + }, + "test_class": TestRR, }, { "test_id": 9, - "randrepeat": 0, - "randseed": "12345", - "test_obj": TestRS, + "fio_opts": { + "randrepeat": 0, + "randseed": "12345", + }, + "test_class": TestRS, }, { "test_id": 10, - "randrepeat": 0, - "randseed": "12345", - "test_obj": TestRS, + "fio_opts": { + "randrepeat": 0, + "randseed": "12345", + }, + "test_class": TestRS, }, { "test_id": 11, - "randrepeat": 1, - "randseed": "12345", - "test_obj": TestRS, + "fio_opts": { + "randrepeat": 1, + "randseed": "12345", + }, + "test_class": TestRS, }, { "test_id": 12, - "allrandrepeat": 0, - "randseed": "12345", - "test_obj": TestRS, + "fio_opts": { + "allrandrepeat": 0, + "randseed": "12345", + }, + "test_class": TestRS, }, { "test_id": 13, - "allrandrepeat": 1, - "randseed": "12345", - "test_obj": TestRS, + "fio_opts": { + "allrandrepeat": 1, + "randseed": "12345", + }, + "test_class": TestRS, }, { "test_id": 14, - "randrepeat": 0, - "randseed": "67890", - "test_obj": TestRS, + "fio_opts": { + "randrepeat": 0, + "randseed": "67890", + }, + "test_class": TestRS, }, { "test_id": 15, - "randrepeat": 1, - "randseed": "67890", - "test_obj": TestRS, + "fio_opts": { + "randrepeat": 1, + "randseed": "67890", + }, + "test_class": TestRS, }, { "test_id": 16, - "allrandrepeat": 0, - "randseed": "67890", - "test_obj": TestRS, + "fio_opts": { + "allrandrepeat": 0, + "randseed": "67890", + }, + "test_class": TestRS, }, { "test_id": 17, - "allrandrepeat": 1, - "randseed": "67890", - "test_obj": TestRS, + "fio_opts": { + "allrandrepeat": 1, + "randseed": "67890", + }, + "test_class": TestRS, }, ] - passed = 0 - failed = 0 - skipped = 0 - - for test in test_list: - if (args.skip and test['test_id'] in args.skip) or \ - (args.run_only and test['test_id'] not in args.run_only): - skipped = skipped + 1 - outcome = 'SKIPPED (User request)' - else: - test_obj = test['test_obj'](artifact_root, test, args.debug) - status = test_obj.run_fio(fio) - if status: - status = test_obj.check() - if status: - passed = passed + 1 - outcome = 'PASSED' - else: - failed = failed + 1 - outcome = 'FAILED' - - print(f"**********Test {test['test_id']} {outcome}**********") - - print(f"{passed} tests passed, {failed} failed, {skipped} skipped") + test_env = { + 'fio_path': fio_path, + 'fio_root': str(Path(__file__).absolute().parent.parent), + 'artifact_root': artifact_root, + 'basename': 'random', + } + _, failed, _ = run_fio_tests(test_list, test_env, args) sys.exit(failed) From cab6c7d1745e4f437f833ed5fc4030f007a64afe Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Tue, 6 Jun 2023 20:00:23 -0400 Subject: [PATCH 0512/1097] t/random_seed: fixes from pylint - Use % formatting for logging.debug - Eliminate f-string without interpolated values - Iterate with items() instead of just over keys - Remove unused subprocess import Signed-off-by: Vincent Fu --- t/random_seed.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/t/random_seed.py b/t/random_seed.py index 9996fdf07c..02187046e5 100755 --- a/t/random_seed.py +++ b/t/random_seed.py @@ -25,7 +25,6 @@ import locale import logging import argparse -import subprocess from pathlib import Path from fiotestlib import FioJobCmdTest, run_fio_tests @@ -52,7 +51,8 @@ def setup(self, parameters): def get_rand_seeds(self): """Collect random seeds from --debug=random output.""" - with open(self.filenames['output'], "r", encoding=locale.getpreferredencoding()) as out_file: + with open(self.filenames['output'], "r", + encoding=locale.getpreferredencoding()) as out_file: file_data = out_file.read() offsets = 0 @@ -97,14 +97,14 @@ def check_result(self): if not TestRR.seeds[rr]: TestRR.seeds[rr] = rand_seeds - logging.debug(f"TestRR: saving rand_seeds for [a]rr={rr}") + logging.debug("TestRR: saving rand_seeds for [a]rr=%d", rr) else: if rr: if TestRR.seeds[1] != rand_seeds: self.passed = False print(f"TestRR: unexpected seed mismatch for [a]rr={rr}") else: - logging.debug(f"TestRR: seeds correctly match for [a]rr={rr}") + logging.debug("TestRR: seeds correctly match for [a]rr=%d", rr) if TestRR.seeds[0] == rand_seeds: self.passed = False print("TestRR: seeds unexpectedly match those from system RNG") @@ -113,10 +113,10 @@ def check_result(self): self.passed = False print(f"TestRR: unexpected seed match for [a]rr={rr}") else: - logging.debug(f"TestRR: seeds correctly don't match for [a]rr={rr}") + logging.debug("TestRR: seeds correctly don't match for [a]rr=%d", rr) if TestRR.seeds[1] == rand_seeds: self.passed = False - print(f"TestRR: random seeds unexpectedly match those from [a]rr=1") + print("TestRR: random seeds unexpectedly match those from [a]rr=1") class TestRS(FioRandTest): @@ -134,7 +134,7 @@ def check_result(self): rand_seeds = self.get_rand_seeds() randseed = self.fio_opts['randseed'] - logging.debug(f"randseed = {randseed}") + logging.debug("randseed = %s", randseed) if randseed not in TestRS.seeds: TestRS.seeds[randseed] = rand_seeds @@ -148,9 +148,9 @@ def check_result(self): # Now try to find seeds generated using a different randseed and make # sure they *don't* match - for key in TestRS.seeds: + for key, value in TestRS.seeds.items(): if key != randseed: - if TestRS.seeds[key] == rand_seeds: + if value == rand_seeds: self.passed = False print("TestRS: randseeds differ but generated seeds match.") else: From fef0c6fd2ad91debdd4b9fa1d64314b8bb90e9da Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Wed, 7 Jun 2023 15:21:31 +0000 Subject: [PATCH 0513/1097] t/readonly: adapt to use fiotestlib Use the test runner and testclass provided in fiotestlib. Signed-off-by: Vincent Fu --- t/readonly.py | 220 +++++++++++++++++++++++++++----------------------- 1 file changed, 121 insertions(+), 99 deletions(-) diff --git a/t/readonly.py b/t/readonly.py index 80fac6393d..d36faafa7c 100755 --- a/t/readonly.py +++ b/t/readonly.py @@ -2,8 +2,8 @@ # SPDX-License-Identifier: GPL-2.0-only # # Copyright (c) 2019 Western Digital Corporation or its affiliates. -# -# + +""" # readonly.py # # Do some basic tests of the --readonly parameter @@ -18,122 +18,144 @@ # REQUIREMENTS # Python 3.5+ # -# +""" +import os import sys +import time import argparse -import subprocess +from pathlib import Path +from fiotestlib import FioJobCmdTest, run_fio_tests +from fiotestcommon import SUCCESS_DEFAULT, SUCCESS_NONZERO + + +class FioReadOnlyTest(FioJobCmdTest): + """fio read only test.""" + + def setup(self, parameters): + """Setup the test.""" + + fio_args = [ + "--name=readonly", + "--ioengine=null", + "--time_based", + "--runtime=1s", + "--size=1M", + f"--rw={self.fio_opts['rw']}", + ] + if 'readonly-pre' in parameters: + fio_args.insert(0, "--readonly") + if 'readonly-post' in parameters: + fio_args.append("--readonly") + + super().setup(fio_args) + + +TEST_LIST = [ + { + "test_id": 1, + "fio_opts": { "rw": "randread", }, + "readonly-pre": 1, + "success": SUCCESS_DEFAULT, + "test_class": FioReadOnlyTest, + }, + { + "test_id": 2, + "fio_opts": { "rw": "randwrite", }, + "readonly-pre": 1, + "success": SUCCESS_NONZERO, + "test_class": FioReadOnlyTest, + }, + { + "test_id": 3, + "fio_opts": { "rw": "randtrim", }, + "readonly-pre": 1, + "success": SUCCESS_NONZERO, + "test_class": FioReadOnlyTest, + }, + { + "test_id": 4, + "fio_opts": { "rw": "randread", }, + "readonly-post": 1, + "success": SUCCESS_DEFAULT, + "test_class": FioReadOnlyTest, + }, + { + "test_id": 5, + "fio_opts": { "rw": "randwrite", }, + "readonly-post": 1, + "success": SUCCESS_NONZERO, + "test_class": FioReadOnlyTest, + }, + { + "test_id": 6, + "fio_opts": { "rw": "randtrim", }, + "readonly-post": 1, + "success": SUCCESS_NONZERO, + "test_class": FioReadOnlyTest, + }, + { + "test_id": 7, + "fio_opts": { "rw": "randread", }, + "success": SUCCESS_DEFAULT, + "test_class": FioReadOnlyTest, + }, + { + "test_id": 8, + "fio_opts": { "rw": "randwrite", }, + "success": SUCCESS_DEFAULT, + "test_class": FioReadOnlyTest, + }, + { + "test_id": 9, + "fio_opts": { "rw": "randtrim", }, + "success": SUCCESS_DEFAULT, + "test_class": FioReadOnlyTest, + }, + ] def parse_args(): + """Parse command-line arguments.""" + parser = argparse.ArgumentParser() - parser.add_argument('-f', '--fio', - help='path to fio executable (e.g., ./fio)') + parser.add_argument('-f', '--fio', help='path to fio executable (e.g., ./fio)') + parser.add_argument('-a', '--artifact-root', help='artifact root directory') + parser.add_argument('-s', '--skip', nargs='+', type=int, + help='list of test(s) to skip') + parser.add_argument('-o', '--run-only', nargs='+', type=int, + help='list of test(s) to run, skipping all others') args = parser.parse_args() return args -def run_fio(fio, test, index): - fio_args = [ - "--max-jobs=16", - "--name=readonly", - "--ioengine=null", - "--time_based", - "--runtime=1s", - "--size=1M", - "--rw={rw}".format(**test), - ] - if 'readonly-pre' in test: - fio_args.insert(0, "--readonly") - if 'readonly-post' in test: - fio_args.append("--readonly") - - output = subprocess.run([fio] + fio_args, stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - - return output - - -def check_output(output, test): - expect_error = False - if 'readonly-pre' in test or 'readonly-post' in test: - if 'write' in test['rw'] or 'trim' in test['rw']: - expect_error = True - -# print(output.stdout) -# print(output.stderr) - - if output.returncode == 0: - if expect_error: - return False - else: - return True - else: - if expect_error: - return True - else: - return False - +def main(): + """Run readonly tests.""" -if __name__ == '__main__': args = parse_args() - tests = [ - { - "rw": "randread", - "readonly-pre": 1, - }, - { - "rw": "randwrite", - "readonly-pre": 1, - }, - { - "rw": "randtrim", - "readonly-pre": 1, - }, - { - "rw": "randread", - "readonly-post": 1, - }, - { - "rw": "randwrite", - "readonly-post": 1, - }, - { - "rw": "randtrim", - "readonly-post": 1, - }, - { - "rw": "randread", - }, - { - "rw": "randwrite", - }, - { - "rw": "randtrim", - }, - ] - - index = 1 - passed = 0 - failed = 0 - if args.fio: - fio_path = args.fio + fio_path = str(Path(args.fio).absolute()) else: fio_path = 'fio' + print(f"fio path is {fio_path}") - for test in tests: - output = run_fio(fio_path, test, index) - status = check_output(output, test) - print("Test {0} {1}".format(index, ("PASSED" if status else "FAILED"))) - if status: - passed = passed + 1 - else: - failed = failed + 1 - index = index + 1 + artifact_root = args.artifact_root if args.artifact_root else \ + f"readonly-test-{time.strftime('%Y%m%d-%H%M%S')}" + os.mkdir(artifact_root) + print(f"Artifact directory is {artifact_root}") - print("{0} tests passed, {1} failed".format(passed, failed)) + test_env = { + 'fio_path': fio_path, + 'fio_root': str(Path(__file__).absolute().parent.parent), + 'artifact_root': artifact_root, + 'basename': 'readonly', + } + _, failed, _ = run_fio_tests(TEST_LIST, test_env, args) sys.exit(failed) + + +if __name__ == '__main__': + main() From cebaf30e072440c271b70468f45911c8405cdb2f Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Wed, 7 Jun 2023 16:49:18 +0000 Subject: [PATCH 0514/1097] t/nvmept: adapt to use fiotestlib Use the FioJobCmdTest class and the test runner from fiotestlib. Signed-off-by: Vincent Fu --- t/nvmept.py | 447 +++++++++++++++++++--------------------------------- 1 file changed, 159 insertions(+), 288 deletions(-) diff --git a/t/nvmept.py b/t/nvmept.py index a25192f2d4..e235d160ca 100755 --- a/t/nvmept.py +++ b/t/nvmept.py @@ -17,42 +17,20 @@ """ import os import sys -import json import time -import locale import argparse -import subprocess from pathlib import Path +from fiotestlib import FioJobCmdTest, run_fio_tests -class FioTest(): - """fio test.""" - def __init__(self, artifact_root, test_opts, debug): - """ - artifact_root root directory for artifacts (subdirectory will be created under here) - test test specification - """ - self.artifact_root = artifact_root - self.test_opts = test_opts - self.debug = debug - self.filename_stub = None - self.filenames = {} - self.json_data = None - - self.test_dir = os.path.abspath(os.path.join(self.artifact_root, - f"{self.test_opts['test_id']:03d}")) - if not os.path.exists(self.test_dir): - os.mkdir(self.test_dir) - - self.filename_stub = f"pt{self.test_opts['test_id']:03d}" - self.filenames['command'] = os.path.join(self.test_dir, f"{self.filename_stub}.command") - self.filenames['stdout'] = os.path.join(self.test_dir, f"{self.filename_stub}.stdout") - self.filenames['stderr'] = os.path.join(self.test_dir, f"{self.filename_stub}.stderr") - self.filenames['exitcode'] = os.path.join(self.test_dir, f"{self.filename_stub}.exitcode") - self.filenames['output'] = os.path.join(self.test_dir, f"{self.filename_stub}.output") +class PassThruTest(FioJobCmdTest): + """ + NVMe pass-through test class. Check to make sure output for selected data + direction(s) is non-zero and that zero data appears for other directions. + """ - def run_fio(self, fio_path): - """Run a test.""" + def setup(self, parameters): + """Setup a test.""" fio_args = [ "--name=nvmept", @@ -61,300 +39,172 @@ def run_fio(self, fio_path): "--iodepth=8", "--iodepth_batch=4", "--iodepth_batch_complete=4", - f"--filename={self.test_opts['filename']}", - f"--rw={self.test_opts['rw']}", + f"--filename={self.fio_opts['filename']}", + f"--rw={self.fio_opts['rw']}", f"--output={self.filenames['output']}", - f"--output-format={self.test_opts['output-format']}", + f"--output-format={self.fio_opts['output-format']}", ] for opt in ['fixedbufs', 'nonvectored', 'force_async', 'registerfiles', 'sqthread_poll', 'sqthread_poll_cpu', 'hipri', 'nowait', 'time_based', 'runtime', 'verify', 'io_size']: - if opt in self.test_opts: - option = f"--{opt}={self.test_opts[opt]}" + if opt in self.fio_opts: + option = f"--{opt}={self.fio_opts[opt]}" fio_args.append(option) - command = [fio_path] + fio_args - with open(self.filenames['command'], "w+", - encoding=locale.getpreferredencoding()) as command_file: - command_file.write(" ".join(command)) - - passed = True - - try: - with open(self.filenames['stdout'], "w+", - encoding=locale.getpreferredencoding()) as stdout_file, \ - open(self.filenames['stderr'], "w+", - encoding=locale.getpreferredencoding()) as stderr_file, \ - open(self.filenames['exitcode'], "w+", - encoding=locale.getpreferredencoding()) as exitcode_file: - proc = None - # Avoid using subprocess.run() here because when a timeout occurs, - # fio will be stopped with SIGKILL. This does not give fio a - # chance to clean up and means that child processes may continue - # running and submitting IO. - proc = subprocess.Popen(command, - stdout=stdout_file, - stderr=stderr_file, - cwd=self.test_dir, - universal_newlines=True) - proc.communicate(timeout=300) - exitcode_file.write(f'{proc.returncode}\n') - passed &= (proc.returncode == 0) - except subprocess.TimeoutExpired: - proc.terminate() - proc.communicate() - assert proc.poll() - print("Timeout expired") - passed = False - except Exception: - if proc: - if not proc.poll(): - proc.terminate() - proc.communicate() - print(f"Exception: {sys.exc_info()}") - passed = False - - if passed: - if 'output-format' in self.test_opts and 'json' in \ - self.test_opts['output-format']: - if not self.get_json(): - print('Unable to decode JSON data') - passed = False - - return passed - - def get_json(self): - """Convert fio JSON output into a python JSON object""" - - filename = self.filenames['output'] - with open(filename, 'r', encoding=locale.getpreferredencoding()) as file: - file_data = file.read() - - # - # Sometimes fio informational messages are included at the top of the - # JSON output, especially under Windows. Try to decode output as JSON - # data, lopping off up to the first four lines - # - lines = file_data.splitlines() - for i in range(5): - file_data = '\n'.join(lines[i:]) - try: - self.json_data = json.loads(file_data) - except json.JSONDecodeError: - continue - else: - return True - - return False - - @staticmethod - def check_empty(job): - """ - Make sure JSON data is empty. - - Some data structures should be empty. This function makes sure that they are. - - job JSON object that we need to check for emptiness - """ - - return job['total_ios'] == 0 and \ - job['slat_ns']['N'] == 0 and \ - job['clat_ns']['N'] == 0 and \ - job['lat_ns']['N'] == 0 - - def check_all_ddirs(self, ddir_nonzero, job): - """ - Iterate over the data directions and check whether each is - appropriately empty or not. - """ - - retval = True - ddirlist = ['read', 'write', 'trim'] - - for ddir in ddirlist: - if ddir in ddir_nonzero: - if self.check_empty(job[ddir]): - print(f"Unexpected zero {ddir} data found in output") - retval = False - else: - if not self.check_empty(job[ddir]): - print(f"Unexpected {ddir} data found in output") - retval = False - - return retval - - def check(self): - """Check test output.""" - - raise NotImplementedError() + super().setup(fio_args) -class PTTest(FioTest): - """ - NVMe pass-through test class. Check to make sure output for selected data - direction(s) is non-zero and that zero data appears for other directions. - """ + def check_result(self): + if 'rw' not in self.fio_opts: + return - def check(self): - if 'rw' not in self.test_opts: - return True + if not self.passed: + return job = self.json_data['jobs'][0] - retval = True - if self.test_opts['rw'] in ['read', 'randread']: - retval = self.check_all_ddirs(['read'], job) - elif self.test_opts['rw'] in ['write', 'randwrite']: - if 'verify' not in self.test_opts: - retval = self.check_all_ddirs(['write'], job) + if self.fio_opts['rw'] in ['read', 'randread']: + self.passed = self.check_all_ddirs(['read'], job) + elif self.fio_opts['rw'] in ['write', 'randwrite']: + if 'verify' not in self.fio_opts: + self.passed = self.check_all_ddirs(['write'], job) else: - retval = self.check_all_ddirs(['read', 'write'], job) - elif self.test_opts['rw'] in ['trim', 'randtrim']: - retval = self.check_all_ddirs(['trim'], job) - elif self.test_opts['rw'] in ['readwrite', 'randrw']: - retval = self.check_all_ddirs(['read', 'write'], job) - elif self.test_opts['rw'] in ['trimwrite', 'randtrimwrite']: - retval = self.check_all_ddirs(['trim', 'write'], job) + self.passed = self.check_all_ddirs(['read', 'write'], job) + elif self.fio_opts['rw'] in ['trim', 'randtrim']: + self.passed = self.check_all_ddirs(['trim'], job) + elif self.fio_opts['rw'] in ['readwrite', 'randrw']: + self.passed = self.check_all_ddirs(['read', 'write'], job) + elif self.fio_opts['rw'] in ['trimwrite', 'randtrimwrite']: + self.passed = self.check_all_ddirs(['trim', 'write'], job) else: - print(f"Unhandled rw value {self.test_opts['rw']}") - retval = False - - return retval - + print(f"Unhandled rw value {self.fio_opts['rw']}") + self.passed = False -def parse_args(): - """Parse command-line arguments.""" - parser = argparse.ArgumentParser() - parser.add_argument('-f', '--fio', help='path to file executable (e.g., ./fio)') - parser.add_argument('-a', '--artifact-root', help='artifact root directory') - parser.add_argument('-d', '--debug', help='enable debug output', action='store_true') - parser.add_argument('-s', '--skip', nargs='+', type=int, - help='list of test(s) to skip') - parser.add_argument('-o', '--run-only', nargs='+', type=int, - help='list of test(s) to run, skipping all others') - parser.add_argument('--dut', help='target NVMe character device to test ' - '(e.g., /dev/ng0n1). WARNING: THIS IS A DESTRUCTIVE TEST', required=True) - args = parser.parse_args() - - return args - - -def main(): - """Run tests using fio's io_uring_cmd ioengine to send NVMe pass through commands.""" - - args = parse_args() - - artifact_root = args.artifact_root if args.artifact_root else \ - f"nvmept-test-{time.strftime('%Y%m%d-%H%M%S')}" - os.mkdir(artifact_root) - print(f"Artifact directory is {artifact_root}") - - if args.fio: - fio = str(Path(args.fio).absolute()) - else: - fio = 'fio' - print(f"fio path is {fio}") - - test_list = [ - { - "test_id": 1, +TEST_LIST = [ + { + "test_id": 1, + "fio_opts": { "rw": 'read', "timebased": 1, "runtime": 3, "output-format": "json", - "test_obj": PTTest, - }, - { - "test_id": 2, + }, + "test_class": PassThruTest, + }, + { + "test_id": 2, + "fio_opts": { "rw": 'randread', "timebased": 1, "runtime": 3, "output-format": "json", - "test_obj": PTTest, - }, - { - "test_id": 3, + }, + "test_class": PassThruTest, + }, + { + "test_id": 3, + "fio_opts": { "rw": 'write', "timebased": 1, "runtime": 3, "output-format": "json", - "test_obj": PTTest, - }, - { - "test_id": 4, + }, + "test_class": PassThruTest, + }, + { + "test_id": 4, + "fio_opts": { "rw": 'randwrite', "timebased": 1, "runtime": 3, "output-format": "json", - "test_obj": PTTest, - }, - { - "test_id": 5, + }, + "test_class": PassThruTest, + }, + { + "test_id": 5, + "fio_opts": { "rw": 'trim', "timebased": 1, "runtime": 3, "output-format": "json", - "test_obj": PTTest, - }, - { - "test_id": 6, + }, + "test_class": PassThruTest, + }, + { + "test_id": 6, + "fio_opts": { "rw": 'randtrim', "timebased": 1, "runtime": 3, "output-format": "json", - "test_obj": PTTest, - }, - { - "test_id": 7, + }, + "test_class": PassThruTest, + }, + { + "test_id": 7, + "fio_opts": { "rw": 'write', "io_size": 1024*1024, "verify": "crc32c", "output-format": "json", - "test_obj": PTTest, - }, - { - "test_id": 8, + }, + "test_class": PassThruTest, + }, + { + "test_id": 8, + "fio_opts": { "rw": 'randwrite', "io_size": 1024*1024, "verify": "crc32c", "output-format": "json", - "test_obj": PTTest, - }, - { - "test_id": 9, + }, + "test_class": PassThruTest, + }, + { + "test_id": 9, + "fio_opts": { "rw": 'readwrite', "timebased": 1, "runtime": 3, "output-format": "json", - "test_obj": PTTest, - }, - { - "test_id": 10, + }, + "test_class": PassThruTest, + }, + { + "test_id": 10, + "fio_opts": { "rw": 'randrw', "timebased": 1, "runtime": 3, "output-format": "json", - "test_obj": PTTest, - }, - { - "test_id": 11, + }, + "test_class": PassThruTest, + }, + { + "test_id": 11, + "fio_opts": { "rw": 'trimwrite', "timebased": 1, "runtime": 3, "output-format": "json", - "test_obj": PTTest, - }, - { - "test_id": 12, + }, + "test_class": PassThruTest, + }, + { + "test_id": 12, + "fio_opts": { "rw": 'randtrimwrite', "timebased": 1, "runtime": 3, "output-format": "json", - "test_obj": PTTest, - }, - { - "test_id": 13, + }, + "test_class": PassThruTest, + }, + { + "test_id": 13, + "fio_opts": { "rw": 'randread', "timebased": 1, "runtime": 3, @@ -364,10 +214,12 @@ def main(): "registerfiles": 1, "sqthread_poll": 1, "output-format": "json", - "test_obj": PTTest, - }, - { - "test_id": 14, + }, + "test_class": PassThruTest, + }, + { + "test_id": 14, + "fio_opts": { "rw": 'randwrite', "timebased": 1, "runtime": 3, @@ -377,36 +229,55 @@ def main(): "registerfiles": 1, "sqthread_poll": 1, "output-format": "json", - "test_obj": PTTest, - }, - ] + }, + "test_class": PassThruTest, + }, +] - passed = 0 - failed = 0 - skipped = 0 +def parse_args(): + """Parse command-line arguments.""" - for test in test_list: - if (args.skip and test['test_id'] in args.skip) or \ - (args.run_only and test['test_id'] not in args.run_only): - skipped = skipped + 1 - outcome = 'SKIPPED (User request)' - else: - test['filename'] = args.dut - test_obj = test['test_obj'](artifact_root, test, args.debug) - status = test_obj.run_fio(fio) - if status: - status = test_obj.check() - if status: - passed = passed + 1 - outcome = 'PASSED' - else: - failed = failed + 1 - outcome = 'FAILED' + parser = argparse.ArgumentParser() + parser.add_argument('-f', '--fio', help='path to file executable (e.g., ./fio)') + parser.add_argument('-a', '--artifact-root', help='artifact root directory') + parser.add_argument('-s', '--skip', nargs='+', type=int, + help='list of test(s) to skip') + parser.add_argument('-o', '--run-only', nargs='+', type=int, + help='list of test(s) to run, skipping all others') + parser.add_argument('--dut', help='target NVMe character device to test ' + '(e.g., /dev/ng0n1). WARNING: THIS IS A DESTRUCTIVE TEST', required=True) + args = parser.parse_args() + + return args + + +def main(): + """Run tests using fio's io_uring_cmd ioengine to send NVMe pass through commands.""" + + args = parse_args() + + artifact_root = args.artifact_root if args.artifact_root else \ + f"nvmept-test-{time.strftime('%Y%m%d-%H%M%S')}" + os.mkdir(artifact_root) + print(f"Artifact directory is {artifact_root}") + + if args.fio: + fio_path = str(Path(args.fio).absolute()) + else: + fio_path = 'fio' + print(f"fio path is {fio_path}") - print(f"**********Test {test['test_id']} {outcome}**********") + for test in TEST_LIST: + test['fio_opts']['filename'] = args.dut - print(f"{passed} tests passed, {failed} failed, {skipped} skipped") + test_env = { + 'fio_path': fio_path, + 'fio_root': str(Path(__file__).absolute().parent.parent), + 'artifact_root': artifact_root, + 'basename': 'readonly', + } + _, failed, _ = run_fio_tests(TEST_LIST, test_env, args) sys.exit(failed) From 885e170af6501530f884e3be40fe413095d39fb1 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Wed, 7 Jun 2023 21:21:50 +0000 Subject: [PATCH 0515/1097] t/fiotestlib: add ability to ingest iops logs Enhance the FioJobCmdTest class with the ability to read in an IOPS log if one was generated during the course of the test run. This reads in only the first IOPS log written as a result of --write_iops_log. Signed-off-by: Vincent Fu --- t/fiotestlib.py | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/t/fiotestlib.py b/t/fiotestlib.py index 69f630a08b..0fe17b74ba 100755 --- a/t/fiotestlib.py +++ b/t/fiotestlib.py @@ -279,17 +279,17 @@ def __init__(self, fio_path, success, testnum, artifact_root, fio_opts, basename self.basename = basename if basename else os.path.basename(fio_path) self.fio_opts = fio_opts self.json_data = None + self.iops_log_lines = None super().__init__(fio_path, success, testnum, artifact_root) - filename_stub = f"{self.basename}{self.testnum:03d}" - self.filenames['cmd'] = os.path.join(self.paths['test_dir'], f"{filename_stub}.command") - self.filenames['stdout'] = os.path.join(self.paths['test_dir'], f"{filename_stub}.stdout") - self.filenames['stderr'] = os.path.join(self.paths['test_dir'], f"{filename_stub}.stderr") - self.filenames['output'] = os.path.abspath(os.path.join(self.paths['test_dir'], - f"{filename_stub}.output")) - self.filenames['exitcode'] = os.path.join(self.paths['test_dir'], - f"{filename_stub}.exitcode") + filename_stub = os.path.join(self.paths['test_dir'], f"{self.basename}{self.testnum:03d}") + self.filenames['cmd'] = f"{filename_stub}.command" + self.filenames['stdout'] = f"{filename_stub}.stdout" + self.filenames['stderr'] = f"{filename_stub}.stderr" + self.filenames['output'] = os.path.abspath(f"{filename_stub}.output") + self.filenames['exitcode'] = f"{filename_stub}.exitcode" + self.filenames['iopslog'] = os.path.abspath(f"{filename_stub}") def run(self): super().run() @@ -300,6 +300,16 @@ def run(self): print('Unable to decode JSON data') self.passed = False + if any('--write_iops_log=' in param for param in self.parameters): + self.get_iops_log() + + def get_iops_log(self): + """Read IOPS log from the first job.""" + + log_filename = self.filenames['iopslog'] + "_iops.1.log" + with open(log_filename, 'r', encoding=locale.getpreferredencoding()) as iops_file: + self.iops_log_lines = iops_file.read() + def get_json(self): """Convert fio JSON output into a python JSON object""" From 5a36d0e473f41e3dac7556ae7bbcf66614f79b85 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Wed, 7 Jun 2023 21:24:40 +0000 Subject: [PATCH 0516/1097] t/strided: adapt to use fiotestlib Use the FioJobCmdTest class and the run_fio_test runner to execute the tests. Also update run-fio-tests.py to accommodate the new way of specifying the path to the fio executable. Signed-off-by: Vincent Fu --- t/run-fio-tests.py | 2 +- t/strided.py | 687 +++++++++++++++++++++++++-------------------- 2 files changed, 391 insertions(+), 298 deletions(-) diff --git a/t/run-fio-tests.py b/t/run-fio-tests.py index 4d63a86e2e..1448f7cb9f 100755 --- a/t/run-fio-tests.py +++ b/t/run-fio-tests.py @@ -890,7 +890,7 @@ def check_result(self): 'test_id': 1006, 'test_class': FioExeTest, 'exe': 't/strided.py', - 'parameters': ['{fio_path}'], + 'parameters': ['--fio', '{fio_path}'], 'success': SUCCESS_DEFAULT, 'requirements': [], }, diff --git a/t/strided.py b/t/strided.py index 45e6f148e1..c5d667778c 100755 --- a/t/strided.py +++ b/t/strided.py @@ -1,5 +1,6 @@ #!/usr/bin/env python3 -# + +""" # strided.py # # Test zonemode=strided. This uses the null ioengine when no file is @@ -16,9 +17,6 @@ # dd if=/dev/zero of=temp bs=1M count=32 # python t/strided.py ./fio -f temp # -# REQUIREMENTS -# Python 2.6+ -# # ===TEST MATRIX=== # # --zonemode=strided, zoneskip unset @@ -28,322 +26,417 @@ # zonesize= test['filesize']: - zonestart = 0 if 'offset' not in test else test['offset'] - - iosperzone = iosperzone + 1 - tokens = line.split(',') - offset = int(tokens[4]) - if offset < zonestart or offset >= zonestart + test['zonerange']: - print("Offset {0} outside of zone starting at {1}".format( - offset, zonestart)) - return False - - # skip next section if norandommap is enabled with no - # random_generator or with a random_generator != lfsr - if 'norandommap' in test: - if 'random_generator' in test: - if test['random_generator'] != 'lfsr': - continue - else: + if 'filename' in self.fio_opts: + for opt in ['filename', 'filesize']: + option = f"--{opt}={self.fio_opts[opt]}" + fio_args.append(option) + else: + fio_args.append('--ioengine=null') + for opt in ['size', 'io_size', 'filesize']: + option = f"--{opt}={self.fio_opts[opt]}" + fio_args.append(option) + + super().setup(fio_args) + + def check_result(self): + zonestart = 0 if 'offset' not in self.fio_opts else self.fio_opts['offset'] + iospersize = self.fio_opts['zonesize'] / self.fio_opts['bs'] + iosperrange = self.fio_opts['zonerange'] / self.fio_opts['bs'] + iosperzone = 0 + lines = self.iops_log_lines.split('\n') + zoneset = set() + + for line in lines: + if len(line) == 0: continue - # we either have a random map enabled or we - # are using an LFSR - # so all blocks should be unique and we should have - # covered the entire zone when iosperzone % iosperrange == 0 - block = (offset - zonestart) / test['bs'] - if block in zoneset: - print("Offset {0} in zone already touched".format(offset)) - return False - - zoneset.add(block) - if iosperzone % iosperrange == 0: - if len(zoneset) != iosperrange: - print("Expected {0} blocks in zone but only saw {1}".format( - iosperrange, len(zoneset))) + if iosperzone == iospersize: + # time to move to a new zone + iosperzone = 0 + zoneset = set() + zonestart += self.fio_opts['zonerange'] + if zonestart >= self.fio_opts['filesize']: + zonestart = 0 if 'offset' not in self.fio_opts else self.fio_opts['offset'] + + iosperzone = iosperzone + 1 + tokens = line.split(',') + offset = int(tokens[4]) + if offset < zonestart or offset >= zonestart + self.fio_opts['zonerange']: + print(f"Offset {offset} outside of zone starting at {zonestart}") return False - zoneset = set() - return True + # skip next section if norandommap is enabled with no + # random_generator or with a random_generator != lfsr + if 'norandommap' in self.fio_opts: + if 'random_generator' in self.fio_opts: + if self.fio_opts['random_generator'] != 'lfsr': + continue + else: + continue + # we either have a random map enabled or we + # are using an LFSR + # so all blocks should be unique and we should have + # covered the entire zone when iosperzone % iosperrange == 0 + block = (offset - zonestart) / self.fio_opts['bs'] + if block in zoneset: + print(f"Offset {offset} in zone already touched") + return False + + zoneset.add(block) + if iosperzone % iosperrange == 0: + if len(zoneset) != iosperrange: + print(f"Expected {iosperrange} blocks in zone but only saw {len(zoneset)}") + return False + zoneset = set() + + return True + + +TEST_LIST = [ # randommap enabled + { + "test_id": 1, + "fio_opts": { + "zonerange": 4096, + "zonesize": 4096, + "bs": 4096, + "offset": 8*4096, + "size": 16*4096, + "io_size": 16*4096, + }, + "test_class": StridedTest, + }, + { + "test_id": 2, + "fio_opts": { + "zonerange": 4096, + "zonesize": 4096, + "bs": 4096, + "size": 16*4096, + "io_size": 16*4096, + }, + "test_class": StridedTest, + }, + { + "test_id": 3, + "fio_opts": { + "zonerange": 16*1024*1024, + "zonesize": 16*1024*1024, + "bs": 4096, + "size": 256*1024*1024, + "io_size": 256*1024*204, + }, + "test_class": StridedTest, + }, + { + "test_id": 4, + "fio_opts": { + "zonerange": 4096, + "zonesize": 4*4096, + "bs": 4096, + "size": 16*4096, + "io_size": 16*4096, + }, + "test_class": StridedTest, + }, + { + "test_id": 5, + "fio_opts": { + "zonerange": 16*1024*1024, + "zonesize": 32*1024*1024, + "bs": 4096, + "size": 256*1024*1024, + "io_size": 256*1024*204, + }, + "test_class": StridedTest, + }, + { + "test_id": 6, + "fio_opts": { + "zonerange": 8192, + "zonesize": 4096, + "bs": 4096, + "size": 16*4096, + "io_size": 16*4096, + }, + "test_class": StridedTest, + }, + { + "test_id": 7, + "fio_opts": { + "zonerange": 16*1024*1024, + "zonesize": 8*1024*1024, + "bs": 4096, + "size": 256*1024*1024, + "io_size": 256*1024*204, + }, + "test_class": StridedTest, + }, + # lfsr + { + "test_id": 8, + "fio_opts": { + "random_generator": "lfsr", + "zonerange": 4096*1024, + "zonesize": 4096*1024, + "bs": 4096, + "offset": 8*4096*1024, + "size": 16*4096*1024, + "io_size": 16*4096*1024, + }, + "test_class": StridedTest, + }, + { + "test_id": 9, + "fio_opts": { + "random_generator": "lfsr", + "zonerange": 4096*1024, + "zonesize": 4096*1024, + "bs": 4096, + "size": 16*4096*1024, + "io_size": 16*4096*1024, + }, + "test_class": StridedTest, + }, + { + "test_id": 10, + "fio_opts": { + "random_generator": "lfsr", + "zonerange": 16*1024*1024, + "zonesize": 16*1024*1024, + "bs": 4096, + "size": 256*1024*1024, + "io_size": 256*1024*204, + }, + "test_class": StridedTest, + }, + { + "test_id": 11, + "fio_opts": { + "random_generator": "lfsr", + "zonerange": 4096*1024, + "zonesize": 4*4096*1024, + "bs": 4096, + "size": 16*4096*1024, + "io_size": 16*4096*1024, + }, + "test_class": StridedTest, + }, + { + "test_id": 12, + "fio_opts": { + "random_generator": "lfsr", + "zonerange": 16*1024*1024, + "zonesize": 32*1024*1024, + "bs": 4096, + "size": 256*1024*1024, + "io_size": 256*1024*204, + }, + "test_class": StridedTest, + }, + { + "test_id": 13, + "fio_opts": { + "random_generator": "lfsr", + "zonerange": 8192*1024, + "zonesize": 4096*1024, + "bs": 4096, + "size": 16*4096*1024, + "io_size": 16*4096*1024, + }, + "test_class": StridedTest, + }, + { + "test_id": 14, + "fio_opts": { + "random_generator": "lfsr", + "zonerange": 16*1024*1024, + "zonesize": 8*1024*1024, + "bs": 4096, + "size": 256*1024*1024, + "io_size": 256*1024*204, + }, + "test_class": StridedTest, + }, + # norandommap + { + "test_id": 15, + "fio_opts": { + "norandommap": 1, + "zonerange": 4096, + "zonesize": 4096, + "bs": 4096, + "offset": 8*4096, + "size": 16*4096, + "io_size": 16*4096, + }, + "test_class": StridedTest, + }, + { + "test_id": 16, + "fio_opts": { + "norandommap": 1, + "zonerange": 4096, + "zonesize": 4096, + "bs": 4096, + "size": 16*4096, + "io_size": 16*4096, + }, + "test_class": StridedTest, + }, + { + "test_id": 17, + "fio_opts": { + "norandommap": 1, + "zonerange": 16*1024*1024, + "zonesize": 16*1024*1024, + "bs": 4096, + "size": 256*1024*1024, + "io_size": 256*1024*204, + }, + "test_class": StridedTest, + }, + { + "test_id": 18, + "fio_opts": { + "norandommap": 1, + "zonerange": 4096, + "zonesize": 8192, + "bs": 4096, + "size": 16*4096, + "io_size": 16*4096, + }, + "test_class": StridedTest, + }, + { + "test_id": 19, + "fio_opts": { + "norandommap": 1, + "zonerange": 16*1024*1024, + "zonesize": 32*1024*1024, + "bs": 4096, + "size": 256*1024*1024, + "io_size": 256*1024*204, + }, + "test_class": StridedTest, + }, + { + "test_id": 20, + "fio_opts": { + "norandommap": 1, + "zonerange": 8192, + "zonesize": 4096, + "bs": 4096, + "size": 16*4096, + "io_size": 16*4096, + }, + "test_class": StridedTest, + }, + { + "test_id": 21, + "fio_opts": { + "norandommap": 1, + "zonerange": 16*1024*1024, + "zonesize": 8*1024*1024, + "bs": 4096, + "size": 256*1024*1024, + "io_size": 256*1024*1024, + }, + "test_class": StridedTest, + }, +] + + +def parse_args(): + """Parse command-line arguments.""" + + parser = argparse.ArgumentParser() + parser.add_argument('-f', '--fio', help='path to file executable (e.g., ./fio)') + parser.add_argument('-a', '--artifact-root', help='artifact root directory') + parser.add_argument('-s', '--skip', nargs='+', type=int, + help='list of test(s) to skip') + parser.add_argument('-o', '--run-only', nargs='+', type=int, + help='list of test(s) to run, skipping all others') + parser.add_argument('--dut', + help='target file/device to test.') + args = parser.parse_args() + + return args + + +def main(): + """Run zonemode=strided tests.""" -if __name__ == '__main__': args = parse_args() - tests = [ # randommap enabled - { - "zonerange": 4096, - "zonesize": 4096, - "bs": 4096, - "offset": 8*4096, - "size": 16*4096, - "io_size": 16*4096, - }, - { - "zonerange": 4096, - "zonesize": 4096, - "bs": 4096, - "size": 16*4096, - "io_size": 16*4096, - }, - { - "zonerange": 16*1024*1024, - "zonesize": 16*1024*1024, - "bs": 4096, - "size": 256*1024*1024, - "io_size": 256*1024*204, - }, - { - "zonerange": 4096, - "zonesize": 4*4096, - "bs": 4096, - "size": 16*4096, - "io_size": 16*4096, - }, - { - "zonerange": 16*1024*1024, - "zonesize": 32*1024*1024, - "bs": 4096, - "size": 256*1024*1024, - "io_size": 256*1024*204, - }, - { - "zonerange": 8192, - "zonesize": 4096, - "bs": 4096, - "size": 16*4096, - "io_size": 16*4096, - }, - { - "zonerange": 16*1024*1024, - "zonesize": 8*1024*1024, - "bs": 4096, - "size": 256*1024*1024, - "io_size": 256*1024*204, - }, - # lfsr - { - "random_generator": "lfsr", - "zonerange": 4096*1024, - "zonesize": 4096*1024, - "bs": 4096, - "offset": 8*4096*1024, - "size": 16*4096*1024, - "io_size": 16*4096*1024, - }, - { - "random_generator": "lfsr", - "zonerange": 4096*1024, - "zonesize": 4096*1024, - "bs": 4096, - "size": 16*4096*1024, - "io_size": 16*4096*1024, - }, - { - "random_generator": "lfsr", - "zonerange": 16*1024*1024, - "zonesize": 16*1024*1024, - "bs": 4096, - "size": 256*1024*1024, - "io_size": 256*1024*204, - }, - { - "random_generator": "lfsr", - "zonerange": 4096*1024, - "zonesize": 4*4096*1024, - "bs": 4096, - "size": 16*4096*1024, - "io_size": 16*4096*1024, - }, - { - "random_generator": "lfsr", - "zonerange": 16*1024*1024, - "zonesize": 32*1024*1024, - "bs": 4096, - "size": 256*1024*1024, - "io_size": 256*1024*204, - }, - { - "random_generator": "lfsr", - "zonerange": 8192*1024, - "zonesize": 4096*1024, - "bs": 4096, - "size": 16*4096*1024, - "io_size": 16*4096*1024, - }, - { - "random_generator": "lfsr", - "zonerange": 16*1024*1024, - "zonesize": 8*1024*1024, - "bs": 4096, - "size": 256*1024*1024, - "io_size": 256*1024*204, - }, - # norandommap - { - "norandommap": 1, - "zonerange": 4096, - "zonesize": 4096, - "bs": 4096, - "offset": 8*4096, - "size": 16*4096, - "io_size": 16*4096, - }, - { - "norandommap": 1, - "zonerange": 4096, - "zonesize": 4096, - "bs": 4096, - "size": 16*4096, - "io_size": 16*4096, - }, - { - "norandommap": 1, - "zonerange": 16*1024*1024, - "zonesize": 16*1024*1024, - "bs": 4096, - "size": 256*1024*1024, - "io_size": 256*1024*204, - }, - { - "norandommap": 1, - "zonerange": 4096, - "zonesize": 8192, - "bs": 4096, - "size": 16*4096, - "io_size": 16*4096, - }, - { - "norandommap": 1, - "zonerange": 16*1024*1024, - "zonesize": 32*1024*1024, - "bs": 4096, - "size": 256*1024*1024, - "io_size": 256*1024*204, - }, - { - "norandommap": 1, - "zonerange": 8192, - "zonesize": 4096, - "bs": 4096, - "size": 16*4096, - "io_size": 16*4096, - }, - { - "norandommap": 1, - "zonerange": 16*1024*1024, - "zonesize": 8*1024*1024, - "bs": 4096, - "size": 256*1024*1024, - "io_size": 256*1024*1024, - }, - - ] - - index = 1 - passed = 0 - failed = 0 - - if args.filename: - statinfo = os.stat(args.filename) + artifact_root = args.artifact_root if args.artifact_root else \ + f"strided-test-{time.strftime('%Y%m%d-%H%M%S')}" + os.mkdir(artifact_root) + print(f"Artifact directory is {artifact_root}") + + if args.fio: + fio_path = str(Path(args.fio).absolute()) + else: + fio_path = 'fio' + print(f"fio path is {fio_path}") + + if args.dut: + statinfo = os.stat(args.dut) filesize = statinfo.st_size if filesize == 0: - f = os.open(args.filename, os.O_RDONLY) + f = os.open(args.dut, os.O_RDONLY) filesize = os.lseek(f, 0, os.SEEK_END) os.close(f) - for test in tests: - if args.filename: - test['filename'] = args.filename - test['filesize'] = filesize + for test in TEST_LIST: + if args.dut: + test['fio_opts']['filename'] = os.path.abspath(args.dut) + test['fio_opts']['filesize'] = filesize else: - test['filesize'] = test['size'] - iops_log = run_fio(args.fio, test, index) - status = check_output(iops_log, test) - print("Test {0} {1}".format(index, ("PASSED" if status else "FAILED"))) - if status: - passed = passed + 1 - else: - failed = failed + 1 - index = index + 1 + test['fio_opts']['filesize'] = test['fio_opts']['size'] - print("{0} tests passed, {1} failed".format(passed, failed)) + test_env = { + 'fio_path': fio_path, + 'fio_root': str(Path(__file__).absolute().parent.parent), + 'artifact_root': artifact_root, + 'basename': 'strided', + } + _, failed, _ = run_fio_tests(TEST_LIST, test_env, args) sys.exit(failed) + + +if __name__ == '__main__': + main() From 03317fbb1548fef308decf29b4ce3c8bb31d01cb Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Wed, 7 Jun 2023 22:45:49 +0000 Subject: [PATCH 0517/1097] t/strided: increase minumum recommended size to 64MiB Test 8 has offset=32M. So it fails with a 32MiB file. Increase the minimum recommended test file/device size to 64MiB. Signed-off-by: Vincent Fu --- t/strided.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/t/strided.py b/t/strided.py index c5d667778c..b7655e1e92 100755 --- a/t/strided.py +++ b/t/strided.py @@ -6,7 +6,7 @@ # Test zonemode=strided. This uses the null ioengine when no file is # specified. If a file is specified, use it for randdom read testing. # Some of the zoneranges in the tests are 16MiB. So when using a file -# a minimum size of 32MiB is recommended. +# a minimum size of 64MiB is recommended. # # USAGE # python strided.py fio-executable [-f file/device] @@ -14,7 +14,7 @@ # EXAMPLES # python t/strided.py ./fio # python t/strided.py ./fio -f /dev/sda -# dd if=/dev/zero of=temp bs=1M count=32 +# dd if=/dev/zero of=temp bs=1M count=64 # python t/strided.py ./fio -f temp # # ===TEST MATRIX=== From a48070461ed561ce040e15d98702d335179581fa Mon Sep 17 00:00:00 2001 From: Shin'ichiro Kawasaki Date: Thu, 8 Jun 2023 16:06:02 +0900 Subject: [PATCH 0518/1097] zbd: rename 'open zones' to 'write zones' Current fio code for zonemode=zbd uses the word 'open zone' to mean the zones that fio jobs write to. Before fio starts writing to a zone, it calls zbd_open_zone(). When fio completes writing to a zone, it calls zbd_close_zone(). This wording is good for zoned block devices with max_open_zones limit, such as ZBC and ZAC devices. The devices use same word 'open' to express the zone condition that the devices assign resources for data write to zones. However, the word 'open' gets confusing to support zoned block devices which has max_active_zones limit, such as ZNS devices. These devices have both 'open' and 'active' keywords to mean two different kinds of resources on the device. This 'active' status does not fit with the 'open zone' wording in the fio code. Also, the word 'open' zone in fio code does not always match with the 'open' condition of zones on the device (e.g. when --ignore_zone_limits option is specified). To avoid the confusion, stop using the word 'open zone' in the fio code. Instead, use the word 'write zone' to mean that the zone is the write target. When fio starts a write to a zone, it adds the zone to write_zones array. When fio completes writing to a zone, it removes the zone from the write_zones array. For this purpose, rename struct fields, functions and a macro: ZBD_MAX_OPEN_ZONES -> ZBD_MAX_WRITE_ZONES struct fio_zone_info open -> write struct thread_data num_open_zones -> num_write_zones struct zoned_block_device_info: max_open_zones -> max_write_zones num_open_zones -> num_write_zones open_zones[] -> write_zones[] zbd_open_zone() -> zbd_write_zone_get() zbd_close_zone() -> zbd_write_zone_put() zbd_convert_to_open_zone() -> zbd_convert_to_write_zone() To match up these changes, rename local variables and goto labels. Also rephrase code comments. Of note is that this rename is only for the fio code. The fio options max_open_zones and job_max_open_zones are not renamed to not confuse users. Suggested-by: Niklas Cassel Signed-off-by: Shin'ichiro Kawasaki Reviewed-by: Niklas Cassel Signed-off-by: Vincent Fu --- fio.h | 2 +- options.c | 4 +- zbd.c | 218 ++++++++++++++++++++++++++-------------------------- zbd.h | 23 +++--- zbd_types.h | 2 +- 5 files changed, 126 insertions(+), 123 deletions(-) diff --git a/fio.h b/fio.h index 6fc7fb9c63..c5453d131b 100644 --- a/fio.h +++ b/fio.h @@ -275,7 +275,7 @@ struct thread_data { unsigned long long num_unique_pages; struct zone_split_index **zone_state_index; - unsigned int num_open_zones; + unsigned int num_write_zones; unsigned int verify_batch; unsigned int trim_batch; diff --git a/options.c b/options.c index 8193fb29fe..a7c4ef6edb 100644 --- a/options.c +++ b/options.c @@ -3618,7 +3618,7 @@ struct fio_option fio_options[FIO_MAX_OPTS] = { .lname = "Per device/file maximum number of open zones", .type = FIO_OPT_INT, .off1 = offsetof(struct thread_options, max_open_zones), - .maxval = ZBD_MAX_OPEN_ZONES, + .maxval = ZBD_MAX_WRITE_ZONES, .help = "Limit on the number of simultaneously opened sequential write zones with zonemode=zbd", .def = "0", .category = FIO_OPT_C_IO, @@ -3629,7 +3629,7 @@ struct fio_option fio_options[FIO_MAX_OPTS] = { .lname = "Job maximum number of open zones", .type = FIO_OPT_INT, .off1 = offsetof(struct thread_options, job_max_open_zones), - .maxval = ZBD_MAX_OPEN_ZONES, + .maxval = ZBD_MAX_WRITE_ZONES, .help = "Limit on the number of simultaneously opened sequential write zones with zonemode=zbd by one thread/process", .def = "0", .category = FIO_OPT_C_IO, diff --git a/zbd.c b/zbd.c index 5f1a7d7f3e..7529e68bef 100644 --- a/zbd.c +++ b/zbd.c @@ -304,39 +304,39 @@ static int zbd_reset_zone(struct thread_data *td, struct fio_file *f, } /** - * zbd_close_zone - Remove a zone from the open zones array. + * zbd_write_zone_put - Remove a zone from the write target zones array. * @td: FIO thread data. - * @f: FIO file associated with the disk for which to reset a write pointer. + * @f: FIO file that has the write zones array to remove. * @zone_idx: Index of the zone to remove. * * The caller must hold f->zbd_info->mutex. */ -static void zbd_close_zone(struct thread_data *td, const struct fio_file *f, - struct fio_zone_info *z) +static void zbd_write_zone_put(struct thread_data *td, const struct fio_file *f, + struct fio_zone_info *z) { - uint32_t ozi; + uint32_t zi; - if (!z->open) + if (!z->write) return; - for (ozi = 0; ozi < f->zbd_info->num_open_zones; ozi++) { - if (zbd_get_zone(f, f->zbd_info->open_zones[ozi]) == z) + for (zi = 0; zi < f->zbd_info->num_write_zones; zi++) { + if (zbd_get_zone(f, f->zbd_info->write_zones[zi]) == z) break; } - if (ozi == f->zbd_info->num_open_zones) + if (zi == f->zbd_info->num_write_zones) return; - dprint(FD_ZBD, "%s: closing zone %u\n", + dprint(FD_ZBD, "%s: removing zone %u from write zone array\n", f->file_name, zbd_zone_idx(f, z)); - memmove(f->zbd_info->open_zones + ozi, - f->zbd_info->open_zones + ozi + 1, - (ZBD_MAX_OPEN_ZONES - (ozi + 1)) * - sizeof(f->zbd_info->open_zones[0])); + memmove(f->zbd_info->write_zones + zi, + f->zbd_info->write_zones + zi + 1, + (ZBD_MAX_WRITE_ZONES - (zi + 1)) * + sizeof(f->zbd_info->write_zones[0])); - f->zbd_info->num_open_zones--; - td->num_open_zones--; - z->open = 0; + f->zbd_info->num_write_zones--; + td->num_write_zones--; + z->write = 0; } /** @@ -405,7 +405,7 @@ static int zbd_reset_zones(struct thread_data *td, struct fio_file *f, zone_lock(td, f, z); pthread_mutex_lock(&f->zbd_info->mutex); - zbd_close_zone(td, f, z); + zbd_write_zone_put(td, f, z); pthread_mutex_unlock(&f->zbd_info->mutex); if (z->wp != z->start) { @@ -450,19 +450,19 @@ static int zbd_get_max_open_zones(struct thread_data *td, struct fio_file *f, } /** - * zbd_open_zone - Add a zone to the array of open zones. + * zbd_write_zone_get - Add a zone to the array of write zones. * @td: fio thread data. - * @f: fio file that has the open zones to add. + * @f: fio file that has the write zones array to add. * @zone_idx: Index of the zone to add. * - * Open a ZBD zone if it is not already open. Returns true if either the zone - * was already open or if the zone was successfully added to the array of open - * zones without exceeding the maximum number of open zones. Returns false if - * the zone was not already open and opening the zone would cause the zone limit - * to be exceeded. + * Add a ZBD zone to write target zones array, if it is not yet added. Returns + * true if either the zone was already added or if the zone was successfully + * added to the array without exceeding the maximum number of write zones. + * Returns false if the zone was not already added and addition of the zone + * would cause the zone limit to be exceeded. */ -static bool zbd_open_zone(struct thread_data *td, const struct fio_file *f, - struct fio_zone_info *z) +static bool zbd_write_zone_get(struct thread_data *td, const struct fio_file *f, + struct fio_zone_info *z) { const uint64_t min_bs = td->o.min_bs[DDIR_WRITE]; struct zoned_block_device_info *zbdi = f->zbd_info; @@ -480,20 +480,20 @@ static bool zbd_open_zone(struct thread_data *td, const struct fio_file *f, return false; /* - * zbdi->max_open_zones == 0 means that there is no limit on the maximum - * number of open zones. In this case, do no track open zones in - * zbdi->open_zones array. + * zbdi->max_write_zones == 0 means that there is no limit on the + * maximum number of write target zones. In this case, do no track write + * target zones in zbdi->write_zones array. */ - if (!zbdi->max_open_zones) + if (!zbdi->max_write_zones) return true; pthread_mutex_lock(&zbdi->mutex); - if (z->open) { + if (z->write) { /* * If the zone is going to be completely filled by writes - * already in-flight, handle it as a full zone instead of an - * open zone. + * already in-flight, handle it as a full zone instead of a + * write target zone. */ if (!zbd_zone_remainder(z)) res = false; @@ -503,17 +503,17 @@ static bool zbd_open_zone(struct thread_data *td, const struct fio_file *f, res = false; /* Zero means no limit */ if (td->o.job_max_open_zones > 0 && - td->num_open_zones >= td->o.job_max_open_zones) + td->num_write_zones >= td->o.job_max_open_zones) goto out; - if (zbdi->num_open_zones >= zbdi->max_open_zones) + if (zbdi->num_write_zones >= zbdi->max_write_zones) goto out; - dprint(FD_ZBD, "%s: opening zone %u\n", + dprint(FD_ZBD, "%s: adding zone %u to write zone array\n", f->file_name, zone_idx); - zbdi->open_zones[zbdi->num_open_zones++] = zone_idx; - td->num_open_zones++; - z->open = 1; + zbdi->write_zones[zbdi->num_write_zones++] = zone_idx; + td->num_write_zones++; + z->write = 1; res = true; out: @@ -894,7 +894,7 @@ static int parse_zone_info(struct thread_data *td, struct fio_file *f) return ret; } -static int zbd_set_max_open_zones(struct thread_data *td, struct fio_file *f) +static int zbd_set_max_write_zones(struct thread_data *td, struct fio_file *f) { struct zoned_block_device_info *zbd = f->zbd_info; unsigned int max_open_zones; @@ -902,7 +902,7 @@ static int zbd_set_max_open_zones(struct thread_data *td, struct fio_file *f) if (zbd->model != ZBD_HOST_MANAGED || td->o.ignore_zone_limits) { /* Only host-managed devices have a max open limit */ - zbd->max_open_zones = td->o.max_open_zones; + zbd->max_write_zones = td->o.max_open_zones; goto out; } @@ -913,13 +913,13 @@ static int zbd_set_max_open_zones(struct thread_data *td, struct fio_file *f) if (!max_open_zones) { /* No device limit */ - zbd->max_open_zones = td->o.max_open_zones; + zbd->max_write_zones = td->o.max_open_zones; } else if (!td->o.max_open_zones) { /* No user limit. Set limit to device limit */ - zbd->max_open_zones = max_open_zones; + zbd->max_write_zones = max_open_zones; } else if (td->o.max_open_zones <= max_open_zones) { /* Both user limit and dev limit. User limit not too large */ - zbd->max_open_zones = td->o.max_open_zones; + zbd->max_write_zones = td->o.max_open_zones; } else { /* Both user limit and dev limit. User limit too large */ td_verror(td, EINVAL, @@ -931,15 +931,15 @@ static int zbd_set_max_open_zones(struct thread_data *td, struct fio_file *f) out: /* Ensure that the limit is not larger than FIO's internal limit */ - if (zbd->max_open_zones > ZBD_MAX_OPEN_ZONES) { + if (zbd->max_write_zones > ZBD_MAX_WRITE_ZONES) { td_verror(td, EINVAL, "'max_open_zones' value is too large"); log_err("'max_open_zones' value is larger than %u\n", - ZBD_MAX_OPEN_ZONES); + ZBD_MAX_WRITE_ZONES); return -EINVAL; } - dprint(FD_ZBD, "%s: using max open zones limit: %"PRIu32"\n", - f->file_name, zbd->max_open_zones); + dprint(FD_ZBD, "%s: using max write zones limit: %"PRIu32"\n", + f->file_name, zbd->max_write_zones); return 0; } @@ -981,7 +981,7 @@ static int zbd_create_zone_info(struct thread_data *td, struct fio_file *f) assert(f->zbd_info); f->zbd_info->model = zbd_model; - ret = zbd_set_max_open_zones(td, f); + ret = zbd_set_max_write_zones(td, f); if (ret) { zbd_free_zone_info(f); return ret; @@ -1174,7 +1174,7 @@ int zbd_setup_files(struct thread_data *td) assert(f->min_zone < f->max_zone); if (td->o.max_open_zones > 0 && - zbd->max_open_zones != td->o.max_open_zones) { + zbd->max_write_zones != td->o.max_open_zones) { log_err("Different 'max_open_zones' values\n"); return 1; } @@ -1184,25 +1184,25 @@ int zbd_setup_files(struct thread_data *td) * global max open zones limit. (As the tracking of open zones * is disabled when there is no global max open zones limit.) */ - if (td->o.job_max_open_zones && !zbd->max_open_zones) { + if (td->o.job_max_open_zones && !zbd->max_write_zones) { log_err("'job_max_open_zones' cannot be used without a global open zones limit\n"); return 1; } /* - * zbd->max_open_zones is the global limit shared for all jobs + * zbd->max_write_zones is the global limit shared for all jobs * that target the same zoned block device. Force sync the per * thread global limit with the actual global limit. (The real * per thread/job limit is stored in td->o.job_max_open_zones). */ - td->o.max_open_zones = zbd->max_open_zones; + td->o.max_open_zones = zbd->max_write_zones; for (zi = f->min_zone; zi < f->max_zone; zi++) { z = &zbd->zone_info[zi]; if (z->cond != ZBD_ZONE_COND_IMP_OPEN && z->cond != ZBD_ZONE_COND_EXP_OPEN) continue; - if (zbd_open_zone(td, f, z)) + if (zbd_write_zone_get(td, f, z)) continue; /* * If the number of open zones exceeds specified limits, @@ -1284,12 +1284,12 @@ void zbd_file_reset(struct thread_data *td, struct fio_file *f) zbd_reset_write_cnt(td, f); } -/* Return random zone index for one of the open zones. */ +/* Return random zone index for one of the write target zones. */ static uint32_t pick_random_zone_idx(const struct fio_file *f, const struct io_u *io_u) { return (io_u->offset - f->file_offset) * - f->zbd_info->num_open_zones / f->io_size; + f->zbd_info->num_write_zones / f->io_size; } static bool any_io_in_flight(void) @@ -1303,35 +1303,35 @@ static bool any_io_in_flight(void) } /* - * Modify the offset of an I/O unit that does not refer to an open zone such - * that it refers to an open zone. Close an open zone and open a new zone if - * necessary. The open zone is searched across sequential zones. + * Modify the offset of an I/O unit that does not refer to a zone such that + * in write target zones array. Add a zone to or remove a zone from the lsit if + * necessary. The write target zone is searched across sequential zones. * This algorithm can only work correctly if all write pointers are * a multiple of the fio block size. The caller must neither hold z->mutex * nor f->zbd_info->mutex. Returns with z->mutex held upon success. */ -static struct fio_zone_info *zbd_convert_to_open_zone(struct thread_data *td, - struct io_u *io_u) +static struct fio_zone_info *zbd_convert_to_write_zone(struct thread_data *td, + struct io_u *io_u) { const uint64_t min_bs = td->o.min_bs[io_u->ddir]; struct fio_file *f = io_u->file; struct zoned_block_device_info *zbdi = f->zbd_info; struct fio_zone_info *z; - unsigned int open_zone_idx = -1; + unsigned int write_zone_idx = -1; uint32_t zone_idx, new_zone_idx; int i; - bool wait_zone_close; + bool wait_zone_write; bool in_flight; bool should_retry = true; assert(is_valid_offset(f, io_u->offset)); - if (zbdi->max_open_zones || td->o.job_max_open_zones) { + if (zbdi->max_write_zones || td->o.job_max_open_zones) { /* - * This statement accesses zbdi->open_zones[] on purpose + * This statement accesses zbdi->write_zones[] on purpose * without locking. */ - zone_idx = zbdi->open_zones[pick_random_zone_idx(f, io_u)]; + zone_idx = zbdi->write_zones[pick_random_zone_idx(f, io_u)]; } else { zone_idx = zbd_offset_to_zone_idx(f, io_u->offset); } @@ -1361,34 +1361,34 @@ static struct fio_zone_info *zbd_convert_to_open_zone(struct thread_data *td, if (z->has_wp) { if (z->cond != ZBD_ZONE_COND_OFFLINE && - zbdi->max_open_zones == 0 && + zbdi->max_write_zones == 0 && td->o.job_max_open_zones == 0) goto examine_zone; - if (zbdi->num_open_zones == 0) { - dprint(FD_ZBD, "%s(%s): no zones are open\n", + if (zbdi->num_write_zones == 0) { + dprint(FD_ZBD, "%s(%s): no zone is write target\n", __func__, f->file_name); - goto open_other_zone; + goto choose_other_zone; } } /* - * List of opened zones is per-device, shared across all + * Array of write target zones is per-device, shared across all * threads. Start with quasi-random candidate zone. Ignore * zones which don't belong to thread's offset/size area. */ - open_zone_idx = pick_random_zone_idx(f, io_u); - assert(!open_zone_idx || - open_zone_idx < zbdi->num_open_zones); - tmp_idx = open_zone_idx; + write_zone_idx = pick_random_zone_idx(f, io_u); + assert(!write_zone_idx || + write_zone_idx < zbdi->num_write_zones); + tmp_idx = write_zone_idx; - for (i = 0; i < zbdi->num_open_zones; i++) { + for (i = 0; i < zbdi->num_write_zones; i++) { uint32_t tmpz; - if (tmp_idx >= zbdi->num_open_zones) + if (tmp_idx >= zbdi->num_write_zones) tmp_idx = 0; - tmpz = zbdi->open_zones[tmp_idx]; + tmpz = zbdi->write_zones[tmp_idx]; if (f->min_zone <= tmpz && tmpz < f->max_zone) { - open_zone_idx = tmp_idx; + write_zone_idx = tmp_idx; goto found_candidate_zone; } @@ -1406,7 +1406,7 @@ static struct fio_zone_info *zbd_convert_to_open_zone(struct thread_data *td, return NULL; found_candidate_zone: - new_zone_idx = zbdi->open_zones[open_zone_idx]; + new_zone_idx = zbdi->write_zones[write_zone_idx]; if (new_zone_idx == zone_idx) break; zone_idx = new_zone_idx; @@ -1425,32 +1425,32 @@ static struct fio_zone_info *zbd_convert_to_open_zone(struct thread_data *td, goto out; } -open_other_zone: - /* Check if number of open zones reaches one of limits. */ - wait_zone_close = - zbdi->num_open_zones == f->max_zone - f->min_zone || - (zbdi->max_open_zones && - zbdi->num_open_zones == zbdi->max_open_zones) || +choose_other_zone: + /* Check if number of write target zones reaches one of limits. */ + wait_zone_write = + zbdi->num_write_zones == f->max_zone - f->min_zone || + (zbdi->max_write_zones && + zbdi->num_write_zones == zbdi->max_write_zones) || (td->o.job_max_open_zones && - td->num_open_zones == td->o.job_max_open_zones); + td->num_write_zones == td->o.job_max_open_zones); pthread_mutex_unlock(&zbdi->mutex); /* Only z->mutex is held. */ /* - * When number of open zones reaches to one of limits, wait for - * zone close before opening a new zone. + * When number of write target zones reaches to one of limits, wait for + * zone write completion to one of them before trying a new zone. */ - if (wait_zone_close) { + if (wait_zone_write) { dprint(FD_ZBD, - "%s(%s): quiesce to allow open zones to close\n", + "%s(%s): quiesce to remove a zone from write target zones array\n", __func__, f->file_name); io_u_quiesce(td); } retry: - /* Zone 'z' is full, so try to open a new zone. */ + /* Zone 'z' is full, so try to choose a new zone. */ for (i = f->io_size / zbdi->zone_size; i > 0; i--) { zone_idx++; if (z->has_wp) @@ -1465,18 +1465,18 @@ static struct fio_zone_info *zbd_convert_to_open_zone(struct thread_data *td, if (!z->has_wp) continue; zone_lock(td, f, z); - if (z->open) + if (z->write) continue; - if (zbd_open_zone(td, f, z)) + if (zbd_write_zone_get(td, f, z)) goto out; } /* Only z->mutex is held. */ - /* Check whether the write fits in any of the already opened zones. */ + /* Check whether the write fits in any of the write target zones. */ pthread_mutex_lock(&zbdi->mutex); - for (i = 0; i < zbdi->num_open_zones; i++) { - zone_idx = zbdi->open_zones[i]; + for (i = 0; i < zbdi->num_write_zones; i++) { + zone_idx = zbdi->write_zones[i]; if (zone_idx < f->min_zone || zone_idx >= f->max_zone) continue; pthread_mutex_unlock(&zbdi->mutex); @@ -1492,13 +1492,14 @@ static struct fio_zone_info *zbd_convert_to_open_zone(struct thread_data *td, /* * When any I/O is in-flight or when all I/Os in-flight get completed, - * the I/Os might have closed zones then retry the steps to open a zone. - * Before retry, call io_u_quiesce() to complete in-flight writes. + * the I/Os might have removed zones from the write target array then + * retry the steps to choose a zone. Before retry, call io_u_quiesce() + * to complete in-flight writes. */ in_flight = any_io_in_flight(); if (in_flight || should_retry) { dprint(FD_ZBD, - "%s(%s): wait zone close and retry open zones\n", + "%s(%s): wait zone write and retry write target zone selection\n", __func__, f->file_name); pthread_mutex_unlock(&zbdi->mutex); zone_unlock(z); @@ -1512,7 +1513,7 @@ static struct fio_zone_info *zbd_convert_to_open_zone(struct thread_data *td, zone_unlock(z); - dprint(FD_ZBD, "%s(%s): did not open another zone\n", + dprint(FD_ZBD, "%s(%s): did not choose another write zone\n", __func__, f->file_name); return NULL; @@ -1582,7 +1583,8 @@ zbd_find_zone(struct thread_data *td, struct io_u *io_u, uint64_t min_bytes, * @io_u: I/O unit * @z: zone info pointer * - * If the write command made the zone full, close it. + * If the write command made the zone full, remove it from the write target + * zones array. * * The caller must hold z->mutex. */ @@ -1594,7 +1596,7 @@ static void zbd_end_zone_io(struct thread_data *td, const struct io_u *io_u, if (io_u->ddir == DDIR_WRITE && io_u->offset + io_u->buflen >= zbd_zone_capacity_end(z)) { pthread_mutex_lock(&f->zbd_info->mutex); - zbd_close_zone(td, f, z); + zbd_write_zone_put(td, f, z); pthread_mutex_unlock(&f->zbd_info->mutex); } } @@ -1954,7 +1956,7 @@ enum io_u_action zbd_adjust_block(struct thread_data *td, struct io_u *io_u) if (zbd_zone_remainder(zb) > 0 && zbd_zone_remainder(zb) < min_bs) { pthread_mutex_lock(&f->zbd_info->mutex); - zbd_close_zone(td, f, zb); + zbd_write_zone_put(td, f, zb); pthread_mutex_unlock(&f->zbd_info->mutex); dprint(FD_ZBD, "%s: finish zone %d\n", @@ -1977,11 +1979,11 @@ enum io_u_action zbd_adjust_block(struct thread_data *td, struct io_u *io_u) zone_lock(td, f, zb); } - if (!zbd_open_zone(td, f, zb)) { + if (!zbd_write_zone_get(td, f, zb)) { zone_unlock(zb); - zb = zbd_convert_to_open_zone(td, io_u); + zb = zbd_convert_to_write_zone(td, io_u); if (!zb) { - dprint(FD_IO, "%s: can't convert to open zone", + dprint(FD_IO, "%s: can't convert to write target zone", f->file_name); goto eof; } diff --git a/zbd.h b/zbd.h index 05189555e0..25a3e0f1f9 100644 --- a/zbd.h +++ b/zbd.h @@ -29,8 +29,8 @@ enum io_u_action { * @type: zone type (BLK_ZONE_TYPE_*) * @cond: zone state (BLK_ZONE_COND_*) * @has_wp: whether or not this zone can have a valid write pointer - * @open: whether or not this zone is currently open. Only relevant if - * max_open_zones > 0. + * @write: whether or not this zone is the write target at this moment. Only + * relevant if zbd->max_open_zones > 0. * @reset_zone: whether or not this zone should be reset before writing to it */ struct fio_zone_info { @@ -41,16 +41,17 @@ struct fio_zone_info { enum zbd_zone_type type:2; enum zbd_zone_cond cond:4; unsigned int has_wp:1; - unsigned int open:1; + unsigned int write:1; unsigned int reset_zone:1; }; /** * zoned_block_device_info - zoned block device characteristics * @model: Device model. - * @max_open_zones: global limit on the number of simultaneously opened - * sequential write zones. A zero value means unlimited open zones, - * and that open zones will not be tracked in the open_zones array. + * @max_write_zones: global limit on the number of sequential write zones which + * are simultaneously written. A zero value means unlimited zones of + * simultaneous writes and that write target zones will not be tracked in + * the write_zones array. * @mutex: Protects the modifiable members in this structure (refcount and * num_open_zones). * @zone_size: size of a single zone in bytes. @@ -61,10 +62,10 @@ struct fio_zone_info { * if the zone size is not a power of 2. * @nr_zones: number of zones * @refcount: number of fio files that share this structure - * @num_open_zones: number of open zones + * @num_write_zones: number of write target zones * @write_cnt: Number of writes since the latest zone reset triggered by * the zone_reset_frequency fio job parameter. - * @open_zones: zone numbers of open zones + * @write_zones: zone numbers of write target zones * @zone_info: description of the individual zones * * Only devices for which all zones have the same size are supported. @@ -73,7 +74,7 @@ struct fio_zone_info { */ struct zoned_block_device_info { enum zbd_zoned_model model; - uint32_t max_open_zones; + uint32_t max_write_zones; pthread_mutex_t mutex; uint64_t zone_size; uint64_t wp_valid_data_bytes; @@ -82,9 +83,9 @@ struct zoned_block_device_info { uint32_t zone_size_log2; uint32_t nr_zones; uint32_t refcount; - uint32_t num_open_zones; + uint32_t num_write_zones; uint32_t write_cnt; - uint32_t open_zones[ZBD_MAX_OPEN_ZONES]; + uint32_t write_zones[ZBD_MAX_WRITE_ZONES]; struct fio_zone_info zone_info[0]; }; diff --git a/zbd_types.h b/zbd_types.h index 0a8630cb71..5f44f308f6 100644 --- a/zbd_types.h +++ b/zbd_types.h @@ -8,7 +8,7 @@ #include -#define ZBD_MAX_OPEN_ZONES 4096 +#define ZBD_MAX_WRITE_ZONES 4096 /* * Zoned block device models. From 8ac768899d63baca5259b21823b76f42aaaa509b Mon Sep 17 00:00:00 2001 From: Shin'ichiro Kawasaki Date: Thu, 8 Jun 2023 16:06:03 +0900 Subject: [PATCH 0519/1097] zbd: do not reset extra zones in open conditions The commit 954217b90191 ("zbd: Initialize open zones list referring zone status at fio start") introduced zone resets for zones in open condition which exceeds the limit of max_open_zones. However, this zone reset may break data in the zones even when fio does no write to them. Avoid the zone reset and report it as an error. Fixes: 954217b90191 ("zbd: Initialize open zones list referring zone status at fio start") Signed-off-by: Shin'ichiro Kawasaki Reviewed-by: Niklas Cassel Signed-off-by: Vincent Fu --- zbd.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/zbd.c b/zbd.c index 7529e68bef..832868cb45 100644 --- a/zbd.c +++ b/zbd.c @@ -1206,12 +1206,10 @@ int zbd_setup_files(struct thread_data *td) continue; /* * If the number of open zones exceeds specified limits, - * reset all extra open zones. + * error out. */ - if (zbd_reset_zone(td, f, z) < 0) { - log_err("Failed to reest zone %d\n", zi); - return 1; - } + log_err("Number of open zones exceeds max_open_zones limit\n"); + return 1; } } From f539b98c87990c0694df666119087e44993fbac6 Mon Sep 17 00:00:00 2001 From: Shin'ichiro Kawasaki Date: Thu, 8 Jun 2023 16:06:04 +0900 Subject: [PATCH 0520/1097] zbd: fix write zone accounting of almost full zones For zonemode=zbd, fio checks condition of each zone and account it as write target zone if it has open condition. However, when such a zone in open condition is almost full and its remainder area for write is smaller than the block size, fio does not handle it as a write target zone. This causes difference between open zones accounting on the device and write target zones accounting by fio. It results in unexpected max_open_zones limit failure. Avoid the zone accounting difference by handling the almost full zones as write target zones at fio start. Introduce the helper function __zbd_write_zone_get() which does same operation as zbd_write_zone_get() except the check for the almost full zones. At fio start, call __zbd_write_zone_get() so that almost full zones are added to write target zones. During fio workload run, call zbd_write_zone_get() so that the almost full zones are not chosen for write target. Suggested-by: Niklas Cassel Signed-off-by: Shin'ichiro Kawasaki Reviewed-by: Niklas Cassel Signed-off-by: Vincent Fu --- zbd.c | 47 ++++++++++++++++++++++++++++++++++++----------- 1 file changed, 36 insertions(+), 11 deletions(-) diff --git a/zbd.c b/zbd.c index 832868cb45..36b68d6259 100644 --- a/zbd.c +++ b/zbd.c @@ -450,21 +450,19 @@ static int zbd_get_max_open_zones(struct thread_data *td, struct fio_file *f, } /** - * zbd_write_zone_get - Add a zone to the array of write zones. + * __zbd_write_zone_get - Add a zone to the array of write zones. * @td: fio thread data. * @f: fio file that has the write zones array to add. * @zone_idx: Index of the zone to add. * - * Add a ZBD zone to write target zones array, if it is not yet added. Returns - * true if either the zone was already added or if the zone was successfully - * added to the array without exceeding the maximum number of write zones. - * Returns false if the zone was not already added and addition of the zone - * would cause the zone limit to be exceeded. + * Do same operation as @zbd_write_zone_get, except it adds the zone at + * @zone_idx to write target zones array even when it does not have remainder + * space to write one block. */ -static bool zbd_write_zone_get(struct thread_data *td, const struct fio_file *f, - struct fio_zone_info *z) +static bool __zbd_write_zone_get(struct thread_data *td, + const struct fio_file *f, + struct fio_zone_info *z) { - const uint64_t min_bs = td->o.min_bs[DDIR_WRITE]; struct zoned_block_device_info *zbdi = f->zbd_info; uint32_t zone_idx = zbd_zone_idx(f, z); bool res = true; @@ -476,7 +474,7 @@ static bool zbd_write_zone_get(struct thread_data *td, const struct fio_file *f, * Skip full zones with data verification enabled because resetting a * zone causes data loss and hence causes verification to fail. */ - if (td->o.verify != VERIFY_NONE && zbd_zone_full(f, z, min_bs)) + if (td->o.verify != VERIFY_NONE && zbd_zone_remainder(z) == 0) return false; /* @@ -521,6 +519,33 @@ static bool zbd_write_zone_get(struct thread_data *td, const struct fio_file *f, return res; } +/** + * zbd_write_zone_get - Add a zone to the array of write zones. + * @td: fio thread data. + * @f: fio file that has the open zones to add. + * @zone_idx: Index of the zone to add. + * + * Add a ZBD zone to write target zones array, if it is not yet added. Returns + * true if either the zone was already added or if the zone was successfully + * added to the array without exceeding the maximum number of write zones. + * Returns false if the zone was not already added and addition of the zone + * would cause the zone limit to be exceeded. + */ +static bool zbd_write_zone_get(struct thread_data *td, const struct fio_file *f, + struct fio_zone_info *z) +{ + const uint64_t min_bs = td->o.min_bs[DDIR_WRITE]; + + /* + * Skip full zones with data verification enabled because resetting a + * zone causes data loss and hence causes verification to fail. + */ + if (td->o.verify != VERIFY_NONE && zbd_zone_full(f, z, min_bs)) + return false; + + return __zbd_write_zone_get(td, f, z); +} + /* Verify whether direct I/O is used for all host-managed zoned block drives. */ static bool zbd_using_direct_io(void) { @@ -1202,7 +1227,7 @@ int zbd_setup_files(struct thread_data *td) if (z->cond != ZBD_ZONE_COND_IMP_OPEN && z->cond != ZBD_ZONE_COND_EXP_OPEN) continue; - if (zbd_write_zone_get(td, f, z)) + if (__zbd_write_zone_get(td, f, z)) continue; /* * If the number of open zones exceeds specified limits, From 67282020ce1b9427f296af1a449ea0d6895530bc Mon Sep 17 00:00:00 2001 From: Shin'ichiro Kawasaki Date: Thu, 8 Jun 2023 16:06:05 +0900 Subject: [PATCH 0521/1097] zbd: fix write zone accounting of trim workload The commit e3be810bf0fd ("zbd: Support zone reset by trim") supported trim for zonemode=zbd by introducing the function zbd_do_io_u_trim(), which calls zbd_reset_zone(). However, it did not call zbd_write_zone_put() to the trim target zone, then trim operation resulted in wrong accounting of write zones. To fix the issue, call zbd_write_zone_put() from zbd_reset_zone(). To cover the case to reset zones without a zbd_write_zone_put() call, prepare another function __zbd_reset_zone(). While at it, simplify zbd_reset_zones() by calling the modified zbd_reset_zone(). Of note is that the constifier of the argument td of do_io_u_trim() is removed since zbd_write_zone_put() requires changes in that argument. Fixes: e3be810bf0fd ("zbd: Support zone reset by trim") Suggested-by: Niklas Cassel Signed-off-by: Shin'ichiro Kawasaki Reviewed-by: Niklas Cassel Signed-off-by: Vincent Fu --- engines/io_uring.c | 2 +- io_u.c | 2 +- io_u.h | 2 +- zbd.c | 39 +++++++++++++++++++++++++++++++-------- zbd.h | 2 +- 5 files changed, 35 insertions(+), 12 deletions(-) diff --git a/engines/io_uring.c b/engines/io_uring.c index ff64fc9fbf..73e4a27abf 100644 --- a/engines/io_uring.c +++ b/engines/io_uring.c @@ -561,7 +561,7 @@ static inline void fio_ioring_cmdprio_prep(struct thread_data *td, ld->sqes[io_u->index].ioprio = io_u->ioprio; } -static int fio_ioring_cmd_io_u_trim(const struct thread_data *td, +static int fio_ioring_cmd_io_u_trim(struct thread_data *td, struct io_u *io_u) { struct fio_file *f = io_u->file; diff --git a/io_u.c b/io_u.c index 6f5fc94d9a..faf512e53a 100644 --- a/io_u.c +++ b/io_u.c @@ -2379,7 +2379,7 @@ int do_io_u_sync(const struct thread_data *td, struct io_u *io_u) return ret; } -int do_io_u_trim(const struct thread_data *td, struct io_u *io_u) +int do_io_u_trim(struct thread_data *td, struct io_u *io_u) { #ifndef FIO_HAVE_TRIM io_u->error = EINVAL; diff --git a/io_u.h b/io_u.h index 55b4d08312..b432a54010 100644 --- a/io_u.h +++ b/io_u.h @@ -162,7 +162,7 @@ void io_u_mark_submit(struct thread_data *, unsigned int); bool queue_full(const struct thread_data *); int do_io_u_sync(const struct thread_data *, struct io_u *); -int do_io_u_trim(const struct thread_data *, struct io_u *); +int do_io_u_trim(struct thread_data *, struct io_u *); #ifdef FIO_INC_DEBUG static inline void dprint_io_u(struct io_u *io_u, const char *p) diff --git a/zbd.c b/zbd.c index 36b68d6259..9455140ac8 100644 --- a/zbd.c +++ b/zbd.c @@ -254,7 +254,7 @@ static int zbd_reset_wp(struct thread_data *td, struct fio_file *f, } /** - * zbd_reset_zone - reset the write pointer of a single zone + * __zbd_reset_zone - reset the write pointer of a single zone * @td: FIO thread data. * @f: FIO file associated with the disk for which to reset a write pointer. * @z: Zone to reset. @@ -263,8 +263,8 @@ static int zbd_reset_wp(struct thread_data *td, struct fio_file *f, * * The caller must hold z->mutex. */ -static int zbd_reset_zone(struct thread_data *td, struct fio_file *f, - struct fio_zone_info *z) +static int __zbd_reset_zone(struct thread_data *td, struct fio_file *f, + struct fio_zone_info *z) { uint64_t offset = z->start; uint64_t length = (z+1)->start - offset; @@ -339,6 +339,32 @@ static void zbd_write_zone_put(struct thread_data *td, const struct fio_file *f, z->write = 0; } +/** + * zbd_reset_zone - reset the write pointer of a single zone and remove the zone + * from the array of write zones. + * @td: FIO thread data. + * @f: FIO file associated with the disk for which to reset a write pointer. + * @z: Zone to reset. + * + * Returns 0 upon success and a negative error code upon failure. + * + * The caller must hold z->mutex. + */ +static int zbd_reset_zone(struct thread_data *td, struct fio_file *f, + struct fio_zone_info *z) +{ + int ret; + + ret = __zbd_reset_zone(td, f, z); + if (ret) + return ret; + + pthread_mutex_lock(&f->zbd_info->mutex); + zbd_write_zone_put(td, f, z); + pthread_mutex_unlock(&f->zbd_info->mutex); + return 0; +} + /** * zbd_finish_zone - finish the specified zone * @td: FIO thread data. @@ -404,9 +430,6 @@ static int zbd_reset_zones(struct thread_data *td, struct fio_file *f, continue; zone_lock(td, f, z); - pthread_mutex_lock(&f->zbd_info->mutex); - zbd_write_zone_put(td, f, z); - pthread_mutex_unlock(&f->zbd_info->mutex); if (z->wp != z->start) { dprint(FD_ZBD, "%s: resetting zone %u\n", @@ -2048,7 +2071,7 @@ enum io_u_action zbd_adjust_block(struct thread_data *td, struct io_u *io_u) */ io_u_quiesce(td); zb->reset_zone = 0; - if (zbd_reset_zone(td, f, zb) < 0) + if (__zbd_reset_zone(td, f, zb) < 0) goto eof; if (zb->capacity < min_bs) { @@ -2167,7 +2190,7 @@ char *zbd_write_status(const struct thread_stat *ts) * Return io_u_completed when reset zone succeeds. Return 0 when the target zone * does not have write pointer. On error, return negative errno. */ -int zbd_do_io_u_trim(const struct thread_data *td, struct io_u *io_u) +int zbd_do_io_u_trim(struct thread_data *td, struct io_u *io_u) { struct fio_file *f = io_u->file; struct fio_zone_info *z; diff --git a/zbd.h b/zbd.h index 25a3e0f1f9..f0ac98763c 100644 --- a/zbd.h +++ b/zbd.h @@ -100,7 +100,7 @@ enum fio_ddir zbd_adjust_ddir(struct thread_data *td, struct io_u *io_u, enum fio_ddir ddir); enum io_u_action zbd_adjust_block(struct thread_data *td, struct io_u *io_u); char *zbd_write_status(const struct thread_stat *ts); -int zbd_do_io_u_trim(const struct thread_data *td, struct io_u *io_u); +int zbd_do_io_u_trim(struct thread_data *td, struct io_u *io_u); static inline void zbd_close_file(struct fio_file *f) { From 5b4c9c4efa00527806625273ebf0769d4e3bbe8e Mon Sep 17 00:00:00 2001 From: Shin'ichiro Kawasaki Date: Thu, 8 Jun 2023 16:06:06 +0900 Subject: [PATCH 0522/1097] t/zbd: reset zones before tests with max_open_zones option After the recent fix, fio no longer resets zones when it finds more zones in open condition than the max_open_zones option. This results in failure of test cases 12, 13, 29, 32, 48 and 51. To avoid the failures, reset zones at the beginning of the test cases. Signed-off-by: Shin'ichiro Kawasaki Signed-off-by: Vincent Fu --- t/zbd/test-zbd-support | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/t/zbd/test-zbd-support b/t/zbd/test-zbd-support index 996160e769..8657795226 100755 --- a/t/zbd/test-zbd-support +++ b/t/zbd/test-zbd-support @@ -460,7 +460,8 @@ test11() { test12() { local size off capacity - prep_write + [ -n "$is_zbd" ] && reset_zone "$dev" -1 + size=$((8 * zone_size)) off=$((first_sequential_zone_sector * 512)) capacity=$(total_zone_capacity 8 $off $dev) @@ -477,7 +478,8 @@ test13() { require_max_open_zones 4 || return $SKIP_TESTCASE - prep_write + [ -n "$is_zbd" ] && reset_zone "$dev" -1 + size=$((8 * zone_size)) off=$((first_sequential_zone_sector * 512)) capacity=$(total_zone_capacity 8 $off $dev) @@ -726,7 +728,9 @@ test29() { require_seq_zones 80 || return $SKIP_TESTCASE off=$((first_sequential_zone_sector * 512 + 64 * zone_size)) size=$((16*zone_size)) - prep_write + + [ -n "$is_zbd" ] && reset_zone "$dev" -1 + opts=("--debug=zbd") for ((i=0;i Date: Thu, 8 Jun 2023 16:06:07 +0900 Subject: [PATCH 0523/1097] t/zbd: test write zone accounting of almost full zones Recent commit fixed the bug of the write zone accounting for almost full zones. Add a test case which confirms the fix. Signed-off-by: Shin'ichiro Kawasaki Signed-off-by: Vincent Fu --- t/zbd/test-zbd-support | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/t/zbd/test-zbd-support b/t/zbd/test-zbd-support index 8657795226..cdaa057482 100755 --- a/t/zbd/test-zbd-support +++ b/t/zbd/test-zbd-support @@ -1368,6 +1368,27 @@ test63() { check_reset_count -eq 3 || return $? } +# Test write zone accounting handles almost full zones correctly. Prepare an +# almost full, but not full zone. Write to the zone with verify using larger +# block size. Then confirm fio does not report write zone accounting failure. +test64() { + local bs cap + + [ -n "$is_zbd" ] && reset_zone "$dev" -1 + + bs=$((zone_size / 8)) + cap=$(total_zone_capacity 1 $((first_sequential_zone_sector*512)) $dev) + run_fio_on_seq "$(ioengine "psync")" --rw=write --bs="$bs" \ + --size=$((zone_size)) \ + --io_size=$((cap - bs)) \ + >> "${logfile}.${test_number}" 2>&1 || return $? + + bs=$((zone_size / 2)) + run_fio_on_seq "$(ioengine "psync")" --rw=write --bs="$bs" \ + --size=$((zone_size)) --do_verify=1 --verify=md5 \ + >> "${logfile}.${test_number}" 2>&1 || return $? +} + SECONDS=0 tests=() dynamic_analyzer=() From edaee5b96fd87c3c5fe7f64ec917a175cd9237fc Mon Sep 17 00:00:00 2001 From: Shin'ichiro Kawasaki Date: Thu, 8 Jun 2023 16:06:08 +0900 Subject: [PATCH 0524/1097] t/zbd: test write zone accounting of trim workload Recent commit fixed the bug of the write zone accounting of trim workload. Add a test case which confirms the fix. Signed-off-by: Shin'ichiro Kawasaki Signed-off-by: Vincent Fu --- t/zbd/test-zbd-support | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/t/zbd/test-zbd-support b/t/zbd/test-zbd-support index cdaa057482..a3d37a7d95 100755 --- a/t/zbd/test-zbd-support +++ b/t/zbd/test-zbd-support @@ -1389,6 +1389,30 @@ test64() { >> "${logfile}.${test_number}" 2>&1 || return $? } +# Test open zone accounting handles trim workload correctly. Prepare open zones +# as many as max_open_zones=4. Trim one of the 4 zones. Then write to another +# zone and check the write amount is expected size. +test65() { + local off capacity + + [ -n "$is_zbd" ] && reset_zone "$dev" -1 + + off=$((first_sequential_zone_sector * 512)) + capacity=$(total_zone_capacity 1 $off "$dev") + run_fio --zonemode=zbd --direct=1 --zonesize="$zone_size" --thread=1 \ + --filename="$dev" --group_reporting=1 --max_open_zones=4 \ + "$(ioengine "psync")" \ + --name="prep_open_zones" --rw=randwrite --offset="$off" \ + --size="$((zone_size * 4))" --bs=4096 --io_size="$zone_size" \ + --name=trimjob --wait_for="prep_open_zones" --rw=trim \ + --bs="$zone_size" --offset="$off" --size="$zone_size" \ + --name=write --wait_for="trimjob" --rw=write --bs=4096 \ + --offset="$((off + zone_size * 4))" --size="$zone_size" \ + >> "${logfile}.${test_number}" 2>&1 + + check_written $((zone_size + capacity)) +} + SECONDS=0 tests=() dynamic_analyzer=() From 62ac66490f5077e5fca1bd5b49165147cafc5a0d Mon Sep 17 00:00:00 2001 From: Shin'ichiro Kawasaki Date: Sat, 10 Jun 2023 08:59:14 +0900 Subject: [PATCH 0525/1097] zbd: avoid Coverity defect report Coverity reported a defect related to the local variable "in_flight": Using an unreliable value of "in_flight" inside the second locked section. If the data that "in_flight" depends on was changed by another thread, this use might be incorrect. The variable "in_flight" is thread local and other threads can not change its value. Then the report should be false-positive. Just to suppress the report, change reference timing of the valuable. Signed-off-by: Shin'ichiro Kawasaki Link: https://lore.kernel.org/r/20230609235914.1376567-1-shinichiro.kawasaki@wdc.com Signed-off-by: Jens Axboe --- zbd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zbd.c b/zbd.c index 9455140ac8..7fcf1ec432 100644 --- a/zbd.c +++ b/zbd.c @@ -1547,11 +1547,11 @@ static struct fio_zone_info *zbd_convert_to_write_zone(struct thread_data *td, dprint(FD_ZBD, "%s(%s): wait zone write and retry write target zone selection\n", __func__, f->file_name); + should_retry = in_flight; pthread_mutex_unlock(&zbdi->mutex); zone_unlock(z); io_u_quiesce(td); zone_lock(td, f, z); - should_retry = in_flight; goto retry; } From 8ce9c4003aeaafa91c3278c1c7de4a32fadc5ea0 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Fri, 16 Jun 2023 10:41:25 -0400 Subject: [PATCH 0526/1097] docs: clarify opendir description Make explicit how opendir deals with colons in the path. Fixes: https://github.com/axboe/fio/issues/1573 Signed-off-by: Vincent Fu --- HOWTO.rst | 4 +++- fio.1 | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/HOWTO.rst b/HOWTO.rst index 32fff5ecbd..2e1e55c26e 100644 --- a/HOWTO.rst +++ b/HOWTO.rst @@ -843,7 +843,9 @@ Target file/device .. option:: opendir=str - Recursively open any files below directory `str`. + Recursively open any files below directory `str`. This accepts only a + single directory and unlike related options, colons appearing in the + path must not be escaped. .. option:: lockfile=str diff --git a/fio.1 b/fio.1 index 80bf3371a3..73b7e8c9ea 100644 --- a/fio.1 +++ b/fio.1 @@ -627,7 +627,9 @@ generated filenames (with a directory specified) with the source of the client connecting. To disable this behavior, set this option to 0. .TP .BI opendir \fR=\fPstr -Recursively open any files below directory \fIstr\fR. +Recursively open any files below directory \fIstr\fR. This accepts only a +single directory and unlike related options, colons appearing in the path must +not be escaped. .TP .BI lockfile \fR=\fPstr Fio defaults to not locking any files before it does I/O to them. If a file From 5087502fb05b2b4d756045c594a2e09c2ffc97dc Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Tue, 20 Jun 2023 14:11:36 -0400 Subject: [PATCH 0527/1097] init: don't adjust time units again for subjobs We adjust max_latency and latency_target values to be nsec internally. Make sure we do this only once for the parent job and don't do it a second time for a subjob. Fixes: https://github.com/axboe/fio/issues/1582 Signed-off-by: Vincent Fu --- init.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/init.c b/init.c index 437406ecae..10e63cca6c 100644 --- a/init.c +++ b/init.c @@ -951,13 +951,16 @@ static int fixup_options(struct thread_data *td) if (o->disable_slat) o->slat_percentiles = 0; - /* - * Fix these up to be nsec internally - */ - for_each_rw_ddir(ddir) - o->max_latency[ddir] *= 1000ULL; + /* Do this only for the parent job */ + if (!td->subjob_number) { + /* + * Fix these up to be nsec internally + */ + for_each_rw_ddir(ddir) + o->max_latency[ddir] *= 1000ULL; - o->latency_target *= 1000ULL; + o->latency_target *= 1000ULL; + } /* * Dedupe working set verifications From 41508de67c06661ff1d473d108a8a01912ade114 Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Mon, 3 Jul 2023 09:16:45 -0600 Subject: [PATCH 0528/1097] fio/server: fix confusing sk_out check The previous assert check we had here just checked for sk_out->sk being -1, but if sk_out itself was set. Fixes: 83276370ce4d ("fixed compiler warnings if NDEBUG enabled in core code") Signed-off-by: Jens Axboe --- server.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server.c b/server.c index 5419059446..bb423702a7 100644 --- a/server.c +++ b/server.c @@ -2343,7 +2343,8 @@ void fio_server_send_add_job(struct thread_data *td) void fio_server_send_start(struct thread_data *td) { struct sk_out *sk_out = pthread_getspecific(sk_out_key); - if (!sk_out || sk_out->sk == -1) { + + if (sk_out->sk == -1) { log_err("pthread getting specific for key failed, sk_out %p, sk %i, err: %i:%s", sk_out, sk_out->sk, errno, strerror(errno)); abort(); From 2810cf2863a4b63dab7c434f2fbf3263cc69e4a9 Mon Sep 17 00:00:00 2001 From: Martin Steigerwald Date: Tue, 4 Jul 2023 14:29:14 +0200 Subject: [PATCH 0529/1097] Keep C pre processor hardening build flags. This allows for distributions like Debian to apply hardening flags without patching the makefile. Signed-off-by: Martin Steigerwald --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 6d7fd4e2bb..cc8164b27d 100644 --- a/Makefile +++ b/Makefile @@ -20,7 +20,7 @@ include config-host.mak endif DEBUGFLAGS = -DFIO_INC_DEBUG -CPPFLAGS= -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 -DFIO_INTERNAL $(DEBUGFLAGS) +CPPFLAGS+= -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 -DFIO_INTERNAL $(DEBUGFLAGS) OPTFLAGS= -g -ffast-math FIO_CFLAGS= -std=gnu99 -Wwrite-strings -Wall -Wdeclaration-after-statement $(OPTFLAGS) $(EXTFLAGS) $(BUILD_CFLAGS) -I. -I$(SRCDIR) LIBS += -lm $(EXTLIBS) From 4885a6eba420ce216e4102df3e42229e167d1b7b Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Tue, 27 Jun 2023 18:48:53 +0000 Subject: [PATCH 0530/1097] engines/io_uring_cmd: make trims async Instead of using a synchronous IOCTL to send a trim/deallocate request, just use the io_uring pass-through interface to send the dataset management command with the deallocate request just like we already do for read and write commands. Signed-off-by: Vincent Fu --- engines/io_uring.c | 23 +++++++++++++++---- engines/nvme.c | 57 +++++++++++++++++++++++++++++++++++++--------- engines/nvme.h | 2 +- 3 files changed, 65 insertions(+), 17 deletions(-) diff --git a/engines/io_uring.c b/engines/io_uring.c index 73e4a27abf..7cdbdafaec 100644 --- a/engines/io_uring.c +++ b/engines/io_uring.c @@ -78,6 +78,8 @@ struct ioring_data { struct ioring_mmap mmap[3]; struct cmdprio cmdprio; + + struct nvme_dsm_range *dsm; }; struct ioring_options { @@ -410,7 +412,7 @@ static int fio_ioring_cmd_prep(struct thread_data *td, struct io_u *io_u) if (o->cmd_type != FIO_URING_CMD_NVME) return -EINVAL; - if (io_u->ddir == DDIR_TRIM) + if (io_u->ddir == DDIR_TRIM && td->io_ops->flags & FIO_ASYNCIO_SYNC_TRIM) return 0; sqe = &ld->sqes[(io_u->index) << 1]; @@ -444,7 +446,8 @@ static int fio_ioring_cmd_prep(struct thread_data *td, struct io_u *io_u) cmd = (struct nvme_uring_cmd *)sqe->cmd; return fio_nvme_uring_cmd_prep(cmd, io_u, - o->nonvectored ? NULL : &ld->iovecs[io_u->index]); + o->nonvectored ? NULL : &ld->iovecs[io_u->index], + &ld->dsm[io_u->index]); } static struct io_u *fio_ioring_event(struct thread_data *td, int event) @@ -594,7 +597,7 @@ static enum fio_q_status fio_ioring_queue(struct thread_data *td, if (ld->queued == ld->iodepth) return FIO_Q_BUSY; - if (io_u->ddir == DDIR_TRIM) { + if (io_u->ddir == DDIR_TRIM && td->io_ops->flags & FIO_ASYNCIO_SYNC_TRIM) { if (ld->queued) return FIO_Q_BUSY; @@ -734,6 +737,7 @@ static void fio_ioring_cleanup(struct thread_data *td) free(ld->io_u_index); free(ld->iovecs); free(ld->fds); + free(ld->dsm); free(ld); } } @@ -1146,6 +1150,16 @@ static int fio_ioring_init(struct thread_data *td) return 1; } + /* + * For io_uring_cmd, trims are async operations unless we are operating + * in zbd mode where trim means zone reset. + */ + if (!strcmp(td->io_ops->name, "io_uring_cmd") && td_trim(td) && + td->o.zone_mode == ZONE_MODE_ZBD) + td->io_ops->flags |= FIO_ASYNCIO_SYNC_TRIM; + else + ld->dsm = calloc(ld->iodepth, sizeof(*ld->dsm)); + return 0; } @@ -1361,8 +1375,7 @@ static struct ioengine_ops ioengine_uring = { static struct ioengine_ops ioengine_uring_cmd = { .name = "io_uring_cmd", .version = FIO_IOOPS_VERSION, - .flags = FIO_ASYNCIO_SYNC_TRIM | FIO_NO_OFFLOAD | - FIO_MEMALIGN | FIO_RAWIO | + .flags = FIO_NO_OFFLOAD | FIO_MEMALIGN | FIO_RAWIO | FIO_ASYNCIO_SETS_ISSUE_TIME, .init = fio_ioring_init, .post_init = fio_ioring_cmd_post_init, diff --git a/engines/nvme.c b/engines/nvme.c index 1047ade2b9..2901803a17 100644 --- a/engines/nvme.c +++ b/engines/nvme.c @@ -5,8 +5,41 @@ #include "nvme.h" +static inline __u64 get_slba(struct nvme_data *data, struct io_u *io_u) +{ + if (data->lba_ext) + return io_u->offset / data->lba_ext; + else + return io_u->offset >> data->lba_shift; +} + +static inline __u32 get_nlb(struct nvme_data *data, struct io_u *io_u) +{ + if (data->lba_ext) + return io_u->xfer_buflen / data->lba_ext - 1; + else + return (io_u->xfer_buflen >> data->lba_shift) - 1; +} + +void fio_nvme_uring_cmd_trim_prep(struct nvme_uring_cmd *cmd, struct io_u *io_u, + struct nvme_dsm_range *dsm) +{ + struct nvme_data *data = FILE_ENG_DATA(io_u->file); + + cmd->opcode = nvme_cmd_dsm; + cmd->nsid = data->nsid; + cmd->cdw10 = 0; + cmd->cdw11 = NVME_ATTRIBUTE_DEALLOCATE; + cmd->addr = (__u64) (uintptr_t) dsm; + cmd->data_len = sizeof(*dsm); + + dsm->slba = get_slba(data, io_u); + /* nlb is a 1-based value for deallocate */ + dsm->nlb = get_nlb(data, io_u) + 1; +} + int fio_nvme_uring_cmd_prep(struct nvme_uring_cmd *cmd, struct io_u *io_u, - struct iovec *iov) + struct iovec *iov, struct nvme_dsm_range *dsm) { struct nvme_data *data = FILE_ENG_DATA(io_u->file); __u64 slba; @@ -14,21 +47,23 @@ int fio_nvme_uring_cmd_prep(struct nvme_uring_cmd *cmd, struct io_u *io_u, memset(cmd, 0, sizeof(struct nvme_uring_cmd)); - if (io_u->ddir == DDIR_READ) + switch (io_u->ddir) { + case DDIR_READ: cmd->opcode = nvme_cmd_read; - else if (io_u->ddir == DDIR_WRITE) + break; + case DDIR_WRITE: cmd->opcode = nvme_cmd_write; - else + break; + case DDIR_TRIM: + fio_nvme_uring_cmd_trim_prep(cmd, io_u, dsm); + return 0; + default: return -ENOTSUP; - - if (data->lba_ext) { - slba = io_u->offset / data->lba_ext; - nlb = (io_u->xfer_buflen / data->lba_ext) - 1; - } else { - slba = io_u->offset >> data->lba_shift; - nlb = (io_u->xfer_buflen >> data->lba_shift) - 1; } + slba = get_slba(data, io_u); + nlb = get_nlb(data, io_u); + /* cdw10 and cdw11 represent starting lba */ cmd->cdw10 = slba & 0xffffffff; cmd->cdw11 = slba >> 32; diff --git a/engines/nvme.h b/engines/nvme.h index f7cb820d9a..34be2de16e 100644 --- a/engines/nvme.h +++ b/engines/nvme.h @@ -226,7 +226,7 @@ int fio_nvme_get_info(struct fio_file *f, __u32 *nsid, __u32 *lba_sz, __u32 *ms, __u64 *nlba); int fio_nvme_uring_cmd_prep(struct nvme_uring_cmd *cmd, struct io_u *io_u, - struct iovec *iov); + struct iovec *iov, struct nvme_dsm_range *dsm); int fio_nvme_get_zoned_model(struct thread_data *td, struct fio_file *f, enum zbd_zoned_model *model); From 7f57e30f54078b36667afefdf6ea5794b6142b27 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Tue, 27 Jun 2023 19:19:11 +0000 Subject: [PATCH 0531/1097] engines/io_uring: remove dead code related to trim Now that we are no longer using an NVMe IOCTL to send io_uring_cmd trim commands we can just use the existing pathway to carry out synchronous trim commands for zoned devices. So we no longer need this code. Signed-off-by: Vincent Fu --- engines/io_uring.c | 26 +------------------------- engines/nvme.c | 39 --------------------------------------- engines/nvme.h | 3 --- 3 files changed, 1 insertion(+), 67 deletions(-) diff --git a/engines/io_uring.c b/engines/io_uring.c index 7cdbdafaec..5021239e14 100644 --- a/engines/io_uring.c +++ b/engines/io_uring.c @@ -564,27 +564,6 @@ static inline void fio_ioring_cmdprio_prep(struct thread_data *td, ld->sqes[io_u->index].ioprio = io_u->ioprio; } -static int fio_ioring_cmd_io_u_trim(struct thread_data *td, - struct io_u *io_u) -{ - struct fio_file *f = io_u->file; - int ret; - - if (td->o.zone_mode == ZONE_MODE_ZBD) { - ret = zbd_do_io_u_trim(td, io_u); - if (ret == io_u_completed) - return io_u->xfer_buflen; - if (ret) - goto err; - } - - return fio_nvme_trim(td, f, io_u->offset, io_u->xfer_buflen); - -err: - io_u->error = ret; - return 0; -} - static enum fio_q_status fio_ioring_queue(struct thread_data *td, struct io_u *io_u) { @@ -601,10 +580,7 @@ static enum fio_q_status fio_ioring_queue(struct thread_data *td, if (ld->queued) return FIO_Q_BUSY; - if (!strcmp(td->io_ops->name, "io_uring_cmd")) - fio_ioring_cmd_io_u_trim(td, io_u); - else - do_io_u_trim(td, io_u); + do_io_u_trim(td, io_u); io_u_mark_submit(td, 1); io_u_mark_complete(td, 1); diff --git a/engines/nvme.c b/engines/nvme.c index 2901803a17..b18ad4c28a 100644 --- a/engines/nvme.c +++ b/engines/nvme.c @@ -83,45 +83,6 @@ int fio_nvme_uring_cmd_prep(struct nvme_uring_cmd *cmd, struct io_u *io_u, return 0; } -static int nvme_trim(int fd, __u32 nsid, __u32 nr_range, __u32 data_len, - void *data) -{ - struct nvme_passthru_cmd cmd = { - .opcode = nvme_cmd_dsm, - .nsid = nsid, - .addr = (__u64)(uintptr_t)data, - .data_len = data_len, - .cdw10 = nr_range - 1, - .cdw11 = NVME_ATTRIBUTE_DEALLOCATE, - }; - - return ioctl(fd, NVME_IOCTL_IO_CMD, &cmd); -} - -int fio_nvme_trim(const struct thread_data *td, struct fio_file *f, - unsigned long long offset, unsigned long long len) -{ - struct nvme_data *data = FILE_ENG_DATA(f); - struct nvme_dsm_range dsm; - int ret; - - if (data->lba_ext) { - dsm.nlb = len / data->lba_ext; - dsm.slba = offset / data->lba_ext; - } else { - dsm.nlb = len >> data->lba_shift; - dsm.slba = offset >> data->lba_shift; - } - - ret = nvme_trim(f->fd, data->nsid, 1, sizeof(struct nvme_dsm_range), - &dsm); - if (ret) - log_err("%s: nvme_trim failed for offset %llu and len %llu, err=%d\n", - f->file_name, offset, len, ret); - - return ret; -} - static int nvme_identify(int fd, __u32 nsid, enum nvme_identify_cns cns, enum nvme_csi csi, void *data) { diff --git a/engines/nvme.h b/engines/nvme.h index 34be2de16e..238471dd76 100644 --- a/engines/nvme.h +++ b/engines/nvme.h @@ -216,9 +216,6 @@ struct nvme_dsm_range { __le64 slba; }; -int fio_nvme_trim(const struct thread_data *td, struct fio_file *f, - unsigned long long offset, unsigned long long len); - int fio_nvme_iomgmt_ruhs(struct thread_data *td, struct fio_file *f, struct nvme_fdp_ruh_status *ruhs, __u32 bytes); From 8604be055293f38300980da5fc508615f94b58eb Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Tue, 27 Jun 2023 20:16:13 +0000 Subject: [PATCH 0532/1097] t/nvmept: add check for iodepth Make sure that we achieve the iodepth specified in the job options. Signed-off-by: Vincent Fu --- t/nvmept.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/t/nvmept.py b/t/nvmept.py index e235d160ca..200219b4aa 100755 --- a/t/nvmept.py +++ b/t/nvmept.py @@ -80,6 +80,10 @@ def check_result(self): print(f"Unhandled rw value {self.fio_opts['rw']}") self.passed = False + if job['iodepth_level']['8'] < 95: + print("Did not achieve requested iodepth") + self.passed = False + TEST_LIST = [ { From d47132c6a6be57bb5033c57db5e86cfba179c12f Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Tue, 27 Jun 2023 20:17:10 +0000 Subject: [PATCH 0533/1097] t/nvmept: add trim test with ioengine options enabled Add a test for a trim workload with ioengine options enabled like the ones we have for read and write. fixedbufs cannot be enabled for a trim-only workload because fio does not allocate data buffers for these workloads. Signed-off-by: Vincent Fu --- t/nvmept.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/t/nvmept.py b/t/nvmept.py index 200219b4aa..cc26d1523e 100755 --- a/t/nvmept.py +++ b/t/nvmept.py @@ -236,6 +236,23 @@ def check_result(self): }, "test_class": PassThruTest, }, + { + # We can't enable fixedbufs because for trim-only + # workloads fio actually does not allocate any buffers + "test_id": 15, + "fio_opts": { + "rw": 'randtrim', + "timebased": 1, + "runtime": 3, + "fixedbufs": 0, + "nonvectored": 1, + "force_async": 1, + "registerfiles": 1, + "sqthread_poll": 1, + "output-format": "json", + }, + "test_class": PassThruTest, + }, ] def parse_args(): From 8e2b81b854286f32eae7951a434dddebd968f9d5 Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Wed, 5 Jul 2023 14:29:15 -0700 Subject: [PATCH 0534/1097] zbd: Support finishing zones on Android BLKFINISHZONE is missing from older versions of the Android NDK header files. Hence, define BLKFINISHZONE if it has not been defined and detect at runtime whether or not the kernel supports finishing zones. Cc: Damien Le Moal Cc: Shin'ichiro Kawasaki Signed-off-by: Bart Van Assche Link: https://lore.kernel.org/r/20230705212915.3373438-1-bvanassche@acm.org Signed-off-by: Jens Axboe --- oslib/linux-blkzoned.c | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/oslib/linux-blkzoned.c b/oslib/linux-blkzoned.c index c3130d0e2a..722e09925b 100644 --- a/oslib/linux-blkzoned.c +++ b/oslib/linux-blkzoned.c @@ -22,6 +22,9 @@ #include "zbd_types.h" #include +#ifndef BLKFINISHZONE +#define BLKFINISHZONE _IOW(0x12, 136, struct blk_zone_range) +#endif /* * If the uapi headers installed on the system lacks zone capacity support, @@ -312,7 +315,6 @@ int blkzoned_reset_wp(struct thread_data *td, struct fio_file *f, int blkzoned_finish_zone(struct thread_data *td, struct fio_file *f, uint64_t offset, uint64_t length) { -#ifdef BLKFINISHZONE struct blk_zone_range zr = { .sector = offset >> 9, .nr_sectors = length >> 9, @@ -327,21 +329,19 @@ int blkzoned_finish_zone(struct thread_data *td, struct fio_file *f, return -errno; } - if (ioctl(fd, BLKFINISHZONE, &zr) < 0) + if (ioctl(fd, BLKFINISHZONE, &zr) < 0) { ret = -errno; + /* + * Kernel versions older than 5.5 do not support BLKFINISHZONE + * and return the ENOTTY error code. These old kernels only + * support block devices that close zones automatically. + */ + if (ret == ENOTTY) + ret = 0; + } if (f->fd < 0) close(fd); return ret; -#else - /* - * Kernel versions older than 5.5 does not support BLKFINISHZONE. These - * old kernels assumed zones are closed automatically at max_open_zones - * limit. Also they did not support max_active_zones limit. Then there - * was no need to finish zones to avoid errors caused by max_open_zones - * or max_active_zones. For those old versions, just do nothing. - */ - return 0; -#endif } From 10bad6b9a15f56a4eefb1a12a0ebb7f43c9d4a42 Mon Sep 17 00:00:00 2001 From: Jueji Yang Date: Thu, 6 Jul 2023 20:07:28 +0800 Subject: [PATCH 0535/1097] fix: io_uring sqpoll issue_time empty when kernel not yet read sq MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In io_uring sqpoll mode, when kernel side thread has not yet read the sqring before second fio_ioring_commit() called, the sq_ring.head will remain the same. The second fio_ioring_commit() will initialize the wrong io_u's issue_time. The old(in head) io_u‘s issue_time will to be initialized twice and new(in tail - 1) io_u's issue_time will not to be initialized. This problem will cause clat is weird, sometimes larger than lat. Signed-off-by: Jueji Yang --- engines/io_uring.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engines/io_uring.c b/engines/io_uring.c index 5021239e14..9f8e8e6f48 100644 --- a/engines/io_uring.c +++ b/engines/io_uring.c @@ -646,7 +646,7 @@ static int fio_ioring_commit(struct thread_data *td) */ if (o->sqpoll_thread) { struct io_sq_ring *ring = &ld->sq_ring; - unsigned start = *ld->sq_ring.head; + unsigned start = *ld->sq_ring.tail - ld->queued; unsigned flags; flags = atomic_load_acquire(ring->flags); From 98cd3c0e705c91628c87bf27f03d42b4f34353c1 Mon Sep 17 00:00:00 2001 From: Ankit Kumar Date: Wed, 12 Jul 2023 15:50:38 +0530 Subject: [PATCH 0536/1097] fdp: use macros use macros for directive type and max ruhs. Signed-off-by: Ankit Kumar Signed-off-by: Vincent Fu --- engines/io_uring.c | 2 +- fdp.c | 8 ++++---- fdp.h | 3 +++ 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/engines/io_uring.c b/engines/io_uring.c index 5021239e14..407d65ce22 100644 --- a/engines/io_uring.c +++ b/engines/io_uring.c @@ -1310,7 +1310,7 @@ static int fio_ioring_cmd_fetch_ruhs(struct thread_data *td, struct fio_file *f, struct nvme_fdp_ruh_status *ruhs; int bytes, ret, i; - bytes = sizeof(*ruhs) + 128 * sizeof(struct nvme_fdp_ruh_status_desc); + bytes = sizeof(*ruhs) + FDP_MAX_RUHS * sizeof(struct nvme_fdp_ruh_status_desc); ruhs = scalloc(1, bytes); if (!ruhs) return -ENOMEM; diff --git a/fdp.c b/fdp.c index d92dbc67cc..b83fd66054 100644 --- a/fdp.c +++ b/fdp.c @@ -45,7 +45,7 @@ static int init_ruh_info(struct thread_data *td, struct fio_file *f) struct fio_ruhs_info *ruhs, *tmp; int i, ret; - ruhs = scalloc(1, sizeof(*ruhs) + 128 * sizeof(*ruhs->plis)); + ruhs = scalloc(1, sizeof(*ruhs) + FDP_MAX_RUHS * sizeof(*ruhs->plis)); if (!ruhs) return -ENOMEM; @@ -56,8 +56,8 @@ static int init_ruh_info(struct thread_data *td, struct fio_file *f) goto out; } - if (ruhs->nr_ruhs > 128) - ruhs->nr_ruhs = 128; + if (ruhs->nr_ruhs > FDP_MAX_RUHS) + ruhs->nr_ruhs = FDP_MAX_RUHS; if (td->o.fdp_nrpli == 0) { f->ruhs_info = ruhs; @@ -123,6 +123,6 @@ void fdp_fill_dspec_data(struct thread_data *td, struct io_u *io_u) ruhs->pli_loc = 0; dspec = ruhs->plis[ruhs->pli_loc++]; - io_u->dtype = 2; + io_u->dtype = FDP_DIR_DTYPE; io_u->dspec = dspec; } diff --git a/fdp.h b/fdp.h index 81691f62ca..6ecbfcbe3b 100644 --- a/fdp.h +++ b/fdp.h @@ -3,6 +3,9 @@ #include "io_u.h" +#define FDP_DIR_DTYPE 2 +#define FDP_MAX_RUHS 128 + struct fio_ruhs_info { uint32_t nr_ruhs; uint32_t pli_loc; From 154d27e6b6d51461db4869d80a7e5b9f8fb1d93f Mon Sep 17 00:00:00 2001 From: Ankit Kumar Date: Wed, 12 Jul 2023 15:50:39 +0530 Subject: [PATCH 0537/1097] fdp: fix placement id check Number of reclaim unit handle descriptors are 1 based, whereas the input placement id index / indices are 0 based. Add the correct check for that. Fixes: a7e8aae0 ("fio: add fdp support ..") Signed-off-by: Ankit Kumar Signed-off-by: Vincent Fu --- fdp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fdp.c b/fdp.c index b83fd66054..6e124ce600 100644 --- a/fdp.c +++ b/fdp.c @@ -65,7 +65,7 @@ static int init_ruh_info(struct thread_data *td, struct fio_file *f) } for (i = 0; i < td->o.fdp_nrpli; i++) { - if (td->o.fdp_plis[i] > ruhs->nr_ruhs) { + if (td->o.fdp_plis[i] >= ruhs->nr_ruhs) { ret = -EINVAL; goto out; } From d3e310c531059fb606f04819c362b4d46c518b84 Mon Sep 17 00:00:00 2001 From: Ankit Kumar Date: Wed, 12 Jul 2023 15:50:40 +0530 Subject: [PATCH 0538/1097] fdp: support random placement id selection Allow user to either roundrobin or select random placement ID from the available placement IDs. Signed-off-by: Ankit Kumar Signed-off-by: Vincent Fu --- HOWTO.rst | 15 +++++++++++++++ fdp.c | 12 +++++++++--- fdp.h | 10 ++++++++++ fio.1 | 16 ++++++++++++++++ fio.h | 2 ++ init.c | 2 ++ options.c | 20 ++++++++++++++++++++ thread_options.h | 2 ++ 8 files changed, 76 insertions(+), 3 deletions(-) diff --git a/HOWTO.rst b/HOWTO.rst index 2e1e55c26e..24789f4168 100644 --- a/HOWTO.rst +++ b/HOWTO.rst @@ -2435,6 +2435,21 @@ with the caveat that when used on the command line, they must come after the Enable Flexible Data Placement mode for write commands. +.. option:: fdp_pli_select=str : [io_uring_cmd] + + Defines how fio decides which placement ID to use next. The following + types are defined: + + **random** + Choose a placement ID at random (uniform). + + **roundrobin** + Round robin over available placement IDs. This is the + default. + + The available placement ID index/indices is defined by the option + :option:`fdp_pli`. + .. option:: fdp_pli=str : [io_uring_cmd] Select which Placement ID Index/Indicies this job is allowed to use for diff --git a/fdp.c b/fdp.c index 6e124ce600..49c80d2c61 100644 --- a/fdp.c +++ b/fdp.c @@ -119,10 +119,16 @@ void fdp_fill_dspec_data(struct thread_data *td, struct io_u *io_u) return; } - if (ruhs->pli_loc >= ruhs->nr_ruhs) - ruhs->pli_loc = 0; + if (td->o.fdp_pli_select == FIO_FDP_RR) { + if (ruhs->pli_loc >= ruhs->nr_ruhs) + ruhs->pli_loc = 0; + + dspec = ruhs->plis[ruhs->pli_loc++]; + } else { + ruhs->pli_loc = rand_between(&td->fdp_state, 0, ruhs->nr_ruhs - 1); + dspec = ruhs->plis[ruhs->pli_loc]; + } - dspec = ruhs->plis[ruhs->pli_loc++]; io_u->dtype = FDP_DIR_DTYPE; io_u->dspec = dspec; } diff --git a/fdp.h b/fdp.h index 6ecbfcbe3b..accbac384a 100644 --- a/fdp.h +++ b/fdp.h @@ -6,6 +6,16 @@ #define FDP_DIR_DTYPE 2 #define FDP_MAX_RUHS 128 +/* + * How fio chooses what placement identifier to use next. Choice of + * uniformly random, or roundrobin. + */ + +enum { + FIO_FDP_RANDOM = 0x1, + FIO_FDP_RR = 0x2, +}; + struct fio_ruhs_info { uint32_t nr_ruhs; uint32_t pli_loc; diff --git a/fio.1 b/fio.1 index 73b7e8c9ea..0257513ba5 100644 --- a/fio.1 +++ b/fio.1 @@ -2195,6 +2195,22 @@ file blocks are fully allocated and the disk request could be issued immediately .BI (io_uring_cmd)fdp \fR=\fPbool Enable Flexible Data Placement mode for write commands. .TP +.BI (io_uring_cmd)fdp_pli_select \fR=\fPstr +Defines how fio decides which placement ID to use next. The following types +are defined: +.RS +.RS +.TP +.B random +Choose a placement ID at random (uniform). +.TP +.B roundrobin +Round robin over available placement IDs. This is the default. +.RE +.P +The available placement ID index/indices is defined by \fBfdp_pli\fR option. +.RE +.TP .BI (io_uring_cmd)fdp_pli \fR=\fPstr Select which Placement ID Index/Indicies this job is allowed to use for writes. By default, the job will cycle through all available Placement IDs, so use this diff --git a/fio.h b/fio.h index c5453d131b..a54f57c93e 100644 --- a/fio.h +++ b/fio.h @@ -144,6 +144,7 @@ enum { FIO_RAND_POISSON3_OFF, FIO_RAND_PRIO_CMDS, FIO_RAND_DEDUPE_WORKING_SET_IX, + FIO_RAND_FDP_OFF, FIO_RAND_NR_OFFS, }; @@ -262,6 +263,7 @@ struct thread_data { struct frand_state verify_state_last_do_io; struct frand_state trim_state; struct frand_state delay_state; + struct frand_state fdp_state; struct frand_state buf_state; struct frand_state buf_state_prev; diff --git a/init.c b/init.c index 10e63cca6c..105339fa28 100644 --- a/init.c +++ b/init.c @@ -1082,6 +1082,8 @@ void td_fill_rand_seeds(struct thread_data *td) init_rand_seed(&td->buf_state, td->rand_seeds[FIO_RAND_BUF_OFF], use64); frand_copy(&td->buf_state_prev, &td->buf_state); + + init_rand_seed(&td->fdp_state, td->rand_seeds[FIO_RAND_FDP_OFF], use64); } static int setup_random_seeds(struct thread_data *td) diff --git a/options.c b/options.c index a7c4ef6edb..0f73931724 100644 --- a/options.c +++ b/options.c @@ -3679,6 +3679,26 @@ struct fio_option fio_options[FIO_MAX_OPTS] = { .category = FIO_OPT_C_IO, .group = FIO_OPT_G_INVALID, }, + { + .name = "fdp_pli_select", + .lname = "FDP Placement ID select", + .type = FIO_OPT_STR, + .off1 = offsetof(struct thread_options, fdp_pli_select), + .help = "Select which FDP placement ID to use next", + .def = "roundrobin", + .category = FIO_OPT_C_IO, + .group = FIO_OPT_G_INVALID, + .posval = { + { .ival = "random", + .oval = FIO_FDP_RANDOM, + .help = "Choose a Placement ID at random (uniform)", + }, + { .ival = "roundrobin", + .oval = FIO_FDP_RR, + .help = "Round robin select Placement IDs", + }, + }, + }, { .name = "fdp_pli", .lname = "FDP Placement ID indicies", diff --git a/thread_options.h b/thread_options.h index a24ebee69c..1715b36c67 100644 --- a/thread_options.h +++ b/thread_options.h @@ -388,6 +388,7 @@ struct thread_options { #define FIO_MAX_PLIS 16 unsigned int fdp; + unsigned int fdp_pli_select; unsigned int fdp_plis[FIO_MAX_PLIS]; unsigned int fdp_nrpli; @@ -703,6 +704,7 @@ struct thread_options_pack { uint32_t log_prio; uint32_t fdp; + uint32_t fdp_pli_select; uint32_t fdp_plis[FIO_MAX_PLIS]; uint32_t fdp_nrpli; From e5f3b613876d6fd026ccf532f89fd21b841ab99d Mon Sep 17 00:00:00 2001 From: Ankit Kumar Date: Wed, 12 Jul 2023 15:50:41 +0530 Subject: [PATCH 0539/1097] engines/xnvme: add support for fdp Add FDP support to xnvme I/O engine. This support can be used only with nvme-ns generic character device (/dev/ngXnY). The available backends are --xnvme_async=io_uring_cmd and --xnvme_sync=nvme. Add a xnvme-fdp config example file. Update the minimum required xnvme version to 0.7.0 Signed-off-by: Ankit Kumar Signed-off-by: Vincent Fu --- HOWTO.rst | 6 ++-- configure | 2 +- engines/xnvme.c | 78 +++++++++++++++++++++++++++++++++++++++++- examples/xnvme-fdp.fio | 36 +++++++++++++++++++ fio.1 | 6 ++-- 5 files changed, 120 insertions(+), 8 deletions(-) create mode 100644 examples/xnvme-fdp.fio diff --git a/HOWTO.rst b/HOWTO.rst index 24789f4168..b047877ea3 100644 --- a/HOWTO.rst +++ b/HOWTO.rst @@ -2431,11 +2431,11 @@ with the caveat that when used on the command line, they must come after the For direct I/O, requests will only succeed if cache invalidation isn't required, file blocks are fully allocated and the disk request could be issued immediately. -.. option:: fdp=bool : [io_uring_cmd] +.. option:: fdp=bool : [io_uring_cmd] [xnvme] Enable Flexible Data Placement mode for write commands. -.. option:: fdp_pli_select=str : [io_uring_cmd] +.. option:: fdp_pli_select=str : [io_uring_cmd] [xnvme] Defines how fio decides which placement ID to use next. The following types are defined: @@ -2450,7 +2450,7 @@ with the caveat that when used on the command line, they must come after the The available placement ID index/indices is defined by the option :option:`fdp_pli`. -.. option:: fdp_pli=str : [io_uring_cmd] +.. option:: fdp_pli=str : [io_uring_cmd] [xnvme] Select which Placement ID Index/Indicies this job is allowed to use for writes. By default, the job will cycle through all available Placement diff --git a/configure b/configure index 74416fd48b..6c93825173 100755 --- a/configure +++ b/configure @@ -2651,7 +2651,7 @@ fi ########################################## # Check if we have xnvme if test "$xnvme" != "no" ; then - if check_min_lib_version xnvme 0.2.0; then + if check_min_lib_version xnvme 0.7.0; then xnvme="yes" xnvme_cflags=$(pkg-config --cflags xnvme) xnvme_libs=$(pkg-config --libs xnvme) diff --git a/engines/xnvme.c b/engines/xnvme.c index bb92a121dc..ce7b2bdd4b 100644 --- a/engines/xnvme.c +++ b/engines/xnvme.c @@ -16,6 +16,7 @@ #include #include "fio.h" #include "zbd_types.h" +#include "fdp.h" #include "optgroup.h" static pthread_mutex_t g_serialize = PTHREAD_MUTEX_INITIALIZER; @@ -509,6 +510,7 @@ static enum fio_q_status xnvme_fioe_queue(struct thread_data *td, struct io_u *i uint16_t nlb; int err; bool vectored_io = ((struct xnvme_fioe_options *)td->eo)->xnvme_iovec; + uint32_t dir = io_u->dtype; fio_ro_check(td, io_u); @@ -524,6 +526,10 @@ static enum fio_q_status xnvme_fioe_queue(struct thread_data *td, struct io_u *i ctx->cmd.common.nsid = nsid; ctx->cmd.nvm.slba = slba; ctx->cmd.nvm.nlb = nlb; + if (dir) { + ctx->cmd.nvm.dtype = io_u->dtype; + ctx->cmd.nvm.cdw13.dspec = io_u->dspec; + } switch (io_u->ddir) { case DDIR_READ: @@ -947,6 +953,72 @@ static int xnvme_fioe_reset_wp(struct thread_data *td, struct fio_file *f, uint6 return err; } +static int xnvme_fioe_fetch_ruhs(struct thread_data *td, struct fio_file *f, + struct fio_ruhs_info *fruhs_info) +{ + struct xnvme_opts opts = xnvme_opts_from_fioe(td); + struct xnvme_dev *dev; + struct xnvme_spec_ruhs *ruhs; + struct xnvme_cmd_ctx ctx; + uint32_t ruhs_nbytes; + uint32_t nsid; + int err = 0, err_lock; + + if (f->filetype != FIO_TYPE_CHAR) { + log_err("ioeng->fdp_ruhs(): ignoring filetype: %d\n", f->filetype); + return -EINVAL; + } + + err = pthread_mutex_lock(&g_serialize); + if (err) { + log_err("ioeng->fdp_ruhs(): pthread_mutex_lock(), err(%d)\n", err); + return -err; + } + + dev = xnvme_dev_open(f->file_name, &opts); + if (!dev) { + log_err("ioeng->fdp_ruhs(): xnvme_dev_open(%s) failed, errno: %d\n", + f->file_name, errno); + err = -errno; + goto exit; + } + + ruhs_nbytes = sizeof(*ruhs) + (FDP_MAX_RUHS * sizeof(struct xnvme_spec_ruhs_desc)); + ruhs = xnvme_buf_alloc(dev, ruhs_nbytes); + if (!ruhs) { + err = -errno; + goto exit; + } + memset(ruhs, 0, ruhs_nbytes); + + ctx = xnvme_cmd_ctx_from_dev(dev); + nsid = xnvme_dev_get_nsid(dev); + + err = xnvme_nvm_mgmt_recv(&ctx, nsid, XNVME_SPEC_IO_MGMT_RECV_RUHS, 0, ruhs, ruhs_nbytes); + + if (err || xnvme_cmd_ctx_cpl_status(&ctx)) { + err = err ? err : -EIO; + log_err("ioeng->fdp_ruhs(): err(%d), sc(%d)", err, ctx.cpl.status.sc); + goto free_buffer; + } + + fruhs_info->nr_ruhs = ruhs->nruhsd; + for (uint32_t idx = 0; idx < fruhs_info->nr_ruhs; ++idx) { + fruhs_info->plis[idx] = le16_to_cpu(ruhs->desc[idx].pi); + } + +free_buffer: + xnvme_buf_free(dev, ruhs); +exit: + xnvme_dev_close(dev); + + err_lock = pthread_mutex_unlock(&g_serialize); + if (err_lock) + log_err("ioeng->fdp_ruhs(): pthread_mutex_unlock(), err(%d)\n", err_lock); + + return err; +} + static int xnvme_fioe_get_file_size(struct thread_data *td, struct fio_file *f) { struct xnvme_opts opts = xnvme_opts_from_fioe(td); @@ -971,7 +1043,9 @@ static int xnvme_fioe_get_file_size(struct thread_data *td, struct fio_file *f) f->real_file_size = xnvme_dev_get_geo(dev)->tbytes; fio_file_set_size_known(f); - f->filetype = FIO_TYPE_BLOCK; + + if (td->o.zone_mode == ZONE_MODE_ZBD) + f->filetype = FIO_TYPE_BLOCK; exit: xnvme_dev_close(dev); @@ -1011,6 +1085,8 @@ FIO_STATIC struct ioengine_ops ioengine = { .get_zoned_model = xnvme_fioe_get_zoned_model, .report_zones = xnvme_fioe_report_zones, .reset_wp = xnvme_fioe_reset_wp, + + .fdp_fetch_ruhs = xnvme_fioe_fetch_ruhs, }; static void fio_init fio_xnvme_register(void) diff --git a/examples/xnvme-fdp.fio b/examples/xnvme-fdp.fio new file mode 100644 index 0000000000..86fbe0d31a --- /dev/null +++ b/examples/xnvme-fdp.fio @@ -0,0 +1,36 @@ +; README +; +; This job-file is intended to be used either as: +; +; # Use the xNVMe io-engine engine io_uring_cmd async. impl. +; fio examples/xnvme-fdp.fio \ +; --section=default \ +; --ioengine=xnvme \ +; --xnvme_async=io_uring_cmd \ +; --filename=/dev/ng0n1 +; +; # Use the xNVMe io-engine engine with nvme sync. impl. +; fio examples/xnvme-fdp.fio \ +; --section=default \ +; --ioengine=xnvme \ +; --xnvme_sync=nvme \ +; --filename=/dev/ng0n1 +; +; FIO_BS="512" FIO_RW="read" FIO_IODEPTH=16 fio examples/xnvme-fdp.fio \ +; --section=override --ioengine=xnvme --xnvme_sync=nvme --filename=/dev/ng0n1 +; +[global] +rw=randwrite +size=2M +iodepth=1 +bs=4K +thread=1 +fdp=1 +fdp_pli=4,5 + +[default] + +[override] +rw=${FIO_RW} +iodepth=${FIO_IODEPTH} +bs=${FIO_BS} diff --git a/fio.1 b/fio.1 index 0257513ba5..86cb2af683 100644 --- a/fio.1 +++ b/fio.1 @@ -2192,10 +2192,10 @@ cached data. Currently the RWF_NOWAIT flag does not supported for cached write. For direct I/O, requests will only succeed if cache invalidation isn't required, file blocks are fully allocated and the disk request could be issued immediately. .TP -.BI (io_uring_cmd)fdp \fR=\fPbool +.BI (io_uring_cmd,xnvme)fdp \fR=\fPbool Enable Flexible Data Placement mode for write commands. .TP -.BI (io_uring_cmd)fdp_pli_select \fR=\fPstr +.BI (io_uring_cmd,xnvme)fdp_pli_select \fR=\fPstr Defines how fio decides which placement ID to use next. The following types are defined: .RS @@ -2211,7 +2211,7 @@ Round robin over available placement IDs. This is the default. The available placement ID index/indices is defined by \fBfdp_pli\fR option. .RE .TP -.BI (io_uring_cmd)fdp_pli \fR=\fPstr +.BI (io_uring_cmd,xnvme)fdp_pli \fR=\fPstr Select which Placement ID Index/Indicies this job is allowed to use for writes. By default, the job will cycle through all available Placement IDs, so use this to isolate these identifiers to specific jobs. If you want fio to use placement From 228d65ea716cf6f71328694ad36622a8f0ed844c Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Thu, 13 Jul 2023 12:07:19 -0400 Subject: [PATCH 0540/1097] options: add code for FDP pli selection use in client/server mode For every new option, we need to convert it from the host's storage format to the on-wire protocol and back in order to be able to use it in client/server mode. Fixes: d3e310c531059fb606f04819c362b4d46c518b84 ("fdp: support random placement id selection") Signed-off-by: Vincent Fu --- cconv.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cconv.c b/cconv.c index 9095d5195e..1bfa770f61 100644 --- a/cconv.c +++ b/cconv.c @@ -351,6 +351,7 @@ int convert_thread_options_to_cpu(struct thread_options *o, o->merge_blktrace_iters[i].u.f = fio_uint64_to_double(le64_to_cpu(top->merge_blktrace_iters[i].u.i)); o->fdp = le32_to_cpu(top->fdp); + o->fdp_pli_select = le32_to_cpu(top->fdp_pli_select); o->fdp_nrpli = le32_to_cpu(top->fdp_nrpli); for (i = 0; i < o->fdp_nrpli; i++) o->fdp_plis[i] = le32_to_cpu(top->fdp_plis[i]); @@ -645,6 +646,7 @@ void convert_thread_options_to_net(struct thread_options_pack *top, top->merge_blktrace_iters[i].u.i = __cpu_to_le64(fio_double_to_uint64(o->merge_blktrace_iters[i].u.f)); top->fdp = cpu_to_le32(o->fdp); + top->fdp_pli_select = cpu_to_le32(o->fdp_pli_select); top->fdp_nrpli = cpu_to_le32(o->fdp_nrpli); for (i = 0; i < o->fdp_nrpli; i++) top->fdp_plis[i] = cpu_to_le32(o->fdp_plis[i]); From a9d83cff1cecb493948525d9a27d2f885ab30e89 Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Thu, 13 Jul 2023 09:54:22 -0700 Subject: [PATCH 0541/1097] diskutil: Improve disk utilization data structure documentation Document the meaning of the members of struct disk_util_stats. Signed-off-by: Bart Van Assche --- diskutil.h | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/diskutil.h b/diskutil.h index 7d7ef802bf..9dca42c42e 100644 --- a/diskutil.h +++ b/diskutil.h @@ -7,6 +7,16 @@ #include "helper_thread.h" #include "fio_sem.h" +/** + * @ios: Number of I/O operations that have been completed successfully. + * @merges: Number of I/O operations that have been merged. + * @sectors: I/O size in 512-byte units. + * @ticks: Time spent on I/O in milliseconds. + * @io_ticks: CPU time spent on I/O in milliseconds. + * @time_in_queue: Weighted time spent doing I/O in milliseconds. + * + * For the array members, index 0 refers to reads and index 1 refers to writes. + */ struct disk_util_stats { uint64_t ios[2]; uint64_t merges[2]; @@ -18,7 +28,7 @@ struct disk_util_stats { }; /* - * Disk utils as read in /sys/block//stat + * Disk utilization as read from /sys/block//stat */ struct disk_util_stat { uint8_t name[FIO_DU_NAME_SZ]; From 8a4c41dd9ed5b10b611b4581c3ac9f4a529ce984 Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Thu, 13 Jul 2023 09:54:30 -0700 Subject: [PATCH 0542/1097] diskutil: Remove casts from get_io_ticks() This patch does not change the behavior of the code. Signed-off-by: Bart Van Assche --- diskutil.c | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/diskutil.c b/diskutil.c index ace7af3d5b..b08ce905e4 100644 --- a/diskutil.c +++ b/diskutil.c @@ -1,3 +1,4 @@ +#include #include #include #include @@ -65,18 +66,14 @@ static int get_io_ticks(struct disk_util *du, struct disk_util_stat *dus) dprint(FD_DISKUTIL, "%s: %s", du->path, p); - ret = sscanf(p, "%llu %llu %llu %llu %llu %llu %llu %llu %u %llu %llu\n", - (unsigned long long *) &dus->s.ios[0], - (unsigned long long *) &dus->s.merges[0], - §ors[0], - (unsigned long long *) &dus->s.ticks[0], - (unsigned long long *) &dus->s.ios[1], - (unsigned long long *) &dus->s.merges[1], - §ors[1], - (unsigned long long *) &dus->s.ticks[1], - &in_flight, - (unsigned long long *) &dus->s.io_ticks, - (unsigned long long *) &dus->s.time_in_queue); + ret = sscanf(p, "%"SCNu64" %"SCNu64" %llu %"SCNu64" " + "%"SCNu64" %"SCNu64" %llu %"SCNu64" " + "%u %"SCNu64" %"SCNu64"\n", + &dus->s.ios[0], &dus->s.merges[0], §ors[0], + &dus->s.ticks[0], + &dus->s.ios[1], &dus->s.merges[1], §ors[1], + &dus->s.ticks[1], + &in_flight, &dus->s.io_ticks, &dus->s.time_in_queue); fclose(f); dprint(FD_DISKUTIL, "%s: stat read ok? %d\n", du->path, ret == 1); dus->s.sectors[0] = sectors[0]; From e08d9f694d972f02e143902124f953df6c3aadfa Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Thu, 13 Jul 2023 10:03:08 -0700 Subject: [PATCH 0543/1097] diskutil: Simplify get_io_ticks() Remove the sectors[] array. Remove the set-but-not-used in_flight variable. Signed-off-by: Bart Van Assche --- diskutil.c | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/diskutil.c b/diskutil.c index b08ce905e4..8aff82ee11 100644 --- a/diskutil.c +++ b/diskutil.c @@ -45,8 +45,6 @@ static void disk_util_free(struct disk_util *du) static int get_io_ticks(struct disk_util *du, struct disk_util_stat *dus) { - unsigned in_flight; - unsigned long long sectors[2]; char line[256]; FILE *f; char *p; @@ -66,19 +64,17 @@ static int get_io_ticks(struct disk_util *du, struct disk_util_stat *dus) dprint(FD_DISKUTIL, "%s: %s", du->path, p); - ret = sscanf(p, "%"SCNu64" %"SCNu64" %llu %"SCNu64" " - "%"SCNu64" %"SCNu64" %llu %"SCNu64" " - "%u %"SCNu64" %"SCNu64"\n", - &dus->s.ios[0], &dus->s.merges[0], §ors[0], + ret = sscanf(p, "%"SCNu64" %"SCNu64" %"SCNu64" %"SCNu64" " + "%"SCNu64" %"SCNu64" %"SCNu64" %"SCNu64" " + "%*u %"SCNu64" %"SCNu64"\n", + &dus->s.ios[0], &dus->s.merges[0], &dus->s.sectors[0], &dus->s.ticks[0], - &dus->s.ios[1], &dus->s.merges[1], §ors[1], + &dus->s.ios[1], &dus->s.merges[1], &dus->s.sectors[1], &dus->s.ticks[1], - &in_flight, &dus->s.io_ticks, &dus->s.time_in_queue); + &dus->s.io_ticks, &dus->s.time_in_queue); fclose(f); dprint(FD_DISKUTIL, "%s: stat read ok? %d\n", du->path, ret == 1); - dus->s.sectors[0] = sectors[0]; - dus->s.sectors[1] = sectors[1]; - return ret != 11; + return ret != 10; } static void update_io_tick_disk(struct disk_util *du) From 843b07e1be99dc86ba02c81461a2d498ea1ab9e1 Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Thu, 13 Jul 2023 10:04:06 -0700 Subject: [PATCH 0544/1097] diskutil: Fix a debug statement in get_io_ticks() Report correctly whether or not reading statistics succeeded. Signed-off-by: Bart Van Assche --- diskutil.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/diskutil.c b/diskutil.c index 8aff82ee11..cf4ede8577 100644 --- a/diskutil.c +++ b/diskutil.c @@ -73,7 +73,7 @@ static int get_io_ticks(struct disk_util *du, struct disk_util_stat *dus) &dus->s.ticks[1], &dus->s.io_ticks, &dus->s.time_in_queue); fclose(f); - dprint(FD_DISKUTIL, "%s: stat read ok? %d\n", du->path, ret == 1); + dprint(FD_DISKUTIL, "%s: stat read ok? %d\n", du->path, ret == 10); return ret != 10; } From 75cbc26d500fc5f7e36f6203c9b8e08b9c6f007c Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Thu, 13 Jul 2023 12:44:54 -0700 Subject: [PATCH 0545/1097] diskutil: Report how many sectors have been read and written It is useful to know how much data has been read and/or written. Report this information. Signed-off-by: Bart Van Assche --- HOWTO.rst | 4 +++- fio.1 | 2 +- stat.c | 7 +++++-- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/HOWTO.rst b/HOWTO.rst index b047877ea3..7ae8ea7b2b 100644 --- a/HOWTO.rst +++ b/HOWTO.rst @@ -4528,13 +4528,15 @@ For each data direction it prints: And finally, the disk statistics are printed. This is Linux specific. They will look like this:: Disk stats (read/write): - sda: ios=16398/16511, merge=30/162, ticks=6853/819634, in_queue=826487, util=100.00% + sda: ios=16398/16511, sectors=32321/65472, merge=30/162, ticks=6853/819634, in_queue=826487, util=100.00% Each value is printed for both reads and writes, with reads first. The numbers denote: **ios** Number of I/Os performed by all groups. +**sectors** + Amount of data transferred in units of 512 bytes for all groups. **merge** Number of merges performed by the I/O scheduler. **ticks** diff --git a/fio.1 b/fio.1 index 86cb2af683..da8752767f 100644 --- a/fio.1 +++ b/fio.1 @@ -4184,7 +4184,7 @@ They will look like this: .P .nf Disk stats (read/write): - sda: ios=16398/16511, merge=30/162, ticks=6853/819634, in_queue=826487, util=100.00% + sda: ios=16398/16511, sectors=32321/65472, merge=30/162, ticks=6853/819634, in_queue=826487, util=100.00% .fi .P Each value is printed for both reads and writes, with reads first. The diff --git a/stat.c b/stat.c index 015b8e280f..ced7364563 100644 --- a/stat.c +++ b/stat.c @@ -1030,11 +1030,14 @@ void print_disk_util(struct disk_util_stat *dus, struct disk_util_agg *agg, if (agg->slavecount) log_buf(out, " "); - log_buf(out, " %s: ios=%llu/%llu, merge=%llu/%llu, " - "ticks=%llu/%llu, in_queue=%llu, util=%3.2f%%", + log_buf(out, " %s: ios=%llu/%llu, sectors=%llu/%llu, " + "merge=%llu/%llu, ticks=%llu/%llu, in_queue=%llu, " + "util=%3.2f%%", dus->name, (unsigned long long) dus->s.ios[0], (unsigned long long) dus->s.ios[1], + (unsigned long long) dus->s.sectors[0], + (unsigned long long) dus->s.sectors[1], (unsigned long long) dus->s.merges[0], (unsigned long long) dus->s.merges[1], (unsigned long long) dus->s.ticks[0], From fc2501310842cc2cd11168074e8f69cf39699263 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Fri, 14 Jul 2023 12:36:20 -0400 Subject: [PATCH 0546/1097] stat: add new diskutil sectors to json output A recent commit added sectors read/written to the disk utilization data. Allow these counts to also appear in the JSON output. Fixes: 75cbc26d500fc5f7e36f6203c9b8e08b9c6f007c ("diskutil: Report how many sectors have been read and written") Signed-off-by: Vincent Fu --- stat.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/stat.c b/stat.c index ced7364563..fc73930d9a 100644 --- a/stat.c +++ b/stat.c @@ -1084,6 +1084,8 @@ void json_array_add_disk_util(struct disk_util_stat *dus, json_object_add_value_string(obj, "name", (const char *)dus->name); json_object_add_value_int(obj, "read_ios", dus->s.ios[0]); json_object_add_value_int(obj, "write_ios", dus->s.ios[1]); + json_object_add_value_int(obj, "read_sectors", dus->s.sectors[0]); + json_object_add_value_int(obj, "write_sectors", dus->s.sectors[1]); json_object_add_value_int(obj, "read_merges", dus->s.merges[0]); json_object_add_value_int(obj, "write_merges", dus->s.merges[1]); json_object_add_value_int(obj, "read_ticks", dus->s.ticks[0]); @@ -1101,6 +1103,10 @@ void json_array_add_disk_util(struct disk_util_stat *dus, agg->ios[0] / agg->slavecount); json_object_add_value_int(obj, "aggr_write_ios", agg->ios[1] / agg->slavecount); + json_object_add_value_int(obj, "aggr_read_sectors", + agg->sectors[0] / agg->slavecount); + json_object_add_value_int(obj, "aggr_write_sectors", + agg->sectors[1] / agg->slavecount); json_object_add_value_int(obj, "aggr_read_merges", agg->merges[0] / agg->slavecount); json_object_add_value_int(obj, "aggr_write_merge", From bb08a260413e54465f370523ae26a1bebb42308c Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Fri, 14 Jul 2023 12:58:34 -0400 Subject: [PATCH 0547/1097] stat: add diskutil aggregated sectors to normal output Since we are now collecting sectors in the disk utilization data we should include them in the aggregated data as well. I tested this with an LVM mirror. I also tested this on an mdadm mirror but all the aggregated and slave data was zero. Fixes: 75cbc26d500fc5f7e36f6203c9b8e08b9c6f007c ("diskutil: Report how many sectors have been read and written") Signed-off-by: Vincent Fu --- stat.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/stat.c b/stat.c index fc73930d9a..7fad73d139 100644 --- a/stat.c +++ b/stat.c @@ -957,11 +957,13 @@ static void show_agg_stats(struct disk_util_agg *agg, int terse, return; if (!terse) { - log_buf(out, ", aggrios=%llu/%llu, aggrmerge=%llu/%llu, " - "aggrticks=%llu/%llu, aggrin_queue=%llu, " - "aggrutil=%3.2f%%", + log_buf(out, ", aggrios=%llu/%llu, aggsectors=%llu/%llu, " + "aggrmerge=%llu/%llu, aggrticks=%llu/%llu, " + "aggrin_queue=%llu, aggrutil=%3.2f%%", (unsigned long long) agg->ios[0] / agg->slavecount, (unsigned long long) agg->ios[1] / agg->slavecount, + (unsigned long long) agg->sectors[0] / agg->slavecount, + (unsigned long long) agg->sectors[1] / agg->slavecount, (unsigned long long) agg->merges[0] / agg->slavecount, (unsigned long long) agg->merges[1] / agg->slavecount, (unsigned long long) agg->ticks[0] / agg->slavecount, From 14adf6e31487aa2bc8e47cd037428036089a3834 Mon Sep 17 00:00:00 2001 From: Michael Kelley Date: Fri, 14 Jul 2023 17:06:01 +0000 Subject: [PATCH 0548/1097] thinktime: Avoid calculating a negative time left to wait When the thinktime_spin option specifies a value that is within a few milliseconds of the thinktime value, in handle_thinktime() it's possible in a VM environment for the duration of usec_spin() to exceed the thinktime value. While doing usec_spin(), the vCPU could get de-scheduled or the hypervisor could steal CPU time from the vCPU. When the guest vCPU runs after being scheduled again, it may read the clock and find that more time has elapsed than intended. In such a case, the time left to wait could be calculated as a negative value. Subsequent calculations then go awry because the time left is cast as unsigned. Fix this by detecting when the time left would go negative and just set it to zero. Fixes: 1a9bf8146 ("Add option to ignore thinktime for rated IO") Fixes: https://github.com/axboe/fio/issues/1588 Link: https://lore.kernel.org/fio/1689354334-131024-1-git-send-email-mikelley@microsoft.com/T/#u Signed-off-by: Michael Kelley Signed-off-by: Vincent Fu --- backend.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/backend.c b/backend.c index d67a4a07c5..b06a11a562 100644 --- a/backend.c +++ b/backend.c @@ -897,7 +897,16 @@ static void handle_thinktime(struct thread_data *td, enum fio_ddir ddir, if (left) total = usec_spin(left); - left = td->o.thinktime - total; + /* + * usec_spin() might run for slightly longer than intended in a VM + * where the vCPU could get descheduled or the hypervisor could steal + * CPU time. Ensure "left" doesn't become negative. + */ + if (total < td->o.thinktime) + left = td->o.thinktime - total; + else + left = 0; + if (td->o.timeout) { runtime_left = td->o.timeout - utime_since_now(&td->epoch); if (runtime_left < (unsigned long long)left) From 9e523ef81a086f813481fab39ccf5b94e8f6dd81 Mon Sep 17 00:00:00 2001 From: Shin'ichiro Kawasaki Date: Wed, 19 Jul 2023 19:57:44 +0900 Subject: [PATCH 0549/1097] zbd: get max_active_zones limit value from zoned devices As a preparation to improve open zones accounting for devices with the max_active_zones limit, get the limit from the devices. In same manner as max_open_zones, call get_max_active_zones callback if the I/O engine supports it. Add the new call back to the I/O engine API and bump up FIO_IOOPS_VERSION. It is expected that io_uring and xnvme engines to support the callback later. When the callback is not available, refer max_active_zones sysfs attribute for block devices. When the limit value is not available, use zero value which means no limit. Keep the obtained limit value in the struct zoned_block_device_info. Signed-off-by: Shin'ichiro Kawasaki Reviewed-by: Niklas Cassel Link: https://lore.kernel.org/r/20230719105756.553146-2-shinichiro.kawasaki@wdc.com Signed-off-by: Vincent Fu --- ioengines.h | 4 +++- oslib/blkzoned.h | 9 +++++++++ oslib/linux-blkzoned.c | 23 +++++++++++++++++++++++ zbd.c | 29 +++++++++++++++++++++++++++++ zbd.h | 4 ++++ 5 files changed, 68 insertions(+), 1 deletion(-) diff --git a/ioengines.h b/ioengines.h index 9484265e6f..4391b31e3c 100644 --- a/ioengines.h +++ b/ioengines.h @@ -9,7 +9,7 @@ #include "zbd_types.h" #include "fdp.h" -#define FIO_IOOPS_VERSION 32 +#define FIO_IOOPS_VERSION 33 #ifndef CONFIG_DYNAMIC_ENGINES #define FIO_STATIC static @@ -62,6 +62,8 @@ struct ioengine_ops { uint64_t, uint64_t); int (*get_max_open_zones)(struct thread_data *, struct fio_file *, unsigned int *); + int (*get_max_active_zones)(struct thread_data *, struct fio_file *, + unsigned int *); int (*finish_zone)(struct thread_data *, struct fio_file *, uint64_t, uint64_t); int (*fdp_fetch_ruhs)(struct thread_data *, struct fio_file *, diff --git a/oslib/blkzoned.h b/oslib/blkzoned.h index 29fb034f58..e598bd4f80 100644 --- a/oslib/blkzoned.h +++ b/oslib/blkzoned.h @@ -18,6 +18,9 @@ extern int blkzoned_reset_wp(struct thread_data *td, struct fio_file *f, uint64_t offset, uint64_t length); extern int blkzoned_get_max_open_zones(struct thread_data *td, struct fio_file *f, unsigned int *max_open_zones); +extern int blkzoned_get_max_active_zones(struct thread_data *td, + struct fio_file *f, + unsigned int *max_active_zones); extern int blkzoned_finish_zone(struct thread_data *td, struct fio_file *f, uint64_t offset, uint64_t length); #else @@ -53,6 +56,12 @@ static inline int blkzoned_get_max_open_zones(struct thread_data *td, struct fio { return -EIO; } +static inline int blkzoned_get_max_active_zones(struct thread_data *td, + struct fio_file *f, + unsigned int *max_open_zones) +{ + return -EIO; +} static inline int blkzoned_finish_zone(struct thread_data *td, struct fio_file *f, uint64_t offset, uint64_t length) diff --git a/oslib/linux-blkzoned.c b/oslib/linux-blkzoned.c index 722e09925b..2c3ecf33b4 100644 --- a/oslib/linux-blkzoned.c +++ b/oslib/linux-blkzoned.c @@ -186,6 +186,29 @@ int blkzoned_get_max_open_zones(struct thread_data *td, struct fio_file *f, return 0; } +int blkzoned_get_max_active_zones(struct thread_data *td, struct fio_file *f, + unsigned int *max_active_zones) +{ + char *max_active_str; + + if (f->filetype != FIO_TYPE_BLOCK) + return -EIO; + + max_active_str = blkzoned_get_sysfs_attr(f->file_name, "queue/max_active_zones"); + if (!max_active_str) { + *max_active_zones = 0; + return 0; + } + + dprint(FD_ZBD, "%s: max active zones supported by device: %s\n", + f->file_name, max_active_str); + *max_active_zones = atoll(max_active_str); + + free(max_active_str); + + return 0; +} + static uint64_t zone_capacity(struct blk_zone_report *hdr, struct blk_zone *blkz) { diff --git a/zbd.c b/zbd.c index d45652157c..a5cb34d228 100644 --- a/zbd.c +++ b/zbd.c @@ -471,6 +471,34 @@ static int zbd_get_max_open_zones(struct thread_data *td, struct fio_file *f, return ret; } +/** + * zbd_get_max_active_zones - Get the maximum number of active zones + * @td: FIO thread data + * @f: FIO file for which to get max active zones + * + * Returns max_active_zones limit value of the target file if it is available. + * Otherwise return zero, which means no limit. + */ +static unsigned int zbd_get_max_active_zones(struct thread_data *td, + struct fio_file *f) +{ + unsigned int max_active_zones; + int ret; + + if (td->io_ops && td->io_ops->get_max_active_zones) + ret = td->io_ops->get_max_active_zones(td, f, + &max_active_zones); + else + ret = blkzoned_get_max_active_zones(td, f, &max_active_zones); + if (ret < 0) { + dprint(FD_ZBD, "%s: max_active_zones is not available\n", + f->file_name); + return 0; + } + + return max_active_zones; +} + /** * __zbd_write_zone_get - Add a zone to the array of write zones. * @td: fio thread data. @@ -927,6 +955,7 @@ static int parse_zone_info(struct thread_data *td, struct fio_file *f) f->zbd_info->zone_size_log2 = is_power_of_2(zone_size) ? ilog2(zone_size) : 0; f->zbd_info->nr_zones = nr_zones; + f->zbd_info->max_active_zones = zbd_get_max_active_zones(td, f); if (same_zone_cap) dprint(FD_ZBD, "Zone capacity = %"PRIu64" KB\n", diff --git a/zbd.h b/zbd.h index f0ac98763c..a5cf59d139 100644 --- a/zbd.h +++ b/zbd.h @@ -52,6 +52,9 @@ struct fio_zone_info { * are simultaneously written. A zero value means unlimited zones of * simultaneous writes and that write target zones will not be tracked in * the write_zones array. + * @max_active_zones: device side limit on the number of sequential write zones + * in open or closed conditions. A zero value means unlimited number of + * zones in the conditions. * @mutex: Protects the modifiable members in this structure (refcount and * num_open_zones). * @zone_size: size of a single zone in bytes. @@ -75,6 +78,7 @@ struct fio_zone_info { struct zoned_block_device_info { enum zbd_zoned_model model; uint32_t max_write_zones; + uint32_t max_active_zones; pthread_mutex_t mutex; uint64_t zone_size; uint64_t wp_valid_data_bytes; From bab838f806eb139b1ffece82454df77d9b6be233 Mon Sep 17 00:00:00 2001 From: Shin'ichiro Kawasaki Date: Wed, 19 Jul 2023 19:57:45 +0900 Subject: [PATCH 0550/1097] zbd: write to closed zones on the devices with max_active_zones limit Current fio implementation does not handle zones in closed condition as write target zones. When the device has max_active_zones limit, the write to other zones may cause errors by exceeding the limit, since the zones in closed condition consume the device resource for the max_active_zones limit. To avoid the error, handle the zones in closed condition as write target in same manner as the zones in open condition when the device has the max_active_zones limit. At the job start, check each condition of the zones in the IO ranges and if it has closed condition, pass the zone to zbd_write_zones_get() in same manner as the zones in open condition. Signed-off-by: Shin'ichiro Kawasaki Reviewed-by: Niklas Cassel Link: https://lore.kernel.org/r/20230719105756.553146-3-shinichiro.kawasaki@wdc.com Signed-off-by: Vincent Fu --- zbd.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/zbd.c b/zbd.c index a5cb34d228..b05d2360d9 100644 --- a/zbd.c +++ b/zbd.c @@ -1276,7 +1276,11 @@ int zbd_setup_files(struct thread_data *td) for (zi = f->min_zone; zi < f->max_zone; zi++) { z = &zbd->zone_info[zi]; if (z->cond != ZBD_ZONE_COND_IMP_OPEN && - z->cond != ZBD_ZONE_COND_EXP_OPEN) + z->cond != ZBD_ZONE_COND_EXP_OPEN && + z->cond != ZBD_ZONE_COND_CLOSED) + continue; + if (!zbd->max_active_zones && + z->cond == ZBD_ZONE_COND_CLOSED) continue; if (__zbd_write_zone_get(td, f, z)) continue; From 8b403508bfe3ea815d522d813b66e86b43e3fa82 Mon Sep 17 00:00:00 2001 From: Shin'ichiro Kawasaki Date: Wed, 19 Jul 2023 19:57:46 +0900 Subject: [PATCH 0551/1097] zbd: print max_active_zones limit error message When zoned block devices have max_active_zones limit and when write operations exceed that limit, Linux block sub-system reports EOVERFLOW. However, the strerror() string for EOVERFLOW does not mention about max_active_zones then it confuses users. To avoid the confusion, print additional error message to indicate the max_active_zones limit. For this purpose, add a hook function zbd_log_err() and call it from __io_u_log_error(). Signed-off-by: Shin'ichiro Kawasaki Reviewed-by: Niklas Cassel Link: https://lore.kernel.org/r/20230719105756.553146-4-shinichiro.kawasaki@wdc.com Signed-off-by: Vincent Fu --- io_u.c | 2 ++ zbd.c | 12 ++++++++++++ zbd.h | 1 + 3 files changed, 15 insertions(+) diff --git a/io_u.c b/io_u.c index 27b6c92ab9..07e5bac5ef 100644 --- a/io_u.c +++ b/io_u.c @@ -1879,6 +1879,8 @@ static void __io_u_log_error(struct thread_data *td, struct io_u *io_u) io_ddir_name(io_u->ddir), io_u->offset, io_u->xfer_buflen); + zbd_log_err(td, io_u); + if (td->io_ops->errdetails) { char *err = td->io_ops->errdetails(io_u); diff --git a/zbd.c b/zbd.c index b05d2360d9..caac68bb37 100644 --- a/zbd.c +++ b/zbd.c @@ -2243,3 +2243,15 @@ int zbd_do_io_u_trim(struct thread_data *td, struct io_u *io_u) return io_u_completed; } + +void zbd_log_err(const struct thread_data *td, const struct io_u *io_u) +{ + const struct fio_file *f = io_u->file; + + if (td->o.zone_mode != ZONE_MODE_ZBD) + return; + + if (io_u->error == EOVERFLOW) + log_err("%s: Exceeded max_active_zones limit. Check conditions of zones out of I/O ranges.\n", + f->file_name); +} diff --git a/zbd.h b/zbd.h index a5cf59d139..5750a0b808 100644 --- a/zbd.h +++ b/zbd.h @@ -105,6 +105,7 @@ enum fio_ddir zbd_adjust_ddir(struct thread_data *td, struct io_u *io_u, enum io_u_action zbd_adjust_block(struct thread_data *td, struct io_u *io_u); char *zbd_write_status(const struct thread_stat *ts); int zbd_do_io_u_trim(struct thread_data *td, struct io_u *io_u); +void zbd_log_err(const struct thread_data *td, const struct io_u *io_u); static inline void zbd_close_file(struct fio_file *f) { From 23a846b389235e5aa0058042319922b7ef7c01fa Mon Sep 17 00:00:00 2001 From: Shin'ichiro Kawasaki Date: Wed, 19 Jul 2023 19:57:47 +0900 Subject: [PATCH 0552/1097] docs: modify max_open_zones option description A recent commit modified the max_open_zones option to improve handling of zoned block devices with max_active_zones limit. Modify description of the option to meet the change. For that purpose, explain the relation between the max_open_zones option and the device side limits max_active_zones and max_open_zones. Also mention about three zone conditions 'implicit open', 'explict open' and 'closed'. And replace the word 'zone state' with 'zone condition'. Signed-off-by: Shin'ichiro Kawasaki Reviewed-by: Niklas Cassel Link: https://lore.kernel.org/r/20230719105756.553146-5-shinichiro.kawasaki@wdc.com Signed-off-by: Vincent Fu --- HOWTO.rst | 44 ++++++++++++++++++++++++++++---------------- fio.1 | 36 ++++++++++++++++++++++++------------ 2 files changed, 52 insertions(+), 28 deletions(-) diff --git a/HOWTO.rst b/HOWTO.rst index 7ae8ea7b2b..7fe70fbdd0 100644 --- a/HOWTO.rst +++ b/HOWTO.rst @@ -1056,22 +1056,34 @@ Target file/device .. option:: max_open_zones=int - A zone of a zoned block device is in the open state when it is partially - written (i.e. not all sectors of the zone have been written). Zoned - block devices may have a limit on the total number of zones that can - be simultaneously in the open state, that is, the number of zones that - can be written to simultaneously. The :option:`max_open_zones` parameter - limits the number of zones to which write commands are issued by all fio - jobs, that is, limits the number of zones that will be in the open - state. This parameter is relevant only if the :option:`zonemode` =zbd is - used. The default value is always equal to maximum number of open zones - of the target zoned block device and a value higher than this limit - cannot be specified by users unless the option - :option:`ignore_zone_limits` is specified. When - :option:`ignore_zone_limits` is specified or the target device has no - limit on the number of zones that can be in an open state, - :option:`max_open_zones` can specify 0 to disable any limit on the - number of zones that can be simultaneously written to by all jobs. + When a zone of a zoned block device is partially written (i.e. not all + sectors of the zone have been written), the zone is in one of three + conditions: 'implicit open', 'explicit open' or 'closed'. Zoned block + devices may have a limit called 'max_open_zones' (same name as the + parameter) on the total number of zones that can simultaneously be in + the 'implicit open' or 'explicit open' conditions. Zoned block devices + may have another limit called 'max_active_zones', on the total number of + zones that can simultaneously be in the three conditions. The + :option:`max_open_zones` parameter limits the number of zones to which + write commands are issued by all fio jobs, that is, limits the number of + zones that will be in the conditions. When the device has the + max_open_zones limit and does not have the max_active_zones limit, the + :option:`max_open_zones` parameter limits the number of zones in the two + open conditions up to the limit. In this case, fio includes zones in the + two open conditions to the write target zones at fio start. When the + device has both the max_open_zones and the max_active_zones limits, the + :option:`max_open_zones` parameter limits the number of zones in the + three conditions up to the limit. In this case, fio includes zones in + the three conditions to the write target zones at fio start. + + This parameter is relevant only if the :option:`zonemode` =zbd is used. + The default value is always equal to the max_open_zones limit of the + target zoned block device and a value higher than this limit cannot be + specified by users unless the option :option:`ignore_zone_limits` is + specified. When :option:`ignore_zone_limits` is specified or the target + device does not have the max_open_zones limit, :option:`max_open_zones` + can specify 0 to disable any limit on the number of zones that can be + simultaneously written to by all jobs. .. option:: job_max_open_zones=int diff --git a/fio.1 b/fio.1 index da8752767f..20acd081c3 100644 --- a/fio.1 +++ b/fio.1 @@ -832,18 +832,30 @@ numbers fio only reads beyond the write pointer if explicitly told to do so. Default: false. .TP .BI max_open_zones \fR=\fPint -A zone of a zoned block device is in the open state when it is partially written -(i.e. not all sectors of the zone have been written). Zoned block devices may -have limit a on the total number of zones that can be simultaneously in the -open state, that is, the number of zones that can be written to simultaneously. -The \fBmax_open_zones\fR parameter limits the number of zones to which write -commands are issued by all fio jobs, that is, limits the number of zones that -will be in the open state. This parameter is relevant only if the -\fBzonemode=zbd\fR is used. The default value is always equal to maximum number -of open zones of the target zoned block device and a value higher than this -limit cannot be specified by users unless the option \fBignore_zone_limits\fR is -specified. When \fBignore_zone_limits\fR is specified or the target device has -no limit on the number of zones that can be in an open state, +When a zone of a zoned block device is partially written (i.e. not all sectors +of the zone have been written), the zone is in one of three +conditions: 'implicit open', 'explicit open' or 'closed'. Zoned block devices +may have a limit called 'max_open_zones' (same name as the parameter) on the +total number of zones that can simultaneously be in the 'implicit open' +or 'explicit open' conditions. Zoned block devices may have another limit +called 'max_active_zones', on the total number of zones that can simultaneously +be in the three conditions. The \fBmax_open_zones\fR parameter limits +the number of zones to which write commands are issued by all fio jobs, that is, +limits the number of zones that will be in the conditions. When the device has +the max_open_zones limit and does not have the max_active_zones limit, the +\fBmax_open_zones\fR parameter limits the number of zones in the two open +conditions up to the limit. In this case, fio includes zones in the two open +conditions to the write target zones at fio start. When the device has both the +max_open_zones and the max_active_zones limits, the \fBmax_open_zones\fR +parameter limits the number of zones in the three conditions up to the limit. +In this case, fio includes zones in the three conditions to the write target +zones at fio start. + +This parameter is relevant only if the \fBzonemode=zbd\fR is used. The default +value is always equal to the max_open_zones limit of the target zoned block +device and a value higher than this limit cannot be specified by users unless +the option \fBignore_zone_limits\fR is specified. When \fBignore_zone_limits\fR +is specified or the target device does not have the max_open_zones limit, \fBmax_open_zones\fR can specify 0 to disable any limit on the number of zones that can be simultaneously written to by all jobs. .TP From 00080a1eb135a91e46ff17ea630512d142124708 Mon Sep 17 00:00:00 2001 From: Shin'ichiro Kawasaki Date: Wed, 19 Jul 2023 19:57:48 +0900 Subject: [PATCH 0553/1097] t/zbd: add close_zone helper function Add a helper function which sets the specified zone in closed condition. Signed-off-by: Shin'ichiro Kawasaki Link: https://lore.kernel.org/r/20230719105756.553146-6-shinichiro.kawasaki@wdc.com Signed-off-by: Vincent Fu --- t/zbd/functions | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/t/zbd/functions b/t/zbd/functions index 9a6d699910..fe5b3397f5 100644 --- a/t/zbd/functions +++ b/t/zbd/functions @@ -4,6 +4,7 @@ blkzone=$(type -p blkzone 2>/dev/null) sg_inq=$(type -p sg_inq 2>/dev/null) zbc_report_zones=$(type -p zbc_report_zones 2>/dev/null) zbc_reset_zone=$(type -p zbc_reset_zone 2>/dev/null) +zbc_close_zone=$(type -p zbc_close_zone 2>/dev/null) zbc_info=$(type -p zbc_info 2>/dev/null) if [ -z "${blkzone}" ] && { [ -z "${zbc_report_zones}" ] || [ -z "${zbc_reset_zone}" ]; }; then @@ -304,6 +305,18 @@ reset_zone() { fi } +# Close the zone on device $1 at offset $2. The offset must be specified in +# units of 512 byte sectors. +close_zone() { + local dev=$1 offset=$2 + + if [ -n "${blkzone}" ] && [ -z "${use_libzbc}" ]; then + ${blkzone} close -o "${offset}" -c 1 "$dev" + else + ${zbc_close_zone} -sector "$dev" "${offset}" >/dev/null + fi +} + # Extract the number of bytes that have been transferred from a line like # READ: bw=6847KiB/s (7011kB/s), 6847KiB/s-6847KiB/s (7011kB/s-7011kB/s), io=257MiB (269MB), run=38406-38406msec fio_io() { From 1a5411cd73840b40737f629ec703b082aed08067 Mon Sep 17 00:00:00 2001 From: Shin'ichiro Kawasaki Date: Wed, 19 Jul 2023 19:57:49 +0900 Subject: [PATCH 0554/1097] t/zbd: add max_active_zone variable To test fio behavior on zoned block devices with max_active_zones limit, add a global variable which holds the limit value. Also add helper functions to check max_active_zones limit of the test target devices and max_active_zones requirement of test cases. Signed-off-by: Shin'ichiro Kawasaki Link: https://lore.kernel.org/r/20230719105756.553146-7-shinichiro.kawasaki@wdc.com Signed-off-by: Vincent Fu --- t/zbd/functions | 12 ++++++++++++ t/zbd/test-zbd-support | 17 +++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/t/zbd/functions b/t/zbd/functions index fe5b3397f5..529e7944a2 100644 --- a/t/zbd/functions +++ b/t/zbd/functions @@ -239,6 +239,18 @@ max_open_zones() { fi } +# If sysfs provides, get max_active_zones limit of the zoned block device. +max_active_zones() { + local dev=$1 + local sys_queue="/sys/block/${dev##*/}/queue/" + + if [[ -e "$sys_queue/max_active_zones" ]]; then + cat "$sys_queue/max_active_zones" + return + fi + echo 0 +} + # Get minimum block size to write to seq zones. Refer the sysfs attribute # zone_write_granularity which shows the valid minimum size regardless of zoned # block device type. If the sysfs attribute is not available, refer physical diff --git a/t/zbd/test-zbd-support b/t/zbd/test-zbd-support index a3d37a7d95..eed3d00d73 100755 --- a/t/zbd/test-zbd-support +++ b/t/zbd/test-zbd-support @@ -272,6 +272,20 @@ require_max_open_zones() { return 0 } +require_max_active_zones() { + local min=${1} + + if ((max_active_zones == 0)); then + SKIP_REASON="$dev does not have max_active_zones limit" + return 1 + fi + if ((max_active_zones < min)); then + SKIP_REASON="max_active_zones of $dev is smaller than $min" + return 1 + fi + return 0 +} + # Check whether buffered writes are refused for block devices. test1() { require_block_dev || return $SKIP_TESTCASE @@ -1497,6 +1511,7 @@ if [[ -b "$realdev" ]]; then echo "Failed to determine maximum number of open zones" exit 1 fi + max_active_zones=$(max_active_zones "$dev") set_io_scheduler "$basename" deadline || exit $? if [ -n "$reset_all_zones" ]; then reset_zone "$dev" -1 @@ -1508,6 +1523,7 @@ if [[ -b "$realdev" ]]; then zone_size=$(max 65536 "$min_seq_write_size") sectors_per_zone=$((zone_size / 512)) max_open_zones=128 + max_active_zones=0 set_io_scheduler "$basename" none || exit $? ;; esac @@ -1543,6 +1559,7 @@ elif [[ -c "$realdev" ]]; then echo "Failed to determine maximum number of open zones" exit 1 fi + max_active_zones=0 if [ -n "$reset_all_zones" ]; then reset_zone "$dev" -1 fi From b3be0f00a4bda09588798500659cab0d1668177d Mon Sep 17 00:00:00 2001 From: Shin'ichiro Kawasaki Date: Wed, 19 Jul 2023 19:57:50 +0900 Subject: [PATCH 0555/1097] t/zbd: add test case to check zones in closed condition When the zoned block device has max_active_zones limit, the zones in open or closed condition consume resource on the device. If the number of zones in open or closed condition gets larger than the max_active_zones limit, the device reports an error. Until the recent fix ("zbd: write to closed zones on the devices with max_active_zones limit"), fio handled only zones in open condition as write target then fio was not able to avoid the error. Add a test which confirms that the fix avoids the error by handling zones in closed condition as write target. This test case requires the device has max_active_zones limit. Prepare zones in closed condition as many as the max_active_zones limit. Do random write and check no error. Signed-off-by: Shin'ichiro Kawasaki Link: https://lore.kernel.org/r/20230719105756.553146-8-shinichiro.kawasaki@wdc.com Signed-off-by: Vincent Fu --- t/zbd/test-zbd-support | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/t/zbd/test-zbd-support b/t/zbd/test-zbd-support index eed3d00d73..6d44faf1ac 100755 --- a/t/zbd/test-zbd-support +++ b/t/zbd/test-zbd-support @@ -1427,6 +1427,43 @@ test65() { check_written $((zone_size + capacity)) } +# Test closed zones are handled as open zones. This test case requires zoned +# block devices which has same max_open_zones and max_active_zones. +test66() { + local i off + + require_zbd || return $SKIP_TESTCASE + require_max_active_zones 2 || return $SKIP_TESTCASE + require_max_open_zones "${max_active_zones}" || return $SKIP_TESTCASE + require_seq_zones $((max_active_zones * 16)) || return $SKIP_TESTCASE + + reset_zone "$dev" -1 + + # Prepare max_active_zones in closed condition. + off=$((first_sequential_zone_sector * 512)) + run_fio --name=w --filename="$dev" --zonemod=zbd --direct=1 \ + --offset=$((off)) --zonesize="${zone_size}" --rw=randwrite \ + --bs=4096 --size="$((zone_size * max_active_zones))" \ + --io_size="${zone_size}" "$(ioengine "psync")" \ + >> "${logfile}.${test_number}" 2>&1 || return $? + for ((i = 0; i < max_active_zones; i++)); do + close_zone "$dev" $((off / 512)) || return $? + off=$((off + zone_size)) + done + + # Run random write to the closed zones and empty zones. This confirms + # that fio handles closed zones as write target open zones. Otherwise, + # fio writes to the empty zones and hit the max_active_zones limit. + off=$((first_sequential_zone_sector * 512)) + run_one_fio_job --zonemod=zbd --direct=1 \ + "$(ioengine "psync")" --rw=randwrite --bs=4096 \ + --max_open_zones="$max_active_zones" --offset=$((off)) \ + --size=$((max_active_zones * 16 * zone_size)) \ + --io_size=$((zone_size)) --zonesize="${zone_size}" \ + --time_based --runtime=5s \ + >> "${logfile}.${test_number}" 2>&1 +} + SECONDS=0 tests=() dynamic_analyzer=() From 25868b05c9d388055441a2cf4133534ea51cebc1 Mon Sep 17 00:00:00 2001 From: Shin'ichiro Kawasaki Date: Wed, 19 Jul 2023 19:57:51 +0900 Subject: [PATCH 0556/1097] t/zbd: add test case to check max_active_zones limit error message The recent fio change introduced a new error message to indicate max_active_zones limit error of zoned block devices. Add a test case to check the error message is reported. Signed-off-by: Shin'ichiro Kawasaki Link: https://lore.kernel.org/r/20230719105756.553146-9-shinichiro.kawasaki@wdc.com Signed-off-by: Vincent Fu --- t/zbd/test-zbd-support | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/t/zbd/test-zbd-support b/t/zbd/test-zbd-support index 6d44faf1ac..1d1d389baf 100755 --- a/t/zbd/test-zbd-support +++ b/t/zbd/test-zbd-support @@ -1464,6 +1464,34 @@ test66() { >> "${logfile}.${test_number}" 2>&1 } +# Test max_active_zones limit failure is reported with good error message. +test67() { + local i off + + require_zbd || return $SKIP_TESTCASE + require_max_active_zones 2 || return $SKIP_TESTCASE + require_max_open_zones "${max_active_zones}" || return $SKIP_TESTCASE + require_seq_zones $((max_active_zones + 1)) || return $SKIP_TESTCASE + + reset_zone "$dev" -1 + + # Prepare max_active_zones in open condition. + off=$((first_sequential_zone_sector * 512)) + run_fio --name=w --filename="$dev" --zonemod=zbd --direct=1 \ + --offset=$((off)) --zonesize="${zone_size}" --rw=randwrite \ + --bs=4096 --size="$((zone_size * max_active_zones))" \ + --io_size="${zone_size}" "$(ioengine "psync")" \ + >> "${logfile}.${test_number}" 2>&1 || return $? + + # Write to antoher zone and trigger max_active_zones limit error. + off=$((off + zone_size * max_active_zones)) + run_one_fio_job --zonemod=zbd --direct=1 "$(ioengine "psync")" \ + --rw=write --bs=$min_seq_write_size --offset=$((off)) \ + --size=$((zone_size)) --zonesize="${zone_size}" \ + >> "${logfile}.${test_number}" 2>&1 && return $? + grep -q 'Exceeded max_active_zones limit' "${logfile}.${test_number}" +} + SECONDS=0 tests=() dynamic_analyzer=() From af26c9bf6992e47f5da18955e1edb2d318325db3 Mon Sep 17 00:00:00 2001 From: Shin'ichiro Kawasaki Date: Wed, 19 Jul 2023 19:57:52 +0900 Subject: [PATCH 0557/1097] t/zbd: get max_open_zones from sysfs The helper bash function gets max_open_zones limit of the test target device using sg_inq and libzbc tools. This works for SAS/SATA devices but does not work for ZNS or null_blk devices. This results is running the test case 31 with wrong max_open_zones value. Fix this by referring max_open_zones sysfs attribute. Signed-off-by: Shin'ichiro Kawasaki Link: https://lore.kernel.org/r/20230719105756.553146-10-shinichiro.kawasaki@wdc.com Signed-off-by: Vincent Fu --- t/zbd/functions | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/t/zbd/functions b/t/zbd/functions index 529e7944a2..4faa45a92d 100644 --- a/t/zbd/functions +++ b/t/zbd/functions @@ -212,8 +212,14 @@ last_online_zone() { # max_open_zones in sysfs, or which lacks zoned block device support completely. max_open_zones() { local dev=$1 + local realdev syspath - if [ -n "${sg_inq}" ] && [ ! -n "${use_libzbc}" ]; then + realdev=$(readlink -f "$dev") + syspath=/sys/block/${realdev##*/}/queue/max_open_zones + + if [ -b "${realdev}" ] && [ -r "${syspath}" ]; then + cat ${syspath} + elif [ -n "${sg_inq}" ] && [ ! -n "${use_libzbc}" ]; then if ! ${sg_inq} -e --page=0xB6 --len=20 --hex "$dev" \ > /dev/null 2>&1; then # When sg_inq can not get max open zones, specify 0 which indicates From 4c99637ecee9bcbd96b4e91f2b627b276a905118 Mon Sep 17 00:00:00 2001 From: Shin'ichiro Kawasaki Date: Wed, 19 Jul 2023 19:57:53 +0900 Subject: [PATCH 0558/1097] t/zbd: fix fio failure check and SG node failure in test case 31 The test case 31 runs fio twice but the failure of the first fio run was not checked. This allowed the test case pass even with wrong max_open_zones value. To fix this, check exit code of the fio run. Also, the first fio run fails when the test target devices are SG nodes, since libzbc I/O engine is not used. To fix this, call the ioengine() helper function which adjusts I/O engine for each device. Signed-off-by: Shin'ichiro Kawasaki Link: https://lore.kernel.org/r/20230719105756.553146-11-shinichiro.kawasaki@wdc.com Signed-off-by: Vincent Fu --- t/zbd/test-zbd-support | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/t/zbd/test-zbd-support b/t/zbd/test-zbd-support index 1d1d389baf..71cb18bb00 100755 --- a/t/zbd/test-zbd-support +++ b/t/zbd/test-zbd-support @@ -794,9 +794,10 @@ test31() { opts=("--name=$dev" "--filename=$dev" "--rw=write" "--bs=${bs}") opts+=("--offset=$off" "--size=$((inc * nz))" "--io_size=$((bs * nz))") opts+=("--zonemode=strided" "--zonesize=${bs}" "--zonerange=${inc}") - opts+=("--direct=1") + opts+=("--direct=1" "$(ioengine "psync")") echo "fio ${opts[@]}" >> "${logfile}.${test_number}" - "$(dirname "$0")/../../fio" "${opts[@]}" >> "${logfile}.${test_number}" 2>&1 + "$(dirname "$0")/../../fio" "${opts[@]}" >> "${logfile}.${test_number}" \ + 2>&1 || return $? # Next, run the test. opts=("--name=$dev" "--filename=$dev" "--offset=$off" "--size=$size") From 4f9333c944fa19c83f198daab62ae50a6416b99e Mon Sep 17 00:00:00 2001 From: Shin'ichiro Kawasaki Date: Wed, 19 Jul 2023 19:57:54 +0900 Subject: [PATCH 0559/1097] t/zbd: add missing prep_write for test cases with write workloads The test cases from 54 to 57 do writes but miss prep_write() call which resets zones of the test target device with max_active_zones limit. This results in failures due to open zones out of I/O ranges and max_active_zones limit error. Add the missing prep_write() call to avoid the failures. Signed-off-by: Shin'ichiro Kawasaki Link: https://lore.kernel.org/r/20230719105756.553146-12-shinichiro.kawasaki@wdc.com Signed-off-by: Vincent Fu --- t/zbd/test-zbd-support | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/t/zbd/test-zbd-support b/t/zbd/test-zbd-support index 71cb18bb00..c8f3eb614f 100755 --- a/t/zbd/test-zbd-support +++ b/t/zbd/test-zbd-support @@ -1197,6 +1197,7 @@ test54() { require_zbd || return $SKIP_TESTCASE require_seq_zones 8 || return $SKIP_TESTCASE + prep_write run_fio --name=job --filename=${dev} "$(ioengine "libaio")" \ --time_based=1 --runtime=30s --continue_on_error=0 \ --offset=$((first_sequential_zone_sector * 512)) \ @@ -1218,6 +1219,7 @@ test55() { # offset=1z + offset_increment=10z + size=2z require_seq_zones 13 || return $SKIP_TESTCASE + prep_write run_fio --name=j \ --filename=${dev} \ --direct=1 \ @@ -1243,6 +1245,7 @@ test56() { require_regular_block_dev || return $SKIP_TESTCASE require_seq_zones 10 || return $SKIP_TESTCASE + prep_write run_fio --name=j \ --filename=${dev} \ --direct=1 \ @@ -1264,6 +1267,7 @@ test57() { require_zbd || return $SKIP_TESTCASE + prep_write bs=$((4096 * 7)) off=$((first_sequential_zone_sector * 512)) From 709706209b50ae9697b25057e491ea5527178320 Mon Sep 17 00:00:00 2001 From: Dmitry Fomichev Date: Wed, 19 Jul 2023 19:57:55 +0900 Subject: [PATCH 0560/1097] t/zbd: fix null_blk configuration in run-tests-against-nullb Correctly set max_open in null_blk configfs. Fix displayed number of conventional zones in section config banner. Signed-off-by: Dmitry Fomichev Signed-off-by: Shin'ichiro Kawasaki Link: https://lore.kernel.org/r/20230719105756.553146-13-shinichiro.kawasaki@wdc.com Signed-off-by: Vincent Fu --- t/zbd/run-tests-against-nullb | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/t/zbd/run-tests-against-nullb b/t/zbd/run-tests-against-nullb index 7d2c7fa8fc..4617e398f1 100755 --- a/t/zbd/run-tests-against-nullb +++ b/t/zbd/run-tests-against-nullb @@ -67,13 +67,20 @@ configure_nullb() fi echo "${zone_capacity}" > zone_capacity fi + if ((conv_pcnt)); then if ((!conv_supported)); then echo "null_blk does not support conventional zones" return 2 fi nr_conv=$((dev_size/zone_size*conv_pcnt/100)) - echo "${nr_conv}" > zone_nr_conv + else + nr_conv=0 + fi + echo "${nr_conv}" > zone_nr_conv + + if ((max_open)); then + echo "${max_open}" > zone_max_open fi fi From caf7ac7ef000097765b1c56404adb5e68b227977 Mon Sep 17 00:00:00 2001 From: Dmitry Fomichev Date: Wed, 19 Jul 2023 19:57:56 +0900 Subject: [PATCH 0561/1097] t/zbd: add max_active configs to run-tests-against-nullb Introduce several new test device configurations to cover the cases with max_active_zones is not being zero, i.e. limited. Two group of new configurations are added, one with max_active_zones == max_open_zones and the other with max_active_zones > max_open_zones. Signed-off-by: Dmitry Fomichev Signed-off-by: Shin'ichiro Kawasaki Link: https://lore.kernel.org/r/20230719105756.553146-14-shinichiro.kawasaki@wdc.com Signed-off-by: Vincent Fu --- t/zbd/run-tests-against-nullb | 194 ++++++++++++++++++++++++++++++++-- 1 file changed, 188 insertions(+), 6 deletions(-) diff --git a/t/zbd/run-tests-against-nullb b/t/zbd/run-tests-against-nullb index 4617e398f1..97d2996675 100755 --- a/t/zbd/run-tests-against-nullb +++ b/t/zbd/run-tests-against-nullb @@ -81,6 +81,13 @@ configure_nullb() if ((max_open)); then echo "${max_open}" > zone_max_open + if ((max_active)); then + if ((!max_act_supported)); then + echo "null_blk does not support active zone counts" + return 2 + fi + echo "${max_active}" > zone_max_active + fi fi fi @@ -97,6 +104,11 @@ show_nullb_config() echo " $(printf "Zone Capacity: %d MB" ${zone_capacity})" if ((max_open)); then echo " $(printf "Max Open: %d Zones" ${max_open})" + if ((max_active)); then + echo " $(printf "Max Active: %d Zones" ${max_active})" + else + echo " Max Active: Unlimited Zones" + fi else echo " Max Open: Unlimited Zones" fi @@ -131,6 +143,7 @@ section3() zone_size=4 zone_capacity=3 max_open=0 + max_active=0 } # Zoned device with mostly sequential zones, ZCAP == ZSIZE, unlimited MaxOpen. @@ -140,6 +153,7 @@ section4() zone_size=1 zone_capacity=1 max_open=0 + max_active=0 } # Zoned device with mostly sequential zones, ZCAP < ZSIZE, unlimited MaxOpen. @@ -149,6 +163,7 @@ section5() zone_size=4 zone_capacity=3 max_open=0 + max_active=0 } # Zoned device with mostly conventional zones, ZCAP == ZSIZE, unlimited MaxOpen. @@ -158,6 +173,7 @@ section6() zone_size=1 zone_capacity=1 max_open=0 + max_active=0 } # Zoned device with mostly conventional zones, ZCAP < ZSIZE, unlimited MaxOpen. @@ -168,9 +184,11 @@ section7() zone_size=4 zone_capacity=3 max_open=0 + max_active=0 } -# Zoned device with no conventional zones, ZCAP == ZSIZE, limited MaxOpen. +# Zoned device with no conventional zones, ZCAP == ZSIZE, limited MaxOpen, +# unlimited MaxActive. section8() { dev_size=1024 @@ -179,9 +197,11 @@ section8() zone_capacity=1 max_open=${set_max_open} zbd_test_opts+=("-o ${max_open}") + max_active=0 } -# Zoned device with no conventional zones, ZCAP < ZSIZE, limited MaxOpen. +# Zoned device with no conventional zones, ZCAP < ZSIZE, limited MaxOpen, +# unlimited MaxActive. section9() { conv_pcnt=0 @@ -189,9 +209,11 @@ section9() zone_capacity=3 max_open=${set_max_open} zbd_test_opts+=("-o ${max_open}") + max_active=0 } -# Zoned device with mostly sequential zones, ZCAP == ZSIZE, limited MaxOpen. +# Zoned device with mostly sequential zones, ZCAP == ZSIZE, limited MaxOpen, +# unlimited MaxActive. section10() { conv_pcnt=10 @@ -199,9 +221,11 @@ section10() zone_capacity=1 max_open=${set_max_open} zbd_test_opts+=("-o ${max_open}") + max_active=0 } -# Zoned device with mostly sequential zones, ZCAP < ZSIZE, limited MaxOpen. +# Zoned device with mostly sequential zones, ZCAP < ZSIZE, limited MaxOpen, +# unlimited MaxActive. section11() { conv_pcnt=10 @@ -209,9 +233,11 @@ section11() zone_capacity=3 max_open=${set_max_open} zbd_test_opts+=("-o ${max_open}") + max_active=0 } -# Zoned device with mostly conventional zones, ZCAP == ZSIZE, limited MaxOpen. +# Zoned device with mostly conventional zones, ZCAP == ZSIZE, limited MaxOpen, +# unlimited MaxActive. section12() { conv_pcnt=66 @@ -219,9 +245,11 @@ section12() zone_capacity=1 max_open=${set_max_open} zbd_test_opts+=("-o ${max_open}") + max_active=0 } -# Zoned device with mostly conventional zones, ZCAP < ZSIZE, limited MaxOpen. +# Zoned device with mostly conventional zones, ZCAP < ZSIZE, limited MaxOpen, +# unlimited MaxActive. section13() { dev_size=2048 @@ -230,6 +258,155 @@ section13() zone_capacity=3 max_open=${set_max_open} zbd_test_opts+=("-o ${max_open}") + max_active=0 +} + +# Zoned device with no conventional zones, ZCAP == ZSIZE, limited MaxOpen, +# MaxActive == MaxOpen. +section14() +{ + dev_size=1024 + conv_pcnt=0 + zone_size=1 + zone_capacity=1 + max_open=${set_max_open} + zbd_test_opts+=("-o ${max_open}") + max_active=${set_max_open} +} + +# Zoned device with no conventional zones, ZCAP < ZSIZE, limited MaxOpen, +# MaxActive == MaxOpen. +section15() +{ + conv_pcnt=0 + zone_size=4 + zone_capacity=3 + max_open=${set_max_open} + zbd_test_opts+=("-o ${max_open}") + max_active=${set_max_open} +} + +# Zoned device with mostly sequential zones, ZCAP == ZSIZE, limited MaxOpen, +# MaxActive == MaxOpen. +section16() +{ + conv_pcnt=10 + zone_size=1 + zone_capacity=1 + max_open=${set_max_open} + zbd_test_opts+=("-o ${max_open}") + max_active=${set_max_open} +} + +# Zoned device with mostly sequential zones, ZCAP < ZSIZE, limited MaxOpen, +# MaxActive == MaxOpen. +section17() +{ + conv_pcnt=10 + zone_size=4 + zone_capacity=3 + max_open=${set_max_open} + zbd_test_opts+=("-o ${max_open}") + max_active=${set_max_open} +} + +# Zoned device with mostly conventional zones, ZCAP == ZSIZE, limited MaxOpen, +# MaxActive == MaxOpen. +section18() +{ + conv_pcnt=66 + zone_size=1 + zone_capacity=1 + max_open=${set_max_open} + zbd_test_opts+=("-o ${max_open}") + max_active=${set_max_open} +} + +# Zoned device with mostly conventional zones, ZCAP < ZSIZE, limited MaxOpen, +# MaxActive == MaxOpen. +section19() +{ + dev_size=2048 + conv_pcnt=66 + zone_size=4 + zone_capacity=3 + max_open=${set_max_open} + zbd_test_opts+=("-o ${max_open}") + max_active=${set_max_open} +} + +# Zoned device with no conventional zones, ZCAP == ZSIZE, limited MaxOpen, +# MaxActive > MaxOpen. +section20() +{ + dev_size=1024 + conv_pcnt=0 + zone_size=1 + zone_capacity=1 + max_open=${set_max_open} + zbd_test_opts+=("-o ${max_open}") + max_active=$((set_max_open+set_extra_max_active)) +} + +# Zoned device with no conventional zones, ZCAP < ZSIZE, limited MaxOpen, +# MaxActive > MaxOpen. +section21() +{ + conv_pcnt=0 + zone_size=4 + zone_capacity=3 + max_open=${set_max_open} + zbd_test_opts+=("-o ${max_open}") + max_active=$((set_max_open+set_extra_max_active)) +} + +# Zoned device with mostly sequential zones, ZCAP == ZSIZE, limited MaxOpen, +# MaxActive > MaxOpen. +section22() +{ + conv_pcnt=10 + zone_size=1 + zone_capacity=1 + max_open=${set_max_open} + zbd_test_opts+=("-o ${max_open}") + max_active=$((set_max_open+set_extra_max_active)) +} + +# Zoned device with mostly sequential zones, ZCAP < ZSIZE, limited MaxOpen, +# MaxActive > MaxOpen. +section23() +{ + conv_pcnt=10 + zone_size=4 + zone_capacity=3 + max_open=${set_max_open} + zbd_test_opts+=("-o ${max_open}") + max_active=$((set_max_open+set_extra_max_active)) +} + +# Zoned device with mostly conventional zones, ZCAP == ZSIZE, limited MaxOpen, +# MaxActive > MaxOpen. +section24() +{ + conv_pcnt=66 + zone_size=1 + zone_capacity=1 + max_open=${set_max_open} + zbd_test_opts+=("-o ${max_open}") + max_active=$((set_max_open+set_extra_max_active)) +} + +# Zoned device with mostly conventional zones, ZCAP < ZSIZE, limited MaxOpen, +# MaxActive > MaxOpen. +section25() +{ + dev_size=2048 + conv_pcnt=66 + zone_size=4 + zone_capacity=3 + max_open=${set_max_open} + zbd_test_opts+=("-o ${max_open}") + max_active=$((set_max_open+set_extra_max_active)) } # @@ -240,10 +417,12 @@ scriptdir="$(cd "$(dirname "$0")" && pwd)" sections=() zcap_supported=1 conv_supported=1 +max_act_supported=1 list_only=0 dev_size=1024 dev_blocksize=4096 set_max_open=8 +set_extra_max_active=2 zbd_test_opts=() num_of_runs=1 test_case=0 @@ -283,6 +462,9 @@ fi if ! cat /sys/kernel/config/nullb/features | grep -q zone_nr_conv; then conv_supported=0 fi +if ! cat /sys/kernel/config/nullb/features | grep -q zone_max_active; then + max_act_supported=0 +fi rc=0 test_rc=0 From fc37e3dedee9026a4e745c7ab03ed293cb04eb60 Mon Sep 17 00:00:00 2001 From: Shin'ichiro Kawasaki Date: Fri, 21 Jul 2023 13:44:44 +0900 Subject: [PATCH 0562/1097] backend: clear IO_U_F_FLIGHT flag in zero byte read path When read io_u completes with zero byte read, it sets EIO as the error and put the io_u. However, it does not clear the IO_U_F_FLIGHT flag. When fio runs with --ignore_error=EIO option, the io_u with the flag is reused for next I/O and causes an assertion failure: fio: ioengines.c:335: td_io_queue: Assertion `(io_u->flags & IO_U_F_FLIGHT) == 0' failed. The failure is observed with blktests test case block/011 which runs fio with the --ignore_error=EIO option [1]. [1] https://github.com/osandov/blktests/issues/29 Fix this by calling clear_io_u() instead of put_io_u() in the zero byte read path. clear_io_u() clears the IO_U_F_FLIGHT flag then calls put_io_u(). Signed-off-by: Shin'ichiro Kawasaki Link: https://lore.kernel.org/r/20230721044444.749537-1-shinichiro.kawasaki@wdc.com Signed-off-by: Jens Axboe --- backend.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend.c b/backend.c index b06a11a562..cb5844dba2 100644 --- a/backend.c +++ b/backend.c @@ -466,7 +466,7 @@ int io_queue_event(struct thread_data *td, struct io_u *io_u, int *ret, if (!from_verify) unlog_io_piece(td, io_u); td_verror(td, EIO, "full resid"); - put_io_u(td, io_u); + clear_io_u(td, io_u); break; } From d4d503a557d4a664ff3fd7a811966df038d06634 Mon Sep 17 00:00:00 2001 From: Damien Le Moal Date: Fri, 21 Jul 2023 20:05:05 +0900 Subject: [PATCH 0563/1097] os-linux: Cleanup IO priority class and value macros In os/os-linux.h, define the ioprio() macro using the already defined IOPRIO_MAX_PRIO macro instead of hard coding the maximum priority value again. Also move the definitions of the ioprio_class() and ioprio() macros before the ioprio_value() function and use ioprio_class() inside ioprio_value_is_class_rt() instead of re-coding the iopriority class extraction again in that function. Signed-off-by: Damien Le Moal Reviewed-by: Niklas Cassel Link: https://lore.kernel.org/r/20230721110510.44772-2-dlemoal@kernel.org Signed-off-by: Jens Axboe --- os/os-linux.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/os/os-linux.h b/os/os-linux.h index 2f9f7e796d..72727ac3b6 100644 --- a/os/os-linux.h +++ b/os/os-linux.h @@ -131,6 +131,9 @@ enum { #define IOPRIO_MIN_PRIO_CLASS 0 #define IOPRIO_MAX_PRIO_CLASS 3 +#define ioprio_class(ioprio) ((ioprio) >> IOPRIO_CLASS_SHIFT) +#define ioprio(ioprio) ((ioprio) & IOPRIO_MAX_PRIO) + static inline int ioprio_value(int ioprio_class, int ioprio) { /* @@ -144,7 +147,7 @@ static inline int ioprio_value(int ioprio_class, int ioprio) static inline bool ioprio_value_is_class_rt(unsigned int priority) { - return (priority >> IOPRIO_CLASS_SHIFT) == IOPRIO_CLASS_RT; + return ioprio_class(priority) == IOPRIO_CLASS_RT; } static inline int ioprio_set(int which, int who, int ioprio_class, int ioprio) @@ -153,9 +156,6 @@ static inline int ioprio_set(int which, int who, int ioprio_class, int ioprio) ioprio_value(ioprio_class, ioprio)); } -#define ioprio_class(ioprio) ((ioprio) >> IOPRIO_CLASS_SHIFT) -#define ioprio(ioprio) ((ioprio) & 7) - #ifndef CONFIG_HAVE_GETTID static inline int gettid(void) { From 2838f77aa80605c13cd18ce5e941005a42cec4a4 Mon Sep 17 00:00:00 2001 From: Damien Le Moal Date: Fri, 21 Jul 2023 20:05:06 +0900 Subject: [PATCH 0564/1097] cmdprio: Introduce generic option definitions The definition of the per-I/O priority options for the io_uring and libaio I/O engines are almost identical, differing only by the option group and option data structure used. Introduce the CMDPRIO_OPTIONS macro in engines/cmdprio.h to generically define these options in the io_uring and libaio engines to simplify the code. Signed-off-by: Damien Le Moal Reviewed-by: Niklas Cassel Link: https://lore.kernel.org/r/20230721110510.44772-3-dlemoal@kernel.org Signed-off-by: Jens Axboe --- engines/cmdprio.h | 84 ++++++++++++++++++++++++++++++++++++++++++++++ engines/io_uring.c | 82 +------------------------------------------- engines/libaio.c | 82 +------------------------------------------- 3 files changed, 86 insertions(+), 162 deletions(-) diff --git a/engines/cmdprio.h b/engines/cmdprio.h index 755da8d0f8..2c9d87bc2f 100644 --- a/engines/cmdprio.h +++ b/engines/cmdprio.h @@ -7,6 +7,7 @@ #define FIO_CMDPRIO_H #include "../fio.h" +#include "../optgroup.h" /* read and writes only, no trim */ #define CMDPRIO_RWDIR_CNT 2 @@ -42,6 +43,89 @@ struct cmdprio_options { char *bssplit_str; }; +#ifdef FIO_HAVE_IOPRIO_CLASS +#define CMDPRIO_OPTIONS(opt_struct, opt_group) \ + { \ + .name = "cmdprio_percentage", \ + .lname = "high priority percentage", \ + .type = FIO_OPT_INT, \ + .off1 = offsetof(opt_struct, \ + cmdprio_options.percentage[DDIR_READ]), \ + .off2 = offsetof(opt_struct, \ + cmdprio_options.percentage[DDIR_WRITE]), \ + .minval = 0, \ + .maxval = 100, \ + .help = "Send high priority I/O this percentage of the time", \ + .category = FIO_OPT_C_ENGINE, \ + .group = opt_group, \ + }, \ + { \ + .name = "cmdprio_class", \ + .lname = "Asynchronous I/O priority class", \ + .type = FIO_OPT_INT, \ + .off1 = offsetof(opt_struct, \ + cmdprio_options.class[DDIR_READ]), \ + .off2 = offsetof(opt_struct, \ + cmdprio_options.class[DDIR_WRITE]), \ + .help = "Set asynchronous IO priority class", \ + .minval = IOPRIO_MIN_PRIO_CLASS + 1, \ + .maxval = IOPRIO_MAX_PRIO_CLASS, \ + .interval = 1, \ + .category = FIO_OPT_C_ENGINE, \ + .group = opt_group, \ + }, \ + { \ + .name = "cmdprio", \ + .lname = "Asynchronous I/O priority level", \ + .type = FIO_OPT_INT, \ + .off1 = offsetof(opt_struct, \ + cmdprio_options.level[DDIR_READ]), \ + .off2 = offsetof(opt_struct, \ + cmdprio_options.level[DDIR_WRITE]), \ + .help = "Set asynchronous IO priority level", \ + .minval = IOPRIO_MIN_PRIO, \ + .maxval = IOPRIO_MAX_PRIO, \ + .interval = 1, \ + .category = FIO_OPT_C_ENGINE, \ + .group = opt_group, \ + }, \ + { \ + .name = "cmdprio_bssplit", \ + .lname = "Priority percentage block size split", \ + .type = FIO_OPT_STR_STORE, \ + .off1 = offsetof(opt_struct, cmdprio_options.bssplit_str), \ + .help = "Set priority percentages for different block sizes", \ + .category = FIO_OPT_C_ENGINE, \ + .group = opt_group, \ + } +#else +#define CMDPRIO_OPTIONS(opt_struct, opt_group) \ + { \ + .name = "cmdprio_percentage", \ + .lname = "high priority percentage", \ + .type = FIO_OPT_UNSUPPORTED, \ + .help = "Platform does not support I/O priority classes", \ + }, \ + { \ + .name = "cmdprio_class", \ + .lname = "Asynchronous I/O priority class", \ + .type = FIO_OPT_UNSUPPORTED, \ + .help = "Platform does not support I/O priority classes", \ + }, \ + { \ + .name = "cmdprio", \ + .lname = "Asynchronous I/O priority level", \ + .type = FIO_OPT_UNSUPPORTED, \ + .help = "Platform does not support I/O priority classes", \ + }, \ + { \ + .name = "cmdprio_bssplit", \ + .lname = "Priority percentage block size split", \ + .type = FIO_OPT_UNSUPPORTED, \ + .help = "Platform does not support I/O priority classes", \ + } +#endif + struct cmdprio { struct cmdprio_options *options; struct cmdprio_prio perc_entry[CMDPRIO_RWDIR_CNT]; diff --git a/engines/io_uring.c b/engines/io_uring.c index f30a3c00ce..5613c4c691 100644 --- a/engines/io_uring.c +++ b/engines/io_uring.c @@ -127,87 +127,6 @@ static struct fio_option options[] = { .category = FIO_OPT_C_ENGINE, .group = FIO_OPT_G_IOURING, }, -#ifdef FIO_HAVE_IOPRIO_CLASS - { - .name = "cmdprio_percentage", - .lname = "high priority percentage", - .type = FIO_OPT_INT, - .off1 = offsetof(struct ioring_options, - cmdprio_options.percentage[DDIR_READ]), - .off2 = offsetof(struct ioring_options, - cmdprio_options.percentage[DDIR_WRITE]), - .minval = 0, - .maxval = 100, - .help = "Send high priority I/O this percentage of the time", - .category = FIO_OPT_C_ENGINE, - .group = FIO_OPT_G_IOURING, - }, - { - .name = "cmdprio_class", - .lname = "Asynchronous I/O priority class", - .type = FIO_OPT_INT, - .off1 = offsetof(struct ioring_options, - cmdprio_options.class[DDIR_READ]), - .off2 = offsetof(struct ioring_options, - cmdprio_options.class[DDIR_WRITE]), - .help = "Set asynchronous IO priority class", - .minval = IOPRIO_MIN_PRIO_CLASS + 1, - .maxval = IOPRIO_MAX_PRIO_CLASS, - .interval = 1, - .category = FIO_OPT_C_ENGINE, - .group = FIO_OPT_G_IOURING, - }, - { - .name = "cmdprio", - .lname = "Asynchronous I/O priority level", - .type = FIO_OPT_INT, - .off1 = offsetof(struct ioring_options, - cmdprio_options.level[DDIR_READ]), - .off2 = offsetof(struct ioring_options, - cmdprio_options.level[DDIR_WRITE]), - .help = "Set asynchronous IO priority level", - .minval = IOPRIO_MIN_PRIO, - .maxval = IOPRIO_MAX_PRIO, - .interval = 1, - .category = FIO_OPT_C_ENGINE, - .group = FIO_OPT_G_IOURING, - }, - { - .name = "cmdprio_bssplit", - .lname = "Priority percentage block size split", - .type = FIO_OPT_STR_STORE, - .off1 = offsetof(struct ioring_options, - cmdprio_options.bssplit_str), - .help = "Set priority percentages for different block sizes", - .category = FIO_OPT_C_ENGINE, - .group = FIO_OPT_G_IOURING, - }, -#else - { - .name = "cmdprio_percentage", - .lname = "high priority percentage", - .type = FIO_OPT_UNSUPPORTED, - .help = "Your platform does not support I/O priority classes", - }, - { - .name = "cmdprio_class", - .lname = "Asynchronous I/O priority class", - .type = FIO_OPT_UNSUPPORTED, - .help = "Your platform does not support I/O priority classes", - }, - { - .name = "cmdprio", - .lname = "Asynchronous I/O priority level", - .type = FIO_OPT_UNSUPPORTED, - .help = "Your platform does not support I/O priority classes", - }, - { - .name = "cmdprio_bssplit", - .lname = "Priority percentage block size split", - .type = FIO_OPT_UNSUPPORTED, - .help = "Your platform does not support I/O priority classes", - }, -#endif { .name = "fixedbufs", .lname = "Fixed (pre-mapped) IO buffers", @@ -297,6 +216,7 @@ static struct fio_option options[] = { .category = FIO_OPT_C_ENGINE, .group = FIO_OPT_G_IOURING, }, + CMDPRIO_OPTIONS(struct ioring_options, FIO_OPT_G_IOURING), { .name = NULL, }, diff --git a/engines/libaio.c b/engines/libaio.c index 6a0745aad6..aaccc7ce09 100644 --- a/engines/libaio.c +++ b/engines/libaio.c @@ -72,87 +72,6 @@ static struct fio_option options[] = { .category = FIO_OPT_C_ENGINE, .group = FIO_OPT_G_LIBAIO, }, -#ifdef FIO_HAVE_IOPRIO_CLASS - { - .name = "cmdprio_percentage", - .lname = "high priority percentage", - .type = FIO_OPT_INT, - .off1 = offsetof(struct libaio_options, - cmdprio_options.percentage[DDIR_READ]), - .off2 = offsetof(struct libaio_options, - cmdprio_options.percentage[DDIR_WRITE]), - .minval = 0, - .maxval = 100, - .help = "Send high priority I/O this percentage of the time", - .category = FIO_OPT_C_ENGINE, - .group = FIO_OPT_G_LIBAIO, - }, - { - .name = "cmdprio_class", - .lname = "Asynchronous I/O priority class", - .type = FIO_OPT_INT, - .off1 = offsetof(struct libaio_options, - cmdprio_options.class[DDIR_READ]), - .off2 = offsetof(struct libaio_options, - cmdprio_options.class[DDIR_WRITE]), - .help = "Set asynchronous IO priority class", - .minval = IOPRIO_MIN_PRIO_CLASS + 1, - .maxval = IOPRIO_MAX_PRIO_CLASS, - .interval = 1, - .category = FIO_OPT_C_ENGINE, - .group = FIO_OPT_G_LIBAIO, - }, - { - .name = "cmdprio", - .lname = "Asynchronous I/O priority level", - .type = FIO_OPT_INT, - .off1 = offsetof(struct libaio_options, - cmdprio_options.level[DDIR_READ]), - .off2 = offsetof(struct libaio_options, - cmdprio_options.level[DDIR_WRITE]), - .help = "Set asynchronous IO priority level", - .minval = IOPRIO_MIN_PRIO, - .maxval = IOPRIO_MAX_PRIO, - .interval = 1, - .category = FIO_OPT_C_ENGINE, - .group = FIO_OPT_G_LIBAIO, - }, - { - .name = "cmdprio_bssplit", - .lname = "Priority percentage block size split", - .type = FIO_OPT_STR_STORE, - .off1 = offsetof(struct libaio_options, - cmdprio_options.bssplit_str), - .help = "Set priority percentages for different block sizes", - .category = FIO_OPT_C_ENGINE, - .group = FIO_OPT_G_LIBAIO, - }, -#else - { - .name = "cmdprio_percentage", - .lname = "high priority percentage", - .type = FIO_OPT_UNSUPPORTED, - .help = "Your platform does not support I/O priority classes", - }, - { - .name = "cmdprio_class", - .lname = "Asynchronous I/O priority class", - .type = FIO_OPT_UNSUPPORTED, - .help = "Your platform does not support I/O priority classes", - }, - { - .name = "cmdprio", - .lname = "Asynchronous I/O priority level", - .type = FIO_OPT_UNSUPPORTED, - .help = "Your platform does not support I/O priority classes", - }, - { - .name = "cmdprio_bssplit", - .lname = "Priority percentage block size split", - .type = FIO_OPT_UNSUPPORTED, - .help = "Your platform does not support I/O priority classes", - }, -#endif { .name = "nowait", .lname = "RWF_NOWAIT", @@ -162,6 +81,7 @@ static struct fio_option options[] = { .category = FIO_OPT_C_ENGINE, .group = FIO_OPT_G_LIBAIO, }, + CMDPRIO_OPTIONS(struct libaio_options, FIO_OPT_G_LIBAIO), { .name = NULL, }, From b3048c1b564fc793ee11004a17b2a4de5a82be27 Mon Sep 17 00:00:00 2001 From: Damien Le Moal Date: Fri, 21 Jul 2023 20:05:07 +0900 Subject: [PATCH 0565/1097] os-linux: add initial support for IO priority hints Add initial support for Linux to allow specifying a hint for any priority value. With this change, a priority value becomes the combination of a priority class, a priority level and a hint. The generic os.h ioprio manipulation macros, as well as the os-dragonfly.h ioprio manipulation macros are modified to ignore this hint. For all other OSes that do not support priority classes, priotity hints are ignored and always equal to 0. Signed-off-by: Damien Le Moal Reviewed-by: Niklas Cassel Link: https://lore.kernel.org/r/20230721110510.44772-4-dlemoal@kernel.org Signed-off-by: Jens Axboe --- backend.c | 5 +++-- engines/cmdprio.c | 4 ++-- options.c | 2 +- os/os-dragonfly.h | 4 ++-- os/os-linux.h | 19 +++++++++++++++---- os/os.h | 7 +++++-- 6 files changed, 28 insertions(+), 13 deletions(-) diff --git a/backend.c b/backend.c index cb5844dba2..7e1c37733a 100644 --- a/backend.c +++ b/backend.c @@ -1800,12 +1800,13 @@ static void *thread_main(void *data) /* ioprio_set() has to be done before td_io_init() */ if (fio_option_is_set(o, ioprio) || fio_option_is_set(o, ioprio_class)) { - ret = ioprio_set(IOPRIO_WHO_PROCESS, 0, o->ioprio_class, o->ioprio); + ret = ioprio_set(IOPRIO_WHO_PROCESS, 0, o->ioprio_class, + o->ioprio, 0); if (ret == -1) { td_verror(td, errno, "ioprio_set"); goto err; } - td->ioprio = ioprio_value(o->ioprio_class, o->ioprio); + td->ioprio = ioprio_value(o->ioprio_class, o->ioprio, 0); td->ts.ioprio = td->ioprio; } diff --git a/engines/cmdprio.c b/engines/cmdprio.c index 979a81b6c3..e6ff1fc2f1 100644 --- a/engines/cmdprio.c +++ b/engines/cmdprio.c @@ -342,7 +342,7 @@ static int fio_cmdprio_gen_perc(struct thread_data *td, struct cmdprio *cmdprio) prio = &cmdprio->perc_entry[ddir]; prio->perc = options->percentage[ddir]; prio->prio = ioprio_value(options->class[ddir], - options->level[ddir]); + options->level[ddir], 0); assign_clat_prio_index(prio, &values[ddir]); ret = init_ts_clat_prio(ts, ddir, &values[ddir]); @@ -400,7 +400,7 @@ static int fio_cmdprio_parse_and_gen_bssplit(struct thread_data *td, goto err; implicit_cmdprio = ioprio_value(options->class[ddir], - options->level[ddir]); + options->level[ddir], 0); ret = fio_cmdprio_generate_bsprio_desc(&cmdprio->bsprio_desc[ddir], &parse_res[ddir], diff --git a/options.c b/options.c index 0f73931724..143d358384 100644 --- a/options.c +++ b/options.c @@ -344,7 +344,7 @@ static int parse_cmdprio_bssplit_entry(struct thread_options *o, case 4: /* bs/perc/class/level case */ class = min(class, (unsigned int) IOPRIO_MAX_PRIO_CLASS); level = min(level, (unsigned int) IOPRIO_MAX_PRIO); - entry->prio = ioprio_value(class, level); + entry->prio = ioprio_value(class, level, 0); break; default: log_err("fio: invalid cmdprio_bssplit format\n"); diff --git a/os/os-dragonfly.h b/os/os-dragonfly.h index bde39101b5..4ce7253956 100644 --- a/os/os-dragonfly.h +++ b/os/os-dragonfly.h @@ -171,8 +171,8 @@ static inline int fio_getaffinity(int pid, os_cpu_mask_t *mask) * ioprio_set() with 4 arguments, so define fio's ioprio_set() as a macro. * Note that there is no idea of class within ioprio_set(2) unlike Linux. */ -#define ioprio_value(ioprio_class, ioprio) (ioprio) -#define ioprio_set(which, who, ioprio_class, ioprio) \ +#define ioprio_value(ioprio_class, ioprio, ioprio_hint) (ioprio) +#define ioprio_set(which, who, ioprio_class, ioprio, ioprio_hint) \ ioprio_set(which, who, ioprio) #define ioprio(ioprio) (ioprio) diff --git a/os/os-linux.h b/os/os-linux.h index 72727ac3b6..c5cd651581 100644 --- a/os/os-linux.h +++ b/os/os-linux.h @@ -125,16 +125,24 @@ enum { #define IOPRIO_BITS 16 #define IOPRIO_CLASS_SHIFT 13 +#define IOPRIO_HINT_BITS 10 +#define IOPRIO_HINT_SHIFT 3 + #define IOPRIO_MIN_PRIO 0 /* highest priority */ #define IOPRIO_MAX_PRIO 7 /* lowest priority */ #define IOPRIO_MIN_PRIO_CLASS 0 #define IOPRIO_MAX_PRIO_CLASS 3 +#define IOPRIO_MIN_PRIO_HINT 0 +#define IOPRIO_MAX_PRIO_HINT ((1 << IOPRIO_HINT_BITS) - 1) + #define ioprio_class(ioprio) ((ioprio) >> IOPRIO_CLASS_SHIFT) #define ioprio(ioprio) ((ioprio) & IOPRIO_MAX_PRIO) +#define ioprio_hint(ioprio) \ + (((ioprio) >> IOPRIO_HINT_SHIFT) & IOPRIO_MAX_PRIO_HINT) -static inline int ioprio_value(int ioprio_class, int ioprio) +static inline int ioprio_value(int ioprio_class, int ioprio, int ioprio_hint) { /* * If no class is set, assume BE @@ -142,7 +150,9 @@ static inline int ioprio_value(int ioprio_class, int ioprio) if (!ioprio_class) ioprio_class = IOPRIO_CLASS_BE; - return (ioprio_class << IOPRIO_CLASS_SHIFT) | ioprio; + return (ioprio_class << IOPRIO_CLASS_SHIFT) | + (ioprio_hint << IOPRIO_HINT_SHIFT) | + ioprio; } static inline bool ioprio_value_is_class_rt(unsigned int priority) @@ -150,10 +160,11 @@ static inline bool ioprio_value_is_class_rt(unsigned int priority) return ioprio_class(priority) == IOPRIO_CLASS_RT; } -static inline int ioprio_set(int which, int who, int ioprio_class, int ioprio) +static inline int ioprio_set(int which, int who, int ioprio_class, int ioprio, + int ioprio_hint) { return syscall(__NR_ioprio_set, which, who, - ioprio_value(ioprio_class, ioprio)); + ioprio_value(ioprio_class, ioprio, ioprio_hint)); } #ifndef CONFIG_HAVE_GETTID diff --git a/os/os.h b/os/os.h index 036fc233fe..0f1823240f 100644 --- a/os/os.h +++ b/os/os.h @@ -120,11 +120,14 @@ extern int fio_cpus_split(os_cpu_mask_t *mask, unsigned int cpu); #define ioprio_value_is_class_rt(prio) (false) #define IOPRIO_MIN_PRIO_CLASS 0 #define IOPRIO_MAX_PRIO_CLASS 0 +#define ioprio_hint(prio) 0 +#define IOPRIO_MIN_PRIO_HINT 0 +#define IOPRIO_MAX_PRIO_HINT 0 #endif #ifndef FIO_HAVE_IOPRIO -#define ioprio_value(prioclass, prio) (0) +#define ioprio_value(prioclass, prio, priohint) (0) #define ioprio(ioprio) 0 -#define ioprio_set(which, who, prioclass, prio) (0) +#define ioprio_set(which, who, prioclass, prio, priohint) (0) #define IOPRIO_MIN_PRIO 0 #define IOPRIO_MAX_PRIO 0 #endif From 860462dad312ef121766f5d17b1df7b05d1f47ee Mon Sep 17 00:00:00 2001 From: Damien Le Moal Date: Fri, 21 Jul 2023 20:05:08 +0900 Subject: [PATCH 0566/1097] options: add priohint option Introduce the new option priohint to allow users to specify an I/O priority hint applying to all IOs issued by a job. This increases fio server version (FIO_SERVER_VER) to 101. Signed-off-by: Damien Le Moal Reviewed-by: Niklas Cassel Link: https://lore.kernel.org/r/20230721110510.44772-5-dlemoal@kernel.org Signed-off-by: Jens Axboe --- HOWTO.rst | 9 +++++++++ backend.c | 8 +++++--- cconv.c | 2 ++ fio.1 | 8 ++++++++ options.c | 18 ++++++++++++++++++ server.h | 2 +- thread_options.h | 3 ++- 7 files changed, 45 insertions(+), 5 deletions(-) diff --git a/HOWTO.rst b/HOWTO.rst index 7fe70fbdd0..d1a476e494 100644 --- a/HOWTO.rst +++ b/HOWTO.rst @@ -3436,6 +3436,15 @@ Threads, processes and job synchronization priority setting, see I/O engine specific :option:`cmdprio_percentage` and :option:`cmdprio_class` options. +.. option:: priohint=int + + Set the I/O priority hint. This is only applicable to platforms that + support I/O priority classes and to devices with features controlled + through priority hints, e.g. block devices supporting command duration + limits, or CDL. CDL is a way to indicate the desired maximum latency + of I/Os so that the device can optimize its internal command scheduling + according to the latency limits indicated by the user. + .. option:: cpus_allowed=str Controls the same options as :option:`cpumask`, but accepts a textual diff --git a/backend.c b/backend.c index 7e1c37733a..5f0740395b 100644 --- a/backend.c +++ b/backend.c @@ -1799,14 +1799,16 @@ static void *thread_main(void *data) /* ioprio_set() has to be done before td_io_init() */ if (fio_option_is_set(o, ioprio) || - fio_option_is_set(o, ioprio_class)) { + fio_option_is_set(o, ioprio_class) || + fio_option_is_set(o, ioprio_hint)) { ret = ioprio_set(IOPRIO_WHO_PROCESS, 0, o->ioprio_class, - o->ioprio, 0); + o->ioprio, o->ioprio_hint); if (ret == -1) { td_verror(td, errno, "ioprio_set"); goto err; } - td->ioprio = ioprio_value(o->ioprio_class, o->ioprio, 0); + td->ioprio = ioprio_value(o->ioprio_class, o->ioprio, + o->ioprio_hint); td->ts.ioprio = td->ioprio; } diff --git a/cconv.c b/cconv.c index 1bfa770f61..ce6acbe6ab 100644 --- a/cconv.c +++ b/cconv.c @@ -281,6 +281,7 @@ int convert_thread_options_to_cpu(struct thread_options *o, o->nice = le32_to_cpu(top->nice); o->ioprio = le32_to_cpu(top->ioprio); o->ioprio_class = le32_to_cpu(top->ioprio_class); + o->ioprio_hint = le32_to_cpu(top->ioprio_hint); o->file_service_type = le32_to_cpu(top->file_service_type); o->group_reporting = le32_to_cpu(top->group_reporting); o->stats = le32_to_cpu(top->stats); @@ -496,6 +497,7 @@ void convert_thread_options_to_net(struct thread_options_pack *top, top->nice = cpu_to_le32(o->nice); top->ioprio = cpu_to_le32(o->ioprio); top->ioprio_class = cpu_to_le32(o->ioprio_class); + top->ioprio_hint = cpu_to_le32(o->ioprio_hint); top->file_service_type = cpu_to_le32(o->file_service_type); top->group_reporting = cpu_to_le32(o->group_reporting); top->stats = cpu_to_le32(o->stats); diff --git a/fio.1 b/fio.1 index 20acd081c3..e2a363272c 100644 --- a/fio.1 +++ b/fio.1 @@ -3144,6 +3144,14 @@ Set the I/O priority class. See man \fBionice\fR\|(1). For per-command priority setting, see the I/O engine specific `cmdprio_percentage` and `cmdprio_class` options. .TP +.BI priohint \fR=\fPint +Set the I/O priority hint. This is only applicable to platforms that support +I/O priority classes and to devices with features controlled through priority +hints, e.g. block devices supporting command duration limits, or CDL. CDL is a +way to indicate the desired maximum latency of I/Os so that the device can +optimize its internal command scheduling according to the latency limits +indicated by the user. +.TP .BI cpus_allowed \fR=\fPstr Controls the same options as \fBcpumask\fR, but accepts a textual specification of the permitted CPUs instead and CPUs are indexed from 0. So diff --git a/options.c b/options.c index 143d358384..56672960a7 100644 --- a/options.c +++ b/options.c @@ -3806,6 +3806,18 @@ struct fio_option fio_options[FIO_MAX_OPTS] = { .category = FIO_OPT_C_GENERAL, .group = FIO_OPT_G_CRED, }, + { + .name = "priohint", + .lname = "I/O nice priority hint", + .type = FIO_OPT_INT, + .off1 = offsetof(struct thread_options, ioprio_hint), + .help = "Set job IO priority hint", + .minval = IOPRIO_MIN_PRIO_HINT, + .maxval = IOPRIO_MAX_PRIO_HINT, + .interval = 1, + .category = FIO_OPT_C_GENERAL, + .group = FIO_OPT_G_CRED, + }, #else { .name = "prioclass", @@ -3813,6 +3825,12 @@ struct fio_option fio_options[FIO_MAX_OPTS] = { .type = FIO_OPT_UNSUPPORTED, .help = "Your platform does not support IO priority classes", }, + { + .name = "priohint", + .lname = "I/O nice priority hint", + .type = FIO_OPT_UNSUPPORTED, + .help = "Your platform does not support IO priority hints", + }, #endif { .name = "thinktime", diff --git a/server.h b/server.h index 601d334043..ad7061184b 100644 --- a/server.h +++ b/server.h @@ -51,7 +51,7 @@ struct fio_net_cmd_reply { }; enum { - FIO_SERVER_VER = 100, + FIO_SERVER_VER = 101, FIO_SERVER_MAX_FRAGMENT_PDU = 1024, FIO_SERVER_MAX_CMD_MB = 2048, diff --git a/thread_options.h b/thread_options.h index 1715b36c67..38a9993d23 100644 --- a/thread_options.h +++ b/thread_options.h @@ -248,6 +248,7 @@ struct thread_options { unsigned int nice; unsigned int ioprio; unsigned int ioprio_class; + unsigned int ioprio_hint; unsigned int file_service_type; unsigned int group_reporting; unsigned int stats; @@ -568,6 +569,7 @@ struct thread_options_pack { uint32_t nice; uint32_t ioprio; uint32_t ioprio_class; + uint32_t ioprio_hint; uint32_t file_service_type; uint32_t group_reporting; uint32_t stats; @@ -601,7 +603,6 @@ struct thread_options_pack { uint32_t lat_percentiles; uint32_t slat_percentiles; uint32_t percentile_precision; - uint32_t pad5; fio_fp64_t percentile_list[FIO_IO_U_LIST_MAX_LEN]; uint8_t read_iolog_file[FIO_TOP_STR_MAX]; From 79012fece0a0ecb02ad6a3322913980efdb1d726 Mon Sep 17 00:00:00 2001 From: Damien Le Moal Date: Fri, 21 Jul 2023 20:05:09 +0900 Subject: [PATCH 0567/1097] cmdprio: Add support for per I/O priority hint Introduce the new option cmdprio_hint to allow specifying I/O priority hints per IO with the io_uring and libaio IO engines. A third acceptable format for the cmdprio_bssplit option is also introduced to allow specifying an I/O hint in addition to a priority class and level. Signed-off-by: Damien Le Moal Reviewed-by: Niklas Cassel Link: https://lore.kernel.org/r/20230721110510.44772-6-dlemoal@kernel.org Signed-off-by: Jens Axboe --- HOWTO.rst | 28 ++++++++++++++++++++++++---- engines/cmdprio.c | 9 ++++++--- engines/cmdprio.h | 22 ++++++++++++++++++++++ engines/io_uring.c | 4 ++-- fio.1 | 27 +++++++++++++++++++++++---- options.c | 13 ++++++++++--- 6 files changed, 87 insertions(+), 16 deletions(-) diff --git a/HOWTO.rst b/HOWTO.rst index d1a476e494..ac8314f353 100644 --- a/HOWTO.rst +++ b/HOWTO.rst @@ -2287,6 +2287,16 @@ with the caveat that when used on the command line, they must come after the reads and writes. See :manpage:`ionice(1)`. See also the :option:`prioclass` option. +.. option:: cmdprio_hint=int[,int] : [io_uring] [libaio] + + Set the I/O priority hint to use for I/Os that must be issued with + a priority when :option:`cmdprio_percentage` or + :option:`cmdprio_bssplit` is set. If not specified when + :option:`cmdprio_percentage` or :option:`cmdprio_bssplit` is set, + this defaults to 0 (no hint). A single value applies to reads and + writes. Comma-separated values may be specified for reads and writes. + See also the :option:`priohint` option. + .. option:: cmdprio=int[,int] : [io_uring] [libaio] Set the I/O priority value to use for I/Os that must be issued with @@ -2313,9 +2323,9 @@ with the caveat that when used on the command line, they must come after the cmdprio_bssplit=blocksize/percentage:blocksize/percentage - In this case, each entry will use the priority class and priority - level defined by the options :option:`cmdprio_class` and - :option:`cmdprio` respectively. + In this case, each entry will use the priority class, priority hint + and priority level defined by the options :option:`cmdprio_class`, + :option:`cmdprio` and :option:`cmdprio_hint` respectively. The second accepted format for this option is: @@ -2326,7 +2336,14 @@ with the caveat that when used on the command line, they must come after the accepted format does not restrict all entries to have the same priority class and priority level. - For both formats, only the read and write data directions are supported, + The third accepted format for this option is: + + cmdprio_bssplit=blocksize/percentage/class/level/hint:... + + This is an extension of the second accepted format that allows to also + specify a priority hint. + + For all formats, only the read and write data directions are supported, values for trim IOs are ignored. This option is mutually exclusive with the :option:`cmdprio_percentage` option. @@ -3445,6 +3462,9 @@ Threads, processes and job synchronization of I/Os so that the device can optimize its internal command scheduling according to the latency limits indicated by the user. + For per-I/O priority hint setting, see the I/O engine specific + :option:`cmdprio_hint` option. + .. option:: cpus_allowed=str Controls the same options as :option:`cpumask`, but accepts a textual diff --git a/engines/cmdprio.c b/engines/cmdprio.c index e6ff1fc2f1..153e36911a 100644 --- a/engines/cmdprio.c +++ b/engines/cmdprio.c @@ -267,7 +267,8 @@ static int fio_cmdprio_percentage(struct cmdprio *cmdprio, struct io_u *io_u, * to be set. If the random percentage value is within the user specified * percentage of I/Os that should use a cmdprio priority value (rather than * the default priority), then this function updates the io_u with an ioprio - * value as defined by the cmdprio/cmdprio_class or cmdprio_bssplit options. + * value as defined by the cmdprio/cmdprio_hint/cmdprio_class or + * cmdprio_bssplit options. * * Return true if the io_u ioprio was changed and false otherwise. */ @@ -342,7 +343,8 @@ static int fio_cmdprio_gen_perc(struct thread_data *td, struct cmdprio *cmdprio) prio = &cmdprio->perc_entry[ddir]; prio->perc = options->percentage[ddir]; prio->prio = ioprio_value(options->class[ddir], - options->level[ddir], 0); + options->level[ddir], + options->hint[ddir]); assign_clat_prio_index(prio, &values[ddir]); ret = init_ts_clat_prio(ts, ddir, &values[ddir]); @@ -400,7 +402,8 @@ static int fio_cmdprio_parse_and_gen_bssplit(struct thread_data *td, goto err; implicit_cmdprio = ioprio_value(options->class[ddir], - options->level[ddir], 0); + options->level[ddir], + options->hint[ddir]); ret = fio_cmdprio_generate_bsprio_desc(&cmdprio->bsprio_desc[ddir], &parse_res[ddir], diff --git a/engines/cmdprio.h b/engines/cmdprio.h index 2c9d87bc2f..81e6c390f0 100644 --- a/engines/cmdprio.h +++ b/engines/cmdprio.h @@ -40,6 +40,7 @@ struct cmdprio_options { unsigned int percentage[CMDPRIO_RWDIR_CNT]; unsigned int class[CMDPRIO_RWDIR_CNT]; unsigned int level[CMDPRIO_RWDIR_CNT]; + unsigned int hint[CMDPRIO_RWDIR_CNT]; char *bssplit_str; }; @@ -74,6 +75,21 @@ struct cmdprio_options { .category = FIO_OPT_C_ENGINE, \ .group = opt_group, \ }, \ + { \ + .name = "cmdprio_hint", \ + .lname = "Asynchronous I/O priority hint", \ + .type = FIO_OPT_INT, \ + .off1 = offsetof(opt_struct, \ + cmdprio_options.hint[DDIR_READ]), \ + .off2 = offsetof(opt_struct, \ + cmdprio_options.hint[DDIR_WRITE]), \ + .help = "Set asynchronous IO priority hint", \ + .minval = IOPRIO_MIN_PRIO_HINT, \ + .maxval = IOPRIO_MAX_PRIO_HINT, \ + .interval = 1, \ + .category = FIO_OPT_C_ENGINE, \ + .group = opt_group, \ + }, \ { \ .name = "cmdprio", \ .lname = "Asynchronous I/O priority level", \ @@ -112,6 +128,12 @@ struct cmdprio_options { .type = FIO_OPT_UNSUPPORTED, \ .help = "Platform does not support I/O priority classes", \ }, \ + { \ + .name = "cmdprio_hint", \ + .lname = "Asynchronous I/O priority hint", \ + .type = FIO_OPT_UNSUPPORTED, \ + .help = "Platform does not support I/O priority classes", \ + }, \ { \ .name = "cmdprio", \ .lname = "Asynchronous I/O priority level", \ diff --git a/engines/io_uring.c b/engines/io_uring.c index 5613c4c691..e1abf6888f 100644 --- a/engines/io_uring.c +++ b/engines/io_uring.c @@ -285,8 +285,8 @@ static int fio_ioring_prep(struct thread_data *td, struct io_u *io_u) /* * Since io_uring can have a submission context (sqthread_poll) * that is different from the process context, we cannot rely on - * the IO priority set by ioprio_set() (option prio/prioclass) - * to be inherited. + * the IO priority set by ioprio_set() (options prio, prioclass, + * and priohint) to be inherited. * td->ioprio will have the value of the "default prio", so set * this unconditionally. This value might get overridden by * fio_ioring_cmdprio_prep() if the option cmdprio_percentage or diff --git a/fio.1 b/fio.1 index e2a363272c..f62617e7f7 100644 --- a/fio.1 +++ b/fio.1 @@ -2084,6 +2084,14 @@ is set, this defaults to the highest priority class. A single value applies to reads and writes. Comma-separated values may be specified for reads and writes. See man \fBionice\fR\|(1). See also the \fBprioclass\fR option. .TP +.BI (io_uring,libaio)cmdprio_hint \fR=\fPint[,int] +Set the I/O priority hint to use for I/Os that must be issued with a +priority when \fBcmdprio_percentage\fR or \fBcmdprio_bssplit\fR is set. +If not specified when \fBcmdprio_percentage\fR or \fBcmdprio_bssplit\fR +is set, this defaults to 0 (no hint). A single value applies to reads and +writes. Comma-separated values may be specified for reads and writes. +See also the \fBpriohint\fR option. +.TP .BI (io_uring,libaio)cmdprio \fR=\fPint[,int] Set the I/O priority value to use for I/Os that must be issued with a priority when \fBcmdprio_percentage\fR or \fBcmdprio_bssplit\fR is set. @@ -2109,8 +2117,9 @@ The first accepted format for this option is the same as the format of the cmdprio_bssplit=blocksize/percentage:blocksize/percentage .RE .P -In this case, each entry will use the priority class and priority level defined -by the options \fBcmdprio_class\fR and \fBcmdprio\fR respectively. +In this case, each entry will use the priority class, priority hint and +priority level defined by the options \fBcmdprio_class\fR, \fBcmdprio\fR +and \fBcmdprio_hint\fR respectively. .P The second accepted format for this option is: .RS @@ -2123,7 +2132,16 @@ entry. In comparison with the first accepted format, the second accepted format does not restrict all entries to have the same priority class and priority level. .P -For both formats, only the read and write data directions are supported, values +The third accepted format for this option is: +.RS +.P +cmdprio_bssplit=blocksize/percentage/class/level/hint:... +.RE +.P +This is an extension of the second accepted format that allows to also +specify a priority hint. +.P +For all formats, only the read and write data directions are supported, values for trim IOs are ignored. This option is mutually exclusive with the \fBcmdprio_percentage\fR option. .RE @@ -3150,7 +3168,8 @@ I/O priority classes and to devices with features controlled through priority hints, e.g. block devices supporting command duration limits, or CDL. CDL is a way to indicate the desired maximum latency of I/Os so that the device can optimize its internal command scheduling according to the latency limits -indicated by the user. +indicated by the user. For per-I/O priority hint setting, see the I/O engine +specific \fBcmdprio_hint\fB option. .TP .BI cpus_allowed \fR=\fPstr Controls the same options as \fBcpumask\fR, but accepts a textual diff --git a/options.c b/options.c index 56672960a7..48aa0d7b1c 100644 --- a/options.c +++ b/options.c @@ -313,15 +313,17 @@ static int parse_cmdprio_bssplit_entry(struct thread_options *o, int matches = 0; char *bs_str = NULL; long long bs_val; - unsigned int perc = 0, class, level; + unsigned int perc = 0, class, level, hint; /* * valid entry formats: * bs/ - %s/ - set perc to 0, prio to -1. * bs/perc - %s/%u - set prio to -1. * bs/perc/class/level - %s/%u/%u/%u + * bs/perc/class/level/hint - %s/%u/%u/%u/%u */ - matches = sscanf(str, "%m[^/]/%u/%u/%u", &bs_str, &perc, &class, &level); + matches = sscanf(str, "%m[^/]/%u/%u/%u/%u", + &bs_str, &perc, &class, &level, &hint); if (matches < 1) { log_err("fio: invalid cmdprio_bssplit format\n"); return 1; @@ -342,9 +344,14 @@ static int parse_cmdprio_bssplit_entry(struct thread_options *o, case 2: /* bs/perc case */ break; case 4: /* bs/perc/class/level case */ + case 5: /* bs/perc/class/level/hint case */ class = min(class, (unsigned int) IOPRIO_MAX_PRIO_CLASS); level = min(level, (unsigned int) IOPRIO_MAX_PRIO); - entry->prio = ioprio_value(class, level, 0); + if (matches == 5) + hint = min(hint, (unsigned int) IOPRIO_MAX_PRIO_HINT); + else + hint = 0; + entry->prio = ioprio_value(class, level, hint); break; default: log_err("fio: invalid cmdprio_bssplit format\n"); From 219a868280716e4e221b57269ee3b8a39ea9c29b Mon Sep 17 00:00:00 2001 From: Damien Le Moal Date: Fri, 21 Jul 2023 20:05:10 +0900 Subject: [PATCH 0568/1097] stats: Add hint information to per priority level stats Modify the json and standard per-priority output stats to display the hint value together with the priority class and level. Signed-off-by: Damien Le Moal Reviewed-by: Niklas Cassel Link: https://lore.kernel.org/r/20230721110510.44772-7-dlemoal@kernel.org Signed-off-by: Jens Axboe --- stat.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/stat.c b/stat.c index 7fad73d139..7b791628f4 100644 --- a/stat.c +++ b/stat.c @@ -597,10 +597,11 @@ static void show_ddir_status(struct group_run_stats *rs, struct thread_stat *ts, continue; snprintf(buf, sizeof(buf), - "%s prio %u/%u", + "%s prio %u/%u/%u", clat_type, ioprio_class(ts->clat_prio[ddir][i].ioprio), - ioprio(ts->clat_prio[ddir][i].ioprio)); + ioprio(ts->clat_prio[ddir][i].ioprio), + ioprio_hint(ts->clat_prio[ddir][i].ioprio)); display_lat(buf, min, max, mean, dev, out); } } @@ -640,10 +641,11 @@ static void show_ddir_status(struct group_run_stats *rs, struct thread_stat *ts, continue; snprintf(prio_name, sizeof(prio_name), - "%s prio %u/%u (%.2f%% of IOs)", + "%s prio %u/%u/%u (%.2f%% of IOs)", clat_type, ioprio_class(ts->clat_prio[ddir][i].ioprio), ioprio(ts->clat_prio[ddir][i].ioprio), + ioprio_hint(ts->clat_prio[ddir][i].ioprio), 100. * (double) prio_samples / (double) samples); show_clat_percentiles(ts->clat_prio[ddir][i].io_u_plat, prio_samples, ts->percentile_list, @@ -1533,6 +1535,8 @@ static void add_ddir_status_json(struct thread_stat *ts, ioprio_class(ts->clat_prio[ddir][i].ioprio)); json_object_add_value_int(obj, "prio", ioprio(ts->clat_prio[ddir][i].ioprio)); + json_object_add_value_int(obj, "priohint", + ioprio_hint(ts->clat_prio[ddir][i].ioprio)); tmp_object = add_ddir_lat_json(ts, ts->clat_percentiles | ts->lat_percentiles, From b85c01f7e9dfc468eb78faf86692433e6105178d Mon Sep 17 00:00:00 2001 From: Kookoo Gu Date: Wed, 26 Jul 2023 12:48:35 +0800 Subject: [PATCH 0569/1097] iolog.c: fix inaccurate clat when replay trace When do timestamp replay with high qd it will only reap the completed commands when the qd reach the max iodepth, the commands probably are finished long ago before command completion handling. Fix is to use io_u_queued_complete instead of just usec_sleep in iolog_delay Signed-off-by: Kookoo Gu --- iolog.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/iolog.c b/iolog.c index cc2cbc65ef..97ba43967f 100644 --- a/iolog.c +++ b/iolog.c @@ -82,8 +82,8 @@ static void iolog_delay(struct thread_data *td, unsigned long delay) { uint64_t usec = utime_since_now(&td->last_issue); unsigned long orig_delay = delay; - uint64_t this_delay; struct timespec ts; + int ret = 0; if (delay < td->time_offset) { td->time_offset = 0; @@ -97,13 +97,13 @@ static void iolog_delay(struct thread_data *td, unsigned long delay) delay -= usec; fio_gettime(&ts, NULL); - while (delay && !td->terminate) { - this_delay = delay; - if (this_delay > 500000) - this_delay = 500000; - usec_sleep(td, this_delay); - delay -= this_delay; + while (delay && !td->terminate) { + ret = io_u_queued_complete(td, 0); + if (ret < 0) + td_verror(td, -ret, "io_u_queued_complete"); + if (utime_since_now(&ts) > delay) + break; } usec = utime_since_now(&ts); From b49eb7153784dd287280fc7959506575e743bce8 Mon Sep 17 00:00:00 2001 From: Denis Pronin Date: Thu, 27 Jul 2023 21:49:31 +0300 Subject: [PATCH 0570/1097] diskutil.h: fix missing headers wanted by the header diskutil.h requires 3 more headers to fulfill several types therein without having to rely on headers hopefully included before this one Signed-off-by: Denis Pronin --- diskutil.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/diskutil.h b/diskutil.h index 9dca42c42e..9b28379983 100644 --- a/diskutil.h +++ b/diskutil.h @@ -2,10 +2,13 @@ #define FIO_DISKUTIL_H #define FIO_DU_NAME_SZ 64 +#include #include #include "helper_thread.h" #include "fio_sem.h" +#include "flist.h" +#include "lib/ieee754.h" /** * @ios: Number of I/O operations that have been completed successfully. From b29033254cbae585f319857e2a7acef176f046f2 Mon Sep 17 00:00:00 2001 From: Denis Pronin Date: Thu, 27 Jul 2023 22:06:59 +0300 Subject: [PATCH 0571/1097] helper_thread.h: include missing stdbool.h because 'bool' type is used missing headers should be included at the places where they are certainly used Signed-off-by: Denis Pronin --- helper_thread.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/helper_thread.h b/helper_thread.h index d7df6c4d80..91b504638f 100644 --- a/helper_thread.h +++ b/helper_thread.h @@ -1,6 +1,8 @@ #ifndef FIO_HELPER_THREAD_H #define FIO_HELPER_THREAD_H +#include + extern void helper_reset(void); extern void helper_do_stat(void); extern bool helper_should_exit(void); From 5bf944a011984675f31e591272eda8efb2092d78 Mon Sep 17 00:00:00 2001 From: Denis Pronin Date: Thu, 27 Jul 2023 22:08:45 +0300 Subject: [PATCH 0572/1097] helper_thread.h: forwardly declare structures fio_sem and sk_out helper_thread_create() function requires two structures to be declared Signed-off-by: Denis Pronin --- helper_thread.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/helper_thread.h b/helper_thread.h index 91b504638f..1c8167e83b 100644 --- a/helper_thread.h +++ b/helper_thread.h @@ -3,6 +3,9 @@ #include +struct fio_sem; +struct sk_out; + extern void helper_reset(void); extern void helper_do_stat(void); extern bool helper_should_exit(void); From 13229219c961d4b61d16b67d007a4c34de00c49b Mon Sep 17 00:00:00 2001 From: Denis Pronin Date: Fri, 28 Jul 2023 12:39:58 +0300 Subject: [PATCH 0573/1097] fix missing headers in multiple files some files require to have some missing headers included Signed-off-by: Denis Pronin --- cairo_text_helpers.c | 2 ++ cairo_text_helpers.h | 2 ++ goptions.h | 2 ++ log.c | 2 ++ 4 files changed, 8 insertions(+) diff --git a/cairo_text_helpers.c b/cairo_text_helpers.c index 19fb8e03c1..5bdd60219f 100644 --- a/cairo_text_helpers.c +++ b/cairo_text_helpers.c @@ -1,3 +1,5 @@ +#include "cairo_text_helpers.h" + #include #include #include diff --git a/cairo_text_helpers.h b/cairo_text_helpers.h index 014001ad2f..d0f52d51ff 100644 --- a/cairo_text_helpers.h +++ b/cairo_text_helpers.h @@ -1,6 +1,8 @@ #ifndef CAIRO_TEXT_HELPERS_H #define CAIRO_TEXT_HELPERS_H +#include + void draw_centered_text(cairo_t *cr, const char *font, double x, double y, double fontsize, const char *text); diff --git a/goptions.h b/goptions.h index a225a8d1b6..0361750946 100644 --- a/goptions.h +++ b/goptions.h @@ -1,6 +1,8 @@ #ifndef GFIO_OPTIONS_H #define GFIO_OPTIONS_H +#include + void gopt_get_options_window(GtkWidget *window, struct gfio_client *gc); void gopt_init(void); void gopt_exit(void); diff --git a/log.c b/log.c index 237bac2889..df58ea07a5 100644 --- a/log.c +++ b/log.c @@ -1,3 +1,5 @@ +#include "log.h" + #include #include #include From 913028e97ceedcf2cf1ec6ec32228b3c50e7337c Mon Sep 17 00:00:00 2001 From: Denis Pronin Date: Fri, 28 Jul 2023 01:26:22 +0300 Subject: [PATCH 0574/1097] correctly free thread_data options at the topmost parent process for non-threaded mode: since thread_data::eo is a pointer within shared memory between the topmost fio parent process and its children let the fio parent process set the pointer to NULL as just it frees its copy of 'eo' as memory previously allocated by means of 'malloc' meaning that each child and the parent process itself must free it for threaded mode we leave it as it has always been also we do not need to check td->io_ops for being able to free td->eo in fio_options_free() Signed-off-by: Denis Pronin --- backend.c | 4 ---- ioengines.c | 3 ++- options.c | 4 ++-- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/backend.c b/backend.c index 5f0740395b..b21c36640e 100644 --- a/backend.c +++ b/backend.c @@ -2494,10 +2494,7 @@ static void run_threads(struct sk_out *sk_out) strerror(ret)); } else { pid_t pid; - void *eo; dprint(FD_PROCESS, "will fork\n"); - eo = td->eo; - read_barrier(); pid = fork(); if (!pid) { int ret; @@ -2506,7 +2503,6 @@ static void run_threads(struct sk_out *sk_out) _exit(ret); } else if (__td_index == fio_debug_jobno) *fio_debug_jobp = pid; - free(eo); free(fd); fd = NULL; } diff --git a/ioengines.c b/ioengines.c index 361727250e..fd8c9d1a65 100644 --- a/ioengines.c +++ b/ioengines.c @@ -238,7 +238,8 @@ void free_ioengine(struct thread_data *td) if (td->eo && td->io_ops->options) { options_free(td->io_ops->options, td->eo); free(td->eo); - td->eo = NULL; + if (td->o.use_thread) + td->eo = NULL; } if (td->io_ops->dlhandle) { diff --git a/options.c b/options.c index 48aa0d7b1c..9432a0fbdf 100644 --- a/options.c +++ b/options.c @@ -5829,9 +5829,9 @@ void fio_options_free(struct thread_data *td) options_free(fio_options, &td->o); if (td->eo && td->io_ops && td->io_ops->options) { options_free(td->io_ops->options, td->eo); - free(td->eo); - td->eo = NULL; } + free(td->eo); + td->eo = NULL; } void fio_dump_options_free(struct thread_data *td) From 5c15a9111487734e448dc10359ec63c56a302938 Mon Sep 17 00:00:00 2001 From: Denis Pronin Date: Fri, 28 Jul 2023 17:25:06 +0300 Subject: [PATCH 0575/1097] io_uring engine: 'atomic_load_relaxed' instead of 'atomic_load_acquire' motivation here is that we do not have here any explicit READ dependency on atomic load because actually we just need in these places only operation to perform atomically without any explicit barriers given by memory model Signed-off-by: Denis Pronin --- engines/io_uring.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/engines/io_uring.c b/engines/io_uring.c index e1abf6888f..b361e6a5bb 100644 --- a/engines/io_uring.c +++ b/engines/io_uring.c @@ -509,7 +509,7 @@ static enum fio_q_status fio_ioring_queue(struct thread_data *td, tail = *ring->tail; next_tail = tail + 1; - if (next_tail == atomic_load_acquire(ring->head)) + if (next_tail == atomic_load_relaxed(ring->head)) return FIO_Q_BUSY; if (ld->cmdprio.mode != CMDPRIO_MODE_NONE) @@ -569,7 +569,7 @@ static int fio_ioring_commit(struct thread_data *td) unsigned start = *ld->sq_ring.tail - ld->queued; unsigned flags; - flags = atomic_load_acquire(ring->flags); + flags = atomic_load_relaxed(ring->flags); if (flags & IORING_SQ_NEED_WAKEUP) io_uring_enter(ld, ld->queued, 0, IORING_ENTER_SQ_WAKEUP); From 824912be19542f94264e485a25d37b55a9f68f0e Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Fri, 28 Jul 2023 11:32:22 -0600 Subject: [PATCH 0576/1097] Revert "correctly free thread_data options at the topmost parent process" This reverts commit 913028e97ceedcf2cf1ec6ec32228b3c50e7337c. This commit is causing the static analyzers to freak out, and also crashes on Windows. Revert it for now. Signed-off-by: Jens Axboe --- backend.c | 4 ++++ ioengines.c | 3 +-- options.c | 4 ++-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/backend.c b/backend.c index b21c36640e..5f0740395b 100644 --- a/backend.c +++ b/backend.c @@ -2494,7 +2494,10 @@ static void run_threads(struct sk_out *sk_out) strerror(ret)); } else { pid_t pid; + void *eo; dprint(FD_PROCESS, "will fork\n"); + eo = td->eo; + read_barrier(); pid = fork(); if (!pid) { int ret; @@ -2503,6 +2506,7 @@ static void run_threads(struct sk_out *sk_out) _exit(ret); } else if (__td_index == fio_debug_jobno) *fio_debug_jobp = pid; + free(eo); free(fd); fd = NULL; } diff --git a/ioengines.c b/ioengines.c index fd8c9d1a65..361727250e 100644 --- a/ioengines.c +++ b/ioengines.c @@ -238,8 +238,7 @@ void free_ioengine(struct thread_data *td) if (td->eo && td->io_ops->options) { options_free(td->io_ops->options, td->eo); free(td->eo); - if (td->o.use_thread) - td->eo = NULL; + td->eo = NULL; } if (td->io_ops->dlhandle) { diff --git a/options.c b/options.c index 9432a0fbdf..48aa0d7b1c 100644 --- a/options.c +++ b/options.c @@ -5829,9 +5829,9 @@ void fio_options_free(struct thread_data *td) options_free(fio_options, &td->o); if (td->eo && td->io_ops && td->io_ops->options) { options_free(td->io_ops->options, td->eo); + free(td->eo); + td->eo = NULL; } - free(td->eo); - td->eo = NULL; } void fio_dump_options_free(struct thread_data *td) From febae487985a8b63185fe8ac4c1623dd2ad3cf58 Mon Sep 17 00:00:00 2001 From: Denis Pronin Date: Mon, 31 Jul 2023 01:29:04 +0300 Subject: [PATCH 0577/1097] use 'const' where it is required protect variables and parameters from programmer's point of view with 'constness' Signed-off-by: Denis Pronin --- client.c | 10 +++++----- client.h | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/client.c b/client.c index 7cd2ba66e1..c257036bf5 100644 --- a/client.c +++ b/client.c @@ -34,7 +34,7 @@ static void handle_start(struct fio_client *client, struct fio_net_cmd *cmd); static void convert_text(struct fio_net_cmd *cmd); static void client_display_thread_status(struct jobs_eta *je); -struct client_ops fio_client_ops = { +struct client_ops const fio_client_ops = { .text = handle_text, .disk_util = handle_du, .thread_status = handle_ts, @@ -446,7 +446,7 @@ int fio_client_add_ini_file(void *cookie, const char *ini_file, bool remote) return 0; } -int fio_client_add(struct client_ops *ops, const char *hostname, void **cookie) +int fio_client_add(struct client_ops const *ops, const char *hostname, void **cookie) { struct fio_client *existing = *cookie; struct fio_client *client; @@ -1772,7 +1772,7 @@ static int fio_send_file(struct fio_client *client, struct cmd_sendfile *pdu, int fio_handle_client(struct fio_client *client) { - struct client_ops *ops = client->ops; + struct client_ops const *ops = client->ops; struct fio_net_cmd *cmd; dprint(FD_NET, "client: handle %s\n", client->hostname); @@ -1957,7 +1957,7 @@ int fio_clients_send_trigger(const char *cmd) return 0; } -static void request_client_etas(struct client_ops *ops) +static void request_client_etas(struct client_ops const *ops) { struct fio_client *client; struct flist_head *entry; @@ -2089,7 +2089,7 @@ static int fio_check_clients_timed_out(void) return ret; } -int fio_handle_clients(struct client_ops *ops) +int fio_handle_clients(struct client_ops const *ops) { struct pollfd *pfds; int i, ret = 0, retval = 0; diff --git a/client.h b/client.h index 8033325ed0..d77b6076f1 100644 --- a/client.h +++ b/client.h @@ -69,7 +69,7 @@ struct fio_client { uint16_t argc; char **argv; - struct client_ops *ops; + struct client_ops const *ops; void *client_data; struct client_file *files; @@ -84,7 +84,7 @@ typedef void (client_eta_op)(struct jobs_eta *je); typedef void (client_timed_out_op)(struct fio_client *); typedef void (client_jobs_eta_op)(struct fio_client *client, struct jobs_eta *je); -extern struct client_ops fio_client_ops; +extern struct client_ops const fio_client_ops; struct client_ops { client_cmd_op *text; @@ -128,8 +128,8 @@ extern int fio_start_client(struct fio_client *); extern int fio_start_all_clients(void); extern int fio_clients_send_ini(const char *); extern int fio_client_send_ini(struct fio_client *, const char *, bool); -extern int fio_handle_clients(struct client_ops *); -extern int fio_client_add(struct client_ops *, const char *, void **); +extern int fio_handle_clients(struct client_ops const*); +extern int fio_client_add(struct client_ops const*, const char *, void **); extern struct fio_client *fio_client_add_explicit(struct client_ops *, const char *, int, int); extern void fio_client_add_cmd_option(void *, const char *); extern int fio_client_add_ini_file(void *, const char *, bool); From df50839f20a95d6b576b76b034041c6a12015ee0 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Wed, 2 Aug 2023 12:23:37 -0400 Subject: [PATCH 0578/1097] t/nvmept: fix typo Make the filenames for the nvmept artifacts start with nvmept instead of readonly. Signed-off-by: Vincent Fu --- t/nvmept.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/t/nvmept.py b/t/nvmept.py index cc26d1523e..c08fb35041 100755 --- a/t/nvmept.py +++ b/t/nvmept.py @@ -295,7 +295,7 @@ def main(): 'fio_path': fio_path, 'fio_root': str(Path(__file__).absolute().parent.parent), 'artifact_root': artifact_root, - 'basename': 'readonly', + 'basename': 'nvmept', } _, failed, _ = run_fio_tests(TEST_LIST, test_env, args) From 7b57011427a8204bd63671b08dde56cd9e879d68 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Wed, 2 Aug 2023 12:30:17 -0400 Subject: [PATCH 0579/1097] t/fiotestlib: make recorded command prettier Instead of recording fio test commands as a single very long line, put each option on its own line to make the command easier for humans to digest. Signed-off-by: Vincent Fu --- t/fiotestlib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/t/fiotestlib.py b/t/fiotestlib.py index 0fe17b74ba..1f35de0ae8 100755 --- a/t/fiotestlib.py +++ b/t/fiotestlib.py @@ -75,7 +75,7 @@ def run(self): command = [self.paths['exe']] + self.parameters with open(self.filenames['cmd'], "w+", encoding=locale.getpreferredencoding()) as command_file: - command_file.write(" ".join(command)) + command_file.write(" \\\n ".join(command)) try: with open(self.filenames['stdout'], "w+", From 62f35562722f0c903567096d0f10a836d1ae2f60 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Wed, 2 Aug 2023 20:53:21 -0400 Subject: [PATCH 0580/1097] eta: calculate aggregate bw statistics even when eta is disabled The --bandwidth-log command-line option instructs fio to generate aggregate bandwidth log files. These measurements are recorded by the code generating the eta status line. When eta is disabled the aggregate bandwidth log measurements are not calculated. Change the eta code to record the measurements even when eta is not needed. eta is disabled under these conditions - explicitly with --eta=never - STDOUT is not a TTY (shell redirection, nohup, etc) - output format excludes normal output Fixes: https://github.com/axboe/fio/issues/1599 Signed-off-by: Vincent Fu --- eta.c | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/eta.c b/eta.c index af4027e0e2..cc3424613e 100644 --- a/eta.c +++ b/eta.c @@ -375,6 +375,22 @@ bool eta_time_within_slack(unsigned int time) return time > ((eta_interval_msec * 95) / 100); } +/* + * These are the conditions under which we might be able to skip the eta + * calculation. + */ +static bool skip_eta() +{ + if (!(output_format & FIO_OUTPUT_NORMAL) && f_out == stdout) + return true; + if (temp_stall_ts || eta_print == FIO_ETA_NEVER) + return true; + if (!isatty(STDOUT_FILENO) && eta_print != FIO_ETA_ALWAYS) + return true; + + return false; +} + /* * Print status of the jobs we know about. This includes rate estimates, * ETA, thread state, etc. @@ -393,14 +409,12 @@ bool calc_thread_status(struct jobs_eta *je, int force) static unsigned long long disp_io_iops[DDIR_RWDIR_CNT]; static struct timespec rate_prev_time, disp_prev_time; - if (!force) { - if (!(output_format & FIO_OUTPUT_NORMAL) && - f_out == stdout) - return false; - if (temp_stall_ts || eta_print == FIO_ETA_NEVER) - return false; + bool ret = true; - if (!isatty(STDOUT_FILENO) && (eta_print != FIO_ETA_ALWAYS)) + if (!force && skip_eta()) { + if (write_bw_log) + ret = false; + else return false; } @@ -534,7 +548,7 @@ bool calc_thread_status(struct jobs_eta *je, int force) je->nr_threads = thread_number; update_condensed_str(__run_str, run_str); memcpy(je->run_str, run_str, strlen(run_str)); - return true; + return ret; } static int gen_eta_str(struct jobs_eta *je, char *p, size_t left, From a904d1829b058506bdb9cf6c223f45260a30e723 Mon Sep 17 00:00:00 2001 From: Ankit Kumar Date: Mon, 14 Aug 2023 20:27:38 +0530 Subject: [PATCH 0581/1097] engines:io_uring: add missing error during open file This change ensures the error is propogated to upper layers to make fio exit with a non-zero return code. Add filename for errors when block size is not a multiple of logical blocks. Signed-off-by: Ankit Kumar Link: https://lore.kernel.org/r/20230814145747.114725-2-ankit.kumar@samsung.com Signed-off-by: Vincent Fu --- engines/io_uring.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/engines/io_uring.c b/engines/io_uring.c index b361e6a5bb..0906546349 100644 --- a/engines/io_uring.c +++ b/engines/io_uring.c @@ -1115,10 +1115,12 @@ static int fio_ioring_cmd_open_file(struct thread_data *td, struct fio_file *f) if (td->o.min_bs[ddir] % lba_size || td->o.max_bs[ddir] % lba_size) { if (data->lba_ext) - log_err("block size must be a multiple of " - "(LBA data size + Metadata size)\n"); + log_err("%s: block size must be a multiple of (LBA data size + Metadata size)\n", + f->file_name); else - log_err("block size must be a multiple of LBA data size\n"); + log_err("%s: block size must be a multiple of LBA data size\n", + f->file_name); + td_verror(td, EINVAL, "fio_ioring_cmd_open_file"); return 1; } } From e7e5023b98214b6b610e6858aff6839e591536ca Mon Sep 17 00:00:00 2001 From: Ankit Kumar Date: Mon, 14 Aug 2023 20:27:39 +0530 Subject: [PATCH 0582/1097] engines:io_uring: update arguments to fetch nvme data This is a prep patch to keep number of arguments for fio_nvme_get_info in check. The follow up patches will enable metadata, protection info. Signed-off-by: Ankit Kumar Link: https://lore.kernel.org/r/20230814145747.114725-3-ankit.kumar@samsung.com Signed-off-by: Vincent Fu --- engines/io_uring.c | 38 +++++++++++++------------------------- engines/nvme.c | 16 ++++++++++------ engines/nvme.h | 5 +++-- 3 files changed, 26 insertions(+), 33 deletions(-) diff --git a/engines/io_uring.c b/engines/io_uring.c index 0906546349..30d9ccd798 100644 --- a/engines/io_uring.c +++ b/engines/io_uring.c @@ -1086,30 +1086,24 @@ static int fio_ioring_cmd_open_file(struct thread_data *td, struct fio_file *f) if (o->cmd_type == FIO_URING_CMD_NVME) { struct nvme_data *data = NULL; - unsigned int nsid, lba_size = 0; - __u32 ms = 0; + unsigned int lba_size = 0; __u64 nlba = 0; int ret; /* Store the namespace-id and lba size. */ data = FILE_ENG_DATA(f); if (data == NULL) { - ret = fio_nvme_get_info(f, &nsid, &lba_size, &ms, &nlba); - if (ret) - return ret; - data = calloc(1, sizeof(struct nvme_data)); - data->nsid = nsid; - if (ms) - data->lba_ext = lba_size + ms; - else - data->lba_shift = ilog2(lba_size); + ret = fio_nvme_get_info(f, &nlba, data); + if (ret) { + free(data); + return ret; + } FILE_SET_ENG_DATA(f, data); } - assert(data->lba_shift < 32); - lba_size = data->lba_ext ? data->lba_ext : (1U << data->lba_shift); + lba_size = data->lba_ext ? data->lba_ext : data->lba_size; for_each_rw_ddir(ddir) { if (td->o.min_bs[ddir] % lba_size || @@ -1173,23 +1167,17 @@ static int fio_ioring_cmd_get_file_size(struct thread_data *td, if (o->cmd_type == FIO_URING_CMD_NVME) { struct nvme_data *data = NULL; - unsigned int nsid, lba_size = 0; - __u32 ms = 0; __u64 nlba = 0; int ret; - ret = fio_nvme_get_info(f, &nsid, &lba_size, &ms, &nlba); - if (ret) - return ret; - data = calloc(1, sizeof(struct nvme_data)); - data->nsid = nsid; - if (ms) - data->lba_ext = lba_size + ms; - else - data->lba_shift = ilog2(lba_size); + ret = fio_nvme_get_info(f, &nlba, data); + if (ret) { + free(data); + return ret; + } - f->real_file_size = lba_size * nlba; + f->real_file_size = data->lba_size * nlba; fio_file_set_size_known(f); FILE_SET_ENG_DATA(f, data); diff --git a/engines/nvme.c b/engines/nvme.c index b18ad4c28a..7e891eeda7 100644 --- a/engines/nvme.c +++ b/engines/nvme.c @@ -99,8 +99,7 @@ static int nvme_identify(int fd, __u32 nsid, enum nvme_identify_cns cns, return ioctl(fd, NVME_IOCTL_ADMIN_CMD, &cmd); } -int fio_nvme_get_info(struct fio_file *f, __u32 *nsid, __u32 *lba_sz, - __u32 *ms, __u64 *nlba) +int fio_nvme_get_info(struct fio_file *f, __u64 *nlba, struct nvme_data *data) { struct nvme_id_ns ns; int namespace_id; @@ -137,7 +136,7 @@ int fio_nvme_get_info(struct fio_file *f, __u32 *nsid, __u32 *lba_sz, return err; } - *nsid = namespace_id; + data->nsid = namespace_id; /* * 16 or 64 as maximum number of supported LBA formats. @@ -149,21 +148,26 @@ int fio_nvme_get_info(struct fio_file *f, __u32 *nsid, __u32 *lba_sz, else format_idx = (ns.flbas & 0xf) + (((ns.flbas >> 5) & 0x3) << 4); - *lba_sz = 1 << ns.lbaf[format_idx].ds; + data->lba_size = 1 << ns.lbaf[format_idx].ds; /* * Only extended LBA can be supported. * Bit 4 for flbas indicates if metadata is transferred at the end of * logical block creating an extended LBA. */ - *ms = le16_to_cpu(ns.lbaf[format_idx].ms); - if (*ms && !((ns.flbas >> 4) & 0x1)) { + data->ms = le16_to_cpu(ns.lbaf[format_idx].ms); + if (data->ms && !((ns.flbas >> 4) & 0x1)) { log_err("%s: only extended logical block can be supported\n", f->file_name); err = -ENOTSUP; goto out; } + if (data->ms) + data->lba_ext = data->lba_size + data->ms; + else + data->lba_shift = ilog2(data->lba_size); + /* Check for end to end data protection support */ if (ns.dps & 0x3) { log_err("%s: end to end data protection not supported\n", diff --git a/engines/nvme.h b/engines/nvme.h index 238471dd76..e742b14fb8 100644 --- a/engines/nvme.h +++ b/engines/nvme.h @@ -88,7 +88,9 @@ enum nvme_zns_zs { struct nvme_data { __u32 nsid; __u32 lba_shift; + __u32 lba_size; __u32 lba_ext; + __u16 ms; }; struct nvme_lbaf { @@ -219,8 +221,7 @@ struct nvme_dsm_range { int fio_nvme_iomgmt_ruhs(struct thread_data *td, struct fio_file *f, struct nvme_fdp_ruh_status *ruhs, __u32 bytes); -int fio_nvme_get_info(struct fio_file *f, __u32 *nsid, __u32 *lba_sz, - __u32 *ms, __u64 *nlba); +int fio_nvme_get_info(struct fio_file *f, __u64 *nlba, struct nvme_data *data); int fio_nvme_uring_cmd_prep(struct nvme_uring_cmd *cmd, struct io_u *io_u, struct iovec *iov, struct nvme_dsm_range *dsm); From 2d6451c97582baef27e4ca28892b033b8061faaa Mon Sep 17 00:00:00 2001 From: Ankit Kumar Date: Mon, 14 Aug 2023 20:27:40 +0530 Subject: [PATCH 0583/1097] engines:io_uring: enable support for separate metadata buffer This patch enables support for separate metadata buffer with io_uring_cmd ioengine. As we are unaware of metadata size during buffer allocation, we provide an option md_per_io_size. This option must be used to specify metadata buffer size for single IO, if namespace is formatted with a separate metadata buffer. For the sake of consistency this is the same option as used by SPDK's external ioengine. Signed-off-by: Ankit Kumar Link: https://lore.kernel.org/r/20230814145747.114725-4-ankit.kumar@samsung.com Signed-off-by: Vincent Fu --- HOWTO.rst | 4 ++++ engines/io_uring.c | 54 ++++++++++++++++++++++++++++++++++++++++++++++ engines/nvme.c | 16 ++++++-------- fio.1 | 3 +++ 4 files changed, 67 insertions(+), 10 deletions(-) diff --git a/HOWTO.rst b/HOWTO.rst index ac8314f353..6e0677f28c 100644 --- a/HOWTO.rst +++ b/HOWTO.rst @@ -2487,6 +2487,10 @@ with the caveat that when used on the command line, they must come after the want fio to use placement identifier only at indices 0, 2 and 5 specify ``fdp_pli=0,2,5``. +.. option:: md_per_io_size=int : [io_uring_cmd] + + Size in bytes for separate metadata buffer per IO. Default: 0. + .. option:: cpuload=int : [cpuio] Attempt to use the specified percentage of CPU cycles. This is a mandatory diff --git a/engines/io_uring.c b/engines/io_uring.c index 30d9ccd798..4916e3b052 100644 --- a/engines/io_uring.c +++ b/engines/io_uring.c @@ -59,6 +59,7 @@ struct ioring_data { int ring_fd; struct io_u **io_u_index; + char *md_buf; int *fds; @@ -95,6 +96,7 @@ struct ioring_options { unsigned int uncached; unsigned int nowait; unsigned int force_async; + unsigned int md_per_io_size; enum uring_cmd_type cmd_type; }; @@ -217,6 +219,16 @@ static struct fio_option options[] = { .group = FIO_OPT_G_IOURING, }, CMDPRIO_OPTIONS(struct ioring_options, FIO_OPT_G_IOURING), + { + .name = "md_per_io_size", + .lname = "Separate Metadata Buffer Size per I/O", + .type = FIO_OPT_INT, + .off1 = offsetof(struct ioring_options, md_per_io_size), + .def = "0", + .help = "Size of separate metadata buffer per I/O (Default: 0)", + .category = FIO_OPT_C_ENGINE, + .group = FIO_OPT_G_IOURING, + }, { .name = NULL, }, @@ -631,6 +643,7 @@ static void fio_ioring_cleanup(struct thread_data *td) fio_cmdprio_cleanup(&ld->cmdprio); free(ld->io_u_index); + free(ld->md_buf); free(ld->iovecs); free(ld->fds); free(ld->dsm); @@ -1016,6 +1029,7 @@ static int fio_ioring_init(struct thread_data *td) { struct ioring_options *o = td->eo; struct ioring_data *ld; + unsigned long long md_size; int ret; /* sqthread submission requires registered files */ @@ -1036,6 +1050,28 @@ static int fio_ioring_init(struct thread_data *td) /* io_u index */ ld->io_u_index = calloc(td->o.iodepth, sizeof(struct io_u *)); + + /* + * metadata buffer for nvme command. + * We are only supporting iomem=malloc / mem=malloc as of now. + */ + if (!strcmp(td->io_ops->name, "io_uring_cmd") && + (o->cmd_type == FIO_URING_CMD_NVME) && o->md_per_io_size) { + md_size = (unsigned long long) o->md_per_io_size + * (unsigned long long) td->o.iodepth; + md_size += page_mask + td->o.mem_align; + if (td->o.mem_align && td->o.mem_align > page_size) + md_size += td->o.mem_align - page_size; + if (td->o.mem_type == MEM_MALLOC) { + ld->md_buf = malloc(md_size); + if (!ld->md_buf) + return 1; + } else { + log_err("fio: Only iomem=malloc or mem=malloc is supported\n"); + return 1; + } + } + ld->iovecs = calloc(td->o.iodepth, sizeof(struct iovec)); td->io_ops_data = ld; @@ -1062,8 +1098,17 @@ static int fio_ioring_init(struct thread_data *td) static int fio_ioring_io_u_init(struct thread_data *td, struct io_u *io_u) { struct ioring_data *ld = td->io_ops_data; + struct ioring_options *o = td->eo; + char *p; ld->io_u_index[io_u->index] = io_u; + + if (!strcmp(td->io_ops->name, "io_uring_cmd")) { + p = PTR_ALIGN(ld->md_buf, page_mask) + td->o.mem_align; + p += o->md_per_io_size * io_u->index; + io_u->mmap_data = p; + } + return 0; } @@ -1117,6 +1162,15 @@ static int fio_ioring_cmd_open_file(struct thread_data *td, struct fio_file *f) td_verror(td, EINVAL, "fio_ioring_cmd_open_file"); return 1; } + if (data->ms && !data->lba_ext && ddir != DDIR_TRIM && + (o->md_per_io_size < ((td->o.max_bs[ddir] / data->lba_size) * + data->ms))) { + log_err("%s: md_per_io_size should be at least %llu bytes\n", + f->file_name, + ((td->o.max_bs[ddir] / data->lba_size) * data->ms)); + td_verror(td, EINVAL, "fio_ioring_cmd_open_file"); + return 1; + } } } if (!ld || !o->registerfiles) diff --git a/engines/nvme.c b/engines/nvme.c index 7e891eeda7..65725e3c89 100644 --- a/engines/nvme.c +++ b/engines/nvme.c @@ -79,6 +79,10 @@ int fio_nvme_uring_cmd_prep(struct nvme_uring_cmd *cmd, struct io_u *io_u, cmd->addr = (__u64)(uintptr_t)io_u->xfer_buf; cmd->data_len = io_u->xfer_buflen; } + if (data->lba_shift && data->ms) { + cmd->metadata = (__u64)(uintptr_t)io_u->mmap_data; + cmd->metadata_len = (nlb + 1) * data->ms; + } cmd->nsid = data->nsid; return 0; } @@ -149,21 +153,13 @@ int fio_nvme_get_info(struct fio_file *f, __u64 *nlba, struct nvme_data *data) format_idx = (ns.flbas & 0xf) + (((ns.flbas >> 5) & 0x3) << 4); data->lba_size = 1 << ns.lbaf[format_idx].ds; + data->ms = le16_to_cpu(ns.lbaf[format_idx].ms); /* - * Only extended LBA can be supported. * Bit 4 for flbas indicates if metadata is transferred at the end of * logical block creating an extended LBA. */ - data->ms = le16_to_cpu(ns.lbaf[format_idx].ms); - if (data->ms && !((ns.flbas >> 4) & 0x1)) { - log_err("%s: only extended logical block can be supported\n", - f->file_name); - err = -ENOTSUP; - goto out; - } - - if (data->ms) + if (data->ms && ((ns.flbas >> 4) & 0x1)) data->lba_ext = data->lba_size + data->ms; else data->lba_shift = ilog2(data->lba_size); diff --git a/fio.1 b/fio.1 index f62617e7f7..6b49a74756 100644 --- a/fio.1 +++ b/fio.1 @@ -2247,6 +2247,9 @@ By default, the job will cycle through all available Placement IDs, so use this to isolate these identifiers to specific jobs. If you want fio to use placement identifier only at indices 0, 2 and 5 specify, you would set `fdp_pli=0,2,5`. .TP +.BI (io_uring_cmd)md_per_io_size \fR=\fPint +Size in bytes for separate metadata buffer per IO. Default: 0. +.TP .BI (cpuio)cpuload \fR=\fPint Attempt to use the specified percentage of CPU cycles. This is a mandatory option when using cpuio I/O engine. From 3ee8311a30dcea86c5709b2523b9cd5cf7ba2423 Mon Sep 17 00:00:00 2001 From: Ankit Kumar Date: Mon, 14 Aug 2023 20:27:41 +0530 Subject: [PATCH 0584/1097] engines:io_uring: uring_cmd add support for protection info This patch enables support for protection information to nvme command backend of io_uring_cmd ioengine. The patch only supports protection information action bit set to 1, for read and write operation. This adds 4 new ioengine specific options * pi_act - Protection information action. Default: 1 * pi_chk - Can be set to GUARD, APPTAG or REFTAG * apptag - Sets apptag field of command dword 15 * apptag_mask - Sets apptag_mask field of command dword 15 For the sake of consistency these options are the same as the ones used by SPDK's external ioengine. For pi_act=1, if namespace is formatted with metadata size equal to protection information size, the nvme controller inserts and removes protection information for write and read command respectively. Added a check so that fio doesn't send metadata for such cases. Storage tag support is not present, so return an error for that. Signed-off-by: Ankit Kumar Link: https://lore.kernel.org/r/20230814145747.114725-5-ankit.kumar@samsung.com Signed-off-by: Vincent Fu --- HOWTO.rst | 35 +++++++++ engines/io_uring.c | 95 +++++++++++++++++++++- engines/nvme.c | 112 +++++++++++++++++++++++--- engines/nvme.h | 191 ++++++++++++++++++++++++++++++++++++++++++++- fio.1 | 35 +++++++++ 5 files changed, 454 insertions(+), 14 deletions(-) diff --git a/HOWTO.rst b/HOWTO.rst index 6e0677f28c..8903294156 100644 --- a/HOWTO.rst +++ b/HOWTO.rst @@ -2491,6 +2491,41 @@ with the caveat that when used on the command line, they must come after the Size in bytes for separate metadata buffer per IO. Default: 0. +.. option:: pi_act=int : [io_uring_cmd] + + Action to take when nvme namespace is formatted with protection + information. If this is set to 1 and namespace is formatted with + metadata size equal to protection information size, fio won't use + separate metadata buffer or extended logical block. If this is set to + 1 and namespace is formatted with metadata size greater than protection + information size, fio will not generate or verify the protection + information portion of metadata for write or read case respectively. + If this is set to 0, fio generates protection information for + write case and verifies for read case. Default: 1. + +.. option:: pi_chk=str[,str][,str] : [io_uring_cmd] + + Controls the protection information check. This can take one or more + of these values. Default: none. + + **GUARD** + Enables protection information checking of guard field. + **REFTAG** + Enables protection information checking of logical block + reference tag field. + **APPTAG** + Enables protection information checking of application tag field. + +.. option:: apptag=int : [io_uring_cmd] + + Specifies logical block application tag value, if namespace is + formatted to use end to end protection information. Default: 0x1234. + +.. option:: apptag_mask=int : [io_uring_cmd] + + Specifies logical block application tag mask value, if namespace is + formatted to use end to end protection information. Default: 0xffff. + .. option:: cpuload=int : [cpuio] Attempt to use the specified percentage of CPU cycles. This is a mandatory diff --git a/engines/io_uring.c b/engines/io_uring.c index 4916e3b052..376a2a270d 100644 --- a/engines/io_uring.c +++ b/engines/io_uring.c @@ -97,6 +97,11 @@ struct ioring_options { unsigned int nowait; unsigned int force_async; unsigned int md_per_io_size; + unsigned int pi_act; + unsigned int apptag; + unsigned int apptag_mask; + unsigned int prchk; + char *pi_chk; enum uring_cmd_type cmd_type; }; @@ -229,6 +234,46 @@ static struct fio_option options[] = { .category = FIO_OPT_C_ENGINE, .group = FIO_OPT_G_IOURING, }, + { + .name = "pi_act", + .lname = "Protection Information Action", + .type = FIO_OPT_BOOL, + .off1 = offsetof(struct ioring_options, pi_act), + .def = "1", + .help = "Protection Information Action bit (pi_act=1 or pi_act=0)", + .category = FIO_OPT_C_ENGINE, + .group = FIO_OPT_G_IOURING, + }, + { + .name = "pi_chk", + .lname = "Protection Information Check", + .type = FIO_OPT_STR_STORE, + .off1 = offsetof(struct ioring_options, pi_chk), + .def = NULL, + .help = "Control of Protection Information Checking (pi_chk=GUARD,REFTAG,APPTAG)", + .category = FIO_OPT_C_ENGINE, + .group = FIO_OPT_G_IOURING, + }, + { + .name = "apptag", + .lname = "Application Tag used in Protection Information", + .type = FIO_OPT_INT, + .off1 = offsetof(struct ioring_options, apptag), + .def = "0x1234", + .help = "Application Tag used in Protection Information field (Default: 0x1234)", + .category = FIO_OPT_C_ENGINE, + .group = FIO_OPT_G_IOURING, + }, + { + .name = "apptag_mask", + .lname = "Application Tag Mask", + .type = FIO_OPT_INT, + .off1 = offsetof(struct ioring_options, apptag_mask), + .def = "0xffff", + .help = "Application Tag Mask used with Application Tag (Default: 0xffff)", + .category = FIO_OPT_C_ENGINE, + .group = FIO_OPT_G_IOURING, + }, { .name = NULL, }, @@ -486,6 +531,33 @@ static int fio_ioring_getevents(struct thread_data *td, unsigned int min, return r < 0 ? r : events; } +static inline void fio_ioring_cmd_nvme_pi(struct thread_data *td, + struct io_u *io_u) +{ + struct ioring_data *ld = td->io_ops_data; + struct ioring_options *o = td->eo; + struct nvme_uring_cmd *cmd; + struct io_uring_sqe *sqe; + struct nvme_cmd_ext_io_opts ext_opts = {0}; + struct nvme_data *data = FILE_ENG_DATA(io_u->file); + + if (io_u->ddir == DDIR_TRIM) + return; + + sqe = &ld->sqes[(io_u->index) << 1]; + cmd = (struct nvme_uring_cmd *)sqe->cmd; + + if (data->pi_type) { + if (o->pi_act) + ext_opts.io_flags |= NVME_IO_PRINFO_PRACT; + ext_opts.io_flags |= o->prchk; + ext_opts.apptag = o->apptag; + ext_opts.apptag_mask = o->apptag_mask; + } + + fio_nvme_pi_fill(cmd, io_u, &ext_opts); +} + static inline void fio_ioring_cmdprio_prep(struct thread_data *td, struct io_u *io_u) { @@ -500,6 +572,7 @@ static enum fio_q_status fio_ioring_queue(struct thread_data *td, struct io_u *io_u) { struct ioring_data *ld = td->io_ops_data; + struct ioring_options *o = td->eo; struct io_sq_ring *ring = &ld->sq_ring; unsigned tail, next_tail; @@ -527,6 +600,10 @@ static enum fio_q_status fio_ioring_queue(struct thread_data *td, if (ld->cmdprio.mode != CMDPRIO_MODE_NONE) fio_ioring_cmdprio_prep(td, io_u); + if (!strcmp(td->io_ops->name, "io_uring_cmd") && + o->cmd_type == FIO_URING_CMD_NVME) + fio_ioring_cmd_nvme_pi(td, io_u); + ring->array[tail & ld->sq_ring_mask] = io_u->index; atomic_store_release(ring->tail, next_tail); @@ -1025,6 +1102,19 @@ static int fio_ioring_cmd_post_init(struct thread_data *td) return 0; } +static void parse_prchk_flags(struct ioring_options *o) +{ + if (!o->pi_chk) + return; + + if (strstr(o->pi_chk, "GUARD") != NULL) + o->prchk = NVME_IO_PRINFO_PRCHK_GUARD; + if (strstr(o->pi_chk, "REFTAG") != NULL) + o->prchk |= NVME_IO_PRINFO_PRCHK_REF; + if (strstr(o->pi_chk, "APPTAG") != NULL) + o->prchk |= NVME_IO_PRINFO_PRCHK_APP; +} + static int fio_ioring_init(struct thread_data *td) { struct ioring_options *o = td->eo; @@ -1071,6 +1161,7 @@ static int fio_ioring_init(struct thread_data *td) return 1; } } + parse_prchk_flags(o); ld->iovecs = calloc(td->o.iodepth, sizeof(struct iovec)); @@ -1139,7 +1230,7 @@ static int fio_ioring_cmd_open_file(struct thread_data *td, struct fio_file *f) data = FILE_ENG_DATA(f); if (data == NULL) { data = calloc(1, sizeof(struct nvme_data)); - ret = fio_nvme_get_info(f, &nlba, data); + ret = fio_nvme_get_info(f, &nlba, o->pi_act, data); if (ret) { free(data); return ret; @@ -1225,7 +1316,7 @@ static int fio_ioring_cmd_get_file_size(struct thread_data *td, int ret; data = calloc(1, sizeof(struct nvme_data)); - ret = fio_nvme_get_info(f, &nlba, data); + ret = fio_nvme_get_info(f, &nlba, o->pi_act, data); if (ret) { free(data); return ret; diff --git a/engines/nvme.c b/engines/nvme.c index 65725e3c89..8793d7423a 100644 --- a/engines/nvme.c +++ b/engines/nvme.c @@ -87,6 +87,39 @@ int fio_nvme_uring_cmd_prep(struct nvme_uring_cmd *cmd, struct io_u *io_u, return 0; } +void fio_nvme_pi_fill(struct nvme_uring_cmd *cmd, struct io_u *io_u, + struct nvme_cmd_ext_io_opts *opts) +{ + struct nvme_data *data = FILE_ENG_DATA(io_u->file); + __u64 slba; + + slba = get_slba(data, io_u); + cmd->cdw12 |= opts->io_flags; + + switch (data->pi_type) { + case NVME_NS_DPS_PI_TYPE1: + case NVME_NS_DPS_PI_TYPE2: + switch (data->guard_type) { + case NVME_NVM_NS_16B_GUARD: + cmd->cdw14 = (__u32)slba; + break; + case NVME_NVM_NS_64B_GUARD: + cmd->cdw14 = (__u32)slba; + cmd->cdw3 = ((slba >> 32) & 0xffff); + break; + default: + break; + } + cmd->cdw15 = (opts->apptag_mask << 16 | opts->apptag); + break; + case NVME_NS_DPS_PI_TYPE3: + cmd->cdw15 = (opts->apptag_mask << 16 | opts->apptag); + break; + case NVME_NS_DPS_PI_NONE: + break; + } +} + static int nvme_identify(int fd, __u32 nsid, enum nvme_identify_cns cns, enum nvme_csi csi, void *data) { @@ -103,12 +136,15 @@ static int nvme_identify(int fd, __u32 nsid, enum nvme_identify_cns cns, return ioctl(fd, NVME_IOCTL_ADMIN_CMD, &cmd); } -int fio_nvme_get_info(struct fio_file *f, __u64 *nlba, struct nvme_data *data) +int fio_nvme_get_info(struct fio_file *f, __u64 *nlba, __u32 pi_act, + struct nvme_data *data) { struct nvme_id_ns ns; + struct nvme_id_ctrl ctrl; + struct nvme_nvm_id_ns nvm_ns; int namespace_id; int fd, err; - __u32 format_idx; + __u32 format_idx, elbaf; if (f->filetype != FIO_TYPE_CHAR) { log_err("ioengine io_uring_cmd only works with nvme ns " @@ -127,6 +163,12 @@ int fio_nvme_get_info(struct fio_file *f, __u64 *nlba, struct nvme_data *data) goto out; } + err = nvme_identify(fd, 0, NVME_IDENTIFY_CNS_CTRL, NVME_CSI_NVM, &ctrl); + if (err) { + log_err("%s: failed to fetch identify ctrl\n", f->file_name); + goto out; + } + /* * Identify namespace to get namespace-id, namespace size in LBA's * and LBA data size. @@ -136,8 +178,7 @@ int fio_nvme_get_info(struct fio_file *f, __u64 *nlba, struct nvme_data *data) if (err) { log_err("%s: failed to fetch identify namespace\n", f->file_name); - close(fd); - return err; + goto out; } data->nsid = namespace_id; @@ -155,6 +196,62 @@ int fio_nvme_get_info(struct fio_file *f, __u64 *nlba, struct nvme_data *data) data->lba_size = 1 << ns.lbaf[format_idx].ds; data->ms = le16_to_cpu(ns.lbaf[format_idx].ms); + /* Check for end to end data protection support */ + if (data->ms && (ns.dps & NVME_NS_DPS_PI_MASK)) + data->pi_type = (ns.dps & NVME_NS_DPS_PI_MASK); + + if (!data->pi_type) + goto check_elba; + + if (ctrl.ctratt & NVME_CTRL_CTRATT_ELBAS) { + err = nvme_identify(fd, namespace_id, NVME_IDENTIFY_CNS_CSI_NS, + NVME_CSI_NVM, &nvm_ns); + if (err) { + log_err("%s: failed to fetch identify nvm namespace\n", + f->file_name); + goto out; + } + + elbaf = le32_to_cpu(nvm_ns.elbaf[format_idx]); + + /* Currently we don't support storage tags */ + if (elbaf & NVME_ID_NS_NVM_STS_MASK) { + log_err("%s: Storage tag not supported\n", + f->file_name); + err = -ENOTSUP; + goto out; + } + + data->guard_type = (elbaf >> NVME_ID_NS_NVM_GUARD_SHIFT) & + NVME_ID_NS_NVM_GUARD_MASK; + + /* No 32 bit guard, as storage tag is mandatory for it */ + switch (data->guard_type) { + case NVME_NVM_NS_16B_GUARD: + data->pi_size = sizeof(struct nvme_16b_guard_pif); + break; + case NVME_NVM_NS_64B_GUARD: + data->pi_size = sizeof(struct nvme_64b_guard_pif); + break; + default: + break; + } + } else { + data->guard_type = NVME_NVM_NS_16B_GUARD; + data->pi_size = sizeof(struct nvme_16b_guard_pif); + } + + /* + * when PRACT bit is set to 1, and metadata size is equal to protection + * information size, controller inserts and removes PI for write and + * read commands respectively. + */ + if (pi_act && data->ms == data->pi_size) + data->ms = 0; + + data->pi_loc = (ns.dps & NVME_NS_DPS_PI_FIRST); + +check_elba: /* * Bit 4 for flbas indicates if metadata is transferred at the end of * logical block creating an extended LBA. @@ -164,13 +261,6 @@ int fio_nvme_get_info(struct fio_file *f, __u64 *nlba, struct nvme_data *data) else data->lba_shift = ilog2(data->lba_size); - /* Check for end to end data protection support */ - if (ns.dps & 0x3) { - log_err("%s: end to end data protection not supported\n", - f->file_name); - err = -ENOTSUP; - goto out; - } *nlba = ns.nsze; out: diff --git a/engines/nvme.h b/engines/nvme.h index e742b14fb8..f359835255 100644 --- a/engines/nvme.h +++ b/engines/nvme.h @@ -42,6 +42,7 @@ struct nvme_uring_cmd { #define NVME_DEFAULT_IOCTL_TIMEOUT 0 #define NVME_IDENTIFY_DATA_SIZE 4096 #define NVME_IDENTIFY_CSI_SHIFT 24 +#define NVME_NQN_LENGTH 256 #define NVME_ZNS_ZRA_REPORT_ZONES 0 #define NVME_ZNS_ZRAS_FEAT_ERZ (1 << 16) @@ -52,6 +53,7 @@ struct nvme_uring_cmd { enum nvme_identify_cns { NVME_IDENTIFY_CNS_NS = 0x00, + NVME_IDENTIFY_CNS_CTRL = 0x01, NVME_IDENTIFY_CNS_CSI_NS = 0x05, NVME_IDENTIFY_CNS_CSI_CTRL = 0x06, }; @@ -85,12 +87,48 @@ enum nvme_zns_zs { NVME_ZNS_ZS_OFFLINE = 0xf, }; +enum nvme_id_ctrl_ctratt { + NVME_CTRL_CTRATT_ELBAS = 1 << 15, +}; + +enum { + NVME_ID_NS_NVM_STS_MASK = 0x7f, + NVME_ID_NS_NVM_GUARD_SHIFT = 7, + NVME_ID_NS_NVM_GUARD_MASK = 0x3, +}; + +enum { + NVME_NVM_NS_16B_GUARD = 0, + NVME_NVM_NS_32B_GUARD = 1, + NVME_NVM_NS_64B_GUARD = 2, +}; + struct nvme_data { __u32 nsid; __u32 lba_shift; __u32 lba_size; __u32 lba_ext; __u16 ms; + __u16 pi_size; + __u8 pi_type; + __u8 guard_type; + __u8 pi_loc; +}; + +enum nvme_id_ns_dps { + NVME_NS_DPS_PI_NONE = 0, + NVME_NS_DPS_PI_TYPE1 = 1, + NVME_NS_DPS_PI_TYPE2 = 2, + NVME_NS_DPS_PI_TYPE3 = 3, + NVME_NS_DPS_PI_MASK = 7 << 0, + NVME_NS_DPS_PI_FIRST = 1 << 3, +}; + +enum nvme_io_control_flags { + NVME_IO_PRINFO_PRCHK_REF = 1U << 26, + NVME_IO_PRINFO_PRCHK_APP = 1U << 27, + NVME_IO_PRINFO_PRCHK_GUARD = 1U << 28, + NVME_IO_PRINFO_PRACT = 1U << 29, }; struct nvme_lbaf { @@ -99,6 +137,20 @@ struct nvme_lbaf { __u8 rp; }; +/* 16 bit guard protection Information format */ +struct nvme_16b_guard_pif { + __be16 guard; + __be16 apptag; + __be32 srtag; +}; + +/* 64 bit guard protection Information format */ +struct nvme_64b_guard_pif { + __be64 guard; + __be16 apptag; + __u8 srtag[6]; +}; + struct nvme_id_ns { __le64 nsze; __le64 ncap; @@ -141,6 +193,133 @@ struct nvme_id_ns { __u8 vs[3712]; }; +struct nvme_id_psd { + __le16 mp; + __u8 rsvd2; + __u8 flags; + __le32 enlat; + __le32 exlat; + __u8 rrt; + __u8 rrl; + __u8 rwt; + __u8 rwl; + __le16 idlp; + __u8 ips; + __u8 rsvd19; + __le16 actp; + __u8 apws; + __u8 rsvd23[9]; +}; + +struct nvme_id_ctrl { + __le16 vid; + __le16 ssvid; + char sn[20]; + char mn[40]; + char fr[8]; + __u8 rab; + __u8 ieee[3]; + __u8 cmic; + __u8 mdts; + __le16 cntlid; + __le32 ver; + __le32 rtd3r; + __le32 rtd3e; + __le32 oaes; + __le32 ctratt; + __le16 rrls; + __u8 rsvd102[9]; + __u8 cntrltype; + __u8 fguid[16]; + __le16 crdt1; + __le16 crdt2; + __le16 crdt3; + __u8 rsvd134[119]; + __u8 nvmsr; + __u8 vwci; + __u8 mec; + __le16 oacs; + __u8 acl; + __u8 aerl; + __u8 frmw; + __u8 lpa; + __u8 elpe; + __u8 npss; + __u8 avscc; + __u8 apsta; + __le16 wctemp; + __le16 cctemp; + __le16 mtfa; + __le32 hmpre; + __le32 hmmin; + __u8 tnvmcap[16]; + __u8 unvmcap[16]; + __le32 rpmbs; + __le16 edstt; + __u8 dsto; + __u8 fwug; + __le16 kas; + __le16 hctma; + __le16 mntmt; + __le16 mxtmt; + __le32 sanicap; + __le32 hmminds; + __le16 hmmaxd; + __le16 nsetidmax; + __le16 endgidmax; + __u8 anatt; + __u8 anacap; + __le32 anagrpmax; + __le32 nanagrpid; + __le32 pels; + __le16 domainid; + __u8 rsvd358[10]; + __u8 megcap[16]; + __u8 rsvd384[128]; + __u8 sqes; + __u8 cqes; + __le16 maxcmd; + __le32 nn; + __le16 oncs; + __le16 fuses; + __u8 fna; + __u8 vwc; + __le16 awun; + __le16 awupf; + __u8 icsvscc; + __u8 nwpc; + __le16 acwu; + __le16 ocfs; + __le32 sgls; + __le32 mnan; + __u8 maxdna[16]; + __le32 maxcna; + __u8 rsvd564[204]; + char subnqn[NVME_NQN_LENGTH]; + __u8 rsvd1024[768]; + + /* Fabrics Only */ + __le32 ioccsz; + __le32 iorcsz; + __le16 icdoff; + __u8 fcatt; + __u8 msdbd; + __le16 ofcs; + __u8 dctype; + __u8 rsvd1807[241]; + + struct nvme_id_psd psd[32]; + __u8 vs[1024]; +}; + +struct nvme_nvm_id_ns { + __le64 lbstm; + __u8 pic; + __u8 rsvd9[3]; + __le32 elbaf[64]; + __u8 rsvd268[3828]; +}; + static inline int ilog2(uint32_t i) { int log = -1; @@ -218,14 +397,24 @@ struct nvme_dsm_range { __le64 slba; }; +struct nvme_cmd_ext_io_opts { + __u32 io_flags; + __u16 apptag; + __u16 apptag_mask; +}; + int fio_nvme_iomgmt_ruhs(struct thread_data *td, struct fio_file *f, struct nvme_fdp_ruh_status *ruhs, __u32 bytes); -int fio_nvme_get_info(struct fio_file *f, __u64 *nlba, struct nvme_data *data); +int fio_nvme_get_info(struct fio_file *f, __u64 *nlba, __u32 pi_act, + struct nvme_data *data); int fio_nvme_uring_cmd_prep(struct nvme_uring_cmd *cmd, struct io_u *io_u, struct iovec *iov, struct nvme_dsm_range *dsm); +void fio_nvme_pi_fill(struct nvme_uring_cmd *cmd, struct io_u *io_u, + struct nvme_cmd_ext_io_opts *opts); + int fio_nvme_get_zoned_model(struct thread_data *td, struct fio_file *f, enum zbd_zoned_model *model); diff --git a/fio.1 b/fio.1 index 6b49a74756..f0dc49ab00 100644 --- a/fio.1 +++ b/fio.1 @@ -2250,6 +2250,41 @@ identifier only at indices 0, 2 and 5 specify, you would set `fdp_pli=0,2,5`. .BI (io_uring_cmd)md_per_io_size \fR=\fPint Size in bytes for separate metadata buffer per IO. Default: 0. .TP +.BI (io_uring_cmd)pi_act \fR=\fPint +Action to take when nvme namespace is formatted with protection information. +If this is set to 1 and namespace is formatted with metadata size equal to +protection information size, fio won't use separate metadata buffer or extended +logical block. If this is set to 1 and namespace is formatted with metadata +size greater than protection information size, fio will not generate or verify +the protection information portion of metadata for write or read case +respectively. If this is set to 0, fio generates protection information for +write case and verifies for read case. Default: 1. +.TP +.BI (io_uring_cmd)pi_chk \fR=\fPstr[,str][,str] +Controls the protection information check. This can take one or more of these +values. Default: none. +.RS +.RS +.TP +.B GUARD +Enables protection information checking of guard field. +.TP +.B REFTAG +Enables protection information checking of logical block reference tag field. +.TP +.B APPTAG +Enables protection information checking of application tag field. +.RE +.RE +.TP +.BI (io_uring_cmd)apptag \fR=\fPint +Specifies logical block application tag value, if namespace is formatted to use +end to end protection information. Default: 0x1234. +.TP +.BI (io_uring_cmd)apptag_mask \fR=\fPint +Specifies logical block application tag mask value, if namespace is formatted +to use end to end protection information. Default: 0xffff. +.TP .BI (cpuio)cpuload \fR=\fPint Attempt to use the specified percentage of CPU cycles. This is a mandatory option when using cpuio I/O engine. From 519fb6dadc6f7a4145d572ce0f6e41d7b1e59008 Mon Sep 17 00:00:00 2001 From: Ankit Kumar Date: Mon, 14 Aug 2023 20:27:42 +0530 Subject: [PATCH 0585/1097] io_u: move engine data out of union io_uring_cmd ioengine requires engine data to store nvme protection information data. Signed-off-by: Ankit Kumar Link: https://lore.kernel.org/r/20230814145747.114725-6-ankit.kumar@samsung.com Signed-off-by: Vincent Fu --- io_u.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/io_u.h b/io_u.h index b432a54010..786251d5be 100644 --- a/io_u.h +++ b/io_u.h @@ -89,8 +89,8 @@ struct io_u { union { unsigned int index; unsigned int seen; - void *engine_data; }; + void *engine_data; union { struct flist_head verify_list; From ea13c4a7ec39ae1c4b9227af61fcb8cc45d93841 Mon Sep 17 00:00:00 2001 From: Ankit Kumar Date: Mon, 14 Aug 2023 20:27:43 +0530 Subject: [PATCH 0586/1097] crc: pull required crc16-t10 files from linux kernel Pull the required crc16 t10 files from the linux kernel. This is required to generate and verify guard tag for nvme backend of io_uring_cmd ioengine. Signed-off-by: Ankit Kumar Link: https://lore.kernel.org/r/20230814145747.114725-7-ankit.kumar@samsung.com Signed-off-by: Vincent Fu --- crc/crc-t10dif.h | 9 +++++ crc/crct10dif_common.c | 78 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+) create mode 100644 crc/crc-t10dif.h create mode 100644 crc/crct10dif_common.c diff --git a/crc/crc-t10dif.h b/crc/crc-t10dif.h new file mode 100644 index 0000000000..fde4ccd7e3 --- /dev/null +++ b/crc/crc-t10dif.h @@ -0,0 +1,9 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __CRC_T10DIF_H +#define __CRC_T10DIF_H + +extern unsigned short fio_crc_t10dif(unsigned short crc, + const unsigned char *buffer, + unsigned int len); + +#endif diff --git a/crc/crct10dif_common.c b/crc/crct10dif_common.c new file mode 100644 index 0000000000..cfb2a1b18d --- /dev/null +++ b/crc/crct10dif_common.c @@ -0,0 +1,78 @@ +/* + * Cryptographic API. + * + * T10 Data Integrity Field CRC16 Crypto Transform + * + * Copyright (c) 2007 Oracle Corporation. All rights reserved. + * Written by Martin K. Petersen + * Copyright (C) 2013 Intel Corporation + * Author: Tim Chen + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +#include "crc-t10dif.h" + +/* Table generated using the following polynomium: + * x^16 + x^15 + x^11 + x^9 + x^8 + x^7 + x^5 + x^4 + x^2 + x + 1 + * gt: 0x8bb7 + */ +static const unsigned short t10_dif_crc_table[256] = { + 0x0000, 0x8BB7, 0x9CD9, 0x176E, 0xB205, 0x39B2, 0x2EDC, 0xA56B, + 0xEFBD, 0x640A, 0x7364, 0xF8D3, 0x5DB8, 0xD60F, 0xC161, 0x4AD6, + 0x54CD, 0xDF7A, 0xC814, 0x43A3, 0xE6C8, 0x6D7F, 0x7A11, 0xF1A6, + 0xBB70, 0x30C7, 0x27A9, 0xAC1E, 0x0975, 0x82C2, 0x95AC, 0x1E1B, + 0xA99A, 0x222D, 0x3543, 0xBEF4, 0x1B9F, 0x9028, 0x8746, 0x0CF1, + 0x4627, 0xCD90, 0xDAFE, 0x5149, 0xF422, 0x7F95, 0x68FB, 0xE34C, + 0xFD57, 0x76E0, 0x618E, 0xEA39, 0x4F52, 0xC4E5, 0xD38B, 0x583C, + 0x12EA, 0x995D, 0x8E33, 0x0584, 0xA0EF, 0x2B58, 0x3C36, 0xB781, + 0xD883, 0x5334, 0x445A, 0xCFED, 0x6A86, 0xE131, 0xF65F, 0x7DE8, + 0x373E, 0xBC89, 0xABE7, 0x2050, 0x853B, 0x0E8C, 0x19E2, 0x9255, + 0x8C4E, 0x07F9, 0x1097, 0x9B20, 0x3E4B, 0xB5FC, 0xA292, 0x2925, + 0x63F3, 0xE844, 0xFF2A, 0x749D, 0xD1F6, 0x5A41, 0x4D2F, 0xC698, + 0x7119, 0xFAAE, 0xEDC0, 0x6677, 0xC31C, 0x48AB, 0x5FC5, 0xD472, + 0x9EA4, 0x1513, 0x027D, 0x89CA, 0x2CA1, 0xA716, 0xB078, 0x3BCF, + 0x25D4, 0xAE63, 0xB90D, 0x32BA, 0x97D1, 0x1C66, 0x0B08, 0x80BF, + 0xCA69, 0x41DE, 0x56B0, 0xDD07, 0x786C, 0xF3DB, 0xE4B5, 0x6F02, + 0x3AB1, 0xB106, 0xA668, 0x2DDF, 0x88B4, 0x0303, 0x146D, 0x9FDA, + 0xD50C, 0x5EBB, 0x49D5, 0xC262, 0x6709, 0xECBE, 0xFBD0, 0x7067, + 0x6E7C, 0xE5CB, 0xF2A5, 0x7912, 0xDC79, 0x57CE, 0x40A0, 0xCB17, + 0x81C1, 0x0A76, 0x1D18, 0x96AF, 0x33C4, 0xB873, 0xAF1D, 0x24AA, + 0x932B, 0x189C, 0x0FF2, 0x8445, 0x212E, 0xAA99, 0xBDF7, 0x3640, + 0x7C96, 0xF721, 0xE04F, 0x6BF8, 0xCE93, 0x4524, 0x524A, 0xD9FD, + 0xC7E6, 0x4C51, 0x5B3F, 0xD088, 0x75E3, 0xFE54, 0xE93A, 0x628D, + 0x285B, 0xA3EC, 0xB482, 0x3F35, 0x9A5E, 0x11E9, 0x0687, 0x8D30, + 0xE232, 0x6985, 0x7EEB, 0xF55C, 0x5037, 0xDB80, 0xCCEE, 0x4759, + 0x0D8F, 0x8638, 0x9156, 0x1AE1, 0xBF8A, 0x343D, 0x2353, 0xA8E4, + 0xB6FF, 0x3D48, 0x2A26, 0xA191, 0x04FA, 0x8F4D, 0x9823, 0x1394, + 0x5942, 0xD2F5, 0xC59B, 0x4E2C, 0xEB47, 0x60F0, 0x779E, 0xFC29, + 0x4BA8, 0xC01F, 0xD771, 0x5CC6, 0xF9AD, 0x721A, 0x6574, 0xEEC3, + 0xA415, 0x2FA2, 0x38CC, 0xB37B, 0x1610, 0x9DA7, 0x8AC9, 0x017E, + 0x1F65, 0x94D2, 0x83BC, 0x080B, 0xAD60, 0x26D7, 0x31B9, 0xBA0E, + 0xF0D8, 0x7B6F, 0x6C01, 0xE7B6, 0x42DD, 0xC96A, 0xDE04, 0x55B3 +}; + +extern unsigned short fio_crc_t10dif(unsigned short crc, + const unsigned char *buffer, + unsigned int len) +{ + unsigned int i; + + for (i = 0 ; i < len ; i++) + crc = (crc << 8) ^ t10_dif_crc_table[((crc >> 8) ^ buffer[i]) & 0xff]; + + return crc; +} From 5163f35e362419c6f2ea23dcb9c48a8044608171 Mon Sep 17 00:00:00 2001 From: Ankit Kumar Date: Mon, 14 Aug 2023 20:27:44 +0530 Subject: [PATCH 0587/1097] engines:io_uring: generate and verify pi for 16b guard Generate and verify protection information for 16 bit guard format, for the nvme backend of io_uring_cmd ioengine. The support is there for both the cases where metadata is transferred in separate buffer, or transferred at the end of logical block creating an extended logical block. This support also takes into consideration when protection information resides in last or first 8 bytes of metadata. Signed-off-by: Ankit Kumar Link: https://lore.kernel.org/r/20230814145747.114725-8-ankit.kumar@samsung.com Signed-off-by: Vincent Fu --- engines/io_uring.c | 34 +++++++++ engines/nvme.c | 173 +++++++++++++++++++++++++++++++++++++++++++++ engines/nvme.h | 12 ++++ 3 files changed, 219 insertions(+) diff --git a/engines/io_uring.c b/engines/io_uring.c index 376a2a270d..7ac7c755b2 100644 --- a/engines/io_uring.c +++ b/engines/io_uring.c @@ -456,7 +456,9 @@ static struct io_u *fio_ioring_cmd_event(struct thread_data *td, int event) struct ioring_options *o = td->eo; struct io_uring_cqe *cqe; struct io_u *io_u; + struct nvme_data *data; unsigned index; + int ret; index = (event + ld->cq_ring_off) & ld->cq_ring_mask; if (o->cmd_type == FIO_URING_CMD_NVME) @@ -470,6 +472,15 @@ static struct io_u *fio_ioring_cmd_event(struct thread_data *td, int event) else io_u->error = 0; + if (o->cmd_type == FIO_URING_CMD_NVME) { + data = FILE_ENG_DATA(io_u->file); + if (data->pi_type && (io_u->ddir == DDIR_READ) && !o->pi_act) { + ret = fio_nvme_pi_verify(data, io_u); + if (ret) + io_u->error = ret; + } + } + return io_u; } @@ -1190,6 +1201,7 @@ static int fio_ioring_io_u_init(struct thread_data *td, struct io_u *io_u) { struct ioring_data *ld = td->io_ops_data; struct ioring_options *o = td->eo; + struct nvme_pi_data *pi_data; char *p; ld->io_u_index[io_u->index] = io_u; @@ -1198,11 +1210,32 @@ static int fio_ioring_io_u_init(struct thread_data *td, struct io_u *io_u) p = PTR_ALIGN(ld->md_buf, page_mask) + td->o.mem_align; p += o->md_per_io_size * io_u->index; io_u->mmap_data = p; + + if (!o->pi_act) { + pi_data = calloc(1, sizeof(*pi_data)); + pi_data->io_flags |= o->prchk; + pi_data->apptag_mask = o->apptag_mask; + pi_data->apptag = o->apptag; + io_u->engine_data = pi_data; + } } return 0; } +static void fio_ioring_io_u_free(struct thread_data *td, struct io_u *io_u) +{ + struct ioring_options *o = td->eo; + struct nvme_pi *pi; + + if (!strcmp(td->io_ops->name, "io_uring_cmd") && + (o->cmd_type == FIO_URING_CMD_NVME)) { + pi = io_u->engine_data; + free(pi); + io_u->engine_data = NULL; + } +} + static int fio_ioring_open_file(struct thread_data *td, struct fio_file *f) { struct ioring_data *ld = td->io_ops_data; @@ -1411,6 +1444,7 @@ static struct ioengine_ops ioengine_uring_cmd = { .init = fio_ioring_init, .post_init = fio_ioring_cmd_post_init, .io_u_init = fio_ioring_io_u_init, + .io_u_free = fio_ioring_io_u_free, .prep = fio_ioring_cmd_prep, .queue = fio_ioring_queue, .commit = fio_ioring_commit, diff --git a/engines/nvme.c b/engines/nvme.c index 8793d7423a..6896dfc062 100644 --- a/engines/nvme.c +++ b/engines/nvme.c @@ -4,6 +4,7 @@ */ #include "nvme.h" +#include "../crc/crc-t10dif.h" static inline __u64 get_slba(struct nvme_data *data, struct io_u *io_u) { @@ -21,6 +22,158 @@ static inline __u32 get_nlb(struct nvme_data *data, struct io_u *io_u) return (io_u->xfer_buflen >> data->lba_shift) - 1; } +static void fio_nvme_generate_pi_16b_guard(struct nvme_data *data, + struct io_u *io_u, + struct nvme_cmd_ext_io_opts *opts) +{ + struct nvme_pi_data *pi_data = io_u->engine_data; + struct nvme_16b_guard_pif *pi; + unsigned char *buf = io_u->xfer_buf; + unsigned char *md_buf = io_u->mmap_data; + __u64 slba = get_slba(data, io_u); + __u32 nlb = get_nlb(data, io_u) + 1; + __u32 lba_num = 0; + __u16 guard = 0; + + if (data->pi_loc) { + if (data->lba_ext) + pi_data->interval = data->lba_ext - data->ms; + else + pi_data->interval = 0; + } else { + if (data->lba_ext) + pi_data->interval = data->lba_ext - sizeof(struct nvme_16b_guard_pif); + else + pi_data->interval = data->ms - sizeof(struct nvme_16b_guard_pif); + } + + if (io_u->ddir != DDIR_WRITE) + return; + + while (lba_num < nlb) { + if (data->lba_ext) + pi = (struct nvme_16b_guard_pif *)(buf + pi_data->interval); + else + pi = (struct nvme_16b_guard_pif *)(md_buf + pi_data->interval); + + if (opts->io_flags & NVME_IO_PRINFO_PRCHK_GUARD) { + if (data->lba_ext) { + guard = fio_crc_t10dif(0, buf, pi_data->interval); + } else { + guard = fio_crc_t10dif(0, buf, data->lba_size); + guard = fio_crc_t10dif(guard, md_buf, pi_data->interval); + } + pi->guard = cpu_to_be16(guard); + } + + if (opts->io_flags & NVME_IO_PRINFO_PRCHK_APP) + pi->apptag = cpu_to_be16(pi_data->apptag); + + if (opts->io_flags & NVME_IO_PRINFO_PRCHK_REF) { + switch (data->pi_type) { + case NVME_NS_DPS_PI_TYPE1: + case NVME_NS_DPS_PI_TYPE2: + pi->srtag = cpu_to_be32((__u32)slba + lba_num); + break; + case NVME_NS_DPS_PI_TYPE3: + break; + } + } + if (data->lba_ext) { + buf += data->lba_ext; + } else { + buf += data->lba_size; + md_buf += data->ms; + } + lba_num++; + } +} + +static int fio_nvme_verify_pi_16b_guard(struct nvme_data *data, + struct io_u *io_u) +{ + struct nvme_pi_data *pi_data = io_u->engine_data; + struct nvme_16b_guard_pif *pi; + struct fio_file *f = io_u->file; + unsigned char *buf = io_u->xfer_buf; + unsigned char *md_buf = io_u->mmap_data; + __u64 slba = get_slba(data, io_u); + __u32 nlb = get_nlb(data, io_u) + 1; + __u32 lba_num = 0; + __u16 unmask_app, unmask_app_exp, guard = 0; + + while (lba_num < nlb) { + if (data->lba_ext) + pi = (struct nvme_16b_guard_pif *)(buf + pi_data->interval); + else + pi = (struct nvme_16b_guard_pif *)(md_buf + pi_data->interval); + + if (data->pi_type == NVME_NS_DPS_PI_TYPE3) { + if (pi->apptag == NVME_PI_APP_DISABLE && + pi->srtag == NVME_PI_REF_DISABLE) + goto next; + } else if (data->pi_type == NVME_NS_DPS_PI_TYPE1 || + data->pi_type == NVME_NS_DPS_PI_TYPE2) { + if (pi->apptag == NVME_PI_APP_DISABLE) + goto next; + } + + if (pi_data->io_flags & NVME_IO_PRINFO_PRCHK_GUARD) { + if (data->lba_ext) { + guard = fio_crc_t10dif(0, buf, pi_data->interval); + } else { + guard = fio_crc_t10dif(0, buf, data->lba_size); + guard = fio_crc_t10dif(guard, md_buf, pi_data->interval); + } + if (be16_to_cpu(pi->guard) != guard) { + log_err("%s: Guard compare error: LBA: %llu Expected=%x, Actual=%x\n", + f->file_name, (unsigned long long)slba, + guard, be16_to_cpu(pi->guard)); + return -EIO; + } + } + + if (pi_data->io_flags & NVME_IO_PRINFO_PRCHK_APP) { + unmask_app = be16_to_cpu(pi->apptag) & pi_data->apptag_mask; + unmask_app_exp = pi_data->apptag & pi_data->apptag_mask; + if (unmask_app != unmask_app_exp) { + log_err("%s: APPTAG compare error: LBA: %llu Expected=%x, Actual=%x\n", + f->file_name, (unsigned long long)slba, + unmask_app_exp, unmask_app); + return -EIO; + } + } + + if (pi_data->io_flags & NVME_IO_PRINFO_PRCHK_REF) { + switch (data->pi_type) { + case NVME_NS_DPS_PI_TYPE1: + case NVME_NS_DPS_PI_TYPE2: + if (be32_to_cpu(pi->srtag) != + ((__u32)slba + lba_num)) { + log_err("%s: REFTAG compare error: LBA: %llu Expected=%x, Actual=%x\n", + f->file_name, (unsigned long long)slba, + (__u32)slba + lba_num, + be32_to_cpu(pi->srtag)); + return -EIO; + } + break; + case NVME_NS_DPS_PI_TYPE3: + break; + } + } +next: + if (data->lba_ext) { + buf += data->lba_ext; + } else { + buf += data->lba_size; + md_buf += data->ms; + } + lba_num++; + } + + return 0; +} + void fio_nvme_uring_cmd_trim_prep(struct nvme_uring_cmd *cmd, struct io_u *io_u, struct nvme_dsm_range *dsm) { @@ -96,6 +249,11 @@ void fio_nvme_pi_fill(struct nvme_uring_cmd *cmd, struct io_u *io_u, slba = get_slba(data, io_u); cmd->cdw12 |= opts->io_flags; + if (data->pi_type && !(opts->io_flags & NVME_IO_PRINFO_PRACT)) { + if (data->guard_type == NVME_NVM_NS_16B_GUARD) + fio_nvme_generate_pi_16b_guard(data, io_u, opts); + } + switch (data->pi_type) { case NVME_NS_DPS_PI_TYPE1: case NVME_NS_DPS_PI_TYPE2: @@ -120,6 +278,21 @@ void fio_nvme_pi_fill(struct nvme_uring_cmd *cmd, struct io_u *io_u, } } +int fio_nvme_pi_verify(struct nvme_data *data, struct io_u *io_u) +{ + int ret = 0; + + switch (data->guard_type) { + case NVME_NVM_NS_16B_GUARD: + ret = fio_nvme_verify_pi_16b_guard(data, io_u); + break; + default: + break; + } + + return ret; +} + static int nvme_identify(int fd, __u32 nsid, enum nvme_identify_cns cns, enum nvme_csi csi, void *data) { diff --git a/engines/nvme.h b/engines/nvme.h index f359835255..a1102dfe8c 100644 --- a/engines/nvme.h +++ b/engines/nvme.h @@ -44,6 +44,9 @@ struct nvme_uring_cmd { #define NVME_IDENTIFY_CSI_SHIFT 24 #define NVME_NQN_LENGTH 256 +#define NVME_PI_APP_DISABLE 0xFFFF +#define NVME_PI_REF_DISABLE 0xFFFFFFFF + #define NVME_ZNS_ZRA_REPORT_ZONES 0 #define NVME_ZNS_ZRAS_FEAT_ERZ (1 << 16) #define NVME_ZNS_ZSA_RESET 0x4 @@ -131,6 +134,13 @@ enum nvme_io_control_flags { NVME_IO_PRINFO_PRACT = 1U << 29, }; +struct nvme_pi_data { + __u32 interval; + __u32 io_flags; + __u16 apptag; + __u16 apptag_mask; +}; + struct nvme_lbaf { __le16 ms; __u8 ds; @@ -415,6 +425,8 @@ int fio_nvme_uring_cmd_prep(struct nvme_uring_cmd *cmd, struct io_u *io_u, void fio_nvme_pi_fill(struct nvme_uring_cmd *cmd, struct io_u *io_u, struct nvme_cmd_ext_io_opts *opts); +int fio_nvme_pi_verify(struct nvme_data *data, struct io_u *io_u); + int fio_nvme_get_zoned_model(struct thread_data *td, struct fio_file *f, enum zbd_zoned_model *model); From 377f62d827068fd44a788cb1d027c808730fcd94 Mon Sep 17 00:00:00 2001 From: Ankit Kumar Date: Mon, 14 Aug 2023 20:27:45 +0530 Subject: [PATCH 0588/1097] crc: pull required crc64 nvme apis from linux kernel Pull the required nvme crc64 apis and table from the linux kernel. This is required to generate and verify 64 bit guard tag for nvme backend of io_uring_cmd ioengine. Signed-off-by: Ankit Kumar Link: https://lore.kernel.org/r/20230814145747.114725-9-ankit.kumar@samsung.com Signed-off-by: Vincent Fu --- crc/crc64.c | 32 ++++++++++++ crc/crc64.h | 3 ++ crc/crc64table.h | 130 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 165 insertions(+) create mode 100644 crc/crc64table.h diff --git a/crc/crc64.c b/crc/crc64.c index bf24a97bf2..c910e5b845 100644 --- a/crc/crc64.c +++ b/crc/crc64.c @@ -1,4 +1,16 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * crc64nvme[256] table is from the generator polynomial specified by NVMe + * 64b CRC and is defined as, + * + * x^64 + x^63 + x^61 + x^59 + x^58 + x^56 + x^55 + x^52 + x^49 + x^48 + x^47 + + * x^46 + x^44 + x^41 + x^37 + x^36 + x^34 + x^32 + x^31 + x^28 + x^26 + x^23 + + * x^22 + x^19 + x^16 + x^13 + x^12 + x^10 + x^9 + x^6 + x^4 + x^3 + 1 + * + */ + #include "crc64.h" +#include "crc64table.h" /* * poly 0x95AC9329AC4BC9B5ULL and init 0xFFFFFFFFFFFFFFFFULL @@ -102,3 +114,23 @@ unsigned long long fio_crc64(const unsigned char *buffer, unsigned long length) return crc; } +/** + * fio_crc64_nvme - Calculate bitwise NVMe CRC64 + * @crc: seed value for computation. 0 for a new CRC calculation, or the + * previous crc64 value if computing incrementally. + * @p: pointer to buffer over which CRC64 is run + * @len: length of buffer @p + */ +unsigned long long fio_crc64_nvme(unsigned long long crc, const void *p, + unsigned int len) +{ + const unsigned char *_p = p; + unsigned int i; + + crc = ~crc; + + for (i = 0; i < len; i++) + crc = (crc >> 8) ^ crc64nvmetable[(crc & 0xff) ^ *_p++]; + + return ~crc; +} diff --git a/crc/crc64.h b/crc/crc64.h index fe9cad3e26..e586edee2d 100644 --- a/crc/crc64.h +++ b/crc/crc64.h @@ -3,4 +3,7 @@ unsigned long long fio_crc64(const unsigned char *, unsigned long); +unsigned long long fio_crc64_nvme(unsigned long long crc, const void *p, + unsigned int len); + #endif diff --git a/crc/crc64table.h b/crc/crc64table.h new file mode 100644 index 0000000000..04224d4fc6 --- /dev/null +++ b/crc/crc64table.h @@ -0,0 +1,130 @@ +static const unsigned long long crc64nvmetable[256] = { + 0x0000000000000000ULL, 0x7f6ef0c830358979ULL, + 0xfedde190606b12f2ULL, 0x81b31158505e9b8bULL, + 0xc962e5739841b68fULL, 0xb60c15bba8743ff6ULL, + 0x37bf04e3f82aa47dULL, 0x48d1f42bc81f2d04ULL, + 0xa61cecb46814fe75ULL, 0xd9721c7c5821770cULL, + 0x58c10d24087fec87ULL, 0x27affdec384a65feULL, + 0x6f7e09c7f05548faULL, 0x1010f90fc060c183ULL, + 0x91a3e857903e5a08ULL, 0xeecd189fa00bd371ULL, + 0x78e0ff3b88be6f81ULL, 0x078e0ff3b88be6f8ULL, + 0x863d1eabe8d57d73ULL, 0xf953ee63d8e0f40aULL, + 0xb1821a4810ffd90eULL, 0xceecea8020ca5077ULL, + 0x4f5ffbd87094cbfcULL, 0x30310b1040a14285ULL, + 0xdefc138fe0aa91f4ULL, 0xa192e347d09f188dULL, + 0x2021f21f80c18306ULL, 0x5f4f02d7b0f40a7fULL, + 0x179ef6fc78eb277bULL, 0x68f0063448deae02ULL, + 0xe943176c18803589ULL, 0x962de7a428b5bcf0ULL, + 0xf1c1fe77117cdf02ULL, 0x8eaf0ebf2149567bULL, + 0x0f1c1fe77117cdf0ULL, 0x7072ef2f41224489ULL, + 0x38a31b04893d698dULL, 0x47cdebccb908e0f4ULL, + 0xc67efa94e9567b7fULL, 0xb9100a5cd963f206ULL, + 0x57dd12c379682177ULL, 0x28b3e20b495da80eULL, + 0xa900f35319033385ULL, 0xd66e039b2936bafcULL, + 0x9ebff7b0e12997f8ULL, 0xe1d10778d11c1e81ULL, + 0x606216208142850aULL, 0x1f0ce6e8b1770c73ULL, + 0x8921014c99c2b083ULL, 0xf64ff184a9f739faULL, + 0x77fce0dcf9a9a271ULL, 0x08921014c99c2b08ULL, + 0x4043e43f0183060cULL, 0x3f2d14f731b68f75ULL, + 0xbe9e05af61e814feULL, 0xc1f0f56751dd9d87ULL, + 0x2f3dedf8f1d64ef6ULL, 0x50531d30c1e3c78fULL, + 0xd1e00c6891bd5c04ULL, 0xae8efca0a188d57dULL, + 0xe65f088b6997f879ULL, 0x9931f84359a27100ULL, + 0x1882e91b09fcea8bULL, 0x67ec19d339c963f2ULL, + 0xd75adabd7a6e2d6fULL, 0xa8342a754a5ba416ULL, + 0x29873b2d1a053f9dULL, 0x56e9cbe52a30b6e4ULL, + 0x1e383fcee22f9be0ULL, 0x6156cf06d21a1299ULL, + 0xe0e5de5e82448912ULL, 0x9f8b2e96b271006bULL, + 0x71463609127ad31aULL, 0x0e28c6c1224f5a63ULL, + 0x8f9bd7997211c1e8ULL, 0xf0f5275142244891ULL, + 0xb824d37a8a3b6595ULL, 0xc74a23b2ba0eececULL, + 0x46f932eaea507767ULL, 0x3997c222da65fe1eULL, + 0xafba2586f2d042eeULL, 0xd0d4d54ec2e5cb97ULL, + 0x5167c41692bb501cULL, 0x2e0934dea28ed965ULL, + 0x66d8c0f56a91f461ULL, 0x19b6303d5aa47d18ULL, + 0x980521650afae693ULL, 0xe76bd1ad3acf6feaULL, + 0x09a6c9329ac4bc9bULL, 0x76c839faaaf135e2ULL, + 0xf77b28a2faafae69ULL, 0x8815d86aca9a2710ULL, + 0xc0c42c4102850a14ULL, 0xbfaadc8932b0836dULL, + 0x3e19cdd162ee18e6ULL, 0x41773d1952db919fULL, + 0x269b24ca6b12f26dULL, 0x59f5d4025b277b14ULL, + 0xd846c55a0b79e09fULL, 0xa72835923b4c69e6ULL, + 0xeff9c1b9f35344e2ULL, 0x90973171c366cd9bULL, + 0x1124202993385610ULL, 0x6e4ad0e1a30ddf69ULL, + 0x8087c87e03060c18ULL, 0xffe938b633338561ULL, + 0x7e5a29ee636d1eeaULL, 0x0134d92653589793ULL, + 0x49e52d0d9b47ba97ULL, 0x368bddc5ab7233eeULL, + 0xb738cc9dfb2ca865ULL, 0xc8563c55cb19211cULL, + 0x5e7bdbf1e3ac9decULL, 0x21152b39d3991495ULL, + 0xa0a63a6183c78f1eULL, 0xdfc8caa9b3f20667ULL, + 0x97193e827bed2b63ULL, 0xe877ce4a4bd8a21aULL, + 0x69c4df121b863991ULL, 0x16aa2fda2bb3b0e8ULL, + 0xf86737458bb86399ULL, 0x8709c78dbb8deae0ULL, + 0x06bad6d5ebd3716bULL, 0x79d4261ddbe6f812ULL, + 0x3105d23613f9d516ULL, 0x4e6b22fe23cc5c6fULL, + 0xcfd833a67392c7e4ULL, 0xb0b6c36e43a74e9dULL, + 0x9a6c9329ac4bc9b5ULL, 0xe50263e19c7e40ccULL, + 0x64b172b9cc20db47ULL, 0x1bdf8271fc15523eULL, + 0x530e765a340a7f3aULL, 0x2c608692043ff643ULL, + 0xadd397ca54616dc8ULL, 0xd2bd67026454e4b1ULL, + 0x3c707f9dc45f37c0ULL, 0x431e8f55f46abeb9ULL, + 0xc2ad9e0da4342532ULL, 0xbdc36ec59401ac4bULL, + 0xf5129aee5c1e814fULL, 0x8a7c6a266c2b0836ULL, + 0x0bcf7b7e3c7593bdULL, 0x74a18bb60c401ac4ULL, + 0xe28c6c1224f5a634ULL, 0x9de29cda14c02f4dULL, + 0x1c518d82449eb4c6ULL, 0x633f7d4a74ab3dbfULL, + 0x2bee8961bcb410bbULL, 0x548079a98c8199c2ULL, + 0xd53368f1dcdf0249ULL, 0xaa5d9839ecea8b30ULL, + 0x449080a64ce15841ULL, 0x3bfe706e7cd4d138ULL, + 0xba4d61362c8a4ab3ULL, 0xc52391fe1cbfc3caULL, + 0x8df265d5d4a0eeceULL, 0xf29c951de49567b7ULL, + 0x732f8445b4cbfc3cULL, 0x0c41748d84fe7545ULL, + 0x6bad6d5ebd3716b7ULL, 0x14c39d968d029fceULL, + 0x95708ccedd5c0445ULL, 0xea1e7c06ed698d3cULL, + 0xa2cf882d2576a038ULL, 0xdda178e515432941ULL, + 0x5c1269bd451db2caULL, 0x237c997575283bb3ULL, + 0xcdb181ead523e8c2ULL, 0xb2df7122e51661bbULL, + 0x336c607ab548fa30ULL, 0x4c0290b2857d7349ULL, + 0x04d364994d625e4dULL, 0x7bbd94517d57d734ULL, + 0xfa0e85092d094cbfULL, 0x856075c11d3cc5c6ULL, + 0x134d926535897936ULL, 0x6c2362ad05bcf04fULL, + 0xed9073f555e26bc4ULL, 0x92fe833d65d7e2bdULL, + 0xda2f7716adc8cfb9ULL, 0xa54187de9dfd46c0ULL, + 0x24f29686cda3dd4bULL, 0x5b9c664efd965432ULL, + 0xb5517ed15d9d8743ULL, 0xca3f8e196da80e3aULL, + 0x4b8c9f413df695b1ULL, 0x34e26f890dc31cc8ULL, + 0x7c339ba2c5dc31ccULL, 0x035d6b6af5e9b8b5ULL, + 0x82ee7a32a5b7233eULL, 0xfd808afa9582aa47ULL, + 0x4d364994d625e4daULL, 0x3258b95ce6106da3ULL, + 0xb3eba804b64ef628ULL, 0xcc8558cc867b7f51ULL, + 0x8454ace74e645255ULL, 0xfb3a5c2f7e51db2cULL, + 0x7a894d772e0f40a7ULL, 0x05e7bdbf1e3ac9deULL, + 0xeb2aa520be311aafULL, 0x944455e88e0493d6ULL, + 0x15f744b0de5a085dULL, 0x6a99b478ee6f8124ULL, + 0x224840532670ac20ULL, 0x5d26b09b16452559ULL, + 0xdc95a1c3461bbed2ULL, 0xa3fb510b762e37abULL, + 0x35d6b6af5e9b8b5bULL, 0x4ab846676eae0222ULL, + 0xcb0b573f3ef099a9ULL, 0xb465a7f70ec510d0ULL, + 0xfcb453dcc6da3dd4ULL, 0x83daa314f6efb4adULL, + 0x0269b24ca6b12f26ULL, 0x7d0742849684a65fULL, + 0x93ca5a1b368f752eULL, 0xeca4aad306bafc57ULL, + 0x6d17bb8b56e467dcULL, 0x12794b4366d1eea5ULL, + 0x5aa8bf68aecec3a1ULL, 0x25c64fa09efb4ad8ULL, + 0xa4755ef8cea5d153ULL, 0xdb1bae30fe90582aULL, + 0xbcf7b7e3c7593bd8ULL, 0xc399472bf76cb2a1ULL, + 0x422a5673a732292aULL, 0x3d44a6bb9707a053ULL, + 0x759552905f188d57ULL, 0x0afba2586f2d042eULL, + 0x8b48b3003f739fa5ULL, 0xf42643c80f4616dcULL, + 0x1aeb5b57af4dc5adULL, 0x6585ab9f9f784cd4ULL, + 0xe436bac7cf26d75fULL, 0x9b584a0fff135e26ULL, + 0xd389be24370c7322ULL, 0xace74eec0739fa5bULL, + 0x2d545fb4576761d0ULL, 0x523aaf7c6752e8a9ULL, + 0xc41748d84fe75459ULL, 0xbb79b8107fd2dd20ULL, + 0x3acaa9482f8c46abULL, 0x45a459801fb9cfd2ULL, + 0x0d75adabd7a6e2d6ULL, 0x721b5d63e7936bafULL, + 0xf3a84c3bb7cdf024ULL, 0x8cc6bcf387f8795dULL, + 0x620ba46c27f3aa2cULL, 0x1d6554a417c62355ULL, + 0x9cd645fc4798b8deULL, 0xe3b8b53477ad31a7ULL, + 0xab69411fbfb21ca3ULL, 0xd407b1d78f8795daULL, + 0x55b4a08fdfd90e51ULL, 0x2ada5047efec8728ULL, +}; From ca59c41c6526dc718a897a27c7b9255a55aec3b2 Mon Sep 17 00:00:00 2001 From: Ankit Kumar Date: Mon, 14 Aug 2023 20:27:46 +0530 Subject: [PATCH 0589/1097] engines:nvme: pull required 48 bit accessors from linux kernel Pull the 48 bit helpers, required for supporting 48 bit reference tags. Add GPL 2.0 license to nvme.c and nvme.h files. Signed-off-by: Ankit Kumar Link: https://lore.kernel.org/r/20230814145747.114725-10-ankit.kumar@samsung.com Signed-off-by: Vincent Fu --- engines/nvme.c | 1 + engines/nvme.h | 17 +++++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/engines/nvme.c b/engines/nvme.c index 6896dfc062..41e7d07b63 100644 --- a/engines/nvme.c +++ b/engines/nvme.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0 /* * nvme structure declarations and helper functions for the * io_uring_cmd engine. diff --git a/engines/nvme.h b/engines/nvme.h index a1102dfe8c..fb1f776097 100644 --- a/engines/nvme.h +++ b/engines/nvme.h @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0 /* * nvme structure declarations and helper functions for the * io_uring_cmd engine. @@ -440,4 +441,20 @@ int fio_nvme_reset_wp(struct thread_data *td, struct fio_file *f, int fio_nvme_get_max_open_zones(struct thread_data *td, struct fio_file *f, unsigned int *max_open_zones); +static inline void put_unaligned_be48(__u64 val, __u8 *p) +{ + *p++ = val >> 40; + *p++ = val >> 32; + *p++ = val >> 24; + *p++ = val >> 16; + *p++ = val >> 8; + *p++ = val; +} + +static inline __u64 get_unaligned_be48(__u8 *p) +{ + return (__u64)p[0] << 40 | (__u64)p[1] << 32 | (__u64)p[2] << 24 | + p[3] << 16 | p[4] << 8 | p[5]; +} + #endif From 08371767da0ad33b7901f93682356ac46158c0f9 Mon Sep 17 00:00:00 2001 From: Ankit Kumar Date: Mon, 14 Aug 2023 20:27:47 +0530 Subject: [PATCH 0590/1097] engines:io_uring: generate and verify pi for 64b guard Generate and verify protection information for 64 bit guard format, for the nvme backend of io_uring_cmd ioengine. The support is there for both the cases where metadata is transferred in separate buffer, or transferred at the end of logical block creating an extended logical block. This support also takes into consideration when protection information resides in last or first 16 bytes of metadata. Signed-off-by: Ankit Kumar Link: https://lore.kernel.org/r/20230814145747.114725-11-ankit.kumar@samsung.com Signed-off-by: Vincent Fu --- engines/nvme.c | 158 +++++++++++++++++++++++++++++++++++++++++++++++++ engines/nvme.h | 7 +++ 2 files changed, 165 insertions(+) diff --git a/engines/nvme.c b/engines/nvme.c index 41e7d07b63..08503b3399 100644 --- a/engines/nvme.c +++ b/engines/nvme.c @@ -6,6 +6,7 @@ #include "nvme.h" #include "../crc/crc-t10dif.h" +#include "../crc/crc64.h" static inline __u64 get_slba(struct nvme_data *data, struct io_u *io_u) { @@ -175,6 +176,158 @@ static int fio_nvme_verify_pi_16b_guard(struct nvme_data *data, return 0; } +static void fio_nvme_generate_pi_64b_guard(struct nvme_data *data, + struct io_u *io_u, + struct nvme_cmd_ext_io_opts *opts) +{ + struct nvme_pi_data *pi_data = io_u->engine_data; + struct nvme_64b_guard_pif *pi; + unsigned char *buf = io_u->xfer_buf; + unsigned char *md_buf = io_u->mmap_data; + uint64_t guard = 0; + __u64 slba = get_slba(data, io_u); + __u32 nlb = get_nlb(data, io_u) + 1; + __u32 lba_num = 0; + + if (data->pi_loc) { + if (data->lba_ext) + pi_data->interval = data->lba_ext - data->ms; + else + pi_data->interval = 0; + } else { + if (data->lba_ext) + pi_data->interval = data->lba_ext - sizeof(struct nvme_64b_guard_pif); + else + pi_data->interval = data->ms - sizeof(struct nvme_64b_guard_pif); + } + + if (io_u->ddir != DDIR_WRITE) + return; + + while (lba_num < nlb) { + if (data->lba_ext) + pi = (struct nvme_64b_guard_pif *)(buf + pi_data->interval); + else + pi = (struct nvme_64b_guard_pif *)(md_buf + pi_data->interval); + + if (opts->io_flags & NVME_IO_PRINFO_PRCHK_GUARD) { + if (data->lba_ext) { + guard = fio_crc64_nvme(0, buf, pi_data->interval); + } else { + guard = fio_crc64_nvme(0, buf, data->lba_size); + guard = fio_crc64_nvme(guard, md_buf, pi_data->interval); + } + pi->guard = cpu_to_be64(guard); + } + + if (opts->io_flags & NVME_IO_PRINFO_PRCHK_APP) + pi->apptag = cpu_to_be16(pi_data->apptag); + + if (opts->io_flags & NVME_IO_PRINFO_PRCHK_REF) { + switch (data->pi_type) { + case NVME_NS_DPS_PI_TYPE1: + case NVME_NS_DPS_PI_TYPE2: + put_unaligned_be48(slba + lba_num, pi->srtag); + break; + case NVME_NS_DPS_PI_TYPE3: + break; + } + } + if (data->lba_ext) { + buf += data->lba_ext; + } else { + buf += data->lba_size; + md_buf += data->ms; + } + lba_num++; + } +} + +static int fio_nvme_verify_pi_64b_guard(struct nvme_data *data, + struct io_u *io_u) +{ + struct nvme_pi_data *pi_data = io_u->engine_data; + struct nvme_64b_guard_pif *pi; + struct fio_file *f = io_u->file; + unsigned char *buf = io_u->xfer_buf; + unsigned char *md_buf = io_u->mmap_data; + __u64 slba = get_slba(data, io_u); + __u64 ref, ref_exp, guard = 0; + __u32 nlb = get_nlb(data, io_u) + 1; + __u32 lba_num = 0; + __u16 unmask_app, unmask_app_exp; + + while (lba_num < nlb) { + if (data->lba_ext) + pi = (struct nvme_64b_guard_pif *)(buf + pi_data->interval); + else + pi = (struct nvme_64b_guard_pif *)(md_buf + pi_data->interval); + + if (data->pi_type == NVME_NS_DPS_PI_TYPE3) { + if (pi->apptag == NVME_PI_APP_DISABLE && + fio_nvme_pi_ref_escape(pi->srtag)) + goto next; + } else if (data->pi_type == NVME_NS_DPS_PI_TYPE1 || + data->pi_type == NVME_NS_DPS_PI_TYPE2) { + if (pi->apptag == NVME_PI_APP_DISABLE) + goto next; + } + + if (pi_data->io_flags & NVME_IO_PRINFO_PRCHK_GUARD) { + if (data->lba_ext) { + guard = fio_crc64_nvme(0, buf, pi_data->interval); + } else { + guard = fio_crc64_nvme(0, buf, data->lba_size); + guard = fio_crc64_nvme(guard, md_buf, pi_data->interval); + } + if (be64_to_cpu((uint64_t)pi->guard) != guard) { + log_err("%s: Guard compare error: LBA: %llu Expected=%llx, Actual=%llx\n", + f->file_name, (unsigned long long)slba, + guard, be64_to_cpu((uint64_t)pi->guard)); + return -EIO; + } + } + + if (pi_data->io_flags & NVME_IO_PRINFO_PRCHK_APP) { + unmask_app = be16_to_cpu(pi->apptag) & pi_data->apptag_mask; + unmask_app_exp = pi_data->apptag & pi_data->apptag_mask; + if (unmask_app != unmask_app_exp) { + log_err("%s: APPTAG compare error: LBA: %llu Expected=%x, Actual=%x\n", + f->file_name, (unsigned long long)slba, + unmask_app_exp, unmask_app); + return -EIO; + } + } + + if (pi_data->io_flags & NVME_IO_PRINFO_PRCHK_REF) { + switch (data->pi_type) { + case NVME_NS_DPS_PI_TYPE1: + case NVME_NS_DPS_PI_TYPE2: + ref = get_unaligned_be48(pi->srtag); + ref_exp = (slba + lba_num) & ((1ULL << 48) - 1); + if (ref != ref_exp) { + log_err("%s: REFTAG compare error: LBA: %llu Expected=%llx, Actual=%llx\n", + f->file_name, (unsigned long long)slba, + ref_exp, ref); + return -EIO; + } + break; + case NVME_NS_DPS_PI_TYPE3: + break; + } + } +next: + if (data->lba_ext) { + buf += data->lba_ext; + } else { + buf += data->lba_size; + md_buf += data->ms; + } + lba_num++; + } + + return 0; +} void fio_nvme_uring_cmd_trim_prep(struct nvme_uring_cmd *cmd, struct io_u *io_u, struct nvme_dsm_range *dsm) { @@ -253,6 +406,8 @@ void fio_nvme_pi_fill(struct nvme_uring_cmd *cmd, struct io_u *io_u, if (data->pi_type && !(opts->io_flags & NVME_IO_PRINFO_PRACT)) { if (data->guard_type == NVME_NVM_NS_16B_GUARD) fio_nvme_generate_pi_16b_guard(data, io_u, opts); + else if (data->guard_type == NVME_NVM_NS_64B_GUARD) + fio_nvme_generate_pi_64b_guard(data, io_u, opts); } switch (data->pi_type) { @@ -287,6 +442,9 @@ int fio_nvme_pi_verify(struct nvme_data *data, struct io_u *io_u) case NVME_NVM_NS_16B_GUARD: ret = fio_nvme_verify_pi_16b_guard(data, io_u); break; + case NVME_NVM_NS_64B_GUARD: + ret = fio_nvme_verify_pi_64b_guard(data, io_u); + break; default: break; } diff --git a/engines/nvme.h b/engines/nvme.h index fb1f776097..792b35d830 100644 --- a/engines/nvme.h +++ b/engines/nvme.h @@ -457,4 +457,11 @@ static inline __u64 get_unaligned_be48(__u8 *p) p[3] << 16 | p[4] << 8 | p[5]; } +static inline bool fio_nvme_pi_ref_escape(__u8 *reftag) +{ + __u8 ref_esc[6] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; + + return memcmp(reftag, ref_esc, sizeof(ref_esc)) == 0; +} + #endif From 140a30e36eeb87b2e6cd037e622e119886db34d0 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Mon, 31 Jul 2023 17:02:55 +0000 Subject: [PATCH 0591/1097] t/fiotestlib: use config variable to skip test at runtime Check a test config variable to skip a test at runtime. This will be used to skip a test when the test runner determines that it should not be run. Signed-off-by: Vincent Fu --- t/fiotestlib.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/t/fiotestlib.py b/t/fiotestlib.py index 1f35de0ae8..a96338a384 100755 --- a/t/fiotestlib.py +++ b/t/fiotestlib.py @@ -382,9 +382,10 @@ def run_fio_tests(test_list, test_env, args): for config in test_list: if (args.skip and config['test_id'] in args.skip) or \ - (args.run_only and config['test_id'] not in args.run_only): + (args.run_only and config['test_id'] not in args.run_only) or \ + ('force_skip' in config and config['force_skip']): skipped = skipped + 1 - print(f"Test {config['test_id']} SKIPPED (User request)") + print(f"Test {config['test_id']} SKIPPED (User request or override)") continue if issubclass(config['test_class'], FioJobFileTest): From d0f7d9fef46666a3b5f0a166d9d402bec302af78 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Fri, 28 Jul 2023 15:47:12 +0000 Subject: [PATCH 0592/1097] t/nvmept_pi: test script for protection information Carry out tests of the code supporting end-to-end data protection via the io_uring_cmd ioengine's nvme command type. The test script detects the available protection information formats supported by the target device. Then for each of these configurations, the script formats the device and runs a series of tests. Signed-off-by: Vincent Fu --- t/nvmept_pi.py | 949 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 949 insertions(+) create mode 100755 t/nvmept_pi.py diff --git a/t/nvmept_pi.py b/t/nvmept_pi.py new file mode 100755 index 0000000000..5de77c9dbc --- /dev/null +++ b/t/nvmept_pi.py @@ -0,0 +1,949 @@ +#!/usr/bin/env python3 +""" +# nvmept_pi.py +# +# Test fio's io_uring_cmd ioengine support for DIF/DIX end-to-end data +# protection. +# +# USAGE +# see python3 nvmept_pi.py --help +# +# EXAMPLES (THIS IS A DESTRUCTIVE TEST!!) +# python3 t/nvmept_pi.py --dut /dev/ng0n1 -f ./fio +# python3 t/nvmept_pi.py --dut /dev/ng0n1 -f ./fio --lbaf 1 +# +# REQUIREMENTS +# Python 3.6 +# +""" +import os +import sys +import json +import time +import locale +import logging +import argparse +import itertools +import subprocess +from pathlib import Path +from fiotestlib import FioJobCmdTest, run_fio_tests +from fiotestcommon import SUCCESS_NONZERO + +NUMBER_IOS = 8192 +BS_LOW = 1 +BS_HIGH = 16 + +class DifDixTest(FioJobCmdTest): + """ + NVMe DIF/DIX test class. + """ + + def setup(self, parameters): + """Setup a test.""" + + fio_args = [ + "--name=nvmept_pi", + "--ioengine=io_uring_cmd", + "--cmd_type=nvme", + f"--filename={self.fio_opts['filename']}", + f"--rw={self.fio_opts['rw']}", + f"--bsrange={self.fio_opts['bsrange']}", + f"--output={self.filenames['output']}", + f"--output-format={self.fio_opts['output-format']}", + f"--md_per_io_size={self.fio_opts['md_per_io_size']}", + f"--pi_act={self.fio_opts['pi_act']}", + f"--pi_chk={self.fio_opts['pi_chk']}", + f"--apptag={self.fio_opts['apptag']}", + f"--apptag_mask={self.fio_opts['apptag_mask']}", + ] + for opt in ['fixedbufs', 'nonvectored', 'force_async', 'registerfiles', + 'sqthread_poll', 'sqthread_poll_cpu', 'hipri', 'nowait', + 'time_based', 'runtime', 'verify', 'io_size', 'offset', 'number_ios']: + if opt in self.fio_opts: + option = f"--{opt}={self.fio_opts[opt]}" + fio_args.append(option) + + super().setup(fio_args) + + +TEST_LIST = [ +# +# Write data with pi_act=1 and then read the data back (with both +# pi_act=[0,1]). +# + { + # Write workload with variable IO sizes + # pi_act=1 + "test_id": 101, + "fio_opts": { + "rw": 'write', + "number_ios": NUMBER_IOS, + "output-format": "json", + "apptag": "0x8888", + "apptag_mask": "0xFFFF", + "pi_act": 1, + }, + "pi_chk": "APPTAG,GUARD,REFTAG", + "bs_low": BS_LOW, + "bs_high": BS_HIGH, + "test_class": DifDixTest, + }, + { + # Read workload with fixed small IO size + # pi_act=0 + "test_id": 102, + "fio_opts": { + "rw": 'read', + "number_ios": NUMBER_IOS, + "output-format": "json", + "pi_act": 0, + "apptag": "0x8888", + "apptag_mask": "0xFFFF", + }, + "pi_chk": "APPTAG,GUARD,REFTAG", + "bs_low": BS_LOW, + "bs_high": BS_LOW, + "test_class": DifDixTest, + }, + { + # Read workload with fixed small IO size + # pi_act=1 + "test_id": 103, + "fio_opts": { + "rw": 'read', + "number_ios": NUMBER_IOS, + "output-format": "json", + "pi_act": 1, + "apptag": "0x8888", + "apptag_mask": "0xFFFF", + }, + "pi_chk": "APPTAG,GUARD,REFTAG", + "bs_low": BS_LOW, + "bs_high": BS_LOW, + "test_class": DifDixTest, + }, + { + # Write workload with fixed large IO size + # Precondition for read workloads to follow + # pi_act=1 + "test_id": 104, + "fio_opts": { + "rw": 'write', + "number_ios": NUMBER_IOS, + "output-format": "json", + "apptag": "0x8888", + "apptag_mask": "0xFFFF", + "pi_act": 1, + }, + "pi_chk": "APPTAG,GUARD,REFTAG", + "bs_low": BS_HIGH, + "bs_high": BS_HIGH, + "test_class": DifDixTest, + }, + { + # Read workload with variable IO sizes + # pi_act=0 + "test_id": 105, + "fio_opts": { + "rw": 'read', + "number_ios": NUMBER_IOS, + "output-format": "json", + "pi_act": 0, + "apptag": "0x8888", + "apptag_mask": "0xFFFF", + }, + "pi_chk": "APPTAG,GUARD,REFTAG", + "bs_low": BS_LOW, + "bs_high": BS_HIGH, + "test_class": DifDixTest, + }, + { + # Read workload with variable IO sizes + # pi_act=1 + "test_id": 106, + "fio_opts": { + "rw": 'read', + "number_ios": NUMBER_IOS, + "output-format": "json", + "pi_act": 1, + "apptag": "0x8888", + "apptag_mask": "0xFFFF", + }, + "pi_chk": "APPTAG,GUARD,REFTAG", + "bs_low": BS_LOW, + "bs_high": BS_HIGH, + "test_class": DifDixTest, + }, +# +# Write data with pi_act=0 and then read the data back (with both +# pi_act=[0,1]). +# + { + # Write workload with variable IO sizes + # pi_act=0 + "test_id": 201, + "fio_opts": { + "rw": 'write', + "number_ios": NUMBER_IOS, + "output-format": "json", + "apptag": "0x8888", + "apptag_mask": "0xFFFF", + "pi_act": 0, + }, + "pi_chk": "APPTAG,GUARD,REFTAG", + "bs_low": BS_LOW, + "bs_high": BS_HIGH, + "test_class": DifDixTest, + }, + { + # Read workload with fixed small IO size + # pi_act=0 + "test_id": 202, + "fio_opts": { + "rw": 'read', + "number_ios": NUMBER_IOS, + "output-format": "json", + "pi_act": 0, + "apptag": "0x8888", + "apptag_mask": "0xFFFF", + }, + "pi_chk": "APPTAG,GUARD,REFTAG", + "bs_low": BS_LOW, + "bs_high": BS_LOW, + "test_class": DifDixTest, + }, + { + # Read workload with fixed small IO size + # pi_act=1 + "test_id": 203, + "fio_opts": { + "rw": 'read', + "number_ios": NUMBER_IOS, + "output-format": "json", + "pi_act": 1, + "apptag": "0x8888", + "apptag_mask": "0xFFFF", + }, + "pi_chk": "APPTAG,GUARD,REFTAG", + "bs_low": BS_LOW, + "bs_high": BS_LOW, + "test_class": DifDixTest, + }, + { + # Write workload with fixed large IO sizes + # pi_act=0 + "test_id": 204, + "fio_opts": { + "rw": 'write', + "number_ios": NUMBER_IOS, + "output-format": "json", + "apptag": "0x8888", + "apptag_mask": "0xFFFF", + "pi_act": 0, + }, + "pi_chk": "APPTAG,GUARD,REFTAG", + "bs_low": BS_HIGH, + "bs_high": BS_HIGH, + "test_class": DifDixTest, + }, + { + # Read workload with variable IO sizes + # pi_act=0 + "test_id": 205, + "fio_opts": { + "rw": 'read', + "number_ios": NUMBER_IOS, + "output-format": "json", + "pi_act": 0, + "apptag": "0x8888", + "apptag_mask": "0xFFFF", + }, + "pi_chk": "APPTAG,GUARD,REFTAG", + "bs_low": BS_LOW, + "bs_high": BS_HIGH, + "test_class": DifDixTest, + }, + { + # Read workload with variable IO sizes + # pi_act=1 + "test_id": 206, + "fio_opts": { + "rw": 'read', + "number_ios": NUMBER_IOS, + "output-format": "json", + "pi_act": 1, + "apptag": "0x8888", + "apptag_mask": "0xFFFF", + }, + "pi_chk": "APPTAG,GUARD,REFTAG", + "bs_low": BS_LOW, + "bs_high": BS_HIGH, + "test_class": DifDixTest, + }, +# +# Test apptag errors. +# + { + # Read workload with variable IO sizes + # pi_act=0 + # trigger an apptag error + "test_id": 301, + "fio_opts": { + "rw": 'read', + "number_ios": NUMBER_IOS, + "output-format": "json", + "pi_act": 0, + "apptag": "0x0888", + "apptag_mask": "0xFFFF", + }, + "pi_chk": "APPTAG,GUARD,REFTAG", + "bs_low": BS_LOW, + "bs_high": BS_HIGH, + "success": SUCCESS_NONZERO, + "test_class": DifDixTest, + }, + { + # Read workload with variable IO sizes + # pi_act=1 + # trigger an apptag error + "test_id": 302, + "fio_opts": { + "rw": 'read', + "number_ios": NUMBER_IOS, + "output-format": "json", + "pi_act": 1, + "apptag": "0x0888", + "apptag_mask": "0xFFFF", + }, + "pi_chk": "APPTAG,GUARD,REFTAG", + "bs_low": BS_LOW, + "bs_high": BS_HIGH, + "success": SUCCESS_NONZERO, + "test_class": DifDixTest, + }, + { + # Read workload with variable IO sizes + # pi_act=0 + # trigger an apptag error + # same as above but with pi_chk=APPTAG only + "test_id": 303, + "fio_opts": { + "rw": 'read', + "number_ios": NUMBER_IOS, + "output-format": "json", + "pi_act": 0, + "apptag": "0x0888", + "apptag_mask": "0xFFFF", + }, + "pi_chk": "APPTAG", + "bs_low": BS_LOW, + "bs_high": BS_HIGH, + "success": SUCCESS_NONZERO, + "test_class": DifDixTest, + }, + { + # Read workload with variable IO sizes + # pi_act=1 + # trigger an apptag error + # same as above but with pi_chk=APPTAG only + "test_id": 304, + "fio_opts": { + "rw": 'read', + "number_ios": NUMBER_IOS, + "output-format": "json", + "pi_act": 1, + "apptag": "0x0888", + "apptag_mask": "0xFFFF", + }, + "pi_chk": "APPTAG", + "bs_low": BS_LOW, + "bs_high": BS_HIGH, + "success": SUCCESS_NONZERO, + "test_class": DifDixTest, + }, + { + # Read workload with variable IO sizes + # pi_act=0 + # this case would trigger an apptag error, but pi_chk says to check + # only the Guard PI and reftag, so there should be no error + "test_id": 305, + "fio_opts": { + "rw": 'read', + "number_ios": NUMBER_IOS, + "output-format": "json", + "pi_act": 0, + "apptag": "0x0888", + "apptag_mask": "0xFFFF", + }, + "pi_chk": "GUARD,REFTAG", + "bs_low": BS_LOW, + "bs_high": BS_HIGH, + "test_class": DifDixTest, + }, + { + # Read workload with variable IO sizes + # pi_act=1 + # this case would trigger an apptag error, but pi_chk says to check + # only the Guard PI and reftag, so there should be no error + "test_id": 306, + "fio_opts": { + "rw": 'read', + "number_ios": NUMBER_IOS, + "output-format": "json", + "pi_act": 1, + "apptag": "0x0888", + "apptag_mask": "0xFFFF", + }, + "pi_chk": "GUARD,REFTAG", + "bs_low": BS_LOW, + "bs_high": BS_HIGH, + "test_class": DifDixTest, + }, + { + # Read workload with variable IO sizes + # pi_act=0 + # this case would trigger an apptag error, but pi_chk says to check + # only the Guard PI, so there should be no error + "test_id": 307, + "fio_opts": { + "rw": 'read', + "number_ios": NUMBER_IOS, + "output-format": "json", + "pi_act": 0, + "apptag": "0x0888", + "apptag_mask": "0xFFFF", + }, + "pi_chk": "GUARD", + "bs_low": BS_LOW, + "bs_high": BS_HIGH, + "test_class": DifDixTest, + }, + { + # Read workload with variable IO sizes + # pi_act=1 + # this case would trigger an apptag error, but pi_chk says to check + # only the Guard PI, so there should be no error + "test_id": 308, + "fio_opts": { + "rw": 'read', + "number_ios": NUMBER_IOS, + "output-format": "json", + "pi_act": 1, + "apptag": "0x0888", + "apptag_mask": "0xFFFF", + }, + "pi_chk": "GUARD", + "bs_low": BS_LOW, + "bs_high": BS_HIGH, + "test_class": DifDixTest, + }, + { + # Read workload with variable IO sizes + # pi_act=0 + # this case would trigger an apptag error, but pi_chk says to check + # only the reftag, so there should be no error + # This case will be skipped when the device is formatted with Type 3 PI + # since Type 3 PI ignores the reftag + "test_id": 309, + "fio_opts": { + "rw": 'read', + "number_ios": NUMBER_IOS, + "output-format": "json", + "pi_act": 0, + "apptag": "0x0888", + "apptag_mask": "0xFFFF", + }, + "pi_chk": "REFTAG", + "bs_low": BS_LOW, + "bs_high": BS_HIGH, + "skip": "type3", + "test_class": DifDixTest, + }, + { + # Read workload with variable IO sizes + # pi_act=1 + # this case would trigger an apptag error, but pi_chk says to check + # only the reftag, so there should be no error + # This case will be skipped when the device is formatted with Type 3 PI + # since Type 3 PI ignores the reftag + "test_id": 310, + "fio_opts": { + "rw": 'read', + "number_ios": NUMBER_IOS, + "output-format": "json", + "pi_act": 1, + "apptag": "0x0888", + "apptag_mask": "0xFFFF", + }, + "pi_chk": "REFTAG", + "bs_low": BS_LOW, + "bs_high": BS_HIGH, + "skip": "type3", + "test_class": DifDixTest, + }, + { + # Read workload with variable IO sizes + # pi_act=0 + # use apptag mask to ignore apptag mismatch + "test_id": 311, + "fio_opts": { + "rw": 'read', + "number_ios": NUMBER_IOS, + "output-format": "json", + "pi_act": 0, + "apptag": "0x0888", + "apptag_mask": "0x0FFF", + }, + "pi_chk": "APPTAG,GUARD,REFTAG", + "bs_low": BS_LOW, + "bs_high": BS_HIGH, + "test_class": DifDixTest, + }, + { + # Read workload with variable IO sizes + # pi_act=1 + # use apptag mask to ignore apptag mismatch + "test_id": 312, + "fio_opts": { + "rw": 'read', + "number_ios": NUMBER_IOS, + "output-format": "json", + "pi_act": 1, + "apptag": "0x0888", + "apptag_mask": "0x0FFF", + }, + "pi_chk": "APPTAG,GUARD,REFTAG", + "bs_low": BS_LOW, + "bs_high": BS_HIGH, + "test_class": DifDixTest, + }, + { + # Read workload with variable IO sizes + # pi_act=0 + # use apptag mask to ignore apptag mismatch + "test_id": 313, + "fio_opts": { + "rw": 'read', + "number_ios": NUMBER_IOS, + "output-format": "json", + "pi_act": 0, + "apptag": "0xF888", + "apptag_mask": "0x0FFF", + }, + "pi_chk": "APPTAG,GUARD,REFTAG", + "bs_low": BS_LOW, + "bs_high": BS_HIGH, + "test_class": DifDixTest, + }, + { + # Read workload with variable IO sizes + # pi_act=1 + # use apptag mask to ignore apptag mismatch + "test_id": 314, + "fio_opts": { + "rw": 'read', + "number_ios": NUMBER_IOS, + "output-format": "json", + "pi_act": 1, + "apptag": "0xF888", + "apptag_mask": "0x0FFF", + }, + "pi_chk": "APPTAG,GUARD,REFTAG", + "bs_low": BS_LOW, + "bs_high": BS_HIGH, + "test_class": DifDixTest, + }, + { + # Write workload with fixed large IO sizes + # Set apptag=0xFFFF to disable all checking for Type 1 and 2 + # pi_act=1 + "test_id": 315, + "fio_opts": { + "rw": 'write', + "number_ios": NUMBER_IOS, + "output-format": "json", + "apptag": "0xFFFF", + "apptag_mask": "0xFFFF", + "pi_act": 1, + }, + "pi_chk": "APPTAG,GUARD,REFTAG", + "bs_low": BS_HIGH, + "bs_high": BS_HIGH, + "skip": "type3", + "test_class": DifDixTest, + }, + { + # Read workload with variable IO sizes + # pi_act=0 + # Data was written with apptag=0xFFFF + # Reading the data back should disable all checking for Type 1 and 2 + "test_id": 316, + "fio_opts": { + "rw": 'read', + "number_ios": NUMBER_IOS, + "output-format": "json", + "pi_act": 0, + "apptag": "0x0101", + "apptag_mask": "0xFFFF", + }, + "pi_chk": "APPTAG,GUARD,REFTAG", + "bs_low": BS_LOW, + "bs_high": BS_HIGH, + "skip": "type3", + "test_class": DifDixTest, + }, + { + # Read workload with variable IO sizes + # pi_act=1 + # Data was written with apptag=0xFFFF + # Reading the data back should disable all checking for Type 1 and 2 + "test_id": 317, + "fio_opts": { + "rw": 'read', + "number_ios": NUMBER_IOS, + "output-format": "json", + "pi_act": 1, + "apptag": "0x0000", + "apptag_mask": "0xFFFF", + }, + "pi_chk": "APPTAG,GUARD,REFTAG", + "bs_low": BS_LOW, + "bs_high": BS_HIGH, + "skip": "type3", + "test_class": DifDixTest, + }, +# +# Error cases related to block size and metadata size +# + { + # Use a min block size that is not a multiple of lba/elba size to + # trigger an error. + "test_id": 401, + "fio_opts": { + "rw": 'read', + "number_ios": NUMBER_IOS, + "output-format": "json", + "pi_act": 0, + "apptag": "0x8888", + "apptag_mask": "0x0FFF", + }, + "pi_chk": "APPTAG,GUARD,REFTAG", + "bs_low": BS_LOW+0.5, + "bs_high": BS_HIGH, + "success": SUCCESS_NONZERO, + "test_class": DifDixTest, + }, + { + # Use metadata size that is too small + "test_id": 402, + "fio_opts": { + "rw": 'read', + "number_ios": NUMBER_IOS, + "output-format": "json", + "pi_act": 0, + "apptag": "0x8888", + "apptag_mask": "0x0FFF", + }, + "pi_chk": "APPTAG,GUARD,REFTAG", + "bs_low": BS_LOW, + "bs_high": BS_HIGH, + "mdsize_adjustment": -1, + "success": SUCCESS_NONZERO, + "skip": "elba", + "test_class": DifDixTest, + }, + { + # Read workload with variable IO sizes + # pi_act=0 + # Should still work even if metadata size is too large + "test_id": 403, + "fio_opts": { + "rw": 'read', + "number_ios": NUMBER_IOS, + "output-format": "json", + "pi_act": 0, + "apptag": "0x8888", + "apptag_mask": "0x0FFF", + }, + "pi_chk": "APPTAG,GUARD,REFTAG", + "bs_low": BS_LOW, + "bs_high": BS_HIGH, + "mdsize_adjustment": 1, + "test_class": DifDixTest, + }, +] + + +def parse_args(): + """Parse command-line arguments.""" + + parser = argparse.ArgumentParser() + parser.add_argument('-d', '--debug', help='Enable debug messages', action='store_true') + parser.add_argument('-f', '--fio', help='path to file executable (e.g., ./fio)') + parser.add_argument('-a', '--artifact-root', help='artifact root directory') + parser.add_argument('-s', '--skip', nargs='+', type=int, + help='list of test(s) to skip') + parser.add_argument('-o', '--run-only', nargs='+', type=int, + help='list of test(s) to run, skipping all others') + parser.add_argument('--dut', help='target NVMe character device to test ' + '(e.g., /dev/ng0n1). WARNING: THIS IS A DESTRUCTIVE TEST', required=True) + parser.add_argument('-l', '--lbaf', nargs='+', type=int, + help='list of lba formats to test') + args = parser.parse_args() + + return args + + +def get_lbafs(args): + """ + Determine which LBA formats to use. Use either the ones specified on the + command line or if none are specified query the device and use all lba + formats with metadata. + """ + lbaf_list = [] + id_ns_cmd = f"sudo nvme id-ns --output-format=json {args.dut}".split(' ') + id_ns_output = subprocess.check_output(id_ns_cmd) + lbafs = json.loads(id_ns_output)['lbafs'] + if args.lbaf: + for lbaf in args.lbaf: + lbaf_list.append({'lbaf': lbaf, 'ds': 2 ** lbafs[lbaf]['ds'], + 'ms': lbafs[lbaf]['ms'], }) + if lbafs[lbaf]['ms'] == 0: + print(f'Error: lbaf {lbaf} has metadata size zero') + sys.exit(1) + else: + for lbaf_num, lbaf in enumerate(lbafs): + if lbaf['ms'] != 0: + lbaf_list.append({'lbaf': lbaf_num, 'ds': 2 ** lbaf['ds'], + 'ms': lbaf['ms'], }) + + return lbaf_list + + +def get_guard_pi(lbaf_list, args): + """ + Find out how many bits of guard protection information are associated with + each lbaf to be used. If this is not available assume 16-bit guard pi. + Also record the bytes of protection information associated with the number + of guard PI bits. + """ + nvm_id_ns_cmd = f"sudo nvme nvm-id-ns --output-format=json {args.dut}".split(' ') + try: + nvm_id_ns_output = subprocess.check_output(nvm_id_ns_cmd) + except subprocess.CalledProcessError: + print(f"Non-zero return code from {' '.join(nvm_id_ns_cmd)}; " \ + "assuming all lbafs use 16b Guard Protection Information") + for lbaf in lbaf_list: + lbaf['guard_pi_bits'] = 16 + else: + elbafs = json.loads(nvm_id_ns_output)['elbafs'] + for elbaf_num, elbaf in enumerate(elbafs): + for lbaf in lbaf_list: + if lbaf['lbaf'] == elbaf_num: + lbaf['guard_pi_bits'] = 16 << elbaf['pif'] + + # For 16b Guard Protection Information, the PI requires 8 bytes + # For 32b and 64b Guard PI, the PI requires 16 bytes + for lbaf in lbaf_list: + if lbaf['guard_pi_bits'] == 16: + lbaf['pi_bytes'] = 8 + else: + lbaf['pi_bytes'] = 16 + + +def get_capabilities(args): + """ + Determine what end-to-end data protection features the device supports. + """ + caps = { 'pil': [], 'pitype': [], 'elba': [] } + id_ns_cmd = f"sudo nvme id-ns --output-format=json {args.dut}".split(' ') + id_ns_output = subprocess.check_output(id_ns_cmd) + id_ns_json = json.loads(id_ns_output) + + mc = id_ns_json['mc'] + if mc & 1: + caps['elba'].append(1) + if mc & 2: + caps['elba'].append(0) + + dpc = id_ns_json['dpc'] + if dpc & 1: + caps['pitype'].append(1) + if dpc & 2: + caps['pitype'].append(2) + if dpc & 4: + caps['pitype'].append(3) + if dpc & 8: + caps['pil'].append(1) + if dpc & 16: + caps['pil'].append(0) + + for _, value in caps.items(): + if len(value) == 0: + logging.error("One or more end-to-end data protection features unsupported: %s", caps) + sys.exit(-1) + + return caps + + +def format_device(args, lbaf, pitype, pil, elba): + """ + Format device using specified lba format with specified pitype, pil, and + elba values. + """ + + format_cmd = f"sudo nvme format {args.dut} --lbaf={lbaf['lbaf']} " \ + f"--pi={pitype} --pil={pil} --ms={elba} --force" + logging.debug("Format command: %s", format_cmd) + format_cmd = format_cmd.split(' ') + format_cmd_result = subprocess.run(format_cmd, capture_output=True, check=False, + encoding=locale.getpreferredencoding()) + + # Sometimes nvme-cli may format the device successfully but fail to + # rescan the namespaces after the format. Continue if this happens but + # abort if some other error occurs. + if format_cmd_result.returncode != 0: + if 'failed to rescan namespaces' not in format_cmd_result.stderr \ + or 'Success formatting namespace' not in format_cmd_result.stdout: + logging.error(format_cmd_result.stdout) + logging.error(format_cmd_result.stderr) + print("Unable to format device; skipping this configuration") + return False + + logging.debug(format_cmd_result.stdout) + return True + + +def difdix_test(test_env, args, lbaf, pitype, elba): + """ + Adjust test arguments based on values of lbaf, pitype, and elba. Then run + the tests. + """ + for test in TEST_LIST: + test['force_skip'] = False + + blocksize = lbaf['ds'] + # Set fio blocksize parameter at runtime + # If we formatted the device in extended LBA mode (e.g., 520-byte + # sectors), we usually need to add the lba data size and metadata size + # together for fio's bs parameter. However, if pi_act == 1 and the + # device is formatted so that the metadata is the same size as the PI, + # then the device will take care of everything and the application + # should just use regular power of 2 lba data size even when the device + # is in extended lba mode. + if elba: + if not test['fio_opts']['pi_act'] or lbaf['ms'] != lbaf['pi_bytes']: + blocksize += lbaf['ms'] + test['fio_opts']['md_per_io_size'] = 0 + else: + # If we are using a separate buffer for metadata, fio doesn't need to + # do anything when pi_act==1 and protection information size is equal to + # metadata size since the device is taking care of it all. If either of + # the two conditions do not hold, then we do need to allocate a + # separate metadata buffer. + if test['fio_opts']['pi_act'] and lbaf['ms'] == lbaf['pi_bytes']: + test['fio_opts']['md_per_io_size'] = 0 + else: + test['fio_opts']['md_per_io_size'] = lbaf['ms'] * test['bs_high'] + + test['fio_opts']['bsrange'] = f"{blocksize * test['bs_low']}-{blocksize * test['bs_high']}" + if 'mdsize_adjustment' in test: + test['fio_opts']['md_per_io_size'] += test['mdsize_adjustment'] + + # Set fio pi_chk parameter at runtime. If the device is formatted + # with Type 3 protection information, this means that the reference + # tag is not checked and I/O commands may throw an error if they + # are submitted with the REFTAG bit set in pi_chk. Make sure fio + # does not set pi_chk's REFTAG bit if the device is formatted with + # Type 3 PI. + if 'pi_chk' in test: + if pitype == 3 and 'REFTAG' in test['pi_chk']: + test['fio_opts']['pi_chk'] = test['pi_chk'].replace('REFTAG','') + logging.debug("Type 3 PI: dropping REFTAG bit") + else: + test['fio_opts']['pi_chk'] = test['pi_chk'] + + if 'skip' in test: + if pitype == 3 and 'type3' in test['skip']: + test['force_skip'] = True + logging.debug("Type 3 PI: skipping test case") + if elba and 'elba' in test['skip']: + test['force_skip'] = True + logging.debug("extended lba format: skipping test case") + + logging.debug("Test %d: pi_act=%d, bsrange=%s, md_per_io_size=%d", test['test_id'], + test['fio_opts']['pi_act'], test['fio_opts']['bsrange'], + test['fio_opts']['md_per_io_size']) + + return run_fio_tests(TEST_LIST, test_env, args) + + +def main(): + """ + Run tests using fio's io_uring_cmd ioengine to exercise end-to-end data + protection capabilities. + """ + + args = parse_args() + + if args.debug: + logging.basicConfig(level=logging.DEBUG) + else: + logging.basicConfig(level=logging.INFO) + + artifact_root = args.artifact_root if args.artifact_root else \ + f"nvmept_pi-test-{time.strftime('%Y%m%d-%H%M%S')}" + os.mkdir(artifact_root) + print(f"Artifact directory is {artifact_root}") + + if args.fio: + fio_path = str(Path(args.fio).absolute()) + else: + fio_path = 'fio' + print(f"fio path is {fio_path}") + + lbaf_list = get_lbafs(args) + get_guard_pi(lbaf_list, args) + caps = get_capabilities(args) + print("Device capabilities:", caps) + + for test in TEST_LIST: + test['fio_opts']['filename'] = args.dut + + test_env = { + 'fio_path': fio_path, + 'fio_root': str(Path(__file__).absolute().parent.parent), + 'artifact_root': artifact_root, + 'basename': 'nvmept_pi', + } + + total = { 'passed': 0, 'failed': 0, 'skipped': 0 } + + try: + for lbaf, pil, pitype, elba in itertools.product(lbaf_list, caps['pil'], caps['pitype'], + caps['elba']): + print(f"\nlbaf: {lbaf}, pil: {pil}, pitype: {pitype}, elba: {elba}") + + if not format_device(args, lbaf, pitype, pil, elba): + continue + + test_env['artifact_root'] = \ + os.path.join(artifact_root, f"lbaf{lbaf['lbaf']}pil{pil}pitype{pitype}" \ + f"elba{elba}") + os.mkdir(test_env['artifact_root']) + + passed, failed, skipped = difdix_test(test_env, args, lbaf, pitype, elba) + + total['passed'] += passed + total['failed'] += failed + total['skipped'] += skipped + except KeyboardInterrupt: + pass + + print(f"\n\n{total['passed']} test(s) passed, {total['failed']} failed, " \ + f"{total['skipped']} skipped") + sys.exit(total['failed']) + + +if __name__ == '__main__': + main() From 6795954bde09c8697e0accb865b4f438d62c601f Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Mon, 14 Aug 2023 19:59:20 -0600 Subject: [PATCH 0593/1097] engines/io_uring: fix leak of 'ld' in error path Not really important as we're exiting anyway, but this silences some of the static checkers that like to complain about this sort of thing. Signed-off-by: Jens Axboe --- engines/io_uring.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/engines/io_uring.c b/engines/io_uring.c index 7ac7c755b2..6cdf1b4fe4 100644 --- a/engines/io_uring.c +++ b/engines/io_uring.c @@ -1165,10 +1165,13 @@ static int fio_ioring_init(struct thread_data *td) md_size += td->o.mem_align - page_size; if (td->o.mem_type == MEM_MALLOC) { ld->md_buf = malloc(md_size); - if (!ld->md_buf) + if (!ld->md_buf) { + free(ld); return 1; + } } else { log_err("fio: Only iomem=malloc or mem=malloc is supported\n"); + free(ld); return 1; } } From b311162c37a2867873e1222ce6b5f38c88be4d80 Mon Sep 17 00:00:00 2001 From: Ankit Kumar Date: Wed, 16 Aug 2023 15:16:16 +0530 Subject: [PATCH 0594/1097] examples: add example and fiograph for protection information options Add missing io_uring_cmd ioengine options to fiograph config. Add two example job files for the protection information options. These include one for DIF i.e. extended LBA data size, and the other for DIX i.e. separate metadata buffer case. Add the corresponding fiograph diagram for these. Signed-off-by: Ankit Kumar Link: https://lore.kernel.org/r/20230816094616.132240-1-ankit.kumar@samsung.com Signed-off-by: Vincent Fu --- examples/uring-cmd-pi-ext.fio | 31 +++++++++++++++++++++++++++++++ examples/uring-cmd-pi-ext.png | Bin 0 -> 81014 bytes examples/uring-cmd-pi-sb.fio | 32 ++++++++++++++++++++++++++++++++ examples/uring-cmd-pi-sb.png | Bin 0 -> 87357 bytes tools/fiograph/fiograph.conf | 2 +- 5 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 examples/uring-cmd-pi-ext.fio create mode 100644 examples/uring-cmd-pi-ext.png create mode 100644 examples/uring-cmd-pi-sb.fio create mode 100644 examples/uring-cmd-pi-sb.png diff --git a/examples/uring-cmd-pi-ext.fio b/examples/uring-cmd-pi-ext.fio new file mode 100644 index 0000000000..e22ec06243 --- /dev/null +++ b/examples/uring-cmd-pi-ext.fio @@ -0,0 +1,31 @@ +# Protection information test with io_uring_cmd I/O engine for nvme-ns generic +# character device. +# +# This requires nvme device to be formatted with extended LBA data size and +# protection information enabled. This can be done with nvme-cli utility. +# Replace bs below with the correct extended LBA size. +# +# First we sequentially write to the device, without protection information +# action being set. FIO will generate and send necessary protection +# information data as per the protection information check option. Later on we +# sequentially read and verify the device returned protection information data. +# +[global] +filename=/dev/ng0n1 +ioengine=io_uring_cmd +cmd_type=nvme +size=1G +iodepth=32 +bs=4160 +pi_act=0 +pi_chk=GUARD,APPTAG,REFTAG +apptag=0x0888 +apptag_mask=0xFFFF +thread=1 +stonewall=1 + +[write] +rw=write + +[read] +rw=read diff --git a/examples/uring-cmd-pi-ext.png b/examples/uring-cmd-pi-ext.png new file mode 100644 index 0000000000000000000000000000000000000000..a102fc1a7ac4165534302611939934a2ff03b56b GIT binary patch literal 81014 zcmc$`1yG#Z_bu2+aCZrwK#&j!5Q2w5fB?bWB|w4(cXx*n+}+*XAvh$sySqDcI=}hP zd-dv#)y&lNty?#@Azz zEUEus%&Ae)WIMsGtYEcd{xhL5=W0{YLet$KKF82l)iA>lj~Rwx)sJM(s<; zIWfX7Ga>{2qp9F2lTF?&u92(3q~CA0c()>vyeK^a{=LrI-QYAuKM4POh5CP{#D=~i z$#rqW{=JnY8V8T_@4X#tTF?Ldnv>7}jbGz%%#}{{6Q-8En`v^aEa`MIr^iF_@%2p? z^2cD|7p$!9 zivKnqO;Av9d$zg|dNuZmqus4kcLMF!;|^5gUNm&{lj9YkP%CO`ry8OiZU{AX0hhkM zzRSbKR0D>$aaz8cayU63j`rN_>@Euq_$lKsh&)Hc=%`HSkZ3>WZz5iYGBwAM6u$1W z$x`i7%f(o2w?MGc4BNd)mbY)cgsBBpRN_RM;Sez(ke2&XiBSE-cV->eCu=LKtFZYB zK}yAHx{FPYuU@?x4S2X(au@yagT>U$j4E1uWoIW4T%~<(PQCbxg~btxJNftT-{S<_ z`CVLHsbU9o&CSE?4rX=tr^~N56D?#kG&IcS*Lp&|Gc#$j)oQ&+GpRshgCzO=qP+C*rqWN&T9B!e}-akH`7(CDh+dV&Nfd>WN+yb{~ zcV&*~q@)lo>&>>_UPMDfLlzDW-`H5fj*gDw<#xF8l*-D=p6VAk$T+yT*jQL;O79H% z@#^a9^EG>?rU-EY;fIHZr9+J7YRn?UUi=;!!XNnf@nb<#O@4lU&yA|8s>9R675IA) zMn*;=Qqs=8K4fo1%pMRx)xr_KfPmf4l;CON5)#ruu-tKQaJux*wnhjCv|UaROlPa|dr~_m3YDLUii(2Y(9xkZovHXF zCM`|)_U&6Ad`2ZCh)*()y%Mo~Fu`k|f!H^wnB=@z4K_Q}F^vrkDBumkipok~G9J5t zjm5>qkDosWD&5K7IHRMX;ejCeHrgMdxVgFM-84C#6+G}WxGPJ8w`rzlW&)S=U0j-e zYz>pOFDz)@hk&=cZ=W8oTD$keUf`^ZrVDvdB7Ia-OIY6Aq;)yzCe&LHc)FJ_ep6mv z&Jniv*%cht2o-a1QtXC@5;>o7a&m$j(AA|%;~n|9>~sWd%-SdR3JI7QCXB{E2mo-9f^?yXUi74{+E& z8xbHJ0DS1oVIzE=+>Fw*w@2~mtC&6 zF$L~r4jeh^wdCY*GBPr{8io=$aJ4xt7ByU4ua9-Bvzwt0aID_i2=c+s?p=f3L6Dc1 z7xx=)vsZn6eeazwi>_N%w~TdkAolk5Cw#HKAhTdVAIk_OAz`djt?Y!WDufcr>$+jJ z*+4OwCU(HYRnzxl`eln%$0a)gLip3vi%78)H4NUvsCT#W_-t%!C)JX)@;osqDO#m9 zi~{-=kM}ov8SnLb(8`mKhDn(sV6BC1%mvz9&n_^74EtpU3PgU+d zdiC;UwyYjC^&fj^t|yV%SkKBRc=t6wOo72H;73aT;-c1p>ju%;>8bI8;l@l&Qc}_^ zqLS$BwzT5u*;!$;pSQPA^Q@hXje>Kl)rL5dm&}HAqus&yuAjeu=BHqS)w^to*n**c zSZVUiji8_)7Sm~#>FMcZfu|d3KBvqZ#89vdU0vO3+rR)Z9%z~4Nb7$JD2{_HG}soF zcRCityjJh%?G<%#zq>R$v6Yev>nYNz2TwZGC#%UFY<*FX?YbKXnKfj1YQa4d5D=&| z*nAhVd*)$xD<2(npycv8QjF!>Xj*R1P1at0eSQ1Lh;)(qMCbh7_13fPpb&xm+jJlwoWHwhr#m0vF`Sa&E z7F}3SDOk;Bs}S~tK7mJIU@}=G2sY#4QfoLm>AQdp5Xj@x(*pZD9{WQd@VSs>PgiGUgb>4J z%OOL`Dk?sLWZrEJ4#|(T9(0YmAA-*t@ng}HT{M-UE2Q{hx!eYm#P3Q1(uGR16USRN zHW&zvXxR8{9LVM3B-W%P)YPsK;7Ep$@hJVUvm*!z4fQ2>t%g)-IE0fUomx`ENb@)w ze-iwrW%SjXr~JCcZ?+dsSBIJh<@MHED8YnpN=~QgQeCKP9_B1skvsj+aX~78CiM2s zP9c>>yW-IrGxhSv&7nk-3$Co}Y-CgdCM<5d{a3#?tlGP}gn!uE6D~S#k*zE?J4^5X zUF{C`9m^Co`9(@Xf&wC;m~vLz`f!l{aB_Mo$J0A7P$=S>-^8=sOX1FBb1@q4Uuh7VORY0(D9xqDW z`O2j^$n@jm<2!=z8GV>F8=yPd3nZ-N+0�dNir9!5k&WV&2mwAsG-1vPbs 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 HeewSQ#6pQ> literal 0 HcmV?d00001 diff --git a/examples/uring-cmd-pi-sb.fio b/examples/uring-cmd-pi-sb.fio new file mode 100644 index 0000000000..b201a7ce00 --- /dev/null +++ b/examples/uring-cmd-pi-sb.fio @@ -0,0 +1,32 @@ +# Protection information test with io_uring_cmd I/O engine for nvme-ns generic +# character device. +# +# This requires nvme device to be formatted with separate metadata buffer and +# protection information enabled. This can be done with nvme-cli utility. +# Replace md_per_io_size as per the required metadata buffer size for each IO. +# +# First we sequentially write to the device, without protection information +# action being set. FIO will generate and send necessary protection +# information data as per the protection information check option. Later on we +# sequentially read and verify the device returned protection information data. +# +[global] +filename=/dev/ng0n1 +ioengine=io_uring_cmd +cmd_type=nvme +size=1G +iodepth=32 +bs=4096 +md_per_io_size=64 +pi_act=0 +pi_chk=GUARD,APPTAG,REFTAG +apptag=0x0888 +apptag_mask=0xFFFF +thread=1 +stonewall=1 + +[write] +rw=write + +[read] +rw=read diff --git a/examples/uring-cmd-pi-sb.png b/examples/uring-cmd-pi-sb.png new file mode 100644 index 0000000000000000000000000000000000000000..dcdda8cdab7fd9f46f48d3033994a282e6a1e0bf GIT binary patch literal 87357 zcmc$G1yogi^yQ;dx)Bgil#meV5_wW8NOz+k4bm;rDIzH$0@5YjNU9)+Qqo9DOE+_# z-@m5*GqcvLnX4|o@4d&n_x|FXefHkxhCNb}yM#}Nk3b+UDaa#L5eSUu@J|gF8(tyN zr&)#{*rxa8kcbP^pSMl9aR|f}gaT4p-92?}(nDW;;T&hHpO=@H@Pq@8B>Shy8tx5I645-|C5isc(y&S5Q=1p8W}Fu+hfpt@ik3MtI(*% ze%j8V;rH*~xWvSA-O&stCMIZMNOT+=9Ot!B($dE5aHV}|B=TX1!WB{#eoaHe=z%Ob z8Me6DoJ7OB3JPJ(GFDW_d#gCoBs1&lljbKj;r&*t!^O3(>nTd(jXo!W=;-KWk9(*l zY8^4&2-@CNRKzbaYKfNgIi6W9ZIRX0O;I{fKq8|e{(e6dHFXFE4$<4ZJltuY1BUa% zIbxp>q=02f#?bac&yd2Db!k)Z?;pACA3vfI5fL3OXT=y>TNf_jhK1&pBw><~3Yhu& z`nn%2Br?YS+&^Pnw8n}5)%`XkiCmU*0*s!hl`wdjB_Sr5LM4=blaR- zSr@dQDyo`n_BkoD8seyY`UAVGtBWKI2_rdDa4@80gofB(AJ47gzIBU?ob7>sLhEyGw|)MM$J4Y+W;ao*vU)x!=aR7+%^>!z$;&P2%^R4;OuZVr!_%Gqp>g$* z((wKQ!SCO{^Lp*u93CFFPE1@@&8Lu*l%(N*LI`&f^6S?xx*7@3-DNYg!q3&V<5$SS z9zA;WmYaZp;Kxdc{MCTuMd*COA|s3T9BZ8x!e{2^uOdhyBO?QeB@gd>>g}cY@Zm!sC8s7XB2ZcCta7YY zugdD0e!a7T`_|0!Z#nm=UFr%83(LHZoS1odlt;_WLh2{FyX9_ha#|0btDot=c=00W z|BH6Q4fG*ZQ7$R-F~tlDVH9yX*-T>(yu;)ZKM(*fNW29qZDJ zJHGn7U+SXKbFw3hd}skipsc(+e}wNk3rpG&Psj_{pcw5T#3S0iTQg{|LRJQY6B84U zn{sk;j(wb*J{+H=iMctTzWPIMZf=)gUtix)we6E9?*))$rhcD4e-4d~)*QJ+NH{nq z$;O5qcU`%pdL80f;k2H%cHVSDU7a)+m32JE9YsaOti4>(`0?UX!+iB$FmVEQT5rxI z4kn(xRLnuB$5>lVH%lyjeG_tVx+GQWb->-1E)ntc$9n-?8<+T3uL6GLDi}xVgD6*=5zAR3q+?VD6pZ5HpIq(kDOATsnK7n`_Ob^>8%3 z8zNuS@K%inH?8DAL zJo1^1jc7Cs?3unaG0(7KQH=~*ZI_IAsTYHT>S>!9p`q!Ks5r@5kdmsdIT^?$5G#MR z9(O71^5x676AHVu;aCzZG_>vM?=ST--W#Mj%U4oSQHeTnaCWwFIAN8G?r8ElAfcoT za#s4W8+Gm-}auQc^~%1%^StewpG1y!}(K zY}Au*a{vO4%jC{dzqO5?swz6%JQ9gq{=kW#%at0+pRijvV2Q<*2`2O~O^q3!K07%u zF*ipo#NFRNa5kF`7>Sr9BO&w^)|86RS4o!p>zeu1 z8F7Q1T^dqemya8(Y^3EQoA?k+Eng#}2G{j~H*e?>Lwr&m+gG=@x6AA%Md#<|5eV5R zx*^L95mHhK>A3!TGDve{ICl`-7apt}k_QhS1VVJcd$!n1`s8a$4qpT-f>~11@Nm0_ zr|Ju*VY~%(alYYnvyZiYjqyixlh3cN4V9d%RrC+qUYwt7w!z-UqNk_-*wKO9+TEoP zw41n5X+5H3;0GDpMU;q^mbT4nv+4Tb$q50(FkS%xN^x=VKz6nFxNaL09yS>XTska~ zwEVJ#`dm~5e0+REb~`mKXX{~SfA;r>N4wzWHGT3$F_x}pc(XXKk7@Ao^OI+*=95rR zTs>Vix+oa3(4cR*IB(#!pAye}w$v986%}=bDgw*P%PSKyY?~aDPj)(A<%$`P75SSh zxOW{L%XyubAE6@r;gZzF(P)yj7tzA}JT9kZ5lOxCZx(G`U3^Hw0a;mDyyl-sOg5*Q z0#x&hG^WEs<(JMm$uJpC=jD({?2ki38WL}or<%Mjxe8D3t&Ru;C$n9<_O#bj5sBQT z5Nn-%i$rG6eu`yvaB&%;qe@CoFY(QmnLH9n7fi$uzS(lYh=qkkbITleZEX#e ziYFS}i0zy9sJIOqLq`m~9!{IAe4VI!CJ-Ee?jI6@(-qB7n2SPuE5QeFO4LD1G(`^I z2Zy;l>N5VmM#2Ud1$AHtLXbcmv1lf!)52kX=l{#cBKH^&5HyTDJw@^H@pl;@f{+R@ zTUb~?pc?e@GW@>}bN_SC+-#&GxHt?6Wg({S&p= zuaf!Fsc(3kMB9JaFwr+qA*|H%Cs`=&>l6^T?PDA&GYY|Ix-$lj5sa#U8W2~g51 z7#Ywy?GhH6Qnow729k4OEKqFj+nJe4j`A zLxaqHDH<<<$F22e#Uk#do2{uLKONk7enQ$jijtIe&j2X%0Cg?$<)1L2UfBu0$iYIi?GF3v_wp?(4YEui>O7AYyw|4E0+ub= z+uGAI=e_xMY5xAgBkx9bDjwIiH};D&>opb3zhb(O$ghSYC7p-!aq6kUCefed#e70! zl==Q%7u$8Y$eJl9>HhS4+O@p*r)Nn>V~Mb^D9!d(9(Zqu-Hc|8|1EaeQ;`oA)h*uG z*lONe=;wc82qm|lhc46ZZucA=j!$owKZ(MacOu%mVtcxy4BNtP>b3E-BxmD}5)~Jh zdpj=aK*dU$+lQAK_5cX0hTLxKQkcRXGQT5eUoF!eokJsX%V;{21ak0)8ZG0l$dJQb zHTwC*Z6n|B9sB{IpYCMm(9X>((6TA_%_4ct9Vqazp7f_*l9G~w42y-rIYeQ315L+P zjFFL;mqpF_)JBcWz3O^qm)!wVG zti0{+;gN47WH<56EE`$R8pqD(?!bIKl=4yGBIsiLVGm}QMikMrxC%= z&re9hi#R_&N1a?j{KA`*+wiu`Q}z)5Z><09m6Wc%RkFrivQ4b3?dCQ%sARh;M);0x zq}+`COV7PpmmbXK=F8X*IsQ3~XFUx!_rfvJ3tWW5T-PN+dcg;U-bdRD?q_==nd%$^ z4Q}@4=H|w~zvsBmMF}AgP?WvwSrDjha9P!yX!fBu^4X(QRa47+bb}Z2=-OD-{j4b{ zC{V$@NUui8+Tfl(ly@kwmuO*bKJ!Casxx0L_ho7-VUB7TBxciAf3ze)TRIzC5du~T zAKl7FKARR#==kxj3J~F3W4T4r*(u8_wDjnarzj*eB0M}i2PY@h#G8hVG^H))qKKK^ zWd2W|J|Pg7#oZqnT$hw&OixeOuCZevBPWkdO2W-oFEG3SfYCNON;Bh4Ik+MIDrB3}Jb4FqEGBcyK2VZKp$nXgc2tfBfm~<0%U8DK(ND2AKR69UVdc4e6gM@=PpP>N{cIvu)xa64cF%Cf@Cw)6Cc+E7pQx7 zzA{uOFE5WkbaZvGh=|bgG#{E)*@}vYn2c3EEvv4U)z+r5v9WO`TYYyg0$>mZ!u?Mt z_3Z2{nwFMUU|=8up{lA{=DMz%wiY3u$kBX&b&f3`{|{f#X=!Og zn||j<78KOfLY79r5Ip~zbbdzxpznH+2B5ejmQ`+McJ{f)&SHKKF_R==oqjYRP-CcM z1ge*om-BXLU}XvB3;kILcvdp?kM_@ocoDw05F@In^*RRBp&#l9{!;bvP)H(V_~u4LhXuB`BIn> zUWb`GdrknkW@ly)2wfQ|&S{xE&V+=7t*xy`Jl&n0%5xNM z-?MM*o27kyB>{1w=n}xPvuSQq10FU|FaOKP8Z6A0Y@a|~{}#YI!r$K?0i1(u(*`V@ zt?lja?shXpIu$bu3ptMVh7b1)efBi=wxPlqsIu0AV&ClNhsQKhzT$shU(0|AJp>5& zXUw|veKGM@B z%e@uD87sG!Bn6j70T__|`vClN+CMw}`AL!zx=r2-U+(h?P!=XWb5bn#IdQoxl^#B* zF4Froij>4Sgk5dyogDF3uY?a3_seYc(0z}tQU3nzq^+Cs)>;dvH+>z$$Y|B-z-@b; z#Ad=BD_7a#4i{DCGYc^!@-Rz-Gg)@L#%{sv$9vAu=su)JoxkgP5wCTEkmSWFRbN^U zCL%DX&a4%YXmz4CeK!1FS^3wx4=(<_^S{y6!dg08Gkq&IP7NQS-B3+H#nYTe>1CEd zy*liY@6y7_)KXb#I{H&#=J(SuQq~Dx?^;f!jJ|K70vsz?Hlq%-*OlK(vyf)l8(DDa zEykRl-YU|kjagnM{AA9yJUvZ&KjBb@?Z;_Ni4zkA{#zLw>%b7VJeNk~?iv|hK~<fs|h# zBg#E?C@+g81PEBy8}C}WA@0Zq7uYF;p|6g(N!Qp#%PVnrY%i3DM)`zFczI^Q;1-`< zqMHxMR~!G>-AzZ$^TcFj(6Z9nBIpIqN&f0oom!|$mT!e|Co3r%6Mfftmv?kb(Rur5aH-M#Rrs8i>bs5<0kq|*Nm8deAbzH0&F3|^ zbbf_|kvH8o^_dM?;`ON)5|_B?xNC{u*4~dA*rX&HlfKJFwGDbR{4tf8>lfp%+4k>>j{4GuhO{g!rvnW&arNt;K3(~=5bn8G zTzyikoUxnFouOm)Mzo5d-p$Ql-6~Qk%_jCGFIjcrMR~CFzq|ILXG<%fKKuc87z~Y+hanbuD>$&JR)sshXRId4;IWCWMJ=>r+@cII_N+ zSDwtQ+Xh9^W5mWXmufMEF8}<9V%Jk90QVsL&(HKMU*Eqfi%aB+bSy{DRnEnDbP4PB zEi#i<1a(wB41wkNFn%J>(v_PJPcn4<{JwHN<>ppJ-0?ils?Sx*-yg`0d2-p+^}Xef z_r5J+SYZ_q{_4X@QWc2ktLHI{A z2!s&SSTYZVGUe3~D=9tM+R8f^!eH@~-AV^3z)CeIM{#~B@#CO+kqa?7LRdKOeBf&@ zm*xl-q=ro^o1Oi2^h)^k8x@s4{K|4N=gYgvE!E27#?P;+Eiya z(3_Uj$XWG5w^mK;FJhO^8OzYJ{XN&{{{2s3SWwUtgIHXWhkngJcbGg!2BR1$QUUg`4%~zUwom;$GA1T$mi5h*cb zKr7eI{vm9B%Z!35AuCTlbFbe?Q*(~ccJOfq8JX+~?}ro^oI(OtR?;tDUh6J+cs$-C zu6|DU_ey?LUEj+zy~V(COaC$vl<5!cKD}H*AX?5--m4hu7If4-OXlS#pF3L8XkRpR z&%8!~k4DXH{tGo7YJ=eNf2%x9Dk2i!-(rZ1CqB13WggoTp|R;#V9wYLRPlTgM-L!_IZ zfBmb6Ck_?kscvo;9t>4(Z4s4~#~Ke6Xs3$AJ%7i7WjFB&^$V-s8ijTzwvCbk3H9Dj zs)ibB$Zy>W|C4cX8U!EOn7Y|fpy60nQ>rIcKm7NS^(xpHVMKyqjq8%{%rMM9}cHUDB(@M9Xu3qKoc(E`s&+ zsgrz87cOpD=WF)$Zxy+V{7X(wihuh33Rd2~|6=m^n)J#3FqDzi%R@bQh^?gz;y+(F zuM1Sa;cKVgS*i(191)^hI6J9n&CS@yL|nZ_X*}1}bGR-vR59r7Q@qtD#!&9Es?pK8 za79vbxBSfG%%$IRy++W-%Budf*NRoDg>ZGab8h~#^ZY`5rkNZ)D%6FBN;w`m5g~>i zynTTqmFp6Tc7@Mzkld_vb4hJ{=N)$eqm-DKn)B+Sf88^mf%>X@QA=AhAGv7+6f4BV z8zlk)F7vvsu}5v!Oz(G&+n-qOW&Y9$sjxVispuzLJu8y2NegKG-*fYAEi5US?Hr*~vr1)U1NfqR#{Jy74dJs zBqo3+F4!|`QDwQ@*ifj*DdevWk9UA}_6cPZ%28^Y3NPdbY}^V?6yH=DiC zBB%_I4rQ#-j0}O~*MotXPH?|C)9y_@l8YO;&T`B7?>!qI9x-$!@#XF^$Y!Z=U^O00 z-P6`dHf|5*c5u4&qPQ57QwzIwauOZ&Wl!@zD2z3`mEunOdJ(UW>#>SAVn8G_F=PFR zTIR65lv3sI=})w?6)Gc|%1e5X|5*s-g& zx$mja5RHvRm19;vy2ubOU#Sk7??E|$3X22$OMsEkp#BxqT|GUuxzJjYj;ow}A9XD< z3W{n@qr!6|zXh4A=#5RqZ;B*INgYjTAf_PkgZwjm6}$W{ym0XF>L93wG&B%q$<=fP z;@KLTUqwf(O?Z&6ju>GhgvH)~l;mZMf_+h6gH`dISIi@hcE$^@Gdz_)BcT2;v#@sw zF*H;d7#QmkUe#8tcQji08~x{7-t>^=n_|7)qH2(pTHywFTl?44xtU zBdBGJA3s*jt>!c}to%f5JPBk&Bbkt%UbWDI=w&>ok4p z^qaMvt z$-L9ONn%xUGRz?jIh7oOH-LFYYxzh3kFtoLiesW_*YA=AykHO@Ydx;KiQD?>S|YE# zAtBpCs|c9o&@kF)ECSJ)vx7Na3#G`e_-|b$|FRGANH5XM1n@Mw8x~HCjLby}A)~dAevk5n}mAZjh`2MCHn-Sr?fhfDjP= z@7}G%*&qNE4BYVh)0vP28AC4Sy>V~S4M>b8Q;qwV>`uOAHjK7h2palSU1C$(4Sex} z-NyDBjaaq7dnMbD&6bv!$p(7?9Us87e+LtJbo6+%ijx!wI)Gx)jpAQT0)`9(3PC0- zs}Z^GY;5!4msPu1QI`@GzH+Q4G6G1Q|_=nH{K@^FUk-MjCQU$x=}LF4>eG={!^ z5GMPe7Xzv?*4u-W)z$Aw!uH zngF0~CG!D(P*KnRrijV8ucsH~sB9d=NHkh~sn%NrCrh^KyS5uD0lu|R#wlL+y^tWs z!dlqcuWtMB*sra-TX&Vi+F-n7BiuwIe)YENzm3jS2q&mWuxuIJaxqB`cy#4zQFits zPJH6)8zj_;#VQFeUxq|RnjRkOS{n=%-uKBLq2X(eSCzrHLLIu z3OND3d1N-7qF7@`w6*=ABSwlXD|>!mNHgeeYb@)*>@Svk7w0K5I-AoxpdJ}??5|Ij zo5>L;C5Q3Z>fVOhmTu7Xj=xp#@Ux{`mA{JICy;mi4dRCWb@=2vOi2Om-+|&y@@$t4 z1-Jlu4-6!aHVEZ+t#jRc2>ghKe*No9utg3JL$u3g#2-9}Bq5!hetXOyI(mgY9|5r$ zJ`mO5MleuS=#yr4iFgx1PmiZr9NwACPkVzyb*3vyoJ-%GG&)+D(Q|AxG7_oXB1uwL z_gK4jiv@Lyacbd|oTiQ^u4uz0>*#7fwapKu71=r_b!2&oSJ}%(@>!pf=f_ul^hK5V4OY zwJ$>up!$bWY2Z};H%evLS*rkUx>M@hkrHZTX8sBqK|!+J^>K74H-H$&MBM3)78G{Q zlR{j%y^D$B3cTFxiwqe;V3lS*!reNk=?uV%Nk7rbEZMs_=485`a2nQ=$kf&)8}Rf; zBcE@hT$AZTMUK|4sKh_x_Q{WKAdHMq{I{IVq$j1Lqey5BvF)Il9=%rSqG?Y8XRQM> zfntuoQra4?TS;k`qfR*s6=0Jb!}HmY=MK)dWkyP-tt$m!r$7N_0D*sgG2vI7vfWHa z*zJi1Eg(4c^|O~@`S4pDw|@Lssjx~;OXuSawWbqF@wnG?oki>)#lveXuC=~Eex)zn zr(9)4D2Q1mI;&RuxmvE_H@6!|BqF8zKx*hA$Qvk%m!%-9x7QRC+qabO0U2Urnx`X8 z3~Tx44;AHDpq?ajURdBzO4t<$u&}gomNo`}sc2b@@O$~hgesSTCkUp8ZCzcMLU!{e z3t!y-9f<*;V@(rhG2NJ`TmD&*%bmi);`tsaE4v~V_yn9NZ?s#^KY0G^S*7nynvFWz zsbbPJ%s8%JgpD=X;APgI-ok4)@iaTP$Hdk)04iM)T3SpYp&hoc7oZnPI={l)U;9a( zB^NV%PYsFer!Yh-O8Xo)KKvBByMEZ7@Z-m5T%yG?Wmp%!?#lnW3f|B-@TJUTVOHMp ziHWOX$R=Tr?d;+5(R!D{=JvmiF?1MSy4Il~%77~~Jv=RgH>7-yZv(ScUarKJVe9&D z?ycg@M<|sswDNyoD+@J=9${r+d8hlcZij8Sw1p7i;40Yuvw~+uSO-k(v5|&{QZ4N-dnl_48eDCuBN^Av)$L`F>d&>%6?lN3=Jh-GK0H2d z1EbNCCr`Ha_hsGPg&D=&-)daMNS)uYw7dZV>6KV{;536Cz7d3MFK32M#l(~$x@iUs zt1fvROpPf(4+lrb=SUW5P_6%LG;H4GP8<;xWO--t5kNz1k}y8xz+<8u+46E?QYtE} zM|*7IDG^4?@f7HbGy8 z6v`BE_3G8URmelXV6qq)-at< zzO4;%*LiD4ERxSm={GE2<{Z^l2&sYb@pytZ%ji(G0!LKl^cXQQd9%!N;NI&5jy(o8 z(gC#~)DCyU7ocHbQbEty0wvVy>SFBX-wtW)q)SVqJ_O=;w#)6b}gNOQCNgCyouiITq z8Pjcb&k8!O0yzZ?smCwJ2b!FL1o~ELgkf z=hBzvvwcP?F5yy@{c(Gqwt@2FO=ZN^z9eSQ-8@I1$3#T$L!%k@J3N6f#z5#+zO(|w zb6-()h?akmf?NNSYAMq{e1J(?Apc4~A%feG@OeC?+%uR`Flq|0gX1u2L8DO91Qb4j z;g)Ah(OphM*`OJfeLTtI!NK|ZX5E^cJ~lJ#e}(G{KFhHw+eLi)MqcaFfGE`LM6rm6 zhuW-se8f<&wON;@eFV%xFj+4$*wCCYZBl7({{mR)K`~84?X%p!1*Brq#o zlM}OMX>|AAs>e(DNQb?ZP+g|nk1sUHy&k-wikj-G#e!IW3j&Z(DPBNf`DVG|%N!6DA{(d=6&-&qg zmq|IFA=pAPgG~(>?;l#@SX<)(j85YHNS>Kl0=7>i+>rH9Vh<%Sxrj8eacK_9_Wph< zAc8WB9)xKWR)*9JRW);)PBtFlZ*PAAj1@E0lx7Uf;dr%XXe435UgcQ~In`oxG=uGx z_;5a_Ywb$9pTsOg_7@p0DCdn~(AT-S722{dG>}NnYu3hpf3g(A^)Yjl@eeHdd{pww=&~ z>=&OF$0>H|#;^k94v34REiveUknZ?mWtC-EEmvJ?8|-R#vr5v^*vBe8ywNr2DGxqq zIJjzFDqkB_smQpgD<7L)B7&dEeYQMagT-yw3wt^dMV$@F2>6|QI7Dfop#YoT$+!Z< z9iZGV%t9gsvF_g=a&u5o*^|0~_(nH&^ef^fpINX_G~>gMNqj1RFxwkF#%i!hNF@q^ zcI3E&3~SZrUk zTx4ZF1eDZ~%x_WVw4j(O;Y9`ii!9VE=5<+7N2%zb(;^U|9A|@ii9T!NHNf-l-Sp*qL7ilb1(~fkE_9IAt2DW+q~6d@*7qg@M4u|Il9R z$VhW*UPL`UQ7iiQ4?npGDplmYoH>w0OgeDaTQCHkMtY%3NNB{l<>Wk7-qlc9oHfa> zvV%fY*h>%tBp=Y!?i71TTAOu-EAg%l@yjGG@D%L5kdH;OH`Fwou7NL;p2aq z1(D=&&v{@>Ow};uV}MqaAuqnGZK5m-Y^h*wkXKMZL!dV4`A_isruM^b4GeNS_nC z5s~3$AB`CQd>SfUR0*V@fF-bBTs~CAlqSZc(qHn_hU+eb5U7V3p}IEmd-$V(CyuuL z=TAadC2OMt81MsvzQHPUO;o0y5RSWt&aZB!I2G0HWsY1(_@81=92{fcfnsCQaIi@| z+jPKfda}PUTAqc|+pB)|sc+aGT^Tv34ijAgwv$NcIhfm+Y`DSAEvv78xeHA9(j>;< zlVK2ZZ5co7C5*Kz69%+ z-0?n@;nH?s1Wq(Gbj$wH84-icOOOpf42BPMO*RBAj}0~Vl?R8o`A0=zLSFpUn@mVS z$qw=$BNcb!;i%lz+ttUHcTkl0nI|^@f=^#KK7wcW5-$3;ilw<0H3Ngkqs>iXP#^M| z$=+7HZ>_XO9~T>oyE=lyB2+_2Ky8SG5*s-Ea?^WUAOnI%YG7=2yliEw5{v%h{XJHe zcjS$YTtjE_%86!+z0)d&hApJrAiIXi=WV(-hhrmI)}~vSP}Pja10)@o#~s&30sS>l zFZE}1Pd2y#z}2m?3WD|sQz(13c6NTaB_W5vc8h_yEbQ=BWeiZCoV4_MiMWS{$FFwc zAAC02KMe9c56dVn4!bo{#gl`f!UN6uTa7(|uxQF`oU-~suj`uS3z?8(48&a-rpD$`##<(w6OV^UoQz-bc(fHdm7bztYcjdku09 zXJ?~>eootRmVg(8zO`4?CbsF^Fj{Yu2C1q(A~KrF_=;>|l3xWk}!VrxO+kRMPnr-}2)a7W?*wgmE;BLOatX z{PGk14o$9HX88K8YW0B5_UoGwMt%#=_WsSXVV^?fdaXZCr-BI+C7Unu9Yw^%+S}eu zk;gI^gH3HPlu!b%s4zTxW9=t3eU7gh1U}*L2ukRO!14*Xi^P2E0hRaU?b~?sDBkRy z=$BqDS>u@lT^SspYfvS$@?}M6$H`Xy%YCmf|L4L5s$7<22?!0{wfpaj<3c}wH-pvH zl&maT*UgP639s|ETF2bjbncL3UClp&bG0`hv>h&u8dKpeE7LtlAK&lK>9YgfvUPux-{zeAG%zfos?%FrImx*+2HoQY&9Jn9RQ{N z`T6jaU+?0|Lr<`qB{MOP2RtyZ#tc59ogZZZYHzc<$-$3 zaQR#$3n{7;g4PEZKWq*zt_Rc1sFtm#Gx7g@Kh~DXAF#tb5ObQBTK>**4h z4wW;@cb>my5DQ)TP);Z>nT1DHP(DIZ3Ch3DT#4rZZX;=*DNdWfi@z~ZLxDP=+iQ!q z+GFPlv~Iy)X!2PRyZr9@cq1)Im^x2-1!^Rs0?6CNJHc*7LPLYg$;p{2^6Y)zIM~lw z#ls7Nm?No>-U&af=LE5s{qH<}=Y~Cgpp&x_oPE zSGRU6J3>kAXe1{n!DM25yld%lmX}9eX43u`b|rQgGP2nxt=uMA3Th>&KE**yxDuEA z*3ZuZR66412Rq6?|6OLte1u>S)drzOrpa{rU&#YhZZI^Q278++si|pj*8N0Qy1k{Z z)-lQaO(+g)UoxAUNwo4BI=an}t;qjG;$Y?dW|;MLZH>%kJi*_{SK*##{bh)<5INC6 z)N36Xsn9Q^7RcF zXl}Z+zqy%JF3INzMTB@ng$2Lk>7g;)3C3GRFf+OnNt zZX6|+lpL0S(A)m*UgFl6wZ+z^-@#qrbP@iMk>PW>t;c%?-@r42l1e~EHuSiL@3p@x zyVU2Zye5aLbPvuPAv6vg_M%Fz!Tj|y6PYW3Y1;<#2a?2uuyKg@UjR=5qcHnbsDHLM zIg&2sM<8B0?j;uuCM|&p z%;9+9O9}=NK4X6ZFb;s6Zo2rue)spB{<@%v$;R^N05*&ht6Y7itsW=fI*_$6@c`QEvWg0=#=aXCmsvkg;TrDa$BIR|l(_>L^YbiRhHD|dXS%Hu z`jOSP#akb73?S)E`AJo)9II<($u$(7d@oEShoW3oe?mswTf3ly-^1x^H@id=O?z-v zQT2LoI`P-pL$FT>{Np!^X1I@r*j+D7+d5k{`j{j>ZvVj ze0Wtbi>H^~NBBHS4`R)t@wXby0Zf2_U28e$0$eVXbtt9b>C>pViQ%A@mc^ml)YR+< zOK(XaCoQL^>c{48!_w|BU7y3ez2^wir$427*Nb8Bn0vzQ>PCrSB9ic3f^$I3$oXau#9H)+fH zv2~PBi|?6eFfkK|=1_gU$@0567xvmOl1+Q{ii}IInh@FdKRA6dhUc`kEr%(?)Z4LhRKGfbRp#{jBrhmezgbtxr-6B|J|bEuHJ>TKNi+g{>SCXxPcXjwv2?3=jLeh`Dhu_N7Gt1&9KR*OgLRp?h_3FOk~-%wbpJ zKHnar6?MLWDGhxVGiKx9G?tw5IWWl1$uR*kceKKS7@&3xw0M~g6==vmeAo%9Oxd#~ zB`%|8{YfQx`A}K0c<|I`f{%8z!Hoy>I1H2@7QXsMT{P90ktBB zxn|mlpT|h5UP~D%$tPVvR$d_bM-pH0>MpCa?eA@fZ4%am z+;~M++w-46Qn_w^ixyg_0>R783dN5_#Z#q}+j!5PKY#hz9MwE9kST+6dN>z@+TB2O z8Jn0q2jhQRdpm~I>7xcCBWRcm%+9`QGg(i`%*4X!sm_g1i=HyINP%cMVD;s#jD0T~VM`5rA$J1>7j1G9IqI!91N1wV9M2SU&E z?|FBcY^5|1YcWjCp5u|Lhkc(di~0DkUVy>;f_aOz(fS%8k6Yk~;`mH|Tizvyi%r%I zbr;!+6pwEWX|xhC_%r#bUkgD)^A8H*@FgaGo3O-1brb#7D|Lm(@}XAy-(1(#6*40T zw)V&Gz}l#*gUahpsbPT+Hda2__wTB2zl%f|`KojZyYpU&NAK%vegCjvUttrPBZ&|W z+vX|5^}v<_>M+bbDWq}L<>RVsYY)khSL7&iYf#PS`|^eHq5JKBgxhLQ9TvW}!#}n! z<23>d8p!^v*Boqh(T~X(aKb|PZrVfL`l^mp z1~yc;KuYFFD#=ApbWdMw{Iopr8jfZnjV+<^CL0BhdDLSep&>#$c6N5rr&2d=$eSJ2 zEu|M$H$uO5)zp&Ebl&?LOp@NUvUU0S?ASz?aiJRkibty==mC%cGrYi;)6>(fiD&)H zf`U|l*AePk!u@Gt;EH~KMO0K*0`oR>SpaocsNt<1RmN-lnQUf8dWhs}5!aAL)p4Fv z-!H!R#(U=gy8=Cpmp# zJquo5i9@;=&Vyuhq?{BTddf@0R_aj0*4Dm*i$FIB_J?~Tw_)?Jf~jDk$1D~sePQG- zZxCQc{;%rtGbt}OMrxNTDc@6pwG)US{b?Te6K~Oqha{u8mE^O&qsIQD z%d~yoeM_nMui|og-y}Yoqfl{i*|hsuWsQ}cOYL~<%po&)oi#JGv<1`kdOAAH`6iNa#l<%fIy&K$Lz3r*X3a;7^ykN`JdI6FeEj@#KP@se zh{S`i-7kDP-!9fnQ@pr)^U}qU#5aq&5es@-y*w z88({|kr|Q$4^rJzTA$zrT&cWWuKYFy zo-T3YX7RbW!c;ret|j{M@iD-uxQvV#b&g^|lA;uBEBn&=TZYg_0!q-O^8##05P^w? zo?)neASy;PN+^PyOAaFh&l2c;BUD;mxH~WGsm@2^^h`~Bb#bHQ@F|C`Zg_qivDKnv zM02z0OiD(nw*2D9=f*>~zrmDN?dmMdlv?rLh`sT?xOw-LX~l7t9JBakHl@I!LdqZ7 zK~#@TGP~P??6~xoHRn#6Jat~Bif48mtax1pQZ7(g^0(1CbhGaZWNRwtD!sRv6s_%Z zW@NO5>a%prpe+S_TH6bW7^@E~Ofu!Plju!M1Zs@#f16L4wEq~u$ff-)Yv7CTsED4~ z-%+W&ey;Sk9y;x7T~-smltXv=IW*8biFw?H*%^X*3W$1i;_KHA04WJCU7{7Rdi})< z+I+8G%ijob-k1=92WvRExpjY(F88hu3c^a{W^a}r8X9^G?M7cHm6^md9ubd}u%}m@ zoO^W^#0Lk52i^Zr@n$2Hh$tLOVZ;ZHLT#N3ql zu!bfLRHPnp6+A2Le^*%OE?8#OsWxB{^rYIwnxnKy^Q6K*;0kWQH@Z;iw<+l!i?nb( zqh%%-aEaO3*}N8gmlqf+7b{IW@h>wl;1LnAiHg$YtAB~-)JnSb7#}EJ;2Cb@25IsM z3O)fk?-XwRlQ}%sggjZ)g~QX+6W-3tPUADf22D99J3Fwbhzo_rpm`D1>E350DT$9y z!Rb(udIKixU=>^JFUB&Qb01?WN6@7Z0$*!oqI~LfiW9O(8SvREa_w^(T)%;xOHy znj!Vkv^1UeaZ5|)pS!_TbcJJu?&v5dl2*;O*Cj)`v?ks{80jsu~1^^G<%CwS64S4uM{>OExs8YACH3v4bV zm7<~|XxqBW7Uu}H6JQ+#>Ipn7u`gf04uth#JzRu(J_D3c!GyE{IXTy$y&m=G8J+!d zv+m`wDo*GKWU(EqgiiMO48QZ_e#w(aV7g=*h?FCLR#5(6>QC30I1=+SEDv8QGYS7V z{bw#*`s??cszg%i+}c8yQe_j%yisg)itf!l9dU6APJ2zAdiC;G%i9a#&!-DZp(~f; zy}{!AjqT<=&Gm)rY#h-{!f&;0?y-O`k#gKn{%W`_o%yZKi;)3~@*1roGMeN5l-OYa+PePgBXlei6M5!UADPTW4^JF_=(g?IsO5#=)LWG=rKA-k)C);S%Tu z29#bbzWd3hUU<@ZL84xZ8WMW@yg0<~=Vt$~s;G=W)n4S18T;u|p|IHI;;&9`Flaft zP8EAcE5Tfnq{Jb)OP}Vx^Q5>ETjq1(3Uu9sHXq&rt&rVictA=n7y%d<{@_~pC|m{S z;|m?IqTCPJo~IE~DtAFD;Dt)|WGloWV7}$zUS}jHD@Z^0d;^X}v2tG@wtk80g56Y@ z((bQ9r!Qid;8n0k-2U1;7uG;NI<~hwfO;^;!YVwx0|i|95EQzlj1n`=l0C(G-!fm& zAn;Lp?nVX$lp0Av^d0+df*Wvryr;JI_4?m;uZWX%I6p$If7IW=>!CE5|6CuuD=yt} z<{OWrFtOP z&=9B8evRABeX`WJFL?Q$3fv0)VAi`+jh=$AhOC3W-%9CE6!c7j2$M>C{2$nPSy@@> zLJvsg+S+)Cxmk14TV%~FciA&0D<#G%wg{OTR!%rfVp>OVDU>I#plrxY7{QVTiZYnR~2Tzm`gG@T} z`zz^W0n7DB>-H~-1={rz!k?)9Fw(i??BN*&OIYAC!2@LX_eL3vo7X94*S>nL~c~Fr3>ToVPV3n~~mm9|k2BxNzL|ocN*H;>8={VmD3#F0; zluu3_491vw%gQM+gWFpu2(!!b+Da%NKDU`y+uTCGaX3)~**vaUS+dsRIpyU7Ha303 zvD-g<_>j7L4pkd2U_Y3hKibl>uteY8wR!w3iw+kzXOx~I^)n7#@BVK4WzL}rycr{s z7i|v_JIl{sp%_o8Wx(eD9D_vM_;k+>G>$)Vl-!@cdmQ}rtMJ~Qc>nOQ-nOPnlE*}N zz3a8n?mQbl(MHj`w=#xmqlH~D>}&YGz7rN>zd<;WUu7fI7J##I0?8{b?VRo|#u+j@ z-BmU$X&JcSlQTxWZKje#sF1S<-nNd=MoiPM+pC5fc-U z)pXcyFRvK*v{|71`{XAYZ+56%wu)xAT(#GXQeSQRx^uCko!zYQ+|Za#kA|^8_tX}% zuFm3DFC!J-#Z3qVqw9(~G}>Ft9JUH^n&oei5lr9h7u7!4GJpN@C3k433MyLA(xf%Y zU}5s1(83Ks(42#Ut_aes#@_imY%sEVdN?g1$xd_TbuD!@n2kzlsj0Oh)g6wIL97cW z)L9@%*MgM`Wo#dqqNr*e(OZTTRbr{x4=~$;&D%fR2-%qzhFvG==lVBT#IH)>Mwz-C z6gB;pCDNHa0zAGAnvsjf>rny~6}zCIvfX2+G%a3=FC<;^9YmxaUbRkLs&!S=-gn0J z4AO!F0}067e*5L+`SRPTDJnLHQP%(cYvP@f!f9m{m8U%&4eIY1&+9yKv`Q8CJ?s3`VG!66v)KTt}9fow^5YY!p<;(HUN2-tt&A|qSoaz0l=c-?g*O%;UM)uWARy`9%+z}CqNZ$$zTT#dkQrulBy1VUTnNyJ2jB7wZew`*z)7l!k;4{oTW_y=#MlvTADM173IYKmEIN_x$MTkzXo@R}_igB}@87 zStZv~HU$S=r;GPgNJ742xx|&_9KYXxlW+YkTRXunjEBbtD<7y*9cRDQKh5@KWlw|f z=e~uFz@_vi_B{6#2nh;WN0xVT>N0C5T#wcnK*AT$EayKsC_5Q#w0Oo?0HR*W@-8Bt zp(MepUxsr(2B9w!yK|F@avDH?nYlGns5t5s2TUh?dzyc} zVc|CW^Fjk&UfP{t{1lTFZ0bik((rPZ*e*FM=dUB4*T>&yBn!EPM$BeI&m6a>#MIP! z1}lB2{bDd^5u4n7Z0z9>V*gw48Tf=bq@x32Sr#v4p^K7K{?gD;(P`g8tE_ycS*!fb zM$^cshJ&=SWPqA3HzpzBAwB&;L|@(E`k{WlVR{DcvrGYH)6JK!V3&cd@$77ZL%`3- zG9O>l*cdNYYq@r(W?`=1SF5&Qe?X$~CQ#5s7ndZ?%dr(^(cw|bZ*+C(F)(~Brp7R} zrac?X3j&DNXMHiv0*r4ayL8?~l90GG+fh@a`LeY5Y=WJN3OTzhh()ib)>)O@uWva3b?qa$HcCYhez(OBroJn z)0jyk{z=?Ad1Ta+@JJP+yE6^>_*oEak)@R(e6yipYxmGn#<@D7s>%X(eOB|49WaVR zE)Io~p=NhYqUG>bA93CHEAwjn9BOusE1ynynpe z*Ea*W(49CLe0?q$EfOO4fbhr2h`)-CR&$;6{>gf7Ap{3to*oR!IPdpx#WKHrCE-WS z&c;Hb2q&(|p)W=`GNQ^~>P?g{&VcFRw{hLJBVgq&wf_9yc5CV8Xa-0vvM;B@;9`^4Bd z!Mlt18GENqv@9$EocP@Gm$lzN{#G9!8v|{Pwe8|BtUH*b-JaLyI*4cmKrITRFr-XG zpyc}rnMbQr)oh^uv0RfS>{(FFq!xQAb|Vqb(P&dNr0>v`c;ji-J=C=?K0c&7jmbX? z8;t4V1({f<4(oAP7QVII_bw$%yZr>Rbtl1A~r zbKRLdv}7fc-EM6hRPov^V=&ddQ#`G4Jtm9c=<*4rAc$?6(rbbD?kV>G1=t&ckDb;(uZtSRWC8@dK~F~Ge7TOyxbfgijn1Mr@pUA+F%1Dovw-9U(37% zl&xaK1^FrUsCCV-_xYX!U_%S-mBxqb4e*8)8TY+|dYBa|FG`BkyFx-FE{7$H@z!^S zUAsPJWi<=G*3$1yxHT{k@OyOhyS}J?6w?mkI|;HUnGPmEtN92Cm;S{2_Wi3Y<*Sq3 zts94vMd@e+dhHNH2UY}fY%1dPs;Z9|BtfWmSGzX~otm4zEi(}#p6E#aXhdOd5-Ks@ zSQJwu@tEe^{f{Cp$J;$g{H~t^0`#ELgzTHIkR&p5j|T<06iDcGHJQydcL3+XDob=9#Ru+#_RS>XE{}#p)Gy z9pNP#HY-iE>T1daKJJ=yAuM>kS=X~##c9Tg%4@ID^nqe(Ho4E3JJe+SYfz&fC3W%VUA6iZuM zTUbw?FgsVH#AJ=!zCcd4$G)gb*SD>NFYW8MuvxD0~m%7 zL_$z&!`V7r`Lh0OvW|=lCMeiNLP&cc_9ns(K%d$R2v{>Z)&M764xqvM*N_ zJHm$tx(ryQllZc`b5m0l^y|r#Gbi*04rWQ-Gfzj$)wozYyB5K>^HuuZr1Gp-&51)n-6fZbW-(nef zDHY-93ld6*JEFVGdzUa7YZ~okbg16epb2@M^jT3%LG!5TknY#p8K?bs_v~JIW(aJ} zSorl3!bNS-1#}Lrh957D#;5o#5le`O1}*J1U6rJmOwh`rLF#FwFovP>Xf z?*W|9-H_??MpDuM)TjoAhPzAAI*`@VJYH(tKRPPP7>!Lpfcnfz3BpbH08A;iSrDH#_?8P6FE#uNUso}4|=*RN^z z5vhsz1O#(iTlp5bU%!Iek{jfoi%Uyj=q3iVNL)nZ4umODb8$suQHZ<)L0sekQtcS2tEE zt|g@x>5k6~Jb042_)Zyjc{#6)DXOumiQV^~RhMYq$-{qP(OK`YFc)~i#=YP=oqJxP z`_cVl_2C>t1&S5*vL4!1JlSeEVHC+p;|nA7_|{-ds|R?veic!U$a#dzNHS`N2N|5;i%X7GlVl_*qx5Zq4noS&T7%|Nb4MrE{ltr(k<<-mYGW zSgW+06oFW4XNVz##g!LjeFl;rU`>(W2D=?LE25s(QlCs>^3ur*@vm@Ri2Gn>FB%uacn!P8>L2< zRAV;^?M$q$*|T-0^fxQG0L}Qh(zDz@=SKP+&1x5sbk8K`INS z-R$R{B{J;%dFelp2aMK~C6}uBWpZrnce(kf+1l~uxOY^Payi32Kt$JO>UrTF&coh1 zyypxa+&ZgS?P7H`b#-;`uA{v@7ge0x@p@M-q*PHAoglF4c#uDTu9|xuuIkcDz|>4g z<(%+ragE5H+Zpy-^sb|I)jk2|GwtL} zIici)ee7ldhl38+)AlOcHBlH$e=(_;lVDS|N@lOf zixOyD)ow&}dDGZYC(hf2a+WrS>@eD(;^b`T-NiXlD4Ja7M2f71EQ!v(8t$-ISTh(E zdi^4qJuh7*ZXzryT4ZFJp17N1Gn}yPmV*u+0$zL{X1_k<509Q0_FagmeGR9qgx^zk z*+9&jhlhtO92~D{Bjq7X%<^$eWTX;9JwV>@=MfGL4IaH|o@!jqCQ6^am6bF^qRD5M z%^)||>=_#w-3+LWKJM!1c*z(|5t?Q7Q)9TTqT+h9??5*$BOse&V6?w|sOgbuam2^4 zk7mftd9`Z80~+{hva*^l`l(;9nsDePTl^>?;|r-y;XZwR3b)~q^zKNc;Qn^~hV|8p zh$BYv~hR$Fqb1SbbSO2bStF>Qsav(Mjz2YBJaO50>)XF`;uiKUe@_&+*pI> zY03@tR{P&MG@(j^uW}(l?cGvmq}dwiUEYAWIk>MkU%fm4gM?@3R8reAu4MCED(2gJ zY5nV~%i)>*zkhWByfj;yt+Y<=+y$KyBJDUO$FX5YHC_lTbnxt z`9rLb{jPXA(c0<*rzw|AvnIs9g=Sw?bUq_cYPnZT(T~Do6XNca~KwA{C5B!57n;ii}y|A;>2jRxBk2*^DQLw+r z#lOX<(~tnFiU-8R-Ob)8G|!&>xj5a2<21jGR}{i}n>srDA?Y?a+2hO~v|VtLkC&NZ zOGME>;p5X>sC_S`cI3TcV{dN&HwIxf0VWErG?v307nmpqjGMW8C@3h{BqYI#v~Z*8 zAX?K0(pcf}&S7YRnot_j5sQn9|4)Jn1ZZ3;1)6hh!9;-H>_Of?3y5PyLbA)%1#Gx2 zgC-x;F~oR#lBRu2`>@%DlcZZcVRc9BjHrq?iFPKr-@9*XsSsxu4r0X-TO z)E669c z0TU}kmc9g7o^--U>GbmQ67}9a`Q_*i17aTg&k#>#<_wJ=VtT`SsOurNQTNx^Cm;d@ zI2dnz}Z@`?tD-(xX$UadrM3;=0kEO znYD#Ga;`32U6iwpsG}eDed*sRQiwvy!;{JLW5peseJFs59}y*oJnQd~=avWgwHCL+IoCx>AgGf$TUX+Z1L3Zbp$ zn&SsHRR2RJ1S=bjwrbJPGR&XR_EQsm9}UxyCOA$v*;rX2+6>*$(2%xW zK|ui;sJEaTXLuC*H?8wm-?yFw&Il#G-_&;UVjbddg;n~g!FdaZgDrHS@nEP93ky@{ zdGrz)8i2b9=krZuKik_ER#yDLRR)?ptF^zMsDCH^;JtOl^p&WZ>lr=6 z(<^m8kN68_Hg^48Rm8`daN{v8v$?y_lalryY@Q-RW++{z^ir9Eay(Akl5TE%i98NwYY)iOAWId7YQQ%Hx*r!B z;M0plSzcE=SBxHBVePj`^W5d!QGAVa&ODQjR32~n5-o(2hZIk|-aWnXFwE+y4c!iI z7?S_qDep@lpqnPo@owJK)8L6vrlVQI2&W=x8B>KpCvFfEt2R-|Wz-pgeQ{>4JbfDh za)#k)zxUGdIQ4-BB?=>5jhNr%5mc+1nwmr6p1bp}!QcD~G?q4Yb}f+H2|q#fQjnFQ za0QXZgeL|`ZR&2xO|&pX0~$|Yt3nc?Q~8HD3=$2i%;lOX_%6d)@m220r=W-K@>#La zmXyfv4yspGU%sfc!nK$cNJ|$=DUt8hu)g$)jb+SLK5T1xQT%2YVD&d|emwyYAA`)} z4u8G!E=l3zyCKzJ_KSjeR9o0STrNp;R8@b0$a8fl8#~$kNDW(L>^Y*^L3BqDeXp*e zk628kq`lxd?~R~QP;K#OkdY8?!)~nxfCCUs*PCX6ms_azDy83YGv$H5#^DX!s91 z^(`#A0SAbC_PwnVG!;;y4T;Bxh5g8oieK$dMN!C*%;wP2)qM;>$VwEiRXz0{JVQKs ze;~Q@G@VJ2czKZpPSXU*Jl=byrD@XAHBV26Iq%m%o*)hqFym}?MhUFfKKJ`l3k$XM z$2qdIzQO>MN=QxG&}>KuQB?08lpzAGxu~{BHMY8X&biU@|0dfY=w+g;zonxiVs~MZ z%}A2B!s1kd;5u7@8~F7*kDR6%b&m4cNb#ci6&16%@`oqXldGy$)+M9E$Zt2z%oI(y zDzyVRo8ur2Po?e8@^TwIwd}k+VTq011cR(?qzAe%Cjp)^D@+{m%K&s};fK$CbaW~G z|6JwTATPYC#%;lYPdLgy9M1|$fci$OHZDBDE&BKGLCM53Ivr2`2TV-n;Awme)G?dU zPn%WQ%S?7s-l%2(A3uGvH@A8!g2Uwa11?70UsgQK_G36d50S~3H_B#a` z1jUH(YD>!=C=cQ@SK-H~82!dG$g zD{=|)(0B7cQK7N9@vEoD5OSf31>9n~yCo&1q;?Mu7K6EFz?7gwSqS5RaBy(UhTA75 z;y}GxgFQkHIn>|O(ei`;rFWzXva&u>O1MC}AjY!{#sc86SoAUWfhj!QZEqWQE!7b; zH|k#f?sf@kvZp|~ABJqaLFDZpiY5oOoWZ?ESJTmo5L1 zAS^HX3Z9+F!H`10+6t6Z7Gp(Th*}I3(4V1cg~I%Wfx%Od<+`13RYIs|2(0BWfM{UY z!*j^!F5g2;O9>4P-8=L}Cqm?A123+EfmB*j5(B3SB_(B^X7xiRCMK)tnpSYUwm=Gb z69hjXGLWNeASay4%E!~dm@Z=r`WvoEZjRmeGo?82lB?qg30Wc9&vOfLb-lLg{|`BV63QN$bY`YCB_u2-(Z)2K4Gg(qhOw1Kn+91ba0W1PeWCWNG#mGN|;ZD6sc0ASk#NJ!z{2{o4&U3p^ z#m+2(*9$O37}&%iBKPt4D*Wf|H4Yo$}iMjTUIt9Slf-hh`EF zI!}bqt-MQ8LV|*s`2n;HFyZ8L!M32zQ}CA1CKGK!W1Vl9mXSe=;?;91`3-h&Ft4t_ z-9seWFWAPZr=s1|flO?KrU?Hv>Z??k-bDf#<_7-}YSe%;Pr~mvEfBizjw~3rw=E;u zq4$wcR%R(ESVnKSuPXOsWhk52!U7F)5HnGFwcN>ajFU{eKz%>Z&|;R63VD7>z)n?zHg~_tsp8!*PLLPCXo55B+cH=;#0(O2foN&B*u#?29lT2AXjE!ulii z#H9`cR#2QhV`5r?)3fErD+dW;et!P$?rxCbU!1Nao0yN*2iV&QV4utfGVOtw4SKB} zNEiQFHh@yz%7zdJy$Wahzv_x@`tC#*<};+g231lTc)D4d3#5^_jm>Rvego%`8zt~G zjc99vpx9!Z`wMZ$3o1Hl&|o;th>`Qw#+VLX5xjByKjqDXIn6BQuOuWuU*`^qi2BVR z1rd4%rosrPSt)WjN0z!=N6j_RVPo^nA-okA7dJmQ_Y&sKA@T6=pb@fvM#Mt+3x6MJ z_yGGZWWxJJL_9)>jU$awLA02kpv(iLGc7GGm}M2qS)qIV`KQ@?xM6>EEFmEQB7PRZ z!EZ5BS3P1M89Che%|iNr9cSDW6qYm2?TG+g4nPxjaVeu~lmV$g-hcPxC3X1TGn0UF zxO3}CjnlQ)=|1+bK>OLD_Q^T#UJn-y8h%mo+ZK!>*JJb~&jx;^!aCcBIy$XnAcDWM zInI{AqCc@a<0^KrhTZeiU$mo-$#AmbNtxw&U#EQ_E=@)G`{h{gTen;Hk0!S}jrY}G z6>3KT===z%z)%WIR)b%CK+`?I;fQJWw$z|!gl$j~-Vh-T4I&hjR`D*Vt(SpFLDXL1 z;Vl5bL6ZUC=(pYy*sR<7Q$^K4UV_lvb7;MJbC-n2UKbEjgs3rFQCwS_6l8CR^X-$b zZ;pdsQFeDCZ#0C`1M`6ZPNCliG)GQTvwNv4`gAc|1`(13znF`w64zgjywDsqzUJQ7 zH)9R%wLtEdiGvgqFnb_^O<2OIq;Bd0Xe%Y}VPr%Fu#j20gQKI~<(XuXt91QAu;Ek{ zXow{s=xUB=1wj@`rP6p_gWnZ~&9%TLUj2TT9E?5CL(m^5qpUT6WRcasj5)0goGs}eStSLt!W z#PMxb3CzwNH2fDZ>2Y0}zKpg{H_FmAVQ6PST_-g{%UXom0bli*-t%1>zprAg0P|5OpeTT3XJljm!NlTr@(%bxHHLag zDhNRi@2rD8EZ9+t!?M(OtdyF-|EcDwf~Mxxi>cS$@lLm>Gy8rIYIvtFi3X8g@{4}C z(8|x-+hA{|8D4gvR_|vQ5gsAdS@$;{IVu3u3wo z;#Gyw9|(&9MEfA$L3_-}S<$wC8!$lt9D$sHT?KN=SRk-1_h;z;3$>_b?#|9#SmWS= z?=Z>y^yw{N>uPIxVOKz$6VTg+g@!i6x9ot59smy50wpQyi;Gzh2{`~oM|Z(FYhAXH zBNopQQCP@k>wI^iopC7Mhh0~~ly$$??B?pr>d%>=occjd8@ut<_ZhLns-+U0hw zgvHdVkkvL3G5@-#l@$x}9bqh@c;$uDkKQ2fAh1wN#`g zB%$SfkaPcNc*QuaBpg0=ko`3YXVQp-tBpiKcS6bW955pd^$$JYc}iGTSxp;xbNf`eE& z1NHuW3MQtI&!6v#d?C)J=Y@}rmCTTv9^x*{a2O~<&joN7s=2J7LhIQEi}4a4aL&Oj zzEnAPBEs+*%aI7k2LUmiz{W_h?xrerclQBIX9&y6Dh31jgGctzko|Bq4Y9jC16LD4 zuU((RkDtQP+tc~u6_%Uz=1x^@c9O~4Z69t}vT%;Hwy&7B1+59cdUdDL8joi1lTYGf zxRakhV+9ePmX&N~WwHD+FeDt7O58V7rk(Kos))`Vj zwl`2>kT|1EnO|Se5BLZamI_mK&ZQIFV1!oEhWkePp{&ev+w1~Bd88mh4sptS*r&8= z%_>kLc~*wYq>poDh!7Tz43$D{+V-ibDU-}Wctl)EW${~I~7UK64m;U$fA4ykX(uf3lX9T7*yposaTw9BC#~Sj=4Bd2Yy|uA1%Cihy<@DR&=`Jkq z29xt(V~3j=!VMfrRX*f}HIyTt%vqNV6W4;5 z#wUQqiV{&)#s#50M@DvF2FuH7SVH3Bv`}a^H@!enP^dTCnH(oT4#@UVGQH8=Gdili zJe=!fGAdn;-rb|rH8QBcr>a$+{}O_VY|*;>`ci)ijsHOf{91b&5wQ|9_-PQ-#Bfoo^TCn;y_cgLPF-Uj7H* z5dFjXS5E$aO-ymTUjWwHvHSGR2X8k>#(bZn9ckfvZCAA#Gq4Ikm$PE8$r&oz2@SkA&REJVwY{_R`d z#0zG++_SRQHXduC4Mn)OI?~cCw%mMU!#V3_=H_X9%6;2eHj+OUmOy!G)ElJG(C}`~ z`QXr8XPoih!Ud(NxA#LhwJ4W!P+Se@0vI%|$OW!A2?zqRJ0U6-hOP)-UyXqLDFi@( zhp3Pf#$xijZ@3depswIW?8Ea!xbh&@R%w9v`5*7_@b#Vjp`n{@ZW!~RPCK5(%w-Ya zsw8_xo0GK*-3|jWiPYa?r$xc*wY0sR+6=dcsGyP@09%0jnJSF;96VT++mDWOFnt3f zXH->J5!hR)Q8hS!)7zW@x|#zgO8G((Zza+7u_xnUPW$EhppMyK%xD=W%yR^2wq)iM zq3623ir)KGZYlvsgfZ+;K!g@~)UV?;!Q<_QX%B#$?1MNM)7*RmH$dJ*GT(@26$Zf~ z!MNoAW&=}f1D0W?E_{fJT60Nfj#4g+RQ9&*4?82|SKqy%!@*F75T5|K)t@Elz%G)w zKIp3i6%F`}WVt~}`@j5WD%JN*3=D)#O|$pP0f36Y;7&zR z{h@3DOI1*#C49OyO!I4{cbbYJAIZsyD6;m0h)7LD)YX3bhrq5Ciy60ye*K4WDsSK3 zo|$QU@IdxhPc;sod%$aP5fx$&xLV#qQ3;~^rs477=F_hk)#3FE6b((n;S~k#5Cs6%uuH zU_(^3(dpg{d~y~i(yg)TeI6t+F{d}u1~+2Q=&dCt7COJ-`UzxtFsS@0`4Zj#GDNVg#3B|($F8tC z@3BZHb(_g1p%b0a3{t|C%0r~i%t3EVrtrUAElNK7ng zZ_h5D9RZWfm<>B0flL!nEZLbP?P9&#($YX%PMBYS;C69kB|JX93lM145(8AAHwb5+ zXo9m1Ok_{sP=wY8V814qtR2f~sUAL4Y!<{boQa+b6)ZL`u84&NJ!@n=>l*9G*Z5*etk1^>Yt8HN8I({4*V>c)PDBXUn z0E`S7US&b;kw7#gGix z&?7>Z=nb7CquceR!$)uIucN6O?Cg&4$djBi7~a0Miiqw~t2xjNU(A+pU+*Wrd>w8+ zF;P~*X(>*`Y4Hap;$@bUJOs8Dmq}aF(2x?sDWIeckBIoGqT~KLgp^;MM==Gf{Fi_i zJc-*)zkh#)u!NfdG+w|#z`uz7=n+H(<98qnCT1=6k(8c1khwom?N~+ z2atgcLka;(hdLji@N&6=Tq?^EStZxzt?78TorDc|j0va2kZ)H(cvuuKf zgjJ2e4?~LT@trlx!R;#ohn5}4( zt&X}npcxP6oVYq%_!jFs-GCtgFD*7bA>wMq2k%JFxH7bKkH`0&qkHPIk5@-K&dbKC1@T36rfAzdnhlT2C2Z2r2O#o=tn61FpVFOT>gDM zv^}a?jES#{+ypg9wW~^fWIS0nVh4MFG+tr06#V(GVr)g06@Of9Bl}|9%>)5{Wp11@ zf$!h%usQDyh8V_kR;;d?0BY-ZF;htmXqaZTtq;Is5vbTyvOwP*&w7T$$7|ddrDd-$ za;GXXW2%~fB5UU$M(!k^11l&W`NHDJd>QU`1`tY-w!0cd0=$Hy2vk@Eb<7H_(p`!4_3jqYfkX zn1*6|Thr0oLmE}L-s@|sNEJptW(WBn%zutxb2(_)`4}I&W((#ssfR6JNJdYO8fakpY^5$Qz98=E#rdqv=!Pbo7oWgm@H&vN!QrL36-WKIT zaawZ9DrcNsXqlM_(9k#s;rFNbkYj(Uc`c&d3VwbNQ4&1=OWm7P-y*?u2{{9Tzf;>W zVW>Uza|c?{*ej^E3|#<#jEKmpMEZFNmNm7CP5Kr;vZi4NCS;VWpIuDXoq$F|u#$^2 zuxEfKF3e#`%{H9hz597-r*UH@z+h*Fh_;xAcZZ6%_9VqwQ}al%OJPz5#IVCT)^2rr zJ3~<}Ml1lt#>AjeNh@A3o*g&hTu&EMl+PHZr{jbrY6bcGV}mM1V|VO{9eji+!bxz3gKr%snzEaLWQOhrahJus(?$P-o2Y;GwcAxsv>Q= zXb7nu(g{AccM~B@$o7MTa;vE-7%ui?cXt;^m$ybnZJmQtXw$^}0|#ngE`#D22?nuL*oNh={!XmaE;yD@T0jNiX~ZU8T2pkYnO2yKT1w zuij&0hH#ddXTFab8LdQya~**3$JCj^`K+B?OF)G6O;!8DOA7(_^~@~U*TU&JM}=8m z!{arMtBEL%K7O=?ago7GU47HF$V$f%+){h&%KUM;jYBJOq&yMYTM}Q-8;NtIq)vhC z&FyttHwm_!h=F0F?_m}!M7Cr*Y~%+71YGycB>E|-<+~GKTYV*XBz&Vw?JF^s-LEGo zL!D}UHd$XX=hCo6B*BfnDIO}k7{Q~c)jkG{STASXa((Y>*NKn`EL zKted_-?sR2S*LxxkBQl?%lrTb2Zl}IG2^2xA3DhwpPW`>5pV~krR81gkX6RqKyDy^ z+!T(CBJ#UvF8?sT{o^M;vY)zlK@;qCA5WeI|3}Zp&f-yM=-b7mXbmi4O%gmjSM{>L z@N#o%)<$*F6;8cZ8U1k&^V(;BWiXZ{mGa4x@^^N*uYw7s`3@ByYt=leyD?n!_OP49 zVO2K#>sNKCrf>>0tFNeVp^esG{Kdbx90!A`z9_G}iOP)ciQ1X<%x(1yYY!TCS|AEA z8O|w1BiL(?;AdhIef7%hEqmO_*~V<4>umBhvgfFl&S2xUa$SAmSXnms8HcO4-MQq4 zB9YUvT;KiWl1#p$$X9oU*}ht9Lminz+cbe55lej%HjRTH9Z&1dt#;xC3csC;1U*Z< zPF0FeD!MCA$;Uw z>mY=<`p2K<$^3%J$rT2X+HoV0?o(}E?U^q?elK)JirbB|wu_FxzPdn@5UP~hyZ00O z>UPCwsWHQh^9m`_{5)$c|2YKMCTDuMJ0-amjHByUCw=-*!Gc!T)vwd3%!`;M*SFc?%(-|Q24MhJCj=1}Z@&h+FcOHvVU09=BB2+e8JqJK}fvD+_fnT1nP~q!MHhw)jm<)$o5WmPBi}}awFqGZ#ak9a~v~qfLCUS`Qv`}2~rQ{YxHyJ**NrU zc$%#TJC)VB9L5dXH^5~A_8=69JNxK;Uq`qKB<4STmgV9#x_^4c!)C2*{sr!u>rUb~ zrybsaa*D;{*%c`|42%NlNLsEL)ATaE%JT0=scC{tK1u^S_Dh|NtuEW$u`gR|cKT@7 zc^S}k+*l&vfYxPKFAV9Oy__*&*JH;2dw7@*u}tii6OU>eVKB7Sees*Gh10qzDYaS* zN@i}ec)ABy9tQ&&+1B5Wfr*I%Hz71CN+C_SsAzNR?RZHqB)h<{ZpRFn>(D#sQ>No3 z@2;!)J*HfuXTx0nHs5i(*tO2na7waSxEWa2W#D~h8!3U$`O>?nwA7B_)Ym z`qRX)3AwC-0t35(3BxBp$h%@;(b%b}iFlmR^nc$x+0BIq4>VEvLz&yRt9Bad_M0lK zL&|GL;QfR_R0e*wwbe?^qX(zKI| zX61~-k;gYHEiOWiy+42FzU?-B0-*SHD z*>e2;?d?wWuQT;KX{Li_o8#e41zNj4HPkxqPnd*XQ`$nL$&`KWRmtaR+`kP2Io(}l=y)_u~6F&h4lRgz^qFl_P)Y;wihHEi1{9D#=VP> zlJXQ1x=?i~Q=ZBS3wI{?`62JGT>pSkPGPW3{tBhzJKsEh{(KB*lC`}2`{%;5RmgrtQWO@adBzspmx-h45>++6toUOKaDM*rhNPn{0f%;+$-Tt&iPs zC<_QdPos%4GHw@kP&E>>vIP?hdPG*v3N)|wyB1e+J$g>LbGo9T%iPp>RqiJh&lnaF z5!`PAVwi9lLH?fZSU=~h)2QX_5~N&}&-@4zMVTO$S?F9k%Fx=m?L$ ziRdP0-?ao!X<|n+8DkL;vh77iC*mUbFFItkCC4~UWFk`p0;ffO9&f3Ol7_l~W9 zet&Iqu%=LTmY(i;o&S`|sMtcy*r?~spUmUUPZRe3l61U2fytpdY+YetH2rq+>gQ%<|H=R+7UZ2!Z|lnEC-(vVXtHkHJAKPftu9 z`>c)+HypY(X7KEHP{@SsR%OX|cL`!7ofYi8y>s_Z7Os{`b5+U`*_3LZtY*K3(KH0$HEHa#w!6HVnjwXZH1_qiU?W`(p~N*AsKb4?5s>TGQY z6c%k5)6MfT$W)x2>vVnrq6wT@T!pGdOp&)bEv3Z1jm3pE35GRI*Sbb;*rnUnx*QUc z@OpH8xKR!ws`C7n>hv|OnX`ynGalk`x1u^bPa$|`Z?m*7N<@J?eH%PzhR`5aU z!}xiyN$YMf3)A9+qiTC_-*Y;q<=dB3(D1X97_5C%&2Q=?TfLQ@S^1P-Yk#Iy*}|2% z=#8QMnmkqGby6>NvVDe8Pnb22V(gk$z*LerB`QL8#X+J-`_#J`i4S>oMZv=GSuoKg z&z(t_-}m(GThX56%gaXA!bZA)#)4tGU!PE=zzSPH-u|pIGmaxM(0wF9b>N?0{LlLh zITkm9h}HJ8i~9S$C7vMCuRAg+$RV={{?KAkdLfdg$;l%>hrNIwVPS=e3yXdGClLD$ z<#)sKBA6J5}iEt@uxAom=JBv}sawVKC_T^m8OMxW&;P zDVc1KCKQvj4P<*{-kj{~=$Q)_O8$G;U&x*Kp3oS@IPRRoSa<`=-g=0{yID5J9TzKf z(p%(a*H-{3peizifXE6AI0L7GEd;|>v-{b1E%i2J#ivz78}-Z&MuWZ#i*WL8fEzw$4VDRz7D)zFUw( z`-ob3vJHbb7M)1Z*)KEg)zh0ORHT14+8f%2nIC#X9kVb z%Ds1gTQ2suJ?Gcv#kS1KV+UO^&~v#KTcwz6Irei}*?v^`?3({5bYs7um>4k)w8Jsp z&T>I1Jak1Db!}(eX#D(n3Ko4-C^&;-xg1#V(F}}?)HNI3PFX=kks+5V0+=Hx4_DYH1aCtNgi#?VKG)+dj(Tz7wD0j(TlOxq!||>}Q?AoID4v49;5wZj z|Gklr`#ipDYio0DG>wc!MW0^eAy@Jk+KbPgpI`Id@%Bo(!7tV;X|%8#yu2r28hvu{ zcLmLY^DChh2mQ8<&AYj1&+AtUalsNpGFVvE?lh?M6rfURFK>YSsJ%QHYI$1EPDR*Qyy>PlD7Y;8fZn+pGHB+)V_x$-)8z`~ycA)IzHD5G(tj zA?PeDJX)HS!C(S?V~f8Q?*neRx~i7>+~|+*7>kq+NHYa9%7^APqqqK?@6X0}RR~)* z$Cxi}^X8A@2EaWG;`A-{0+or)*@3CR`6e4;%GwLKMQQ-bq7!i%z|^vXomnzaY3;+q z@8||Wyz6!~$_J1g6+zCEdWA&?!2R5ACq+_cupCq~8I$#TN4VWQuUu_|xNl8k{aj=W zXW$g;;56DFNva(CyK#q+O6O;G0JhU;L;PI6?_mC00K_+~&k~xPz3}aVeFCz5HPDav z>kDNa^cD0Fj=pP>3!N$AX)D#^p(O>R6Xz2(t^M3M6-S=kvg*7Va|a~GOr46HN@{F;`{NyKvXla(MNz|N z3fiJp87_4nw^c`sp{)(rx481ell`o(cP+*LwQ; z44w7$_4lXNC)?XmK7a&-^|P{Ynthr<`U99BS8F~Soa;@;U{Fpk`IWx@O1O>{&BSzWptoS&<3Vw!Ya<6eOmiP_aL>&aSn5^zEN2 zCqn&I*wb7u+{rNrF0F8IXVwU*>P)ya0%8c&+zI%iBz zog*ix$E-12u$%OdV=l1Tqz`%DS{Egd$2V4YJ7qS~S>VB@9oBCzON0+fJDBQ(1+t+P zC~(=MjSUTp!F>z9i7J4q7`DX#TLYi)w{L=1*bWfe!KhvLMppJ~#>ap+KFe#HYJV*5 zSgTmiD=bteI1-RKkr^GuiTx#M&f~7}_4GKBL7Et{YWe@Dd+VsI-mYB~Q9(sOq(MMY zL8U=JS|ufwZV;qPx9cpWfRlby&4?rbI_p5GNwD5#g#I8^<79xh}_XZM3bsy zwz>wGepFFZ6D<%b03MMWjPRRSU6lmR9^~^dO~3$72{OjYTjDizAPf`nxo|@v=dsyn zBxsCmRO3pG&(GHbebuBJ7svlkgT~!Gg#*$bcEvOI>A|W#Ye8p9Jh*LPVcBnMLxCKZ z;c#>DHKr)n+><_u7`Z_sRN)-*N{`;Vsf~T7CBb^-_Q85Z+*LAUF#l(u;tZKE&sEoxrYXsR-ab@R4}PV$o5{UzDD_{EQ` zOxwe3PfxMnrcFBf_=IzR8YtwGow6Pu?Dm=wEs>#7`q8m#TmyMPL2-Nz)E z+1at8_)X6g;D!ij6)VijPlSLr4U`ImqxAJFAJ#Q2Ow5`$%&(clp17WJLszm6D4T=i z=GW-xRi6P_NY%2=z1om1kEx^K!b)hd-QSGYu#xoeUGl<;S`S0 zjg)`;ZSoogmW;Wy&T7^qrcR2`m9p=fSd%iH(rLSew)zbcGMZ5 zUG!?vT@U;-#n&BrC=7%Qye{k7!OrDh(D|Iderv{Cbm)mU3oPUASiR~U^=!-9p#M{w zuv6<#HE*Kh4ozlD=AG#OHxL>fr&N3UTAtJL{lO5d2~Z%4a|(CrC~& zcfCtM=M^gz^QVUp2{cbn%TGYAWO8*?eR!z6dtLUSH5G$f0xx{_1rVyhc&MU~@WlO(6WjF4xfIbe<*oXeizjw~g zJo;f4C2=0sGx&_^RM~(EsPQsGi%6%wXq+tZbuen7&-uNbKmH-@T)QhX zv``pF4IeC)tA^w6bPi{UM@jLvebwQAVts&TcCtc;#b+6s*zgL87?!WQ9l_O|zw!*JPtNX2LDPid-In zQ)C8r=``%TZm}^rbENT0eaAuk$cAK~cKIfcc4h13oU&|IBMJ@g2sf37g7lPHkkukz z$l|GoQqVL&I3-_=u@xFuD!m_Y5iU+yd zS;Olpv0^s2I=~bVb-fn-3qkHu`*=aCR=&;ra#!1U=`;aJY^^QhV2y zPfI<2zc0`B%Sh$0iRFNcA(yY*t%bqgG-nGt6w+DJb{vyByHO+V5rL#5q}Fo>}}RkN9` zvNa7(C*bM^>5Wwq^B~e8K5TfIWs!Ad30J7NXrEieRljtX9E$c9jtnnIVwwrF1*@cFzIyR`<>^3TWc45W~c|=c3o)g-etFq29ge zs+9YxpI1oAXI2B6z|<0KaX2_TBZ&q`mp$R;+oe8l){pqLmm8cd)ha};m3}`}6)CHL zYdj`7qk(;Vyt$^)yd#q(jq=v|`0m5jkeCP5diMyq$Z`BP;}*W_)K^ux!MM!K*HvgL zu;>ezo1jbOIDVkwm4NS<`7!Q}fC*eKL$4m4JY;}eMeC1%-#hZzEX3fnlr(-(XPF=V z&XSImeyR8R8#qtZ3AHM@ufFm68nXwe%FwT^xsLtx;h)3C3Arg8L*;BrG8ThB>&TGE zcYzgEkx}ykaUZkScA0o63V=H`zQO`=+z-LE_+Mj~6u4xA#`PQq7B)hysLF}Q=>p(0!#{`H zs@f*)?=MK%dp^N=hEYziUvL!KWOJH_TVaS9?N^MAnauv+x6dCCxjylIykpWB9&04h zD8k(5Y{Ypw0tR;fk?UyoW!*KY^@6gHknZWNI=$^4?=n8)(g>B`G? z0BXw=-a~jW5N*DP_r;Y~)}iLdFQPY|k< zUxR~d0sLsOPL^$mczpz@h)Q)-SeX5Y9Z=q3r1ei2yA2vSEvj=FCW!nQT*cJFf+ukP z0lS!jF!0kUxy#9p0(2${KveJ+@Ct}NQdRO>C&~P7ada*gg0hqzs8T^!cSIAQ8nW{) zczYux*U=37(px}fV*whA$;ru~fw{BP(bi^dRBhhHyFQI>C{zO6PvI|BWjcKw-bn7V zwl;IVi>}MhAx1wlZwo2skT|W!oR-*foGNCY-*TZ)F7Fr|Hto-2e7EF#t&poo#feR` zVziZ<5S`H5g3FDp@E2e3Sa~h;`HS z(o0PXa!1yT)bDQ!7&pM5V@iscx019Es&iNoML&?b*?ukpy?0V{i07n2++f8~>U%7hqHN@&xD}y_~oC*C^B4SJL zLQD}598}BDb|SqvG{;xC_yX(HLqK)L zK8b1W56Nd6_&qI$mK-ZtJv@8erS9P0Eg$bbaErjsE3kn#C}fM)4{XH2nYQ9lovz!eB{gdYf%IhgG*OW0Lf|+! zY+3-vRo5s<0k6O!j>CTm1{DE#1x#hP*;j?6VbG-j86B-^!B_QChp@3em_j8YCRRLA z3Vr-PfRc$ExiV)*3kwU-xf04edW8oq<49letV_N@*Jo&e3yg|_20mP|?mUlS&Q(m}jh!B*)wF$P>Z^wkJ5er|0D>QeSIA?CZw{3haPfRkk_`Gg0J2B7!${dp zFO5ULPyT}prEDtO5$maokwb7eHSNt|HKC6=$FQgIXR4q#!LWH9nSh`m4lb_D&t1Xk zUIaxeind0QU+|94z|y@Se&Mt`rAIqj8ftK-%Zv4=u9=?HQPB#lzK97at@r4xoCM`wx=P-Q)NK6xu-hubKyK`RpObr!mga(6A?22}U6iT|WkWTqu?s6waRpDX9N|ZMZy;?f!$`T}w|-VcBMD ziIa-ERM)AjiBMDG^H+ObIj%bPX$L~`yT6~IpAhF7;-NsqI0;dWqK!T=+351q z{QTmLo{kKnc4HJ2KbO-7ZoDK`BYA$+w<;k`T$6NlXX^8SuU%VqcK;_uM0eqPTAOlph| z3y4V73&$L4uTJ~u;{Wpb8O%QcYYH|8sU$^knp`Ux{qAsa^mJ%QerDtZBN|O9l2>wp z_tV3X1iXi3lTK@IPfqW}-5;NkUS|-AIAohSdA&wxmln72ao>G-y~snv;NjN7cNlIz zR^QO@@GW~CjP;YwH&p|x*{qWw6*KEe9jytZ(&IJPvnt?;LhEh7*RL;^ZP2k>RVvil z?VTZ!QhS$K{x{&TQGEQ;-$0zcP4R-;5G|OAn@Onzmb7s1%q@G4^IG5 zY{M`1@KE&TCVtWPSFOc|vjzdTrB%xM7^2}41_15Z z`@^17#~_Tykqy)k5dI6GK3_qK2OO(C#6;dai;CSYcsnD{M<1+0!E6AO!N9u#qkZoS z5fdsFZjldCJj5@KqHh9ZF14$Ud}Wk;)Ai@j+^ z>0Cy=w9re~0Ol0Pq7#{Q!2dH(ZmN^l-RaQU<-m)`hH-!l?6a-%qM7&NqW5EH;$^bn|)LP!1$P-P(Iad?bGh!8&Z51nnO^n8d`Y0v5Vvm0BmMgWm# z6!g3WB3hbIiRp+JBLvmh{vzuXU-uh0{#q#u>VZ1g^izhja^#2GcOX~~??@4r8B5_c zB3I)lYvatrdIelAB5^7iPpqfRMAuVQ3p*(n0#usRY)1s4M+n3Ya;+Z_z-QM$XDh9Z zAKfFkdzgO&Lp|6cXgv`9Tw7aPq}zqrfDf8JySuxe_w4*91`FiDj_qS-`B1(bLuGfO zjbmtXy7%(YXVBnHR>&}AiYFL{zS&4lYcHs&k_u9tW0eSAaUc3X@DYt1 z+w$z69t;G(0((i090$r1lsCg%lB~Z!Ln1|G5q$0CG`_1f|$+-#PcCu$*4B5S) zhGE=K3inR$b52Hl0da@leIBA*L)oOfp^O)N2vV8pQYUSaf6sw+RdBmo-vMnq$NE9v zj>@Fjbq(G~7KK&-l(Z0(v&S!W);HO99sKysJi6weF8#fX>VwBOc`iC+nNJIq#q9IE zcK)+}f0Yo;ncKqy7l&;TT86#}Bz?IA+LMuxnT8-F7gLCeA$_jweJygI4R)@IyCw_V zD4+?Txtxa;-<=aW_Tg8Vgc65Vd>a2l0ti3BtN#qQWJOuD>wOI%gp=(d7ca;5)ixVv z0%1pC%ab3^`0Z0$v}fB)mU48Son78O`^{&W-x9Ad1+=-#aQi=QQopBB7!5zWB07W2 z^+X7J`T6;2{Qjb;kn-eVRq8^?x*rG+X;oj(`s#~u>Jo0{KeGL8`#o5g4hSQ6X{ehb zFSRbPBvjah@3U1Fda=v}v_koX_SaWndDbXQ-g^s4bUco`NLYJYf=7Le8o(!EGcl?HMpY0#J-CbI22dQSVAF#d)b$5`eRgMyyWSZ!rJFOX;9vAmRh5c9pMve z5uLI7WB$kXqfA#C)x^mk4C^td-=i?5PZdz|$p{D-w`nqmiRWQG7ZC75e> zd65b3I!6Q*R~nZHP(!GqgpD||K`H+39lG}bLK*XBhAB|k1-*V|fA>HyBT{F*&doe& z@()0N25QBu<ffM`BC_ImO%%$K>rs`nhbMf?ZDG|c!!wqI zUHrc+L)z2?G`w_y@q%Fb|4;!~`pO5wD~?Zhfrl{fD1`hH0vrMxrxU;-h);jfs$2S> z*tWSUL5UV+Whrltuk@5(v!xo7u# zBDF$zJr&{ao0qu!_Ph5{hspq3UAWV=0B|6J(RE+ZoT&H&L8rQxUBhDZ7bD^lr?Vyi zwZC|~Vf@c*0I#09*Uze&SF98J-rqR8;o^1RNkgpQ)uV3U2zWA#G-6`&85DqW^Y#6= zDBiXVWfMG*0dbdYnj>%{%^u-eDU@bb&e1%J{DW#;lp^*;(^sqdYa(@rWDU&?)5Tq5 z7k)#H69obFf2RM1;eH!({afVEyrrhHhFjvXhGxrCC{H{guuZLav!$!tYiO`B&^JN| za9{ytzMTB6WitKg5s#2H*D&%^9y@@S`ki%c-&?b!>#X-T)?G;?hDwV@-DqoBWGp_8 zf9FqDV4_Kf6gtFnMItdf-Qw4N|l%i47(MU;dlODFG zDVNjINvmL+0D~orPr*7_S(E2K7xH8NSg%9>of|$E|LB^U1fD6rkbUaa<7`+=wwV6r zym=F=k1!Vzy2$ucZXwv_A4>fE0x)VLOvdQXQ6->$n7!0e~$Sz&EY z50*bGwd=CzJUmnMh-pZ1=5aOdT(a^Nl+=Vw`oUZe+zo0)<~N=-cOcTWSn5PP=(pnb^dHN7`7uiV*?>^T zJCE!F<#04(2#A!+lXQ24z&2XgmDrlUdl znmhhIw1c@<-a++HJ>vy%$7<$1uHg8qC<^jYPG-zqLPc0+2PbnET_bUc%qc&>=R;Y3 zv-N7p+zZa`B~A0Oh3c;6Azr^yy}bbK<{#!QwcH~M2E50o{6N{cSOHb+ur58{!j=5p z62a<*D_ZHbFMA3fNjOI^C!ah>*)au!CZ(lJ*+i~fi_{?APM`x50nSZBwpt8p3~uOJ zepC6zaqS!ji6fpAp;WCEQ5D~Zvr5orCeAYU5F9Zlt79r`db1Vw;h^ZqcCPf!8va%b zfd;Uj#>mftG3(gg0;305KTrBO+S`loK#{LP+k~R3&?~#}G*K?LpP+OFjqXpfu)G0h zqR3_7fSU5%=b?NKb;EkPR7&di`CiBO-tO&xu&sfDe$T?%*^poBKNV*G=jUhPKcXam zQGSOWIW;_)2)Ub=cZxLKf@+%@kd$-{B}fMA(%6E5guM0<2Pt1pog%zx4l6 zQTuhyb|-R0o7&QjF+k^ELN3C)hkh;r|Kc zjLF`CO;ogiV(w6=$$5p{REFd~O6iZ0L$5>muQ+d%+mHpLOr>Hp+Y!oFsN<`HwwCk9 zcPyiTz17l)Lvx|uQWx9L>GaAPmVF3V6%<{;)=8-1G57KD&OF&gJ6fu}@r2=qc6#Q^ zjo!B@m1PNgBfne!*5#Cz?Fg~eoA)znBlrlnB@rsbwpl{N(($cB7D>5Cgb4L|=nu*h ztJ52RS)lX}4Oku*PR$+y&Vc}2bR8-lVxCo14Q_eXgPT@!2?mp0aWB`vk(-*K1vb3N zvc3V2f&0HACNR()c*+!Wu7ujH2_QSrQ;eEuw}BBDf_|9grsF^u-E@HYYwgDG4$2-b zB_O=jL$-ycKkqSGW^kw}_ov;Z6nDq7ELMceq+E}4sy9ZLqwEUp!j`6~-ybM9n>~{m z`!zeg)%NeojQ`2e>ul=+Sv0F>>`dAQ(j#+QTZYvY4MR@E@p^ZV33Zj3I>I@;0$h}h zIp3+7Nx`w=!%5AxiatJ9lk|wNz!%dEB8&Dt+D#2~V2#5voUONM(AX)Az3EZD8J6@p zQrF;vVZvx23?*ldGW98%SR5AxejNdpsz<|{$#{GX44%TKzAK^!Q!yrTtSyCq*c49=6JV<&Y(mbywdQn|Vh<@Fy%v8~ z?xCH5=lMr-m@=lH>@cd{{iv{jKs}jL;XWOWk4E&-%xBVBIL|Y`7T`Q znwDN7(V5l2qfuR#mM2km;Q%Te+f4}nWBe~%Ss@WsYbz30;`;<%>oy>C@NjsKHdqMO z)SGj@8j#$9In4?pEh`|&s zs^kB~1wgSYcF3OXU@gg=qti8we$m`R1nU}~WF|9uaKr^MN7A+ihzJc^SV)y_a5N1PkmN4Jsg0WI88l08Zw%)pKqcnqB5s~w5mirf@RgGK#Y?p z1+1p;$^D!t2qZvedDslOTsG1?;D1f>F4D4)fA#)5ux><%;r3_Fv^tXpa{C3K1l<8p z_`>h}>^UG4q%Bg%-Dmu-h$DpcLn6A%;hD3C5yB?Am9`lDz+{2Y8Rb@_-3>rPZ+77- z0pU^QXa0?lo9=-5L(V$0&??nco#0uB2?d;54fajJZo*qqPY{03PRSw@l>YsOPp;UY zHH`&D7dd(L<(1c#SspRYk{HYmeQ7uSA?U&mBqgR<5)8Tgf9TltZ8yL)kR!_|O(m2^ z(NMp8*MO<~G6W4-7S)ZUy)pP4-W_{ibWO(!|4D3^4OZ?!VuQA?eK(9Lr4F%82v}Y1 zNX?wF#x1GJS@~P)^93aS0scckP@o_X1P6Oe>VUUUQm`Nh+yBp|doV6+dlW9o70t;( z&yO~kIvw8yr^77{W<$(E;Ts|J0luoC@7i)u_VtrD59sAo6}k19(v0$F_u|IFvaPY^ z(j#d)iyuC?N&wG?Asp)wB_;kJDCz!_#Xw~bGNq3(S}v2KwLz#$eyfb(yVgSpFXCm| zZC^gyTd>BElUm+6K!xP4EA17#Dw*olM4Sq6G0;9zg0~SM$OY+|p13;`P_I8j&jYf! zClR}Iv2GOp;X#J!gkaVGUCzbq%no}b{sqE`lD$G_l$!&ss;d}JRYg_b@3`mzZoa(xE;duxQ^NBya^X@Gr`^duz`v-6W|Hanz}c**@|8>9#j5BD zteuCQ^-(<;kYls zpo1la%;oNha*-TmXgQO_>vN`Ff50y{r1RARS}MXf!VfGnY#lcaCfb^o?f1|}{W{)E zw;%A!_XRJj{3$gX@$Pt29U7sK0FI5^Wa|O2FSHq_@24tpD9P{HnDpcCYb)!QA*t!( zYhXpRjQ*1FShtL7A!x2v^_hU6-D0(o1JDl@-8PK>gURkEUCzwUPJpxm-0%A6>0UvA z`KRVcabRoJK~%4pKSYN_$m*VUpMdlO30Ty;ScmxO*feT_2P~PaEQCL@>slCKMAkt_ zUqnKp)L!trQugPPLqk+IwD@o4vcWFJ$E0m$PSNnxH>@_^z&<;^yH9}}vO7(m6}!p$ z%x?u2i?hW%49zHZwS}IfZ*tD>pgBlh2h0Rw)+r^0Za^B6+$v|VIP?uDr5#Bw)Rv{N z2zJhMOU8V;QBW@6R^vt(XRyOITSskCkVd5piX)PDU7iVH`Of5yZL9a|LiwSc#lEPk z%W40_eVHR0{T(oZm=oF}14?tW21!bh9wDXi+<{U(7FvBp4&i&0dBI@@-|e?PLk~FZ zojV43Si+*7f1BKDN>9WPYm)!_=e_r2T)o=eiEqeVOZ1oe1O%pg@kv!awMH#LM>(PX z2xnLCgvi5(1~(HC_a5}I{ESsH@@kg_9WqRRsl8avFYw#EVI2+(u zhqAXteEs?u6*Y(aB`tCqk_6DMi~O)&64z}_eZTve0NQ|@4-MobV{9}BbDF?4{dG)1 z9+jZT>gq}1YNb2CEP;WKQ%DxYIa5JuCOhXz9bnKt9-da^e@z68gvkA_5_^I`YHDUj z8_YaY>s;$6unJxQ0n0=@MFcyGYEwxGH%-WVWnUs2`_T_3?Q}-{m)8QRk8a|}?;z{# z)-B6b`z1+G()WG5nD$%qjuO|0q2Rv#MGbpXFAe52ys7r!8Yt)(Iro59Bl$Ewy{=lF z9_B!Y`NL>tX5cFWt|6pRk^3{_npzD3AevL!*d=weSxef}(8%1+Neh>aOB@7-y*7q+*xF~0G(HPUf)F0xQ^vw>r7)Q6PkJnxgO2xDpG-Vng0 zk~DrkRwmS&RxA=oC76dLws?VB1^qT9WggV$!iHTO>BWK{Yb|!y!d8Mgkp%f8!TeLT zho7Dc#HBNmko9L)xiOS)Yi;RD4d&5JPx&!)Cir#q{&>MNsmZqV;uR(3j-0gg&4Gb| zp268lw{)ld9}HtIOP(F`$k*i9J8Q#V_xD~+x82`)@E~(Unwpvr26umC zF!%%H)&Cz^w#~mGiR?=SI$=Es`KXHF zczSfxN#z9>Y`!7_)w}=v zpAI8YBRP6U;;Xwbv`A;l1F1h8mNkGoIIA-TCBLYH9R+G;7R=C=JYSKxO;prcREqZ8 zr6*-4y>(zYWjz!0K-h=sIX?!N*pSR&BJ^w}zD_}b#gw?Xds#4^C|4rzYM$xNbV^1^ zf7yuJm+N#S{-3{-trNG8^uG5PzTs9W?niEaoiw;=zC&sMkYI@OUgmqKR6?0G*21qt-w{Z`3EYt-t{LGHjzlKTHcWt-4$><(B}h^C9N8eHq)k=Ld-_CH>y|vXUNH>8}*7+qWM* ziZJNv)E~(DO+pD-4De>B>ZxP)e>>#z@$%L*M>)b%voIzL?hYW8K;2BD6W@|$jCu|m1IwXAuxua#MAoFq` z>f@qaub-hfD|p%#ZJ&n)#r{Wk7~88SYZ_>k0-7Dy4mP&Cd>Wc8e_Ja`*CCEJQ%1vi zre?VCnRr-0t5L@NiN$0-3U7~Y1-JwPqdxU}^sQ0z^EbS#%ZUon5ch2}KmJTXw+(By zL%0hDra!*5Ev${>?~tL8!9D82^`1_JTd^?9AG|K-d4ELI3n}*X{V9opnP62=a_l+x zjjW3UEPj_Ur0tGOT;Z`JQ3@H7FjlvwsxJ7&-YwZx<g!P1%WZvf-z6ydb4St4ie2 zkr+>|CBb(JH(T$COYgKBhTX;!;uR9Y29?~$vhZG1KKSlh*%I_w#9>JT$fqR?OC*pW zdHD-TkPbvFi&4lFl=k+Q-mXxgE=#Mr`B35MJI{wQ^joXNE^pq=pR9frezVD}4Pib7kA$fsdj4Juz>-QJA7JnIM2b;x1O#EoEgoLp6J zUdf;9M|?oi<>=^|>tx}vq}3ky=PO4EYZ+n2Ho2TScfre}-&@73MW>CN{o1vhO9C#j z$o)$mZ+c^paZV2*fNGK;a6n(mPFEz!v_izQc;l~&&At1u;pw^8<)2PQ=oFxcMc1hA zdSVS-g;ji~8`w8*-ee3DcLM1r0U;q|t_z&}CZH#PHADrb4CumqyTkvP(V+9*@ri3O z&Cd>fI7?+(Aa{Wfmw|jcF*_>+1O}w-8@MNNAgn_$Hwu$-K!8^l27ydZPrvPwU}thU z-d85Z@L%jKFmKp%5%t2w!MV)lE3|m61%knT&k}B7ZtfgB*~N4qufp`G7sDeXO^q-u z@)OKh1_4A(m^Pp@1A_pxfg?}?J)FRW+UilzK!*Qb!@xjTyF@Sm7O0szJA5$6LAMS1 z$bRq-6qw)TJK4td<2!uFh2B;cSw4t!a?_Gm{ECSU_A2Lo;bK})YIRfz?3`!7_~coQn+==%$*lsfE8`U{g+c6H$bnIYO?@gYp%LVHaYzJ6jr$6v0n@rB)L)0G?UuXuEUNayS3BpVYV8}Xv`V?Fq&EJKi zb^^{a$ZBalCTVIsf71N?9ImG&f+L`AcDj?{x>0cvyS>ETa<(-}Y^=-~zR?kwpR-$S zd({SYAj7y`sQcdyH2=XR|38JTe*-500mIQgjhMLsJ<#WzoP=o$ zcTH~jd-QuD*A!O^s!s(gc|P2y`;ySYMn|IT+9(rqq!^=}fq7TxF^os(*j^z;^3Cd6 zB*GB!VTx$MBMdn$AcD`Y(Qtzx>tTqgsYqKs5Qfq!6WdmB|g;}nLj;);kc!gLvdzH}d8+B2G#4Op~E zD=YJwmDzt;D-*l@u)!lgpQY5{^5%Beil@gq-JX+5K9=@EXO;0-^q0zUn6u~O;kh90 zO%P3B62lo(lcnSC=`Hpnv0l8`h8I-lo=`Zw^&;bvMj>MPb)Wz9jEEEiie|^p)#m8Zx<|X=W%QtK<@o97IXq9=?W2{o3*<>l{e&62@X0AeXAJv3~r*R7GQ zb`Hv?%6mt=q2leCu?!s@WHh%jJRYlJ>1Hco%t2U{^fyGKk!63JLzg0dpUKItW>F#J zMc`zA2}dR650MuQ+RdSgo15aa%H2MrrEbU%4mPWdhwc17VHC627>l6<1d1)r&P6De z6mXR~qMJ`V$6_eWi8(o9*y<2IS?Epk?>R6nFTEUeX@TdCkTjq~9p(EL-(U%gJFKn{x}LM47B*JH<*%T#M~`Yo*W?2sA5O|lb~ zi|C)nmzV2v3~+Lc7ivCZ&Utta)P1~a62_9S#d@^9l)(WKT+dn~8}4unBo@03;;1;E ziM2qF6OvCPySt{JC!R~e419kMwhEi0hQjXfhmX(92rTugr>YKkf4|K>+cMM^FLrRI zu*XP9KMhF7JvTJg+N!FjilNwMB^?;q13>3rX4tz0>XmUS`9cN7r1O0Yq0ii&gUewK zVIVxF_pN%POK=mnE9~^dt!YrbFpF9!Hc8YuW!^relUgOYSx>$z+9`_+?i$*i1w!ag zZ*j3*b9D{5MrM?<^wa8COG~u24Gv+&Q-xoWn0giOzFPPZ!!D2FJeXKD)$C53{rKtN z25CgKw%;cl9H@M|zu%Yw?;3`lnc1Ka*;a%-F~Li#Ht06}Mmy;6HYKJzVu_NhN!ise zX|9jg63hUD2`qwudbH=tP~f|O_; zSPdqzo=SI`S?8P3aMZoK5N!U~a9T8iP3{Q8NU&9A|wK8*M z#X?{P6KoOC=h)aVr=m{qxGOBA#rQTs~;>ISpP>&5tzk;?h_ ztEs`_G`_X6f|+~$$=od2zZWYITwa-))nsbp2^y=EsMsBZHb1o28q6CNa+X* z2w#?wNP->qsmZ+EF_%`z+w-l_Nf*DT+?BDR!Txx~ETN%=)qGVIYio|;_U-Q$E3)~pMxu3QaG|gu zlpGv__ozth7vfLi;-;BiKOZ7lt7-^TY7Wgz8NUCMQcGhS;i)*J((_SHHqK>(n%6)i zK8*uve498|?Jo(!7vhhR)7F=M3g{b`X<^M~nB~I4<&quw7b71p-R0$_x0)Ysn(}k+ z$k1R!Ioeg5jdsjazQwL1`h-Q->h|MpG4IVvH&V!Q@=-t-uB9{Hy}vMfTFDRxhnIU# zl^U{#E9h8pmg}0`tVdhpZYC*Kqh8xdd2(}l@;FLLpAf^8XDUI-lKoXO>Nu2z8X`+) zIA>VjhOb)9w@0Mb5{-kS?(->4j*$|~@NO)yI3{7Wh__vP8tu&XK|ZY{-2oGuZ17`8 zEL~GT!# zghxIku;S3P|98vuOjv*33KrnY)*k5?Nq*ZWW9#!ANO&vvEg%4z^3HxH)^D!;fJuTd zvd<<+H$U#Q%lZx_W)xyaGL49dFE>XOhrp@{Y2zI2B`}!`w%IvyZFS46n2<&H!)M|c z)FY1rCsXCwGt*SHp%_12&X)a+p%Vi~3|uZ6)}oUUJj5F|WJ%QImPJDBvgbUHNPo@29?KG(6bfT<`xx&mWt&MG~$ssc{YPMbM$7ZVy&U+?v?i2w=xhrP} zF(b|4x$b4oZu`4KLjzelM7AS6-xSmb+Pag@(sn?@YF840ocevhoidvav#Gehn)32) zf$ltVC#OiItT-+>)B8uojArTz%6Z#by{SBSczAuHL*=L8O`kE#!387n#MLWrYuegA zQAg_wpPg31`GvSR#3-%I!{a>EH+XBaB05v;DSo={{;QynxUmrv4PEGqzUcE0{&Xrg z4dYinepE$y&hMN$2ukCm(0yk4KmGBl_=Vfdoo$Py^&)3yDV9m{gCOBe3bRrqqZ+f4 zT@S5bM9dBHRTZNhctq@%j;kdNwPFLQ2y}^EZJQo34+>EftUa4MJE81t#7IM#u@EsO8fyZ?f^W^4( zuu@JNW7&8uIv7bv&=gEYMNj|fCnd|u;K6#g%!Jc==Tt*z?ufCe9U6o%83a+UO>4w~w6pU0bs^2D^GS!n_Uy|J>K4qlTxBO3qcr>A=?$NSpMQha33Ae6|J*AO^Y8xg_u z@{xEmn4=~jZ2P7sSsjnCNIld6%dKVYJ2ladY zz10iQepv;d4XA}U_$UBE-+Zb~e0()c66_BQDo{KNWuR-Se~^@II)dU$-lDnOuc(~6 zsa5&G6eU%uD`aCT9}MdQNEuS3hvLay3g}?N(W=^g1u0Ujcje_6-UQYVd&p0L!nqt7|Yz5rg9hJNdck5 zY%M{lYZ%^Pd}kGyeHp%rAq;%ty1I9mn4%WO_=vEvFGBbM#sN3BL-ycVvY@VILuzc* z8Wr1JN++eF9CT>$1%ZDST^7Uw!C9icS&^J$!d4Z@;Rp~Up&t){km@jO@gnaq7Ou@k z8uU;$laLy5eThCooO8rp+U@gogFkf?V}^3%I1^&8{KKFvIBnF@mi-3K>q2)L?qTriCnsC zY$gF7USOci$CQ){*w}7zK*ye`vt(t>r2cI(6i}>1MMaV}N<(KfGB~tY3e%+5r~II= zC$Kh4hbMLSnSA_8=f^^ga?O-Ou(2Dpq{5y)642t?uABsUF9L?fq48`8+=6itlvKZ1 zUcG98IY2d)7e-yr?qLv;)2i_9J1#7I3T+7%yLLfZX6t>xbb9hmMygDZq8bE8+g6ki z91W`mLc5*As8_Nl5A4ue^$})&e(*1RAs_iOS<3I;c>w^;rzumQE&K^~%AYD{yNw#> z9J4he)+XhFyru}IlFqfI^PSdV@e<F*V^n)S2jSq;xYl#&rihXl^dz z$=lm4fJ|hH2Lx_;c&z7!_oRqB+uBlJ!*J!=YyT0kznskyd;$WZim%^|EhOaSaL&hl$Z)TS=+PdOAx!llI=yiBpc|!=*`>PgvobBeKBFrsW z7!9Ov85kU}ciZ%(-?!BV75{-pt(|oe&_WbH*$2VtC zA!VzsiC=G^-caCa9U2P5r}6!5YeoVG#0{N+#d5ao2^SY+4F{hWZI|bTot)UNUKQG1 zm6#eXyydWSEzv6V--T2#t?eW}fwiTh<9&^{<5&Mz^;>OgckbW!gKS*g#hy>E^-Z?^ zD_d1H*NDpI7FH&>ekLZhE0{aKlvt$XGgkSf1N@U@T*2w9&g4Pn&x+`&Z`36mtXIjz z{hAGg*lsQ+W+uv3^2W`{+G@|&=k~3lQgu`rVIUEn!zbze%`za74o%})`|E=dNkvYD z@A}la{y@M90o%mf+@Ff~KFG6M$@ja^o)k#2)Q63B`;j3AEF}^lKz{jKPl^PdG31M3 z99#Kwr$NcaCtV5Ip)7h7?pSKI;M86O{wX{=?UnOl<>ift7sJl9)VoH7DCV8c`fNhQ&{myiVtbBee|K>@6Pp?S zr~FA7^iQmnrAT%4?Wq%%UYo9E6Wud5FL9{&g1{(3(Rd~#bis0N_5Phoj}Ar9=rn-w zJ}jf7t$=?xs<=}F)m#oatktuMAz%KEHBmWDp{}0)&3(H4mQ z1O!bcc84P1nX`wetd$+*^?3tsDQbY2HuSQke(E4Rt=XlBA)PZT!uO}y^A3S32N{_;p;=zZ( zVv$;Pz5&&5nnQzu7PPnI3pnak@#tF$yPW?G$m?`>s)YIE!Gj00Y5BezE$oDlloBc^ zT!bgfXsSwshIy@0^Y`-Bno0jU4U69C|Ha%}M^)K(?V>0Oq5>jHhmuNnhe1gSDBVbx zbc2WrNJ%%SlnBxx4bm+Q(%qelJaggm?EUStzwaAoj6Kd7XZ`bz_YoFr-S_>Q^P1PZ z=Df`1i)|MYu3RnE+yi=dFIswjB?$eI&S!dS>$)8EjlK*96e4bi5|^Ap2BE5y&Y#vJ z@W2$Uo`z7yA>jp?%c4(y*G(`B!Z|$TQGOtc6LN&qulV*w4-JW=$&85F*@-LtSQfQr zb=&LDVNiMfsa}MVx#BlQZUv(G{}mY4WU~01CSHIy_Aig;1@Mv0*@j0da(D6{Y71&n zi2r~at}v?aLnFzyzJ9e1NFZKYKIIH@=IV-I&+}u7^W*BurD66rkTBuM*M`ZhgY?5H zRW7L}vr#}#gdK}qcHwYKdfX`)zp9F9aLvf%mu3P`Dwj4lbI&5u3K=MqQ#t`Y^0;t# z10iS9-|Bz$M%krYLmBPH;EBd}7+=6WCu?-ad_4a7ev|iv>ugA|a^MdpIBWYd$f_$v zY;Os-Ipx{f=AL~mfEgb$Q>hm(*kNl6)$p*W(q5n#F7ibDCB}RMc8wZ7Cf>orWB07u z+b(V$_^$z9!LO3F>PS|7wI0X~>+5J@fu^!RxdFW(u&+XZgHz??L_i>1#n}<-4Ahs# zoX6@=$P<-l?$cxRnR&x|!1dy5Eh2fm!2o8cvHfrzp5OGX`cxzCmY%1%&gRW6*7QtSp0a2`Eu2`Sa6ibTVVxj-A0deUbXA3t1r%`zr}t zp|hc*Mu>t{ckagUT2u5c3pzTY!vDoH0J7~0dbu@_qd;VEzo_QZ8!KkSfc-T@>U|b{ z%@JG{*$IzciY#drE8M?NEyciZpTBAZ6$ceHA!93DZ?G3CaRFpL+xb<4iK;dODNb$+ zBA0&E8c2PIy-CF|!Cf<2nvhfas*uacHHu-Ry@T-_`}&*y9uOXww}wBQf?epMV7Evu zSosN%UY}bo^p)Nrbt)2pKwiyO9?0ff&xqc}4TLP9R-rwfHQc|i_x}J;a#uyQx3z6I z6?i{wITLksY_ow<=c7O4!O%;?c5RQ+8)Idh)Benz&}I=v0X~AVG1#gjSC?D0AtFY# zezrxp`)3|M+(2_U)s*5~Ij^ft_2hteA4dTu=f6g$Zc5;|K~y6rwI@R5s6!X;pu2>I~adPDsfy*P9n>{6hZ}tG4}l`whw<-C%XT>`Og+ zYwLPAL-FwxHnuWKemYCe7O8Cd>ys$SVw^_6qr$y}rh+b%oYFDdxCKVy+l$Fx@GOXL z<~mq-0`~_a*YpKO8>#kY%zTdf)ocMa{q>18aaLHf9o$pDzu1WdTk39&1E@tAwejD} znwJw!{gj|7;`9@nx$0*yaO*z|!$n1i=q|`TA{en9hOi3^BC@uEVj_}S7lBarbaM2l z@Ue5&fbI?t zS2Op2q`fRAA1ia$ba0e`A{-Li{91`sW9=od73lt6m7b1!|Gp(09VB+$+GGLjs_{N* zbl@G-lNgwL^AqP)k37z-3gQ8Go zH!&%enS?fwKQg6f;QW7wZ#8ysFgSnE>676W1Z#V=eE$47y06deY&&Wux&uT=j|T;m z3k(ypw8nf23*Ep&G5Pc4vY)$*jh!993o>v)-9FiIg*FK#cq<|^DMWbaoscRIP8gcm z+R8}B-Vv9U#)l%H?IO!LXKT{unt0};bYzEc!H#LUJR2a(bj4K4&Go* z8}r6Oa$;vsNs)d~HF6hv*paq^n=~yYJL^%&K$9@|gj4c&FXMc11 z`*vWnkTU2>6oUKrsB-fq!^+4#D+5UZggFeLf+H+xm*fy3N7=6g% zFGLdfZP@SSsme$y;3?!&U{sJ(9TyEO)pLBhFeJxCo!Z@Nr9LiWQk~cou z$IQ1!B2N3a$fFsu;u~;6JJV=UfK(|{^dn~%FJAvao@l2JDK$eLKu4C=DVW^Seu|D3 zPZnb;v{>nA4(TXe$u@*OENuC+HrUhG-_U6ZeQG`y>+A3D<%Rkb>hw;)n`~C&kEf?Y zq1rdxoV+#Pz7&w2J{O#?n4Yf!oSn7x!~fCZ%)E2__p0AQ0c!0w(GfKYM2~WZG0}r{ zZS}q+p-NQ6^bfFu83S8eTj|V4m?PCp`8>b)$&=B;og^#6;rzPQi6k*=R=cI?t^h*P z#cs-)uN`pM$K7@aCfuVQDTLx!Ss6i9_nU!L!q zx?Yp=%o@N{4XNWOWW*`t^(~puqT)L;Lc_XQJ3Jg>(n|qss6QrgCk})+4BS6#MzRa} zI|+hupuL-zfqq|0D4E>`8SEUJ-(1c_=e4ErLH705v4{u}a(s51`eS96ls06dHz|eB zqJPbIJcV!5J6#D}wDV>y*QmSWPJaHkZ;W81EOWeH)t*TD4E`rxAf_ct)s@$t&L3NN zReWRI3x$d{Dk#aX!N0t>-0>E)J~w-$j)$wy*~s1dk}SuFUX}^>r|z9uTsnU_ck`3R)TH@_10u-i5n@#XBQ-x?uy*f_zQO&{jw=X=k7UstO4Qr*!WDi%-rIhU;b{aV1M!N5yvQ8PSrWK_HZ%SfvIz`P>ztOW zvI?OFdLw^2~hIEo)&V>u+-uh5-0?8ZWFr-ARqTy+?+yE_q@i1ckjmX<9owaWyK zj^6zW^_#DeT~#EwpPd!>{AxHgIT;LQIr6>{1J40y+6OB&ELw%V#lip#C3)ipfaH49 zu+R&oJ}KW}B)=>!%{x67m0;Y_p}ptY7a!c@qA~0H9~!g0Wq8cYZDm+k)UKQV^iLl! zomq%O`O6ATx}Rl4o0}{uMZ~uzCEeUQ7~Z=;2?33dC}ayrQLcyW=7sN|=(mhQQ` z#kVnHUwcgrpa)X=$%#gDE8UgPW`jA|F6)20y{EBTFAoxH=$&2FJ-C>dsAoLqK7=)q z-O!(JPhxB|8O(kGB)xNgvDK{0x9p2!`&Z%utBC4>WCKRk#^xhk#5JWoxBuCsmAv99 zeEdQcYferff{-`=D)2;8R-XJFxN?Ov0GBtLgo|qi2UACnE=bOSwY0Q(RO*(`U{)n0 z)CrAV>)Gb!%+Bza^?CZq-`&jZiCDi9wndQ_6Qo&Q# zD@>8hgRftU!EmM4R8V^)=f^wzbqa(;Og6^(tD#osb+`@=E$gV3WN z_4FhLb`@lNVMq)eETC7}SM=h42a9jLr6L8!|3ia#>wh$eKjbj65Rf>{hWyUAK0@aB zlh-3e{Mt{6g>gcbFXZ&5V2*v!?fowrt$Q>m{~PfL_T74o0h)76I6uAxk)A^PnC5F9IeP;8>dMxF6b^ftrgYnuHZ|zMJ6hnU;?66&6~dFdXNTk4<4sYa1c8S zC@7$0Wvw0?W8*7BWmIzltX2+WGzER;C!jBdPB{{}Cj=U^R73VGKdg+qs!y%0wVUOT z8bZsob^oyc-3J~I$Z?N@eQ&{R!txO%#phjKuco2GB?>=3vH!J!I#ja3b}(AdKOOy0 z@fRUqAx5&edZf6x<7cC;N9&c2aiR$JE|JZ4_O(m!g$$Q^p019@Jb?xQ1URRorG3y> zK66-=xFosGgILa-+Zd$pTIyA8k0}U#>ccbx1ZT>#DY#>1nJ&muZmf(Qw8to!RCzD{ zvQnz$#lko6NPv~#fIfZ#|1rC5+Mey@-rgJF_Zhk+1$wEOnI}G2RRU*D1Q1u0e`5D9 znI;WGzH3cPRWSwO1}sZu#50hJB$);Sz0JMBr^XZG+i1-F?t*;{Teuk(E+Qgzw#mW1 z$f^kdmJikM#FH6vBgA5IZw$4JOuZ18?r zHl6@UDRaBY0&*cvMYfcXum97b3W>_yy}<=&5rzWHTl%f*4RUHUV7S|lHUt5 z5lOr2rvBu@Nuj0zBQbC_zDMv_&zZPb(LM{rOC}@ttw*Ta%5$@)JFMZKq zo>wwf90DCAla=Rh%JAjv*@JKI+V2@X8Uy;-IUeGSN?W+nvi#itcL;eTQ zbtB$1bJgmz!C0)9BC{VZfTYOkxZ?yhEI5Mw$nvQ{$0s}NNZJz4 z9Os=Fn8)aqMsjDqGI6(~1$eVp?afz78B}e2f2iZax;K{BI@g%1hLC}FzJp&vLxT-$ zJ8qJZ002tZ+ATc6`Jav1t6>;?4!hKRW%?0%>IL&x{PD>z-=nGz6h227Ml$lZ1HXL- z|0(F9+M~O@$OJDl1W8G?Ep%`PZ;d%(AmRAFLz;5VqePD~K_Fh{_FZQhM^sCz{@KP*}?-De0D4@blE{uX%p9)_44@r|fABo`)$HUirD8RTesM&q8 zsw%BXm2w77$o6saH_5FaMA%HkUD;H$glfnZZ z6GVsJz^-Eh0FvUYSiz=HjeBw8*e!Uvw>HQHM6zdc6`>`wmzJMr_+KpmMunjOm>iD=`ZQTRTaL3j`?ZooZZO{-WPqU~+O+9{6jn`D zy?kL~Lx#d!k$a)3*zLV^c*M5@PfEq%v=E3gftf;sWTD~E4nWuUqiG{!3CCz0uAf9* z%0=Ir9y9^Z(q%p1+7WPm=Y_U&eIw1*kUmVE9ZpZ_>YN-7aG1?MHaE|2$b+v#1EgYf zbm#5{c>w6l0BA)rtv$BM-}*4k_TRurJ&;aDK}VmE;~Og(8QQO3Zca0STG1VbTFwTZ zrE9`~q@ir69~<*y(mdgHFz>TBx|Cyt*rtpZPC~{KBr4X@k}b4cYiNzAggRS)yy8@& zEL?kPA5zhMhW0qn7(!3-7Qtfc4Q9Kq_GUSB4F^E5!=FipsSm~&j*PL5>FhrQ10PP@ za8a_egOKzmC~r2mtPOV;@!(;Tg;wK6rDJ~9S(&{Nk1b6czSeKH=NZ_yhsqC*BE3D0_9$I8QVNBb+ky6B3tQgEzkFvnI(qp_UipV}>z27{9|*|S zpT5o|hL(MJ$1RYczxmJ5sByw4hoxz&mk!BXmKE1(CTk;SreGKuI9a4jsY)>8*BU0t zP_&?H5A*?u`$FXg>@h7GaNx6eHfR3cRCKWDJcwt_vMtEil9 zTjY^nNTG{<0sSc+x1AA_N$`HcTntQE!tcY0muwjn4lK z%eL-U*smS+zZ~JSr71UG($83yy^T~Ww$j!2_dj13Dy*pqo#g|8<1dS<6C3<8d$W3c z?f5x3Hz)u}#E|t3K*#T|`SF?Ow`arnO?v7RO2(JH3wEqL&6om->4ZNhBYW#IaV{tz zy)fO=di3a-s;Oy9wB>N91c;9h#!4zji;>l6YlJw;;d;3)$Yxc)sEC?&R<_6VzSVEi zqNSnv0P9s*t*1pA0|>p#kBiPd!#-TB1n4sX2`gEcr4u9bb-*+$m+l&XX9RzFF)|or;(|fN?3+ksSYz6O2Q%$&IK5TVZCrc7@#0k)f!H!o$n^8$^y))$Z(ASOW_Wf?HmJ*{^0iXxc$jz!148wAbf3 zT$nK{#%6DFGlv5TPT)O;iWxJ^J#{h|)g>Es7Jq;2`uMT$<>SYX1rA1Sk(*^lT~>aa zbe*4IBbU6xZs1~jV+H?1vA7T2#?zCvZSC<0cE~UF^(PKKJ&TK2g~n4YARnON;_`!(-qU;IaJSb9$+Hv~ ziVpm_sr%ncL)8U0Ph9l$_P5%Y2|=6|78~28eP%;a5OaMDVFLvQkVxRZHkMuLlgku1 z6#*(G=e_=QjVg_rLX&c2>}hX*GLUl~0g!+I^j(WhQ`?9(c*@8l2j>B4sb2#YLfZyyp%d_ihmDf2mvOJ>vDc zkd+Rwa#@r~Jb8;lPL2;O&D>T~9q7alzn`MBCi3uNj2}Qkt#!`a#Ve2 zv@tRfWNV%}Py0DUc)Y5G=Cs@PGlBEd4QeJWj_KuP3259y?>iRSWYd$c_nc^?uYrH` zM$YhA(zzS^Qy;E}{>Lu*#!^sW<(c-&@2{0hgT@<=Q2+?NG^FlzbhP!{`>Axh5ENG9 z-%keAy`9fbJvd48o9#6>jwisOrG+*r*Je%QsCs#_rbk~aW_D|b&-UW}YgyByc@wQ#? zxuJ53GX*a;q;q+4u+TJI!tblEQ^NnbtP)?)ut=J7b?c(}?CD@qDjWXo&cFW_BAqAz ze(lPjU*2oQ5|w=X4_kc4ub_Y(j6&wXOhN|+n=zCy z6bkP_`|On_6PK^o@DcgvA0hwzC|gf2=BVq2m{`zBQU23XgZDn#)KstdnV5>rhibti1x{xI zzkffIpkM+#4pCq#AF24tUQ$6#smB1(qY;U>A4F_&Xpg{avQ2=7=eN^gaT}P zuAp2-72GDhaWm`e+@7g7y({4^QIzisO{8@re2)gxndxcJeW|xS{F=}0x^E6|=>OsX zHls6WmW9uE#O*Xw29M^BLncNX&IGqT_%;1^hp66g{)74U7R`AeLYnzE>?Q-bgK)?a zeY>wQsw*wL$B*qPvzI~n^^1wsVb#+1SicEvdQwMGXW}X=f%M64@71cKWrmQDLFn{- z@o>lgfMrp$IpwHpVPPOI{P1BMh*(?FWTJcnJ5t0jU4a>z@j_n)Cv;M!tiukHb1zio z%{@)b+0HUiF)TU`GSBl37J`q(S^d4eUSs9QcxoHFOLtIA`)yhs8FE(JW5OvC6h%@P zU_^8qv5=$>Mwi8Bu1stUrkAg6HsEXPa1en~?bu4K3zn6GolXQ-;q8CEW&Jf_v4Bne znm3H~^(kQFDeknC`Y4toL(I;OO)-sRx?9{4M1lz>gPmbom8Q6)g3+w{4h1HPTTvXA z6J%`#UF-VO^@h;F#7o8*$UcQ8b7^w|yizLF9)e(hP+wc?1$TDm_fza{uuw8LH@|k{hCb*n=l5X>xvsYM z(wonp??(nSQZ2Vc+`a#jqB?pxkV0l~qvR8;R^iWVG+ zo|ZeTAt%Q{JEh3)_<9uyJxY>|Fhb8dIfV8XQAyc(|4U5C!lL)Xq>mo^)RT>EIP!F4~YqcuWvCj$M-3rgB1kryxWOU-8*{ZZ^z=EHK%e8?T9 zTWH;-NadVg+ZLeS6BMMK$O{M5-cojdJ#@sWr`ASElQsp*9KF zLcWIZsp}gJp+{57z{dQnwUZ0}ffOB`SVN{bQ>!FF?dyNwz5_U$J+I-i{}jO9uA?3z!iVPA(c>~AuG!W20@ z{S6-v??X`$r=Y7^HU$`sA^k)FZGbmM2fNSRmZm13ckixbYgMXEZh^nV<=4B5@@Uv( z8X;d)FZ3sKOb?g%XcW&gL#QMHX>Bx@f=Wh%9e0SX7M}b;|Ya;IwYB3A) z>zUEgs%LATCj(3Q3;KT13JvNCYk!x*5_ct zlN=knO?-N2WMl?@F)$Dl4vdJnIR0TTU=Q24xc=i6Ig{Iu-T*`)Ff%iQz@)s`^Sd<# znaNud;^IhQmTcko7ZC`8B!Vsx7`XfTMn)Zd8H|!+)q#O`*G62yKc(e(L)?1Llq)6% z&0#}!vY@MTHdEMIPjR`=8r;N^&ButO)YP!@@(88l@MNDnp@FH{W{nb6cI?jhXLCKN zH?c@1XGcpp{uKXEU5=Le_2v@~6TVG-ScJ|}2P z>6tO?Y;7qYJn)5e2B#P9Wx+3U)D+{iho9Jf@KF6 zGkqnlH>_QtRq<>uvmaabEoh)jpcA^_Yiot^XGJzQ}x?h0Gs`5abNjI{yq zz8WdEXou!Dn2Sv0jstK@Op6L>V$)XG!y3Hia2;b1Um(~V1^#K zumSXQfZhSMJ%*(P@OV|gY$cp9B4T3V%c!XI)ME6#KLq5!IfEO?bC{Z%hAbZ)aRJlG zbF|N>cAC$oQv;*E1w*Af^Vpqt!4gwGvHBbWCd_x;K>Do12ZDB<5xAQSp2XJx;A62{ zd=AxAZhn62LHwgP;Gg#f_9%ot_O|q3-UEIf+Gmw05|Waz+>H9&`}fvMJ=C!D!{krl zLpT;GwhNsGe2t9i#VjyseK{EX8vVie3eO2Lus$-afKB5qa`NC>otnBjRBdf-xJ=D3 z{{{ObWc~EEx3+5g`-?;@ymBG{QjY#z2B6!zb8sFeC-e;746X>{PSsqaWZV@~WP=`F zchtd!g~;}l$ZVHn`uI4@o)>% zYl40cbqQDx!pb2GEUfI+=PA)hEGtn;!1^l~hw?;nOwP`3PEZ=Tl-C?Q9_@fYA>uTB z;qX8~`htLi2-aY;zp4rgEJP$EBn*5gs6QhQH@rU10cxEA31kW^%=jO*#zk>bVAPfG zvS$i>mSRTNOjzi@Te(H`AmnX$o=ZVIMKlSY$q!FSV3;74FvwpEwW>mVfLOT$zLffA znJ=atQe*xpcV2**9^{cpSdD^&#mG|VLTLlPR+<4vkkzoA;Nok((&*;G*fG)haVDC8KQ0s8nf_lUmbcxLSk7_FqUs1 z-yVZ|=}mAjCBNgi_Bv~1A5>5(dDGnc>y^^x=4dEFLb81sGtxd-iKDi1j1a_i4X#3n zvRm3#tP&b`PDibD>F27}%AEv>v4~pVaPIwRvA|J=Ry{ohxD&|+ZOd6|{gpL`mr<)} zDeIn3vbwsO5pmfW4CQ_}*-3KLHL%}tP*Q^7<0eu^o?d|6Po6CIR+Khj=5%y~(>T!6 zgPu6uekC@5ctOq|uU~4^5a4V)kM)mcYvFS@#YC6ZYL%J8f@wD`%M^x*USieCGJY?w ze*ZDcfh;BHi!P0<$0|j}$CES$k;42sI+Q^9JdqI*4e%TT=P(IYhOnHiAoOc^ehTs~ zlp3U@6aPFI4-6ndn3e8MWGY*HO-9Eh(J0?+t?|Y25D~|wx%XMEv|bS}0I5JG)&KqZ z$Iys6%)RjMudOt7CL9##JSH|BbfDqb|+;5u1X~<#pASTa$5CotJNsc|}BZQd6jpedX^OKiU zz39D`+ze}Z*ByOKDGL6N){rOeAhW`s!pJLEUetAWqd7YAs}+9hTpfN4Gg?1KJFQ?R zL*)^POx!$8tFtE$endh7=cP;i&}k}GQCHuZ^693cHkc?3gjAZCh;;>h5ST?K;CG{6 zW_E6m!cyP6Gs%j7hQ>x$7srmVko&yiNpRT>Nf@KJ-aqW*p_|@O2tC+Uvdz@&-7W;& zjofS0Z45QX2Z&d&C&n*080qHv&c|m5P9E%@+-b#n*?K`jlp6f}Ih1d|;_kNjIK35X zt{8cVrJ}^TD@xIW1$s6=?141bt=AwYHBVn$ptyLTJ)ZDNjpIZ@oz4S~+uG&DxLR6R z1UG496B&+f3%$YDHWzD)>>sY+jkI6s#4puUZ5Yo>#z%qWLsYcd%<^$XX0AQyU8JI> z#{=pXjFF6Z_9S;toga3%UVpv#+;d0a_cRQcW#1%KJGXL{Y5P-VzXDf@oAva(gcc|* zh`w8}5QxobQwO|MRRQRGd3i;UWQ22GM2eCO<|^M>V-D-{Q^)bb#PV{|rXWm)Zt}kO zh@+JPfkPel%cz-9$a6AjcY;3hEqBxd>2U@v>vNUj{I^qe#Fv1uvl6vbR?0hf-p~7X z;2M$A5qQU*Bx1|?VTvD4 zcr6aA@p#zU6APx6%CBE3;QWo$D#Z-fwI})kAf(wYkn`Hi(gh0^d*d-yr91CzAQpr@ zr)s?D=Z|!CquTe}-Ic>4UKV2BI|CXvE`~k$cXAWj<;!4$xf6`!eBiMNjfttQe)moq zh7F{i|K|Hsi0}%b^qd~c}GXtH23fCq*UH9?@cNs#+KI{F22+Ml`d4(!tG_YIOYYz==ZaI}ae zgh+wOW9L|I3lNTT$8l-W?IUjgw-q^`oY0b4w_#&IG90<+Qd@P~!`Br0P3!Ze3;SDZ zC1X}D*=MIKuUA!Wr^{0Bz_XX3)KT^Okb)BZuB-I>7AXN>CF9rr3}y?;J%5e~kQGe_ zJhj>rh-(Layvtt(I00tW}}`_yS!4 zhJztRMcbhw`b5=&AU!j)2Kt;kRj;zCELu%T?(R81>~t+NQCyD__T;o%>@Z5Q>|6Jv zC5m8J6fV7+W1{*Ijw-Amg9LXs`pT2_{NL14kt|?{Wje_8M@-Ge^W+%DBb=Qe&s3cMSf|gQbx%+0wJFLW)Z2N^hJSzA!?@m>@OXKs z+domzZ0Gc#+7=*te_s|&c~D}|PGAtnwT^65=E^)jI~wwuocsgZe|^~a z&$%b!izKo#l}pl%y8a+{W`^B#J?U5*IOD~ z?f*UuxeqhZ<-1=4C57Zjnn0s`Vp3FSRr=M0nJ+#bOuKGZi{gX?D0&PiGD>Z2@>@zYS|=t*t*pdl<9~{|opeMg;59S` z;$K=E=zwY+`tjjbc(-8N77<>KyPJ(s@bom>b%7?U>eS6i?~&Z%;^_!wwHLrMdDgq` znbz_%XKRjxEiTrA0KT$j#(4|BnDAq_@vkFhPu6$==RkN`*UP(XB4LB@C}DiuTH4{l z+NtAuMG~ZzTo5Ey9a7AvS&vFTd4h*hT&&dh&3Z?*BLAZxohi)SAHYVYAa0p?$)2q__!a^EsmVd&H^A9dwSI|<=($q zfFEkI<`ZO1v7FUAHK_LfZcISTeXQ7lU3IACjt7i|S|i1rpre=`$}_Xu(8_Gio3(c6 zB=t01?rGzFfHc zB|Q6w%b7^a#2eoC&riF#xJro+U{_gIR@Ua$))zpFuq*J+O0@?nmFJDWWEM5GX49+I z>KoG$^rZ#dqNV4{hO28otZfS@spt4iOn4h4ulTn%qD3-qKr@l_{{0c)2cL47FRINB z#{(;}IpssV-9lRpLSyH~z#Zm&rZ7z6vdg#b+Rvct8W|Dux^e}gVQO?3@bc0OWFZoA zs+Pqvl@Pe85Rk#7y*o?IK3l>s+}j%ymzGQa)a*lLBBXEC5cJ+BrnDn^4#p7iX|qauZl%kxWP{0Gi33)C>r57y~; zhMf#!p)~mT>TYf%)(*wGOFdXT{39%e?dxo+t!fG|E5qtiNDk>3%D)Waf%4jM?7qx} zC&^-#Btr2L($c10_5(T9j{=GIIV?|_n7rb+wZDpC`dmJLj&=>BMU$_@VGVKF(SWh} z^kCvyZ(2XZIavHX?cYZuXS`_rs}6T*>^SI2$ue{B5dG6L8)NwNUA;}@d4Ebax_oLk@sK`Mhm8^>moLjL7TQ#9KgEdP53xbjns z$vq%-9LISZ7Q3r~+c!sKZewhh zGPtDlgU_f+{QJE?;_}{n155>l>;m1o28zJC*6UhMUml`ZRamd45nICZk*(39*PA9K zus?)_1d#XdW2)qS4s4Kahi|n7r66KwX^zO%;K34) zRqp;u)mg~oTVKxr59_Z0LUA4^798(d5hy=mSiU^`p+0!UCJ7e^FH8SoY=P%dh@Fu; z2n}FgDo!1!banCl0y{p7VWR&j+L;{viJK*I#9bGNO1mYeDlk#eIVa5_ zUR1P3QBEYDRb7QYlcI#R+bS^MUnkOk1_c^0*TuUV+6x~R=$B6mN~Sq~NJQ)%Yqvs( z#Lmvney_|hG>8memii@%w}09$oZfZO%J>r+meZTj8+?;Q#YeS3?WF@x38!Y;NK$A` zby5$F*WVJ}lb!|_w$burXBP{cyY;qWEflAjJ={LH;HE^*lmOJu9!&Hic6*teoFhDX zzkHV-RG8g(hE(*1*yNe3-m#q*iPk;_ifY1BnZ&bFIp^O0WEec<0L4&y48*JS6x~6% zUeq~rdVgCi^!CEt-QI2Yh@ehyN&Z(^`tsj(;vsbZF7B4N`|o1$l#GvWRQu92)pD7%Z{~qVhIhaSW%ER^Yv)CJ`t>YZ zinA*_C0z?O2vmXqwDqbT%fz*9h94`1oyvnc9JwboHdTR#k>E+HW2P^)qt-zGrMWQXA!6jd|Q(}W~zBqmy!yurmT;beg>ItB(W(_s5&NHscgUL2^DZjUS7 zVaXVq7~fW(w~j9R_M7{S+w}5HMg781C41{%d4pKYQs?;RTHg<)C~+mBUqS5r49i3T zXpJ8Hk)`SjY=sTa9R!NJ?a$T36HM)$rrT&$9Q8D-&e*e!HW!V8^OqOtv@ks0S9Yz> zmD5($4R^-KPXtgb+q{SgQWX(7yKsc^z4O$~RDhkUL+AOuv5V=*IE zOyk+(FC4#jnjKV%of7TEY|q@u#~hwNG7I{lE47P9hT-zANlo|zxX0Lk@7LWwEhx=w z)CBACz}fa!{*3-f9k2|YicrE#Z)*N{A%dV*ct@dQQCIe&q0~aCH*NQ!K^}lJK#=wup&?=#0#CxK# zVSFylUQU}A^ATf-8X~CQri+n3%&AdBsjd_aMY$(uHZv(Y@mdWcWoJHLADRr`=3fM= z#u?=y|6aOg<4Av;PPI;>BVn*ln0QDdHLz3{8@V1jE)Dj^6k8K@Il3pJB%#P^3w4B( zr*AGU1aG(0CH>B}itLr{6v zlcLSwrs9B{?xPwMzJU!D<1sLOL5W{W*detoZeuKuwC>OtDAH91Ls;Z9QpT~WCx5;$ z;emFKlD19*FNp}dW_B-Z=|wee()o_S3Rp`qi)8T!B!SJNVke}#1t zb5di~@>J27>L_72D)GMjx{1Tf;DUbBgYLDGUT7?jO1-! zxLz&+^IotOgAk)Ix%FO{`PEd{ zjY~rUF6?`mgX!`eVvp9{-+A7Ri&x(t9G1a%#iu!lZl5KKEC_UN(O^$9oX_VU+qm+% z%y!T1bisnjmYVVW^gZyu7kn?>qj5nS&xjHnNtvk8`UdDMgP|JhH^e=g0Nz!h@;C$6 zDAx56>k7LKDWp@}bwOp-3EV^DOmXMJ{$xb!Ac=b2yCPhtv&04G@4bq6P2f~nGo9e? z=NqW=dZ!jzae9;}AMLE}*PKn*I$o{pIv284g@^a>^@PtnVV`hYd77uY9ZZi(kX^_I zZK6B`6L76*8CofDDZnh0wuTl3{8Uli;-SSj4ipv@r-BDk#qnghimGZJr;FM9CQXI4 z<)XU5-Y9apGvg{x>>9`b>-Bu5<%4PB$z=1}nS{z)aVt^G8AvDMOiJx;;m_3#?YakI zV_VjHQ}E;$FOsted*&itpjkN}5E1`_E?YYNl)R=;%WCs>9BwrBPI;1UCM^VSkZ8Lt9NsQuY1S|rPBfU`?)2wZ)+-D( zI!Bxyc5WGTl?q-Z({|KLKnR*0iS=F&%HBL|t@As?4LEeU^7w|gG1#otRTFf2?Vz{r zl8ne&Q97BhK{%@3;B9K58|IJkiJSNfMSmyfJlR_4_=x_-H2S z3(lJOEkWK`l%zM30ZM1)wta>I@dCz*B{E)3$LPDXE;bwqM0VdNn{DpmX{+9-z9ZYb zeM@6ibBM(Tb)?B6-Lj}mAb%UjA%0kX`K*gNLBEeX`Pvv+D>w!l+{*KnsQpV+9!+oJ zgC{ae<$;~r*zk2o-0&Vht6NSCD;I3JshPQu$6rm^+MQ9RI z2KP`I&I$_0mZd_emqcECl}#+WpAldQ=yF|u(Y>{H>`pEpmAp7JfetCfwluH^^Az>S z_95anfW+`+l7iz-L+K{g67wEL3QzLC7jllk7A3bNFK1Q)WerkSFY^2P>IjxQTiWB-Br%ns~5>~4f0T14t8vv26 zj1kuNO=k4%d^5_{XCXXXnw0~6%H0r*5V*9@t6kh7^|fve)O8#%o)vAZ>6frOu4oh| zjDM*7Avm_a-010g8U>kL>xOIKn2{sW$Szn+&Li zl7-zM>B?d(Ab>i+_PE2~=`0#ecASKQ*lhhJKG=h(Mg;t#w4_i$-lkdl&;F6=ERZK@|=EC-# zd>^ZrT+qC>_NhOqkwdP@E{(0;D}95fyR*hmgfMlTR;F$m^G$x(ikNk0HWt9o*3^M7 z6(1n%R~abSq%B~XofH~z`Axwo`7`Up3K4`_W6|v<*u@oc0ktfhzl?xfXzeodo}1r# zVA+iMLO@bFxs~819!(fBS25;}feAE58T&OjqkPhlGC ziPh))&i|5|mXv(s7!*W(myfYbaGn6@vd*{FgBs(|`6%zSWbLGUtQunzs|Jb^aDe}& z_ycRib3}_~CgTB|GM!)bl^zGNZBI&kLgQoF`?M67dU9`cI&>boBG_{#1mJ$|?8(Qo z1{rf-2CqtVSE1a+8K~Cam~?Z0w)(PlswzKz@V0SgVW|lW7h8TN%4e+@{JQccBI2dr z3}8zM(3Hj9JXut5mfz95puHInZp=)}IdOJmC^5yRvR{pFPUGkOkV~^U~ zU2AC-SDH5?o+Nz~#Om{{P5x@*?x1U(+o&6gu~ufOs59VoyaoCWCpyWY43^7gSEtvO zEtrlGB}tdI$GjKTmSyUQFmJ3Tf74&>`;05&9=mN(Ujn&1W~Hin!9CIZ_*GS)!u5|E zT_3lrCX59*3KLECE$jOuvTzaXDXw(Z!yR$xJR%SMs(P!o671y?O zwtOA^(@K=wqCzu+$OMX#WyS@qT+e|uyO+m5x;MY~b*_ExPjMDGnW)(LFPM*vhXZ{*L_|>S`k)c+VFUCoH=}vTf6U{>8 z-SC_E)MRjA|4MaD_PO?d@|T50Ml^1nlQ8eB?TEXxe{`?j&^O(DfyP1l$Uc1;0N7S{ zRd95vvti8n7p=&dhoHl9Y+R*cr|q5YzJYv)q4H0W31UZkyKa8HNk$tNpAI}CY3*De zAggeBQn4-g{!WYQKHC=G6rZ~_yc}}MPSI=`OnX|1#V}qP4L2Lnvz z4-GP?Ao&KHwsOFdH)&AOI~P2Ep7e}KEtRFvqbYV9)}OWOw6FEH_4_xC2*_eC5X~Q# zfd#!J=|N!lpi}Fwv~Mzfd*7t=b6{ILL{_wuya2YY{(wxpf;Y||`NfFoi4%=?u0Scb z2_Fs$cGBPy=Zfa7yMuv^VV`23PdfeSH}lC!XBJL8>7FjoWg$ZTOwpKGZ?b%_LIV|r zf;=*9HZxl3mH0upZ%QGjK^`aw^Pj>)>KA;3jJ7%(q?dS8Mc7RtF%;$uCW2Ap_{5G7 zoYT;lu$J|La%2HC#}cYB$C=o}78?M(gTf@kNc}qQ*6Z!bz1+cJYeRG>82ze9I&Mr< zKc5V34Gq|uDkpF3s13X-%=|e@n;8g{k0jdGcR3OjG18+8J#K4dYay1^)m7QkH%PJ# zwDfzh!z3hR?meXRThi_IdlsNOa9Y6NPz^4H5vl+y871&>N@c*2CnQ#Fb3MXyZe_$* z)PEXmK#cdKV2!6Fs?+CT)OK=~B-(1XP$+cttQm%wqh;jd7+M?ciJG9?Mww;e)yOx) z(3RKKkgR%Fp3D!&iY@wVD}F#eVIVM59>Bcq zME3_Um6LJ9(=4}F)N3n6U5+P^k>)MpnsSUhIfa!sVmiAvyU&8R1(mTSxf+3~**;4= zP(8@8M`DhYL&@!`b9(`_WG|Er6s^@W3ahhi8gVAlsl|86h-rQ@$9TlZg6-t5O95h( zNM6BKGbtI0-_#%@sHer;*}vDUu&tb_-n8H?tkI`IijIs=+~mb-VgHx69GoCU*@noc#G3_?cUc$}Y~hI9Sh z)q``%W}{@qLAgYo?b5saK<4|LUISLeG)>X^@*Zb@oI7a}`)QadwoB5Gja88=Z*t{z zBS?SAMA*9xvgK^G%H?dDM;Rd>p%=d%YK`m?Sq51Oj3JiT1fzVLfBdEFUhu+m!rW#d;zf)sO?RE`^jR0Y##RE+?73m2Os%* zapC%f)Drz4Ve#EuCn$2(viS3YAk(A2`I{uQT5uji1P79B;N)niUY}=(?}eTS0v=f` zEuQ9srYd9+!bGKA183m_C(*XVZ_1nIe7ET7>1|KVVg?VV>anwgOB12UY;JE!iBxA) z8}AMcSrfZGiTm6y87QRCo{J7!-0sTKX!)*|mCEa46TXKL}+#9g&Du zYj&Xsr)*gok?c$MeGrW`OR{Cln#h)2jO_bTl1hlN?;$(ccjmiC=UnG}*Y%xmxxV@5 zbz$ax-e>ORcmICR^W0t3mS^76*d{f!bdv)F80fKWc~3wNWER+2d890qi5aO!J9JU_ zDnQiC+BDz-t^gPWY2>r78YpvPIotAqSAmi@)sb`%z!6`U#Ht%S2|Wm*E$bvS(pF-P zYWiIjzLSlk-RrQzt;eUv)GZD|=FgZ+nRNEODf}H;j}AURYG$K*s!T-z zz?R)lw*&|e7116i%-zkG>LyUKrRQVo_J3Ngx=3}s!!iEG5vyChd z1F&<=mYCzF!A<(%R z7F%QAPF{k+iW(dcO;eAFHJ?T>S~Fh6MR}pQFL3*~>P)Y+yflCmdts*Sd?txmhxJ|HbYND8;t~t6^(JyK1p&kW%1fNLl(k`cF5hxmBw-*m{ZhFxU#C2fdyc9-b5pQYK2zV7C|Vyqr&c(02!_%a{(>vLr(Fgp{7 zwH6xXOeZW)JVjBPPUM7sT@Z|dw!4J(n{!$uUIO$0Frju*{2O52Q8J=f$yuSR_WhXk zk!IEMIP^aLIx7}{-WYKBK0usl8z;M+VXsc1bMsr`&8b}?35Gx59d~LHmQ~wdfWUwar_|H3PE5jf3ET)+XWI*ZcUYUljPCzDR0*7MVg z13CywR8&>PmhAK%VIf)`n8N9T#OatZjhWd&8Vynnh8~Mgfc{nDj4d0@+8j=EN=RfJ{<6Mjom zp$>lN&gd>GG$U<>WUYU-L+qXGwRgrFs>u~WTh0N$h5V4n&ezYS6g*}y7sXmLPf z95ebE`LiM`nC*GB$<@K)2ttg$JSzf+$!A+ z;kO{FSoT|{8z{HGIL>N%89>KyM`B6d)iB(&OGH#m`qy;AbC2v<4#=J-*i03^HbOUY zSrorqjo#7>gui(->+j6cf(BBvHYP^bTQXfOuEl3h;5_Ou@l*#^!ixVgMU3;r2*O6( zc8B5}OccQB?-uj7f!iXOL2HPNg`1|jf_;?65r1_|#yzH2BB6RE`TS?!Ul23u2ZavA zM?eIH?7g#o1b&q4Mj1fBKMqavg3(;# zYWp0qR9aTZQ&8hjFcO|t0sI1t=qKP}K`BDj zow+4KDl&z;kvv^3VOJ=Y4Tg2kH%q(AR{hPH0G_kss;eweS6z4BIBPp!#yv`|?q4T) zC{CFx^1y!VL5*H5aIw?HQptr=eGKuvR;ZB}+6Ubo_qhfNk9*Ut=?rBnLRM&CUS;IPtqYtiJPApXFLQ$a@XWe0 zmBEd#&wog9&4tZh4LiN$`fgw8-3@UM_vORqTZ%5GiabJBV$NOiGC2s^fZ9n)*Xk^~ z2R(Fujc0dz>pL+2V;1uk7|GRy2%FFmw?OPi+K1y^bz&r z@-jN&#jz!isxdu&kawMT4{%#)(|gKCU}8Eb4Mp)KV+Q$8~qeX;s5>VpZit+Yo)j2BOe4Kp7%&U{>{O{sa{4ol*xBq z@|}%^0;s47zEvTu!KWTzu<%2j$GRc{-Lzn|4K%pjC-?RB#S;x7cLcXxaLrS@5m6U` zR;)Cn_)C=Q{Zo|Nh}#se4xdAI%PT3HKtW{_Hln1fa27>{D#;&=HLtoMz%Wf*F$kUUj?z4!bbSp zkY{V5&=hWp#i!s{{Vh=DcGkzT093oC@=G7G6}@jh_FFQ8@~5l|CO=I0p&WyXvKjJQ zT$v$*R4lb&(_31g3y(EyVh3H(t;I?U!VxmQbcR>YXt{fEgA{yTm?ieJIBP6pgvhob)R zzLdPCYmKE!WG<{YQ4oZ^nvDh>*fXL}>E8W1d2?ex5S$D|!!-DCnBq3?OrmX<(dKls zjQDwYKg++Qca8OB2!+DCt zD|Q|LrTXbrzT@`c_SXKT<#YJ*gt<=KzRZ!Q^Loo}QbRqbg;Kx2jcSo9qQd?Au!s#~ z6ulI%Prv}+DTROVJzXsoNrp=z++viZIst^u2EVfKsVkn45#o=`FJrzVo0J*JoF`Eg zf3uYYFP%Vz5t5dFNsqzFQ0^-gP;EP#Gv!J%!l|DQVmHKnWbkPeN^MIm%d@!ll?0S7 z5#L;b$^x+F5by<3W)VRDHG|FIks_SKCeBGCkIa)N**1`bi%%>PdH#_xTq81F1p`^o>oPtpxx#klt#5xr0>B z0;Ub5`C5N!`|8%<MsYA2{h}4QBD{yZ*LMUXVoDelpwZTbw$>?~TvDkQVo^jF77=lWo zkT62T@=ds}_XXMEs|qrL!O_ggD5q#eVNt`9XJ&MkeUyzMVHf2&b2wCbvb3~|9D~}r zlKDlc5DjuFaQj57yzOv6ASAww&cM*dzG%AO zZV~U2GB6P##wIG@c^ZVC5FSTV(Cfl8IX$~STG08r;Iw??X6(}aq1V^sBfHykrP|M0 z3Dy;1R4Wap=Mu*4Og4%+EA5J=(%V&(G(*xQQI`cxC!%9l23c*fop!OgW70lWxCIi1658Ai{c6seei zNqezVjLdJorYqg4c=TkSD<7xfw!W3-#)6}zjXm`RyU@{)?Gx|N8$y+w0Mj;UxvAUL z7dUpD7I|f1nID1J_JS|L8_CcA=rwC5vTIC(^a?+x%KSMUuXihJW$oC>`a3=2q;%G} zoP+oxhi6G6`z68>bt;|7B9WgDxpW4@ZF<*ql6u$0GOuj;=rTz-YQ8*Yq7aD`Q@%3c zOHr9Hr0s;eILz!=jZ6~HIbB|t?O^@UaN z2K%diQ?|)j59KP&M#PI*MuX;PzHFbDLVH!mhU;OX=T+BrTJ5wej8heI6-Av;16ST5 z5Kgq2zJv&mTV|@6k$bEysA6l2g}WI|8M9eM$!d$aDChmj)TkJSD9-9uO!~}aCby~Q z?-#%0Z2~{4>CPmqp2$eXd7bCum&P}CCG}tQ&UX*hROW9+F+}Aqr1q0hQrxc)*QG%v zrtk+Dua6{cO*)C`eB|!0lrE(#jTf{ElU-*_h4+(4Q{7!6suW$kF`P3^g7Cf)1QxS= zLPTzKXwE_HcJ$?(_bMGr+vzju=<;c%wy5MLWlIOk{4zOzX-TGaHJk4#zNa^nPkHP*9O$#+k+}f>LaaNv+1dC>inPaGT zxndewyB~LjWtR6tEqUmy!3$-LMA;E3_xf$oLT(H?%;9eC(Gnv#hb*R_BLrJtCN?bW zW=DkE^VxM%#YSqR!19x(BtJ^~OV6zgGHbM_Bo%2Mvga+aIw$moEl8?%CSz~4zd_%3 zp5Z)?K**-Sz(u9^?oZ zOl8mrl^EEHi1jMhE$%e?Y*qMW&iuZWQZ8p=jP}lf!u{&p?r&Ww?`iyYVfl;gmU}OS zUw`BJ!;zNVvTdDik)Ag*N)ZnaW3Wr7R1gS{n5OxSvid$k#Ml}qC+DP-D9$Y<-G^w; z`fy(%lxcouQZ1ip?;s9J%zWP;q_k$#_cp)Th{x||Zzc~EfRmIm>~+1(igq+dwm6d| zIXe#x$BWunC9FiMT`kUTErY?13>{>yhE3+=l_`l6y0e4ZMyaw{Zc{eue1x%X3}AYr zy{;O>v?;(auNL8M%fP(lKPkT~@4HLUndya!yiBV>dqqT2gP4u1YtpjwPK1c%z_O+G zL8H~8M4e7#w1cBR-AMiNDsq5jK&3=VE7`1cZ|F%q?_D!hv(s#Z4@&$=f=MXn3d%=$ zG_5W2NrS`LJar1OMr zuH{5WlcY_IkyQ@;{>kYsf)9E`%Q8s}wa(BZym_1s9qYYtjwEb8ypT7%%cX`CpjF2Vx zpxG8lQ*$zQpfpuhjsW50qgVYzP1Oin;h^U(#kVa1nJex#8XYQZJBKmw`eAh*q1$qU zIryQYI|p|@a%CltRN5N)Cf&VmG>?l#}k)dxpw3`%u4zhx$yxFsY72#?WyE zehXu|Dcekd(7h=n#x<;l%QJlTx>h ze)EhW%2QtWc*bg0s=!-gFY7q1J~!+v^=R~Quk~QrgEV6T1kd}G31L%H8SC*67L<%r zox2-Zr>QwmFhs^qczTU1S<3Y?grYL(kEV^Q%;vqry;gA9RDr=_KRD4M9@luSx>Z02A~ zs`QcdTU{PW895OS;W8CN!`33r?HGtJFhy$mO9kUb3GoTs3i6iU8mW`JXF47gY46ww zCuC={`#sfNbvsmsBxp}NAM(3P(x7bzwdAq6>0}}`Z7Xblv8NSkDO!pEad_)?phm5m z5XXh@uT<~pJqsNB$ZLkKWO+WDg78KYA9E4%s8DPS5NtY*@U}i7;*Uo-|0~1;|Mstd zX+=a7KuD*bzdz@-YklArh!k>S5)%EMc@F%5tA`8#P&Ihq69ByaFF5PJyX(Kfw8KaV he$gW&m!f#;keb-T%;8QGzbF3I@-j$i%&iAc{sY^109yb6 literal 0 HcmV?d00001 diff --git a/tools/fiograph/fiograph.conf b/tools/fiograph/fiograph.conf index 91c5fcfeed..123c39ae71 100644 --- a/tools/fiograph/fiograph.conf +++ b/tools/fiograph/fiograph.conf @@ -54,7 +54,7 @@ specific_options=ime_psync ime_psyncv specific_options=hipri cmdprio_percentage cmdprio_class cmdprio cmdprio_bssplit fixedbufs registerfiles sqthread_poll sqthread_poll_cpu nonvectored uncached nowait force_async [ioengine_io_uring_cmd] -specific_options=hipri cmdprio_percentage cmdprio_class cmdprio cmdprio_bssplit fixedbufs registerfiles sqthread_poll sqthread_poll_cpu nonvectored uncached nowait force_async cmd_type +specific_options=hipri cmdprio_percentage cmdprio_class cmdprio cmdprio_bssplit fixedbufs registerfiles sqthread_poll sqthread_poll_cpu nonvectored uncached nowait force_async cmd_type md_per_io_size pi_act pi_chk apptag apptag_mask [ioengine_libaio] specific_options=userspace_reap cmdprio_percentage cmdprio_class cmdprio cmdprio_bssplit nowait From 21540653a3189fb49b2e11d53fefa262756f79ef Mon Sep 17 00:00:00 2001 From: Michal Biesek Date: Wed, 23 Aug 2023 01:03:02 +0200 Subject: [PATCH 0595/1097] Add RISC-V 64 support Signed-off-by: Michal Biesek --- arch/arch-riscv64.h | 32 ++++++++++++++++++++++++++++++++ arch/arch.h | 3 +++ configure | 24 +++++++++++++++++++++++- libfio.c | 1 + os/os-linux-syscall.h | 7 +++++++ 5 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 arch/arch-riscv64.h diff --git a/arch/arch-riscv64.h b/arch/arch-riscv64.h new file mode 100644 index 0000000000..a74b7d4758 --- /dev/null +++ b/arch/arch-riscv64.h @@ -0,0 +1,32 @@ +#ifndef ARCH_RISCV64_H +#define ARCH_RISCV64_H + +#include +#include +#include +#include + +#define FIO_ARCH (arch_riscv64) + +#define nop __asm__ __volatile__ ("nop") +#define read_barrier() __asm__ __volatile__("fence r, r": : :"memory") +#define write_barrier() __asm__ __volatile__("fence w, w": : :"memory") + +static inline unsigned long long get_cpu_clock(void) +{ + unsigned long val; + + asm volatile("rdcycle %0" : "=r"(val)); + return val; +} +#define ARCH_HAVE_CPU_CLOCK + +#define ARCH_HAVE_INIT +extern bool tsc_reliable; +static inline int arch_init(char *envp[]) +{ + tsc_reliable = true; + return 0; +} + +#endif diff --git a/arch/arch.h b/arch/arch.h index 6e476701b5..3ee9b0538d 100644 --- a/arch/arch.h +++ b/arch/arch.h @@ -24,6 +24,7 @@ enum { arch_mips, arch_aarch64, arch_loongarch64, + arch_riscv64, arch_generic, @@ -100,6 +101,8 @@ extern unsigned long arch_flags; #include "arch-aarch64.h" #elif defined(__loongarch64) #include "arch-loongarch64.h" +#elif defined(__riscv) && __riscv_xlen == 64 +#include "arch-riscv64.h" #else #warning "Unknown architecture, attempting to use generic model." #include "arch-generic.h" diff --git a/configure b/configure index 6c93825173..36184a58ad 100755 --- a/configure +++ b/configure @@ -133,6 +133,20 @@ EOF compile_object } +check_val() { + cat > $TMPC <> $config_host_mak echo "#define $1" >> $config_host_h @@ -501,13 +515,21 @@ elif check_define __hppa__ ; then cpu="hppa" elif check_define __loongarch64 ; then cpu="loongarch64" +elif check_define __riscv ; then + if check_val __riscv_xlen 32 ; then + cpu="riscv32" + elif check_val __riscv_xlen 64 ; then + cpu="riscv64" + elif check_val __riscv_xlen 128 ; then + cpu="riscv128" + fi else cpu=`uname -m` fi # Normalise host CPU name and set ARCH. case "$cpu" in - ia64|ppc|ppc64|s390|s390x|sparc64|loongarch64) + ia64|ppc|ppc64|s390|s390x|sparc64|loongarch64|riscv64) cpu="$cpu" ;; i386|i486|i586|i686|i86pc|BePC) diff --git a/libfio.c b/libfio.c index 5e3fd30b71..237ce34cb3 100644 --- a/libfio.c +++ b/libfio.c @@ -75,6 +75,7 @@ static const char *fio_arch_strings[arch_nr] = { "mips", "aarch64", "loongarch64", + "riscv64", "generic" }; diff --git a/os/os-linux-syscall.h b/os/os-linux-syscall.h index 67ee4d9109..626330adde 100644 --- a/os/os-linux-syscall.h +++ b/os/os-linux-syscall.h @@ -286,6 +286,13 @@ #define __NR_sys_tee 77 #define __NR_sys_vmsplice 75 #endif + +/* Linux syscalls for riscv64 */ +#elif defined(ARCH_RISCV64_H) +#ifndef __NR_ioprio_set +#define __NR_ioprio_set 30 +#define __NR_ioprio_get 31 +#endif #else #warning "Unknown architecture" #endif From 2e0a52594ed11e738d2402a3e22d05ec5597535a Mon Sep 17 00:00:00 2001 From: aggieNick02 Date: Fri, 1 Sep 2023 17:01:05 -0500 Subject: [PATCH 0596/1097] Add basic error checking to parsing nr from rw=randrw:, etc Previously this was parsed by just doing atoi(). This returns 0 or has undefined behavior in error cases. Silently getting a 0 for nr is not great. In fact, 0 (or less) should likely not be allowed for nr; while the code handles it, the effective result is that the randomness is gone - all I/O becomes sequential. It makes sense to prohibit 0 as an nr value in the random case. We leverage str_to_decimal to do our parsing instead of atoi. It isn't perfect, but it is a lot more resilient than atoi, and used in other similar places. We can then return an error when parsing fails, and also return an error when the parsed numeric value is outside of the ranges that can be stored in the unsigned int used for nr, along with when nr is 0. Fixes #1622 Signed-off-by: Nick Neumann nick@pcpartpicker.com --- options.c | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/options.c b/options.c index 48aa0d7b1c..65b2813c27 100644 --- a/options.c +++ b/options.c @@ -596,9 +596,21 @@ static int str_rw_cb(void *data, const char *str) if (!nr) return 0; - if (td_random(td)) - o->ddir_seq_nr = atoi(nr); - else { + if (td_random(td)) { + long long val; + + if (str_to_decimal(nr, &val, 1, o, 0, 0)) { + log_err("fio: randrw postfix parsing failed\n"); + free(nr); + return 1; + } + if ((val <= 0) || (val > UINT_MAX)) { + log_err("fio: randrw postfix parsing out of range\n"); + free(nr); + return 1; + } + o->ddir_seq_nr = (unsigned int) val; + } else { long long val; if (str_to_decimal(nr, &val, 1, o, 0, 0)) { From 12d325cac3a05f2eb1fb0f4026346129d38c023b Mon Sep 17 00:00:00 2001 From: aggieNick02 Date: Fri, 1 Sep 2023 10:50:34 -0500 Subject: [PATCH 0597/1097] Record job start time to fix time pain points Add a new key in the json per-job output, job_start, that records the job start time obtained via a call to clock_gettime using the clock_id specified by the new job_start_clock_id option. This allows times of fio jobs and log entries to be compared/ordered against each other and against other system events recorded against the same clock_id. Add a note to the documentation for group_reporting about how there are several per-job values for which only the first job's value is recorded in the json output format when group_reporting is enabled. Fixes #1544 Signed-off-by: Nick Neumann nick@pcpartpicker.com --- HOWTO.rst | 11 +++++++++++ backend.c | 2 +- cconv.c | 2 ++ client.c | 1 + fio.1 | 13 +++++++++++++ fio.h | 3 ++- fio_time.h | 2 +- libfio.c | 2 +- options.c | 10 ++++++++++ rate-submit.c | 2 +- server.c | 1 + stat.c | 6 +++++- stat.h | 1 + thread_options.h | 7 ++++--- time.c | 20 ++++++++++++++------ 15 files changed, 68 insertions(+), 15 deletions(-) diff --git a/HOWTO.rst b/HOWTO.rst index 8903294156..9c825cc27d 100644 --- a/HOWTO.rst +++ b/HOWTO.rst @@ -755,6 +755,10 @@ Time related parameters calls will be excluded from other uses. Fio will manually clear it from the CPU mask of other jobs. +.. option:: job_start_clock_id=int + The clock_id passed to the call to `clock_gettime` used to record job_start + in the `json` output format. Default is 0, or CLOCK_REALTIME. + Target file/device ~~~~~~~~~~~~~~~~~~ @@ -3966,6 +3970,13 @@ Measurements and reporting same reporting group, unless if separated by a :option:`stonewall`, or by using :option:`new_group`. + NOTE: When :option: `group_reporting` is used along with `json` output, + there are certain per-job properties which can be different between jobs + but do not have a natural group-level equivalent. Examples include + `kb_base`, `unit_base`, `sig_figs`, `thread_number`, `pid`, and + `job_start`. For these properties, the values for the first job are + recorded for the group. + .. option:: new_group Start a new reporting group. See: :option:`group_reporting`. If not given, diff --git a/backend.c b/backend.c index 5f0740395b..a5895fec4d 100644 --- a/backend.c +++ b/backend.c @@ -1858,7 +1858,7 @@ static void *thread_main(void *data) if (rate_submit_init(td, sk_out)) goto err; - set_epoch_time(td, o->log_unix_epoch | o->log_alternate_epoch, o->log_alternate_epoch_clock_id); + set_epoch_time(td, o->log_alternate_epoch_clock_id, o->job_start_clock_id); fio_getrusage(&td->ru_start); memcpy(&td->bw_sample_time, &td->epoch, sizeof(td->epoch)); memcpy(&td->iops_sample_time, &td->epoch, sizeof(td->epoch)); diff --git a/cconv.c b/cconv.c index ce6acbe6ab..b0127dd870 100644 --- a/cconv.c +++ b/cconv.c @@ -219,6 +219,7 @@ int convert_thread_options_to_cpu(struct thread_options *o, o->log_unix_epoch = le32_to_cpu(top->log_unix_epoch); o->log_alternate_epoch = le32_to_cpu(top->log_alternate_epoch); o->log_alternate_epoch_clock_id = le32_to_cpu(top->log_alternate_epoch_clock_id); + o->job_start_clock_id = le32_to_cpu(top->job_start_clock_id); o->norandommap = le32_to_cpu(top->norandommap); o->softrandommap = le32_to_cpu(top->softrandommap); o->bs_unaligned = le32_to_cpu(top->bs_unaligned); @@ -458,6 +459,7 @@ void convert_thread_options_to_net(struct thread_options_pack *top, top->log_unix_epoch = cpu_to_le32(o->log_unix_epoch); top->log_alternate_epoch = cpu_to_le32(o->log_alternate_epoch); top->log_alternate_epoch_clock_id = cpu_to_le32(o->log_alternate_epoch_clock_id); + top->job_start_clock_id = cpu_to_le32(o->job_start_clock_id); top->norandommap = cpu_to_le32(o->norandommap); top->softrandommap = cpu_to_le32(o->softrandommap); top->bs_unaligned = cpu_to_le32(o->bs_unaligned); diff --git a/client.c b/client.c index c257036bf5..345fa9101b 100644 --- a/client.c +++ b/client.c @@ -956,6 +956,7 @@ static void convert_ts(struct thread_stat *dst, struct thread_stat *src) dst->error = le32_to_cpu(src->error); dst->thread_number = le32_to_cpu(src->thread_number); dst->groupid = le32_to_cpu(src->groupid); + dst->job_start = le64_to_cpu(src->job_start); dst->pid = le32_to_cpu(src->pid); dst->members = le32_to_cpu(src->members); dst->unified_rw_rep = le32_to_cpu(src->unified_rw_rep); diff --git a/fio.1 b/fio.1 index f0dc49ab00..3e5b840c94 100644 --- a/fio.1 +++ b/fio.1 @@ -537,6 +537,10 @@ copy that segment, instead of entering the kernel with a \fBgettimeofday\fR\|(2) call. The CPU set aside for doing these time calls will be excluded from other uses. Fio will manually clear it from the CPU mask of other jobs. +.TP +.BI job_start_clock_id \fR=\fPint +The clock_id passed to the call to \fBclock_gettime\fR used to record job_start +in the \fBjson\fR output format. Default is 0, or CLOCK_REALTIME. .SS "Target file/device" .TP .BI directory \fR=\fPstr @@ -3664,6 +3668,15 @@ quickly becomes unwieldy. To see the final report per-group instead of per-job, use \fBgroup_reporting\fR. Jobs in a file will be part of the same reporting group, unless if separated by a \fBstonewall\fR, or by using \fBnew_group\fR. +.RS +.P +NOTE: When \fBgroup_reporting\fR is used along with \fBjson\fR output, there +are certain per-job properties which can be different between jobs but do not +have a natural group-level equivalent. Examples include \fBkb_base\fR, +\fBunit_base\fR, \fBsig_figs\fR, \fBthread_number\fR, \fBpid\fR, and +\fBjob_start\fR. For these properties, the values for the first job are +recorded for the group. +.RE .TP .BI new_group Start a new reporting group. See: \fBgroup_reporting\fR. If not given, diff --git a/fio.h b/fio.h index a54f57c93e..1322656fd4 100644 --- a/fio.h +++ b/fio.h @@ -388,7 +388,8 @@ struct thread_data { struct timespec start; /* start of this loop */ struct timespec epoch; /* time job was started */ - unsigned long long alternate_epoch; /* Time job was started, clock_gettime's clock_id epoch based. */ + unsigned long long alternate_epoch; /* Time job was started, as clock_gettime(log_alternate_epoch_clock_id) */ + unsigned long long job_start; /* Time job was started, as clock_gettime(job_start_clock_id) */ struct timespec last_issue; long time_offset; struct timespec ts_cache; diff --git a/fio_time.h b/fio_time.h index 62d92120a5..b20e734c4a 100644 --- a/fio_time.h +++ b/fio_time.h @@ -30,6 +30,6 @@ extern bool ramp_time_over(struct thread_data *); extern bool in_ramp_time(struct thread_data *); extern void fio_time_init(void); extern void timespec_add_msec(struct timespec *, unsigned int); -extern void set_epoch_time(struct thread_data *, int, clockid_t); +extern void set_epoch_time(struct thread_data *, clockid_t, clockid_t); #endif diff --git a/libfio.c b/libfio.c index 237ce34cb3..5c43327769 100644 --- a/libfio.c +++ b/libfio.c @@ -149,7 +149,7 @@ void reset_all_stats(struct thread_data *td) td->ts.runtime[i] = 0; } - set_epoch_time(td, td->o.log_unix_epoch | td->o.log_alternate_epoch, td->o.log_alternate_epoch_clock_id); + set_epoch_time(td, td->o.log_alternate_epoch_clock_id, td->o.job_start_clock_id); memcpy(&td->start, &td->epoch, sizeof(td->epoch)); memcpy(&td->iops_sample_time, &td->epoch, sizeof(td->epoch)); memcpy(&td->bw_sample_time, &td->epoch, sizeof(td->epoch)); diff --git a/options.c b/options.c index 48aa0d7b1c..281ea609e4 100644 --- a/options.c +++ b/options.c @@ -4952,6 +4952,16 @@ struct fio_option fio_options[FIO_MAX_OPTS] = { .category = FIO_OPT_C_GENERAL, .group = FIO_OPT_G_CLOCK, }, + { + .name = "job_start_clock_id", + .lname = "Job start clock_id", + .type = FIO_OPT_INT, + .off1 = offsetof(struct thread_options, job_start_clock_id), + .help = "The clock_id passed to the call to clock_gettime used to record job_start in the json output format. Default is 0, or CLOCK_REALTIME", + .verify = gtod_cpu_verify, + .category = FIO_OPT_C_GENERAL, + .group = FIO_OPT_G_CLOCK, + }, { .name = "unified_rw_reporting", .lname = "Unified RW Reporting", diff --git a/rate-submit.c b/rate-submit.c index 6f6d15bd66..92be3df75e 100644 --- a/rate-submit.c +++ b/rate-submit.c @@ -185,7 +185,7 @@ static int io_workqueue_init_worker_fn(struct submit_worker *sw) if (td->io_ops->post_init && td->io_ops->post_init(td)) goto err_io_init; - set_epoch_time(td, td->o.log_unix_epoch | td->o.log_alternate_epoch, td->o.log_alternate_epoch_clock_id); + set_epoch_time(td, td->o.log_alternate_epoch_clock_id, td->o.job_start_clock_id); fio_getrusage(&td->ru_start); clear_io_state(td, 1); diff --git a/server.c b/server.c index bb423702a7..27332e326e 100644 --- a/server.c +++ b/server.c @@ -1706,6 +1706,7 @@ void fio_server_send_ts(struct thread_stat *ts, struct group_run_stats *rs) p.ts.error = cpu_to_le32(ts->error); p.ts.thread_number = cpu_to_le32(ts->thread_number); p.ts.groupid = cpu_to_le32(ts->groupid); + p.ts.job_start = cpu_to_le64(ts->job_start); p.ts.pid = cpu_to_le32(ts->pid); p.ts.members = cpu_to_le32(ts->members); p.ts.unified_rw_rep = cpu_to_le32(ts->unified_rw_rep); diff --git a/stat.c b/stat.c index 7b791628f4..8c8d69f047 100644 --- a/stat.c +++ b/stat.c @@ -1712,6 +1712,7 @@ static struct json_object *show_thread_status_json(struct thread_stat *ts, root = json_create_object(); json_object_add_value_string(root, "jobname", ts->name); json_object_add_value_int(root, "groupid", ts->groupid); + json_object_add_value_int(root, "job_start", ts->job_start); json_object_add_value_int(root, "error", ts->error); /* ETA Info */ @@ -2526,6 +2527,7 @@ void __show_run_stats(void) */ ts->thread_number = td->thread_number; ts->groupid = td->groupid; + ts->job_start = td->job_start; /* * first pid in group, not very useful... @@ -3048,7 +3050,9 @@ static void __add_log_sample(struct io_log *iolog, union io_sample_data data, s = get_sample(iolog, cur_log, cur_log->nr_samples); s->data = data; - s->time = t + (iolog->td ? iolog->td->alternate_epoch : 0); + s->time = t; + if (iolog->td && (iolog->td->o.log_unix_epoch || iolog->td->o.log_alternate_epoch)) + s->time += iolog->td->alternate_epoch; io_sample_set_ddir(iolog, s, ddir); s->bs = bs; s->priority = priority; diff --git a/stat.h b/stat.h index 8ceabc48c7..bd986d4e71 100644 --- a/stat.h +++ b/stat.h @@ -169,6 +169,7 @@ struct thread_stat { uint32_t error; uint32_t thread_number; uint32_t groupid; + uint64_t job_start; /* Time job was started, as clock_gettime(job_start_clock_id) */ uint32_t pid; char description[FIO_JOBDESC_SIZE]; uint32_t members; diff --git a/thread_options.h b/thread_options.h index 38a9993d23..91c194cf71 100644 --- a/thread_options.h +++ b/thread_options.h @@ -273,6 +273,7 @@ struct thread_options { unsigned int unified_rw_rep; unsigned int gtod_reduce; unsigned int gtod_cpu; + unsigned int job_start_clock_id; enum fio_cs clocksource; unsigned int no_stall; unsigned int trim_percentage; @@ -422,7 +423,6 @@ struct thread_options_pack { uint32_t iodepth_batch_complete_min; uint32_t iodepth_batch_complete_max; uint32_t serialize_overlap; - uint32_t pad; uint64_t size; uint64_t io_size; @@ -433,13 +433,11 @@ struct thread_options_pack { uint32_t fill_device; uint32_t file_append; uint32_t unique_filename; - uint32_t pad3; uint64_t file_size_low; uint64_t file_size_high; uint64_t start_offset; uint64_t start_offset_align; uint32_t start_offset_nz; - uint32_t pad4; uint64_t bs[DDIR_RWDIR_CNT]; uint64_t ba[DDIR_RWDIR_CNT]; @@ -511,6 +509,7 @@ struct thread_options_pack { struct zone_split zone_split[DDIR_RWDIR_CNT][ZONESPLIT_MAX]; uint32_t zone_split_nr[DDIR_RWDIR_CNT]; + uint32_t pad; fio_fp64_t zipf_theta; fio_fp64_t pareto_h; fio_fp64_t gauss_dev; @@ -593,6 +592,7 @@ struct thread_options_pack { uint32_t unified_rw_rep; uint32_t gtod_reduce; uint32_t gtod_cpu; + uint32_t job_start_clock_id; uint32_t clocksource; uint32_t no_stall; uint32_t trim_percentage; @@ -603,6 +603,7 @@ struct thread_options_pack { uint32_t lat_percentiles; uint32_t slat_percentiles; uint32_t percentile_precision; + uint32_t pad2; fio_fp64_t percentile_list[FIO_IO_U_LIST_MAX_LEN]; uint8_t read_iolog_file[FIO_TOP_STR_MAX]; diff --git a/time.c b/time.c index 5c4d6de039..7cbab6ff4f 100644 --- a/time.c +++ b/time.c @@ -172,14 +172,22 @@ void set_genesis_time(void) fio_gettime(&genesis, NULL); } -void set_epoch_time(struct thread_data *td, int log_alternate_epoch, clockid_t clock_id) +void set_epoch_time(struct thread_data *td, clockid_t log_alternate_epoch_clock_id, clockid_t job_start_clock_id) { + struct timespec ts; fio_gettime(&td->epoch, NULL); - if (log_alternate_epoch) { - struct timespec ts; - clock_gettime(clock_id, &ts); - td->alternate_epoch = (unsigned long long)(ts.tv_sec) * 1000 + - (unsigned long long)(ts.tv_nsec) / 1000000; + clock_gettime(log_alternate_epoch_clock_id, &ts); + td->alternate_epoch = (unsigned long long)(ts.tv_sec) * 1000 + + (unsigned long long)(ts.tv_nsec) / 1000000; + if (job_start_clock_id == log_alternate_epoch_clock_id) + { + td->job_start = td->alternate_epoch; + } + else + { + clock_gettime(job_start_clock_id, &ts); + td->job_start = (unsigned long long)(ts.tv_sec) * 1000 + + (unsigned long long)(ts.tv_nsec) / 1000000; } } From d252275b59feb8ca92078ca8f61bab691e539186 Mon Sep 17 00:00:00 2001 From: aggieNick02 Date: Fri, 8 Sep 2023 15:34:09 -0500 Subject: [PATCH 0598/1097] Make log_unix_epoch an official alias of log_alternate_epoch log_alternate_epoch was introduced along with log_alternate_epoch_clock_id, and generalized the idea of log_unix_epoch. Both options had the same effect. So we make log_unix_epoch an official alias of log_alternate_epoch, instead of maintaining both redundant options. Signed-off-by: Nick Neumann nick@pcpartpicker.com --- HOWTO.rst | 10 ++++------ cconv.c | 2 -- fio.1 | 10 ++++------ options.c | 12 ++---------- stat.c | 2 +- thread_options.h | 5 +---- 6 files changed, 12 insertions(+), 29 deletions(-) diff --git a/HOWTO.rst b/HOWTO.rst index 9c825cc27d..7f26978a74 100644 --- a/HOWTO.rst +++ b/HOWTO.rst @@ -4114,9 +4114,7 @@ Measurements and reporting .. option:: log_unix_epoch=bool - If set, fio will log Unix timestamps to the log files produced by enabling - write_type_log for each log type, instead of the default zero-based - timestamps. + Backwards compatible alias for log_alternate_epoch. .. option:: log_alternate_epoch=bool @@ -4127,9 +4125,9 @@ Measurements and reporting .. option:: log_alternate_epoch_clock_id=int - Specifies the clock_id to be used by clock_gettime to obtain the alternate epoch - if either log_unix_epoch or log_alternate_epoch are true. Otherwise has no - effect. Default value is 0, or CLOCK_REALTIME. + Specifies the clock_id to be used by clock_gettime to obtain the alternate + epoch if log_alternate_epoch is true. Otherwise has no effect. Default + value is 0, or CLOCK_REALTIME. .. option:: block_error_percentiles=bool diff --git a/cconv.c b/cconv.c index b0127dd870..341388d456 100644 --- a/cconv.c +++ b/cconv.c @@ -216,7 +216,6 @@ int convert_thread_options_to_cpu(struct thread_options *o, o->log_prio = le32_to_cpu(top->log_prio); o->log_gz = le32_to_cpu(top->log_gz); o->log_gz_store = le32_to_cpu(top->log_gz_store); - o->log_unix_epoch = le32_to_cpu(top->log_unix_epoch); o->log_alternate_epoch = le32_to_cpu(top->log_alternate_epoch); o->log_alternate_epoch_clock_id = le32_to_cpu(top->log_alternate_epoch_clock_id); o->job_start_clock_id = le32_to_cpu(top->job_start_clock_id); @@ -456,7 +455,6 @@ void convert_thread_options_to_net(struct thread_options_pack *top, top->log_prio = cpu_to_le32(o->log_prio); top->log_gz = cpu_to_le32(o->log_gz); top->log_gz_store = cpu_to_le32(o->log_gz_store); - top->log_unix_epoch = cpu_to_le32(o->log_unix_epoch); top->log_alternate_epoch = cpu_to_le32(o->log_alternate_epoch); top->log_alternate_epoch_clock_id = cpu_to_le32(o->log_alternate_epoch_clock_id); top->job_start_clock_id = cpu_to_le32(o->job_start_clock_id); diff --git a/fio.1 b/fio.1 index 3e5b840c94..8159caa47f 100644 --- a/fio.1 +++ b/fio.1 @@ -3808,9 +3808,7 @@ decompressed with fio, using the \fB\-\-inflate\-log\fR command line parameter. The files will be stored with a `.fz' suffix. .TP .BI log_unix_epoch \fR=\fPbool -If set, fio will log Unix timestamps to the log files produced by enabling -write_type_log for each log type, instead of the default zero-based -timestamps. +Backward-compatible alias for \fBlog_alternate_epoch\fR. .TP .BI log_alternate_epoch \fR=\fPbool If set, fio will log timestamps based on the epoch used by the clock specified @@ -3819,9 +3817,9 @@ enabling write_type_log for each log type, instead of the default zero-based timestamps. .TP .BI log_alternate_epoch_clock_id \fR=\fPint -Specifies the clock_id to be used by clock_gettime to obtain the alternate epoch -if either \fBBlog_unix_epoch\fR or \fBlog_alternate_epoch\fR are true. Otherwise has no -effect. Default value is 0, or CLOCK_REALTIME. +Specifies the clock_id to be used by clock_gettime to obtain the alternate +epoch if \fBlog_alternate_epoch\fR is true. Otherwise has no effect. Default +value is 0, or CLOCK_REALTIME. .TP .BI block_error_percentiles \fR=\fPbool If set, record errors in trim block-sized units from writes and trims and diff --git a/options.c b/options.c index 281ea609e4..95001b4aaf 100644 --- a/options.c +++ b/options.c @@ -4600,17 +4600,9 @@ struct fio_option fio_options[FIO_MAX_OPTS] = { .help = "Install libz-dev(el) to get compression support", }, #endif - { - .name = "log_unix_epoch", - .lname = "Log epoch unix", - .type = FIO_OPT_BOOL, - .off1 = offsetof(struct thread_options, log_unix_epoch), - .help = "Use Unix time in log files", - .category = FIO_OPT_C_LOG, - .group = FIO_OPT_G_INVALID, - }, { .name = "log_alternate_epoch", + .alias = "log_unix_epoch", .lname = "Log epoch alternate", .type = FIO_OPT_BOOL, .off1 = offsetof(struct thread_options, log_alternate_epoch), @@ -4623,7 +4615,7 @@ struct fio_option fio_options[FIO_MAX_OPTS] = { .lname = "Log alternate epoch clock_id", .type = FIO_OPT_INT, .off1 = offsetof(struct thread_options, log_alternate_epoch_clock_id), - .help = "If log_alternate_epoch or log_unix_epoch is true, this option specifies the clock_id from clock_gettime whose epoch should be used. If neither of those is true, this option has no effect. Default value is 0, or CLOCK_REALTIME", + .help = "If log_alternate_epoch is true, this option specifies the clock_id from clock_gettime whose epoch should be used. If log_alternate_epoch is false, this option has no effect. Default value is 0, or CLOCK_REALTIME", .category = FIO_OPT_C_LOG, .group = FIO_OPT_G_INVALID, }, diff --git a/stat.c b/stat.c index 8c8d69f047..7cf6bee194 100644 --- a/stat.c +++ b/stat.c @@ -3051,7 +3051,7 @@ static void __add_log_sample(struct io_log *iolog, union io_sample_data data, s->data = data; s->time = t; - if (iolog->td && (iolog->td->o.log_unix_epoch || iolog->td->o.log_alternate_epoch)) + if (iolog->td && iolog->td->o.log_alternate_epoch) s->time += iolog->td->alternate_epoch; io_sample_set_ddir(iolog, s, ddir); s->bs = bs; diff --git a/thread_options.h b/thread_options.h index 91c194cf71..fdde055e7d 100644 --- a/thread_options.h +++ b/thread_options.h @@ -170,7 +170,6 @@ struct thread_options { unsigned int log_offset; unsigned int log_gz; unsigned int log_gz_store; - unsigned int log_unix_epoch; unsigned int log_alternate_epoch; unsigned int log_alternate_epoch_clock_id; unsigned int norandommap; @@ -492,7 +491,6 @@ struct thread_options_pack { uint32_t log_offset; uint32_t log_gz; uint32_t log_gz_store; - uint32_t log_unix_epoch; uint32_t log_alternate_epoch; uint32_t log_alternate_epoch_clock_id; uint32_t norandommap; @@ -509,7 +507,6 @@ struct thread_options_pack { struct zone_split zone_split[DDIR_RWDIR_CNT][ZONESPLIT_MAX]; uint32_t zone_split_nr[DDIR_RWDIR_CNT]; - uint32_t pad; fio_fp64_t zipf_theta; fio_fp64_t pareto_h; fio_fp64_t gauss_dev; @@ -603,7 +600,7 @@ struct thread_options_pack { uint32_t lat_percentiles; uint32_t slat_percentiles; uint32_t percentile_precision; - uint32_t pad2; + uint32_t pad; fio_fp64_t percentile_list[FIO_IO_U_LIST_MAX_LEN]; uint8_t read_iolog_file[FIO_TOP_STR_MAX]; From ba342e585aa512edbd37cf736850fcb3cb5ca76d Mon Sep 17 00:00:00 2001 From: Ankit Kumar Date: Mon, 11 Sep 2023 21:54:59 +0530 Subject: [PATCH 0599/1097] engines:io_uring_cmd: disallow verify for e2e pi with extended blocks For extended logical block sizes we cannot use verify when end to end data protection checks are enabled. The CRC field in PI section of data buffer creates conflict during verify phase. The verify check is also redundant as end to end data protection already ensures data integrity. So disallow use of verify for this case. Signed-off-by: Ankit Kumar Signed-off-by: Jens Axboe --- engines/io_uring.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/engines/io_uring.c b/engines/io_uring.c index 6cdf1b4fe4..05703df8e3 100644 --- a/engines/io_uring.c +++ b/engines/io_uring.c @@ -18,6 +18,7 @@ #include "../lib/memalign.h" #include "../lib/fls.h" #include "../lib/roundup.h" +#include "../verify.h" #ifdef ARCH_HAVE_IOURING @@ -1299,6 +1300,19 @@ static int fio_ioring_cmd_open_file(struct thread_data *td, struct fio_file *f) return 1; } } + + /* + * For extended logical block sizes we cannot use verify when + * end to end data protection checks are enabled, as the PI + * section of data buffer conflicts with verify. + */ + if (data->ms && data->pi_type && data->lba_ext && + td->o.verify != VERIFY_NONE) { + log_err("%s: for extended LBA, verify cannot be used when E2E data protection is enabled\n", + f->file_name); + td_verror(td, EINVAL, "fio_ioring_cmd_open_file"); + return 1; + } } if (!ld || !o->registerfiles) return generic_open_file(td, f); From e4a9812dee084b058eca6ebde9634a3d573a0079 Mon Sep 17 00:00:00 2001 From: Ankit Kumar Date: Mon, 11 Sep 2023 21:55:00 +0530 Subject: [PATCH 0600/1097] engines:nvme: fill command fields as per pi check bits Fill the application and reference tag field for read and write command only when pi_chk has the relevant bit set. Signed-off-by: Ankit Kumar Signed-off-by: Jens Axboe --- engines/nvme.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/engines/nvme.c b/engines/nvme.c index 08503b3399..75a5e0c121 100644 --- a/engines/nvme.c +++ b/engines/nvme.c @@ -415,19 +415,24 @@ void fio_nvme_pi_fill(struct nvme_uring_cmd *cmd, struct io_u *io_u, case NVME_NS_DPS_PI_TYPE2: switch (data->guard_type) { case NVME_NVM_NS_16B_GUARD: - cmd->cdw14 = (__u32)slba; + if (opts->io_flags & NVME_IO_PRINFO_PRCHK_REF) + cmd->cdw14 = (__u32)slba; break; case NVME_NVM_NS_64B_GUARD: - cmd->cdw14 = (__u32)slba; - cmd->cdw3 = ((slba >> 32) & 0xffff); + if (opts->io_flags & NVME_IO_PRINFO_PRCHK_REF) { + cmd->cdw14 = (__u32)slba; + cmd->cdw3 = ((slba >> 32) & 0xffff); + } break; default: break; } - cmd->cdw15 = (opts->apptag_mask << 16 | opts->apptag); + if (opts->io_flags & NVME_IO_PRINFO_PRCHK_APP) + cmd->cdw15 = (opts->apptag_mask << 16 | opts->apptag); break; case NVME_NS_DPS_PI_TYPE3: - cmd->cdw15 = (opts->apptag_mask << 16 | opts->apptag); + if (opts->io_flags & NVME_IO_PRINFO_PRCHK_APP) + cmd->cdw15 = (opts->apptag_mask << 16 | opts->apptag); break; case NVME_NS_DPS_PI_NONE: break; From e2c5f17e3559cc7c96706cd75c2609f12675c60b Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Thu, 14 Sep 2023 18:54:25 -0400 Subject: [PATCH 0601/1097] verify: open state file in binary mode on Windows Make sure we open the verify state file in binary mode to avoid any possible conversion of NL to CR+NL. This is the same fix we did for 1fb215e991d260a128e35d761f6850e8d9e4c333. Fixes: https://github.com/axboe/fio/issues/1631 Signed-off-by: Vincent Fu --- verify.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/verify.c b/verify.c index 2848b68612..f7355f3025 100644 --- a/verify.c +++ b/verify.c @@ -1648,6 +1648,10 @@ static int open_state_file(const char *name, const char *prefix, int num, else flags = O_RDONLY; +#ifdef _WIN32 + flags |= O_BINARY; +#endif + verify_state_gen_name(out, sizeof(out), name, prefix, num); fd = open(out, flags, 0644); From 702e3f34787c027daf03af22cd71459feb344432 Mon Sep 17 00:00:00 2001 From: aggieNick02 Date: Tue, 19 Sep 2023 18:14:54 -0500 Subject: [PATCH 0602/1097] Update docs to clarify how to pass job options in client mode When run in client mode, fio does not pass any job options specified on the command line to the fio server. When run in client mode, all job options must be specified via local or remote job files. Update the docs to indicate this to avoid end-user confusion. Fixes #1629 Signed-off-by: Nick Neumann nick@pcpartpicker.com --- HOWTO.rst | 3 +++ fio.1 | 3 +++ 2 files changed, 6 insertions(+) diff --git a/HOWTO.rst b/HOWTO.rst index 7f26978a74..cc7124b132 100644 --- a/HOWTO.rst +++ b/HOWTO.rst @@ -5105,6 +5105,9 @@ is the connect string, and `remote-args` and `job file(s)` are sent to the server. The `server` string follows the same format as it does on the server side, to allow IP/hostname/socket and port strings. +Note that all job options must be defined in job files when running fio as a +client. Any job options specified in `remote-args` will be ignored. + Fio can connect to multiple servers this way:: fio --client= --client= diff --git a/fio.1 b/fio.1 index 8159caa47f..628e278dff 100644 --- a/fio.1 +++ b/fio.1 @@ -4838,6 +4838,9 @@ is the connect string, and `remote\-args' and `job file(s)' are sent to the server. The `server' string follows the same format as it does on the server side, to allow IP/hostname/socket and port strings. .P +Note that all job options must be defined in job files when running fio as a +client. Any job options specified in `remote\-args' will be ignored. +.P Fio can connect to multiple servers this way: .RS .P From 996ac91f54844e63ef43092472fc1f7610567b67 Mon Sep 17 00:00:00 2001 From: Shin'ichiro Kawasaki Date: Wed, 13 Sep 2023 10:52:49 +0900 Subject: [PATCH 0603/1097] t/zbd: set mq-deadline scheduler to device-mapper destination devices When write workloads run on zoned block devices, mq-deadline scheduler is required to ensure write operations are sequential. To fulfill this requirement, the test script t/zbd/test-zbd-support sets mq-deadline to the sysfs attribute "queue/scheduler". However, this preparation does not work when the write target device is a bio based device-mapper device. The device is bio based then I/O scheduler does not work. Setting mq-deadline to the sysfs attribute has no effect. On top of that, the sysfs attribute "queue/scheduler" is no longer available for bio based device-mapper devices since Linux kernel version v6.5. To ensure mq-deadline scheduler for bio based device-mapper devices, improve the helper function set_io_scheduler. If the sysfs attribute "queue/scheduler" is available, use it. Otherwise, check if the test device is a zoned device-mapper (linear, flakey or crypt). If so, set mq-deadline scheduler to destination devices of the device-mapper device. To implement these, add some helper functions. Signed-off-by: Shin'ichiro Kawasaki Link: https://lore.kernel.org/r/20230913015249.2226799-1-shinichiro.kawasaki@wdc.com Signed-off-by: Vincent Fu --- t/zbd/functions | 11 ++++++++ t/zbd/test-zbd-support | 61 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 71 insertions(+), 1 deletion(-) diff --git a/t/zbd/functions b/t/zbd/functions index 4faa45a92d..028df4040e 100644 --- a/t/zbd/functions +++ b/t/zbd/functions @@ -27,6 +27,17 @@ blkzone_reports_capacity() { "${blkzone}" report -c 1 -o 0 "${dev}" | grep -q 'cap ' } +has_command() { + local cmd="${1}" + + cmd_path=$(type -p "${cmd}" 2>/dev/null) + if [ -z "${cmd_path}" ]; then + echo "${cmd} is not available" + return 1 + fi + return 0 +} + # Whether or not $1 (/dev/...) is a NVME ZNS device. is_nvme_zns() { local s diff --git a/t/zbd/test-zbd-support b/t/zbd/test-zbd-support index c8f3eb614f..0436d319da 100755 --- a/t/zbd/test-zbd-support +++ b/t/zbd/test-zbd-support @@ -46,6 +46,55 @@ ioengine() { fi } +get_dev_path_by_id() { + for d in /sys/block/* /sys/block/*/*; do + if [[ ! -r "${d}/dev" ]]; then + continue + fi + if [[ "${1}" == "$(<"${d}/dev")" ]]; then + echo "/dev/${d##*/}" + return 0 + fi + done + return 1 +} + +dm_destination_dev_set_io_scheduler() { + local dev=$1 sched=$2 + local dest_dev_id dest_dev path + + has_command dmsetup || return 1 + + while read -r dest_dev_id; do + if ! dest_dev=$(get_dev_path_by_id "${dest_dev_id}"); then + continue + fi + path=${dest_dev/dev/sys\/block}/queue/scheduler + if [[ ! -w ${path} ]]; then + echo "Can not set scheduler of device mapper destination: ${dest_dev}" + continue + fi + echo "${2}" > "${path}" + done < <(dmsetup table "$(<"/sys/block/$dev/dm/name")" | + sed -n 's/.* \([0-9]*:[0-9]*\).*/\1/p') +} + +dev_has_dm_map() { + local dev=${1} target_type=${2} + local dm_name + + has_command dmsetup || return 1 + + dm_name=$(<"/sys/block/$dev/dm/name") + if ! dmsetup status "${dm_name}" | grep -qe "${target_type}"; then + return 1 + fi + if dmsetup status "${dm_name}" | grep -v "${target_type}"; then + return 1 + fi + return 0 +} + set_io_scheduler() { local dev=$1 sched=$2 @@ -62,7 +111,17 @@ set_io_scheduler() { esac fi - echo "$sched" >"/sys/block/$dev/queue/scheduler" + if [ -w "/sys/block/$dev/queue/scheduler" ]; then + echo "$sched" >"/sys/block/$dev/queue/scheduler" + elif [ -r "/sys/block/$dev/dm/name" ] && + ( dev_has_dm_map "$dev" linear || + dev_has_dm_map "$dev" flakey || + dev_has_dm_map "$dev" crypt ); then + dm_destination_dev_set_io_scheduler "$dev" "$sched" + else + echo "can not set io scheduler" + exit 1 + fi } check_read() { From 242c63dbbf104d2b0dd03fb2966ebd8ff9a6e2a3 Mon Sep 17 00:00:00 2001 From: Igor Pylypiv Date: Thu, 28 Sep 2023 16:37:14 -0700 Subject: [PATCH 0604/1097] verify: Fix the bad pattern block offset value We offset buf by header_size for pattern verification. Add header_size to the mismatched buf offset to get the correct block offset value. Signed-off-by: Igor Pylypiv --- verify.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/verify.c b/verify.c index f7355f3025..78f333e67e 100644 --- a/verify.c +++ b/verify.c @@ -398,7 +398,8 @@ static int verify_io_u_pattern(struct verify_header *hdr, struct vcont *vc) (unsigned char)buf[i], (unsigned char)pattern[mod], bits); - log_err("fio: bad pattern block offset %u\n", i); + log_err("fio: bad pattern block offset %u\n", + i + header_size); vc->name = "pattern"; log_verify_failure(hdr, vc); return EILSEQ; From 1353b1b9bbd6717c89e32883e87aa5cfe4cb04dc Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Fri, 29 Sep 2023 10:04:50 -0400 Subject: [PATCH 0605/1097] workqueue: handle nice better nice returns the program's schedule priority. This can be a negative value when there is no error condition. To check if nice is triggering an error we have to check errno. The most recent three macOS test failures appear to be due to this problem. https://github.com/axboe/fio/actions/runs/6347762639/job/17243247222 https://github.com/axboe/fio/actions/runs/6346019205/job/17239370410 https://github.com/axboe/fio/actions/runs/6241934089/job/16944981876 Signed-off-by: Vincent Fu --- workqueue.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/workqueue.c b/workqueue.c index 9e6c41ff2f..3636bc3a8a 100644 --- a/workqueue.c +++ b/workqueue.c @@ -136,7 +136,8 @@ static void *worker_thread(void *data) sk_out_assign(sw->sk_out); if (wq->ops.nice) { - if (nice(wq->ops.nice) < 0) { + errno = 0; + if (nice(wq->ops.nice) == -1 && errno != 0) { log_err("workqueue: nice %s\n", strerror(errno)); ret = 1; } From c95b52caacc8ef5c1235fb3754186e981b109bdb Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Fri, 29 Sep 2023 08:58:10 -0400 Subject: [PATCH 0606/1097] ci: switch macos runs from macos-12 to macos-13 macOS 13 was released in Oct 2022 and the GitHub Actions image debuted Apr 2023. Let's switch to this new platform. It seems to work fine based on two runs testing the nice fix in the previous patch. https://github.com/vincentkfu/fio/actions/runs/6352923622/job/17256648185 https://github.com/vincentkfu/fio/actions/runs/6353159388/job/17257286332 Signed-off-by: Vincent Fu --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 69fedf7751..b8000024fb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,7 +27,7 @@ jobs: os: ubuntu-22.04 cc: clang - build: macos - os: macos-12 + os: macos-13 - build: linux-i686-gcc os: ubuntu-22.04 arch: i686 From 6f9cdcfcc7598c7d7b19c4a5120a251a80dab183 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Mon, 2 Oct 2023 06:41:54 -0700 Subject: [PATCH 0607/1097] iolog: don't truncate time values We store iolog timestamps as 64-bit unsigned integers but when we print timestamps in the logs we type cast them to unsigned longs. This is fine on platforms with 64-bit longs but on platforms like Windows with 32-bit longs this truncates the timestamps. Truncation causes problems especially when options such as --log_unix_epoch are enabled because the number of milliseconds since 01-01-1970 does not fit in a 32-bit unsigned long. Fix this by getting rid of the type cast and using PRIu64 as the format specifier. Fixes: https://github.com/axboe/fio/issues/1638 Signed-off-by: Vincent Fu --- iolog.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/iolog.c b/iolog.c index 97ba43967f..5213c60f1a 100644 --- a/iolog.c +++ b/iolog.c @@ -1002,14 +1002,14 @@ void flush_samples(FILE *f, void *samples, uint64_t sample_size) if (log_offset) { if (log_prio) - fmt = "%lu, %" PRId64 ", %u, %llu, %llu, 0x%04x\n"; + fmt = "%" PRIu64 ", %" PRId64 ", %u, %llu, %llu, 0x%04x\n"; else - fmt = "%lu, %" PRId64 ", %u, %llu, %llu, %u\n"; + fmt = "%" PRIu64 ", %" PRId64 ", %u, %llu, %llu, %u\n"; } else { if (log_prio) - fmt = "%lu, %" PRId64 ", %u, %llu, 0x%04x\n"; + fmt = "%" PRIu64 ", %" PRId64 ", %u, %llu, 0x%04x\n"; else - fmt = "%lu, %" PRId64 ", %u, %llu, %u\n"; + fmt = "%" PRIu64 ", %" PRId64 ", %u, %llu, %u\n"; } nr_samples = sample_size / __log_entry_sz(log_offset); @@ -1024,7 +1024,7 @@ void flush_samples(FILE *f, void *samples, uint64_t sample_size) if (!log_offset) { fprintf(f, fmt, - (unsigned long) s->time, + s->time, s->data.val, io_sample_ddir(s), (unsigned long long) s->bs, prio_val); @@ -1032,7 +1032,7 @@ void flush_samples(FILE *f, void *samples, uint64_t sample_size) struct io_sample_offset *so = (void *) s; fprintf(f, fmt, - (unsigned long) s->time, + s->time, s->data.val, io_sample_ddir(s), (unsigned long long) s->bs, (unsigned long long) so->offset, From 4b3d7ff7216dfdb1708cc8ad1b267186440eac11 Mon Sep 17 00:00:00 2001 From: Alexey Neyman Date: Thu, 5 Oct 2023 18:43:17 +0000 Subject: [PATCH 0608/1097] Change memcpy() calls to assignments This is to avoid triggering a spurious warning caused by [1], which is triggered by the next commit in chain (unrelated change in update_io_tick_disk()). [1] https://gcc.gnu.org/bugzilla/show_bug.cgi?id=111696 Signed-off-by: Alexey Neyman --- diskutil.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/diskutil.c b/diskutil.c index cf4ede8577..40f2c26030 100644 --- a/diskutil.c +++ b/diskutil.c @@ -103,8 +103,8 @@ static void update_io_tick_disk(struct disk_util *du) fio_gettime(&t, NULL); dus->s.msec += mtime_since(&du->time, &t); - memcpy(&du->time, &t, sizeof(t)); - memcpy(&ldus->s, &__dus.s, sizeof(__dus.s)); + du->time = t; + ldus->s = __dus.s; } int update_io_ticks(void) From 5f7bd351630d02c223772ade57bf321a75fba020 Mon Sep 17 00:00:00 2001 From: Alexey Neyman Date: Tue, 3 Oct 2023 22:49:02 +0000 Subject: [PATCH 0609/1097] Handle 32-bit overflows in disk utilization stats Linux prints [1] some of the values reported in block device's `stat` sysfs file as 32-bit unsigned integers. fio interprets them as 64-bit integers when reading that sysfs file and performs further arithmetics on them in 64-bits. If the reported value overflows during fio run, a huge bogus value is reported in the "disk utilization" block instead. [1] https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/block/genhd.c#n962 Signed-off-by: Alexey Neyman --- diskutil.c | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/diskutil.c b/diskutil.c index 40f2c26030..69b3dd263f 100644 --- a/diskutil.c +++ b/diskutil.c @@ -77,6 +77,23 @@ static int get_io_ticks(struct disk_util *du, struct disk_util_stat *dus) return ret != 10; } +static uint64_t safe_32bit_diff(uint64_t nval, uint64_t oval) +{ + /* Linux kernel prints some of the stat fields as 32-bit integers. It is + * possible that the value overflows, but since fio uses unsigned 64-bit + * arithmetic in update_io_tick_disk(), it instead results in a huge + * bogus value being added to the respective accumulating field. Just + * in case Linux starts reporting these metrics as 64-bit values in the + * future, check that overflow actually happens around the 32-bit + * unsigned boundary; assume overflow only happens once between + * successive polls. + */ + if (oval <= nval || oval >= (1ull << 32)) + return nval - oval; + else + return (1ull << 32) + nval - oval; +} + static void update_io_tick_disk(struct disk_util *du) { struct disk_util_stat __dus, *dus, *ldus; @@ -96,10 +113,11 @@ static void update_io_tick_disk(struct disk_util *du) dus->s.ios[1] += (__dus.s.ios[1] - ldus->s.ios[1]); dus->s.merges[0] += (__dus.s.merges[0] - ldus->s.merges[0]); dus->s.merges[1] += (__dus.s.merges[1] - ldus->s.merges[1]); - dus->s.ticks[0] += (__dus.s.ticks[0] - ldus->s.ticks[0]); - dus->s.ticks[1] += (__dus.s.ticks[1] - ldus->s.ticks[1]); - dus->s.io_ticks += (__dus.s.io_ticks - ldus->s.io_ticks); - dus->s.time_in_queue += (__dus.s.time_in_queue - ldus->s.time_in_queue); + dus->s.ticks[0] += safe_32bit_diff(__dus.s.ticks[0], ldus->s.ticks[0]); + dus->s.ticks[1] += safe_32bit_diff(__dus.s.ticks[1], ldus->s.ticks[1]); + dus->s.io_ticks += safe_32bit_diff(__dus.s.io_ticks, ldus->s.io_ticks); + dus->s.time_in_queue += + safe_32bit_diff(__dus.s.time_in_queue, ldus->s.time_in_queue); fio_gettime(&t, NULL); dus->s.msec += mtime_since(&du->time, &t); From 50b94305b08a746c21a2c644ffb3cb56915d86ee Mon Sep 17 00:00:00 2001 From: Shin'ichiro Kawasaki Date: Fri, 6 Oct 2023 16:13:20 +0900 Subject: [PATCH 0610/1097] t/zbd: avoid test case 45 failure When zonemode=zbd option is not specified, random writes to zoned block devices fail because writes to sequential write required zones shall happen only at write pointers. Randomly chosen write addresses do not match with the write pointers, then fail. On such failures, fio prints out the message below and tell users how to avoid the failures: "fio: first I/O failed. If .* is a zoned block device, consider --zonemode=zbd". The test case 45 in t/zbd/test-zbd-support confirms the message is printed when the first random write command to a sequential write required zone fails. However, the random write can succeed very rarely since the randomly chosen write address can be same as the write pointer address. For example, a zoned block device with 1MB zone size with 4KB block size device can have the first random write at write pointer with ratio of 4KB/1MB = 1/256. This causes sporadic test case failures. Avoid the failures by two changes. Firstly, change the random write range from a zone to whole sequential write required zones to reduce the failure ratio. Secondly, repeat the test if the message is not printed by the accidental write success. As the test repeated, failure ratio is multiplied and the failure ratio becomes as small as it can be ignored. Signed-off-by: Shin'ichiro Kawasaki Link: https://lore.kernel.org/r/20231006071320.425270-1-shinichiro.kawasaki@wdc.com Signed-off-by: Vincent Fu --- t/zbd/test-zbd-support | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/t/zbd/test-zbd-support b/t/zbd/test-zbd-support index 0436d319da..2f15a19135 100755 --- a/t/zbd/test-zbd-support +++ b/t/zbd/test-zbd-support @@ -1058,15 +1058,20 @@ test44() { test45() { local bs i + local grep_str="fio: first I/O failed. If .* is a zoned block device, consider --zonemode=zbd" require_zbd || return $SKIP_TESTCASE prep_write bs=$((min_seq_write_size)) - run_one_fio_job "$(ioengine "psync")" --iodepth=1 --rw=randwrite --bs=$bs\ - --offset=$((first_sequential_zone_sector * 512)) \ - --size="$zone_size" --do_verify=1 --verify=md5 2>&1 | - tee -a "${logfile}.${test_number}" | - grep -q "fio: first I/O failed. If .* is a zoned block device, consider --zonemode=zbd" + for ((i = 0; i < 10; i++)); do + run_one_fio_job "$(ioengine "psync")" --iodepth=1 --rw=randwrite \ + --offset=$((first_sequential_zone_sector * 512)) \ + --bs="$bs" --time_based --runtime=1s \ + --do_verify=1 --verify=md5 \ + >> "${logfile}.${test_number}" 2>&1 + grep -qe "$grep_str" "${logfile}.${test_number}" && return 0 + done + return 1 } # Random write to sequential zones, libaio, 8 jobs, queue depth 64 per job From c5d8ce3fc736210ded83b126c71e3225c7ffd7c9 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Mon, 16 Oct 2023 10:03:36 -0400 Subject: [PATCH 0611/1097] ci: explicitly install pygments and certifi on macos The documentation build on macOS started failing because of errors with the pygments and certifi modules. Homebrew is not automatically installing pygments and python-certifi which are listed as packages that sphinx-doc depends on because they are already present in the runner image at the required versions (2.16.1 and 2023.7.22, respectively). Explicitly installing the two packages bumps the versions to slightly newer ones (2.16.1_1 and 2023.7.22_1, respectively). This appears to resolve the documentation build problem. https://formulae.brew.sh/formula/sphinx-doc https://github.com/axboe/fio/actions/runs/6533001329/job/17739452911#step:13:155 https://github.com/vincentkfu/fio/actions/runs/6535039949/job/17743571376#step:13:148 https://github.com/vincentkfu/fio/actions/runs/6535229986/job/17744177918#step:6:10 Signed-off-by: Vincent Fu --- ci/actions-install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/actions-install.sh b/ci/actions-install.sh index 95241e7882..76335fbc3e 100755 --- a/ci/actions-install.sh +++ b/ci/actions-install.sh @@ -86,7 +86,7 @@ install_macos() { #echo "Updating homebrew..." #brew update >/dev/null 2>&1 echo "Installing packages..." - HOMEBREW_NO_AUTO_UPDATE=1 brew install cunit libnfs sphinx-doc + HOMEBREW_NO_AUTO_UPDATE=1 brew install cunit libnfs sphinx-doc pygments python-certifi brew link sphinx-doc --force pip3 install scipy six statsmodels } From 1fa44a7ed981b36af3049dc99b5e8c2754bb51a0 Mon Sep 17 00:00:00 2001 From: Shai Levy <58184723+shailevi23@users.noreply.github.com> Date: Mon, 16 Oct 2023 14:26:20 +0300 Subject: [PATCH 0612/1097] configure: improve pthread_sigmask detection. On Windows system, pthread_sigmask is defined as a noop which will trigger unused variable warning for sigmask. By triggering the same warning in the configure script, we make CONFIG_PTHREAD_SIGMASK undefined in the Windows msys2 build. Signed-off-by: Shai Levy . --- configure | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/configure b/configure index 36184a58ad..742cb7c533 100755 --- a/configure +++ b/configure @@ -864,7 +864,8 @@ cat > $TMPC < /* pthread_sigmask() */ int main(void) { - return pthread_sigmask(0, NULL, NULL); + sigset_t sigmask; + return pthread_sigmask(0, NULL, &sigmask); } EOF if compile_prog "" "$LIBS" "pthread_sigmask" ; then From 864742594519946b0bcf9f5f351676b01772b601 Mon Sep 17 00:00:00 2001 From: Shai Levy <58184723+shailevi23@users.noreply.github.com> Date: Mon, 16 Oct 2023 14:29:04 +0300 Subject: [PATCH 0613/1097] helper_thread: fix pthread_sigmask typo. Signed-off-by: Shai Levy . --- helper_thread.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/helper_thread.c b/helper_thread.c index 53dea44bab..2a9dabf5f7 100644 --- a/helper_thread.c +++ b/helper_thread.c @@ -106,13 +106,14 @@ static int read_from_pipe(int fd, void *buf, size_t len) static void block_signals(void) { -#ifdef HAVE_PTHREAD_SIGMASK +#ifdef CONFIG_PTHREAD_SIGMASK sigset_t sigmask; + int ret; + ret = pthread_sigmask(SIG_UNBLOCK, NULL, &sigmask); assert(ret == 0); ret = pthread_sigmask(SIG_BLOCK, &sigmask, NULL); - assert(ret == 0); #endif } From e8a0b539cb154ab32ff19e479ff7bf2361ad0701 Mon Sep 17 00:00:00 2001 From: "zhuqingsong.0909" Date: Thu, 19 Oct 2023 11:29:27 +0800 Subject: [PATCH 0614/1097] fix assert failed when timeout during call rate_ddir. Adding DDIR_TIMEOUT in enum fio_ddir, and rate_ddir returns it when fio timeouts. set_io_u_file will directly break out of the loop, and fill_io_u won't be called, which causes assert to fail in rate_ddir, because td->rwmix_ddir is DDIR_INVAL. Signed-off-by: QingSong Zhu zhuqingsong.0909@bytedance.com --- io_ddir.h | 1 + io_u.c | 10 +++++++--- zbd.c | 1 + 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/io_ddir.h b/io_ddir.h index 217eb62862..280c1e796a 100644 --- a/io_ddir.h +++ b/io_ddir.h @@ -11,6 +11,7 @@ enum fio_ddir { DDIR_WAIT, DDIR_LAST, DDIR_INVAL = -1, + DDIR_TIMEOUT = -2, DDIR_RWDIR_CNT = 3, DDIR_RWDIR_SYNC_CNT = 4, diff --git a/io_u.c b/io_u.c index 07e5bac5ef..131878822e 100644 --- a/io_u.c +++ b/io_u.c @@ -717,7 +717,7 @@ static enum fio_ddir rate_ddir(struct thread_data *td, enum fio_ddir ddir) * check if the usec is capable of taking negative values */ if (now > td->o.timeout) { - ddir = DDIR_INVAL; + ddir = DDIR_TIMEOUT; return ddir; } usec = td->o.timeout - now; @@ -726,7 +726,7 @@ static enum fio_ddir rate_ddir(struct thread_data *td, enum fio_ddir ddir) now = utime_since_now(&td->epoch); if ((td->o.timeout && (now > td->o.timeout)) || td->terminate) - ddir = DDIR_INVAL; + ddir = DDIR_TIMEOUT; return ddir; } @@ -951,7 +951,7 @@ static int fill_io_u(struct thread_data *td, struct io_u *io_u) set_rw_ddir(td, io_u); - if (io_u->ddir == DDIR_INVAL) { + if (io_u->ddir == DDIR_INVAL || io_u->ddir == DDIR_TIMEOUT) { dprint(FD_IO, "invalid direction received ddir = %d", io_u->ddir); return 1; } @@ -1419,6 +1419,10 @@ static long set_io_u_file(struct thread_data *td, struct io_u *io_u) put_file_log(td, f); td_io_close_file(td, f); io_u->file = NULL; + + if (io_u->ddir == DDIR_TIMEOUT) + return 1; + if (td->o.file_service_type & __FIO_FSERVICE_NONUNIFORM) fio_file_reset(td, f); else { diff --git a/zbd.c b/zbd.c index caac68bb37..c4f7b12f75 100644 --- a/zbd.c +++ b/zbd.c @@ -2171,6 +2171,7 @@ enum io_u_action zbd_adjust_block(struct thread_data *td, struct io_u *io_u) case DDIR_WAIT: case DDIR_LAST: case DDIR_INVAL: + case DDIR_TIMEOUT: goto accept; } From b823153a6a8a24956a6c54f9758f1fdef6ab8d7e Mon Sep 17 00:00:00 2001 From: Michal Biesek Date: Wed, 23 Aug 2023 17:26:22 +0200 Subject: [PATCH 0615/1097] riscv64: add syscall helpers Use syscall helpers to optimized io_uring_enter(2) calls, eliminating the need for libc functions. These wrappers are adapted from liburing. Signed-off-by: michalbiesek --- arch/arch-riscv64.h | 86 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/arch/arch-riscv64.h b/arch/arch-riscv64.h index a74b7d4758..9b8fd001d7 100644 --- a/arch/arch-riscv64.h +++ b/arch/arch-riscv64.h @@ -29,4 +29,90 @@ static inline int arch_init(char *envp[]) return 0; } +#define __do_syscallM(...) ({ \ + __asm__ volatile ( \ + "ecall" \ + : "=r"(a0) \ + : __VA_ARGS__ \ + : "memory", "a1"); \ + (long) a0; \ +}) + +#define __do_syscallN(...) ({ \ + __asm__ volatile ( \ + "ecall" \ + : "=r"(a0) \ + : __VA_ARGS__ \ + : "memory"); \ + (long) a0; \ +}) + +#define __do_syscall0(__n) ({ \ + register long a7 __asm__("a7") = __n; \ + register long a0 __asm__("a0"); \ + \ + __do_syscallM("r" (a7)); \ +}) + +#define __do_syscall1(__n, __a) ({ \ + register long a7 __asm__("a7") = __n; \ + register __typeof__(__a) a0 __asm__("a0") = __a; \ + \ + __do_syscallM("r" (a7), "0" (a0)); \ +}) + +#define __do_syscall2(__n, __a, __b) ({ \ + register long a7 __asm__("a7") = __n; \ + register __typeof__(__a) a0 __asm__("a0") = __a; \ + register __typeof__(__b) a1 __asm__("a1") = __b; \ + \ + __do_syscallN("r" (a7), "0" (a0), "r" (a1)); \ +}) + +#define __do_syscall3(__n, __a, __b, __c) ({ \ + register long a7 __asm__("a7") = __n; \ + register __typeof__(__a) a0 __asm__("a0") = __a; \ + register __typeof__(__b) a1 __asm__("a1") = __b; \ + register __typeof__(__c) a2 __asm__("a2") = __c; \ + \ + __do_syscallN("r" (a7), "0" (a0), "r" (a1), "r" (a2)); \ +}) + +#define __do_syscall4(__n, __a, __b, __c, __d) ({ \ + register long a7 __asm__("a7") = __n; \ + register __typeof__(__a) a0 __asm__("a0") = __a; \ + register __typeof__(__b) a1 __asm__("a1") = __b; \ + register __typeof__(__c) a2 __asm__("a2") = __c; \ + register __typeof__(__d) a3 __asm__("a3") = __d; \ + \ + __do_syscallN("r" (a7), "0" (a0), "r" (a1), "r" (a2), "r" (a3));\ +}) + +#define __do_syscall5(__n, __a, __b, __c, __d, __e) ({ \ + register long a7 __asm__("a7") = __n; \ + register __typeof__(__a) a0 __asm__("a0") = __a; \ + register __typeof__(__b) a1 __asm__("a1") = __b; \ + register __typeof__(__c) a2 __asm__("a2") = __c; \ + register __typeof__(__d) a3 __asm__("a3") = __d; \ + register __typeof__(__e) a4 __asm__("a4") = __e; \ + \ + __do_syscallN("r" (a7), "0" (a0), "r" (a1), "r" (a2), "r" (a3), \ + "r"(a4)); \ +}) + +#define __do_syscall6(__n, __a, __b, __c, __d, __e, __f) ({ \ + register long a7 __asm__("a7") = __n; \ + register __typeof__(__a) a0 __asm__("a0") = __a; \ + register __typeof__(__b) a1 __asm__("a1") = __b; \ + register __typeof__(__c) a2 __asm__("a2") = __c; \ + register __typeof__(__d) a3 __asm__("a3") = __d; \ + register __typeof__(__e) a4 __asm__("a4") = __e; \ + register __typeof__(__f) a5 __asm__("a5") = __f; \ + \ + __do_syscallN("r" (a7), "0" (a0), "r" (a1), "r" (a2), "r" (a3), \ + "r" (a4), "r"(a5)); \ +}) + +#define FIO_ARCH_HAS_SYSCALL + #endif From 624e263f6acb1563471a83601ce19dfb77ac5694 Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Fri, 20 Oct 2023 04:30:43 -0600 Subject: [PATCH 0616/1097] Fio 3.36 Signed-off-by: Jens Axboe --- FIO-VERSION-GEN | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FIO-VERSION-GEN b/FIO-VERSION-GEN index 4b0d56d038..cf8dbb0efa 100755 --- a/FIO-VERSION-GEN +++ b/FIO-VERSION-GEN @@ -1,7 +1,7 @@ #!/bin/sh GVF=FIO-VERSION-FILE -DEF_VER=fio-3.35 +DEF_VER=fio-3.36 LF=' ' From 577b85ec41b5560a81a4fb09d1ec7760bcacc49f Mon Sep 17 00:00:00 2001 From: Gilbert Gilb's Date: Sun, 22 Oct 2023 19:06:45 +0200 Subject: [PATCH 0617/1097] riscv64: get clock from `rdtime` instead of `rdcycle` `rdcycle` pseudo-instruction accesses the "cycle CSR" which holds the real count of CPU core clock cycles [1]. As this leaves room for side-channel attacks, access to this register from userland might be forbidden by the kernel, which results in a SIGILL [2]. Anyhow, it seems that the actual usage of the `get_cpu_clock` function in fio is about getting a wall-clock rather than the actual CPU core clock (for instance, x86 uses `rdtsc`), so this is technically a bug. The "time CSR" is the proper register to track time on riscv64. Also, the "time CSR" is more likely to be available from userspace and not cause a crash. [1] RISC-V ISA Section 10.1: https://github.com/riscv/riscv-isa-manual/releases/download/Ratified-IMAFDQC/riscv-spec-20191213.pdf [2] https://lore.kernel.org/all/YxIzgYP3MujXdqwj@aurel32.net/T/ Signed-off-by: N. Le Roux Signed-off-by: Gilbert Gilb's --- arch/arch-riscv64.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arch-riscv64.h b/arch/arch-riscv64.h index 9b8fd001d7..8ac33fa31c 100644 --- a/arch/arch-riscv64.h +++ b/arch/arch-riscv64.h @@ -16,7 +16,7 @@ static inline unsigned long long get_cpu_clock(void) { unsigned long val; - asm volatile("rdcycle %0" : "=r"(val)); + asm volatile("rdtime %0" : "=r"(val)); return val; } #define ARCH_HAVE_CPU_CLOCK From 85ccc10a2d829c28f2b7a31f9d5424938966d9dc Mon Sep 17 00:00:00 2001 From: Martin Steigerwald Date: Mon, 23 Oct 2023 16:14:50 +0200 Subject: [PATCH 0618/1097] Various spelling fixes. Most of them have been reported by Debian's Lintian tool. Signed-off-by: Martin Steigerwald --- HOWTO.rst | 14 +++++++------- configure | 2 +- fio.1 | 8 ++++---- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/HOWTO.rst b/HOWTO.rst index cc7124b132..6a8fb3e375 100644 --- a/HOWTO.rst +++ b/HOWTO.rst @@ -2344,8 +2344,8 @@ with the caveat that when used on the command line, they must come after the cmdprio_bssplit=blocksize/percentage/class/level/hint:... - This is an extension of the second accepted format that allows to also - specify a priority hint. + This is an extension of the second accepted format that allows one to + also specify a priority hint. For all formats, only the read and write data directions are supported, values for trim IOs are ignored. This option is mutually exclusive with @@ -3014,7 +3014,7 @@ with the caveat that when used on the command line, they must come after the **hugepage** Use hugepages, instead of existing posix memory backend. The memory backend uses hugetlbfs. This require users to allocate - hugepages, mount hugetlbfs and set an enviornment variable for + hugepages, mount hugetlbfs and set an environment variable for XNVME_HUGETLB_PATH. **spdk** Uses SPDK's memory allocator. @@ -3047,7 +3047,7 @@ with the caveat that when used on the command line, they must come after the creating but before connecting the libblkio instance. Each property must have the format ``=``. Colons can be escaped as ``\:``. These are set after the engine sets any other properties, so those can - be overriden. Available properties depend on the libblkio version in use + be overridden. Available properties depend on the libblkio version in use and are listed at https://libblkio.gitlab.io/libblkio/blkio.html#properties @@ -3071,7 +3071,7 @@ with the caveat that when used on the command line, they must come after the connecting but before starting the libblkio instance. Each property must have the format ``=``. Colons can be escaped as ``\:``. These are set after the engine sets any other properties, so those can - be overriden. Available properties depend on the libblkio version in use + be overridden. Available properties depend on the libblkio version in use and are listed at https://libblkio.gitlab.io/libblkio/blkio.html#properties @@ -3635,8 +3635,8 @@ Threads, processes and job synchronization By default, fio will continue running all other jobs when one job finishes. Sometimes this is not the desired action. Setting ``exitall`` will instead make fio terminate all jobs in the same group. The option - ``exit_what`` allows to control which jobs get terminated when ``exitall`` is - enabled. The default is ``group`` and does not change the behaviour of + ``exit_what`` allows one to control which jobs get terminated when ``exitall`` + is enabled. The default is ``group`` and does not change the behaviour of ``exitall``. The setting ``all`` terminates all jobs. The setting ``stonewall`` terminates all currently running jobs across all groups and continues execution with the next stonewalled group. diff --git a/configure b/configure index 742cb7c533..3e3f8132a3 100755 --- a/configure +++ b/configure @@ -334,7 +334,7 @@ if test "$show_help" = "yes" ; then fi cross_prefix=${cross_prefix-${CROSS_COMPILE}} -# Preferred compiler (can be overriden later after we know the platform): +# Preferred compiler (can be overridden later after we know the platform): # ${CC} (if set) # ${cross_prefix}gcc (if cross-prefix specified) # gcc if available diff --git a/fio.1 b/fio.1 index 628e278dff..a8dc8f6c51 100644 --- a/fio.1 +++ b/fio.1 @@ -2142,7 +2142,7 @@ The third accepted format for this option is: cmdprio_bssplit=blocksize/percentage/class/level/hint:... .RE .P -This is an extension of the second accepted format that allows to also +This is an extension of the second accepted format that allows one to also specify a priority hint. .P For all formats, only the read and write data directions are supported, values @@ -2774,7 +2774,7 @@ This is the default posix memory backend for linux NVMe driver. .BI hugepage Use hugepages, instead of existing posix memory backend. The memory backend uses hugetlbfs. This require users to allocate hugepages, mount hugetlbfs and -set an enviornment variable for XNVME_HUGETLB_PATH. +set an environment variable for XNVME_HUGETLB_PATH. .TP .BI spdk Uses SPDK's memory allocator. @@ -2803,7 +2803,7 @@ support it; see \fIhttps://libblkio.gitlab.io/libblkio/blkio.html#drivers\fR A colon-separated list of additional libblkio properties to be set after creating but before connecting the libblkio instance. Each property must have the format \fB=\fR. Colons can be escaped as \fB\\:\fR. These are -set after the engine sets any other properties, so those can be overriden. +set after the engine sets any other properties, so those can be overridden. Available properties depend on the libblkio version in use and are listed at \fIhttps://libblkio.gitlab.io/libblkio/blkio.html#properties\fR .TP @@ -2821,7 +2821,7 @@ may support it; see \fIhttps://libblkio.gitlab.io/libblkio/blkio.html#drivers\fR A colon-separated list of additional libblkio properties to be set after connecting but before starting the libblkio instance. Each property must have the format \fB=\fR. Colons can be escaped as \fB\\:\fR. These are -set after the engine sets any other properties, so those can be overriden. +set after the engine sets any other properties, so those can be overridden. Available properties depend on the libblkio version in use and are listed at \fIhttps://libblkio.gitlab.io/libblkio/blkio.html#properties\fR .TP From 0905f371046906353108ded80b1564f8188ce85e Mon Sep 17 00:00:00 2001 From: Vitaly Chikunov Date: Tue, 24 Oct 2023 05:29:40 +0300 Subject: [PATCH 0619/1097] nfs: Fix incorrect engine registering for '--enghelp' list `ioengine` from `nfs` (internal) engine is incorrectly exported thus overriding its value in constructor callbacks of other external engines, that are used for registering engine for listing with `--enghelp`. Because flist is unsafe to double adding it also making `engine_list` to become corrupt and causing infinite loop or abnormal list termination when printing engine list. Issue: https://github.com/axboe/fio/issues/1655 Fixes: 9326926b ("NFS engine") Signed-off-by: Vitaly Chikunov --- engines/nfs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engines/nfs.c b/engines/nfs.c index 970962a3f9..ce748d1448 100644 --- a/engines/nfs.c +++ b/engines/nfs.c @@ -308,7 +308,7 @@ static int fio_libnfs_close(struct thread_data *td, struct fio_file *f) return ret; } -struct ioengine_ops ioengine = { +static struct ioengine_ops ioengine = { .name = "nfs", .version = FIO_IOOPS_VERSION, .setup = fio_libnfs_setup, From ecc734c6b20f2d1729f0da45fb1490ca092f7e85 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Wed, 25 Oct 2023 18:47:45 +0000 Subject: [PATCH 0620/1097] engines/io_uring_cmd: allocate enough ranges for async trims We round up the iodepth to the next highest power of 2. So io_u->index can be greater than the iodepth specified by the user. Make sure we allocate enough of the buffers used to store the ranges for async trim commands when the iodepth specified by the user is not a power of 2. Fixes: 4885a6eba420ce216e4102df3e42229e167d1b7b ("engines/io_uring_cmd: make trims async") Signed-off-by: Vincent Fu --- engines/io_uring.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engines/io_uring.c b/engines/io_uring.c index 05703df8e3..38c36fdca2 100644 --- a/engines/io_uring.c +++ b/engines/io_uring.c @@ -1196,7 +1196,7 @@ static int fio_ioring_init(struct thread_data *td) td->o.zone_mode == ZONE_MODE_ZBD) td->io_ops->flags |= FIO_ASYNCIO_SYNC_TRIM; else - ld->dsm = calloc(ld->iodepth, sizeof(*ld->dsm)); + ld->dsm = calloc(td->o.iodepth, sizeof(*ld->dsm)); return 0; } From a4614bffd786fe06a824f11f2d8602641b3d0e98 Mon Sep 17 00:00:00 2001 From: Ankit Kumar Date: Wed, 1 Nov 2023 00:15:40 +0530 Subject: [PATCH 0621/1097] crct10: use isa-l for crc if available isa-l provides fast implementation for various polynomials. This will be only used for end to end data protection, and has a significant impact on performance. See: https://github.com/intel/isa-l Signed-off-by: Ankit Kumar --- HOWTO.rst | 4 ++++ configure | 29 +++++++++++++++++++++++++++++ crc/crct10dif_common.c | 13 +++++++++++++ fio.1 | 4 ++++ 4 files changed, 50 insertions(+) diff --git a/HOWTO.rst b/HOWTO.rst index 6a8fb3e375..34d6afdf6e 100644 --- a/HOWTO.rst +++ b/HOWTO.rst @@ -2507,6 +2507,10 @@ with the caveat that when used on the command line, they must come after the If this is set to 0, fio generates protection information for write case and verifies for read case. Default: 1. + For 16 bit CRC generation fio will use isa-l if available otherwise + it will use the default slower generator. + (see: https://github.com/intel/isa-l) + .. option:: pi_chk=str[,str][,str] : [io_uring_cmd] Controls the protection information check. This can take one or more diff --git a/configure b/configure index 3e3f8132a3..420d97dbe6 100755 --- a/configure +++ b/configure @@ -189,6 +189,7 @@ libiscsi="no" libnbd="no" libnfs="" xnvme="" +isal="" libblkio="" libzbc="" dfs="" @@ -262,6 +263,8 @@ for opt do ;; --disable-xnvme) xnvme="no" ;; + --disable-isal) isal="no" + ;; --disable-libblkio) libblkio="no" ;; --disable-tcmalloc) disable_tcmalloc="yes" @@ -322,6 +325,7 @@ if test "$show_help" = "yes" ; then echo "--enable-libiscsi Enable iscsi support" echo "--enable-libnbd Enable libnbd (NBD engine) support" echo "--disable-xnvme Disable xnvme support even if found" + echo "--disable-isal Disable isal support even if found" echo "--disable-libblkio Disable libblkio support even if found" echo "--disable-libzbc Disable libzbc even if found" echo "--disable-tcmalloc Disable tcmalloc support" @@ -2684,6 +2688,28 @@ if test "$xnvme" != "no" ; then fi print_config "xnvme engine" "$xnvme" +if test "$targetos" = "Linux" ; then +########################################## +# Check ISA-L support +cat > $TMPC << EOF +#include +#include +int main(void) +{ + return crc16_t10dif(0, NULL, 4096); +} +EOF +if test "$isal" != "no" ; then + if compile_prog "" "-lisal" "ISAL"; then + isal="yes" + LIBS="-lisal $LIBS" + else + isal="no" + fi +fi +print_config "isal" "$isal" +fi + ########################################## # Check if we have libblkio if test "$libblkio" != "no" ; then @@ -3334,6 +3360,9 @@ if test "$xnvme" = "yes" ; then echo "LIBXNVME_CFLAGS=$xnvme_cflags" >> $config_host_mak echo "LIBXNVME_LIBS=$xnvme_libs" >> $config_host_mak fi +if test "$isal" = "yes" ; then + output_sym "CONFIG_LIBISAL" +fi if test "$libblkio" = "yes" ; then output_sym "CONFIG_LIBBLKIO" echo "LIBBLKIO_CFLAGS=$libblkio_cflags" >> $config_host_mak diff --git a/crc/crct10dif_common.c b/crc/crct10dif_common.c index cfb2a1b18d..1763b1c66b 100644 --- a/crc/crct10dif_common.c +++ b/crc/crct10dif_common.c @@ -24,6 +24,17 @@ * */ +#ifdef CONFIG_LIBISAL +#include + +extern unsigned short fio_crc_t10dif(unsigned short crc, + const unsigned char *buffer, + unsigned int len) +{ + return crc16_t10dif(crc, buffer, len); +} + +#else #include "crc-t10dif.h" /* Table generated using the following polynomium: @@ -76,3 +87,5 @@ extern unsigned short fio_crc_t10dif(unsigned short crc, return crc; } + +#endif diff --git a/fio.1 b/fio.1 index a8dc8f6c51..c4742aa925 100644 --- a/fio.1 +++ b/fio.1 @@ -2263,6 +2263,10 @@ size greater than protection information size, fio will not generate or verify the protection information portion of metadata for write or read case respectively. If this is set to 0, fio generates protection information for write case and verifies for read case. Default: 1. + +For 16 bit CRC generation fio will use isa-l if available otherwise it will +use the default slower generator. +(see: https://github.com/intel/isa-l) .TP .BI (io_uring_cmd)pi_chk \fR=\fPstr[,str][,str] Controls the protection information check. This can take one or more of these From 48cf0c63e5b867c8953f25deaa02466bf94a2eed Mon Sep 17 00:00:00 2001 From: Ankit Kumar Date: Thu, 2 Nov 2023 19:29:28 +0530 Subject: [PATCH 0622/1097] engines/xnvme: fix fdp support for userspace drivers The xNVMe backend supports FDP commands for userspace drivers such as SPDK. Enable support in the xnvme ioengine. Update the xnvme fdp example file accordingly. Signed-off-by: Ankit Kumar Link: https://lore.kernel.org/r/20231102135928.195372-1-ankit.kumar@samsung.com Signed-off-by: Jens Axboe --- engines/xnvme.c | 2 +- examples/xnvme-fdp.fio | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/engines/xnvme.c b/engines/xnvme.c index ce7b2bdd4b..b782401363 100644 --- a/engines/xnvme.c +++ b/engines/xnvme.c @@ -964,7 +964,7 @@ static int xnvme_fioe_fetch_ruhs(struct thread_data *td, struct fio_file *f, uint32_t nsid; int err = 0, err_lock; - if (f->filetype != FIO_TYPE_CHAR) { + if (f->filetype != FIO_TYPE_CHAR && f->filetype != FIO_TYPE_FILE) { log_err("ioeng->fdp_ruhs(): ignoring filetype: %d\n", f->filetype); return -EINVAL; } diff --git a/examples/xnvme-fdp.fio b/examples/xnvme-fdp.fio index 86fbe0d31a..c50959f1f3 100644 --- a/examples/xnvme-fdp.fio +++ b/examples/xnvme-fdp.fio @@ -16,6 +16,26 @@ ; --xnvme_sync=nvme \ ; --filename=/dev/ng0n1 ; +; # Use the xNVMe io-engine engine with SPDK backend, note that you have to set the Namespace-id +; fio examples/xnvme-fdp.fio \ +; --section=default \ +; --ioengine=xnvme \ +; --xnvme_dev_nsid=1 \ +; --filename=0000\\:01\\:00.0 +; +; NOTE: The URI encoded in the filename above, the ":" must be escaped. +; +; On the command-line using two "\\": +; +; --filename=0000\\:01\\:00.0 +; +; Within a fio-script using a single "\": +; +; filename=0000\:01\:00.0 +; +; NOTE: If you want to override the default bs, iodepth, and workload, then +; invoke it as: +; ; FIO_BS="512" FIO_RW="read" FIO_IODEPTH=16 fio examples/xnvme-fdp.fio \ ; --section=override --ioengine=xnvme --xnvme_sync=nvme --filename=/dev/ng0n1 ; From 0d8cc753b8a18d46e265e33e50fb9f92d4ca468e Mon Sep 17 00:00:00 2001 From: Christian Loehle Date: Mon, 23 Oct 2023 10:42:26 +0100 Subject: [PATCH 0623/1097] fio: Introduce new constant thinkcycles option The thinkcycles parameter allows to set a number of cycles to spin between requests to model real-world applications more realistically The thinktime parameter family can be used to model an application processing the data to be able to model real-world applications more closely. Unfortunately this is currently set per constant time and therefore is affected by CPU frequency settings or task migration to a CPU with different capacity. The new thinkcycles parameter closes that gap and allows specifying a constant number of cycles instead, such that CPU capacity is taken into account. Signed-off-by: Christian Loehle --- HOWTO.rst | 8 ++++++++ backend.c | 4 ++++ cconv.c | 2 ++ fio.1 | 7 +++++++ fio_time.h | 1 + options.c | 12 ++++++++++++ thread_options.h | 8 ++++++-- time.c | 11 +++++++++++ 8 files changed, 51 insertions(+), 2 deletions(-) diff --git a/HOWTO.rst b/HOWTO.rst index 34d6afdf6e..42b2b11974 100644 --- a/HOWTO.rst +++ b/HOWTO.rst @@ -3209,6 +3209,14 @@ I/O depth I/O rate ~~~~~~~~ +.. option:: thinkcycles=int + + Stall the job for the specified number of cycles after an I/O has completed before + issuing the next. May be used to simulate processing being done by an application. + This is not taken into account for the time to be waited on for :option:`thinktime`. + Might not have any effect on some platforms, this can be checked by trying a setting + a high enough amount of thinkcycles. + .. option:: thinktime=time Stall the job for the specified period of time after an I/O has completed before issuing the diff --git a/backend.c b/backend.c index a5895fec4d..1fab467a1b 100644 --- a/backend.c +++ b/backend.c @@ -49,6 +49,7 @@ #include "helper_thread.h" #include "pshared.h" #include "zone-dist.h" +#include "fio_time.h" static struct fio_sem *startup_sem; static struct flist_head *cgroup_list; @@ -1133,6 +1134,9 @@ static void do_io(struct thread_data *td, uint64_t *bytes_done) if (ret < 0) break; + if (ddir_rw(ddir) && td->o.thinkcycles) + cycles_spin(td->o.thinkcycles); + if (ddir_rw(ddir) && td->o.thinktime) handle_thinktime(td, ddir, &comp_time); diff --git a/cconv.c b/cconv.c index 341388d456..c92984081b 100644 --- a/cconv.c +++ b/cconv.c @@ -233,6 +233,7 @@ int convert_thread_options_to_cpu(struct thread_options *o, o->random_generator = le32_to_cpu(top->random_generator); o->hugepage_size = le32_to_cpu(top->hugepage_size); o->rw_min_bs = le64_to_cpu(top->rw_min_bs); + o->thinkcycles = le32_to_cpu(top->thinkcycles); o->thinktime = le32_to_cpu(top->thinktime); o->thinktime_spin = le32_to_cpu(top->thinktime_spin); o->thinktime_blocks = le32_to_cpu(top->thinktime_blocks); @@ -472,6 +473,7 @@ void convert_thread_options_to_net(struct thread_options_pack *top, top->random_generator = cpu_to_le32(o->random_generator); top->hugepage_size = cpu_to_le32(o->hugepage_size); top->rw_min_bs = __cpu_to_le64(o->rw_min_bs); + top->thinkcycles = cpu_to_le32(o->thinkcycles); top->thinktime = cpu_to_le32(o->thinktime); top->thinktime_spin = cpu_to_le32(o->thinktime_spin); top->thinktime_blocks = cpu_to_le32(o->thinktime_blocks); diff --git a/fio.1 b/fio.1 index c4742aa925..d62da6885e 100644 --- a/fio.1 +++ b/fio.1 @@ -2962,6 +2962,13 @@ reporting if I/O gets backed up on the device side (the coordinated omission problem). Note that this option cannot reliably be used with async IO engines. .SS "I/O rate" .TP +.BI thinkcycles \fR=\fPint +Stall the job for the specified number of cycles after an I/O has completed before +issuing the next. May be used to simulate processing being done by an application. +This is not taken into account for the time to be waited on for \fBthinktime\fR. +Might not have any effect on some platforms, this can be checked by trying a setting +a high enough amount of thinkcycles. +.TP .BI thinktime \fR=\fPtime Stall the job for the specified period of time after an I/O has completed before issuing the next. May be used to simulate processing being done by an application. diff --git a/fio_time.h b/fio_time.h index b20e734c4a..969ad68d5b 100644 --- a/fio_time.h +++ b/fio_time.h @@ -22,6 +22,7 @@ extern uint64_t time_since_now(const struct timespec *); extern uint64_t time_since_genesis(void); extern uint64_t mtime_since_genesis(void); extern uint64_t utime_since_genesis(void); +extern void cycles_spin(unsigned int); extern uint64_t usec_spin(unsigned int); extern uint64_t usec_sleep(struct thread_data *, unsigned long); extern void fill_start_time(struct timespec *); diff --git a/options.c b/options.c index 6b2cb53f77..53df03de9e 100644 --- a/options.c +++ b/options.c @@ -3875,6 +3875,18 @@ struct fio_option fio_options[FIO_MAX_OPTS] = { .category = FIO_OPT_C_IO, .group = FIO_OPT_G_THINKTIME, }, + { + .name = "thinkcycles", + .lname = "Think cycles", + .type = FIO_OPT_INT, + .off1 = offsetof(struct thread_options, thinkcycles), + .help = "Spin for a constant amount of cycles between requests", + .def = "0", + .parent = "thinktime", + .hide = 1, + .category = FIO_OPT_C_IO, + .group = FIO_OPT_G_THINKTIME, + }, { .name = "thinktime_blocks", .lname = "Thinktime blocks", diff --git a/thread_options.h b/thread_options.h index fdde055e7d..24f695fe1a 100644 --- a/thread_options.h +++ b/thread_options.h @@ -309,6 +309,8 @@ struct thread_options { char *exec_prerun; char *exec_postrun; + unsigned int thinkcycles; + unsigned int thinktime; unsigned int thinktime_spin; unsigned int thinktime_blocks; @@ -355,8 +357,8 @@ struct thread_options { unsigned long long latency_target; unsigned long long latency_window; - fio_fp64_t latency_percentile; uint32_t latency_run; + fio_fp64_t latency_percentile; /* * flow support @@ -626,6 +628,8 @@ struct thread_options_pack { uint8_t exec_prerun[FIO_TOP_STR_MAX]; uint8_t exec_postrun[FIO_TOP_STR_MAX]; + uint32_t thinkcycles; + uint32_t thinktime; uint32_t thinktime_spin; uint32_t thinktime_blocks; @@ -671,8 +675,8 @@ struct thread_options_pack { uint64_t latency_target; uint64_t latency_window; uint64_t max_latency[DDIR_RWDIR_CNT]; - fio_fp64_t latency_percentile; uint32_t latency_run; + fio_fp64_t latency_percentile; /* * flow support diff --git a/time.c b/time.c index 7cbab6ff4f..7f85c8de3b 100644 --- a/time.c +++ b/time.c @@ -38,6 +38,17 @@ uint64_t usec_spin(unsigned int usec) return t; } +/* + * busy loop for a fixed amount of cycles + */ +void cycles_spin(unsigned int n) +{ + unsigned long i; + + for (i=0; i < n; i++) + nop; +} + uint64_t usec_sleep(struct thread_data *td, unsigned long usec) { struct timespec req; From 9af6c387191423eefee821e4087987350eaa5e6a Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Mon, 6 Nov 2023 13:41:53 -0500 Subject: [PATCH 0624/1097] client/server: enable per_job_logs option On the client side log files were being overwritten when per_job_logs was set to false because of the flags used when log files were opened. Add per_job_logs to the on-wire protocol so that the client can adjust the flags and open files in append mode when per_job_logs is set to false. Fixes: https://github.com/axboe/fio/issues/1032 Signed-off-by: Vincent Fu --- client.c | 18 ++++++++++++++---- server.c | 1 + server.h | 3 ++- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/client.c b/client.c index 345fa9101b..699a2e5b98 100644 --- a/client.c +++ b/client.c @@ -1452,10 +1452,13 @@ static int fio_client_handle_iolog(struct fio_client *client, if (store_direct) { ssize_t wrote; size_t sz; - int fd; + int fd, flags; - fd = open((const char *) log_pathname, - O_WRONLY | O_CREAT | O_TRUNC, 0644); + if (pdu->per_job_logs) + flags = O_WRONLY | O_CREAT | O_TRUNC; + else + flags = O_WRONLY | O_CREAT | O_APPEND; + fd = open((const char *) log_pathname, flags, 0644); if (fd < 0) { log_err("fio: open log %s: %s\n", log_pathname, strerror(errno)); @@ -1476,7 +1479,13 @@ static int fio_client_handle_iolog(struct fio_client *client, ret = 0; } else { FILE *f; - f = fopen((const char *) log_pathname, "w"); + const char *mode; + + if (pdu->per_job_logs) + mode = "w"; + else + mode = "a"; + f = fopen((const char *) log_pathname, mode); if (!f) { log_err("fio: fopen log %s : %s\n", log_pathname, strerror(errno)); @@ -1695,6 +1704,7 @@ static struct cmd_iolog_pdu *convert_iolog(struct fio_net_cmd *cmd, ret->log_offset = le32_to_cpu(ret->log_offset); ret->log_prio = le32_to_cpu(ret->log_prio); ret->log_hist_coarseness = le32_to_cpu(ret->log_hist_coarseness); + ret->per_job_logs = le32_to_cpu(ret->per_job_logs); if (*store_direct) return ret; diff --git a/server.c b/server.c index 27332e326e..06eac5840f 100644 --- a/server.c +++ b/server.c @@ -2260,6 +2260,7 @@ int fio_send_iolog(struct thread_data *td, struct io_log *log, const char *name) .thread_number = cpu_to_le32(td->thread_number), .log_type = cpu_to_le32(log->log_type), .log_hist_coarseness = cpu_to_le32(log->hist_coarseness), + .per_job_logs = cpu_to_le32(td->o.per_job_logs), }; struct sk_entry *first; struct flist_head *entry; diff --git a/server.h b/server.h index ad7061184b..0eb594ce85 100644 --- a/server.h +++ b/server.h @@ -51,7 +51,7 @@ struct fio_net_cmd_reply { }; enum { - FIO_SERVER_VER = 101, + FIO_SERVER_VER = 102, FIO_SERVER_MAX_FRAGMENT_PDU = 1024, FIO_SERVER_MAX_CMD_MB = 2048, @@ -198,6 +198,7 @@ struct cmd_iolog_pdu { uint32_t log_offset; uint32_t log_prio; uint32_t log_hist_coarseness; + uint32_t per_job_logs; uint8_t name[FIO_NET_NAME_MAX]; struct io_sample samples[0]; }; From 05fce19c7d2668adb38243636a1781c0f8fae523 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Mon, 6 Nov 2023 13:44:48 -0500 Subject: [PATCH 0625/1097] docs: add warning to per_job_logs option Add a warning about the file open mode used when per_job_logs is set to false. Log files are opened in append mode, so if the files already exist, the previous contents will not be overwritten. Signed-off-by: Vincent Fu --- HOWTO.rst | 8 +++++--- fio.1 | 6 ++++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/HOWTO.rst b/HOWTO.rst index 42b2b11974..d173702b1d 100644 --- a/HOWTO.rst +++ b/HOWTO.rst @@ -3968,9 +3968,11 @@ Measurements and reporting .. option:: per_job_logs=bool - If set, this generates bw/clat/iops log with per file private filenames. If - not set, jobs with identical names will share the log filename. Default: - true. + If set to true, fio generates bw/clat/iops logs with per job unique + filenames. If set to false, jobs with identical names will share a log + filename. Note that when this option is set to false log files will be + opened in append mode and if log files already exist the previous + contents will not be overwritten. Default: true. .. option:: group_reporting diff --git a/fio.1 b/fio.1 index d62da6885e..8f659f1dbd 100644 --- a/fio.1 +++ b/fio.1 @@ -3667,8 +3667,10 @@ interpreted in seconds. .SS "Measurements and reporting" .TP .BI per_job_logs \fR=\fPbool -If set, this generates bw/clat/iops log with per file private filenames. If -not set, jobs with identical names will share the log filename. Default: +If set to true, fio generates bw/clat/iops logs with per job unique filenames. +If set to false, jobs with identical names will share a log filename. Note that +when this option is set to false log files will be opened in append mode and if +log files already exist the previous contents will not be overwritten. Default: true. .TP .BI group_reporting From afdde534004397ba1fb00ccc6f5906fa50dd667f Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Tue, 7 Nov 2023 12:18:05 -0500 Subject: [PATCH 0626/1097] t/jobs/t0012.fio: make this job time_based From time to time we see failures (especially on Windows) with this test because one of its jobs does not run long enough. Make the jobs run for this test time_based so that they run for a minimum of 12s. Example error output: DEBUG:root:sample 0: job1 iops=343858.0 job2 iops=1719327.0 job3 iops=3438690.0 job3/job2=2.000 job3/job1=10.000 DEBUG:root:sample 1: job1 iops=682225.0 job2 iops=3411146.0 job3 iops=6822273.0 job3/job2=2.000 job3/job1=10.000 DEBUG:root:sample 2: job1 iops=1019335.0 job2 iops=5096697.0 job3 iops=10193375.0 job3/job2=2.000 job3/job1=10.000 DEBUG:root:sample 3: job1 iops=1367550.0 job2 iops=6837771.0 job3 iops=13675524.0 job3/job2=2.000 job3/job1=10.000 DEBUG:root:sample 4: job1 iops=1704137.0 job2 iops=8520706.0 job3 iops=17041393.0 job3/job2=2.000 job3/job1=10.000 DEBUG:root:sample 5: job1 iops=2056314.0 job2 iops=10281595.0 job3 iops=20563173.0 job3/job2=2.000 job3/job1=10.000 DEBUG:root:sample 6: job1 iops=2399343.0 job2 iops=11996744.0 job3 iops=23993468.0 job3/job2=2.000 job3/job1=10.000 DEBUG:root:Test 12 exception: Traceback (most recent call last): File "D:\a\fio\fio\t\fiotestlib.py", line 465, in run_fio_tests test.check_result() File "D:\a\fio\fio\t\run-fio-tests.py", line 180, in check_result iops3 = iops3 + float(iops_files[2][i].split(',')[1]) Test 12 FAILED: list index out of range t0012.fio IndexError: list index out of range Signed-off-by: Vincent Fu --- t/jobs/t0012.fio | 1 + 1 file changed, 1 insertion(+) diff --git a/t/jobs/t0012.fio b/t/jobs/t0012.fio index d712396691..e01d2b01b6 100644 --- a/t/jobs/t0012.fio +++ b/t/jobs/t0012.fio @@ -14,6 +14,7 @@ flow_sleep=100 thread log_avg_msec=1000 write_iops_log=t0012.fio +time_based [flow1] flow=1 From 63f210e8c80bf9576264aca81780846e3b31b1a8 Mon Sep 17 00:00:00 2001 From: "Simon A. F. Lund" Date: Mon, 20 Nov 2023 09:20:42 +0100 Subject: [PATCH 0627/1097] engines/xnvme: only include entry-header ('libxnvme.h') This changes how the xNVMe fio io-engine consumes the xNVMe library by only including the library-entry header "libxnvme.h". From version 0.7.0 the xNVMe API headers are refactored to drop header guards on the individual headers and abide by the idiom of "headers must not include other headers". The exception is the library-entry header "libxnvme.h". The library-entry-header includes all headers provided with xNVMe, which is a convenient approach to consuming the library. One where, in case the API namespace grows or shrinks, then the xNVMe fio io-engine need not change how it includes xNVMe. However, since fio has consumed the main-entry header and individual headers, xNVMe has held back on removing the guards on _nvm, _zns, and _spec to avoid breaking the xNVMe fio engine. They will eventually be deprecated. Thus, this change to consume xNVMe in the manner intended from version v0.7.0 and onwards. Signed-off-by: Simon A. F. Lund --- engines/xnvme.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/engines/xnvme.c b/engines/xnvme.c index b782401363..2a0b3520bd 100644 --- a/engines/xnvme.c +++ b/engines/xnvme.c @@ -10,10 +10,6 @@ #include #include #include -#include -#include -#include -#include #include "fio.h" #include "zbd_types.h" #include "fdp.h" From 0cfea592fedf0011e695a604a6961e9cbc1fe9b6 Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Thu, 7 Dec 2023 10:22:19 -0800 Subject: [PATCH 0628/1097] Fall back to F_SET_RW_HINT if F_SET_FILE_RW_HINT is not supported Linux kernel commit 7b12e49669c9 ("fs: remove fs.f_write_hint") removed F_SET_FILE_RW_HINT support from the Linux kernel. Hence fall back to F_SET_RW_HINT if F_SET_FILE_RW_HINT is not supported. Signed-off-by: Bart Van Assche --- ioengines.c | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/ioengines.c b/ioengines.c index 361727250e..87cc2286e0 100644 --- a/ioengines.c +++ b/ioengines.c @@ -590,19 +590,21 @@ int td_io_open_file(struct thread_data *td, struct fio_file *f) if (fio_option_is_set(&td->o, write_hint) && (f->filetype == FIO_TYPE_BLOCK || f->filetype == FIO_TYPE_FILE)) { uint64_t hint = td->o.write_hint; - int cmd; + int res; /* - * For direct IO, we just need/want to set the hint on - * the file descriptor. For buffered IO, we need to set - * it on the inode. + * For direct IO, set the hint on the file descriptor if that is + * supported. Otherwise set it on the inode. For buffered IO, we + * need to set it on the inode. */ - if (td->o.odirect) - cmd = F_SET_FILE_RW_HINT; - else - cmd = F_SET_RW_HINT; - - if (fcntl(f->fd, cmd, &hint) < 0) { + if (td->o.odirect) { + res = fcntl(f->fd, F_SET_FILE_RW_HINT, &hint); + if (res < 0) + res = fcntl(f->fd, F_SET_RW_HINT, &hint); + } else { + res = fcntl(f->fd, F_SET_RW_HINT, &hint); + } + if (res < 0) { td_verror(td, errno, "fcntl write hint"); goto err; } From 3cb50530f4a029992a4b2e2c435481f4007c5cb7 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Mon, 11 Dec 2023 16:25:33 +0000 Subject: [PATCH 0629/1097] engines/io_uring_cmd: friendlier bad bs error msg It can tricky to specify block sizes for devices formatted in extended LBA mode because the block sizes are no longer powers of two. Add a suggested block size in the error message when we abort a job because the specified block size is not a multiple of the extended LBA size. Signed-off-by: Vincent Fu --- engines/io_uring.c | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/engines/io_uring.c b/engines/io_uring.c index 38c36fdca2..5ae3135bc7 100644 --- a/engines/io_uring.c +++ b/engines/io_uring.c @@ -1279,14 +1279,21 @@ static int fio_ioring_cmd_open_file(struct thread_data *td, struct fio_file *f) lba_size = data->lba_ext ? data->lba_ext : data->lba_size; for_each_rw_ddir(ddir) { - if (td->o.min_bs[ddir] % lba_size || - td->o.max_bs[ddir] % lba_size) { - if (data->lba_ext) - log_err("%s: block size must be a multiple of (LBA data size + Metadata size)\n", - f->file_name); - else + if (td->o.min_bs[ddir] % lba_size || td->o.max_bs[ddir] % lba_size) { + if (data->lba_ext) { + log_err("%s: block size must be a multiple of %u " + "(LBA data size + Metadata size)\n", f->file_name, lba_size); + if (td->o.min_bs[ddir] == td->o.max_bs[ddir] && + !(td->o.min_bs[ddir] % data->lba_size)) { + /* fixed block size is actually a multiple of LBA data size */ + unsigned long long suggestion = lba_size * + (td->o.min_bs[ddir] / data->lba_size); + log_err("Did you mean to use a block size of %llu?\n", suggestion); + } + } else { log_err("%s: block size must be a multiple of LBA data size\n", f->file_name); + } td_verror(td, EINVAL, "fio_ioring_cmd_open_file"); return 1; } From f53eaac02ec46bdf7f87058f30667be80975caf6 Mon Sep 17 00:00:00 2001 From: Ankit Kumar Date: Tue, 12 Dec 2023 20:17:18 +0530 Subject: [PATCH 0630/1097] engines/io_uring_cmd: skip pi verify checks for error cases If any error is observed for read requests, skip all end to end data protection checks. Signed-off-by: Ankit Kumar Link: https://lore.kernel.org/r/20231212144718.568406-1-ankit.kumar@samsung.com Signed-off-by: Vincent Fu --- engines/io_uring.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/engines/io_uring.c b/engines/io_uring.c index 5ae3135bc7..c0cb5a78f7 100644 --- a/engines/io_uring.c +++ b/engines/io_uring.c @@ -468,10 +468,12 @@ static struct io_u *fio_ioring_cmd_event(struct thread_data *td, int event) cqe = &ld->cq_ring.cqes[index]; io_u = (struct io_u *) (uintptr_t) cqe->user_data; - if (cqe->res != 0) + if (cqe->res != 0) { io_u->error = -cqe->res; - else + return io_u; + } else { io_u->error = 0; + } if (o->cmd_type == FIO_URING_CMD_NVME) { data = FILE_ENG_DATA(io_u->file); From a8ce8eb3651b59205b647702be8c9237e9542401 Mon Sep 17 00:00:00 2001 From: Mateusz Piotrowski <0mp@FreeBSD.org> Date: Thu, 14 Dec 2023 15:48:04 +0100 Subject: [PATCH 0631/1097] doc: Reference geom(4) for FreeBSD users On FreeBSD, write access to Rank 1 geom providers is disabled by default. However, it can be enabled with the `kern.geom.debugflags` sysctl as documented in geom(4). Point users to that manual page for smoother experience. Signed-off-by: Mateusz Piotrowski 0mp@FreeBSD.org --- HOWTO.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HOWTO.rst b/HOWTO.rst index d173702b1d..847c035637 100644 --- a/HOWTO.rst +++ b/HOWTO.rst @@ -801,7 +801,7 @@ Target file/device On Windows, disk devices are accessed as :file:`\\\\.\\PhysicalDrive0` for the first device, :file:`\\\\.\\PhysicalDrive1` for the second etc. - Note: Windows and FreeBSD prevent write access to areas + Note: Windows and FreeBSD (refer to geom(4)) prevent write access to areas of the disk containing in-use data (e.g. filesystems). The filename "`-`" is a reserved name, meaning *stdin* or *stdout*. Which From 9e66b0606cad74cb98dc44cb91903432171585a9 Mon Sep 17 00:00:00 2001 From: Pavel Reichl Date: Thu, 14 Dec 2023 22:10:25 +0100 Subject: [PATCH 0632/1097] engines/http: Fix memory leak Found by Red Hat's OpenScanHub: fio-3.35/engines/http.c:253: leaked_storage: Variable r going out of scope leaks the storage it points to. Signed-off-by: Pavel Reichl --- engines/http.c | 1 + 1 file changed, 1 insertion(+) diff --git a/engines/http.c b/engines/http.c index 56dc7d1b14..83cfe8bb71 100644 --- a/engines/http.c +++ b/engines/http.c @@ -250,6 +250,7 @@ static char *_aws_uriencode(const char *uri) for (i = 0; (c = uri[i]); i++) { if (n > bufsize-5) { log_err("encoding the URL failed\n"); + free(r); return NULL; } From 922ce62e387ee4158bf61e08c25ef1ce8b359600 Mon Sep 17 00:00:00 2001 From: Pavel Reichl Date: Fri, 15 Dec 2023 18:43:16 +0100 Subject: [PATCH 0633/1097] engines/rdma: remove dead code The issues was found by Red Hat's OpenScanHub: fio-3.35/engines/rdma.c:279:3: warning[deadcode.DeadStores]: Value stored to 'ret' is never read Signed-off-by: Pavel Reichl --- engines/rdma.c | 1 - 1 file changed, 1 deletion(-) diff --git a/engines/rdma.c b/engines/rdma.c index ebdbcb1c80..07336f3b88 100644 --- a/engines/rdma.c +++ b/engines/rdma.c @@ -276,7 +276,6 @@ static int cq_event_handler(struct thread_data *td, enum ibv_wc_opcode opcode) int i; while ((ret = ibv_poll_cq(rd->cq, 1, &wc)) == 1) { - ret = 0; compevnum++; if (wc.status) { From 09f4a16838b6c81060660514b9211e751984c6d8 Mon Sep 17 00:00:00 2001 From: Pavel Reichl Date: Fri, 15 Dec 2023 19:04:42 +0100 Subject: [PATCH 0634/1097] client/server: remove dead code The issue was found by Red Hat's OpenScanHub: fio-3.35/server.c:1884:3: warning[deadcode.DeadStores]: Value stored to 'extended_buf_wp' is never read Signed-off-by: Pavel Reichl --- server.c | 1 - 1 file changed, 1 deletion(-) diff --git a/server.c b/server.c index 06eac5840f..b9f0e2ac3c 100644 --- a/server.c +++ b/server.c @@ -1883,7 +1883,6 @@ void fio_server_send_ts(struct thread_stat *ts, struct group_run_stats *rs) offset = (char *)extended_buf_wp - (char *)extended_buf; ptr->ts.ss_bw_data_offset = cpu_to_le64(offset); - extended_buf_wp = ss_bw + (int) ts->ss_dur; } fio_net_queue_cmd(FIO_NET_CMD_TS, extended_buf, extended_buf_size, NULL, SK_F_COPY); From 46a34f1e4fbee9e4a805bbf5d85646c12a4391dc Mon Sep 17 00:00:00 2001 From: Pavel Reichl Date: Fri, 15 Dec 2023 19:20:33 +0100 Subject: [PATCH 0635/1097] engines/http: Drop unused varible Signed-off-by: Pavel Reichl --- engines/http.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/engines/http.c b/engines/http.c index 83cfe8bb71..001479b055 100644 --- a/engines/http.c +++ b/engines/http.c @@ -640,7 +640,6 @@ static enum fio_q_status fio_http_queue(struct thread_data *td, char url[1024]; long status; CURLcode res; - int r = -1; fio_ro_check(td, io_u); memset(&_curl_stream, 0, sizeof(_curl_stream)); @@ -712,7 +711,7 @@ static enum fio_q_status fio_http_queue(struct thread_data *td, log_err("WARNING: Only DDIR_READ/DDIR_WRITE/DDIR_TRIM are supported!\n"); err: - io_u->error = r; + io_u->error = -1; td_verror(td, io_u->error, "transfer"); out: curl_slist_free_all(slist); From c77fe6859b6ef937a6ca900c1fab009175d721f8 Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Fri, 15 Dec 2023 13:17:13 -0700 Subject: [PATCH 0636/1097] engines/http: use proper error value The engine sets -1 for some odd reason, where ->error fields are supposed to be a positive value. Set it to EIO. Signed-off-by: Jens Axboe --- engines/http.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engines/http.c b/engines/http.c index 001479b055..99f4e119a4 100644 --- a/engines/http.c +++ b/engines/http.c @@ -711,7 +711,7 @@ static enum fio_q_status fio_http_queue(struct thread_data *td, log_err("WARNING: Only DDIR_READ/DDIR_WRITE/DDIR_TRIM are supported!\n"); err: - io_u->error = -1; + io_u->error = EIO; td_verror(td, io_u->error, "transfer"); out: curl_slist_free_all(slist); From cbbfe5a9c91895b382aff061bff658c211acd495 Mon Sep 17 00:00:00 2001 From: Shin'ichiro Kawasaki Date: Wed, 20 Dec 2023 09:58:45 +0900 Subject: [PATCH 0637/1097] zbd: avoid write with rwmixwrite=0 option Since the commit fb0259fb276a ("zbd: Ensure first I/O is write for random read/write to sequential zones"), fio issues write as the first I/O when zonemode=zbd and rw=randrw options are specified. However, fio issues the first write even when rwmixwrite=0 option is specified. Users do not expect such write and it confuses the users. To avoid the confusion, suppress the write by referring td->o.rwmix[DDIR_WRITE]. Fixes: fb0259fb276a ("zbd: Ensure first I/O is write for random read/write to sequential zones") Signed-off-by: Shin'ichiro Kawasaki Reviewed-by: Damien Le Moal Link: https://lore.kernel.org/r/20231220005846.1371456-2-shinichiro.kawasaki@wdc.com Signed-off-by: Jens Axboe --- zbd.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/zbd.c b/zbd.c index c4f7b12f75..61b5b688ca 100644 --- a/zbd.c +++ b/zbd.c @@ -1876,7 +1876,8 @@ enum fio_ddir zbd_adjust_ddir(struct thread_data *td, struct io_u *io_u, if (ddir != DDIR_READ || !td_rw(td)) return ddir; - if (io_u->file->last_start[DDIR_WRITE] != -1ULL || td->o.read_beyond_wp) + if (io_u->file->last_start[DDIR_WRITE] != -1ULL || + td->o.read_beyond_wp || td->o.rwmix[DDIR_WRITE] == 0) return DDIR_READ; return DDIR_WRITE; From be943a3ef5d94d8a9fefa11dc004789f66beb8e6 Mon Sep 17 00:00:00 2001 From: Shin'ichiro Kawasaki Date: Wed, 20 Dec 2023 09:58:46 +0900 Subject: [PATCH 0638/1097] t/zbd: add test case to confirm no write with rwmixwrite=0 option The previous commit fixed the issue of the unexpected write with options zonemode=zbd, rw=randrw and rwmixwrite=0. Add a test to confirm the fix. Signed-off-by: Shin'ichiro Kawasaki Reviewed-by: Damien Le Moal Link: https://lore.kernel.org/r/20231220005846.1371456-3-shinichiro.kawasaki@wdc.com Signed-off-by: Jens Axboe --- t/zbd/test-zbd-support | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/t/zbd/test-zbd-support b/t/zbd/test-zbd-support index 2f15a19135..532860ebc8 100755 --- a/t/zbd/test-zbd-support +++ b/t/zbd/test-zbd-support @@ -1561,6 +1561,29 @@ test67() { grep -q 'Exceeded max_active_zones limit' "${logfile}.${test_number}" } +# Test rw=randrw and rwmixwrite=0 options do not issue write I/O unit +test68() { + local off size + + require_zbd || return "$SKIP_TESTCASE" + + reset_zone "${dev}" -1 + + # Write some data as preparation + off=$((first_sequential_zone_sector * 512)) + size=$min_seq_write_size + run_one_fio_job "$(ioengine "psync")" --rw=write --offset="$off" \ + --io_size="$size" --zonemode=strided \ + --zonesize="$zone_size" --zonerange="$zone_size" \ + >> "${logfile}.${test_number}" 2>&1 || return $? + # Run random mixed read and write specifying zero write ratio + run_fio_on_seq "$(ioengine "psync")" --rw=randrw --rwmixwrite=0 \ + --time_based --runtime=1s \ + >> "${logfile}.${test_number}" 2>&1 || return $? + # "WRITE:" shall be recoreded only once for the preparation + [[ $(grep -c "WRITE:" "${logfile}.${test_number}") == 1 ]] +} + SECONDS=0 tests=() dynamic_analyzer=() From 4883f8f65180ef4e22d6caf4abaf6028faa1af61 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Tue, 26 Dec 2023 21:55:59 -0500 Subject: [PATCH 0639/1097] t/nvmept: call parent class check_result() Make sure we call the parent class' check_result() method to check the return code, stderr output, etc. Signed-off-by: Vincent Fu --- t/nvmept.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/t/nvmept.py b/t/nvmept.py index c08fb35041..1ade64dc57 100755 --- a/t/nvmept.py +++ b/t/nvmept.py @@ -55,6 +55,8 @@ def setup(self, parameters): def check_result(self): + super().check_result() + if 'rw' not in self.fio_opts: return From 6f2b92cfba7f5036a66575da61044b3af25cb1d3 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Thu, 28 Dec 2023 22:10:43 -0500 Subject: [PATCH 0640/1097] t/random_seed: call parent class check_result() Make sure we call the parent class' check_result() method to check the return code, stderr output, etc. Signed-off-by: Vincent Fu --- t/random_seed.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/t/random_seed.py b/t/random_seed.py index 02187046e5..82beca65b5 100755 --- a/t/random_seed.py +++ b/t/random_seed.py @@ -91,6 +91,10 @@ class TestRR(FioRandTest): def check_result(self): """Check output for allrandrepeat=1.""" + super().check_result() + if not self.passed: + return + opt = 'randrepeat' if 'randrepeat' in self.fio_opts else 'allrandrepeat' rr = self.fio_opts[opt] rand_seeds = self.get_rand_seeds() @@ -131,6 +135,10 @@ class TestRS(FioRandTest): def check_result(self): """Check output for randseed=something.""" + super().check_result() + if not self.passed: + return + rand_seeds = self.get_rand_seeds() randseed = self.fio_opts['randseed'] From f4c55efe562b1dacc4c260c25300fe29081bba67 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Thu, 28 Dec 2023 22:12:20 -0500 Subject: [PATCH 0641/1097] t/strided: call parent class check_result() Make sure we call the parent class' check_result() method to check the return code, stderr output, etc. Signed-off-by: Vincent Fu --- t/strided.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/t/strided.py b/t/strided.py index b7655e1e92..b8396aef69 100755 --- a/t/strided.py +++ b/t/strided.py @@ -71,6 +71,10 @@ def setup(self, parameters): super().setup(fio_args) def check_result(self): + super().check_result() + if not self.passed: + return + zonestart = 0 if 'offset' not in self.fio_opts else self.fio_opts['offset'] iospersize = self.fio_opts['zonesize'] / self.fio_opts['bs'] iosperrange = self.fio_opts['zonerange'] / self.fio_opts['bs'] From 06c40418f97811092c0aece1760487400bcdd506 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Thu, 28 Dec 2023 22:12:47 -0500 Subject: [PATCH 0642/1097] t/strided: check_result() has no return value check_result() erroneously returned True or False. Drop the return value to be consistent with the parent and related classes. Signed-off-by: Vincent Fu --- t/strided.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/t/strided.py b/t/strided.py index b8396aef69..75c429e454 100755 --- a/t/strided.py +++ b/t/strided.py @@ -99,7 +99,7 @@ def check_result(self): offset = int(tokens[4]) if offset < zonestart or offset >= zonestart + self.fio_opts['zonerange']: print(f"Offset {offset} outside of zone starting at {zonestart}") - return False + return # skip next section if norandommap is enabled with no # random_generator or with a random_generator != lfsr @@ -117,17 +117,15 @@ def check_result(self): block = (offset - zonestart) / self.fio_opts['bs'] if block in zoneset: print(f"Offset {offset} in zone already touched") - return False + return zoneset.add(block) if iosperzone % iosperrange == 0: if len(zoneset) != iosperrange: print(f"Expected {iosperrange} blocks in zone but only saw {len(zoneset)}") - return False + return zoneset = set() - return True - TEST_LIST = [ # randommap enabled { From 96d289092af6d7391a263d97fcb586f7afda62c5 Mon Sep 17 00:00:00 2001 From: Mateusz Piotrowski <0mp@FreeBSD.org> Date: Sun, 14 Jan 2024 18:16:19 +0100 Subject: [PATCH 0643/1097] doc: group_reporting: Fix indentation and syntax Use tabs consistently for the paragraphs describing the group_reporting option. Also, make sure that there is no space between ":option:" and "`group_reporting`". Signed-off-by: Mateusz Piotrowski <0mp@FreeBSD.org> --- HOWTO.rst | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/HOWTO.rst b/HOWTO.rst index 847c035637..d0ba802173 100644 --- a/HOWTO.rst +++ b/HOWTO.rst @@ -3984,12 +3984,12 @@ Measurements and reporting same reporting group, unless if separated by a :option:`stonewall`, or by using :option:`new_group`. - NOTE: When :option: `group_reporting` is used along with `json` output, - there are certain per-job properties which can be different between jobs - but do not have a natural group-level equivalent. Examples include - `kb_base`, `unit_base`, `sig_figs`, `thread_number`, `pid`, and - `job_start`. For these properties, the values for the first job are - recorded for the group. + NOTE: When :option:`group_reporting` is used along with `json` output, + there are certain per-job properties which can be different between jobs + but do not have a natural group-level equivalent. Examples include + `kb_base`, `unit_base`, `sig_figs`, `thread_number`, `pid`, and + `job_start`. For these properties, the values for the first job are + recorded for the group. .. option:: new_group From 9eefdcc1dd820a936684168468fa9c81960ea461 Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Wed, 17 Jan 2024 09:11:15 -0700 Subject: [PATCH 0644/1097] configure: enable NVME_URING_CMD checking for Android Link: https://github.com/axboe/fio/issues/1701 Signed-off-by: Jens Axboe --- configure | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure b/configure index 420d97dbe6..f86fcf77df 100755 --- a/configure +++ b/configure @@ -2656,7 +2656,7 @@ if test "$libzbc" != "no" ; then fi print_config "libzbc engine" "$libzbc" -if test "$targetos" = "Linux" ; then +if test "$targetos" = "Linux" || test "$targetos" = "Android"; then ########################################## # Check NVME_URING_CMD support cat > $TMPC << EOF From aa84b5ba581add84ce6e73b20ca0fbd04f6058c8 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Thu, 18 Jan 2024 12:20:29 -0500 Subject: [PATCH 0645/1097] ci: stop hard coding number of jobs for make GitHub increased the number of CPUs in its GitHub-hosted runners from two to four for Linux and Windows. macOS remains at two CPUs. Stop hard-coding the number of CPUs when we build fio and detect the number at runtime. https://github.blog/2024-01-17-github-hosted-runners-double-the-power-for-open-source/ Signed-off-by: Vincent Fu --- ci/actions-build.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ci/actions-build.sh b/ci/actions-build.sh index 31d3446c07..47d4f044ec 100755 --- a/ci/actions-build.sh +++ b/ci/actions-build.sh @@ -61,7 +61,9 @@ main() { configure_flags+=(--extra-cflags="${extra_cflags}") ./configure "${configure_flags[@]}" - make -j 2 + make -j "$(nproc 2>/dev/null || sysctl -n hw.logicalcpu)" +# macOS does not have nproc, so we have to use sysctl to obtain the number of +# logical CPUs. } main From 8b3190c3ea38af87778a68c576947f8797215d33 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Fri, 19 Jan 2024 17:30:57 +0000 Subject: [PATCH 0646/1097] filesetup: clear O_RDWR flag for verify_only write workloads If verify_only is set we don't need to open the file with the O_RDWR flagi for write workloads. So we should clear this flag. This will help when the file is on a read-only file system. Fixes: https://github.com/axboe/fio/issues/1681 Signed-off-by: Vincent Fu --- filesetup.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/filesetup.c b/filesetup.c index 816d10816e..2d277a6428 100644 --- a/filesetup.c +++ b/filesetup.c @@ -749,6 +749,11 @@ int generic_open_file(struct thread_data *td, struct fio_file *f) if (!read_only) flags |= O_RDWR; + if (td->o.verify_only) { + flags &= ~O_RDWR; + flags |= O_RDONLY; + } + if (f->filetype == FIO_TYPE_FILE && td->o.allow_create) flags |= O_CREAT; From 1ee0469f6d180a98d31196bea787f37269ff9cdd Mon Sep 17 00:00:00 2001 From: Chris Packham Date: Wed, 24 Jan 2024 09:26:36 +1300 Subject: [PATCH 0647/1097] configure: Don't use cross_prefix when invoking pkg-config pkg-config doesn't need to have a cross prefixed version. It can be pointed at alternate sysroots with environment variables like PKG_CONFIG_SYSROOT_DIR. The configure script was already inconsistent with using `pkg-config` in some places and `${cross_prefix}pkg-config` in others. Make check_min_lib_version() and the gtk checks consistent with the rest by dropping the `${cross_prefix}` usage with pkg-config. Signed-off-by: Chris Packham Link: https://lore.kernel.org/r/20240123202636.179467-1-chris.packham@alliedtelesis.co.nz Signed-off-by: Jens Axboe --- configure | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/configure b/configure index f86fcf77df..dea8d07d11 100755 --- a/configure +++ b/configure @@ -155,11 +155,11 @@ output_sym() { check_min_lib_version() { _feature=$3 - if "${cross_prefix}"pkg-config --atleast-version="$2" "$1" > /dev/null 2>&1; then + if pkg-config --atleast-version="$2" "$1" > /dev/null 2>&1; then return 0 fi : "${_feature:=${1}}" - if "${cross_prefix}"pkg-config --version > /dev/null 2>&1; then + if pkg-config --version > /dev/null 2>&1; then if test "$(eval echo \"\$$_feature\")" = "yes" ; then feature_not_found "$_feature" "$1 >= $2" fi @@ -1631,14 +1631,14 @@ int main(void) return GTK_CHECK_VERSION(2, 18, 0) ? 0 : 1; /* 0 on success */ } EOF -GTK_CFLAGS=$(${cross_prefix}pkg-config --cflags gtk+-2.0 gthread-2.0) +GTK_CFLAGS=$(pkg-config --cflags gtk+-2.0 gthread-2.0) ORG_LDFLAGS=$LDFLAGS LDFLAGS=$(echo $LDFLAGS | sed s/"-static"//g) if test "$?" != "0" ; then echo "configure: gtk and gthread not found" exit 1 fi -GTK_LIBS=$(${cross_prefix}pkg-config --libs gtk+-2.0 gthread-2.0) +GTK_LIBS=$(pkg-config --libs gtk+-2.0 gthread-2.0) if test "$?" != "0" ; then echo "configure: gtk and gthread not found" exit 1 From 7f250f7514bacef1a3cea24a22ecce8bd30378bd Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Wed, 24 Jan 2024 18:58:11 +0000 Subject: [PATCH 0648/1097] ci: resolve GitHub Actions Node.js warnings Switch from actions/checkout@v3 to v4 and from actions/upload-artifacts@v3 to v4. This resolves the below warnings from GitHub Actions: Node.js 16 actions are deprecated. Please update the following actions to use Node.js 20: actions/checkout@v3. For more information see: https://github.blog/changelog/2023-09-22-github-actions-transitioning-from-node-16-to-node-20/. Node.js 16 actions are deprecated. Please update the following actions to use Node.js 20: actions/upload-artifact@v3. For more information see: https://github.blog/changelog/2023-09-22-github-actions-transitioning-from-node-16-to-node-20/ Signed-off-by: Vincent Fu --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b8000024fb..e53082c302 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -64,7 +64,7 @@ jobs: if: ${{ contains( matrix.build, 'windows' ) }} run: git config --global core.autocrlf input - name: Checkout repo - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install Cygwin toolchain (Windows) if: ${{ startsWith(matrix.build, 'windows-cygwin') }} uses: cygwin/cygwin-install-action@master @@ -110,7 +110,7 @@ jobs: - name: Upload installer as artifact (Windows) if: ${{ contains( matrix.build, 'windows' ) }} - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: ${{ matrix.build }}-installer path: os\windows\*.msi From 0e14633ce541b7c5f29155a7edee208fb79311d0 Mon Sep 17 00:00:00 2001 From: Ankit Kumar Date: Thu, 25 Jan 2024 16:31:23 +0530 Subject: [PATCH 0649/1097] stat: log out both average and max over the window Add option log_window_value alias of log_max_value which reports average, max or both the values. Retain backward compatibility by allowing =0 and =1 values to specify avg and max values respectively. There is no change to existing log formats while reporting only average or max values. Signed-off-by: Ankit Kumar Link: https://lore.kernel.org/r/20240125110124.55137-2-ankit.kumar@samsung.com Signed-off-by: Vincent Fu --- client.c | 6 +++-- iolog.c | 74 ++++++++++++++++++++++++++++++++++++++++--------------- iolog.h | 21 +++++++++++++--- options.c | 34 +++++++++++++++++++++---- server.c | 6 +++-- stat.c | 32 ++++++++++++++---------- 6 files changed, 128 insertions(+), 45 deletions(-) diff --git a/client.c b/client.c index 699a2e5b98..4cb7dffede 100644 --- a/client.c +++ b/client.c @@ -1718,8 +1718,10 @@ static struct cmd_iolog_pdu *convert_iolog(struct fio_net_cmd *cmd, s = (struct io_sample *)((char *)s + sizeof(struct io_u_plat_entry) * i); s->time = le64_to_cpu(s->time); - if (ret->log_type != IO_LOG_TYPE_HIST) - s->data.val = le64_to_cpu(s->data.val); + if (ret->log_type != IO_LOG_TYPE_HIST) { + s->data.val.val0 = le64_to_cpu(s->data.val.val0); + s->data.val.val1 = le64_to_cpu(s->data.val.val1); + } s->__ddir = __le32_to_cpu(s->__ddir); s->bs = le64_to_cpu(s->bs); s->priority = le16_to_cpu(s->priority); diff --git a/iolog.c b/iolog.c index 5213c60f1a..b4c7a8f131 100644 --- a/iolog.c +++ b/iolog.c @@ -862,6 +862,8 @@ void setup_log(struct io_log **log, struct log_params *p, l->log_ddir_mask = LOG_OFFSET_SAMPLE_BIT; if (l->log_prio) l->log_ddir_mask |= LOG_PRIO_SAMPLE_BIT; + if (l->td->o.log_max == IO_LOG_SAMPLE_BOTH) + l->log_ddir_mask |= LOG_AVG_MAX_SAMPLE_BIT; INIT_FLIST_HEAD(&l->chunk_list); @@ -988,7 +990,7 @@ static void flush_hist_samples(FILE *f, int hist_coarseness, void *samples, void flush_samples(FILE *f, void *samples, uint64_t sample_size) { struct io_sample *s; - int log_offset, log_prio; + int log_offset, log_prio, log_avg_max; uint64_t i, nr_samples; unsigned int prio_val; const char *fmt; @@ -999,17 +1001,32 @@ void flush_samples(FILE *f, void *samples, uint64_t sample_size) s = __get_sample(samples, 0, 0); log_offset = (s->__ddir & LOG_OFFSET_SAMPLE_BIT) != 0; log_prio = (s->__ddir & LOG_PRIO_SAMPLE_BIT) != 0; + log_avg_max = (s->__ddir & LOG_AVG_MAX_SAMPLE_BIT) != 0; if (log_offset) { - if (log_prio) - fmt = "%" PRIu64 ", %" PRId64 ", %u, %llu, %llu, 0x%04x\n"; - else - fmt = "%" PRIu64 ", %" PRId64 ", %u, %llu, %llu, %u\n"; + if (log_prio) { + if (log_avg_max) + fmt = "%" PRIu64 ", %" PRId64 ", %" PRId64 ", %u, %llu, %llu, 0x%04x\n"; + else + fmt = "%" PRIu64 ", %" PRId64 ", %u, %llu, %llu, 0x%04x\n"; + } else { + if (log_avg_max) + fmt = "%" PRIu64 ", %" PRId64 ", %" PRId64 ", %u, %llu, %llu, %u\n"; + else + fmt = "%" PRIu64 ", %" PRId64 ", %u, %llu, %llu, %u\n"; + } } else { - if (log_prio) - fmt = "%" PRIu64 ", %" PRId64 ", %u, %llu, 0x%04x\n"; - else - fmt = "%" PRIu64 ", %" PRId64 ", %u, %llu, %u\n"; + if (log_prio) { + if (log_avg_max) + fmt = "%" PRIu64 ", %" PRId64 ", %" PRId64 ", %u, %llu, 0x%04x\n"; + else + fmt = "%" PRIu64 ", %" PRId64 ", %u, %llu, 0x%04x\n"; + } else { + if (log_avg_max) + fmt = "%" PRIu64 ", %" PRId64 ", %" PRId64 ", %u, %llu, %u\n"; + else + fmt = "%" PRIu64 ", %" PRId64 ", %u, %llu, %u\n"; + } } nr_samples = sample_size / __log_entry_sz(log_offset); @@ -1023,20 +1040,37 @@ void flush_samples(FILE *f, void *samples, uint64_t sample_size) prio_val = ioprio_value_is_class_rt(s->priority); if (!log_offset) { - fprintf(f, fmt, - s->time, - s->data.val, - io_sample_ddir(s), (unsigned long long) s->bs, - prio_val); + if (log_avg_max) + fprintf(f, fmt, + s->time, + s->data.val.val0, + s->data.val.val1, + io_sample_ddir(s), (unsigned long long) s->bs, + prio_val); + else + fprintf(f, fmt, + s->time, + s->data.val.val0, + io_sample_ddir(s), (unsigned long long) s->bs, + prio_val); } else { struct io_sample_offset *so = (void *) s; - fprintf(f, fmt, - s->time, - s->data.val, - io_sample_ddir(s), (unsigned long long) s->bs, - (unsigned long long) so->offset, - prio_val); + if (log_avg_max) + fprintf(f, fmt, + s->time, + s->data.val.val0, + s->data.val.val1, + io_sample_ddir(s), (unsigned long long) s->bs, + (unsigned long long) so->offset, + prio_val); + else + fprintf(f, fmt, + s->time, + s->data.val.val0, + io_sample_ddir(s), (unsigned long long) s->bs, + (unsigned long long) so->offset, + prio_val); } } } diff --git a/iolog.h b/iolog.h index 62cbd1b02f..26dd5cca81 100644 --- a/iolog.h +++ b/iolog.h @@ -26,13 +26,23 @@ struct io_hist { struct flist_head list; }; +enum { + IO_LOG_SAMPLE_AVG = 0, + IO_LOG_SAMPLE_MAX, + IO_LOG_SAMPLE_BOTH, +}; + +struct io_sample_value { + uint64_t val0; + uint64_t val1; +}; union io_sample_data { - uint64_t val; + struct io_sample_value val; struct io_u_plat_entry *plat_entry; }; -#define sample_val(value) ((union io_sample_data) { .val = value }) +#define sample_val(value) ((union io_sample_data) { .val.val0 = value }) #define sample_plat(plat) ((union io_sample_data) { .plat_entry = plat }) /* @@ -154,8 +164,13 @@ struct io_log { * If the bit following the upper bit is set, then we have the priority */ #define LOG_PRIO_SAMPLE_BIT 0x40000000U +/* + * If the bit following prioity sample vit is set, we report both avg and max + */ +#define LOG_AVG_MAX_SAMPLE_BIT 0x20000000U -#define LOG_SAMPLE_BITS (LOG_OFFSET_SAMPLE_BIT | LOG_PRIO_SAMPLE_BIT) +#define LOG_SAMPLE_BITS (LOG_OFFSET_SAMPLE_BIT | LOG_PRIO_SAMPLE_BIT |\ + LOG_AVG_MAX_SAMPLE_BIT) #define io_sample_ddir(io) ((io)->__ddir & ~LOG_SAMPLE_BITS) static inline void io_sample_set_ddir(struct io_log *log, diff --git a/options.c b/options.c index 53df03de9e..1da4de7815 100644 --- a/options.c +++ b/options.c @@ -4540,14 +4540,38 @@ struct fio_option fio_options[FIO_MAX_OPTS] = { .group = FIO_OPT_G_INVALID, }, { - .name = "log_max_value", - .lname = "Log maximum instead of average", - .type = FIO_OPT_BOOL, + .name = "log_window_value", + .alias = "log_max_value", + .lname = "Log maximum, average or both values", + .type = FIO_OPT_STR, .off1 = offsetof(struct thread_options, log_max), - .help = "Log max sample in a window instead of average", - .def = "0", + .help = "Log max, average or both sample in a window", + .def = "avg", .category = FIO_OPT_C_LOG, .group = FIO_OPT_G_INVALID, + .posval = { + { .ival = "avg", + .oval = IO_LOG_SAMPLE_AVG, + .help = "Log average value over the window", + }, + { .ival = "max", + .oval = IO_LOG_SAMPLE_MAX, + .help = "Log maximum value in the window", + }, + { .ival = "both", + .oval = IO_LOG_SAMPLE_BOTH, + .help = "Log both average and maximum values over the window" + }, + /* Compatibility with former boolean values */ + { .ival = "0", + .oval = IO_LOG_SAMPLE_AVG, + .help = "Alias for 'avg'", + }, + { .ival = "1", + .oval = IO_LOG_SAMPLE_MAX, + .help = "Alias for 'max'", + }, + }, }, { .name = "log_offset", diff --git a/server.c b/server.c index b9f0e2ac3c..afaeb3482b 100644 --- a/server.c +++ b/server.c @@ -2288,8 +2288,10 @@ int fio_send_iolog(struct thread_data *td, struct io_log *log, const char *name) struct io_sample *s = get_sample(log, cur_log, i); s->time = cpu_to_le64(s->time); - if (log->log_type != IO_LOG_TYPE_HIST) - s->data.val = cpu_to_le64(s->data.val); + if (log->log_type != IO_LOG_TYPE_HIST) { + s->data.val.val0 = cpu_to_le64(s->data.val.val0); + s->data.val.val1 = cpu_to_le64(s->data.val.val1); + } s->__ddir = __cpu_to_le32(s->__ddir); s->bs = cpu_to_le64(s->bs); diff --git a/stat.c b/stat.c index 7cf6bee194..11b586268b 100644 --- a/stat.c +++ b/stat.c @@ -3149,7 +3149,7 @@ void reset_io_stats(struct thread_data *td) } static void __add_stat_to_log(struct io_log *iolog, enum fio_ddir ddir, - unsigned long elapsed, bool log_max) + unsigned long elapsed, int log_max) { /* * Note an entry in the log. Use the mean from the logged samples, @@ -3159,10 +3159,16 @@ static void __add_stat_to_log(struct io_log *iolog, enum fio_ddir ddir, if (iolog->avg_window[ddir].samples) { union io_sample_data data; - if (log_max) - data.val = iolog->avg_window[ddir].max_val; - else - data.val = iolog->avg_window[ddir].mean.u.f + 0.50; + if (log_max == IO_LOG_SAMPLE_AVG) { + data.val.val0 = iolog->avg_window[ddir].mean.u.f + 0.50; + data.val.val1 = 0; + } else if (log_max == IO_LOG_SAMPLE_MAX) { + data.val.val0 = iolog->avg_window[ddir].max_val; + data.val.val1 = 0; + } else { + data.val.val0 = iolog->avg_window[ddir].mean.u.f + 0.50; + data.val.val1 = iolog->avg_window[ddir].max_val; + } __add_log_sample(iolog, data, ddir, 0, elapsed, 0, 0); } @@ -3171,7 +3177,7 @@ static void __add_stat_to_log(struct io_log *iolog, enum fio_ddir ddir, } static void _add_stat_to_log(struct io_log *iolog, unsigned long elapsed, - bool log_max) + int log_max) { enum fio_ddir ddir; @@ -3205,7 +3211,7 @@ static unsigned long add_log_sample(struct thread_data *td, * Add the sample. If the time period has passed, then * add that entry to the log and clear. */ - add_stat_sample(&iolog->avg_window[ddir], data.val); + add_stat_sample(&iolog->avg_window[ddir], data.val.val0); /* * If period hasn't passed, adding the above sample is all we @@ -3221,7 +3227,7 @@ static unsigned long add_log_sample(struct thread_data *td, return diff; } - __add_stat_to_log(iolog, ddir, elapsed, td->o.log_max != 0); + __add_stat_to_log(iolog, ddir, elapsed, td->o.log_max); iolog->avg_last[ddir] = elapsed - (elapsed % iolog->avg_msec); @@ -3235,15 +3241,15 @@ void finalize_logs(struct thread_data *td, bool unit_logs) elapsed = mtime_since_now(&td->epoch); if (td->clat_log && unit_logs) - _add_stat_to_log(td->clat_log, elapsed, td->o.log_max != 0); + _add_stat_to_log(td->clat_log, elapsed, td->o.log_max); if (td->slat_log && unit_logs) - _add_stat_to_log(td->slat_log, elapsed, td->o.log_max != 0); + _add_stat_to_log(td->slat_log, elapsed, td->o.log_max); if (td->lat_log && unit_logs) - _add_stat_to_log(td->lat_log, elapsed, td->o.log_max != 0); + _add_stat_to_log(td->lat_log, elapsed, td->o.log_max); if (td->bw_log && (unit_logs == per_unit_log(td->bw_log))) - _add_stat_to_log(td->bw_log, elapsed, td->o.log_max != 0); + _add_stat_to_log(td->bw_log, elapsed, td->o.log_max); if (td->iops_log && (unit_logs == per_unit_log(td->iops_log))) - _add_stat_to_log(td->iops_log, elapsed, td->o.log_max != 0); + _add_stat_to_log(td->iops_log, elapsed, td->o.log_max); } void add_agg_sample(union io_sample_data data, enum fio_ddir ddir, From 065212b3d615020242c1720862cd550718ca6d29 Mon Sep 17 00:00:00 2001 From: Ankit Kumar Date: Thu, 25 Jan 2024 16:31:24 +0530 Subject: [PATCH 0650/1097] docs: update fio man page for log_window_value Signed-off-by: Ankit Kumar Link: https://lore.kernel.org/r/20240125110124.55137-3-ankit.kumar@samsung.com Signed-off-by: Vincent Fu --- HOWTO.rst | 45 +++++++++++++++++++++++++++++++++++---------- fio.1 | 47 +++++++++++++++++++++++++++++++++++++---------- 2 files changed, 72 insertions(+), 20 deletions(-) diff --git a/HOWTO.rst b/HOWTO.rst index d0ba802173..dcea1ea15f 100644 --- a/HOWTO.rst +++ b/HOWTO.rst @@ -4067,7 +4067,7 @@ Measurements and reporting I/O that completes. When writing to the disk log, that can quickly grow to a very large size. Setting this option makes fio average the each log entry over the specified period of time, reducing the resolution of the log. See - :option:`log_max_value` as well. Defaults to 0, logging all entries. + :option:`log_window_value` as well. Defaults to 0, logging all entries. Also see `Log File Formats`_. .. option:: log_hist_msec=int @@ -4088,11 +4088,28 @@ Measurements and reporting histogram logs contain 1216 latency bins. See :option:`write_hist_log` and `Log File Formats`_. -.. option:: log_max_value=bool +.. option:: log_window_value=int, log_max_value=int - If :option:`log_avg_msec` is set, fio logs the average over that window. If - you instead want to log the maximum value, set this option to 1. Defaults to - 0, meaning that averaged values are logged. + If :option:`log_avg_msec` is set, fio by default logs the average over that + window. This option determines whether fio logs the average, maximum or + both the values over the window. This only affects the latency logging, + as both average and maximum values for iops or bw log will be same. + Accepted values are: + + **avg** + Log average value over the window. The default. + + **max** + Log maximum value in the window. + + **both** + Log both average and maximum value over the window. + + **0** + Backward-compatible alias for **avg**. + + **1** + Backward-compatible alias for **max**. .. option:: log_offset=bool @@ -5061,11 +5078,19 @@ toggled with :option:`log_offset`. by the ioengine specific :option:`cmdprio_percentage`. Fio defaults to logging every individual I/O but when windowed logging is set -through :option:`log_avg_msec`, either the average (by default) or the maximum -(:option:`log_max_value` is set) *value* seen over the specified period of time -is recorded. Each *data direction* seen within the window period will aggregate -its values in a separate row. Further, when using windowed logging the *block -size* and *offset* entries will always contain 0. +through :option:`log_avg_msec`, either the average (by default), the maximum +(:option:`log_window_value` is set to max) *value* seen over the specified period +of time, or both the average *value* and maximum *value1* (:option:`log_window_value` +is set to both) is recorded. The log file format when both the values are reported +takes this form: + + *time* (`msec`), *value*, *value1*, *data direction*, *block size* (`bytes`), + *offset* (`bytes`), *command priority* + + +Each *data direction* seen within the window period will aggregate its values in a +separate row. Further, when using windowed logging the *block size* and *offset* +entries will always contain 0. Client/Server diff --git a/fio.1 b/fio.1 index 8f659f1dbd..718aed80ea 100644 --- a/fio.1 +++ b/fio.1 @@ -3764,7 +3764,7 @@ By default, fio will log an entry in the iops, latency, or bw log for every I/O that completes. When writing to the disk log, that can quickly grow to a very large size. Setting this option makes fio average the each log entry over the specified period of time, reducing the resolution of the log. See -\fBlog_max_value\fR as well. Defaults to 0, logging all entries. +\fBlog_window_value\fR as well. Defaults to 0, logging all entries. Also see \fBLOG FILE FORMATS\fR section. .TP .BI log_hist_msec \fR=\fPint @@ -3782,10 +3782,28 @@ the histogram logs enabled with \fBlog_hist_msec\fR. For each increment in coarseness, fio outputs half as many bins. Defaults to 0, for which histogram logs contain 1216 latency bins. See \fBLOG FILE FORMATS\fR section. .TP -.BI log_max_value \fR=\fPbool -If \fBlog_avg_msec\fR is set, fio logs the average over that window. If -you instead want to log the maximum value, set this option to 1. Defaults to -0, meaning that averaged values are logged. +.BI log_window_value \fR=\fPint "\fR,\fP log_max_value" \fR=\fPint +If \fBlog_avg_msec\fR is set, fio by default logs the average over that window. +This option determines whether fio logs the average, maximum or both the +values over the window. This only affects the latency logging, as both average +and maximum values for iops or bw log will be same. Accepted values are: +.RS +.TP +.B avg +Log average value over the window. The default. +.TP +.B max +Log maximum value in the window. +.TP +.B both +Log both average and maximum value over the window. +.TP +.B 0 +Backward-compatible alias for \fBavg\fR. +.TP +.B 1 +Backward-compatible alias for \fBmax\fR. +.RE .TP .BI log_offset \fR=\fPbool If this is set, the iolog options will include the byte offset for the I/O @@ -4797,11 +4815,20 @@ number with the lowest 13 bits indicating the priority value (\fBprio\fR and (\fBprioclass\fR and \fBcmdprio_class\fR options). .P Fio defaults to logging every individual I/O but when windowed logging is set -through \fBlog_avg_msec\fR, either the average (by default) or the maximum -(\fBlog_max_value\fR is set) `value' seen over the specified period of time -is recorded. Each `data direction' seen within the window period will aggregate -its values in a separate row. Further, when using windowed logging the `block -size' and `offset' entries will always contain 0. +through \fBlog_avg_msec\fR, either the average (by default), the maximum +(\fBlog_window_value\fR is set to max) `value' seen over the specified period of +time, or both the average `value' and maximum `value1' (\fBlog_window_value\fR is +set to both) is recorded. The log file format when both the values are reported +takes this form: +.RS +.P +time (msec), value, value1, data direction, block size (bytes), offset (bytes), +command priority +.RE +.P +Each `data direction' seen within the window period will aggregate its values +in a separate row. Further, when using windowed logging the `block size' and +`offset' entries will always contain 0. .SH CLIENT / SERVER Normally fio is invoked as a stand-alone application on the machine where the I/O workload should be generated. However, the backend and frontend of fio can From f391405cf55fccd3a9eb3f1846cfd7bce92c7201 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Thu, 25 Jan 2024 08:56:35 -0500 Subject: [PATCH 0651/1097] docs: change listed type for log_window_value to str Signed-off-by: Vincent Fu --- HOWTO.rst | 2 +- fio.1 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/HOWTO.rst b/HOWTO.rst index dcea1ea15f..ba160551c0 100644 --- a/HOWTO.rst +++ b/HOWTO.rst @@ -4088,7 +4088,7 @@ Measurements and reporting histogram logs contain 1216 latency bins. See :option:`write_hist_log` and `Log File Formats`_. -.. option:: log_window_value=int, log_max_value=int +.. option:: log_window_value=str, log_max_value=str If :option:`log_avg_msec` is set, fio by default logs the average over that window. This option determines whether fio logs the average, maximum or diff --git a/fio.1 b/fio.1 index 718aed80ea..aef1dc855e 100644 --- a/fio.1 +++ b/fio.1 @@ -3782,7 +3782,7 @@ the histogram logs enabled with \fBlog_hist_msec\fR. For each increment in coarseness, fio outputs half as many bins. Defaults to 0, for which histogram logs contain 1216 latency bins. See \fBLOG FILE FORMATS\fR section. .TP -.BI log_window_value \fR=\fPint "\fR,\fP log_max_value" \fR=\fPint +.BI log_window_value \fR=\fPstr "\fR,\fP log_max_value" \fR=\fPstr If \fBlog_avg_msec\fR is set, fio by default logs the average over that window. This option determines whether fio logs the average, maximum or both the values over the window. This only affects the latency logging, as both average From 4502ad2c5b18085a2f4f5a71741ef0ca24cdaf38 Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Thu, 25 Jan 2024 08:09:14 -0700 Subject: [PATCH 0652/1097] t/io_uring: remove dma map option This didn't make it into the kernel just yet, and if/when it does, it'll likely look a bit different than the prototype. Since it's dead code for now, just prune it and we can always reintroduce it when kernel support is there. Signed-off-by: Jens Axboe --- t/io_uring.c | 46 ++-------------------------------------------- 1 file changed, 2 insertions(+), 44 deletions(-) diff --git a/t/io_uring.c b/t/io_uring.c index bf0aa26ece..efc50caad0 100644 --- a/t/io_uring.c +++ b/t/io_uring.c @@ -129,7 +129,6 @@ static int batch_complete = BATCH_COMPLETE; static int bs = BS; static int polled = 1; /* use IO polling */ static int fixedbufs = 1; /* use fixed user buffers */ -static int dma_map; /* pre-map DMA buffers */ static int register_files = 1; /* use fixed files */ static int buffered = 0; /* use buffered IO, not O_DIRECT */ static int sq_thread_poll = 0; /* use kernel submission/poller thread */ @@ -155,17 +154,6 @@ static float plist[] = { 1.0, 5.0, 10.0, 20.0, 30.0, 40.0, 50.0, 60.0, 70.0, 80.0, 90.0, 95.0, 99.0, 99.5, 99.9, 99.95, 99.99 }; static int plist_len = 17; -#ifndef IORING_REGISTER_MAP_BUFFERS -#define IORING_REGISTER_MAP_BUFFERS 26 -struct io_uring_map_buffers { - __s32 fd; - __u32 buf_start; - __u32 buf_end; - __u32 flags; - __u64 rsvd[2]; -}; -#endif - static int nvme_identify(int fd, __u32 nsid, enum nvme_identify_cns cns, enum nvme_csi csi, void *data) { @@ -405,22 +393,6 @@ static void add_stat(struct submitter *s, int clock_index, int nr) #endif } -static int io_uring_map_buffers(struct submitter *s) -{ - struct io_uring_map_buffers map = { - .fd = s->files[0].real_fd, - .buf_end = depth, - }; - - if (do_nop) - return 0; - if (s->nr_files > 1) - fprintf(stdout, "Mapping buffers may not work with multiple files\n"); - - return syscall(__NR_io_uring_register, s->ring_fd, - IORING_REGISTER_MAP_BUFFERS, &map, 1); -} - static int io_uring_register_buffers(struct submitter *s) { if (do_nop) @@ -950,14 +922,6 @@ static int setup_ring(struct submitter *s) perror("io_uring_register_buffers"); return 1; } - - if (dma_map) { - ret = io_uring_map_buffers(s); - if (ret < 0) { - perror("io_uring_map_buffers"); - return 1; - } - } } if (register_files) { @@ -1071,7 +1035,7 @@ static int submitter_init(struct submitter *s) } if (!init_printed) { - printf("polled=%d, fixedbufs=%d/%d, register_files=%d, buffered=%d, QD=%d\n", polled, fixedbufs, dma_map, register_files, buffered, depth); + printf("polled=%d, fixedbufs=%d, register_files=%d, buffered=%d, QD=%d\n", polled, fixedbufs, register_files, buffered, depth); printf("%s", buf); init_printed = 1; } @@ -1519,7 +1483,6 @@ static void usage(char *argv, int status) " -b : Block size, default %d\n" " -p : Polled IO, default %d\n" " -B : Fixed buffers, default %d\n" - " -D : DMA map fixed buffers, default %d\n" " -F : Register files, default %d\n" " -n : Number of threads, default %d\n" " -O : Use O_DIRECT, default %d\n" @@ -1534,7 +1497,7 @@ static void usage(char *argv, int status) " -P : Automatically place on device home node %d\n" " -u : Use nvme-passthrough I/O, default %d\n", argv, DEPTH, BATCH_SUBMIT, BATCH_COMPLETE, BS, polled, - fixedbufs, dma_map, register_files, nthreads, !buffered, do_nop, + fixedbufs, register_files, nthreads, !buffered, do_nop, stats, runtime == 0 ? "unlimited" : runtime_str, random_io, aio, use_sync, register_ring, numa_placement, pt); exit(status); @@ -1656,9 +1619,6 @@ int main(int argc, char *argv[]) case 'r': runtime = atoi(optarg); break; - case 'D': - dma_map = !!atoi(optarg); - break; case 'R': random_io = !!atoi(optarg); break; @@ -1694,8 +1654,6 @@ int main(int argc, char *argv[]) batch_complete = depth; if (batch_submit > depth) batch_submit = depth; - if (!fixedbufs && dma_map) - dma_map = 0; submitter = calloc(nthreads, sizeof(*submitter) + roundup_pow2(depth) * sizeof(struct iovec)); From acc481b6d34aab3ee6e19f22b64f8bf0dd30480c Mon Sep 17 00:00:00 2001 From: Ankit Kumar Date: Fri, 26 Jan 2024 05:03:28 +0530 Subject: [PATCH 0653/1097] iolog: fix reported defect from coverity scan Fix the two Null pointer dereferences issue reported by Coverity scan Null pointer dereferences (FORWARD_NULL) Dereferencing null pointer "l->td" Null pointer dereferences (REVERSE_INULL) Null-checking "p->td" suggests that it may be null, but it has already been dereferenced on all paths leading to the check. For aggregate read, write and trim bandwidth log, the setup_log function gets called with NULL pointer reference for thread data. Thus before dereferencing further we should check "l->td". Signed-off-by: Ankit Kumar --- iolog.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/iolog.c b/iolog.c index b4c7a8f131..f52a9a80f7 100644 --- a/iolog.c +++ b/iolog.c @@ -862,7 +862,12 @@ void setup_log(struct io_log **log, struct log_params *p, l->log_ddir_mask = LOG_OFFSET_SAMPLE_BIT; if (l->log_prio) l->log_ddir_mask |= LOG_PRIO_SAMPLE_BIT; - if (l->td->o.log_max == IO_LOG_SAMPLE_BOTH) + /* + * The bandwidth-log option generates agg-read_bw.log, + * agg-write_bw.log and agg-trim_bw.log for which l->td is NULL. + * Check if l->td is valid before dereferencing it. + */ + if (l->td && l->td->o.log_max == IO_LOG_SAMPLE_BOTH) l->log_ddir_mask |= LOG_AVG_MAX_SAMPLE_BIT; INIT_FLIST_HEAD(&l->chunk_list); From db7e983dd3d548cf9ebcf7aa4efc4afc44bedb9b Mon Sep 17 00:00:00 2001 From: Niklas Cassel Date: Fri, 26 Jan 2024 19:13:52 +0100 Subject: [PATCH 0654/1097] examples: cmdprio_bssplit: s,IO,I/O, Replace IO with I/O. Signed-off-by: Niklas Cassel Link: https://lore.kernel.org/r/20240126181353.4151771-1-cassel@kernel.org Signed-off-by: Vincent Fu --- examples/cmdprio-bssplit.fio | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/cmdprio-bssplit.fio b/examples/cmdprio-bssplit.fio index f3b2fac02d..05f0d0b2a8 100644 --- a/examples/cmdprio-bssplit.fio +++ b/examples/cmdprio-bssplit.fio @@ -12,9 +12,9 @@ iodepth=16 ; use the same prio class and prio level defined by the cmdprio_class ; and cmdprio options. [cmdprio] -; 40% of read IOs are 64kB and 60% are 1MB. 100% of writes are 1MB. +; 40% of read I/Os are 64kB and 60% are 1MB. 100% of writes are 1MB. ; 100% of the 64kB reads are executed with prio class 1 and prio level 0. -; All other IOs are executed without a priority set. +; All other I/Os are executed without a priority set. bssplit=64k/40:1024k/60,1024k/100 cmdprio_bssplit=64k/100:1024k/0,1024k/0 cmdprio_class=1 @@ -23,20 +23,20 @@ cmdprio=0 ; Advanced cmdprio_bssplit format. Each non-zero percentage entry can ; use a different prio class and prio level (appended to each entry). [cmdprio-adv] -; 40% of read IOs are 64kB and 60% are 1MB. 100% of writes are 1MB. +; 40% of read I/Os are 64kB and 60% are 1MB. 100% of writes are 1MB. ; 25% of the 64kB reads are executed with prio class 1 and prio level 1, ; 75% of the 64kB reads are executed with prio class 3 and prio level 2. -; All other IOs are executed without a priority set. +; All other I/Os are executed without a priority set. stonewall bssplit=64k/40:1024k/60,1024k/100 cmdprio_bssplit=64k/25/1/1:64k/75/3/2:1024k/0,1024k/0 ; Identical to the previous example, but with a default priority defined. [cmdprio-adv-def] -; 40% of read IOs are 64kB and 60% are 1MB. 100% of writes are 1MB. +; 40% of read I/Os are 64kB and 60% are 1MB. 100% of writes are 1MB. ; 25% of the 64kB reads are executed with prio class 1 and prio level 1, ; 75% of the 64kB reads are executed with prio class 3 and prio level 2. -; All other IOs are executed with prio class 2 and prio level 7. +; All other I/Os are executed with prio class 2 and prio level 7. stonewall prioclass=2 prio=7 From 625b155dcd3d56595ced60806e091126446c1e08 Mon Sep 17 00:00:00 2001 From: Niklas Cassel Date: Fri, 26 Jan 2024 19:13:53 +0100 Subject: [PATCH 0655/1097] examples: cmdprio_bssplit: add CDL example Add an example of how to use cmdprio_bssplit with Command Duration Limits. Signed-off-by: Niklas Cassel Link: https://lore.kernel.org/r/20240126181353.4151771-2-cassel@kernel.org Signed-off-by: Vincent Fu --- examples/cmdprio-bssplit.fio | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/examples/cmdprio-bssplit.fio b/examples/cmdprio-bssplit.fio index 05f0d0b2a8..ee202d74f1 100644 --- a/examples/cmdprio-bssplit.fio +++ b/examples/cmdprio-bssplit.fio @@ -42,3 +42,38 @@ prioclass=2 prio=7 bssplit=64k/40:1024k/60,1024k/100 cmdprio_bssplit=64k/25/1/1:64k/75/3/2:1024k/0,1024k/0 + +; Example of how to use cmdprio_bssplit with Command Duration Limits (CDL) +; using I/O priority hints. The drive has to support CDL, and CDL has to be +; enabled in sysfs, otherwise the hints will not be sent down to the drive. +[cmdprio-hints] +; 40% of the I/Os are 1MB reads and 60% of the I/Os are 2MB reads. +; +; 10% of the 1MB reads are executed with prio class 2 (Best Effort), +; prio level 0, and prio hint 1. Prio hint 1 means CDL descriptor 1. +; Since 40% of read I/Os are 1MB, and 10% of the 1MB I/Os use CDL desc 1, +; this means that 4% of all the issued I/O will use this configuration. +; +; 30% of the 1MB reads are executed with prio class 2 (Best Effort), +; prio level 0, and prio hint 2. Prio hint 2 means CDL descriptor 2. +; Since 40% of read I/Os are 1MB, and 30% of the 1MB I/Os use CDL desc 2, +; this means that 12% of all the issued I/O will use this configuration. +; +; 60% of the 1MB reads are executed with prio class 2 (Best Effort), +; prio level 0, and prio hint 0. Prio hint 0 means no hint. +; Since 40% of read I/Os are 1MB, and 60% of the 1MB I/Os use no hint, +; this means that 24% of all the issued I/O will use this configuration. +; +; 10% of the 2MB reads are executed with prio class 2 (Best Effort), +; prio level 0, and prio hint 3. Prio hint 3 means CDL descriptor 3. +; Since 60% of read I/Os are 2MB, and 10% of the 2MB I/Os use CDL desc 3, +; this means that 6% of all the issued I/O will use this configuration. +; +; 90% of the 2MB reads are executed with prio class 2 (Best Effort), +; prio level 0, and prio hint 0. Prio hint 0 means no hint. +; Since 60% of read I/Os are 2MB, and 90% of the 2MB I/Os use no hint, +; this means that 54% of all the issued I/O will use this configuration. +stonewall +rw=randread +bssplit=1M/40:2M/60 +cmdprio_bssplit=1M/10/2/0/1:1M/30/2/0/2:1M/60/2/0/0:2M/10/2/0/3:2M/90/2/0/0 From 9c8b90ae8be24856c4da309d005cc5e31a68ee8f Mon Sep 17 00:00:00 2001 From: Oleg Krasnov Date: Sun, 28 Jan 2024 18:24:02 +0000 Subject: [PATCH 0656/1097] fix wrong offset for VERIFY_PATTERN_NO_HDR --- verify.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/verify.c b/verify.c index 78f333e67e..3e0294435b 100644 --- a/verify.c +++ b/verify.c @@ -338,12 +338,20 @@ static void dump_verify_buffers(struct verify_header *hdr, struct vcont *vc) static void log_verify_failure(struct verify_header *hdr, struct vcont *vc) { unsigned long long offset; + uint32_t len; + struct thread_data *td = vc->td; offset = vc->io_u->verify_offset; - offset += vc->hdr_num * hdr->len; + if (td->o.verify != VERIFY_PATTERN_NO_HDR) { + len = hdr->len; + offset += vc->hdr_num * len; + } else { + len = vc->io_u->buflen; + } + log_err("%.8s: verify failed at file %s offset %llu, length %u" " (requested block: offset=%llu, length=%llu, flags=%x)\n", - vc->name, vc->io_u->file->file_name, offset, hdr->len, + vc->name, vc->io_u->file->file_name, offset, len, vc->io_u->verify_offset, vc->io_u->buflen, vc->io_u->flags); if (vc->good_crc && vc->bad_crc) { From 3f96645f7a0b26f018fc7386c4f9116526d24e0a Mon Sep 17 00:00:00 2001 From: Dmitry Fomichev Date: Tue, 6 Feb 2024 19:57:51 +0900 Subject: [PATCH 0657/1097] zbd: avoid assertions during sequential read I/O MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The following assert has been observed to be triggered if the I/O offset + I/O size exceeds the device capacity in a sequential read workload - “zbd.c:110: zone_lock: Assertion `f->min_zone <= nz && nz < f->max_zone' failed.” The code in zbd_zone_align_file_sizes() rounds down the I/O size to avoid these situations, but it is bypassed if td->o.td_ddir == TD_DDIR_READ. Avoid this issue by modifying zbd_zone_align_file_sizes() to round down the I/O size for read I/Os and leave the I/O offset untouched. Fixes: bfbdd35b3e8f ("Add support for zoned block devices") Signed-off-by: Dmitry Fomichev Reviewed-by: Shin'ichiro Kawasaki Tested-by: Shin'ichiro Kawasaki Link: https://lore.kernel.org/r/20240206105755.214891-2-dmitry.fomichev@wdc.com Signed-off-by: Vincent Fu --- zbd.c | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/zbd.c b/zbd.c index 61b5b688ca..7577472e9f 100644 --- a/zbd.c +++ b/zbd.c @@ -674,9 +674,20 @@ static bool zbd_zone_align_file_sizes(struct thread_data *td, return false; } + if (td->o.td_ddir == TD_DDIR_READ) { + z = zbd_offset_to_zone(f, f->file_offset + f->io_size); + new_end = z->start; + if (f->file_offset + f->io_size > new_end) { + log_info("%s: rounded io_size from %"PRIu64" to %"PRIu64"\n", + f->file_name, f->io_size, + new_end - f->file_offset); + f->io_size = new_end - f->file_offset; + } + return true; + } + z = zbd_offset_to_zone(f, f->file_offset); - if ((f->file_offset != z->start) && - (td->o.td_ddir != TD_DDIR_READ)) { + if (f->file_offset != z->start) { new_offset = zbd_zone_end(z); if (new_offset >= f->file_offset + f->io_size) { log_info("%s: io_size must be at least one zone\n", @@ -692,8 +703,7 @@ static bool zbd_zone_align_file_sizes(struct thread_data *td, z = zbd_offset_to_zone(f, f->file_offset + f->io_size); new_end = z->start; - if ((td->o.td_ddir != TD_DDIR_READ) && - (f->file_offset + f->io_size != new_end)) { + if (f->file_offset + f->io_size != new_end) { if (new_end <= f->file_offset) { log_info("%s: io_size must be at least one zone\n", f->file_name); From db46d820d092aebd345b340c526b9dcbabc76748 Mon Sep 17 00:00:00 2001 From: Dmitry Fomichev Date: Tue, 6 Feb 2024 19:57:52 +0900 Subject: [PATCH 0658/1097] oslib: log BLKREPORTZONE error code BLKREPORTZONE may fail because of a variety of reasons. Log both the return code and errno when this ioctl fails. Signed-off-by: Dmitry Fomichev Reviewed-by: Shin'ichiro Kawasaki Tested-by: Shin'ichiro Kawasaki Link: https://lore.kernel.org/r/20240206105755.214891-3-dmitry.fomichev@wdc.com Signed-off-by: Vincent Fu --- oslib/linux-blkzoned.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/oslib/linux-blkzoned.c b/oslib/linux-blkzoned.c index 2c3ecf33b4..1cc8d288b4 100644 --- a/oslib/linux-blkzoned.c +++ b/oslib/linux-blkzoned.c @@ -242,6 +242,8 @@ int blkzoned_report_zones(struct thread_data *td, struct fio_file *f, hdr->sector = offset >> 9; ret = ioctl(fd, BLKREPORTZONE, hdr); if (ret) { + log_err("%s: BLKREPORTZONE ioctl failed, ret=%d, err=%d.\n", + f->file_name, ret, -errno); ret = -errno; goto out; } From 69c53a63667f914ebf7e7a85c83351d90a7e2bc8 Mon Sep 17 00:00:00 2001 From: Dmitry Fomichev Date: Tue, 6 Feb 2024 19:57:53 +0900 Subject: [PATCH 0659/1097] zbd: use a helper to calculate zone index zone_lock() function contains the debug code to verify that the zone being locked belongs to the job's working zone range. Clean up this code by using a previously defined helper for calculating the zone index to check. Signed-off-by: Dmitry Fomichev Reviewed-by: Shin'ichiro Kawasaki Tested-by: Shin'ichiro Kawasaki Link: https://lore.kernel.org/r/20240206105755.214891-4-dmitry.fomichev@wdc.com Signed-off-by: Vincent Fu --- zbd.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/zbd.c b/zbd.c index 7577472e9f..3741766051 100644 --- a/zbd.c +++ b/zbd.c @@ -104,8 +104,7 @@ static void zone_lock(struct thread_data *td, const struct fio_file *f, struct fio_zone_info *z) { #ifndef NDEBUG - struct zoned_block_device_info *zbd = f->zbd_info; - uint32_t const nz = z - zbd->zone_info; + unsigned int const nz = zbd_zone_idx(f, z); /* A thread should never lock zones outside its working area. */ assert(f->min_zone <= nz && nz < f->max_zone); assert(z->has_wp); From 7d5a66e1be864ef16fb9e9b6e0b9234da1e2a199 Mon Sep 17 00:00:00 2001 From: Dmitry Fomichev Date: Tue, 6 Feb 2024 19:57:54 +0900 Subject: [PATCH 0660/1097] t/zbd: check device for unrestricted read support ZBD unit tests in t/zbd/test-zbd-support currently assume that the drive that is being tested supports unrestricted reads, i.e. reads that (partially or entirely) occur above the write pointer. This is always the case with ZBD core code because Linux kernel rejects zoned devices with restricted reads. However, libzbc ioengine does support such devices. The restricted/unrestricted reads feature is controlled by URSWRZ device bit ("Unrestricted Reads of Sequential Write Required Zones") which, depending on the device design, can be hard-coded to be reported as 1 or 0 or it can be made configurable via MODE SET or SET FEATURES commands. The unit tests need to behave correctly with any URSWRZ bit value reported by the device if libzbc ioengine is used for testing. Test #4 in the test script currently expects the device to have unrestricted SWR zone reads. This test is guaranteed to fail if the script is run against a drive that reports URSWRZ=0 with libzbc ioengine. Check if the drive has unrestricted read support disabled and process the outcome of test #4 accordingly. Signed-off-by: Dmitry Fomichev Reviewed-by: Shin'ichiro Kawasaki Tested-by: Shin'ichiro Kawasaki Link: https://lore.kernel.org/r/20240206105755.214891-5-dmitry.fomichev@wdc.com Signed-off-by: Vincent Fu --- t/zbd/functions | 22 ++++++++++++++++++++++ t/zbd/test-zbd-support | 15 +++++++++++++-- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/t/zbd/functions b/t/zbd/functions index 028df4040e..7734371e5e 100644 --- a/t/zbd/functions +++ b/t/zbd/functions @@ -290,6 +290,28 @@ min_seq_write_size() { fi } +urswrz() { + local dev=$1 + + if [ -n "${sg_inq}" ] && [ ! -n "${use_libzbc}" ]; then + if ! ${sg_inq} -e --page=0xB6 --len=10 --hex "$dev" \ + > /dev/null 2>&1; then + # Couldn't get URSWRZ bit. Assume the reads are unrestricted + # because this configuration is more common. + echo 1 + else + ${sg_inq} -e --page=0xB6 --len=10 --hex "$dev" | tail -1 | + { + read -r offset b0 b1 b2 b3 b4 trailer && \ + echo $(( $b4 & 0x01 )) || echo 0 + } + fi + else + ${zbc_info} "$dev" | + sed -n 's/^[[:blank:]].*Read commands are \(un\)restricted*/\1/p' | grep -q ^ && echo 1 || echo 0 + fi +} + is_zbc() { local dev=$1 diff --git a/t/zbd/test-zbd-support b/t/zbd/test-zbd-support index 532860ebc8..defb36527b 100755 --- a/t/zbd/test-zbd-support +++ b/t/zbd/test-zbd-support @@ -412,8 +412,16 @@ test4() { opts+=("--size=$size" "--thread=1" "--read_beyond_wp=1") opts+=("$(ioengine "psync")" "--rw=read" "--direct=1" "--disable_lat=1") opts+=("--zonemode=zbd" "--zonesize=${zone_size}") - run_fio "${opts[@]}" >> "${logfile}.${test_number}" 2>&1 || return $? - check_read $size || return $? + run_fio "${opts[@]}" >> "${logfile}.${test_number}" 2>&1 + fio_rc=$? + if [[ $unrestricted_reads != 0 ]]; then + if [[ $fio_rc != 0 ]]; then + return "$fio_rc" + fi + check_read $size || return $? + else + [ $fio_rc == 0 ] && return 1 || return 0 + fi } # Sequential write to sequential zones. @@ -1664,6 +1672,7 @@ if [[ -b "$realdev" ]]; then first_sequential_zone_sector=${result[0]} sectors_per_zone=${result[1]} zone_size=$((sectors_per_zone * 512)) + unrestricted_reads=$(urswrz "$dev") if ! max_open_zones=$(max_open_zones "$dev"); then echo "Failed to determine maximum number of open zones" exit 1 @@ -1681,6 +1690,7 @@ if [[ -b "$realdev" ]]; then sectors_per_zone=$((zone_size / 512)) max_open_zones=128 max_active_zones=0 + unrestricted_reads=1 set_io_scheduler "$basename" none || exit $? ;; esac @@ -1712,6 +1722,7 @@ elif [[ -c "$realdev" ]]; then first_sequential_zone_sector=${result[0]} sectors_per_zone=${result[1]} zone_size=$((sectors_per_zone * 512)) + unrestricted_reads=$(urswrz "$dev") if ! max_open_zones=$(max_open_zones "$dev"); then echo "Failed to determine maximum number of open zones" exit 1 From 12067650d11d4777dee0cd64a136923c2fd2d073 Mon Sep 17 00:00:00 2001 From: Dmitry Fomichev Date: Tue, 6 Feb 2024 19:57:55 +0900 Subject: [PATCH 0661/1097] t/zbd: add -s option to test-zbd-support script The total number of ZBD tests in test-zbd-support script has grown considerably over the years and zoned drive capacity has significantly increased as well. Today, the test run duration may reach one hour for large drives. If a terminal session failure happens during a run, it is more efficient to restart the tests from the point where the last run stopped rather than from the beginning. Add -s option to the script command line to specify the starting test number. Signed-off-by: Dmitry Fomichev Reviewed-by: Shin'ichiro Kawasaki Tested-by: Shin'ichiro Kawasaki Link: https://lore.kernel.org/r/20240206105755.214891-6-dmitry.fomichev@wdc.com Signed-off-by: Vincent Fu --- t/zbd/test-zbd-support | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/t/zbd/test-zbd-support b/t/zbd/test-zbd-support index defb36527b..c27d2ad68a 100755 --- a/t/zbd/test-zbd-support +++ b/t/zbd/test-zbd-support @@ -15,6 +15,7 @@ usage() { echo -e "\t-w Reset all zones before executing each write test case" echo -e "\t-o Run fio with max_open_zones limit" echo -e "\t-t Run only a single test case with specified number" + echo -e "\t-s Start testing from the case with the specified number" echo -e "\t-q Quit the test run after any failed test" echo -e "\t-z Run fio with debug=zbd option" echo -e "\t-u Use io_uring ioengine in place of libaio" @@ -1602,6 +1603,7 @@ zbd_debug= max_open_zones_opt= quit_on_err= force_io_uring= +start_test=1 while [ "${1#-}" != "$1" ]; do case "$1" in @@ -1615,6 +1617,7 @@ while [ "${1#-}" != "$1" ]; do -w) reset_before_write=1; shift;; -t) tests+=("$2"); shift; shift;; -o) max_open_zones_opt="${2}"; shift; shift;; + -s) start_test=$2; shift; shift;; -v) dynamic_analyzer=(valgrind "--read-var-info=yes"); shift;; -q) quit_on_err=1; shift;; @@ -1694,6 +1697,7 @@ if [[ -b "$realdev" ]]; then set_io_scheduler "$basename" none || exit $? ;; esac + elif [[ -c "$realdev" ]]; then # For an SG node, we must have libzbc option specified if [[ ! -n "$use_libzbc" ]]; then @@ -1772,6 +1776,7 @@ trap 'intr=1' SIGINT ret=0 for test_number in "${tests[@]}"; do + [ "${test_number}" -lt "${start_test}" ] && continue rm -f "${logfile}.${test_number}" unset SKIP_REASON echo -n "Running test $(printf "%02d" $test_number) ... " From 9cfa60d874e9a1da057677619a370409428ea3cf Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Thu, 8 Feb 2024 17:37:01 -0500 Subject: [PATCH 0662/1097] verify: fix potential overflow before widen vc->hdr_num and len are both 32 bits wide and their product will be a 32-bit result. So any overflow will be lost. Cast hdr_num to unsigned long long so that nothing is lost if the product overflows a 32-bit integer. This fixes the following issue reported by Coverity. ** CID 486274: Integer handling issues (OVERFLOW_BEFORE_WIDEN) /verify.c: 347 in log_verify_failure() ________________________________________________________________________________________________________ *** CID 486274: Integer handling issues (OVERFLOW_BEFORE_WIDEN) /verify.c: 347 in log_verify_failure() 341 uint32_t len; 342 struct thread_data *td = vc->td; 343 344 offset = vc->io_u->verify_offset; 345 if (td->o.verify != VERIFY_PATTERN_NO_HDR) { 346 len = hdr->len; >>> CID 486274: Integer handling issues (OVERFLOW_BEFORE_WIDEN) >>> Potentially overflowing expression "vc->hdr_num * len" with type "unsigned int" (32 bits, unsigned) is evaluated using 32-bit arithmetic, and then used in a context that expects an expression of type "unsigned long long" (64 bits, unsigned). 347 offset += vc->hdr_num * len; 348 } else { 349 len = vc->io_u->buflen; 350 } 351 352 log_err("%.8s: verify failed at file %s offset %llu, length %u" Fixes: 9c8b90ae ("fix wrong offset for VERIFY_PATTERN_NO_HDR") Signed-off-by: Vincent Fu --- verify.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/verify.c b/verify.c index 3e0294435b..b438eed6bd 100644 --- a/verify.c +++ b/verify.c @@ -344,7 +344,7 @@ static void log_verify_failure(struct verify_header *hdr, struct vcont *vc) offset = vc->io_u->verify_offset; if (td->o.verify != VERIFY_PATTERN_NO_HDR) { len = hdr->len; - offset += vc->hdr_num * len; + offset += (unsigned long long) vc->hdr_num * len; } else { len = vc->io_u->buflen; } From 80cc242a580de64c8b6c913a50d252fc496234c1 Mon Sep 17 00:00:00 2001 From: Marco Pinna Date: Tue, 6 Feb 2024 10:52:54 +0100 Subject: [PATCH 0663/1097] Add support for VSOCK to engine/net.c * configure: add option to enable/disable vsock support * engines/net.c: add vsock support The VSOCK address family facilitates communication between virtual machines and the host they are running on. The addressing is formed by 2 integers: - CID: Context ID, it is the ID assigned to the VM 0, 1, 2 CIDs are reserved: 0 - hypervisor CID (rarely used) 1 - local communication (loopback) 2 - host CID (the guest can always reach the host using CID=2) - port: port number on 32bit to reach a specific process * examples: add 3 simple job files for vsock (one sender, one receiver and one that uses vsock loopback interface similar to examples/netio.fio) * fio.1: add vsock to supported protocols together with the required parameters * HOWTO.rst: add vsock to supported protocols together with the required parameters Signed-off-by: Marco Pinna --- HOWTO.rst | 7 +- configure | 22 +++++ engines/net.c | 132 +++++++++++++++++++++++++++++- examples/netio_vsock.fio | 22 +++++ examples/netio_vsock_receiver.fio | 14 ++++ examples/netio_vsock_sender.fio | 17 ++++ fio.1 | 9 +- 7 files changed, 217 insertions(+), 6 deletions(-) create mode 100644 examples/netio_vsock.fio create mode 100644 examples/netio_vsock_receiver.fio create mode 100644 examples/netio_vsock_sender.fio diff --git a/HOWTO.rst b/HOWTO.rst index ba160551c0..53b03021d4 100644 --- a/HOWTO.rst +++ b/HOWTO.rst @@ -2626,10 +2626,13 @@ with the caveat that when used on the command line, they must come after the User datagram protocol V6. **unix** UNIX domain socket. + **vsock** + VSOCK protocol. - When the protocol is TCP or UDP, the port must also be given, as well as the - hostname if the job is a TCP listener or UDP reader. For unix sockets, the + When the protocol is TCP, UDP or VSOCK, the port must also be given, as well as the + hostname if the job is a TCP or VSOCK listener or UDP reader. For unix sockets, the normal :option:`filename` option should be used and the port is invalid. + When the protocol is VSOCK, the :option:`hostname` is the CID of the remote VM. .. option:: listen : [netsplice] [net] diff --git a/configure b/configure index dea8d07d11..becb193ec8 100755 --- a/configure +++ b/configure @@ -1728,6 +1728,25 @@ elif compile_prog "" "-lws2_32" "TCP_NODELAY"; then fi print_config "TCP_NODELAY" "$tcp_nodelay" +########################################## +# Check whether we have vsock +if test "$vsock" != "yes" ; then + vsock="no" +fi +cat > $TMPC << EOF +#include +#include +#include +int main(int argc, char **argv) +{ + return socket(AF_VSOCK, SOCK_STREAM, 0); +} +EOF +if compile_prog "" "" "vsock"; then + vsock="yes" +fi +print_config "vsock" "$vsock" + ########################################## # Check whether we have SO_SNDBUF if test "$window_size" != "yes" ; then @@ -3192,6 +3211,9 @@ fi if test "$ipv6" = "yes" ; then output_sym "CONFIG_IPV6" fi +if test "$vsock" = "yes"; then + output_sym "CONFIG_VSOCK" +fi if test "$http" = "yes" ; then output_sym "CONFIG_HTTP" fi diff --git a/engines/net.c b/engines/net.c index fec53d7417..29150bb348 100644 --- a/engines/net.c +++ b/engines/net.c @@ -18,6 +18,16 @@ #include #include +#ifdef CONFIG_VSOCK +#include +#else +struct sockaddr_vm { +}; +#ifndef AF_VSOCK +#define AF_VSOCK -1 +#endif +#endif + #include "../fio.h" #include "../verify.h" #include "../optgroup.h" @@ -30,6 +40,7 @@ struct netio_data { struct sockaddr_in addr; struct sockaddr_in6 addr6; struct sockaddr_un addr_un; + struct sockaddr_vm addr_vm; uint64_t udp_send_seq; uint64_t udp_recv_seq; }; @@ -69,6 +80,7 @@ enum { FIO_TYPE_UNIX = 3, FIO_TYPE_TCP_V6 = 4, FIO_TYPE_UDP_V6 = 5, + FIO_TYPE_VSOCK_STREAM = 6, }; static int str_hostname_cb(void *data, const char *input); @@ -126,6 +138,10 @@ static struct fio_option options[] = { .oval = FIO_TYPE_UNIX, .help = "UNIX domain socket", }, + { .ival = "vsock", + .oval = FIO_TYPE_VSOCK_STREAM, + .help = "Virtual socket", + }, }, .category = FIO_OPT_C_ENGINE, .group = FIO_OPT_G_NETIO, @@ -223,6 +239,11 @@ static inline int is_ipv6(struct netio_options *o) return o->proto == FIO_TYPE_UDP_V6 || o->proto == FIO_TYPE_TCP_V6; } +static inline int is_vsock(struct netio_options *o) +{ + return o->proto == FIO_TYPE_VSOCK_STREAM; +} + static int set_window_size(struct thread_data *td, int fd) { #ifdef CONFIG_NET_WINDOWSIZE @@ -732,6 +753,9 @@ static int fio_netio_connect(struct thread_data *td, struct fio_file *f) } else if (o->proto == FIO_TYPE_UNIX) { domain = AF_UNIX; type = SOCK_STREAM; + } else if (is_vsock(o)) { + domain = AF_VSOCK; + type = SOCK_STREAM; } else { log_err("fio: bad network type %d\n", o->proto); f->fd = -1; @@ -809,7 +833,14 @@ static int fio_netio_connect(struct thread_data *td, struct fio_file *f) close(f->fd); return 1; } + } else if (is_vsock(o)) { + socklen_t len = sizeof(nd->addr_vm); + if (connect(f->fd, (struct sockaddr *) &nd->addr_vm, len) < 0) { + td_verror(td, errno, "connect"); + close(f->fd); + return 1; + } } else { struct sockaddr_un *addr = &nd->addr_un; socklen_t len; @@ -849,6 +880,9 @@ static int fio_netio_accept(struct thread_data *td, struct fio_file *f) if (o->proto == FIO_TYPE_TCP) { socklen = sizeof(nd->addr); f->fd = accept(nd->listenfd, (struct sockaddr *) &nd->addr, &socklen); + } else if (is_vsock(o)) { + socklen = sizeof(nd->addr_vm); + f->fd = accept(nd->listenfd, (struct sockaddr *) &nd->addr_vm, &socklen); } else { socklen = sizeof(nd->addr6); f->fd = accept(nd->listenfd, (struct sockaddr *) &nd->addr6, &socklen); @@ -890,6 +924,9 @@ static void fio_netio_send_close(struct thread_data *td, struct fio_file *f) if (is_ipv6(o)) { to = (struct sockaddr *) &nd->addr6; len = sizeof(nd->addr6); + } else if (is_vsock(o)) { + to = NULL; + len = 0; } else { to = (struct sockaddr *) &nd->addr; len = sizeof(nd->addr); @@ -960,6 +997,9 @@ static int fio_netio_send_open(struct thread_data *td, struct fio_file *f) if (is_ipv6(o)) { len = sizeof(nd->addr6); to = (struct sockaddr *) &nd->addr6; + } else if (is_vsock(o)) { + len = sizeof(nd->addr_vm); + to = (struct sockaddr *) &nd->addr_vm; } else { len = sizeof(nd->addr); to = (struct sockaddr *) &nd->addr; @@ -1023,13 +1063,17 @@ static int fio_fill_addr(struct thread_data *td, const char *host, int af, memset(&hints, 0, sizeof(hints)); - if (is_tcp(o)) + if (is_tcp(o) || is_vsock(o)) hints.ai_socktype = SOCK_STREAM; else hints.ai_socktype = SOCK_DGRAM; if (is_ipv6(o)) hints.ai_family = AF_INET6; +#ifdef CONFIG_VSOCK + else if (is_vsock(o)) + hints.ai_family = AF_VSOCK; +#endif else hints.ai_family = AF_INET; @@ -1110,12 +1154,50 @@ static int fio_netio_setup_connect_unix(struct thread_data *td, return 0; } +static int fio_netio_setup_connect_vsock(struct thread_data *td, + const char *host, unsigned short port) +{ +#ifdef CONFIG_VSOCK + struct netio_data *nd = td->io_ops_data; + struct sockaddr_vm *addr = &nd->addr_vm; + int cid; + + if (!host) { + log_err("fio: connect with no host to connect to.\n"); + if (td_read(td)) + log_err("fio: did you forget to set 'listen'?\n"); + + td_verror(td, EINVAL, "no hostname= set"); + return 1; + } + + addr->svm_family = AF_VSOCK; + addr->svm_port = port; + + if (host) { + cid = atoi(host); + if (cid < 0 || cid > UINT32_MAX) { + log_err("fio: invalid CID %d\n", cid); + return 1; + } + addr->svm_cid = cid; + } + + return 0; +#else + td_verror(td, -EINVAL, "vsock not supported"); + return 1; +#endif +} + static int fio_netio_setup_connect(struct thread_data *td) { struct netio_options *o = td->eo; if (is_udp(o) || is_tcp(o)) return fio_netio_setup_connect_inet(td, td->o.filename,o->port); + else if (is_vsock(o)) + return fio_netio_setup_connect_vsock(td, td->o.filename, o->port); else return fio_netio_setup_connect_unix(td, td->o.filename); } @@ -1268,6 +1350,47 @@ static int fio_netio_setup_listen_inet(struct thread_data *td, short port) return 0; } +static int fio_netio_setup_listen_vsock(struct thread_data *td, short port, int type) +{ +#ifdef CONFIG_VSOCK + struct netio_data *nd = td->io_ops_data; + struct sockaddr_vm *addr = &nd->addr_vm; + int fd, opt; + socklen_t len; + + fd = socket(AF_VSOCK, type, 0); + if (fd < 0) { + td_verror(td, errno, "socket"); + return 1; + } + + opt = 1; + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (void *) &opt, sizeof(opt)) < 0) { + td_verror(td, errno, "setsockopt"); + close(fd); + return 1; + } + + len = sizeof(*addr); + + nd->addr_vm.svm_family = AF_VSOCK; + nd->addr_vm.svm_cid = VMADDR_CID_ANY; + nd->addr_vm.svm_port = port; + + if (bind(fd, (struct sockaddr *) addr, len) < 0) { + td_verror(td, errno, "bind"); + close(fd); + return 1; + } + + nd->listenfd = fd; + return 0; +#else + td_verror(td, -EINVAL, "vsock not supported"); + return -1; +#endif +} + static int fio_netio_setup_listen(struct thread_data *td) { struct netio_data *nd = td->io_ops_data; @@ -1276,6 +1399,8 @@ static int fio_netio_setup_listen(struct thread_data *td) if (is_udp(o) || is_tcp(o)) ret = fio_netio_setup_listen_inet(td, o->port); + else if (is_vsock(o)) + ret = fio_netio_setup_listen_vsock(td, o->port, SOCK_STREAM); else ret = fio_netio_setup_listen_unix(td, td->o.filename); @@ -1311,6 +1436,9 @@ static int fio_netio_init(struct thread_data *td) if (o->proto == FIO_TYPE_UNIX && o->port) { log_err("fio: network IO port not valid with unix socket\n"); return 1; + } else if (is_vsock(o) && !o->port) { + log_err("fio: network IO requires port for vsock\n"); + return 1; } else if (o->proto != FIO_TYPE_UNIX && !o->port) { log_err("fio: network IO requires port for tcp or udp\n"); return 1; @@ -1318,7 +1446,7 @@ static int fio_netio_init(struct thread_data *td) o->port += td->subjob_number; - if (!is_tcp(o)) { + if (!is_tcp(o) && !is_vsock(o)) { if (o->listen) { log_err("fio: listen only valid for TCP proto IO\n"); return 1; diff --git a/examples/netio_vsock.fio b/examples/netio_vsock.fio new file mode 100644 index 0000000000..8c328f7dd3 --- /dev/null +++ b/examples/netio_vsock.fio @@ -0,0 +1,22 @@ +# Example network vsock job, just defines two clients that send/recv data +[global] +ioengine=net + +port=8888 +protocol=vsock +bs=4k +size=100g + +#set the below option to enable end-to-end data integrity tests +#verify=md5 + +[receiver] +listen +rw=read + +[sender] +# 1 (VMADDR_CID_LOCAL) is the well-known address +# for local communication (loopback) +hostname=1 +startdelay=1 +rw=write diff --git a/examples/netio_vsock_receiver.fio b/examples/netio_vsock_receiver.fio new file mode 100644 index 0000000000..e2a00c4d79 --- /dev/null +++ b/examples/netio_vsock_receiver.fio @@ -0,0 +1,14 @@ +# Example network vsock job, just defines a receiver +[global] +ioengine=net +port=8888 +protocol=vsock +bs=4k +size=100g + +#set the below option to enable end-to-end data integrity tests +#verify=md5 + +[receiver] +listen +rw=read diff --git a/examples/netio_vsock_sender.fio b/examples/netio_vsock_sender.fio new file mode 100644 index 0000000000..2451d99005 --- /dev/null +++ b/examples/netio_vsock_sender.fio @@ -0,0 +1,17 @@ +# Example network vsock job, just defines a sender +[global] +ioengine=net +port=8888 +protocol=vsock +bs=4k +size=100g + +#set the below option to enable end-to-end data integrity tests +#verify=md5 + +[sender] +# set the 'hostname' option to the CID of the listening domain +hostname=3 +startdelay=1 +rw=write + diff --git a/fio.1 b/fio.1 index aef1dc855e..227fcb4786 100644 --- a/fio.1 +++ b/fio.1 @@ -2376,11 +2376,16 @@ User datagram protocol V6. .TP .B unix UNIX domain socket. +.TP +.B vsock +VSOCK protocol. .RE .P -When the protocol is TCP or UDP, the port must also be given, as well as the -hostname if the job is a TCP listener or UDP reader. For unix sockets, the +When the protocol is TCP, UDP or VSOCK, the port must also be given, as well as the +hostname if the job is a TCP or VSOCK listener or UDP reader. For unix sockets, the normal \fBfilename\fR option should be used and the port is invalid. +When the protocol is VSOCK, the \fBhostname\fR is the CID of the remote VM. + .RE .TP .BI (netsplice,net)listen From d09dcd34db7bdff65bb31d4055f5823898d9e629 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Wed, 24 Jan 2024 17:38:18 +0000 Subject: [PATCH 0664/1097] logging: record timestamp for each thread Instead of recording a timestamp once before iterating through all the threads to check if we should add a new bw or iops log measurement, record a new timestamp before checking each thread. We were already querying the time anyway for the mtime_since_now() call. We might as well have it available locally for more accurate logging. Signed-off-by: Vincent Fu --- stat.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/stat.c b/stat.c index 11b586268b..ab901ecd71 100644 --- a/stat.c +++ b/stat.c @@ -3585,10 +3585,9 @@ int calc_log_samples(void) struct timespec now; long elapsed_time = 0; - fio_gettime(&now, NULL); - for_each_td(td) { - elapsed_time = mtime_since_now(&td->epoch); + fio_gettime(&now, NULL); + elapsed_time = mtime_since(&td->epoch, &now); if (!td->o.stats) continue; From e4a0c352f4ce59bbdffcd621143db70faef23f9b Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Tue, 23 Jan 2024 21:56:31 +0000 Subject: [PATCH 0665/1097] helper_thread: do not send A_EXIT message when exit is called When helper_thread_exit() is called, it uses two means to tell helper_thread_main() to stop. It sends an A_EXIT message via the pipes used for inter-process communication and it also sets the exit flag in struct helper_data. When helper_thread_main() receives the A_EXIT message, it immediately breaks out of its main loop without carrying out any further activity such as recording final log entries. This leads to missing final log entries when log_avg_msec is enabled. Removing the A_EXIT message helps resolve this issue in some cases as with its removal the actions in the body of the loop have an opportunity to be taken before checking the flag in struct helper_data. Signed-off-by: Vincent Fu --- helper_thread.c | 1 - 1 file changed, 1 deletion(-) diff --git a/helper_thread.c b/helper_thread.c index 2a9dabf5f7..332ccb53c2 100644 --- a/helper_thread.c +++ b/helper_thread.c @@ -161,7 +161,6 @@ void helper_thread_exit(void) return; helper_data->exit = 1; - submit_action(A_EXIT); pthread_join(helper_data->thread, NULL); } From ba03f5afa7cd13a428305298a234db959d3f0c82 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Wed, 24 Jan 2024 17:41:16 +0000 Subject: [PATCH 0666/1097] logging: expand runstates eligible for logging Currently recording log entries for IOPS and BW logging with log_avg_msec enabled only happens when a thread has completed its ramp time and is in the TD_RUNNING or TD_VERIFYING run states. It may happen that a final bandwidth or IOPS log entry is missed when a job transitions from TD_RUNNING/TD_VERIFYING to TD_FINISHING before the helper thread has a chance to calculate a final log entry. This patch expands the run states where logging is permitted, allowing log entries to be recorded for jobs in the TD_FINISHING or TD_EXITED states. Each job cleans itself up and typically transitions quickly from TD_FINISHING to TD_EXITED. The main fio backend thread carries out the transition from TD_EXITED to TD_REAPED. The window during which a job is in the TD_FINISHING and TD_EXITED states is short, so measurements should still be reasonablly accurate. I tested these patches with the following job: fio --name=test --ioengine=null --time_based --runtime=3s --filesize=1T \ --write_iops_log=test --write_bw_log=test --log_avg_msec=1000 \ && cat test_iops.1.log && cat test_bw.1.log Before this patch series 10/10 trials had missing log entries. With only the helper_thread change in the preceding patch 3/10 trials had missing log entries. With this entire patch series, only 1/10 trials had missing log entries. Signed-off-by: Vincent Fu --- stat.c | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/stat.c b/stat.c index ab901ecd71..b98e8b27c3 100644 --- a/stat.c +++ b/stat.c @@ -3576,6 +3576,22 @@ static int add_iops_samples(struct thread_data *td, struct timespec *t) td->ts.iops_stat, td->iops_log, false); } +static bool td_in_logging_state(struct thread_data *td) +{ + if (in_ramp_time(td)) + return false; + + switch(td->runstate) { + case TD_RUNNING: + case TD_VERIFYING: + case TD_FINISHING: + case TD_EXITED: + return true; + default: + return false; + } +} + /* * Returns msecs to next event */ @@ -3591,8 +3607,7 @@ int calc_log_samples(void) if (!td->o.stats) continue; - if (in_ramp_time(td) || - !(td->runstate == TD_RUNNING || td->runstate == TD_VERIFYING)) { + if (!td_in_logging_state(td)) { next = min(td->o.iops_avg_time, td->o.bw_avg_time); continue; } From 119b7ce8e53226aa889cc243c08ec4892a5ec63b Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Mon, 12 Feb 2024 12:13:01 -0500 Subject: [PATCH 0667/1097] docs: explain duplicate logging timestamps When a fio job ends, it cleans up by flushing any accumulated latency log data for jobs with log_avg_msec enabled. This means that the final logging interval may be different from what was specified by log_avg_msec. In some cases there may even be duplicate timestamps. Add an explanation for this phenomenon to the documentation. During job cleanup it's possible to simply suppress the final log entry if log_avg_msec has not passed since the previous log entry was recorded but this throws away data that some users may depend on. For instance, a 55s job with log_avg_msec=10000 would have no long entry for the final 5s if we suppressed the final log entry. Users concerned about final log entries with duplicate timestamps should just ignore the second entry since it is likely based on only a handful of I/Os. Duplicate log entry example: $ sudo ./fio --name=test --iodepth=2 --ioengine=libaio --time_based --runtime=5s --log_avg_msec=1000 --write_lat_log=test --filename=/dev/vda --direct=1 test: (g=0): rw=read, bs=(R) 4096B-4096B, (W) 4096B-4096B, (T) 4096B-4096B, ioengine=libaio, iodepth=2 fio-3.36-61-g9cfa-dirty Starting 1 process Jobs: 1 (f=1): [R(1)][100.0%][r=250MiB/s][r=64.0k IOPS][eta 00m:00s] test: (groupid=0, jobs=1): err= 0: pid=1490: Mon Feb 12 12:19:13 2024 read: IOPS=63.6k, BW=248MiB/s (260MB/s)(1242MiB/5001msec) slat (nsec): min=691, max=37070, avg=1272.54, stdev=674.26 clat (usec): min=4, max=1731, avg=29.83, stdev= 7.03 lat (usec): min=15, max=1734, avg=31.10, stdev= 7.19 clat percentiles (usec): | 1.00th=[ 23], 5.00th=[ 25], 10.00th=[ 26], 20.00th=[ 27], | 30.00th=[ 28], 40.00th=[ 29], 50.00th=[ 30], 60.00th=[ 31], | 70.00th=[ 32], 80.00th=[ 33], 90.00th=[ 35], 95.00th=[ 37], | 99.00th=[ 41], 99.50th=[ 43], 99.90th=[ 58], 99.95th=[ 74], | 99.99th=[ 104] bw ( KiB/s): min=244464, max=258112, per=100.00%, avg=254410.67, stdev=4788.90, samples=9 iops : min=61116, max=64528, avg=63602.67, stdev=1197.23, samples=9 lat (usec) : 10=0.01%, 20=0.06%, 50=99.76%, 100=0.16%, 250=0.01% lat (usec) : 500=0.01%, 750=0.01%, 1000=0.01% lat (msec) : 2=0.01% cpu : usr=11.46%, sys=18.20%, ctx=159414, majf=0, minf=49 IO depths : 1=0.1%, 2=100.0%, 4=0.0%, 8=0.0%, 16=0.0%, 32=0.0%, >=64=0.0% submit : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.0% complete : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.0% issued rwts: total=317842,0,0,0 short=0,0,0,0 dropped=0,0,0,0 latency : target=0, window=0, percentile=100.00%, depth=2 Run status group 0 (all jobs): READ: bw=248MiB/s (260MB/s), 248MiB/s-248MiB/s (260MB/s-260MB/s), io=1242MiB (1302MB), run=5001-5001msec Disk stats (read/write): vda: ios=311248/0, sectors=2489984/0, merge=0/0, ticks=7615/0, in_queue=7615, util=98.10% $ cat test_lat.1.log 1000, 31907, 0, 0, 0 2000, 30705, 0, 0, 0 3000, 30738, 0, 0, 0 4000, 31196, 0, 0, 0 5000, 30997, 0, 0, 0 5000, 31559, 0, 0, 0 Signed-off-by: Vincent Fu --- HOWTO.rst | 15 +++++++++------ fio.1 | 14 ++++++++------ 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/HOWTO.rst b/HOWTO.rst index 53b03021d4..e5a5be0376 100644 --- a/HOWTO.rst +++ b/HOWTO.rst @@ -4066,12 +4066,15 @@ Measurements and reporting .. option:: log_avg_msec=int - By default, fio will log an entry in the iops, latency, or bw log for every - I/O that completes. When writing to the disk log, that can quickly grow to a - very large size. Setting this option makes fio average the each log entry - over the specified period of time, reducing the resolution of the log. See - :option:`log_window_value` as well. Defaults to 0, logging all entries. - Also see `Log File Formats`_. + By default, fio will log an entry in the iops, latency, or bw log for + every I/O that completes. When writing to the disk log, that can + quickly grow to a very large size. Setting this option directs fio to + instead record an average over the specified duration for each log + entry, reducing the resolution of the log. When the job completes, fio + will flush any accumulated latency log data, so the final log interval + may not match the value specified by this option and there may even be + duplicate timestamps. See :option:`log_window_value` as well. Defaults + to 0, logging entries for each I/O. Also see `Log File Formats`_. .. option:: log_hist_msec=int diff --git a/fio.1 b/fio.1 index 227fcb4786..d832dba2d6 100644 --- a/fio.1 +++ b/fio.1 @@ -3765,12 +3765,14 @@ resulting in more precise time-related I/O statistics. Also see \fBlog_avg_msec\fR as well. Defaults to 1024. .TP .BI log_avg_msec \fR=\fPint -By default, fio will log an entry in the iops, latency, or bw log for every -I/O that completes. When writing to the disk log, that can quickly grow to a -very large size. Setting this option makes fio average the each log entry -over the specified period of time, reducing the resolution of the log. See -\fBlog_window_value\fR as well. Defaults to 0, logging all entries. -Also see \fBLOG FILE FORMATS\fR section. +By default, fio will log an entry in the iops, latency, or bw log for every I/O +that completes. When writing to the disk log, that can quickly grow to a very +large size. Setting this option directs fio to instead record an average over +the specified duration for each log entry, reducing the resolution of the log. +When the job completes, fio will flush any accumulated latency log data, so the +final log interval may not match the value specified by this option and there +may even be duplicate timestamps. See \fBlog_window_value\fR as well. Defaults +to 0, logging entries for each I/O. Also see \fBLOG FILE FORMATS\fR section. .TP .BI log_hist_msec \fR=\fPint Same as \fBlog_avg_msec\fR, but logs entries for completion latency From 7f6a38693b8a695f5b6d4789341c1946f77ace24 Mon Sep 17 00:00:00 2001 From: Ankit Kumar Date: Tue, 13 Feb 2024 21:03:11 +0530 Subject: [PATCH 0668/1097] engines/xnvme: allocate iovecs only if vectored I/O is enabled Signed-off-by: Ankit Kumar Reviewed-by: Jens Axboe Link: https://lore.kernel.org/r/20240213153315.134202-2-ankit.kumar@samsung.com Signed-off-by: Vincent Fu --- engines/xnvme.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/engines/xnvme.c b/engines/xnvme.c index 2a0b3520bd..88bbba4e26 100644 --- a/engines/xnvme.c +++ b/engines/xnvme.c @@ -325,6 +325,7 @@ static int _dev_open(struct thread_data *td, struct fio_file *f) static int xnvme_fioe_init(struct thread_data *td) { struct xnvme_fioe_data *xd = NULL; + struct xnvme_fioe_options *o = td->eo; struct fio_file *f; unsigned int i; @@ -347,12 +348,14 @@ static int xnvme_fioe_init(struct thread_data *td) return 1; } - xd->iovec = calloc(td->o.iodepth, sizeof(*xd->iovec)); - if (!xd->iovec) { - free(xd->iocq); - free(xd); - log_err("ioeng->init(): !calloc(xd->iovec), err(%d)\n", errno); - return 1; + if (o->xnvme_iovec) { + xd->iovec = calloc(td->o.iodepth, sizeof(*xd->iovec)); + if (!xd->iovec) { + free(xd->iocq); + free(xd); + log_err("ioeng->init(): !calloc(xd->iovec), err(%d)\n", errno); + return 1; + } } xd->prev = -1; From be5514e3827db9e1a34009a38d51b9df75fa9d03 Mon Sep 17 00:00:00 2001 From: Ankit Kumar Date: Tue, 13 Feb 2024 21:03:12 +0530 Subject: [PATCH 0669/1097] engines/xnvme: add support for metadata This enables support for separate metadata buffers with xnvme ioengine. This is done by providing xnvme specific option md_per_io_size, which for the sake of consistency is the same option used by io_uring_cmd engine and SPDK's external ioengine. Bump up the required xnvme support to v0.7.4 Signed-off-by: Ankit Kumar Reviewed-by: Jens Axboe Link: https://lore.kernel.org/r/20240213153315.134202-3-ankit.kumar@samsung.com Signed-off-by: Vincent Fu --- HOWTO.rst | 2 +- configure | 2 +- engines/xnvme.c | 122 +++++++++++++++++++++++++++++++++++++++++++----- fio.1 | 2 +- 4 files changed, 114 insertions(+), 14 deletions(-) diff --git a/HOWTO.rst b/HOWTO.rst index e5a5be0376..04b055d948 100644 --- a/HOWTO.rst +++ b/HOWTO.rst @@ -2491,7 +2491,7 @@ with the caveat that when used on the command line, they must come after the want fio to use placement identifier only at indices 0, 2 and 5 specify ``fdp_pli=0,2,5``. -.. option:: md_per_io_size=int : [io_uring_cmd] +.. option:: md_per_io_size=int : [io_uring_cmd] [xnvme] Size in bytes for separate metadata buffer per IO. Default: 0. diff --git a/configure b/configure index becb193ec8..3eef022b90 100755 --- a/configure +++ b/configure @@ -2697,7 +2697,7 @@ fi ########################################## # Check if we have xnvme if test "$xnvme" != "no" ; then - if check_min_lib_version xnvme 0.7.0; then + if check_min_lib_version xnvme 0.7.4; then xnvme="yes" xnvme_cflags=$(pkg-config --cflags xnvme) xnvme_libs=$(pkg-config --libs xnvme) diff --git a/engines/xnvme.c b/engines/xnvme.c index 88bbba4e26..da32678dd9 100644 --- a/engines/xnvme.c +++ b/engines/xnvme.c @@ -30,8 +30,10 @@ struct xnvme_fioe_fwrap { uint32_t ssw; uint32_t lba_nbytes; + uint32_t md_nbytes; + uint32_t lba_pow2; - uint8_t _pad[24]; + uint8_t _pad[16]; }; XNVME_STATIC_ASSERT(sizeof(struct xnvme_fioe_fwrap) == 64, "Incorrect size") @@ -58,19 +60,24 @@ struct xnvme_fioe_data { uint64_t nallocated; struct iovec *iovec; - - uint8_t _pad[8]; + struct iovec *md_iovec; struct xnvme_fioe_fwrap files[]; }; XNVME_STATIC_ASSERT(sizeof(struct xnvme_fioe_data) == 64, "Incorrect size") +struct xnvme_fioe_request { + /* Separate metadata buffer pointer */ + void *md_buf; +}; + struct xnvme_fioe_options { void *padding; unsigned int hipri; unsigned int sqpoll_thread; unsigned int xnvme_dev_nsid; unsigned int xnvme_iovec; + unsigned int md_per_io_size; char *xnvme_be; char *xnvme_mem; char *xnvme_async; @@ -171,6 +178,16 @@ static struct fio_option options[] = { .category = FIO_OPT_C_ENGINE, .group = FIO_OPT_G_XNVME, }, + { + .name = "md_per_io_size", + .lname = "Separate Metadata Buffer Size per I/O", + .type = FIO_OPT_INT, + .off1 = offsetof(struct xnvme_fioe_options, md_per_io_size), + .def = "0", + .help = "Size of separate metadata buffer per I/O (Default: 0)", + .category = FIO_OPT_C_ENGINE, + .group = FIO_OPT_G_XNVME, + }, { .name = NULL, @@ -249,6 +266,7 @@ static void xnvme_fioe_cleanup(struct thread_data *td) free(xd->iocq); free(xd->iovec); + free(xd->md_iovec); free(xd); td->io_ops_data = NULL; } @@ -297,6 +315,12 @@ static int _dev_open(struct thread_data *td, struct fio_file *f) fwrap->ssw = xnvme_dev_get_ssw(fwrap->dev); fwrap->lba_nbytes = fwrap->geo->lba_nbytes; + fwrap->md_nbytes = fwrap->geo->nbytes_oob; + + if (fwrap->geo->lba_extended) + fwrap->lba_pow2 = 0; + else + fwrap->lba_pow2 = 1; fwrap->fio_file = f; fwrap->fio_file->filetype = FIO_TYPE_BLOCK; @@ -358,6 +382,17 @@ static int xnvme_fioe_init(struct thread_data *td) } } + if (o->xnvme_iovec && o->md_per_io_size) { + xd->md_iovec = calloc(td->o.iodepth, sizeof(*xd->md_iovec)); + if (!xd->md_iovec) { + free(xd->iocq); + free(xd->iovec); + free(xd); + log_err("ioeng->init(): !calloc(xd->md_iovec), err(%d)\n", errno); + return 1; + } + } + xd->prev = -1; td->io_ops_data = xd; @@ -365,8 +400,8 @@ static int xnvme_fioe_init(struct thread_data *td) { if (_dev_open(td, f)) { /* - * Note: We are not freeing xd, iocq and iovec. This - * will be done as part of cleanup routine. + * Note: We are not freeing xd, iocq, iovec and md_iovec. + * This will be done as part of cleanup routine. */ log_err("ioeng->init(): failed; _dev_open(%s)\n", f->file_name); return 1; @@ -421,13 +456,61 @@ static void xnvme_fioe_iomem_free(struct thread_data *td) static int xnvme_fioe_io_u_init(struct thread_data *td, struct io_u *io_u) { + struct xnvme_fioe_request *fio_req; + struct xnvme_fioe_options *o = td->eo; + struct xnvme_fioe_data *xd = td->io_ops_data; + struct xnvme_fioe_fwrap *fwrap = &xd->files[0]; + + if (!fwrap->dev) { + log_err("ioeng->io_u_init(): failed; no dev-handle\n"); + return 1; + } + io_u->mmap_data = td->io_ops_data; + io_u->engine_data = NULL; + + fio_req = calloc(1, sizeof(*fio_req)); + if (!fio_req) { + log_err("ioeng->io_u_init(): !calloc(fio_req), err(%d)\n", errno); + return 1; + } + + if (o->md_per_io_size) { + fio_req->md_buf = xnvme_buf_alloc(fwrap->dev, o->md_per_io_size); + if (!fio_req->md_buf) { + free(fio_req); + return 1; + } + } + + io_u->engine_data = fio_req; return 0; } static void xnvme_fioe_io_u_free(struct thread_data *td, struct io_u *io_u) { + struct xnvme_fioe_data *xd = NULL; + struct xnvme_fioe_fwrap *fwrap = NULL; + struct xnvme_fioe_request *fio_req = NULL; + + if (!td->io_ops_data) + return; + + xd = td->io_ops_data; + fwrap = &xd->files[0]; + + if (!fwrap->dev) { + log_err("ioeng->io_u_free(): failed no dev-handle\n"); + return; + } + + fio_req = io_u->engine_data; + if (fio_req->md_buf) + xnvme_buf_free(fwrap->dev, fio_req->md_buf); + + free(fio_req); + io_u->mmap_data = NULL; } @@ -504,6 +587,7 @@ static enum fio_q_status xnvme_fioe_queue(struct thread_data *td, struct io_u *i struct xnvme_fioe_data *xd = td->io_ops_data; struct xnvme_fioe_fwrap *fwrap; struct xnvme_cmd_ctx *ctx; + struct xnvme_fioe_request *fio_req = io_u->engine_data; uint32_t nsid; uint64_t slba; uint16_t nlb; @@ -516,8 +600,13 @@ static enum fio_q_status xnvme_fioe_queue(struct thread_data *td, struct io_u *i fwrap = &xd->files[io_u->file->fileno]; nsid = xnvme_dev_get_nsid(fwrap->dev); - slba = io_u->offset >> fwrap->ssw; - nlb = (io_u->xfer_buflen >> fwrap->ssw) - 1; + if (fwrap->lba_pow2) { + slba = io_u->offset >> fwrap->ssw; + nlb = (io_u->xfer_buflen >> fwrap->ssw) - 1; + } else { + slba = io_u->offset / fwrap->lba_nbytes; + nlb = (io_u->xfer_buflen / fwrap->lba_nbytes) - 1; + } ctx = xnvme_queue_get_cmd_ctx(fwrap->queue); ctx->async.cb_arg = io_u; @@ -551,11 +640,22 @@ static enum fio_q_status xnvme_fioe_queue(struct thread_data *td, struct io_u *i if (vectored_io) { xd->iovec[io_u->index].iov_base = io_u->xfer_buf; xd->iovec[io_u->index].iov_len = io_u->xfer_buflen; - - err = xnvme_cmd_passv(ctx, &xd->iovec[io_u->index], 1, io_u->xfer_buflen, NULL, 0, - 0); + if (fwrap->md_nbytes && fwrap->lba_pow2) { + xd->md_iovec[io_u->index].iov_base = fio_req->md_buf; + xd->md_iovec[io_u->index].iov_len = fwrap->md_nbytes * (nlb + 1); + err = xnvme_cmd_passv(ctx, &xd->iovec[io_u->index], 1, io_u->xfer_buflen, + &xd->md_iovec[io_u->index], 1, + fwrap->md_nbytes * (nlb + 1)); + } else { + err = xnvme_cmd_passv(ctx, &xd->iovec[io_u->index], 1, io_u->xfer_buflen, + NULL, 0, 0); + } } else { - err = xnvme_cmd_pass(ctx, io_u->xfer_buf, io_u->xfer_buflen, NULL, 0); + if (fwrap->md_nbytes && fwrap->lba_pow2) + err = xnvme_cmd_pass(ctx, io_u->xfer_buf, io_u->xfer_buflen, + fio_req->md_buf, fwrap->md_nbytes * (nlb + 1)); + else + err = xnvme_cmd_pass(ctx, io_u->xfer_buf, io_u->xfer_buflen, NULL, 0); } switch (err) { case 0: diff --git a/fio.1 b/fio.1 index d832dba2d6..437fbebcfc 100644 --- a/fio.1 +++ b/fio.1 @@ -2251,7 +2251,7 @@ By default, the job will cycle through all available Placement IDs, so use this to isolate these identifiers to specific jobs. If you want fio to use placement identifier only at indices 0, 2 and 5 specify, you would set `fdp_pli=0,2,5`. .TP -.BI (io_uring_cmd)md_per_io_size \fR=\fPint +.BI (io_uring_cmd,xnvme)md_per_io_size \fR=\fPint Size in bytes for separate metadata buffer per IO. Default: 0. .TP .BI (io_uring_cmd)pi_act \fR=\fPint From 90ec1ecc1dabf46c8b3b931cc809717cdf2b3678 Mon Sep 17 00:00:00 2001 From: Ankit Kumar Date: Tue, 13 Feb 2024 21:03:13 +0530 Subject: [PATCH 0670/1097] engines:xnvme: add support for end to end data protection This patch enables support for protection information to xnvme ioengine. This adds 4 new ioengine specific options * pi_act - Protection information action. Default: 1 * pi_chk - Can be set to GUARD, APPTAG or REFTAG * apptag - Sets apptag field of command dword 15 * apptag_mask - Sets apptag_mask field of command dword 15 For the sake of consistency these options are the same as the ones used by io_uring_cmd ioengine and SPDK's external ioengine. Signed-off-by: Ankit Kumar Reviewed-by: Jens Axboe Link: https://lore.kernel.org/r/20240213153315.134202-4-ankit.kumar@samsung.com Signed-off-by: Vincent Fu --- HOWTO.rst | 8 +-- engines/xnvme.c | 145 ++++++++++++++++++++++++++++++++++++++++++++++++ fio.1 | 8 +-- 3 files changed, 153 insertions(+), 8 deletions(-) diff --git a/HOWTO.rst b/HOWTO.rst index 04b055d948..5bc1713cfd 100644 --- a/HOWTO.rst +++ b/HOWTO.rst @@ -2495,7 +2495,7 @@ with the caveat that when used on the command line, they must come after the Size in bytes for separate metadata buffer per IO. Default: 0. -.. option:: pi_act=int : [io_uring_cmd] +.. option:: pi_act=int : [io_uring_cmd] [xnvme] Action to take when nvme namespace is formatted with protection information. If this is set to 1 and namespace is formatted with @@ -2511,7 +2511,7 @@ with the caveat that when used on the command line, they must come after the it will use the default slower generator. (see: https://github.com/intel/isa-l) -.. option:: pi_chk=str[,str][,str] : [io_uring_cmd] +.. option:: pi_chk=str[,str][,str] : [io_uring_cmd] [xnvme] Controls the protection information check. This can take one or more of these values. Default: none. @@ -2524,12 +2524,12 @@ with the caveat that when used on the command line, they must come after the **APPTAG** Enables protection information checking of application tag field. -.. option:: apptag=int : [io_uring_cmd] +.. option:: apptag=int : [io_uring_cmd] [xnvme] Specifies logical block application tag value, if namespace is formatted to use end to end protection information. Default: 0x1234. -.. option:: apptag_mask=int : [io_uring_cmd] +.. option:: apptag_mask=int : [io_uring_cmd] [xnvme] Specifies logical block application tag mask value, if namespace is formatted to use end to end protection information. Default: 0xffff. diff --git a/engines/xnvme.c b/engines/xnvme.c index da32678dd9..c726b8058c 100644 --- a/engines/xnvme.c +++ b/engines/xnvme.c @@ -67,6 +67,9 @@ struct xnvme_fioe_data { XNVME_STATIC_ASSERT(sizeof(struct xnvme_fioe_data) == 64, "Incorrect size") struct xnvme_fioe_request { + /* Context for NVMe PI */ + struct xnvme_pi_ctx pi_ctx; + /* Separate metadata buffer pointer */ void *md_buf; }; @@ -78,6 +81,10 @@ struct xnvme_fioe_options { unsigned int xnvme_dev_nsid; unsigned int xnvme_iovec; unsigned int md_per_io_size; + unsigned int pi_act; + unsigned int apptag; + unsigned int apptag_mask; + unsigned int prchk; char *xnvme_be; char *xnvme_mem; char *xnvme_async; @@ -86,6 +93,20 @@ struct xnvme_fioe_options { char *xnvme_dev_subnqn; }; +static int str_pi_chk_cb(void *data, const char *str) +{ + struct xnvme_fioe_options *o = data; + + if (strstr(str, "GUARD") != NULL) + o->prchk = XNVME_PI_FLAGS_GUARD_CHECK; + if (strstr(str, "REFTAG") != NULL) + o->prchk |= XNVME_PI_FLAGS_REFTAG_CHECK; + if (strstr(str, "APPTAG") != NULL) + o->prchk |= XNVME_PI_FLAGS_APPTAG_CHECK; + + return 0; +} + static struct fio_option options[] = { { .name = "hipri", @@ -188,6 +209,46 @@ static struct fio_option options[] = { .category = FIO_OPT_C_ENGINE, .group = FIO_OPT_G_XNVME, }, + { + .name = "pi_act", + .lname = "Protection Information Action", + .type = FIO_OPT_BOOL, + .off1 = offsetof(struct xnvme_fioe_options, pi_act), + .def = "1", + .help = "Protection Information Action bit (pi_act=1 or pi_act=0)", + .category = FIO_OPT_C_ENGINE, + .group = FIO_OPT_G_XNVME, + }, + { + .name = "pi_chk", + .lname = "Protection Information Check", + .type = FIO_OPT_STR_STORE, + .def = NULL, + .help = "Control of Protection Information Checking (pi_chk=GUARD,REFTAG,APPTAG)", + .cb = str_pi_chk_cb, + .category = FIO_OPT_C_ENGINE, + .group = FIO_OPT_G_XNVME, + }, + { + .name = "apptag", + .lname = "Application Tag used in Protection Information", + .type = FIO_OPT_INT, + .off1 = offsetof(struct xnvme_fioe_options, apptag), + .def = "0x1234", + .help = "Application Tag used in Protection Information field (Default: 0x1234)", + .category = FIO_OPT_C_ENGINE, + .group = FIO_OPT_G_XNVME, + }, + { + .name = "apptag_mask", + .lname = "Application Tag Mask", + .type = FIO_OPT_INT, + .off1 = offsetof(struct xnvme_fioe_options, apptag_mask), + .def = "0xffff", + .help = "Application Tag Mask used with Application Tag (Default: 0xffff)", + .category = FIO_OPT_C_ENGINE, + .group = FIO_OPT_G_XNVME, + }, { .name = NULL, @@ -198,6 +259,10 @@ static void cb_pool(struct xnvme_cmd_ctx *ctx, void *cb_arg) { struct io_u *io_u = cb_arg; struct xnvme_fioe_data *xd = io_u->mmap_data; + struct xnvme_fioe_request *fio_req = io_u->engine_data; + struct xnvme_fioe_fwrap *fwrap = &xd->files[io_u->file->fileno]; + bool pi_act = (fio_req->pi_ctx.pi_flags >> 3); + int err; if (xnvme_cmd_ctx_cpl_status(ctx)) { xnvme_cmd_ctx_pr(ctx, XNVME_PR_DEF); @@ -205,6 +270,15 @@ static void cb_pool(struct xnvme_cmd_ctx *ctx, void *cb_arg) io_u->error = EIO; } + if (!io_u->error && fwrap->geo->pi_type && (io_u->ddir == DDIR_READ) && !pi_act) { + err = xnvme_pi_verify(&fio_req->pi_ctx, io_u->xfer_buf, + fio_req->md_buf, io_u->xfer_buflen / fwrap->lba_nbytes); + if (err) { + xd->ecount += 1; + io_u->error = EIO; + } + } + xd->iocq[xd->completed++] = io_u; xnvme_queue_put_cmd_ctx(ctx->async.queue, ctx); } @@ -281,6 +355,7 @@ static void xnvme_fioe_cleanup(struct thread_data *td) static int _dev_open(struct thread_data *td, struct fio_file *f) { struct xnvme_opts opts = xnvme_opts_from_fioe(td); + struct xnvme_fioe_options *o = td->eo; struct xnvme_fioe_data *xd = td->io_ops_data; struct xnvme_fioe_fwrap *fwrap; int flags = 0; @@ -322,6 +397,20 @@ static int _dev_open(struct thread_data *td, struct fio_file *f) else fwrap->lba_pow2 = 1; + /* + * When PI action is set and PI size is equal to metadata size, the + * controller inserts/removes PI. So update the LBA data and metadata + * sizes accordingly. + */ + if (o->pi_act && fwrap->geo->pi_type && + fwrap->geo->nbytes_oob == xnvme_pi_size(fwrap->geo->pi_format)) { + if (fwrap->geo->lba_extended) { + fwrap->lba_nbytes -= fwrap->geo->nbytes_oob; + fwrap->lba_pow2 = 1; + } + fwrap->md_nbytes = 0; + } + fwrap->fio_file = f; fwrap->fio_file->filetype = FIO_TYPE_BLOCK; fwrap->fio_file->real_file_size = fwrap->geo->tbytes; @@ -585,6 +674,7 @@ static int xnvme_fioe_getevents(struct thread_data *td, unsigned int min, unsign static enum fio_q_status xnvme_fioe_queue(struct thread_data *td, struct io_u *io_u) { struct xnvme_fioe_data *xd = td->io_ops_data; + struct xnvme_fioe_options *o = td->eo; struct xnvme_fioe_fwrap *fwrap; struct xnvme_cmd_ctx *ctx; struct xnvme_fioe_request *fio_req = io_u->engine_data; @@ -637,6 +727,61 @@ static enum fio_q_status xnvme_fioe_queue(struct thread_data *td, struct io_u *i return FIO_Q_COMPLETED; } + if (fwrap->geo->pi_type && !o->pi_act) { + err = xnvme_pi_ctx_init(&fio_req->pi_ctx, fwrap->lba_nbytes, + fwrap->geo->nbytes_oob, fwrap->geo->lba_extended, + fwrap->geo->pi_loc, fwrap->geo->pi_type, + (o->pi_act << 3 | o->prchk), slba, o->apptag_mask, + o->apptag, fwrap->geo->pi_format); + if (err) { + log_err("ioeng->queue(): err: '%d'\n", err); + + xnvme_queue_put_cmd_ctx(ctx->async.queue, ctx); + + io_u->error = abs(err); + return FIO_Q_COMPLETED; + } + + if (io_u->ddir == DDIR_WRITE) + xnvme_pi_generate(&fio_req->pi_ctx, io_u->xfer_buf, fio_req->md_buf, + nlb + 1); + } + + if (fwrap->geo->pi_type) + ctx->cmd.nvm.prinfo = (o->pi_act << 3 | o->prchk); + + switch (fwrap->geo->pi_type) { + case XNVME_PI_TYPE1: + case XNVME_PI_TYPE2: + switch (fwrap->geo->pi_format) { + case XNVME_SPEC_NVM_NS_16B_GUARD: + if (o->prchk & XNVME_PI_FLAGS_REFTAG_CHECK) + ctx->cmd.nvm.ilbrt = (uint32_t)slba; + break; + case XNVME_SPEC_NVM_NS_64B_GUARD: + if (o->prchk & XNVME_PI_FLAGS_REFTAG_CHECK) { + ctx->cmd.nvm.ilbrt = (uint32_t)slba; + ctx->cmd.common.cdw03 = ((slba >> 32) & 0xffff); + } + break; + default: + break; + } + if (o->prchk & XNVME_PI_FLAGS_APPTAG_CHECK) { + ctx->cmd.nvm.lbat = o->apptag; + ctx->cmd.nvm.lbatm = o->apptag_mask; + } + break; + case XNVME_PI_TYPE3: + if (o->prchk & XNVME_PI_FLAGS_APPTAG_CHECK) { + ctx->cmd.nvm.lbat = o->apptag; + ctx->cmd.nvm.lbatm = o->apptag_mask; + } + break; + case XNVME_PI_DISABLE: + break; + } + if (vectored_io) { xd->iovec[io_u->index].iov_base = io_u->xfer_buf; xd->iovec[io_u->index].iov_len = io_u->xfer_buflen; diff --git a/fio.1 b/fio.1 index 437fbebcfc..7ec5c745a5 100644 --- a/fio.1 +++ b/fio.1 @@ -2254,7 +2254,7 @@ identifier only at indices 0, 2 and 5 specify, you would set `fdp_pli=0,2,5`. .BI (io_uring_cmd,xnvme)md_per_io_size \fR=\fPint Size in bytes for separate metadata buffer per IO. Default: 0. .TP -.BI (io_uring_cmd)pi_act \fR=\fPint +.BI (io_uring_cmd,xnvme)pi_act \fR=\fPint Action to take when nvme namespace is formatted with protection information. If this is set to 1 and namespace is formatted with metadata size equal to protection information size, fio won't use separate metadata buffer or extended @@ -2268,7 +2268,7 @@ For 16 bit CRC generation fio will use isa-l if available otherwise it will use the default slower generator. (see: https://github.com/intel/isa-l) .TP -.BI (io_uring_cmd)pi_chk \fR=\fPstr[,str][,str] +.BI (io_uring_cmd,xnvme)pi_chk \fR=\fPstr[,str][,str] Controls the protection information check. This can take one or more of these values. Default: none. .RS @@ -2285,11 +2285,11 @@ Enables protection information checking of application tag field. .RE .RE .TP -.BI (io_uring_cmd)apptag \fR=\fPint +.BI (io_uring_cmd,xnvme)apptag \fR=\fPint Specifies logical block application tag value, if namespace is formatted to use end to end protection information. Default: 0x1234. .TP -.BI (io_uring_cmd)apptag_mask \fR=\fPint +.BI (io_uring_cmd,xnvme)apptag_mask \fR=\fPint Specifies logical block application tag mask value, if namespace is formatted to use end to end protection information. Default: 0xffff. .TP From f560239d472644345aa082507b47c52f1586dbec Mon Sep 17 00:00:00 2001 From: Ankit Kumar Date: Tue, 13 Feb 2024 21:03:14 +0530 Subject: [PATCH 0671/1097] engines/xnvme: add checks for verify, block size and metadata size Add checks to ensure that the correct block size and metadata size is passed by the user. Disable normal verify checks when end to end data protection checks are enabled because of CRC conflict. Signed-off-by: Ankit Kumar Reviewed-by: Jens Axboe Link: https://lore.kernel.org/r/20240213153315.134202-5-ankit.kumar@samsung.com Signed-off-by: Vincent Fu --- engines/xnvme.c | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/engines/xnvme.c b/engines/xnvme.c index c726b8058c..a813728684 100644 --- a/engines/xnvme.c +++ b/engines/xnvme.c @@ -11,6 +11,7 @@ #include #include #include "fio.h" +#include "verify.h" #include "zbd_types.h" #include "fdp.h" #include "optgroup.h" @@ -345,6 +346,49 @@ static void xnvme_fioe_cleanup(struct thread_data *td) td->io_ops_data = NULL; } +static int _verify_options(struct thread_data *td, struct fio_file *f, + struct xnvme_fioe_fwrap *fwrap) +{ + struct xnvme_fioe_options *o = td->eo; + unsigned int correct_md_size; + + for_each_rw_ddir(ddir) { + if (td->o.min_bs[ddir] % fwrap->lba_nbytes || td->o.max_bs[ddir] % fwrap->lba_nbytes) { + if (!fwrap->lba_pow2) { + log_err("ioeng->_verify_options(%s): block size must be a multiple of %u " + "(LBA data size + Metadata size)\n", f->file_name, fwrap->lba_nbytes); + } else { + log_err("ioeng->_verify_options(%s): block size must be a multiple of LBA data size\n", + f->file_name); + } + return 1; + } + if (ddir == DDIR_TRIM) + continue; + + correct_md_size = (td->o.max_bs[ddir] / fwrap->lba_nbytes) * fwrap->md_nbytes; + if (fwrap->md_nbytes && fwrap->lba_pow2 && (o->md_per_io_size < correct_md_size)) { + log_err("ioeng->_verify_options(%s): md_per_io_size should be at least %u bytes\n", + f->file_name, correct_md_size); + return 1; + } + } + + /* + * For extended logical block sizes we cannot use verify when + * end to end data protection checks are enabled, as the PI + * section of data buffer conflicts with verify. + */ + if (fwrap->md_nbytes && fwrap->geo->pi_type && !fwrap->lba_pow2 && + td->o.verify != VERIFY_NONE) { + log_err("ioeng->_verify_options(%s): for extended LBA, verify cannot be used when E2E data protection is enabled\n", + f->file_name); + return 1; + } + + return 0; +} + /** * Helper function setting up device handles as addressed by the naming * convention of the given `fio_file` filename. @@ -411,6 +455,11 @@ static int _dev_open(struct thread_data *td, struct fio_file *f) fwrap->md_nbytes = 0; } + if (_verify_options(td, f, fwrap)) { + td_verror(td, EINVAL, "_dev_open"); + goto failure; + } + fwrap->fio_file = f; fwrap->fio_file->filetype = FIO_TYPE_BLOCK; fwrap->fio_file->real_file_size = fwrap->geo->tbytes; From a99bd37f690ab246caa9b9d65adfa65a25967190 Mon Sep 17 00:00:00 2001 From: Ankit Kumar Date: Tue, 13 Feb 2024 21:03:15 +0530 Subject: [PATCH 0672/1097] examples: add PI example with xnvme ioengine Signed-off-by: Ankit Kumar Reviewed-by: Jens Axboe Link: https://lore.kernel.org/r/20240213153315.134202-6-ankit.kumar@samsung.com Signed-off-by: Vincent Fu --- examples/xnvme-pi.fio | 53 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 examples/xnvme-pi.fio diff --git a/examples/xnvme-pi.fio b/examples/xnvme-pi.fio new file mode 100644 index 0000000000..ca8c0101ae --- /dev/null +++ b/examples/xnvme-pi.fio @@ -0,0 +1,53 @@ +; README +; +; This job-file is intended to be used either as: +; +; # Use the xNVMe io-engine engine io_uring_cmd async. impl. +; fio examples/xnvme-pi.fio \ +; --ioengine=xnvme \ +; --xnvme_async=io_uring_cmd \ +; --filename=/dev/ng0n1 +; +; # Use the xNVMe io-engine engine with nvme sync. impl. +; fio examples/xnvme-pi.fio \ +; --ioengine=xnvme \ +; --xnvme_sync=nvme \ +; --filename=/dev/ng0n1 +; +; # Use the xNVMe io-engine engine with SPDK backend, note that you have to set the Namespace-id +; fio examples/xnvme-pi.fio \ +; --ioengine=xnvme \ +; --xnvme_dev_nsid=1 \ +; --filename=0000\\:01\\:00.0 +; +; NOTE: The URI encoded in the filename above, the ":" must be escaped. +; +; On the command-line using two "\\": +; +; --filename=0000\\:01\\:00.0 +; +; Within a fio-script using a single "\": +; +; filename=0000\:01\:00.0 +; +; NOTE: This example configuration assumes that the NVMe device is formatted +; with a separate metadata buffer. If you want to run on an extended LBA format +; update the "bs" accordingly. +; +[global] +size=100M +iodepth=16 +bs=4K +md_per_io_size=64 +pi_act=0 +pi_chk=GUARD,APPTAG,REFTAG +apptag=0x0234 +apptag_mask=0xFFFF +thread=1 +stonewall=1 + +[write] +rw=write + +[read] +rw=read From 96a7e64c8967631ac565481672209ce96df02332 Mon Sep 17 00:00:00 2001 From: Shin'ichiro Kawasaki Date: Wed, 14 Feb 2024 21:20:07 +0900 Subject: [PATCH 0673/1097] verify: fix loops option behavior of read-verify workloads The commit 191d6634e8a6 ("verify: fix bytes_done accounting of experimental verify") introduced td->bytes_verified to separate the verified bytes from the read bytes in td->bytes_done[]. This fixed the issue of experimental verify feature. However, it caused another issue. When the verify workload does only read and does not do write, the read bytes in td->bytes_done[] is no longer updated and always zero. This zero value is returned from do_io() to thread_main() in the bytes_done array. If the read bytes is zero, thread_main() marks the job to terminate and it makes the loops option ignored. For example, the job below should do 8k read, but it does only 4k read. [global] filename=/tmp/fio.test size=4k verify=md5 [write] rw=write do_verify=0 [read] stonewall=1 rw=read loops=2 do_verify=1 To make the loops option work together with the read-verify workloads, modify io_u_update_bytes_done(). After updating td->bytes_verified, check if the workload does not write. If so, do not return from io_u_update_bytes_done() and update td->bytes_done[] for DDIR_READ in the following code. Fixes: 191d6634e8a6 ("verify: fix bytes_done accounting of experimental verify") Signed-off-by: Shin'ichiro Kawasaki Link: https://lore.kernel.org/r/20240214122008.4123286-2-shinichiro.kawasaki@wdc.com Signed-off-by: Jens Axboe --- io_u.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/io_u.c b/io_u.c index 131878822e..4254675acc 100644 --- a/io_u.c +++ b/io_u.c @@ -2151,7 +2151,8 @@ static void io_u_update_bytes_done(struct thread_data *td, if (td->runstate == TD_VERIFYING) { td->bytes_verified += icd->bytes_done[DDIR_READ]; - return; + if (td_write(td)) + return; } for (ddir = 0; ddir < DDIR_RWDIR_CNT; ddir++) From 7aec5ac0bdd1adcaeba707f26d5bc583de6ab6c9 Mon Sep 17 00:00:00 2001 From: Shin'ichiro Kawasaki Date: Wed, 14 Feb 2024 21:20:08 +0900 Subject: [PATCH 0674/1097] test: add the test for loops option and read-verify workloads Add t/jobs/t0029.fio to test that the loops option works together with read-verify workloads. Signed-off-by: Shin'ichiro Kawasaki Link: https://lore.kernel.org/r/20240214122008.4123286-3-shinichiro.kawasaki@wdc.com Signed-off-by: Jens Axboe --- t/jobs/t0029.fio | 14 ++++++++++++++ t/run-fio-tests.py | 21 +++++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 t/jobs/t0029.fio diff --git a/t/jobs/t0029.fio b/t/jobs/t0029.fio new file mode 100644 index 0000000000..481de6f316 --- /dev/null +++ b/t/jobs/t0029.fio @@ -0,0 +1,14 @@ +[global] +filename=t0029file +size=4k +verify=md5 + +[write] +rw=write +do_verify=0 + +[read] +stonewall=1 +rw=read +loops=2 +do_verify=1 diff --git a/t/run-fio-tests.py b/t/run-fio-tests.py index 1448f7cb9f..2f76d3fcad 100755 --- a/t/run-fio-tests.py +++ b/t/run-fio-tests.py @@ -542,6 +542,17 @@ def check_result(self): if data != self.pattern: self.passed = False +class FioJobFileTest_t0029(FioJobFileTest): + """Test loops option works with read-verify workload.""" + def check_result(self): + super().check_result() + + if not self.passed: + return + + if self.json_data['jobs'][1]['read']['io_kbytes'] != 8: + self.passed = False + class FioJobFileTest_iops_rate(FioJobFileTest): """Test consists of fio test job t0011 Confirm that job0 iops == 1000 @@ -838,6 +849,16 @@ def check_result(self): 'pre_success': None, 'requirements': [], }, + { + 'test_id': 29, + 'test_class': FioJobFileTest_t0029, + 'job': 't0029.fio', + 'success': SUCCESS_DEFAULT, + 'pre_job': None, + 'pre_success': None, + 'output_format': 'json', + 'requirements': [], + }, { 'test_id': 1000, 'test_class': FioExeTest, From b5d2b658c867917ebecc228cf7565fb5d246003d Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Thu, 15 Feb 2024 08:16:55 -0700 Subject: [PATCH 0675/1097] t/io_uring: account and ignore IO errors It's still useful to test on devices that may trigger a single or few IO errors, just log them and ignore them in the IOPS calculations. And for stats calculations. Signed-off-by: Jens Axboe --- t/io_uring.c | 38 +++++++++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/t/io_uring.c b/t/io_uring.c index efc50caad0..8fd9a4cca3 100644 --- a/t/io_uring.c +++ b/t/io_uring.c @@ -94,6 +94,7 @@ struct submitter { unsigned long reaps; unsigned long done; unsigned long calls; + unsigned long io_errors; volatile int finish; __s32 *fds; @@ -717,10 +718,14 @@ static int reap_events_uring(struct submitter *s) f = &s->files[fileno]; f->pending_ios--; if (cqe->res != bs) { - printf("io: unexpected ret=%d\n", cqe->res); - if (polled && cqe->res == -EOPNOTSUPP) - printf("Your filesystem/driver/kernel doesn't support polled IO\n"); - return -1; + if (cqe->res == -ENODATA || cqe->res == -EIO) { + s->io_errors++; + } else { + printf("io: unexpected ret=%d\n", cqe->res); + if (polled && cqe->res == -EOPNOTSUPP) + printf("Your filesystem/driver/kernel doesn't support polled IO\n"); + return -1; + } } } if (stats) { @@ -1102,10 +1107,14 @@ static int reap_events_aio(struct submitter *s, struct io_event *events, int evs f->pending_ios--; if (events[reaped].res != bs) { - printf("io: unexpected ret=%ld\n", events[reaped].res); - return -1; - } - if (stats) { + if (events[reaped].res == -ENODATA || + events[reaped].res == -EIO) { + s->io_errors++; + } else { + printf("io: unexpected ret=%ld\n", events[reaped].res); + return -1; + } + } else if (stats) { int clock_index = data >> 32; if (last_idx != clock_index) { @@ -1550,7 +1559,7 @@ static void write_tsc_rate(void) int main(int argc, char *argv[]) { struct submitter *s; - unsigned long done, calls, reap; + unsigned long done, calls, reap, io_errors; int i, j, flags, fd, opt, threads_per_f, threads_rem = 0, nfiles; struct file f; void *ret; @@ -1661,7 +1670,7 @@ int main(int argc, char *argv[]) s = get_submitter(j); s->numa_node = -1; s->index = j; - s->done = s->calls = s->reaps = 0; + s->done = s->calls = s->reaps = s->io_errors = 0; } flags = O_RDONLY | O_NOATIME; @@ -1746,11 +1755,12 @@ int main(int argc, char *argv[]) #endif } - reap = calls = done = 0; + reap = calls = done = io_errors = 0; do { unsigned long this_done = 0; unsigned long this_reap = 0; unsigned long this_call = 0; + unsigned long this_io_errors = 0; unsigned long rpc = 0, ipc = 0; unsigned long iops, bw; @@ -1771,6 +1781,7 @@ int main(int argc, char *argv[]) this_done += s->done; this_call += s->calls; this_reap += s->reaps; + this_io_errors += s->io_errors; } if (this_call - calls) { rpc = (this_done - done) / (this_call - calls); @@ -1778,6 +1789,7 @@ int main(int argc, char *argv[]) } else rpc = ipc = -1; iops = this_done - done; + iops -= this_io_errors - io_errors; if (bs > 1048576) bw = iops * (bs / 1048576); else @@ -1805,6 +1817,7 @@ int main(int argc, char *argv[]) done = this_done; calls = this_call; reap = this_reap; + io_errors = this_io_errors; } while (!finish); for (j = 0; j < nthreads; j++) { @@ -1812,6 +1825,9 @@ int main(int argc, char *argv[]) pthread_join(s->thread, &ret); close(s->ring_fd); + if (s->io_errors) + printf("%d: %lu IO errors\n", s->tid, s->io_errors); + if (stats) { unsigned long nr; From 6067863c7016faa8033f4363ba8702fa15063900 Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Thu, 15 Feb 2024 08:17:31 -0700 Subject: [PATCH 0676/1097] t/io_uring: pre-calculate per-file depth Rather than do this math every time, just calculate it at setup time and use that. Note that there's something wonky with how the depths are distributed, it's quite common to see the first file eat the majority of the queue depth, causing multiple file runs to be both slower than they should be, and with very unevenly distributed depths. To be investigated. Signed-off-by: Jens Axboe --- t/io_uring.c | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/t/io_uring.c b/t/io_uring.c index 8fd9a4cca3..46b153dcbe 100644 --- a/t/io_uring.c +++ b/t/io_uring.c @@ -110,6 +110,7 @@ struct submitter { #endif int numa_node; + int per_file_depth; const char *filename; struct file files[MAX_FDS]; @@ -491,11 +492,6 @@ static int io_uring_enter(struct submitter *s, unsigned int to_submit, #endif } -static unsigned file_depth(struct submitter *s) -{ - return (depth + s->nr_files - 1) / s->nr_files; -} - static unsigned long long get_offset(struct submitter *s, struct file *f) { unsigned long long offset; @@ -517,7 +513,7 @@ static unsigned long long get_offset(struct submitter *s, struct file *f) return offset; } -static struct file *init_new_io(struct submitter *s) +static struct file *get_next_file(struct submitter *s) { struct file *f; @@ -525,7 +521,7 @@ static struct file *init_new_io(struct submitter *s) f = &s->files[0]; } else { f = &s->files[s->cur_file]; - if (f->pending_ios >= file_depth(s)) { + if (f->pending_ios >= s->per_file_depth) { s->cur_file++; if (s->cur_file == s->nr_files) s->cur_file = 0; @@ -547,7 +543,7 @@ static void init_io(struct submitter *s, unsigned index) return; } - f = init_new_io(s); + f = get_next_file(s); if (register_files) { sqe->flags = IOSQE_FIXED_FILE; @@ -588,7 +584,7 @@ static void init_io_pt(struct submitter *s, unsigned index) unsigned long long slba; unsigned long long nlb; - f = init_new_io(s); + f = get_next_file(s); offset = get_offset(s, f); @@ -872,6 +868,7 @@ static int setup_aio(struct submitter *s) fixedbufs = register_files = 0; } + s->per_file_depth = (depth + s->nr_files - 1) / s->nr_files; return io_queue_init(roundup_pow2(depth), &s->aio_ctx); #else fprintf(stderr, "Legacy AIO not available on this system/build\n"); @@ -976,6 +973,7 @@ static int setup_ring(struct submitter *s) for (i = 0; i < p.sq_entries; i++) sring->array[i] = i; + s->per_file_depth = (depth + s->nr_files - 1) / s->nr_files; return 0; } @@ -1002,8 +1000,8 @@ static int submitter_init(struct submitter *s) static int init_printed; char buf[80]; s->tid = gettid(); - printf("submitter=%d, tid=%d, file=%s, node=%d\n", s->index, s->tid, - s->filename, s->numa_node); + printf("submitter=%d, tid=%d, file=%s, nfiles=%d, node=%d\n", s->index, s->tid, + s->filename, s->nr_files, s->numa_node); set_affinity(s); @@ -1082,7 +1080,7 @@ static int prep_more_ios_aio(struct submitter *s, int max_ios, struct iocb *iocb while (index < max_ios) { struct iocb *iocb = &iocbs[index]; - f = init_new_io(s); + f = get_next_file(s); io_prep_pread(iocb, f->real_fd, s->iovecs[index].iov_base, s->iovecs[index].iov_len, get_offset(s, f)); @@ -1388,7 +1386,7 @@ static void *submitter_sync_fn(void *data) uint64_t offset; struct file *f; - f = init_new_io(s); + f = get_next_file(s); #ifdef ARCH_HAVE_CPU_CLOCK if (stats) From b3251e31bae2c2ec7c8db0e7e7511eb03b8b0190 Mon Sep 17 00:00:00 2001 From: Ankit Kumar Date: Thu, 15 Feb 2024 20:48:10 +0530 Subject: [PATCH 0677/1097] trim: add support for multiple ranges NVMe specification allow multiple ranges for the dataset management commands. Currently the block ioctl only allows a single range for trim, however multiple ranges can be specified using nvme character device. Add an option num_range to send multiple range per trim request, which only works if the data direction is solely trim i.e. trim or randtrim. Add FIO_MULTI_RANGE_TRIM as the ioengine flag, to restrict the usage of this new option. For multi range trim request this modifies the way IO buffers are used. The buffer length will depend on number of trim ranges and the actual buffer will contains start and length of each range entry. This increases fio server version (FIO_SERVER_VER) to 103. Signed-off-by: Ankit Kumar Link: https://lore.kernel.org/r/20240215151812.138370-2-ankit.kumar@samsung.com Signed-off-by: Jens Axboe --- HOWTO.rst | 9 +++++ backend.c | 20 ++++++++-- cconv.c | 2 + fio.1 | 7 ++++ fio.h | 18 +++++++++ init.c | 13 +++++++ io_u.c | 97 ++++++++++++++++++++++++++++++++++++++++-------- io_u.h | 4 ++ ioengines.h | 2 + options.c | 11 ++++++ server.h | 2 +- thread_options.h | 3 ++ 12 files changed, 168 insertions(+), 20 deletions(-) diff --git a/HOWTO.rst b/HOWTO.rst index 5bc1713cfd..4b02100c65 100644 --- a/HOWTO.rst +++ b/HOWTO.rst @@ -2534,6 +2534,15 @@ with the caveat that when used on the command line, they must come after the Specifies logical block application tag mask value, if namespace is formatted to use end to end protection information. Default: 0xffff. +.. option:: num_range=int : [io_uring_cmd] + + For trim command this will be the number of ranges to trim per I/O + request. The number of logical blocks per range is determined by the + :option:`bs` option which should be a multiple of logical block size. + This cannot be used with read or write. Note that setting this + option > 1, :option:`log_offset` will not be able to log all the + offsets. Default: 1. + .. option:: cpuload=int : [cpuio] Attempt to use the specified percentage of CPU cycles. This is a mandatory diff --git a/backend.c b/backend.c index 1fab467a1b..2f2221bf9c 100644 --- a/backend.c +++ b/backend.c @@ -1333,7 +1333,7 @@ static int init_io_u(struct thread_data *td) int init_io_u_buffers(struct thread_data *td) { struct io_u *io_u; - unsigned long long max_bs, min_write; + unsigned long long max_bs, min_write, trim_bs = 0; int i, max_units; int data_xfer = 1; char *p; @@ -1344,7 +1344,18 @@ int init_io_u_buffers(struct thread_data *td) td->orig_buffer_size = (unsigned long long) max_bs * (unsigned long long) max_units; - if (td_ioengine_flagged(td, FIO_NOIO) || !(td_read(td) || td_write(td))) + if (td_trim(td) && td->o.num_range > 1) { + trim_bs = td->o.num_range * sizeof(struct trim_range); + td->orig_buffer_size = trim_bs + * (unsigned long long) max_units; + } + + /* + * For reads, writes, and multi-range trim operations we need a + * data buffer + */ + if (td_ioengine_flagged(td, FIO_NOIO) || + !(td_read(td) || td_write(td) || (td_trim(td) && td->o.num_range > 1))) data_xfer = 0; /* @@ -1396,7 +1407,10 @@ int init_io_u_buffers(struct thread_data *td) fill_verify_pattern(td, io_u->buf, max_bs, io_u, 0, 0); } } - p += max_bs; + if (td_trim(td) && td->o.num_range > 1) + p += trim_bs; + else + p += max_bs; } return 0; diff --git a/cconv.c b/cconv.c index c92984081b..ead472480b 100644 --- a/cconv.c +++ b/cconv.c @@ -111,6 +111,7 @@ int convert_thread_options_to_cpu(struct thread_options *o, o->serialize_overlap = le32_to_cpu(top->serialize_overlap); o->size = le64_to_cpu(top->size); o->io_size = le64_to_cpu(top->io_size); + o->num_range = le32_to_cpu(top->num_range); o->size_percent = le32_to_cpu(top->size_percent); o->io_size_percent = le32_to_cpu(top->io_size_percent); o->fill_device = le32_to_cpu(top->fill_device); @@ -609,6 +610,7 @@ void convert_thread_options_to_net(struct thread_options_pack *top, top->size = __cpu_to_le64(o->size); top->io_size = __cpu_to_le64(o->io_size); + top->num_range = __cpu_to_le32(o->num_range); top->verify_backlog = __cpu_to_le64(o->verify_backlog); top->start_delay = __cpu_to_le64(o->start_delay); top->start_delay_high = __cpu_to_le64(o->start_delay_high); diff --git a/fio.1 b/fio.1 index 7ec5c745a5..e6b291a76c 100644 --- a/fio.1 +++ b/fio.1 @@ -2293,6 +2293,13 @@ end to end protection information. Default: 0x1234. Specifies logical block application tag mask value, if namespace is formatted to use end to end protection information. Default: 0xffff. .TP +.BI (io_uring_cmd)num_range \fR=\fPint +For trim command this will be the number of ranges to trim per I/O request. +The number of logical blocks per range is determined by the \fBbs\fR option +which should be a multiple of logical block size. This cannot be used with +read or write. Note that setting this option > 1, \fBlog_offset\fR will not be +able to log all the offsets. Default: 1. +.TP .BI (cpuio)cpuload \fR=\fPint Attempt to use the specified percentage of CPU cycles. This is a mandatory option when using cpuio I/O engine. diff --git a/fio.h b/fio.h index 1322656fd4..fc3e3ececa 100644 --- a/fio.h +++ b/fio.h @@ -71,6 +71,16 @@ struct fio_sem; +#define MAX_TRIM_RANGE 256 + +/* + * Range for trim command + */ +struct trim_range { + unsigned long long start; + unsigned long long len; +}; + /* * offset generator types */ @@ -609,6 +619,14 @@ static inline void fio_ro_check(const struct thread_data *td, struct io_u *io_u) !(io_u->ddir == DDIR_TRIM && !td_trim(td))); } +static inline bool multi_range_trim(struct thread_data *td, struct io_u *io_u) +{ + if (io_u->ddir == DDIR_TRIM && td->o.num_range > 1) + return true; + + return false; +} + static inline bool should_fsync(struct thread_data *td) { if (td->last_was_sync) diff --git a/init.c b/init.c index 105339fa28..7a0b14a3d2 100644 --- a/init.c +++ b/init.c @@ -618,6 +618,19 @@ static int fixup_options(struct thread_data *td) ret |= 1; } + if (td_trimwrite(td) && o->num_range > 1) { + log_err("fio: trimwrite cannot be used with multiple" + " ranges.\n"); + ret |= 1; + } + + if (td_trim(td) && o->num_range > 1 && + !td_ioengine_flagged(td, FIO_MULTI_RANGE_TRIM)) { + log_err("fio: can't use multiple ranges with IO engine %s\n", + td->io_ops->name); + ret |= 1; + } + #ifndef CONFIG_PSHARED if (!o->use_thread) { log_info("fio: this platform does not support process shared" diff --git a/io_u.c b/io_u.c index 4254675acc..2b8e17f843 100644 --- a/io_u.c +++ b/io_u.c @@ -940,6 +940,65 @@ static void setup_strided_zone_mode(struct thread_data *td, struct io_u *io_u) fio_file_reset(td, f); } +static int fill_multi_range_io_u(struct thread_data *td, struct io_u *io_u) +{ + bool is_random; + uint64_t buflen, i = 0; + struct trim_range *range; + struct fio_file *f = io_u->file; + uint8_t *buf; + + buf = io_u->buf; + buflen = 0; + + while (i < td->o.num_range) { + range = (struct trim_range *)buf; + if (get_next_offset(td, io_u, &is_random)) { + dprint(FD_IO, "io_u %p, failed getting offset\n", + io_u); + break; + } + + io_u->buflen = get_next_buflen(td, io_u, is_random); + if (!io_u->buflen) { + dprint(FD_IO, "io_u %p, failed getting buflen\n", io_u); + break; + } + + if (io_u->offset + io_u->buflen > io_u->file->real_file_size) { + dprint(FD_IO, "io_u %p, off=0x%llx + len=0x%llx exceeds file size=0x%llx\n", + io_u, + (unsigned long long) io_u->offset, io_u->buflen, + (unsigned long long) io_u->file->real_file_size); + break; + } + + range->start = io_u->offset; + range->len = io_u->buflen; + buflen += io_u->buflen; + f->last_start[io_u->ddir] = io_u->offset; + f->last_pos[io_u->ddir] = io_u->offset + range->len; + + buf += sizeof(struct trim_range); + i++; + + if (td_random(td) && file_randommap(td, io_u->file)) + mark_random_map(td, io_u, io_u->offset, io_u->buflen); + dprint_io_u(io_u, "fill"); + } + if (buflen) { + /* + * Set buffer length as overall trim length for this IO, and + * tell the ioengine about the number of ranges to be trimmed. + */ + io_u->buflen = buflen; + io_u->number_trim = i; + return 0; + } + + return 1; +} + static int fill_io_u(struct thread_data *td, struct io_u *io_u) { bool is_random; @@ -966,22 +1025,27 @@ static int fill_io_u(struct thread_data *td, struct io_u *io_u) else if (td->o.zone_mode == ZONE_MODE_ZBD) setup_zbd_zone_mode(td, io_u); - /* - * No log, let the seq/rand engine retrieve the next buflen and - * position. - */ - if (get_next_offset(td, io_u, &is_random)) { - dprint(FD_IO, "io_u %p, failed getting offset\n", io_u); - return 1; - } + if (multi_range_trim(td, io_u)) { + if (fill_multi_range_io_u(td, io_u)) + return 1; + } else { + /* + * No log, let the seq/rand engine retrieve the next buflen and + * position. + */ + if (get_next_offset(td, io_u, &is_random)) { + dprint(FD_IO, "io_u %p, failed getting offset\n", io_u); + return 1; + } - io_u->buflen = get_next_buflen(td, io_u, is_random); - if (!io_u->buflen) { - dprint(FD_IO, "io_u %p, failed getting buflen\n", io_u); - return 1; + io_u->buflen = get_next_buflen(td, io_u, is_random); + if (!io_u->buflen) { + dprint(FD_IO, "io_u %p, failed getting buflen\n", io_u); + return 1; + } } - offset = io_u->offset; + if (td->o.zone_mode == ZONE_MODE_ZBD) { ret = zbd_adjust_block(td, io_u); if (ret == io_u_eof) { @@ -1004,11 +1068,12 @@ static int fill_io_u(struct thread_data *td, struct io_u *io_u) /* * mark entry before potentially trimming io_u */ - if (td_random(td) && file_randommap(td, io_u->file)) + if (!multi_range_trim(td, io_u) && td_random(td) && file_randommap(td, io_u->file)) io_u->buflen = mark_random_map(td, io_u, offset, io_u->buflen); out: - dprint_io_u(io_u, "fill"); + if (!multi_range_trim(td, io_u)) + dprint_io_u(io_u, "fill"); io_u->verify_offset = io_u->offset; td->zone_bytes += io_u->buflen; return 0; @@ -1814,7 +1879,7 @@ struct io_u *get_io_u(struct thread_data *td) assert(fio_file_open(f)); - if (ddir_rw(io_u->ddir)) { + if (ddir_rw(io_u->ddir) && !multi_range_trim(td, io_u)) { if (!io_u->buflen && !td_ioengine_flagged(td, FIO_NOIO)) { dprint(FD_IO, "get_io_u: zero buflen on %p\n", io_u); goto err_put; diff --git a/io_u.h b/io_u.h index 786251d5be..cfacf310e1 100644 --- a/io_u.h +++ b/io_u.h @@ -80,6 +80,10 @@ struct io_u { struct io_piece *ipo; + /* + * number of trim ranges for this IO. + */ + unsigned int number_trim; unsigned long long resid; unsigned int error; diff --git a/ioengines.h b/ioengines.h index 4391b31e3c..2fd7f52ca7 100644 --- a/ioengines.h +++ b/ioengines.h @@ -97,6 +97,8 @@ enum fio_ioengine_flags { FIO_RO_NEEDS_RW_OPEN = 1 << 18, /* open files in rw mode even if we have a read job; only affects ioengines using generic_open_file */ + FIO_MULTI_RANGE_TRIM + = 1 << 19, /* ioengine supports trim with more than one range */ }; /* diff --git a/options.c b/options.c index 1da4de7815..25e042d0b7 100644 --- a/options.c +++ b/options.c @@ -2395,6 +2395,17 @@ struct fio_option fio_options[FIO_MAX_OPTS] = { .category = FIO_OPT_C_IO, .group = FIO_OPT_G_INVALID, }, + { + .name = "num_range", + .lname = "Number of ranges", + .type = FIO_OPT_INT, + .off1 = offsetof(struct thread_options, num_range), + .maxval = MAX_TRIM_RANGE, + .help = "Number of ranges for trim command", + .def = "1", + .category = FIO_OPT_C_IO, + .group = FIO_OPT_G_INVALID, + }, { .name = "bs", .lname = "Block size", diff --git a/server.h b/server.h index 0eb594ce85..6d2659b05a 100644 --- a/server.h +++ b/server.h @@ -51,7 +51,7 @@ struct fio_net_cmd_reply { }; enum { - FIO_SERVER_VER = 102, + FIO_SERVER_VER = 103, FIO_SERVER_MAX_FRAGMENT_PDU = 1024, FIO_SERVER_MAX_CMD_MB = 2048, diff --git a/thread_options.h b/thread_options.h index 24f695fe1a..c2e71518b3 100644 --- a/thread_options.h +++ b/thread_options.h @@ -353,6 +353,8 @@ struct thread_options { unsigned long long offset_increment; unsigned long long number_ios; + unsigned int num_range; + unsigned int sync_file_range; unsigned long long latency_target; @@ -711,6 +713,7 @@ struct thread_options_pack { uint32_t fdp_plis[FIO_MAX_PLIS]; uint32_t fdp_nrpli; + uint32_t num_range; /* * verify_pattern followed by buffer_pattern from the unpacked struct */ From 0d610785a8072eba146dd05f2928a1a89da49bd5 Mon Sep 17 00:00:00 2001 From: Ankit Kumar Date: Thu, 15 Feb 2024 20:48:11 +0530 Subject: [PATCH 0678/1097] engines/nvme: pass offset and len instead of io_u Remove io_u dependency while calculating start and number of lba. This is a prep patch for multi-range trim support where an io_u entry will contain multiple start offset and buffer len. Signed-off-by: Ankit Kumar Link: https://lore.kernel.org/r/20240215151812.138370-3-ankit.kumar@samsung.com Signed-off-by: Jens Axboe --- engines/nvme.c | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/engines/nvme.c b/engines/nvme.c index 75a5e0c121..1933b9c812 100644 --- a/engines/nvme.c +++ b/engines/nvme.c @@ -8,20 +8,20 @@ #include "../crc/crc-t10dif.h" #include "../crc/crc64.h" -static inline __u64 get_slba(struct nvme_data *data, struct io_u *io_u) +static inline __u64 get_slba(struct nvme_data *data, __u64 offset) { if (data->lba_ext) - return io_u->offset / data->lba_ext; - else - return io_u->offset >> data->lba_shift; + return offset / data->lba_ext; + + return offset >> data->lba_shift; } -static inline __u32 get_nlb(struct nvme_data *data, struct io_u *io_u) +static inline __u32 get_nlb(struct nvme_data *data, __u64 len) { if (data->lba_ext) - return io_u->xfer_buflen / data->lba_ext - 1; - else - return (io_u->xfer_buflen >> data->lba_shift) - 1; + return len / data->lba_ext - 1; + + return (len >> data->lba_shift) - 1; } static void fio_nvme_generate_pi_16b_guard(struct nvme_data *data, @@ -32,8 +32,8 @@ static void fio_nvme_generate_pi_16b_guard(struct nvme_data *data, struct nvme_16b_guard_pif *pi; unsigned char *buf = io_u->xfer_buf; unsigned char *md_buf = io_u->mmap_data; - __u64 slba = get_slba(data, io_u); - __u32 nlb = get_nlb(data, io_u) + 1; + __u64 slba = get_slba(data, io_u->offset); + __u32 nlb = get_nlb(data, io_u->xfer_buflen) + 1; __u32 lba_num = 0; __u16 guard = 0; @@ -99,8 +99,8 @@ static int fio_nvme_verify_pi_16b_guard(struct nvme_data *data, struct fio_file *f = io_u->file; unsigned char *buf = io_u->xfer_buf; unsigned char *md_buf = io_u->mmap_data; - __u64 slba = get_slba(data, io_u); - __u32 nlb = get_nlb(data, io_u) + 1; + __u64 slba = get_slba(data, io_u->offset); + __u32 nlb = get_nlb(data, io_u->xfer_buflen) + 1; __u32 lba_num = 0; __u16 unmask_app, unmask_app_exp, guard = 0; @@ -185,8 +185,8 @@ static void fio_nvme_generate_pi_64b_guard(struct nvme_data *data, unsigned char *buf = io_u->xfer_buf; unsigned char *md_buf = io_u->mmap_data; uint64_t guard = 0; - __u64 slba = get_slba(data, io_u); - __u32 nlb = get_nlb(data, io_u) + 1; + __u64 slba = get_slba(data, io_u->offset); + __u32 nlb = get_nlb(data, io_u->xfer_buflen) + 1; __u32 lba_num = 0; if (data->pi_loc) { @@ -251,9 +251,9 @@ static int fio_nvme_verify_pi_64b_guard(struct nvme_data *data, struct fio_file *f = io_u->file; unsigned char *buf = io_u->xfer_buf; unsigned char *md_buf = io_u->mmap_data; - __u64 slba = get_slba(data, io_u); + __u64 slba = get_slba(data, io_u->offset); __u64 ref, ref_exp, guard = 0; - __u32 nlb = get_nlb(data, io_u) + 1; + __u32 nlb = get_nlb(data, io_u->xfer_buflen) + 1; __u32 lba_num = 0; __u16 unmask_app, unmask_app_exp; @@ -368,8 +368,8 @@ int fio_nvme_uring_cmd_prep(struct nvme_uring_cmd *cmd, struct io_u *io_u, return -ENOTSUP; } - slba = get_slba(data, io_u); - nlb = get_nlb(data, io_u); + slba = get_slba(data, io_u->offset); + nlb = get_nlb(data, io_u->xfer_buflen); /* cdw10 and cdw11 represent starting lba */ cmd->cdw10 = slba & 0xffffffff; @@ -400,7 +400,7 @@ void fio_nvme_pi_fill(struct nvme_uring_cmd *cmd, struct io_u *io_u, struct nvme_data *data = FILE_ENG_DATA(io_u->file); __u64 slba; - slba = get_slba(data, io_u); + slba = get_slba(data, io_u->offset); cmd->cdw12 |= opts->io_flags; if (data->pi_type && !(opts->io_flags & NVME_IO_PRINFO_PRACT)) { From 5d4ee0de9d6afaa74c7c15c3d4f210a06d4e2139 Mon Sep 17 00:00:00 2001 From: Ankit Kumar Date: Thu, 15 Feb 2024 20:48:12 +0530 Subject: [PATCH 0679/1097] engines/io_uring: add multi range dsm support Update the io_uring_cmd ioengine to support multiple ranges for trim. This includes allocating buffer for multiple ranges, and changes to the nvme trim helper functions. Add an example on how to use multi range trim. Signed-off-by: Ankit Kumar Link: https://lore.kernel.org/r/20240215151812.138370-4-ankit.kumar@samsung.com Signed-off-by: Jens Axboe --- engines/io_uring.c | 34 ++++++++++++++++++++----- engines/nvme.c | 34 ++++++++++++++++++------- engines/nvme.h | 7 ++++- examples/uring-cmd-trim-multi-range.fio | 21 +++++++++++++++ 4 files changed, 79 insertions(+), 17 deletions(-) create mode 100644 examples/uring-cmd-trim-multi-range.fio diff --git a/engines/io_uring.c b/engines/io_uring.c index c0cb5a78f7..9069fa3e81 100644 --- a/engines/io_uring.c +++ b/engines/io_uring.c @@ -81,7 +81,7 @@ struct ioring_data { struct cmdprio cmdprio; - struct nvme_dsm_range *dsm; + struct nvme_dsm *dsm; }; struct ioring_options { @@ -385,6 +385,9 @@ static int fio_ioring_cmd_prep(struct thread_data *td, struct io_u *io_u) struct fio_file *f = io_u->file; struct nvme_uring_cmd *cmd; struct io_uring_sqe *sqe; + struct nvme_dsm *dsm; + void *ptr = ld->dsm; + unsigned int dsm_size; /* only supports nvme_uring_cmd */ if (o->cmd_type != FIO_URING_CMD_NVME) @@ -423,9 +426,13 @@ static int fio_ioring_cmd_prep(struct thread_data *td, struct io_u *io_u) } cmd = (struct nvme_uring_cmd *)sqe->cmd; + dsm_size = sizeof(*ld->dsm) + td->o.num_range * sizeof(struct nvme_dsm_range); + ptr += io_u->index * dsm_size; + dsm = (struct nvme_dsm *)ptr; + return fio_nvme_uring_cmd_prep(cmd, io_u, o->nonvectored ? NULL : &ld->iovecs[io_u->index], - &ld->dsm[io_u->index]); + dsm); } static struct io_u *fio_ioring_event(struct thread_data *td, int event) @@ -1133,8 +1140,11 @@ static int fio_ioring_init(struct thread_data *td) { struct ioring_options *o = td->eo; struct ioring_data *ld; + struct nvme_dsm *dsm; + void *ptr; + unsigned int dsm_size; unsigned long long md_size; - int ret; + int ret, i; /* sqthread submission requires registered files */ if (o->sqpoll_thread) @@ -1195,10 +1205,19 @@ static int fio_ioring_init(struct thread_data *td) * in zbd mode where trim means zone reset. */ if (!strcmp(td->io_ops->name, "io_uring_cmd") && td_trim(td) && - td->o.zone_mode == ZONE_MODE_ZBD) + td->o.zone_mode == ZONE_MODE_ZBD) { td->io_ops->flags |= FIO_ASYNCIO_SYNC_TRIM; - else - ld->dsm = calloc(td->o.iodepth, sizeof(*ld->dsm)); + } else { + dsm_size = sizeof(*ld->dsm) + + td->o.num_range * sizeof(struct nvme_dsm_range); + ld->dsm = calloc(td->o.iodepth, dsm_size); + ptr = ld->dsm; + for (i = 0; i < td->o.iodepth; i++) { + dsm = (struct nvme_dsm *)ptr; + dsm->nr_ranges = td->o.num_range; + ptr += dsm_size; + } + } return 0; } @@ -1466,7 +1485,8 @@ static struct ioengine_ops ioengine_uring_cmd = { .name = "io_uring_cmd", .version = FIO_IOOPS_VERSION, .flags = FIO_NO_OFFLOAD | FIO_MEMALIGN | FIO_RAWIO | - FIO_ASYNCIO_SETS_ISSUE_TIME, + FIO_ASYNCIO_SETS_ISSUE_TIME | + FIO_MULTI_RANGE_TRIM, .init = fio_ioring_init, .post_init = fio_ioring_cmd_post_init, .io_u_init = fio_ioring_io_u_init, diff --git a/engines/nvme.c b/engines/nvme.c index 1933b9c812..c6629e8644 100644 --- a/engines/nvme.c +++ b/engines/nvme.c @@ -329,24 +329,40 @@ static int fio_nvme_verify_pi_64b_guard(struct nvme_data *data, return 0; } void fio_nvme_uring_cmd_trim_prep(struct nvme_uring_cmd *cmd, struct io_u *io_u, - struct nvme_dsm_range *dsm) + struct nvme_dsm *dsm) { struct nvme_data *data = FILE_ENG_DATA(io_u->file); + struct trim_range *range; + uint8_t *buf_point; + int i; cmd->opcode = nvme_cmd_dsm; cmd->nsid = data->nsid; - cmd->cdw10 = 0; cmd->cdw11 = NVME_ATTRIBUTE_DEALLOCATE; - cmd->addr = (__u64) (uintptr_t) dsm; - cmd->data_len = sizeof(*dsm); - - dsm->slba = get_slba(data, io_u); - /* nlb is a 1-based value for deallocate */ - dsm->nlb = get_nlb(data, io_u) + 1; + cmd->addr = (__u64) (uintptr_t) (&dsm->range[0]); + + if (dsm->nr_ranges == 1) { + dsm->range[0].slba = get_slba(data, io_u->offset); + /* nlb is a 1-based value for deallocate */ + dsm->range[0].nlb = get_nlb(data, io_u->xfer_buflen) + 1; + cmd->cdw10 = 0; + cmd->data_len = sizeof(struct nvme_dsm_range); + } else { + buf_point = io_u->xfer_buf; + for (i = 0; i < io_u->number_trim; i++) { + range = (struct trim_range *)buf_point; + dsm->range[i].slba = get_slba(data, range->start); + /* nlb is a 1-based value for deallocate */ + dsm->range[i].nlb = get_nlb(data, range->len) + 1; + buf_point += sizeof(struct trim_range); + } + cmd->cdw10 = io_u->number_trim - 1; + cmd->data_len = io_u->number_trim * sizeof(struct nvme_dsm_range); + } } int fio_nvme_uring_cmd_prep(struct nvme_uring_cmd *cmd, struct io_u *io_u, - struct iovec *iov, struct nvme_dsm_range *dsm) + struct iovec *iov, struct nvme_dsm *dsm) { struct nvme_data *data = FILE_ENG_DATA(io_u->file); __u64 slba; diff --git a/engines/nvme.h b/engines/nvme.h index 792b35d830..2d5204fc01 100644 --- a/engines/nvme.h +++ b/engines/nvme.h @@ -408,6 +408,11 @@ struct nvme_dsm_range { __le64 slba; }; +struct nvme_dsm { + __u32 nr_ranges; + struct nvme_dsm_range range[]; +}; + struct nvme_cmd_ext_io_opts { __u32 io_flags; __u16 apptag; @@ -421,7 +426,7 @@ int fio_nvme_get_info(struct fio_file *f, __u64 *nlba, __u32 pi_act, struct nvme_data *data); int fio_nvme_uring_cmd_prep(struct nvme_uring_cmd *cmd, struct io_u *io_u, - struct iovec *iov, struct nvme_dsm_range *dsm); + struct iovec *iov, struct nvme_dsm *dsm); void fio_nvme_pi_fill(struct nvme_uring_cmd *cmd, struct io_u *io_u, struct nvme_cmd_ext_io_opts *opts); diff --git a/examples/uring-cmd-trim-multi-range.fio b/examples/uring-cmd-trim-multi-range.fio new file mode 100644 index 0000000000..b376481bbb --- /dev/null +++ b/examples/uring-cmd-trim-multi-range.fio @@ -0,0 +1,21 @@ +# Multi-range trim command test with io_uring_cmd I/O engine for nvme-ns +# generic character device. +# +[global] +filename=/dev/ng0n1 +ioengine=io_uring_cmd +cmd_type=nvme +size=10M +iodepth=32 +thread=1 +stonewall=1 + +[write_bs] +bs=4096 +rw=randtrim +num_range=8 + +[write_bssplit] +bssplit=4k/10:64k/50:32k/40 +rw=trim +num_range=8 From 1a41b353a54a7ec2ebab5f66bbf25747f963f79f Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Thu, 15 Feb 2024 08:39:49 -0700 Subject: [PATCH 0680/1097] io_u: move number_trim to reclaim 8 bytes in struct io_u Move it under clat_prio_index, which already has a 6 byte gap. This saves 8 bytes total in struct io_u, which is always nice. Signed-off-by: Jens Axboe --- io_u.h | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/io_u.h b/io_u.h index cfacf310e1..ab93d50f96 100644 --- a/io_u.h +++ b/io_u.h @@ -52,6 +52,11 @@ struct io_u { unsigned short ioprio; unsigned short clat_prio_index; + /* + * number of trim ranges for this IO. + */ + unsigned int number_trim; + /* * Allocated/set buffer and length */ @@ -80,10 +85,6 @@ struct io_u { struct io_piece *ipo; - /* - * number of trim ranges for this IO. - */ - unsigned int number_trim; unsigned long long resid; unsigned int error; From 66cbbb187894a3a5f142a2d6cea62d3dede08d63 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Tue, 12 Dec 2023 18:42:20 +0000 Subject: [PATCH 0681/1097] t/nvmept_trim.py: test multi-range trim This test script contains some regression tests for existing functionality and also some tests for the new feature. The multi-range trim tests basically count the number of requests submitted and make sure that they are consistent with the block sizes and number of ranges per request. Signed-off-by: Vincent Fu --- t/nvmept_trim.py | 586 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 586 insertions(+) create mode 100755 t/nvmept_trim.py diff --git a/t/nvmept_trim.py b/t/nvmept_trim.py new file mode 100755 index 0000000000..57568384f6 --- /dev/null +++ b/t/nvmept_trim.py @@ -0,0 +1,586 @@ +#!/usr/bin/env python3 +# +# Copyright 2024 Samsung Electronics Co., Ltd All Rights Reserved +# +# For conditions of distribution and use, see the accompanying COPYING file. +# +""" +# nvmept_trim.py +# +# Test fio's io_uring_cmd ioengine with NVMe pass-through dataset management +# commands that trim multiple ranges. +# +# USAGE +# see python3 nvmept_trim.py --help +# +# EXAMPLES +# python3 t/nvmept_trim.py --dut /dev/ng0n1 +# python3 t/nvmept_trim.py --dut /dev/ng1n1 -f ./fio +# +# REQUIREMENTS +# Python 3.6 +# +""" +import os +import sys +import time +import logging +import argparse +from pathlib import Path +from fiotestlib import FioJobCmdTest, run_fio_tests +from fiotestcommon import SUCCESS_NONZERO + + +class TrimTest(FioJobCmdTest): + """ + NVMe pass-through test class. Check to make sure output for selected data + direction(s) is non-zero and that zero data appears for other directions. + """ + + def setup(self, parameters): + """Setup a test.""" + + fio_args = [ + "--name=nvmept-trim", + "--ioengine=io_uring_cmd", + "--cmd_type=nvme", + f"--filename={self.fio_opts['filename']}", + f"--rw={self.fio_opts['rw']}", + f"--output={self.filenames['output']}", + f"--output-format={self.fio_opts['output-format']}", + ] + for opt in ['fixedbufs', 'nonvectored', 'force_async', 'registerfiles', + 'sqthread_poll', 'sqthread_poll_cpu', 'hipri', 'nowait', + 'time_based', 'runtime', 'verify', 'io_size', 'num_range', + 'iodepth', 'iodepth_batch', 'iodepth_batch_complete', + 'size', 'rate', 'bs', 'bssplit', 'bsrange', 'randrepeat', + 'buffer_pattern', 'verify_pattern', 'verify', 'offset']: + if opt in self.fio_opts: + option = f"--{opt}={self.fio_opts[opt]}" + fio_args.append(option) + + super().setup(fio_args) + + + def check_result(self): + + super().check_result() + + if 'rw' not in self.fio_opts or \ + not self.passed or \ + 'json' not in self.fio_opts['output-format']: + return + + job = self.json_data['jobs'][0] + + if self.fio_opts['rw'] in ['read', 'randread']: + self.passed = self.check_all_ddirs(['read'], job) + elif self.fio_opts['rw'] in ['write', 'randwrite']: + if 'verify' not in self.fio_opts: + self.passed = self.check_all_ddirs(['write'], job) + else: + self.passed = self.check_all_ddirs(['read', 'write'], job) + elif self.fio_opts['rw'] in ['trim', 'randtrim']: + self.passed = self.check_all_ddirs(['trim'], job) + elif self.fio_opts['rw'] in ['readwrite', 'randrw']: + self.passed = self.check_all_ddirs(['read', 'write'], job) + elif self.fio_opts['rw'] in ['trimwrite', 'randtrimwrite']: + self.passed = self.check_all_ddirs(['trim', 'write'], job) + else: + logging.error("Unhandled rw value %s", self.fio_opts['rw']) + self.passed = False + + if 'iodepth' in self.fio_opts: + # We will need to figure something out if any test uses an iodepth + # different from 8 + if job['iodepth_level']['8'] < 95: + logging.error("Did not achieve requested iodepth") + self.passed = False + else: + logging.debug("iodepth 8 target met %s", job['iodepth_level']['8']) + + +class RangeTrimTest(TrimTest): + """ + Multi-range trim test class. + """ + + def get_bs(self): + """Calculate block size and determine whether bs will be an average or exact.""" + + if 'bs' in self.fio_opts: + exact_size = True + bs = self.fio_opts['bs'] + elif 'bssplit' in self.fio_opts: + exact_size = False + bs = 0 + total = 0 + for split in self.fio_opts['bssplit'].split(':'): + [blocksize, share] = split.split('/') + total += int(share) + bs += int(blocksize) * int(share) / 100 + if total != 100: + logging.error("bssplit '%s' total percentage is not 100", self.fio_opts['bssplit']) + self.passed = False + else: + logging.debug("bssplit: average block size is %d", int(bs)) + # The only check we do here for bssplit is to calculate an average + # blocksize and see if the IOPS and bw are consistent + elif 'bsrange' in self.fio_opts: + exact_size = False + [minbs, maxbs] = self.fio_opts['bsrange'].split('-') + minbs = int(minbs) + maxbs = int(maxbs) + bs = int((minbs + maxbs) / 2) + logging.debug("bsrange: average block size is %d", int(bs)) + # The only check we do here for bsrange is to calculate an average + # blocksize and see if the IOPS and bw are consistent + else: + exact_size = True + bs = 4096 + + return bs, exact_size + + + def check_result(self): + """ + Make sure that the number of IO requests is consistent with the + blocksize and num_range values. In other words, if the blocksize is + 4KiB and num_range is 2, we should have 128 IO requests to trim 1MiB. + """ + # TODO Enable debug output to check the actual offsets + + super().check_result() + + if not self.passed or 'json' not in self.fio_opts['output-format']: + return + + job = self.json_data['jobs'][0]['trim'] + bs, exact_size = self.get_bs() + + # make sure bw and IOPS are consistent + bw = job['bw_bytes'] + iops = job['iops'] + runtime = job['runtime'] + + calculated = int(bw*runtime/1000) + expected = job['io_bytes'] + if abs(calculated - expected) / expected > 0.05: + logging.error("Total bytes %d from bw does not match reported total bytes %d", + calculated, expected) + self.passed = False + else: + logging.debug("Total bytes %d from bw matches reported total bytes %d", calculated, + expected) + + calculated = int(iops*runtime/1000*bs*self.fio_opts['num_range']) + if abs(calculated - expected) / expected > 0.05: + logging.error("Total bytes %d from IOPS does not match reported total bytes %d", + calculated, expected) + self.passed = False + else: + logging.debug("Total bytes %d from IOPS matches reported total bytes %d", calculated, + expected) + + if 'size' in self.fio_opts: + io_count = self.fio_opts['size'] / self.fio_opts['num_range'] / bs + if exact_size: + delta = 0.1 + else: + delta = 0.05*job['total_ios'] + + if abs(job['total_ios'] - io_count) > delta: + logging.error("Expected numbers of IOs %d does not match actual value %d", + io_count, job['total_ios']) + self.passed = False + else: + logging.debug("Expected numbers of IOs %d matches actual value %d", io_count, + job['total_ios']) + + if 'rate' in self.fio_opts: + if abs(bw - self.fio_opts['rate']) / self.fio_opts['rate'] > 0.05: + logging.error("Actual rate %f does not match expected rate %f", bw, + self.fio_opts['rate']) + self.passed = False + else: + logging.debug("Actual rate %f matches expeected rate %f", bw, self.fio_opts['rate']) + + + +TEST_LIST = [ + # The group of tests below checks existing use cases to make sure there are + # no regressions. + { + "test_id": 1, + "fio_opts": { + "rw": 'trim', + "time_based": 1, + "runtime": 3, + "output-format": "json", + }, + "test_class": TrimTest, + }, + { + "test_id": 2, + "fio_opts": { + "rw": 'randtrim', + "time_based": 1, + "runtime": 3, + "output-format": "json", + }, + "test_class": TrimTest, + }, + { + "test_id": 3, + "fio_opts": { + "rw": 'trim', + "time_based": 1, + "runtime": 3, + "iodepth": 8, + "iodepth_batch": 4, + "iodepth_batch_complete": 4, + "output-format": "json", + }, + "test_class": TrimTest, + }, + { + "test_id": 4, + "fio_opts": { + "rw": 'randtrim', + "time_based": 1, + "runtime": 3, + "iodepth": 8, + "iodepth_batch": 4, + "iodepth_batch_complete": 4, + "output-format": "json", + }, + "test_class": TrimTest, + }, + { + "test_id": 5, + "fio_opts": { + "rw": 'trimwrite', + "time_based": 1, + "runtime": 3, + "output-format": "json", + }, + "test_class": TrimTest, + }, + { + "test_id": 6, + "fio_opts": { + "rw": 'randtrimwrite', + "time_based": 1, + "runtime": 3, + "output-format": "json", + }, + "test_class": TrimTest, + }, + { + "test_id": 7, + "fio_opts": { + "rw": 'randtrim', + "time_based": 1, + "runtime": 3, + "fixedbufs": 0, + "nonvectored": 1, + "force_async": 1, + "registerfiles": 1, + "sqthread_poll": 1, + "fixedbuffs": 1, + "output-format": "json", + }, + "test_class": TrimTest, + }, + # The group of tests below try out the new functionality + { + "test_id": 100, + "fio_opts": { + "rw": 'trim', + "num_range": 2, + "size": 16*1024*1024, + "output-format": "json", + }, + "test_class": RangeTrimTest, + }, + { + "test_id": 101, + "fio_opts": { + "rw": 'randtrim', + "num_range": 2, + "size": 16*1024*1024, + "output-format": "json", + }, + "test_class": RangeTrimTest, + }, + { + "test_id": 102, + "fio_opts": { + "rw": 'randtrim', + "num_range": 256, + "size": 64*1024*1024, + "output-format": "json", + }, + "test_class": RangeTrimTest, + }, + { + "test_id": 103, + "fio_opts": { + "rw": 'trim', + "num_range": 2, + "bs": 16*1024, + "size": 32*1024*1024, + "output-format": "json", + }, + "test_class": RangeTrimTest, + }, + { + "test_id": 104, + "fio_opts": { + "rw": 'randtrim', + "num_range": 2, + "bs": 16*1024, + "size": 32*1024*1024, + "output-format": "json", + }, + "test_class": RangeTrimTest, + }, + { + "test_id": 105, + "fio_opts": { + "rw": 'randtrim', + "num_range": 2, + "bssplit": "4096/50:16384/50", + "size": 80*1024*1024, + "output-format": "json", + "randrepeat": 0, + }, + "test_class": RangeTrimTest, + }, + { + "test_id": 106, + "fio_opts": { + "rw": 'randtrim', + "num_range": 4, + "bssplit": "4096/25:8192/25:12288/25:16384/25", + "size": 80*1024*1024, + "output-format": "json", + "randrepeat": 0, + }, + "test_class": RangeTrimTest, + }, + { + "test_id": 107, + "fio_opts": { + "rw": 'randtrim', + "num_range": 4, + "bssplit": "4096/20:8192/20:12288/20:16384/20:20480/20", + "size": 72*1024*1024, + "output-format": "json", + "randrepeat": 0, + }, + "test_class": RangeTrimTest, + }, + { + "test_id": 108, + "fio_opts": { + "rw": 'randtrim', + "num_range": 2, + "bsrange": "4096-16384", + "size": 80*1024*1024, + "output-format": "json", + "randrepeat": 0, + }, + "test_class": RangeTrimTest, + }, + { + "test_id": 109, + "fio_opts": { + "rw": 'randtrim', + "num_range": 4, + "bsrange": "4096-20480", + "size": 72*1024*1024, + "output-format": "json", + "randrepeat": 0, + }, + "test_class": RangeTrimTest, + }, + { + "test_id": 110, + "fio_opts": { + "rw": 'randtrim', + "time_based": 1, + "runtime": 10, + "rate": 1024*1024, + "num_range": 2, + "output-format": "json", + }, + "test_class": RangeTrimTest, + }, + # All of the tests below should fail + # TODO check the error messages resulting from the jobs below + { + "test_id": 200, + "fio_opts": { + "rw": 'randtrimwrite', + "time_based": 1, + "runtime": 10, + "rate": 1024*1024, + "num_range": 2, + "output-format": "normal", + }, + "test_class": RangeTrimTest, + "success": SUCCESS_NONZERO, + }, + { + "test_id": 201, + "fio_opts": { + "rw": 'trimwrite', + "time_based": 1, + "runtime": 10, + "rate": 1024*1024, + "num_range": 2, + "output-format": "normal", + }, + "test_class": RangeTrimTest, + "success": SUCCESS_NONZERO, + }, + { + "test_id": 202, + "fio_opts": { + "rw": 'trim', + "time_based": 1, + "runtime": 10, + "num_range": 257, + "output-format": "normal", + }, + "test_class": RangeTrimTest, + "success": SUCCESS_NONZERO, + }, + # The sequence of jobs below constitute a single test with multiple steps + # - write a data pattern + # - verify the data pattern + # - trim the first half of the LBA space + # - verify that the trim'd LBA space no longer returns the original data pattern + # - verify that the remaining LBA space has the expected pattern + { + "test_id": 300, + "fio_opts": { + "rw": 'write', + "output-format": 'json', + "buffer_pattern": 0x0f, + "size": 256*1024*1024, + }, + "test_class": TrimTest, + }, + { + "test_id": 301, + "fio_opts": { + "rw": 'read', + "output-format": 'json', + "verify_pattern": 0x0f, + "verify": "pattern", + "size": 256*1024*1024, + }, + "test_class": TrimTest, + }, + { + "test_id": 302, + "fio_opts": { + "rw": 'randtrim', + "num_range": 8, + "output-format": 'json', + "size": 128*1024*1024, + }, + "test_class": TrimTest, + }, + # The identify namespace data structure has a DLFEAT field which specifies + # what happens when reading data from deallocated blocks. There are three + # options: + # - read behavior not reported + # - deallocated logical block returns all bytes 0x0 + # - deallocated logical block returns all bytes 0xff + # The test below merely checks that the original data pattern is not returned. + # Source: Figure 97 from + # https://nvmexpress.org/wp-content/uploads/NVM-Express-NVM-Command-Set-Specification-1.0c-2022.10.03-Ratified.pdf + { + "test_id": 303, + "fio_opts": { + "rw": 'read', + "output-format": 'json', + "verify_pattern": 0x0f, + "verify": "pattern", + "size": 128*1024*1024, + }, + "test_class": TrimTest, + "success": SUCCESS_NONZERO, + }, + { + "test_id": 304, + "fio_opts": { + "rw": 'read', + "output-format": 'json', + "verify_pattern": 0x0f, + "verify": "pattern", + "offset": 128*1024*1024, + "size": 128*1024*1024, + }, + "test_class": TrimTest, + }, +] + +def parse_args(): + """Parse command-line arguments.""" + + parser = argparse.ArgumentParser() + parser.add_argument('-d', '--debug', help='Enable debug messages', action='store_true') + parser.add_argument('-f', '--fio', help='path to file executable (e.g., ./fio)') + parser.add_argument('-a', '--artifact-root', help='artifact root directory') + parser.add_argument('-s', '--skip', nargs='+', type=int, + help='list of test(s) to skip') + parser.add_argument('-o', '--run-only', nargs='+', type=int, + help='list of test(s) to run, skipping all others') + parser.add_argument('--dut', help='target NVMe character device to test ' + '(e.g., /dev/ng0n1). WARNING: THIS IS A DESTRUCTIVE TEST', required=True) + args = parser.parse_args() + + return args + + +def main(): + """Run tests using fio's io_uring_cmd ioengine to send NVMe pass through commands.""" + + args = parse_args() + + if args.debug: + logging.basicConfig(level=logging.DEBUG) + else: + logging.basicConfig(level=logging.INFO) + + artifact_root = args.artifact_root if args.artifact_root else \ + f"nvmept-trim-test-{time.strftime('%Y%m%d-%H%M%S')}" + os.mkdir(artifact_root) + print(f"Artifact directory is {artifact_root}") + + if args.fio: + fio_path = str(Path(args.fio).absolute()) + else: + fio_path = 'fio' + print(f"fio path is {fio_path}") + + for test in TEST_LIST: + test['fio_opts']['filename'] = args.dut + + test_env = { + 'fio_path': fio_path, + 'fio_root': str(Path(__file__).absolute().parent.parent), + 'artifact_root': artifact_root, + 'basename': 'nvmept-trim', + } + + _, failed, _ = run_fio_tests(TEST_LIST, test_env, args) + sys.exit(failed) + + +if __name__ == '__main__': + main() From 2d0debb3fca7ddc4374624acb8c70fc8292d860d Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Fri, 15 Dec 2023 15:52:27 +0000 Subject: [PATCH 0682/1097] t/run-fio-tests: add t/nvmept_trim.py Add the pass-through multi-range trim tests to the automated test suite. Signed-off-by: Vincent Fu --- t/run-fio-tests.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/t/run-fio-tests.py b/t/run-fio-tests.py index 2f76d3fcad..d4742e96f8 100755 --- a/t/run-fio-tests.py +++ b/t/run-fio-tests.py @@ -981,6 +981,14 @@ def check_result(self): 'success': SUCCESS_DEFAULT, 'requirements': [Requirements.linux, Requirements.nvmecdev], }, + { + 'test_id': 1015, + 'test_class': FioExeTest, + 'exe': 't/nvmept_trim.py', + 'parameters': ['-f', '{fio_path}', '--dut', '{nvmecdev}'], + 'success': SUCCESS_DEFAULT, + 'requirements': [Requirements.linux, Requirements.nvmecdev], + }, ] From 38d01cc56366aa2fd3af42dbab522888b6359dec Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Fri, 16 Feb 2024 01:33:21 +0000 Subject: [PATCH 0683/1097] verify: fix integer sizes in verify state file nofiles and depth are 32-bit integers. So we shouldn't use 64-bit conversion functions and casts. The current code actually works fine on little-endian platforms since the conversion is a noop but this is broken on big-endian platforms. Fixes: 94a6e1bb ("Fix verify state for multiple files") Signed-off-by: Vincent Fu --- verify.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/verify.c b/verify.c index b438eed6bd..b2fede2471 100644 --- a/verify.c +++ b/verify.c @@ -1619,8 +1619,8 @@ struct all_io_list *get_all_io_list(int save_mask, size_t *sz) comps = fill_file_completions(td, s, &index); s->no_comps = cpu_to_le64((uint64_t) comps); - s->depth = cpu_to_le64((uint64_t) td->o.iodepth); - s->nofiles = cpu_to_le64((uint64_t) td->o.nr_files); + s->depth = cpu_to_le32((uint32_t) td->o.iodepth); + s->nofiles = cpu_to_le32((uint32_t) td->o.nr_files); s->numberio = cpu_to_le64((uint64_t) td->io_issues[DDIR_WRITE]); s->index = cpu_to_le64((uint64_t) __td_index); if (td->random_state.use64) { From 1a6bc51a70e450546727502d96fe3317c1d2616f Mon Sep 17 00:00:00 2001 From: Miko Larsson Date: Thu, 22 Feb 2024 12:44:54 +0100 Subject: [PATCH 0684/1097] t/io_uring: include libgen.h This fixes the build with musl + clang >=15; musl doesn't declare basename() anywhere else, and clang >=15 doesn't allow implicit declarations. Fixes: 4b9e13dc27fb (t/io_uring: support NUMA placement) Signed-off-by: Miko Larsson --- t/io_uring.c | 1 + 1 file changed, 1 insertion(+) diff --git a/t/io_uring.c b/t/io_uring.c index 46b153dcbe..6fc40cbb67 100644 --- a/t/io_uring.c +++ b/t/io_uring.c @@ -28,6 +28,7 @@ #include #include #include +#include #include "../arch/arch.h" #include "../os/os.h" From 72af98b3ea4c6e468d9bd59c840ce4fb75f9f491 Mon Sep 17 00:00:00 2001 From: Miko Larsson Date: Thu, 22 Feb 2024 13:11:27 +0100 Subject: [PATCH 0685/1097] t/io_uring: use char * for name arg in detect_node Fixes the following compiler warning: warning: passing 'const char *' to parameter of type 'char *' discards qualifiers [-Wincompatible-pointer-types-discards-qualifiers] Signed-off-by: Miko Larsson --- t/io_uring.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/t/io_uring.c b/t/io_uring.c index 6fc40cbb67..18e8b38e7d 100644 --- a/t/io_uring.c +++ b/t/io_uring.c @@ -820,7 +820,7 @@ static void set_affinity(struct submitter *s) #endif } -static int detect_node(struct submitter *s, const char *name) +static int detect_node(struct submitter *s, char *name) { #ifdef CONFIG_LIBNUMA const char *base = basename(name); From dcb1f3a975b7551b68ef9c8216a9488a6b490de3 Mon Sep 17 00:00:00 2001 From: Miko Larsson Date: Thu, 22 Feb 2024 13:02:23 +0100 Subject: [PATCH 0686/1097] options: declare *__val as long long Fixes CFI as the function signatures will now match with parse.h Signed-off-by: Miko Larsson --- options.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/options.c b/options.c index 25e042d0b7..de935efcb6 100644 --- a/options.c +++ b/options.c @@ -647,7 +647,7 @@ static int fio_clock_source_cb(void *data, const char *str) return 0; } -static int str_rwmix_read_cb(void *data, unsigned long long *val) +static int str_rwmix_read_cb(void *data, long long *val) { struct thread_data *td = cb_data_to_td(data); @@ -656,7 +656,7 @@ static int str_rwmix_read_cb(void *data, unsigned long long *val) return 0; } -static int str_rwmix_write_cb(void *data, unsigned long long *val) +static int str_rwmix_write_cb(void *data, long long *val) { struct thread_data *td = cb_data_to_td(data); @@ -1625,7 +1625,7 @@ static int str_gtod_reduce_cb(void *data, int *il) return 0; } -static int str_offset_cb(void *data, unsigned long long *__val) +static int str_offset_cb(void *data, long long *__val) { struct thread_data *td = cb_data_to_td(data); unsigned long long v = *__val; @@ -1646,7 +1646,7 @@ static int str_offset_cb(void *data, unsigned long long *__val) return 0; } -static int str_offset_increment_cb(void *data, unsigned long long *__val) +static int str_offset_increment_cb(void *data, long long *__val) { struct thread_data *td = cb_data_to_td(data); unsigned long long v = *__val; @@ -1667,7 +1667,7 @@ static int str_offset_increment_cb(void *data, unsigned long long *__val) return 0; } -static int str_size_cb(void *data, unsigned long long *__val) +static int str_size_cb(void *data, long long *__val) { struct thread_data *td = cb_data_to_td(data); unsigned long long v = *__val; @@ -1711,7 +1711,7 @@ static int str_io_size_cb(void *data, unsigned long long *__val) return 0; } -static int str_zoneskip_cb(void *data, unsigned long long *__val) +static int str_zoneskip_cb(void *data, long long *__val) { struct thread_data *td = cb_data_to_td(data); unsigned long long v = *__val; From 9af4af7ab40c4e505033d0e077cc42ac84996b09 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Thu, 22 Feb 2024 09:31:50 -0500 Subject: [PATCH 0687/1097] ci: fix macOS sphinx install issues We have had a lot of failures installing sphinx on macOS. The latest failure suggested using pip instead of homebrew to install sphinx. So let's try that. https://github.com/axboe/fio/actions/runs/8004639029/job/21863677360?pr=1727 Signed-off-by: Vincent Fu --- ci/actions-install.sh | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/ci/actions-install.sh b/ci/actions-install.sh index 76335fbc3e..6eb2d795e7 100755 --- a/ci/actions-install.sh +++ b/ci/actions-install.sh @@ -86,9 +86,8 @@ install_macos() { #echo "Updating homebrew..." #brew update >/dev/null 2>&1 echo "Installing packages..." - HOMEBREW_NO_AUTO_UPDATE=1 brew install cunit libnfs sphinx-doc pygments python-certifi - brew link sphinx-doc --force - pip3 install scipy six statsmodels + HOMEBREW_NO_AUTO_UPDATE=1 brew install cunit libnfs + pip3 install scipy six statsmodels sphinx } install_windows() { From 97ef7f3a2755902dc547a8933724f63639473ac0 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Tue, 27 Feb 2024 11:03:59 -0500 Subject: [PATCH 0688/1097] howto: fix job_start_clock_id formatting Signed-off-by: Vincent Fu --- HOWTO.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/HOWTO.rst b/HOWTO.rst index 4b02100c65..169cdc2a01 100644 --- a/HOWTO.rst +++ b/HOWTO.rst @@ -756,8 +756,9 @@ Time related parameters CPU mask of other jobs. .. option:: job_start_clock_id=int - The clock_id passed to the call to `clock_gettime` used to record job_start - in the `json` output format. Default is 0, or CLOCK_REALTIME. + + The clock_id passed to the call to `clock_gettime` used to record + job_start in the `json` output format. Default is 0, or CLOCK_REALTIME. Target file/device From 5ae4f4220a48dddddc84c8b839ef9d8a1ed4edb1 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Tue, 27 Feb 2024 10:26:00 -0500 Subject: [PATCH 0689/1097] gettime: fix cpuclock-test on AMD platforms Starting with gcc 11 __sync_synchronize() compiles to lock or QWORD PTR [rsp], 0 on x86_64 platforms. Previously it compiled to an mfence instruction. See line 47 of https://godbolt.org/z/xfE18K7b4 for an example. On Intel platforms this change does not affect the result of fio's CPU clock test. But on AMD platforms, this change causes fio's CPU clock test to fail and fio to fall back to clock_gettime() instead of using the CPU clock for timing. This patch has fio explicitly use an mfence instruction instead of __sync_synchornize() in the CPU clock test code on x86_64 platforms in order to allow the CPU clock test to pass on AMD platforms. Reviewed-by: Jens Axboe Link: https://lore.kernel.org/r/20240227155856.5012-1-vincent.fu@samsung.com Signed-off-by: Vincent Fu --- arch/arch-x86_64.h | 5 +++++ arch/arch.h | 7 +++++++ gettime.c | 2 +- 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/arch/arch-x86_64.h b/arch/arch-x86_64.h index 86ce1b7ed7..b402dc6df3 100644 --- a/arch/arch-x86_64.h +++ b/arch/arch-x86_64.h @@ -26,6 +26,11 @@ static inline unsigned long arch_ffz(unsigned long bitmask) return bitmask; } +static inline void tsc_barrier(void) +{ + __asm__ __volatile__("mfence":::"memory"); +} + static inline unsigned long long get_cpu_clock(void) { unsigned int lo, hi; diff --git a/arch/arch.h b/arch/arch.h index 3ee9b0538d..7e294ddfb8 100644 --- a/arch/arch.h +++ b/arch/arch.h @@ -108,6 +108,13 @@ extern unsigned long arch_flags; #include "arch-generic.h" #endif +#if !defined(__x86_64__) && defined(CONFIG_SYNC_SYNC) +static inline void tsc_barrier(void) +{ + __sync_synchronize(); +} +#endif + #include "../lib/ffz.h" /* IWYU pragma: end_exports */ diff --git a/gettime.c b/gettime.c index bc66a3ac9f..5ca3120633 100644 --- a/gettime.c +++ b/gettime.c @@ -623,7 +623,7 @@ static void *clock_thread_fn(void *data) seq = *t->seq; if (seq == UINT_MAX) break; - __sync_synchronize(); + tsc_barrier(); tsc = get_cpu_clock(); } while (seq != atomic32_compare_and_swap(t->seq, seq, seq + 1)); From 3c826d1cafe6c703237378314bdcd123770971e3 Mon Sep 17 00:00:00 2001 From: Jaeho <48780754+kcoms555@users.noreply.github.com> Date: Mon, 4 Mar 2024 19:27:53 +0900 Subject: [PATCH 0690/1097] ioengines: Make td_io_queue print log_err when got error * ioengines prints error log when got I/O error Signed-off-by: Cho Jaeho --- ioengines.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ioengines.c b/ioengines.c index 87cc2286e0..5dd4355d29 100644 --- a/ioengines.c +++ b/ioengines.c @@ -413,7 +413,7 @@ enum fio_q_status td_io_queue(struct thread_data *td, struct io_u *io_u) if (io_u->error == EINVAL && td->io_issues[io_u->ddir & 1] == 1 && td->o.odirect) { - log_info("fio: first direct IO errored. File system may not " + log_err("fio: first direct IO errored. File system may not " "support direct IO, or iomem_align= is bad, or " "invalid block size. Try setting direct=0.\n"); } @@ -421,7 +421,7 @@ enum fio_q_status td_io_queue(struct thread_data *td, struct io_u *io_u) if (zbd_unaligned_write(io_u->error) && td->io_issues[io_u->ddir & 1] == 1 && td->o.zone_mode != ZONE_MODE_ZBD) { - log_info("fio: first I/O failed. If %s is a zoned block device, consider --zonemode=zbd\n", + log_err("fio: first I/O failed. If %s is a zoned block device, consider --zonemode=zbd\n", io_u->file->file_name); } From 5b7565b0b236f77761126d728f085a965f3e61bc Mon Sep 17 00:00:00 2001 From: Avri Altman Date: Tue, 5 Mar 2024 11:00:04 +0200 Subject: [PATCH 0691/1097] fio: Some minor code cleanups limit the scope of variables when possible, fix style isses etc. Signed-off-by: Avri Altman Link: https://lore.kernel.org/r/20240305090008.1216-2-avri.altman@wdc.com Signed-off-by: Vincent Fu --- HOWTO.rst | 2 +- backend.c | 4 ++-- eta.c | 8 +++++--- io_u.c | 3 ++- parse.h | 2 +- stat.h | 1 - 6 files changed, 11 insertions(+), 9 deletions(-) diff --git a/HOWTO.rst b/HOWTO.rst index 169cdc2a01..c404d724aa 100644 --- a/HOWTO.rst +++ b/HOWTO.rst @@ -2786,7 +2786,7 @@ with the caveat that when used on the command line, they must come after the .. option:: sg_write_mode=str : [sg] - Specify the type of write commands to issue. This option can take three values: + Specify the type of write commands to issue. This option can take ten values: **write** This is the default where write opcodes are issued as usual. diff --git a/backend.c b/backend.c index 2f2221bf9c..fb7dc68a92 100644 --- a/backend.c +++ b/backend.c @@ -2094,14 +2094,14 @@ static void reap_threads(unsigned int *nr_running, uint64_t *t_rate, uint64_t *m_rate) { unsigned int cputhreads, realthreads, pending; - int status, ret; + int ret; /* * reap exited threads (TD_EXITED -> TD_REAPED) */ realthreads = pending = cputhreads = 0; for_each_td(td) { - int flags = 0; + int flags = 0, status; if (!strcmp(td->o.ioengine, "cpuio")) cputhreads++; diff --git a/eta.c b/eta.c index cc3424613e..7d07708ff6 100644 --- a/eta.c +++ b/eta.c @@ -215,8 +215,9 @@ static unsigned long thread_eta(struct thread_data *td) perc = td->o.rwmix[DDIR_WRITE]; bytes_total += (bytes_total * perc) / 100; - } else + } else { bytes_total <<= 1; + } } if (td->runstate == TD_RUNNING || td->runstate == TD_VERIFYING) { @@ -228,8 +229,9 @@ static unsigned long thread_eta(struct thread_data *td) perc = (double) bytes_done / (double) bytes_total; if (perc > 1.0) perc = 1.0; - } else + } else { perc = 0.0; + } if (td->o.time_based) { if (timeout) { @@ -395,7 +397,7 @@ static bool skip_eta() * Print status of the jobs we know about. This includes rate estimates, * ETA, thread state, etc. */ -bool calc_thread_status(struct jobs_eta *je, int force) +static bool calc_thread_status(struct jobs_eta *je, int force) { int unified_rw_rep; bool any_td_in_ramp; diff --git a/io_u.c b/io_u.c index 2b8e17f843..09e5f15a8b 100644 --- a/io_u.c +++ b/io_u.c @@ -1895,8 +1895,9 @@ struct io_u *get_io_u(struct thread_data *td) io_u->buflen); } else if ((td->flags & TD_F_SCRAMBLE_BUFFERS) && !(td->flags & TD_F_COMPRESS) && - !(td->flags & TD_F_DO_VERIFY)) + !(td->flags & TD_F_DO_VERIFY)) { do_scramble = 1; + } } else if (io_u->ddir == DDIR_READ) { /* * Reset the buf_filled parameters so next time if the diff --git a/parse.h b/parse.h index d68484eaf0..806a76ee09 100644 --- a/parse.h +++ b/parse.h @@ -32,7 +32,7 @@ enum fio_opt_type { */ struct value_pair { const char *ival; /* string option */ - unsigned long long oval;/* output value */ + unsigned long long oval; /* output value */ const char *help; /* help text for sub option */ int orval; /* OR value */ void *cb; /* sub-option callback */ diff --git a/stat.h b/stat.h index bd986d4e71..0d57cceb21 100644 --- a/stat.h +++ b/stat.h @@ -345,7 +345,6 @@ extern void stat_exit(void); extern struct json_object * show_thread_status(struct thread_stat *ts, struct group_run_stats *rs, struct flist_head *, struct buf_output *); extern void show_group_stats(struct group_run_stats *rs, struct buf_output *); -extern bool calc_thread_status(struct jobs_eta *je, int force); extern void display_thread_status(struct jobs_eta *je); extern void __show_run_stats(void); extern int __show_running_run_stats(void); From c0efd624902ded76c57cf7ffd3d7331f7fe02e73 Mon Sep 17 00:00:00 2001 From: Avri Altman Date: Tue, 5 Mar 2024 11:00:05 +0200 Subject: [PATCH 0692/1097] t/jobs: Further clarify regression test 7 Add some more details explaining why the the successful result should be 87,040KB data. Signed-off-by: Avri Altman Link: https://lore.kernel.org/r/20240305090008.1216-3-avri.altman@wdc.com Signed-off-by: Vincent Fu --- t/jobs/t0007-37cf9e3c.fio | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/t/jobs/t0007-37cf9e3c.fio b/t/jobs/t0007-37cf9e3c.fio index d3c987517d..b2592694e6 100644 --- a/t/jobs/t0007-37cf9e3c.fio +++ b/t/jobs/t0007-37cf9e3c.fio @@ -1,4 +1,7 @@ -# Expected result: fio reads 87040KB of data +# Expected result: fio reads 87040KB of data: +# first read is at offset 0, then 2nd read is at offset 1.5m, then the 3rd +# read is at offset 3m, and after the last read at offset 127m - we have only +# read 87,040K data. # Buggy result: fio reads the full 128MB of data [foo] size=128mb From 5c763be5f592269dd4d3fc33d3a2fc177e05f5e6 Mon Sep 17 00:00:00 2001 From: Avri Altman Date: Tue, 5 Mar 2024 11:00:06 +0200 Subject: [PATCH 0693/1097] t/jobs: Rename test job 15 Make it designate the correct fixing commit. Signed-off-by: Avri Altman Link: https://lore.kernel.org/r/20240305090008.1216-4-avri.altman@wdc.com Signed-off-by: Vincent Fu --- t/jobs/{t0015-e78980ff.fio => t0015-4e7e7898.fio} | 0 t/run-fio-tests.py | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename t/jobs/{t0015-e78980ff.fio => t0015-4e7e7898.fio} (100%) diff --git a/t/jobs/t0015-e78980ff.fio b/t/jobs/t0015-4e7e7898.fio similarity index 100% rename from t/jobs/t0015-e78980ff.fio rename to t/jobs/t0015-4e7e7898.fio diff --git a/t/run-fio-tests.py b/t/run-fio-tests.py index d4742e96f8..08134e50e1 100755 --- a/t/run-fio-tests.py +++ b/t/run-fio-tests.py @@ -722,7 +722,7 @@ def check_result(self): { 'test_id': 15, 'test_class': FioJobFileTest_t0015, - 'job': 't0015-e78980ff.fio', + 'job': 't0015-4e7e7898.fio', 'success': SUCCESS_DEFAULT, 'pre_job': None, 'pre_success': None, From 1e34a6eade43132d5a2076a540b7759a93dcea97 Mon Sep 17 00:00:00 2001 From: Avri Altman Date: Tue, 5 Mar 2024 11:00:07 +0200 Subject: [PATCH 0694/1097] t/jobs: Fix a typo in jobs 23 & 24 s/bsrange/bssplit Fixes: commit c37183f8a161 (test: test job for randtrimwrite) Signed-off-by: Avri Altman Link: https://lore.kernel.org/r/20240305090008.1216-5-avri.altman@wdc.com Signed-off-by: Vincent Fu --- t/jobs/t0023.fio | 4 ++-- t/jobs/t0024.fio | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/t/jobs/t0023.fio b/t/jobs/t0023.fio index 4f0bef8902..8e14a110bc 100644 --- a/t/jobs/t0023.fio +++ b/t/jobs/t0023.fio @@ -33,7 +33,7 @@ bsrange=512-4k # block sizes match # Buggy result: something else [bssplit] -bsrange=512/25:1k:25:2k:25:4k/25 +bssplit=512/25:1k/:2k/:4k/ # Expected result: trim issued to random offset followed by write to same offset # block sizes match @@ -59,5 +59,5 @@ norandommap=1 # block sizes match # Buggy result: something else [bssplit_no_rm] -bsrange=512/25:1k:25:2k:25:4k/25 +bssplit=512/25:1k/:2k/:4k/ norandommap=1 diff --git a/t/jobs/t0024.fio b/t/jobs/t0024.fio index 393a2b70e1..2b3dc94c96 100644 --- a/t/jobs/t0024.fio +++ b/t/jobs/t0024.fio @@ -33,4 +33,4 @@ bsrange=512-4k # block sizes match # Buggy result: something else [bssplit] -bsrange=512/25:1k:25:2k:25:4k/25 +bssplit=512/25:1k/:2k/:4k/ From 9b699fb150bbed56939d317ffc004b3bf19f098f Mon Sep 17 00:00:00 2001 From: Avri Altman Date: Tue, 5 Mar 2024 11:00:08 +0200 Subject: [PATCH 0695/1097] Doc: Make note of using bsrange with ':' Which is also a supported form of delimiter. Signed-off-by: Avri Altman Link: https://lore.kernel.org/r/20240305090008.1216-6-avri.altman@wdc.com Signed-off-by: Vincent Fu --- HOWTO.rst | 2 +- fio.1 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/HOWTO.rst b/HOWTO.rst index c404d724aa..2386d8062f 100644 --- a/HOWTO.rst +++ b/HOWTO.rst @@ -1631,7 +1631,7 @@ Block size Comma-separated ranges may be specified for reads, writes, and trims as described in :option:`blocksize`. - Example: ``bsrange=1k-4k,2k-8k``. + Example: ``bsrange=1k-4k,2k-8k`` also the ':' delimiter ``bsrange=1k:4k,2k:8k``. .. option:: bssplit=str[,str][,str] diff --git a/fio.1 b/fio.1 index e6b291a76c..d955385d2b 100644 --- a/fio.1 +++ b/fio.1 @@ -1434,7 +1434,7 @@ described in \fBblocksize\fR. Example: .RS .RS .P -bsrange=1k\-4k,2k\-8k +bsrange=1k\-4k,2k\-8k or bsrange=1k:4k,2k:8k .RE .RE .TP From 2af323c7993ac94e07d25c05e54c8d05ddfe19ad Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Thu, 7 Mar 2024 12:55:31 -0500 Subject: [PATCH 0696/1097] examples: update plot for cmdprio-bssplit Signed-off-by: Vincent Fu --- examples/cmdprio-bssplit.png | Bin 45606 -> 134359 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/examples/cmdprio-bssplit.png b/examples/cmdprio-bssplit.png index a0bb3ff439595071149575c36913659887a5695e..83a5570bc19178312d67450f63a2ef13e467de93 100644 GIT binary patch literal 134359 zcmb@u1yt4j_a%I71p!H=mF_NS~Mv+bg;rWT2Qovc<*^}DCR2FfU> zXeukKvAmw#fSTiaVSVJUM**VPQXC5R6`sAoNl6)%e};qeAdS3sy{LN2n{}@0F@wR2 z-AEG~e1e_}agx>`%Ej{yiw8s^9tdkcn4>{_jDaQU5=$^i^Z^>NSOni#;wc&QFhuNsrdY z=9ZU7-_9yG_4F_c3MQ&^adQh<&xEz{k&n-l6BDPSS@oiWK7IOhFzRSxvf?n~ zcC_4=8pCdcPRv^pn0I`7O3G_%+!4u$)YsEfBE2^3TToCS{CNB5$Vkwy>5XBsjx+JY zhup@!U+3rb(a-{Jq2leYkFj%daz1|in2(PTCMit(pZ7DjuqZ7lQ6Bs|UF*VaH7$)y zPENjc>z1jh9f8E!>?|fOu8Lgp&yLA3uJW5?<~P z$c2T4`O-Xo{A%Yx?=jyfh`6!RC z?eFhPMKNK96c)ZKW&D%I@E=W@_g$WE3(JsAKtVxiZ*Pam6Y;$LJwc`Ek4%AA{QUVd6*aXKMQ9|W zrn8e%0I86UoSfXTi0%2wfn+LK%7+ghX53FVR)=y{R#swTW6{ykr>Y%}c6R#u`V`VX zgiaMGrzv#`U7qcAL@~2dIypH#PY(7uIan(WYDeSr=jsV=P@0mK|@13M$xXe z*RFRLI4#scr$Pwtk-=$?Vc_K*A0CeMsnFU~)pa|7_1Kyy?-?6Ymy=_rq@-kGvK-0R zUcZdjuCjgV`gglKfiLDKKR=1pV5R~p9>d?$t$G0gff(Mkkpd$^3>1_;ojS04x_2vM=y`>%_$&!*17@@mmhx@BnuYyPfZeI6%9v>4EBaMJRSliiAl8J~kCV5;O!xdm+VnXU08rs7-%MR)c z61q^cP-fPxvwi#aF&Q>?7^(Z;CpS*Paj@1I_hr;e8wZpV1CiTrJYMn-2CB>d5NT2Ws25WVJC7bG|$GLnt< z4yN0|kgBTBU zmJ?+Gv}YUTGdM-)F_Q-Zm;z3_A8GZOU90WZn!{VgcE^m8SU=6q&R+KvemVBSNnJgr zxR_n3$ZqXr`o;0yQVf>1_EdOyI30Q*n?fPR^+Z;^+tsh%ydm>$^bH75Nq@dk)swCg zfk(pc7);B`#g(7prBcG;u&MPaH#P7MHsb4;DHAr@HAMCUEUeJz=vD@dM=b9d9qjD` zeSD0EvLl|m+3zCwWGdfCSX&n<(-{zW9Pd&pwHneU@Hym@kwE0)hVB64b)*JxD5BjVwYkCYPL+)^{qH_{i$jEBP1vyH<5!NpwHbMHvq90@BkB3v_Df zECOmJBdugqRihZc`1^PC^wjTDEeZyODg+Qc4UOSZP*bb0UTD8|^%AE&3c!1qD}{5Y=p4hd&mOsNAY`*$}eBOtPkkWRe_+vj=H|KK}U(@OK15U z30swH*c9qF@6u96HIsYx!e%1{?)H5&Gy#Xr$+P3V+L{_i{zPf2Y6uhGB*NS;&mCaS zA42&Qsbmds@bJ20IoIZbgk+_pa{4|W*s)wg#1dX57z!Mer$p$*440aAuC6umG+%st zGNtC$&#cLuFU~3(o~IQR6^)jB%%gp9cf8b0)gN(kwAHg+1VLok2iXWjTT?U+4%&*I0mU;C(i2XYEkO9XyxQ_lGN+?HwKTbaYiVOR~ns#{PJW;+CVDGGZ2Rkjw@jBZO`GeiV;gLrh~RX4%5gJUl!^ zeoIZAo1J}aXgFM;TmSLn%^2x2lL4yZY>S?(VKGi`Bs_b4~|`Bbhj^wt132`j(c0rh}P~j#?p+G&)TOCV29P9PlP4!D=%noga&ogS+6nF$FAEiy%Vd3hBU z7S`7bDUo1dr6ec+oP7(4EJ`x27Ebp=UDuz2;y-$Ozsn^FW@TmBJ&lH4#22FqqfJVI z9P9*d<^pF4QqR?hs(CO4ocEU@67CN1+AcpJB%~L*@w2zLx3!h#$&+Vwv0-7@E(;p+ z@;~?~hnt#SXlkCeN6?#dqN1V#7Jz)x70v49V{UGag^isg;sHzRbBv z9727S!`4MvX<1p>`WG8ANf{Xf9djc~%kFP#D*k?cbPNpPGYV-6g=}nW@B$qj9WpX9 z8yg#Al?Uz0nc7Dd78Wna6mV<1J3FPt#e>BuU_+(L8XXVU)ger#rs}12m6?q&J$?F= znOUyxJDj`q#){0vtEl%>1VG((oqWZ*O--%Zf?4Z9uDV`)d~RQ!ro`n%Qvz?q_^8jp0i|iHAOd0f zoZ?T7Yg>T-oOX3H209)ErOE!5Zi(Z&vVedJd$ib?n3d(_L95{2NdS!RPn4@6ZAj=H zTZegE4v}!>zTKRR*SOS%^57O5zII*i`T0?^uP=d8#$s1uLTZ-!oSBe5vAhGU%2&Z5 zvb759PeLwbm7hL29`7<>1S#U)x{c_Km!OCj@!l^pXSII&U}HSO(3~NwZ?Tp4>n6+F zYY2k=zEo$WjI#Bq>c4+?mc*YT5Xv7rB4iTd9oE$(WziivA9b_@IiV3_a`0Wf%}-dL z?odib!|u+hny0mR4rkX8Uqy+CegV>%oUCze$MGLj=2yHsO$+&%~+x*7++re=i30!#wK_pExu3=NXg>e0u zuY2xiBq_494c4FLzYuA;-gOrOyfH$v>`^w%q6^=VjtTLj+Q>xx;`|8T<1!B$zs+h{ z?Q7)^QdcNTNQ4%NpQaoQsgCjTI+`#cgc~2A_@5roqu*r-5oc+|oxTO5+|IX{G#)I= zGoEk#@EQH)*V|;|6XgZT#P?2qlft+ zdu#H$mEk;1NPe}owR8kg;uOV3J%B)`_E!d8kcH6MUy~2X%X9qwBc;*%fPnXbF&zJC20!Y?@)L{DyRZe86OLAR5eckcAWaR=ocPdUs0xMwCKBNGyu865@W z^b-<*ioAU9Pp>=JSXei&KZ8=_A)|)Akx`4K_0i@OAhhWkr{c`a)`o^R4Il5JxagLe z4nb8q7DTU_&+mTb2uRht@tw8x$#QB?^A~&(qzi=3a!X=Oi?!iAfLbjEA8Zl9&Gu6)x&SCJHL5$nfw-j~>q#b&sM@t3d@35*>3IROD)K^q=H_7Y9JZ#3Nl7mkqI7lYT(3Yo#O{lW3u@W; z?Sq4WprEBaT{Pk`SY^nt&!0cn($eZ57{DOnVR`%e$Kc>#qc;Y#b`=LTH8lsv;LMCJ zSqRI)<@wRf&RmQ4=g*R9{a$x468W9j1O(38!l-wzF{l?&@bE;RJ3?LYAN)T$Jj}(* zdlF|=rw%m_=JD}yn^=*>1ecxN0Tiq%IWI9op2z1x-DYLAb$MEU>2k8alCND26}sTX z-?{DWZ7ANm;-7{EV1ErPDNTFv!m;j7CK22g#|(Y8hJl=~b*`ryHv z)WrLIzK`FZCiPH2pcW8+PLAq9*!TMS`j{AXD5BG4;+n(P$BM!eSK||Wchu`cR|ydj zpa}2^&Q0_J)UfRZyzl<}xr%w+jIDD&2>m6es#o+^rpLWReTqX9$IbHT*HQGP*z zKy3YkwY@Y31_p&4*FC?EpO~5!T+F|_sGfRR_|Zq74Ii`C^*EHVZ*WjQYGE(T4pFr1=L)^|ssRKKtPFdAhi4DB7tjS@LYM_?lyoddR$u_ssPQp;?hu*? zxh?0HmaL|#B4TtexT@_pB<18hcK+NtrO*P9=i+iYe(SsCR22-2?HW>46b}z?bG(#Z zNGQqYHW~qk)9(B@Cn}=%F%KJm83M5?2l#nNPn(FCxXeQ8=rDh2ho#pX=5e;$!3;4+ zLP7$|_t)THiBV4?yHR(^_wRR6P(nR)?_k3DW~ZfHfwKCw6c7M#xx@kpD0MAbgP%Yk zc$@;Ub91zCZm}y4sFcoA-aul$X2G!b>1ja<3W_IBUc)S<+W-&-5%W=9)&&;owO}bU ziTltnFnoz2(0OM8)By%DFA@n<(&^!b-t>rqhDLTlK~G(QmzVd?X96(-6JLDFtmFz( z0-@9o4D7D(S%+= zMwTqk1J&2CDfE7k)zdsj88IN6^7~ z4X_L>0wVUhl2Tp(p@6hZDFJV=WKB<@iqwmr+^c%9G^JN{YphQQc^(1p$8zT+y_`tD z4_1RM0Cr4KAlq>`-@A7UsG@lFM9z*+9Sh%cii$fL8rc<lVs2aK1inp1FbZH<>Q6srAM&mGB$8p~3G zv_?;r!ew#a+~O8W;ZWv6`KKeUiE^Rvw(RV$d0hv4HrZ8}FLCcgViKS4E_M+Ws`VG9 zDvab)?dIsSRoT(=F);}V>FS&1kmJOfnY_!fC>!5GMIZ=hI{|W1kQh#-hs)P3?K9=yh@p(_8TVjH?{h4F`Hwn!8^F#fm zZf}*AmlhrEACD}R?IAJodkz3K1wvzly8_ zIPztcVb{6)`SUk=d)b!%H3jBLK%$~OKc6{t6OBYiPg2XFj5gF?%Bt7$I{&(xgGM+x zG{}R*!Hp1rso=Bc6AO%cHzq4(B)VOmpTBNo;I`Uqn0 z+}xsC7Xe>!DstT!)QK{IvwbBoG&D}Klk+2LZ*&1-?r!^yaoJ9Hxg>W%qi#DAIg4&0 z)pX<&E=tXO-kNe^zp-L%6}MNw3#485JSnJmW+iplaxZZ|_X;2+Wl--=^a%|^wXu0& z)Pv5#F^}2DJySv~$ZtDcLkQpb{nqr@?HYvQ&sV3OgY^i_u*>>2(`(<>#>UZsf&JmU zctY+XA4p0q$o-~=`RlkF+p{kX4K-kCkqqv04v@dhqi>;m^V+e|F-FulDl~)>@&Enk zb9SCE2H_=-fuSVgevw~f1V+KLR(%Q~MYDFCJAz3r^WtPS^v&I}+>gKB2(x~GnbdP| zXc-YKFVE`oTIKYA4(X-@MUgkgUWHa*US6PUWo>PD{iPu=WJ{ZtL=U4f(wpz0NY1vN z2X`-IM}&+2`LjDw#`y|$q4=(w8wn=v!zgwar`>PwI~LT`YeNjxG)#>QBIvMM8;6_K*7hPWu*%+^7jCpskD#0seOJ#z%y%iNx>yQ^ zhFM--mQBOi=XE$#0aq!^>*}sP!N+{ZLxk?lV-+}s&o}(pXMdk#9(BmnQB{pDZ?{3W z^567p7_F(Mv8h9&7^}_ne_^9^efh#MCM!}}G3ct+q4McisHj?PXBD1cnRxo>!(ai&V_|eVbl}FH#~g+i=71rKg`ph|E*HKQHbj6nIRbZcYBDvK4On5d8( zdY@4-&;RNXRZFPH=#Lfgb;^@c9z%~Tjj~{}(|^*d#c99BgcB3sRVv3rqgQ#CF-za(6k zJPs}xf`JCyM`wQLT+MRpKw>GsFP#ibkv>gqS;|yc61X3FdLFXd!5HD7d7Opd5XK$B zQq*5wlvwHRMbPtQ=j6QO(Kl}8b710xm9w_)J6IbNy7)`OS<1s+{Kd!G8tE!njyLlV zk=Yu<&WoJi&>oV}ASNaz2~vG=>%Hl0y*0v^%+-w%S2A7Jj)=8kb$4OEq@*9>j8Zy} z{rcZ#&82kdcaOL_cXt=Q3=U!m2n0Y-;U9`Tc3j@!>2<{WW`FXWlXuW*WMV=f47w66M3EEa8LA2` zKqrZbW%!{Wh8k+n5%0(8>97iPh|hojT$4bBMgF^+W_;#YeftaaH-Ec%VZMaqO8vkX zPKC$5!r$KH$lJFGf=Cv;y^UOVeH-tPj&j%<0|eN@yaIux8F^g-*54eov@X2vR6=w; zKP}g)DyePA*@T}#Kq3~_uB=#wUz}Hz4PuMA^9nz}*Z%@+;5+}vKIH!ZEZcQJ(WYtD z##XwWije);89dxNGF^VVs;i)IoGc;-1XrYFmdKsPAi*MbmfxJV@_2)QyLW>-#^P882N^aN|@ni-}1i7uDi-=WlCivEDacgP0*G_z)7k<#Giy zSA8>gY6Ibum(M zG*RL8Wg!Q9xPOg_!NWXQe$DpkN9FNe0jUs^`REULS(D6n!>mxcs{Fc!K(kyQErg=i zY?&tZAvt-M&C+cXGaEv#FD*fP%sSG9tPOq=PkqF7lek?gh6?JbCM!aQ()=YFKLgB@ z5w=*gBLZ^M3qvsTNFMR^8dN9w$s)$-xw$2Tyf;x$rKnT769VQ@1^B;soO90pX|lcA zKY52}8FYlqM`_XIWRso) zzwdrp)N2_JkCMV5a>T-qNz3cnm>6;Wb(j8_OG=8;8$$>#h7wsam&Lx&`JjUPhS24= zvx`$DGB&n9dFlhlKRiD!FKhWC$CJHgTS)m>lnNJ)>KqD|bUi3_D?>x#=tJ9#$2l^R zJls2TPYzU>(a=i$h-ddIiTMcJwmtKbU#nnOQ_BSgi|eZ>v#|U=-xet^&kOMHASa`z z_jH9u>^O3NS-~9_3Kay+y(=hqYhj^?xz4A?k#z%Z%WZU98d_Q*dj^xW;b=5M{*!&y zIKB~!y~XG_?qWzgYwNk(uRm;5+A!lWRz-v>hsSG;W`*^6la9v=p1aOAWPZ55^G)JV zI_|}b1#Bvv;;!Ko}I>eTS?KRtrgWO!0z3HF=j=5JC;jr*RsO6&Z0EkK@F(5}$& z{LC{i1}f?AGU7B)Ys~JV{x-!b!j_Gfzp@tBHY zR_+_I+xOqLb~2o=WK;;akZg?&q>9{0m2{RxB8!cx+p{HkePm4NBErbY$h4=p({q=X zGqL+RqEfPh_z{TrY)}jeD{VJoyn`)vKevlp8PJvKAYuD9A~+&b;ebkpEk=Y`)yQ?cv?QurUAOyfyw;!ct_b6C;y=2LVGuEnwfEJ zxXDL`h0--O6<}d*OGIRgYx#zS_;|O2YyE8Z+6|4v4MH{>!}(9BUQi<^UF$D3OSt_k zB1+0ve530ZQd>K~2T3NOt#KQHc=fxj648r8#1|=!)N3C7PN~IY)DoC4;$0S6FkD@Z zU;H(_xv;SBNy>Ds%-9JR*N2D9lB4ZcwSkXlfV042wuR;mNPd1Us;W!-YewDf>oDLS z?;TbYou7HC)oIkbGmaTxVCdP|bx-iJ*{`n!Mn@YTuIR-k>Np=;J1-;@uzLz8_#jib zEu+FCC4I!jG6a*xAVqiKZQ5~ALHF9@{2=Oia%t(oV3ragMPd~&V(_IWbaHwc7Z+Dw z%PYLl-eHiHMaiESTUeOx>eO`qXY2Aoz%4X1^r()$ktk*bJ&*B+{%H_k;YULH2rXjc z_H=9HUHw0Wgrvt3h$U-S=0jft>tWQ0>c;KcLs~zJiCI~l^fCahA>jn?m!Ylk6g2iL z#`&Jg9G~XHmTrVCV z%g>)<)fjMnzpZa)@G7dRDyc5{G$|IU(FxI}^ka%qQu={%;|*E=aQ@h3q1D>m0fMB< z;XCWY?{vJYtJ~__?m-H1ajCE4{MAXmHMo)&CIRmr?wy;;YBEy?{hp11{{HA(5g8fA zD^%l^jh!sNyt>+MNQ@|LoRu%Pq=FK(ua6m`*Vt|ZeQHl=>xx9{Qs>RNCN=kFNnhsFVNh1}r%m>o%Kw!W%Gc~ma+;WW_^EpFgz6P$g z4%6u$R8?6@Z0_*&R<|;8mqY%*fUeVR5xzTjrZkSyOkD+!Cafl!n-@1`l758$r?j@& z)Z+eh%@CHV)a$DDz$q~^a@>B^v(T}%ze&9Q`>Uq>V?6&f%`)4S(LNTzx>r{S(=l02 zu#(NWq_n!aFO<>&Fc>A)*e~B%fRk%~QZDN+rvd`H=;^~`;~xncemRI{ z^SJas+~uA7{WUH@o6EIAC5ow8wZIU5Y`VPQsmsHrAHsUQUn9NlByqF#OUp80`F?}q z%>ECOk4$lt&s)SB{oqa68=Zxp`Kv=6wu{maKLzR=M&jW{^u)|@nML|bV*DO08wd<#IE=I|XMf*0IyyQ#^9Hrp*x7B&)YpT4#r2ji z()!};_%=E-e4mOMyMH(`j*{Ed`AaAl3(VPprJYKQoejS zUR6b4H9X6XDkuoEE_dFi(PfAnomg94HT~7R$XYI_QQ|oF>u{%#h^P0@&fM9qbyjgP zNz&!2hw7)=4n0^2cYUMa4u_9DO8^323ecz4Nyi&=+vm!-x_T%R*78d!(58&Yd_-Rn=q*YcPgU;YKI#KJOZ%_~^Q2plSxv{b1AhZFNJd#P< z`Sj4jaAPUSBi~i&O5Xu}@R$V<9=N!;_#$_qvn!dVC??j@+A2;?o=RtbvKffVaRZ{l z`WedSz(D76OTW3?aN5_IIW`*Q!$v|?l`E7^fR@WKoIg{aAl&LxtIld z2^t(}?=5~!UksrpjuF#VD!p(1ljH$vg!^*R44rM5GVzbx*VPn7#DFc@PHecFL*+wE z`2q>|)y3GD29uJCij7hl@2Ac=c1Yn)4s}3mG!6Qpd^+tdb2F{Y90ydKhxN{Bv7vmo zj2T>A8QfeM2zdcKzZXTQi(ylQxoAGB<_E>r6+al#@X?y=PtEyCBBw?6>aeK-+SQC= z5&Fb3?PJA8Ha0eVw#z>vzYsm`pO}a{zY4lyVq#>b1LchzO6^M4 z|5=P*uXM!1tPj`6Kv}oHIaRG$ZYkXc>pWTt5`a^nKw}7TAf6i7ya&Ax8|u%WKT*)= z2Eup4Cuy(VnuE1sq%RsNCU$u+qPyO~`c2V9Sopo`C$o|Kn7B9_o_JvJL1M&1K`{?X zAzE5mP(nGuyR8gllv_@|DEc4cZIrEG(JM84yHa+YNNF{t=uqH;MwAGyK+epNM>} zsH(yW0S5f-+qcrvomYA(Hnx(|(rVk4U67BguGPC^)k7N`%EP?|!5HabAoaKEPP;+R zrQR{!0{a7cgN2SL)%%~F9P?}Z_@+RZbCz@OiW>d8GTSQm@TKVu^d%q!NL{x*{jPs- zprW8al7-wS<_%}8d^aZw{H?F};|H+hL$(N94%4~j0P+;QmcXIH1faIzkkQfc@ba2Z zL~d?qs{nX*G&1brB2U?v7|6-gGc-(3P2I~U`;dNbk2kgeS{{`)OL(}rg_QLdC-lWE z9v80Ph^}@(UYDgI`xwfH79gU>^0oPNb#+0m*xK6aad{rqv*fYY#SJP)v*qQhpZ>b? zqc(8S3_4MAiTqDNX9>Fu!alJe5;u@%z_4J#a|Q$m+*`NOcz6@v+Vb~R*}W2JQS=3v z$Uf`jlwi`$s+?V;Rc58z0B8?b%AwM=l`N%#kDt?PEnIi@S5%3K<<=+hTOtQDp@F%XJD}S@!p1siF&3?!rVMDX=X?^;lp^j(7{d%CbyO0NWN^^YQ<3ve;<@x z)G~f=9WFCu;*`!$y&2RV;1b5EHj%6yAyVczH+X)ypsI@%1Cdc>zj4w=oy4G1sosHCf6Ko^iq za;nBhNf{~;)^K^oZqgQdI8tyL&rdrzyeOOGegHw(FG4O}934$Yi6*zNu=eefp&##; zW?xGqfw3CM(4DGarJ~Q-w>a3Ec6(|$N$Dvpz~lHMO-dv;_a-*=XECJyV2SLDgQ6q5 zwav>d4~IMVjhzn7FEeSj?ACZ&e$59GIqghV4oVW{&fM?`9LbzRY(2fIqtHaD!X(nz zvPul)4ve+vHuS3PGn#|y&n&O2m2X}&dOMvSx!A3|$xUl2{oXtM{ijz;&U}w9kmA`Y zN1}lx*&UnRNpiR-T@_mBm=A%_Y}(&nmFT~=IQj~eEwyxJVF4h5?BeUr4bCId-8iUn3VgM-kE zR$f?B&el1sv$t4M$>D9YT3AdkNNbXjWy+O5r5pO!&%ogloNv-%uj$quiE}I()PeKa@USd zh1sPb^BYo8gYKREuu?QaZR^eh;sOOn#Fh|$BMP($Cbjyy6EJ#`kC#gOejG39yM%gW*JZMJX{XBW$hWmqdaZ@D+6sPPbBZV{oY$5`>-$&0 zVXT-5d{OP20u=f`A{h$-R4p$0`51S_b^}51=B0omi?zWb&)4cmD2_t^0hX8>Sl=^P8{k z*$=T;TI@0xYr#hTTCo1}N3<@F8SXG;XtwCrm}q02>c#W2QVf!LtdKfEtFBm=3^}2m zb~;YZ1&l|%)mq%Nrq`THcZusElM)Z=aNk z1>ud44{03~!3cd?C*zrQTU}jGN`#MjOO>i4<&tEdq{Ogm&!eimL-g$se7duJ(O3)8 z3lyk6!~$y`;q1Gv^dBlH2zq*+gMMnKi@O}1)SdV3tB${a6*_xAahh$)ByAGmuN3P` zoc{P>go=mE)3CF;a4k0*aRtnMJ{R;YPg8G{D=gHnIiT8hYr2D&uj+-UGhA9?hjqv3 zc&*NtuQL1=m;6=pzvz7bYN!9LXST6+clTEq)eEiq#23h27u}-yZm8sEXT2`Zlv)?8 z=T&KK=eO#WBAF~maTiXPlAa4D@i|%lBK4r4b)xa}r-b@7fwl3t9oOp9D<7K2zq^5j zVbB~Fq-&?dTcZd+_{GpWS{IXS&qSbNY1T^$mV%gM^UBKM>G z$MiYoz!8zUzM9*ZlC0Chz4IM!mNsk5p6KgqkUzaoe>K+}4ctYV)Wp%+h~i^;Zq~kd z&W_O$ zpSIz0$T2j$SXHr{r@IARHN|&Z5UX+NdF@yAe=XTel(J8jw244Hq=la+zp>YoP;JLP zyEnvV%kssa(8RbkXB1~3gi>n!Y8+x>Q{A#g9g$2B(el*Zant8W$A~D%KfZt)x4GFo zJ|1z?W$*$s{f2x;tB zQYYHG_ne*qZBi3rc(&`ml##^S9>t=UiVtlejrfqc6&oz{mYvlN&qTddSRdaO~*j9%^A!RMy$#ANTW4 zUmAXXFCU#68XZVaM>ExX$C7CN&b!}Ubw;yra)Lx_19y=g>Y)^EJ`u<{3yqMdAE*5J zV}0BZiggbYvJs80w+}k;s@-0?T!!pW&#SDy>4grUw{)qgYrEnE=K1ogZm6Yhn|}Lu z(1r|kTK+=AJ&06yD;b1TMI|NX%MVPUM8L!GfzHAZdfsrKleTB$6Q-!frFtf4xqQcP z1iArg>DXAUPpl`+&9QPxcyt_^JBmL>)!4~XIryrjF9=G;bHd8)e*HrF1F;cNmCMc2 zEC2VIvS9T4_vi_{_~!?z{qt>@$objV9Iro2eSL-}-IYBuoLPL37P!Z&QQA)hLVKCx z=X!cFWbNHdlqtQvCBW}NwTwb&IfhkiAax&8yMm>)S@Z|a;!;#kG^UAVcfBpG0L%86 z4`xA9xQ3#lI~>D?ua~BfRj?E^#tFH5ql4`2tJ+7hh!->?#r1VvF-;89OJ93cH{cep zqC?)Za6|-1SrnmLSy_Ru)EVQ}v=PAg4Pb{rbq+c&`3NY6GIk7!f$Yi87k%bgk(sI0 z_B5frg!Vz^L)?gm52fa@F$~EnK1^yAN*^}=_Fi9SJK0(-6XnY*A@A(yj(+lcC^RWw zp7jyBH^2*mr~QBc?Kd82Bs|F)`5I<&jZaO^mISSY57`^+U5U zGE&l%Bg@OD%7J^V&}(z1hnTZHmaztC86W3;7KJ+54VsxIa1sjDmCCBu>^&}tj(|3i1o_5RNrz`G=TcO{=h z1L6%N-2zG^C%=1TY+$glu|m6A=^=AEtJ8}iv$euv*SadTvsE{v8+Q^<*V!{Lgi2K0 zZ5Hl*T7-KJ7dt9KVl?9;>sBxcPj5=Q4E>V_nd~?A+)uJjGZkK6T#U#ju6Nj;ogFLF z({K1~A+j_;Bmj+7qi+7olQk}BM=W0k8k#f%DM7w=*%;o^RQ(QUe!K3|Z2kVJ6H%v8 zC~9g8`PF%gN4W8-xnqoS#iyjD9j*=?G3iJ{s{=Z->UC$9hK4H;fd-SixNhIBiJ;#S zMe0NU=+7LOS}GJ)L=^1oIJqqRD&6=pz9Fq4nSMe|1tl6JLfy5%Tn$`ef>;e@<%@lj z*|TGANa0aT7R;B4)pXm(lb7dPP=zwj;C1$=Igki;V*7p@8#4gCL1>%!#XWuq#Rc7E zCVJcZCa+CR+cqYi0ua}(Qoi^-RW-LWIGn$62K}eaNkU)WPshi9!l;v=TdL)a{#hw~ z@M7|N3C{ZD7$bFt`C_LTS%@-S#HY{cXpiLQI7DRviY*gdbQ1r@Sy>twMgpL^etnaQ z3NK%MAf_uMJWHh1`EvvtR+zWI9=w zU@%C1x6rIAcTll2rRG*Gu`vRl7&Ejxp=n)h&l$tr(>44O3brV{md}MlhTXRKJl_{G z6$+%TcFEhj&6`xmPzumY9D7TI51dr3`ilzyxC;wxMn<)uSUB47ueYWQ^Jxw!R+dQq9*PTf$`y9{a8}LWV+>LYnK*tN-iD`phacJq~?PVDS_?mX=OkTN#bw5;p{hTwNX?(Wa{ zyUGXSrhFaEKS);XH}2DK%!N_knsIYjx;T%zeVgCIBMRDUIaLmJwx{;pSIfIu!@wA! zritq6ssXr7Ye(B!Dry=WR9UVP1v=@chfYwQcE&k)F`jK!+e2LL@W_jd{16_V0tT!! zmOViZ4!e_mv;A~VGhUBv&j42mY3b>784Sy-Zk_>1b-Ln~UhbSz4-Ln#JUN4ozJQDK zckK2xgROyd?OI3H06LqqitlBvtRn3SW~F@Z+3*3i zsEUcXLG+|Uksf-xC|s;4gy8~ggmGDS0^w-y6AnxGId%24ocuT->Z+GAk5wTon{7TU)Pg4&YJ6ryp zGL>~L99*ZPgj<;!{&*TW=MkX#b)hk_jTwZeQ+W! z1zj6B9j1ZKqgS74Ztbl@n}KRmOc}hEFLdKDNxUc7ShhtNs%G{(aZO z^%&vD%hR=|U|TVKlk#^u3FOsq`(bE4O@BbE*p(xO-Mj+(gOQJO>Jgk0+vKK za9}cODDRnmr+RZ}4y!b@`@?g8%Aw@#lc}2HYzmspP)`X=%>9`WF{AF@i*p_Q0KNvxRIR@uZ=4kg=afuaaf}jjPiIdB~U={`5 zz~KfCo+vNx{F>Bd!fo3Fx^o8tdGWjOd$6>q$EA#?=l(kWy_%4w{KN&Og$zMHFs((Afp%7rLT}3 zje$czns5#n85ykrX~;|rW^tI}XjdVbb^Dk5kVsMw>9n-LbhKMHgI^lRy|A@Qdyk-F z&X9^~ok_B`F&XEuwn;6hPrsDCu{srZ{W=X>=mj5IYhMUFaz?=8w6Rh1eL5y84-Ymr zP_Jv$L{rI8F+~Yp#&d8O_fwo~aXdC!8qvJk1~uta(Z1zAePeN5*JmQ${hL$si=tME zw_)$LXDP72Z#7szYB3Fbf1gLjHF)s49a6~Jl_)dUqV(#qx`rbA(|^~<*EIZy7X-;4 zpFgo7{5deNx7Q;?O-4$Z1OzMbQ)_(?BwqLY0aSZ)^XHC*B29dJe6aX{m2!Q3U2-CZ z%YpzCjhL4MY@*;+@Z@sPcW$m$53DY@9{A>cN2YF-Q1=oC*gOa?s$A0P)rJ)?}ue%EP;JD zrXBR`<)x*JG&J&Ko~}u@tH=ai1yee=OMwKQXO@<>$87inkd-NpDC>Uy`uMRywH%XQ zU}dkz*T>a$d7P8i`s3mH6VLbN=B>?(0eD}&h|^GLYs0$An;VUDkfkJx(DFnN=cS5x z8#mk}OL-!)yskc8>}}XZM#jeFUW;pXV2X188>gAh%x!$u59=Ey`^L^^(XewgUh^Ai+C4xfU_0}u2f}z zd(f^oFl1(BJ>`XZE;A>GkC*r5)H-wh*>fa#-wmX|k^}(<%wg`}JtzzASO~;t|9rkY zH>an6VNLw{ziR>XZ9x`YQ^QYIR9pk^)X)5C%SeUkrPA6wsSSVhRf2eDj4s<|=sWj#4r2zXR50vajx)l|@_K$sZe8 zsb4^Qyn+pm)Q$zXr|nV7eYt83Frk?A^mI@zyH|DySv5=7})%I)`y zt_-jr?_sCQB~61N9E=y%P6lLJ%5x-ILBpE>xC zOL@_zqXmD?VMa;8h<6-SIO*{QijBTyNx((Xw~#?zO(Bc6)`=iEjZz}5;I=w3zc@Bk z@BYeQ$}Kb!>9yeyUkNmH=(i(&oM!YH5v$C-8s2KV5`>@1}JBM)#}@0Lr(eyZWe@^9-6#$);xAAe#J z)|kvKcSG@~iSX)bK|j_ftEV7N7vJQN&lTP`Mc^uByg(hVjw&i%**DqU{(5;TWc@CA z(Vn0tibSA&rfefw{?R1R@&a6hkN&Q&4s|-4e^)HGTri(F1RLLgj*gLXEwhS$p!-up z>1e>H52M1sEe7(H9A6`1@GgS?Z@s0zzkfWSKAXV15qdE}(o{;(eC~72Xz1SLI4EPG z@$vB`B^-x(ppAz*2}-7o^>v@tkr8f2Mj0igfsPKdgViDE;Ly?1jsf9Td*|$ejwwBa z++?m2as4|l9wPV)8ftGy8Ip1t z;W(yaq44?;1ZWupI5^un{9hKNYnDTTi5)W8uCulu21q+Jt|9NHl4hVE&ZPCI^zCb9 zfim?h=w1NewdhMJy2H*vCv^5;Y0JZ6RauO^Sajwge3U^N%$S6PB#2Zf(9ikcp<9z5 zt`_Cey}{mCPBU;z;4x`M#~iP6cYwSJw$1@uERY?{Il<-v_Km2>NURWX3UKcxsxj!4 znd5smK1qQ#6qryWE*%nxv9Za++M-`;bTv^Y^={8{ICG#q5{~0iFs5NtlgH?CcI=&N z*`F~HrhiGpr~NH`IQI!7qeewAL=NLQ(JGgybUEtEf%k($CKs-BjFeyuHlBu4Vlu|| z=dF(q8JY1^)yT?A>HBT^gSoO^i@yey&E`adK)?>_bN z6dJ70lUc~fK3w@6e$_;Y(BP2p+WPo_mtypeM4oo_@X$~x*cf0zAOeA{R905@-^U)j zg{+7>;p^{@jq>R#HHuS6d%?HhPv8lFrX42Ar_-aY0uBlcj36*I`82&D1QWQ!5ts+^ z)C%7m?=E!i*#n}Y!$Fywo9pWAgms*opC1HQj*ZP81Tsjk(=#(kCi-2moX|o9M+Rui zV1A`RK|NxmZXld?jv(zl`gwUyYPa&dC1{|@e}A8LVWCggBjodKiy2uR9oy|!lySd) z1V2oODm0Jr=~D$Al6#*@TpTDj zel-Dr94QDx?q>gVITEM&#^(S>>*I6h4ijM#65HA9zD@diBAc6o*|>RX&pM*&ou@bZ zQYmm0UHM~T6p{qz=aJe~!Ie)FcwelI3_xbhQy2O9Q~&id0cZKO;rTfcQa5DRJcn^4 za3j0>zfRS{7uL+OGBWyl6#$h6aJEw~X=rHNzIpRIY0SvSjwo=@v!g;_INF+#6Blpo z>aw%30Yc2q!NDk9Q4L)6(a|&|sq$~Etl%R_n04QnnbDK_#*VL*bU~_y&x2@dYlBT0 zA0G!s!%+9}^2$n^G{?x`;5sw`!)cX(g`~iem?*O-EiE0xGlb6?0O0%O%NH=?0EW@j z)PzJnPMp%*G7-mX>sV@TD#=gMMtrC$5`?3sQK#VTBvawq2D7ZJ=siCT4;HSh1e8%# zHNSabO82RhGbWm*>}qgwCnr|nH4sK8O@tE@ZBMx%BeQsYy=Z&;OGyb92*X`mh#egC z(7mA%0J!d{((7S{DCiVzZ^H)!$QI}rf=C=`d+BbeuOC^)HPX>h@&}PUI$C2kQq0$*(v9hXV-ok?|Mj zUCdhIdaCa#i9Q8>OOFH&0z^R2YN1F|aego@N|mZvUNrY-cW>`SSsCy1+z(D!EP04JQGp`p3X&!V!jd>$%DF!1Gf-nz7h+YoF7 zpVD&qDu1z|%{P9!X3(+Y;R%SvDD9@paQ`JaS|SgF@z+v<=iyhNgfFhMGqb@&cz8JC zi*Hd88HAX>u1zfSF|ms5{AjC&Gj$)YRN58GPL7XY0yPFwU((aZtLzeD+U`GlMhkY8 zyEj+i14+imR)E&Y)p4`hGYkS$H9D$gX7)@??F4#^?iZDJT$Y|urGj#Zh(QTUDq$XQ9=|1ML-%vK^o~sKtSn6TIuePMg);ix*Mc7 zARwIv(%s$CAt4QC;`2V|oNtUX#^<>I@J2T5eO>EXYtCQIy}w%wEG+%=a$ic!UhzG8 zQeydMzH({FiCQ9;J$|DjRv02Dpc8QZ^{e0Ty?U`dA+yIoW#RaeeEP>n zPFqd=O5OcRTWyh@adGfmt@__;{A77~&h2%$73zNIHTmV6jJ$kAetk`X>z@ok#FsJK z#pg+ZfsUy{yNgss??Whxt4-u>P*AQRrW;vo?OaQ2=SV10-wGHrC2aVe94jk#1P5E3 zpS^Ewzj?iBZAh@?Y8-MmlL{l2v4<)Uv0vXiN!zc(DSxJQ^g&7KhNY!_Q#?!K- zKfd8aQ28m094Hs9uRI3C5Ka(r0IH$X1rHuXiwyPMN&oca6$JP{AywF}M_ z(u;F$as&a>n%q=uNBd28w*;-1tq#VCJb%_5@sj1(Iilzp3LNbFX_P;I_S@fo$cuph z`|b56U$hzHwqKzmDhj5rzQ3iMJ)Akn4J|WX?OXj?pe{Tz>{V&B7eeHwa?u=IJFtrF zu`R}B_-B4=G3+|xWIw#JO!?+51}x0B;?zd8Pvivg*OaU1Gju8p@}S={zTVRApqoWcZ3i$0un^WZp?gn31Ep z(c6EQh55Ia#^<`a8_V`L?!cE5UMeYRn49}ITvQtI%3A-G;(Vs1<;$+4u0B6hFd0fA zoxAivpxdex?=c_WT`8#yFE668v7xN2a>O$gl{<*VCDrxS7uTAG3kf=61F*=?Mu*k< z#x?tXN%W0rB=RL6_My+!-1fsgF`CoH5M3dBuyejG;ER~I`wt=ALP?o{$Ap}X zjegyPnGuW*$++}Pin_VF^&l&wJvFg+aHm?<$7yG`-QproN<@%+?xc2+=InwOzKf}(g{ zAht)2@`mWkxL?L)yrB}3gbu%Xy#(gwemJgC^PcFlX`GRY!xqDHb@_7{?(?frvlP2+ zPlvX(E!DesL`M$^x<&CC1^tbCLOeF6I#T5MLXH{3=SRSTr4>G0xMJP&vn6S?;O4)7 zKb4m$t0-scHG<$*QA53D7rs)V_N78Cj!%`FDt%+Vd2w;ERJY!coxNapufY8v;(RB4 zbb^2Q=QHD-SNNsQj~?|!MAVFx>4ozzqoW)1^W`I7_FYF*nG@c-2V$KQPvK*FoVBpP zz|NgHa}_ZqrFOWqJskyJ`;Q0OrRHY8HOLC3HD}_rst3ceQ&)XbUvE%|T(q*nS1;xo z`$DDP8RtVKrqcWqH8UefO4Y)YVIoNZ8b5oSaIG zI^4pB{s@grz^~e#mc{VdwMTb7M!bS|JB=p-SB;PK6CbXqj$c8+#nGPx7TrZd6ZQ2q zsqSQpr_6ygABi%*MPF9VRL$h=HeNa{oIS^#T{YXkcP$-ce`ge>;CSf__tON%N2?+FYIRp{vg!M#4wEf?;VNVT15 z1wKP<$4|JT!g#)2?!w_Gw77myB7-_};xXkQ?;TW-r7@zZV8a(uA&BOngo{2L?; z*+aIEt6}fR$ll*>BF-T7l+=#V5)^-bz$8CztUSdP&3zQZtcvdQ4#VNiqN18wOjy`# zI7&KAxnYsP+;@KlHKgf*f&%R!;FY?KUiF!oPpz`j(wdqOjRF#IB^45zXpy*$3H(g& z6=Fwb^#i#I!?l0)Qra^~B@V;ey;1&BT7nFaon~637bmJnXR0to>X)_et+YsmTeeIw zPp^K4=hcHG%4BFpXJSS-lESHUst0?ds4$_29tLa%@Yh_cxmIYo?8YWUF%76dvH%@SMbN_wW>Xsy#9+>3spr#iUP4~TRf*KiG@UG}l*YE4Z zMRm2Joz&UMNy8RM)y>F@+(=2lo0%zeBug3WrOj(MO0d zyhwhIxn33$F@ub<%x#alz3P>+4{6Wkqf_LJh$CdpW$v&6cIH zd7{1VI`z(}0HcsgopV`-F)O2D-u`H*cVB@Z(WuB+s7pTqOXq#6#8ULKYyG|ylU)6* zefl>n*_b4iuzkwkA~M!8syfm6QTf#g$9^ezerDf44vnrQ4=g$zNAoYYBsQ6l`zh3Z zQ*7a8H||_n!F{v3ICrl7)2~_J{Tn!wKCUg!If=%s#oU~~X+eQJSK7}~Zf-JUzuY~Q zmX@%1yXpJsre#l=VC)Z;TFq*?vLv;a8gCen;#|TiXS9W7iDZkC!sEl;Q@4#uAG|O{!4ZB5y(85`w^g5<4DKLON3x#VruzFBKf-x)vQ|{odYd(Q7cwHQwt(JX6eLV}Qu`1EI zQU$j+pQwnby^}c%{_7rjhK}Gci9f631x92oAdKSkuKfe#uY(;Z0Z{rJ%SV z+xhN9#~lI=VTy|Y%2Rbkm+VcM zqThSUI%RH1X{Bf-)o3Mr!aK#U2Oab8OXW*hdsL+}7k2Zm+1CFam#vt{PEQ=C%Dd_9P)Y0S@&>S^tK-o0;pusPtDQ!!=P?DrE^ z+ShIy9XZ!emmc0mm_~>%o5uW$M`1=TF*>QP$&ySKQ$2-Y$>mJ{D8`&Da3CPB1 zD=4xmxXgwJO16gHcDpShC2{LQYiA_0u}G%&yQ*e^%WdJ2rP$3RHM47}uDGI`d4j`E zU&c24ZxL#~e0c_>M0IsFz}3K02-q*SLh=r8*(OAdXlb2-SR4E+0Hc?E{px6EmzQ_^ zcVZ2oY@ita?(Uud+|JI%24FYrG_?@CMR2ba^uhqBz}f^N%xb!>y0kQc@8>Jl+)e;q zwucKR0R05OQA|uM`N~wgf9?8p5(_$$3m~CxgI%Dn?+G_|Y`ZO}#36u9E}qQ@B5I(g z1O-2+pYO99%c$ZXl0EgT`(g2g&&v%tgs*mQe7lrlQ}9Hmy>>stF|>euOMUXP;qb7T zn=nsc$Z1GfUWRkR@;AkT-b=+dd@En4`RWBj|Jufs?ag)LvB-A{2=00894GZB>3G$K zT(WtQU)ELole#(^PNv&kHXLy`3S3Hzex!aYnJY%Cm-5+unAO}kCB>|?Z}D3IhIq6% zPA0|Q>G28c938b6YVW!p?j4SfF2Fze(7fIyU0ZDNpE~zy0h+dGg=5bskt6fWT|(=*R{o3vNq z2w6OxWiH#g03+p_Abi;Eg$fqRTE@Sh-P(H>>DyDAlKT4eApA>nECIWQjOQcI9y>jf zC9mf5_F4wX910!Qopr({>*Xmm9X+EAW43n3!4E0a68ofcOWOw*d#4APBcbdR7zpp% zm&z99!!hn!=*$$ONYU@gKe#`Q9VIr~Aij*+{h7+G9i;01GF4oqcca19ez=YIuS08; zdSLN}%h+<%Z`ccmZD`^zk3tr#uTQo{ZQ@iEH&*K=FxOjsugDu#mW=;2zErv1mP5;; zR{v$B5(7aK%sbcW*X6=}*uQZAzeTw)hnSuu<&CSp&m7|Fb4tGXsM=x@ntr?QZK#8Q zw40imvT?CNPqcNObzAh^lsjIJ&f0ZGYUhaNn2MMwpZH*Jd_#YC%=lNMzwG4Oe&342 z)_HZ)=cf!^5++92_}H$J-{sqCc54pg4NEj`>fhAH8!w#ZJ=2cWt8maPz+~Eb>ZdSa z7OL0+#~8}1&GB-Tb|?VBVAzwCL`rW2wgSWsFs6!)ec5u!HL!f_rTq2?T)UHsx&2W^ zM#sm!z%Ikdsj8>fsNsLbeA63(G#>lCY)l2KFp<$RVRx!%M=ysqc}$ z920G3+zUw?c3aD+2V2f|&jyO+7^LZXC|Faav^n*M0%K|a3LjfzmPbV0b-j*_pzo*W zyUACgQPRcqOh+P_)E*?GNxvTih#&muX6X^eca+kRI6w4iR1Q4fKiy2MlNXorm-OnU zO^d0JJ=&Hb{s%+F}?3Z0N`R2Ya}R8v?od)A9n?@%!E!j8UqI|;aLj3{>H zHsHF=d5*iI2fv0ws_amv`ylh1o-f5up;pGWdw1@w7WTP|_FyKhyO8j=S2H`Gwr(zq z^++0tKhfwsS#oeoz3`gZ^IYs%&Q8M1zzkm+WHq1PozK=yxQXCE(KHA)LV5KonkZ>N za!Zcj?(SkVlHuze_Eh+TRZPjW^+%y zfr3sAlsiCW7p;9Q{(dE=wCekqhYD7OFBprohMyz{i%npN960B-qMJ7!Wirt zon|pJn`9#d|JN?#O!fiSyo8Iwi8a1+bo3=W%Q{or4gBB{O_v~(q1l7`eC^sb^wN{EYtd((a@Sw4>St&7+7 zciSc%>rkQGz{H(NSvn27c-yAm@4u3`ZJm9$O_gGC9`$DV^973?{Zig6-m7I$vAX%b ztshGji+2Il$4D&pPpY2Ea}_NSem-hGE{0*z4qtQr<-jS`n(IvgAvm({2TRx9?2oee ze$(%46q?cO7_W|Xuou})9|Y>*#>0=qc!I-)J@JrF{xKCJ3PM~}TqVdQ%tc#w^5ok($;KiDT|C~q=Fvm_)av|WfpyoURjajTl|dYTS;6$A0Nbsa1(eD_z|tG9*2StK1x_1nJ;e5 zowIlrEFeEM>)Vk%s?Eh0o2L(w3@yKNt>KnWvvJAW)8e<3o3)MZ*Zi8YOA--2a05{r z&}FC-J|v}zR{5GKobPx&ymX08JfB*+a4S5%k!Li-IcA`vD`ND`aIY=%qvZ7_%5xF7 zUAMiWo{9Z81A#AS5wlW9_|i@t_}A6L;xS0D$ln;g61PG-4sTI3*ReFWTkBTjZ^P&% zJ0PoWUw$ARTRvPGlhvU_XUKca&*$@p&*d>rkzK0=V~3J%@34euxPKpiU+&N7N3ags zK1T7giLw#i^ip<{Me#XY-S*7%*sh*lxK!^gZ?(7+e}~&-X7SAaWN2A^D8ziN&%-Oo z=*xXX6Xzi9WloXNU=g?Sf^)^t^dgzW|7%F zw_L+fiFvlZm^`2Z9R2!uBg1t8C>;w%^4@hj!kBf^-Ng-Fg$Em{awx0`y=Et+O7ySR#0(_JWF0e46 zF{eQl`MLbFY|L^kH6!ZmUiY`0`1eGzS9wt1tK(1*TjEn43>}#gx9ZBJsO~vnJ0u{O zg!ogMjTJAYO2#&laa^FK_!{b*ioVxk)MEF$xTxSF=`tSnU0ROkZLV!ev=Jm)@#b+6 z6m2|h-Ff*1;|I1QzHe`-e6CJiop0oOhL`q{l~}~nanL0yy+c6WO;=5shVDTgRpFfo z0{i8KKJ8 zl7%Kat4F(?G;A(9;Y2B#+dOW7!(e}>8T!AxC@9p7j65qjajVzB%SWan1zhoW|66U# zAC8c@syEY-y7j~euo6m4J)wR0u&TP+ZtH=n@Fj%N<&K4(#mHJvl^^C2%7fxtpHfHNB^cwr?Se&rY?=e5l!-6*fQu1d+FOV=|T z6LMEf*Gd#?3U=Y?z&f!^TWzf&G{oIjXTwH^=TQ9 zD_*7#T|wmsh3gjXN5!|>g4-Nqk8+>qe;Gw~_RAoSOg4lKWPcSMYS}DVaZZ^0Etxea zb=06!q+_Gq&V0!Ggty2>p0~rsiM^;Z&drQIR5;jr$BkM1g%T!0C96RzNTU1x=h)BJ zo08>Sl!BGFy=tWKp9?u}ldMX6b<$n__0#3dbQ@nX+a8#PbNSJ4TKkwb{jzI!ujdvQ z95cq$z3wV#U0|ede2~ibECMgmVmR#GWyFh9y$zff*u30Jq{})^6Hd2x>~s<){ES!s z@8{MXNu&4W)iO$7dk8UvT?P_t#`PN=na@Pu%!=`f#(02LlJtJ552-Rye9=~xaG0+S+`CNlw)hLA6o&wH-Ivm^8kfbg z(P`rP9>1i(o?r^2eXG-eqq8@NFAA6Gm|7Roe*^cc?|A+&sR{Kx8X?+Ua*1Ur)%jeU zHY1UI-9tOpm@~ba&vp5$8s2!(^*f?W!{nK_eJLR#odbjzO-)bH-#vi>4!q5yqocOW z`RE827Z*Z8LQq2j@B}J7_&f0X-B<@UhXGhbY=*5ajfI*DH$uhWgaL}JCN)(L6g?pA zP)ELeZE4w=nU!=56HTV3rq+c?(GZY&`UaGbFpdHrA78yldv$iU$<+}w8vs;b-?{}~ zpMgOB+>eEC0!Cgro$bFYh{TWdtX^AMkPoHqfAGxznaOxJhXU727Y8z(qlqN~f_p-z zr(@;RBj`WT@2L_vsc$uS?YpfdyL{%P9UI@ALu4cxw_R~ z%l+NjgYM`%FYz3iuEA9;(&#&%KVP>iARbsu$lUlqxRz1tg=pZJv%~a(>x87GdX&OO zVP9>f#5PgA$v*$&woEILz4o`$$9c$X#xjmb%~wsTLa6|!4tA>)Y!yG?W-(`7#wl9A zZ_cy7P3s+y9biu*VJM;Sh(Y?XvJE?>QLNE*pZ8xS&W&hc1GFzw-=3+{DXwO2C^_=& zK(ilRNS$R7i(Sn;KG1k36ik-OAEy0$vv1~|lt;o&6!SBRK2Ov$gpl-Y87zUF-6rwa z;3Ni#2W$`8Ejo^E&;l>JQG|+aUCNrim7C@*9P+DJhbA$&yd^iYuI>z|YM9g_2^&@G zJ(|e)q@>Pj%D10$b9q29d-4df{%so?eN}|B(EP(-7vgyH{KX41&=M7MplrmIHBCC^F&CRSkxVX3@@B0wK6p7IL2WvbVm-m>77I`~ zS($agr5bv0$>*87_6>hL$4?h*J{6PI#dzFgcp@C$WvtAtS{EAe3{~72ZEGA}lbdFG zzI*(cDJNMqq1o_Bhj}~sPk}hYU-fdx^45N%sm&=D;R(;Pu0O|IdnlCUJVX4gT5>Yh z4Vz~)@-{M15w4>bPRutp@0Bc9gij!vgul&*^rTlO67~A8Sdfk&UBCR^I4vtX?L_yP zps>O4#?)P?dhfu$K(O(;S-wqCLvgEeYN>gV`^dA>m2WvygsDk>dB;KLXd}tsTZ`_) z#!+tT)F8v_?jGE^kuGu;oNs%ss=^#+Vkbte3MA~%F)?~GBUghz&dxl#(eCGVeCQ6* zb@GfhaFL{|xd(^_5&$$mQ_->wV<$(`+>Qq`@BUQpl=)VP>J>J;3&l~3QjlSiDl{4{ za@H0cnqU9n$3Ui*;_ZFW{f%FfP1A_e$gJ6HLH1X%vvyaHVnj%8C+m;bKjg7u@gf)K zQ-j`q)0e5|aPnP6CF4!@>^Tg_aq9dVKc$x=TrIewU)xMxDfmA1zOIHk@93)?968gS z-^L3xPj8TVc^*xkI_%Ex1|A0jJD~O6X6__|7$Fys^f&YndRqiy=J|{|U2*47r#gSp z?Tfn}tSgj5Me&}-YaQ#tLGBM9Q?B5x;>8k<$W~}cy}ns~NSN9jAxRrAn{PCHv3Hi* zF&B%5hB^>DpnhAMcjRP(lq|6e`t|hO{0|N|1v?=-f<1o`PhYE2K}|B_!PeuJl6g^i zSjnZao30*^zn0%G-Jh3rPvEQD)$K^IyoT^I_Iq#aXFo0B^7j8Q12?8|u(@roi5wjr z$$(h=QWP zn+4u3-hCN#lVcUEhfZcrcCYQa55ws`Fs!tBdp<6z%NEFD6@Sk>AY{W_Y&1-R|4?;A z)uP>0sb|IE1)WSAQ_qr2I6*X*5*vLkFd(h6%vmlUFl^^GUK}rkD>!dh-LD61YSKCh2?%W5Z>Dic{ zMPeq`8mU*&txy_P4VlDuT))kK(Lg!uK4)X>Q!Q)zR4*6&+0`@P>6{eX~Zueo(}7<9p_9#H$hwmU+FO#w`SD_srYO zKCbYJie$`rE7az9*sw$K$AlrOL3;~do$sLQgA|HuAl$RA6>U--bVKvU1 zBPO?fdvcR~sztl2j;Y8^2Y=o^X+B@wp&g<5@}b{3IZ`Xdopoh)tFP9(p9*7khSbkXaFZLj z%k)&0b50WA0LjQyOkQW14fRnu>ROHPO5K&}ArM-aO#7Sj&f5X^WN7w^{!CyUf3t78 z&__mwCOUJR7eIO=T~EQiE|K&fUrDvk@I&$c#g}T97A=5Z4CIxC>(-kxjuja;d1Q!dv^_-<4@(gZWIj z+_?S`?s-USCF+BJ6fBzd6^;A3eVSai2V;HGX z=60}F@%U2G@%QM_NS@{sqxSmQ+0LDvczgTgt%e--mS2rUeL>D)Dq&xj$5OcSY~mQR z154Ya0-}>7Q-2C;yfL6U-fM}b5Jwf~Q6}$kue)5jm}U9atlEsg-Ie^ns8jK!bf>pK zOdc=wchu7Jpy!%qnoQ+-=14=CXbo*ukE4Uvm#CK;rIx%kNh!Y5ZzBHymZFDMU&U@q zi}1@1Slb@NfWRLnYgKK7a2R@!+@`;4n&4aYJu%BhbCl)kP911yjfs9|)tY^Z z4fj0lYG$7iex^9t49j|Nq-i()$%iaYJWu6)`g<|Fk%!DjHgCOsz0aety-2PgGg$S2 zr_t)gVIiuQs^tii%@-@I7Cv{aw7QM&d>D4N5!nR^t`%+N>!ddI! z2ybnZPanOL^YKTDK5nky5)Z{2Ea;7xifdi195biYznBx7P8%p`wfcusnJ)aKPP2Ra z#}#{p%b(^!yB6!^#tk<@ha_=?W?o5ZzVCTy-JUVH?#~tu0C3wZgj=z zr$9x_f1eNDB!H5y;>8}klk`;5A{xmXPG~F4f79{y>s^n*(ryFNL`-q<`PhHF(3} z3Q<6Y^#eKbjka^X)u3^fi;av-fSe)0?~H7^}A0%x#H{{@*H(rkP%J`?OQL3zAv&@G8L(msms zqQlbQwU*!G(>Z{8bj42K@Lf#jX?d|luyvMMK zLO%{8J+@~WE5Cll!oi_sM@2{1)Y5|BQwU>($wDc7PHTV}K$Q*OnPdbSUl>&g*=9nt zv?gPvMo@Yj?C*azuZFmBm{;lN=Lar?*p!s5xw$76!;A#MoE}HET#167C;KNQe>B*( zcZh%1wn6^}R_tp&Ibe)|R45n-2t%B}ln5GSFrQGju){ap!o)=Jf#*Fk^Z>xVC642Rn;i!z36V~yv|7EA3WWybLy$MZ2^tR@>TVS)xlPr`vuJFE3%7dW0e z6HQ{mODMUVKl%GZxVuYTooIFYWa*4^5^1?LS#9_pWb>@|L;jOw zCjI<5^wU2lCLk@!2w3|h@ulCt!CdiNU0q#C$sQaKV3PqL=^@UO`>+mhV1x*OI3Vu8 z#0-RGVgs>qJ)BhnQon@-E)r#4myHoJYqP@Wp@<0Z&V7dw5s+6&%uvxR*eTDh)zKWB zx-})8z}_P zaP+&V+2(kxtLw}Z+wJ>la~qZq>F6vCA40Th@$PO-EK8#tDntQNad5PBbyp`-7%M{a7_V|LWgHssoiC42^-AB`~}UhN$K~V`pcF_%aaPK>3xMn;R8% z4=f}Al_?T|sRb1Qo}+n2DG0UGy;!HlLlptfoQbiqQaPlo#U~`>=jACLV_@A!oXzR5 zrOy4CF{Cd6C0ItrOobSkkOvQf!{uQU(1QtMn%2`Y2*j?1dwZ@T1Q}>m-bHye$s@S- zSi~nYQ_YEUp)a#1$wWgVPeItiTngo>u`%y)xW}aFk*!tt+4-r(4$;HVh+~}4e#1z* zzkk;Td%l0?z`*Dl*ED|ml%50QKUb=(|8*9Yj&VmMlL(ziv{N+nQXSij%Wkvzu4w;= zz;ZbAmfL!us|_j$D)!Uk-Cye+7qKuy{n(jCRammQTG8~gDQG-sqjWyJ=E6 zZ(?IVy0L|E7skKF$hP7#oX$2nq^HB6|4fQJ#bAf+8D{u<)E> zf+ZWLKv;PAT<6o2{>7kh0`->6_`*lDIoA+TQ4O^d%4!4{J{uc1>3@utaIdE(hAI3! z1}nE8wF(N9=MCN3;RCmWf@X#uFm!CMGro!H^nMdXlPD7&e#|Ti`GJ$ayPS6P$U=>s z1)+~^VE2GVzJ77|E_&qaT+^zrGyUw_J#`mnn%+4Lk?{jSCJj#T%KS|T;U*H!JPdiU zOshzT2&je-z`` zc$M-33M3~}RBSv`Fi@QT!t;FI;KwXBumCvxPh{RRm)VU<2p-#rjj0ia-Jf-y`groZ z;h`b$!XVhI)XBOkdN0Qf2Gfy9EbOTlk%#T{^z@J;AA0*0$D^BR@tcS(!cnzG$@GYV5aG*39KK2 z5z$ZFRphSbTbu6hG;yz9xVWsoy6#P45&-C(s^0pL;nKalczc z+<-30aD{Chy;zI;4OuAGItFyHZ#>^gIxRUB%hdKi>n$`4yP%x5`X`H3a~F6GM-dDu zIi34~(Jko-Paw>X0#ye*fgr;N-#b{WrKO~JtHjEbp@Hr_IOVInG zPIcbK`i(4CycGCy3Xx~dr$;M&)TM^InQ?LV5rf0S*Pg<(FcI88f3PjHe|kG@zWR=O z9YI-XkP$}FCWc2#e64A3*(`}@x}obX?kEY##LAinGqvOz!Y6gcgxy%PmMSQXcPAa2c>my9U?671DgrVP7xI+1&tExR-51g1XRw!`qM|}(0kCsf}O_rxtn-g{^FW>-f+XM6r=>7%3H zU4BIDZXsj^7zmht97<8#=FkZ6qN0hRfLUGwuBACSCNeTu$#N7=^nZdXg@dzk%6b|`E%gXrp6xCLH#A(N ziv^%#-S71!|J`_nLT^|%HpW(}svh_v@bLUy z_c|d}G?ScCpKUAvCm@vE=n!S>_}db+$P98o$I$jX8OI@RZ{u2a5fkj^VZbz|Q9lAXuV^trgW z&MmjN9%Fm323Rk*pKROu4=dOYURMIp25xObWu*&D)v~ku1A}j`G))k!QBzd~ zizZi=QFD=kC|+Hs>$wbL3j4C6zW$#E&)Z~V_x_;`^TSyZWbh0NfSVDP9;kWKKYzYT z3x=8QFbYrwTB1y`1uo;x``~XzR5`BR6~SFh!ZMH1s`M)Rs$y?fO-t*N>$1H^L}VnN z!sGN^Hfg%Chvq{)bTBABZ@;~8KXo3Lx;jQhbN5U`51qb}yna2WQDzMMM?vOwv+;U& zY@48ZQ&%1HD_jBoN;Qc2|0iTGWG+u21uVy) zY>ecL>*(3Z==vxL0}-fv04;#lbFSk%3MC^oBWp?XTAH3B5D|e|}A&W*rt(e1uKq3=S*!f^Kk>#IXf?gU-2mq)e-{(NcXe}Mpqm;Fnv=1vt}dbi@n))) zRqr_Uud`qt<*HZb`DfpU2%fW(FKJCKjbkCvApyj@Zs~^Bu3H^Zu>e#=Jcy_tawlOR zaUhVssH>WpU-Vr}i=s?G6vk?@u&c6n-yOAdekt)1P)#$Zyo#iXcPa0HlX&OX zi#6NKFgGv$syp6&|4l#capI`6#nD6nx>(`sK~isrq;`Xeu3@Qxpk`w-H>VFWed@gw zMjZLy+@MTMp%w1=uXe3AM@qhkI58xs#R-n}!ERd|20w*ZRRB}S7SA8fVTKBTvi|-X z$JwPX9e_XCG}IA!JJ+>Xg=!izMbGJW3T{(wvu}5AV2#Err|hx*o%`v4cj>vu5>53) z*zRZBovq5r$eR(R9i^4RQ$j1FX>pVHyER)yyWc0BFCPoL;=7JKDE`R9JMY!$2|o$y ztL2#G6i;vG#kO$JHCd}!TD@&HMtg{^fOp%H%Y7m9qk(+rLGWHjacko}FSEIi{xSrS z>x|Rsx;JYbSB9w2gTJ}9{}>HOUrqlwbh@@z;1zJ9gH``hTL+vD93U;IVqXA>z;S3$ z>N?;-xl^;|_I|zc^76VpQ`6_l-8pdQnw^6Icx&Cy$j`vDk>BC@)7`>fd(GwJ<`ZWC zdQe5e=sLEU)uWYkHmefKM$g*Mlq2O)5Q$yn4sIbXVTapljko&z5I`f9SLKh|q@*(H z5Fpg}i^yG!lUl5B8mqlfMQjO_R8$>3`t1<8rNwJ5RWs;~*bXP%ynKHiBN!{RCbw-H z#foo_nN9q`(=uSvG;iF;;Aj6Ia`+TJ_$oZkv<^AIOM_iA!=|vNF~~tQ&?R^4?-t0M zMx|vJ@vwe+n>ETjW|eoz+~q#+-lxAGGZe)e@6Dtp zr6MJ-8@hMz!oWi30`r2+Fw*OGBZoc@a$$|7f%e~9^JD3ZZ(R^}| zj}fg5Jp+*=3rIy(spu2XDiKnmM*VEGy2C*8P)$**&v%sP{HW$OJcSs77{nsfKb|wo zWZ&WFoX;{P+0~H2e?wg2plONB7|5`)yDWtAoZiHFw zf4+h50Ki16nlYY4eq~NBB7BbJ8bHp?F|Llu^O_r!t-?uxk)1RsK2f>%OfnFCE1f%C zD~Ti&R#AA74!urcc(IvM2Bp=6!nXLLxTv9$o=2gD_>b@(X5Gt1-mmh#nISTXJx4vr zJM2*OOq=(3>vM9&)RepnEQK!BE|Nk*fH5a$x`a8X_jT54eSZ3YafX&0SevpXi&T^wadEdAeRSDoDd^3mGF}1 z;>iIkjtwK11>m(H{qr0-z5C#<;e31;JBzZe^K(lON-UUtSALza1E{Co4@y%I_(4+( ztkrnwxJpGxf5`9m`HwO=2W?j}t0duYqMb?6=6XdG>V`7=a;F5?!M}Yg1Cp6N4{F6> zKZ^G5W@ALGRX$odA0=znCQxh{0?oy3}Nzhe*Z0 z=Dm*KacR8TfF8Ha3Iz8kENk>!_YtKeHQ-UQr|K|>V5w->?D^d2b*-@o~*=> z^9UN8R2{vb!%uX&f@#2g0l7)zG%24Hb#yji76xdvq411MJ?n?+Z+!f!xj-Vd8$4XK z0!D}|r|XECFw>-UT=oTL$OUIx^qQ3cyw&Qkuu!+LsSc-Rz{1jWI34-y`#JbE;Y@Zt zQ$E0Y{cB3#a{@LRXw-W#chFH6)fbml7mu5d>t#K)G}JX(kOn0h32X`IH__p7XqhIx z_58+j#^)X}`$HKLsefTU!mU~+D~4gE#HzSMshQ=Y%SJCQj`h*TKpvt zrdAxeJ}|IS*%4kirr z<4e9+9iWBg3_OxlgZ2g?Ew)*}^~3g>M5oM@ZNC&hWHLNTZlL$Dv6a3f;%+qYgaOjO z25npa)a>vIFgRH;+#1xYyzE$vJ&rzx3(Q=veUttcaQ(A7tu}(*3sT3atCtBRTI)BT zW4*fW{^i@N?q|M~0s*dkhxT53OR{8T-4%&e-pLm(LQ^jo5IrGR^{YkSv zC2Z|2=PGCFW-9UGeS}|sSfAKN4S=K^LYedb*+IBb6T2)bBhkSa3>cyZH!os;Z3xEK z3JVI>Lmi5PDuOtgt!=C2$axVt+cH82qduZyWA|Fqii?*wliAJh6$|8wfiPOQ_`x>d zB8|`MZ&3C3XSE}2cV68N6@Xv~xMu$@81P`e@7{r-K>FegBDu`3fdFF$3`F3cguxqN zI|1Aps%=FDg;~D({QR}vbTN>ag9vqaco=k$(UFlBBj}WV3KlDceG@OofH1o%?A*T( zExs}}y^1Ay#hN1Q-K}5J`V)FPOgtKyBpxuII)fn|#!Xx~s6aOhzp%yC8Zt?$>=wWf zw+vmOX>S@Rjx=7r{G6Tb{EQ!0bS0o_tmTeM7><5HutT?Ybnx@=*vjgI^JNp> zFi;>l*&@H7e})t-n9S_whYC`48pxLe+2+XD7!d+ycpF$+3K)0B!B}C?*cK;i{fEd@ z$bnN6o8R+<0|7H5l<43JWlDT(Z*AS#+snLb#xZ_*acG6I0$V6tY8a0Wp8NikzJF{e z)nNQwZYBy`61ZT11i?#_Rsl^$gh9Yb77PZ%yy?ZQ_c=ZNn|{d-6~n*g!0$27nyC*+ zLBD*lwX(93mBlu3Gc%(@dO|&j9s#YZTmA znC@ItT}?#dJuwG2)fN~CV3!aIT3RsoMp0e85?pA$lzcSS|9PK$6q7&lUjz5x3VA;B zkdFXUy(}%0l9P4R)GEvdvSAX%Erf@M$0u^Zgt$0h0tBF`y-G~PkN*7mGlUbsMP_;V zB`x!ZK$*Oy`pFR6IJnRfy zEGX9W+2TRjcn=S6J+AjEF~ARZ4e7P2*aAM~29qB0U0E{VwBVu-R{3URWrLfHVJXA05-MD=$w7cw}+>P1iH(vAW1X#;DUAHWP99`S$-j^vW8!P-2+aAm(j9}1I z&dQ05k4%cwc1I7q;@FR})Fa`ezk<9exPD;1a&UBlux*>)Fq+OYQc_uitNNv`8MPpD4djn~gITYA%3l3$chMh$TVgW_lX(Cy>m z;r)0-bi)Y++;;MlDIYLh`Zq~M`$g%^8Tj~M6fklM3(Qc4Aawa3o1S?q3IZ&4LIa*0 zMjWn5AE1-wTv4Y0$9D}@9m#KsZ7}=%f9+Re#v)fsqW#J%ezMA0!3GimjE87Pv_~e< z#(n-P^@DD*T&bPM3d)?BCsIez+c zm(6v)jMERD{hiRPrQ!%eY7qV-%tz8r(r9-6|0Xkt#kwXOIsWNtrv4~Fe&744&rz1kgR9o{+P|84 zpIAHIbMfE~=pUl5t1GQ{1l-566Zl=V@+%|fR!Y##Ah-Jclc+tj&9ri8xTx4)=4)Lz z|K;v>eNX*O5+va<*e@$at)E1joQ?fS2^9>L5y+s=%BRnwXMT{PnN;I2*bUeLqDgT^ zQC`;uM4*Y}HhfQ3QR5|jaKr9}e;qEhW(6&YG~q46-N{g1itXmfi6`b5Iv=KAx}Tc= zwQ@E26QJ#XH_dpEca18sr3==33Lk zvKJ~rpv`zX^_F*u_n%q-JPZid^kr0fF>oQl8c6cj4z0P30Bw+p6wVSkpFJP6R#`(? zDj8`$vsa`CME*(=)pYKCUIwY(nIE5m4dnql5rTK9_ibzX$dx}L?A_vI2hm02%oUIe z;uq}}@FRf0w{I;yP0ofKp?k=Tq!gM?%QP_M#3G(LEm;W$7MlOX*;_zWy?t$?TRAEq5+bOiC?zS~ASg&kcS}iw(jlRgbW4|%Y)ZO8 zKxw2qrMp|=&VAnd|HgN}amP2_@&1P6=-C^7wbq(z#xtKs_Tu&QmaV|buT0Wmds+#) zYo5p`I^)MaA6nU%$wAs(*ea9ow#7JyA2F8zyE(A+{9E&43}O$s9NuhGQ+|5AT9=IepI@O?Fo7laIHb&iIf!Gj2R%kWu2;&ZG3`3y~_zYCyRg}*BHo(;ywJV`OZxWe$Ovy;e%Qr4E^@+dZ4NJuB z$SO?dxIG1R1@%`lev%&XCxbLi%PUtQ3Qu8};xgkFln@B;+l^z4=p|@P#m&Q>F0A;R zI}cqbFT9mww9@9w`#+JyrLvo-`9p?+SFOdXV5w0&rY(N*=9QiiMV!svW_xU6EX=a7 zfqVaE3V!_lk+gt$hcMx=XWyf8da(jZjo(sNmS_?Ja|7C=AnVoBnhYK7?4QJp@bT`A z5me^JgIX92b^fL1Lxz?Jcd2g=??O5OQV7(6fIK>Xv~;a1$I6&mD#pITG4x94Xp65P zXZ-^0r2P>tI_^iUKCajVwva@+~dbr(Mg33Ioy~Jr3c0 zF5l?0QRn!`J9^=`b(2~8+KcBR{|IfXURClOPuix~T50YrB->iQ?@wM4P?As)9Uh+0 zv>&n;BHJsnF+f9j7NE1`AyPZjr$@q-B3`1O&W7UD=GBS#`W~>X+FC3Xai|VEZ6A~m zl`t;d-(NqK;*Swe7UTG7(`JqDq3LL!)QXLxDKOC=-|sJjuidS^Z8UwGA8*BA8GnOt z!(!pXTpj+5x5s?hK&ND@Bqbq%5jggWai=KGu|BT9O2P7>Bi|f$T+#!AGz~*B3SHL-*_)xpZT~yi0|p_DeM?g7-C(H z&t+9kXx#~!=W{=DZ>MSxZW41v`%B$ZcUZPxSF>W}qRt!BojILmm1oL8S`t_Okm6E@J6r~i+iOssJmZZNdp&<}$I3SB_=REkASEp=5sZ*(V z?kf*<(yHMC<>`va!l*vSS{ZS1^BWem-(tTD$2;X0VJM$m6ghXXy7U!QS`;KM>$9kZ z_%eoKfa=}(_X0R29QSUc+IEAVU;Ai9re@aKU26aFsQV=>KpjQEER)FUO(8aMBhyfjSLIVTSeMMAN9j9xZ`x!x;58%zqi=Zyi1Qf7*JUs26B`<|jCBHDJD`;tv zJbc(bzYBcLfJG}rjyGwdqh10+NAcC~@6|4cNRXeUN%??cLV-&_Fi=%l2^b7{*IXnL z_#dx8DKg-fJ`x(&X#g%0*>FQdTuCVkeyKlG3b-#ITT*|%C&k8A0^y#bYeG+Qz~|3u z6;|4i9?5M4cn47ZGEq@wo>sB4u|-Bk3Z1Pyfles^t+MkhKp+MRfd2meMMXsrQ$7%K zuid~0c*p4P-&Rm1JUu;yvutf`K~+!(P6;fhkzXn-CpZDt4f&b3Mn;yS-~Rx8_})vZ zo=1S{hHF_tof3*-uYu%OTpVhZYk-J@FfJGE-=XhSA$2Hx0>~eL@dJbla6SM|q4fR$ z1eK`H7gzi_TQ3Bl7zPD!2*Ioy1BBL195M#k+e$uAN`;d`wNh3} zDi9Y4Ut2;*U4ixmy6u6T6@qO}K$WG0u*3oAD8Hbf-f^2uSs%zBb3nsy=2_g;ksI75 zaER2~EC$#)QUf3eDrwu(0uV)th|9}|2L^TmiVQ+-D+>z-o&{(KgE^DB#rfdD1B;pZ z3y6}Flam=k6d-1M_wIXSK|vkFnj<1?t}floNMOGDLU)q=+8`j=%3KO>qNYfyH1CE^E3`mD zjP(V?Q>d`KaiI|UR55E3n%W>5S84|;6ySb8YQ_;F5pb~^FEs^zi7y!>d!^sq{yGSz zG}x^w&@Ddl?p-~AO5g8Tmh^NN3JRY4hl|O$`X(loZyG;AT@wOB@ewRc%&!XAB5jbG zz@T8(t^c5mVjwrQuz;#2OSBEHNM3$EK;y>s7iVYR&@HC^o?BR;^ibMUtG3IbJhXu< zUx{IB9L%s*xDt zSU1!2gSkwFY43F?=1a>?N!SQ)2i57 z@SSktV)_z%At@yXX!Yp5V?T;T56xvFZLs!6B-N{IsDi~xii*OOAngJQC^m+MfJW#8 z^4YcZ^+oHC$X>(Ceu)0LEXQA3nwyy5iqO&1Q@Q47QuTkM{CMU|N=SJ0ce|l;MMgyA zlO4t3uDw;hoD_Gwo$w{c`q=nW}UpWwA~5ohN0nMNlY;D6<`V4U#G#% zqDsyvih02gCx9LRXeJjZX`X7TK@A*62D)^e_{qvtAK@}+Rm$znJ~dm4FkG~QH4olu zB)9bI*AT4)9Kn-;iE2Rd4OUU=&H&-M!}fGOplNnn$t%pg(J2f;TKF}!El}`AsH*{0 zZ(3TK4H=WH(jL}z6nHRV;t?=3K%)utmUtR6ksxTJ)EEf;MZ0Iz#!uaA4if_7q*naW zp!E8mXG+jBGBB`R?azcBK!DYQ$lyEfL_WtYpzJ#b^#lgObuecr#annLsH1{)1q=^R zP<3~A;eg@Chuu;-Vsdz>WF)r|HxSXOhE4;judh z84r5OWgw89;oGhq7##)3;tgmk`2PKwnaU{K#y6i4_`#me&IGWxkUa;a5e5a&+Cdv) zu1da{pd{o=c${`-p#>LE9DqaR1{QmmdiU-DGozIR>FSsC^mL(bfeSs z2xDAY(52n#ieqbTZic50CFS$2_~&|UQkV@q2~BF5y+3EL*k91J1*ZIhRL|-VSCyLe`X_SSqEk>sXEzQ}OVqpNVc1_nDN!Itd}>l1cmc z@h-^3d~W`e2)GK4iKh$5+Kph1`5sAscxnLY(P+C{8N9={Y4@2_7{#D)7tdP8+4_mujn`O96S2BY3^@cfu~ zyQ}|1q^;c2hjs12!-o=NXE?orq~}=lCPT0#5)%=jb}6?HFw;SqWZLa`N=AwYF@5*f z(64K)*UgB{8_54&yxDL3bv`G}#dYI?>|iQ(JL@<>2ECo32aB8V$J^ox+3BtRW~vHS z#}^B}F`~@sSi^y`x@}e;3MMA2tZZpW;=|;>>{>2V60-Zj1}##cEPYgk(e(dBT?QHdoz@omtHJwF-0z*bVG3NXz3% z7TAGV3MOCw%*<6FbA2p0h6xD?(4z*=#J9Ww#O~llWD*uxOjRF(_HvS~j_2=+)upV& zj>MrSs(;5%ZIpP1tA`t;&%2*DzquGXq&mNgyk7X_xNK%5c{q8EZcX6O^^$X!OV}$_ z+8B==eK=G{=!XSD(`TwRqlX#a-4vJe1M#V4q!~HR>1cOTaDLO4aX<5J$xG658NTb_ z3anIz+1r)FWQ05~u#kh)n07p`N_yA~-U{S-({pnt;%IKH7~)CbdE=66 zl1pF&HWHa}l$IAAZ67pXP*n6=;fd5*)>0AJ4cS}TS;m)9-q!TlG&$MEcO}Il!Q%17 z@WBur673&4i#u?I)-%`$n7Y0zTn8|*5n~Vw2ZwfzL%xE{o!+=LpF;qmAYUur8TLrN znOm49i~h8Lso?%F#FFr-J%Rly=D3ArKcNOvLt64+@Gt-wD0!OcOY%a3Leodu(2QeN zm2~vuIxlz%_imx$o)-)^r4*G*kOcv{Ju#PLveL?|vk$&MV7T+;W<#>>fZUTLr>H%t z3A?WWR&y{qYJSsGzU)<~b6r2o>&B|I_sy`fN?3E@(yC4hKDE{NEY=I!66}-55W6c z7a6{Wm>THNqjxnq9>uMqpre92lVz6Db=PxdX*e-&D9kaHqW3FYD6X2(xj1q-+l=>( z&zQ^TxjNHo(e@!UMVejOtj9p?dgIQg>{D5xsuO?A7mMrRy7Z6VK!jFs``-vXvR1$g zSHGf<$>%;*0bF8+epk&x{b@Mi_qWPygzNwl?OhVzvo>!kmYjM zuyYTWqqJkUJGVQ@PPm%Z0$Px(oFV1Gk(2x}+^RqFTTjx&M@KTYcjH=fj_rRg{*vb7 z4Mr~2w#VT{?1^F?%AYBd{Z2@v;gLNvYGTg6nfFNksr>%$uE-Ci6DF_FPu(|;S;3#Lgvmj=9`Iu@8qxw0!$~N9i#tZG|potKFh<*3q>wGM8hy{XMv0 zDpN81!Cd}vyzh@s#!u_%05a+s(x03+4~-e5WVdQh$%NeVHgKPRge`KxL)GnAPD`8r z?Nw|n70c^0p(~oMoUV3;&csRAVjDCV*cJEjNP8#)J40JNDH&$%AMlp#R^)CAt%y|Y zmK%|Jhjom1{JY?p7K^JSeo3r988JtiGVQ5lY*fM&t4`oFCe<%e!+Q_Qo)s;mvG3ul zP$6Ey{hwV;Ya*5$mN@jGGt`%Fe{*Eh$4f2q6eKqYgO+l+)j!l{+Dp8tdBt2)@n|nDYLY{)J4w?;9%tQf{;StQn1e3)x=MF=qB{H z-ox4hUm73zc4faezoLS5wJR)*LP>#tw*WKQ> zIyJvdWP_EXyNf}woRXA1u#(pEBByY0nsfTS^ZUpo8s=TCJ)|PTkyhU4oxxZ$bpmyQ zPXRl#??}tBDHd!~dT7YvD;ez6PbS|H1yj3ONJzJTT)p+ppp~4Y;qYh}M_Wiq?;Ea& z48P#}z1?>T+F`RzsEQc$*HKO>f#P)lg-OqUE10P5nnZ9!cH=ZJkuw>xe;22Sa|kj1E`nnVre zFY$qI&VLrBBP*t-iIN@)44SP}881`w$nKRM@gHwYo*FMF1tsd+>JO=}g@}c8<3u?n z*^?GGmkgJ2E7@*MA1rCvO57j{e%n}cuL2hQqgg@HQj-;!C4%GfIJ1}yRs^F(aTR+*U)T-J7HRNfUP?Mq5gZ?}8YH9jNi zBNSfZ3>vmZcap>!_?KBIf2({E#)g(G>ovZe^_Rd~_ z=FB6sEqlZd&Blfb59t@(s(QMo$Gb66xFYq~SJj0Qx@aLE6~2DOBMNN48~7{utC+-} zl268#mNactpK*(6TtYJ_`C0fYitni3a#r+14a=<-NLWOZq9N?aKL|F|*XxM+CJk)H!G_&iDELZ3TQmvxYi@xn z-BN1y?~^b@RG&RqFY1<;=F>q{LH+Q?|<_dMUp^ZF7S8&qv~IAzQi;?3e-$TpTu1a$ zL(T>Y{H%0PZnv7!;I>N41LNLnO32Ll*xO=4g*H7TJRA=cpz=Pax;8->3U&OCw*oNt zsN?^-r^I%^MP~DDdH(GAzoJ2>s9vj{xTtVkE%~wjgX6kq#Vwdph;u(V1}%bev*Ws{ z06rhciz^gn-cY^~YD?~TSW-*D`v>{7Nz*_GJ0Q)=HC9vN<;PyqfOistLf<{D03Q`{$M?Xx(>-(wQF#lg6gdyXH{6>{XRIw z;5}alznbqM51t5^9>OEmAY@6G>)4WJLe3`I08XNt!;w|X6sE9`8Uwb& z?dkg^q2u<3EqDaJm}S!y1w~9n+$=jUA5nM<8+T#%_nk7Szr&Rwun zaP8vypz`=>psTQQ<5;#aejvVc89%*tuBlr#4>6BojiFy3wSEyd9S7aN!B~%q-Z9sv z5JV27CjCgX{G*!b{i>XW7g=ZaF89A(sd z9j1gD+m7FajuOnsOK_)Nk(0SaT)m{dd&IWMFt=ugzaKz4YK3j9^>UhD!Bm1g5mF;FMcC3I2XJBIl z3v2h&6{2ehT08^DYj%WOLsa7krs(SGGN(5U=T{rF?ztXCDQD+{7j8pm3l4@cRxC@w z2sc{DHHw8F7ijl&wBE3&H(U7PiBwuJ1&g_cUCc_&FuSfDZoxQ$Le4qW#oll`RJzlzf2)lSQ*XYkc z-PuslP*P;8Wi^Q-FQjI}NQy;@PQlt_GN4W*8VtMs%R{;lFXzwTBAoFbtn z^Kb6udk#mAK33CRHKZfhHAPq_**e=fGq#nR@9unQNsbkOWkI_6OhZvQ5`wcF=51w= zRFrNr@u)hsi6LWhH8|=wDfO(s=8emd6`xc;mFX>IYGk@Uxy-@C57B&M|AV(|c`|Ah zr$xyHmFsGX#L#=8w1t$w#(XfWG5D3!os9p5$@4U{nMH@y<{ z@abHtRz^;fQPUC)FVJ)D4_`&^jATql#*?f}45$_hd~1EQ zW^BWg9`)n~uiVg}-Z~bcSNEeX{S4tcl&N56* z-eb?-W|#1<_q}|UpyxB>^KY2VM59IReR6yhqxA0CyYItgTR$EczUhyvcev8QBj+P$ zN^oU6d<%*Lb{x(H@f*oYN=yGFqeq?Fav#0ZoQTnj-F((w9$avTg0g~D^|EE-`qx#%b<2Mm|BQcdqJ@u+s$*<)?6bk(0&M|N zgoDl_ys_-R`%U>qP4FvZD>;>96IYtVE z2OljLVb+Nu5R%@g9l;+;v8SAS6Sh6SgB>2Y!6VX4r>vN-@DcO8q7+3Z`Uh5G^rhv= zA&UtXp%>LXj2I`Tv_2E*qw%$|^0LMFE32m zp6aT}o|8G`+LXOe;;%Y36!w}0*N1$GpTtNAITfskakK1VY_aEY#A6focrZkA4D9{v zHYM*O&lRDV|xK_Wu!gY7Njxu|G-be%JT^L~IgL^%z2FTW-)R@9!S%amVrHh89Ldt|^Qt7uS!Ib3(_HBbQbS><>79}wz*Uk z*XpZ=ohGRusZd|FC#@i%;FH=kG#M>sLNmiBvH5kn`q9{;(Ix>ayEpFL=(}YMc5xJ~ z-?P$Mh9>PhGZ>XFPR>uPn;InOrAqpX?)r;@UsiIfTW7AXDUsfEaQ$gAA;0B44(w;7 zvoozisn$OR&a>&6^C1W-87NUS&KcntR$tfftHWA18qIAmy}Dz-*@ph&vIJLzY*7$m z5NQ|n>ilF&7Vyh^u1t1b0q`Q~gI|5(JLjn(`%gDN!guP@V+=9s6>L^QUCt*T7WW-= ziJV_^v!?~=mYe-VrdXY4{0F|FJ6GlH^7kcQfkrOMLvfSTFdhF?a=Vzmc=OJNIg!~? z@kpn~s74NNO&3mGoRUirq8WZxx z-81e@VS3A?y+tHkaqq5vm%cxjHB*#)E9iGO|FP2$?`4F9iD$^5*)WSq*iZn0(Z`RL z?mr_FbqMS31^$9iG9qHKV^T|JE0xTPvNI}o9hq|fa=A0kFuV&)fo zKG4};Wns1N_-1(@B$@Wgl*+&tg^8x()wR56tZXPD7;l;&&-VGJ{(BiC8I}e$P3Z}- zk*x34&r0|salbb9uUJmj3_V-mzfBbyqY8#nMQ;MwI(6Uo(VD%clQFx;0p`c-J9`T` ziLRD5NeN}vP(d9w-FjuoicH;i!79os`B4+=H_TNphU#<0G}d#xgN~@YAM2@z5*B_j z&bH2O>~~Vx3DWJM@s2f%Tl;RGfs-x9N4-9SH(y)@p%-|goHSo>=k%AFQrW5}IX+oE zrEMD=uhzRb(|-tR?LXFEKt_o)XDF|ErlB=e_)}p$-DYW6sx;j{1i4Kr5<(Wp>H?Rx zBMPtXTja6s&(pHM7?NTNNO7h9H2-HR!q0E<7P->RTb(XiWX0G{Zm3*WQpZ>o6H~$J z?eW>}zDE|H#RF1x9tyjiZ;d;GZ5Sl3{3GpY{txN5R8MuaHai=nqQj%!i$CpEh~Avq zS~HoHVY;V!E9;#u6)(7OogVKf*JK?f@s{-ra_uu{aXLb-LP-0{ba^U?*CHus4-34k z;dV`evwm|k=NDq?V(H(&hfg(hMk^J6I)YxEYd*q0Y7w4m-YRsHC=~;h;|&>H!^$1k zi1+hTe)sOz`RO7n@){IzV!4a_yk1=N2;FZl@O`MVbM~wm6TurDAMezHHQnen9vyvh zg@f>96@6(!N%`&O4P`n$TqpY3LP3OGd03bYb)>~aR_?}g1Y%m}@US$+y+46H2WhR# zu;%&O6ZR+G1Ycj1)4gJY*3x7l>kEn!2oury99HfTun7Lmdp-8?$Ha9A-)I{LbNC&P z$3ulUndq9>7y$^Q>BMb3!j!~ZPs_n>0ma~*^5-yQlFzdvEhjM9*?*FISTNwudO(gW zX}KMLNLGj=kt0{(J+s9P6Ks!-YtMN~OJDvyT3lXYMMIN*--?DvwVkZZ5p*rjNl2K@ zEtHHRy{VK$yC&8H2h23MM;QGxMW7irr zZ=!BA(nl0@i+LOz?kHx(y8X=;B+I@HkA#7N{P;0z#x1+ygY#;r5Mj=@S$oUgv7Il^ z5i8>P^;F+QALE_QT$Mt{K0#o6B2Tnp_8t~i^=U%qH3V<#KsK^9)I3F~aI8>wlWQjG zU~4LQd0~$6o^}{A6|3Gvn9L%<}VyO3OrJq2SR=T~=1Q zXCtO?m!3C1DYCh=qVVcuKENM8*QC^e^K%}W4mm`WE8>Nb_Ht(CrWIB>R~Hp$CC13H zu`VQ?FK&gvthADNo1%yq9Uqn_;WuPJS(&JS>+tcm?)rM?_Qi^k^zWV>tH)@5% z#!gmePY)Lt*L_T%Lgeh`rW@q+e`8@FQg1ibK~}?a82H?sTwEZeQdN%Sv3+aYf`;fV zgTyf;`lRIKaFeF@W~6U=;o;);w(ml2p5nC;WK6CkrKOb}&=EV5Ky1c1z5eFe0|4m& zs0)rHasot~SMwhzT0Z3qs31ZmM-XkI!09qEk5m^xiidc9|KBU6T2jA`?d|P-j@}0_Tzh+E z2W|vH8-c1rc$Pn8Hwgpg2#TLj(^R&mhZ`_;;^KMErKBq>CG$4=3+u^;59HV7J`1{K zY1fo1zM^@=5R?u@g=z{S>AXxTRxWGMK>jGE=)m5KX98uDF&1EaRZvvaUEb-CJgc&e z$HODSAxC2PoTRXOiU?2}@=~;SV56lb3=9)GI}g21R1>gxkkV-4!AF7(`}w!|xlf$U zb=aO;&mX03PGpx@lgG&1|9iS?+(m2SP{77XXlC*`ij?TNW2XIT|JU7l-SwF32q%K@ z&q^w+tZd3;LMk;5(iiig!Hf(92Psh1((KT-kRz@VVWI zhUVPH4t6=Ay-R7VSoC;KG{o;!ceOp$moIlLEg@4Jg>74Zt@>F}ti^an&AYVS-n36` zZF#e^m>DEsdM?y7IM2__|KuoEWo8CFRdkuG>r`=g+4~m8sv0@2F<$KuYW*K9z}&Bv zTiEYB&WlXuTOPZ)r4>JcY_wdeu$Ng+V*BAyK^$A_La0z_6e)J;onf#t5|8oJtNLkq zq*qwb}JV%HT}S;Qp*cxL+u9u_|2b{P}>o zy&aXbzP1P|Xg4Zlu73uD^!^9poOFW-wz<^xDjQLQ9inRl-4^@r(g$8!DJX3h2i2#UwEW=f6~!B5iON|JHEDEll!({PambKyA? z>VaeHTh2^(wSDIcyU zQ%iR@BNf%JmKHI*;M+b;fUh`S9WaK9AD{sPxJ7gCjSG8n5eAD1o_(8ZxfA^9IzPp# zG~_K)>HR*Is*&nKX~IyrGN@un;VTh|d+e`aT&fs8sJX8=C}JQ{R-B%T$3MsjMyZLD*3(mYMTc1%Z9 z2MsEz7(D47Q|H;|Ld+`1zFrC;<%=S{@nfR1J!fl9RNCm(wS3q&dX2yxK&POl4hF-W z+8p)e3mNyjSzyJ1HZyNrMMXtG9_`Mz;t+FgbVSp4eqoFpJYUN#Ocr!=`FmtZ7Mh)% zjbi1H6}mV8kS8#MNJ&c%0U^Xp!&Qu~yQ!rmBM+sZpgVj40Lq2^Q)vDI2#|%%5nz)@ zpsQ{Vh1e3XU3#sr4>Bd|Vm6h#JcK!hJ!N|}M{h#Zgx4p2y4){d4@lGAcckD zN+dG!LL((oIrseh9C{l;S01`W0sDzfF1WG1tt&52VSEd)T;w2_h+zTef&If97bXet z#IrpJf;)M%=(#1Vt*xZ!wOwk>t3~0-^n!uf8<$9W>^;IRjJf{gYiVf|?%e$RK1fWh zudKYQngpff13)7?F9Puj4==Axl@_q+Au|dJtU#ueh=>RpGrvlLMF$M=w@u(Tj>DhVKJQ28v(&nLyj{jq3c zbigDel!D9Zu%QOhrl6e_VBw&>1zGIW+|<6yQ+y0-R@6qA`VSOgKofEhFoJcN zj)1te0-%xf+A*{|j?1a4W^+PA`2#FpJB%R)3QBMGscrLY6KXZRd zoe}t1r}OMO`w8L`0HO%X0RQe?v7iT_zkC7nAApl)s+xQzz^}EsDq*P@s3wylXlZCj z`4oTzB>#2o43Fr-f`YBl7NBmXoG2?Ri`a}OQpZXDGMJZ}8+mVSjni!zQb=YTh}Eu_ zyar@ME>;UJA=v)MLv2_k&-IBL=%1U_|7&d@F zlAp{Pyv@PAb=9#tR}_Jgj!yD|L?tZkvZ5kWT?8y-b{3Yj>i|U00S(+bSFX_3C*DCs z9CSd1rhE(ZD#cY|6M5|=AK@nQJF@~*a*8yg&jhqmv&!e^=Z(8!`_BC}0ofsjZ_q9D zgZM=;f2pX)#V=?frKJ*8A@wZC1hO-Lk%1X*>*~6P=>xPq;HLs!6wG+HE*IY2yAg45 z&Zm1u5*S{M+_pJtFplxiq607!U7=6zjU5iqI3jCZW-9d~9MfkuqdTL~JSdF?*C=z_$2 zK8Ug`xRDmqa6_wu$MKZo?(Qzu&1QhVg8vA#bmb}tu4Go*KNr7HbNESzs;Q>>oW(Q}m1QD2u~)(Hq?T}TaR?F#RIYg&n`JFQ;f@5AK_98S(S7DLD=;G? zosmc^S=kqwn$oLv_V!J##{mNar}{;s?Gh8+-JW9Rbz>@pKn~|w{6_m00#~GMYs%l_ zJe=uNO_y1tiq)YjwvCtz6B>S+nrvQ$)K)uK*2>Xl(G3oPcBjYAZ@!VO$*0I~-}r2) z2U=ttmn& zb$ZR7583j*MQGPVxSuXoWl`@($|hn^z#UXMu^3;RU4HyHtmIQz?{YpL+T(IGNFq3? z*1t56y+}cU{Y2~mzQ`vpR~Kg=>zT{F6?xgUu%TRXA$Kic1xRg=?9PecFrV`lDay=j ziJ$`Zi8>ckE`RCYUyS@l=D1_yk%}~%w2ascRIIF5ZFr#+FR;)lMADoZKdr!CK|)$~ zaSky1-Cd)Qka5^n6^^+C&)Tf^7S_SBJfE=s+kpIkhsfrS&MgGHvW||&nO#KXX z$vn0eLqiY!urr5;F*`fgA(|N+QBC3*&`{8u=64loOM8*iFTJ{&qu_sy;^&X&-IH2R zILZRf)U>o*7bTO6SX}!lZ6z*d-#C-+QUdt8S&@*2#uC+v^6WwB>};}kSo*6g|L)FC zlSN76_9!}6*Zg`n`vM)e#byW1Ivvb4XhHBihwu&&FZnv}9wzWs#@q2qD||zCtn|Hj z^3*@(i%R$W&}|=m4vwXA%d6kF@B07r4r>jS%1kgTjuMfQ%FM_J8JCgBxf|#oP2hL5 zE%7<$speS*Ynw@2fNt_mhCT2XJ?!efDw%6)5sSo)h;2(3ge60G5B+ya4$qkzl~fk8Mo7 z@PNAL*NbD#QnlAVi)F92S2c1T$vSm_Oh&2;!@~d=QrRTQ?SWUuv(L4$Ux_L#E2BMG zK12nMkHOLDjY?1F^b(em%8j^uUsoU#8`-R?XrQKPYi90)bu;*vKD#d*uzpCMF!oNnE&+4=JCEu^(h?UOmYUdPVHVt%nM7sCBl` zss;$-VgrPy2XL`;jwU2_Vcwo+P=A~&!#4>gkSHD>Uyb<%7PKOQg zjR{p6>a3rWS_XTDfrJK~9dn%n#Tz^Kz^&e(SKT8MWT5hn`ZAmlk8Ev?;lL2&t&T;` zFB&}{OFo_zUTyZv1h$XIoaJ`Ab2@|bHH4kP-npA$ks1m1)&(8D^zWyGh+Yf@MI!;d)xrjDsV@&QGwgO0 zV=1kZ(t{r8ZESR1Tsa}XrU%l~6WiITk&*d!&+x*UvESBV2_5-X*kfb+_qfcis3_BM zn@|dQ`d72pWUj=xgo-LT*_Vl_+8ehhcCsEIsG>edy5=eEnT~*gn&TTnLXp}vlbS)& zk8nH2ii7^^p@V`vmZjkg8QH4vxL^g4^Ruao^9CR0#e_992okz2F2DSMp_!ZOaIU?H zk6&I`xSOx(V`@6&jeGyTHmt6#^&5y@#`gW=@PAWu_E7pEDaQ8X`m9ilO(iKgXJoYd zDug{es`i6AkNbzgR`Lmtv!~On;7!;F5BE`_HW*rIg}WfhaI&^0ww5kVIbFjQ*PbCA zr=K?Y17=mK0SXY1%04_yLO^hKvMXO--(tt>ypzh#n17a?a+5R83UGJ`ZBodd$q^YF zTN!4|c-_j($x)7uzGJ&I>g-Cqy7E1W6!tTO-7{FEsjuyLySt@lx}|l;KEj8d*HP;R zb^KpZTmNPj;iv!qkif@~88_y3dIrWk1-HL2<@a*Rv5Et?wB`80Twgi>-Ltdx#6LG{ z5;x~+)u_DN)X)(Y&j0u^J~ww7W;27zZ6OW+mvcRWuCXyRY9(1IUS5RCqgn1gK8v8& zQ}0+<6Hm0?=oQh{c3xU)#+&pO1fV#(+FD!Hs*B}lu`#QsYQqWW6K~$>cUA7RI1=(W zNfs&`E#J)a@o7OSyJ-YxOTXe6IYq1V@aU*>{lz#O$I@&z_tJKc2n~Ae?!)Zf(KA(7 zPpYp+mXydm_oVV0XyOXs)RudwL3)&!7>`fW9}(eX!D%r1Z2qYt!vm~%!$P&!<90{b zwYY-~JjKl~TK_Pj>?*HUXlPLh8yD?LInvB;O2513ex5G5kXv>1`HZ%XQIj_r6BFwafx!wU?UayNa=W@1n5v0<{Ny5DRnU(1@ch(5 zL|j?fE91Mgf$N#wTkWK7uBUla;gcMQ_^8y`KYF=^0PW5sC^(4HAIz;A>STA5>Ye;r+8UF084K+1Y1~yq(j{p3l<{+Y!;&${*pzf^c202p<^q}ss4)*Cm zW6r=5$3Q=_+`{?KMwi=Vtw^=jWl??(p4hAV%$iH$=ua8*HH(@|VW&5XNlEdq=H2g2 z1KOj=ZI8;P_nv~!7tunW2j+BSL9^2~={eWQ-z@bnH-FR^F| z;fO52#aFahQ#$)5pA``Sug2NAu88IDPYh;d<(m|SqtBjbYIZyCZ(W?N2PW}N>DWgl z-Y>0E@vF-ct2AtPgT}k5nyD2#B8s2GU)XGyW;846wW|94kpo#JjvGE0g)=)ipN{DMph|U2#ELWaE>``qvKI_x$X7!(T?Gg_9GSNqMU-{h&m3HsUcMe=_C>d$WJ z25FcGM_cw46(b!GB+Sv8N|HQhWF&a?YLnWl#L>F{VD-!ErG_yNzMJnB#PL;C1)x{9 zvU&|qgq0O(L_)QE!?IvVPso+CBqPJ>7pC!AE+fTHaKcrYH=n#xAY`kZV+ZmuxN#3{$n)LjRO!U0f-mSS@oNte}^4au6a2Oe*4O;ub$;Y4&a5c+U z`vV4KZ+|(Omp9K7A?4`k_4Vuc3qJ+04Zgk`;7Oky4uo5keE)v5(l_YsEy=;*>*v=P z^ziDm`bnM0`>qEAU`PfAa={a61{L0fw-Wi>QG$Q_GwVV~B;0ntM)X*-bB(M_CD4zR zS!p*ck>=#(jSMLt?mUa-?$Z*@ywmV~Vfi-vthCHh4)T)bfr$7Eua*8xXaWrY4eo;j zrMlW6n7GmBEA$41{7IHF)qj=9xh!3Fk7s z^Lfj0P-P`6QSbxe+$8RB_hp&z=e;JT?QyKXzFN;*X{|&>AxZgkE-(K+<7st7r0T?b)YtWqo}rii}q{_Nz@~ z0_^A%5)!p~%|Q!7HIJEI{%wkE)tq>b>MTPb+I#NTUR@>?7gvYj-hu`B>3ZWww5LMn zI|uz#y4Kb}7?59OyZt3*#N%wW8N9r+qalHJkDQM-_=GN3?h?gsZd>;?G;AdcNDO4p zVBI-f9g<2g+h6SLP%p7!dr<0go7Ck%&g0;l#cKbpewye!xjdv9`t8mnj?T9$a?Lf4 zfhvXWB!pjTJg(%ZBdK{@g+D%`NYn49-2aGnDDt8th5soI#`{!N`0Z?{nKAOSZzse#rVR7KP!Trr65LJt`>|bqyOETXRlp8DJ`q zu-M=mEMh(C7#Le>6t8Apo%hBN=;|8P^&nkdLvp!Q6HvT_z~S`cJs6n&%;L9Q_#!YN zj6!__Fx`gp_*`6&ku(Wh?4RAQ1Ps;&i6NRl_QJ8q;T`-L1O)9#iKRv0MZ~IA8A&zO zyGi(AJBwe*Fw`unnzqWro(7@9s6`9c%t1QSN&=hb2lx4_%a}34sT;7d zyfq#<`J1pk`X?)WKjrG@_1V5lHfx-7My5cIp)mqGigCQ;#Od*n z7bB>?qc)n9pIMp87uz1qR9y5QJ_Oa(HQ%_NmY!aonRzo(LRLC|yvX30{rcpzj``2e zpP3sz+#}%V7*tBa)i=#y0Y41x<#0&#>U`^R%ovBBZmlQfcy$UO&AW#jJmAOh^Q+n@ zv}67X!n!OoclY%*^A5v%5(KyP_n3xUFp<^KN>)nh&e{eK4~Oe9c3N^6n@O0vB&KX} zuJbFI6f({?o=o-%w{GRb_K>e}Sb2J!Pxz}fMz?0^l|548nW$))y!;Jt;v@L4f0HHO z%8(ezu^e+Yvtah`S=c6WiHY^>IkgQmS+cc|0#K3AeUx!s_(8r5F*J>wf8+df*c6 zpw+vCMjP59Ccm${l5=H7M06Qi_cz3RZS4x-F4Bal7yA8J>?N)_!9Ul^%(P3*NXyDp zG|dS#`AW_Hek)VSPoMi%qGFjpZYIC}PY#;=`x4Nd+j>lb;bQ|fn}ZW^E&z!5j%GN)5EckFSdQ$ND*$@?(S|{lZv-)S+*a_Q&SVs z$ow-t%0KbbLN85%`CUc%us-Xy(>c0!`o=E^teOp>Vg^9&PM zKPEJF_lR3EBvib^N{nw)uB)jRt@_y%kG;;hL8pKge*P7o!~$7ZNJyZprA#$aXg)oC z^B^_jQ{V90)iq=ipX$4-%LZZ=0sey!GE%V%^`k!QJLSu%WbYmlomqrqMYf~gDT5Ti zV1MC(-KDHh=H*WFa~_X4CfaWatc%FHx}$1)L4_IduU`X1M7mb2t4qhnzo?h!CnnjK zTkJ+_1hl-tJtJoOHKjc=GV+YAgqq^dn8WCD8G|Ox|HIl>fMwNfTk{!&C?P5(Ag!dd zbSYiZ-JMD|Dy32a0t!;n-CcrocZW!Kcm4DA+;h*3bMN#2|GoS1`5r%zm%aB|YpyxR z7;DUK+t+;`0-@6-TUc!)uiiB@Qhg>)TMDRxxikD*Yku9RtXM%tiRq5ta$i-{wY43p zb}5UFe!dY$IrpwKPQb;~tN{L3f5+<IBex!n5ht3iYG1;`g|8XQZ&V1r=?a4@P#OFx%>XXzG9^vK28|v!jcSx*V zXNB}&mYJ`)r!l4v-e~{X9~S#fQ|o2l;|k5~l>y{_bA`8o)AyZ7=K;s`j{>3Eo7&(m zV{D<-HI2%A>D8~24h~mlMJX>RB2Q-nJ`HOrn3$P69%o1o3M6w;`V0=U4~R}so)}oI z=}t~Lc%3wb^w`vT)$uSFnhs?i6LI$klVBz|F1PbZtYp@=&ov!6IJ>SI84W3_TrIKp zUG-3hW9{~dcFSW*jF)Y-4P{X(q>h#otB$#A9rjD{wV#OvX652eg>Q_aHpX~w$aw)bCqu6h=%ixwOm zHnWF6bH6PX_GhM+%Kto0e#sLXBS22>uzQ7B;go(eKUA1q| zNBVrXG;RIQ>$wfDrxuAe8|jn_6H^Pr|6pD`NMPGi!%-pJ^l&|fiK)7~G()t8NbQmH zAtm%?k1h?v2#f2EqDzZ(JU+HR_P94Fz|3sEx)y(Der2G!p}`dg=MJaN8t^>iCoh@5 zOipr{3s(mRo6CJ#fmKDwethr2u;}yo61QS>6v-TOtPfzn)?*XL1ogk*M43c75DQl$ zr$+r@=`UW(ha5s85QeqePAh_7*@!ZbgMx<8NH;fKy-^6oJ<7HZ+4BqaLx`Hc3QJ?VHsxMK!T4?t#C@_+jrleBF22YzY=HF&V?;K@J z)7ePp+avTVpFhHCZjN+u!56E|6ms19ScPTl=l@n%2Q)Vcc?v2fo`+w-yK1k}0c|j8 z(pP8)lb<3bZIAmR;BzXK-82t1U!BWbGexMF6lz9>+2yQ%BRj2%GW*6xDNJ{U9Qr~a^fy8Ee;d&d>J!-dT>C$)O}yBW9NzD zN1=`Dp&sydEzB~S44J`I36&I+M45s@@g+YCtq_dE_H3%xE1`~R~MuBEY@;Y8s*!; zhOwQU(K#9l={mA#q|(ygm9mKUobT8zaVZ?V8n;Be(*P`a9mezD0cSJrd+8|)GoA7u zaKyOXpMjL>M^{cz3yy>JH5{C*+AF@q^yL{%mB;tCMng}x^?b|9{)&)rfV>ofqO;42 zzn}WmrCax0ON0+_vgg1#1qBJ6wmq;toJQN#M0IH(_wn&G4ljAd< z9oE(lV7R;BOI^`mccnxvGY>jxzU(hdbIl_3nLG*{U9K6tSH@nf@5 z$7xe@WK)X@;^*qG;hqke)z_y+L`c7V8@0bb2rFd&VCZm@NXSDxY;|f%%W0wga!_rs zif&nWZ;|un&9D8}@p+D`BO;)uUruoF>fuHn_$HIOA;T1|bvt@OYPfG2u)n{`?p;ti zeERG9l1;5k@oR(cp`jw;9|Rs@`T14^k=P7vzKjm6?`LXk+)mH%JkI}qfXDf8_}xtg zZl_culaa-RA&-+3f1w%Da*s3GEERswtr}HYTKZ5iO;uGA0`nsM7ldk)OYt+bGdK^=&!_6@@!=3%`QaP#<`x<(mxxO*05WKh*ym87Th@n4NyBUVv~J#_^-m`LmQY@&+R6h%dAOP6$%=ZEK3 zR+7fXL7AB!i;6G;n%)PvNnB93)C*3uipBQW9PXUB{eGU+72}bW8EtE~HaMj8?VJ7b zMX-F$+H!h7|JdT=l*?>`5J{KK0T3&3jB)Xc=8!fEbvM3HO}kabW}+S_YZRUVd8RJ1Y6pmZ}3 zQJLL~DJrs1RK&x;;M$)%Nr4gV>)npmVp3MNw6k+`bfga%5x{kIT~$$Hlh7V6v6y3H z`wBUnL>%}!E47}v=a7j}~->ey^bs&WV+ zZjHTJoy%SvuV5A9?P~P4Jp`{jYrEqpWCS#6ulHk)wPnvRoyG{FfQEyN`u4n9?d3J7 zDdNjLwgvr12A_O4YHCV)^yLTf_kruU0d40eW%3FqCOya<&MPJ%L&CYw3WK%(g8dOm zz^qY!r{3E$HjVCDk*#E0O63c6g7o1!1fnKPjIJ{KW z#B~)t&Aq7-f(yB$T7CF^VDZ(g-t3)?+~r~v9P>eAEt)Ne&EPQPNyZb-!$T$=NuA7m z;kj}As=c()_)QO6S??6mDz$OnLG-iDbxB6a5yhd~`IyK0$GEBEsT&v)j~@_Mco|Lv zS;ZO0`KJ+C7X`0J!Xb+*39yR_rv=}G+YR}>C8+kO-_F`K$?22WHP&|9LYFG{$J$(W zNLzKl+`qpT72Fr|V0I0UyC`-;QWY z3o%}dPQ;9{w-z_&u4WV;H>Vc!4NlM@+oK}n=ZTHar4l9Ye#Ch=!fjnqn?mS{`_We~ z?Ps=qP6M?s9BEEx8saG@rWH~yRy#3Y97?BtDHtpmyBVy~A^BJwZxn` z(|(tElswNL%0?6TqgFBHIn}l2@L+A5%Muz1rj54W;3X6&g zo4jlBY5Ms-cv_-wJUX3i`oZ+vR7;(h(@WX!$X_r&o%2ywjcHVr zp_0N(3iE~kz;QE~X(D_g@tj|)nUS<>v}mLZ%?$DjT73U99?1cIy({3H^2Y$;*VpC1 ztWMs_WZz?9&|vm2dwPrWa-$>!4MYe)4Q ze;&4M^yIb<>zav`FdA76j_Yh*2%+vU?{S@MinlpBHf@gIuL{6o9&}vZWAB)zE$7%6 zF)KtltDIPypt5$G?n@4%pz?lV`hf1E>b)2oTGI!k%M((Y{4S+imZ`TCY@2h9w5y(N zeP6FN)rq}Y4LRmd=bs=P*NJobiX}gz>BW6?Nd1#9v~H|(R|?s?erQqRfi0(|keK6( z(r@+ldL2s&Jrcbd=mTEZ4{O^}RPGuSK>J z2jrzN4!Nc6n7iCYHdqbKutVw5Vl!6?NMC5ZAfFNSBGGv$yF{b(F7rOFO16tt(7A<7 zBr-kyJg)02-deoNn>8ZKuEPgZdN?(#I&sD)cih=h3Bv0?(+$cGkohR0XJ&*zq*4fx z8d4lO-;3oh87f%_l#7c@ne49d;9A*l9^p`5Rqyw`75&g9SCZQAQ5rEHrxx3dCh12= z`$mYVf0b#TKF(?#9{ZL^G?t~^Wl;aB3B1SR^}Wb^?v31Af)lsnC|vlQaAbM?_BwlQ zZ7AQ%!EN!`-uis?CO>FORyhZ92 zMY!g8xkS5~XPWnu_>WkDr;kmFxCpqU8>kXhAFHX(D9RFj=ZjwX(&YXS=R`31oP%rL@@u489Ov2b zI^|!I#&^zpye;mtay}1U>+d7A7sHJImP_#NHx(ArDRVZQ*Yd*8zeB+F%D1d%VWICic<$QTfXHgsB1`|7O>=p^S&CJxB05l;kI?`MsoX7H?>D9 zL*(Ayo+6RdUh&^P1<=0RjGp8=yW|m=y=&bigT;%OU-`XS&!d~8A@C4!IQd-Ob=4n6 zW3Ac5+#W~y52SV!Ja^zOXF3PH(u?^ab>M9{(b(ap)u@v z>MgtcgI_Ws>X(sg(W}!}a~I+lR36)_DA1!-T6}x+*F0m`0?Ex6mu;lBnz$$ay=c7d zww30nA7kJUS4V!sEu_l`)v(dgelf4=PzP_UyQU;!sN%CJx5t=#T=n8hD-WNCe9#Fw zR)73J-a_4wB*Idsm(H}vgYzZ&k7(a6A1rP9p4T-=zvI$bQm*dZ_mePmO*YiC7hxq$ zPO4+T5z5}r2HztY5g83?%dWh39e39Px&5u(2-pTmWk|yU4i=ck>^#Ql7v|4e4)pye8Z)#tWnBk>%df3t~UBjCF1BTX+{Z;W*;-YFht&-&A#sVECBrb z*e9a=OXAo>WB1aRUur2#eNAT3#28WUsm$_YY#M4jNFpWCYAe4>jH9ir(VVqlRav@X z+}-8mF+-%wmoU(U%#F;5rg}G=sOU2Oz~@tOVext9c`!p4wU6qux;y^~S2Jc!q}OgDOb61f=`9ri}+ue=SnoYaC;3k_|slaq4n%)|6_(A*Nc>hVhR5)~zV z5r@c;T3K!gy`RCTd!uzVM$(`Aw+P2cjfIpTj5pX5j?|0uAIcjUYOz{b@*jM#BIv7{|QZ0d4WDf%JOmx8T>bbo5-Mq-Otc*3Y)syLjBfrtxW=D@neOzrf zVayGtnysq}=!N=Hbv%x znS#pw0drNuIv>1(uZ-YacjqRN8Ii*0jva9^yp%Q?eK7w9$;e*F z!k+o!5X(nDafzyjlwBA@%7dMdv3}mZ4QoE{darQnzmymi%rtMlBM#jNehzc6zs|Kb z3L8P8!HU}SvwgpU$ep-0x6edkvdO(|6`>w;HFH~5#C2+`^>Q^gFBg%-gogWQb$#!H zWEI@3&?wQ!zj0SfCNs({&kwQ=XIB+&D#fU@RE4NKKl|uc$BEQSh|r`Uwd*x7xfJey zC`?*TyvM}}nQx?PWK4_I=CblS@ii|yqy&!Dh^u-i6bz^1_uM`ANqxS_aBY-N@4!rS zl>JNYF~3HgNBGgKuM-l|6vDAepaAyjv~|R39f3B4gbs22F@Zau4PEEQ(@S8XItU|Gt7}7EmNTg_)r(7Nc(^kLYefgU@Vi?j-1D+GFq3aI zMVLDph~s}X-l>_3Zr;-tb=r*k zks})5Gz11oln7%ZRMO`Z51I^HlDFkqRSATx{9(@*C$5QexOmw|F7!U5bVV^QL!MIM zRJJ0*jkbcdqPAjMuH#kek<@DTe&K%kd3kg*fdRdN7q^#!l|qILO9#u={M0PRc9(r8 z(*NnT0U1xUFkh_t`ib5{q>9Mo+s3l=|$Z=+T!}zy|5t03eq)>yw@W*!|{UYLq zhB~vz3QSXvIw2`BT8g@e-9180!ya}Wh6Uu|v9bo+@Zy~~(b!Q6OLa&9A(;In^5Z7p zOf^YitjnK_U8`lWH{l4vha0HGhP3e!En}{`>hY_Aa_R6>>F*MeI;sr!&{l3i#DG62 zTf~-HVVcp^tu}pPOQAlW$~>lny923wvOj3rv7f9*j|_Ki#|aMUyw+>-^J0I7&w0lQ zPe$pzSl}^O9l^^?q?tUby&BH5i5Q8J-TtC$qU&_Nvm>bN-EjWXP}DltVA>iX=9c4H zr$Sj?Yd7y;NYP;)Vvk*B3S~&Pkth!6m<6j9(nhHkOL%4|U*pld2+Z)MNuI{$9@b}( zKzpd_6nVK5(Q^__j3enDj+I;2=}~e~)$|(KU3aiy_yuIWp=kHe5^?cUr7LC2H+qU^ zpIA~`GB1iR<&f-Tk-XFILzajvX>iuHVV@{QN9Myr=Pss_&r%%j`pkyfG0OmoQzU^r z#Cc1=v<30xNm3-@Bqtr26nvkYy!1^JqW8V~(dYM4EMk!{K7l>SBQ`t~$Gg5YxAj3l z)BZ&enJ2b`({~(AY0a-RQeX}=7#sk(!pGBFska8Y8iO(#IvIMvv3h?4qd>KK>5!OT zgI|_Kmc{@(pvmWxU!-46*^vy%)2S~8la-9?h!e2O_w(}riDv0%CYWPRO-`jCd-9Hf z2g@m8;LfVqsx;x#4ah=Om~(r5!BwrJo6*A5EP+O9g+HaZbl0~~J5hhl36yBxWmRjU4>!)8ga*%ojEKW&vk`{RiJ%-}&L~2vXXcUGHwD zJ-A&)vZ*^S_fiM@D=}xE0oesjh>eIRIn+#b`8>?L{e7)WWz0tq(z@n2V%EOk2nBLB z|6KQrmFCN8>#zQ5X9q>7)*Y>;JN#7Ki-NP>G=ld@De{;@T*X_tug~-i3@T^>5rgD$ z%Uk^?5)!YVv2ki((o&CILqj<}e&MP;Wc9&qAH*4?l?^iQ6cP;f6we%f*%(B=&9#<5 zJD`T5MHb;82S5XW1HU3XVT-KtUxniVHlfAIZyVoWea_KSu>X{}(*@V!$VOZ$69ITt9GuZXVd8X18}mI0_J~LD&fXL*oIVgN`qiu9zg(^VWh!6alj*<&*8rEr zYM#(G!zQ;b*6l1@FTR-1&_vh71akYQl@&T;_XzI8%R(H)z!DOJw74X$WIpG%m4lU) zffc3Sprlq$&^Mvx%HRk~io|EL?yD!+yL3$0ZeLubU^V*~kl_4Mp!?)E)=Gg}joDzp3$#(a$i`Hq#q^5 z*-B9S7aFO`F^Jh=ERrHsfyBCezZ4rX^55sLj%22}$)*vEc~~4RuEm&~b;oV;)_kx1 z{j0`Xbd!XWeeVl+FdJHeHxT3HrGDVrmrLUd%tj*TrBQA|?U`3xEB2;!y?-rUKb)Lq zIcOamD3Yy;E^o)ti22Dh!W8w;GX@ZkLfT2%auv$-?@^p7yYe%a7gki>Cj~Ptk(H5{ zw@B!MKWz$$q+|GDkYJp=x@s_2 zLA71w@h!1Z&&12fxl7solX7zDInTR4{3#;0OSe<`JfU4Xji6HF`g_mRi}a_SwP$-x z#R0pr1_>U&oR4{NPMI_PB7zLuMZBP2ulEiBtC(LgZep$`_M zF}}Gk^c+Y%?4i(ZYhhS1Uj{2g+$a+2p;+JCn>7aY>OIjJ4fY_ygkB`54)3j%FGnji z2gB2`(y+{lR2*M8+L{$6g_1)96XtHKZ-yQ&La~4wv>Z4IKPjdpthAQ%2<-a8NK%w3 z3zWtv8}6Qx1E%oVaYcDLr*ORRG}To|>~vs%yjeX_>~Y1tc7cI6mn`1(y3AjY*G(wR zrPzeTiqb2LhU>u&<`I(kb3w(V0&ybOdJiJ_*b}9b{fob7(8FAR=NCj4%unBS5prO6 zBsS()2Q;-hlgEO0jc|-3+DEz`qrPWNML}r~pa8-=>u;A6_KFS@dM^gr6Nr~oESyH3-8^bT zujc)3S5R|Y8%!&kD;HCg5kinj;Pa_6m8*@5{`&(aY$w*i3h$-q*4iDr$(}5coN*_U znQwx26gqjNLyV-2v1^wd8g<)*$4BU!qI@4T?^l?Mt_RQAkZ=sKM^Mw;)B<7ratEve z0h4mODVRTi&Uny!PTKA z@i`Zg9%GsWqyWVC_?FQM9PZ^sM#+UeItk+g`%M?` zr5(a!7?gV-UVtNUb2LfrWp9Pt5=I=Ly>!PG^%HzHl6=?89O@3b4wmG*U&=mAHRS1n zmcA3Z5L5*bVd9k8Z+=SyDM1rXQ!@w0@%Nyi&lwxGW)7eYax-n>PLJrCIltnC3?6qL zj}=fgxY@uo{6oJ(fFwF5*Q_t6U?}g5`9a|75RO!E3zen%Hfd`mnUvorGLwoxl?qu+ zS0Ir8sYZf%xkNascnmoed{3ZXf_REJ0K@>NbnkyX0pOY%MaNNc3Jl&XMYr*RJF^~r13XjkY$ zrW0~L-qk4WX!#M5JSat}OsU&T+TM>8qZ@biSi3C0fDd|5`2aw>j(3qBwu?By1T9UY zMgSpSI$qlP(cDA#zhbQ}tle#H;ed_MXkTMQv5duWi6rFeSGrwbaiD~`#Eq_ZPRO-I ztv1*t43cr_hB{GS`B?6=27y!3qn^4DFbM8gaMTd%B>+Vb@$)BYqSi*%;$1}>Q;x`v zR1d?ePMk7G8qi2@BMYktmr2OI7MhhlA)2(OAKghBNBvJhN<0rnTp5!X-f^U+;g-q{)PsO?O{e)AQj0i8hKb z3OPObiEtMfE8b(5=Txe?_}KV8Ec!0sgrZt4Ffk)C#{1F@HE|(Eba&lFdPDYTZTbG+ zQpz^2Urj?CBWIV(XKneie9i~nwY`i;0wM3RlT?0K+akTeZBjO(_6nVJI)%x%{1}n^ zAg+IAJ`X7r;`;x^2L7a*+swzO{Ude!=doDKkWkc@VbD}omAte7k5qKh#*WUl`6}xB zQ(6{61>3+6Ojuj)vF`EkY`ltb0NddzeJ8*cq6RR(960j~sa9UGYPd$VNegd)x z=2hT#z?_HY{+Jyk4;sgZB{o1_Kp~=q7jc_bG`mwZGBGE2ig!vJDtL>@x36&(7>8Mh zdGchGU4}=m8w8v*xlwO<9IyP0wON;`F7yu|yNUJGRCxyCGvcD1e3P!^9FR)f$UW+SZIfHVJ+h(&m)~QND4zS?uGLVCo z^};@x`-*LE6^fV)qYMJNJAeW$kalQr#I;LKy|!zKt>y-H6Q=(o|Ac%!JwHofq1I~t zrHk+#Oj5(|Esg({^}4}z!;|w|60nUnE*OZ7k5kYn!<<{u{OEA*cgtFCwj_;s|4=W{ zERVigFEd2Lr8|e(^)+U7GU2?Wc~v#FR=-Ofm!$pvYZ`3bdg2rk?A|1(9+uaXgH
    gM`SjpT>{~6UESh}p<6y`<63|^meapn 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 i7S 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

    ^IRATw` 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%0D!pXL8r&HCVrrSSm$q-D@>#J92G~NFx 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 F{~Je+FX{jQ literal 45606 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 W&)0NyXS%ELE6AukDUdRG^WOly&*4M> From 349d46e0caf12eada5f9b66aed260e4cd3d4b65e Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Thu, 7 Mar 2024 13:27:11 -0500 Subject: [PATCH 0697/1097] examples: add plots for xNVMe examples Signed-off-by: Vincent Fu --- examples/xnvme-fdp.png | Bin 0 -> 53284 bytes examples/xnvme-pi.png | Bin 0 -> 76492 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 examples/xnvme-fdp.png create mode 100644 examples/xnvme-pi.png diff --git a/examples/xnvme-fdp.png b/examples/xnvme-fdp.png new file mode 100644 index 0000000000000000000000000000000000000000..7f802741971bac19c7a666739dc34a0dea176bec GIT binary patch literal 53284 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 P@&jpcd9ebKm+$@$N>IhK literal 0 HcmV?d00001 diff --git a/examples/xnvme-pi.png b/examples/xnvme-pi.png new file mode 100644 index 0000000000000000000000000000000000000000..def7e68087f2e47650ecf910339747740191e6da GIT binary patch literal 76492 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 M7kicQ^4;J619U2;=>Px# literal 0 HcmV?d00001 From 3b4ce394a450b640c393546cfa8bf338299649c6 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Thu, 7 Mar 2024 13:28:40 -0500 Subject: [PATCH 0698/1097] examples: add fiograph plot for uring-cmd-trim-multi-range Signed-off-by: Vincent Fu --- examples/uring-cmd-trim-multi-range.png | Bin 0 -> 67853 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 examples/uring-cmd-trim-multi-range.png diff --git a/examples/uring-cmd-trim-multi-range.png b/examples/uring-cmd-trim-multi-range.png new file mode 100644 index 0000000000000000000000000000000000000000..c3ffd54640ec39f96aad0a1f1d6f604e07cce30b GIT binary patch literal 67853 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 literal 0 HcmV?d00001 From 715aa11c1b069ad63f8d9b7f992c3b6bfb46075a Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Thu, 7 Mar 2024 13:32:04 -0500 Subject: [PATCH 0699/1097] examples: add fiograph plots for netio_vsock examples Signed-off-by: Vincent Fu --- examples/netio_vsock.png | Bin 0 -> 46920 bytes examples/netio_vsock_receiver.png | Bin 0 -> 34879 bytes examples/netio_vsock_sender.png | Bin 0 -> 35539 bytes 3 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 examples/netio_vsock.png create mode 100644 examples/netio_vsock_receiver.png create mode 100644 examples/netio_vsock_sender.png diff --git a/examples/netio_vsock.png b/examples/netio_vsock.png new file mode 100644 index 0000000000000000000000000000000000000000..01aadde556dbac30dd7214e9c96bbc2321798831 GIT binary patch literal 46920 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

    (C**>X#l@t`en*7y*sa08-Re6@I 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 BnGpa0 literal 0 HcmV?d00001 diff --git a/examples/netio_vsock_receiver.png b/examples/netio_vsock_receiver.png new file mode 100644 index 0000000000000000000000000000000000000000..524a7a1c95f534e8e541c8ecbf517fccd4cc4e1f GIT binary patch literal 34879 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 literal 0 HcmV?d00001 diff --git a/examples/netio_vsock_sender.png b/examples/netio_vsock_sender.png new file mode 100644 index 0000000000000000000000000000000000000000..75802aafe4cf82ec4111856e45c010f3be090883 GIT binary patch literal 35539 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 zay�=>L zy#tu%6TpM(R5HP{2m-vedcdhADZ;2WdJSP=A=qqPY<%{yF?~jGQQ+ql-x#b)K`N7- zV8(r9w|XZ;Cey>yb7QJj6V}x~nTC|Q$qWq^bo8HFzvFYW{$}V~N3JZ${HC~!wB_TXOq~aWU88?yB!|$=WvZkh3)Z*P=K1Eel z#9#!-g`FVHErC1pjO@hT8^03UH<%>6lY})f5IpJ&sf5R0lhZ#8jB#Xtt5)>bc=@53 zkZ6U0p&DtnaCY1D8Dqq_PIKpo{gzCA4;>?qqADjw!1?Ll@ONh4BD7syw4j~C!|~bM zdFFX}(R+5b`soup8{6?$Zw!1vDMl6bo&mw>x`V#GrPsv7Dgr6D$&x;1(Guf7K|zFt zwf!&Cls*eTk7~`3#`6Bx5E;sHJ-~TM!a)CCS}Nbvl#7H9Gd7lzswyWhZ){(0abbzM zp?+7BkN*DG@soqtg}jAtZ8kT3HhCmp*48|~j^1cgJ}2krS6$3*d*qPg_Ll6x?23=5 zyMcl6JG#?(aamZ@W~x3hC04T}OXdT=a^#Znw_^Reh=|q0%>{EyT}8!EA(6tAlpg-y zx#8hi;o-E$(K0BKO@5kr){=gY)pd0xUGihX!{_F^9+{dBf19+bF!yVhKB+JuDO zm7$N{BXtv_q0$@?Ql$xcf8LB^F?fZCn`wtv*5VS>+$@E474LOJ@1EvQTtj^+gMx*& z&9^L1inDroA;>P|Q4>$3X^<>n9jD}Rv^-Mu5RHmNW}4c9o8yt8p^de*h*>AT#2S}L zV`|&%{@PfX(MK7olF!5{4_|H#AqiXC zph-D6I6{xQQ`UHAn-8j_bh9Hj2MsOl?#Y3Tw8G2l4`r|a=Y5nba{oV?cbyr!^FlXK zw?8OxxvFWbpj?>oP*GBLt}qL{7?;wzt57u6;2FytotSv}ISo1p5WbX@lz5VWZnq>R zA<4_m77`JGcYr47{UF&?T>NgFm5(oOs<7^)M{5E)8X)EC>gxE&AA*E*cC_QZIdv>> z1R~HRh^NZRyrl2sr{r1w#|uC}Cokr)9q_orw0yMG$mYqD<|o|H!c$UF-ItLWf8pj$ z*`{jQ88z_nF$>}Qd#G0ciwk-M+!sy@-HBK%EZnZFot;SR4Lm%lex-`1ZA)15&*M`B zB@@rw$$dsttyxvluwr7G&02et)F0!pu>4uZjETv}y0*8}=AVZW^u}-E?HXS|tIYO4 zbR(mTA5uER-y(zGsG@pY-dW#}HXtX(A-#94Miuo*`I8L;Zf*n}b$-722Z4-CMsO-R zG%sH`lUlB1q_$ZhQBl+Y;LsBjbwS<+z0}NX9vG1a!}?#n_=DRBNJyBzkPF&T2Dg=0 zRlWE`1&DdZ@05dy$r5mZw6ye4p4uU>E{BH${r&&;_Cz7Ymz8mq$3rJxOT@y&^u4=# z6|@cL-zlz>(~~hy zOsrQcLoGenXXiAc6lZ6DmLDB$@gW$&T!zt3PR-p|{b_;I^_dw+x)=&Ns(kpXGBs3Y zuHcTarwdA*y+PiG8Tpv<5kZ%#sN35C;@(fTjG5Vk+_##Wk<_%z|1;(efQWx*NCyJY zQ;57{3%Wa?qF1k85m)ho77B3&I0yokoQ!N52qp-<-d+_M8PnO;*I&I3>io{V=u*n` z%I81{1nI`f!66Dd^|3KY_svwOr+ImncAJSS3UC?JYuA8O0O1;BOfUp!X=?8Ea5drE z(9zNYHdtR@Uv{a3Q$O2I?jIZ&e&RCHZt`=OyWak-RZS8l7!{kOHUAN}ExE~Le+ot* z`pX)jcXx3A{o7tAt6Js@sC91n34{N1QH7i56@_5)Bt>tpl*~0^&LHEj89!j0ivuF5 zpN?@lI6xI091C_{l9EFc6MqR2`%?oG6QQ7piVCbqs)Z&e8~p79nVk$--`ME7 z_2|(Lpt%6e!#a+B4O%LQVDk#MZ{G%`2$WEu2@z6Y*#Og!uud{hf~Q_ajAc@nu488c763 zCnPW?TJe!1RqHgCfFhmrftFLs;UUGT2vW)s_{kpD!OFow1WHj{+&rhQhQ_sk)Ktof zpe{4ubT&m`g0Gwj7*O)J0P49oIql{@`UpMLw{QvtdU^4%CP_WRwzl9ut5`BVA3ng% z4L(N(TZ2>KVeoHVVQv?5YAOx_w#x(@iK|oi~)nBx(Bb_~{$aoXA z>W4S;W~BUB+1XE~gqC0CN%MBJ6V}zr-M$DuNulo za&cwwKkkU?TNLv(P&b)r_%f2U*xg%9$^FMms+rB#w?No={|&At&dmXG{#Bg^59%`R z-xpUF9C!kE&%?{x6)7w%3=G}Ij~_tMLdL1KF}o4g@irtRV`v4iH3K%l$7i*E@g`rr z;=%DzW+DkJG!hjZ4SkY05)u+JJ~2TMspR-&;@tNP(|M`2BMfH6MMOySzMmfjUs2XI zn6deJsnb++ZmU}ea;W>wR9g1>;7YSAxs>OzZr4PSZd5$l`N_+-SV+J-oCg1!$X(iN zZf+l_<5wT$N$+)a|12(9FgLf_nEsI}d~>YC=3{$ZPhqKruGVmx&{JNUgw0P+l}by0 zNlV|i;wxG0nr~q^ZonxKq3!5uL5EymS}2v8k{V8>^lD?hJ`8n zJEQ3)>HhTRH&8OF1{u|7*qaQFzQHEB%ZAOU_U6~G(a&NQIA-Q8jlSx}c#4YKJzRql zc_=S_UH|I!GLao%(WSjy zh`kX{py%1qz1K3eUI(lQ(CQ;ax~KpNz!-pyyiZ9H6%xYd7kn|!4OXLo00hi(6P1=g z4KlN_kq{GWFvm$=9Q~bigVe)Js08d)N?)!zGbH;)|4WJ2GT^R&z9U1O83Lqr>cgDGT_@2G6}kAa`|2A5UyzRPkSlX^s8F@R1@ zt*-N?RVG9IPoyOIb0$V~WE2O&{YQxzPorl-8 z*c+(mkng7&arc%`&kilVkLyK6zoZg<(t!mReA))R%74poRu)vp+=r;b!rXj8DT1?| z1j{seD63e@E>D$}8Qm3q#hIA$l%%CqHk9EAGnmlgiQ3+JgM{ZvJ9C5niM z0Q;6@xn66l_*@?jAy!9Ej}3@2ukf;C9bJOf#$I;3D)lF?sjIuz3_8By5T{2{wh z;dR9Oa8bax#C&R+dZsb$d^LV3N4em~4^J)Dj;Y6+c^_P?s<`|M9fk18lAKT7;9vlf zgEuyXM=s^`-_S>eV1cNNsRuy9CRK=Z`^>({MVCL1y`-}L2>`smwC=q5E$ zpM1)@Z&u2~Lq}HD&Dgj)DCkpsd_z%@ zK%lhkyi{MR=Z8MKbrU}_vR^MzN5i%L=ot0=ZalYDSKlo)(s}m1aJM!d0p@*kB3AUN zOf4XA+`%io+bQwV@(Udq1x4_iH^5k;N?BNdqG54U(PU8fT{sONVO2E>!ow@ASxJ%2 zHQn{An#h`*2&`qNS;DqR&KNBaiKBz%M}mVew- zh{RhRQ^P|dsMV$_77kvaeT9GK;3~t#Iss*E@$7gO;5?v4PtS5;=R1XkJbuR=_<#2H z%-ZkHQHlPR@Heip=vyi^X<+_UvB2*>1{_82dq;oZk61LTR@Z3w_}V%aHi2LPu0kxl z*odO2jO*ZUCot9jy(8N@ebAFAH)WI< zKVog8OerB2rIdp-*EkmPT1=TVN!YbscYXR4ON|bIE^71ktP;e1FkZ*jl-0s=J|5B* z%r;Y>NJ5z^W5JDq*RCLMad~1h!=QX}_T^N?a_NC&)3`im77pp%%dgsfrsf@Si6jq| zi-f!dJU6F;Qv~#J$bL%D_2OGUNTkG|V}?o&ntu4(x7{H3UAuNIfy;z*IV9>Xr6{`n zZxAin4bf3hu7-uz=O|Gc7x5Z@ zWyQOp$ix^=aQkDrnANlAo&1ydGe&F651ia>pSFlOG$%hW!4wd93yRQ<8y`H#$pqG& z>410R;JAU1!fOMm9F3}~_R5OF&TbOU>}vr%^B6 z{9>$I77-Vn$)UH+EL>Z>(uZ$N=Yg9`20mwy7TK1WqcZdjQPa`WNMl))zz?j2-2E9{ zC!z|ZXgDK17gx8DiFP~+Vr4~yeDd|aw4MB2YH(l*qzq7@s2JU9srf7n3_o9$c67Y% zDn*f!fAeqC$Vy$^NmX?uPpz5~Z+>121>CE+YIIIcCg=+xA?o>BZ>#v+L)9PlEsgoy z@}{Sk-P!f}HZ3D9?dmJO)E2BJ_SaU?Rum5%33|;klzXsy;OlwpMDB@(o%!$0mHj{g zXewY2tE&%y@0>z?0T~k|pk|DYiK)&i_PEaV9EP{iTX1Q(VE+8{_P;G^DPt0=dobx4 z8S~}Ow1KKIGq)ndTKE>;3-2sSD7RE9@FnA?^zH2P{YhGkZf zjRoUdUzx{)ysl%RL65REGY}TeQBiTigFin~bPEd&9Zw4hINqa1yZ)YyIYi$8+|JMv zuKpUI5^|)C?tSu{k2U4|xT4#>Nt{~Tz`XZ;?uW*wmBRk#2ai-$T`n)UNCbX2SZ%Zq zw%?IcSYl!M;e8Nuw}+0_=~bX6-kld^j@*_5jJmpG18GIyjgC%SZy?XD-}+n91^v1c z0c`C7VNb#V5mAoaul>6G5V<%?@sru<`kz)qUIe=AK&{=RR=vCRiJmdRQRss`FF{o` zLq%oL_zP7<#lC~JBp+3OnH-Jb5i+oym6vzODtdYv>FI?TOPo|M^n|y!=dX=+#JGQq ziRnH&GbSf};22qJWeD+b`tPg%zv3wN(6`;G$ZpD?7U_nKX(^= zQU1*Mhxz!L_?_vM7O^FLFfn@cNYLGG3zjc_(;-wRF<+5_ni_e{*he}#iRs4o{2G$W z>?NI;Rm{vjCyw#T$$6QZr^UygZBFa>xSg+R@9ZuN0D-^1pG}JMlsIgB?$^1N0~4yFHGHr&a5jt4N9{LwEns|n z4tR7%rbHW?u(2^`C#ORH6UFaCL)$5nO16s|lRuw*mk0{B?CJ_-We!QfZpdju;N{%3VV751^acG&D`QX!ny~=9fgqxctBaL|@0W8jN*cjligI{M3W55s%3mY4{-EDBW z15NZ}Y)n_90(%N`?c1+&^4_2uxBtT$(5%*h*#;b`VA@~;7m_#ZT1W<*m+?_5|Kq)+ z-ZW8na9oxee+6%>Cy+VczI_9TxRb*`)?2J!2?VKHrOg;8Xr7>FhQnnrMXm>3w*X@c z4Gj&jahka*pb1}sZQ?o=fi=0r!*CD}UxE#|E0zgdn0w!;{nvkgt>=%$&5C$Q=Scvf z9D>l$WP&FV2+C6Z%G^(%9H#4Bh4w3X+rYyWgnwe%9mfLp0T3s1{?m)UZ+9{9Ah~h% zPu*aAI$WQ)O-V^fO3J*fH|Tz)gUMj-fc)07Zcr9cOIR#T=A?y>ZpvWsqd4M z>n!@nLA(HisC00k2WWi*tE;Pv10R~Kh8U$WetuHDW1JXk94E2ToNePccAe@$n~0|e z<^d(Rzd>d{1)D!$dHQ8Ne6HRn5^S8D60WPmAo8P8VZNi5^aFp8*y}W=hRxZTD*v*E z8mEN?qLw!VA8y;%_x&3`eh5?dZxGb-!?w{^J4MLbR$fn+xI$!yuPcxmdtR}@B1FzqKY050>)qT=O|l2 zV!g*vlGT|0F_T8ZvtqQ$>etj(=6a`ycv5lkIyfBsE{>NJV;RFq@5v0#W^ip^KbKKZ z$m4CY_!=8Lw(#ES{SP^jt;y%2o*wyM&;L#C5wTrRiRsa>zi=^|C-wHOc3m?~WcOsv z7ZINt%)Gy`u>rd%imrqh?31j((iSkGQc3t6!Hx|OeIaUKuteQAeJ;*~k@3k%KwStD z+*S+y)q@8QbaWD`s`lY3SH|4Ot~G$SJkhp*6Zx;P*me8AJ5Hfz-%=D*bh9jgoK1RI5^!0e!la{VFJEk5ZTcX z)_8IPf)*;zz**l(s!N4pakB)o&G2!G6aqF@c9E*X!45iN(RIfsW0fdR;?G&@8{H7~ zmO`Q9?fL1W`W)>X)}O3+xp6Uk<9)3)Dk|+;Ln?%zm9-q0o(>~p0<`2 zXv$U9)zej0v0!@E(IG}i0cIQ@gGEJ7P7bb{7#9ahz|7#__LmvI6mf5X34+UTl_VYp z@MD0d;_tU`*d_LJWQ0K%JUO>ZMn*?flli(jI^=Y8fSqpp@#CI|h=H2g7AOq3n~ZAb zuxu}nmp*{W3fwzr0pKV&JwArC23R11j8`7E6HQP1LUsfH4Y#16nzAz8+Te3OuqH`f z9Ze9IyRsKww>|3q?uAqb*Sr(blpMN!Q&ojKiQ9g;A*MUVvHsh1c$1;N=E~B%WuYAw zVkKu~vDW8v-zRwg&YWdi++>(t(6y>X+2%Nr<7rpo%Az;yji)q=e!F)r&fb%Q_-OH+tdTbED!ZMM4nOQ{gY?UJ{A>$v?Ena3wFXC$gPH-UmS0InF)!3 z3S_9KM@UR8P?I8i9uN=!JPkfWe}6yJJ5Ekc2*|lD8ZiLIV#E zFN}l>4;PnVRlPt92*uq_1aY7Dar?eRU~{ult(UWq`K>D}8vcZ+XJ?UKgF<8_Jr zk4)`N%g%${(2BBJrs-ED&;HKdotBo}Vzg#dJxyKwknV4_!*HAbHm*J{aujt+bMNjW zIRi8#^+fnbkk!klFYE2Mj3wM^Lz8b5C7>Y&Dje{9QgpMvWug+1q^qnhF7&TovnVQ> zga<3Da(-0LP@`UnC<_EWv>z7>3wBcsgL@Flz_sP~Hr!X!g8aF%tX}zGpog=Ln+v_o zggog_6*db6DBsw40jXM9NvXyrf?6{Du>dGt0igXY3e5faBgF|@k{}}HCqIQ^_}7%_ zfqxJjvnuIgwO3E(d{~;D&G)o52;v5Kx|r9nNfFqjc?B@O3JceQi7GB3L129SaC?G^ z&#V2{V;Q^FuqqNrx3^ZXpH?jBDzFPn9vioQ{r&c9Xm22Q^`#afbg)h6{?Im8VCNYQ z;zCeItjSi*U0oZ#%)E@hf{g&%uV-cGOX23^=cFYQm-&_^sSyb%e771Xd1skr)witi zWd%yK8b0NzaJO=&iLYmT4W|rgwrB`|Sze6(>j6#a7YDW};eVRyitw)Xq`oVwcOA0)ZxmB5v{u2?SNTN?F{|7fQ9A958) zac*v(!SM9z)iw9pw{Ml4oOsF~8ycphr*qQN&o3^1Ghz}H`~tHH{wV3t^VjdK%Gryqmz zW#t{^;g`J?+%bwRvFfzijuU*p8Fj#)lq|N8?39eQfJTii!NVZoUPym1LL%!Dt?~Y+ z*#nNX(rCKTuYDcBqzCLB=h02Mp{* zMn)eCizZM|7E#fe=a+Ob&>NG4_b$GL6Km!^pO`41XH<1^_xCw<`?os61$u?Unod|y zPz8%-)?(k<#@}b{mqPE4jzUYOU{|&a5mASLosa9X`ZWX)$+c^}U<5+b#vp)}1-!OS z;Mm)2!imnP@Q4-O5mYinxW$CE}wHVs2tTU)`&{6b)IZ0vB-{W%#q`R>r86u#DJ z@^E?Btp~f8&)#{T8~d8xqTe(WAR=mTX9%&(OK~*}zT6Zcy&s2yn2(qkMVA&A(0|Lt zrmnZsmT{Y;&k`Cy;JfBDpQc{f|J{|3C+&8-;M{> z%S^fj7Z#4Mjir#0E1?F2hOYnHIN#WyJ3IdixQHGBheuy${#133_51^nQlg^9Ylpf= z1zwz=j7QVOFDx{jl~p-pHda@QA^U4glP50OwL+A7Ur#zZ!U~!$?dF#ARdY02TPsFO z!s`7c{l7RD-t$APKy#PltN0X5se2KZrOYsZ18{)&CBRP&|z6NnC@GXwx1!kKj(_d=4poRzaGqsj04WI-aW_Da|FS^la7Ya$uq|i zJ?G182_%Bx{qduAY6Awm)6${zu_88}s-yGJ@v)_mr{^Pm{a=6;ut`=;>JuDKTr|Fq zNl&GwrBRD}fr>^C5Ha{&;LgDT*q?THc4*K)ewtVZ-6YT__YIiIrSgWLOm{j~0I{~W zy}dkE_TdwE6Uke6(#_HK_I4l+(?47u3tVjs_XQy&^M0^^hCh^lv-9TWW{`Q&u!$W^ zP{8GVdR3$q(X6el8vuFR*nm?(*;sHDq$w{ik(548cJ{N=t)@_n62mDYjN2qchh%G- z8v47Z9W!gf2Z;J({f60J=$l30hc``mEMQJ(SzOmpSbDLyG2g?*XMdn+72TVo z0;*Gb2HJJ&XFOTebtiR_oEJMHdk0c!FT;lq@%}Nc>W=Anr|#vOkD=G0J1wa}8Zm79 ze5+?7XQGj&F{?F;?VJA{pOU^|V&Yqjc3;Jn{Px)wk}9fr>@E9NKnL52uzJQLhm!Fr zN?TLYL`R2>Rf&k?a@7RY)AJtEIZ(F9nLSi6h*d&~;e17nj*t7esyS>wL4%zO-5ZdO zCqZ_z;`;@$3xIMp$}$?hS^!AeTG%2Eo3|-QN#~cAxb!R9!J`4~su>lSp#f;I8#m}d z+{5nhJ_}1TGj^l8;emmh$dyD3-}wM~3u-fLA+YF6<_FILY&I~Z?*o7grUC)mU&F=n z068~*e^uUB?M;^{C5LXeP`l{LhCo12Zq{`Rnz8%$+u-{hV0-#i)u$Y9RwGBYhX=a4 zpuyieI54WTz;)+`J>F?jakB0UdvOIAZU`CzI8|b6O`g?ytkRm5fNK6zJ7RIHP zNd54?d*(ipv+C*y8=Hyhm$NTh243+m@F}w@>WJZq_>FPpcM&kJlS3Sco@0L zy1baUfZDtwocEnP94)|Et^eF}Yrsk8cyj&A=gAje)FxDkvb@d*!fL9X)%UqYZ)6qd zXr_#WB)yp^wW<&K9Xg@esV(u$cm3gpxV+b()jq%OXlkTzSzdmkMpPDfIWKH#vfrBp z0-kDG_I;!vy>x#QgX~T34ECkl##XmcU(0Z2MuXJpP1{_dJ==eVo;Qy1BcuGt&qJy; zTngg;9duEYC+j%gvBHT$?e92MS z*$?#fued|^TLArW0h5|O6u9V{UfHr<2+!;8-ohhf&~-JVY7bYF1 zJ&eBRg0PE~gd`DWc5Gw>zL)}fR!C+EOZ^dmWPzH3%_?vJlmR5%-Y#*Ol2ux&YDRM% z<`VhCNu#j1JA)}``j2pzPFTzhw)}#r2e!WSbasMYVeH2bt1|;UcVG0tAF!!lGgER( z;Te4I0qCd&MMb;<0v6n)Q#Rn_0+Idpty^HC3YpkDx)GTtdJ(CYaytvtHnnQ!(6Onh z$CHVX-6GpUOULM2siktL?}6*U9Wn3G4E-(V-z2)kY|1P)JvOmpx1hDnV*RKzI{aub z6M6035LWui&Y5C1{k>i4j?gpyXS;NsBo>=dwUU!(lpk)Z^*%q`Xg0oMcAoW?*7Js^ zFT=S##uJq0&s~!`?@V@Rwor|N?6R%f#q-IKQQ;CpKA%ShkCHz(2;9T_()C|>JGxP> zw_a0|<##S&Qu{9QGA>cq_BnG&J~KM<>|4HZ!+WnK)ri1Akw>u6_hPoq=ia^5!AzwY z+kHuwItSa0rLgaAZeJGrH^gm|-MrUdCz0{e(k{)mH9Y#*_&C;kpP3qg4eKI}cysq0 z{xri@bJ!ogCzkM1L{Gtt8=x%gps#mXiK(p>esoo+z!CDajhytST5o8pCNq2q%f@(w zyuA1J4Cc%C;tSp-R@T*#D8ST^skeC0G&0hF$i(1s}1th(81@Nwcd&ntQ zXgF;?egW+bnn;Rpiq(4*DOIa}YLZL!IIUD(Gu7Bh+F>Oe^pUYIGjDJmFE0$+Eu2*z zng_mmqi5i!%1=nxUiz4<+~5D~9zdn_|(I}8{wJR#c^|CBC_uI_OCGcKgh*V+R z18Wm(Uxws;27GBUnJtNY!yYpuWA6Vd?JJ|AjK6L{5e6isK_sP-lI|g-K^i4RN=l`r zr6dHAZUK>$F6k5zL_(xNK)M^H-ZTE!dt=?Z*8Om2`9)^tnJ0d6&OUqZ15&wxcQW94 z!L;|OJVYOtH}Mh5b+7EiuOFnQ#@3Y$-V@Meld4 z1vdGQ>U_Q)IdRLl3WCNI?I{T5e%@{8Cm*a$xTAZ;zsYOT@>PD0#pC-#>up!P`$z>8 z0O0ne#L<9n1Q`76GEww{PCS z{vV1*y|l29Q`~oIhA!p$KuN!rvbIrdo(lK1!u~Ppwe8<5`Vpv|>gf}VAzFpKg_$RR zI>RzG5Tb-K6TgS<*STidcG&1NMCFhsoVZ&U`y8hy{H=;dm8 zGfPVD1S5fgg17G)a%(CO9sINYVovhbHa4ay=l(rCZSnq7k)4eM^b5p@U0v5sKg@z+ z4W0=h5=suhhLl49-}!r*NTr?K-rnBe;NaLgP!u7egZF-njz)xqsX+yzxA%HW&o0Fv2u&P1k;I`V%36VhJ=a@@7IR zg?sl`}60e?d{T%l70{~ zgAjOiRV2SM>C~CP3NAfW(1B`4vz5mk#C70^rFRbfm6fHWE! zg=D@CJMxtrgE6Q$0A&XNUKo5xq@*t4_CShIk~`9CvEwXtZafwNPCND1&;B)z9D87)ffFxzYS7LA!>a2W8@|TesX%uVZiy0xlfz z>w&xky!iFi)k7D1pwiUV*4ES<&2@y@hT4J}ERYbqRPe4}Utb+-OTw~nbi9Da^y7!l z#0HaqK)f6S5Tbbvv3iZm#4*8zC{S&IhKYv&^WTWDN6T$&oN8~s(jfJ%yL)+M<;3su z*bfTwP(_rWF}Q!f9ME%g^Z*!wXCl@X77GBy;NjtUdj0{Is`@VHL}2X$LfW?83sQSw zVPUA{&^Er$&tHKX0)&;0%z(A(D1a)Hk_>D5 z0NmUA^(zJjhKs8!aP$FCB>eV*r>Ccfot+&RwWXz{8tUo_i1Eou_sQb0upYQ%FlK7D zAFr60cmVUy*?Au@!C(9L|ITfdJ}@NSzz|VK8=ebpiIjxx9Rj zIhI8==@%%;Yv=Blx&p%p>YwY|+uOf?clPwynw#%}myt%A@Uxu{?f;e|RS{10VbPTa+AtQqxCI<#=i7_$BJm$z+*Rn&i6&(FygSI+L zuyYGYe^%#n>SmQ;kpjj>k(5o3-!%O!|7S4udG-;<1!nNXpH zQVW>Tz@5Ruzd0~Gyza&rjLyrNqX_C?c*ZrgwcCF+#d`6U$C8AKU+m7y&n6O)?7Ufr3dB1NTwEY1ACf7?HNuqXpNsKete(nqDEAGN>t z6hudJmloK{zAAeXkCWUNLReUA?{m>uTvwN@%1lXtSYO2+teKrr=VE`dcl=!%L4>e5 ze}O0NpMn3rNK3-oAyh^WMS1%|BU1Q6GyS9*6oNB-9$a0u2*p@xPddU`{Jg@FQN<4zqS zH6`usL%>^d->#v%ae3(F^Z4*(aC`gnFgfJ&=dKP8sUacK>gwlNvbMx5$`}|)&!72R z;zUMre);y%$u5WKw(j~Gj_-vLE%n3swL&@Ooc(2Id|GvF{beD(8zuQR5-zMGtf~m=fFlh#eF_{ z8i>pS`T$B_KN5h;4G;pE)BJE=B|eR^ zn76RQXi53kuihpmRGE?^zn6a2R%zZbtpU4Cw`^h-zE5?p+_%Zj4)w!xayI-fEP{+L zkGDCu7K2orJ)`RqUW`(zLYS_%wX&jw1F``Ib(!K*_Ol zyfd*rr<&bF|AQqh4eCyK3`8^%DJ8{y){huo5;EMt?oBKT3i_QU=|IoX`jJR-FmcZr4jim_sK^`?@NF8Q6v%CZ&aPn7t8rdY4I#p2oX_%X^X=`g zkNH;DM$T#k*&PJ*^(`AO>Rs0!^G&V}8zQn?*MICD42~Ie4ry`HX8~r0gK52SC|&$A zG*mPr3W5FX##){>D5m^U7$}x=~W2~K)1%AyaP^WuqG=h^*|!<^~)ERNh?4XK~f5~ zWgz>q&9u3PfMB&dnigDTfMNutp_wmVYyedp!z6>j1u3&jIqxiA;10z~L9e;FIgsBG zS>+bpV3Rr9;wQzy5uwVgkt(PrD|@w+n3_6*5OCb&=x72Y7*6(l`}UDmzfNQNv5AUG zcfi%zL?fd{0~LAsH%G90?eX&$AuwLn)O`6-fU~i2shpUaGC?cZcX{?H<@M|QJQh*g zw>OU(qD#~yC8PWFJAwwfO#hMN(2Jg5KP` zWTdEjsOx85f)`Ho?#N0_ue>~7o<^RTYhxpBZ*OXLcJq&sYKWXfgjt=RzuggqB?FNK zrkNJY51NhcsYW)GqE7gxkDQ$FE;?fnAq$I=a<~{v-o^T;g8f|##ykD;UL?Uo$uaj; z7>GznJZyX8<2+28IaO4Ox>$w>Ru82a8HtKPL{8ukY&-nkcd60%(CZeig65l!aYMno zceh81o~x(~Mn&~sC$G>*5X9gN#gD2p%<{!w`jdfvX;7IqxtZ|Jm z?B_>2bM9STuX%Y*zkQR{NLL2#rmm)@#wfmneP7j-orm$^#9}DHKG6IAoHRbDET6rEF`1?n|q79%*+;#9)&OWg{6z|FTucIrn0IbUtHuuB5%sbEXm8O zWn?(9#+ld0+e1VN_8@@$8PXsqg1mVC<6;TfbuQPz-d?@SDn!|Y7iA}qtbvGy z9HY!hQ7#=|!2>aJaXW+;r1r-!C+crr5Rv%eZSbm;!^`fD0ABXYK-@gT$F3Q*#<||0Y zkRZ_(77+pH0p)s=BG3!m4K=k*;A?<9tjbnRs)Q$YdILyTg0!>{drFDFh>Iq-vK~D< znsg8mxd2fs28J*Z($Z25o}Gh3$M8^slH$%|>(Rax*Lx3AjytgQB_;8L27Z3DvgkHR z5T=tEC==UuP)Boyn#S?{ac1plVc% zkAd6sf(JxXu)W77Bz&ORJ2^j|g~%FWfRxl!(1rr?1y6aj*f3Ck3}iXTSB|zwUS8gf zNpgB7;Qi(2C&k1BG;y)9ee&G@5EN7s zY>fA*<31YT+n?i|wM?t6CCW%oN>x!$V@-_HIhvP8|L-$+K!kYZ#&=IqQQg_O`{lKR zz$D|3CLcoxMSaC%mCrMB8vRWF{B`}5l~oqjbDVXPm-o4vni!1o)q{N%pthJVNf)<= z5htIpYWVtg^rwf>(C9FK7kdm|da?*luNFJI9QBlgQqsl1my%K*I}5bzf@X4A-YtuV zvE*4Q6or*mJs}}W#!V?wWi&SlVeTf2dse(Nquo>CQ4D!h3`Ch|FicTXd+ip=0xTD( z%)n+0Ii>woN515Z_4Nq-R_0Q(N};DN+8R&<0?(H6QSfeA+t>iuB9|r=++D1nKV-w? zve@M(hNl5^jaJ6@5}L-wG-7GVNvF?5F{f{`0?`>kulCJPZD4qSPyun9DUKls$kT*K zluuVeb32Fksa`Z;cr^Ddp{;J3D~98=e&4aNpD_ecQB9Q978H;o=mo0%Bru3#u?Q-JO6lT!C?n1J?z1&1zxK)1~8@H2)w=f7-+QtmPz;kghhk-bj z7aje>i4%#n^>|09l*4SstJm(TNp$^v0Uod}V=^+%K^Ac@I-HDC6QH2g!>9H%DSLYf zOIG16ej#CDU*7dQjf|4>-6c_1%>TQu;Zq-GDkwPL;LdXIULq^oE@078o?1*?-(Jt= z{Yj2^6R&%`V@@=4&(7`{HufECY)3)Cm(dzpGBU9xCBF_0v=Uj(QH^4BTyuwI5_`Ug9>6`gF;I zw1U5}j={U6A><)uKZwzx!(?xNxwg<5_MDjI>*{*MAlX~RCL?f~3%ZmaEBYT_M9leR zU2IElbbk|0xX3ISb&OI5e8M93Z6Wau_TXRh!QBf7&GvX8C zsuY#qCUV{%9KF;J-`FZC$zw@N(6g{%y=xJIi-i@_bJdmuZpBBVg2Ljy?lVtgqmZrS z#B?#&g?Z)l^u?90=Vz+QDi0;S^Eo-~ZxAn}@pXL}(uzBx9n&zusF!^Hlu$z4*UiYD z2dv(FLGlPVA*?yz!bcgIk#Q?b?rUG)d#!>KwU-GA3E(8omt0&@GV|ky977E3A*;;g zU}y{_NB%##MIS$^(Gfx@Au1-eJGrx z7`v)mUq4Hm-bK}2KQJ}rz4J>3w!!*y=>@p}CoGdXxpJhgbr zz>ub{eqcIN#mOnx&~$xhJs^_f;DD_LA1}}fD=j0VEW_m3nuL70oosni>GICY6sndK zDk)?0QYp5C{F0L97l&(rp_+@mH-E&s)CNk|VuSMR{QOr(qbFpLlaf8Z!^vserDy72kc)0g=+(F+NkmRn#~SI@eqaBdle1sD!?MI zQd7O42ms^BTgYI&4V->~JPhvk5J@-zk_J@+;4*>BC)geWJsh;(5Vith2=HxJlqfiO zYiq|=R#t-7dPs-@;B*iSk#U=B%*_Q5qL35tLzwvhHgPeEjZf!5=Od${5&;JEVD`?* z#iaq=01OQ30H*>AVpx&^H3y~f;0)s`)kAG;76?^!QUz z2D#0}`S{J7OhE(9KD%STfA%JVK3ag&w8U!;`9$XjdC)*dsHd75xQHdKu0>(v*BCeb zEf0`+m7cC;Vd15vm0VQxvp~BC_mRbiMU#&8!qGbmpQ96bg$TfX=uDNwapI z-t+$5-6hX2e3DbN?sNNg0Hjb(Gn`wqq*fnNea;ODoMpvLG0t9s5eNYHC_QhdAMq7T zOgj==K+db2K;#Y1W>ps5`1VZ#*4E3!Eb0R#Hr;J)&%lBKAVKh^1b$FaQ4z}6-?2xm zUXTdsvi=FcH3{&KgdGxWOQDPefH3$90rlbCDb{Fp`oE=76dnSk>OKIhV-ehXpO**3 z3#PkwNlXXvQRePoTIus=FIcd0m5QG~Z@oJ8+`{pWrQ=1Zr&}y@&*DO$n(~YtB zkPtEkm2v%j@gy`k!3(dn$fO}MS<2L64*3mIiYImtxs5A2^s71y9`-Wb<_Zh{i)6a- zHuhUw97frx^nYT@1In*|7=GzP%Ys`#gqM$~H`PKf!^kQfmP25)1Alo5_6HR13N@L#ln9ALd#_9j4S)E5eu9&^C1 zN`Ld_DcHWEqg#!>K$EqCpF_|y#MoF^@Si18KY#Yj+!c`%P<3~^(Nng5LQTrd>sGla z4w^Lg(e^C3te=7eR>Jp;qJfwg);KUJUS7`2$>~^Ikrq-I5vPC}#yeVIBZ7nHD?)Y- z4wxkrdMKQqfPerC3rp`FR=v=~7GV0IZFrlU3_j~_R|3FQQ0brW8w@_656S$}QVqDb z!e?J#9Gk%>&+4hDsZlhqEX|#2d4OOtGcy4MfSR2lHMJQo>Tn8;UaQAQVk8l=eX2b` zJ)jsr@1FHcnrF(nOU6v2p(tz%H`!vM^}wAL3SUqaSX((Mk3b}R!=22NM#KMi2yt0k z+3nq#+lRNqg2RfVa_{xX_k=Fsx11lvPy?A2th^ww1Ct?Ap)*u3hT_>pp#(&`-7 z-X<>58=~+FRf`=`d-pe>+5lQwi!z?`C>k+PRVlaD-nrVL3gsyPNC3Ftnw0FJHa@|8;B()g$HR z&9aV(r&yG@DF3B*ZpGN97R=UyrhdMTmX5!QT6f}hiXuvtcU9l^?vcRdVGzo zP@C!j^&J)?$(!ycA?tWFVYl>+jGd}8+QhMg2*twEpDEs(k9xHm&LwEn_n7={=iRE? zRoi*9v-}RxXo+Y9SOl?HS3acs?k}+)DZ?X&lY!+B`b9wAq!^U>$Fju2T&?6Gn3ar(if6Z;q6`Cm zZ%{LU;6}{jBJ+rr^nAYiZ#XFuiS=h6eDCgIVWivNnrwJg^@@^;e@tn^$li#Y%;?Qh z;;r{)DU+$SGn4gQRuPeC+60~gwL(q}Eg!SX8GhW33&fD62t-1Vk@Y_Ky2|DTl>yE5 z6T|=f5|Ja(jR3ia3~em;hmEv#^xxkp^nJV(bJ=-rz1)%BK)d}jaY!hUKQosfO%fKb2O#YU26LE8A({Yj-W0>kR#KqNAmFv`rpedEd3l&K0k&AD_HIC+xplV z+Znsb*Cf=D4X0!JwD!;R=F?Kh&;kD-@|HuKVEx;WpO`^}LSjNl#iWNP+mwE%>ig#g zXS{+eg1=?I?CN(%i_z5TRcsZsWhP@;5ng!tZYM}znJ#Kr0o0N z7f7^B+U-TWlk8=Uweof)^a6nDv( zF-Q|SH31a%vA8&?xCLk!{r&0^5=}C%anJ_znXassuC$aLI!v-{o@O7pYKn<>dp;tz zo%QpgC83pDJ(FaQGme=a9}koGaMwFho1)fYDC+pyw$H88GEp+L5P>?ZeVTaX+=mkg zG#)LpQ^qCz#ppRo3wCQ!5t|Cu(v{J*Qir&jkHoe06m@1Jf|K^mCl`<8q=VAl8Tr_q z*QPi{O+-Cpain>P2%Nok&r*J{KX5R1Xj1gq`Z{;%RTGTg`it(GVn#^gsmN&^Ih|XU zl8+cGj8&Qw3^PQOEZ>Iab`#1WT3=m7^_(cLtn7I42QAVlWGKYK<1<~5u;NY3A=zWbipHFbgx*n-#!YC7M% z8ih<)xtYHAwy8XPd3YUdfb=m4M~Da}X@S_?^|{L|MwG}gCM%Pvh_yJMwRp^j=lMaK zOZx4rXe&5NZSTH(QEb1282{HIIQ_I-UNnHV5(_I7d|#ox4VZUN9PR)r0y+|~TXuBZ z0?7)>3j$$*1sj#`{WooN@L_mP5FbsnzkLgQVh$L?#G`4 z1Hb0xpfnK$Od{c43B=fej;ubJ7{ zRu>m_q13Jq3F~x=-(}si1u(1x!UB|Xr+)ri+T8s1&hp<;#J`x;y8|j7^ropv!lb>Y zM+EfMOG`_D>COd1IBR7krBDpTUa-x58ygD@mr%Y3@Wo&HS1DD69)KU9o!zIpI>>iy z0AlSTLW0ws2ef%ZOouptAr2x7a%K8368Of6ybR_r9#C3k` zFbd8UBl|~qK%G+{24B(^MC0^9z>5e?tg5L24o+;(Z>T*l1Vhmj3il_IP@pFxB?Zz7 z_$xA?zJ*H#S8@!cbwCRp$I9sW_$q)4#=!xnl?x{b6J^ZcMo4HVD9-mGR)u>38z(du zP2T3hBqC=xJ9i1P*W+p_!w#iJ0fRliv z_40h%50I8S^z`Nq4qQN`BPQlIZ^zIJC4hrPJ}+b5c8AVSicA&d=XG=t_<#oxyo@=3 z0SgoM+sP#GMY0e?sPn(vSD6h23RKVc@7*TgEA+LeXV8iV^$tai?<#1R4>B*$^0KnP zDv^PWt!H{VePj;xu0LCg3#K!09ILACLXAo-Rd92*CDY9e0b;wn4@Vz*WMW}^dE`om zRyM{hVe`n!iVU`fHC7142mr+>NY`PHSALl9C8w`qv_3I!2T`Vf*0G8;{4~$}&eI)n zSHG|DX|pEN7yoEUXpAH%s#m0kMny((Q#F~_xyOhF#2KY6jU~1vG6>2Z3Ln-Jf1k%p z7D2Bo8^8K3x{Yieqz%XFFw+Me$!|97TwKv(akgU)lU{p)b11t;!X7Mx3)8x@G4Kn< z<5GfZvAV9d?yug~m5r65zgm*&lEj9whkEM*U3V#s)zB0WsEwC#MyI-9e6pfk&V01P zcxIuAV4U`A;g=M5+nIv#ye;dg*?pVue%2SqLYuQ?e=pyk2cAnMNNl83^s4r6Xtn1? zT)%VVf+Nnd5~^LP5zHKN#1@iK&#&vS2Ag>m+n{n~JjXXZH!*?_#aZ@Sxt~7Le8k$o zPx_in_LFSOv&y=^OC*upMDUH^in}`+1v84|#Zy>ZZUAwKf)&fU(we1X4++Edal5g~ z*s<;R({1b!Kgsz-y(NDeeQ|So?B^@e8TF>k_X~GD?jMSUluVmnyoUgk9FCq;)uAvR zlA8X|HFAx&+XBMaW({ww_u6P>WT@5czPyH3bcx&>PMaM2eZEUePgxNp>zZ?1M%>J! zjxH`5u4q15`k*H%b~D~S{Kh^ngudau@$DX{7VGnRdl;lh_vqMz2R5`q3mt70EtNHu zztfy(9ddpOF(7tF^-e%1xQ6SsPxLbL)P{_fyJL+I6D>J?s(L`FdsXRYY{RO1Vk&1# z?2B%t?jGdKJUl(3OolYqbjD#umO|CvR?Aic8?XibNv5dZ#OI%2bvXQZh@kh8IxoqD zT}Nd{>7djSEhOyXF%Pi$Ai%wP3x}c~mb`lValWfpDa2^2p*j#8^p^+8TcN6x!Kz5s zc)okYxoM~*uLQw9gfx0Mdfa=aaRPDEe?9DmgVZ7)o+NwnTqV7F6+`YF#~j_W9p_!4 z8bRNQy@JV4J!p|G=RIcNug?e56m@OZuow#P@RU_nTDa1gl})wyWx~QRavzt>?M6e> z2Ok^cN=zN2wocyD@@MoxR;zvCy|2ZN&I}A?lY<+(UPT4;sXOLXS(N5wWYo92bX_^W zHawCytJu7o^((NTn9YQrRdVHd`{G!2`J12qG59D_lP`8t1!;)UO?F z&Rl;|a%vc3ct~1svA8iEus0hE8|UliJKH-;M@-cos}Umq6D&`@aI!OUgl*VI&G}n= zCr1GH)mn`S{MG9e*M8YPL2G@Ls1tX0dH&``^^v(lkmga1eZ44br#MI)h@Q?uFvzJB zKh|%4ix$E_NUtqH*vmw1kz^ox3L)QR+^ht3Z3E8=mT6MN0S{MP8dtU=m$dYU*fE>I>)IDiN2z$K0j6qgCY+FPiJ4J*~& ze~6v_F<5*_+o-YexaTbtk@c`pyeqTKd^vzpuoMEV5v1mSfy_P%@ME_pquBg2X&Fm|hH4p3&K)xRW z`;PIkG2ja8m72Vs9uWpO6ErquqCn|^E2@J}K%>gCx6m^TvUXla^u(kjkkWL4yC>kx z#l_-wKcU**3;P*Z?cXXQq@ht`h=H>62es6Yc)+Vo?%(I-=XVCh4Xu=~90L08B}>!M z1%aCqP(eXEQI--41QY>*%@=pB1V9UuCYTj~I0k6~g<+E(q6Ttqlj5Qx=zan2SFu5I z+^4z0p`o*lo|T1#$|fehUE!o~USqj$03pS}$#(bzHV;63g)|kg2VmT^2Vvu8Ke}Kr zyA22OK0Db3t^)v>kl2WeiJ&=OcYL- za@TeUt_N7^u(j$H8w3zm)YL$LW8>s>yt_L%FtAd6tq)X4sAdts8HO@4XhH!1`A>Hr z*a`ofoIn^b&nO4ZrjnA9P~U)FJOB8X0I1T^(%Nf$1(qPVL%{1xPY=8a-vHDTyjI~3 z45o8PNuktY&=*5lRrMyo4iHNaA*(`B4MD)&!w|Omz(CojPs@UX<)Lv)U{TeRkwSQE z3?N%Dv~X}}0@5Vlfp9no9lk?%j+#Y4I;I;vok8vjGJ{L-74`HKS;$XnK>!0UEF!}B z*)yPW8#Q|Dg2PLuxaT7qn-j1ZK|ec2jUU_$^aiYbAo&|NxTz)cEdqLG0355>l#~f* z0t19%c((BBXm7XbPxFLy)^_*mK>IgOpi3&2OS zlpzSx@|_zn)iZ`@yoIsp-$UnLP#Amb^d*7B2z$gIG4WObgyu7F>DGv*-6~8Pl=G?pwvmahpN3PeK zt$g@p=CtbOOGHfEVG7YYLl8{w7f%GGq-b4NU%r3;xHi6_pVJE1=>s34J#-9$;3r#o zgv=;$j~1ahp%!?|)JNO2JOfK$AgROh2e?19sYKdO1@({tQ<)HnhbQ6K9jw-$t5x8k z1UL1vJ}|uqx?ch^Rf2z+?^lLSRc}RkkWYJ`*N>?OjpvUU6tLfs;?8 z?oZ95)o6{rn{PFfb{Mwh4IWX1NxxYA^Sq4l%#*E)tvT8!HX>H(U#z7UdexWos$lqC zXyJll6y<@V!R_17MDpm!t#JZa?ZDxOp0KEal7{joPI7Ad{2AicrEk;|`x#Jy8_bp; zvjcKXkfyx+wF_GX1x({K@ZLet+sik=>asOnnK!xR;#C5VbpF}VcSv3ckr1rsajB}S zTkSoy%6_!OU0nYNhIajBZXeF1I`Zjmg>VgXB5 zhZW1k+h4_Y+!L`n!t`0Doa(uSjPLYw%y>DVVfOo5@&H(g;sPISo*t~8(ozC#ABrLy zD=V#5g$zL`e*s9$et#i?Zofc(fesVED7o(UJa3c?@HA+Z>tKv$9}7 zz_k$4S6TLwZQQrsFoM3hkhsFUfUN~2KCUhQ904yrGtn^3QCny%M1HUkq`fyEgRS#Br{va*5% z5@j?4`;m)_9AbCh?~*`ONr?^GyMRPRk?Dzz4YYxQOZ`<&AGD+6e`JJl1^PIp3nsy? zFz2jRwg+)UKa4aE`WQtC<6Ov6p%WLh%LQ5_2o?Zggl6i)3W}3_NK^-^JdS~X7p$PK zF9g{$KtVM%dh+s?Jj`-edE(X{t}i@a2!9eGJT43nlnawf*>n2`N@BT9`6XX2gQbwK z{1NLPo4GHcf|#tC^u{p5a8xUguGlQczN71Nuuq(JvNQMjt)myyb*>_$BDqF+*@s4x$eHX(|6Z!E8OsvfcB~5vzxm$^4v}nID)nR_ z9X4(7!U@`o48J@~`jVn4Rsiv7`Hgs9b~h@w=^Dx%hIhn++ZD)&)O4@haplhe=&d)d zwiH2Y9m35sqezGa1w*e|KYBEjM`nY4{W^a>=zZW7*aSleULT5D+uC^DHVn?V00K3( z?@Q~Qm`Ev+14Xk;pD^q!QkQ4Mw1%<>7(dWd&?3yZ7WhG=C8H7k^n?hIC-ep3welSE zT`o?f+EsT}<67cUKcK#1f~mNxa!vHwS8To%|F_05O(Z@lz zLm**ZRap&X4ly}<8iUN8RXgfS<-hbfGUejsvM^kzdRx_`aWr8&M)O>_Q)Yg}G(VB} z&gk9Ix`%bcw+`tR0w_w8zEY5W)+g|ffpRU?6JVA@iv_S$d1P&U4{;3(3sN+1h*hgN zHqg=jt_=*!;yeO|{s=MS+} zJbQcCV;da!@ABT|%t6N z^T!P>DtnMZ)KyR7^C9A6UlT{PT?V&Qys8K*#zwwUq`MF|+6oqaA{I4ADf@8wAaxi)|NU9U2}T__wh=Frwxm7% zls*(yKWvvDfk*BP*oBuDzSM8!AyREaZA>tQVWc{nmihtV2xL?=k6`74*44$LVrYTZ znVZeCjJ}yS?~Ws9+Ex4ejAp!>XUXr1T?0xh5l3aZHIIXmogzQeN`ec7b z$BKk$wWD#g^P#ha(TxBmjuVSHTQKgp(c&3uw;Zfg$vX!|V|7yU(jIp+*{E<+j*v(yAARN(pbhOB5NQ>6Vl-Fw-(3Z6S7Q%+gvT(y*&{= zE*H~(mOZ~Gi`$M09_Ajz%g0r6RJ8o`)+N+xwIlmUI&>@dPGN>g!q$`D;n6tsK^0#s zZ&^!hx&4lqP_ks!u_RjD4PVGyl$8_G?5-MJrhf&VmS85>jFy?okcV)^)q7!50QIbK>Pt%X;Qxi5RjsGs3=%CT)h@f1XawZ4%TZ3dLQI31(*1pd&;) zbg0>SQP)d9W*)vq;)uT$H&0?8-n02*a`s8{IP0X5Mst)}yu`(tFm=r9t@4}_2ZkUc zTq8++@8-0pBDH+_rphh(CHdLqNK@rYi=KOzEVeH}l!pX!T&w-Xye&i7XRRXXZjI9aB>ay5H*b%gipW-xFDwsJc65=|eN=qU51RB=A&a7PjO3IpVK0GcKHB-y!@6~))!g{^l**L-14#i~4Sf1eTOPT) z-<>n{@9H9+Vsrj5H5k|V!+A{U(2JjfFOgm~DTs~n^84M)KY#ZZS~_dBC%ArdeddUY zJ|WrtxJ12Kt`eEp_ag3kCGYkN~bL@M#O0 z2tc$zp)dSgPFOT$`QqFuvM3;P*ZN215B0T-f@!CRrxCq9b4ilMKuB;(NpUESy5^FT zcc|YmlIg_*$gZecI2B_kS_vC}=0~yAT@##^3hVE6 zcz6$mh3Nur;r$g4$3R1qSal`AroZdgT$N;SPg;7FuycQ1kwY!w;^sGn$J&%ReDbb1 z(|i1A!()n7RdJW+$A!05o*2?xUzvFq&47+3-SVmKhr={`O6K>HcWCZ=Sdle#M?2m} z)&|(u7xJ{QE3HN&3KdzPx4h{Hl|MPjFVfRR9mot-Tf`OMuUtyzr>=1% zu;(FhoStv+jCES>$MHZo-VJ~QwL+V^L?spXVgq_ZeXN$?)?Y6!Gr;HczWedo>hK(P z-TiC6?r3NR{;FHzzK!pjGpb%xKUVvu$>~ertF}G!>{Ek^ddiQgXBMPz5?68gfG}aF zsO%OLL=qoY_AP@eJ+`BN?P)8k%Q_iQ9O8iMJlByccT!L7`s*GJni-|;NKLBHb?|<6 zUjAxS&m^7I8scRJT`b)LmPr}+Cr>YH@Ihtn4BhUSmn(HHJ!TwQbH%9WtN zz-uHV6SejQRW*(;r4nUY1O#t%w4X%NlFu*aZB6L%I#BibP%8bc%BDeq%IL!_xS0Ek z*H16b83qT%pFiKMsd>G$^zF$L2XNa~S3jY-_F`~Anv)G2{TDorBE{IlLzQnAKhUjw zqAMWod3PR#*qOb~>@nlXlyVLV-S6t$`MucIM=>D8q?K3xthdVcH9W|U3RgxF_&v0r zCx3834=dZ(zm#9ZSY6Zpn_}Slty^nJyd Date: Thu, 7 Mar 2024 19:25:16 +0000 Subject: [PATCH 0700/1097] t/nvmept_pi: drop JSON output for error cases Avoid errors decoding JSON data when testing invalid configurations. Signed-off-by: Vincent Fu --- t/nvmept_pi.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/t/nvmept_pi.py b/t/nvmept_pi.py index 5de77c9dbc..01fe705b40 100755 --- a/t/nvmept_pi.py +++ b/t/nvmept_pi.py @@ -49,7 +49,6 @@ def setup(self, parameters): f"--rw={self.fio_opts['rw']}", f"--bsrange={self.fio_opts['bsrange']}", f"--output={self.filenames['output']}", - f"--output-format={self.fio_opts['output-format']}", f"--md_per_io_size={self.fio_opts['md_per_io_size']}", f"--pi_act={self.fio_opts['pi_act']}", f"--pi_chk={self.fio_opts['pi_chk']}", @@ -58,7 +57,8 @@ def setup(self, parameters): ] for opt in ['fixedbufs', 'nonvectored', 'force_async', 'registerfiles', 'sqthread_poll', 'sqthread_poll_cpu', 'hipri', 'nowait', - 'time_based', 'runtime', 'verify', 'io_size', 'offset', 'number_ios']: + 'time_based', 'runtime', 'verify', 'io_size', 'offset', 'number_ios', + 'output-format']: if opt in self.fio_opts: option = f"--{opt}={self.fio_opts[opt]}" fio_args.append(option) @@ -622,7 +622,6 @@ def setup(self, parameters): "fio_opts": { "rw": 'read', "number_ios": NUMBER_IOS, - "output-format": "json", "pi_act": 0, "apptag": "0x8888", "apptag_mask": "0x0FFF", @@ -639,7 +638,6 @@ def setup(self, parameters): "fio_opts": { "rw": 'read', "number_ios": NUMBER_IOS, - "output-format": "json", "pi_act": 0, "apptag": "0x8888", "apptag_mask": "0x0FFF", @@ -660,7 +658,6 @@ def setup(self, parameters): "fio_opts": { "rw": 'read', "number_ios": NUMBER_IOS, - "output-format": "json", "pi_act": 0, "apptag": "0x8888", "apptag_mask": "0x0FFF", From b140fc5e484638a467480e369485b91290288d58 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Thu, 7 Mar 2024 18:55:22 +0000 Subject: [PATCH 0701/1097] t/nvmept_pi: add support for xNVMe ioengine Add a command-line option to run this script using the xNVMe ioengine with the async io_uring_cmd backend. The default remains to use the io_uring_cmd ioengine. Example: python3 t/nvmept_pi.py --dut /dev/ng1n1 --lbaf 6 --ioengine xnvme --fio ./fio Signed-off-by: Vincent Fu --- t/nvmept_pi.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/t/nvmept_pi.py b/t/nvmept_pi.py index 01fe705b40..df7c0b9fd8 100755 --- a/t/nvmept_pi.py +++ b/t/nvmept_pi.py @@ -43,8 +43,7 @@ def setup(self, parameters): fio_args = [ "--name=nvmept_pi", - "--ioengine=io_uring_cmd", - "--cmd_type=nvme", + f"--ioengine={self.fio_opts['ioengine']}", f"--filename={self.fio_opts['filename']}", f"--rw={self.fio_opts['rw']}", f"--bsrange={self.fio_opts['bsrange']}", @@ -63,6 +62,12 @@ def setup(self, parameters): option = f"--{opt}={self.fio_opts[opt]}" fio_args.append(option) + if self.fio_opts['ioengine'] == 'io_uring_cmd': + fio_args.append('--cmd_type=nvme') + elif self.fio_opts['ioengine'] == 'xnvme': + fio_args.append('--thread=1') + fio_args.append('--xnvme_async=io_uring_cmd') + super().setup(fio_args) @@ -686,6 +691,7 @@ def parse_args(): '(e.g., /dev/ng0n1). WARNING: THIS IS A DESTRUCTIVE TEST', required=True) parser.add_argument('-l', '--lbaf', nargs='+', type=int, help='list of lba formats to test') + parser.add_argument('-i', '--ioengine', default='io_uring_cmd') args = parser.parse_args() return args @@ -906,6 +912,7 @@ def main(): for test in TEST_LIST: test['fio_opts']['filename'] = args.dut + test['fio_opts']['ioengine'] = args.ioengine test_env = { 'fio_path': fio_path, From 7d6c99e917f7d68ffebbd1750802f7aed9c3d461 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Mon, 18 Mar 2024 14:51:10 -0400 Subject: [PATCH 0702/1097] docs: fix documentation for rate_cycle rate_cycle affects only rate_min and rate_iops_min. Signed-off-by: Vincent Fu --- HOWTO.rst | 4 ++-- fio.1 | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/HOWTO.rst b/HOWTO.rst index 2386d8062f..4c8ac3314e 100644 --- a/HOWTO.rst +++ b/HOWTO.rst @@ -3323,8 +3323,8 @@ I/O rate .. option:: rate_cycle=int - Average bandwidth for :option:`rate` and :option:`rate_min` over this number - of milliseconds. Defaults to 1000. + Average bandwidth for :option:`rate_min` and :option:`rate_iops_min` + over this number of milliseconds. Defaults to 1000. I/O latency diff --git a/fio.1 b/fio.1 index d955385d2b..09c6b621c6 100644 --- a/fio.1 +++ b/fio.1 @@ -3064,7 +3064,7 @@ ignore the thinktime and continue doing IO at the specified rate, instead of entering a catch-up mode after thinktime is done. .TP .BI rate_cycle \fR=\fPint -Average bandwidth for \fBrate\fR and \fBrate_min\fR over this number +Average bandwidth for \fBrate_min\fR and \fBrate_iops_min\fR over this number of milliseconds. Defaults to 1000. .SS "I/O latency" .TP From 2e976800fe74a58fadbbd9b069c01120b48cf9f0 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Wed, 20 Mar 2024 14:24:26 -0400 Subject: [PATCH 0703/1097] t/fiotestlib: pass command-line options to FioJobFileTest Add a means to specify arbitrary command-line options when we are running a test that consists of a fio job file. Signed-off-by: Vincent Fu --- t/fiotestlib.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/t/fiotestlib.py b/t/fiotestlib.py index a96338a384..466e482dcd 100755 --- a/t/fiotestlib.py +++ b/t/fiotestlib.py @@ -175,7 +175,7 @@ def __init__(self, fio_path, fio_job, success, testnum, artifact_root, super().__init__(fio_path, success, testnum, artifact_root) - def setup(self, parameters=None): + def setup(self, parameters): """Setup instance variables for fio job test.""" self.filenames['fio_output'] = f"{os.path.basename(self.fio_job)}.output" @@ -185,6 +185,8 @@ def setup(self, parameters=None): f"--output={self.filenames['fio_output']}", self.fio_job, ] + if parameters: + fio_args += parameters super().setup(fio_args) @@ -206,7 +208,7 @@ def run_pre_job(self): self.testnum, self.paths['artifacts'], output_format=self.output_format) - precon.setup() + precon.setup(None) precon.run() precon.check_result() self.precon_failed = not precon.passed @@ -412,7 +414,7 @@ def run_fio_tests(test_list, test_env, args): fio_pre_success=fio_pre_success, output_format=output_format) desc = config['job'] - parameters = [] + parameters = config['parameters'] if 'parameters' in config else None elif issubclass(config['test_class'], FioJobCmdTest): if not 'success' in config: config['success'] = SUCCESS_DEFAULT From f226220049035290e5e193d1fc117d3057fc1270 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Wed, 20 Mar 2024 14:08:46 -0400 Subject: [PATCH 0704/1097] t/jobs/t0030: add test for --bandwidth-log option Add a test to detect changes that break the --bandwidth-log option. This option uses the logging data structures in a way that differs from how the other logs use them. So it's easy to forget about this special case. Recent patches resolving related breakage are: d72244761b2230fbb2d6eaec59cdedd3ea651d4f ("stat: fix segfault with fio option --bandwidth-log") acc481b6d34aab3ee6e19f22b64f8bf0dd30480c ("iolog: fix reported defect from coverity scan") Signed-off-by: Vincent Fu --- t/jobs/t0030.fio | 10 ++++++++++ t/run-fio-tests.py | 10 ++++++++++ 2 files changed, 20 insertions(+) create mode 100644 t/jobs/t0030.fio diff --git a/t/jobs/t0030.fio b/t/jobs/t0030.fio new file mode 100644 index 0000000000..8bbc810e73 --- /dev/null +++ b/t/jobs/t0030.fio @@ -0,0 +1,10 @@ +# run with --bandwidth-log +# broken behavior: seg fault +# successful behavior: test runs to completion with 0 as the exit code + +[test] +ioengine=null +filesize=1T +rw=read +time_based +runtime=2s diff --git a/t/run-fio-tests.py b/t/run-fio-tests.py index 08134e50e1..acdbbf8879 100755 --- a/t/run-fio-tests.py +++ b/t/run-fio-tests.py @@ -859,6 +859,16 @@ def check_result(self): 'output_format': 'json', 'requirements': [], }, + { + 'test_id': 30, + 'test_class': FioJobFileTest, + 'job': 't0030.fio', + 'success': SUCCESS_DEFAULT, + 'pre_job': None, + 'pre_success': None, + 'parameters': ['--bandwidth-log'], + 'requirements': [], + }, { 'test_id': 1000, 'test_class': FioExeTest, From c3c398e0701187716b6fba847fdae192e469b80b Mon Sep 17 00:00:00 2001 From: Shin'ichiro Kawasaki Date: Thu, 21 Mar 2024 12:10:10 +0900 Subject: [PATCH 0705/1097] iolog: regrow logs in iolog_delay() The commit b85c01f7e9df ("iolog.c: fix inaccurate clat when replay trace") triggered the assertion failure below for the workload which does I/O replay as asynchronous I/O together with log recording options such as write_lat_log. fio: stat.c:3030: get_cur_log: Assertion `iolog->pending->nr_samples < iolog->pending->max_samples' failed. fio: pid=40120, got signal=6 The assertion means that too many logs are recorded in the pending log space which keeps the logs until next log space regrow by reglow_logs() call. However, reglow_logs() is not called, and the pending log space runs out. The trigger commit modified iolog_delay() to call io_u_queued_complete() so that the asynchronous I/Os can be completed during delays between replayed I/Os. Before this commit, replayed I/Os were not completed until all I/O units are consumed. So the free I/O unit list gets empty periodically, then wait_for_completion() and regrow_logs() were called periodically. After this commit, all I/O units are not consumed, so wait_for_completion() and regrow_logs() are no longer called for long duration. Hence the assertion failure. To avoid the assertion, add the check for log regrow and reglow_logs() call in iolog_delay(). Fixes: b85c01f7e9df ("iolog.c: fix inaccurate clat when replay trace") Signed-off-by: Shin'ichiro Kawasaki Link: https://lore.kernel.org/r/20240321031011.4140040-2-shinichiro.kawasaki@wdc.com Signed-off-by: Jens Axboe --- iolog.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/iolog.c b/iolog.c index f52a9a80f7..251e9d7fa2 100644 --- a/iolog.c +++ b/iolog.c @@ -102,6 +102,8 @@ static void iolog_delay(struct thread_data *td, unsigned long delay) ret = io_u_queued_complete(td, 0); if (ret < 0) td_verror(td, -ret, "io_u_queued_complete"); + if (td->flags & TD_F_REGROW_LOGS) + regrow_logs(td); if (utime_since_now(&ts) > delay) break; } From 140c58beeee44a10358a817c7699b66c5c7290f9 Mon Sep 17 00:00:00 2001 From: Shin'ichiro Kawasaki Date: Thu, 21 Mar 2024 12:10:11 +0900 Subject: [PATCH 0706/1097] test: add the test for regrow logs with asynchronous I/O replay Add t/jobs/t0031-pre.fio and t/jobs/t0031.fio to test that the log space is regrown for the asynchronous I/O replay jobs. This test case confirms the fix by the previous commit titled "iolog: regrow logs in iolog_delay()". Signed-off-by: Shin'ichiro Kawasaki Link: https://lore.kernel.org/r/20240321031011.4140040-3-shinichiro.kawasaki@wdc.com Signed-off-by: Jens Axboe --- t/jobs/t0031-pre.fio | 8 ++++++++ t/jobs/t0031.fio | 7 +++++++ t/run-fio-tests.py | 9 +++++++++ 3 files changed, 24 insertions(+) create mode 100644 t/jobs/t0031-pre.fio create mode 100644 t/jobs/t0031.fio diff --git a/t/jobs/t0031-pre.fio b/t/jobs/t0031-pre.fio new file mode 100644 index 0000000000..ce4ee3b691 --- /dev/null +++ b/t/jobs/t0031-pre.fio @@ -0,0 +1,8 @@ +[job] +rw=write +ioengine=libaio +size=1mb +time_based=1 +runtime=1 +filename=t0030file +write_iolog=iolog diff --git a/t/jobs/t0031.fio b/t/jobs/t0031.fio new file mode 100644 index 0000000000..ae8f74428d --- /dev/null +++ b/t/jobs/t0031.fio @@ -0,0 +1,7 @@ +[job] +rw=read +ioengine=libaio +iodepth=128 +filename=t0030file +read_iolog=iolog +write_lat_log=lat_log diff --git a/t/run-fio-tests.py b/t/run-fio-tests.py index acdbbf8879..1b884d871d 100755 --- a/t/run-fio-tests.py +++ b/t/run-fio-tests.py @@ -869,6 +869,15 @@ def check_result(self): 'parameters': ['--bandwidth-log'], 'requirements': [], }, + { + 'test_id': 31, + 'test_class': FioJobFileTest, + 'job': 't0031.fio', + 'success': SUCCESS_DEFAULT, + 'pre_job': 't0031-pre.fio', + 'pre_success': SUCCESS_DEFAULT, + 'requirements': [], + }, { 'test_id': 1000, 'test_class': FioExeTest, From 20f42c101f7876648705a4fb8a9e2a647dc936ce Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Thu, 21 Mar 2024 08:36:14 -0400 Subject: [PATCH 0707/1097] t/run-fio-tests: restrict t0031 to Linux only This test uses libaio. So run it only on Linux when libaio is available. Signed-off-by: Vincent Fu --- t/run-fio-tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/t/run-fio-tests.py b/t/run-fio-tests.py index 1b884d871d..225806134e 100755 --- a/t/run-fio-tests.py +++ b/t/run-fio-tests.py @@ -876,7 +876,7 @@ def check_result(self): 'success': SUCCESS_DEFAULT, 'pre_job': 't0031-pre.fio', 'pre_success': SUCCESS_DEFAULT, - 'requirements': [], + 'requirements': [Requirements.linux, Requirements.libaio], }, { 'test_id': 1000, From a7a817a2154f351aa1261078eafe91dae270135d Mon Sep 17 00:00:00 2001 From: friendy-su Date: Wed, 10 Jan 2024 17:20:36 +0800 Subject: [PATCH 0708/1097] ioengines: implement dircreate, dirstat, dirdelete engines to fileoperations.c Similar to file operation, directory operation performance is an important benchmark to file system in practice. * dircreate engine measures directories create performance * dirstat engine measures directories lookup performance * dirdelete engine measures directories delete performance Signed-off-by: friendy-su Signed-off-by: Vincent Fu --- HOWTO.rst | 15 ++++ engines/fileoperations.c | 121 ++++++++++++++++++++++++++++++-- examples/dircreate-ioengine.fio | 25 +++++++ examples/dirdelete-ioengine.fio | 18 +++++ examples/dirstat-ioengine.fio | 18 +++++ fio.1 | 15 ++++ 6 files changed, 207 insertions(+), 5 deletions(-) create mode 100644 examples/dircreate-ioengine.fio create mode 100644 examples/dirdelete-ioengine.fio create mode 100644 examples/dirstat-ioengine.fio diff --git a/HOWTO.rst b/HOWTO.rst index 4c8ac3314e..fb067fe528 100644 --- a/HOWTO.rst +++ b/HOWTO.rst @@ -2192,6 +2192,21 @@ I/O engine and 'nrfiles', so that the files will be created. This engine is to measure file delete. + **dircreate** + Simply create the directories and do no I/O to them. You still need to + set `filesize` so that all the accounting still occurs, but no + actual I/O will be done other than creating the directories. + + **dirstat** + Simply do stat() and do no I/O to the directories. You need to set 'filesize' + and 'nrfiles', so that directories will be created. + This engine is to measure directory lookup and meta data access. + + **dirdelete** + Simply delete the directories by rmdir() and do no I/O to them. You need to set 'filesize' + and 'nrfiles', so that the directories will be created. + This engine is to measure directory delete. + **libpmem** Read and write using mmap I/O to a file on a filesystem mounted with DAX on a persistent memory device through the PMDK diff --git a/engines/fileoperations.c b/engines/fileoperations.c index 1db60da181..a3e92e6c1e 100644 --- a/engines/fileoperations.c +++ b/engines/fileoperations.c @@ -1,8 +1,8 @@ /* - * fileoperations engine + * file/directory operations engine * - * IO engine that doesn't do any IO, just operates files and tracks the latency - * of the file operation. + * IO engine that doesn't do any IO, just operates files/directories + * and tracks the latency of the operation. */ #include #include @@ -15,9 +15,15 @@ #include "../optgroup.h" #include "../oslib/statx.h" +enum fio_engine { + UNKNOWN_OP_ENGINE = 0, + FILE_OP_ENGINE = 1, + DIR_OP_ENGINE = 2, +}; struct fc_data { enum fio_ddir stat_ddir; + enum fio_engine op_engine; }; struct filestat_options { @@ -61,6 +67,26 @@ static struct fio_option options[] = { }, }; +static int setup_dirs(struct thread_data *td) +{ + int ret = 0; + int i; + struct fio_file *f; + + for_each_file(td, f, i) { + dprint(FD_FILE, "setup directory %s\n", f->file_name); + ret = fio_mkdir(f->file_name, 0700); + if ((ret && errno != EEXIST)) { + log_err("create directory %s failed with %d\n", + f->file_name, errno); + break; + } + ret = 0; + } + return ret; +} + + static int open_file(struct thread_data *td, struct fio_file *f) { @@ -81,7 +107,14 @@ static int open_file(struct thread_data *td, struct fio_file *f) if (do_lat) fio_gettime(&start, NULL); - f->fd = open(f->file_name, O_CREAT|O_RDWR, 0600); + if (((struct fc_data *)td->io_ops_data)->op_engine == FILE_OP_ENGINE) + f->fd = open(f->file_name, O_CREAT|O_RDWR, 0600); + else if (((struct fc_data *)td->io_ops_data)->op_engine == DIR_OP_ENGINE) + f->fd = fio_mkdir(f->file_name, S_IFDIR); + else { + log_err("fio: unknown file/directory operation engine\n"); + return 1; + } if (f->fd == -1) { char buf[FIO_VERROR_SIZE]; @@ -195,7 +228,16 @@ static int delete_file(struct thread_data *td, struct fio_file *f) if (do_lat) fio_gettime(&start, NULL); - ret = unlink(f->file_name); + if (((struct fc_data *)td->io_ops_data)->op_engine == FILE_OP_ENGINE) + ret = unlink(f->file_name); + else if (((struct fc_data *)td->io_ops_data)->op_engine == DIR_OP_ENGINE) + ret = rmdir(f->file_name); + else { + log_err("fio: unknown file/directory operation engine\n"); + return 1; + } + + if (ret == -1) { char buf[FIO_VERROR_SIZE]; @@ -250,6 +292,17 @@ static int init(struct thread_data *td) else if (td_write(td)) data->stat_ddir = DDIR_WRITE; + data->op_engine = UNKNOWN_OP_ENGINE; + + if (!strncmp(td->o.ioengine, "file", 4)) { + data->op_engine = FILE_OP_ENGINE; + dprint(FD_FILE, "Operate engine type: file\n"); + } + if (!strncmp(td->o.ioengine, "dir", 3)) { + data->op_engine = DIR_OP_ENGINE; + dprint(FD_FILE, "Operate engine type: directory\n"); + } + td->io_ops_data = data; return 0; } @@ -261,6 +314,12 @@ static void cleanup(struct thread_data *td) free(data); } +static int remove_dir(struct thread_data *td, struct fio_file *f) +{ + dprint(FD_FILE, "remove directory %s\n", f->file_name); + return rmdir(f->file_name); +} + static struct ioengine_ops ioengine_filecreate = { .name = "filecreate", .version = FIO_IOOPS_VERSION, @@ -302,12 +361,61 @@ static struct ioengine_ops ioengine_filedelete = { FIO_NOSTATS | FIO_NOFILEHASH, }; +static struct ioengine_ops ioengine_dircreate = { + .name = "dircreate", + .version = FIO_IOOPS_VERSION, + .init = init, + .cleanup = cleanup, + .queue = queue_io, + .get_file_size = get_file_size, + .open_file = open_file, + .close_file = generic_close_file, + .unlink_file = remove_dir, + .flags = FIO_DISKLESSIO | FIO_SYNCIO | FIO_FAKEIO | + FIO_NOSTATS | FIO_NOFILEHASH, +}; + +static struct ioengine_ops ioengine_dirstat = { + .name = "dirstat", + .version = FIO_IOOPS_VERSION, + .setup = setup_dirs, + .init = init, + .cleanup = cleanup, + .queue = queue_io, + .invalidate = invalidate_do_nothing, + .get_file_size = generic_get_file_size, + .open_file = stat_file, + .unlink_file = remove_dir, + .flags = FIO_DISKLESSIO | FIO_SYNCIO | FIO_FAKEIO | + FIO_NOSTATS | FIO_NOFILEHASH, + .options = options, + .option_struct_size = sizeof(struct filestat_options), +}; + +static struct ioengine_ops ioengine_dirdelete = { + .name = "dirdelete", + .version = FIO_IOOPS_VERSION, + .setup = setup_dirs, + .init = init, + .invalidate = invalidate_do_nothing, + .cleanup = cleanup, + .queue = queue_io, + .get_file_size = get_file_size, + .open_file = delete_file, + .unlink_file = remove_dir, + .flags = FIO_DISKLESSIO | FIO_SYNCIO | FIO_FAKEIO | + FIO_NOSTATS | FIO_NOFILEHASH, +}; + static void fio_init fio_fileoperations_register(void) { register_ioengine(&ioengine_filecreate); register_ioengine(&ioengine_filestat); register_ioengine(&ioengine_filedelete); + register_ioengine(&ioengine_dircreate); + register_ioengine(&ioengine_dirstat); + register_ioengine(&ioengine_dirdelete); } static void fio_exit fio_fileoperations_unregister(void) @@ -315,4 +423,7 @@ static void fio_exit fio_fileoperations_unregister(void) unregister_ioengine(&ioengine_filecreate); unregister_ioengine(&ioengine_filestat); unregister_ioengine(&ioengine_filedelete); + unregister_ioengine(&ioengine_dircreate); + unregister_ioengine(&ioengine_dirstat); + unregister_ioengine(&ioengine_dirdelete); } diff --git a/examples/dircreate-ioengine.fio b/examples/dircreate-ioengine.fio new file mode 100644 index 0000000000..c89d9e4d00 --- /dev/null +++ b/examples/dircreate-ioengine.fio @@ -0,0 +1,25 @@ +# Example dircreate job +# +# create_on_open is needed so that the open happens during the run and not the +# setup. +# +# openfiles needs to be set so that you do not exceed the maximum allowed open +# files. +# +# filesize needs to be set to a non zero value so fio will actually run, but the +# IO will not really be done and the write latency numbers will only reflect the +# open times. +[global] +create_on_open=1 +nrfiles=30 +ioengine=dircreate +fallocate=none +filesize=4k +openfiles=1 + +[t0] +[t1] +[t2] +[t3] +[t4] +[t5] diff --git a/examples/dirdelete-ioengine.fio b/examples/dirdelete-ioengine.fio new file mode 100644 index 0000000000..4e5b1e2c7b --- /dev/null +++ b/examples/dirdelete-ioengine.fio @@ -0,0 +1,18 @@ +# Example dirdelete job + +# 'filedelete' engine only do 'rmdir(dirname)'. +# 'filesize' must be set, then directories will be created at setup stage. +# 'unlink' is better set to 0, since the directory is deleted in measurement. +# the options disabled completion latency output such as 'disable_clat' and 'gtod_reduce' must not set. +[global] +ioengine=dirdelete +filesize=4k +nrfiles=200 +unlink=0 + +[t0] +[t1] +[t2] +[t3] +[t4] +[t5] diff --git a/examples/dirstat-ioengine.fio b/examples/dirstat-ioengine.fio new file mode 100644 index 0000000000..1322dd28fa --- /dev/null +++ b/examples/dirstat-ioengine.fio @@ -0,0 +1,18 @@ +# Example dirstat job + +# 'dirstat' engine only do 'stat(dirname)', file will not be open(). +# 'filesize' must be set, then files will be created at setup stage. + +[global] +ioengine=dirstat +numjobs=10 +filesize=4k +nrfiles=5 +thread + +[t0] +[t1] +[t2] +[t3] +[t4] +[t5] diff --git a/fio.1 b/fio.1 index 09c6b621c6..63375c6257 100644 --- a/fio.1 +++ b/fio.1 @@ -2004,6 +2004,21 @@ Simply delete files by unlink() and do no I/O to the file. You need to set 'file and 'nrfiles', so that files will be created. This engine is to measure file delete. .TP +.B dircreate +Simply create the directories and do no I/O to them. You still need to set +\fBfilesize\fR so that all the accounting still occurs, but no actual I/O will be +done other than creating the directories. +.TP +.B dirstat +Simply do stat() and do no I/O to the directory. You need to set 'filesize' +and 'nrfiles', so that directories will be created. +This engine is to measure directory lookup and meta data access. +.TP +.B dirdelete +Simply delete directories by unlink() and do no I/O to the directory. You need to set 'filesize' +and 'nrfiles', so that directories will be created. +This engine is to measure directory delete. +.TP .B libpmem Read and write using mmap I/O to a file on a filesystem mounted with DAX on a persistent memory device through the PMDK From 2dee6cc12677c4cac7ce7e247bcb172cbd110895 Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Fri, 22 Mar 2024 10:16:30 -0400 Subject: [PATCH 0709/1097] engines/fileoperations: remove extra blank lines Signed-off-by: Vincent Fu --- engines/fileoperations.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/engines/fileoperations.c b/engines/fileoperations.c index a3e92e6c1e..58a8149637 100644 --- a/engines/fileoperations.c +++ b/engines/fileoperations.c @@ -86,8 +86,6 @@ static int setup_dirs(struct thread_data *td) return ret; } - - static int open_file(struct thread_data *td, struct fio_file *f) { struct timespec start; @@ -207,7 +205,6 @@ static int stat_file(struct thread_data *td, struct fio_file *f) return 0; } - static int delete_file(struct thread_data *td, struct fio_file *f) { struct timespec start; @@ -237,8 +234,6 @@ static int delete_file(struct thread_data *td, struct fio_file *f) return 1; } - - if (ret == -1) { char buf[FIO_VERROR_SIZE]; int e = errno; @@ -407,7 +402,6 @@ static struct ioengine_ops ioengine_dirdelete = { FIO_NOSTATS | FIO_NOFILEHASH, }; - static void fio_init fio_fileoperations_register(void) { register_ioengine(&ioengine_filecreate); From caa20ee59240348c33284f56b72e4f8aeb4044ac Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Fri, 22 Mar 2024 10:21:56 -0400 Subject: [PATCH 0710/1097] engines/fileoperations: use local var for ioengine data Improve code readability by using a local variable for ioengine data. Signed-off-by: Vincent Fu --- engines/fileoperations.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/engines/fileoperations.c b/engines/fileoperations.c index 58a8149637..c52f09004e 100644 --- a/engines/fileoperations.c +++ b/engines/fileoperations.c @@ -90,6 +90,7 @@ static int open_file(struct thread_data *td, struct fio_file *f) { struct timespec start; int do_lat = !td->o.disable_lat; + struct fc_data *fcd = td->io_ops_data; dprint(FD_FILE, "fd open %s\n", f->file_name); @@ -105,9 +106,9 @@ static int open_file(struct thread_data *td, struct fio_file *f) if (do_lat) fio_gettime(&start, NULL); - if (((struct fc_data *)td->io_ops_data)->op_engine == FILE_OP_ENGINE) + if (fcd->op_engine == FILE_OP_ENGINE) f->fd = open(f->file_name, O_CREAT|O_RDWR, 0600); - else if (((struct fc_data *)td->io_ops_data)->op_engine == DIR_OP_ENGINE) + else if (fcd->op_engine == DIR_OP_ENGINE) f->fd = fio_mkdir(f->file_name, S_IFDIR); else { log_err("fio: unknown file/directory operation engine\n"); @@ -209,6 +210,7 @@ static int delete_file(struct thread_data *td, struct fio_file *f) { struct timespec start; int do_lat = !td->o.disable_lat; + struct fc_data *fcd = td->io_ops_data; int ret; dprint(FD_FILE, "fd delete %s\n", f->file_name); @@ -225,9 +227,9 @@ static int delete_file(struct thread_data *td, struct fio_file *f) if (do_lat) fio_gettime(&start, NULL); - if (((struct fc_data *)td->io_ops_data)->op_engine == FILE_OP_ENGINE) + if (fcd->op_engine == FILE_OP_ENGINE) ret = unlink(f->file_name); - else if (((struct fc_data *)td->io_ops_data)->op_engine == DIR_OP_ENGINE) + else if (fcd->op_engine == DIR_OP_ENGINE) ret = rmdir(f->file_name); else { log_err("fio: unknown file/directory operation engine\n"); From da7ec916243221b28ce361329aa5a8e40a90962d Mon Sep 17 00:00:00 2001 From: Vincent Fu Date: Fri, 22 Mar 2024 10:30:43 -0400 Subject: [PATCH 0711/1097] examples: fiograph plots for dir operation ioengines Signed-off-by: Vincent Fu --- examples/dircreate-ioengine.png | Bin 0 -> 42659 bytes examples/dirdelete-ioengine.png | Bin 0 -> 45530 bytes examples/dirstat-ioengine.png | Bin 0 -> 33597 bytes 3 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 examples/dircreate-ioengine.png create mode 100644 examples/dirdelete-ioengine.png create mode 100644 examples/dirstat-ioengine.png diff --git a/examples/dircreate-ioengine.png b/examples/dircreate-ioengine.png new file mode 100644 index 0000000000000000000000000000000000000000..da1a8c40a070e9c2a6e1cae71be9e88a561414bf GIT binary patch literal 42659 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 z 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 D^`www literal 0 HcmV?d00001 diff --git a/examples/dirdelete-ioengine.png b/examples/dirdelete-ioengine.png new file mode 100644 index 0000000000000000000000000000000000000000..af2461952d644714c4df3e7169f2d3a0cd7d455b GIT binary patch literal 45530 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