From ba64dfcccb1f1bd7a259dc5d563ba422b8375582 Mon Sep 17 00:00:00 2001 From: Simon Marchi Date: Thu, 26 Mar 2020 16:33:13 -0400 Subject: [PATCH] Add initial Python bindings documentation This initial documentation contains a home page, an installation page, and a few examples to understand how the `bt2` package works. Still missing: how exactly the bindings wrap libbabeltrace2 (wraping rules, exceptions, etc.). Changes: `README.adoc`: Specify that you need Sphinx to build the Python bindings documentation. `configure.ac` and `m4/check_sphinx.m4`: Add `--enable-python-bindings-doc` which requires `--enable-python-bindings`. This is because the Sphinx configuration file actually imports the `bt2` package to get the version (and, eventually, for Sphinx's autodoc to find docstrings within the `bt2` modules). `doc/bindings/python/source`: The actual documentation's contents and configuration. `doc/bindings/python/ext/bt2sphinxurl.py`: A Sphinx extension to add Babeltrace 2 manual page and other links of which the URL includes the project's version. Signed-off-by: Simon Marchi Signed-off-by: Philippe Proulx Change-Id: I4811336d567ff379cbe9e789099af8d6c7661a62 Reviewed-on: https://review.lttng.org/c/babeltrace/+/3278 Tested-by: jenkins --- README.adoc | 7 +- configure.ac | 26 + doc/Makefile.am | 2 +- doc/bindings/Makefile.am | 5 + doc/bindings/python/Makefile.am | 33 + doc/bindings/python/ext/bt2sphinxurl.py | 109 +++ doc/bindings/python/source/common.rst | 5 + doc/bindings/python/source/conf.py | 22 + doc/bindings/python/source/examples.rst | 910 ++++++++++++++++++ .../source/images/basic-convert-graph.png | Bin 0 -> 26459 bytes doc/bindings/python/source/index.rst | 104 ++ doc/bindings/python/source/installation.rst | 35 + m4/check_sphinx.m4 | 32 + 13 files changed, 1288 insertions(+), 2 deletions(-) create mode 100644 doc/bindings/Makefile.am create mode 100644 doc/bindings/python/Makefile.am create mode 100644 doc/bindings/python/ext/bt2sphinxurl.py create mode 100644 doc/bindings/python/source/common.rst create mode 100644 doc/bindings/python/source/conf.py create mode 100644 doc/bindings/python/source/examples.rst create mode 100644 doc/bindings/python/source/images/basic-convert-graph.png create mode 100644 doc/bindings/python/source/index.rst create mode 100644 doc/bindings/python/source/installation.rst create mode 100644 m4/check_sphinx.m4 diff --git a/README.adoc b/README.adoc index ef2b70c7..d89390ee 100644 --- a/README.adoc +++ b/README.adoc @@ -1,7 +1,7 @@ // Render with Asciidoctor = Babeltrace -15 October 2019 +13 April 2020 :btversion: 2.0 :bt2: Babeltrace{nbsp}2 @@ -98,6 +98,11 @@ _**If you need the {bt2} manual pages**_:: * https://www.methods.co.nz/asciidoc/[Asciidoc]{nbsp}≥{nbsp}8.6.8 * https://pagure.io/xmlto[xmlto]{nbsp}≥{nbsp}0.0.25 +_**If you need the `bt2` Python bindings documentation**_:: + * https://www.sphinx-doc.org/[Sphinx]{nbsp}≥{nbsp}1.3 for + Python{nbsp}3 + (Debian/Ubuntu/Fedora: `python3-sphinx`) + === Procedure diff --git a/configure.ac b/configure.ac index 8ab012e0..e52538bd 100644 --- a/configure.ac +++ b/configure.ac @@ -350,6 +350,14 @@ AC_ARG_ENABLE([python-bindings], [enable_python_bindings=unspecified] ) +# Python bindings documentation +# Disabled by default +AC_ARG_ENABLE([python-bindings-doc], + [AC_HELP_STRING([--enable-python-bindings-doc], [build the Python bindings documentation])], + [], dnl AC_ARG_ENABLE will fill enable_python_bindings_doc with the user choice + [enable_python_bindings_doc=no] +) + # Python plugins # Disabled by default AC_ARG_ENABLE([python-plugins], @@ -397,6 +405,7 @@ AC_ARG_ENABLE([man-pages], # Set automake variables for optionnal feature conditionnals in Makefile.am AM_CONDITIONAL([ENABLE_PYTHON_BINDINGS], [test "x$enable_python_bindings" = xyes]) +AM_CONDITIONAL([ENABLE_PYTHON_BINDINGS_DOC], [test "x$enable_python_bindings_doc" = xyes]) AM_CONDITIONAL([ENABLE_PYTHON_PLUGINS], [test "x$enable_python_plugins" = xyes]) AM_CONDITIONAL([ENABLE_DEBUG_INFO], [test "x$enable_debug_info" = xyes]) AM_CONDITIONAL([ENABLE_API_DOC], [test "x$enable_api_doc" = xyes]) @@ -527,6 +536,19 @@ AS_IF([test "x$enable_python_bindings" = xyes || test "x$enable_python_plugins" ]) ]) +AS_IF([test "x$enable_python_bindings_doc" = xyes], + [ + AM_CHECK_PYTHON_SPHINX([PYTHON]) + AS_IF([test "x$PYTHON_SPHINX_EXISTS" = xno], [ + AC_MSG_ERROR([The Sphinx package for Python 3 is required to build the Python bindings documentation]) + ]) + + AS_IF([test "x$enable_python_bindings" != xyes], [ + AC_MSG_ERROR([The Python bindings are required to build their documentation]) + ]) + ] +) + AS_IF([test "x$enable_debug_info" = xyes], [ # Check if libelf and libdw are present @@ -732,6 +754,8 @@ AC_CONFIG_FILES([ doc/api/Makefile doc/api/libbabeltrace2/Doxyfile doc/api/libbabeltrace2/Makefile + doc/bindings/Makefile + doc/bindings/python/Makefile doc/contributing-images/Makefile doc/Makefile doc/man/asciidoc-attrs.conf @@ -917,6 +941,8 @@ m4_popdef([build_man_pages_msg]) test "x$enable_api_doc" = "xyes" && value=1 || value=0 PPRINT_PROP_BOOL_CUSTOM([HTML API documentation], $value, [To build documentation, use --enable-api-doc]) +test "x$enable_python_bindings_doc" = "xyes" && value=1 || value=0 +PPRINT_PROP_BOOL_CUSTOM([Python bindings documentation], $value, [To build the Python bindings documentation, use --enable-python-bindings-doc]) AS_ECHO PPRINT_SUBTITLE([Logging]) diff --git a/doc/Makefile.am b/doc/Makefile.am index d5215a9c..7047f0e5 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -1,6 +1,6 @@ # SPDX-License-Identifier: MIT -SUBDIRS = contributing-images man +SUBDIRS = contributing-images man bindings if ENABLE_API_DOC SUBDIRS += api diff --git a/doc/bindings/Makefile.am b/doc/bindings/Makefile.am new file mode 100644 index 00000000..a9d7ab1b --- /dev/null +++ b/doc/bindings/Makefile.am @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: MIT + +if ENABLE_PYTHON_BINDINGS_DOC +SUBDIRS = python +endif diff --git a/doc/bindings/python/Makefile.am b/doc/bindings/python/Makefile.am new file mode 100644 index 00000000..f5d8490a --- /dev/null +++ b/doc/bindings/python/Makefile.am @@ -0,0 +1,33 @@ +# SPDX-License-Identifier: MIT + +SPHINX_SOURCE_DIR = $(srcdir)/source +SPHINX_EXT_DIR = $(srcdir)/ext +SPHINX_BUILD_DIR = $(builddir)/build +SPHINX_BUILD_HTML_DIR = $(SPHINX_BUILD_DIR)/html +SPHINX_HTML_TARGET = $(SPHINX_BUILD_HTML_DIR)/index.html + +EXTRA_DIST = $(SPHINX_SOURCE_DIR) $(SPHINX_EXT_DIR) + +all-local: $(SPHINX_HTML_TARGET) + +PYTHON_BT2_BUILD_LIB_DIR = $(abs_top_builddir)/src/bindings/python/bt2/build/build_lib +PP = $(PYTHON_BT2_BUILD_LIB_DIR) +LLP = $(abs_top_builddir)/src/lib/.libs + +# `PATH` is used as a replacement for `LD_LIBRARY_PATH` on Windows +# builds (Cygwin, MinGW). +# +# `DYLD_LIBRARY_PATH` is used a replacement for `LD_LIBRARY_PATH` on +# macOS builds. +SPHINXBUILD = PATH="$(LLP):$$PATH" PYTHONPATH="$(PP):$(SPHINX_EXT_DIR)" LD_LIBRARY_PATH="$(LLP)" DYLD_LIBRARY_PATH="$(LLP)" $(PYTHON) -m sphinx +SPHINX_SRC = \ + $(SPHINX_SOURCE_DIR)/common.rst \ + $(SPHINX_SOURCE_DIR)/index.rst \ + $(SPHINX_SOURCE_DIR)/installation.rst \ + $(SPHINX_SOURCE_DIR)/examples.rst + +$(SPHINX_HTML_TARGET): $(SPHINX_SRC) + $(SPHINXBUILD) -b html -E $(SPHINX_SOURCE_DIR) $(SPHINX_BUILD_HTML_DIR) + +clean-local: + rm -rf $(SPHINX_BUILD_DIR) diff --git a/doc/bindings/python/ext/bt2sphinxurl.py b/doc/bindings/python/ext/bt2sphinxurl.py new file mode 100644 index 00000000..b3e2608a --- /dev/null +++ b/doc/bindings/python/ext/bt2sphinxurl.py @@ -0,0 +1,109 @@ +# SPDX-License-Identifier: MIT +# +# Copyright (c) 2020 Philippe Proulx +# +# This file is a Sphinx extension which adds the following roles: +# +# `bt2man`: +# A typical manual page reference, like `grep(1)`. +# +# Example: +# +# :bt2man:`grep(1)` +# +# This role creates a simple inline literal node with the role's +# text if it's not a Babeltrace 2 manual page reference, or an +# external link to the corresponding online manual page (on +# `babeltrace.org`) with the appropriate project's version +# (`version` configuration entry) otherwise. +# +# `bt2link`: +# An external link with an URL in which a specific placeholder is +# replaced with the project's version. +# +# The role's text follows the typical external link format, for +# example: +# +# Link text +# +# Any `@ver@` in the URL is replaced with the project's version +# (`version` configuration entry). +# +# Example: +# +# :bt2link:`libbabeltrace2 ` + +import docutils +import docutils.utils +import docutils.nodes +import re +import functools + + +def _bt2man_role( + bt2_version, name, rawtext, text, lineno, inliner, options=None, content=None +): + # match a manual page reference + m = re.match(r'^([a-zA-Z0-9_.:-]+)\(([a-zA-Z0-9]+)\)$', text) + + if not m: + msg = 'Cannot parse manual page reference `{}`'.format(text) + inliner.reporter.severe(msg, line=lineno) + return [inliner.problematic(rawtext, rawtext, msg)], [msg] + + # matched manual page and volume + page = m.group(1) + vol = m.group(2) + + # create nodes: `ret_node` is the node to return + page_node = docutils.nodes.strong(rawtext, page) + vol_node = docutils.nodes.inline(rawtext, '({})'.format(vol)) + man_node = docutils.nodes.inline(rawtext, '', page_node, vol_node) + ret_node = docutils.nodes.literal(rawtext, '', man_node) + + if page.startswith('babeltrace2'): + # Babeltrace 2 manual page: wrap `ret_node` with an external + # link node + url_tmpl = 'https://babeltrace.org/docs/v{ver}/man{vol}/{page}.{vol}/' + url = url_tmpl.format(ver=bt2_version, vol=vol, page=page) + ret_node = docutils.nodes.reference( + rawtext, '', ret_node, internal=False, refuri=url + ) + + return [ret_node], [] + + +def _bt2link_role( + bt2_version, name, rawtext, text, lineno, inliner, options=None, content=None +): + # match link text and URL + m = re.match(r'^([^<]+) <([^>]+)>$', text) + + if not m: + msg = 'Cannot parse link template `{}`'.format(text) + inliner.reporter.severe(msg, line=lineno) + return [inliner.problematic(rawtext, rawtext, msg)], [msg] + + link_text = m.group(1) + + # replace `@ver@` with the project's version + url = m.group(2).replace('@ver@', bt2_version) + + # create and return an external link node + node = docutils.nodes.reference(rawtext, link_text, internal=False, refuri=url) + return [node], [] + + +def _add_roles(app): + # add the extension's roles; the role functions above expect the + # project's version as their first parameter + app.add_role('bt2man', functools.partial(_bt2man_role, app.config.version)) + app.add_role('bt2link', functools.partial(_bt2link_role, app.config.version)) + + +def setup(app): + app.connect('builder-inited', _add_roles) + return { + 'version': app.config.version, + 'parallel_read_safe': True, + } diff --git a/doc/bindings/python/source/common.rst b/doc/bindings/python/source/common.rst new file mode 100644 index 00000000..812127ab --- /dev/null +++ b/doc/bindings/python/source/common.rst @@ -0,0 +1,5 @@ +.. non-breaking space substitution + (see ): + +.. |~| unicode:: U+00A0 + :trim: diff --git a/doc/bindings/python/source/conf.py b/doc/bindings/python/source/conf.py new file mode 100644 index 00000000..859ae57a --- /dev/null +++ b/doc/bindings/python/source/conf.py @@ -0,0 +1,22 @@ +# SPDX-License-Identifier: MIT +# +# Copyright (c) 2020 Philippe Proulx + +import bt2 +import re + +# project +project = 'Babeltrace 2 Python bindings' +copyright = '2020, EfficiOS, Inc' +author = 'EfficiOS, Inc' +release = bt2.__version__ +version = re.match(r'^\d+\.\d+', release).group(0) + +# index +master_doc = 'index' + +# extensions +extensions = ['bt2sphinxurl'] + +# theme +html_theme = 'alabaster' diff --git a/doc/bindings/python/source/examples.rst b/doc/bindings/python/source/examples.rst new file mode 100644 index 00000000..21e5f438 --- /dev/null +++ b/doc/bindings/python/source/examples.rst @@ -0,0 +1,910 @@ +.. include:: common.rst + +.. _examples: + +Examples +======== +This section contains a few short and straightforward examples which +show how to use the Babeltrace |~| 2 Python bindings. + +The :mod:`bt2` package provides the Babeltrace |~| 2 Python bindings. +Note that the :mod:`babeltrace` package is part of the Babeltrace |~| 1 +project: it's somewhat out-of-date and not compatible with the +:mod:`bt2` package. + +Assume that all the examples below are named :file:`example.py`. + +.. _examples_tcmi: + +Iterate trace events +-------------------- +The most convenient and high-level way to iterate the events of one or +more traces is with a :class:`bt2.TraceCollectionMessageIterator` +object. + +A :class:`bt2.TraceCollectionMessageIterator` object roughly offers the +same features as the ``convert`` command of the :command:`babeltrace2` +command-line program (see the :bt2man:`babeltrace2-convert(1)` manual +page), but in a programmatic, Pythonic way. + +As of Babeltrace |~| |version|, the trace collection message iterator +class is a Python bindings-only feature: the Python code uses +libbabeltrace2 internally, but the latter does not offer this utility as +such. + +The :class:`bt2.TraceCollectionMessageIterator` interface features: + +* **Automatic source component (trace format) discovery**. + + ``convert`` command equivalent example: + + .. code-block:: text + + $ babeltrace2 /path/to/my/trace + +* **Explicit component class instantiation**. + + ``convert`` command equivalent example: + + .. code-block:: text + + $ babeltrace2 --component=source.my.format + +* **Passing initialization parameters to both auto-discovered and + explicitly created components**. + + ``convert`` command equivalent example: + + .. code-block:: text + + $ babeltrace2 /path/to/my/trace --params=detailed=no \ + --component=source.ctf.fs \ + --params='inputs=["/path/to/my/trace"]' + +* **Trace event muxing**. + + The message iterator muxes (combines) the events from multiple + compatible streams into a single, time-sorted sequence of events. + + .. code-block:: text + + $ babeltrace2 /path/to/trace1 /path/to/trace2 /path/to/trace3 + +* **Stream intersection mode**. + + ``convert`` command equivalent example: + + .. code-block:: text + + $ babeltrace2 /path/to/my/trace --stream-intersection + +* **Stream trimming with beginning and/or end times**. + + ``convert`` command equivalent example: + + .. code-block:: text + + $ babeltrace2 /path/to/my/trace --begin=22:14:38 --end=22:15:07 + +While the :command:`babeltrace2 convert` command creates a ``sink.text.pretty`` +component class (by default) to pretty-print events as plain text lines, +a :class:`bt2.TraceCollectionMessageIterator` object is a Python +iterator which makes its user a message consumer (there's no sink +component):: + + import bt2 + + for msg in bt2.TraceCollectionMessageIterator('/path/to/trace'): + if type(msg) is bt2._EventMessageConst: + print(msg.event.name) + +.. _examples_tcmi_autodisc: + +Discover traces +~~~~~~~~~~~~~~~ +Pass one or more file paths, directory paths, or other strings when you +build a :class:`bt2.TraceCollectionMessageIterator` object to let it +automatically determine which source components to create for you. + +If you pass a directory path, the message iterator traverses the +directory recursively to find traces, automatically selecting the +appropriate source component classes to instantiate. + +The :class:`bt2.TraceCollectionMessageIterator` object and the +:command:`babeltrace2 convert` CLI command share the same automatic +component discovery algorithm. See the +:bt2link:`Create implicit components from non-option arguments ` +section of the :bt2man:`babeltrace2-convert(1)` manual page for more +details. + +The following example shows how to use a +:class:`bt2.TraceCollectionMessageIterator` object to automatically +discover one or more traces from a single path (file or directory). For +each trace event, the example prints its name:: + + import bt2 + import sys + + # Get the trace path from the first command-line argument. + path = sys.argv[1] + + # Create a trace collection message iterator with this path. + msg_it = bt2.TraceCollectionMessageIterator(path) + + # Iterate the trace messages. + for msg in msg_it: + # `bt2._EventMessageConst` is the Python type of an event message. + if type(msg) is bt2._EventMessageConst: + # An event message holds a trace event. + event = msg.event + + # Print event's name. + print(event.name) + +Run this example: + +.. code-block:: text + + $ python3 example.py /path/to/one/or/more/traces + +Output example: + +.. code-block:: text + + kmem_kmalloc + kmem_kfree + kmem_cache_alloc_node + block_getrq + kmem_kmalloc + block_plug + kmem_kfree + block_rq_insert + kmem_kmalloc + kmem_kfree + kmem_kmalloc + kmem_kfree + +The example above is simplistic; it does not catch the exceptions that +some statements can raise: + +* ``bt2.TraceCollectionMessageIterator(path)`` raises an exception if + it cannot find any trace. + +* Each iteration of the loop, or, more precisely, the + :meth:`bt2.TraceCollectionMessageIterator.__next__` method, raises + an exception if there's any error during the iteration process. + + For example, an internal source component message iterator can fail + when trying to decode a malformed trace file. + +.. _examples_tcmi_expl: + +Create explicit source components +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +If `automatic source component discovery <#examples-tcmi-autodisc>`_ +doesn't work for you (for example, because the source component class +you actually need to instantiate doesn't offer the +``babeltrace.support-info`` query object), create explicit source +components when you build a :class:`bt2.TraceCollectionMessageIterator` +object. + +The following example builds a trace collection message iterator to +explicitly instantiate a ``source.ctf.fs`` component class (found in the +``ctf`` plugin). Again, for each trace event, the example prints its +name:: + + import bt2 + import sys + + # Find the `ctf` plugin (shipped with Babeltrace 2). + ctf_plugin = bt2.find_plugin('ctf') + + # Get the `source.ctf.fs` component class from the plugin. + fs_cc = ctf_plugin.source_component_classes['fs'] + + # Create a trace collection message iterator, instantiating a single + # `source.ctf.fs` component class with the `inputs` initialization + # parameter set to open a single CTF trace. + msg_it = bt2.TraceCollectionMessageIterator(bt2.ComponentSpec(fs_cc, { + # Get the CTF trace path from the first command-line argument. + 'inputs': [sys.argv[1]], + })) + + # Iterate the trace messages. + for msg in msg_it: + # `bt2._EventMessageConst` is the Python type of an event message. + if type(msg) is bt2._EventMessageConst: + # Print event's name. + print(msg.event.name) + +Run this example: + +.. code-block:: text + + $ python3 example.py /path/to/ctf/trace + +Output example: + +.. code-block:: text + + kmem_kmalloc + kmem_kfree + kmem_cache_alloc_node + block_getrq + kmem_kmalloc + block_plug + kmem_kfree + block_rq_insert + kmem_kmalloc + kmem_kfree + kmem_kmalloc + kmem_kfree + +The example above looks similar to the previous one using +`automatic source component discovery <#examples-tcmi-autodisc>`_, +but there are notable differences: + +* A ``source.ctf.fs`` component expects to receive the path to a + *single* `CTF `_ trace (a directory + containing a file named ``metadata``). + + Unlike the previous example, you must pass the exact + :abbr:`CTF (Common Trace Format)` trace directory path, *not* a + parent directory path. + +* Unlike the previous example, the example above can only read a single + trace. + + If you want to read multiple :abbr:`CTF (Common Trace Format)` traces + using explicit component class instantiation with a single trace + collection message iterator, you must create one ``source.ctf.fs`` + component per trace. + +Note that the :class:`bt2.ComponentSpec` class offers the +:meth:`from_named_plugin_and_component_class` convenience static method +which finds the plugin and component class for you. You could therefore +rewrite the trace collection message iterator creation part of the +example above as:: + + # Create a trace collection message iterator, instantiating a single + # `source.ctf.fs` component class with the `inputs` initialization + # parameter set to open a single CTF trace. + msg_it = bt2.TraceCollectionMessageIterator( + bt2.ComponentSpec.from_named_plugin_and_component_class('ctf', 'fs', { + # Get the CTF trace path from the first command-line argument. + 'inputs': [sys.argv[1]], + }) + ) + +.. _examples_tcmi_ev_field: + +Get a specific event field's value +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +The :ref:`examples_tcmi_autodisc` and :ref:`examples_tcmi_expl` examples +show that a :class:`bt2.TraceCollectionMessageIterator` iterates the +time-sorted *messages* of one or more traces. + +One specific type of message is :class:`bt2._EventMessageConst`, which +holds a trace event object. + +.. note:: + + Everything you can find in the :mod:`bt2` package is publicly + accessible. + + Names which start with ``_`` (underscore), like + :class:`bt2._EventMessageConst`, indicate that you can't + *instantiate* such a class (you cannot call the class). However, the + type itself remains public so that you can use its name to check an + object's type: + + .. code-block:: python + + if type(msg) is bt2._EventMessageConst: + # ... + + .. code-block:: python + + if isinstance(field, bt2._IntegerFieldConst): + # ... + +Access an event object's field by using the event as a simple mapping +(like a read-only :class:`dict`), where keys are field names. The field +can belong to any part of the event (contexts or payload) and to its +packet's context, if any:: + + import bt2 + import sys + + # Create a trace collection message iterator from the first + # command-line argument. + msg_it = bt2.TraceCollectionMessageIterator(sys.argv[1]) + + # Iterate the trace messages. + for msg in msg_it: + # `bt2._EventMessageConst` is the Python type of an event message. + # Only keep such messages. + if type(msg) is not bt2._EventMessageConst: + continue + + # An event message holds a trace event. + event = msg.event + + # Only check `sched_switch` events. + if event.name != 'sched_switch': + continue + + # In an LTTng trace, the `cpu_id` field is a packet context field. + # The mapping interface of `event` can still find it. + cpu_id = event['cpu_id'] + + # Previous and next process short names are found in the event's + # `prev_comm` and `next_comm` fields. + prev_comm = event['prev_comm'] + next_comm = event['next_comm'] + + # Print line, using field values. + msg = 'CPU {}: Switching process `{}` → `{}`' + print(msg.format(cpu_id, prev_comm, next_comm)) + +The example above assumes that the traces to open are +`LTTng `_ Linux kernel traces. + +Run this example: + +.. code-block:: text + + $ python3 example.py /path/to/one/or/more/lttng/traces + +Output example: + +.. code-block:: text + + CPU 2: Switching process `Timer` → `swapper/2` + CPU 0: Switching process `swapper/0` → `firefox` + CPU 0: Switching process `firefox` → `swapper/0` + CPU 0: Switching process `swapper/0` → `rcu_preempt` + CPU 0: Switching process `rcu_preempt` → `swapper/0` + CPU 3: Switching process `swapper/3` → `alsa-sink-ALC26` + CPU 2: Switching process `swapper/2` → `Timer` + CPU 2: Switching process `Timer` → `swapper/2` + CPU 2: Switching process `swapper/2` → `pulseaudio` + CPU 0: Switching process `swapper/0` → `firefox` + CPU 1: Switching process `swapper/1` → `threaded-ml` + CPU 2: Switching process `pulseaudio` → `Timer` + +If you need to access a specific field, use: + +Event payload + :attr:`bt2._EventConst.payload_field` property. + +Event specific context + :attr:`bt2._EventConst.specific_context_field` property. + +Event common context + :attr:`bt2._EventConst.common_context_field` property. + +Packet context + :attr:`bt2._PacketConst.context_field` property. + +Use Python's ``in`` operator to verify if: + +A specific "root" field (in the list above) contains a given field by name + .. code-block:: python + + if 'next_comm' in event.payload_field: + # ... + +Any of the root fields contains a given field by name + .. code-block:: python + + if 'next_comm' in event: + # ... + +The following example iterates the events of a given trace, printing the +value of the ``fd`` payload field if it's available:: + + import bt2 + import sys + + # Create a trace collection message iterator from the first command-line + # argument. + msg_it = bt2.TraceCollectionMessageIterator(sys.argv[1]) + + # Iterate the trace messages. + for msg in msg_it: + # `bt2._EventMessageConst` is the Python type of an event message. + if type(msg) is bt2._EventMessageConst: + # Check if the `fd` event payload field exists. + if 'fd' in msg.event.payload_field: + # Print the `fd` event payload field's value. + print(msg.event.payload_field['fd']) + +Output example: + +.. code-block:: text + + 14 + 15 + 16 + 19 + 30 + 31 + 33 + 42 + 0 + 1 + 2 + 3 + +.. _examples_tcmi_ev_time: + +Get an event's time +~~~~~~~~~~~~~~~~~~~ +The time, or timestamp, of an event object belongs to its message as +a *default clock snapshot*. + +An event's clock snapshot is a *snapshot* (an immutable value) of the +value of the event's stream's clock when the event occurred. As of +Babeltrace |~| |version|, a stream can only have one clock: its default +clock. + +Use the :attr:`default_clock_snapshot` property of an event message +to get its default clock snapshot. A clock snapshot object offers, +amongst other things, the following properties: + +:attr:`value` (:class:`int`) + Value of the clock snapshot in clock cycles. + + A stream clock can have any frequency (Hz). + +:attr:`ns_from_origin` (:class:`int`) + Number of nanoseconds from the stream clock's origin (often the Unix + epoch). + +The following example prints, for each event, its name, its date/time, +and the difference, in seconds, since the previous event's time (if +any):: + + import bt2 + import sys + import datetime + + # Create a trace collection message iterator from the first command-line + # argument. + msg_it = bt2.TraceCollectionMessageIterator(sys.argv[1]) + + # Last event's time (ns from origin). + last_event_ns_from_origin = None + + # Iterate the trace messages. + for msg in msg_it: + # `bt2._EventMessageConst` is the Python type of an event message. + if type(msg) is bt2._EventMessageConst: + # Get event message's default clock snapshot's ns from origin + # value. + ns_from_origin = msg.default_clock_snapshot.ns_from_origin + + # Compute the time difference since the last event message. + diff_s = 0 + + if last_event_ns_from_origin is not None: + diff_s = (ns_from_origin - last_event_ns_from_origin) / 1e9 + + # Create a `datetime.datetime` object from `ns_from_origin` for + # presentation. Note that such an object is less accurate than + # `ns_from_origin` as it holds microseconds, not nanoseconds. + dt = datetime.datetime.fromtimestamp(ns_from_origin / 1e9) + + # Print line. + fmt = '{} (+{:.6f} s): {}' + print(fmt.format(dt, diff_s, msg.event.name)) + + # Update last event's time. + last_event_ns_from_origin = ns_from_origin + +Run this example: + +.. code-block:: text + + $ python3 example.py /path/to/one/or/more/traces + +Output example: + +.. code-block:: text + + 2015-09-09 22:40:41.551451 (+0.000004 s): lttng_ust_statedump:end + 2015-09-09 22:40:43.003397 (+1.451946 s): lttng_ust_dl:dlopen + 2015-09-09 22:40:43.003412 (+0.000015 s): lttng_ust_dl:build_id + 2015-09-09 22:40:43.003861 (+0.000449 s): lttng_ust_dl:dlopen + 2015-09-09 22:40:43.003865 (+0.000004 s): lttng_ust_dl:build_id + 2015-09-09 22:40:43.003879 (+0.000014 s): my_provider:my_first_tracepoint + 2015-09-09 22:40:43.003895 (+0.000016 s): my_provider:my_first_tracepoint + 2015-09-09 22:40:43.003898 (+0.000003 s): my_provider:my_other_tracepoint + 2015-09-09 22:40:43.003922 (+0.000023 s): lttng_ust_dl:dlclose + +Bonus: Print top 5 running processes using LTTng +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +As :ref:`examples_tcmi_ev_field` shows, a +:class:`bt2.TraceCollectionMessageIterator` can read +`LTTng `_ traces. + +The following example is similar to :ref:`examples_tcmi_ev_time`: it +reads a whole LTTng Linux kernel trace, but instead of printing the time +difference for each event, it accumulates them to print the short names +of the top |~| 5 running processes on CPU |~| 0 during the whole trace. + +.. code-block:: python + + import bt2 + import sys + import collections + + # Create a trace collection message iterator from the first command-line + # argument. + msg_it = bt2.TraceCollectionMessageIterator(sys.argv[1]) + + # This counter dictionary will hold execution times: + # + # Task command name -> Total execution time (ns) + exec_times = collections.Counter() + + # This holds the last `sched_switch` event time. + last_ns_from_origin = None + + for msg in msg_it: + # `bt2._EventMessageConst` is the Python type of an event message. + # Only keep such messages. + if type(msg) is not bt2._EventMessageConst: + continue + + # An event message holds a trace event. + event = msg.event + + # Only check `sched_switch` events. + if event.name != 'sched_switch': + continue + + # Keep only events which occurred on CPU 0. + if event['cpu_id'] != 0: + continue + + # Get event message's default clock snapshot's ns from origin value. + ns_from_origin = msg.default_clock_snapshot.ns_from_origin + + if last_ns_from_origin is None: + # We start here. + last_ns_from_origin = ns_from_origin + + # Previous process's short name. + prev_comm = str(event['prev_comm']) + + # Initialize an entry in our dictionary if not done yet. + if prev_comm not in exec_times: + exec_times[prev_comm] = 0 + + # Compute previous process's execution time. + diff_ns = ns_from_origin - last_ns_from_origin + + # Update execution time of this command. + exec_times[prev_comm] += diff_ns + + # Update last event's time. + last_ns_from_origin = ns_from_origin + + # Print top 5. + for comm, ns in exec_times.most_common(5): + print('{:20}{} s'.format(comm, ns / 1e9)) + +Run this example: + +.. code-block:: text + + $ python3 example.py /path/to/lttng/trace + +Output example: + +.. code-block:: text + + swapper/0 326.294314471 s + chromium 2.500456202 s + Xorg.bin 0.546656895 s + threaded-ml 0.545098185 s + pulseaudio 0.53677713 s + +Note that ``swapper/0`` is the "idle" process of CPU |~| 0 on Linux; +since we weren't using the CPU that much when tracing, its first +position in the list makes sense. + +Inspect event classes +~~~~~~~~~~~~~~~~~~~~~ +Each event stream is a *stream class* instance. + +A stream class contains *event classes*. A stream class's event classes +describe all the possible events you can find in its instances. Stream +classes and event classes form the *metadata* of streams and events. + +The following example shows how to list all the event classes of a +stream class. For each event class, the example also prints the names of +its payload field class's first-level members. + +.. note:: + + As of Babeltrace |~| |version|, there's no way to access a stream class + without consuming at least one message for one of its instances + (streams). + + A source component can add new event classes to existing stream + classes during the trace processing task. Therefore, this example + only lists the initial stream class's event classes. + +.. code-block:: python + + import bt2 + import sys + + # Create a trace collection message iterator from the first command-line + # argument. + msg_it = bt2.TraceCollectionMessageIterator(sys.argv[1]) + + # Get the message iterator's first stream beginning message. + for msg in msg_it: + # `bt2._StreamBeginningMessageConst` is the Python type of a stream + # beginning message. + if type(msg) is bt2._StreamBeginningMessageConst: + break + + # A stream beginning message holds a stream. + stream = msg.stream + + # Get the stream's class. + stream_class = stream.cls + + # The stream class object offers a mapping interface (like a read-only + # `dict`), where keys are event class IDs and values are + # `bt2._EventClassConst` objects. + for event_class in stream_class.values(): + print('{}:'.format(event_class.name)) + + # The `payload_field_class` property of an event class returns a + # `bt2._StructureFieldClassConst` object. This object offers a + # mapping interface, where keys are member names and values are + # `bt2._StructureFieldClassMemberConst` objects. + for member in event_class.payload_field_class.values(): + fmt = ' {}: `{}.{}`' + print(fmt.format(member.name, bt2.__name__, + member.field_class.__class__.__name__)) + +Run this example: + +.. code-block:: text + + $ python3 example.py /path/to/trace + +Output example: + +.. code-block:: text + + sched_migrate_task: + comm: `bt2._StringFieldClassConst` + tid: `bt2._SignedIntegerFieldClassConst` + prio: `bt2._SignedIntegerFieldClassConst` + orig_cpu: `bt2._SignedIntegerFieldClassConst` + dest_cpu: `bt2._SignedIntegerFieldClassConst` + sched_switch: + prev_comm: `bt2._StringFieldClassConst` + prev_tid: `bt2._SignedIntegerFieldClassConst` + prev_prio: `bt2._SignedIntegerFieldClassConst` + prev_state: `bt2._SignedIntegerFieldClassConst` + next_comm: `bt2._StringFieldClassConst` + next_tid: `bt2._SignedIntegerFieldClassConst` + next_prio: `bt2._SignedIntegerFieldClassConst` + sched_wakeup_new: + comm: `bt2._StringFieldClassConst` + tid: `bt2._SignedIntegerFieldClassConst` + prio: `bt2._SignedIntegerFieldClassConst` + target_cpu: `bt2._SignedIntegerFieldClassConst` + +.. _examples_graph: + +Build and run a trace processing graph +-------------------------------------- +Internally, a :class:`bt2.TraceCollectionMessageIterator` object (see +:ref:`examples_tcmi`) builds a *trace processing graph*, just like the +:bt2man:`babeltrace2-convert(1)` CLI command, and then offers a +Python iterator interface on top of it. + +See the :bt2man:`babeltrace2-intro(7)` manual page to learn more about +the Babeltrace |~| 2 project and its core concepts. + +The following examples shows how to manually build and then run a trace +processing graph yourself (like the :bt2man:`babeltrace2-run(1)` CLI +command does). The general steps to do so are: + +#. Create an empty graph. + +#. Add components to the graph. + + This process is also known as *instantiating a component class* + because the graph must first create the component from its class + before adding it. + + A viable graph contains at least one source component and one sink + component. + +#. Connect component ports. + + On initialization, components add input and output ports, depending + on their type. + + You can connect component output ports to input ports within a graph. + +#. Run the graph. + + This is a blocking operation which makes each sink component consume + some messages in a round robin fashion until there are no more. + +.. code-block:: python + + import bt2 + import sys + + # Create an empty graph. + graph = bt2.Graph() + + # Add a `source.text.dmesg` component. + # + # graph.add_component() returns the created and added component. + # + # Such a component reads Linux kernel ring buffer messages (see + # `dmesg(1)`) from the standard input and creates corresponding event + # messages. See `babeltrace2-source.text.dmesg(7)`. + # + # `my source` is the unique name of this component within `graph`. + comp_cls = bt2.find_plugin('text').source_component_classes['dmesg'] + src_comp = graph.add_component(comp_cls, 'my source') + + # Add a `sink.text.pretty` component. + # + # Such a component pretty-prints event messages on the standard output + # (one message per line). See `babeltrace2-sink.text.pretty(7)`. + # + # The `babeltrace2 convert` CLI command uses a `sink.text.pretty` + # sink component by default. + comp_cls = bt2.find_plugin('text').sink_component_classes['pretty'] + sink_comp = graph.add_component(comp_cls, 'my sink') + + # Connect the `out` output port of the `source.text.dmesg` component + # to the `in` input port of the `sink.text.pretty` component. + graph.connect_ports(src_comp.output_ports['out'], + sink_comp.input_ports['in']) + + # Run the trace processing graph. + graph.run() + +Run this example: + +.. code-block:: text + + $ dmesg -t | python3 example.py + +Output example: + +.. code-block:: text + + string: { str = "ata1.00: NCQ Send/Recv Log not supported" } + string: { str = "ata1.00: ACPI cmd ef/02:00:00:00:00:a0 (SET FEATURES) succeeded" } + string: { str = "ata1.00: ACPI cmd f5/00:00:00:00:00:a0 (SECURITY FREEZE LOCK) filtered out" } + string: { str = "ata1.00: ACPI cmd ef/10:03:00:00:00:a0 (SET FEATURES) filtered out" } + string: { str = "ata1.00: NCQ Send/Recv Log not supported" } + string: { str = "ata1.00: configured for UDMA/133" } + string: { str = "ata1.00: Enabling discard_zeroes_data" } + string: { str = "OOM killer enabled." } + string: { str = "Restarting tasks ... done." } + string: { str = "PM: suspend exit" } + +Query a component class +----------------------- +Component classes, provided by plugins, can implement a method to +support *query operations*. + +A query operation is similar to a function call: the caller makes a +request (a query) with parameters and the component class's query +method returns a result object. + +The query operation feature exists so that you can benefit from a +component class's implementation to get information about a trace, a +stream, a distant server, and so on. For example, the +``source.ctf.lttng-live`` component class (see +:bt2man:`babeltrace2-source.ctf.lttng-live(7)`) offers the ``sessions`` +object to list the available +`LTTng live `_ tracing +session names and other properties. + +The semantics of the query parameters and the returned object are +completely defined by the component class implementation: the library +and its Python bindings don't enforce or suggest any layout. +The best way to know which objects you can query from a component class, +what are the expected and optional parameters, and what the returned +object contains is to read this component class's documentation. + +The following example queries the "standard" ``babeltrace.support-info`` +query object (see +:bt2man:`babeltrace2-query-babeltrace.support-info(7)`) from the +``source.ctf.fs`` component class +(see :bt2man:`babeltrace2-source.ctf.fs(7)`) and +pretty-prints the result. The ``babeltrace.support-info`` query object +indicates whether or not a given path locates a +:abbr:`CTF (Common Trace Format)` trace directory:: + + import bt2 + import sys + + # Get the `source.ctf.fs` component class from the `ctf` plugin. + comp_cls = bt2.find_plugin('ctf').source_component_classes['fs'] + + # The `babeltrace.support-info` query operation expects a `type` + # parameter (set to `directory` here) and an `input` parameter (the + # actual path or string to check, in this case the first command-line + # argument). + # + # See `babeltrace2-query-babeltrace.support-info(7)`. + params = { + 'type': 'directory', + 'input': sys.argv[1], + } + + # Create a query executor. + # + # This is the environment in which query operations happens. The + # queried component class has access to this executor, for example to + # retrieve the query operation's logging level. + query_exec = bt2.QueryExecutor(comp_cls, 'babeltrace.support-info', + params) + + # Query the component class through the query executor. + # + # This method returns the result. + result = query_exec.query() + + # Print the result. + print(result) + +As you can see, no trace processing graph is involved (like in +:ref:`examples_tcmi` and :ref:`examples_graph`): a query operation +is *not* a sequential trace processing task, but a simple, atomic +procedure call. + +Run this example: + +.. code-block:: text + + $ python3 example.py /path/to/ctf/trace + +Output example: + +.. code-block:: text + + {'group': '21c63a42-40bc-4c08-9761-3815ae01f43d', 'weight': 0.75} + +This result indicates that the component class is 75 |~| % confident that +:file:`/path/to/ctf/trace` is a CTF trace directory path. It also shows +that this specific CTF trace belongs to the +``21c63a42-40bc-4c08-9761-3815ae01f43d`` group; a single component can +handle multiple traces which belong to the same group. + +Let's try the sample example with a path that doesn't locate a CTF +trace: + +.. code-block:: text + + $ python3 example.py /etc + +Output: + +.. code-block:: text + + {'weight': 0.0} + +As expected, the zero weight indicates that ``/etc`` isn't a CTF trace +directory path. diff --git a/doc/bindings/python/source/images/basic-convert-graph.png b/doc/bindings/python/source/images/basic-convert-graph.png new file mode 100644 index 0000000000000000000000000000000000000000..a97dfcacee0b263d0cc0011387e28140bc113c26 GIT binary patch literal 26459 zcmaI7c|4Tw_di~g2u1e2key-}J7o`%Y>g%RXc#102q}?0k!>s?A-l0ATlQrvW8br9 z&%X1!Mz7cV`~G}>-^cflmIwEJUFV$ZT+egP^PD^Ik%ltqRr;$JE?gjeprY{j!i7tL z7cN|MCBy?ivEnna1pg2{SAjWQxIlaZ_va#Z-23>#1#8j=3iowhj;vOlgd1DC?~T@6 zSWLMQ$iwV?_45+rEi)~iPs+1WIzfHVD}J5YU;GE$S+DN1zn6L;DD0^EoJ8ycnf&wz zj)s)5M6sliPIlKOhq#TQ$c=_OZ<|9Lk8g0g%BehiMz}!s%<03c>YW&)s*1r8&)tcw zqQR#}TmAZmw)tN9p5x0&(z}NpF5`oTRm6n1{K(|MU*B^lEv}yb5Bvp{|L4b-P`CW% z?{|frSIQFA&Hn{9H@kL(SGOzyYr1^GQ!NA%9N zzqRvvx?~N9=)1&Q0|zX}&essCqG`s>*T+BE?9PATEp)t6UU|1F<=cZAuR~pIzD?w0 z?}5cn@nd3QIl?+P;jQgtkDZD^myDfAmJ{0*_pQM*lh5%(Z1x+M<)+ErCgd8{6Vsf% zXZERav)Ci66BT)Mw#0Kyy|B*5Z7+j0sC=+*5sRj!$Apqkqpd?WG0D!Z7c-i_UbNQN z@<7Dbma~d>&wh!C+%-usJZq7{KNQXzrQyHA~$$oLP^pVc2M&o;=() z4$F_O?ikirGa1>(^y?SyFSDF^P}W3;cD^B#E1;7j{M25nt#EBpC4K0tp@5T0eB-RJ zVpYF%!mh2M>Aa_qodUi^poUI}(1HEq=5JMNt7EkZX=$@!`B{9w_~qN)>Bpxo79tGw z$>~heW1=SrPqJBD82D!vo{TVj3*BBOtI;0Lf4ouFWb$*L_)QJ_ zsl-9$!s|~vV>GRM2#odQnExjBbuaU@sBm>^SkfYwPd%hX)8}Mn z2%B%U2>a`ZDKD&QDcrP_ii(H)#cnC))K(PJ`opGwu2ZKz!gq+F#v{5$qM_YIXYkV6 zImuDW%^|`|Ha-laKYPJo1B-p|KAMI;YK<##G|FKn;N$#*r=Q7G#;M3Youq%>H;lBR zQ4V?$pUqg45|Klj&6U`ckjQnkvsajI(vC&n^dm$1lgVL*_zaO>C8Z`0(%p{hh`6~=9o0@}%NH&Rz2=V$IyBLni(qK3}$;;89VE2zaH|4T>v zUc*(>f*ke-2!~AQi#78IOi+!;q8s1olSjwdzBN*fvb_p__>U}9j}K#*84fmFb82_fY+8xYZTiCb8PT~g-BXrW87`Sbo)>34T zKdu#61ulHb)KPtwUL(?2VpeD=C`2{WuAOsd`b&dw2F`Vx;U?`$|H>3EsMP1`q*b!d z#ot3MYIiM1e1khkw_MEd&ujZ%+fd%l`KDp+)Y23+SVN*w)TiJrxTek)zbQ?-?-#Q| zXJjG0b(0z~d-aE?!C*L~GuQj-*AnxhXN96Vkj0}9)G|AoHQ;36Wlb!~*jK-t$G?`Q zwPc*xe4s+`o$Tyo1^<^M^NQ(+AiJ!lVK?-T;^8iS9FUnp{2tYC8R8G*6;W}!U{~(g zMLPxKfDM?lPnpN-DQx-MhDABt2Z2s7#9<$Rjud8XK3Lp!2SdS)eGJ8x$JI*s)<7@h z?f%+^ox(Y`=u{m2z_jnmzBu7R4g{SYtnaptk){cpc6Rp0d~jephs>0ugWX8Vv_nG$$GG7UARR{bPlQFhgK%fPA1Dv^E489y3%`2S4Ydbnynb6UOsx+6i#u=e!u%NvsCE5 zt|DPdwH2D>Y`=M0kbESB-{;R;7Q)o|Ig_*1(PW=fckcwkoxqpJ&Q7bx^HU}#$CI&F z8uZC;`5lH~G!i9BYzOn4!^IwYGD>fiX7Z8doNQ;v?)Z933JGnto18xOEI#Se)?D1# z-<@hpHjD^U#AzH;=2@Tkp35v_g?>x(Y4z3V!Ghu)f=~A-r`p1Oj%RL4 zh}?BV$UB5S=^65yc^2!)CEeDtu=TBrPq%33)5aYx4ToOAO3^Ym+PhB#2^dqv?J^4) zzUcS-HhR@cHtp|lOOZ*fA)iN%aOVz+NCO-Dq`M`rY}Q#B9TcJDiRZ%Tb_4HnXX8|% zAK%AYY>aNVh-|W8)G4Z!H^i*S`R&A5X;wp@INAg(N|~JK=t;wMAJ>9UqbwYi7*+8}O`LDbn#smR-R6!CmR%n@l`BAa<^CU#cV!$4I42?IzWST3Iuy8i267;i z>^+-4B3A7OCBhGmUim;-h_xqG8;N92KTY%1h^jLi`9#qDY&=F%IHOSvzx8o#F#QRl z@N`;NSeyo20mFh;_k&s64t(5=0Irm?U1xrc%L)+&xqgBZf@!|4B9o#TN#w_lcb3CXW{=|HLj<*G1hW$&2525E-}4}ksdwyT z5WboIjx|_O49gRdnos*6$DNlS8@m=#XGz>q#2VV@v_UElq*tjrSe|_Jg$nT(&UKCf23qL~j`V(HOWM%U=UN~WIjM!KQ+~qB zCr-zhG>PP5!8@CKY#;TT zrX;9aLpe7*PId;%>_(feQ$g!@N(mJhe@aF9%YRY?` zK`Qc5ajp8xCaGUn4>yj&ZY(#^LgfL3jh5PZ&V-8YR{$0An3G?bZG9t}PkHu`IEAQ# zUh7XLxa?mE=IJzePF62~Q!s<{nIK(#g$}{nF7wRc ze|S8m_tn@#Fc&A84|~pbYwQoEAG@u%CNIPXO+%{TUQyH79xZ6w>{2q z6WR-!C|eiDbR@9(-n)%<{RuCCS%1}G!#$?`&tpG^kYMEwc|le|pLh2z5$u@0Wp=R} z3gM&xDx?4?WK#}%^o<-^&j+=*-IaibDMX)!qiR+YN1m%Zba0rIS%OP?@2_yKy{tXT zKUkBW$O=VjdR!%c&*tLM4^!<*mqwHwX;l5Luc@?VO?|}+UEgHBuAV>Q3UQ&jcyGO9 zcRz$V>8VSO$5zH*?)dR2*<2QlGy!R={%nuG2+^U+QGbVBs;J8}xkr82-NcbhFK6b+ zEVbT3LC?Y(<+IYC4!k&RB>&N7k%=K(o1?l9r5Dm=B39S}_h6{qN_HclNs-fmn^I`K zl&qDPEU_LRx*p{I)}L-~@ooeemt@Rtxgk1Pki&m=)!Zia;)CX^h&N++sYTLss0zcK z8MWS)@sv9XP_nPc=%xcJB^ zd(euz7CpfC-~*vdE)%N6zV9*f`Dd&sMKbOZEd@fe`3Loq=7HN>%B?|;Xv( zukk|ZQOym>U_qq+VKC;Y*N*NG3ACP!@ZG)nI~jt($6w-nedXzVzjRVRN=H)Pj7n_h zY`a0(&!HI4=Pr~p!AjGEi1aWVgRr5j4w~$M>#xJVu99+px2hTHbFpW`4P?G@Dq+O_!Z)a7WOP~u7V-P_$2j%9nl<( zkPy*DQ*+H=AL)+0A`^ZQ-f^dGZtsA0n)E0m{w{J{%0#Sv3C{O#Tpjz3^0R?josBWEdeYt@;2!=t9Z{v zXrpv)0W9i551n)@iC9{3nkN?q5dvMiWrfB{gqD~=e2lrTyulqx80K8RWy>T3!}WU# zg%T>7<>h_3T8ZJqdwLAo*DhI{XL8VbcHx0u3yOvL5;8*MK+EJfgxvkpT$$>FIj^CR z7dLUf#WK9o5J;h6MFN#{n(qK0eIQs5FL#r6JidK{9z}_lRhMXgy%1JdZH|yjLsN{Z z%B)a8)BY;Y2Biojy?)(xW~k6&x>$7Lr3HXUkGW3TSlJQ)!znUt zh_M=#z^xnf4DQb{?4PKfcbzD0rJo<(Pk7iTCTWlD?i6`Rx3$?T zV6mr~&t@Zf{cb)_{9!S(;qJxC0#d(U)}$qV;d!Hrkd?lm zKjx87VO+7>`b0*OM^Lf=(%*C3Jlx&urG9>lZsz?6r7?#A2Vo!WTHr3E!z+0Tjp6G& z&zhi4SfaPwcD;tkUCzCyZylXP&#tbH?v2gsQXFTDwp5lT4Ha&PZN2u~a1fwqHZfyPOsLAvIBW|is~f@?^a-A%VMg^cMVclW)Nx#e2EGa1QlHOK)!7sf{=%J=K77Y%>Y zFEuyX4na0qVBo@v^p5NPuVBhS;8;x|xN9a|4=mQQ|#O(xHa}Jph8!~>y!Jf`COG#D93u~ zDvoxgo2Aa9Aqn&?=6V0H`kr0X)mTB^5WK_k4Bp{KR+Q&11EQ&ab|*ab8ncgZ+8$E2 z(L-vi5(oLG<*)xEp{^x5aORF;#j9Ia(x(iwoSXTkCU#PK^9xLGfJL-2A|ygVlwn1M zYF7Wc3>Qf3aza=a6GjKrOeK|i&t>hs{XZ*pAhub0xDzKmxG%5SI_BRIXWPZ0&R-bx4T@fJe{;=XRDetyjc)4KP(o|6WmsvCQNpVDSAFuxT^5zy zxeTb!3}T9|PcW?_t5M_+ICDQA;7GWA>fq8%a1)6a>$q*MoXrQBw{x)UP~*(J)e@7; zY6KF8Y92)WM+i7+okjY?vauKKb1^2~t%f529&TSYb{)2|iGxN)^BYnp?sHW=mS=(5 z&hpytfBDcOd%s;+*$+f7N$kJ*`v;y+ba+_3nD>6Yh|N=w;eutl%4W;i^a|< zT(i2sy&n{Qp5ttuodvTbzzn)Em}ov2>)MT^PeQ1Oj`*Y`YOh@5%F^QG{%rk>P>`qa zZE+45CEpL@`$A4gm+bdsTHx(N3Mj=vG`#unU2lCm%YBP>M$)M6SjzS{tsWruRado126Adtb)C@8r+_ zv8jqqICOifNJHhE(dqTxRbrF+B&vuNInaxQGv4S@c+wi@i-)3|*p^^JDROl@ic)N9 zuqY}UucmEL9qmZMyowIl#<`3|bb{G=nnX$z1k}U+?&V90e%B)u*udJT#s4<{Yet3( zwHN(S=o>sp#FGzC2Xa_%i47sIC32hwzS$mV_0=Ey(<-dr?bn-tc~hd=!z)Bp-}qh+ zaXUP0Slxjwzo;#^J03m!-8Olw{FOSjFVav_M9Mk7Qgny}4a`acG$&?v)QV(mG$*j| ztH@co*VDJupUtL;jiRauTY5Mu(a=5Dh``6k=iX26fe=BnZTZRaB`6$40#WJR{eBqw zMobZ3bLs-rKKko3J@JY{ag@lWe|nnvjg*nP$U6WT!c2jVz{!?`JA>nAx#H8dv*LK( zAQ2nkIv~! z1;(AcLJB6$TfDvAO91<6iI`3e827`NXG%B2(>%LIMh7$&tL5DmysN)Sav1BSbEFxuM0tAjZqk^K^XZWHoNl0c8&`)*ssYsbLWYJ z>z~>7dijiZHVn~WUqi3xF?XL54=ApkADHNrVqHy$JSKV;{9 z=P?e_BT2KOVoHplZK2`s4;So+5L}R!=n<68?@VW=-?@$&ew#*tat+(|7$ezx#{U6d z`_9^ghdEHxIdEjHx{eCu<*m59{0O%=Pcz*7)_pC$TWZ-t)Wp8RFnsE81;bJ#Os|zq zW|OtJCAA|+*oIx}i{L;;9XzgCGx2P+>(zXiXLSbY5J}hRaBm{)L#f@Wj-}KtQA!=W zTmuKE=Z>1vR!Fy0k9g5<%UagjrVhBJZQbK8Cu1S@*`1)Ik!QT_7_u#5hn{M zeAGHYw85bfCK>}>WrQ6^+wX4%MObi}LB^<{#BypD&qn+)cX9YwK$q$-vlK&rIOuhH z*chmnTMaRPRzUaE6gTf^duq5?5{r#kwo;WXt1r(LD5$R)#W?6w=j4&}9ng z05B|wvy&=bZ2O&>vC&{^{u|hiX4riGf)YIoOU>*@we1lHBveeI5V4>VNl-wS6oE;w zd9sc73#f>b7~~JhMZ+T{ws$I5gf50a1s<@6T{W#-`j%bXJesYa5B;NJLl)XAp|H*K8BKRc_F{n9&F*=v$S-!f{WwZY_FA!}x)m9ocUXL7xbA~<)V1W33q=Qk zi~JhKc0+uP=p+GBA;;tNhrCS{Om+X9AdFLW(aO~H=Dn7CR`Y^?Ue;)_oAov58Jn0dEs^V>ej3kJ!4vI;a&& zik!~7@QrXeJ$M>#$f&Vl)c^bY4E|Zxva18#$>HU3Yp04v-*+_e#YQZUT%|o4TS~5d zA&w9cs7aR92mjoY`?mLAGb^zxdH-?9`_P-VT}p&<|FEPdUEBg+>L#IP@RjFAr9-qm zTq%s({2C94oI8f@l=xg_Z6e8VF@8}gST>K;TLQ2xQn2~@nT>|tmrNG7%1TkEOlW>Q zZSpH$$s7M3SP2G>c+G2=m|m93WKAORP%u>&A;nX46(Y_7hpTyNaPuOi1nG=hk?o5p)UH+QD1J_{rpKKWO2fPxA_;3{g=KPy~ocP#jPQxJ8aO7_XW z;;}ffVB1TDt|e$oo1M_B7Xezf4Tzu-I_YPlweZ7DSHG3%-r@uzoUhH(MwN5D25w@n zeU+K1AnvUkhaeLzGMd>O8hqEFbe0cKmHX$xpAbxHz&!G6cKZuHLrZjsj+$T&1R9LL z_tGzGdnrGg()0sUcCO}I44|8)@DO5E)X`^}u zI##n+q1;$nL=gAGbZKionsgdAqiAYV=l%~|6d!XVwn`73Lpp`_v+m!c|3}!Z?SzZJ z-|Mx(dzx0+^T&^ry7K&dHk}QCW_0z7a`w`{sYk?r@*vmEUyGN$ z*|&d6J(O(w&{1}nqG1u5_z8+=BuDY&J_ASq(!Ho(&Rr)0@o@u{yBma-};}UBytX z#UoFEu>HkzW6aDEh%u&s@dO#cH6(m}!h`jyU-2~qn?)B}eBSFiXRyqECkGSns zpPzKNG5lM*_%GMPH^E&Y$SwVYL{1xB5iHBYr9`-d8hiPTHSxWC$8 z#ve0paR9sWl3K8AykCG?IZMe%|34+ce{|U~Lx*i3M@P74hJlINPOUq%Jsd-;5RLl{ z8!_i8JdRNSW!-g@)k{5eQD~HUt;^1@_*So@Bnw;_{Eser$mnU9VRnmAX<^$-ToJ6i zn=n-kbZ-H`E`$3hnevH!iC1UinUu$bYBTJi(qy5DhYCN4DX zxkP!%?%ySv3SehNFpvdjf2JGlc)&^WwU`v?Z-y54jdPtb)zz^7Vu_cRU6wD|M3D(< zy~B7+5DU$e*}p^pKFVwexl8?wsN<4>ot3&Sbyqx}c;#O9nPd}FIqRgIFI_t~?zKW{ z!X;}A3(CxBP6%IMbV#qzm$K{I))<;Yt<#ibtQXnzLKQ?1oC#_7-lYks7)ITea#H;w zwcYKkn3vw8?ctStlHVo$-Z40j5>=RhtD|w1YLk~X;#Y2l8cVE4;amJU-%nS*J^HZ$ z80Bbzc3;f%eDv_I_y=vips)vxH)wtN9mdPFql!6%mARF>t$CA@VJqTnQlQ>H8A3KU z%~TqAPhSRE9B7mv{T;6#)%FEHeOWrU`<98%-|-VgZQymQG9M(2y`dniT%Rm()-iVBm)$Z9FnVC(M}2z-q>al+C2_LB>tE#rW8u%k~uNIlYg2jH2&jihwHUt72)5%apYk= z91$<@57prBIN5)h4i}5^Tpgsz1%(4Jj`(KLpeWMTOhmn`jI3X+u=rd+O-U3E z4>eQ_pUIq4_UA{QG*f3)xiFoj`ljkh0Cjk_Ue~J3>S0Txa$)PkYkmAHUs1^;kbM%Q z|8fI7K%=I}@qqSB7?iSqPnAWw9RA|kMn~|lKXd%9N2&0~uN0)$6YT|KzcQQC8SZM0 z*x7)Zd@i>#NtxU25t$kyZoWwh^G4P)p8_I1h!uHwimNYBj6A| zf6diSy3m%{n-rK9IF%^`y!g=yfp<4CkM=RDC-nEV^Fxb}wq~wb=Q?BkS0`F2Lqe(c zRGZY2sgzz&Xtu(k2S>@n+LsDA#IB&GJPL3o1V`NHSQ&RtnT9s|hex8xG{a`_HA^ z@JZF$3JAd@SLGp1=X|G!!DF6K25`Vo$1tDw|KOQfh>u}mJ?xza6SG9shYad)m*gZ8~Z8B{Nv89A4DetkgwB?fP8c{3%>_?v&`~U)b{(?Ci~+rt2nVyaz6% zPZ3a1{A6My{5pWBlm7<;x##SiR@>pH_niPloPuFSk5`-TfM=IqZl;AU-+@Z%KO$#_ zBO#oLcVn^80y=-px0$z`CLoFBx$3}Y8N`P4KMKWIw*3a|#18rYpdg(ADWLbcu{W8e z9Imf7&J32Pv)VpCNF3uu3pMPfY`!ThOOSgN76y=?0;Q$IVlk9sV)I{2UFG7OCMuuVEX8;efl$)D?vuz+|7b#l;;`iFmf9^C+ zUR1wRoRCWID1R9XWkyM-fz66V@SGAhNgEK_pcefftE13CVbj^p_YJ-aJQAjmrD~z-_HEP1es$`; z#Sd3xMH9#qK|8Emb0|a^M?&Fl?pw@VKsECAhS(U|%VrvODHRX2z)IOc{K1tIV3q@y zL=+4geRUuj0;n=oJ`7UG@4P3c*1MD!)meaVYYri|z6EZy((LbD{5Js@urjGSZNgQG z4wc)h>jy1IhG&G(Z0(o)b9uOwzZ{Oe%>wY16RDTgtjV%J)O>fX7En5Fw{1=lBBR@5 z1-L*2%}!N>{mgX^pMc8cbAY`{9QzQIpRCg9!K<>!Qin+=oHKoIgooNc${zc=;7)@WJ1SM1Jguv#NF6F_RW5)fuDDnM6`#Dq69Mk#hvC`TM@(9uEq5}}mi(F_U{|Byj%~+KPumSR^voGF1Fin zYfYO`SAw?0@o{R-R#UCzs(#sG((hsG4jHVNOX|>!Sv_1NbD0b3q^p!9jUzcp&iluu zWClf+f#qs1_1ch1Lbe_r2HPdBt0P!%z5J(%P~eg1j^rm!UIgMrACzudzPe!^SV;nX zIMmTFSC7%zfL?}_3{EW-2yb=U71dcfLfoFxGq%eJ@e(|_A(~B!qN=w7{v;0_1)T$2 z>Tx;mGM)(k?6~*4A@zIx>_aa7ijAYbz86K9Sy=9rs?Q);CCYwmw((+KEKQP98|!o@ zO6=VlS!^-f)@}L59Q>Q-21Vl-p~)adUE;{P+LmT_9DjMq|tI%d@5{iR_VXjoQwhji}#hk<&eE^;lZ;_e0hK5-=ik19EgQ1e$xz1T0@ki~L%#4T{$*iBOG?a0<-O7Yu`J2%1Y9 z0B$l46SoBLb<@h@#CRGr=HK08y{U)f{(V)0_VGb2a_uC6%B$P#w9zTe_AE7=< zJfyCW{vgEsu1L%Ghh0<^!Jud_VPwOm>$}bUhAkthi9#3u-6-w{i1|3XEeQ`i{^VI5 z_g>|PjsfxPx9mLCm{h9zXIZRlf*Q#D&NG_u=?s{SLp?TY+u zODq+eeYRAy{G*HINZIaeacULGr$j3 zBSq~9ph&@p2nwVZA{+far`9p2r|YscR!)&n(UY$1e)vk#`cF0ByL#f#v_sYFy7pP7 z{2!KOd`_knvh^nE1c7hY!<~MEd__Dcn2^R zcXm_PfNwvs8|wH%d4X+{e5+w9Obo-1Iv|^Rls3YSNM`<6@Xh(6AvdaVw47RbY5E2B zblfaL7Tmy`!1aUaqP-u>HCNRv$@LpB9mXT4X|l)im&AN!X-VA4#$$h~Ll2$uTtq5#fO~r*ZK!txC>!m=*+k204foFx{b! z&mTuFed_6CEP`$Sa-t&2ZGrnf;V+6){#ayXP+b)3vZw)kKZh##UC?Y~VVt;XPO840 zL(NHP6y}Ha>p@jy+r8VrdX&vM1!MOrDR#a}rcc{F$<%?00$>J7M~Hom4W z=vxsh{pB#nEL{qyn(R^t1{wY(c5H|hl|DNBtjfFNqNwQeF(mMnhu@Ct;i1QWYkFS{ zmMfBwJ(Q4YxWl&Vv4BmGU%oS9ed(Z}JPcSV9<_6^wp|^rqm?~YsCFn?2c!!+5>89YH-y-9S zy>XSt>N&#igyw3UKN*nJj{VNy%CN;^Gj1P!Auo3-V}6^@RaN|w1AQlDJl*lE4z6R8 zqQNGz4JZ))4|xS~t(+feA3ijX<@5g36XvNw<6IGt1B>?&V?$BJl(|f#F}A&2a;*JZTws&3M? z@b8`OUSGG6)W;>>LHmN+oT!Zz1V~NgeWH=nX6b}6f$FKR%?Q-ekK69%sq-%M zy+L6xSWD>;A10KNOo{Q1-8untij1ln>}$zAoR8w8)H`@`!2nj5K{)CYbXN!xY1rL^ zoQBuI!%$Rgp8NxNf}6S-N-8z|h(51s6^`4C%4JwPPErZ)PjCVP$Mmvo@;zW+4Q;R7O&P_pv&x4XFIe9X3WPYX9>C*+qwXxoGv!p->dWn3dj=Bn z|7yR?5BHuNuOK?MF!R){$1RJdXfS}}B3e=S7#W?{Xp@NP`wv? zC*l9OlGzmPyp&iHC$JBp@a%aSRMSZ1$sMK4*;wMH|1@fTTz1A>c3AbkW@f1f^yEjC zN&Wj(%0z1zQSqP?J*pA}qK5e8N!P_u>q#X&h668_qetJja6=2$EJp^4Ewdn&!W0s^CdZQ4kVtzuRvECshgi za)769I|0?R;@vG`jXi92*MQK6NuOph)CZk>;l1p#Wa`0EvXyESsK~G;?){xx0*s29 zqaW}7Ch>!B?|AChEaR{1E_j?^BRogbbL^+gxlu$W)|y@gFc%P6B<6&#<;4ldwoa_s z8-346b{KH677yL>h<-m)2M^tDVsLo1i%uRdFnbGyx5Bl&dPKV%o7YAW-0ykm%b^y# z+vs0-civ)^^9rY+Jt$i_iTi@>x11F^MGqvj^r~xwXDKB6t7#c;{ERhXy-Arvek;AW z?7eJBwSTrGlDc0FFMj6R@m6rRWlnwX?MBTOil}+{cnns>AZVWax$~FoAx(qdkkn|I zHvV4bqGO#QWaqFt@=sFu50i$}K~xv=J>e6D7pkKQBs}jzqoieCjW?&Uz#K}nxzY(H zEN%vtEe%Jd>nE!XWR)m$rCtao)Q#jm`C; zukB&BkS`2%jTbFSgNLB93J+BI-wGpFuNx5KZQo@T+L<^E}f60%~u9L-a z;69ViP|h@Rp0SfBs&kzPD*0koMT%;1+2rgnJHPrWib@S-xseiI0Q;;@nWIiz`sER& z_J`adTVa#?Fz&vuiV(G)~4q$~9j5g`OPQe~#~gi=IQ>f=ySW;rzZ{eZ=Zd5SzHX?QWIW*S$`S z5+|5zglm~WjGJh#&k79aBXpuBEkET|sRhHMcP&!4Xjx4JZ`?sMV|h3hE#^(p;?|Jn z&kc$fr4*q~sKYaNKR1CR*tUAX;^^o z*XE2ar4glTkyA|NvO-ff2|*#FnTGjX5IA8FRwJP|)@n`hu~noRF?4IQ;4$@! zDD@Mui$%gkgRn96T3AA=IfpejN_-0~Tp{RvFQWH}gu%b1B5r=84X!5eEyLRGVp7*Y zGIBol<7$q$;Jv7mKe;}I59v+XuEH%#L|Lr_ZG0{Hrb-O+M5dt<#(KmJ!XK5=u`ifH z^Ey#)A9ioPw>TJr9HsS z`C~Fu!p+d$c_}QmF{$Mq2DAC`Zb#tKgFu6t5Ekns2bQM4KBogu;>}E$suM zZmhLtS3f!hN}_oTX3J^ z-$f32PsV+zygRP;+-3~|>qd4}@pNZS=u*!-Vil^tSJPE4A&+I!aDu6j2*Mpw`BUY#9R2Yt- zT`GXpYL1{@!exe54lK_0Gh%A;A^trt=lzN#{Ok1yNKm?Cfa`$qe@HS#zg*Has5og0 zzTs@uCEWSNVkWR{L4ex9r1)9H1hs`Nu9Uos92j(w@ej}-u2q0Nyy`0YLeD7A0!oqn zdq+Cvq;9Bt5ZnFml6wQ(o^2A8ZY<9v+VJjQq_UacE%o@LQL^Vd!q)J3<{1Sg4gMc3 z_>W>t{oWlBlkx3$XDJh^clf1ap1Obu$b*%-46aljvyGP6O!r z;3$93&Bt5xldDB>Ti55dwIjl1vBuRoO?V9B%NZ97hk8}VI%?;oC3tR=PDKm0{)qCX z6uv#ttxV9epGkx|FiI6l(9sV^Z@^Q7NvjUyo`xmqT3)6<=WTFYHtYCwp?U1GudQof(Ir#RNC+;$ zPLI>|q!$YHj2QLfJ#eDOm)+KL+d>p@5whQ7CB2y5o(|@~Zzh=6HcQhu^1e}n-AMba zRz1v~QukVXA?c2Q8k;Hk zE_Z31$MeKRE@INlp_y@`qf2#?{S#Mg!pYP^(;i-UKvJtrr;mkR&)*L^So(VAUgE)9 zmA7r=(O(gSoJTiH?PF!mVtnZ2U$l!==8X)l$#@-1#EGve3>YSBta{~yWBp~ZnIyH1 z4|pXwWW5 z<{uNGnm<{*$(;Upw=P!fVe!EGhWI`Nm~HFiM_r~|?7u~`+6pL7RxYe9H)TmXJR<6u zx)920xo&%4hFd^ft=`u6xr~$r`ge*r3UQ$>Jyhvw8$2R_j;WIhFm4_yX+G()ZO`yg zx{(;m@ik%+XX|8d_D#VA%25R*r{p*Hzy8x^WJX*T@?buFJMA+!`Fk0x?V%P0bgM$a zN<#mtH%JOHx<553{d&{{7lb@CH(Er#r1kdPNj{59c&gQ=18Kr+J8euYUe}U5(b|>w z!^|7XbHG7%$q~rmhFL2XVaxmxa(K0u+A;7?Cq4q0kE~GB0@|_-8INezE;Q zxa1w4=*++|o}wlk|BK^fFb)87`pmLt_YP#9}!Gj9Ibo$z7}wj>@%0%YDW#+O(o>SrU`#5-pT><9UuJ>f;Y5P z4cypiTcwXw*)jxID_-h=%Q4a0pZ=PDN@N}Jn2B9LRzT08m$994E69#jP(=DFG#d{U zr}QJLJ@c1BB^K(z|1K5i`SC`m033~!j-B}W`{Vz`w-#HhEz!bEg+wSt8!HKX2^ab+ zwpLYfoq1al%!DWx{d7RVVjqk5^zinko(9}{s55VHQvl25e%avNP>4jRbjn0-?|Vmz z!JMDX(WBR8o@4U14=n87Rm_h`c}idq$Z-Uc$2c7fFK!qW#;xu2N=YjhgYpuHmz${t zcFxhUgm~HT3Bne?T)Uc{NK39g(OtSLZ1RK(IUiDYvo#9CstPZ{-fAT86tbt@j!oAi zC~$8ykobwjP8$T>P2fmW(y&O=wJCrN_H*?Va(2xO zBU$f1DQX=B(c*ajuB4|IGQ>l3C0VtGc|?+=Nl`9*cOcmY@zXg&IXzuzx&M4$fg7vH zdE75!ZVcA=+cD5{gsLrnR!H{*YO<-V?FoE79XKweYZA!|VX@kHXU!Dz6ilH4q?$TC z|2_b8@ul{rid4+`WNiNFIsE76>&!+Fy34ZEMFcY-cro3(`L`c&-s{1&sBL4LxtJlb zf!);A_s;MKS8PPxZ{WlSkD+C9QYI@8SJ4^d$fmO*TG0*Ard<$py`bV`6*&dkbB)bU zln+ij{!-lk^3r$n!rdIC&F&ihl4_d1i}$_A#r}UkARDXr>3A*rq8FZ;hbZcs3X7jTJUzt0>D z;x?sD!rZkVJfEdi00SibcxW)+yhOS5gB&LQXufHXY^q{Vb)Y`5%GC>Ka0536Nyx01~Xtf3qo&24Ei||)GVyN3gME`^L8a(!QXkBZzYQrprM8{+s68@ z-h{Wfj`mKp-sbZ@Ti_Oyh4F^7^EYlv1koSLAftlq(uH)O2311EbOYnCCRF4&PEC2>Bj90C3X2yhZO69?dpvQ<4L6sx0&3Q`Ujv+c(}GMuxZ(n>9@i_C#D$J5RxXI zN9R4UF)EfM?}0+0w{g2tUdwit(4U}73zsEE5|% zV7>!1!1FE^zun8XV8$IZkZ>k?#YkZaR_Q|7%+cAX-OfWRfNLE2S9ZZ?B6TkM{xi*C zxCsV~7FbIS&n#a4F5aRA7jf_!E@K4c3GPb^oThG1&n!SJX|Y%t&~6V3`*{+F`yH;= zC=b4xal6gq{Yw8nq%H!UaWg$LWffuq}k&h3|kjaDc6Qq7{8Tz#9;KQ zil}B%D}TbTXjcCKL%p9B^X;tI31VG#NzS`C<=0QPX{uY;YtNhXxOn(!)N~g#^+$Nz zO&sr@8v@q=gl6mVleJ`U_Br7QQzLFD#Jt2Xw(!stS62P)9sfHDSP!}YV209`;Jo|w zTyYrLsDE9P+4|hxf5{1W@DuL~20L}b z_TR~g^Dm*?k}=nkUb*AWl*|^CQP$VN2njfX4WCoCS7+bl!&gngHVTPKOQ6&$pB%=G z1?pHKZA0U(ZR2-;+kK5V_kd<*N9do-4yk{&9vejde`Q^DTvbiC7D<&50|^NM1*GdB z9TL*1v>=C;5Rf{QAkrZnhmb~6x=SQP4&B`;B_$M15!+z8#rfh1q zu~CF-)FL*EIlgZ_VUI{%9`B5I2g*(^(`-3neS%%Jp4#_3M3{(lfjp4hyz-XQA^+q{ zL8k`%UImZi*>$1KW_T#RV=`jUAx&JoZtbRJ?G~I;r5myT0IR zpM!-s%6YZ!!s(4__wvI)w0omdAx8IuONhe2BOK26B97zvm73ZX4glZ-{HsOPnLbrq zEr4(GXBNjl?}Wak`{cG8)693GTlg$}K()5Du~sdFdwP2I#rB0il)Wz(h(-Nyi?C}| z1G|R$hH`0|pn~WfBi&y$D%L`9@73D2>U63@qE4FD{??!-!M4J=pA^nJBV1}Lfwp7m ztO5p()m#L{dtWD!p@Z&}8G>V0?DxS(-{NeV;76E^u^K8)ue*$ms~WSMw14(1so^GO zoRkQuV%agt>RynrRDR2~MUyA>!R2HTt+4xp3z&vabjDU-iNyCnQ5ho6f0CwKBR)uxwVV2P%7uY5I`MUplp zl6&DB)^~UqGcg@njXeW{ue+Dt<#`>5a;+rE>#*V631^c75gY?C@Btke{V{T6X zun3^0oFP{CWA&`wSE+t4FlU`eVs~izuA&}`>aE7JXHjJmKBj!l6<`%u<%edktls=F z6P+fbC+=elZ6^+TCP^$v!%`qIr{#1CZhcm_CZp+prjPI>G{51>jKa@Qm}g?dyRI)s z10W(9-@=DJvto60DwqwW!mUWYU>;N&ty?TbucmUR#ZQ-)P319zoGRDP7xCztXIfMx zrHo6%WStjZyEjkvZbbI_qI$%>#Bp9jX_<{8vS{K912p|ef-wd%&`S05-&LpHpAVpq z=J!XZ(c*^hhv5WD?40$%b+c&Q)(vI34;((JJIscp7)6~4&Txf;v`+o+nlpfbqBk(g zuqQ1^4IzB=xHzsln6?!s#%T22Qg+t}Q?MSY-59mbRgUZ@)~$MHSjzM5Kqj$u^nIMb zk41ARs9;O#z0EWA*6COi+H`i?jahnUCnTf-a4F{Sd_(1|NHise^a5Cf*qEz{C%^$- z)5DCogNyjHkEUE0_w+x@6_By$+=jxFP$~w;i3iE{n^1c3k&%^2F zS?|Q6^=&xrpEM}BQW7uNfon3w_hx=(NE_hD8PW)lDTv~@?57yFj@++4Fxlj->ybv7 z#oc_WP|d;2*cbcgdZuPrlWuSTSLUni?A%w;fnO<#=)0Qucmgru;4v87jM}h*Bt{9^ zuB3$2b*CT+cYxIX=09AoDLi{oOPOPRxZ1Yy+iS3pXf0qPWxJvH=eto8N1C1 zp72;}RV;*Uoo}qnzA{Fi-#nf#b~sB-e$L_3%f0*w%QB+BFE>b|4aKTxC$Y_ZinwbeJ;rqNxEIBvDnfbDDSK;;i`skz zPz2RIQQ}5n-=02v#GEZ{;4 z?tr2wz&<2oi~tGAesqqah<>X$GP=OntJ`^`K98y-l4Vu z!cHWrD$05WFe3oAbg!DDtz+*1M5Cv407MwXsE%)VM&pY`VFVO2zzw@ytz)YUD2ILH zJ3BVQiE`D8(b^MB9gKx)oLlOg!d`O8$E$r-_d|vH-M9Rgk*hT1i;1blE7Ms$S!3HY z`GIXPK3Ov+7V;{-TG}vzPL^^HTT&z$>|`ZJv0Q$n$EAW@Q$r)w zRz|l(j&|}IFgGJZZShk?+nE#ViDU15xhEZT+@2muk*mzCS)AUh86YE-w8WmNIMKyI z1j0g&;iTfF_o&uKUDulB41CzpG=?Ny0EAp+13d<@+X@hmt%D#DXF_LH>koDprF+n) zX~|SMZv0my7JHxd0hHjuM^Ra_CUpgJuBy1S`)K{jV|o}lrqP+?So=qBD4z?+rr8@| z2}pIPSBf-i>=?W%2`JJ0*azV~e^L$dPn{`XSJuB>n0ng{X#eh8&coPKC%{9tTMAXL z^=AlXSqc>`9jm}p(J2eC%nO6p4-1BTLT!Kel}pf(W=YOH(6wo%Bh%GNm>jkT)O)_1 zqge2LG?@?>`Fj633;O<8n8{_nNf?-v0JQMFHfKV}JAeA@F6@hTl( zbkk7mY$4p$*Y8T*W#bAzc{R4e+&pV8Z$FqHjM&-_2h>Hm8GAx!9N~Op$Od0^E zZdLmI%4t~0EsTSkNI{UZ3jWb%7&Zcw?^OiClcVW-z?=`a!%J$m7OxFlXx)e~|4Pms z3vX#r?j^1Vsumbu%tA+*_+{C(GNL`@fVOsD^^~ZcLmg&_@Db!<_I(u2 z?`;AFbwu$f9VYfUbp=u`AOFZcd!yAcHlAnAO8CzHi3w-1?0u;cfz(7Xi0*!Qb{9Ae8a;<17QCy-=h3LColVC86dknT~58aF0+k2p7*o8z8%9>5#*RR8n&D94A=bYQ(4ESL|?{&RWv+)fk%)?Nrl&qTpbN7p- znQlGWR|Z(TqllI?@TFlUBPKONLVSSA+*6I&6z1EV)Cx{&l`+u?8^o`G$0_aM-g8j~ zwI-YwG+rnTnB~>eSPfNw@FINvz6765qU(3e0^W{lzTv`ai67n86P2_y@hXQ6m( zg3kTzE-KB}Hx4_9v3emWE8!CMeh~)&X26*$HrVq2UyIOA<8ikjw_BuhV`YGBS7x#i zG*xzr9=7sRN?^nlbG{9S!*W+!qxSn5>pLJ_{yQUf6xQWF3u>Ph27WKF| zlMhb|IFZihumbP*^?%=|bHSA*XmmG&?ZXN=j=1RKg1ai`3!+(y^ zEL@fa$RtDEs;1is( zT~>9$55d9WaUrJHvF2`@ab=bD*x4ED<|jY4Ry#A$3D9AorvdD*4d*wAwSMjTBExO% zL(2QcFt#K{pvyO=!){$L&GCS^+rL71ClN$P zoCL(6l*MJP>5XjtNn;Nl|L{d(%WEg$H?$R}6M`}u(1ajuVf7u{&iBHYtv{(@{EsdN z8GF+0|L!nIpb!!V(pU~jPiFCgc0hc#*Xy~T3@C&D+ShD3Cz&Wm&~gtjB!)geK)?T+ zqpH}DSM}8}^VzwNSZ;sM0a%UyXD)Qd_Xq9}h9uuzvDzFEnMl{=3vlQCYX>TY9? zinAJUpPj*VPyLBp4~?pwM}(x8>5e92-GjmN+HmPcCk43&H-lX4zmHBHQjD>Gy@+t2 zbPnLt{nOHzhCM9&|68I^t#r;1Ujlj@`)O#p_x-A1#uD8^F=)_X0Z)+cfYN$7Gh_}0 zA3*qC$AIBwqj<7Sf(yJrPioD#z?$bZDFeC_?-WEK6qoN7#CHAf zM%Oq0Moxbl9P_+7TX z>Ffn$4@r2|>Nvnd>kLc|-W$gmUuc`0r7BSu63>DC^<3~_zF;^)m~LG0U={d zadS;%d`PdEmP(D_?8o*OPyiJwQ!7vIa&}uC~8Ueef=8 zTTTviPdT04;D5nMfKzr>v)jQrUjrWeW{$7>(S%+1w1@ESJE$6N%szqQ)b1Rsdxrqk zK(qomnSgt2ET(#zR+?2k>B8|t^vpl*8$L#(HBWx&6Q zmG$l(PM`l+2JB#*;Jk3b)BG&iVdFSvRp*4Ixco}6(~U%@HM(W`ZEZ=)IJ zAix=v7MQ~YFL8>@N>iF*P=X*3(?BZyr%-4haK+yPh6*P8S_1bynEDO`MR#W5dfJ8r z-V3-T9DB_y4~IYF^8%l*rM$3T8==D7Bg~0&I^}{Y>_+tzPMz{}cv6A)0|Uu^d4rzb zNYSy0b!``zD4^AG`uNuk;55Xz;Uy+p)q&0;;J)mW=vr_OSV;ew;zYwis5E zTNi7qhf#X`0@4Dbm8H6kH-OCe#ps}zy2MrBo>Pcg-e-XVn-_(4cQfFu$)IK;^e}E+ z#tldNg>l%RyLUBH#|jPjV_+Ha=g)};(CJ@I5gF|fnk)auF8&!CuocAeS4;{8IO|Vd zETI93K^Ir3#X&C5d9bqxB#X<-UiDI<4@QD67W41V%RCmpJrol3FLVSRa1F0+`{zhX z9XL0D6`pelFkOli7{tC0Fr~;i?0=N!YPP#Wm;7Y^4k%fAsTWAinHa$HLQA{6{!RpL zzqtPW9sF^YwqEX*7XK$#o~I*bq{;j z3nFsaZp9U1T*wf5^ZpJ8{tXp@zAWo}S=t^F<>ikTjr+wf`5aBhk6%xn9_n%2#o0$D z9j&Q*unVFDAQKo!aY%ON%eS@pHMkrXA$hyZV%aeko7TUkn3Q;~h_fwuvci0!t-@r)Vr5Q2A}v=2hUFW5yTCy2bx;~E{BuB_(%J)te+RH-S=}5I`vN? z3emtLXekNWyUPekI@LoUO)4Vmue09dA?bcbuLY2ecEu|N6=&YXD|C^ zq%~$v#7llxjmV7Q`o*{yENs1r$}sDz&`73Jm9OW*=Ke$!3VG~q3tu0P-Z%}RF|)2c zTIb5iJQH?Wjk|I0#!fDF9ext=pmuNIyfvSWwVu=2qV>;nx~);SrPZHIn)AtZJBXs9 za_;sK66Jc+PhshJq`|_~eH5Xi{DCwBxd*>fUBA~mhy~KWUHW~yIn*7Xq*q2iR2Uf1 zxo>w9K1Yt&AxR{qR`W?~k0U50gin%x%Sa-|DBG@Yce$y$<4LNTOFtj#v1tt zk&1LuBHg;q4Pz%kq>2tIiDKh6x5CHdZT4O z1mMqp^e9mBVmKZjjPD;z>$zh4WFgHSFZFc%^J~;(FzaANwe65J zczc+&E;4Z-OoZESdj?X0LE}xxiG28|Oqcd(R_5m&8r(y&95@z>Hr!pcE0HB(7CnJ z{wDz+i|8ijYlW19`^m`}K_!jJ#=Tp&5G?H z6C-^U)bsF)qzkSZHFeqdM!DoQjfO$bR0f&|{lbxa?3?5GZ4&6*4(M)ci4-|AJu~Wj zU-!~`v;2FBKZknFd5J+TN6S>eZifDu^qX5E20{MDQw&z@txhzU@ zO$p;-pL&ZFU6tRy-J7mq`S9&dIn2vT#i@jvxyQ_xVdM^3?$a{=rr5**53az7%=Sna zTNXE|2j9Sm=KWXE)t|d)dc(x1Rw|~ur$3O$<}E74!a%iEjP_O4_i;N6H{wYOiced) zJXCyND^}0Qk9$g!P*;m4&>OY*ZDp{L@b{<7kk5$%Iqa+8U<_)1C1Vfrf2cxKM)-vG zrObORu|%dZhm<@ozhgq27KToRHCTL0m5QMZS!_RxPN!|q!gpf1RU8_dUjHyL)?~g< zy*3Ru?pzfGF7x@vnQsXZU!|!ul>#crH3RVaWy{~nTL-@G)H1tI6?u?sSyL1u1J~Is z>HVE%bW_*H8%w}6g zM`o&$j=ZIAn@H7G)j>GnG6;fd`=aSKbqplbYTbHAO`^BMUXmLG1V_ezb8#)BQHjNV z{1nz#Oq`5K<8iCJv_9gP6S-L#9FqV>@>htyJKF+UcJaH!svP2AAIQETb1z9kCE2ll z`}l`3S+joxI-amH;lfvKUSF&GU(ySx<=LN`!FR(ELsAsdq-cS58SUwb6y|(6eLGoC z2R~Gb+IX9ZbUO$AQM+^RmEgd#%&SMVLjGb@3fqCDB0R+Idx4|3%I+kKnH3wzym)Dm zc~GRpOsy~a9w^7oQ^$LYnn4*ZI~>(A%TAlf73gDxR#!RIS4@vHC<(|9P7a#KH#ymZ z7}|T82}6YNSc-8i%u?aoNnd0rCC&-a0#(!7?+Ef6^*$q#_NU0{?lbi^Pq2lP_Q+CR zp$Z86LIG`fH)waH2Pp2Orc2=Kb}I4HT)E4SJ{!cspl4)%FOiM}AP3S>8tLpC!n&Jx z+wUw2PIxHk*qn|krP+X`tr_P@)_fGe46&uk);Oj{t=s8}7Y_V#U<;p?9Bnob4ZD(_ z_bhGf#IjVe>~y~}bmU_LixqB2yez0E&Ce}ip4C$&$E3MWNrR|-z!jZg;pE+{HcT8t#R tDvI+#M!oofH$v<0uYWh_-GXyUx;C{w;T{EJ@W16;k(N*pFA&rB`X4$ty9xjR literal 0 HcmV?d00001 diff --git a/doc/bindings/python/source/index.rst b/doc/bindings/python/source/index.rst new file mode 100644 index 00000000..2a2c8b16 --- /dev/null +++ b/doc/bindings/python/source/index.rst @@ -0,0 +1,104 @@ +.. include:: common.rst + +Welcome! +======== +Welcome to the `Babeltrace 2 `_ Python |~| 3 +bindings documentation (version |version|). + +.. note:: + + This documentation (text and illustrations) is licensed under a + `Creative Commons Attribution-ShareAlike 4.0 International + `_ license. + +.. attention:: + This documentation is **incomplete**. + + In the meantime: + + * See :ref:`examples` to learn how to accomplish common tasks. + + * Have a look at the + :bt2link:`Babeltrace 2 C API documentation `. + + The Babeltrace |~| 2 Python bindings wrap all the functionalities + of libbabeltrace2 in a reasonably systematic manner. + +**Contents**: + +.. toctree:: + :maxdepth: 2 + + installation + examples + +What's Babeltrace 2? +-------------------- +Babeltrace 2 is an open-source software project by +`EfficiOS `_; its purpose +is to process or convert +`traces `_. + +The Babeltrace |~| 2 project contains: + +* A **library**, + :bt2link:`libbabeltrace2 `, + which all the other parts rely on. + + libbabeltrace2 offers a C99 interface. + +* A **command-line program**, :bt2man:`babeltrace2(1)`, which can + convert and manipulate traces. + +* **Python 3 bindings** which offer a Pythonic interface of + libbabeltrace2. + + This documentation is about those bindings. + +* "Standard" **plugins** which ship with the project. + + `Common Trace Format `_ (CTF) input and + output, + :bt2link:`plain text ` + input and output, and various + :bt2link:`utilities ` + are part of those plugins. + +With the Babeltrace |~| 2 Python bindings, you can write programs to +do everything you can do, and more, with libbabeltrace2, that is: + +* Write custom source, filter, and sink *component classes* which + you can package as Python *plugins*. + + Component classes are instantiated as components within a *trace + processing graph* and are assembled to accomplish a trace manipulation + or conversion job. + +* Load plugins (compiled shared object or Python modules), instantiate + their component classes within a trace processing graph, connect the + components as needed, and run the graph to accomplish a trace + manipulation or conversion job. + + This is what the :command:`babeltrace2` CLI tool's ``convert`` and + ``run`` commands do, for example. + +A trace processing graph contains connected components. The specific +component topology determines the trace processing task to realize. + +.. figure:: images/basic-convert-graph.png + + A *conversion graph*, a specific trace processing graph. + +Between the components of a trace processing graph, *messages* flow from +output ports to input ports following the configured connections through +*message iterators*. There are many types of messages, chief amongst +which is the *event message*. + +With the Babeltrace |~| 2 Python bindings, you can also query some +specific object from a component class (for example, the available LTTng +live sessions of an `LTTng `_ relay daemon). This is +what the :command:`babeltrace2` CLI tool's ``query`` command does, for example. + +Make sure to read the :bt2man:`babeltrace2-intro(7)` +manual page to learn even more about the Babeltrace |~| 2 project and +its core concepts. diff --git a/doc/bindings/python/source/installation.rst b/doc/bindings/python/source/installation.rst new file mode 100644 index 00000000..5b0412dc --- /dev/null +++ b/doc/bindings/python/source/installation.rst @@ -0,0 +1,35 @@ +.. include:: common.rst + +Installation +============ +Linux distributions +------------------- +Linux distributions typically provide the Babeltrace |~| 2 Python +bindings as the ``python3-bt2`` package. + +Build and install from source +----------------------------- +When you +`build Babeltrace 2 from source `_, +specify the ``--enable-python-bindings`` option at configuration time, +for example: + +.. code-block:: text + + $ ./configure --enable-python-bindings + +If you also need the Babeltrace |~| 2 library (libbabeltrace2) to +load Python plugins, specify the ``--enable-python-plugins`` option +at configuration time: + +.. code-block:: text + + $ ./configure --enable-python-bindings --enable-python-plugins + +See the project's +:bt2link:`README ` +for build-time requirements and detailed build instructions. + +.. note:: + + The Babeltrace |~| 2 Python bindings only work with Python |~| 3. diff --git a/m4/check_sphinx.m4 b/m4/check_sphinx.m4 new file mode 100644 index 00000000..5e9dcbc1 --- /dev/null +++ b/m4/check_sphinx.m4 @@ -0,0 +1,32 @@ +# check_sphinx.m4 -- check for Sphinx Python package +# +# Copyright (C) 2015 - Philippe Proulx +# +# This file is free software; the Free Software Foundation gives +# unlimited permission to copy and/or distribute it, with or without +# modifications, as long as this notice is preserved. + +# Sphinx ships with a script named "sphinx-build", which is usually +# installed in "/usr/bin". Unfortunately, this script uses +# "/usr/bin/python" as its interpreter. Since "/usr/bin/python" can +# be either Python 2 or Python 3, depending on the distribution, and +# since we absolutely need the Python 3 Sphinx package for Babeltrace +# because it needs to import our bindings for autodocumentation, +# there's no way to tell if "sphinx-build" is actually using Python 2 +# or Python 3. +# +# This macro checks if the Sphinx package ("sphinx") is installed +# and visible from the interpreter designated by the PYTHON variable. +# It sets PYTHON_SPHINX_EXISTS to "yes" if Sphinx is found for the +# given Python interpreter, otherwise "no". + +# AM_CHECK_PYTHON_SPHINX(PYTHON) +# --------------------------------------------------------------------------- +AC_DEFUN([AM_CHECK_PYTHON_SPHINX], + [prog=" +try: + import sphinx + print('yes') +except ImportError: + print('no')" + PYTHON_SPHINX_EXISTS=`${$1} -c "$prog"`]) -- 2.34.1