trim: add support for multiple ranges
authorAnkit Kumar <ankit.kumar@samsung.com>
Thu, 15 Feb 2024 15:18:10 +0000 (20:48 +0530)
committerJens Axboe <axboe@kernel.dk>
Thu, 15 Feb 2024 15:38:39 +0000 (08:38 -0700)
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 <ankit.kumar@samsung.com>
Link: https://lore.kernel.org/r/20240215151812.138370-2-ankit.kumar@samsung.com
Signed-off-by: Jens Axboe <axboe@kernel.dk>
12 files changed:
HOWTO.rst
backend.c
cconv.c
fio.1
fio.h
init.c
io_u.c
io_u.h
ioengines.h
options.c
server.h
thread_options.h

index 5bc1713cfd5bc89aa2d425a35c14f038f081ea7a..4b02100c6568f514fa9dd8f3863a0d57b0b2da40 100644 (file)
--- 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
index 1fab467a1b06083d9428a68ee906d9e7e5b21500..2f2221bf9c6398920628897e82da7d14f1650f36 100644 (file)
--- 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 c92984081b15c42c3236c7d7056da8885d6379dc..ead472480b9a2aa24793802d80542e241e462b92 100644 (file)
--- 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 7ec5c745a5cea81a3baac0dca1c9d2fb24334450..e6b291a76c86126e704bd9ec12f2430dccce0382 100644 (file)
--- 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 1322656fd4497501577804124438f38f55765e93..fc3e3ececa36ef02992f8037faff300f5e4e2892 100644 (file)
--- a/fio.h
+++ b/fio.h
 
 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 105339fa28e0b855af65da9cb6a3c30529156e8d..7a0b14a3d2f5031516db14ab583952de77439a13 100644 (file)
--- 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 4254675acc7d0b922c4950906ca40fef91c81a36..2b8e17f8433684a8aba2fd8b53ee8b342d8a931f 100644 (file)
--- 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 786251d5be92f08e12515c3051f3babed04137a6..cfacf310e1cd7e21f4925b8da028b25c24f50ca2 100644 (file)
--- 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;
 
index 4391b31e3ca2a30900aa85d38bc0275f13ee68f5..2fd7f52ca714231119010f27df6006dd48a72551 100644 (file)
@@ -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 */
 };
 
 /*
index 1da4de78151054237ff2a0f3a3ad5026cca5b70c..25e042d0b7236f302b3f4fefcb11af81762368c4 100644 (file)
--- 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",
index 0eb594ce857aa838a64b7d37e5d424fc50fd4512..6d2659b05a25c144522606feac9b40e925731c04 100644 (file)
--- 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,
index 24f695fe1a4d665b73c7800fadbcbe65354afcd9..c2e71518b37471b2533ce8678580fcc02a016e5c 100644 (file)
@@ -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
         */