Tests: add a recording rule listing test
authorJérémie Galarneau <jeremie.galarneau@efficios.com>
Thu, 15 Jun 2023 20:38:08 +0000 (16:38 -0400)
committerJérémie Galarneau <jeremie.galarneau@efficios.com>
Thu, 22 Jun 2023 18:08:22 +0000 (14:08 -0400)
Add a test that validates that recording rules can be added to a channel
and listed back. Most of the changes are to the lttngtest package in
order to provide the requisite capabilities to the test itself.

This test is also intended as a reproducer for #1373.

Signed-off-by: Jérémie Galarneau <jeremie.galarneau@efficios.com>
Change-Id: I1c6b5e072934ba9760cb1f838395e18a4c6bb211

tests/regression/Makefile.am
tests/regression/tools/client/Makefile.am
tests/regression/tools/client/test_event_rule_listing.py [new file with mode: 0755]
tests/utils/lttngtest/lttng.py
tests/utils/lttngtest/lttngctl.py

index a95feee9893c6429364cd83f2733ddd6e3a69a24..fc7dfc17e725f27926a077598883b0cf16042d55 100644 (file)
@@ -65,7 +65,8 @@ TESTS = tools/base-path/test_ust \
        tools/trigger/name/test_trigger_name_backwards_compat \
        tools/trigger/hidden/test_hidden_trigger \
        tools/context/test_ust.py \
-       tools/client/test_session_commands.py
+       tools/client/test_session_commands.py \
+       tools/client/test_event_rule_listing.py
 
 # Only build kernel tests on Linux.
 if IS_LINUX
index e96a24b13c08399fc9ddb2a1e16fd2e8da8117eb..4cc740ec071108f48acee302c8171526077bc773 100644 (file)
@@ -1,7 +1,7 @@
 # SPDX-License-Identifier: GPL-2.0-only
 
-noinst_SCRIPTS = test_session_commands.py
-EXTRA_DIST = test_session_commands.py
+noinst_SCRIPTS = test_session_commands.py test_event_rule_listing.py
+EXTRA_DIST = test_session_commands.py test_event_rule_listing.py
 
 all-local:
        @if [ x"$(srcdir)" != x"$(builddir)" ]; then \
diff --git a/tests/regression/tools/client/test_event_rule_listing.py b/tests/regression/tools/client/test_event_rule_listing.py
new file mode 100755 (executable)
index 0000000..e06deee
--- /dev/null
@@ -0,0 +1,89 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2023 Jérémie Galarneau <jeremie.galarneau@efficios.com>
+#
+# SPDX-License-Identifier: GPL-2.0-only
+
+import pathlib
+import sys
+import os
+from typing import Any, Callable, Type, Dict, Iterator
+import random
+import string
+from collections.abc import Mapping
+
+"""
+Test the listing of recording rules associated to a channel.
+"""
+
+# Import in-tree test utils
+test_utils_import_path = pathlib.Path(__file__).absolute().parents[3] / "utils"
+sys.path.append(str(test_utils_import_path))
+
+import lttngtest
+
+
+def test_identical_recording_rules_except_log_level_rule_type(tap, test_env):
+    # type: (lttngtest.TapGenerator, lttngtest._Environment) -> None
+    tap.diagnostic(
+        "Test adding and listing event rules that differ only by their log level rule type"
+    )
+
+    client = lttngtest.LTTngClient(test_env, log=tap.diagnostic)
+
+    session = client.create_session()
+    channel = session.add_channel(lttngtest.TracingDomain.User)
+    session.start()
+
+    app = test_env.launch_wait_trace_test_application(100)
+
+    llr_exactly = lttngtest.LogLevelRuleExactly(lttngtest.UserLogLevel.DEBUG_LINE)
+    llr_as_severe_as = lttngtest.LogLevelRuleAsSevereAs(
+        lttngtest.UserLogLevel.DEBUG_LINE
+    )
+
+    recording_rule_log_at_level = lttngtest.UserTracepointEventRule(
+        "lttng*", None, llr_exactly, None
+    )
+    recording_rule_log_at_least_level = lttngtest.UserTracepointEventRule(
+        "lttng*", None, llr_as_severe_as, None
+    )
+    recording_rule_no_log_level = lttngtest.UserTracepointEventRule(
+        "lttng*", None, None, None
+    )
+
+    with tap.case("Adding a recording rule with an `exact` log level rule"):
+        channel.add_recording_rule(recording_rule_log_at_level)
+
+    with tap.case("Adding a recording rule with an `as severe as` log level rule"):
+        channel.add_recording_rule(recording_rule_log_at_least_level)
+
+    with tap.case(
+        "Adding a recording rule without a log level rule (all log levels enabled)"
+    ):
+        channel.add_recording_rule(recording_rule_no_log_level)
+
+    rule_match_count = 0
+    for rule in channel.recording_rules:
+        if (
+            rule != recording_rule_no_log_level
+            and rule != recording_rule_log_at_level
+            and rule != recording_rule_log_at_least_level
+        ):
+            continue
+
+        rule_match_count = rule_match_count + 1
+
+    tap.test(
+        rule_match_count == 3,
+        "Recording rules are added and listed",
+    )
+
+
+tap = lttngtest.TapGenerator(4)
+tap.diagnostic("Test the addition and listing of event rules associated to a channel")
+
+with lttngtest.test_environment(with_sessiond=True, log=tap.diagnostic) as test_env:
+    test_identical_recording_rules_except_log_level_rule_type(tap, test_env)
+
+sys.exit(0 if tap.is_successful else 1)
index 9dd21fb033fc7566411c2d369a1e1d1083ace26c..76c60b5b0f08b4f563474e514e3e76389d68f169 100644 (file)
@@ -6,9 +6,8 @@
 
 
 from . import lttngctl, logger, environment
