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>
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
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
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):
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):
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):
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(
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):
--- /dev/null
+# 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)
--- /dev/null
+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)
--- /dev/null
+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)