]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
test/osd: Add interactive mode to ceph_test_rados_io_sequence
authorJonBailey1993 <jonathan.bailey1@ibm.com>
Mon, 23 Sep 2024 10:23:18 +0000 (11:23 +0100)
committerJonBailey1993 <jonathan.bailey1@ibm.com>
Tue, 15 Oct 2024 16:02:29 +0000 (17:02 +0100)
Reorganisers the main function to be more modular, moving functionality into a TestRunner object and anonymous namespaces.
Adds a new "interactive" test mode that allows you to direct the order of operations rather than using the preset sequences. This allows complete precise control of the tool when you want to test specific IOs.

Signed-off-by: Jon Bailey <jonathan.bailey1@ibm.com>
src/test/osd/ceph_test_rados_io_sequence.cc
src/test/osd/ceph_test_rados_io_sequence.h

index 4ac4a3eca59f622e2f474d984bb320813afbfd5a..5e340c5c9c5bd771ae93748fd151c621e833446b 100644 (file)
 #define dout_subsys ceph_subsys_rados
 #define dout_context g_ceph_context
 
+namespace {
+  struct Size {};
+  void validate(boost::any& v, const std::vector<std::string>& values,
+                Size *target_type, int) {
+    po::validators::check_first_occurrence(v);
+    const std::string &s = po::validators::get_single_string(values);
+
+    std::string parse_error;
+    uint64_t size = strict_iecstrtoll(s, &parse_error);
+    if (!parse_error.empty()) {
+      throw po::validation_error(po::validation_error::invalid_option_value);
+    }
+    v = boost::any(size);
+  }
+
+  struct Pair {};
+  void validate(boost::any& v, const std::vector<std::string>& values,
+                Pair *target_type, int) {
+    po::validators::check_first_occurrence(v);
+    const std::string &s = po::validators::get_single_string(values);
+    auto part = ceph::split(s).begin();
+    std::string parse_error;
+    int first = strict_iecstrtoll(*part++, &parse_error);
+    int second = strict_iecstrtoll(*part, &parse_error);
+    if (!parse_error.empty()) {
+      throw po::validation_error(po::validation_error::invalid_option_value);
+    }
+    v = boost::any(std::pair<int,int>{first,second});
+  }
+
+  struct PluginString {};
+  void validate(boost::any& v, const std::vector<std::string>& values,
+                PluginString *target_type, int) {
+    po::validators::check_first_occurrence(v);
+    const std::string &s = po::validators::get_single_string(values);
+
+    const std::string_view* pluginIt = std::find(
+          ceph::io_sequence::tester::pluginChoices.begin(),
+          ceph::io_sequence::tester::pluginChoices.end(), 
+          s
+    );
+    if(ceph::io_sequence::tester::pluginChoices.end() == pluginIt)
+    {
+      throw po::validation_error(po::validation_error::invalid_option_value);
+    }
+
+    v = boost::any(*pluginIt);
+  }
+
+  constexpr std::string_view usage[] = {
+    "Basic usage:",
+    "",
+    "ceph_test_rados_io_sequence",
+    "\t Test I/O to a single object using default settings. Good for",
+    "\t testing boundary conditions",
+    "",
+    "ceph_test_rados_io_sequence --parallel <n>",
+    "\t Run parallel test to multiple objects. First object is tested with",
+    "\t default settings, other objects are tested with random settings",
+    "",
+    "Advanced usage:",
+    "",
+    "ceph_test_rados_io_sequence --blocksize <b> --km <k,m> --plugin <p>",
+    "                            --objectsize <min,max> --threads <t>",
+    "ceph_test_rados_io_sequence --blocksize <b> --pool <p> --object <oid>",
+    "                            --objectsize <min,max> --threads <t>",
+    "\tCustomize the test, if a pool is specified then it defines the",
+    "\tReplica/EC configuration",
+    "",
+    "ceph_test_rados_io_sequence --listsequence",
+    "\t Display list of supported I/O sequences",
+    "",
+    "ceph_test_rados_io_sequence --dryrun --sequence <n>",
+    "\t Show I/O that will be generated for a sequence, validate",
+    "\t seqeunce has correct I/O barriers to restrict concurrency",
+    "",
+    "ceph_test_rados_io_sequence --seed <seed>",
+    "\t Repeat a previous test with the same random numbers (seed is",
+    "\t displayed at start of test), if threads = 1 then this will produce",
+    "\t the exact same sequence of I/O, if threads > 1 then I/Os are issued",
+    "\t in parallel so ordering might be slightly different",
+    "",
+    "ceph_test_rados_io_sequence --sequence <n> --seqseed <n>",
+    "\t Repeat a sequence from a previous test with the same random",
+    "\t numbers (seqseed is displayed at start of sequence)",
+    "",
+    "ceph_test_rados_io_sequence --pool <p> --object <oid> --interactive",
+    "\t Execute sequence of I/O commands from stdin. Offset and length",
+    "\t are specified with unit of blocksize. Supported commands:",
+    "\t\t create <len>",
+    "\t\t remove",
+    "\t\t read|write <off> <len>",
+    "\t\t read2|write2 <off> <len> <off> <len>",
+    "\t\t read3|write3 <off> <len> <off> <len> <off> <len>",
+    "\t\t done"
+  };
+
+  po::options_description get_options_description()
+  {
+    po::options_description desc("ceph_test_rados_io options");
+    desc.add_options()
+      ("help,h",
+        "show help message")
+      ("listsequence,l",
+        "show list of sequences")
+      ("dryrun,d",
+        "test sequence, do not issue any I/O")
+      ("verbose",
+        "more verbose output during test")
+      ("sequence,s", po::value<int>(),
+        "test specified sequence")
+      ("seed", po::value<int>(),
+        "seed for whole test")
+      ("seqseed", po::value<int>(),
+        "seed for sequence")
+      ("blocksize,b", po::value<Size>(),
+        "block size (default 2048)")
+      ("chunksize,c", po::value<Size>(),
+        "chunk size (default 4096)")
+      ("pool,p", po::value<std::string>(),
+        "pool name")
+      ("object,o", po::value<std::string>()->default_value("test"),
+        "object name")
+      ("km", po::value<Pair>(),
+        "k,m EC pool profile (default 2,2)")
+      ("plugin", po::value<PluginString>(),
+        "EC plugin (isa or jerasure)")
+      ("objectsize", po::value<Pair>(),
+        "min,max object size in blocks (default 1,32)")
+      ("threads,t", po::value<int>(),
+        "number of threads of I/O per object (default 1)")
+      ("parallel,p", po::value<int>()->default_value(1),
+        "number of objects to exercise in parallel")
+      ("interactive",
+        "interactive mode, execute IO commands from stdin");
+
+    return desc;
+  }
+
+  int parse_io_seq_options(
+      po::variables_map& vm,
+      int argc,
+      char** argv)
+  {
+    std::vector<std::string> unrecognized_options;
+    try {
+      po::options_description desc = get_options_description();
+
+      auto parsed = po::command_line_parser(argc, argv)
+        .options(desc)
+        .allow_unregistered()
+        .run();
+      po::store(parsed, vm);
+      po::notify(vm);
+      unrecognized_options = po::collect_unrecognized(parsed.options,
+                                                      po::include_positional);
+
+      if (!unrecognized_options.empty())
+      {
+        std::stringstream ss;
+        ss << "Unrecognised command options supplied: ";
+        while (unrecognized_options.size() > 1)
+        {
+          ss << unrecognized_options.back().c_str() << ", ";
+          unrecognized_options.pop_back();
+        }
+        ss << unrecognized_options.back();
+        dout(0) << ss.str() << dendl;
+        return 1;
+      }
+    } catch(const po::error& e) {
+      std::cerr << "error: " << e.what() << std::endl;
+      return 1;
+    }
+
+    return 0;
+  }
+}
+
 template <typename T, int N, const std::array<T, N>& Ts>
 ceph::io_sequence::tester::ProgramOptionSelector<T, N, Ts>
   ::ProgramOptionSelector(ceph::util::random_number_generator<int>& rng,
@@ -35,7 +214,6 @@ ceph::io_sequence::tester::ProgramOptionSelector<T, N, Ts>
                           bool set_forced,
                           bool select_first)
   : rng(rng),
