bt2: Add bindings for trace classes
authorSimon Marchi <simon.marchi@efficios.com>
Wed, 8 May 2019 20:55:37 +0000 (16:55 -0400)
committerPhilippe Proulx <eeppeliteloop@gmail.com>
Wed, 5 Jun 2019 17:47:34 +0000 (13:47 -0400)
This patch adds a Python wrapper for the bt_trace_class concept.

Trace classes in Python are created using the _create_trace_class on a
_UserComponent.

A TraceClass is seen as a mapping of stream class ids to stream classes.
This means that with trace class `tc`, it's possible to access its
stream classes by id using `tc[id]`.  It's also possible to iterate on
`tc` to iterate on stream class ids.

A brand new test is also added, along with some test utils that should
be useful in future tests as well.

It is not feature-complete yet, for example there is no way to
instantiate a trace using this trace class, this will come in a
subsequent patch (that will handle test_trace.py).  It is possible to
create stream classes in a trace class, but not with every possible
option, just enough to support the trace class tests.

Change-Id: Ib9f29d7ebc21b6ade19e9ffbad3f4b85e790321d
Signed-off-by: Simon Marchi <simon.marchi@efficios.com>
Signed-off-by: Francis Deslauriers <francis.deslauriers@efficios.com>
Reviewed-on: https://review.lttng.org/c/babeltrace/+/1283
Tested-by: jenkins
Reviewed-by: Philippe Proulx <eeppeliteloop@gmail.com>
bindings/python/bt2/Makefile.am
bindings/python/bt2/bt2/__init__.py.in
bindings/python/bt2/bt2/component.py
bindings/python/bt2/bt2/native_bt_trace_class.i
bindings/python/bt2/bt2/stream_class.py
bindings/python/bt2/bt2/trace_class.py [new file with mode: 0644]
tests/bindings/python/bt2/test_trace_class.py [new file with mode: 0644]
tests/bindings/python/bt2/utils.py [new file with mode: 0644]

index bc5c7f187a337390a0a92714293fba9096b2fc37..90ee322e2fac8c20d0224ac1bd075d7fd4f54825 100644 (file)
@@ -55,6 +55,7 @@ STATIC_BINDINGS_DEPS =                                        \
        bt2/stream_class.py                             \
        bt2/stream.py                                   \
        bt2/trace.py                                    \
+       bt2/trace_class.py                              \
        bt2/trace_collection_message_iterator.py        \
        bt2/utils.py                                    \
        bt2/value.py
index 7b866fbd7d38f833e181bca5ae1e4b5210958fec..c46007a3a99d92dfca6c7160b31938e695bfb450 100644 (file)
@@ -68,6 +68,7 @@ from bt2.query_executor import *
 from bt2.stream import _Stream
 from bt2.stream_class import *
 from bt2.trace import *
+from bt2.trace_class import *
 from bt2.trace_collection_message_iterator import *
 from bt2.value import *
 from bt2.value import _Value
index e41e00bd129d1616e6f587a403fd8d50cc695ec9..fcd50785dcfef57f6ebcb950aed0c9c673c47b58 100644 (file)
@@ -620,9 +620,31 @@ class _UserComponent(metaclass=_UserComponentType):
             other_port_ptr, other_port_type)
         self._port_connected(port, other_port)
 
+    def _create_trace_class(self, env=None, uuid=None,
+                            assigns_automatic_stream_class_id=True):
+        ptr = self._as_self_component_ptr(self._ptr)
+        tc_ptr = native_bt.trace_class_create(ptr)
+
+        if tc_ptr is None:
+            raise bt2.CreationError('could not create trace class')
+
+        tc = bt2.TraceClass._create_from_ptr(tc_ptr)
+
+        if env is not None:
+            for key, value in env.items():
+                tc.env[key] = value
+
+        if uuid is not None:
+            tc._uuid = uuid
+
+        tc._assigns_automatic_stream_class_id = assigns_automatic_stream_class_id
+
+        return tc
+
 
 class _UserSourceComponent(_UserComponent, _SourceComponent):
     _as_not_self_specific_component_ptr = staticmethod(native_bt.self_component_source_as_component_source)
