diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3ca7312..2ec8f17 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -64,13 +64,14 @@ jobs: - name: Init submodules run: | git submodule update --init --depth=1 \ - contrib/libbacktrace \ - contrib/librseq \ - contrib/liburing \ - contrib/googletest \ contrib/benchmark \ + contrib/bpftool \ + contrib/cxxopts \ + contrib/googletest \ + contrib/libbacktrace \ contrib/libbpf \ - contrib/bpftool + contrib/librseq \ + contrib/liburing - name: Init Poco submodule if: matrix.build.name == 'release' || matrix.build.name == 'tsan' diff --git a/.gitmodules b/.gitmodules index 6b4babe..b35fbc4 100644 --- a/.gitmodules +++ b/.gitmodules @@ -34,3 +34,6 @@ [submodule "contrib/bpftool"] path = contrib/bpftool url = https://github.com/libbpf/bpftool.git +[submodule "contrib/cxxopts"] + path = contrib/cxxopts + url = https://github.com/jarro2783/cxxopts.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 1471dea..d955863 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -32,7 +32,7 @@ if(POLICY CMP0167) cmake_policy(SET CMP0167 NEW) endif() -find_package(Boost REQUIRED context program_options) +find_package(Boost REQUIRED context) if(CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64") add_compile_options(-march=x86-64-v3) @@ -85,6 +85,7 @@ endif() add_subdirectory(contrib/liburing-cmake) add_subdirectory(contrib/librseq-cmake) +add_subdirectory(contrib/cxxopts-cmake) add_subdirectory(contrib/googletest-cmake) add_subdirectory(contrib/benchmark-cmake) add_subdirectory(contrib/systemtap-sdt) @@ -133,11 +134,12 @@ add_subdirectory(src/util) add_subdirectory(src/fibers) add_subdirectory(src/gdb) +# Perf and profiler targets may pull in system libs with libstdc++ ABI +# (Poco, AWS SDK) that conflict with the instrumented libc++ used in MSan +# builds. Boost::context (used by silk) is assembly-level and has no +# std::string in its ABI, so it works with libc++ despite being a system +# library. if(NOT SANITIZER STREQUAL "memory") - # perf targets link against system Boost::program_options (libstdc++ ABI), - # which is incompatible with the instrumented libc++ used in MSan builds. - # Boost::context (used by silk) is assembly-level and has no std::string - # in its ABI, so it works with libc++ despite being a system library. add_subdirectory(src/perf) if(BUILD_LIBBACKTRACE) # symbolizer.cpp depends directly on . diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index fb1bdbf..e0f8a85 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -8,11 +8,12 @@ Initialize the required submodules after cloning: ``` git submodule update --init --depth=1 \ + contrib/benchmark \ + contrib/cxxopts \ + contrib/googletest \ contrib/libbacktrace \ contrib/librseq \ - contrib/liburing \ - contrib/googletest \ - contrib/benchmark + contrib/liburing ``` To build optional components, initialize their submodules too: diff --git a/README.md b/README.md index 0ab710c..c7d9d24 100644 --- a/README.md +++ b/README.md @@ -18,10 +18,10 @@ A cooperative fiber scheduler for Linux with per-CPU scheduler threads, io_uring - Ninja - Clang 21 - ccache (optional) -- Boost (`libboost-dev`, `libboost-context-dev`, `libboost-program-options-dev`) +- Boost (`libboost-dev`, `libboost-context-dev`) - libelf (`libelf-dev`) — optional, required only for `src/profiler`; the profiler is silently skipped if absent. -GTest, Google Benchmark, libbacktrace, liburing, librseq, libbpf, and bpftool are bundled as submodules under `contrib/` and do not need to be installed separately. Poco, the AWS SDK, and jemalloc are built on demand via `--build-poco`, `--build-aws`, and `--build-jemalloc` passed to `configure`. +GTest, Google Benchmark, libbacktrace, liburing, librseq, libbpf, bpftool, and cxxopts are bundled as submodules under `contrib/` and do not need to be installed separately. Poco, the AWS SDK, and jemalloc are built on demand via `--build-poco`, `--build-aws`, and `--build-jemalloc` passed to `configure`. Runtime dependencies for optional benchmarks: nginx (only for `http-perf --nginx`; the default uses an internal Poco-based server built into the `http-perf` binary), fio (for `fio-perf`), and MinIO (for `s3-perf`). MinIO is downloaded automatically to `.tools/` if not in PATH; the others must be installed separately. diff --git a/bb b/bb index bfbd0d5..d809fa4 100755 --- a/bb +++ b/bb @@ -450,6 +450,7 @@ def _run_flamegraph(preset: str, name: str, client_args: list[str]) -> None: profiler_bin, "--pid", str(client.pid), + "--on-cpu", "--off-cpu", "--kernel-stacks", *verbose_flag, diff --git a/contrib/cxxopts b/contrib/cxxopts new file mode 160000 index 0000000..44380e5 --- /dev/null +++ b/contrib/cxxopts @@ -0,0 +1 @@ +Subproject commit 44380e5a44706ab7347f400698c703eb2a196202 diff --git a/contrib/cxxopts-cmake/CMakeLists.txt b/contrib/cxxopts-cmake/CMakeLists.txt new file mode 100644 index 0000000..0a4bc87 --- /dev/null +++ b/contrib/cxxopts-cmake/CMakeLists.txt @@ -0,0 +1,6 @@ +set(CXXOPTS_SRC ${CMAKE_CURRENT_SOURCE_DIR}/../cxxopts) + +add_library(cxxopts INTERFACE) +target_include_directories(cxxopts SYSTEM INTERFACE ${CXXOPTS_SRC}/include) + +add_library(cxxopts::cxxopts ALIAS cxxopts) diff --git a/src/perf/CMakeLists.txt b/src/perf/CMakeLists.txt index a1992b1..b1414a4 100644 --- a/src/perf/CMakeLists.txt +++ b/src/perf/CMakeLists.txt @@ -2,18 +2,18 @@ add_library(silk-perf STATIC common.cpp) target_link_libraries(silk-perf PUBLIC silk-fibers) add_executable(file-perf file-perf.cpp) -target_link_libraries(file-perf PRIVATE silk-perf Boost::program_options) +target_link_libraries(file-perf PRIVATE silk-perf cxxopts::cxxopts) add_executable(net-perf net-perf.cpp) -target_link_libraries(net-perf PRIVATE silk-perf Boost::program_options) +target_link_libraries(net-perf PRIVATE silk-perf cxxopts::cxxopts) add_executable(net-perf-epoll net-perf-epoll.cpp) -target_link_libraries(net-perf-epoll PRIVATE silk-perf Boost::program_options) +target_link_libraries(net-perf-epoll PRIVATE silk-perf cxxopts::cxxopts) # asio::awaitable requires Boost >= 1.75 if(Boost_VERSION_STRING VERSION_GREATER_EQUAL "1.75") add_executable(net-perf-asio net-perf-asio.cpp) - target_link_libraries(net-perf-asio PRIVATE silk-perf Boost::program_options) + target_link_libraries(net-perf-asio PRIVATE silk-perf cxxopts::cxxopts) if(BUILD_JEMALLOC) target_link_libraries(net-perf-asio PRIVATE jemalloc::jemalloc) @@ -25,7 +25,7 @@ if(BUILD_POCO) target_link_libraries(silk-perf-http PUBLIC silk-perf Poco::Net Poco::Foundation) add_executable(http-perf http-perf.cpp) - target_link_libraries(http-perf PRIVATE silk-perf-http Boost::program_options) + target_link_libraries(http-perf PRIVATE silk-perf-http cxxopts::cxxopts) if(BUILD_JEMALLOC) target_link_libraries(http-perf PRIVATE jemalloc::jemalloc) @@ -34,7 +34,7 @@ endif() if(BUILD_AWS) add_executable(s3-perf s3-perf.cpp) - target_link_libraries(s3-perf PRIVATE silk-perf-http Boost::program_options aws-cpp-sdk-s3 aws-cpp-sdk-core) + target_link_libraries(s3-perf PRIVATE silk-perf-http cxxopts::cxxopts aws-cpp-sdk-s3 aws-cpp-sdk-core) if(BUILD_JEMALLOC) target_link_libraries(s3-perf PRIVATE jemalloc::jemalloc) diff --git a/src/perf/file-perf.cpp b/src/perf/file-perf.cpp index b77e687..dc2cf8a 100644 --- a/src/perf/file-perf.cpp +++ b/src/perf/file-perf.cpp @@ -8,8 +8,6 @@ #include #include -#include - #include #include #include @@ -26,6 +24,7 @@ #include #include +#include #include // @@ -346,44 +345,46 @@ int main(int argc, char ** argv) std::string warmupStr = "2s"; bool verbose = false; - namespace po = boost::program_options; - po::options_description desc("file-perf options"); + cxxopts::Options cli("file-perf", "file-perf options"); // clang-format off - desc.add_options() - ("help,h", "show this help") - ("numjobs", po::value(&cfg.numJobs), "number of concurrent worker fibers") - ("iodepth", po::value(&cfg.iodepth), "per-fiber IO queue depth") - ("bs", po::value(&bsStr), "block size (e.g. 4k, 1m)") - ("rw", po::value(&rwStr), "I/O mode: randread | seqread | randwrite") - ("size", po::value(&sizeStr), "file size (e.g. 1g, 512m)") - ("runtime", po::value(&runtimeStr), "measurement duration (e.g. 10s, 500ms)") - ("warmup", po::value(&warmupStr), "warmup duration (e.g. 2s, 500ms)") - ("filename", po::value(&cfg.filename)->required(), "file path") - ("direct", po::bool_switch(&cfg.direct), "use O_DIRECT (bypass page cache)") - ("print-counters", po::bool_switch(&cfg.printCounters), "enable per-CPU profiler and include counters in the JSON report") - ("verbose,v", po::bool_switch(&verbose), "enable debug logging") + cli.add_options() + ("h,help", "show this help") + ("numjobs", "number of concurrent worker fibers", cxxopts::value(cfg.numJobs)) + ("iodepth", "per-fiber IO queue depth", cxxopts::value(cfg.iodepth)) + ("bs", "block size (e.g. 4k, 1m)", cxxopts::value(bsStr)) + ("rw", "I/O mode: randread | seqread | randwrite", cxxopts::value(rwStr)) + ("size", "file size (e.g. 1g, 512m)", cxxopts::value(sizeStr)) + ("runtime", "measurement duration (e.g. 10s, 500ms)", cxxopts::value(runtimeStr)) + ("warmup", "warmup duration (e.g. 2s, 500ms)", cxxopts::value(warmupStr)) + ("filename", "file path", cxxopts::value(cfg.filename)) + ("direct", "use O_DIRECT (bypass page cache)", cxxopts::value(cfg.direct)) + ("print-counters", "enable per-CPU profiler and include counters in the JSON report", cxxopts::value(cfg.printCounters)) + ("v,verbose", "enable debug logging", cxxopts::value(verbose)) ; // clang-format on - po::variables_map vm; try { - po::store(po::parse_command_line(argc, argv, desc), vm); - if (vm.count("help")) + auto result = cli.parse(argc, argv); + if (result.count("help")) { - std::cout << "usage: file-perf [options]\n" << desc << "\n"; + std::cout << cli.help() << "\n"; return 0; } - po::notify(vm); + if (result.count("filename") == 0) + { + std::cerr << "error: --filename is required\n" << cli.help() << "\n"; + return 1; + } if (verbose) { silk::Logger::setLevel(silk::LogLevel::DEBUG); } } - catch (const po::error & ex) + catch (const cxxopts::exceptions::exception & ex) { - std::cerr << "error: " << ex.what() << "\n" << desc << "\n"; + std::cerr << "error: " << ex.what() << "\n" << cli.help() << "\n"; return 1; } diff --git a/src/perf/http-perf.cpp b/src/perf/http-perf.cpp index e4196e8..b7dc239 100644 --- a/src/perf/http-perf.cpp +++ b/src/perf/http-perf.cpp @@ -25,7 +25,6 @@ #include #include #include -#include #include #include @@ -43,6 +42,8 @@ #include +#include + // // Client // @@ -256,33 +257,30 @@ static void runClient(int argc, char ** argv) std::string warmupStr = "2s"; bool verbose = false; - namespace po = boost::program_options; - po::options_description desc("http-perf client options"); + cxxopts::Options cli("http-perf client", "http-perf client options"); // clang-format off - desc.add_options() - ("help,h", "show this help") - ("host", po::value(&cfg.host), "server host") - ("port", po::value(&cfg.port), "server port") - ("connections", po::value(&cfg.numConnections), "parallel connections or threads") - ("threads", po::bool_switch(&cfg.useThreads), "use OS threads instead of fibers") - ("duration", po::value(&durationStr), "measurement duration (e.g. 10s, 500ms)") - ("warmup", po::value(&warmupStr), "warmup duration (e.g. 2s, 500ms)") - ("print-counters", po::bool_switch(&cfg.printCounters), "enable per-CPU profiler and include counters in the JSON report") - ("verbose,v", po::bool_switch(&verbose), "enable debug logging") + cli.add_options() + ("h,help", "show this help") + ("host", "server host", cxxopts::value(cfg.host)) + ("port", "server port", cxxopts::value(cfg.port)) + ("connections", "parallel connections or threads", cxxopts::value(cfg.numConnections)) + ("threads", "use OS threads instead of fibers", cxxopts::value(cfg.useThreads)) + ("duration", "measurement duration (e.g. 10s, 500ms)", cxxopts::value(durationStr)) + ("warmup", "warmup duration (e.g. 2s, 500ms)", cxxopts::value(warmupStr)) + ("print-counters", "enable per-CPU profiler and include counters in the JSON report", cxxopts::value(cfg.printCounters)) + ("v,verbose", "enable debug logging", cxxopts::value(verbose)) ; // clang-format on - po::variables_map vm; try { - po::store(po::parse_command_line(argc, argv, desc), vm); - if (vm.count("help")) + auto result = cli.parse(argc, argv); + if (result.count("help")) { - std::cout << "usage: http-perf client [options]\n" << desc << "\n"; + std::cout << cli.help() << "\n"; return; } - po::notify(vm); cfg.durationNs = parseDuration(durationStr); cfg.warmupNs = parseDuration(warmupStr); if (verbose) @@ -290,9 +288,9 @@ static void runClient(int argc, char ** argv) silk::Logger::setLevel(silk::LogLevel::DEBUG); } } - catch (const po::error & ex) + catch (const cxxopts::exceptions::exception & ex) { - std::cerr << "error: " << ex.what() << "\n" << desc << "\n"; + std::cerr << "error: " << ex.what() << "\n" << cli.help() << "\n"; exit(1); } @@ -606,40 +604,37 @@ static void runServer(int argc, char ** argv) std::string delayStr = "0"; bool verbose = false; - namespace po = boost::program_options; - po::options_description desc("http-perf server options"); + cxxopts::Options cli("http-perf server", "http-perf server options"); // clang-format off - desc.add_options() - ("help,h", "show this help") - ("port", po::value(&cfg.port), "listen port") - ("queued", po::value(&cfg.maxQueued), "max queued connections (default: 4 * available CPUs)") - ("delay", po::value(&delayStr), "per-request response delay (e.g. 5ms, 100us)") - ("threads", po::bool_switch(&cfg.useThreads), "use OS threads instead of fibers") - ("print-counters", po::bool_switch(&cfg.printCounters), "enable per-CPU profiler and include counters in the JSON report") - ("verbose,v", po::bool_switch(&verbose), "enable debug logging") + cli.add_options() + ("h,help", "show this help") + ("port", "listen port", cxxopts::value(cfg.port)) + ("queued", "max queued connections (default: 4 * available CPUs)", cxxopts::value(cfg.maxQueued)) + ("delay", "per-request response delay (e.g. 5ms, 100us)", cxxopts::value(delayStr)) + ("threads", "use OS threads instead of fibers", cxxopts::value(cfg.useThreads)) + ("print-counters", "enable per-CPU profiler and include counters in the JSON report", cxxopts::value(cfg.printCounters)) + ("v,verbose", "enable debug logging", cxxopts::value(verbose)) ; // clang-format on - po::variables_map vm; try { - po::store(po::parse_command_line(argc, argv, desc), vm); - if (vm.count("help")) + auto result = cli.parse(argc, argv); + if (result.count("help")) { - std::cout << "usage: http-perf server [options]\n" << desc << "\n"; + std::cout << cli.help() << "\n"; return; } - po::notify(vm); cfg.delayNs = parseDuration(delayStr); if (verbose) { silk::Logger::setLevel(silk::LogLevel::DEBUG); } } - catch (const po::error & ex) + catch (const cxxopts::exceptions::exception & ex) { - std::cerr << "error: " << ex.what() << "\n" << desc << "\n"; + std::cerr << "error: " << ex.what() << "\n" << cli.help() << "\n"; exit(1); } diff --git a/src/perf/net-perf-asio.cpp b/src/perf/net-perf-asio.cpp index 13bbf8c..0b4a5f1 100644 --- a/src/perf/net-perf-asio.cpp +++ b/src/perf/net-perf-asio.cpp @@ -8,7 +8,6 @@ #include #include -#include #include #include @@ -24,6 +23,8 @@ #include +#include + namespace asio = boost::asio; using tcp = asio::ip::tcp; @@ -373,43 +374,39 @@ static void printJson(std::vector & latNs, const ClientConfig & cfg) static void runServer(int argc, char ** argv) { ServerConfig cfg; + std::string delayStr = "0"; bool verbose = false; - namespace po = boost::program_options; - po::options_description desc("net-perf-asio server options"); - - std::string delayStr = "0"; + cxxopts::Options cli("net-perf-asio server", "net-perf-asio server options"); // clang-format off - desc.add_options() - ("help,h", "show this help") - ("host", po::value(&cfg.host), "listen host") - ("port", po::value(&cfg.port), "listen port") - ("msg-size", po::value(&cfg.msgSize), "echo message size in bytes") - ("delay", po::value(&delayStr), "server-side delay per message (e.g. 1ms, 100us)") - ("verbose,v", po::bool_switch(&verbose), "enable debug logging") + cli.add_options() + ("h,help", "show this help") + ("host", "listen host", cxxopts::value(cfg.host)) + ("port", "listen port", cxxopts::value(cfg.port)) + ("msg-size", "echo message size in bytes", cxxopts::value(cfg.msgSize)) + ("delay", "server-side delay per message (e.g. 1ms, 100us)", cxxopts::value(delayStr)) + ("v,verbose", "enable debug logging", cxxopts::value(verbose)) ; // clang-format on - po::variables_map vm; try { - po::store(po::parse_command_line(argc, argv, desc), vm); - if (vm.count("help")) + auto result = cli.parse(argc, argv); + if (result.count("help")) { - std::cout << "usage: net-perf-asio server [options]\n" << desc << "\n"; + std::cout << cli.help() << "\n"; return; } - po::notify(vm); cfg.delayNs = parseDuration(delayStr); if (verbose) { silk::Logger::setLevel(silk::LogLevel::DEBUG); } } - catch (const po::error & ex) + catch (const cxxopts::exceptions::exception & ex) { - std::cerr << "error: " << ex.what() << "\n" << desc << "\n"; + std::cerr << "error: " << ex.what() << "\n" << cli.help() << "\n"; exit(1); } @@ -449,39 +446,36 @@ static void runClient(int argc, char ** argv) ClientConfig cfg; bool verbose = false; - namespace po = boost::program_options; - po::options_description desc("net-perf-asio client options"); + cxxopts::Options cli("net-perf-asio client", "net-perf-asio client options"); std::string durationStr = "10s"; std::string warmupStr = "2s"; std::string stallDurationStr = "0"; // clang-format off - desc.add_options() - ("help,h", "show this help") - ("host", po::value(&cfg.host), "server host") - ("port", po::value(&cfg.port), "server port") - ("connections", po::value(&cfg.numConnections), "parallel connections") - ("msg-size", po::value(&cfg.msgSize), "message size in bytes") - ("duration", po::value(&durationStr), "measurement duration (e.g. 10s, 500ms)") - ("warmup", po::value(&warmupStr), "warmup duration (e.g. 2s, 500ms)") - ("stall-rate", po::value(&cfg.stallRateHz), "per-connection Poisson rate of stall messages (Hz, 0 disables)") - ("stall-duration", po::value(&stallDurationStr), "stall duration per stall event (e.g. 100us, 1ms)") - ("print-counters", po::bool_switch(&cfg.printCounters), "include counters in the JSON report") - ("verbose,v", po::bool_switch(&verbose), "enable debug logging") + cli.add_options() + ("h,help", "show this help") + ("host", "server host", cxxopts::value(cfg.host)) + ("port", "server port", cxxopts::value(cfg.port)) + ("connections", "parallel connections", cxxopts::value(cfg.numConnections)) + ("msg-size", "message size in bytes", cxxopts::value(cfg.msgSize)) + ("duration", "measurement duration (e.g. 10s, 500ms)", cxxopts::value(durationStr)) + ("warmup", "warmup duration (e.g. 2s, 500ms)", cxxopts::value(warmupStr)) + ("stall-rate", "per-connection Poisson rate of stall messages (Hz, 0 disables)", cxxopts::value(cfg.stallRateHz)) + ("stall-duration", "stall duration per stall event (e.g. 100us, 1ms)", cxxopts::value(stallDurationStr)) + ("print-counters", "include counters in the JSON report", cxxopts::value(cfg.printCounters)) + ("v,verbose", "enable debug logging", cxxopts::value(verbose)) ; // clang-format on - po::variables_map vm; try { - po::store(po::parse_command_line(argc, argv, desc), vm); - if (vm.count("help")) + auto result = cli.parse(argc, argv); + if (result.count("help")) { - std::cout << "usage: net-perf-asio client [options]\n" << desc << "\n"; + std::cout << cli.help() << "\n"; return; } - po::notify(vm); cfg.durationNs = parseDuration(durationStr); cfg.warmupNs = parseDuration(warmupStr); cfg.stallNs = parseDuration(stallDurationStr); @@ -490,9 +484,9 @@ static void runClient(int argc, char ** argv) silk::Logger::setLevel(silk::LogLevel::DEBUG); } } - catch (const po::error & ex) + catch (const cxxopts::exceptions::exception & ex) { - std::cerr << "error: " << ex.what() << "\n" << desc << "\n"; + std::cerr << "error: " << ex.what() << "\n" << cli.help() << "\n"; exit(1); } diff --git a/src/perf/net-perf-epoll.cpp b/src/perf/net-perf-epoll.cpp index ecc0a63..6bdd208 100644 --- a/src/perf/net-perf-epoll.cpp +++ b/src/perf/net-perf-epoll.cpp @@ -6,8 +6,6 @@ #include #include -#include - #include #include #include @@ -27,6 +25,7 @@ #include #include +#include #include #include #include @@ -1181,33 +1180,30 @@ static void runServer(int argc, char ** argv) ServerConfig cfg; bool verbose = false; - namespace po = boost::program_options; - po::options_description desc("net-perf-epoll server options"); + cxxopts::Options cli("net-perf-epoll server", "net-perf-epoll server options"); std::string delayStr = "0"; // clang-format off - desc.add_options() - ("help,h", "show this help") - ("host", po::value(&cfg.host), "listen host") - ("port", po::value(&cfg.port), "listen port") - ("msg-size", po::value(&cfg.msgSize), "echo message size in bytes") - ("threads", po::value(&cfg.threads), "worker threads (each owns SO_REUSEPORT listener)") - ("delay", po::value(&delayStr), "server-side delay per message (must be 0; not supported)") - ("verbose,v", po::bool_switch(&verbose), "enable debug logging") + cli.add_options() + ("h,help", "show this help") + ("host", "listen host", cxxopts::value(cfg.host)) + ("port", "listen port", cxxopts::value(cfg.port)) + ("msg-size", "echo message size in bytes", cxxopts::value(cfg.msgSize)) + ("threads", "worker threads (each owns SO_REUSEPORT listener)", cxxopts::value(cfg.threads)) + ("delay", "server-side delay per message (must be 0; not supported)", cxxopts::value(delayStr)) + ("v,verbose", "enable debug logging", cxxopts::value(verbose)) ; // clang-format on - po::variables_map vm; try { - po::store(po::parse_command_line(argc, argv, desc), vm); - if (vm.count("help")) + auto result = cli.parse(argc, argv); + if (result.count("help")) { - std::cout << "usage: net-perf-epoll server [options]\n" << desc << "\n"; + std::cout << cli.help() << "\n"; return; } - po::notify(vm); if (parseDuration(delayStr) != 0) { std::cerr << "error: --delay is not supported by net-perf-epoll\n"; @@ -1222,10 +1218,10 @@ static void runServer(int argc, char ** argv) silk::Logger::setLevel(silk::LogLevel::DEBUG); } } - catch (const po::error & ex) + catch (const cxxopts::exceptions::exception & ex) { - std::cerr << "error: " << ex.what() << "\n" << desc << "\n"; - std::exit(1); + std::cerr << "error: " << ex.what() << "\n" << cli.help() << "\n"; + exit(1); } sigset_t mask = blockSignals(); @@ -1252,42 +1248,38 @@ static void runServer(int argc, char ** argv) static void runClient(int argc, char ** argv) { ClientConfig cfg; - bool verbose = false; - - namespace po = boost::program_options; - po::options_description desc("net-perf-epoll client options"); - std::string durationStr = "10s"; std::string warmupStr = "2s"; std::string stallDurationStr = "0"; + bool verbose = false; + + cxxopts::Options cli("net-perf-epoll client", "net-perf-epoll client options"); // clang-format off - desc.add_options() - ("help,h", "show this help") - ("host", po::value(&cfg.host), "server host") - ("port", po::value(&cfg.port), "server port") - ("connections", po::value(&cfg.numConnections), "parallel connections (split across threads)") - ("threads", po::value(&cfg.threads), "worker threads") - ("msg-size", po::value(&cfg.msgSize), "message size in bytes") - ("duration", po::value(&durationStr), "measurement duration (e.g. 10s, 500ms)") - ("warmup", po::value(&warmupStr), "warmup duration (e.g. 2s, 500ms)") - ("stall-rate", po::value(&cfg.stallRateHz), "per-connection Poisson rate of stall messages (Hz, 0 disables)") - ("stall-duration", po::value(&stallDurationStr), "stall duration per stall event (e.g. 100us, 1ms)") - ("print-counters", po::bool_switch(&cfg.printCounters), "include counters in the JSON report") - ("verbose,v", po::bool_switch(&verbose), "enable debug logging") + cli.add_options() + ("h,help", "show this help") + ("host", "server host", cxxopts::value(cfg.host)) + ("port", "server port", cxxopts::value(cfg.port)) + ("connections", "parallel connections (split across threads)", cxxopts::value(cfg.numConnections)) + ("threads", "worker threads", cxxopts::value(cfg.threads)) + ("msg-size", "message size in bytes", cxxopts::value(cfg.msgSize)) + ("duration", "measurement duration (e.g. 10s, 500ms)", cxxopts::value(durationStr)) + ("warmup", "warmup duration (e.g. 2s, 500ms)", cxxopts::value(warmupStr)) + ("stall-rate", "per-connection Poisson rate of stall messages (Hz, 0 disables)", cxxopts::value(cfg.stallRateHz)) + ("stall-duration", "stall duration per stall event (e.g. 100us, 1ms)", cxxopts::value(stallDurationStr)) + ("print-counters", "include counters in the JSON report", cxxopts::value(cfg.printCounters)) + ("v,verbose", "enable debug logging", cxxopts::value(verbose)) ; // clang-format on - po::variables_map vm; try { - po::store(po::parse_command_line(argc, argv, desc), vm); - if (vm.count("help")) + auto result = cli.parse(argc, argv); + if (result.count("help")) { - std::cout << "usage: net-perf-epoll client [options]\n" << desc << "\n"; + std::cout << cli.help() << "\n"; return; } - po::notify(vm); cfg.durationNs = parseDuration(durationStr); cfg.warmupNs = parseDuration(warmupStr); cfg.stallNs = parseDuration(stallDurationStr); @@ -1304,10 +1296,10 @@ static void runClient(int argc, char ** argv) silk::Logger::setLevel(silk::LogLevel::DEBUG); } } - catch (const po::error & ex) + catch (const cxxopts::exceptions::exception & ex) { - std::cerr << "error: " << ex.what() << "\n" << desc << "\n"; - std::exit(1); + std::cerr << "error: " << ex.what() << "\n" << cli.help() << "\n"; + exit(1); } sigset_t mask = blockSignals(); diff --git a/src/perf/net-perf.cpp b/src/perf/net-perf.cpp index 619d617..3d27018 100644 --- a/src/perf/net-perf.cpp +++ b/src/perf/net-perf.cpp @@ -9,8 +9,6 @@ #include #include -#include - #include #include #include @@ -27,6 +25,7 @@ #include #include +#include #include #include #include @@ -737,42 +736,39 @@ static void runServer(int argc, char ** argv) ServerConfig cfg; bool verbose = false; - namespace po = boost::program_options; - po::options_description desc("net-perf server options"); + cxxopts::Options cli("net-perf server", "net-perf server options"); std::string delayStr = "0"; // clang-format off - desc.add_options() - ("help,h", "show this help") - ("host", po::value(&cfg.host), "listen host") - ("port", po::value(&cfg.port), "listen port") - ("msg-size", po::value(&cfg.msgSize), "echo message size in bytes") - ("delay", po::value(&delayStr), "server-side delay per message (e.g. 1ms, 100us)") - ("print-counters", po::bool_switch(&cfg.printCounters), "enable per-CPU profiler and include counters in the JSON report") - ("verbose,v", po::bool_switch(&verbose), "enable debug logging") + cli.add_options() + ("h,help", "show this help") + ("host", "listen host", cxxopts::value(cfg.host)) + ("port", "listen port", cxxopts::value(cfg.port)) + ("msg-size", "echo message size in bytes", cxxopts::value(cfg.msgSize)) + ("delay", "server-side delay per message (e.g. 1ms, 100us)", cxxopts::value(delayStr)) + ("print-counters", "enable per-CPU profiler and include counters in the JSON report", cxxopts::value(cfg.printCounters)) + ("v,verbose", "enable debug logging", cxxopts::value(verbose)) ; // clang-format on - po::variables_map vm; try { - po::store(po::parse_command_line(argc, argv, desc), vm); - if (vm.count("help")) + auto result = cli.parse(argc, argv); + if (result.count("help")) { - std::cout << "usage: net-perf server [options]\n" << desc << "\n"; + std::cout << cli.help() << "\n"; return; } - po::notify(vm); cfg.delayNs = parseDuration(delayStr); if (verbose) { silk::Logger::setLevel(silk::LogLevel::DEBUG); } } - catch (const po::error & ex) + catch (const cxxopts::exceptions::exception & ex) { - std::cerr << "error: " << ex.what() << "\n" << desc << "\n"; + std::cerr << "error: " << ex.what() << "\n" << cli.help() << "\n"; exit(1); } @@ -813,41 +809,37 @@ static void runServer(int argc, char ** argv) static void runClient(int argc, char ** argv) { ClientConfig cfg; - bool verbose = false; - - namespace po = boost::program_options; - po::options_description desc("net-perf client options"); - std::string durationStr = "10s"; std::string warmupStr = "2s"; std::string stallDurationStr = "0"; + bool verbose = false; + + cxxopts::Options cli("net-perf client", "net-perf client options"); // clang-format off - desc.add_options() - ("help,h", "show this help") - ("host", po::value(&cfg.host), "server host") - ("port", po::value(&cfg.port), "server port") - ("connections", po::value(&cfg.numConnections), "parallel connections") - ("msg-size", po::value(&cfg.msgSize), "message size in bytes") - ("duration", po::value(&durationStr), "measurement duration (e.g. 10s, 500ms)") - ("warmup", po::value(&warmupStr), "warmup duration (e.g. 2s, 500ms)") - ("stall-rate", po::value(&cfg.stallRateHz), "per-connection Poisson rate of stall messages (Hz, 0 disables)") - ("stall-duration", po::value(&stallDurationStr), "stall duration per stall event (e.g. 100us, 1ms)") - ("print-counters", po::bool_switch(&cfg.printCounters), "enable per-CPU profiler and include counters in the JSON report") - ("verbose,v", po::bool_switch(&verbose), "enable debug logging") + cli.add_options() + ("h,help", "show this help") + ("host", "server host", cxxopts::value(cfg.host)) + ("port", "server port", cxxopts::value(cfg.port)) + ("connections", "parallel connections", cxxopts::value(cfg.numConnections)) + ("msg-size", "message size in bytes", cxxopts::value(cfg.msgSize)) + ("duration", "measurement duration (e.g. 10s, 500ms)", cxxopts::value(durationStr)) + ("warmup", "warmup duration (e.g. 2s, 500ms)", cxxopts::value(warmupStr)) + ("stall-rate", "per-connection Poisson rate of stall messages (Hz, 0 disables)", cxxopts::value(cfg.stallRateHz)) + ("stall-duration", "stall duration per stall event (e.g. 100us, 1ms)", cxxopts::value(stallDurationStr)) + ("print-counters", "enable per-CPU profiler and include counters in the JSON report", cxxopts::value(cfg.printCounters)) + ("v,verbose", "enable debug logging", cxxopts::value(verbose)) ; // clang-format on - po::variables_map vm; try { - po::store(po::parse_command_line(argc, argv, desc), vm); - if (vm.count("help")) + auto result = cli.parse(argc, argv); + if (result.count("help")) { - std::cout << "usage: net-perf client [options]\n" << desc << "\n"; + std::cout << cli.help() << "\n"; return; } - po::notify(vm); cfg.durationNs = parseDuration(durationStr); cfg.warmupNs = parseDuration(warmupStr); cfg.stallNs = parseDuration(stallDurationStr); @@ -856,9 +848,9 @@ static void runClient(int argc, char ** argv) silk::Logger::setLevel(silk::LogLevel::DEBUG); } } - catch (const po::error & ex) + catch (const cxxopts::exceptions::exception & ex) { - std::cerr << "error: " << ex.what() << "\n" << desc << "\n"; + std::cerr << "error: " << ex.what() << "\n" << cli.help() << "\n"; exit(1); } diff --git a/src/perf/s3-perf.cpp b/src/perf/s3-perf.cpp index 3826307..13ac365 100644 --- a/src/perf/s3-perf.cpp +++ b/src/perf/s3-perf.cpp @@ -31,7 +31,6 @@ #include #include #include -#include #include #include @@ -48,6 +47,8 @@ #include #include +#include + // // I/O modes. // @@ -739,40 +740,37 @@ int main(int argc, char ** argv) std::string warmupStr = "2s"; bool verbose = false; - namespace po = boost::program_options; - po::options_description desc("s3-perf options"); + cxxopts::Options cli("s3-perf", "s3-perf options"); // clang-format off - desc.add_options() - ("help,h", "show this help") - ("endpoint", po::value(&cfg.endpointUrl), "S3 endpoint URL (e.g. http://127.0.0.1:9000)") - ("region", po::value(&cfg.region), "AWS region") - ("bucket", po::value(&cfg.bucket), "S3 bucket") - ("key", po::value(&cfg.key), "object key") - ("access-key", po::value(&cfg.accessKeyId), "AWS access key ID") - ("secret-key", po::value(&cfg.secretAccessKey), "AWS secret access key") - ("size", po::value(&cfg.objectSize), "object size in bytes (for write)") - ("numjobs", po::value(&cfg.numJobs), "concurrent session threads") - ("iodepth", po::value(&cfg.ioDepth), "parallel S3 requests per session") - ("rw", po::value(&rwStr), "I/O mode: read | write | readwrite") - ("threads", po::bool_switch(&cfg.useThreads), "use SDK thread executor instead of FiberExecutor") - ("duration", po::value(&durationStr), "measurement duration (e.g. 10s, 500ms)") - ("warmup", po::value(&warmupStr), "warmup duration (e.g. 2s, 500ms)") - ("print-counters", po::bool_switch(&cfg.printCounters), "enable per-CPU profiler and include counters in the JSON report") - ("verbose,v", po::bool_switch(&verbose), "enable debug logging") + cli.add_options() + ("h,help", "show this help") + ("endpoint", "S3 endpoint URL (e.g. http://127.0.0.1:9000)", cxxopts::value(cfg.endpointUrl)) + ("region", "AWS region", cxxopts::value(cfg.region)) + ("bucket", "S3 bucket", cxxopts::value(cfg.bucket)) + ("key", "object key", cxxopts::value(cfg.key)) + ("access-key", "AWS access key ID", cxxopts::value(cfg.accessKeyId)) + ("secret-key", "AWS secret access key", cxxopts::value(cfg.secretAccessKey)) + ("size", "object size in bytes (for write)", cxxopts::value(cfg.objectSize)) + ("numjobs", "concurrent session threads", cxxopts::value(cfg.numJobs)) + ("iodepth", "parallel S3 requests per session", cxxopts::value(cfg.ioDepth)) + ("rw", "I/O mode: read | write | readwrite", cxxopts::value(rwStr)) + ("threads", "use SDK thread executor instead of FiberExecutor", cxxopts::value(cfg.useThreads)) + ("duration", "measurement duration (e.g. 10s, 500ms)", cxxopts::value(durationStr)) + ("warmup", "warmup duration (e.g. 2s, 500ms)", cxxopts::value(warmupStr)) + ("print-counters", "enable per-CPU profiler and include counters in the JSON report", cxxopts::value(cfg.printCounters)) + ("v,verbose", "enable debug logging", cxxopts::value(verbose)) ; // clang-format on - po::variables_map vm; try { - po::store(po::parse_command_line(argc, argv, desc), vm); - if (vm.count("help")) + auto result = cli.parse(argc, argv); + if (result.count("help")) { - std::cout << "usage: s3-perf [options]\n" << desc << "\n"; + std::cout << cli.help() << "\n"; return 0; } - po::notify(vm); cfg.durationNs = parseDuration(durationStr); cfg.warmupNs = parseDuration(warmupStr); if (verbose) @@ -780,9 +778,9 @@ int main(int argc, char ** argv) silk::Logger::setLevel(silk::LogLevel::DEBUG); } } - catch (const po::error & ex) + catch (const cxxopts::exceptions::exception & ex) { - std::cerr << "error: " << ex.what() << "\n" << desc << "\n"; + std::cerr << "error: " << ex.what() << "\n" << cli.help() << "\n"; return 1; } diff --git a/src/profiler/CMakeLists.txt b/src/profiler/CMakeLists.txt index 96c66c9..deb450c 100644 --- a/src/profiler/CMakeLists.txt +++ b/src/profiler/CMakeLists.txt @@ -94,6 +94,6 @@ add_executable(profiler main.cpp profiler.cpp) add_dependencies(profiler profiler-skel) target_include_directories(profiler PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) -target_link_libraries(profiler PRIVATE Libbpf::Libbpf symbolizer Boost::program_options) +target_link_libraries(profiler PRIVATE Libbpf::Libbpf symbolizer cxxopts::cxxopts) add_subdirectory(tests) diff --git a/src/profiler/main.cpp b/src/profiler/main.cpp index 02d9ff8..a9c85e9 100644 --- a/src/profiler/main.cpp +++ b/src/profiler/main.cpp @@ -3,8 +3,6 @@ #include -#include - #include #include #include @@ -13,6 +11,7 @@ #include #include +#include #include #include @@ -72,40 +71,42 @@ int main(int argc, char ** argv) bool offcpu = false; bool verbose = false; - namespace po = boost::program_options; - po::options_description desc("profiler options"); + cxxopts::Options cli("profiler", "profiler options"); // clang-format off - desc.add_options() - ("help,h", "show this help") - ("pid", po::value(&targetPid)->required(), "target process ID") - ("hz", po::value(&sampleHz)->default_value(sampleHz), "on-CPU sampling frequency") - ("duration", po::value(&durationSec)->default_value(durationSec), "run for N seconds (0 = until Ctrl+C)") - ("kernel-stacks", po::bool_switch(&kernelStacks), "capture kernel stack frames") - ("on-cpu", po::bool_switch(&oncpu), "capture on-CPU stack samples") - ("off-cpu", po::bool_switch(&offcpu), "capture off-CPU blocking time") - ("verbose,v", po::bool_switch(&verbose), "enable debug logging") + cli.add_options() + ("h,help", "show this help") + ("pid", "target process ID", cxxopts::value(targetPid)) + ("hz", "on-CPU sampling frequency", cxxopts::value(sampleHz)) + ("duration", "run for N seconds (0 = until Ctrl+C)", cxxopts::value(durationSec)) + ("kernel-stacks", "capture kernel stack frames", cxxopts::value(kernelStacks)) + ("on-cpu", "capture on-CPU stack samples", cxxopts::value(oncpu)) + ("off-cpu", "capture off-CPU blocking time", cxxopts::value(offcpu)) + ("v,verbose", "enable debug logging", cxxopts::value(verbose)) ; // clang-format on - po::variables_map vm; try { - po::store(po::parse_command_line(argc, argv, desc), vm); - if (vm.count("help")) + auto result = cli.parse(argc, argv); + if (result.count("help")) { - std::cout << "usage: profiler [options]\n" << desc << "\n"; + std::cout << cli.help() << "\n"; return 0; } - po::notify(vm); + if (result.count("pid") == 0) + { + std::cerr << "error: --pid is required\n" << cli.help() << "\n"; + return 1; + } if (verbose) { silk::Logger::setLevel(silk::LogLevel::DEBUG); } } - catch (const po::error & ex) + catch (const cxxopts::exceptions::exception & ex) { - std::cerr << "error: " << ex.what() << "\n" << desc << "\n"; + std::cerr << "error: " << ex.what() << "\n" << cli.help() << "\n"; return 1; }