diff --git a/README.md b/README.md index f5e5fe1..d094e4b 100644 --- a/README.md +++ b/README.md @@ -97,6 +97,10 @@ prmon [--pid PPP] [--filename prmon.txt] [--json-summary prmon.json] \ and remaining arguments are passed to it; `prmon` will then monitor this process instead of being given a PID via `--pid` +`prmon` will exit with `1` if there is a problem with inconsistent or +incomplete arguments. If `prmon` starts a program itself (using `--`) then +`prmon` will exit with the exit code of the child process. + ## Outputs diff --git a/package/src/prmon.cpp b/package/src/prmon.cpp index ad57b9b..ef86a2f 100644 --- a/package/src/prmon.cpp +++ b/package/src/prmon.cpp @@ -96,6 +96,7 @@ int ProcessMonitor(const pid_t mpid, const std::string filename, // Monitoring loop until process exits bool wroteFile = false; std::vector cpids{}; + int return_code = 0; // Scope of 'monitors' ensures safety of bare pointer here auto wallclock_monitor_p = static_cast(monitors["wallmon"].get()); while (kill(mpid, 0) == 0 && prmon::sigusr1 == false) { @@ -179,7 +180,7 @@ int ProcessMonitor(const pid_t mpid, const std::string filename, } } std::this_thread::sleep_for(std::chrono::milliseconds(200)); - prmon::reap_children(); + return_code = prmon::reap_children(); } file.close(); @@ -192,7 +193,7 @@ int ProcessMonitor(const pid_t mpid, const std::string filename, file << std::setw(2) << json_summary << std::endl; file.close(); - return 0; + return return_code; } int main(int argc, char* argv[]) { @@ -335,8 +336,8 @@ int main(int argc, char* argv[]) { if (child == 0) { execvp(argv[child_args], &argv[child_args]); } else if (child > 0) { - ProcessMonitor(child, filename, jsonSummary, interval, store_hw_info, - store_unit_info, netdevs); + return ProcessMonitor(child, filename, jsonSummary, interval, + store_hw_info, store_unit_info, netdevs); } } diff --git a/package/src/prmonutils.cpp b/package/src/prmonutils.cpp index c7dc84c..5af1230 100644 --- a/package/src/prmonutils.cpp +++ b/package/src/prmonutils.cpp @@ -92,26 +92,29 @@ std::vector offspring_pids(const pid_t mother_pid) { void SignalCallbackHandler(int /*signal*/) { sigusr1 = true; } -void reap_children() { +int reap_children() { int status; + int return_code = 0; pid_t pid{1}; while (pid > 0) { pid = waitpid((pid_t)-1, &status, WNOHANG); if (status && pid > 0) { - if (WIFEXITED(status)) + if (WIFEXITED(status)) { + return_code = WEXITSTATUS(status); std::clog << "Child process " << pid - << " had non-zero return value: " << WEXITSTATUS(status) - << std::endl; - else if (WIFSIGNALED(status)) + << " had non-zero return value: " << return_code << std::endl; + } else if (WIFSIGNALED(status)) { std::clog << "Child process " << pid << " exited from signal " << WTERMSIG(status) << std::endl; - else if (WIFSTOPPED(status)) + } else if (WIFSTOPPED(status)) { std::clog << "Child process " << pid << " was stopped by signal" << WSTOPSIG(status) << std::endl; - else if (WIFCONTINUED(status)) + } else if (WIFCONTINUED(status)) { std::clog << "Child process " << pid << " was continued" << std::endl; + } } } + return return_code; } } // namespace prmon diff --git a/package/src/prmonutils.h b/package/src/prmonutils.h index 4c36bdf..f17f7b4 100644 --- a/package/src/prmonutils.h +++ b/package/src/prmonutils.h @@ -25,7 +25,7 @@ extern bool sigusr1; void SignalCallbackHandler(int); // Child process reaper -void reap_children(); +int reap_children(); // Precision specifier for average output, to truncate to an integer // for anything >avg_precision and round the fraction to diff --git a/package/tests/CMakeLists.txt b/package/tests/CMakeLists.txt index db3827c..16baa57 100644 --- a/package/tests/CMakeLists.txt +++ b/package/tests/CMakeLists.txt @@ -38,6 +38,7 @@ script_install(SCRIPT testMEM.py) script_install(SCRIPT netBurner.py) script_install(SCRIPT httpBlock.py DESTINATION cgi-bin) script_install(SCRIPT testCOUNT.py) +script_install(SCRIPT testEXIT.py) # Setup the target version of Python we will use for testing set(PYTHON_TEST "python3" CACHE STRING "Python binary to use for tests") @@ -79,3 +80,6 @@ add_test(NAME basicCOUNT COMMAND ${PYTHON_TEST} testCOUNT.py --procs 2 --threads # Units check test add_test(NAME testUnits COMMAND ${PYTHON_TEST} testCPU.py --units --time 3 --slack 0 --interval 1) + +# Test passing the child exit code works +add_test(NAME testExitCode COMMAND ${PYTHON_TEST} testEXIT.py --exit-code 43) diff --git a/package/tests/testEXIT.py b/package/tests/testEXIT.py new file mode 100755 index 0000000..b0cd1e0 --- /dev/null +++ b/package/tests/testEXIT.py @@ -0,0 +1,38 @@ +#! /usr/bin/env python3 +# +# Copyright (C) 2018-2020 CERN +# License Apache2 - see LICENCE file + +import argparse +import subprocess +import sys +import unittest + +def setupConfigurableTest(exit_code = 0): + '''Wrap the class definition in a function to allow arguments to be passed''' + class configurableProcessMonitor(unittest.TestCase): + def test_runTestWithParams(self): + child_cmd = ['sh', '-c', 'sleep 3 && exit {0}'.format(exit_code)] + + prmon_cmd = ['../prmon', '--interval', '1'] + prmon_cmd.append('--') + prmon_cmd.extend(child_cmd) + prmon_p = subprocess.Popen(prmon_cmd, shell = False) + prmon_rc = prmon_p.wait() + + self.assertEqual(prmon_rc, exit_code, "Wrong return code from prmon (expected {0}".format(exit_code)) + + return configurableProcessMonitor + + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description="Configurable test runner") + parser.add_argument('--exit-code', type=int, default=0) + + args = parser.parse_args() + # Stop unittest from being confused by the arguments + sys.argv=sys.argv[:1] + + cpm = setupConfigurableTest(args.exit_code) + + unittest.main()