--- /dev/null
+# The MIT License (MIT)
+#
+# Copyright (c) 2013-2017 Jérémie Galarneau <jeremie.galarneau@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.
+
+import bt2
+import babeltrace.common as common
+import babeltrace.reader_field_definition as field_definition
+import datetime
+import collections
+
+def _create_event(event_notification, trace_handle=None, trace_collection=None):
+ event = Event.__new__(Event)
+ event._event_notification = event_notification
+ event._trace_handle = trace_handle
+ event._trace_collection = trace_collection
+ return event
+
+class Event(collections.Mapping):
+ """
+ An :class:`Event` object represents a trace event. :class:`Event`
+ objects are returned by :attr:`TraceCollection.events` and are
+ not meant to be instantiated by the user.
+
+ :class:`Event` has a :class:`dict`-like interface for accessing
+ an event's field value by field name:
+
+ .. code-block:: python
+
+ event['my_field']
+
+ If a field name exists in multiple scopes, the value of the first
+ field found is returned. The scopes are searched in the following
+ order:
+
+ 1. Event fields (:attr:`babeltrace.common.CTFScope.EVENT_FIELDS`)
+ 2. Event context (:attr:`babeltrace.common.CTFScope.EVENT_CONTEXT`)
+ 3. Stream event context (:attr:`babeltrace.common.CTFScope.STREAM_EVENT_CONTEXT`)
+ 4. Event header (:attr:`babeltrace.common.CTFScope.STREAM_EVENT_HEADER`)
+ 5. Packet context (:attr:`babeltrace.common.CTFScope.STREAM_PACKET_CONTEXT`)
+ 6. Packet header (:attr:`babeltrace.common.CTFScope.TRACE_PACKET_HEADER`)
+
+ It is still possible to obtain a field's value from a specific
+ scope using :meth:`field_with_scope`.
+
+ Field values are returned as native Python types, that is:
+
+ +-----------------------+----------------------------------+
+ | Field type | Python type |
+ +=======================+==================================+
+ | Integer | :class:`int` |
+ +-----------------------+----------------------------------+
+ | Floating point number | :class:`float` |
+ +-----------------------+----------------------------------+
+ | Enumeration | :class:`str` (enumeration label) |
+ +-----------------------+----------------------------------+
+ | String | :class:`str` |
+ +-----------------------+----------------------------------+
+ | Array | :class:`list` of native Python |
+ | | objects |
+ +-----------------------+----------------------------------+
+ | Sequence | :class:`list` of native Python |
+ | | objects |
+ +-----------------------+----------------------------------+
+ | Structure | :class:`dict` mapping field |
+ | | names to native Python objects |
+ +-----------------------+----------------------------------+
+
+ For example, printing the third element of a sequence named ``seq``
+ in a structure named ``my_struct`` of the ``event``'s field named
+ ``my_field`` is done this way:
+
+ .. code-block:: python
+
+ print(event['my_field']['my_struct']['seq'][2])
+ """
+
+ def __init__(self):
+ raise NotImplementedError("Event cannot be instantiated")
+
+ @property
+ def name(self):
+ """
+ Event name or ``None`` on error.
+ """
+
+ try:
+ return self._event_notification.event.name
+ except bt2.Error:
+ pass
+
+ def _clock_value(self):
+ cc_prio_map = self._event_notification.clock_class_priority_map
+ clock_class = cc_prio_map.highest_priority_clock_class
+ if not clock_class:
+ return
+
+ return self._event_notification.event.clock_value(clock_class)
+
+ @property
+ def cycles(self):
+ """
+ Event timestamp in cycles or -1 on error.
+ """
+
+ try:
+ clock_value = self._clock_value()
+ except bt2.Error:
+ return -1
+
+ if clock_value is not None:
+ return clock_value.cycles
+ else:
+ return -1
+
+ @property
+ def timestamp(self):
+ """
+ Event timestamp (nanoseconds since Epoch).
+ """
+
+ try:
+ clock_value = self._clock_value()
+ except bt2.Error:
+ raise RuntimeError("Failed to get event timestamp")
+
+ if clock_value is not None:
+ return clock_value.ns_from_epoch
+ else:
+ raise RuntimeError("Failed to get event timestamp")
+
+ @property
+ def datetime(self):
+ """
+ Event timestamp as a standard :class:`datetime.datetime`
+ object.
+
+ Note that the :class:`datetime.datetime` class' precision
+ is limited to microseconds, whereas :attr:`timestamp` provides
+ the event's timestamp with a nanosecond resolution.
+ """
+
+ return datetime.date.fromtimestamp(self.timestamp / 1E9)
+
+ def field_with_scope(self, field_name, scope):
+ """
+ Returns the value of a field named *field_name* within the
+ scope *scope*, or ``None`` if the field cannot be found.
+
+ *scope* must be one of :class:`babeltrace.common.CTFScope`
+ constants.
+ """
+
+ if scope not in _SCOPES:
+ raise ValueError("Invalid scope provided")
+
+ field = self._field_with_scope(field_name, scope)
+
+ if field is not None:
+ return field.value
+
+ def field_list_with_scope(self, scope):
+ """
+ Returns a list of field names in the scope *scope*.
+ """
+
+ if scope not in _SCOPES:
+ raise ValueError("Invalid scope provided")
+
+ field_names = []
+
+ for field in self._field_list_with_scope(scope):
+ field_names.append(field.name)
+
+ return field_names
+
+ @property
+ def handle(self):
+ """
+ :class:`TraceHandle` object containing this event, or ``None``
+ on error.
+ """
+
+ try:
+ return self._trace_handle
+ except AttributeError:
+ return None
+
+ @property
+ def trace_collection(self):
+ """
+ :class:`TraceCollection` object containing this event, or
+ ``None`` on error.
+ """
+
+ try:
+ return self._trace_collection
+ except AttributeError:
+ return
+
+ def __getitem__(self, field_name):
+ field = self._field(field_name)
+ if field is None:
+ raise KeyError(field_name)
+ return field.value
+
+ def __iter__(self):
+ for key in self.keys():
+ yield key
+
+ def __len__(self):
+ count = 0
+ for scope in _SCOPES:
+ scope_field = self._get_scope_field(scope)
+ if scope_field is not None and isinstance(scope_field,
+ bt2._StructureField):
+ count += len(scope_field)
+ return count
+
+ def __contains__(self, field_name):
+ return self._field(field_name) is not None
+
+ def keys(self):
+ """
+ Returns the list of field names.
+
+ Note: field names are unique within the returned list, although
+ a field name could exist in multiple scopes. Use
+ :meth:`field_list_with_scope` to obtain the list of field names
+ of a given scope.
+ """
+
+ field_names = set()
+
+ for scope in _SCOPES:
+ for name in self.field_list_with_scope(scope):
+ field_names.add(name)
+
+ return list(field_names)
+
+ def get(self, field_name, default=None):
+ """
+ Returns the value of the field named *field_name*, or *default*
+ when not found.
+
+ See :class:`Event` note about how fields are retrieved by
+ name when multiple fields share the same name in different
+ scopes.
+ """
+
+ field = self._field(field_name)
+
+ if field is None:
+ return default
+
+ return field.value
+
+ def items(self):
+ """
+ Generates pairs of (field name, field value).
+
+ This method iterates :meth:`keys` to find field names, which
+ means some fields could be unavailable if other fields share
+ their names in scopes with higher priorities.
+ """
+
+ for field in self.keys():
+ yield (field, self[field])
+
+ def _get_scope_field(self, scope):
+ try:
+ event = self._event_notification.event
+ if scope is common.CTFScope.EVENT_FIELDS:
+ return event.payload_field
+
+ if scope is common.CTFScope.EVENT_CONTEXT:
+ return event.context_field
+
+ if scope is common.CTFScope.STREAM_EVENT_CONTEXT:
+ return event.stream_event_context_field
+
+ if scope is common.CTFScope.STREAM_EVENT_HEADER:
+ return event.header_field
+
+ if scope is common.CTFScope.STREAM_PACKET_CONTEXT:
+ return event.packet.context_field
+
+ if scope is common.CTFScope.TRACE_PACKET_HEADER:
+ return event.packet.header_field
+ except bt2.Error:
+ return
+
+ raise ValueError("Invalid scope provided")
+
+ def _field_with_scope(self, field_name, scope):
+ scope_field = self._get_scope_field(scope)
+ if scope_field is not None:
+ try:
+ bt2_field = scope_field[field_name]
+ if bt2_field is not None:
+ return field_definition._Definition(scope, bt2_field,
+ field_name)
+ except (KeyError, bt2.Error):
+ return None
+
+ def _field(self, field_name):
+ for scope in _SCOPES:
+ field = self._field_with_scope(field_name, scope)
+ if field is not None:
+ return field
+
+ def _field_list_with_scope(self, scope):
+ fields = []
+ scope_field = self._get_scope_field(scope)
+
+ if scope_field is None or not isinstance(scope_field,
+ bt2._StructureField):
+ return fields
+
+ for name, field in scope_field.items():
+ fields.append(field_definition._Definition(scope, field, name))
+
+ return fields
+
+
+# Priority of the scopes when searching for event fields
+_SCOPES = [
+ common.CTFScope.EVENT_FIELDS,
+ common.CTFScope.EVENT_CONTEXT,
+ common.CTFScope.STREAM_EVENT_CONTEXT,
+ common.CTFScope.STREAM_EVENT_HEADER,
+ common.CTFScope.STREAM_PACKET_CONTEXT,
+ common.CTFScope.TRACE_PACKET_HEADER
+]