From 163a28aa116223093e72b99b0ce4d07d089e1c7b Mon Sep 17 00:00:00 2001 From: JonBailey1993 Date: Mon, 23 Sep 2024 11:23:18 +0100 Subject: [PATCH] test/osd: Add interactive mode to ceph_test_rados_io_sequence 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 --- src/test/osd/ceph_test_rados_io_sequence.cc | 671 ++++++++++++-------- src/test/osd/ceph_test_rados_io_sequence.h | 58 +- 2 files changed, 453 insertions(+), 276 deletions(-) diff --git a/src/test/osd/ceph_test_rados_io_sequence.cc b/src/test/osd/ceph_test_rados_io_sequence.cc index 4ac4a3eca59f6..5e340c5c9c5bd 100644 --- a/src/test/osd/ceph_test_rados_io_sequence.cc +++ b/src/test/osd/ceph_test_rados_io_sequence.cc @@ -27,6 +27,185 @@ #define dout_subsys ceph_subsys_rados #define dout_context g_ceph_context +namespace { + struct Size {}; + void validate(boost::any& v, const std::vector& 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& 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{first,second}); + } + + struct PluginString {}; + void validate(boost::any& v, const std::vector& 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 ", + "\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 --km --plugin

", + " --objectsize --threads ", + "ceph_test_rados_io_sequence --blocksize --pool

--object ", + " --objectsize --threads ", + "\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 ", + "\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 ", + "\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 --seqseed ", + "\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

--object --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 ", + "\t\t remove", + "\t\t read|write ", + "\t\t read2|write2 ", + "\t\t read3|write3 ", + "\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(), + "test specified sequence") + ("seed", po::value(), + "seed for whole test") + ("seqseed", po::value(), + "seed for sequence") + ("blocksize,b", po::value(), + "block size (default 2048)") + ("chunksize,c", po::value(), + "chunk size (default 4096)") + ("pool,p", po::value(), + "pool name") + ("object,o", po::value()->default_value("test"), + "object name") + ("km", po::value(), + "k,m EC pool profile (default 2,2)") + ("plugin", po::value(), + "EC plugin (isa or jerasure)") + ("objectsize", po::value(), + "min,max object size in blocks (default 1,32)") + ("threads,t", po::value(), + "number of threads of I/O per object (default 1)") + ("parallel,p", po::value()->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 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 & Ts> ceph::io_sequence::tester::ProgramOptionSelector ::ProgramOptionSelector(ceph::util::random_number_generator& rng, @@ -35,7 +214,6 @@ ceph::io_sequence::tester::ProgramOptionSelector 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(); @@ -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 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() << " ==" <get_num_io(); } -struct Size {}; -void validate(boost::any& v, const std::vector& 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() : time(nullptr)), + rng(ceph::util::random_number_generator(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(); + } + num_objects = vm["parallel"].as(); + object_name = vm["object"].as(); + 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 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 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& 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{first,second}); + return num; } -struct PluginString {}; -void validate(boost::any& v, const std::vector& 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 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 ioop; + std::unique_ptr model; + + if (dryrun) { + model = std::make_unique(object_name, + sbs.choose(), + rng()); + } else { + const std::string pool = spo.choose(); + model = std::make_unique(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 - >& test_objects, - ceph::mutex& lock) +bool ceph::io_sequence::tester::TestRunner::run_automated_test() { + // Create a test for each object + std::vector> 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( + 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 ", - "\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 --km --plugin

", - " --objectsize --threads ", - "ceph_test_rados_io_sequence --blocksize --pool

--object ", - " --objectsize --threads ", - "\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 ", - "\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 ", - "\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 --seqseed ", - "\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

--object --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 ", - "\t\t remove", - "\t\t read|write ", - "\t\t read2|write2 ", - "\t\t read3|write3 ", - "\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> 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(), - "test specified sequence") - ("seed", po::value(), - "seed for whole test") - ("seqseed", po::value(), - "seed for sequence") - ("blocksize,b", po::value(), - "block size (default 2048)") - ("chunksize,c", po::value(), - "chunk size (default 4096)") - ("pool,p", po::value(), - "pool name") - ("km", po::value(), - "k,m EC pool profile (default 2,2)") - ("plugin", po::value(), - "EC plugin (isa or jerasure)") - ("objectsize", po::value(), - "min,max object size in blocks (default 1,32)") - ("threads,t", po::value(), - "number of threads of I/O per object (default 1)") - ("objects,o", po::value()->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(); - } - dout(0) << "Test using seed " << seed << dendl; - auto rng = ceph::util::random_number_generator(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 num_objects = vm["objects"].as(); - - 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 sbs - = std::make_unique(rng, vm); - - // Select pool - std::unique_ptr spo - = std::make_unique(rng, vm, - rados, - dryrun); - - // Select object size range - std::unique_ptr sos - = std::make_unique(rng, - vm); - - // Select number of threads - std::unique_ptr snt = - std::make_unique(rng, vm); - - // Select range of sequences - std::unique_ptr ssr; + std::unique_ptr runner; try { - ssr = std::make_unique(rng, vm); + runner = std::make_unique(vm, rados); } catch(const po::error& e) { return 1; } + runner->run_test(); - // List seqeunces - if (vm.count("listsequence")) { - std::pair 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 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> test_objects; - - for (int obj = 0; obj < num_objects; obj++) { - test_objects.push_back( - std::make_shared( - "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; } diff --git a/src/test/osd/ceph_test_rados_io_sequence.h b/src/test/osd/ceph_test_rados_io_sequence.h index 3a84b7bc82495..4f77c94027458 100644 --- a/src/test/osd/ceph_test_rados_io_sequence.h +++ b/src/test/osd/ceph_test_rados_io_sequence.h @@ -1,5 +1,7 @@ #include +#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 seqseed); int get_num_io(); bool readyForIo(); @@ -286,8 +287,57 @@ namespace ceph bool done; ceph::util::random_number_generator& rng; bool verbose; - bool has_seqseed; - int seqseed; + std::optional 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 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> guard; + ceph::mutex lock = ceph::make_mutex("RadosIo::lock"); + ceph::condition_variable cond; + + bool input_valid; + + bool verbose; + bool dryrun; + std::optional 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 -- 2.39.5