Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Propagate exit status if given a child program to execute #163

Merged
merged 3 commits into from
Aug 18, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
9 changes: 5 additions & 4 deletions package/src/prmon.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ int ProcessMonitor(const pid_t mpid, const std::string filename,
// Monitoring loop until process exits
bool wroteFile = false;
std::vector<pid_t> cpids{};
int return_code = 0;
// Scope of 'monitors' ensures safety of bare pointer here
auto wallclock_monitor_p = static_cast<wallmon*>(monitors["wallmon"].get());
while (kill(mpid, 0) == 0 && prmon::sigusr1 == false) {
Expand Down Expand Up @@ -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();

Expand All @@ -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[]) {
Expand Down Expand Up @@ -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);
}
}

Expand Down
17 changes: 10 additions & 7 deletions package/src/prmonutils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -92,26 +92,29 @@ std::vector<pid_t> 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
2 changes: 1 addition & 1 deletion package/src/prmonutils.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions package/tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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)
38 changes: 38 additions & 0 deletions package/tests/testEXIT.py
Original file line number Diff line number Diff line change
@@ -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()