-    // choices(choices),
     option_name(option_name) {
   if (set_forced && vm.count(option_name)) {
     force_value = vm[option_name].as<T>();
@@ -180,7 +358,7 @@ const std::string ceph::io_sequence::tester::SelectECPool::choose()
   }
   int k = value.first;
   int m = value.second;
-  
+
   const std::string plugin = std::string(spl.choose());
   const uint64_t chunk_size = scs.choose();
 
@@ -240,9 +418,8 @@ ceph::io_sequence::tester::TestObject::TestObject( const std::string oid,
                         ceph::condition_variable& cond,
                         bool dryrun,
                         bool verbose,
-                        bool has_seqseed,
-                        int  seqseed) :
-  rng(rng), verbose(verbose), has_seqseed(has_seqseed), seqseed(seqseed)
+                        std::optional<int>  seqseed) :
+  rng(rng), verbose(verbose), seqseed(seqseed)
 {
   if (dryrun) {
     verbose = true;
@@ -271,9 +448,7 @@ ceph::io_sequence::tester::TestObject::TestObject( const std::string oid,
   curseq = seq_range.first;
   seq = ceph::io_exerciser::IoSequence::generate_sequence(curseq,
                                                           obj_size_range,
-                                                          has_seqseed ?
-                                                            seqseed :
-                                                            rng());
+                                                          seqseed.value_or(rng()));
   op = seq->next();
   done = false;
   dout(0) << "== " << exerciser_model->get_oid() << " "
@@ -310,9 +485,7 @@ bool ceph::io_sequence::tester::TestObject::next()
       } else {
         seq = ceph::io_exerciser::IoSequence::generate_sequence(curseq,
                                                                 obj_size_range,
-                                                                has_seqseed ?
-                                                                  seqseed :
-                                                                  rng());
+                                                                seqseed.value_or(rng()));
         dout(0) << "== " << exerciser_model->get_oid() << " "
                 << curseq << " " << seq->get_name()
                 << " ==" <<dendl;
@@ -335,97 +508,232 @@ int ceph::io_sequence::tester::TestObject::get_num_io()
   return exerciser_model->get_num_io();
 }
 
