zbd: disable crossing from conventional to sequential zones
[fio.git] / zbd.c
diff --git a/zbd.c b/zbd.c
index a834f94c03fa0f8dd80ea5be6a1d685977c1192d..2cebc5e6d0126bcbe5755dcc4bbf77352cda5b80 100644 (file)
--- a/zbd.c
+++ b/zbd.c
@@ -131,15 +131,6 @@ static uint32_t zbd_zone_idx(const struct fio_file *f, uint64_t offset)
        return min(zone_idx, f->zbd_info->nr_zones);
 }
 
-/**
- * zbd_zone_swr - Test whether a zone requires sequential writes
- * @z: zone info pointer.
- */
-static inline bool zbd_zone_swr(struct fio_zone_info *z)
-{
-       return z->type == ZBD_ZONE_TYPE_SWR;
-}
-
 /**
  * zbd_zone_end - Return zone end location
  * @z: zone info pointer.
@@ -171,11 +162,12 @@ static bool zbd_zone_full(const struct fio_file *f, struct fio_zone_info *z,
 {
        assert((required & 511) == 0);
 
-       return zbd_zone_swr(z) &&
+       return z->has_wp &&
                z->wp + required > zbd_zone_capacity_end(z);
 }
 
-static void zone_lock(struct thread_data *td, 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)
 {
        struct zoned_block_device_info *zbd = f->zbd_info;
        uint32_t nz = z - zbd->zone_info;
@@ -183,6 +175,8 @@ static void zone_lock(struct thread_data *td, struct fio_file *f, struct fio_zon
        /* 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.
@@ -203,6 +197,7 @@ static inline void zone_unlock(struct fio_zone_info *z)
 {
        int ret;
 
+       assert(z->has_wp);
        ret = pthread_mutex_unlock(&z->mutex);
        assert(!ret);
 }
@@ -249,7 +244,7 @@ static bool zbd_is_seq_job(struct fio_file *f)
        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++)
-               if (zbd_zone_swr(get_zone(f, zone_idx)))
+               if (get_zone(f, zone_idx)->has_wp)
                        return true;
 
        return false;
@@ -330,10 +325,6 @@ static bool zbd_verify_sizes(void)
                                         (unsigned long long) new_end - f->file_offset);
                                f->io_size = new_end - f->file_offset;
                        }
-
-                       f->min_zone = zbd_zone_idx(f, f->file_offset);
-                       f->max_zone = zbd_zone_idx(f, f->file_offset + f->io_size);
-                       assert(f->min_zone < f->max_zone);
                }
        }
 
@@ -429,6 +420,7 @@ static int init_zone_info(struct thread_data *td, struct fio_file *f)
                p->type = ZBD_ZONE_TYPE_SWR;
                p->cond = ZBD_ZONE_COND_EMPTY;
                p->capacity = zone_capacity;
+               p->has_wp = 1;
        }
        /* a sentinel */
        p->start = nr_zones * zone_size;
@@ -512,8 +504,17 @@ static int parse_zone_info(struct thread_data *td, struct fio_file *f)
                                p->wp = z->wp;
                                break;
                        }
+
+                       switch (z->type) {
+                       case ZBD_ZONE_TYPE_SWR:
+                               p->has_wp = 1;
+                               break;
+                       default:
+                               p->has_wp = 0;
+                       }
                        p->type = z->type;
                        p->cond = z->cond;
