Tests: add a recording rule listing test
[lttng-tools.git] / tests / utils / lttngtest / lttngctl.py
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
25 def _generate_random_string(length):
26 # type: (int) -> str
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
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
66
67 @property
68 def retriever_name(self):
69 # type: () -> str
70 return self._retriever_name
71
72 @property
73 def field_name(self):
74 # type: () -> str
75 return self._field_name
76
77
78 @enum.unique
79 class TracingDomain(enum.Enum):
80 """Tracing domain."""
81
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)
90
91
92 @enum.unique
93 class BufferSharingPolicy(enum.Enum):
94 """Buffer sharing policy."""
95
96 PerUID = "Per-UID buffering"
97 PerPID = "Per-PID buffering"
98
99 def __repr__(self):
100 return "<%s.%s>" % (self.__class__.__name__, self.name)
101
102
103 class EventRule(abc.ABC):
104 """Event rule base class, see LTTNG-EVENT-RULE(7)."""
105
106 pass
107
108
109 class LogLevelRule:
110 def __eq__(self, other):
111 # type (LogLevelRule) -> bool
112 if type(self) != type(other):
113 return False
114
115 return self.level == other.level
116
117
118 @enum.unique
119 class LogLevel(enum.Enum):
120 pass
121
122
123 @enum.unique
124 class UserLogLevel(LogLevel):
125 EMERGENCY = 0
126 ALERT = 1
127 CRITICAL = 2
128 ERROR = 3
129 WARNING = 4
130 NOTICE = 5
131 INFO = 6
132 DEBUG_SYSTEM = 7
133 DEBUG_PROGRAM = 8
134 DEBUG_PROCESS = 9
135 DEBUG_MODULE = 10
136 DEBUG_UNIT = 11
137 DEBUG_FUNCTION = 12
138 DEBUG_LINE = 13
139 DEBUG = 14
140
141
142 @enum.unique
143 class JULLogLevel(LogLevel):
144 OFF = 2147483647
145 SEVERE = 1000
146 WARNING = 900
147 INFO = 800
148 CONFIG = 700
149 FINE = 500
150 FINER = 400
151 FINEST = 300
152 ALL = -2147483648
153
154
155 @enum.unique
156 class Log4jLogLevel(LogLevel):
157 OFF = 2147483647
158 FATAL = 50000
159 ERROR = 40000
160 WARN = 30000
161 INFO = 20000
162 DEBUG = 10000
163 TRACE = 5000
164 ALL = -2147483648
165
166
167 @enum.unique
168 class PythonLogLevel(LogLevel):
169 CRITICAL = 50
170 ERROR = 40
171 WARNING = 30
172 INFO = 20
173 DEBUG = 10
174 NOTSET = 0
175
176
177 class LogLevelRuleAsSevereAs(LogLevelRule):
178 def __init__(self, level):
179 # type: (LogLevel)
180 self._level = level
181
182 @property
183 def level(self):
184 # type: () -> LogLevel
185 return self._level
186
187
188 class LogLevelRuleExactly(LogLevelRule):
189 def __init__(self, level):
190 # type: (LogLevel)
191 self._level = level
192
193 @property
194 def level(self):
195 # type: () -> LogLevel
196 return self._level
197
198
199 class TracepointEventRule(EventRule):
200 def __init__(
201 self,
202 name_pattern=None, # type: Optional[str]
203 filter_expression=None, # type: Optional[str]
204 ):
205 self._name_pattern = name_pattern # type: Optional[str]
206 self._filter_expression = filter_expression # type: Optional[str]
207
208 def _equals(self, other):
209 # type (TracepointEventRule) -> bool
210 # Overridden by derived classes that have supplementary attributes.
211 return True
212
213 def __eq__(self, other):
214 # type (TracepointEventRule) -> bool
215 if type(self) != type(other):
216 return False
217
218 if self.name_pattern != other.name_pattern:
219 return False
220
221 if self.filter_expression != other.filter_expression:
222 return False
223
224 return self._equals(other)
225
226 @property
227 def name_pattern(self):
228 # type: () -> Optional[str]
229 return self._name_pattern
230
231 @property
232 def filter_expression(self):
233 # type: () -> Optional[str]
234 return self._filter_expression
235
236
237 class UserTracepointEventRule(TracepointEventRule):
238 def __init__(
239 self,
240 name_pattern=None, # type: Optional[str]
241 filter_expression=None, # type: Optional[str]
242 log_level_rule=None, # type: Optional[LogLevelRule]
243 name_pattern_exclusions=None, # type: Optional[List[str]]
244 ):
245 TracepointEventRule.__init__(self, name_pattern, filter_expression)
246 self._log_level_rule = log_level_rule # type: Optional[LogLevelRule]
247 self._name_pattern_exclusions = (
248 name_pattern_exclusions
249 ) # type: Optional[List[str]]
250
251 if log_level_rule and not isinstance(log_level_rule.level, UserLogLevel):
252 raise ValueError("Log level rule must use a UserLogLevel as its value")
253
254 def _equals(self, other):
255 # type (UserTracepointEventRule) -> bool
256 return (
257 self.log_level_rule == other.log_level_rule
258 and self.name_pattern_exclusions == other.name_pattern_exclusions
259 )
260
261 @property
262 def log_level_rule(self):
263 # type: () -> Optional[LogLevelRule]
264 return self._log_level_rule
265
266 @property
267 def name_pattern_exclusions(self):
268 # type: () -> Optional[List[str]]
269 return self._name_pattern_exclusions
270
271
272 class Log4jTracepointEventRule(TracepointEventRule):
273 def __init__(
274 self,
275 name_pattern=None, # type: Optional[str]
276 filter_expression=None, # type: Optional[str]
277 log_level_rule=None, # type: Optional[LogLevelRule]
278 name_pattern_exclusions=None, # type: Optional[List[str]]
279 ):
280 TracepointEventRule.__init__(self, name_pattern, filter_expression)
281 self._log_level_rule = log_level_rule # type: Optional[LogLevelRule]
282 self._name_pattern_exclusions = (
283 name_pattern_exclusions
284 ) # type: Optional[List[str]]
285
286 if log_level_rule and not isinstance(log_level_rule.level, Log4jLogLevel):
287 raise ValueError("Log level rule must use a Log4jLogLevel as its value")
288
289 def _equals(self, other):
290 # type (Log4jTracepointEventRule) -> bool
291 return (
292 self.log_level_rule == other.log_level_rule
293 and self.name_pattern_exclusions == other.name_pattern_exclusions
294 )
295
296 @property
297 def log_level_rule(self):
298 # type: () -> Optional[LogLevelRule]
299 return self._log_level_rule
300
301 @property
302 def name_pattern_exclusions(self):
303 # type: () -> Optional[List[str]]
304 return self._name_pattern_exclusions
305
306
307 class JULTracepointEventRule(TracepointEventRule):
308 def __init__(
309 self,
310 name_pattern=None, # type: Optional[str]
311 filter_expression=None, # type: Optional[str]
312 log_level_rule=None, # type: Optional[LogLevelRule]
313 name_pattern_exclusions=None, # type: Optional[List[str]]
314 ):
315 TracepointEventRule.__init__(self, name_pattern, filter_expression)
316 self._log_level_rule = log_level_rule # type: Optional[LogLevelRule]
317 self._name_pattern_exclusions = (
318 name_pattern_exclusions
319 ) # type: Optional[List[str]]
320
321 if log_level_rule and not isinstance(log_level_rule.level, JULLogLevel):
322 raise ValueError("Log level rule must use a JULLogLevel as its value")
323
324 def _equals(self, other):
325 # type (JULTracepointEventRule) -> bool
326 return (
327 self.log_level_rule == other.log_level_rule
328 and self.name_pattern_exclusions == other.name_pattern_exclusions
329 )
330
331 @property
332 def log_level_rule(self):
333 # type: () -> Optional[LogLevelRule]
334 return self._log_level_rule
335
336 @property
337 def name_pattern_exclusions(self):
338 # type: () -> Optional[List[str]]
339 return self._name_pattern_exclusions
340
341
342 class PythonTracepointEventRule(TracepointEventRule):
343 def __init__(
344 self,
345 name_pattern=None, # type: Optional[str]
346 filter_expression=None, # type: Optional[str]
347 log_level_rule=None, # type: Optional[LogLevelRule]
348 name_pattern_exclusions=None, # type: Optional[List[str]]
349 ):
350 TracepointEventRule.__init__(self, name_pattern, filter_expression)
351 self._log_level_rule = log_level_rule # type: Optional[LogLevelRule]
352 self._name_pattern_exclusions = (
353 name_pattern_exclusions
354 ) # type: Optional[List[str]]
355
356 if log_level_rule and not isinstance(log_level_rule.level, PythonLogLevel):
357 raise ValueError("Log level rule must use a PythonLogLevel as its value")
358
359 def _equals(self, other):
360 # type (PythonTracepointEventRule) -> bool
361 return (
362 self.log_level_rule == other.log_level_rule
363 and self.name_pattern_exclusions == other.name_pattern_exclusions
364 )
365
366 @property
367 def log_level_rule(self):
368 # type: () -> Optional[LogLevelRule]
369 return self._log_level_rule
370
371 @property
372 def name_pattern_exclusions(self):
373 # type: () -> Optional[List[str]]
374 return self._name_pattern_exclusions
375
376
377 class KernelTracepointEventRule(TracepointEventRule):
378 def __init__(
379 self,
380 name_pattern=None, # type: Optional[str]
381 filter_expression=None, # type: Optional[str]
382 ):
383 TracepointEventRule.__init__(**locals())
384
385
386 class Channel(abc.ABC):
387 """
388 A channel is an object which is responsible for a set of ring buffers. It is
389 associated to a domain and
390 """
391
392 @staticmethod
393 def _generate_name():
394 # type: () -> str
395 return "channel_{random_id}".format(random_id=_generate_random_string(8))
396
397 @abc.abstractmethod
398 def add_context(self, context_type):
399 # type: (ContextType) -> None
400 raise NotImplementedError
401
402 @property
403 @abc.abstractmethod
404 def domain(self):
405 # type: () -> TracingDomain
406 raise NotImplementedError
407
408 @property
409 @abc.abstractmethod
410 def name(self):
411 # type: () -> str
412 raise NotImplementedError
413
414 @abc.abstractmethod
415 def add_recording_rule(self, rule) -> None:
416 # type: (Type[EventRule]) -> None
417 raise NotImplementedError
418
419
420 class SessionOutputLocation(abc.ABC):
421 pass
422
423
424 class LocalSessionOutputLocation(SessionOutputLocation):
425 def __init__(self, trace_path):
426 # type: (pathlib.Path)
427 self._path = trace_path
428
429 @property
430 def path(self):
431 # type: () -> pathlib.Path
432 return self._path
433
434
435 class ProcessAttributeTracker(abc.ABC):
436 """
437 Process attribute tracker used to filter before the evaluation of event
438 rules.
439
440 Note that this interface is currently limited as it doesn't allow changing
441 the tracking policy. For instance, it is not possible to set the tracking
442 policy back to "all" once it has transitioned to "include set".
443 """
444
445 @enum.unique
446 class TrackingPolicy(enum.Enum):
447 INCLUDE_ALL = """
448 Track all possible process attribute value of a given type (i.e. no filtering).
449 This is the default state of a process attribute tracker.
450 """
451 EXCLUDE_ALL = "Exclude all possible process attribute values of a given type."
452 INCLUDE_SET = "Track a set of specific process attribute values."
453
454 def __repr__(self):
455 return "<%s.%s>" % (self.__class__.__name__, self.name)
456
457 def __init__(self, policy):
458 # type: (TrackingPolicy)
459 self._policy = policy
460
461 @property
462 def tracking_policy(self):
463 # type: () -> TrackingPolicy
464 return self._policy
465
466
467 class ProcessIDProcessAttributeTracker(ProcessAttributeTracker):
468 @abc.abstractmethod
469 def track(self, pid):
470 # type: (int) -> None
471 raise NotImplementedError
472
473 @abc.abstractmethod
474 def untrack(self, pid):
475 # type: (int) -> None
476 raise NotImplementedError
477
478
479 class VirtualProcessIDProcessAttributeTracker(ProcessAttributeTracker):
480 @abc.abstractmethod
481 def track(self, vpid):
482 # type: (int) -> None
483 raise NotImplementedError
484
485 @abc.abstractmethod
486 def untrack(self, vpid):
487 # type: (int) -> None
488 raise NotImplementedError
489
490
491 class UserIDProcessAttributeTracker(ProcessAttributeTracker):
492 @abc.abstractmethod
493 def track(self, uid):
494 # type: (Union[int, str]) -> None
495 raise NotImplementedError
496
497 @abc.abstractmethod
498 def untrack(self, uid):
499 # type: (Union[int, str]) -> None
500 raise NotImplementedError
501
502
503 class VirtualUserIDProcessAttributeTracker(ProcessAttributeTracker):
504 @abc.abstractmethod
505 def track(self, vuid):
506 # type: (Union[int, str]) -> None
507 raise NotImplementedError
508
509 @abc.abstractmethod
510 def untrack(self, vuid):
511 # type: (Union[int, str]) -> None
512 raise NotImplementedError
513
514
515 class GroupIDProcessAttributeTracker(ProcessAttributeTracker):
516 @abc.abstractmethod
517 def track(self, gid):
518 # type: (Union[int, str]) -> None
519 raise NotImplementedError
520
521 @abc.abstractmethod
522 def untrack(self, gid):
523 # type: (Union[int, str]) -> None
524 raise NotImplementedError
525
526
527 class VirtualGroupIDProcessAttributeTracker(ProcessAttributeTracker):
528 @abc.abstractmethod
529 def track(self, vgid):
530 # type: (Union[int, str]) -> None
531 raise NotImplementedError
532
533 @abc.abstractmethod
534 def untrack(self, vgid):
535 # type: (Union[int, str]) -> None
536 raise NotImplementedError
537
538
539 class Session(abc.ABC):
540 @staticmethod
541 def _generate_name():
542 # type: () -> str
543 return "session_{random_id}".format(random_id=_generate_random_string(8))
544
545 @property
546 @abc.abstractmethod
547 def name(self):
548 # type: () -> str
549 raise NotImplementedError
550
551 @property
552 @abc.abstractmethod
553 def output(self):
554 # type: () -> Optional[Type[SessionOutputLocation]]
555 raise NotImplementedError
556
557 @abc.abstractmethod
558 def add_channel(
559 self,
560 domain,
561 channel_name=None,
562 buffer_sharing_policy=BufferSharingPolicy.PerUID,
563 ):
564 # type: (TracingDomain, Optional[str], BufferSharingPolicy) -> Channel
565 """Add a channel with default attributes to the session."""
566 raise NotImplementedError
567
568 @abc.abstractmethod
569 def start(self):
570 # type: () -> None
571 raise NotImplementedError
572
573 @abc.abstractmethod
574 def stop(self):
575 # type: () -> None
576 raise NotImplementedError
577
578 @abc.abstractmethod
579 def destroy(self):
580 # type: () -> None
581 raise NotImplementedError
582
583 @abc.abstractmethod
584 def is_active(self):
585 # type: () -> bool
586 raise NotImplementedError
587
588 @abc.abstractmethod
589 def rotate(self):
590 # type: () -> None
591 raise NotImplementedError
592
593 @abc.abstractproperty
594 def kernel_pid_process_attribute_tracker(self):
595 # type: () -> Type[ProcessIDProcessAttributeTracker]
596 raise NotImplementedError
597
598 @abc.abstractproperty
599 def kernel_vpid_process_attribute_tracker(self):
600 # type: () -> Type[VirtualProcessIDProcessAttributeTracker]
601 raise NotImplementedError
602
603 @abc.abstractproperty
604 def user_vpid_process_attribute_tracker(
605 self,
606 ) -> Type[VirtualProcessIDProcessAttributeTracker]:
607 # type: () -> Type[VirtualProcessIDProcessAttributeTracker]
608 raise NotImplementedError
609
610 @abc.abstractproperty
611 def kernel_gid_process_attribute_tracker(self):
612 # type: () -> Type[GroupIDProcessAttributeTracker]
613 raise NotImplementedError
614
615 @abc.abstractproperty
616 def kernel_vgid_process_attribute_tracker(self):
617 # type: () -> Type[VirtualGroupIDProcessAttributeTracker]
618 raise NotImplementedError
619
620 @abc.abstractproperty
621 def user_vgid_process_attribute_tracker(self):
622 # type: () -> Type[VirtualGroupIDProcessAttributeTracker]
623 raise NotImplementedError
624
625 @abc.abstractproperty
626 def kernel_uid_process_attribute_tracker(self):
627 # type: () -> Type[UserIDProcessAttributeTracker]
628 raise NotImplementedError
629
630 @abc.abstractproperty
631 def kernel_vuid_process_attribute_tracker(self):
632 # type: () -> Type[VirtualUserIDProcessAttributeTracker]
633 raise NotImplementedError
634
635 @abc.abstractproperty
636 def user_vuid_process_attribute_tracker(self):
637 # type: () -> Type[VirtualUserIDProcessAttributeTracker]
638 raise NotImplementedError
639
640
641 class ControlException(RuntimeError):
642 """Base type for exceptions thrown by a controller."""
643
644 def __init__(self, msg):
645 # type: (str)
646 super().__init__(msg)
647
648
649 class Controller(abc.ABC):
650 """
651 Interface of a top-level control interface. A control interface can be, for
652 example, the LTTng client or a wrapper around liblttng-ctl. It is used to
653 create and manage top-level objects of a session daemon instance.
654 """
655
656 @abc.abstractmethod
657 def create_session(self, name=None, output=None):
658 # type: (Optional[str], Optional[SessionOutputLocation]) -> Session
659 """
660 Create a session with an output. Don't specify an output
661 to create a session without an output.
662 """
663 raise NotImplementedError
664
665 @abc.abstractmethod
666 def start_session_by_name(self, name):
667 # type: (str) -> None
668 """
669 Start a session by name.
670 """
671 raise NotImplementedError
672
673 @abc.abstractmethod
674 def start_session_by_glob_pattern(self, pattern):
675 # type: (str) -> None
676 """
677 Start sessions whose name matches `pattern`, see GLOB(7).
678 """
679 raise NotImplementedError
680
681 @abc.abstractmethod
682 def start_sessions_all(self):
683 """
684 Start all sessions visible to the current user.
685 """
686 # type: () -> None
687 raise NotImplementedError
688
689 @abc.abstractmethod
690 def stop_session_by_name(self, name):
691 # type: (str) -> None
692 """
693 Stop a session by name.
694 """
695 raise NotImplementedError
696
697 @abc.abstractmethod
698 def stop_session_by_glob_pattern(self, pattern):
699 # type: (str) -> None
700 """
701 Stop sessions whose name matches `pattern`, see GLOB(7).
702 """
703 raise NotImplementedError
704
705 @abc.abstractmethod
706 def stop_sessions_all(self):
707 """
708 Stop all sessions visible to the current user.
709 """
710 # type: () -> None
711 raise NotImplementedError
712
713 @abc.abstractmethod
714 def destroy_session_by_name(self, name):
715 # type: (str) -> None
716 """
717 Destroy a session by name.
718 """
719 raise NotImplementedError
720
721 @abc.abstractmethod
722 def destroy_session_by_glob_pattern(self, pattern):
723 # type: (str) -> None
724 """
725 Destroy sessions whose name matches `pattern`, see GLOB(7).
726 """
727 raise NotImplementedError
728
729 @abc.abstractmethod
730 def destroy_sessions_all(self):
731 # type: () -> None
732 """
733 Destroy all sessions visible to the current user.
734 """
735 raise NotImplementedError
736
737 @abc.abstractmethod
738 def list_sessions(self):
739 # type: () -> List[Session]
740 """
741 List all sessions visible to the current user.
742 """
743 raise NotImplementedError
744
745 @abc.abstractmethod
746 def rotate_session_by_name(self, name, wait=True):
747 # type: (str, bool) -> None
748 """
749 Rotate a session
750 """
751 raise NotImplementedError
752
753 @abc.abstractmethod
754 def schedule_size_based_rotation(self, name, size_bytes):
755 # type: (str, int) -> None
756 """
757 Schedule automatic size-based rotations.
758 """
759 raise NotImplementedError
760
761 @abc.abstractmethod
762 def schedule_time_based_rotation(self, name, period_seconds):
763 # type: (str, int) -> None
764 """
765 Schedule automatic time-based rotations.
766 """
767 raise NotImplementedError
This page took 0.044371 seconds and 4 git commands to generate.