]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
test/osd: Add balanced read flags to io_sequence exerciser
authorMatty Williams <Matty.Williams@ibm.com>
Mon, 20 Oct 2025 15:46:43 +0000 (16:46 +0100)
committerJon Bailey <jonathan.bailey1@ibm.com>
Thu, 28 May 2026 14:15:50 +0000 (15:15 +0100)
Added optional "-b"/"balanced" flag to the end of read/read2/read3 operations in interactive mode, to make them balanced reads.
Balanced read percentage is not used in interactive mode.

Add command line argument to specify percentage of read ops that should use the balanced reads flag. Default is 100%.

Signed-off-by: Matty Williams <Matty.Williams@ibm.com>
Signed-off-by: Alex Ainscow <aainscow@uk.ibm.com>
Signed-off-by: Jon Bailey <jonathan.bailey1@ibm.com>
src/common/io_exerciser/IoOp.cc
src/common/io_exerciser/IoOp.h
src/common/io_exerciser/RadosIo.cc
src/common/io_exerciser/RadosIo.h
src/test/osd/ceph_test_rados_io_sequence/ceph_test_rados_io_sequence.cc
src/test/osd/ceph_test_rados_io_sequence/ceph_test_rados_io_sequence.h

index 4bb6fb761eb07162bb041e97ad762cc09882ccd4..11c1b3ca4c3debd3e38502570c8d410aecda8e9d 100644 (file)
@@ -117,6 +117,15 @@ ceph::io_exerciser::ReadWriteOp<opType, numIOs>::ReadWriteOp(
   }
 }
 
+template <OpType opType, int numIOs>
+ceph::io_exerciser::ReadWriteOp<opType, numIOs>::ReadWriteOp(
+    std::array<uint64_t, numIOs>&& offset,
+    std::array<uint64_t, numIOs>&& length,
+    std::optional<bool> balanced_read)
+    : ReadWriteOp<opType, numIOs>(std::move(offset), std::move(length)) {
+      this->balanced_read = balanced_read;
+    }
+
 ConsistencyOp::ConsistencyOp() : TestOp<OpType::Consistency>() {}
 
 std::unique_ptr<ConsistencyOp> ConsistencyOp::generate() {
@@ -175,35 +184,57 @@ std::string ceph::io_exerciser::ReadWriteOp<opType, numIOs>::to_string(
   }
 }
 
-SingleReadOp::SingleReadOp(uint64_t offset, uint64_t length)
-    : ReadWriteOp<OpType::Read, 1>({offset}, {length}) {}
+SingleReadOp::SingleReadOp(uint64_t offset, uint64_t length, std::optional<bool> balanced_read)
+    : ReadWriteOp<OpType::Read, 1>({offset}, {length}, balanced_read) {}
 
 std::unique_ptr<SingleReadOp> SingleReadOp::generate(uint64_t offset,
                                                      uint64_t length) {
-  return std::make_unique<SingleReadOp>(offset, length);
+  return std::make_unique<SingleReadOp>(offset, length, std::nullopt);
+}
+
+std::unique_ptr<SingleReadOp> SingleReadOp::generate(uint64_t offset,
+                                  uint64_t length, bool balanced_read) {
+  return std::make_unique<SingleReadOp>(offset, length, balanced_read);
 }
 
 DoubleReadOp::DoubleReadOp(uint64_t offset1, uint64_t length1, uint64_t offset2,
-                           uint64_t length2)
-    : ReadWriteOp<OpType::Read2, 2>({offset1, offset2}, {length1, length2}) {}
+                           uint64_t length2, std::optional<bool> balanced_read)
+    : ReadWriteOp<OpType::Read2, 2>({offset1, offset2}, {length1, length2}, balanced_read) {}
 
 std::unique_ptr<DoubleReadOp> DoubleReadOp::generate(uint64_t offset1,
                                                      uint64_t length1,
                                                      uint64_t offset2,
                                                      uint64_t length2) {
-  return std::make_unique<DoubleReadOp>(offset1, length1, offset2, length2);
+  return std::make_unique<DoubleReadOp>(offset1, length1, offset2, length2, std::nullopt);
+}
+
+std::unique_ptr<DoubleReadOp> DoubleReadOp::generate(uint64_t offset1,
+                                                     uint64_t length1,
+                                                     uint64_t offset2,
+                                                     uint64_t length2,
+                                                     bool balanced_read) {
+  return std::make_unique<DoubleReadOp>(offset1, length1, offset2, length2, balanced_read);
 }
 
 TripleReadOp::TripleReadOp(uint64_t offset1, uint64_t length1, uint64_t offset2,
-                           uint64_t length2, uint64_t offset3, uint64_t length3)
+                           uint64_t length2, uint64_t offset3, uint64_t length3,
+                           std::optional<bool> balanced_read)
     : ReadWriteOp<OpType::Read3, 3>({offset1, offset2, offset3},
-                                    {length1, length2, length3}) {}
+                                    {length1, length2, length3},
+                                    balanced_read) {}
 
 std::unique_ptr<TripleReadOp> TripleReadOp::generate(
     uint64_t offset1, uint64_t length1, uint64_t offset2, uint64_t length2,
     uint64_t offset3, uint64_t length3) {
   return std::make_unique<TripleReadOp>(offset1, length1, offset2, length2,
-                                        offset3, length3);
+                                        offset3, length3, std::nullopt);
+}
+
+std::unique_ptr<TripleReadOp> TripleReadOp::generate(
+    uint64_t offset1, uint64_t length1, uint64_t offset2, uint64_t length2,
+    uint64_t offset3, uint64_t length3, bool balanced_read) {
+  return std::make_unique<TripleReadOp>(offset1, length1, offset2, length2,
+                                        offset3, length3, balanced_read);
 }
 
 SingleWriteOp::SingleWriteOp(uint64_t offset, uint64_t length)
