diff --git a/test_ros2trace/package.xml b/test_ros2trace/package.xml
index 73125ed2..7757b617 100644
--- a/test_ros2trace/package.xml
+++ b/test_ros2trace/package.xml
@@ -18,6 +18,7 @@
ament_xmllint
launch
launch_ros
+ lttngpy
python3-pytest
ros2run
ros2trace
diff --git a/test_ros2trace/test/test_ros2trace/test_trace.py b/test_ros2trace/test/test_ros2trace/test_trace.py
index 43a3e9a4..d2e3f71c 100644
--- a/test_ros2trace/test/test_ros2trace/test_trace.py
+++ b/test_ros2trace/test/test_ros2trace/test_trace.py
@@ -25,6 +25,7 @@
from launch import LaunchDescription
from launch import LaunchService
from launch_ros.actions import Node
+from lttngpy import impl as lttngpy
from tracetools_trace.tools import tracepoints
from tracetools_trace.tools.lttng import is_lttng_installed
@@ -63,30 +64,26 @@ def tearDown(self) -> None:
# Even if running 'ros2 trace' fails, we do not want any lingering tracing session
self.assertNoTracingSession()
- def get_tracing_sessions(self) -> Tuple[bool, str]:
- output = self.run_lttng_list()
- # If there is no session daemon, then there are no tracing sessions
- no_session_daemon_available = 'No session daemon is available' in output
- if no_session_daemon_available:
- return False, output
- # Starting from LTTng 2.13, 'tracing session' was replaced with 'recording session'
- # (see lttng-tools e971184)
- has_tracing_sessions = not any(
- f'Currently no available {name} session' in output for name in ('tracing', 'recording')
- )
- return has_tracing_sessions, output
-
def assertTracingSession(self) -> None:
- has_tracing_sessions, output = self.get_tracing_sessions()
- self.assertTrue(has_tracing_sessions, 'no tracing sessions exist:\n' + output)
+ self.assertTrue(
+ lttngpy.is_lttng_session_daemon_alive(),
+ 'no tracing sessions exist because there is no daemon',
+ )
+ session_names = lttngpy.get_session_names()
+ has_tracing_sessions = session_names is not None and 0 < len(session_names)
+ self.assertTrue(has_tracing_sessions, 'no tracing sessions exist')
def assertNoTracingSession(self) -> None:
- has_tracing_sessions, output = self.get_tracing_sessions()
- if has_tracing_sessions:
+ # If there is no session daemon, then there are no tracing sessions
+ if not lttngpy.is_lttng_session_daemon_alive():
+ return
+ session_names = lttngpy.get_session_names()
+ no_tracing_sessions = 0 == len(session_names)
+ if not no_tracing_sessions:
# Destroy tracing sessions if there are any, this way we can continue running tests and
# avoid possible interference between them
- self.run_lttng_destroy_all()
- self.assertFalse(has_tracing_sessions, 'tracing session(s) exist:\n' + output)
+ self.assertEqual(0, lttngpy.destroy_all_sessions())
+ self.assertTrue(no_tracing_sessions, f'tracing session(s) exist: {session_names}')
def assertTraceExist(self, trace_dir: str) -> None:
self.assertTrue(os.path.isdir(trace_dir), f'trace directory does not exist: {trace_dir}')
@@ -116,25 +113,6 @@ def create_test_tmpdir(self, test_name: str) -> str:
def get_subdirectories(self, directory: str) -> List[str]:
return [f.name for f in os.scandir(directory) if f.is_dir()]
- def run_lttng_list(self) -> str:
- process = subprocess.run(
- ['lttng', 'list'],
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE,
- encoding='utf-8',
- )
- return process.stdout + process.stderr
-
- def run_lttng_destroy_all(self):
- process = subprocess.run(
- ['lttng', 'destroy', '--all'],
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE,
- encoding='utf-8',
- )
- output = process.stdout + process.stderr
- self.assertEqual(0, process.returncode, f"'lttng destroy' command failed: {output}")
-
def run_command(
self,
args: List[str],
@@ -370,6 +348,17 @@ def test_base_path_not_exist(self) -> None:
shutil.rmtree(tmpdir)
+ def test_no_events(self) -> None:
+ tmpdir = self.create_test_tmpdir('test_no_events')
+
+ # Enabling no events should result in an error
+ ret = self.run_trace_command(
+ ['--path', tmpdir, '--ust', '--kernel'],
+ )
+ self.assertEqual(1, ret)
+
+ shutil.rmtree(tmpdir)
+
def test_unknown_context_field(self) -> None:
tmpdir = self.create_test_tmpdir('test_unknown_context_field')
diff --git a/tracetools_trace/package.xml b/tracetools_trace/package.xml
index c7e02d62..53b55e59 100644
--- a/tracetools_trace/package.xml
+++ b/tracetools_trace/package.xml
@@ -12,8 +12,7 @@
https://github.com/ros2/ros2_tracing/issues
Christophe Bedard
- lttng-tools
- python3-lttng
+ lttngpy
ament_copyright
ament_flake8
diff --git a/tracetools_trace/test/tracetools_trace/test_lttng_tracing.py b/tracetools_trace/test/tracetools_trace/test_lttng_tracing.py
index 63992e3e..312fd793 100644
--- a/tracetools_trace/test/tracetools_trace/test_lttng_tracing.py
+++ b/tracetools_trace/test/tracetools_trace/test_lttng_tracing.py
@@ -13,7 +13,6 @@
# limitations under the License.
import os
-import subprocess
import tempfile
import unittest
from unittest import mock
@@ -35,32 +34,7 @@ def test_is_lttng_installed(self):
with mock.patch('platform.system', return_value='Windows'):
self.assertFalse(is_lttng_installed())
- # LTTng command not found
- class PopenFileNotFound:
-
- def __init__(self, *args, **kwargs):
- raise FileNotFoundError('file not found')
-
- with mock.patch.object(subprocess, 'Popen', PopenFileNotFound):
- self.assertFalse(is_lttng_installed())
-
- # Other error when running LTTng command
- class PopenReturnCodeError:
-
- def __init__(self, *args, **kwargs):
- pass
-
- def communicate(self):
- return 'stdout'.encode(), 'stderr'.encode()
-
- @property
- def returncode(self):
- return 1
-
- with mock.patch.object(subprocess, 'Popen', PopenReturnCodeError):
- self.assertFalse(is_lttng_installed())
-
- # lttng Python package or version not found
+ # lttng-ctl or version not found
with mock.patch('tracetools_trace.tools.lttng.get_lttng_version', return_value=None):
self.assertFalse(is_lttng_installed())
@@ -81,11 +55,18 @@ def test_lttng_not_installed(self):
def test_no_kernel_tracer(self):
from tracetools_trace.tools.lttng_impl import setup
with (
+ mock.patch(
+ 'tracetools_trace.tools.lttng_impl.is_session_daemon_not_alive',
+ return_value=False,
+ ),
+ mock.patch(
+ 'tracetools_trace.tools.lttng_impl.is_session_daemon_unreachable',
+ return_value=False,
+ ),
mock.patch(
'tracetools_trace.tools.lttng_impl.is_kernel_tracer_available',
- return_value=(False, 'some error message'),
+ return_value=False,
),
- mock.patch('lttng.session_daemon_alive', return_value=1),
):
with self.assertRaises(RuntimeError):
setup(
@@ -166,9 +147,15 @@ def test_is_session_daemon_unreachable(self):
def test_unreachable_session_daemon(self):
from tracetools_trace.tools.lttng_impl import setup
- with mock.patch(
- 'tracetools_trace.tools.lttng_impl.is_session_daemon_unreachable',
- return_value=True,
+ with (
+ mock.patch(
+ 'tracetools_trace.tools.lttng_impl.is_session_daemon_not_alive',
+ return_value=False,
+ ),
+ mock.patch(
+ 'tracetools_trace.tools.lttng_impl.is_session_daemon_unreachable',
+ return_value=True,
+ ),
):
with self.assertRaises(RuntimeError):
setup(session_name='test-session', base_path='/tmp')
diff --git a/tracetools_trace/tracetools_trace/tools/lttng.py b/tracetools_trace/tracetools_trace/tools/lttng.py
index 8e109ce8..cd67aff1 100644
--- a/tracetools_trace/tracetools_trace/tools/lttng.py
+++ b/tracetools_trace/tracetools_trace/tools/lttng.py
@@ -16,42 +16,30 @@
"""Interface for tracing with LTTng."""
import platform
-import subprocess
import sys
from typing import Optional
from packaging.version import Version
try:
- from . import lttng_impl
- _lttng = lttng_impl # type: ignore
+ from . import lttng_impl as _lttng
except ImportError:
# Fall back on stub functions so that this still passes linter checks
- from . import lttng_stub
- _lttng = lttng_stub # type: ignore
+ # This will happen if lttngpy isn't found, in which case importing lttng_impl will fail
+ from . import lttng_stub as _lttng # type: ignore
def get_lttng_version() -> Optional[Version]:
"""
- Get version of lttng Python package.
+ Get version of lttng-ctl.
- :return: the version of the lttng Python package, or `None` if it is not available
+ :return: the version of lttng-ctl, or `None` if it is not available
"""
if not hasattr(_lttng, 'get_version') or not callable(_lttng.get_version):
return None
return _lttng.get_version()
-# Check lttng module version
-current_version = get_lttng_version()
-LTTNG_MIN_VERSION = '2.10.7'
-if current_version is None or current_version < Version(LTTNG_MIN_VERSION):
- print(
- f'lttng module version >={LTTNG_MIN_VERSION} required, found {str(current_version)}',
- file=sys.stderr,
- )
-
-
def lttng_init(**kwargs) -> Optional[str]:
"""
Set up and start LTTng session.
@@ -114,14 +102,13 @@ def is_lttng_installed(
Check if LTTng is installed.
It first checks if the OS can support LTTng.
- If so, it then simply checks if LTTng is installed using the 'lttng' command, and checks if the
- lttng Python package is installed (python3-lttng).
+ If so, it then checks if lttng-ctl is installed.
- Optionally, a minimum version can also be specified for the lttng Python package.
+ Optionally, a minimum version can also be specified for lttng-ctl.
- :param minimum_version: the minimum required lttng Python package version
- :return: True if LTTng and the lttng Python package are installed, and optionally if the
- version of lttng Python package is sufficient, False otherwise
+ :param minimum_version: the minimum required lttng-ctl version
+ :return: True if lttng-ctl is installed, and optionally if the version of lttng-ctl is
+ sufficient, False otherwise
"""
# Check system
message_doc = (
@@ -132,32 +119,19 @@ def is_lttng_installed(
if 'Linux' != system:
print(f"System '{system}' does not support LTTng.\n{message_doc}", file=sys.stderr)
return False
- # Check if LTTng (CLI) is installed
- try:
- process = subprocess.Popen(
- ['lttng', '--version'],
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE,
- )
- _, stderr = process.communicate()
- if 0 != process.returncode:
- raise RuntimeError(stderr.decode())
- except (RuntimeError, FileNotFoundError) as e:
- print(f'LTTng not found: {e}\n{message_doc}', file=sys.stderr)
- return False
- # Check if lttng Python package is installed
+ # Check if lttng-ctl is installed
lttng_version = get_lttng_version()
if not lttng_version:
print(
- f'lttng Python package (python3-lttng) not installed\n{message_doc}',
+ f'lttng-ctl (liblttng-ctl-dev) not installed\n{message_doc}',
file=sys.stderr,
)
return False
- # Check if lttng Python package version is sufficient
+ # Check if lttng-ctl version is sufficient
if minimum_version and lttng_version < Version(minimum_version):
print(
(
- f'lttng Python package (python3-lttng) version >={minimum_version} required, '
+ f'lttng-ctl (liblttng-ctl-dev) version >={minimum_version} required, '
f'found {str(lttng_version)}'
),
file=sys.stderr,
diff --git a/tracetools_trace/tracetools_trace/tools/lttng_impl.py b/tracetools_trace/tracetools_trace/tools/lttng_impl.py
index 6b8377b7..9d5e38b6 100644
--- a/tracetools_trace/tracetools_trace/tools/lttng_impl.py
+++ b/tracetools_trace/tracetools_trace/tools/lttng_impl.py
@@ -16,60 +16,43 @@
"""Implementation of the interface for tracing with LTTng."""
import os
-import re
import shlex
import subprocess
from typing import Dict
from typing import List
from typing import Optional
from typing import Set
-from typing import Tuple
from typing import Union
-import lttng
+from lttngpy import impl as lttngpy
from packaging.version import Version
-from .names import CONTEXT_TYPE_CONSTANTS_MAP
from .names import DEFAULT_CONTEXT
from .names import DEFAULT_EVENTS_ROS
+from .names import DOMAIN_TYPE_KERNEL
+from .names import DOMAIN_TYPE_USERSPACE
def get_version() -> Optional[Version]:
"""
- Get the version of the lttng module.
-
- The module does not have a __version__ attribute, but the version is mentioned in its __doc__,
- and seems to be written in a consistent way across versions.
+ Get version of lttng-ctl.
:return: the version as a Version object, or `None` if it cannot be extracted
"""
- doc_lines = str(lttng.__doc__).split('\n')
- filtered_doc_lines: List[str] = list(filter(None, doc_lines))
- if len(filtered_doc_lines) == 0:
- return None
- first_line = filtered_doc_lines[0]
- version_string = first_line.split(' ')[1]
- if not re.compile(r'^[0-9]+\.[0-9]+\.[0-9]+$').match(version_string):
+ if not lttngpy.is_available():
return None
- return Version(version_string)
+ return Version(lttngpy.LTTNG_CTL_VERSION)
-def is_kernel_tracer_available() -> Tuple[bool, Optional[str]]:
+def is_kernel_tracer_available() -> bool:
"""
Check if the kernel tracer is available.
- Runs 'lttng list -k', which gives an error if the kernel tracer is not available.
+ This must not be called if `lttngpy.is_available()` is `False`.
- :return: (`True` if available or `False` if not, stderr output if unavailable)
+ :return: `True` if available or `False` if not
"""
- process = subprocess.run(
- ['lttng', 'list', '-k'],
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE,
- )
- if 0 != process.returncode:
- return False, process.stderr.decode().strip('\n')
- return True, None
+ return not isinstance(lttngpy.get_tracepoints(domain_type=lttngpy.LTTNG_DOMAIN_KERNEL), int)
def get_lttng_home() -> Optional[str]:
@@ -137,6 +120,17 @@ def is_session_daemon_unreachable() -> bool:
return 1 == process.returncode or 'lttng-sessiond' != process.stdout.strip()
+def is_session_daemon_not_alive() -> bool:
+ """
+ Check if the session daemon isn't alive.
+
+ This must not be called if `lttngpy.is_available()` is `False`.
+
+ :return: `True` if the session daemon is not alive, or `False` if it is alive
+ """
+ return not lttngpy.is_lttng_session_daemon_alive()
+
+
def setup(
*,
session_name: str,
@@ -158,6 +152,7 @@ def setup(
Initialization will fail if the list of kernel events to be
enabled is not empty and if the kernel tracer is not installed.
+ This must not be called if `lttngpy.is_available()` is `False`.
Raises RuntimeError on failure, in which case the tracing session might still exist.
:param session_name: the name of the session
@@ -189,7 +184,7 @@ def setup(
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 lttng.session_daemon_alive() == 0:
+ if is_session_daemon_not_alive():
subprocess.run(
['lttng-sessiond', '--daemonize'],
)
@@ -202,22 +197,20 @@ def setup(
'directory exists. See: https://bugs.lttng.org/issues/1371'
)
# Error out if there is still no session daemon
- if lttng.session_daemon_alive() == 0:
+ if is_session_daemon_not_alive():
raise RuntimeError('failed to start lttng session daemon')
# Make sure the kernel tracer is available if there are kernel events
# Do this after spawning a session daemon, otherwise we can't detect the kernel tracer
- if 0 < len(kernel_events):
- kernel_tracer_available, message = is_kernel_tracer_available()
- if not kernel_tracer_available:
- raise RuntimeError(
- f'kernel tracer is not available: {message}\n'
- ' cannot use kernel events:\n'
- " 'ros2 trace' command: cannot use '-k' option\n"
- " 'Trace' action: cannot set 'events_kernel'/'events-kernel' list\n"
- ' install the kernel tracer, e.g., on Ubuntu, install lttng-modules-dkms\n'
- ' see: https://github.com/ros2/ros2_tracing#building'
- )
+ if 0 < len(kernel_events) and not is_kernel_tracer_available():
+ raise RuntimeError(
+ 'kernel tracer is not available:\n'
+ ' cannot use kernel events:\n'
+ " 'ros2 trace' command: cannot use '-k' option\n"
+ " 'Trace' action: cannot set 'events_kernel'/'events-kernel' list\n"
+ ' install the kernel tracer, e.g., on Ubuntu, install lttng-modules-dkms\n'
+ ' see: https://github.com/ros2/ros2_tracing#building'
+ )
# Convert lists to sets
if not isinstance(ros_events, set):
@@ -229,65 +222,86 @@ def setup(
ust_enabled = ros_events is not None and len(ros_events) > 0
kernel_enabled = kernel_events is not None and len(kernel_events) > 0
-
- # Domains
- if ust_enabled:
- domain_ust = lttng.Domain()
- domain_ust.type = lttng.DOMAIN_UST
- # Per-user buffer
- domain_ust.buf_type = lttng.BUFFER_PER_UID
- channel_ust = lttng.Channel()
- channel_ust.name = channel_name_ust
- # Discard, do not overwrite
- channel_ust.attr.overwrite = 0
- # We use 2 sub-buffers because the number of sub-buffers is pointless in discard mode,
- # and switching between sub-buffers introduces noticeable CPU overhead
- channel_ust.attr.subbuf_size = subbuffer_size_ust
- channel_ust.attr.num_subbuf = 2
- # Ignore switch timer interval and use read timer instead
- channel_ust.attr.switch_timer_interval = 0
- channel_ust.attr.read_timer_interval = 200
- # mmap channel output (only option for UST)
- channel_ust.attr.output = lttng.EVENT_MMAP
- events_list_ust = _create_events(ros_events)
- if kernel_enabled:
- domain_kernel = lttng.Domain()
- domain_kernel.type = lttng.DOMAIN_KERNEL
- # Global buffer (only option for kernel domain)
- domain_kernel.buf_type = lttng.BUFFER_GLOBAL
- channel_kernel = lttng.Channel()
- channel_kernel.name = channel_name_kernel
- # Discard, do not overwrite
- channel_kernel.attr.overwrite = 0
- channel_kernel.attr.subbuf_size = subbuffer_size_kernel
- channel_kernel.attr.num_subbuf = 2
- # Ignore switch timer interval and use read timer instead
- channel_kernel.attr.switch_timer_interval = 0
- channel_kernel.attr.read_timer_interval = 200
- # mmap channel output instead of splice
- channel_kernel.attr.output = lttng.EVENT_MMAP
- events_list_kernel = _create_events(kernel_events)
+ if not(ust_enabled or kernel_enabled):
+ raise RuntimeError('no events enabled')
# Create session
# LTTng will create the parent directories if needed
- _create_session(session_name, full_path)
+ _create_session(
+ session_name=session_name,
+ full_path=full_path,
+ )
- # Handles, channels, events
- handle_ust = None
+ # Enable channel, events, and contexts for each domain
+ contexts_dict = _normalize_contexts_dict(context_fields)
if ust_enabled:
- handle_ust = _create_handle(session_name, domain_ust)
- _enable_channel(handle_ust, channel_ust)
- _enable_events(handle_ust, events_list_ust, channel_ust.name)
- handle_kernel = None
+ domain = DOMAIN_TYPE_USERSPACE
+ domain_type = lttngpy.LTTNG_DOMAIN_UST
+ channel_name = channel_name_ust
+ _enable_channel(
+ session_name=session_name,
+ domain_type=domain_type,
+ # Per-user buffer
+ buffer_type=lttngpy.LTTNG_BUFFER_PER_UID,
+ channel_name=channel_name,
+ # Discard, do not overwrite
+ overwrite=0,
+ # We use 2 sub-buffers because the number of sub-buffers is pointless in discard mode,
+ # and switching between sub-buffers introduces noticeable CPU overhead
+ subbuf_size=subbuffer_size_ust,
+ num_subbuf=2,
+ # Ignore switch timer interval and use read timer instead
+ switch_timer_interval=0,
+ read_timer_interval=200,
+ # mmap channel output (only option for UST)
+ output=lttngpy.LTTNG_EVENT_MMAP,
+ )
+ _enable_events(
+ session_name=session_name,
+ domain_type=domain_type,
+ channel_name=channel_name,
+ events=ros_events,
+ )
+ _add_contexts(
+ session_name=session_name,
+ domain_type=domain_type,
+ channel_name=channel_name,
+ context_fields=contexts_dict.get(domain),
+ )
if kernel_enabled:
- handle_kernel = _create_handle(session_name, domain_kernel)
- _enable_channel(handle_kernel, channel_kernel)
- _enable_events(handle_kernel, events_list_kernel, channel_kernel.name)
-
- # Context
- contexts_dict = _normalize_contexts_dict(
- {'kernel': handle_kernel, 'userspace': handle_ust}, context_fields)
- _add_context(contexts_dict)
+ domain = DOMAIN_TYPE_KERNEL
+ domain_type = lttngpy.LTTNG_DOMAIN_KERNEL
+ channel_name = channel_name_kernel
+ _enable_channel(
+ session_name=session_name,
+ domain_type=domain_type,
+ # Global buffer (only option for kernel domain)
+ buffer_type=lttngpy.LTTNG_BUFFER_GLOBAL,
+ channel_name=channel_name,
+ # Discard, do not overwrite
+ overwrite=0,
+ # We use 2 sub-buffers because the number of sub-buffers is pointless in discard mode,
+ # and switching between sub-buffers introduces noticeable CPU overhead
+ subbuf_size=subbuffer_size_kernel,
+ num_subbuf=2,
+ # Ignore switch timer interval and use read timer instead
+ switch_timer_interval=0,
+ read_timer_interval=200,
+ # mmap channel output instead of splice
+ output=lttngpy.LTTNG_EVENT_MMAP,
+ )
+ _enable_events(
+ session_name=session_name,
+ domain_type=domain_type,
+ channel_name=channel_name,
+ events=kernel_events,
+ )
+ _add_contexts(
+ session_name=session_name,
+ domain_type=domain_type,
+ channel_name=channel_name,
+ context_fields=contexts_dict.get(domain),
+ )
return full_path
@@ -300,13 +314,14 @@ def start(
"""
Start LTTng session, and check for errors.
+ This must not be called if `lttngpy.is_available()` is `False`.
Raises RuntimeError on failure to start.
:param session_name: the name of the session
"""
- result = lttng.start(session_name)
+ result = lttngpy.lttng_start_tracing(session_name=session_name)
if result < 0:
- raise RuntimeError(f'failed to start tracing: {lttng.strerror(result)}')
+ raise RuntimeError(f'failed to start tracing: {lttngpy.lttng_strerror(result)}')
def stop(
@@ -318,14 +333,15 @@ def stop(
"""
Stop LTTng session, and check for errors.
+ This must not be called if `lttngpy.is_available()` is `False`.
Raises RuntimeError on failure to stop, unless ignored.
:param session_name: the name of the session
:param ignore_error: whether to ignore any error when stopping
"""
- result = lttng.stop(session_name)
+ result = lttngpy.lttng_stop_tracing(session_name=session_name)
if result < 0 and not ignore_error:
- raise RuntimeError(f'failed to stop tracing: {lttng.strerror(result)}')
+ raise RuntimeError(f'failed to stop tracing: {lttngpy.lttng_strerror(result)}')
def destroy(
@@ -337,181 +353,106 @@ def destroy(
"""
Destroy LTTng session, and check for errors.
- Raises RuntimeError on failure to stop, unless ignored.
+ This must not be called if `lttngpy.is_available()` is `False`.
+ Raises RuntimeError on failure to destroy, unless ignored.
:param session_name: the name of the session
:param ignore_error: whether to ignore any error when destroying
"""
- result = lttng.destroy(session_name)
+ result = lttngpy.lttng_destroy_session(session_name=session_name)
if result < 0 and not ignore_error:
- raise RuntimeError(f'failed to destroy tracing session: {lttng.strerror(result)}')
-
-
-def _create_events(
- event_names: Set[str],
-) -> List[lttng.Event]:
- """
- Create events list from names.
-
- :param event_names: a set of names to create events for
- :return: the list of events
- """
- events_list = []
- for event_name in event_names:
- e = lttng.Event()
- e.name = event_name
- e.type = lttng.EVENT_TRACEPOINT
- e.loglevel_type = lttng.EVENT_LOGLEVEL_ALL
- events_list.append(e)
- return events_list
+ raise RuntimeError(f'failed to destroy tracing session: {lttngpy.lttng_strerror(result)}')
def _create_session(
+ *,
session_name: str,
full_path: str,
) -> None:
"""
Create session from name and full directory path, and check for errors.
+ This must not be called if `lttngpy.is_available()` is `False`.
+ Raises RuntimeError on failure.
+
:param session_name: the name of the session
:param full_path: the full path to the main directory to write trace data to
"""
- result = lttng.create(session_name, full_path)
- # See lttng-tools/include/lttng/lttng-error.h
- if -28 == result:
- # Sessions seem to persist, so if it already exists,
- # just destroy it and try again
+ result = lttngpy.lttng_create_session(
+ session_name=session_name,
+ url=full_path,
+ )
+ if -lttngpy.LTTNG_ERR_EXIST_SESS.value == result:
+ # Sessions may persist if there was an error previously, so if it already exists, just
+ # destroy it and try again
destroy(session_name=session_name)
- result = lttng.create(session_name, full_path)
+ result = lttngpy.lttng_create_session(
+ session_name=session_name,
+ url=full_path,
+ )
if result < 0:
- raise RuntimeError(f'session creation failed: {lttng.strerror(result)}')
-
+ raise RuntimeError(f'session creation failed: {lttngpy.lttng_strerror(result)}')
-def _create_handle(
- session_name: str,
- domain: lttng.Domain,
-) -> lttng.Handle:
- """
- Create a handle for a given session name and a domain, and check for errors.
- :param session_name: the name of the session
- :param domain: the domain to be used
- :return: the handle
+def _enable_channel(**kwargs) -> None:
"""
- handle = None
- handle = lttng.Handle(session_name, domain)
- if handle is None:
- raise RuntimeError('handle creation failed')
- return handle
-
+ Enable channel, and check for errors.
-def _enable_channel(
- handle: lttng.Handle,
- channel: lttng.Channel,
-) -> None:
- """
- Enable channel for a handle, and check for errors.
+ This must not be called if `lttngpy.is_available()` is `False`.
+ Raises RuntimeError on failure.
- :param handle: the handle to be used
- :param channel: the channel to enable
+ See `lttngpy.enable_channel` for kwargs.
"""
- result = lttng.enable_channel(handle, channel)
+ result = lttngpy.enable_channel(**kwargs)
if result < 0:
- raise RuntimeError(f'channel enabling failed: {lttng.strerror(result)}')
-
-
-def _enable_events(
- handle: lttng.Handle,
- events_list: List[lttng.Event],
- channel_name: str,
-) -> None:
- """
- Enable events list for a given handle and channel name, and check for errors.
-
- :param handle: the handle to be used
- :param events_list: the list of events to enable
- :param channel_name: the name of the channel to associate
- """
- for event in events_list:
- result = lttng.enable_event(handle, event, channel_name)
- if result < 0:
- raise RuntimeError(f'event enabling failed: {lttng.strerror(result)}')
+ raise RuntimeError(f'channel enabling failed: {lttngpy.lttng_strerror(result)}')
-context_map = {
- name: getattr(lttng, name_constant, None) if name_constant is not None else None
- for name, name_constant in CONTEXT_TYPE_CONSTANTS_MAP.items()
-}
-
-
-def _context_field_name_to_type(
- context_field_name: str,
-) -> Optional[int]:
- """
- Convert from context name to LTTng enum/constant type.
-
- :param context_field_name: the generic name for the context field
- :return: the associated type, or `None` if it cannot be found
+def _enable_events(**kwargs) -> None:
"""
- return context_map.get(context_field_name, None)
+ Enable events for a given channel name, and check for errors.
+ This must not be called if `lttngpy.is_available()` is `False`.
+ Raises RuntimeError on failure.
-def _create_context_list(
- context_fields: Set[str],
-) -> List[lttng.EventContext]:
+ See `lttngpy.enable_events` for kwargs.
"""
- Create context list from field names, and check for errors.
-
- :param context_fields: the set of context fields
- :return: the event context list
- """
- context_list = []
- for context_field_name in context_fields:
- ec = lttng.EventContext()
- context_type = _context_field_name_to_type(context_field_name)
- if context_type is not None:
- ec.ctx = context_type
- context_list.append(ec)
- else:
- raise RuntimeError(f'failed to find context type: {context_field_name}')
- return context_list
+ result = lttngpy.enable_events(**kwargs)
+ if result < 0:
+ raise RuntimeError(f'event enabling failed: {lttngpy.lttng_strerror(result)}')
def _normalize_contexts_dict(
- handles: Dict[str, Optional[lttng.Handle]],
context_fields: Union[Set[str], Dict[str, List[str]]],
-) -> Dict[lttng.Handle, List[lttng.EventContext]]:
+) -> Dict[str, Set[str]]:
"""
- Normalize context list/set/dict to dict.
+ Normalize context set/dict to dict.
- :param handles: the mapping from domain type name to handle
- :param context_fields: the set of context field names,
- or mapping from domain type name to list of context field names
- :return: a dictionary of handle to list of event contexts
+ :param context_fields: the names of context fields to enable
+ if it's a set, the context fields are enabled for both kernel and userspace;
+ if it's a dictionary: { domain type string -> context fields list }
+ with the domain type string being either 'kernel' or 'userspace'
+ :return: a dictionary of domain type name to list of context field name
"""
- handles = {domain: handle for domain, handle in handles.items() if handle is not None}
- contexts_dict = {}
- if isinstance(context_fields, set):
- contexts_dict = {h: _create_context_list(context_fields) for _, h in handles.items()}
- elif isinstance(context_fields, dict):
- contexts_dict = \
- {h: _create_context_list(set(context_fields[d])) for d, h in handles.items()}
- else:
- assert False
- return contexts_dict
-
-
-def _add_context(
- contexts: Dict[lttng.Handle, List[lttng.EventContext]],
-) -> None:
+ if isinstance(context_fields, dict):
+ return {domain: set(field_names) for domain, field_names in context_fields.items()}
+ assert isinstance(context_fields, set)
+ return {
+ 'userspace': context_fields,
+ 'kernel': context_fields,
+ }
+
+
+def _add_contexts(**kwargs) -> None:
"""
Add context lists to given handles, and check for errors.
- :param contexts: the dictionay of context handles -> event contexts
+ This must not be called if `lttngpy.is_available()` is `False`.
+ Raises RuntimeError on failure.
+
+ See `lttngpy.add_contexts` for kwargs.
"""
- for handle, contexts_list in contexts.items():
- for context in contexts_list:
- result = lttng.add_context(handle, context, None, None)
- if result < 0:
- raise RuntimeError(
- f'failed to add context field {str(context)}: {lttng.strerror(result)}')
+ result = lttngpy.add_contexts(**kwargs)
+ if result < 0:
+ raise RuntimeError(
+ f'failed to add context field: {lttngpy.lttng_strerror(result)}')
diff --git a/tracetools_trace/tracetools_trace/tools/lttng_stub.py b/tracetools_trace/tracetools_trace/tools/lttng_stub.py
index ce7ecc75..2a6b5c05 100644
--- a/tracetools_trace/tracetools_trace/tools/lttng_stub.py
+++ b/tracetools_trace/tracetools_trace/tools/lttng_stub.py
@@ -15,7 +15,7 @@
"""Stub version of the interface for tracing with LTTng."""
-ERROR_MESSAGE = 'lttng module not found, but still tried to use it'
+ERROR_MESSAGE = 'LTTng Python bindings not available, but still tried to use them'
def setup(*args, **kwargs) -> None:
diff --git a/tracetools_trace/tracetools_trace/tools/names.py b/tracetools_trace/tracetools_trace/tools/names.py
index 6b3effa8..ab5d37af 100644
--- a/tracetools_trace/tracetools_trace/tools/names.py
+++ b/tracetools_trace/tracetools_trace/tools/names.py
@@ -101,29 +101,8 @@
DEFAULT_EVENTS_UST = DEFAULT_EVENTS_ROS
-CONTEXT_TYPE_CONSTANTS_MAP = {
- 'pid': 'EVENT_CONTEXT_PID',
- 'procname': 'EVENT_CONTEXT_PROCNAME',
- 'prio': 'EVENT_CONTEXT_PRIO',
- 'nice': 'EVENT_CONTEXT_NICE',
- 'vpid': 'EVENT_CONTEXT_VPID',
- 'tid': 'EVENT_CONTEXT_TID',
- 'vtid': 'EVENT_CONTEXT_VTID',
- 'ppid': 'EVENT_CONTEXT_PPID',
- 'vppid': 'EVENT_CONTEXT_VPPID',
- 'pthread_id': 'EVENT_CONTEXT_PTHREAD_ID',
- 'hostname': 'EVENT_CONTEXT_HOSTNAME',
- 'ip': 'EVENT_CONTEXT_IP',
- 'interruptible': 'EVENT_CONTEXT_INTERRUPTIBLE',
- 'preemptible': 'EVENT_CONTEXT_PREEMPTIBLE',
- 'need_reschedule': 'EVENT_CONTEXT_NEED_RESCHEDULE',
- 'migratable': 'EVENT_CONTEXT_MIGRATABLE',
- 'perf:thread:instructions': None,
- 'perf:thread:cycles': None,
- 'perf:thread:cpu-cycles': None,
-}
-
-CONTEXT = list(CONTEXT_TYPE_CONSTANTS_MAP.keys())
+DOMAIN_TYPE_KERNEL = 'kernel'
+DOMAIN_TYPE_USERSPACE = 'userspace'
# These apply to both kernel & userspace domains
DEFAULT_CONTEXT = [