diff --git a/lttngpy/src/lttngpy/_lttngpy_pybind11.cpp b/lttngpy/src/lttngpy/_lttngpy_pybind11.cpp index d2208b87..388e1a64 100644 --- a/lttngpy/src/lttngpy/_lttngpy_pybind11.cpp +++ b/lttngpy/src/lttngpy/_lttngpy_pybind11.cpp @@ -69,7 +69,7 @@ PYBIND11_MODULE(_lttngpy_pybind11, m) { m.def( "lttng_create_session_live", <tng_create_session_live, - "Create session.", + "Create live session.", py::kw_only(), py::arg("session_name"), py::arg("url"), diff --git a/lttngpy/test/test_session.py b/lttngpy/test/test_session.py index 42f2bb9b..dfc91c65 100644 --- a/lttngpy/test/test_session.py +++ b/lttngpy/test/test_session.py @@ -60,8 +60,47 @@ def test_session_list_create_start_stop_destroy(self): shutil.rmtree(tmpdir) def test_session_live_list_create_start_stop_destroy(self): - # TODO(christophebedard): add test for lttngpy.lttng_create_session_live() - pass + session_name = 'test_session_live_list_create_start_stop_destroy' + tmpdir = self.create_test_tmpdir(session_name) + + self.assertSetEqual(set(), lttngpy.get_session_names()) + result = lttngpy.lttng_create_session_live( + session_name=session_name, + url=None, + timer_interval=1000000 + ) + self.assertEqual(0, result) + self.assertSetEqual({session_name}, lttngpy.get_session_names()) + self.assertEqual( + 0, + lttngpy.enable_channel( + session_name=session_name, + domain_type=lttngpy.LTTNG_DOMAIN_UST, + buffer_type=lttngpy.LTTNG_BUFFER_PER_UID, + channel_name='dummy_channel_live', + overwrite=None, + subbuf_size=None, + num_subbuf=None, + switch_timer_interval=None, + read_timer_interval=None, + output=None, + ), + ) + self.assertEqual(0, lttngpy.lttng_start_tracing(session_name=session_name)) + self.assertEqual(0, lttngpy.lttng_stop_tracing(session_name=session_name)) + self.assertEqual(0, lttngpy.lttng_destroy_session(session_name=session_name)) + self.assertSetEqual(set(), lttngpy.get_session_names()) + + result = lttngpy.lttng_create_session_live( + session_name=session_name, + url=None, + timer_interval=1000000 + ) + self.assertEqual(0, result) + self.assertEqual(0, lttngpy.destroy_all_sessions()) + self.assertSetEqual(set(), lttngpy.get_session_names()) + + shutil.rmtree(tmpdir) def test_error(self): session_name = 'test_error' diff --git a/tracetools_trace/tracetools_trace/tools/args.py b/tracetools_trace/tracetools_trace/tools/args.py index 6d9a1cb5..ad1f5dfb 100644 --- a/tracetools_trace/tracetools_trace/tools/args.py +++ b/tracetools_trace/tracetools_trace/tools/args.py @@ -84,8 +84,13 @@ def _add_arguments_configure(parser: argparse.ArgumentParser) -> None: '--live', dest='live_timer_interval', type=int, nargs='?', # Default value for 'lttng create-session --live': # https://lttng.org/man/1/lttng-create/v2.13/#doc-opt--live - default=1000000, - help='TODO (default: %(default)s)') + const=100000, + help='Create a live tracing session. Optionally set the live timer interval ' + '(default: %(default)s)') + parser.add_argument( + '--live-url', dest='live_url', type=str, + default='net://localhost', + help='Set the live tracing URL origin (default: %(default)s)') def _add_arguments_default_session_name(parser: argparse.ArgumentParser) -> None: diff --git a/tracetools_trace/tracetools_trace/tools/lttng_impl.py b/tracetools_trace/tracetools_trace/tools/lttng_impl.py index c639d02c..5885da5d 100644 --- a/tracetools_trace/tracetools_trace/tools/lttng_impl.py +++ b/tracetools_trace/tracetools_trace/tools/lttng_impl.py @@ -17,6 +17,7 @@ import os import shlex +import socket import subprocess from typing import Dict from typing import List @@ -158,6 +159,7 @@ def setup( subbuffer_size_ust: int = 8 * 4096, subbuffer_size_kernel: int = 32 * 4096, live_timer_interval: Optional[int] = None, + live_url: Optional[str] = None, ) -> Optional[str]: """ Set up LTTng session, with events and context. @@ -190,19 +192,16 @@ def setup( the usual page size) :param subbuffer_size_kernel: the size of the subbuffers for kernel events (defaults to 32 times the usual page size, since there can be way more kernel events than UST events) + :param live_timer_interval: the time interval at which the data should be flushed from the + buffer and sent to the LTTng relay daemon. This is in microseconds. + The created tracing session will be in live mode if this value is not `None`. + :param live_url: the URL of the relay daemon to which the tracing output will be sent. + Used only if live_timer_interval is not `None`. :return: the full path to the trace directory, or `None` if initialization failed """ # Validate parameters if not session_name: raise RuntimeError('empty session name') - # Resolve full tracing directory path - # TODO(christophebedard): do we need to join the base_path with session_name for a live session? - # We need to return a path, so maybe format it like: - # "net://localhost/host/$hostname/$session_name" - full_path = os.path.join(base_path, session_name) - if os.path.isdir(full_path) and not append_trace: - raise RuntimeError( - f'trace directory already exists, use the append option to append to it: {full_path}') # If there is no session daemon running, try to spawn one if is_session_daemon_not_alive(): @@ -248,15 +247,22 @@ def setup( # Create session if live_timer_interval is None: + # Resolve full tracing directory path + full_path = os.path.join(base_path, session_name) + if os.path.isdir(full_path) and not append_trace: + raise RuntimeError( + f'trace directory already exists, use the append option to append to it: {full_path}') # LTTng will create the parent directories if needed _create_session( session_name=session_name, full_path=full_path, ) else: + live_tracing_url = f'{live_url}/host/{socket.gethostname()}/{session_name}' + full_path = live_tracing_url _create_session_live( session_name=session_name, - full_path=full_path, + url=live_url, timer_interval=live_timer_interval, ) @@ -440,7 +446,7 @@ def _create_session( def _create_session_live( *, session_name: str, - full_path: str, + url: str, timer_interval: int, ) -> None: """ @@ -448,10 +454,7 @@ def _create_session_live( """ result = lttngpy.lttng_create_session_live( session_name=session_name, - # TODO(christophebedard): figure out what to provide here as the URL - # This depends on how we expect users to use live tracing - # See the documentation for the url param of lttng_create_session_live() - url=None, + url=url, timer_interval=timer_interval, ) if result < 0: diff --git a/tracetools_trace/tracetools_trace/trace.py b/tracetools_trace/tracetools_trace/trace.py index ca1e7d56..d7a5d568 100644 --- a/tracetools_trace/tracetools_trace/trace.py +++ b/tracetools_trace/tracetools_trace/trace.py @@ -18,6 +18,7 @@ import argparse import os +import socket import sys from typing import Callable from typing import List @@ -43,6 +44,7 @@ def _display_info( syscalls: List[str], context_fields: List[str], display_list: bool, + live_timer_interval: int = None, ) -> None: if ros_events: print(f'userspace tracing enabled ({len(ros_events)} events)') @@ -66,6 +68,8 @@ def _display_info( print(f'context ({len(context_fields)} fields)') if display_list: print_names_list(context_fields) + if live_timer_interval: + print(f'live timer interval set to: {live_timer_interval}') def _resolve_session_path( @@ -79,6 +83,14 @@ def _resolve_session_path( print(f'writing tracing session to: {full_session_path}') return base_path, full_session_path +def _resolve_session_live_url( + *, + live_url: Optional[str], + session_name: str, +) -> str: + full_live_url = f'{live_url}/host/{socket.gethostname()}/{session_name}' + print(f'live trace data will be sent to the relay daemon at {full_live_url}') + return full_live_url def init( *, @@ -90,6 +102,7 @@ def init( syscalls: List[str], context_fields: List[str], live_timer_interval: Optional[int], + live_url: Optional[str], display_list: bool, interactive: bool, ) -> bool: @@ -120,12 +133,19 @@ def init( syscalls=syscalls, context_fields=context_fields, display_list=display_list, + live_timer_interval=live_timer_interval, ) - base_path, full_session_path = _resolve_session_path( - session_name=session_name, - base_path=base_path, - ) + if live_timer_interval is None: + base_path, full_session_path = _resolve_session_path( + session_name=session_name, + base_path=base_path, + ) + else: + full_live_url = _resolve_session_live_url( + live_url=live_url, + session_name=session_name, + ) if interactive: input('press enter to start...') @@ -138,11 +158,15 @@ def init( syscalls=syscalls, context_fields=context_fields, live_timer_interval=live_timer_interval, + live_url=live_url, ) if trace_directory is None: return False # Simple sanity check - assert trace_directory == full_session_path + if live_timer_interval is None: + assert trace_directory == full_session_path + else: + assert trace_directory == full_live_url return True @@ -227,6 +251,7 @@ def work() -> int: syscalls=args.syscalls, context_fields=args.context_fields, live_timer_interval=args.live_timer_interval, + live_url=args.live_url, display_list=args.list, interactive=True, ): @@ -256,6 +281,8 @@ def work() -> int: kernel_events=args.events_kernel, syscalls=args.syscalls, context_fields=args.context_fields, + live_timer_interval=args.live_timer_interval, + live_url=args.live_url, display_list=args.list, interactive=False, )