3 # Copyright (C) 2022 Jérémie Galarneau <jeremie.galarneau@efficios.com>
5 # SPDX-License-Identifier: GPL-2.0-only
8 from . import lttngctl
, logger
, environment
10 from typing
import Callable
, Optional
, Type
, Union
, Iterator
14 import xml
.etree
.ElementTree
17 Implementation of the lttngctl interface based on the `lttng` command line client.
21 class Unsupported(lttngctl
.ControlException
):
22 def __init__(self
, msg
):
27 class InvalidMI(lttngctl
.ControlException
):
28 def __init__(self
, msg
):
33 class ChannelNotFound(lttngctl
.ControlException
):
34 def __init__(self
, msg
):
39 def _get_domain_option_name(domain
):
40 # type: (lttngctl.TracingDomain) -> str
42 lttngctl
.TracingDomain
.User
: "userspace",
43 lttngctl
.TracingDomain
.Kernel
: "kernel",
44 lttngctl
.TracingDomain
.Log4j
: "log4j",
45 lttngctl
.TracingDomain
.Python
: "python",
46 lttngctl
.TracingDomain
.JUL
: "jul",
50 def _get_domain_xml_mi_name(domain
):
51 # type: (lttngctl.TracingDomain) -> str
53 lttngctl
.TracingDomain
.User
: "UST",
54 lttngctl
.TracingDomain
.Kernel
: "KERNEL",
55 lttngctl
.TracingDomain
.Log4j
: "LOG4J",
56 lttngctl
.TracingDomain
.Python
: "PYTHON",
57 lttngctl
.TracingDomain
.JUL
: "JUL",
61 def _get_context_type_name(context
):
62 # type: (lttngctl.ContextType) -> str
63 if isinstance(context
, lttngctl
.VgidContextType
):
65 elif isinstance(context
, lttngctl
.VuidContextType
):
67 elif isinstance(context
, lttngctl
.VpidContextType
):
69 elif isinstance(context
, lttngctl
.JavaApplicationContextType
):
70 return "$app.{retriever}:{field}".format(
71 retriever
=context
.retriever_name
, field
=context
.field_name
75 "Context `{context_name}` is not supported by the LTTng client".format(
76 type(context
).__name
__
81 def _get_log_level_argument_name(log_level
):
82 # type: (lttngctl.LogLevel) -> str
83 if isinstance(log_level
, lttngctl
.UserLogLevel
):
85 lttngctl
.UserLogLevel
.EMERGENCY
: "EMER",
86 lttngctl
.UserLogLevel
.ALERT
: "ALERT",
87 lttngctl
.UserLogLevel
.CRITICAL
: "CRIT",
88 lttngctl
.UserLogLevel
.ERROR
: "ERR",
89 lttngctl
.UserLogLevel
.WARNING
: "WARNING",
90 lttngctl
.UserLogLevel
.NOTICE
: "NOTICE",
91 lttngctl
.UserLogLevel
.INFO
: "INFO",
92 lttngctl
.UserLogLevel
.DEBUG_SYSTEM
: "DEBUG_SYSTEM",
93 lttngctl
.UserLogLevel
.DEBUG_PROGRAM
: "DEBUG_PROGRAM",
94 lttngctl
.UserLogLevel
.DEBUG_PROCESS
: "DEBUG_PROCESS",
95 lttngctl
.UserLogLevel
.DEBUG_MODULE
: "DEBUG_MODULE",
96 lttngctl
.UserLogLevel
.DEBUG_UNIT
: "DEBUG_UNIT",
97 lttngctl
.UserLogLevel
.DEBUG_FUNCTION
: "DEBUG_FUNCTION",
98 lttngctl
.UserLogLevel
.DEBUG_LINE
: "DEBUG_LINE",
99 lttngctl
.UserLogLevel
.DEBUG
: "DEBUG",
101 elif isinstance(log_level
, lttngctl
.JULLogLevel
):
103 lttngctl
.JULLogLevel
.OFF
: "OFF",
104 lttngctl
.JULLogLevel
.SEVERE
: "SEVERE",
105 lttngctl
.JULLogLevel
.WARNING
: "WARNING",
106 lttngctl
.JULLogLevel
.INFO
: "INFO",
107 lttngctl
.JULLogLevel
.CONFIG
: "CONFIG",
108 lttngctl
.JULLogLevel
.FINE
: "FINE",
109 lttngctl
.JULLogLevel
.FINER
: "FINER",
110 lttngctl
.JULLogLevel
.FINEST
: "FINEST",
111 lttngctl
.JULLogLevel
.ALL
: "ALL",
113 elif isinstance(log_level
, lttngctl
.Log4jLogLevel
):
115 lttngctl
.Log4jLogLevel
.OFF
: "OFF",
116 lttngctl
.Log4jLogLevel
.FATAL
: "FATAL",
117 lttngctl
.Log4jLogLevel
.ERROR
: "ERROR",
118 lttngctl
.Log4jLogLevel
.WARN
: "WARN",
119 lttngctl
.Log4jLogLevel
.INFO
: "INFO",
120 lttngctl
.Log4jLogLevel
.DEBUG
: "DEBUG",
121 lttngctl
.Log4jLogLevel
.TRACE
: "TRACE",
122 lttngctl
.Log4jLogLevel
.ALL
: "ALL",
124 elif isinstance(log_level
, lttngctl
.PythonLogLevel
):
126 lttngctl
.PythonLogLevel
.CRITICAL
: "CRITICAL",
127 lttngctl
.PythonLogLevel
.ERROR
: "ERROR",
128 lttngctl
.PythonLogLevel
.WARNING
: "WARNING",
129 lttngctl
.PythonLogLevel
.INFO
: "INFO",
130 lttngctl
.PythonLogLevel
.DEBUG
: "DEBUG",
131 lttngctl
.PythonLogLevel
.NOTSET
: "NOTSET",
134 raise TypeError("Unknown log level type")
137 def _get_log_level_from_mi_log_level_name(mi_log_level_name
):
138 # type: (str) -> lttngctl.LogLevel
140 "TRACE_EMERG": lttngctl
.UserLogLevel
.EMERGENCY
,
141 "TRACE_ALERT": lttngctl
.UserLogLevel
.ALERT
,
142 "TRACE_CRIT": lttngctl
.UserLogLevel
.CRITICAL
,
143 "TRACE_ERR": lttngctl
.UserLogLevel
.ERROR
,
144 "TRACE_WARNING": lttngctl
.UserLogLevel
.WARNING
,
145 "TRACE_NOTICE": lttngctl
.UserLogLevel
.NOTICE
,
146 "TRACE_INFO": lttngctl
.UserLogLevel
.INFO
,
147 "TRACE_DEBUG_SYSTEM": lttngctl
.UserLogLevel
.DEBUG_SYSTEM
,
148 "TRACE_DEBUG_PROGRAM": lttngctl
.UserLogLevel
.DEBUG_PROGRAM
,
149 "TRACE_DEBUG_PROCESS": lttngctl
.UserLogLevel
.DEBUG_PROCESS
,
150 "TRACE_DEBUG_MODULE": lttngctl
.UserLogLevel
.DEBUG_MODULE
,
151 "TRACE_DEBUG_UNIT": lttngctl
.UserLogLevel
.DEBUG_UNIT
,
152 "TRACE_DEBUG_FUNCTION": lttngctl
.UserLogLevel
.DEBUG_FUNCTION
,
153 "TRACE_DEBUG_LINE": lttngctl
.UserLogLevel
.DEBUG_LINE
,
154 "TRACE_DEBUG": lttngctl
.UserLogLevel
.DEBUG
,
155 "JUL_OFF": lttngctl
.JULLogLevel
.OFF
,
156 "JUL_SEVERE": lttngctl
.JULLogLevel
.SEVERE
,
157 "JUL_WARNING": lttngctl
.JULLogLevel
.WARNING
,
158 "JUL_INFO": lttngctl
.JULLogLevel
.INFO
,
159 "JUL_CONFIG": lttngctl
.JULLogLevel
.CONFIG
,
160 "JUL_FINE": lttngctl
.JULLogLevel
.FINE
,
161 "JUL_FINER": lttngctl
.JULLogLevel
.FINER
,
162 "JUL_FINEST": lttngctl
.JULLogLevel
.FINEST
,
163 "JUL_ALL": lttngctl
.JULLogLevel
.ALL
,
164 "LOG4J_OFF": lttngctl
.Log4jLogLevel
.OFF
,
165 "LOG4J_FATAL": lttngctl
.Log4jLogLevel
.FATAL
,
166 "LOG4J_ERROR": lttngctl
.Log4jLogLevel
.ERROR
,
167 "LOG4J_WARN": lttngctl
.Log4jLogLevel
.WARN
,
168 "LOG4J_INFO": lttngctl
.Log4jLogLevel
.INFO
,
169 "LOG4J_DEBUG": lttngctl
.Log4jLogLevel
.DEBUG
,
170 "LOG4J_TRACE": lttngctl
.Log4jLogLevel
.TRACE
,
171 "LOG4J_ALL": lttngctl
.Log4jLogLevel
.ALL
,
172 "PYTHON_CRITICAL": lttngctl
.PythonLogLevel
.CRITICAL
,
173 "PYTHON_ERROR": lttngctl
.PythonLogLevel
.ERROR
,
174 "PYTHON_WARNING": lttngctl
.PythonLogLevel
.WARNING
,
175 "PYTHON_INFO": lttngctl
.PythonLogLevel
.INFO
,
176 "PYTHON_DEBUG": lttngctl
.PythonLogLevel
.DEBUG
,
177 "PYTHON_NOTSET": lttngctl
.PythonLogLevel
.NOTSET
,
181 def _get_tracepoint_event_rule_class_from_domain_type(domain_type
):
182 # type: (lttngctl.TracingDomain) -> Type[lttngctl.UserTracepointEventRule] | Type[lttngctl.Log4jTracepointEventRule] | Type[lttngctl.JULTracepointEventRule] | Type[lttngctl.PythonTracepointEventRule] | Type[lttngctl.KernelTracepointEventRule]
184 lttngctl
.TracingDomain
.User
: lttngctl
.UserTracepointEventRule
,
185 lttngctl
.TracingDomain
.JUL
: lttngctl
.JULTracepointEventRule
,
186 lttngctl
.TracingDomain
.Log4j
: lttngctl
.Log4jTracepointEventRule
,
187 lttngctl
.TracingDomain
.Python
: lttngctl
.PythonTracepointEventRule
,
188 lttngctl
.TracingDomain
.Kernel
: lttngctl
.KernelTracepointEventRule
,
192 class _Channel(lttngctl
.Channel
):
195 client
, # type: LTTngClient
197 domain
, # type: lttngctl.TracingDomain
198 session
, # type: _Session
200 self
._client
= client
# type: LTTngClient
201 self
._name
= name
# type: str
202 self
._domain
= domain
# type: lttngctl.TracingDomain
203 self
._session
= session
# type: _Session
205 def add_context(self
, context_type
):
206 # type: (lttngctl.ContextType) -> None
207 domain_option_name
= _get_domain_option_name(self
.domain
)
208 context_type_name
= _get_context_type_name(context_type
)
209 self
._client
._run
_cmd
(
210 "add-context --{domain_option_name} --channel '{channel_name}' --type {context_type_name}".format(
211 domain_option_name
=domain_option_name
,
212 channel_name
=self
.name
,
213 context_type_name
=context_type_name
,
217 def add_recording_rule(self
, rule
):
218 # type: (Type[lttngctl.EventRule]) -> None
220 "enable-event --session {session_name} --channel {channel_name}".format(
221 session_name
=self
._session
.name
, channel_name
=self
.name
224 if isinstance(rule
, lttngctl
.TracepointEventRule
):
225 domain_option_name
= (
227 if isinstance(rule
, lttngctl
.UserTracepointEventRule
)
230 client_args
= client_args
+ " --{domain_option_name}".format(
231 domain_option_name
=domain_option_name
234 if rule
.name_pattern
:
235 client_args
= client_args
+ " " + rule
.name_pattern
237 client_args
= client_args
+ " --all"
239 if rule
.filter_expression
:
240 client_args
= client_args
+ " " + rule
.filter_expression
242 if rule
.log_level_rule
:
243 if isinstance(rule
.log_level_rule
, lttngctl
.LogLevelRuleAsSevereAs
):
244 client_args
= client_args
+ " --loglevel {log_level}".format(
245 log_level
=_get_log_level_argument_name(
246 rule
.log_level_rule
.level
249 elif isinstance(rule
.log_level_rule
, lttngctl
.LogLevelRuleExactly
):
250 client_args
= client_args
+ " --loglevel-only {log_level}".format(
251 log_level
=_get_log_level_argument_name(
252 rule
.log_level_rule
.level
257 "Unsupported log level rule type `{log_level_rule_type}`".format(
258 log_level_rule_type
=type(rule
.log_level_rule
).__name
__
262 if rule
.name_pattern_exclusions
:
263 client_args
= client_args
+ " --exclude "
264 for idx
, pattern
in enumerate(rule
.name_pattern_exclusions
):
266 client_args
= client_args
+ ","
267 client_args
= client_args
+ pattern
270 "event rule type `{event_rule_type}` is unsupported by LTTng client".format(
271 event_rule_type
=type(rule
).__name
__
275 self
._client
._run
_cmd
(client_args
)
284 # type: () -> lttngctl.TracingDomain
288 def recording_rules(self
):
289 # type: () -> Iterator[lttngctl.EventRule]
290 list_session_xml
= self
._client
._run
_cmd
(
291 "list '{session_name}'".format(session_name
=self
._session
.name
),
292 LTTngClient
.CommandOutputFormat
.MI_XML
,
295 root
= xml
.etree
.ElementTree
.fromstring(list_session_xml
)
296 command_output
= LTTngClient
._mi
_get
_in
_element
(root
, "output")
297 sessions
= LTTngClient
._mi
_get
_in
_element
(command_output
, "sessions")
299 # The channel's session is supposed to be the only session returned by the command
300 if len(sessions
) != 1:
302 "Only one session expected when listing with an explicit session name"
304 session
= sessions
[0]
306 # Look for the channel's domain
308 target_domain_mi_name
= _get_domain_xml_mi_name(self
.domain
)
309 for domain
in LTTngClient
._mi
_get
_in
_element
(session
, "domains"):
311 LTTngClient
._mi
_get
_in
_element
(domain
, "type").text
312 == target_domain_mi_name
314 target_domain
= domain
316 if target_domain
is None:
317 raise ChannelNotFound(
318 "Failed to find channel `{channel_name}`: no channel in target domain".format(
319 channel_name
=self
.name
323 target_channel
= None
324 for channel
in LTTngClient
._mi
_get
_in
_element
(target_domain
, "channels"):
325 if LTTngClient
._mi
_get
_in
_element
(channel
, "name").text
== self
.name
:
326 target_channel
= channel
329 if target_channel
is None:
330 raise ChannelNotFound(
331 "Failed to find channel `{channel_name}`: no such channel in target domain".format(
332 channel_name
=self
.name
336 tracepoint_event_rule_class
= None
338 for event
in LTTngClient
._mi
_get
_in
_element
(target_channel
, "events"):
339 # Note that the "enabled" property is ignored as it is not exposed by
340 # the EventRule interface.
341 pattern
= LTTngClient
._mi
_get
_in
_element
(event
, "name").text
342 type = LTTngClient
._mi
_get
_in
_element
(event
, "type").text
344 filter_expression
= None
345 filter_expression_element
= LTTngClient
._mi
_find
_in
_element
(
346 event
, "filter_expression"
348 if filter_expression_element
:
349 filter_expression
= filter_expression_element
.text
352 for exclusion
in LTTngClient
._mi
_get
_in
_element
(event
, "exclusions"):
353 exclusions
.append(exclusion
.text
)
355 exclusions
= exclusions
if len(exclusions
) > 0 else None
357 if type != "TRACEPOINT":
359 "Non-tracepoint event rules are not supported by this Controller implementation"
362 tracepoint_event_rule_class
= (
363 _get_tracepoint_event_rule_class_from_domain_type(self
.domain
)
366 if self
.domain
!= lttngctl
.TracingDomain
.Kernel
:
367 log_level_element
= LTTngClient
._mi
_find
_in
_element
(event
, "loglevel")
368 log_level_type_element
= LTTngClient
._mi
_find
_in
_element
(
369 event
, "loglevel_type"
372 log_level_rule
= None
373 if log_level_element
is not None and log_level_type_element
is not None:
374 if log_level_element
.text
is None:
375 raise InvalidMI("`loglevel` element of event rule has no text")
377 if log_level_type_element
.text
== "RANGE":
378 log_level_rule
= lttngctl
.LogLevelRuleAsSevereAs(
379 _get_log_level_from_mi_log_level_name(
380 log_level_element
.text
383 elif log_level_type_element
.text
== "SINGLE":
384 log_level_rule
= lttngctl
.LogLevelRuleExactly(
385 _get_log_level_from_mi_log_level_name(
386 log_level_element
.text
390 yield tracepoint_event_rule_class(
391 pattern
, filter_expression
, log_level_rule
, exclusions
394 yield tracepoint_event_rule_class(pattern
, filter_expression
)
398 class _ProcessAttribute(enum
.Enum
):
400 VPID
= "Virtual Process ID"
402 VUID
= "Virtual User ID"
404 VGID
= "Virtual Group ID"
407 return "<%s.%s>" % (self
.__class
__.__name
__, self
.name
)
410 def _get_process_attribute_option_name(attribute
):
411 # type: (_ProcessAttribute) -> str
413 _ProcessAttribute
.PID
: "pid",
414 _ProcessAttribute
.VPID
: "vpid",
415 _ProcessAttribute
.UID
: "uid",
416 _ProcessAttribute
.VUID
: "vuid",
417 _ProcessAttribute
.GID
: "gid",
418 _ProcessAttribute
.VGID
: "vgid",
422 class _ProcessAttributeTracker(lttngctl
.ProcessAttributeTracker
):
425 client
, # type: LTTngClient
426 attribute
, # type: _ProcessAttribute
427 domain
, # type: lttngctl.TracingDomain
428 session
, # type: _Session
430 self
._client
= client
# type: LTTngClient
431 self
._tracked
_attribute
= attribute
# type: _ProcessAttribute
432 self
._domain
= domain
# type: lttngctl.TracingDomain
433 self
._session
= session
# type: _Session
434 if attribute
== _ProcessAttribute
.PID
or attribute
== _ProcessAttribute
.VPID
:
435 self
._allowed
_value
_types
= [int, str] # type: list[type]
437 self
._allowed
_value
_types
= [int] # type: list[type]
439 def _call_client(self
, cmd_name
, value
):
440 # type: (str, Union[int, str]) -> None
441 if type(value
) not in self
._allowed
_value
_types
:
443 "Value of type `{value_type}` is not allowed for process attribute {attribute_name}".format(
444 value_type
=type(value
).__name
__,
445 attribute_name
=self
._tracked
_attribute
.name
,
449 process_attribute_option_name
= _get_process_attribute_option_name(
450 self
._tracked
_attribute
452 domain_name
= _get_domain_option_name(self
._domain
)
453 self
._client
._run
_cmd
(
454 "{cmd_name} --session '{session_name}' --{domain_name} --{tracked_attribute_name} {value}".format(
456 session_name
=self
._session
.name
,
457 domain_name
=domain_name
,
458 tracked_attribute_name
=process_attribute_option_name
,
463 def track(self
, value
):
464 # type: (Union[int, str]) -> None
465 self
._call
_client
("track", value
)
467 def untrack(self
, value
):
468 # type: (Union[int, str]) -> None
469 self
._call
_client
("untrack", value
)
472 class _Session(lttngctl
.Session
):
475 client
, # type: LTTngClient
477 output
, # type: Optional[lttngctl.SessionOutputLocation]
479 self
._client
= client
# type: LTTngClient
480 self
._name
= name
# type: str
481 self
._output
= output
# type: Optional[lttngctl.SessionOutputLocation]
492 buffer_sharing_policy
=lttngctl
.BufferSharingPolicy
.PerUID
,
494 # type: (lttngctl.TracingDomain, Optional[str], lttngctl.BufferSharingPolicy) -> lttngctl.Channel
495 channel_name
= lttngctl
.Channel
._generate
_name
()
496 domain_option_name
= _get_domain_option_name(domain
)
497 self
._client
._run
_cmd
(
498 "enable-channel --session '{session_name}' --{domain_name} '{channel_name}' {buffer_sharing_policy}".format(
499 session_name
=self
.name
,
500 domain_name
=domain_option_name
,
501 channel_name
=channel_name
,
502 buffer_sharing_policy
="--buffers-uid"
503 if buffer_sharing_policy
== lttngctl
.BufferSharingPolicy
.PerUID
504 else "--buffers-pid",
507 return _Channel(self
._client
, channel_name
, domain
, self
)
509 def add_context(self
, context_type
):
510 # type: (lttngctl.ContextType) -> None
515 # type: () -> "Optional[Type[lttngctl.SessionOutputLocation]]"
516 return self
._output
# type: ignore
520 self
._client
._run
_cmd
("start '{session_name}'".format(session_name
=self
.name
))
524 self
._client
._run
_cmd
("stop '{session_name}'".format(session_name
=self
.name
))
528 self
._client
._run
_cmd
("destroy '{session_name}'".format(session_name
=self
.name
))
530 def rotate(self
, wait
=True):
531 # type: (bool) -> None
532 self
._client
.rotate_session_by_name(self
.name
, wait
)
537 list_session_xml
= self
._client
._run
_cmd
(
538 "list '{session_name}'".format(session_name
=self
.name
),
539 LTTngClient
.CommandOutputFormat
.MI_XML
,
542 root
= xml
.etree
.ElementTree
.fromstring(list_session_xml
)
543 command_output
= LTTngClient
._mi
_get
_in
_element
(root
, "output")
544 sessions
= LTTngClient
._mi
_get
_in
_element
(command_output
, "sessions")
545 session_mi
= LTTngClient
._mi
_get
_in
_element
(sessions
, "session")
547 enabled_text
= LTTngClient
._mi
_get
_in
_element
(session_mi
, "enabled").text
548 if enabled_text
not in ["true", "false"]:
550 "Expected boolean value in element '{}': value='{}'".format(
551 session_mi
.tag
, enabled_text
555 return enabled_text
== "true"
558 def kernel_pid_process_attribute_tracker(self
):
559 # type: () -> Type[lttngctl.ProcessIDProcessAttributeTracker]
560 return _ProcessAttributeTracker(self
._client
, _ProcessAttribute
.PID
, lttngctl
.TracingDomain
.Kernel
, self
) # type: ignore
563 def kernel_vpid_process_attribute_tracker(self
):
564 # type: () -> Type[lttngctl.VirtualProcessIDProcessAttributeTracker]
565 return _ProcessAttributeTracker(self
._client
, _ProcessAttribute
.VPID
, lttngctl
.TracingDomain
.Kernel
, self
) # type: ignore
568 def user_vpid_process_attribute_tracker(self
):
569 # type: () -> Type[lttngctl.VirtualProcessIDProcessAttributeTracker]
570 return _ProcessAttributeTracker(self
._client
, _ProcessAttribute
.VPID
, lttngctl
.TracingDomain
.User
, self
) # type: ignore
573 def kernel_gid_process_attribute_tracker(self
):
574 # type: () -> Type[lttngctl.GroupIDProcessAttributeTracker]
575 return _ProcessAttributeTracker(self
._client
, _ProcessAttribute
.GID
, lttngctl
.TracingDomain
.Kernel
, self
) # type: ignore
578 def kernel_vgid_process_attribute_tracker(self
):
579 # type: () -> Type[lttngctl.VirtualGroupIDProcessAttributeTracker]
580 return _ProcessAttributeTracker(self
._client
, _ProcessAttribute
.VGID
, lttngctl
.TracingDomain
.Kernel
, self
) # type: ignore
583 def user_vgid_process_attribute_tracker(self
):
584 # type: () -> Type[lttngctl.VirtualGroupIDProcessAttributeTracker]
585 return _ProcessAttributeTracker(self
._client
, _ProcessAttribute
.VGID
, lttngctl
.TracingDomain
.User
, self
) # type: ignore
588 def kernel_uid_process_attribute_tracker(self
):
589 # type: () -> Type[lttngctl.UserIDProcessAttributeTracker]
590 return _ProcessAttributeTracker(self
._client
, _ProcessAttribute
.UID
, lttngctl
.TracingDomain
.Kernel
, self
) # type: ignore
593 def kernel_vuid_process_attribute_tracker(self
):
594 # type: () -> Type[lttngctl.VirtualUserIDProcessAttributeTracker]
595 return _ProcessAttributeTracker(self
._client
, _ProcessAttribute
.VUID
, lttngctl
.TracingDomain
.Kernel
, self
) # type: ignore
598 def user_vuid_process_attribute_tracker(self
):
599 # type: () -> Type[lttngctl.VirtualUserIDProcessAttributeTracker]
600 return _ProcessAttributeTracker(self
._client
, _ProcessAttribute
.VUID
, lttngctl
.TracingDomain
.User
, self
) # type: ignore
603 class LTTngClientError(lttngctl
.ControlException
):
606 command_args
, # type: str
607 error_output
, # type: str
609 self
._command
_args
= command_args
# type: str
610 self
._output
= error_output
# type: str
613 class LTTngClient(logger
._Logger
, lttngctl
.Controller
):
615 Implementation of a LTTngCtl Controller that uses the `lttng` client as a back-end.
618 class CommandOutputFormat(enum
.Enum
):
622 _MI_NS
= "{https://lttng.org/xml/ns/lttng-mi}"
626 test_environment
, # type: environment._Environment
627 log
, # type: Optional[Callable[[str], None]]
629 logger
._Logger
.__init
__(self
, log
)
630 self
._environment
= test_environment
# type: environment._Environment
633 def _namespaced_mi_element(property):
635 return LTTngClient
._MI
_NS
+ property
637 def _run_cmd(self
, command_args
, output_format
=CommandOutputFormat
.MI_XML
):
638 # type: (str, CommandOutputFormat) -> str
640 Invoke the `lttng` client with a set of arguments. The command is
641 executed in the context of the client's test environment.
643 args
= [str(self
._environment
.lttng_client_path
)] # type: list[str]
644 if output_format
== LTTngClient
.CommandOutputFormat
.MI_XML
:
645 args
.extend(["--mi", "xml"])
647 args
.extend(shlex
.split(command_args
))
649 self
._log
("lttng {command_args}".format(command_args
=command_args
))
651 client_env
= os
.environ
.copy() # type: dict[str, str]
652 client_env
["LTTNG_HOME"] = str(self
._environment
.lttng_home_location
)
654 process
= subprocess
.Popen(
655 args
, stdout
=subprocess
.PIPE
, stderr
=subprocess
.STDOUT
, env
=client_env
658 out
= process
.communicate()[0]
660 if process
.returncode
!= 0:
661 decoded_output
= out
.decode("utf-8")
662 for error_line
in decoded_output
.splitlines():
663 self
._log
(error_line
)
665 raise LTTngClientError(command_args
, decoded_output
)
667 return out
.decode("utf-8")
669 def create_session(self
, name
=None, output
=None):
670 # type: (Optional[str], Optional[lttngctl.SessionOutputLocation]) -> lttngctl.Session
671 name
= name
if name
else lttngctl
.Session
._generate
_name
()
673 if isinstance(output
, lttngctl
.LocalSessionOutputLocation
):
674 output_option
= "--output '{output_path}'".format(output_path
=output
.path
)
676 output_option
= "--no-output"
678 raise TypeError("LTTngClient only supports local or no output")
681 "create '{session_name}' {output_option}".format(
682 session_name
=name
, output_option
=output_option
685 return _Session(self
, name
, output
)
687 def start_session_by_name(self
, name
):
688 # type: (str) -> None
689 self
._run
_cmd
("start '{session_name}'".format(session_name
=name
))
691 def start_session_by_glob_pattern(self
, pattern
):
692 # type: (str) -> None
693 self
._run
_cmd
("start --glob '{pattern}'".format(pattern
=pattern
))
695 def start_sessions_all(self
):
697 self
._run
_cmd
("start --all")
699 def stop_session_by_name(self
, name
):
700 # type: (str) -> None
701 self
._run
_cmd
("stop '{session_name}'".format(session_name
=name
))
703 def stop_session_by_glob_pattern(self
, pattern
):
704 # type: (str) -> None
705 self
._run
_cmd
("stop --glob '{pattern}'".format(pattern
=pattern
))
707 def stop_sessions_all(self
):
709 self
._run
_cmd
("stop --all")
711 def destroy_session_by_name(self
, name
):
712 # type: (str) -> None
713 self
._run
_cmd
("destroy '{session_name}'".format(session_name
=name
))
715 def destroy_session_by_glob_pattern(self
, pattern
):
716 # type: (str) -> None
717 self
._run
_cmd
("destroy --glob '{pattern}'".format(pattern
=pattern
))
719 def destroy_sessions_all(self
):
721 self
._run
_cmd
("destroy --all")
723 def rotate_session_by_name(self
, name
, wait
=True):
725 "rotate '{session_name}' {wait_option}".format(
726 session_name
=name
, wait_option
="-n" if wait
is False else ""
730 def schedule_size_based_rotation(self
, name
, size_bytes
):
731 # type (str, int) -> None
733 "enable-rotation --session '{session_name}' --size {size}".format(
734 session_name
=name
, size
=size_bytes
738 def schedule_time_based_rotation(self
, name
, period_seconds
):
739 # type (str, int) -> None
741 "enable-rotation --session '{session_name}' --timer {period_seconds}s".format(
742 session_name
=name
, period_seconds
=period_seconds
747 def _mi_find_in_element(element
, sub_element_name
):
748 # type: (xml.etree.ElementTree.Element, str) -> Optional[xml.etree.ElementTree.Element]
749 return element
.find(LTTngClient
._namespaced
_mi
_element
(sub_element_name
))
752 def _mi_get_in_element(element
, sub_element_name
):
753 # type: (xml.etree.ElementTree.Element, str) -> xml.etree.ElementTree.Element
754 result
= LTTngClient
._mi
_find
_in
_element
(element
, sub_element_name
)
757 "Failed to find element '{}' within command MI element '{}'".format(
758 element
.tag
, sub_element_name
764 def list_sessions(self
):
765 # type () -> List[Session]
766 list_sessions_xml
= self
._run
_cmd
(
767 "list", LTTngClient
.CommandOutputFormat
.MI_XML
770 root
= xml
.etree
.ElementTree
.fromstring(list_sessions_xml
)
771 command_output
= self
._mi
_get
_in
_element
(root
, "output")
772 sessions
= self
._mi
_get
_in
_element
(command_output
, "sessions")
774 ctl_sessions
= [] # type: list[lttngctl.Session]
776 for session_mi
in sessions
:
777 name
= self
._mi
_get
_in
_element
(session_mi
, "name").text
778 path
= self
._mi
_get
_in
_element
(session_mi
, "path").text
782 "Invalid empty 'name' element in '{}'".format(session_mi
.tag
)
786 "Invalid empty 'path' element in '{}'".format(session_mi
.tag
)
788 if not path
.startswith("/"):
790 "{} does not support non-local session outputs".format(type(self
))
794 _Session(self
, name
, lttngctl
.LocalSessionOutputLocation(path
))