From: Simon Marchi Date: Thu, 26 Mar 2020 20:33:13 +0000 (-0400) Subject: Add initial Python bindings documentation X-Git-Tag: v2.0.3~3 X-Git-Url: http://git.efficios.com/?p=babeltrace.git;a=commitdiff_plain;h=2467456d7a68b314d7d5ce04981b251d0b195396 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 Reviewed-on: https://review.lttng.org/c/babeltrace/+/3278 Backported from ba64dfcccb1f1bd7a259dc5d563ba422b8375582 Modifications: configure.ac: PPRINT_PROP_BOOL_CUSTOM -> PPRINT_PROP_BOOL examples.rst: Removed example usage for `in`: if 'next_comm' in event: This utility was introduced by f03b6364aec2d77bbb5ef0625cbaea8de4179f63, not backported, and only provides a shortcut to iterate over all `root` fields. User can implement their own as needed. Updated referenced version of lttng from 2.11 to "latest" for lttng-live url. Signed-off-by: Jonathan Rajotte Change-Id: Id0f4636a5f66e98d383548bdcb894f53d31812b4 Reviewed-on: https://review.lttng.org/c/babeltrace/+/3408 Reviewed-by: Philippe Proulx Tested-by: jenkins --- 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 4e9ea090..92ae7e8f 100644 --- a/configure.ac +++ b/configure.ac @@ -366,6 +366,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], @@ -413,6 +421,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]) @@ -543,6 +552,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 @@ -730,6 +752,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 @@ -915,6 +939,8 @@ m4_popdef([build_man_pages_msg]) test "x$enable_api_doc" = "xyes" && value=1 || value=0 PPRINT_PROP_BOOL([HTML API documentation], $value) +test "x$enable_python_bindings_doc" = "xyes" && value=1 || value=0 +PPRINT_PROP_BOOL([Python bindings documentation], $value) AS_ECHO PPRINT_SUBTITLE([Logging]) diff --git a/doc/Makefile.am b/doc/Makefile.am index c0aca530..7047f0e5 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -1,4 +1,6 @@ -SUBDIRS = contributing-images man +# SPDX-License-Identifier: MIT + +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..ec57e068 --- /dev/null +++ b/doc/bindings/python/source/examples.rst @@ -0,0 +1,902 @@ +.. 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:: + + if 'next_comm' in event.payload_field: + # ... + +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 00000000..a97dfcac Binary files /dev/null and b/doc/bindings/python/source/images/basic-convert-graph.png differ 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"`])