Add -n flag, change --no-aslr to --aslr, add --stdout and --stderr, chown outputs when run with sudo

This commit is contained in:
Carl Pearson
2019-09-30 11:51:04 -05:00
parent 158bffa61f
commit 1695ebb8ea
4 changed files with 1851 additions and 37 deletions

View File

@@ -59,7 +59,46 @@ If you don't have CUDA, then you could just do
g++ code_using_perfect.cpp -I perfect/include
```
## Usage
## Tools Usage
`perfect` provides some useful tools on Linux:
```
$ tools/perfect-cli -h
SYNOPSIS
./tools/perfect-cli --no-mod [-n <INT>] -- <cmd>...
./tools/perfect-cli ([-u <INT>] | [-s <INT>]) [--no-drop-cache] [--no-max-perf] [--aslr]
[--cpu-turbo] [--stdout <PATH>] [--stderr <PATH>] [-n <INT>] -- <cmd>...
OPTIONS
--no-mod don't control performance
-u number of unshielded CPUs
-s number of shielded CPUs
--no-drop-cache do not drop filesystem caches
--no-max-perf do not max os perf
--aslr enable ASLR
--cpu-turbo enable CPU turbo
--stdout redirect child stdout
--stderr redirect child stderr
-n run multiple times
```
The basic usage is `tools/perfect-cli -- my-exe`, which will attempt to configure the system for repeatable performance before executing `my-exe`, and then restore the system to the original performance state before exiting.
Most modifications require elevated privileges.
The default behavior is to:
* disable ASLR
* drop filesystem caches
* set CPU performance to maximum
* disable CPU turbo
Some options (all should provided before the `--` option):
* `--no-mod` flag will cause `perfect-cli` to not modify the system performance state
* `-n INT` will run the requested program `INT` times.
* `--stderr`/`--stdout` will redirect the program-under-test's stderr and stdout to the provided paths.
* `-s`/`-u`: set the number of shielded /unshielded CPUs. The program-under-test will run on the shielded CPUs. All other tasks will run on the unshielded CPUs.
## API Usage
The `perfect` functions all return a `perfect::Result`, which is defined in [include/perfect/result.hpp].
When things are working, it will be `perfect::Result::SUCCESS`.
@@ -274,3 +313,4 @@ heap: 93824994414192
## Acks
Uses [muellan/clipp](https://github.com/muellan/clipp) for cli option parsing.
Uses [martinmoene/optional-lite](https://github.com/martinmoene/optional-lite).

View File

@@ -12,12 +12,17 @@
#include <nvml.h>
#endif
#ifdef __linux__
#include <cerrno>
#endif
namespace perfect {
enum class Result {
NO_PERMISSION,
NOT_SUPPORTED,
NO_TASK,
NVML_NO_PERMISSION,
NVML_NOT_SUPPORTED,
NVML_UNINITIALIZED,
@@ -39,6 +44,23 @@ Result from_nvml(nvmlReturn_t nvml) {
case NVML_ERROR_INVALID_ARGUMENT:
case NVML_ERROR_GPU_IS_LOST:
case NVML_ERROR_UNKNOWN:
case NVML_ERROR_ALREADY_INITIALIZED:
case NVML_ERROR_NOT_FOUND:
case NVML_ERROR_INSUFFICIENT_SIZE:
case NVML_ERROR_INSUFFICIENT_POWER:
case NVML_ERROR_DRIVER_NOT_LOADED:
case NVML_ERROR_TIMEOUT:
case NVML_ERROR_IRQ_ISSUE:
case NVML_ERROR_LIBRARY_NOT_FOUND:
case NVML_ERROR_FUNCTION_NOT_FOUND:
case NVML_ERROR_CORRUPTED_INFOROM:
case NVML_ERROR_RESET_REQUIRED:
case NVML_ERROR_OPERATING_SYSTEM:
case NVML_ERROR_LIB_RM_VERSION_MISMATCH:
case NVML_ERROR_IN_USE:
case NVML_ERROR_MEMORY:
case NVML_ERROR_NO_DATA:
case NVML_ERROR_VGPU_ECC_NOT_SUPPORTED:
default:
assert(0 && "unhandled nvmlReturn_t");
}
@@ -46,6 +68,16 @@ Result from_nvml(nvmlReturn_t nvml) {
}
#endif
#ifdef __linux__
Result from_errno(int err) {
switch (err) {
default:
assert(0 && "unhandled errno");
}
return Result::UNKNOWN;
}
#endif
const char *get_string(const Result &result) {
switch (result) {
case Result::SUCCESS:

View File

@@ -4,21 +4,31 @@
#include <string>
#include <vector>
#ifdef __linux__
#include <fcntl.h>
#include <pwd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#else
#error "unsupported platform"
#endif
#include "clipp/clipp.h"
#include "nonstd/optional.hpp"
#include "perfect/aslr.hpp"
#include "perfect/cpu_set.hpp"
#include "perfect/cpu_turbo.hpp"
#include "perfect/os_perf.hpp"
#include "perfect/detail/os/linux.hpp"
#include "perfect/drop_caches.hpp"
#include "perfect/os_perf.hpp"
// argv should be null-terminated
int fork_child(char *const *argv) {
// outf and errf are file descriptors to where stdout and stderr should be
// redirected write stdout to out and stderr to err, if not null
int fork_child(char *const *argv, int outf, int errf) {
pid_t pid;
int status;
@@ -30,7 +40,73 @@ int fork_child(char *const *argv) {
} else if (pid == 0) {
// in the child process
// skip the first argument, which is this program
if (outf > 0) {
std::cerr << "redirecting child stdout to file\n";
if (dup2(outf, 1)) {
std::cerr << "dup2 error: " << strerror(errno) << "\n";
/*
EBADF
oldfd isn't an open file descriptor, or newfd is out of the allowed
range for file descriptors. EBUSY (Linux only) This may be returned by
dup2() or dup3() during a race condition with open(2) and dup(). EINTR The
dup2() or dup3() call was interrupted by a signal; see signal(7). EINVAL
(dup3()) flags contain an invalid value. Or, oldfd was equal to newfd.
EMFILE
The process already has the maximum number of file descriptors open and
tried to open a new one.
*/
}
if (close(outf)) {
/*
EBADF
The fildes argument is not a valid file descriptor.
EINTR
The close() function was interrupted by a signal.
The close() function may fail if:
EIO
An I/O error occurred while reading from or writing to the file
system.
*/
}
}
if (errf > 0) {
std::cerr << "redirecting child stderr to file\n";
if (dup2(errf, 2)) {
std::cerr << "dup2 error: " << strerror(errno) << "\n";
/*
EBADF
oldfd isn't an open file descriptor, or newfd is out of the allowed
range for file descriptors. EBUSY (Linux only) This may be returned by
dup2() or dup3() during a race condition with open(2) and dup(). EINTR The
dup2() or dup3() call was interrupted by a signal; see signal(7). EINVAL
(dup3()) flags contain an invalid value. Or, oldfd was equal to newfd.
EMFILE
The process already has the maximum number of file descriptors open and
tried to open a new one.
*/
}
if (close(errf)) {
/*
EBADF
The fildes argument is not a valid file descriptor.
EINTR
The close() function was interrupted by a signal.
The close() function may fail if:
EIO
An I/O error occurred while reading from or writing to the file system.
*/
}
}
// the execv() only return if error occured.
// The return value is -1
@@ -71,34 +147,104 @@ int main(int argc, char **argv) {
size_t numUnshielded = 0;
size_t numShielded = 0;
bool aslr = false;
bool cpuTurbo = false;
bool maxOsPerf = true;
nonstd::optional<bool> cpuTurbo = false;
nonstd::optional<bool> maxOsPerf = true;
bool dropCaches = true;
std::vector<std::string> program;
std::string stdoutPath;
std::string stderrPath;
int iters = 1;
auto shieldGroup =
((option("-u") &
value("UNSH", numUnshielded).doc("number of unshielded CPUs")) |
(option("-s") &
value("SH", numShielded).doc("number of shielded CPUs")));
auto shieldGroup = ((option("-u").doc("number of unshielded CPUs") &
value("INT", numUnshielded)) |
(option("-s").doc("number of shielded CPUs") &
value("INT", numShielded)));
auto noModMode = (option("--no-mod")
.doc("don't control performance")
.set(aslr, true)
.call([&]() { cpuTurbo = nonstd::nullopt; })
.call([&]() { maxOsPerf = nonstd::nullopt; })
.set(dropCaches, false));
auto cli = (shieldGroup,
option("--no-drop-cache").set(dropCaches, false).doc("do not drop filesystem caches"),
option("--no-max-perf").set(maxOsPerf, false).doc("do not max os perf"),
option("--no-aslr").set(aslr, false).doc("disable ASLR"),
option("--cpu-turbo").set(cpuTurbo, true).doc("enable CPU turbo"),
auto modMode = (shieldGroup,
option("--no-drop-cache")
.set(dropCaches, false)
.doc("do not drop filesystem caches"),
option("--no-max-perf").doc("do not max os perf").call([&]() {
maxOsPerf = false;
}),
option("--aslr").set(aslr, true).doc("enable ASLR"),
option("--cpu-turbo").doc("enable CPU turbo").call([&]() {
cpuTurbo = true;
}),
(option("--stdout").doc("redirect child stdout") &
value("PATH", stdoutPath)),
(option("--stderr").doc("redirect child stderr") &
value("PATH", stderrPath)));
auto cli = ((noModMode | modMode),
(option("-n").doc("run multiple times") & value("INT", iters)),
// run everything after "--"
required("--") & greedy(values("cmd", program))
);
if (!parse(argc, argv, cli)) {
std::cout << make_man_page(cli, argv[0]);
auto fmt = doc_formatting{}.doc_column(31);
std::cout << make_man_page(cli, argv[0], fmt);
return -1;
}
// open the redirect files, if needed
int errf = 0;
int outf = 0;
if (!stderrPath.empty()) {
std::cerr << "open " << stderrPath << "\n";
errf = open(stderrPath.c_str(), O_WRONLY | O_CREAT,
S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
if (-1 == errf) {
std::cerr << "error while opening " << stderrPath << ": "
<< strerror(errno) << "\n";
}
}
if (!stdoutPath.empty()) {
outf = open(stdoutPath.c_str(), O_WRONLY | O_CREAT,
S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
if (-1 == outf) {
std::cerr << "error while opening " << stdoutPath << ": "
<< strerror(errno) << "\n";
}
}
// if called with sudo, chown the files to whoever called sudo
const char *sudoUser = std::getenv("SUDO_USER");
if (sudoUser) {
std::cerr << "called with sudo by " << sudoUser << "\n";
uid_t uid;
gid_t gid;
struct passwd *pwd;
pwd = getpwnam(sudoUser);
if (pwd == NULL) {
// die("Failed to get uid");
}
uid = pwd->pw_uid;
gid = pwd->pw_gid;
if (!stdoutPath.empty()) {
if (chown(stdoutPath.c_str(), uid, gid) == -1) {
// die("chown fail");
}
}
if (!stderrPath.empty()) {
if (chown(stderrPath.c_str(), uid, gid) == -1) {
// die("chown fail");
}
}
}
// exec the rest of the options
std::vector<char *> args;
for (auto &c : program) {
@@ -154,23 +300,27 @@ int main(int argc, char **argv) {
// handle CPU turbo
perfect::CpuTurboState cpuTurboState;
PERFECT(perfect::get_cpu_turbo_state(&cpuTurboState));
if (!cpuTurbo) {
std::cerr << "disabling cpu turbo\n";
PERFECT(perfect::disable_cpu_turbo());
} else {
std::cerr << "enabling cpu turbo\n";
PERFECT(perfect::enable_cpu_turbo());
if (cpuTurbo.has_value()) {
PERFECT(perfect::get_cpu_turbo_state(&cpuTurboState));
if (false == cpuTurbo) {
std::cerr << "disabling cpu turbo\n";
PERFECT(perfect::disable_cpu_turbo());
} else {
std::cerr << "enabling cpu turbo\n";
PERFECT(perfect::enable_cpu_turbo());
}
}
// handle governor
perfect::OsPerfState osPerfState;
if (maxOsPerf) {
std::cerr << "set max performance state\n";
if (maxOsPerf.has_value()) {
PERFECT(perfect::get_os_perf_state(osPerfState));
for (auto cpu : perfect::cpus()) {
PERFECT(perfect::os_perf_state_maximum(cpu));
}
if (true == maxOsPerf) {
std::cerr << "set max performance state\n";
for (auto cpu : perfect::cpus()) {
PERFECT(perfect::os_perf_state_maximum(cpu));
}
}
}
// handle file system caches
@@ -180,13 +330,18 @@ int main(int argc, char **argv) {
}
// parent should return
std::cerr << "exec ";
for (size_t i = 0; i < args.size() - 1; ++i) {
std::cerr << args[i] << " ";
for (int runIter = 0; runIter < iters; ++runIter) {
std::cerr << "exec ";
for (size_t i = 0; i < args.size() - 1; ++i) {
std::cerr << args[i] << " ";
}
std::cerr << "\n";
int status = fork_child(args.data(), outf, errf);
if (0 != status) {
std::cerr << "did not terminate successfully\n";
}
std::cerr << "finished execution\n";
}
std::cerr << "\n";
int status = fork_child(args.data());
std::cerr << "finished execution\n";
// clean up CpuSets (if needed)
if (numShielded) {
@@ -196,13 +351,15 @@ int main(int argc, char **argv) {
}
// restore original turbo state
if (cpuTurbo.has_value()) {
std::cerr << "restore CPU turbo\n";
PERFECT(perfect::set_cpu_turbo_state(cpuTurboState));
}
if (maxOsPerf) {
if (maxOsPerf.has_value()) {
std::cerr << "restore os performance state\n";
PERFECT(perfect::set_os_perf_state(osPerfState));
}
return status;
return 0;
}

1585
tools/thirdparty/nonstd/optional.hpp vendored Normal file

File diff suppressed because it is too large Load Diff