index 88165db8180b5d65b1e713e32125a750b956daeb..603cbcb7e2ddd7c8ee8c8b8124949df303bd57ac 100644 (file)
@@ -87,37 +87,52 @@ class ReadWriteOp : public TestOp<opType> {
  public:
   std::array<uint64_t, numIOs> offset;
   std::array<uint64_t, numIOs> length;
+  std::optional<bool> balanced_read = std::nullopt;
 
  protected:
   ReadWriteOp(std::array<uint64_t, numIOs>&& offset,
               std::array<uint64_t, numIOs>&& length);
+  ReadWriteOp(std::array<uint64_t, numIOs>&& offset,
+              std::array<uint64_t, numIOs>&& length,
+              std::optional<bool> balanced_read);
   std::string to_string(uint64_t block_size) const override;
 };
 
 class SingleReadOp : public ReadWriteOp<OpType::Read, 1> {
  public:
-  SingleReadOp(uint64_t offset, uint64_t length);
+  SingleReadOp(uint64_t offset, uint64_t length, std::optional<bool> balanced_read);
   static std::unique_ptr<SingleReadOp> generate(uint64_t offset,
                                                 uint64_t length);
+  static std::unique_ptr<SingleReadOp> generate(uint64_t offset,
+                                uint64_t length, bool balanced_read);
 };
 
 class DoubleReadOp : public ReadWriteOp<OpType::Read2, 2> {
  public:
   DoubleReadOp(uint64_t offset1, uint64_t length1, uint64_t offset2,
-               uint64_t length2);
+               uint64_t length2, std::optional<bool> balanced_read);
   static std::unique_ptr<DoubleReadOp> generate(uint64_t offset1,
                                                 uint64_t length1,
                                                 uint64_t offset2,
                                                 uint64_t length2);
+  static std::unique_ptr<DoubleReadOp> generate(uint64_t offset1,
+                                                uint64_t length1,
+                                                uint64_t offset2,
+                                                uint64_t length2,
+                                                bool balanced_read);
 };
 
 class TripleReadOp : public ReadWriteOp<OpType::Read3, 3> {
  public:
   TripleReadOp(uint64_t offset1, uint64_t length1, uint64_t offset2,
-               uint64_t length2, uint64_t offset3, uint64_t length3);
+               uint64_t length2, uint64_t offset3, uint64_t length3,
+               std::optional<bool> balanced_read);
   static std::unique_ptr<TripleReadOp> generate(
       uint64_t offset1, uint64_t length1, uint64_t offset2, uint64_t length2,
       uint64_t offset3, uint64_t length3);
