root/software/read_test/src/main.cc @ c9d5491b
| a5734307 | David Sorber | #include <algorithm>
|
||
| a0d1d164 | David Sorber | #include <chrono>
|
||
| 058717b0 | David Sorber | #include <cstdlib>
|
||
| f12339cf | david.sorber | #include <filesystem>
|
||
| a0d1d164 | David Sorber | #include <iomanip>
|
||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
| 058717b0 | David Sorber | #include <sys/utsname.h>
|
||
| a5734307 | David Sorber | #include <boost/program_options.hpp>
|
||
| a0d1d164 | David Sorber | #include "AIO_ExecDriver.h"
|
||
#include "ExecDriver.h"
|
||||
| 864b608c | David Sorber | #include "URingExecDriver.h"
|
||
| a0d1d164 | David Sorber | #include "Utilities/BufferAllocator.h"
|
||
#include "Utilities/FileUtils.h"
|
||||
| a5734307 | David Sorber | // Alias the namespace for readability
|
||
namespace bpo = boost::program_options;
|
||||
namespace bsl = BlackLynx::SearchLynx;
|
||||
| f12339cf | david.sorber | namespace sfs = std::filesystem;
|
||
| a5734307 | David Sorber | |||
const std::string BOLD("\033[1m");
|
||||
const std::string ENDC("\033[0m");
|
||||
const std::string RED("\033[31m");
|
||||
const std::string YELLOW("\033[33m");
|
||||
const std::string UP_ONE = "\033[1A";
|
||||
| f12339cf | david.sorber | const std::string VERSION("ReadTest version 1.2.0-dev");
|
||
| a5734307 | David Sorber | |||
| f12339cf | david.sorber | const std::vector<std::string> UNITS{"b/sec", "KB/s", "MB/s", "GB/s", "TB/s"};
|
||
| a0d1d164 | David Sorber | |||
double printRate(double rawRate, uint32_t& unitIdx)
|
||||
{
|
||||
uint32_t idx = 0;
|
||||
double rate = rawRate;
|
||||
while (rate > 1024)
|
||||
{
|
||||
rate /= 1024;
|
||||
++idx;
|
||||
}
|
||||
unitIdx = idx;
|
||||
return rate;
|
||||
}
|
||||
| 058717b0 | David Sorber | void getKernelVersion(uint32_t& major, uint32_t& minor, uint32_t& bugfix)
|
||
{
|
||||
// Read "uname" info
|
||||
struct utsname unameBuf;
|
||||
uname(&unameBuf);
|
||||
// Release contains kernel version; e.g. 5.15.0-33-generic
|
||||
char* startPtr = unameBuf.release;
|
||||
char* endPtr = nullptr;
|
||||
major = std::strtoul(startPtr, &endPtr, 10);
|
||||
startPtr = endPtr + 1;
|
||||
minor = std::strtoul(startPtr, &endPtr, 10);
|
||||
startPtr = endPtr + 1;
|
||||
bugfix = std::strtoul(startPtr, &endPtr, 10);
|
||||
#if 0
|
||||
std::cout << "MAJOR: " << major << std::endl;
|
||||
std::cout << "MINOR: " << minor << std::endl;
|
||||
std::cout << "BUGFIX: " << bugfix << std::endl;
|
||||
std::cout << "sysname: " << unameBuf.sysname << std::endl;
|
||||
std::cout << "nodename: " << unameBuf.nodename << std::endl;
|
||||
std::cout << "release: " << unameBuf.release << std::endl;
|
||||
std::cout << "version: " << unameBuf.version << std::endl;
|
||||
std::cout << "machine: " << unameBuf.machine << std::endl;
|
||||
#endif
|
||||
}
|
||||
bool validateKernelVersion(uint32_t major, uint32_t minor, uint32_t bugfix)
|
||||
{
|
||||
uint32_t majorVer, minorVer, bugfixVer;
|
||||
getKernelVersion(majorVer, minorVer, bugfixVer);
|
||||
if (majorVer >= major)
|
||||
{
|
||||
if (minorVer >= minor)
|
||||
{
|
||||
if (bugfixVer >= bugfix)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
| a5734307 | David Sorber | void usage()
|
||
{
|
||||
| f12339cf | david.sorber | std::cout << BOLD << "Read Test Utility\n\n" << ENDC
|
||
<< "Usage: read_test <--help | -h>\n"
|
||||
<< " read_test --version\n\n"
|
||||
<< "Options:\n"
|
||||
<< " --input-file Path to the file to read.\n"
|
||||
<< " --read-type The type of read to perform: auto, normal, aio, or uring\n"
|
||||
<< " --num-threads The number of reader threads to use.\n"
|
||||
<< " --chunk-size Chunk size in kilobytes.\n\n";
|
||||
| a5734307 | David Sorber | }
|
||
template <typename T>
|
||||
void lowercase(std::basic_string<T>& str)
|
||||
{
|
||||
std::transform(str.begin(), str.end(), str.begin(), tolower);
|
||||
}
|
||||
// Type used to set "read-type" value
|
||||
enum class ReadType
|
||||
{
|
||||
DEFAULT_INVALID,
|
||||
AUTO,
|
||||
NORMAL,
|
||||
| 864b608c | David Sorber | AIO,
|
||
URING
|
||||
| a5734307 | David Sorber | };
|
||
struct ReadTypeValue
|
||||
{
|
||||
ReadTypeValue()
|
||||
{
|
||||
}
|
||||
ReadTypeValue(std::string const& val)
|
||||
: value(val)
|
||||
{
|
||||
}
|
||||
ReadType getValue()
|
||||
{
|
||||
if (value == "auto")
|
||||
{
|
||||
return ReadType::AUTO;
|
||||
}
|
||||
else if (value == "normal")
|
||||
{
|
||||
return ReadType::NORMAL;
|
||||
}
|
||||
else if (value == "aio")
|
||||
{
|
||||
return ReadType::AIO;
|
||||
}
|
||||
| 864b608c | David Sorber | else if (value == "uring")
|
||
{
|
||||
return ReadType::URING;
|
||||
}
|
||||
| a5734307 | David Sorber | return ReadType::DEFAULT_INVALID;
|
||
}
|
||||
std::string value;
|
||||
};
|
||||
void validate(
|
||||
boost::any& v,
|
||||
std::vector<std::string> const& values,
|
||||
ReadTypeValue*,
|
||||
int)
|
||||
{
|
||||
// Make sure no previous assignment to 'v' was made.
|
||||
bpo::validators::check_first_occurrence(v);
|
||||
// Extract the first string from 'values'. If there is more than one string,
|
||||
// it's an error, and exception will be thrown.
|
||||
std::string val = bpo::validators::get_single_string(values);
|
||||
lowercase(val);
|
||||
// Check for valid values
|
||||
| 864b608c | David Sorber | if (val == "auto" || val == "normal" || val == "aio" || val == "uring")
|
||
| a5734307 | David Sorber | {
|
||
v = boost::any(ReadTypeValue(val));
|
||||
}
|
||||
else
|
||||
{
|
||||
throw bpo::validation_error(bpo::validation_error::invalid_option_value);
|
||||
}
|
||||
}
|
||||
| a0d1d164 | David Sorber | |||
int main(int argc, char** argv)
|
||||
{
|
||||
| a5734307 | David Sorber | bpo::variables_map optionsMap;
|
||
ReadTypeValue readType("default");
|
||||
| f12339cf | david.sorber | std::string filePath;
|
||
| 864b608c | David Sorber | uint32_t numThreads = 0;
|
||
| f12339cf | david.sorber | uint32_t chunkSize = 65536; // in Kb
|
||
| a0d1d164 | David Sorber | |||
| a5734307 | David Sorber | try
|
||
{
|
||||
// General options
|
||||
bpo::options_description generalOpts("General options");
|
||||
generalOpts.add_options()
|
||||
("help,h", "display this help message")
|
||||
("version",
|
||||
"output version number and exit")
|
||||
("read-type", bpo::value<ReadTypeValue>(&readType),
|
||||
"read type")
|
||||
| 864b608c | David Sorber | ("num-threads", bpo::value<uint32_t>(&numThreads),
|
||
"number reader threads")
|
||||
| f12339cf | david.sorber | ("input-file", bpo::value<std::string>(&filePath)->required(),
|
||
| a5734307 | David Sorber | "input file")
|
||
| f12339cf | david.sorber | |||
("chunk-size", bpo::value<uint32_t>(&chunkSize), "chunk size in Kb")
|
||||
| a5734307 | David Sorber | ;
|
||
// Positional options
|
||||
bpo::positional_options_description posOpts;
|
||||
posOpts.add("input-file", 1);
|
||||
bpo::parsed_options parsed = bpo::command_line_parser(argc, argv).
|
||||
options(generalOpts).positional(posOpts)
|
||||
.run();
|
||||
bpo::store(parsed, optionsMap);
|
||||
// Display help if the option is listed or if no arguments are provided
|
||||
if (optionsMap.count("help"))// || (argc == 1))
|
||||
{
|
||||
usage();
|
||||
return 1;
|
||||
}
|
||||
// Display version and exist if option listed
|
||||
if (optionsMap.count("version"))
|
||||
{
|
||||
std::cout << VERSION << std::endl;
|
||||
return 2;
|
||||
}
|
||||
// Notify any errors
|
||||
bpo::notify(optionsMap);
|
||||
}
|
||||
catch (std::exception& e)
|
||||
| a0d1d164 | David Sorber | {
|
||
| a5734307 | David Sorber | std::cerr << BOLD << RED << "\nERROR: " << ENDC << e.what() << "\n"
|
||
<< std::endl;
|
||||
| a0d1d164 | David Sorber | return -1;
|
||
}
|
||||
| a5734307 | David Sorber | |||
std::cout << "Read Test Utility\n" << std::endl;
|
||||
std::cout << "Read type: " << readType.value << "\n";
|
||||
| f12339cf | david.sorber | std::cout << "Input file: " << filePath << "\n";
|
||
| a0d1d164 | David Sorber | |||
| f12339cf | david.sorber | // Validate kernel version if user selected uring read type
|
||
| 058717b0 | David Sorber | if (readType.getValue() == ReadType::URING &&
|
||
(! validateKernelVersion(5, 6, 0)))
|
||||
{
|
||||
uint32_t majorVer, minorVer, bugfixVer;
|
||||
getKernelVersion(majorVer, minorVer, bugfixVer);
|
||||
std::cerr << BOLD << RED << "\nERROR: " << ENDC << "The uring read type "
|
||||
<< "requires kernel version >= 5.6; the current kernel version"
|
||||
<< " " << majorVer << "." << minorVer << " is too old.\n"
|
||||
<< std::endl;
|
||||
return -1;
|
||||
}
|
||||
| a0d1d164 | David Sorber | // Start time measurement
|
||
auto start = std::chrono::system_clock::now();
|
||||
| f12339cf | david.sorber | uint64_t fileSize = sfs::file_size(filePath);
|
||
| a5734307 | David Sorber | // Prime the config object then allocate buffers
|
||
auto bufAllocator = bsl::BufferAllocator::getInstance();
|
||||
| f12339cf | david.sorber | bufAllocator->initAllocation(chunkSize, fileSize);
|
||
| a0d1d164 | David Sorber | |||
| a5734307 | David Sorber | // Use the read type argument to decide which ExecDriver instance to create
|
||
bsl::ExecDriver* driver = nullptr;
|
||||
switch (readType.getValue())
|
||||
{
|
||||
case ReadType::DEFAULT_INVALID:
|
||||
case ReadType::AUTO:
|
||||
case ReadType::AIO:
|
||||
| 864b608c | David Sorber | // Set default number of threads
|
||
if (numThreads == 0)
|
||||
{
|
||||
numThreads = 3;
|
||||
}
|
||||
| f12339cf | david.sorber | driver = new bsl::AIO_ExecDriver(filePath, numThreads, chunkSize);
|
||
| a5734307 | David Sorber | break;
|
||
case ReadType::NORMAL:
|
||||
| 864b608c | David Sorber | // Set default number of threads
|
||
if (numThreads == 0)
|
||||
{
|
||||
numThreads = 22;
|
||||
}
|
||||
| f12339cf | david.sorber | driver = new bsl::ExecDriver(filePath, numThreads, chunkSize);
|
||
| 864b608c | David Sorber | break;
|
||
case ReadType::URING:
|
||||
| 3dda5d70 | david.sorber | // numThreads = 1;
|
||
driver = new bsl::URingExecDriver(filePath, numThreads, chunkSize);
|
||||
| a5734307 | David Sorber | break;
|
||
}
|
||||
| 864b608c | David Sorber | std::cout << "Num threads: " << numThreads << "\n";
|
||
| 11aeae96 | david.sorber | std::cout << "Chunk size: " << chunkSize << " KB\n";
|
||
| a5734307 | David Sorber | |||
driver->start();
|
||||
driver->waitUntilComplete();
|
||||
| a0d1d164 | David Sorber | |||
// Deallocate buffers
|
||||
bufAllocator->deallocateBuffers();
|
||||
// End time measurement
|
||||
auto end = std::chrono::system_clock::now();
|
||||
auto diff = end - start;
|
||||
double duration = std::chrono::duration <double>(diff).count() ;
|
||||
| a5734307 | David Sorber | std::cout << "Duration: " << std::fixed << std::setprecision(3)
|
||
| 11aeae96 | david.sorber | << duration << " s" << std::endl;
|
||
| a0d1d164 | David Sorber | |||
double rawRate = fileSize / duration;
|
||||
| a5734307 | David Sorber | std::cout << "Raw rate: " << rawRate << " b/sec" << std::endl;
|
||
| a0d1d164 | David Sorber | |||
uint32_t idx = 0;
|
||||
double rate = printRate(rawRate, idx);
|
||||
| a5734307 | David Sorber | std::cout << "Rate: " << std::fixed << std::setprecision(3) << rate
|
||
| a0d1d164 | David Sorber | << " " << UNITS[idx] << std::endl;
|
||
| a5734307 | David Sorber | // Clean up
|
||
delete driver;
|
||||
driver = nullptr;
|
||||
| a0d1d164 | David Sorber | return 0;
|
||
}
|