+
                        if (j > 0 && p->start != p[-1].start + zone_size) {
                                log_info("%s: invalid zone data\n",
                                         f->file_name);
@@ -525,8 +526,9 @@ 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, ZBD_REPORT_MAX_ZONES);
+               nrz = zbd_report_zones(td, f, offset, zones,
+                                      min((uint32_t)(nr_zones - j),
+                                          ZBD_REPORT_MAX_ZONES));
                if (nrz < 0) {
                        ret = nrz;
                        log_info("fio: report zones (offset %llu) failed for %s (%d).\n",
@@ -675,6 +677,18 @@ int zbd_setup_files(struct thread_data *td)
                if (!zbd)
                        continue;
 
+               f->min_zone = zbd_zone_idx(f, f->file_offset);
+               f->max_zone = zbd_zone_idx(f, f->file_offset + f->io_size);
+
+               /*
+                * When all zones in the I/O range are conventional, io_size
+                * can be smaller than zone size, making min_zone the same
+                * as max_zone. This is why the assert below needs to be made
+                * conditional.
+                */
+               if (zbd_is_seq_job(f))
+                       assert(f->min_zone < f->max_zone);
+
                zbd->max_open_zones = zbd->max_open_zones ?: ZBD_MAX_OPEN_ZONES;
 
                if (td->o.max_open_zones > 0 &&
@@ -729,9 +743,10 @@ static int zbd_reset_zone(struct thread_data *td, struct fio_file *f,
 {
        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 (z->wp == z->start)
+       if (!data_in_zone)
                return 0;
 
        assert(is_valid_offset(f, offset + length - 1));
@@ -750,7 +765,8 @@ 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 -= z->wp - z->start;
+       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;
@@ -811,7 +827,7 @@ static int zbd_reset_zones(struct thread_data *td, struct fio_file *f,
        for (z = zb; z < ze; z++) {
                uint32_t nz = zbd_zone_nr(f, z);
 
-               if (!zbd_zone_swr(z))
+               if (!z->has_wp)
                        continue;
                zone_lock(td, f, z);
                if (all_zones) {
@@ -878,29 +894,37 @@ enum swd_action {
 };
 
 /* Calculate the number of sectors with data (swd) and perform action 'a' */
-static uint64_t zbd_process_swd(const struct fio_file *f, enum swd_action a)
+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 = get_zone(f, f->min_zone);
        ze = get_zone(f, f->max_zone);
        for (z = zb; z < ze; z++) {
-               pthread_mutex_lock(&z->mutex);
+               if (z->has_wp) {
+                       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;
        }
        pthread_mutex_unlock(&f->zbd_info->mutex);
        for (z = zb; z < ze; z++)
-               zone_unlock(z);
+               if (z->has_wp)
+                       zone_unlock(z);
 
        return swd;
 }
@@ -911,37 +935,28 @@ static uint64_t zbd_process_swd(const struct fio_file *f, enum swd_action a)
  */
 static const bool enable_check_swd = false;
 
-/* Check whether the value of zbd_info.sectors_with_data is correct. */
-static void zbd_check_swd(const struct fio_file *f)
-{
-       if (!enable_check_swd)
-               return;
-
-       zbd_process_swd(f, CHECK_SWD);
-}
-
-static void zbd_init_swd(struct fio_file *f)
+/* 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)
 {
-       uint64_t swd;
-
        if (!enable_check_swd)
                return;
 
-       swd = zbd_process_swd(f, SET_SWD);
-       dprint(FD_ZBD, "%s(%s): swd = %" PRIu64 "\n", __func__, f->file_name,
-              swd);
+       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;
+       uint64_t swd;
 
        if (!f->zbd_info || !td_write(td))
                return;
 
        zb = get_zone(f, f->min_zone);
        ze = get_zone(f, f->max_zone);
-       zbd_init_swd(f);
+       swd = zbd_process_swd(td, f, SET_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
@@ -1031,7 +1046,8 @@ static uint32_t pick_random_zone_idx(const struct fio_file *f,
 /*
  * 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. This algorithm can only work correctly if all write pointers are
+ * necessary. The open 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.
  */
@@ -1077,7 +1093,8 @@ static struct fio_zone_info *zbd_convert_to_open_zone(struct thread_data *td,
 
                zone_lock(td, f, z);
                pthread_mutex_lock(&f->zbd_info->mutex);
-               if (td->o.max_open_zones == 0 && td->o.job_max_open_zones == 0)
+               if (z->cond != ZBD_ZONE_COND_OFFLINE &&
+                   td->o.max_open_zones == 0 && td->o.job_max_open_zones == 0)
                        goto examine_zone;
                if (f->zbd_info->num_open_zones == 0) {
                        dprint(FD_ZBD, "%s(%s): no zones are open\n",
@@ -1156,7 +1173,8 @@ open_other_zone:
        /* Zone 'z' is full, so try to open a new zone. */
        for (i = f->io_size / f->zbd_info->zone_size; i > 0; i--) {
                zone_idx++;
-               zone_unlock(z);
+               if (z->has_wp)
+                       zone_unlock(z);
                z++;
                if (!is_valid_offset(f, z->start)) {
                        /* Wrap-around. */
@@ -1164,6 +1182,8 @@ open_other_zone:
                        z = get_zone(f, zone_idx);
                }
                assert(is_valid_offset(f, z->start));
+               if (!z->has_wp)
+                       continue;
                zone_lock(td, f, z);
                if (z->open)
                        continue;
@@ -1199,6 +1219,8 @@ out:
        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;
 }
 
@@ -1224,12 +1246,12 @@ static struct fio_zone_info *zbd_replay_write_order(struct thread_data *td,
 }
 
 /*
- * Find another zone for which @io_u fits below the write pointer. Start
- * searching in zones @zb + 1 .. @zl and continue searching in zones
- * @zf .. @zb - 1.
+ * Find another zone for which @io_u fits in the readable data in the zone.
+ * Search in zones @zb + 1 .. @zl. For random workload, also search in zones
+ * @zb - 1 .. @zf.
  *
- * Either returns NULL or returns a zone pointer and holds the mutex for that
- * zone.
+ * Either returns NULL or returns a zone pointer. When the zone has write
+ * pointer, hold the mutex for the zone.
  */
 static struct fio_zone_info *
 zbd_find_zone(struct thread_data *td, struct io_u *io_u,
@@ -1246,19 +1268,23 @@ zbd_find_zone(struct thread_data *td, struct io_u *io_u,
         */
        for (z1 = zb + 1, z2 = zb - 1; z1 < zl || z2 >= zf; z1++, z2--) {
                if (z1 < zl && z1->cond != ZBD_ZONE_COND_OFFLINE) {
-                       zone_lock(td, f, z1);
+                       if (z1->has_wp)
+                               zone_lock(td, f, z1);
                        if (z1->start + min_bs <= z1->wp)
                                return z1;
-                       zone_unlock(z1);
+                       if (z1->has_wp)
+                               zone_unlock(z1);
                } else if (!td_random(td)) {
                        break;
                }
                if (td_random(td) && z2 >= zf &&
                    z2->cond != ZBD_ZONE_COND_OFFLINE) {
-                       zone_lock(td, f, z2);
+                       if (z2->has_wp)
+                               zone_lock(td, f, z2);
                        if (z2->start + min_bs <= z2->wp)
                                return z2;
-                       zone_unlock(z2);
+                       if (z2->has_wp)
+                               zone_unlock(z2);
                }
        }
        dprint(FD_ZBD, "%s: adjusting random read offset failed\n",
@@ -1313,8 +1339,7 @@ static void zbd_queue_io(struct thread_data *td, struct io_u *io_u, int q,
        assert(zone_idx < zbd_info->nr_zones);
        z = get_zone(f, zone_idx);
 
-       if (!zbd_zone_swr(z))
-               return;
+       assert(z->has_wp);
 
        if (!success)
                goto unlock;
@@ -1332,8 +1357,10 @@ 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.
                 */
-               if (z->wp <= zone_end)
+               if (z->wp <= zone_end) {
                        zbd_info->sectors_with_data += zone_end - z->wp;
+                       zbd_info->wp_sectors_with_data += zone_end - z->wp;
+               }
                pthread_mutex_unlock(&zbd_info->mutex);
                z->wp = zone_end;
                break;
@@ -1373,8 +1400,7 @@ static void zbd_put_io(struct thread_data *td, const struct io_u *io_u)
        assert(zone_idx < zbd_info->nr_zones);
        z = get_zone(f, zone_idx);
 
-       if (!zbd_zone_swr(z))
-               return;
+       assert(z->has_wp);
 
        dprint(FD_ZBD,
               "%s: terminate I/O (%lld, %llu) for zone %u\n",
@@ -1383,7 +1409,7 @@ 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(f);
+       zbd_check_swd(td, f);
 }
 
 /*
@@ -1537,9 +1563,31 @@ enum io_u_action zbd_adjust_block(struct thread_data *td, struct io_u *io_u)
        zb = get_zone(f, zone_idx_b);
        orig_zb = zb;
 
-       /* Accept the I/O offset for conventional zones. */
-       if (!zbd_zone_swr(zb))
+       if (!zb->has_wp) {
+               /* 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.
+                */
+               if (!(zb + 1)->has_wp ||
+                   io_u->offset + io_u->buflen <= (zb + 1)->start)
+                       return io_u_accept;
+
+               if (io_u->offset + min_bs > (zb + 1)->start) {
+                       dprint(FD_IO,
+                              "%s: off=%llu + min_bs=%u > next zone %lu\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);
+               } else {
+                       new_len = (zb + 1)->start - io_u->offset;
+               }
+               io_u->buflen = new_len / min_bs * min_bs;
                return io_u_accept;
+       }
 
        /*
         * Accept the I/O offset for reads if reading beyond the write pointer
@@ -1549,7 +1597,7 @@ 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(f);
+       zbd_check_swd(td, f);
 
        zone_lock(td, f, zb);
 
@@ -1557,7 +1605,12 @@ enum io_u_action zbd_adjust_block(struct thread_data *td, struct io_u *io_u)
        case DDIR_READ:
                if (td->runstate == TD_VERIFYING && td_write(td)) {
                        zb = zbd_replay_write_order(td, io_u, zb);
-                       zone_unlock(zb);
+                       /*
+                        * Since we return with the zone lock still held,
+                        * add an annotation to let Coverity know that it
+                        * is intentional.
+                        */
+                       /* coverity[missing_unlock] */
                        goto accept;
                }
                /*
@@ -1599,6 +1652,12 @@ 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.
                 */
@@ -1625,7 +1684,7 @@ 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 (f->zbd_info->sectors_with_data >=
+                       if (f->zbd_info->wp_sectors_with_data >=
                            f->io_size * td->o.zrt.u.f &&
                            zbd_dec_and_reset_write_cnt(td, f)) {
                                zb->reset_zone = 1;
@@ -1695,7 +1754,7 @@ enum io_u_action zbd_adjust_block(struct thread_data *td, struct io_u *io_u)
        assert(false);
 
 accept:
-       assert(zb);
+       assert(zb->has_wp);
        assert(zb->cond != ZBD_ZONE_COND_OFFLINE);
        assert(!io_u->zbd_queue_io);
        assert(!io_u->zbd_put_io);
@@ -1704,7 +1763,7 @@ accept:
        return io_u_accept;
 
 eof:
-       if (zb)
+       if (zb && zb->has_wp)
                zone_unlock(zb);
        return io_u_eof;
 }