+  static std::unique_ptr<TripleReadOp> generate(
+      uint64_t offset1, uint64_t length1, uint64_t offset2, uint64_t length2,
+      uint64_t offset3, uint64_t length3, bool balanced_read);
 };
 
 class SingleWriteOp : public ReadWriteOp<OpType::Write, 1> {
index 6881a4ccc3c3495236056acecf42fbd47eb03974..f98348ce3e8b7d7a5ad1cc5b5cb4d8cbb9136885 100644 (file)
@@ -49,7 +49,8 @@ RadosIo::RadosIo(librados::Rados& rados, boost::asio::io_context& asio,
                  const std::string& pool, const std::string& primary_oid, const std::string& secondary_oid,
                  uint64_t block_size, int seed, int threads, ceph::mutex& lock,
                  ceph::condition_variable& cond, bool is_replicated_pool,
-                 bool ec_optimizations, GenerationType data_generation_type,
+                 bool ec_optimizations, int balanced_read_percentage,
+                 GenerationType data_generation_type,
                  std::shared_ptr<ceph::io_exerciser::IoSequence> seq, bool delete_objects)
     : Model(primary_oid, secondary_oid, block_size, delete_objects),
       rados(rados),
@@ -62,7 +63,9 @@ RadosIo::RadosIo(librados::Rados& rados, boost::asio::io_context& asio,
       lock(lock),
       cond(cond),
       outstanding_io(0),
-      seq(seq) {
+      seq(seq),
+      rng(seed),
+      balanced_read_percentage(balanced_read_percentage) {
   int rc;
   rc = rados.ioctx_create(pool.c_str(), io);
   ceph_assert(rc == 0);
@@ -271,8 +274,25 @@ void RadosIo::applyReadWriteOp(IoOp& op) {
       }
       finish_io();
     };
+
+    int flags = 0;
+    if (readOp.balanced_read.has_value()) {
+      if (*readOp.balanced_read) {
+        flags = librados::OPERATION_BALANCE_READS;
+      }
+      // Else: keep flags == 0
+    } else {
+      ceph_assert(balanced_read_percentage >= 0);
+      ceph_assert(balanced_read_percentage <= 100);
+      uint64_t range = 100;
+      uint64_t rand_value = rng();
+      int index = rand_value % range;
+      if (index <= balanced_read_percentage) {
+        flags = librados::OPERATION_BALANCE_READS;
+      }
+    }
     librados::async_operate(asio.get_executor(), io, primary_oid,
-                            std::move(rop), 0, nullptr, read_cb);
+                            std::move(rop), flags, nullptr, read_cb);
     num_io++;
   };
 
index 5ed082b593dd12d439a2236d4de18a31eaf63d7b..d79847dfa86b9ada754cff5ba705eab2f5041656 100644 (file)
@@ -6,6 +6,8 @@
 #include "librados/AioCompletionImpl.h"
 #include "common/ceph_mutex.h"
 
+#include <random>
+
 namespace boost::asio { class io_context; }
 
 /* Overview
@@ -40,6 +42,8 @@ class RadosIo : public Model {
   librados::IoCtx io;
   int outstanding_io;
   std::shared_ptr<ceph::io_exerciser::IoSequence> seq;
+  std::mt19937_64 rng;
+  int balanced_read_percentage;
 
   void start_io();
   void finish_io();
@@ -50,7 +54,8 @@ class RadosIo : public Model {
           const std::string& pool, const std::string& primary_oid, const std::string& secondary_oid,
           uint64_t block_size, int seed, int threads, ceph::mutex& lock,
           ceph::condition_variable& cond, bool is_replicated_pool,
-          bool ec_optimizations, ceph::io_exerciser::data_generation::GenerationType data_generation_type,
+          bool ec_optimizations, int balanced_read_percentage,
+          ceph::io_exerciser::data_generation::GenerationType data_generation_type,
           std::shared_ptr<ceph::io_exerciser::IoSequence> seq = nullptr, bool delete_objects = true);
 
   ~RadosIo();
index d7540919f3f70d4a51036bb6844a1c868a5cfdc7..0297312c65625f4703180b6c7abca90544add087 100644 (file)
@@ -166,9 +166,12 @@ constexpr std::string_view usage[] = {
     "\t\t remove",
     "\t\t swap",
     "\t\t copy",
-    "\t\t read|write|failedwrite <off> <len>",
-    "\t\t read2|write2|failedwrite2 <off> <len> <off> <len>",
-    "\t\t read3|write3|failedwrite3 <off> <len> <off> <len> <off> <len>",
+    "\t\t read <off> <len> [balanced]",
+    "\t\t write|failedwrite <off> <len>",
+    "\t\t read2 <off> <len> <off> <len> [balanced]",
+    "\t\t write2|failedwrite2 <off> <len> <off> <len>",
+    "\t\t read3 <off> <len> <off> <len> <off> <len> [balanced]",
+    "\t\t write3|failedwrite3 <off> <len> <off> <len> <off> <len>",
     "\t\t append",
     "\t\t truncate",
     "\t\t injecterror <io_type> <shard> <type> <good_count> <fail_count>",
@@ -225,6 +228,9 @@ po::options_description get_options_description() {
       "dont_delete_objects",
       "Stops the IO exerciser from deleting the object it was running the test "
       "against once the test finishes. Does not affect interactive mode")(
+      "balanced_read_percentage", po::value<int>(),
+      "The percentage of read operations that should be performed with "
+      "balanced reads enabled. 100 by default. Doesn't affect interactive mode.")(
       "data_generation_type", po::value<std::string>(),
       "Data generation type to use for write IOs. Default is HeaderedSeededRandom");
 
@@ -1025,6 +1031,8 @@ void ceph::io_sequence::tester::SelectErasurePool::configureServices(
                                     "the specified plugin type may not "
                                     "support them");
       }
+
+      ceph_assert(rc == 0);
     }
 
     if (allow_pool_ec_overwrites) {
@@ -1060,10 +1068,10 @@ ceph::io_sequence::tester::TestObject::TestObject(
     SelectObjectSize& sos, SelectNumThreads& snt, SelectSeqRange& ssr,
     std::mt19937_64& rng, ceph::mutex& lock,
     ceph::condition_variable& cond, bool dryrun, bool verbose,
-    std::optional<int> seqseed, bool testrecovery, bool checkconsistency,
-    bool delete_objects, GenerationType data_generation_type)
-    : rng(rng), verbose(verbose), seqseed(seqseed), testrecovery(testrecovery),
-      checkconsistency(checkconsistency), delete_objects(delete_objects),
+    std::optional<int> seqseed, bool testrecovery, bool checkconsistency, bool delete_objects,
+    int balanced_read_percentage, GenerationType data_generation_type)
+    : rng(rng), verbose(verbose), seqseed(seqseed), testrecovery(testrecovery), checkconsistency(checkconsistency),
+      delete_objects(delete_objects), balanced_read_percentage(balanced_read_percentage),
       data_generation_type(data_generation_type) {
   if (dryrun) {
     int model_seed = rng();
@@ -1090,7 +1098,8 @@ ceph::io_sequence::tester::TestObject::TestObject(
     exerciser_model = std::make_unique<ceph::io_exerciser::RadosIo>(
         rados, asio, pool, primary_oid, secondary_oid, sbs.select(), model_seed,
         threads, lock, cond, spo.is_replicated_pool(),
-        spo.get_allow_pool_ec_optimizations(), data_generation_type, seq, delete_objects);
+        spo.get_allow_pool_ec_optimizations(), balanced_read_percentage,
+        data_generation_type, seq, delete_objects);
     dout(0) << "= " << primary_oid << " pool=" << pool << " threads=" << threads
             << " blocksize=" << exerciser_model->get_block_size() << " ="
             << dendl;
@@ -1211,6 +1220,21 @@ ceph::io_sequence::tester::TestRunner::TestRunner(
   allow_pool_deep_scrubbing = vm.contains("allow_pool_deep_scrubbing");
   allow_pool_scrubbing = vm.contains("allow_pool_scrubbing");
 
+  if (vm.contains("balanced_read_percentage")) {
+    balanced_read_percentage = vm["balanced_read_percentage"].as<int>();
+    if (balanced_read_percentage > 100) {
+      balanced_read_percentage = 100;
+    } else if (balanced_read_percentage < 0) {
+      balanced_read_percentage = 0;
+    }
+    if (interactive) {
+      dout(0) << "Balanced read percentage cannot be specified in interactive mode. "
+              << "Use the \"-b\" or \"balanced\" flag to make a balanced read operation in the interactive terminal." << dendl;
+    }
+  } else {
+    balanced_read_percentage = 100;
+  }
+
   if (testrecovery && (num_object_pairs > 1)) {
     throw std::invalid_argument("testrecovery option not allowed if parallel is"
                                 " specified, except when parallel=1 is used");
@@ -1362,7 +1386,7 @@ bool ceph::io_sequence::tester::TestRunner::run_interactive_test() {
         rados, asio, pool, primary_object_name, secondary_object_name, sbs.select(), model_seed,
         1,  // 1 thread
         lock, cond, spo.is_replicated_pool(),
-        spo.get_allow_pool_ec_optimizations(),
+        spo.get_allow_pool_ec_optimizations(), balanced_read_percentage,
         data_generation_type);
   }
 
@@ -1385,13 +1409,23 @@ bool ceph::io_sequence::tester::TestRunner::run_interactive_test() {
     } else if (op == "read") {
       uint64_t offset = get_numeric_token();
       uint64_t length = get_numeric_token();
-      ioop = ceph::io_exerciser::SingleReadOp::generate(offset, length);
+      std::optional<std::string> token = get_optional_token();
+      if (token.has_value() && (*token == "-b" || *token == "balanced")) {
+        ioop = ceph::io_exerciser::SingleReadOp::generate(offset, length, true);
+      } else {
+        ioop = ceph::io_exerciser::SingleReadOp::generate(offset, length, false);
+      }
     } else if (op == "read2") {
       uint64_t offset1 = get_numeric_token();
       uint64_t length1 = get_numeric_token();
       uint64_t offset2 = get_numeric_token();
       uint64_t length2 = get_numeric_token();
-      ioop = DoubleReadOp::generate(offset1, length1, offset2, length2);
+      std::optional<std::string> token = get_optional_token();
+      if (token.has_value() && (*token == "-b" || *token == "balanced")) {
+        ioop = DoubleReadOp::generate(offset1, length1, offset2, length2, true);
+      } else {
+        ioop = DoubleReadOp::generate(offset1, length1, offset2, length2, false);
+      }
     } else if (op == "read3") {
       uint64_t offset1 = get_numeric_token();
       uint64_t length1 = get_numeric_token();
@@ -1399,8 +1433,14 @@ bool ceph::io_sequence::tester::TestRunner::run_interactive_test() {
       uint64_t length2 = get_numeric_token();
       uint64_t offset3 = get_numeric_token();
       uint64_t length3 = get_numeric_token();
-      ioop = TripleReadOp::generate(offset1, length1, offset2, length2, offset3,
-                                    length3);
+      std::optional<std::string> token = get_optional_token();
+      if (token.has_value() && (*token == "-b" || *token == "balanced")) {
+        ioop = TripleReadOp::generate(offset1, length1, offset2, length2, offset3,
+                                    length3, true);
+      } else {
+        ioop = TripleReadOp::generate(offset1, length1, offset2, length2, offset3,
+                                    length3, false);
+      }
     } else if (op == "write") {
       uint64_t offset = get_numeric_token();
       uint64_t length = get_numeric_token();
@@ -1519,8 +1559,8 @@ bool ceph::io_sequence::tester::TestRunner::run_automated_test() {
       test_objects.push_back(
           std::make_shared<ceph::io_sequence::tester::TestObject>(
               primary_name, secondary_name, rados, asio, sbs, spo, sos, snt, ssr, rng, lock, cond,
-              dryrun, verbose, seqseed, testrecovery, checkconsistency,
-              delete_objects, data_generation_type));
+              dryrun, verbose, seqseed, testrecovery, checkconsistency, delete_objects,
+              balanced_read_percentage, data_generation_type));
     }
     catch (const std::runtime_error &e) {
       std::cerr << "Error: " << e.what() << std::endl;
index e592d56fc1abddb5ce5dfaa0c523a4a4a5914eda..f4adf0a9174dd1384c61d98b37272cb4892c30cb 100644 (file)
@@ -466,6 +466,7 @@ class TestObject {
              bool testRecovery,
              bool checkConsistency,
              bool delete_objects,
+             int balanced_read_percentage,
              GenerationType data_generation_type);
 
   int get_num_io();
@@ -491,6 +492,7 @@ class TestObject {
   bool testrecovery;
   bool checkconsistency;
   bool delete_objects;
+  int balanced_read_percentage;
   GenerationType data_generation_type;
 };
 
@@ -551,6 +553,8 @@ class TestRunner {
   ceph::split split = ceph::split("");
   ceph::spliterator tokens;
 
+  int balanced_read_percentage;
+
   void clear_tokens();
   std::string get_token(bool allow_eof = false);
   std::optional<std::string> get_optional_token();