-import pathlib
 import os
-from typing import Callable, Optional, Type, Union
+from typing import Callable, Optional, Type, Union, Iterator
 import shlex
 import subprocess
 import enum
@@ -31,20 +30,32 @@ class InvalidMI(lttngctl.ControlException):
         super().__init__(msg)
 
 
+class ChannelNotFound(lttngctl.ControlException):
+    def __init__(self, msg):
+        # type: (str) -> None
+        super().__init__(msg)
+
+
 def _get_domain_option_name(domain):
     # type: (lttngctl.TracingDomain) -> str
-    if domain == lttngctl.TracingDomain.User:
-        return "userspace"
-    elif domain == lttngctl.TracingDomain.Kernel:
-        return "kernel"
-    elif domain == lttngctl.TracingDomain.Log4j:
-        return "log4j"
-    elif domain == lttngctl.TracingDomain.JUL:
-        return "jul"
-    elif domain == lttngctl.TracingDomain.Python:
-        return "python"
-    else:
-        raise Unsupported("Domain `{domain_name}` is not supported by the LTTng client")
+    return {
+        lttngctl.TracingDomain.User: "userspace",
+        lttngctl.TracingDomain.Kernel: "kernel",
+        lttngctl.TracingDomain.Log4j: "log4j",
+        lttngctl.TracingDomain.Python: "python",
+        lttngctl.TracingDomain.JUL: "jul",
+    }[domain]
+
+
+def _get_domain_xml_mi_name(domain):
+    # type: (lttngctl.TracingDomain) -> str
+    return {
+        lttngctl.TracingDomain.User: "UST",
+        lttngctl.TracingDomain.Kernel: "KERNEL",
+        lttngctl.TracingDomain.Log4j: "LOG4J",
+        lttngctl.TracingDomain.Python: "PYTHON",
+        lttngctl.TracingDomain.JUL: "JUL",
+    }[domain]
 
 
 def _get_context_type_name(context):
@@ -67,6 +78,117 @@ def _get_context_type_name(context):
         )
 
 
