Skip to content

Commit 57e6ede

Browse files
Allow enabling syscalls through ros2 trace or the Trace action (#137)
Signed-off-by: Christophe Bedard <[email protected]>
1 parent 4376bdc commit 57e6ede

File tree

11 files changed

+187
-24
lines changed

11 files changed

+187
-24
lines changed

Diff for: README.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ $ ros2 trace stop session_name # Stop tracing after starting or resuming
170170
171171
Run each command with `-h` for more information.
172172
173-
You must [install the kernel tracer](#building) if you want to enable kernel events (using the `-k`/`--kernel-events` option).
173+
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).
174174
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).
175175
176176
### Launch file trace action
@@ -185,7 +185,7 @@ $ ros2 launch tracetools_launch example.launch.py
185185
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.
186186
For more information, see [this example launch file](./tracetools_launch/launch/example.launch.py) and the [`Trace` action](./tracetools_launch/tracetools_launch/action.py).
187187
188-
You must [install the kernel tracer](#building) if you want to enable kernel events (`events_kernel` in Python, `events-kernel` in XML or YAML).
188+
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).
189189
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).
190190
191191
## Design

Diff for: lttngpy/src/lttngpy/_lttngpy_pybind11.cpp

+15
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,16 @@ PYBIND11_MODULE(_lttngpy_pybind11, m) {
130130
py::arg("output"));
131131

