| """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() |