forked from OSchip/llvm-project
[lldbsuite] Remove pre_kill_hook package
This package was only used by dosep.py which has since been removed.
This commit is contained in:
parent
5cc2e0651f
commit
8a82000e48
|
@ -1,55 +0,0 @@
|
|||
# pre\_kill\_hook package
|
||||
|
||||
## Overview
|
||||
|
||||
The pre\_kill\_hook package provides a per-platform method for running code
|
||||
after a test process times out but before the concurrent test runner kills the
|
||||
timed-out process.
|
||||
|
||||
## Detailed Description of Usage
|
||||
|
||||
If a platform defines the hook, then the hook gets called right after a timeout
|
||||
is detected in a test run, but before the process is killed.
|
||||
|
||||
The pre-kill-hook mechanism works as follows:
|
||||
|
||||
* When a timeout is detected in the process_control.ProcessDriver class that
|
||||
runs the per-test lldb process, a new overridable on\_timeout\_pre\_kill() method
|
||||
is called on the ProcessDriver instance.
|
||||
|
||||
* The concurrent test driver's derived ProcessDriver overrides this method. It
|
||||
looks to see if a module called
|
||||
"lldbsuite.pre\_kill\_hook.{platform-system-name}" module exists, where
|
||||
platform-system-name is replaced with platform.system().lower(). (e.g.
|
||||
"Darwin" becomes the darwin.py module).
|
||||
|
||||
* If that module doesn't exist, the rest of the new behavior is skipped.
|
||||
|
||||
* If that module does exist, it is loaded, and the method
|
||||
"do\_pre\_kill(process\_id, context\_dict, output\_stream)" is called. If
|
||||
that method throws an exception, we log it and we ignore further processing
|
||||
of the pre-killed process.
|
||||
|
||||
* The process\_id argument of the do\_pre\_kill function is the process id as
|
||||
returned by the ProcessDriver.pid property.
|
||||
|
||||
* The output\_stream argument of the do\_pre\_kill function takes a file-like
|
||||
object. Output to be collected from doing any processing on the
|
||||
process-to-be-killed should be written into the file-like object. The
|
||||
current impl uses a six.StringIO and then writes this output to
|
||||
{TestFilename}-{pid}.sample in the session directory.
|
||||
|
||||
* Platforms where platform.system() is "Darwin" will get a pre-kill action that
|
||||
runs the 'sample' program on the lldb that has timed out. That data will be
|
||||
collected on CI and analyzed to determine what is happening during timeouts.
|
||||
(This has an advantage over a core in that it is much smaller and that it
|
||||
clearly demonstrates any liveness of the process, if there is any).
|
||||
|
||||
## Running the tests
|
||||
|
||||
To run the tests in the pre\_kill\_hook package, open a console, change into
|
||||
this directory and run the following:
|
||||
|
||||
```
|
||||
python -m unittest discover
|
||||
```
|
|
@ -1 +0,0 @@
|
|||
"""Initialize the package."""
|
|
@ -1,46 +0,0 @@
|
|||
"""Provides a pre-kill method to run on macOS."""
|
||||
from __future__ import print_function
|
||||
|
||||
# system imports
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
# third-party module imports
|
||||
import six
|
||||
|
||||
|
||||
def do_pre_kill(process_id, runner_context, output_stream, sample_time=3):
|
||||
"""Samples the given process id, and puts the output to output_stream.
|
||||
|
||||
@param process_id the local process to sample.
|
||||
|
||||
@param runner_context a dictionary of details about the architectures
|
||||
and platform on which the given process is running. Expected keys are
|
||||
archs (array of architectures), platform_name, platform_url, and
|
||||
platform_working_dir.
|
||||
|
||||
@param output_stream file-like object that should be used to write the
|
||||
results of sampling.
|
||||
|
||||
@param sample_time specifies the time in seconds that should be captured.
|
||||
"""
|
||||
|
||||
# Validate args.
|
||||
if runner_context is None:
|
||||
raise Exception("runner_context argument is required")
|
||||
if not isinstance(runner_context, dict):
|
||||
raise Exception("runner_context argument must be a dictionary")
|
||||
|
||||
# We will try to run sample on the local host only if there is no URL
|
||||
# to a remote.
|
||||
if "platform_url" in runner_context and (
|
||||
runner_context["platform_url"] is not None):
|
||||
import pprint
|
||||
sys.stderr.write(
|
||||
"warning: skipping timeout pre-kill sample invocation because we "
|
||||
"don't know how to run on a remote yet. runner_context={}\n"
|
||||
.format(pprint.pformat(runner_context)))
|
||||
|
||||
output = subprocess.check_output(['sample', six.text_type(process_id),
|
||||
str(sample_time)])
|
||||
output_stream.write(output)
|
|
@ -1,76 +0,0 @@
|
|||
"""Provides a pre-kill method to run on Linux.
|
||||
|
||||
This timeout pre-kill method relies on the Linux perf-tools
|
||||
distribution. The appropriate way to obtain this set of tools
|
||||
will depend on the Linux distribution.
|
||||
|
||||
For Ubuntu 16.04, the invoke the following command:
|
||||
sudo apt-get install perf-tools-unstable
|
||||
"""
|
||||
from __future__ import print_function
|
||||
|
||||
# system imports
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
|
||||
def do_pre_kill(process_id, runner_context, output_stream, sample_time=3):
|
||||
"""Samples the given process id, and puts the output to output_stream.
|
||||
|
||||
@param process_id the local process to sample.
|
||||
|
||||
@param runner_context a dictionary of details about the architectures
|
||||
and platform on which the given process is running. Expected keys are
|
||||
archs (array of architectures), platform_name, platform_url, and
|
||||
platform_working_dir.
|
||||
|
||||
@param output_stream file-like object that should be used to write the
|
||||
results of sampling.
|
||||
|
||||
@param sample_time specifies the time in seconds that should be captured.
|
||||
"""
|
||||
|
||||
# Validate args.
|
||||
if runner_context is None:
|
||||
raise Exception("runner_context argument is required")
|
||||
if not isinstance(runner_context, dict):
|
||||
raise Exception("runner_context argument must be a dictionary")
|
||||
|
||||
# We will try to run sample on the local host only if there is no URL
|
||||
# to a remote.
|
||||
if "platform_url" in runner_context and (
|
||||
runner_context["platform_url"] is not None):
|
||||
import pprint
|
||||
sys.stderr.write(
|
||||
"warning: skipping timeout pre-kill sample invocation because we "
|
||||
"don't know how to run on a remote yet. runner_context={}\n"
|
||||
.format(pprint.pformat(runner_context)))
|
||||
|
||||
# We're going to create a temp file, and immediately overwrite it with the
|
||||
# following command. This just ensures we don't have any races in
|
||||
# creation of the temporary sample file.
|
||||
fileno, filename = tempfile.mkstemp(suffix='perfdata')
|
||||
os.close(fileno)
|
||||
fileno = None
|
||||
|
||||
try:
|
||||
with open(os.devnull, 'w') as devnull:
|
||||
returncode = subprocess.call(['timeout', str(sample_time), 'perf',
|
||||
'record', '-g', '-o', filename, '-p', str(process_id)],
|
||||
stdout=devnull, stderr=devnull)
|
||||
if returncode == 0 or returncode == 124:
|
||||
# This is okay - this is the timeout return code, which is totally
|
||||
# expected.
|
||||
pass
|
||||
else:
|
||||
raise Exception("failed to call 'perf record .., error: {}".format(
|
||||
returncode))
|
||||
|
||||
with open(os.devnull, 'w') as devnull:
|
||||
output = subprocess.check_output(['perf', 'report', '--call-graph',
|
||||
'--stdio', '-i', filename], stderr=devnull)
|
||||
output_stream.write(output)
|
||||
finally:
|
||||
os.remove(filename)
|
|
@ -1,107 +0,0 @@
|
|||
"""Test the pre-kill hook on Darwin."""
|
||||
from __future__ import print_function
|
||||
|
||||
# system imports
|
||||
from multiprocessing import Process, Queue
|
||||
import platform
|
||||
import re
|
||||
from unittest import main, TestCase
|
||||
|
||||
# third party
|
||||
from six import StringIO
|
||||
|
||||
|
||||
def do_child_process(child_work_queue, parent_work_queue, verbose):
|
||||
import os
|
||||
|
||||
pid = os.getpid()
|
||||
if verbose:
|
||||
print("child: pid {} started, sending to parent".format(pid))
|
||||
parent_work_queue.put(pid)
|
||||
if verbose:
|
||||
print("child: waiting for shut-down request from parent")
|
||||
child_work_queue.get()
|
||||
if verbose:
|
||||
print("child: received shut-down request. Child exiting.")
|
||||
|
||||
|
||||
class DarwinPreKillTestCase(TestCase):
|
||||
|
||||
def __init__(self, methodName):
|
||||
super(DarwinPreKillTestCase, self).__init__(methodName)
|
||||
self.process = None
|
||||
self.child_work_queue = None
|
||||
self.verbose = False
|
||||
|
||||
def tearDown(self):
|
||||
if self.verbose:
|
||||
print("parent: sending shut-down request to child")
|
||||
if self.process:
|
||||
self.child_work_queue.put("hello, child")
|
||||
self.process.join()
|
||||
if self.verbose:
|
||||
print("parent: child is fully shut down")
|
||||
|
||||
def test_sample(self):
|
||||
# Ensure we're Darwin.
|
||||
if platform.system() != 'Darwin':
|
||||
self.skipTest("requires a Darwin-based OS")
|
||||
|
||||
# Start the child process.
|
||||
self.child_work_queue = Queue()
|
||||
parent_work_queue = Queue()
|
||||
self.process = Process(target=do_child_process,
|
||||
args=(self.child_work_queue, parent_work_queue,
|
||||
self.verbose))
|
||||
if self.verbose:
|
||||
print("parent: starting child")
|
||||
self.process.start()
|
||||
|
||||
# Wait for the child to report its pid. Then we know we're running.
|
||||
if self.verbose:
|
||||
print("parent: waiting for child to start")
|
||||
child_pid = parent_work_queue.get()
|
||||
|
||||
# Sample the child process.
|
||||
from darwin import do_pre_kill
|
||||
context_dict = {
|
||||
"archs": [platform.machine()],
|
||||
"platform_name": None,
|
||||
"platform_url": None,
|
||||
"platform_working_dir": None
|
||||
}
|
||||
|
||||
if self.verbose:
|
||||
print("parent: running pre-kill action on child")
|
||||
output_io = StringIO()
|
||||
do_pre_kill(child_pid, context_dict, output_io)
|
||||
output = output_io.getvalue()
|
||||
|
||||
if self.verbose:
|
||||
print("parent: do_pre_kill() wrote the following output:", output)
|
||||
self.assertIsNotNone(output)
|
||||
|
||||
# We should have a line with:
|
||||
# Process: .* [{pid}]
|
||||
process_re = re.compile(r"Process:[^[]+\[([^]]+)\]")
|
||||
match = process_re.search(output)
|
||||
self.assertIsNotNone(match, "should have found process id for "
|
||||
"sampled process")
|
||||
self.assertEqual(1, len(match.groups()))
|
||||
self.assertEqual(child_pid, int(match.group(1)))
|
||||
|
||||
# We should see a Call graph: section.
|
||||
callgraph_re = re.compile(r"Call graph:")
|
||||
match = callgraph_re.search(output)
|
||||
self.assertIsNotNone(match, "should have found the Call graph section"
|
||||
"in sample output")
|
||||
|
||||
# We should see a Binary Images: section.
|
||||
binary_images_re = re.compile(r"Binary Images:")
|
||||
match = binary_images_re.search(output)
|
||||
self.assertIsNotNone(match, "should have found the Binary Images "
|
||||
"section in sample output")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
|
@ -1,133 +0,0 @@
|
|||
"""Test the pre-kill hook on Linux."""
|
||||
from __future__ import print_function
|
||||
|
||||
# system imports
|
||||
from multiprocessing import Process, Queue
|
||||
import platform
|
||||
import re
|
||||
import subprocess
|
||||
from unittest import main, TestCase
|
||||
|
||||
# third party
|
||||
from six import StringIO
|
||||
|
||||
|
||||
def do_child_thread():
|
||||
import os
|
||||
x = 0
|
||||
while True:
|
||||
x = x + 42 * os.getpid()
|
||||
return x
|
||||
|
||||
|
||||
def do_child_process(child_work_queue, parent_work_queue, verbose):
|
||||
import os
|
||||
|
||||
pid = os.getpid()
|
||||
if verbose:
|
||||
print("child: pid {} started, sending to parent".format(pid))
|
||||
parent_work_queue.put(pid)
|
||||
|
||||
# Spin up a daemon thread to do some "work", which will show
|
||||
# up in a sample of this process.
|
||||
import threading
|
||||
worker = threading.Thread(target=do_child_thread)
|
||||
worker.daemon = True
|
||||
worker.start()
|
||||
|
||||
if verbose:
|
||||
print("child: waiting for shut-down request from parent")
|
||||
child_work_queue.get()
|
||||
if verbose:
|
||||
print("child: received shut-down request. Child exiting.")
|
||||
|
||||
|
||||
class LinuxPreKillTestCase(TestCase):
|
||||
|
||||
def __init__(self, methodName):
|
||||
super(LinuxPreKillTestCase, self).__init__(methodName)
|
||||
self.process = None
|
||||
self.child_work_queue = None
|
||||
self.verbose = False
|
||||
# self.verbose = True
|
||||
|
||||
def tearDown(self):
|
||||
if self.verbose:
|
||||
print("parent: sending shut-down request to child")
|
||||
if self.process:
|
||||
self.child_work_queue.put("hello, child")
|
||||
self.process.join()
|
||||
if self.verbose:
|
||||
print("parent: child is fully shut down")
|
||||
|
||||
def test_sample(self):
|
||||
# Ensure we're Darwin.
|
||||
if platform.system() != 'Linux':
|
||||
self.skipTest("requires a Linux-based OS")
|
||||
|
||||
# Ensure we have the 'perf' tool. If not, skip the test.
|
||||
try:
|
||||
perf_version = subprocess.check_output(["perf", "version"])
|
||||
if perf_version is None or not (
|
||||
perf_version.startswith("perf version")):
|
||||
raise Exception("The perf executable doesn't appear"
|
||||
" to be the Linux perf tools perf")
|
||||
except Exception:
|
||||
self.skipTest("requires the Linux perf tools 'perf' command")
|
||||
|
||||
# Start the child process.
|
||||
self.child_work_queue = Queue()
|
||||
parent_work_queue = Queue()
|
||||
self.process = Process(target=do_child_process,
|
||||
args=(self.child_work_queue, parent_work_queue,
|
||||
self.verbose))
|
||||
if self.verbose:
|
||||
print("parent: starting child")
|
||||
self.process.start()
|
||||
|
||||
# Wait for the child to report its pid. Then we know we're running.
|
||||
if self.verbose:
|
||||
print("parent: waiting for child to start")
|
||||
child_pid = parent_work_queue.get()
|
||||
|
||||
# Sample the child process.
|
||||
from linux import do_pre_kill
|
||||
context_dict = {
|
||||
"archs": [platform.machine()],
|
||||
"platform_name": None,
|
||||
"platform_url": None,
|
||||
"platform_working_dir": None
|
||||
}
|
||||
|
||||
if self.verbose:
|
||||
print("parent: running pre-kill action on child")
|
||||
output_io = StringIO()
|
||||
do_pre_kill(child_pid, context_dict, output_io)
|
||||
output = output_io.getvalue()
|
||||
|
||||
if self.verbose:
|
||||
print("parent: do_pre_kill() wrote the following output:", output)
|
||||
self.assertIsNotNone(output)
|
||||
|
||||
# We should have a samples count entry.
|
||||
# Samples:
|
||||
self.assertTrue("Samples:" in output, "should have found a 'Samples:' "
|
||||
"field in the sampled process output")
|
||||
|
||||
# We should see an event count entry
|
||||
event_count_re = re.compile(r"Event count[^:]+:\s+(\d+)")
|
||||
match = event_count_re.search(output)
|
||||
self.assertIsNotNone(match, "should have found the event count entry "
|
||||
"in sample output")
|
||||
if self.verbose:
|
||||
print("cpu-clock events:", match.group(1))
|
||||
|
||||
# We should see some percentages in the file.
|
||||
percentage_re = re.compile(r"\d+\.\d+%")
|
||||
match = percentage_re.search(output)
|
||||
self.assertIsNotNone(match, "should have found at least one percentage "
|
||||
"in the sample output")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
Loading…
Reference in New Issue