+def _get_log_level_argument_name(log_level):
+    # type: (lttngctl.LogLevel) -> str
+    if isinstance(log_level, lttngctl.UserLogLevel):
+        return {
+            lttngctl.UserLogLevel.EMERGENCY: "EMER",
+            lttngctl.UserLogLevel.ALERT: "ALERT",
+            lttngctl.UserLogLevel.CRITICAL: "CRIT",
+            lttngctl.UserLogLevel.ERROR: "ERR",
+            lttngctl.UserLogLevel.WARNING: "WARNING",
+            lttngctl.UserLogLevel.NOTICE: "NOTICE",
+            lttngctl.UserLogLevel.INFO: "INFO",
+            lttngctl.UserLogLevel.DEBUG_SYSTEM: "DEBUG_SYSTEM",
+            lttngctl.UserLogLevel.DEBUG_PROGRAM: "DEBUG_PROGRAM",
+            lttngctl.UserLogLevel.DEBUG_PROCESS: "DEBUG_PROCESS",
+            lttngctl.UserLogLevel.DEBUG_MODULE: "DEBUG_MODULE",
+            lttngctl.UserLogLevel.DEBUG_UNIT: "DEBUG_UNIT",
+            lttngctl.UserLogLevel.DEBUG_FUNCTION: "DEBUG_FUNCTION",
+            lttngctl.UserLogLevel.DEBUG_LINE: "DEBUG_LINE",
+            lttngctl.UserLogLevel.DEBUG: "DEBUG",
+        }[log_level]
+    elif isinstance(log_level, lttngctl.JULLogLevel):
+        return {
+            lttngctl.JULLogLevel.OFF: "OFF",
+            lttngctl.JULLogLevel.SEVERE: "SEVERE",
+            lttngctl.JULLogLevel.WARNING: "WARNING",
+            lttngctl.JULLogLevel.INFO: "INFO",
+            lttngctl.JULLogLevel.CONFIG: "CONFIG",
+            lttngctl.JULLogLevel.FINE: "FINE",
+            lttngctl.JULLogLevel.FINER: "FINER",
+            lttngctl.JULLogLevel.FINEST: "FINEST",
+            lttngctl.JULLogLevel.ALL: "ALL",
+        }[log_level]
+    elif isinstance(log_level, lttngctl.Log4jLogLevel):
+        return {
+            lttngctl.Log4jLogLevel.OFF: "OFF",
+            lttngctl.Log4jLogLevel.FATAL: "FATAL",
+            lttngctl.Log4jLogLevel.ERROR: "ERROR",
+            lttngctl.Log4jLogLevel.WARN: "WARN",
+            lttngctl.Log4jLogLevel.INFO: "INFO",
+            lttngctl.Log4jLogLevel.DEBUG: "DEBUG",
+            lttngctl.Log4jLogLevel.TRACE: "TRACE",
+            lttngctl.Log4jLogLevel.ALL: "ALL",
+        }[log_level]
+    elif isinstance(log_level, lttngctl.PythonLogLevel):
+        return {
+            lttngctl.PythonLogLevel.CRITICAL: "CRITICAL",
+            lttngctl.PythonLogLevel.ERROR: "ERROR",
+            lttngctl.PythonLogLevel.WARNING: "WARNING",
+            lttngctl.PythonLogLevel.INFO: "INFO",
+            lttngctl.PythonLogLevel.DEBUG: "DEBUG",
+            lttngctl.PythonLogLevel.NOTSET: "NOTSET",
+        }[log_level]
+
+    raise TypeError("Unknown log level type")
+
+
+def _get_log_level_from_mi_log_level_name(mi_log_level_name):
+    # type: (str) -> lttngctl.LogLevel
+    return {
+        "TRACE_EMERG": lttngctl.UserLogLevel.EMERGENCY,
+        "TRACE_ALERT": lttngctl.UserLogLevel.ALERT,
+        "TRACE_CRIT": lttngctl.UserLogLevel.CRITICAL,
+        "TRACE_ERR": lttngctl.UserLogLevel.ERROR,
+        "TRACE_WARNING": lttngctl.UserLogLevel.WARNING,
+        "TRACE_NOTICE": lttngctl.UserLogLevel.NOTICE,
+        "TRACE_INFO": lttngctl.UserLogLevel.INFO,
+        "TRACE_DEBUG_SYSTEM": lttngctl.UserLogLevel.DEBUG_SYSTEM,
+        "TRACE_DEBUG_PROGRAM": lttngctl.UserLogLevel.DEBUG_PROGRAM,
+        "TRACE_DEBUG_PROCESS": lttngctl.UserLogLevel.DEBUG_PROCESS,
+        "TRACE_DEBUG_MODULE": lttngctl.UserLogLevel.DEBUG_MODULE,
+        "TRACE_DEBUG_UNIT": lttngctl.UserLogLevel.DEBUG_UNIT,
+        "TRACE_DEBUG_FUNCTION": lttngctl.UserLogLevel.DEBUG_FUNCTION,
+        "TRACE_DEBUG_LINE": lttngctl.UserLogLevel.DEBUG_LINE,
+        "TRACE_DEBUG": lttngctl.UserLogLevel.DEBUG,
+        "JUL_OFF": lttngctl.JULLogLevel.OFF,
+        "JUL_SEVERE": lttngctl.JULLogLevel.SEVERE,
+        "JUL_WARNING": lttngctl.JULLogLevel.WARNING,
+        "JUL_INFO": lttngctl.JULLogLevel.INFO,
+        "JUL_CONFIG": lttngctl.JULLogLevel.CONFIG,
+        "JUL_FINE": lttngctl.JULLogLevel.FINE,
+        "JUL_FINER": lttngctl.JULLogLevel.FINER,
+        "JUL_FINEST": lttngctl.JULLogLevel.FINEST,
+        "JUL_ALL": lttngctl.JULLogLevel.ALL,
+        "LOG4J_OFF": lttngctl.Log4jLogLevel.OFF,
+        "LOG4J_FATAL": lttngctl.Log4jLogLevel.FATAL,
+        "LOG4J_ERROR": lttngctl.Log4jLogLevel.ERROR,
+        "LOG4J_WARN": lttngctl.Log4jLogLevel.WARN,
+        "LOG4J_INFO": lttngctl.Log4jLogLevel.INFO,
+        "LOG4J_DEBUG": lttngctl.Log4jLogLevel.DEBUG,
+        "LOG4J_TRACE": lttngctl.Log4jLogLevel.TRACE,
+        "LOG4J_ALL": lttngctl.Log4jLogLevel.ALL,
+        "PYTHON_CRITICAL": lttngctl.PythonLogLevel.CRITICAL,
+        "PYTHON_ERROR": lttngctl.PythonLogLevel.ERROR,
+        "PYTHON_WARNING": lttngctl.PythonLogLevel.WARNING,
+        "PYTHON_INFO": lttngctl.PythonLogLevel.INFO,
+        "PYTHON_DEBUG": lttngctl.PythonLogLevel.DEBUG,
+        "PYTHON_NOTSET": lttngctl.PythonLogLevel.NOTSET,
+    }[mi_log_level_name]
+
+
+def _get_tracepoint_event_rule_class_from_domain_type(domain_type):
+    # type: (lttngctl.TracingDomain) -> Type[lttngctl.UserTracepointEventRule] | Type[lttngctl.Log4jTracepointEventRule] | Type[lttngctl.JULTracepointEventRule] | Type[lttngctl.PythonTracepointEventRule] | Type[lttngctl.KernelTracepointEventRule]
+    return {
+        lttngctl.TracingDomain.User: lttngctl.UserTracepointEventRule,
+        lttngctl.TracingDomain.JUL: lttngctl.JULTracepointEventRule,
+        lttngctl.TracingDomain.Log4j: lttngctl.Log4jTracepointEventRule,
+        lttngctl.TracingDomain.Python: lttngctl.PythonTracepointEventRule,
+        lttngctl.TracingDomain.Kernel: lttngctl.KernelTracepointEventRule,
+    }[domain_type]
+
+
 class _Channel(lttngctl.Channel):
     def __init__(
         self,
@@ -120,11 +242,15 @@ class _Channel(lttngctl.Channel):
             if rule.log_level_rule:
                 if isinstance(rule.log_level_rule, lttngctl.LogLevelRuleAsSevereAs):
                     client_args = client_args + " --loglevel {log_level}".format(
-                        log_level=rule.log_level_rule.level
+                        log_level=_get_log_level_argument_name(
+                            rule.log_level_rule.level
+                        )
                     )
                 elif isinstance(rule.log_level_rule, lttngctl.LogLevelRuleExactly):
                     client_args = client_args + " --loglevel-only {log_level}".format(
-                        log_level=rule.log_level_rule.level
+                        log_level=_get_log_level_argument_name(
+                            rule.log_level_rule.level
+                        )
                     )
                 else:
                     raise Unsupported(
@@ -158,6 +284,115 @@ class _Channel(lttngctl.Channel):
         # type: () -> lttngctl.TracingDomain
         return self._domain
 
+    @property
+    def recording_rules(self):
+        # type: () -> Iterator[lttngctl.EventRule]
+        list_session_xml = self._client._run_cmd(
+            "list '{session_name}'".format(session_name=self._session.name),
+            LTTngClient.CommandOutputFormat.MI_XML,
+        )
+
+        root = xml.etree.ElementTree.fromstring(list_session_xml)
+        command_output = LTTngClient._mi_get_in_element(root, "output")
+        sessions = LTTngClient._mi_get_in_element(command_output, "sessions")
+
+        # The channel's session is supposed to be the only session returned by the command
+        if len(sessions) != 1:
+            raise InvalidMI(
+                "Only one session expected when listing with an explicit session name"
+            )
+        session = sessions[0]
+
+        # Look for the channel's domain
+        target_domain = None
+        target_domain_mi_name = _get_domain_xml_mi_name(self.domain)
+        for domain in LTTngClient._mi_get_in_element(session, "domains"):
+            if (
+                LTTngClient._mi_get_in_element(domain, "type").text
+                == target_domain_mi_name
+            ):
+                target_domain = domain
+
+        if target_domain is None:
+            raise ChannelNotFound(
+                "Failed to find channel `{channel_name}`: no channel in target domain".format(
+                    channel_name=self.name
+                )
+            )
+
+        target_channel = None
+        for channel in LTTngClient._mi_get_in_element(target_domain, "channels"):
+            if LTTngClient._mi_get_in_element(channel, "name").text == self.name:
+                target_channel = channel
+                break
+
+        if target_channel is None:
+            raise ChannelNotFound(
+                "Failed to find channel `{channel_name}`: no such channel in target domain".format(
+                    channel_name=self.name
+                )
+            )
+
+        tracepoint_event_rule_class = None
+
+        for event in LTTngClient._mi_get_in_element(target_channel, "events"):
+            # Note that the "enabled" property is ignored as it is not exposed by
+            # the EventRule interface.
+            pattern = LTTngClient._mi_get_in_element(event, "name").text
+            type = LTTngClient._mi_get_in_element(event, "type").text
+
+            filter_expression = None
+            filter_expression_element = LTTngClient._mi_find_in_element(
+                event, "filter_expression"
+            )
+            if filter_expression_element:
+                filter_expression = filter_expression_element.text
+
+            exclusions = []
+            for exclusion in LTTngClient._mi_get_in_element(event, "exclusions"):
+                exclusions.append(exclusion.text)
+
+            exclusions = exclusions if len(exclusions) > 0 else None
+
+            if type != "TRACEPOINT":
+                raise Unsupported(
+                    "Non-tracepoint event rules are not supported by this Controller implementation"
+                )
+
+            tracepoint_event_rule_class = (
+                _get_tracepoint_event_rule_class_from_domain_type(self.domain)
+            )
+            event_rule = None
+            if self.domain != lttngctl.TracingDomain.Kernel:
+                log_level_element = LTTngClient._mi_find_in_element(event, "loglevel")
+                log_level_type_element = LTTngClient._mi_find_in_element(
+                    event, "loglevel_type"
+                )
+
+                log_level_rule = None
+                if log_level_element is not None and log_level_type_element is not None:
+                    if log_level_element.text is None:
+                        raise InvalidMI("`loglevel` element of event rule has no text")
+
+                    if log_level_type_element.text == "RANGE":
+                        log_level_rule = lttngctl.LogLevelRuleAsSevereAs(
+                            _get_log_level_from_mi_log_level_name(
+                                log_level_element.text
+                            )
+                        )
+                    elif log_level_type_element.text == "SINGLE":
+                        log_level_rule = lttngctl.LogLevelRuleExactly(
+                            _get_log_level_from_mi_log_level_name(
+                                log_level_element.text
+                            )
+                        )
+
+                yield tracepoint_event_rule_class(
+                    pattern, filter_expression, log_level_rule, exclusions
+                )
+            else:
+                yield tracepoint_event_rule_class(pattern, filter_expression)
+
 
 @enum.unique
 class _ProcessAttribute(enum.Enum):
@@ -305,11 +540,11 @@ class _Session(lttngctl.Session):
         )
 
         root = xml.etree.ElementTree.fromstring(list_session_xml)
-        command_output = LTTngClient._mi_find_in_element(root, "output")
-        sessions = LTTngClient._mi_find_in_element(command_output, "sessions")
-        session_mi = LTTngClient._mi_find_in_element(sessions, "session")
+        command_output = LTTngClient._mi_get_in_element(root, "output")
+        sessions = LTTngClient._mi_get_in_element(command_output, "sessions")
+        session_mi = LTTngClient._mi_get_in_element(sessions, "session")
 
-        enabled_text = LTTngClient._mi_find_in_element(session_mi, "enabled").text
+        enabled_text = LTTngClient._mi_get_in_element(session_mi, "enabled").text
         if enabled_text not in ["true", "false"]:
             raise InvalidMI(
                 "Expected boolean value in element '{}': value='{}'".format(
@@ -426,6 +661,7 @@ class LTTngClient(logger._Logger, lttngctl.Controller):
             decoded_output = out.decode("utf-8")
             for error_line in decoded_output.splitlines():
                 self._log(error_line)
+
             raise LTTngClientError(command_args, decoded_output)
         else:
             return out.decode("utf-8")
@@ -509,8 +745,13 @@ class LTTngClient(logger._Logger, lttngctl.Controller):
 
     @staticmethod
     def _mi_find_in_element(element, sub_element_name):
+        # type: (xml.etree.ElementTree.Element, str) -> Optional[xml.etree.ElementTree.Element]
+        return element.find(LTTngClient._namespaced_mi_element(sub_element_name))
+
+    @staticmethod
+    def _mi_get_in_element(element, sub_element_name):
         # type: (xml.etree.ElementTree.Element, str) -> xml.etree.ElementTree.Element
-        result = element.find(LTTngClient._namespaced_mi_element(sub_element_name))
+        result = LTTngClient._mi_find_in_element(element, sub_element_name)
         if result is None:
             raise InvalidMI(
                 "Failed to find element '{}' within command MI element '{}'".format(
@@ -527,14 +768,14 @@ class LTTngClient(logger._Logger, lttngctl.Controller):
         )
 
         root = xml.etree.ElementTree.fromstring(list_sessions_xml)
-        command_output = self._mi_find_in_element(root, "output")
-        sessions = self._mi_find_in_element(command_output, "sessions")
+        command_output = self._mi_get_in_element(root, "output")
+        sessions = self._mi_get_in_element(command_output, "sessions")
 
         ctl_sessions = []  # type: list[lttngctl.Session]
 
         for session_mi in sessions:
-            name = self._mi_find_in_element(session_mi, "name").text
-            path = self._mi_find_in_element(session_mi, "path").text
+            name = self._mi_get_in_element(session_mi, "name").text
+            path = self._mi_get_in_element(session_mi, "path").text
 
             if name is None:
                 raise InvalidMI(
index 2595c6eb473a1ba48919866645ad159b3dfd9a22..c62e74f47b3747028e270ac0af1578966b48f8b0 100644 (file)
@@ -107,28 +107,92 @@ class EventRule(abc.ABC):
 
 
 class LogLevelRule:
+    def __eq__(self, other):
+        # type (LogLevelRule) -> bool
+        if type(self) != type(other):
+            return False
+
+        return self.level == other.level
+
+
+@enum.unique
+class LogLevel(enum.Enum):
     pass
 
 
+@enum.unique
+class UserLogLevel(LogLevel):
+    EMERGENCY = 0
+    ALERT = 1
+    CRITICAL = 2
+    ERROR = 3
+    WARNING = 4
+    NOTICE = 5
+    INFO = 6
+    DEBUG_SYSTEM = 7
+    DEBUG_PROGRAM = 8
+    DEBUG_PROCESS = 9
+    DEBUG_MODULE = 10
+    DEBUG_UNIT = 11
+    DEBUG_FUNCTION = 12
+    DEBUG_LINE = 13
+    DEBUG = 14
+
+
+@enum.unique
+class JULLogLevel(LogLevel):
+    OFF = 2147483647
+    SEVERE = 1000
+    WARNING = 900
+    INFO = 800
+    CONFIG = 700
+    FINE = 500
+    FINER = 400
+    FINEST = 300
+    ALL = -2147483648
+
+
+@enum.unique
+class Log4jLogLevel(LogLevel):
+    OFF = 2147483647
+    FATAL = 50000
+    ERROR = 40000
+    WARN = 30000
+    INFO = 20000
+    DEBUG = 10000
+    TRACE = 5000
+    ALL = -2147483648
+
+
+@enum.unique
+class PythonLogLevel(LogLevel):
+    CRITICAL = 50
+    ERROR = 40
+    WARNING = 30
+    INFO = 20
+    DEBUG = 10
+    NOTSET = 0
+
+
 class LogLevelRuleAsSevereAs(LogLevelRule):
     def __init__(self, level):
-        # type: (int)
+        # type: (LogLevel)
         self._level = level
 
     @property
     def level(self):
-        # type: () -> int
+        # type: () -> LogLevel
         return self._level
 
 
 class LogLevelRuleExactly(LogLevelRule):
     def __init__(self, level):
-        # type: (int)
+        # type: (LogLevel)
         self._level = level
 
     @property
     def level(self):
-        # type: () -> int
+        # type: () -> LogLevel
         return self._level
 
 
@@ -137,15 +201,27 @@ class TracepointEventRule(EventRule):
         self,
         name_pattern=None,  # type: Optional[str]
         filter_expression=None,  # type: Optional[str]
-        log_level_rule=None,  # type: Optional[LogLevelRule]
-        name_pattern_exclusions=None,  # type: Optional[List[str]]
     ):
         self._name_pattern = name_pattern  # type: Optional[str]
         self._filter_expression = filter_expression  # type: Optional[str]
-        self._log_level_rule = log_level_rule  # type: Optional[LogLevelRule]
-        self._name_pattern_exclusions = (
-            name_pattern_exclusions
-        )  # type: Optional[List[str]]
+
+    def _equals(self, other):
+        # type (TracepointEventRule) -> bool
+        # Overridden by derived classes that have supplementary attributes.
+        return True
+
+    def __eq__(self, other):
+        # type (TracepointEventRule) -> bool
+        if type(self) != type(other):
+            return False
+
+        if self.name_pattern != other.name_pattern:
+            return False
+
+        if self.filter_expression != other.filter_expression:
+            return False
+
+        return self._equals(other)
 
     @property
     def name_pattern(self):
@@ -157,6 +233,31 @@ class TracepointEventRule(EventRule):
         # type: () -> Optional[str]
         return self._filter_expression
 
+
+class UserTracepointEventRule(TracepointEventRule):
+    def __init__(
+        self,
+        name_pattern=None,  # type: Optional[str]
+        filter_expression=None,  # type: Optional[str]
+        log_level_rule=None,  # type: Optional[LogLevelRule]
+        name_pattern_exclusions=None,  # type: Optional[List[str]]
+    ):
+        TracepointEventRule.__init__(self, name_pattern, filter_expression)
+        self._log_level_rule = log_level_rule  # type: Optional[LogLevelRule]
+        self._name_pattern_exclusions = (
+            name_pattern_exclusions
+        )  # type: Optional[List[str]]
+
+        if log_level_rule and not isinstance(log_level_rule.level, UserLogLevel):
+            raise ValueError("Log level rule must use a UserLogLevel as its value")
+
+    def _equals(self, other):
+        # type (UserTracepointEventRule) -> bool
+        return (
+            self.log_level_rule == other.log_level_rule
+            and self.name_pattern_exclusions == other.name_pattern_exclusions
+        )
+
     @property
     def log_level_rule(self):
         # type: () -> Optional[LogLevelRule]
@@ -168,7 +269,7 @@ class TracepointEventRule(EventRule):
         return self._name_pattern_exclusions
 
 
-class UserTracepointEventRule(TracepointEventRule):
+class Log4jTracepointEventRule(TracepointEventRule):
     def __init__(
         self,
         name_pattern=None,  # type: Optional[str]
@@ -176,16 +277,108 @@ class UserTracepointEventRule(TracepointEventRule):
         log_level_rule=None,  # type: Optional[LogLevelRule]
         name_pattern_exclusions=None,  # type: Optional[List[str]]
     ):
-        TracepointEventRule.__init__(**locals())
+        TracepointEventRule.__init__(self, name_pattern, filter_expression)
+        self._log_level_rule = log_level_rule  # type: Optional[LogLevelRule]
+        self._name_pattern_exclusions = (
+            name_pattern_exclusions
+        )  # type: Optional[List[str]]
 
+        if log_level_rule and not isinstance(log_level_rule.level, Log4jLogLevel):
+            raise ValueError("Log level rule must use a Log4jLogLevel as its value")
 
-class KernelTracepointEventRule(TracepointEventRule):
+    def _equals(self, other):
+        # type (Log4jTracepointEventRule) -> bool
+        return (
+            self.log_level_rule == other.log_level_rule
+            and self.name_pattern_exclusions == other.name_pattern_exclusions
+        )
+
+    @property
+    def log_level_rule(self):
+        # type: () -> Optional[LogLevelRule]
+        return self._log_level_rule
+
+    @property
+    def name_pattern_exclusions(self):
+        # type: () -> Optional[List[str]]
+        return self._name_pattern_exclusions
+
+
+class JULTracepointEventRule(TracepointEventRule):
     def __init__(
         self,
         name_pattern=None,  # type: Optional[str]
         filter_expression=None,  # type: Optional[str]
         log_level_rule=None,  # type: Optional[LogLevelRule]
         name_pattern_exclusions=None,  # type: Optional[List[str]]
+    ):
+        TracepointEventRule.__init__(self, name_pattern, filter_expression)
+        self._log_level_rule = log_level_rule  # type: Optional[LogLevelRule]
+        self._name_pattern_exclusions = (
+            name_pattern_exclusions
+        )  # type: Optional[List[str]]
+
+        if log_level_rule and not isinstance(log_level_rule.level, JULLogLevel):
+            raise ValueError("Log level rule must use a JULLogLevel as its value")
+
+    def _equals(self, other):
+        # type (JULTracepointEventRule) -> bool
+        return (
+            self.log_level_rule == other.log_level_rule
+            and self.name_pattern_exclusions == other.name_pattern_exclusions
+        )
+
+    @property
+    def log_level_rule(self):
+        # type: () -> Optional[LogLevelRule]
+        return self._log_level_rule
+
+    @property
+    def name_pattern_exclusions(self):
+        # type: () -> Optional[List[str]]
+        return self._name_pattern_exclusions
+
+
+class PythonTracepointEventRule(TracepointEventRule):
+    def __init__(
+        self,
+        name_pattern=None,  # type: Optional[str]
+        filter_expression=None,  # type: Optional[str]
+        log_level_rule=None,  # type: Optional[LogLevelRule]
+        name_pattern_exclusions=None,  # type: Optional[List[str]]
+    ):
+        TracepointEventRule.__init__(self, name_pattern, filter_expression)
+        self._log_level_rule = log_level_rule  # type: Optional[LogLevelRule]
+        self._name_pattern_exclusions = (
+            name_pattern_exclusions
+        )  # type: Optional[List[str]]
+
+        if log_level_rule and not isinstance(log_level_rule.level, PythonLogLevel):
+            raise ValueError("Log level rule must use a PythonLogLevel as its value")
+
+    def _equals(self, other):
+        # type (PythonTracepointEventRule) -> bool
+        return (
+            self.log_level_rule == other.log_level_rule
+            and self.name_pattern_exclusions == other.name_pattern_exclusions
+        )
+
+    @property
+    def log_level_rule(self):
+        # type: () -> Optional[LogLevelRule]
+        return self._log_level_rule
+
+    @property
+    def name_pattern_exclusions(self):
+        # type: () -> Optional[List[str]]
+        return self._name_pattern_exclusions
+
+
+class KernelTracepointEventRule(TracepointEventRule):
+    def __init__(
+        self,
+        name_pattern=None,  # type: Optional[str]
+        filter_expression=None,  # type: Optional[str]
     ):
         TracepointEventRule.__init__(**locals())
 
This page took 0.035961 seconds and 4 git commands to generate.