From 9152a67eece3dc021312265eb026fd7c58066132 Mon Sep 17 00:00:00 2001 From: Francis Deslauriers Date: Thu, 23 Feb 2017 14:00:45 -0500 Subject: [PATCH 2/3] LTTng-UST tracing tests LTTng-UST tests are using the same test cases used in the SystemTap and DTrace tests. Signed-off-by: Francis Deslauriers --- Lib/test/dtracedata/call_stack.lttng.expected | 18 +++ Lib/test/dtracedata/gc.lttng.expected | 8 ++ Lib/test/dtracedata/line.lttng.expected | 20 ++++ Lib/test/test_dtrace.py | 166 +++++++++++++++++++++++++- 4 files changed, 207 insertions(+), 5 deletions(-) create mode 100644 Lib/test/dtracedata/call_stack.lttng.expected create mode 100644 Lib/test/dtracedata/gc.lttng.expected create mode 100644 Lib/test/dtracedata/line.lttng.expected diff --git a/Lib/test/dtracedata/call_stack.lttng.expected b/Lib/test/dtracedata/call_stack.lttng.expected new file mode 100644 index 0000000..02d8a16 --- /dev/null +++ b/Lib/test/dtracedata/call_stack.lttng.expected @@ -0,0 +1,18 @@ +python:function__entry call_stack.py start 23 +python:function__entry call_stack.py function_1 1 +python:function__entry call_stack.py function_3 9 +python:function__return call_stack.py function_3 10 +python:function__return call_stack.py function_1 2 +python:function__entry call_stack.py function_2 5 +python:function__entry call_stack.py function_1 1 +python:function__entry call_stack.py function_3 9 +python:function__return call_stack.py function_3 10 +python:function__return call_stack.py function_1 2 +python:function__return call_stack.py function_2 6 +python:function__entry call_stack.py function_3 9 +python:function__return call_stack.py function_3 10 +python:function__entry call_stack.py function_4 13 +python:function__return call_stack.py function_4 14 +python:function__entry call_stack.py function_5 18 +python:function__return call_stack.py function_5 21 +python:function__return call_stack.py start 28 diff --git a/Lib/test/dtracedata/gc.lttng.expected b/Lib/test/dtracedata/gc.lttng.expected new file mode 100644 index 0000000..be74849 --- /dev/null +++ b/Lib/test/dtracedata/gc.lttng.expected @@ -0,0 +1,8 @@ +python:gc__start generation = 0 +python:gc__done collected = 0 +python:gc__start generation = 1 +python:gc__done collected = 0 +python:gc__start generation = 2 +python:gc__done collected = 0 +python:gc__start generation = 2 +python:gc__done collected = 1 diff --git a/Lib/test/dtracedata/line.lttng.expected b/Lib/test/dtracedata/line.lttng.expected new file mode 100644 index 0000000..9244226 --- /dev/null +++ b/Lib/test/dtracedata/line.lttng.expected @@ -0,0 +1,20 @@ +python:line line.py test_line 2 +python:line line.py test_line 3 +python:line line.py test_line 4 +python:line line.py test_line 5 +python:line line.py test_line 6 +python:line line.py test_line 7 +python:line line.py test_line 8 +python:line line.py test_line 9 +python:line line.py test_line 10 +python:line line.py test_line 11 +python:line line.py test_line 4 +python:line line.py test_line 5 +python:line line.py test_line 6 +python:line line.py test_line 7 +python:line line.py test_line 8 +python:line line.py test_line 10 +python:line line.py test_line 11 +python:line line.py test_line 4 +python:line line.py test_line 12 +python:line line.py test_line 13 diff --git a/Lib/test/test_dtrace.py b/Lib/test/test_dtrace.py index 47a5010..b952509 100644 --- a/Lib/test/test_dtrace.py +++ b/Lib/test/test_dtrace.py @@ -1,11 +1,14 @@ import dis -import os.path +import os +import shutil import re import subprocess import sys import types +import tempfile import unittest +from enum import Enum, unique from test.support import findfile, run_unittest @@ -91,11 +94,156 @@ class DTraceBackend(TraceBackend): EXTENSION = ".d" COMMAND = ["dtrace", "-q", "-s"] - class SystemTapBackend(TraceBackend): EXTENSION = ".stp" COMMAND = ["stap", "-g"] +@unique +class LTTNG_CMD(Enum): + CREATE = 1 + ENABLE_USPACE_EVENTS = 2 + START = 3 + STOP = 4 + VIEW = 5 + DESTROY = 6 + VERSION = 7 + +class LTTngUSTBackend(): + EXTENSION = ".lttng" + trace_path = None + session_name = None + lttng_commands={ + LTTNG_CMD.CREATE:['lttng', 'create'], + LTTNG_CMD.ENABLE_USPACE_EVENTS:['lttng', 'enable-event', '--userspace'], + LTTNG_CMD.START:['lttng', 'start'], + LTTNG_CMD.STOP:['lttng', 'stop'], + LTTNG_CMD.VIEW:['lttng', 'view'], + LTTNG_CMD.DESTROY:['lttng', 'destroy'], + LTTNG_CMD.VERSION:['lttng', '--version'] + } + + def exec_lttng_command(self, cmd, session_cmd, option=None): + try: + cmd_args = list(self.lttng_commands[cmd]) + except KeyError: + raise AssertionError('LTTng command not found.') + + if session_cmd is not None: + cmd_args.append(session_cmd) + + if option is not None: + cmd_args += option + + stdout, _ = subprocess.Popen(cmd_args, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + universal_newlines=True).communicate() + return stdout + + def setup_tracing_session(self, test_case_name): + # Create a temporary file for the tracing session + self.trace_path = tempfile.mkdtemp() + trace_folder = '--output={}'.format(self.trace_path) + self.session_name = 'python_tests_{}'.format(os.getpid()) + _ = self.exec_lttng_command(LTTNG_CMD.CREATE, self.session_name, [trace_folder]) + + # Format the event filter depending on the testcase + event_filter = None + if test_case_name in 'call_stack': + event_filter = ['python:function_*', '--filter=co_filename == "{}"'.format(abspath('call_stack.py'))] + elif test_case_name in 'gc': + event_filter = ['python:function_*,python:gc_*'] + elif test_case_name in 'line': + event_filter = ['python:function_*,python:line', '--filter=co_filename == "{}"'.format(abspath('line.py'))] + enable_event_session = '--session={}'.format(self.session_name) + _ = self.exec_lttng_command(LTTNG_CMD.ENABLE_USPACE_EVENTS, enable_event_session, event_filter) + _ = self.exec_lttng_command(LTTNG_CMD.START, self.session_name) + + def get_trace_output(self): + _ = self.exec_lttng_command(LTTNG_CMD.STOP, self.session_name) + return self.exec_lttng_command(LTTNG_CMD.VIEW, self.session_name) + + def teardown_tracing_session(self): + _ = self.exec_lttng_command(LTTNG_CMD.DESTROY, self.session_name) + shutil.rmtree(self.trace_path) + + def run_case(self, name, optimize_python=None): + actual_output = self.trace_python( + python_file=abspath(name + ".py"), + test_case_name = name, + optimize_python=optimize_python) + with open(abspath(name + self.EXTENSION + ".expected")) as f: + expected_output = f.read().rstrip() + + return (expected_output, actual_output) + + def run_python(self, python_file, optimize_python=None): + python_flags = [] + if optimize_python: + python_flags.extend(["-O"] * optimize_python) + subcommand = [sys.executable] + python_flags + [python_file] + + _, _ = subprocess.Popen(subcommand, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + universal_newlines=True).communicate() + + def sanitize_trace(self, output, test_case_name): + # Filter out any trace event that occured before and after main + start_pattern = '.*python:function__entry.*co_name = "(start|test_line)".*' + end_pattern = '.*python:function__return.*co_name = "(start|test_line)".*' + copy = False + cleaned_output = []; + for line in output.split('\n'): + if re.search(start_pattern, line): + copy = True + elif copy is True and re.search(end_pattern, line): + #Copy the last line + cleaned_output += [line] + copy = False + + if copy: + cleaned_output += [line] + + # Depending on the testcase, define regular expression to capture the + # tracepoint payload + payload_regex = { + 'gc' : r'\[.*\] \(.*\).*(?Ppython:gc_.*):.*{ (?P(collected|generation) = [0-9]+) }', + 'call_stack' : r'\[.*\] \(.*\).*(?Ppython:function_.*):.*{ co_filename = "(?P.*)", co_name = "(?P.*)", line_no = (?P.*) }', + 'line' : r'\[.*\] \(.*\).*(?Ppython:line):.*{ co_filename = "(?P.*)", co_name = "(?P.*)", line_no = (?P.*) }' + } + out = [] + for line in cleaned_output: + match = re.search(payload_regex[test_case_name], line) + if match is not None: + out += [match.groupdict()] + + for evt in out : + if 'filename' in evt: + evt['filename']=os.path.basename(evt['filename']) + + return out + + def trace_python(self, python_file, test_case_name, optimize_python=None): + self.setup_tracing_session(test_case_name) + self.run_python(python_file, optimize_python=optimize_python) + output = self.get_trace_output() + self.teardown_tracing_session() + clean_output = self.sanitize_trace(output, test_case_name=test_case_name) + + out = '\n'.join([' '.join(e.values()) for e in clean_output]) + return out + + def assert_usable(self): + out = None + try: + out = self.exec_lttng_command(LTTNG_CMD.VERSION, session_cmd=None) + except (FileNotFoundError, PermissionError) as fnfe: + out = str(fnfe) + if 'lttng (LTTng Trace Control)' not in out: + raise unittest.SkipTest( + "{} failed: {}".format(" ".join(self.lttng_commands[LTTNG_CMD.VERSION]), out) + ) class TraceTests(unittest.TestCase): # unittest.TestCase options @@ -110,8 +258,9 @@ class TraceTests(unittest.TestCase): self.backend.assert_usable() def run_case(self, name): - actual_output, expected_output = self.backend.run_case( + expected_output, actual_output = self.backend.run_case( name, optimize_python=self.optimize_python) + self.assertEqual(actual_output, expected_output) def test_function_entry_return(self): @@ -168,11 +317,18 @@ class SystemTapOptimizedTests(TraceTests): backend = SystemTapBackend() optimize_python = 2 +class LTTngUSTNormalTests(TraceTests): + backend = LTTngUSTBackend() + optimize_python = 0 + +class LTTngUSTOptimizedTests(TraceTests): + backend = LTTngUSTBackend() + optimize_python = 2 def test_main(): run_unittest(DTraceNormalTests, DTraceOptimizedTests, SystemTapNormalTests, - SystemTapOptimizedTests) - + SystemTapOptimizedTests, LTTngUSTNormalTests, + LTTngUSTOptimizedTests) if __name__ == '__main__': test_main() -- 2.7.4