Commit | Line | Data |
---|---|---|
ef945e4d JG |
1 | #!/usr/bin/env python3 |
2 | # | |
3 | # Copyright (C) 2022 Jérémie Galarneau <jeremie.galarneau@efficios.com> | |
4 | # | |
5 | # SPDX-License-Identifier: GPL-2.0-only | |
6 | ||
7 | import abc | |
8 | import random | |
9 | import string | |
10 | import pathlib | |
11 | import enum | |
12 | from typing import Optional, Type, Union, List | |
13 | ||
14 | """ | |
15 | Defines an abstract interface to control LTTng tracing. | |
16 | ||
17 | The various control concepts are defined by this module. You can use them with a | |
18 | Controller to interact with a session daemon. | |
19 | ||
20 | This interface is not comprehensive; it currently provides a subset of the | |
21 | control functionality that is used by tests. | |
22 | """ | |
23 | ||
24 | ||
ce8470c9 MJ |
25 | def _generate_random_string(length): |
26 | # type: (int) -> str | |
ef945e4d JG |
27 | return "".join( |
28 | random.choice(string.ascii_lowercase + string.digits) for _ in range(length) | |
29 | ) | |
30 | ||
31 | ||
32 | class ContextType(abc.ABC): | |
33 | """Base class representing a tracing context field.""" | |
34 | ||
35 | pass | |
36 | ||
37 | ||
38 | class VpidContextType(ContextType): | |
39 | """Application's virtual process id.""" | |
40 | ||
41 | pass | |
42 | ||
43 | ||
44 | class VuidContextType(ContextType): | |
45 | """Application's virtual user id.""" | |
46 | ||
47 | pass | |
48 | ||
49 | ||
50 | class VgidContextType(ContextType): | |
51 | """Application's virtual group id.""" | |
52 | ||
53 | pass | |
54 | ||
55 | ||
56 | class JavaApplicationContextType(ContextType): | |
57 | """A java application-specific context field is a piece of state which the application provides.""" | |
58 | ||
ce8470c9 MJ |
59 | def __init__( |
60 | self, | |
61 | retriever_name, # type: str | |
62 | field_name, # type: str | |
63 | ): | |
64 | self._retriever_name = retriever_name # type: str | |
65 | self._field_name = field_name # type: str | |
ef945e4d JG |
66 | |
67 | @property | |
ce8470c9 MJ |
68 | def retriever_name(self): |
69 | # type: () -> str | |
ef945e4d JG |
70 | return self._retriever_name |
71 | ||
72 | @property | |
ce8470c9 MJ |
73 | def field_name(self): |
74 | # type: () -> str | |
ef945e4d JG |
75 | return self._field_name |
76 | ||
77 | ||
544d8425 | 78 | @enum.unique |
ef945e4d JG |
79 | class TracingDomain(enum.Enum): |
80 | """Tracing domain.""" | |
81 | ||
544d8425 MJ |
82 | User = "User space tracing domain" |
83 | Kernel = "Linux kernel tracing domain." | |
84 | Log4j = "Log4j tracing back-end." | |
85 | JUL = "Java Util Logging tracing back-end." | |
86 | Python = "Python logging module tracing back-end." | |
87 | ||
88 | def __repr__(self): | |
89 | return "<%s.%s>" % (self.__class__.__name__, self.name) | |
ef945e4d JG |
90 | |
91 | ||
92 | class EventRule(abc.ABC): | |
93 | """Event rule base class, see LTTNG-EVENT-RULE(7).""" | |
94 | ||
95 | pass | |
96 | ||
97 | ||
98 | class LogLevelRule: | |
99 | pass | |
100 | ||
101 | ||
102 | class LogLevelRuleAsSevereAs(LogLevelRule): | |
ce8470c9 MJ |
103 | def __init__(self, level): |
104 | # type: (int) | |
ef945e4d JG |
105 | self._level = level |
106 | ||
107 | @property | |
ce8470c9 MJ |
108 | def level(self): |
109 | # type: () -> int | |
ef945e4d JG |
110 | return self._level |
111 | ||
112 | ||
113 | class LogLevelRuleExactly(LogLevelRule): | |
ce8470c9 MJ |
114 | def __init__(self, level): |
115 | # type: (int) | |
ef945e4d JG |
116 | self._level = level |
117 | ||
118 | @property | |
ce8470c9 MJ |
119 | def level(self): |
120 | # type: () -> int | |
ef945e4d JG |
121 | return self._level |
122 | ||
123 | ||
124 | class TracepointEventRule(EventRule): | |
125 | def __init__( | |
126 | self, | |
ce8470c9 MJ |
127 | name_pattern=None, # type: Optional[str] |
128 | filter_expression=None, # type: Optional[str] | |
129 | log_level_rule=None, # type: Optional[LogLevelRule] | |
130 | name_pattern_exclusions=None, # type: Optional[List[str]] | |
ef945e4d | 131 | ): |
ce8470c9 MJ |
132 | self._name_pattern = name_pattern # type: Optional[str] |
133 | self._filter_expression = filter_expression # type: Optional[str] | |
134 | self._log_level_rule = log_level_rule # type: Optional[LogLevelRule] | |
135 | self._name_pattern_exclusions = ( | |
136 | name_pattern_exclusions | |
137 | ) # type: Optional[List[str]] | |
ef945e4d JG |
138 | |
139 | @property | |
ce8470c9 MJ |
140 | def name_pattern(self): |
141 | # type: () -> Optional[str] | |
ef945e4d JG |
142 | return self._name_pattern |
143 | ||
144 | @property | |
ce8470c9 MJ |
145 | def filter_expression(self): |
146 | # type: () -> Optional[str] | |
ef945e4d JG |
147 | return self._filter_expression |
148 | ||
149 | @property | |
ce8470c9 MJ |
150 | def log_level_rule(self): |
151 | # type: () -> Optional[LogLevelRule] | |
ef945e4d JG |
152 | return self._log_level_rule |
153 | ||
154 | @property | |
ce8470c9 MJ |
155 | def name_pattern_exclusions(self): |
156 | # type: () -> Optional[List[str]] | |
ef945e4d JG |
157 | return self._name_pattern_exclusions |
158 | ||
159 | ||
160 | class UserTracepointEventRule(TracepointEventRule): | |
161 | def __init__( | |
162 | self, | |
ce8470c9 MJ |
163 | name_pattern=None, # type: Optional[str] |
164 | filter_expression=None, # type: Optional[str] | |
165 | log_level_rule=None, # type: Optional[LogLevelRule] | |
166 | name_pattern_exclusions=None, # type: Optional[List[str]] | |
ef945e4d JG |
167 | ): |
168 | TracepointEventRule.__init__(**locals()) | |
169 | ||
170 | ||
171 | class KernelTracepointEventRule(TracepointEventRule): | |
172 | def __init__( | |
173 | self, | |
ce8470c9 MJ |
174 | name_pattern=None, # type: Optional[str] |
175 | filter_expression=None, # type: Optional[str] | |
176 | log_level_rule=None, # type: Optional[LogLevelRule] | |
177 | name_pattern_exclusions=None, # type: Optional[List[str]] | |
ef945e4d JG |
178 | ): |
179 | TracepointEventRule.__init__(**locals()) | |
180 | ||
181 | ||
182 | class Channel(abc.ABC): | |
183 | """ | |
184 | A channel is an object which is responsible for a set of ring buffers. It is | |
185 | associated to a domain and | |
186 | """ | |
187 | ||
188 | @staticmethod | |
ce8470c9 MJ |
189 | def _generate_name(): |
190 | # type: () -> str | |
ef945e4d JG |
191 | return "channel_{random_id}".format(random_id=_generate_random_string(8)) |
192 | ||
193 | @abc.abstractmethod | |
ce8470c9 MJ |
194 | def add_context(self, context_type): |
195 | # type: (ContextType) -> None | |
ef945e4d JG |
196 | pass |
197 | ||
198 | @property | |
199 | @abc.abstractmethod | |
ce8470c9 MJ |
200 | def domain(self): |
201 | # type: () -> TracingDomain | |
ef945e4d JG |
202 | pass |
203 | ||
204 | @property | |
205 | @abc.abstractmethod | |
ce8470c9 MJ |
206 | def name(self): |
207 | # type: () -> str | |
ef945e4d JG |
208 | pass |
209 | ||
210 | @abc.abstractmethod | |
ce8470c9 MJ |
211 | def add_recording_rule(self, rule) -> None: |
212 | # type: (Type[EventRule]) -> None | |
ef945e4d JG |
213 | pass |
214 | ||
215 | ||
216 | class SessionOutputLocation(abc.ABC): | |
217 | pass | |
218 | ||
219 | ||
220 | class LocalSessionOutputLocation(SessionOutputLocation): | |
ce8470c9 MJ |
221 | def __init__(self, trace_path): |
222 | # type: (pathlib.Path) | |
ef945e4d JG |
223 | self._path = trace_path |
224 | ||
225 | @property | |
ce8470c9 MJ |
226 | def path(self): |
227 | # type: () -> pathlib.Path | |
ef945e4d JG |
228 | return self._path |
229 | ||
230 | ||
231 | class ProcessAttributeTracker(abc.ABC): | |
232 | """ | |
233 | Process attribute tracker used to filter before the evaluation of event | |
234 | rules. | |
235 | ||
236 | Note that this interface is currently limited as it doesn't allow changing | |
237 | the tracking policy. For instance, it is not possible to set the tracking | |
238 | policy back to "all" once it has transitioned to "include set". | |
239 | """ | |
240 | ||
544d8425 | 241 | @enum.unique |
ef945e4d | 242 | class TrackingPolicy(enum.Enum): |
544d8425 | 243 | INCLUDE_ALL = """ |
ef945e4d JG |
244 | Track all possible process attribute value of a given type (i.e. no filtering). |
245 | This is the default state of a process attribute tracker. | |
544d8425 MJ |
246 | """ |
247 | EXCLUDE_ALL = "Exclude all possible process attribute values of a given type." | |
248 | INCLUDE_SET = "Track a set of specific process attribute values." | |
249 | ||
250 | def __repr__(self): | |
251 | return "<%s.%s>" % (self.__class__.__name__, self.name) | |
ef945e4d | 252 | |
ce8470c9 MJ |
253 | def __init__(self, policy): |
254 | # type: (TrackingPolicy) | |
ef945e4d JG |
255 | self._policy = policy |
256 | ||
257 | @property | |
ce8470c9 MJ |
258 | def tracking_policy(self): |
259 | # type: () -> TrackingPolicy | |
ef945e4d JG |
260 | return self._policy |
261 | ||
262 | ||
263 | class ProcessIDProcessAttributeTracker(ProcessAttributeTracker): | |
264 | @abc.abstractmethod | |
ce8470c9 MJ |
265 | def track(self, pid): |
266 | # type: (int) -> None | |
ef945e4d JG |
267 | pass |
268 | ||
269 | @abc.abstractmethod | |
ce8470c9 MJ |
270 | def untrack(self, pid): |
271 | # type: (int) -> None | |
ef945e4d JG |
272 | pass |
273 | ||
274 | ||
275 | class VirtualProcessIDProcessAttributeTracker(ProcessAttributeTracker): | |
276 | @abc.abstractmethod | |
ce8470c9 MJ |
277 | def track(self, vpid): |
278 | # type: (int) -> None | |
ef945e4d JG |
279 | pass |
280 | ||
281 | @abc.abstractmethod | |
ce8470c9 MJ |
282 | def untrack(self, vpid): |
283 | # type: (int) -> None | |
ef945e4d JG |
284 | pass |
285 | ||
286 | ||
287 | class UserIDProcessAttributeTracker(ProcessAttributeTracker): | |
288 | @abc.abstractmethod | |
ce8470c9 MJ |
289 | def track(self, uid): |
290 | # type: (Union[int, str]) -> None | |
ef945e4d JG |
291 | pass |
292 | ||
293 | @abc.abstractmethod | |
ce8470c9 MJ |
294 | def untrack(self, uid): |
295 | # type: (Union[int, str]) -> None | |
ef945e4d JG |
296 | pass |
297 | ||
298 | ||
299 | class VirtualUserIDProcessAttributeTracker(ProcessAttributeTracker): | |
300 | @abc.abstractmethod | |
ce8470c9 MJ |
301 | def track(self, vuid): |
302 | # type: (Union[int, str]) -> None | |
ef945e4d JG |
303 | pass |
304 | ||
305 | @abc.abstractmethod | |
ce8470c9 MJ |
306 | def untrack(self, vuid): |
307 | # type: (Union[int, str]) -> None | |
ef945e4d JG |
308 | pass |
309 | ||
310 | ||
311 | class GroupIDProcessAttributeTracker(ProcessAttributeTracker): | |
312 | @abc.abstractmethod | |
ce8470c9 MJ |
313 | def track(self, gid): |
314 | # type: (Union[int, str]) -> None | |
ef945e4d JG |
315 | pass |
316 | ||
317 | @abc.abstractmethod | |
ce8470c9 MJ |
318 | def untrack(self, gid): |
319 | # type: (Union[int, str]) -> None | |
ef945e4d JG |
320 | pass |
321 | ||
322 | ||
323 | class VirtualGroupIDProcessAttributeTracker(ProcessAttributeTracker): | |
324 | @abc.abstractmethod | |
ce8470c9 MJ |
325 | def track(self, vgid): |
326 | # type: (Union[int, str]) -> None | |
ef945e4d JG |
327 | pass |
328 | ||
329 | @abc.abstractmethod | |
ce8470c9 MJ |
330 | def untrack(self, vgid): |
331 | # type: (Union[int, str]) -> None | |
ef945e4d JG |
332 | pass |
333 | ||
334 | ||
335 | class Session(abc.ABC): | |
336 | @staticmethod | |
ce8470c9 MJ |
337 | def _generate_name(): |
338 | # type: () -> str | |
ef945e4d JG |
339 | return "session_{random_id}".format(random_id=_generate_random_string(8)) |
340 | ||
341 | @property | |
342 | @abc.abstractmethod | |
ce8470c9 MJ |
343 | def name(self): |
344 | # type: () -> str | |
ef945e4d JG |
345 | pass |
346 | ||
347 | @property | |
348 | @abc.abstractmethod | |
ce8470c9 MJ |
349 | def output(self): |
350 | # type: () -> Optional[Type[SessionOutputLocation]] | |
ef945e4d JG |
351 | pass |
352 | ||
353 | @abc.abstractmethod | |
ce8470c9 MJ |
354 | def add_channel(self, domain, channel_name=None): |
355 | # type: (TracingDomain, Optional[str]) -> Channel | |
ef945e4d JG |
356 | """Add a channel with default attributes to the session.""" |
357 | pass | |
358 | ||
359 | @abc.abstractmethod | |
ce8470c9 MJ |
360 | def start(self): |
361 | # type: () -> None | |
ef945e4d JG |
362 | pass |
363 | ||
364 | @abc.abstractmethod | |
ce8470c9 MJ |
365 | def stop(self): |
366 | # type: () -> None | |
ef945e4d JG |
367 | pass |
368 | ||
369 | @abc.abstractmethod | |
ce8470c9 MJ |
370 | def destroy(self): |
371 | # type: () -> None | |
ef945e4d JG |
372 | pass |
373 | ||
b9780062 JG |
374 | @abc.abstractmethod |
375 | def is_active(self): | |
376 | # type: () -> bool | |
377 | pass | |
378 | ||
ef945e4d | 379 | @abc.abstractproperty |
ce8470c9 MJ |
380 | def kernel_pid_process_attribute_tracker(self): |
381 | # type: () -> Type[ProcessIDProcessAttributeTracker] | |
ef945e4d JG |
382 | raise NotImplementedError |
383 | ||
384 | @abc.abstractproperty | |
ce8470c9 MJ |
385 | def kernel_vpid_process_attribute_tracker(self): |
386 | # type: () -> Type[VirtualProcessIDProcessAttributeTracker] | |
ef945e4d JG |
387 | raise NotImplementedError |
388 | ||
389 | @abc.abstractproperty | |
390 | def user_vpid_process_attribute_tracker( | |
391 | self, | |
392 | ) -> Type[VirtualProcessIDProcessAttributeTracker]: | |
ce8470c9 | 393 | # type: () -> Type[VirtualProcessIDProcessAttributeTracker] |
ef945e4d JG |
394 | raise NotImplementedError |
395 | ||
396 | @abc.abstractproperty | |
ce8470c9 MJ |
397 | def kernel_gid_process_attribute_tracker(self): |
398 | # type: () -> Type[GroupIDProcessAttributeTracker] | |
ef945e4d JG |
399 | raise NotImplementedError |
400 | ||
401 | @abc.abstractproperty | |
ce8470c9 MJ |
402 | def kernel_vgid_process_attribute_tracker(self): |
403 | # type: () -> Type[VirtualGroupIDProcessAttributeTracker] | |
ef945e4d JG |
404 | raise NotImplementedError |
405 | ||
406 | @abc.abstractproperty | |
ce8470c9 MJ |
407 | def user_vgid_process_attribute_tracker(self): |
408 | # type: () -> Type[VirtualGroupIDProcessAttributeTracker] | |
ef945e4d JG |
409 | raise NotImplementedError |
410 | ||
411 | @abc.abstractproperty | |
ce8470c9 MJ |
412 | def kernel_uid_process_attribute_tracker(self): |
413 | # type: () -> Type[UserIDProcessAttributeTracker] | |
ef945e4d JG |
414 | raise NotImplementedError |
415 | ||
416 | @abc.abstractproperty | |
ce8470c9 MJ |
417 | def kernel_vuid_process_attribute_tracker(self): |
418 | # type: () -> Type[VirtualUserIDProcessAttributeTracker] | |
ef945e4d JG |
419 | raise NotImplementedError |
420 | ||
421 | @abc.abstractproperty | |
ce8470c9 MJ |
422 | def user_vuid_process_attribute_tracker(self): |
423 | # type: () -> Type[VirtualUserIDProcessAttributeTracker] | |
ef945e4d JG |
424 | raise NotImplementedError |
425 | ||
426 | ||
427 | class ControlException(RuntimeError): | |
428 | """Base type for exceptions thrown by a controller.""" | |
429 | ||
ce8470c9 MJ |
430 | def __init__(self, msg): |
431 | # type: (str) | |
ef945e4d JG |
432 | super().__init__(msg) |
433 | ||
434 | ||
435 | class Controller(abc.ABC): | |
436 | """ | |
437 | Interface of a top-level control interface. A control interface can be, for | |
438 | example, the LTTng client or a wrapper around liblttng-ctl. It is used to | |
439 | create and manage top-level objects of a session daemon instance. | |
440 | """ | |
441 | ||
442 | @abc.abstractmethod | |
ce8470c9 MJ |
443 | def create_session(self, name=None, output=None): |
444 | # type: (Optional[str], Optional[SessionOutputLocation]) -> Session | |
ef945e4d JG |
445 | """ |
446 | Create a session with an output. Don't specify an output | |
447 | to create a session without an output. | |
448 | """ | |
449 | pass | |
b9780062 JG |
450 | |
451 | @abc.abstractmethod | |
452 | def start_session_by_name(self, name): | |
453 | # type: (str) -> None | |
454 | """ | |
455 | Start a session by name. | |
456 | """ | |
457 | pass | |
458 | ||
459 | @abc.abstractmethod | |
460 | def start_session_by_glob_pattern(self, pattern): | |
461 | # type: (str) -> None | |
462 | """ | |
463 | Start sessions whose name matches `pattern`, see GLOB(7). | |
464 | """ | |
465 | pass | |
466 | ||
467 | @abc.abstractmethod | |
468 | def start_sessions_all(self): | |
469 | """ | |
470 | Start all sessions visible to the current user. | |
471 | """ | |
472 | # type: () -> None | |
473 | pass | |
474 | ||
475 | @abc.abstractmethod | |
476 | def stop_session_by_name(self, name): | |
477 | # type: (str) -> None | |
478 | """ | |
479 | Stop a session by name. | |
480 | """ | |
481 | pass | |
482 | ||
483 | @abc.abstractmethod | |
484 | def stop_session_by_glob_pattern(self, pattern): | |
485 | # type: (str) -> None | |
486 | """ | |
487 | Stop sessions whose name matches `pattern`, see GLOB(7). | |
488 | """ | |
489 | pass | |
490 | ||
491 | @abc.abstractmethod | |
492 | def stop_sessions_all(self): | |
493 | """ | |
494 | Stop all sessions visible to the current user. | |
495 | """ | |
496 | # type: () -> None | |
497 | pass | |
498 | ||
499 | @abc.abstractmethod | |
500 | def destroy_session_by_name(self, name): | |
501 | # type: (str) -> None | |
502 | """ | |
503 | Destroy a session by name. | |
504 | """ | |
505 | pass | |
506 | ||
507 | @abc.abstractmethod | |
508 | def destroy_session_by_glob_pattern(self, pattern): | |
509 | # type: (str) -> None | |
510 | """ | |
511 | Destroy sessions whose name matches `pattern`, see GLOB(7). | |
512 | """ | |
513 | pass | |
514 | ||
515 | @abc.abstractmethod | |
516 | def destroy_sessions_all(self): | |
517 | # type: () -> None | |
518 | """ | |
519 | Destroy all sessions visible to the current user. | |
520 | """ | |
521 | pass | |
522 | ||
523 | @abc.abstractmethod | |
524 | def list_sessions(self): | |
525 | # type: () -> List[Session] | |
526 | """ | |
527 | List all sessions visible to the current user. | |
528 | """ | |
529 | pass |