132132
// Event
133+
py::enum_<lttng_event_type>(m, "lttng_event_type")
134+
.value("LTTNG_EVENT_ALL", LTTNG_EVENT_ALL)
135+
.value("LTTNG_EVENT_TRACEPOINT", LTTNG_EVENT_TRACEPOINT)
136+
.value("LTTNG_EVENT_PROBE", LTTNG_EVENT_PROBE)
137+
.value("LTTNG_EVENT_FUNCTION", LTTNG_EVENT_FUNCTION)
138+
.value("LTTNG_EVENT_FUNCTION_ENTRY", LTTNG_EVENT_FUNCTION_ENTRY)
139+
.value("LTTNG_EVENT_NOOP", LTTNG_EVENT_NOOP)
140+
.value("LTTNG_EVENT_SYSCALL", LTTNG_EVENT_SYSCALL)
141+
.value("LTTNG_EVENT_USERSPACE_PROBE", LTTNG_EVENT_USERSPACE_PROBE)
142+
.export_values();
133143
py::enum_<lttng_event_output>(m, "lttng_event_output")
134144
.value("LTTNG_EVENT_SPLICE", LTTNG_EVENT_SPLICE)
135145
.value("LTTNG_EVENT_MMAP", LTTNG_EVENT_MMAP)
@@ -141,6 +151,7 @@ PYBIND11_MODULE(_lttngpy_pybind11, m) {
141151
py::kw_only(),
142152
py::arg("session_name"),
143153
py::arg("domain_type"),
154+
py::arg("event_type"),
144155
py::arg("channel_name"),
145156
py::arg("events"));
146157
m.def(
@@ -149,6 +160,10 @@ PYBIND11_MODULE(_lttngpy_pybind11, m) {
149160
"Get tracepoints.",
150161
py::kw_only(),
151162
py::arg("domain_type"));
163+
m.def(
164+
"get_syscalls",
165+
&lttngpy::get_syscalls,
166+
"Get syscalls.");
152167
m.def(
153168
"add_contexts",
154169
&lttngpy::add_contexts,

Diff for: lttngpy/src/lttngpy/event.cpp

+20-1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ namespace lttngpy
3535
int enable_events(
3636
const std::string & session_name,
3737
const enum lttng_domain_type domain_type,
38+
const enum lttng_event_type event_type,
3839
const std::string & channel_name,
3940
const std::set<std::string> & events)
4041
{
@@ -59,7 +60,7 @@ int enable_events(
5960
break;
6061
}
6162
event_name.copy(event->name, LTTNG_SYMBOL_NAME_LEN);
62-
event->type = LTTNG_EVENT_TRACEPOINT;
63+
event->type = event_type;
6364

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

105+
std::variant<int, std::set<std::string>> get_syscalls()
106+
{
107+
struct lttng_event * events = nullptr;
108+
int ret = lttng_list_syscalls(&events);
109+
std::variant<int, std::set<std::string>> syscalls_var = ret;
110+
if (0 <= ret) {
111+
std::set<std::string> syscalls = {};
112+
const int num_events = ret;
113+
for (int i = 0; i < num_events; i++) {
114+
syscalls.insert(events[i].name);
115+
}
116+
syscalls_var = syscalls;
117+
}
118+
119+
std::free(events);
120+
return syscalls_var;
121+
}
122+
104123
int _fill_in_event_context(
105124
const std::string & context_field,
106125
const enum lttng_domain_type domain_type,

Diff for: lttngpy/src/lttngpy/event.hpp

+10
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,15 @@ namespace lttngpy
3030
*
3131
* \param session_name the session name
3232
* \param domain_type the domain type
33+
* \param event_type the event type
3334
* \param channel_name the channel name
3435
* \param events the set of event names
3536
* \return 0 on success, else a negative LTTng error code
3637
*/
3738
int enable_events(
3839
const std::string & session_name,
3940
const enum lttng_domain_type domain_type,
41+
const enum lttng_event_type event_type,
4042
const std::string & channel_name,
4143
const std::set<std::string> & events);
4244

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

54+
/**
55+
* Get syscalls.
56+
*
57+
* \return the set of syscalls, else a negative LTTng error code (e.g., if kernel tracer is not
58+
* available)
59+
*/
60+
std::variant<int, std::set<std::string>> get_syscalls();
61+
5262
/**
5363
* Add contexts.
5464
*

Diff for: test_ros2trace/test/test_ros2trace/test_trace.py

+57-1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
from launch import LaunchService
2727
from launch_ros.actions import Node
2828
from lttngpy import impl as lttngpy
29+
from tracetools_read import get_event_name
2930
from tracetools_test.mark_process import get_corresponding_trace_test_events
3031
from tracetools_test.mark_process import get_trace_test_id
3132
from tracetools_test.mark_process import TRACE_TEST_ID_ENV_VAR
@@ -51,6 +52,24 @@ def are_tracepoints_included() -> bool:
5152
return 0 == process.returncode
5253

5354

55+
def skip_if_no_kernel_tracing(func):
56+
"""Skip test if kernel tracing for kernel tracepoints or syscalls is not available."""
57+
def wrapper(*args, **kwargs):
58+
kernel_tracepoints = lttngpy.get_tracepoints(domain_type=lttngpy.LTTNG_DOMAIN_KERNEL)
59+
syscalls = lttngpy.get_syscalls()
60+
error = ', '.join(
61+
f'{name}: {lttngpy.lttng_strerror(error_code)}' for name, error_code in (
62+
('kernel tracepoints', kernel_tracepoints),
63+
('syscalls', syscalls),
64+
)
65+
if isinstance(error_code, int)
66+
)
67+
if error:
68+
raise unittest.SkipTest(f'kernel tracer is required: {error}')
69+
return func(*args, **kwargs)
70+
return wrapper
71+
72+
5473
@unittest.skipIf(not is_lttng_installed(minimum_version='2.9.0'), 'LTTng is required')
5574
class TestROS2TraceCLI(unittest.TestCase):
5675

@@ -94,6 +113,7 @@ def assertTraceContains(
94113
self,
95114
trace_dir: str,
96115
*,
116+
expected_event_name: List[str] = [],
97117
expected_field_value: List[Tuple[str, str]] = [],
98118
expected_field: List[str] = [],
99119
) -> int:
@@ -108,6 +128,11 @@ def assertTraceContains(
108128
0,
109129
f'no matching trace test events found in trace from events: {events_all}',
110130
)
131+
for event_name in expected_event_name:
132+
self.assertTrue(
133+
any(event_name == get_event_name(event) for event in events),
134+
f'{event_name} not found in events: {events}',
135+
)
111136
for field_value in expected_field_value:
112137
self.assertTrue(
113138
any(field_value in event.items() for event in events),
@@ -309,6 +334,37 @@ def test_default_tracing(self) -> None:
309334

310335
shutil.rmtree(tmpdir)
311336

337+
@skip_if_no_kernel_tracing
338+
def test_kernel_tracing(self) -> None:
339+
tmpdir = self.create_test_tmpdir('test_kernel_tracing')
340+
session_name = 'test_kernel_tracing'
341+
342+
process = self.run_trace_command_start(
343+
[
344+
'--path', tmpdir,
345+
'--ust', TRACE_TEST_ID_TP_NAME,
346+
'--kernel', 'sched_switch',
347+
'--syscall', 'openat',
348+
'--session-name', session_name,
349+
],
350+
wait_for_start=True,
351+
)
352+
self.run_nodes()
353+
ret = self.run_trace_command_stop(process)
354+
self.assertEqual(0, ret)
355+
trace_dir = os.path.join(tmpdir, session_name)
356+
self.assertTraceContains(
357+
trace_dir,
358+
expected_event_name=[
359+
'sched_switch',
360+
'syscall_entry_openat',
361+
'syscall_exit_openat',
362+
],
363+
)
364+
self.assertTracingSessionNotExist(session_name)
365+
366+
shutil.rmtree(tmpdir)
367+
312368
def test_env_var_ros_trace_dir(self) -> None:
313369
tmpdir = self.create_test_tmpdir('test_env_var_ros_trace_dir')
314370
session_name = 'test_env_var_ros_trace_dir'
@@ -398,7 +454,7 @@ def test_no_events(self) -> None:
398454

399455
# Enabling no events should result in an error
400456
ret = self.run_trace_command(
401-
['--path', tmpdir, '--ust', '--kernel', '--session-name', session_name],
457+
['--path', tmpdir, '--ust', '--kernel', '--syscall', '--session-name', session_name],
402458
)
403459
self.assertEqual(1, ret)
404460
self.assertTraceNotExist(os.path.join(tmpdir, session_name))

Diff for: test_tracetools_launch/test/test_tracetools_launch/test_trace_action.py

+10
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ def test_action(self) -> None:
102102
session_name='my-session-name',
103103
base_path=tmpdir,
104104
events_kernel=[],
105+
syscalls=[],
105106
events_ust=[
106107
'ros2:*',
107108
'*',
@@ -126,6 +127,7 @@ def test_action_frontend_xml(self) -> None:
126127
base-path="{}"
127128
append-trace="true"
128129
events-kernel=""
130+
syscalls=""
129131
events-ust="ros2:* *"
130132
subbuffer-size-ust="524288"
131133
subbuffer-size-kernel="1048576"
@@ -154,6 +156,7 @@ def test_action_frontend_yaml(self) -> None:
154156
base-path: {}
155157
append-trace: true
156158
events-kernel: ""
159+
syscalls: ""
157160
events-ust: ros2:* *
158161
subbuffer-size-ust: 524288
159162
subbuffer-size-kernel: 1048576
@@ -176,6 +179,7 @@ def test_action_context_per_domain(self) -> None:
176179
session_name='my-session-name',
177180
base_path=tmpdir,
178181
events_kernel=[],
182+
syscalls=[],
179183
events_ust=[
180184
'ros2:*',
181185
'*',
@@ -193,6 +197,7 @@ def test_action_context_per_domain(self) -> None:
193197
session_name='my-session-name',
194198
base_path=tmpdir,
195199
events_kernel=[],
200+
syscalls=[],
196201
events_ust=[
197202
'ros2:*',
198203
'*',
@@ -234,6 +239,7 @@ def test_action_substitutions(self) -> None:
234239
session_name=LaunchConfiguration(session_name_arg.name),
235240
base_path=TextSubstitution(text=tmpdir),
236241
events_kernel=[],
242+
syscalls=[],
237243
events_ust=[
238244
EnvironmentVariable(name='TestTraceAction__event_ust'),
239245
TextSubstitution(text='*'),
@@ -270,6 +276,7 @@ def test_action_ld_preload(self) -> None:
270276
session_name='my-session-name',
271277
base_path=tmpdir,
272278
events_kernel=[],
279+
syscalls=[],
273280
events_ust=[
274281
'lttng_ust_cyg_profile_fast:*',
275282
'lttng_ust_libc:*',
@@ -323,6 +330,7 @@ def test_append_timestamp(self) -> None:
323330
append_timestamp=True,
324331
base_path=tmpdir,
325332
events_kernel=[],
333+
syscalls=[],
326334
events_ust=[
327335
'ros2:*',
328336
'*',
@@ -348,6 +356,7 @@ def test_append_trace(self) -> None:
348356
base_path=tmpdir,
349357
append_trace=False,
350358
events_kernel=[],
359+
syscalls=[],
351360
events_ust=[
352361
'ros2:*',
353362
'*',
@@ -367,6 +376,7 @@ def test_append_trace(self) -> None:
367376
base_path=tmpdir,
368377
append_trace=True,
369378
events_kernel=[],
379+
syscalls=[],
370380
events_ust=[
371381
'ros2:*',
372382
'*',

Diff for: tracetools_launch/tracetools_launch/action.py

+15
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ def __init__(
112112
append_trace: bool = False,
113113
events_ust: Iterable[SomeSubstitutionsType] = names.DEFAULT_EVENTS_ROS,
114114
events_kernel: Iterable[SomeSubstitutionsType] = [],
115+
syscalls: Iterable[SomeSubstitutionsType] = [],
115116
context_fields:
116117
Union[Iterable[SomeSubstitutionsType], Dict[str, Iterable[SomeSubstitutionsType]]]
117118
= names.DEFAULT_CONTEXT,
@@ -140,6 +141,7 @@ def __init__(
140141
otherwise an error is reported
141142
:param events_ust: the list of ROS UST events to enable
142143
:param events_kernel: the list of kernel events to enable
144+
:param syscalls: the list of syscalls to enable
143145
:param context_fields: the names of context fields to enable
144146
if it's a list or a set, the context fields are enabled for both kernel and userspace;
145147
if it's a dictionary: { domain type string -> context fields list }
@@ -160,6 +162,7 @@ def __init__(
160162
self._trace_directory = None
161163
self._events_ust = [normalize_to_list_of_substitutions(x) for x in events_ust]
162164
self._events_kernel = [normalize_to_list_of_substitutions(x) for x in events_kernel]
165+
self._syscalls = [normalize_to_list_of_substitutions(x) for x in syscalls]
163166
self._context_fields = \
164167
{
165168
domain: [normalize_to_list_of_substitutions(field) for field in fields]
@@ -195,6 +198,10 @@ def events_ust(self):
195198
def events_kernel(self):
196199
return self._events_kernel
197200

201+
@property
202+
def syscalls(self):
203+
return self._syscalls
204+
198205
@property
199206
def context_fields(self):
200207
return self._context_fields
@@ -295,6 +302,10 @@ def parse(cls, entity: Entity, parser: Parser):
295302
if events_kernel is not None:
296303
kwargs['events_kernel'] = cls._parse_cmdline(events_kernel, parser) \
297304
if events_kernel else []
305+
syscalls = entity.get_attr('syscalls', optional=True)
306+
if syscalls is not None:
307+
kwargs['syscalls'] = cls._parse_cmdline(syscalls, parser) \
308+
if syscalls else []
298309
context_fields = entity.get_attr('context-fields', optional=True)
299310
if context_fields is not None:
300311
kwargs['context_fields'] = cls._parse_cmdline(context_fields, parser) \
@@ -374,6 +385,7 @@ def _perform_substitutions(self, context: LaunchContext) -> None:
374385
if self._base_path else path.get_tracing_directory()
375386
self._events_ust = [perform_substitutions(context, x) for x in self._events_ust]
376387
self._events_kernel = [perform_substitutions(context, x) for x in self._events_kernel]
388+
self._syscalls = [perform_substitutions(context, x) for x in self._syscalls]
377389
self._context_fields = \
378390
{
379391
domain: [perform_substitutions(context, field) for field in fields]
@@ -418,6 +430,7 @@ def _setup(self) -> bool:
418430
append_trace=self._append_trace,
419431
ros_events=self._events_ust,
420432
kernel_events=self._events_kernel,
433+
syscalls=self._syscalls,
421434
context_fields=self._context_fields,
422435
subbuffer_size_ust=self._subbuffer_size_ust,
423436
subbuffer_size_kernel=self._subbuffer_size_kernel,
@@ -427,6 +440,7 @@ def _setup(self) -> bool:
427440
self._logger.info(f'Writing tracing session to: {self._trace_directory}')
428441
self._logger.debug(f'UST events: {self._events_ust}')
429442
self._logger.debug(f'Kernel events: {self._events_kernel}')
443+
self._logger.debug(f'Syscalls: {self._syscalls}')
430444
self._logger.debug(f'Context fields: {self._context_fields}')
431445
self._logger.debug(f'LD_PRELOAD: {self._ld_preload_actions}')
432446
self._logger.debug(f'UST subbuffer size: {self._subbuffer_size_ust}')
@@ -454,6 +468,7 @@ def __repr__(self):
454468
f'trace_directory={self._trace_directory}, '
455469
f'events_ust={self._events_ust}, '
456470
f'events_kernel={self._events_kernel}, '
471+
f'syscalls={self._syscalls}, '
457472
f'context_fields={self._context_fields}, '
458473
f'ld_preload_actions={self._ld_preload_actions}, '
459474
f'subbuffer_size_ust={self._subbuffer_size_ust}, '

Diff for: tracetools_trace/test/tracetools_trace/test_lttng_tracing.py

+6
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,12 @@ def test_no_kernel_tracer(self):
7474
base_path='/tmp',
7575
kernel_events=['sched_switch'],
7676
)
77+
with self.assertRaises(RuntimeError):
78+
setup(
79+
session_name='test-session',
80+
base_path='/tmp',
81+
syscalls=['open'],
82+
)
7783

7884
def test_get_lttng_home(self):
7985
from tracetools_trace.tools.lttng_impl import get_lttng_home

0 commit comments

Comments
 (0)