Skip to content

Commit

Permalink
Allow enabling syscalls through ros2 trace or the Trace action (#137)
Browse files Browse the repository at this point in the history
Signed-off-by: Christophe Bedard <[email protected]>
  • Loading branch information
christophebedard authored Oct 8, 2024
1 parent 4376bdc commit 57e6ede
Show file tree
Hide file tree
Showing 11 changed files with 187 additions and 24 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ $ ros2 trace stop session_name # Stop tracing after starting or resuming
Run each command with `-h` for more information.
You must [install the kernel tracer](#building) if you want to enable kernel events (using the `-k`/`--kernel-events` option).
You must [install the kernel tracer](#building) if you want to enable [kernel](https://lttng.org/docs/v2.13/#doc-tracing-the-linux-kernel) events (using the `-k`/`--kernel-events` option) or syscalls (using the `--syscalls` option).
If you have installed the kernel tracer, use kernel tracing, and still encounter an error here, make sure to [add your user to the `tracing` group](#tracing).
### Launch file trace action
Expand All @@ -185,7 +185,7 @@ $ ros2 launch tracetools_launch example.launch.py
The `Trace` action will also set the `LD_PRELOAD` environment to preload [LTTng's userspace tracing helper(s)](https://lttng.org/docs/v2.13/#doc-prebuilt-ust-helpers) if the corresponding event(s) are enabled.
For more information, see [this example launch file](./tracetools_launch/launch/example.launch.py) and the [`Trace` action](./tracetools_launch/tracetools_launch/action.py).
You must [install the kernel tracer](#building) if you want to enable kernel events (`events_kernel` in Python, `events-kernel` in XML or YAML).
You must [install the kernel tracer](#building) if you want to enable [kernel](https://lttng.org/docs/v2.13/#doc-tracing-the-linux-kernel) events (`events_kernel` in Python, `events-kernel` in XML or YAML) or syscalls (`syscalls` in Python, XML, or YAML).
If you have installed the kernel tracer, use kernel tracing, and still encounter an error here, make sure to [add your user to the `tracing` group](#tracing).
## Design
Expand Down
15 changes: 15 additions & 0 deletions lttngpy/src/lttngpy/_lttngpy_pybind11.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,16 @@ PYBIND11_MODULE(_lttngpy_pybind11, m) {
py::arg("output"));

// Event
py::enum_<lttng_event_type>(m, "lttng_event_type")
.value("LTTNG_EVENT_ALL", LTTNG_EVENT_ALL)
.value("LTTNG_EVENT_TRACEPOINT", LTTNG_EVENT_TRACEPOINT)
.value("LTTNG_EVENT_PROBE", LTTNG_EVENT_PROBE)
.value("LTTNG_EVENT_FUNCTION", LTTNG_EVENT_FUNCTION)
.value("LTTNG_EVENT_FUNCTION_ENTRY", LTTNG_EVENT_FUNCTION_ENTRY)
.value("LTTNG_EVENT_NOOP", LTTNG_EVENT_NOOP)
.value("LTTNG_EVENT_SYSCALL", LTTNG_EVENT_SYSCALL)
.value("LTTNG_EVENT_USERSPACE_PROBE", LTTNG_EVENT_USERSPACE_PROBE)
.export_values();
py::enum_<lttng_event_output>(m, "lttng_event_output")
.value("LTTNG_EVENT_SPLICE", LTTNG_EVENT_SPLICE)
.value("LTTNG_EVENT_MMAP", LTTNG_EVENT_MMAP)
Expand All @@ -141,6 +151,7 @@ PYBIND11_MODULE(_lttngpy_pybind11, m) {
py::kw_only(),
py::arg("session_name"),
py::arg("domain_type"),
py::arg("event_type"),
py::arg("channel_name"),
py::arg("events"));
m.def(
Expand All @@ -149,6 +160,10 @@ PYBIND11_MODULE(_lttngpy_pybind11, m) {
"Get tracepoints.",
py::kw_only(),
py::arg("domain_type"));
m.def(
"get_syscalls",
&lttngpy::get_syscalls,
"Get syscalls.");
m.def(
"add_contexts",
&lttngpy::add_contexts,
Expand Down
21 changes: 20 additions & 1 deletion lttngpy/src/lttngpy/event.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ namespace lttngpy
int enable_events(
const std::string & session_name,
const enum lttng_domain_type domain_type,
const enum lttng_event_type event_type,
const std::string & channel_name,
const std::set<std::string> & events)
{
Expand All @@ -59,7 +60,7 @@ int enable_events(
break;
}
event_name.copy(event->name, LTTNG_SYMBOL_NAME_LEN);
event->type = LTTNG_EVENT_TRACEPOINT;
event->type = event_type;

ret = lttng_enable_event(handle, event, channel_name.c_str());
lttng_event_destroy(event);
Expand Down Expand Up @@ -101,6 +102,24 @@ std::variant<int, std::set<std::string>> get_tracepoints(const enum lttng_domain
return tracepoints_var;
}

std::variant<int, std::set<std::string>> get_syscalls()
{
struct lttng_event * events = nullptr;
int ret = lttng_list_syscalls(&events);
std::variant<int, std::set<std::string>> syscalls_var = ret;
if (0 <= ret) {
std::set<std::string> syscalls = {};
const int num_events = ret;
for (int i = 0; i < num_events; i++) {
syscalls.insert(events[i].name);
}
syscalls_var = syscalls;
}

std::free(events);
return syscalls_var;
}

int _fill_in_event_context(
const std::string & context_field,
const enum lttng_domain_type domain_type,
Expand Down
10 changes: 10 additions & 0 deletions lttngpy/src/lttngpy/event.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,15 @@ namespace lttngpy
*
* \param session_name the session name
* \param domain_type the domain type
* \param event_type the event type
* \param channel_name the channel name
* \param events the set of event names
* \return 0 on success, else a negative LTTng error code
*/
int enable_events(
const std::string & session_name,
const enum lttng_domain_type domain_type,
const enum lttng_event_type event_type,
const std::string & channel_name,
const std::set<std::string> & events);

Expand All @@ -49,6 +51,14 @@ int enable_events(
*/
std::variant<int, std::set<std::string>> get_tracepoints(const enum lttng_domain_type domain_type);

/**
* Get syscalls.
*
* \return the set of syscalls, else a negative LTTng error code (e.g., if kernel tracer is not
* available)
*/
std::variant<int, std::set<std::string>> get_syscalls();

/**
* Add contexts.
*
Expand Down
58 changes: 57 additions & 1 deletion test_ros2trace/test/test_ros2trace/test_trace.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
from launch import LaunchService
from launch_ros.actions import Node
from lttngpy import impl as lttngpy
from tracetools_read import get_event_name
from tracetools_test.mark_process import get_corresponding_trace_test_events
from tracetools_test.mark_process import get_trace_test_id
from tracetools_test.mark_process import TRACE_TEST_ID_ENV_VAR
Expand All @@ -51,6 +52,24 @@ def are_tracepoints_included() -> bool:
return 0 == process.returncode


def skip_if_no_kernel_tracing(func):
"""Skip test if kernel tracing for kernel tracepoints or syscalls is not available."""
def wrapper(*args, **kwargs):
kernel_tracepoints = lttngpy.get_tracepoints(domain_type=lttngpy.LTTNG_DOMAIN_KERNEL)
syscalls = lttngpy.get_syscalls()
error = ', '.join(
f'{name}: {lttngpy.lttng_strerror(error_code)}' for name, error_code in (
('kernel tracepoints', kernel_tracepoints),
('syscalls', syscalls),
)
if isinstance(error_code, int)
)
if error:
raise unittest.SkipTest(f'kernel tracer is required: {error}')
return func(*args, **kwargs)
return wrapper


@unittest.skipIf(not is_lttng_installed(minimum_version='2.9.0'), 'LTTng is required')
class TestROS2TraceCLI(unittest.TestCase):

Expand Down Expand Up @@ -94,6 +113,7 @@ def assertTraceContains(
self,
trace_dir: str,
*,
expected_event_name: List[str] = [],
expected_field_value: List[Tuple[str, str]] = [],
expected_field: List[str] = [],
) -> int:
Expand All @@ -108,6 +128,11 @@ def assertTraceContains(
0,
f'no matching trace test events found in trace from events: {events_all}',
)
for event_name in expected_event_name:
self.assertTrue(
any(event_name == get_event_name(event) for event in events),
f'{event_name} not found in events: {events}',
)
for field_value in expected_field_value:
self.assertTrue(
any(field_value in event.items() for event in events),
Expand Down Expand Up @@ -309,6 +334,37 @@ def test_default_tracing(self) -> None:

shutil.rmtree(tmpdir)

@skip_if_no_kernel_tracing
def test_kernel_tracing(self) -> None:
tmpdir = self.create_test_tmpdir('test_kernel_tracing')
session_name = 'test_kernel_tracing'

process = self.run_trace_command_start(
[
'--path', tmpdir,
'--ust', TRACE_TEST_ID_TP_NAME,
'--kernel', 'sched_switch',
'--syscall', 'openat',
'--session-name', session_name,
],
wait_for_start=True,
)
self.run_nodes()
ret = self.run_trace_command_stop(process)
self.assertEqual(0, ret)
trace_dir = os.path.join(tmpdir, session_name)
self.assertTraceContains(
trace_dir,
expected_event_name=[
'sched_switch',
'syscall_entry_openat',
'syscall_exit_openat',
],
)
self.assertTracingSessionNotExist(session_name)

shutil.rmtree(tmpdir)

def test_env_var_ros_trace_dir(self) -> None:
tmpdir = self.create_test_tmpdir('test_env_var_ros_trace_dir')
session_name = 'test_env_var_ros_trace_dir'
Expand Down Expand Up @@ -398,7 +454,7 @@ def test_no_events(self) -> None:

# Enabling no events should result in an error
ret = self.run_trace_command(
['--path', tmpdir, '--ust', '--kernel', '--session-name', session_name],
['--path', tmpdir, '--ust', '--kernel', '--syscall', '--session-name', session_name],
)
self.assertEqual(1, ret)
self.assertTraceNotExist(os.path.join(tmpdir, session_name))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ def test_action(self) -> None:
session_name='my-session-name',
base_path=tmpdir,
events_kernel=[],
syscalls=[],
events_ust=[
'ros2:*',
'*',
Expand All @@ -126,6 +127,7 @@ def test_action_frontend_xml(self) -> None:
base-path="{}"
append-trace="true"
events-kernel=""
syscalls=""
events-ust="ros2:* *"
subbuffer-size-ust="524288"
subbuffer-size-kernel="1048576"
Expand Down Expand Up @@ -154,6 +156,7 @@ def test_action_frontend_yaml(self) -> None:
base-path: {}
append-trace: true
events-kernel: ""
syscalls: ""
events-ust: ros2:* *
subbuffer-size-ust: 524288
subbuffer-size-kernel: 1048576
Expand All @@ -176,6 +179,7 @@ def test_action_context_per_domain(self) -> None:
session_name='my-session-name',
base_path=tmpdir,
events_kernel=[],
syscalls=[],
events_ust=[
'ros2:*',
'*',
Expand All @@ -193,6 +197,7 @@ def test_action_context_per_domain(self) -> None:
session_name='my-session-name',
base_path=tmpdir,
events_kernel=[],
syscalls=[],
events_ust=[
'ros2:*',
'*',
Expand Down Expand Up @@ -234,6 +239,7 @@ def test_action_substitutions(self) -> None:
session_name=LaunchConfiguration(session_name_arg.name),
base_path=TextSubstitution(text=tmpdir),
events_kernel=[],
syscalls=[],
events_ust=[
EnvironmentVariable(name='TestTraceAction__event_ust'),
TextSubstitution(text='*'),
Expand Down Expand Up @@ -270,6 +276,7 @@ def test_action_ld_preload(self) -> None:
session_name='my-session-name',
base_path=tmpdir,
events_kernel=[],
syscalls=[],
events_ust=[
'lttng_ust_cyg_profile_fast:*',
'lttng_ust_libc:*',
Expand Down Expand Up @@ -323,6 +330,7 @@ def test_append_timestamp(self) -> None:
append_timestamp=True,
base_path=tmpdir,
events_kernel=[],
syscalls=[],
events_ust=[
'ros2:*',
'*',
Expand All @@ -348,6 +356,7 @@ def test_append_trace(self) -> None:
base_path=tmpdir,
append_trace=False,
events_kernel=[],
syscalls=[],
events_ust=[
'ros2:*',
'*',
Expand All @@ -367,6 +376,7 @@ def test_append_trace(self) -> None:
base_path=tmpdir,
append_trace=True,
events_kernel=[],
syscalls=[],
events_ust=[
'ros2:*',
'*',
Expand Down
15 changes: 15 additions & 0 deletions tracetools_launch/tracetools_launch/action.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ def __init__(
append_trace: bool = False,
events_ust: Iterable[SomeSubstitutionsType] = names.DEFAULT_EVENTS_ROS,
events_kernel: Iterable[SomeSubstitutionsType] = [],
syscalls: Iterable[SomeSubstitutionsType] = [],
context_fields:
Union[Iterable[SomeSubstitutionsType], Dict[str, Iterable[SomeSubstitutionsType]]]
= names.DEFAULT_CONTEXT,
Expand Down Expand Up @@ -140,6 +141,7 @@ def __init__(
otherwise an error is reported
:param events_ust: the list of ROS UST events to enable
:param events_kernel: the list of kernel events to enable
:param syscalls: the list of syscalls to enable
:param context_fields: the names of context fields to enable
if it's a list or a set, the context fields are enabled for both kernel and userspace;
if it's a dictionary: { domain type string -> context fields list }
Expand All @@ -160,6 +162,7 @@ def __init__(
self._trace_directory = None
self._events_ust = [normalize_to_list_of_substitutions(x) for x in events_ust]
self._events_kernel = [normalize_to_list_of_substitutions(x) for x in events_kernel]
self._syscalls = [normalize_to_list_of_substitutions(x) for x in syscalls]
self._context_fields = \
{
domain: [normalize_to_list_of_substitutions(field) for field in fields]
Expand Down Expand Up @@ -195,6 +198,10 @@ def events_ust(self):
def events_kernel(self):
return self._events_kernel

@property
def syscalls(self):
return self._syscalls

@property
def context_fields(self):
return self._context_fields
Expand Down Expand Up @@ -295,6 +302,10 @@ def parse(cls, entity: Entity, parser: Parser):
if events_kernel is not None:
kwargs['events_kernel'] = cls._parse_cmdline(events_kernel, parser) \
if events_kernel else []
syscalls = entity.get_attr('syscalls', optional=True)
if syscalls is not None:
kwargs['syscalls'] = cls._parse_cmdline(syscalls, parser) \
if syscalls else []
context_fields = entity.get_attr('context-fields', optional=True)
if context_fields is not None:
kwargs['context_fields'] = cls._parse_cmdline(context_fields, parser) \
Expand Down Expand Up @@ -374,6 +385,7 @@ def _perform_substitutions(self, context: LaunchContext) -> None:
if self._base_path else path.get_tracing_directory()
self._events_ust = [perform_substitutions(context, x) for x in self._events_ust]
self._events_kernel = [perform_substitutions(context, x) for x in self._events_kernel]
self._syscalls = [perform_substitutions(context, x) for x in self._syscalls]
self._context_fields = \
{
domain: [perform_substitutions(context, field) for field in fields]
Expand Down Expand Up @@ -418,6 +430,7 @@ def _setup(self) -> bool:
append_trace=self._append_trace,
ros_events=self._events_ust,
kernel_events=self._events_kernel,
syscalls=self._syscalls,
context_fields=self._context_fields,
subbuffer_size_ust=self._subbuffer_size_ust,
subbuffer_size_kernel=self._subbuffer_size_kernel,
Expand All @@ -427,6 +440,7 @@ def _setup(self) -> bool:
self._logger.info(f'Writing tracing session to: {self._trace_directory}')
self._logger.debug(f'UST events: {self._events_ust}')
self._logger.debug(f'Kernel events: {self._events_kernel}')
self._logger.debug(f'Syscalls: {self._syscalls}')
self._logger.debug(f'Context fields: {self._context_fields}')
self._logger.debug(f'LD_PRELOAD: {self._ld_preload_actions}')
self._logger.debug(f'UST subbuffer size: {self._subbuffer_size_ust}')
Expand Down Expand Up @@ -454,6 +468,7 @@ def __repr__(self):
f'trace_directory={self._trace_directory}, '
f'events_ust={self._events_ust}, '
f'events_kernel={self._events_kernel}, '
f'syscalls={self._syscalls}, '
f'context_fields={self._context_fields}, '
f'ld_preload_actions={self._ld_preload_actions}, '
f'subbuffer_size_ust={self._subbuffer_size_ust}, '
Expand Down
6 changes: 6 additions & 0 deletions tracetools_trace/test/tracetools_trace/test_lttng_tracing.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,12 @@ def test_no_kernel_tracer(self):
base_path='/tmp',
kernel_events=['sched_switch'],
)
with self.assertRaises(RuntimeError):
setup(
session_name='test-session',
base_path='/tmp',
syscalls=['open'],
)

def test_get_lttng_home(self):
from tracetools_trace.tools.lttng_impl import get_lttng_home
Expand Down
Loading

0 comments on commit 57e6ede

Please sign in to comment.