+    _as_self_component_ptr = staticmethod(native_bt.self_component_source_as_self_component)
 
     @property
     def _output_ports(self):
@@ -648,6 +670,7 @@ class _UserSourceComponent(_UserComponent, _SourceComponent):
 
 class _UserFilterComponent(_UserComponent, _FilterComponent):
     _as_not_self_specific_component_ptr = staticmethod(native_bt.self_component_filter_as_component_filter)
+    _as_self_component_ptr = staticmethod(native_bt.self_component_filter_as_self_component)
 
     @property
     def _output_ports(self):
@@ -694,6 +717,7 @@ class _UserFilterComponent(_UserComponent, _FilterComponent):
 
 class _UserSinkComponent(_UserComponent, _SinkComponent):
     _as_not_self_specific_component_ptr = staticmethod(native_bt.self_component_sink_as_component_sink)
+    _as_self_component_ptr = staticmethod(native_bt.self_component_sink_as_self_component)
 
     @property
     def _input_ports(self):
index bd0233935aa1677aed3bee7c707d383cdb48cfeb..71061ffacccb30fddf49c18bd33992e7d58769a7 100644 (file)
@@ -46,7 +46,7 @@ extern uint64_t bt_trace_class_get_environment_entry_count(
 
 extern void bt_trace_class_borrow_environment_entry_by_index_const(
                const bt_trace_class *trace_class, uint64_t index,
-               const char **OUT, const bt_value **OUT_VALUE);
+               const char **OUT, const bt_value **OUT);
 
 extern const bt_value *
 bt_trace_class_borrow_environment_entry_value_by_name_const(
index 97cd593fe2d90b325399e647fcb03ef61a76142d..e94c3b2297d3d23fc74a05fc4fa72ff417b7242c 100644 (file)
@@ -49,6 +49,9 @@ class _EventClassIterator(collections.abc.Iterator):
 
 
 class StreamClass(object._SharedObject, collections.abc.Mapping):
+    _get_ref = staticmethod(native_bt.stream_class_get_ref)
+    _put_ref = staticmethod(native_bt.stream_class_put_ref)
+
     def __init__(self, name=None, id=None, packet_context_field_class=None,
                  event_header_field_class=None, event_context_field_class=None,
                  event_classes=None):
diff --git a/bindings/python/bt2/bt2/trace_class.py b/bindings/python/bt2/bt2/trace_class.py
new file mode 100644 (file)
index 0000000..1f473a3
--- /dev/null
@@ -0,0 +1,193 @@
+# The MIT License (MIT)
+#
+# Copyright (c) 2017 Philippe Proulx <pproulx@efficios.com>
+# Copyright (c) 2018 Francis Deslauriers <francis.deslauriers@efficios.com>
+# Copyright (c) 2019 Simon Marchi <simon.marchi@efficios.com>
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+
+__all__ = ['TraceClass']
+
+import bt2
+from bt2 import native_bt, utils, object
+import uuid as uuidp
+import collections.abc
+import functools
+
+
+class _TraceClassEnv(collections.abc.MutableMapping):
+    def __init__(self, trace_class):
+        self._trace_class = trace_class
+
+    def __getitem__(self, key):
+        utils._check_str(key)
+
+        borrow_entry_fn = native_bt.trace_class_borrow_environment_entry_value_by_name_const
+        value_ptr = borrow_entry_fn(self._trace_class._ptr, key)
+
+        if value_ptr is None:
+            raise KeyError(key)
+
+        return bt2.value._create_from_ptr_and_get_ref(value_ptr)
+
+    def __setitem__(self, key, value):
+        if isinstance(value, str):
+            set_env_entry_fn = native_bt.trace_class_set_environment_entry_string
+        elif isinstance(value, int):
+            set_env_entry_fn = native_bt.trace_class_set_environment_entry_integer
+        else:
+            raise TypeError('expected str or int, got {}'.format(type(value)))
+
+        ret = set_env_entry_fn(self._trace_class._ptr, key, value)
+
+        utils._handle_ret(ret, "cannot set trace class object's environment entry")
+
+    def __delitem__(self, key):
+        raise NotImplementedError
+
+    def __len__(self):
+        count = native_bt.trace_class_get_environment_entry_count(self._trace_class._ptr)
+        assert count >= 0
+        return count
+
+    def __iter__(self):
+        trace_class_ptr = self._trace_class_env._trace_class._ptr
+
+        for idx in range(len(self)):
+            borrow_entry_fn = native_bt.trace_class_borrow_environment_entry_by_index_const
+            entry_name, _ = borrow_entry_fn(trace_class_ptr, idx)
+            assert entry_name is not None
+            yield entry_name
+
+
+class _StreamClassIterator(collections.abc.Iterator):
+    def __init__(self, trace_class):
+        self._trace_class = trace_class
+        self._at = 0
+
+    def __next__(self):
+        if self._at == len(self._trace_class):
+            raise StopIteration
+
+        borrow_stream_class_fn = native_bt.trace_class_borrow_stream_class_by_index_const
+        sc_ptr = borrow_stream_class_fn(self._trace_class._ptr, self._at)
+        assert sc_ptr
+        id = native_bt.stream_class_get_id(sc_ptr)
+        assert id >= 0
+        self._at += 1
+        return id
+
+
+def _trace_class_destruction_listener_from_native(user_listener, trace_class_ptr):
+    trace_class = bt2.trace_class.TraceClass._create_from_ptr_and_get_ref(trace_class_ptr)
+    user_listener(trace_class)
+
+
+class TraceClass(object._SharedObject, collections.abc.Mapping):
+    _get_ref = staticmethod(native_bt.trace_class_get_ref)
+    _put_ref = staticmethod(native_bt.trace_class_put_ref)
+
+    @property
+    def uuid(self):
+        uuid_bytes = native_bt.trace_class_get_uuid(self._ptr)
+        if uuid_bytes is None:
+            return
+
+        return uuidp.UUID(bytes=uuid_bytes)
+
+    def _uuid(self, uuid):
+        utils._check_type(uuid, uuidp.UUID)
+        native_bt.trace_class_set_uuid(self._ptr, uuid.bytes)
+
+    _uuid = property(fset=_uuid)
+
+    # Number of stream classes in this trace class.
+
+    def __len__(self):
+        count = native_bt.trace_class_get_stream_class_count(self._ptr)
+        assert count >= 0
+        return count
+
+    # Get a stream class by stream id.
+
+    def __getitem__(self, key):
+        utils._check_uint64(key)
+
+        sc_ptr = native_bt.trace_class_borrow_stream_class_by_id_const(self._ptr, key)
+        if sc_ptr is None:
+            raise KeyError(key)
+
+        return bt2.StreamClass._create_from_ptr_and_get_ref(sc_ptr)
+
+    def __iter__(self):
+        for idx in range(len(self)):
+            sc_ptr = native_bt.trace_class_borrow_stream_class_by_index_const(self._ptr, idx)
+            assert sc_ptr is not None
+
+            id = native_bt.stream_class_get_id(sc_ptr)
+            assert id >= 0
+
+            yield id
+
+    @property
+    def env(self):
+        return _TraceClassEnv(self)
+
+    def create_stream_class(self, id=None):
+
+        if self.assigns_automatic_stream_class_id:
+            if id is not None:
+                raise bt2.CreationError('id provided, but trace class assigns automatic stream class ids')
+
+            sc_ptr = native_bt.stream_class_create(self._ptr)
+        else:
+            if id is None:
+                raise bt2.CreationError('id not provided, but trace class does not assign automatic stream class ids')
+
+            utils._check_uint64(id)
+            sc_ptr = native_bt.stream_class_create_with_id(self._ptr, id)
+
+        return bt2.StreamClass._create_from_ptr(sc_ptr)
+
+    @property
+    def assigns_automatic_stream_class_id(self):
+        return native_bt.trace_class_assigns_automatic_stream_class_id(self._ptr)
+
+    def _assigns_automatic_stream_class_id(self, auto_id):
+        utils._check_bool(auto_id)
+        return native_bt.trace_class_set_assigns_automatic_stream_class_id(self._ptr, auto_id)
+
+    _assigns_automatic_stream_class_id = property(fset=_assigns_automatic_stream_class_id)
+
+    # Add a listener to be called when the trace class is destroyed.
+
+    def add_destruction_listener(self, listener):
+
+        if not callable(listener):
+            raise TypeError("'listener' parameter is not callable")
+
+        fn = native_bt.py3_trace_class_add_destruction_listener
+        listener_from_native = functools.partial(_trace_class_destruction_listener_from_native,
+                                                 listener)
+
+        listener_id = fn(self._ptr, listener_from_native)
+        if listener_id is None:
+            utils._raise_bt2_error('cannot add destruction listener to trace class object')
+
+        return bt2._ListenerHandle(listener_id, self)
diff --git a/tests/bindings/python/bt2/test_trace_class.py b/tests/bindings/python/bt2/test_trace_class.py
new file mode 100644 (file)
index 0000000..265b0de
--- /dev/null
@@ -0,0 +1,148 @@
+import uuid
+import unittest
+import bt2
+from utils import run_in_component_init, get_default_trace_class
+
+
+class TraceClassTestCase(unittest.TestCase):
+
+    def test_create_default(self):
+        def f(comp_self):
+            return comp_self._create_trace_class()
+
+        tc = run_in_component_init(f)
+
+        self.assertEqual(len(tc), 0)
+        self.assertEqual(len(tc.env), 0)
+        self.assertIsNone(tc.uuid)
+        self.assertTrue(tc.assigns_automatic_stream_class_id)
+
+    def test_uuid(self):
+        def f(comp_self):
+            return comp_self._create_trace_class(uuid=uuid.UUID('da7d6b6f-3108-4706-89bd-ab554732611b'))
+
+        tc = run_in_component_init(f)
+
+        self.assertEqual(tc.uuid, uuid.UUID('da7d6b6f-3108-4706-89bd-ab554732611b'))
+
+    def test_automatic_stream_class_id(self):
+        def f(comp_self):
+            return comp_self._create_trace_class(assigns_automatic_stream_class_id=True)
+
+        tc = run_in_component_init(f)
+        self.assertTrue(tc.assigns_automatic_stream_class_id)
+
+        # This should not throw.
+        sc1 = tc.create_stream_class()
+        sc2 = tc.create_stream_class()
+
+        self.assertNotEqual(sc1.id, sc2.id)
+
+    def test_automatic_stream_class_id_raises(self):
+        def f(comp_self):
+            return comp_self._create_trace_class(assigns_automatic_stream_class_id=True)
+
+        tc = run_in_component_init(f)
+        self.assertTrue(tc.assigns_automatic_stream_class_id)
+
+        with self.assertRaises(bt2.CreationError):
+            sc1 = tc.create_stream_class(23)
+
+    def test_no_assigns_automatic_stream_class_id(self):
+        def f(comp_self):
+            return comp_self._create_trace_class(assigns_automatic_stream_class_id=False)
+
+        tc = run_in_component_init(f)
+        self.assertFalse(tc.assigns_automatic_stream_class_id)
+
+        sc = tc.create_stream_class(id=28)
+        self.assertEqual(sc.id, 28)
+
+    def test_no_assigns_automatic_stream_class_id_raises(self):
+        def f(comp_self):
+            return comp_self._create_trace_class(assigns_automatic_stream_class_id=False)
+
+        tc = run_in_component_init(f)
+        self.assertFalse(tc.assigns_automatic_stream_class_id)
+
+        # In this mode, it is required to pass an explicit id.
+        with self.assertRaises(bt2.CreationError):
+            tc.create_stream_class()
+
+    def test_env_get(self):
+        def f(comp_self):
+            return comp_self._create_trace_class(env={'hello': 'you', 'foo': -5})
+
+        tc = run_in_component_init(f)
+
+        self.assertEqual(tc.env['hello'], 'you')
+        self.assertEqual(tc.env['foo'], -5)
+
+    def test_env_get_non_existent(self):
+        def f(comp_self):
+            return comp_self._create_trace_class(env={'hello': 'you', 'foo': -5})
+
+        tc = run_in_component_init(f)
+
+        with self.assertRaises(KeyError):
+            tc.env['lel']
+
+    @staticmethod
+    def _create_trace_class_with_some_stream_classes():
+        def f(comp_self):
+            return comp_self._create_trace_class(assigns_automatic_stream_class_id=False)
+
+        tc = run_in_component_init(f)
+        sc1 = tc.create_stream_class(id=12)
+        sc2 = tc.create_stream_class(id=54)
+        sc3 = tc.create_stream_class(id=2018)
+        return tc, sc1, sc2, sc3
+
+    def test_getitem(self):
+        tc, _, _, sc3 = self._create_trace_class_with_some_stream_classes()
+        self.assertEqual(tc[2018].addr, sc3.addr)
+
+    def test_getitem_wrong_key_type(self):
+        tc, _, _, _ = self._create_trace_class_with_some_stream_classes()
+        with self.assertRaises(TypeError):
+            tc['hello']
+
+    def test_getitem_wrong_key(self):
+        tc, _, _, _ = self._create_trace_class_with_some_stream_classes()
+        with self.assertRaises(KeyError):
+            tc[4]
+
+    def test_len(self):
+        tc = get_default_trace_class()
+        self.assertEqual(len(tc), 0)
+        tc.create_stream_class()
+        self.assertEqual(len(tc), 1)
+
+    def test_iter(self):
+        tc, sc1, sc2, sc3 = self._create_trace_class_with_some_stream_classes()
+
+        for sc_id, stream_class in tc.items():
+            self.assertIsInstance(stream_class, bt2.stream_class.StreamClass)
+
+            if sc_id == 12:
+                self.assertEqual(stream_class.addr, sc1.addr)
+            elif sc_id == 54:
+                self.assertEqual(stream_class.addr, sc2.addr)
+            elif sc_id == 2018:
+                self.assertEqual(stream_class.addr, sc3.addr)
+
+    def test_destruction_listener(self):
+        def on_trace_class_destruction(trace_class):
+            nonlocal trace_class_destroyed
+            trace_class_destroyed = True
+
+        trace_class_destroyed = False
+
+        trace_class = get_default_trace_class()
+        trace_class.add_destruction_listener(on_trace_class_destruction)
+
+        self.assertFalse(trace_class_destroyed)
+
+        del trace_class
+
+        self.assertTrue(trace_class_destroyed)
diff --git a/tests/bindings/python/bt2/utils.py b/tests/bindings/python/bt2/utils.py
new file mode 100644 (file)
index 0000000..768462e
--- /dev/null
@@ -0,0 +1,40 @@
+import bt2
+
+# Run callable `func` in the context of a component's __init__ method.  The
+# callable is passed the Component being instantiated.
+#
+# The value returned by the callable is returned by run_in_component_init.
+
+def run_in_component_init(func):
+    class MySink(bt2._UserSinkComponent):
+        def __init__(self, params):
+            nonlocal res_bound
+            res_bound = func(self)
+
+        def _consume(self):
+            pass
+
+    g = bt2.Graph()
+    res_bound = None
+    g.add_component(MySink, 'comp')
+
+    # We deliberately use a different variable for returning the result than
+    # the variable bound to the MySink.__init__ context and delete res_bound.
+    # The MySink.__init__ context stays alive until the end of the program, so
+    # if res_bound were to still point to our result, it would contribute an
+    # unexpected reference to the refcount of the result, from the point of view
+    # of the user of this function.  It would then affect destruction tests,
+    # for example, which want to test what happens when the refcount of a Python
+    # object reaches 0.
+
+    res = res_bound
+    del res_bound
+    return res
+
+# Create an empty trace class with default values.
+
+def get_default_trace_class():
+    def f(comp_self):
+        return comp_self._create_trace_class()
+
+    return run_in_component_init(f)
This page took 0.031859 seconds and 4 git commands to generate.