aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xcrosperf/crosperf2
-rwxr-xr-xcrosperf/crosperf.py12
-rw-r--r--crosperf/experiment_runner.py19
-rw-r--r--crosperf/suite_runner.py25
-rw-r--r--utils/command_executer.py67
5 files changed, 73 insertions, 52 deletions
diff --git a/crosperf/crosperf b/crosperf/crosperf
index 904a172a..a29dcbfa 100755
--- a/crosperf/crosperf
+++ b/crosperf/crosperf
@@ -1,2 +1,2 @@
#!/bin/bash
-PYTHONPATH=$(dirname $0)/..:$PYTHONPATH python $(dirname $0)/crosperf.py "$@"
+PYTHONPATH=$(dirname $0)/..:$PYTHONPATH exec python $(dirname $0)/crosperf.py "$@"
diff --git a/crosperf/crosperf.py b/crosperf/crosperf.py
index fc0098d2..e107ea48 100755
--- a/crosperf/crosperf.py
+++ b/crosperf/crosperf.py
@@ -7,6 +7,7 @@
import atexit
import optparse
import os
+import signal
import sys
from experiment_runner import ExperimentRunner
from experiment_runner import MockExperimentRunner
@@ -58,6 +59,16 @@ def Cleanup(experiment):
experiment.Cleanup()
+def CallExitHandler(signum, _):
+ """Signal handler that transforms a signal into a call to exit.
+
+ This is useful because functionality registered by "atexit" will
+ be called. It also means you can "catch" the signal by catching
+ the SystemExit exception.
+ """
+ sys.exit(128 + signum)
+
+
def Main(argv):
parser = optparse.OptionParser(usage=Help().GetUsage(),
description=Help().GetHelp(),
@@ -104,6 +115,7 @@ def Main(argv):
json_report = experiment_file.GetGlobalSettings().GetField("json_report")
+ signal.signal(signal.SIGTERM, CallExitHandler)
atexit.register(Cleanup, experiment)
if options.dry_run:
diff --git a/crosperf/experiment_runner.py b/crosperf/experiment_runner.py
index d7749b53..01324b04 100644
--- a/crosperf/experiment_runner.py
+++ b/crosperf/experiment_runner.py
@@ -180,6 +180,12 @@ class ExperimentRunner(object):
self._terminated = True
self.l.LogError("Ctrl-c pressed. Cleaning up...")
experiment.Terminate()
+ raise
+ except SystemExit:
+ self._terminate = True
+ self.l.LogError("Unexpected exit. Cleaning up...")
+ experiment.Terminate()
+ raise
finally:
if not experiment.locks_dir:
self._UnlockAllMachines(experiment)
@@ -252,11 +258,14 @@ class ExperimentRunner(object):
benchmark_run.result.CleanUp(benchmark_run.benchmark.rm_chroot_tmp)
def Run(self):
- self._Run(self._experiment)
- self._PrintTable(self._experiment)
- if not self._terminated:
- self._StoreResults(self._experiment)
- self._Email(self._experiment)
+ try:
+ self._Run(self._experiment)
+ finally:
+ # Always print the report at the end of the run.
+ self._PrintTable(self._experiment)
+ if not self._terminated:
+ self._StoreResults(self._experiment)
+ self._Email(self._experiment)
class MockExperimentRunner(ExperimentRunner):
diff --git a/crosperf/suite_runner.py b/crosperf/suite_runner.py
index f9a2ba30..6531045f 100644
--- a/crosperf/suite_runner.py
+++ b/crosperf/suite_runner.py
@@ -154,10 +154,14 @@ class SuiteRunner(object):
if self.log_level != "verbose":
self._logger.LogOutput("Running test.")
self._logger.LogOutput("CMD: %s" % command)
+ # Use --no-ns-pid so that cros_sdk does not create a different
+ # process namespace and we can kill process created easily by
+ # their process group.
return self._ce.ChrootRunCommand(label.chromeos_root,
command,
True,
- self._ct)
+ self._ct,
+ cros_sdk_options="--no-ns-pid")
def RemoveTelemetryTempFile (self, machine, chromeos_root):
filename = "telemetry@%s" % machine
@@ -208,20 +212,23 @@ class SuiteRunner(object):
profiler_args,
machine))
- chrome_root_options = ""
- chrome_root_options = (" --chrome_root={} --chrome_root_mount={} "
- " FEATURES=\"-usersandbox\" "
+ # Use --no-ns-pid so that cros_sdk does not create a different
+ # process namespace and we can kill process created easily by their
+ # process group.
+ chrome_root_options = ("--no-ns-pid "
+ "--chrome_root={} --chrome_root_mount={} "
+ "FEATURES=\"-usersandbox\" "
"CHROME_ROOT={}".format(label.chrome_src,
CHROME_MOUNT_DIR,
CHROME_MOUNT_DIR))
if self.log_level != "verbose":
self._logger.LogOutput("Running test.")
self._logger.LogOutput("CMD: %s" % cmd)
- return self._ce.ChrootRunCommand (label.chromeos_root,
- cmd,
- return_output=True,
- command_terminator=self._ct,
- cros_sdk_options=chrome_root_options)
+ return self._ce.ChrootRunCommand(label.chromeos_root,
+ cmd,
+ return_output=True,
+ command_terminator=self._ct,
+ cros_sdk_options=chrome_root_options)
def Telemetry_Run(self, machine, label, benchmark, profiler_args):
diff --git a/utils/command_executer.py b/utils/command_executer.py
index 08a5dc74..851791f8 100644
--- a/utils/command_executer.py
+++ b/utils/command_executer.py
@@ -8,6 +8,7 @@ import getpass
import os
import re
import select
+import signal
import subprocess
import tempfile
import time
@@ -55,11 +56,7 @@ class CommandExecuter:
command_timeout=None,
terminated_timeout=10,
print_to_console=True):
- """Run a command.
-
- Note: As this is written, the stdin for the process executed is
- not associated with the stdin of the caller of this routine.
- """
+ """Run a command."""
cmd = str(cmd)
@@ -84,13 +81,13 @@ class CommandExecuter:
user = username + "@"
cmd = "ssh -t -t %s%s -- '%s'" % (user, machine, cmd)
- p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
- stderr=subprocess.PIPE, shell=True)
-
- # We explicitly disconect the client stdin from the command to
- # execute by explicitly requesting a new pipe. Now, let's close it
- # so that the executed process does not try to read from it.
- p.stdin.close()
+ # We use setsid so that the child will have a different session id
+ # and we can easily kill the process group. This is also important
+ # because the child will be disassociated from the parent terminal.
+ # In this way the child cannot mess the parent's terminal.
+ p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE, shell=True,
+ preexec_fn=os.setsid)
full_stdout = ""
full_stderr = ""
@@ -108,15 +105,12 @@ class CommandExecuter:
while len(pipes):
if command_terminator and command_terminator.IsTerminated():
- self.RunCommand("sudo kill -9 " + str(p.pid),
- print_to_console=print_to_console)
- wait = p.wait()
+ os.killpg(os.getpgid(p.pid), signal.SIGTERM)
if self.logger:
- self.logger.LogError("Command was terminated!", print_to_console)
- if return_output:
- return (p.wait, full_stdout, full_stderr)
- else:
- return wait
+ self.logger.LogError("Command received termination request. "
+ "Killed child process group.",
+ print_to_console)
+ break
l=my_poll.poll(100)
for (fd, evt) in l:
@@ -144,21 +138,19 @@ class CommandExecuter:
terminated_time = time.time()
elif (terminated_timeout is not None and
time.time() - terminated_time > terminated_timeout):
- m = ("Timeout of %s seconds reached since process termination."
- % terminated_timeout)
if self.logger:
- self.logger.LogWarning(m, print_to_console)
+ self.logger.LogWarning("Timeout of %s seconds reached since "
+ "process termination."
+ % terminated_timeout, print_to_console)
break
if (command_timeout is not None and
time.time() - started_time > command_timeout):
- m = ("Timeout of %s seconds reached since process started."
- % command_timeout)
+ os.killpg(os.getpgid(p.pid), signal.SIGTERM)
if self.logger:
- self.logger.LogWarning(m, print_to_console)
- self.RunCommand("kill %d || sudo kill %d || sudo kill -9 %d" %
- (p.pid, p.pid, p.pid),
- print_to_console=print_to_console)
+ self.logger.LogWarning("Timeout of %s seconds reached since process"
+ "started. Killed child process group."
+ % command_timeout, print_to_console)
break
if out == err == "":
@@ -448,15 +440,16 @@ class CommandExecuter:
self.logger.LogCmd(cmd)
elif self.logger:
self.logger.LogCmdToFileOnly(cmd)
+
+ # We use setsid so that the child will have a different session id
+ # and we can easily kill the process group. This is also important
+ # because the child will be disassociated from the parent terminal.
+ # In this way the child cannot mess the parent's terminal.
pobject = subprocess.Popen(
cmd, cwd=cwd, bufsize=1024, env=env, shell=shell,
- universal_newlines=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
- stderr=subprocess.STDOUT if join_stderr else subprocess.PIPE)
-
- # We explicitly disconect the client stdin from the command to
- # execute by explicitly requesting a new pipe. Now, let's close it
- # so that the executed process does not try to read from it.
- pobject.stdin.close()
+ universal_newlines=True, stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT if join_stderr else subprocess.PIPE,
+ preexec_fn=os.setsid)
# We provide a default line_consumer
if line_consumer is None:
@@ -484,7 +477,7 @@ class CommandExecuter:
del handlermap[fd]
if timeout is not None and (time.time() - start_time > timeout):
- pobject.kill()
+ os.killpg(os.getpgid(pobject.pid), signal.SIGTERM)
return pobject.wait()