diff options
| author | Stefan Boberg <[email protected]> | 2023-11-21 14:52:56 +0100 |
|---|---|---|
| committer | GitHub <[email protected]> | 2023-11-21 14:52:56 +0100 |
| commit | 8e1cb139d817880c557d407c363eb838c0893f5a (patch) | |
| tree | 69da94b1df3f2fd1d19e623084c88543c0ce85f6 /src/zen/cmds/run_cmd.cpp | |
| parent | basic ZEN_ASSERT_FORMAT implementation (#556) (diff) | |
| download | archived-zen-8e1cb139d817880c557d407c363eb838c0893f5a.tar.xz archived-zen-8e1cb139d817880c557d407c363eb838c0893f5a.zip | |
zen run command (#552)
initial version -- this is primarily intended to be used for running stress tests and/or benchmarks
example usage:
`zen run -n 10 -- zenserver-test`
`zen run -n 10 -- zenserver-test --ts=core.assert` run zenserver-test 10 times (testing only the `core.assert` test suite)
`zen run --time 600 --basepath=d:\test_dir\test1 -- zenserver-test` keeps spawning new instances for 10 minutes (600 seconds)
Diffstat (limited to 'src/zen/cmds/run_cmd.cpp')
| -rw-r--r-- | src/zen/cmds/run_cmd.cpp | 197 |
1 files changed, 197 insertions, 0 deletions
diff --git a/src/zen/cmds/run_cmd.cpp b/src/zen/cmds/run_cmd.cpp new file mode 100644 index 000000000..a99ba9704 --- /dev/null +++ b/src/zen/cmds/run_cmd.cpp @@ -0,0 +1,197 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "run_cmd.h" + +#include <zencore/filesystem.h> +#include <zencore/fmtutils.h> +#include <zencore/logging.h> +#include <zencore/process.h> +#include <zencore/string.h> +#include <zencore/timer.h> + +using namespace std::literals; + +#define ZEN_COLOR_BLACK "\033[0;30m" +#define ZEN_COLOR_RED "\033[0;31m" +#define ZEN_COLOR_GREEN "\033[0;32m" +#define ZEN_COLOR_YELLOW "\033[0;33m" +#define ZEN_COLOR_BLUE "\033[0;34m" +#define ZEN_COLOR_MAGENTA "\033[0;35m" +#define ZEN_COLOR_CYAN "\033[0;36m" +#define ZEN_COLOR_WHITE "\033[0;37m" + +#define ZEN_BRIGHT_COLOR_BLACK "\033[1;30m" +#define ZEN_BRIGHT_COLOR_RED "\033[1;31m" +#define ZEN_BRIGHT_COLOR_GREEN "\033[1;32m" +#define ZEN_BRIGHT_COLOR_YELLOW "\033[1;33m" +#define ZEN_BRIGHT_COLOR_BLUE "\033[1;34m" +#define ZEN_BRIGHT_COLOR_MAGENTA "\033[1;35m" +#define ZEN_BRIGHT_COLOR_CYAN "\033[1;36m" +#define ZEN_BRIGHT_COLOR_WHITE "\033[1;37m" + +#define ZEN_COLOR_RESET "\033[0m" + +namespace zen { + +RunCommand::RunCommand() +{ + m_Options.add_options()("h,help", "Print help"); + m_Options.add_option("", "n", "count", "Number of times to run command", cxxopts::value(m_RunCount), "<count>"); + m_Options.add_option("", "t", "time", "How long to run command(s) for", cxxopts::value(m_RunTime), "<seconds>"); + m_Options.add_option("", + "", + "basepath", + "Where to run command. Each run will execute in its own numbered subdirectory below this directory. Additionally, " + "stdout will be redirected to a file in the provided directory", + cxxopts::value(m_BaseDirectory), + "<path>"); + m_Options.add_option("", + "", + "max-dirs", + "Number of base directories to retain on rotation", + cxxopts::value(m_MaxBaseDirectoryCount), + "<count>"); +} + +RunCommand::~RunCommand() +{ +} + +int +RunCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) +{ + ZEN_UNUSED(GlobalOptions); + + if (!ParseOptions(argc, argv)) + { + return 0; + } + + // Validate arguments + + if (GlobalOptions.PassthroughArgV.empty() || GlobalOptions.PassthroughArgV[0].empty()) + throw OptionParseException("No command specified. The command to run is passed in after a double dash ('--') on the command line"); + + if (m_RunCount < 0) + throw OptionParseException("Invalid count specified"); + + if (m_RunTime < -1 || m_RunTime == 0) + throw OptionParseException("Invalid run time specified"); + + if (m_MaxBaseDirectoryCount < 0) + throw OptionParseException("Invalid directory count specified"); + + if (m_RunTime > 0 && m_RunCount > 0) + throw OptionParseException("Specify either time or count, not both"); + + if (m_RunCount == 0) + m_RunCount = 1; + + std::filesystem::path BaseDirectory; + + if (!m_BaseDirectory.empty()) + { + BaseDirectory = m_BaseDirectory; + + if (m_MaxBaseDirectoryCount) + { + RotateDirectories(BaseDirectory, m_MaxBaseDirectoryCount); + CreateDirectories(BaseDirectory); + } + else + { + CleanDirectory(BaseDirectory); + } + } + + bool TimedRun = false; + auto CommandStartTime = std::chrono::system_clock::now(); + auto CommandDeadlineTime = std::chrono::system_clock::now(); + + if (m_RunTime > 0) + { + m_RunCount = 1'000'000; + TimedRun = true; + + CommandDeadlineTime += std::chrono::seconds(m_RunTime); + } + + struct RunResults + { + int ExitCode = 0; + std::chrono::duration<long, std::milli> Duration{}; + }; + + std::vector<RunResults> Results; + int ErrorCount = 0; + + std::filesystem::path ExecutablePath = SearchPathForExecutable(GlobalOptions.PassthroughArgV[0]); + std::string CommandArguments = GlobalOptions.PassthroughArgs; + + for (int i = 0; i < m_RunCount; ++i) + { + std::filesystem::path RunDir; + if (!BaseDirectory.empty()) + { + RunDir = BaseDirectory / IntNum(i + 1).c_str(); + CreateDirectories(RunDir); + } + + Stopwatch Timer; + + CreateProcOptions ProcOptions; + + if (!RunDir.empty()) + { + ProcOptions.WorkingDirectory = &RunDir; + ProcOptions.StdoutFile = RunDir / "stdout.txt"; + } + + fmt::print(ZEN_BRIGHT_COLOR_WHITE "run #{}" ZEN_COLOR_RESET ": {}\n", i + 1, GlobalOptions.PassthroughCommandLine); + + ProcessHandle Proc; + Proc.Initialize(CreateProc(ExecutablePath, GlobalOptions.PassthroughCommandLine, ProcOptions)); + if (!Proc.IsValid()) + { + throw std::runtime_error(fmt::format("failed to launch '{}'", ExecutablePath)); + } + + int ExitCode = Proc.WaitExitCode(); + + auto EndTime = std::chrono::system_clock::now(); + + if (ExitCode) + ++ErrorCount; + + Results.emplace_back(RunResults{.ExitCode = ExitCode, .Duration = std::chrono::milliseconds(Timer.GetElapsedTimeMs())}); + + if (TimedRun) + { + if (EndTime >= CommandDeadlineTime) + { + m_RunCount = i + 1; + break; + } + } + } + + fmt::print("{:>5} {:>3} {:>6}\n", "run", "rc", "time"); + int i = 0; + for (const auto& Entry : Results) + { + fmt::print("{:5} {:3} {:>6}\n", ++i, Entry.ExitCode, NiceTimeSpanMs(Entry.Duration.count())); + } + + if (ErrorCount) + { + fmt::print("run complete ({}/{} failed)\n", ErrorCount, m_RunCount); + } + else + { + fmt::print("run complete, no error exit code\n", m_RunCount); + } + + return 0; +} + +} // namespace zen |