-struct Size {};
-void validate(boost::any& v, const std::vector<std::string>& values,
-              Size *target_type, int) {
-  po::validators::check_first_occurrence(v);
-  const std::string &s = po::validators::get_single_string(values);
+ceph::io_sequence::tester::TestRunner::TestRunner(po::variables_map& vm,
+                                                  librados::Rados& rados) :
+  rados(rados),
+  seed(vm.contains("seed") ? vm["seed"].as<int>() : time(nullptr)),
+  rng(ceph::util::random_number_generator<int>(seed)),
+  sbs{rng, vm},
+  sos{rng, vm},
+  spo{rng, vm, rados, vm.contains("dryrun")},
+  snt{rng, vm},
+  ssr{rng, vm}
+{
+  dout(0) << "Test using seed " << seed << dendl;
 
-  std::string parse_error;
-  uint64_t size = strict_iecstrtoll(s, &parse_error);
-  if (!parse_error.empty()) {
-    throw po::validation_error(po::validation_error::invalid_option_value);
+  verbose = vm.contains("verbose");
+  dryrun = vm.contains("dryrun");
+
+  seqseed = std::nullopt;
+  if (vm.contains("seqseed")) {
+    seqseed = vm["seqseed"].as<int>();
+  }
+  num_objects = vm["parallel"].as<int>();
+  object_name = vm["object"].as<std::string>();
+  interactive = vm.contains("interactive");
+
+  if (!dryrun)
+  {
+    guard.emplace(boost::asio::make_work_guard(asio));
+    thread = make_named_thread("io_thread",[&asio = asio] { asio.run(); });
+  }
+
+  show_help = vm.contains("help");
+  show_sequence = vm.contains("listsequence");
+}
+
+ceph::io_sequence::tester::TestRunner::~TestRunner()
+{
+  if (!dryrun) {
+    guard = std::nullopt;
+    asio.stop();
+    thread.join();
+    rados.shutdown();
+  }
+}
+
+void ceph::io_sequence::tester::TestRunner::help()
+{
+  std::cout << get_options_description() << std::endl;
+  for (auto line : usage) {
+    std::cout << line << std::endl;
+  }
+}
+
+void ceph::io_sequence::tester::TestRunner::list_sequence()
+{
+  // List seqeunces
+  std::pair<int,int> obj_size_range = sos.choose();
+  for (ceph::io_exerciser::Sequence s
+        = ceph::io_exerciser::Sequence::SEQUENCE_BEGIN;
+        s < ceph::io_exerciser::Sequence::SEQUENCE_END; ++s) {
+    std::unique_ptr<ceph::io_exerciser::IoSequence> seq =
+    ceph::io_exerciser::IoSequence::generate_sequence(s,
+                                                      obj_size_range,
+                                                      seqseed.value_or(rng()));
+    dout(0) << s << " " << seq->get_name() << dendl;
   }
-  v = boost::any(size);
 }
 
-struct Pair {};
-void validate(boost::any& v, const std::vector<std::string>& values,
-              Pair *target_type, int) {
-  po::validators::check_first_occurrence(v);
-  const std::string &s = po::validators::get_single_string(values);
-  auto part = ceph::split(s).begin();
+std::string ceph::io_sequence::tester::TestRunner::get_token()
+{
+  static std::string line;
+  static ceph::split split = ceph::split("");
+  static ceph::spliterator tokens;
+  while (line.empty() || tokens == split.end()) {
+    if (!std::getline(std::cin, line)) {
+      throw std::runtime_error("End of input");
+    }
+    split = ceph::split(line);
+    tokens = split.begin();
+  }
+  return std::string(*tokens++);
+}
+
+uint64_t ceph::io_sequence::tester::TestRunner::get_numeric_token()
+{
   std::string parse_error;
-  int first = strict_iecstrtoll(*part++, &parse_error);
-  int second = strict_iecstrtoll(*part, &parse_error);
+  std::string token = get_token();
+  uint64_t num = strict_iecstrtoll(token, &parse_error);
   if (!parse_error.empty()) {
-    throw po::validation_error(po::validation_error::invalid_option_value);
+    throw std::runtime_error("Invalid number "+token);
   }
-  v = boost::any(std::pair<int,int>{first,second});
+  return num;
 }
 
-struct PluginString {};
-void validate(boost::any& v, const std::vector<std::string>& values,
-              PluginString *target_type, int) {
-  po::validators::check_first_occurrence(v);
-  const std::string &s = po::validators::get_single_string(values);
-
-  const std::string_view* pluginIt = std::find(
-        ceph::io_sequence::tester::pluginChoices.begin(),
-        ceph::io_sequence::tester::pluginChoices.end(), 
-        s
-  );
-  if(ceph::io_sequence::tester::pluginChoices.end() == pluginIt)
+bool ceph::io_sequence::tester::TestRunner::run_test()
+{
+  if (show_help)
   {
-    throw po::validation_error(po::validation_error::invalid_option_value);
+    help();
+    return true;
+  }
+  else if (show_sequence)
+  {
+    list_sequence();
+    return true;
+  }
+  else if (interactive)
+  {
+    return run_interactive_test();
+  }
+  else
+  {
+    return run_automated_test();
   }
-
-  v = boost::any(*pluginIt);
 }
 
-int parse_io_seq_options(
-    po::variables_map& vm,
-    const po::options_description& desc,
-    int argc,
-    char** argv)
-{  
-  std::vector<std::string> unrecognized_options;
-  try {
-    auto parsed = po::command_line_parser(argc, argv)
-      .options(desc)
-      .allow_unregistered()
-      .run();
-    po::store(parsed, vm);
-    po::notify(vm);
-    unrecognized_options = po::collect_unrecognized(parsed.options,
-                                                   po::include_positional);
-
-    if (!unrecognized_options.empty())
-    {
-      std::stringstream ss;
-      ss << "Unrecognised command options supplied: ";
-      while (unrecognized_options.size() > 1)
-      {
-        ss << unrecognized_options.back().c_str() << ", ";
-        unrecognized_options.pop_back();
-      }
-      ss << unrecognized_options.back();
-      dout(0) << ss.str() << dendl;
-      return 1;
+bool ceph::io_sequence::tester::TestRunner::run_interactive_test()
+{
+  bool done = false;
+  std::unique_ptr<ceph::io_exerciser::IoOp> ioop;
+  std::unique_ptr<ceph::io_exerciser::Model> model;
+
+  if (dryrun) {
+    model = std::make_unique<ceph::io_exerciser::ObjectModel>(object_name,
+                                                             sbs.choose(),
+                                                             rng());
+  } else {
+    const std::string pool = spo.choose();
+    model = std::make_unique<ceph::io_exerciser::RadosIo>(rados, asio, pool,
+                                                          object_name, sbs.choose(),
+                                                          rng(), 1, // 1 thread
+                                                          lock, cond);
+  }
+
+  while (!done) {
+    const std::string op = get_token();
+    if (!op.compare("done")  || !op.compare("q") || !op.compare("quit")) {
+      ioop = ceph::io_exerciser::IoOp::generate_done();
+    } else if (!op.compare("create")) {
+      ioop = ceph::io_exerciser::IoOp::generate_create(get_numeric_token());
+    } else if (!op.compare("remove") || !op.compare("delete")) {
+      ioop = ceph::io_exerciser::IoOp::generate_remove();
+    } else if (!op.compare("read")) {
+      uint64_t offset = get_numeric_token();
+      uint64_t length = get_numeric_token();
+      ioop = ceph::io_exerciser::IoOp::generate_read(offset, length);
+    } else if (!op.compare("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 = ceph::io_exerciser::IoOp::generate_read2(offset1, length1,
+                                                      offset2, length2);
+    } else if (!op.compare("read3")) {
+      uint64_t offset1 = get_numeric_token();
+      uint64_t length1 = get_numeric_token();
+      uint64_t offset2 = get_numeric_token();
+      uint64_t length2 = get_numeric_token();
+      uint64_t offset3 = get_numeric_token();
+      uint64_t length3 = get_numeric_token();
+      ioop = ceph::io_exerciser::IoOp::generate_read3(offset1, length1,
+                                                      offset2, length2,
+                                                     offset3, length3);
+    } else if (!op.compare("write")) {
+      uint64_t offset = get_numeric_token();
+      uint64_t length = get_numeric_token();
+      ioop = ceph::io_exerciser::IoOp::generate_write(offset, length);
+    } else if (!op.compare("write2")) {
+      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 = ceph::io_exerciser::IoOp::generate_write2(offset1, length1,
+                                                       offset2, length2);
+    } else if (!op.compare("write3")) {
+      uint64_t offset1 = get_numeric_token();
+      uint64_t length1 = get_numeric_token();
+      uint64_t offset2 = get_numeric_token();
+      uint64_t length2 = get_numeric_token();
+      uint64_t offset3 = get_numeric_token();
+      uint64_t length3 = get_numeric_token();
+      ioop = ceph::io_exerciser::IoOp::generate_write3(offset1, length1,
+                                                       offset2, length2,
+                                                      offset3, length3);
+    } else {
+      throw std::runtime_error("Invalid operation "+op);
+    }
+    dout(0) << ioop->to_string(model->get_block_size()) << dendl;
+    model->applyIoOp(*ioop);
+    done = ioop->done();
+    if (!done) {
+      ioop = ceph::io_exerciser::IoOp::generate_barrier();
+      model->applyIoOp(*ioop);
     }
-  } catch(const po::error& e) {
-    std::cerr << "error: " << e.what() << std::endl;
-    return 1;
   }
 
-  return 0;
+  return true;
 }
 
-void run_test(const std::vector<
-    std::shared_ptr<ceph::io_sequence::tester::TestObject>
-  >& test_objects,
-  ceph::mutex& lock)
+bool ceph::io_sequence::tester::TestRunner::run_automated_test()
 {
+  // Create a test for each object
+  std::vector<std::shared_ptr<
+    ceph::io_sequence::tester::TestObject>> test_objects;
+
+  for (int obj = 0; obj < num_objects; obj++) {
+    std::string name;
+    if (obj == 0) {
+      name = object_name;
+    } else {
+      name = object_name + std::to_string(obj);
+    }
+    test_objects.push_back(
+      std::make_shared<ceph::io_sequence::tester::TestObject>(
+            name,
+            rados, asio,
+            sbs, spo, sos, snt, ssr,
+            rng, lock, cond,
+            dryrun, verbose,
+            seqseed
+      )
+    );
+  }
+  if (!dryrun) {
+    rados.wait_for_latest_osdmap();
+  }
+
   // Main loop of test - while not all test objects have finished
   // check to see if any are able to start a new I/O. If all test
   // objects are waiting for I/O to complete then wait on a cond
@@ -475,56 +783,8 @@ void run_test(const std::vector<
     ceph_assert(to->finished());
   }
   dout(0) << "Total number of IOs = " << total_io << dendl;
-}
 
-namespace {
-  constexpr std::string_view usage[] = {
-    "Basic usage:",
-    "",
-    "ceph_test_rados_io_sequence",
-    "\t Test I/O to a single object using default settings. Good for",
-    "\t testing boundary conditions",
-    "",
-    "ceph_test_rados_io_sequence --parallel <n>",
-    "\t Run parallel test to multiple objects. First object is tested with",
-    "\t default settings, other objects are tested with random settings",
-    "",
-    "Advanced usage:",
-    "",
-    "ceph_test_rados_io_sequence --blocksize <b> --km <k,m> --plugin <p>",
-    "                            --objectsize <min,max> --threads <t>",
-    "ceph_test_rados_io_sequence --blocksize <b> --pool <p> --object <oid>",
-    "                            --objectsize <min,max> --threads <t>",
-    "\tCustomize the test, if a pool is specified then it defines the",
-    "\tReplica/EC configuration",
-    "",
-    "ceph_test_rados_io_sequence --listsequence",
-    "\t Display list of supported I/O sequences",
-    "",
-    "ceph_test_rados_io_sequence --dryrun --sequence <n>",
-    "\t Show I/O that will be generated for a sequence, validate",
-    "\t seqeunce has correct I/O barriers to restrict concurrency",
-    "",
-    "ceph_test_rados_io_sequence --seed <seed>",
-    "\t Repeat a previous test with the same random numbers (seed is",
-    "\t displayed at start of test), if threads = 1 then this will produce",
-    "\t the exact same sequence of I/O, if threads > 1 then I/Os are issued",
-    "\t in parallel so ordering might be slightly different",
-    "",
-    "ceph_test_rados_io_sequence --sequence <n> --seqseed <n>",
-    "\t Repeat a sequence from a previous test with the same random",
-    "\t numbers (seqseed is displayed at start of sequence)",
-    "",
-    "ceph_test_rados_io_sequence --pool <p> --object <oid> --interactive",
-    "\t Execute sequence of I/O commands from stdin. Offset and length",
-    "\t are specified with unit of blocksize. Supported commands:",
-    "\t\t create <len>",
-    "\t\t remove",
-    "\t\t read|write <off> <len>",
-    "\t\t read2|write2 <off> <len> <off> <len>",
-    "\t\t read3|write3 <off> <len> <off> <len> <off> <len>",
-    "\t\t done"
-  };
+  return true;
 }
 
 int main(int argc, char **argv)
@@ -535,161 +795,28 @@ int main(int argc, char **argv)
                         CODE_ENVIRONMENT_UTILITY, 0);
   common_init_finish(cct.get());
 
-  librados::Rados rados;
-  boost::asio::io_context asio;
-  std::thread thread;
-  std::optional<boost::asio::executor_work_guard<
-                  boost::asio::io_context::executor_type>> guard;
-  ceph::mutex lock = ceph::make_mutex("RadosIo::lock");
-  ceph::condition_variable cond;
-
-  po::options_description desc("ceph_test_rados_io options");
-
-  desc.add_options()
-    ("help,h",
-      "show help message")
-    ("listsequence,l",
-      "show list of sequences")
-    ("dryrun,d",
-      "test sequence, do not issue any I/O")
-    ("verbose",
-      "more verbose output during test")
-    ("sequence,s", po::value<int>(),
-      "test specified sequence")
-    ("seed", po::value<int>(),
-      "seed for whole test")
-    ("seqseed", po::value<int>(),
-      "seed for sequence")
-    ("blocksize,b", po::value<Size>(),
-      "block size (default 2048)")
-    ("chunksize,c", po::value<Size>(),
-      "chunk size (default 4096)")
-    ("pool,p", po::value<std::string>(),
-      "pool name")
-    ("km", po::value<Pair>(),
-      "k,m EC pool profile (default 2,2)")
-    ("plugin", po::value<PluginString>(),
-      "EC plugin (isa or jerasure)")
-    ("objectsize", po::value<Pair>(),
-      "min,max object size in blocks (default 1,32)")
-    ("threads,t", po::value<int>(),
-      "number of threads of I/O per object (default 1)")
-    ("objects,o", po::value<int>()->default_value(1),
-      "number of objects to exercise in parallel");
-
   po::variables_map vm;
-  int rc = parse_io_seq_options(vm, desc, argc, argv);
+  int rc = parse_io_seq_options(vm, argc, argv);
   if (rc != 0)
   {
     return rc;
   }
 
-  if (vm.count("help")) {
-    std::cout << desc << std::endl;
-    for (auto line : usage) {
-      std::cout << line << std::endl;
-    }
-    return 0;
-  }
-
-  // Seed
-  int seed = time(nullptr);
-  if (vm.count("seed")) {
-    seed = vm["seed"].as<int>();
-  }
-  dout(0) << "Test using seed " << seed << dendl;
-  auto rng = ceph::util::random_number_generator<int>(seed);
-
-  bool verbose = vm.count("verbose");
-  bool dryrun = vm.count("dryrun");
-  bool has_seqseed = vm.count("seqseed");
-  int seqseed = 0;
-  if (has_seqseed) {
-    seqseed = vm["seqseed"].as<int>();
-  }
-  int num_objects = vm["objects"].as<int>();
-
-  if (!dryrun) {
+  librados::Rados rados;
+  if (!vm.contains("dryrun")) {
     rc = rados.init_with_context(g_ceph_context);
     ceph_assert(rc == 0);
     rc = rados.connect();
     ceph_assert(rc == 0);
+  }
 
-    guard.emplace(boost::asio::make_work_guard(asio));
-    thread = make_named_thread("io_thread",[&asio] { asio.run(); });
-  }
-  
-  // Select block size
-  std::unique_ptr<ceph::io_sequence::tester::SelectBlockSize> sbs
-        = std::make_unique<ceph::io_sequence::tester::SelectBlockSize>(rng, vm);
-
-  // Select pool
-  std::unique_ptr<ceph::io_sequence::tester::SelectECPool> spo
-        = std::make_unique<ceph::io_sequence::tester::SelectECPool>(rng, vm,
-                                                                    rados,
-                                                                    dryrun);
-
-  // Select object size range
-  std::unique_ptr<ceph::io_sequence::tester::SelectObjectSize> sos
-        = std::make_unique<ceph::io_sequence::tester::SelectObjectSize>(rng,
-                                                                        vm);
-
-  // Select number of threads
-  std::unique_ptr<ceph::io_sequence::tester::SelectNumThreads> snt =
-        std::make_unique<ceph::io_sequence::tester::SelectNumThreads>(rng, vm);
-
-  // Select range of sequences
-  std::unique_ptr<ceph::io_sequence::tester::SelectSeqRange> ssr;
+  std::unique_ptr<ceph::io_sequence::tester::TestRunner> runner;
   try {
-    ssr = std::make_unique<ceph::io_sequence::tester::SelectSeqRange>(rng, vm);
+    runner = std::make_unique<ceph::io_sequence::tester::TestRunner>(vm, rados);
   } catch(const po::error& e) {
     return 1;
   }
+  runner->run_test();
 
-  // List seqeunces
-  if (vm.count("listsequence")) {
-    std::pair<int,int> obj_size_range = sos->choose();
-    for (ceph::io_exerciser::Sequence s
-          = ceph::io_exerciser::Sequence::SEQUENCE_BEGIN; 
-         s < ceph::io_exerciser::Sequence::SEQUENCE_END; ++s) {
-      std::unique_ptr<ceph::io_exerciser::IoSequence> seq =
-      ceph::io_exerciser::IoSequence::generate_sequence(s,
-                                                        obj_size_range,
-                                                        has_seqseed ?
-                                                          seqseed :
-                                                          rng());
-      dout(0) << s << " " << seq->get_name() << dendl;
-    }
-    return 0;
-  }
-
-  // Create a test for each object
-  std::vector<std::shared_ptr<
-    ceph::io_sequence::tester::TestObject>> test_objects;
-    
-  for (int obj = 0; obj < num_objects; obj++) {
-    test_objects.push_back(
-      std::make_shared<ceph::io_sequence::tester::TestObject>(
-            "test" + std::to_string(obj),
-            rados, asio,
-            *sbs, *spo, *sos, *snt, *ssr,
-            rng, lock, cond, 
-            dryrun, verbose,
-            has_seqseed, seqseed
-      )
-    );
-  }
-  if (!dryrun) {
-    rados.wait_for_latest_osdmap();
-  }
-
-  run_test(test_objects, lock); 
-
-  if (!dryrun) {
-    guard = std::nullopt;
-    asio.stop();
-    thread.join();
-    rados.shutdown();
-  }
   return 0;
 }
index 3a84b7bc824958bd23f2186487e343fddfbe88f5..4f77c94027458dcdf416caa6e6c7bf7cd13b9f8a 100644 (file)
@@ -1,5 +1,7 @@
 #include <utility>
 
+#include "include/random.h"
+
 #include "global/global_init.h"
 #include "global/global_context.h"
 
@@ -267,8 +269,7 @@ namespace ceph
                   ceph::condition_variable& cond,
                   bool dryrun,
                   bool verbose,
-                  bool has_seqseed,
-                  int  seqseed);
+                  std::optional<int>  seqseed);
       
       int get_num_io();
       bool readyForIo();
@@ -286,8 +287,57 @@ namespace ceph
       bool done;
       ceph::util::random_number_generator<int>& rng;
       bool verbose;
-      bool has_seqseed;
-      int seqseed;
+      std::optional<int> seqseed;
+    };
+
+    class TestRunner
+    {
+    public:
+      TestRunner(po::variables_map& vm, librados::Rados& rados);
+      ~TestRunner();
+
+      bool run_test();
+
+    private:
+      librados::Rados& rados;
+      int seed;
+      ceph::util::random_number_generator<int> rng;
+
+      ceph::io_sequence::tester::SelectBlockSize sbs;
+      ceph::io_sequence::tester::SelectObjectSize sos;
+      ceph::io_sequence::tester::SelectECPool spo;
+      ceph::io_sequence::tester::SelectNumThreads snt;
+      ceph::io_sequence::tester::SelectSeqRange ssr;
+
+      boost::asio::io_context asio;
+      std::thread thread;
+      std::optional<boost::asio::executor_work_guard<
+                    boost::asio::io_context::executor_type>> guard;
+      ceph::mutex lock = ceph::make_mutex("RadosIo::lock");
+      ceph::condition_variable cond;
+
+      bool input_valid;
+
+      bool verbose;
+      bool dryrun;
+      std::optional<int> seqseed;
+      bool interactive;
+
+      bool show_sequence;
+      bool show_help;
+
+      int num_objects;
+      std::string object_name;
+
+      std::string get_token();
+      uint64_t get_numeric_token();
+
+      bool run_automated_test();
+
+      bool run_interactive_test();
+
+      void help();
+      void list_sequence();
     };
   }
 }
\ No newline at end of file