diff options
author | Todd Fiala <todd.fiala@gmail.com> | 2016-09-23 16:10:01 +0000 |
---|---|---|
committer | Todd Fiala <todd.fiala@gmail.com> | 2016-09-23 16:10:01 +0000 |
commit | 72525622b2f2f558dfd2a5a37577b0f6e3ae16a1 (patch) | |
tree | 2d828b6a03291530f145619df86adc7fd90a5b8f /lldb/packages/Python/lldbsuite/pre_kill_hook | |
parent | e9eb0913a92e78ae438dd564729da952284d5972 (diff) | |
download | bcm5719-llvm-72525622b2f2f558dfd2a5a37577b0f6e3ae16a1.tar.gz bcm5719-llvm-72525622b2f2f558dfd2a5a37577b0f6e3ae16a1.zip |
add hook for calling platform-dependent pre-kill action on a timed out test
differential review: https://reviews.llvm.org/D24850
reviewers: clayborg, labath
llvm-svn: 282258
Diffstat (limited to 'lldb/packages/Python/lldbsuite/pre_kill_hook')
5 files changed, 209 insertions, 0 deletions
diff --git a/lldb/packages/Python/lldbsuite/pre_kill_hook/README.md b/lldb/packages/Python/lldbsuite/pre_kill_hook/README.md new file mode 100644 index 00000000000..921eedb4a86 --- /dev/null +++ b/lldb/packages/Python/lldbsuite/pre_kill_hook/README.md @@ -0,0 +1,55 @@ +# 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 +``` diff --git a/lldb/packages/Python/lldbsuite/pre_kill_hook/__init__.py b/lldb/packages/Python/lldbsuite/pre_kill_hook/__init__.py new file mode 100644 index 00000000000..c3a852ea1bf --- /dev/null +++ b/lldb/packages/Python/lldbsuite/pre_kill_hook/__init__.py @@ -0,0 +1 @@ +"""Initialize the package.""" diff --git a/lldb/packages/Python/lldbsuite/pre_kill_hook/darwin.py b/lldb/packages/Python/lldbsuite/pre_kill_hook/darwin.py new file mode 100644 index 00000000000..2bee65a01e3 --- /dev/null +++ b/lldb/packages/Python/lldbsuite/pre_kill_hook/darwin.py @@ -0,0 +1,46 @@ +"""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) diff --git a/lldb/packages/Python/lldbsuite/pre_kill_hook/tests/__init__.py b/lldb/packages/Python/lldbsuite/pre_kill_hook/tests/__init__.py new file mode 100644 index 00000000000..e69de29bb2d --- /dev/null +++ b/lldb/packages/Python/lldbsuite/pre_kill_hook/tests/__init__.py diff --git a/lldb/packages/Python/lldbsuite/pre_kill_hook/tests/test_darwin.py b/lldb/packages/Python/lldbsuite/pre_kill_hook/tests/test_darwin.py new file mode 100644 index 00000000000..48d286c909e --- /dev/null +++ b/lldb/packages/Python/lldbsuite/pre_kill_hook/tests/test_darwin.py @@ -0,0 +1,107 @@ +"""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() |