Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow enabling syscalls through ros2 trace or the Trace action #137

Merged
merged 2 commits into from
Oct 8, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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).
christophebedard marked this conversation as resolved.
Show resolved Hide resolved
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
46 changes: 45 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 Down Expand Up @@ -94,6 +95,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 +110,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 +316,43 @@ def test_default_tracing(self) -> None:

shutil.rmtree(tmpdir)

@unittest.skipIf(
(
isinstance(lttngpy.get_tracepoints(domain_type=lttngpy.LTTNG_DOMAIN_KERNEL), int) or
isinstance(lttngpy.get_syscalls(), int)
),
'kernel tracer is required',
christophebedard marked this conversation as resolved.
Show resolved Hide resolved
)
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 +442,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
4 changes: 4 additions & 0 deletions tracetools_trace/tracetools_trace/tools/args.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ def _add_arguments_configure(parser: argparse.ArgumentParser) -> None:
default=[],
help='the kernel events to enable (default: no kernel events)')
events_kernel_arg.completer = ArgCompleter(names.EVENTS_KERNEL) # type: ignore
parser.add_argument(
'--syscall', nargs='*', dest='syscalls', metavar='SYSCALL',
default=[],
help='the syscalls to enable (default: no syscalls)')
context_arg = parser.add_argument( # type: ignore
'-c', '--context', nargs='*', dest='context_fields', metavar='CONTEXT',
default=names.DEFAULT_CONTEXT,
Expand Down
Loading
Loading