[lttng-dev] [PATCH lttng-ust] Refactor Python agent

Mathieu Desnoyers mathieu.desnoyers at efficios.com
Fri May 15 12:14:56 EDT 2015


Merged, thanks!

Mathieu

----- Original Message -----
> This patch refactors the whole LTTng-UST Python agent.
> Notorious changes are:
> 
>   * Python module "lttng_agent" moved to Python package
>     "lttngust". This removes "agent" from the name, which
>     really is an implementation detail. "lttngust" is used
>     because "lttng" would clash with LTTng-tools Python
>     bindings.
>   * Python package instead of simple module. Splitting the
>     code in various modules will make future development
>     easier.
>   * Use daemon threads to make sure logging with tracing
>     support is available as long as the regular threads live,
>     while making sure that the application exits instantly when
>     its regular threads die.
>   * Create client threads and register to session daemons
>     at import time. This allows the package to be usable just
>     by importing it (no need to instanciate any specific class
>     or call any specific function).
>   * Do not use a semaphore + sleep to synchronize client threads
>     with the importing thread: use a blocking synchronized
>     queue with appropriate timeouts.
>   * Add debug statements at strategic locations, enabled by
>     setting the $LTTNG_UST_PYTHON_DEBUG environment variable
>     to 1 before importing the package.
>   * Override the default session daemon registration timeout
>     with $LTTNG_UST_PYTHON_REGISTER_TIMEOUT (ms).
>   * Override the default session daemon registration retry
>     delay with $LTTNG_UST_PYTHON_REGISTER_RETRY_DELAY (ms).
>   * Honor $LTTNG_HOME (to retrieve session daemon TCP ports).
>   * Do not use an absolute path when loading the tracepoint
>     provider shared object. Users should use the
>     $LD_LIBRARY_PATH environment variable to override the
>     default library path when running Python instead.
>   * Do not keep an event dictionary since this brings issues
>     when enabling/disabling events with the same name in
>     different sessions.
>   * Make sure the reference count does not go below 0,
>     which could happen when destroying a session which contains
>     events that are disabled already.
>   * Minor improvements to make the code more Pythonic.
> 
> Signed-off-by: Philippe Proulx <eeppeliteloop at gmail.com>
> ---
>  liblttng-ust-python-agent/Makefile.am            |  35 +-
>  liblttng-ust-python-agent/__init__.py.in         |  24 +
>  liblttng-ust-python-agent/lttng_agent.py.in      | 567
>  -----------------------
>  liblttng-ust-python-agent/lttngust/__init__.py   |  24 +
>  liblttng-ust-python-agent/lttngust/agent.py      | 389 ++++++++++++++++
>  liblttng-ust-python-agent/lttngust/cmd.py        | 178 +++++++
>  liblttng-ust-python-agent/lttngust/debug.py      |  46 ++
>  liblttng-ust-python-agent/lttngust/loghandler.py |  41 ++
>  8 files changed, 725 insertions(+), 579 deletions(-)
>  create mode 100644 liblttng-ust-python-agent/__init__.py.in
>  delete mode 100644 liblttng-ust-python-agent/lttng_agent.py.in
>  create mode 100644 liblttng-ust-python-agent/lttngust/__init__.py
>  create mode 100644 liblttng-ust-python-agent/lttngust/agent.py
>  create mode 100644 liblttng-ust-python-agent/lttngust/cmd.py
>  create mode 100644 liblttng-ust-python-agent/lttngust/debug.py
>  create mode 100644 liblttng-ust-python-agent/lttngust/loghandler.py
> 
> diff --git a/liblttng-ust-python-agent/Makefile.am
> b/liblttng-ust-python-agent/Makefile.am
> index 8b38132..869add4 100644
> --- a/liblttng-ust-python-agent/Makefile.am
> +++ b/liblttng-ust-python-agent/Makefile.am
> @@ -1,20 +1,31 @@
> +# inputs/outputs
> +LTTNGUST_PY_PACKAGE_DIR = $(srcdir)/lttngust
> +LTTNGUST_PY_PACKAGE_FILES = agent.py cmd.py debug.py loghandler.py
> +LTTNGUST_PY_PACKAGE_SRC = \
> +	$(addprefix $(LTTNGUST_PY_PACKAGE_DIR)/,$(LTTNGUST_PY_PACKAGE_FILES))
> +INIT_PY_IN = $(srcdir)/__init__.py.in
> +INIT_PY = __init__.py
>  
> -AM_CPPFLAGS = $(PYTHON_INCLUDE) -I$(top_srcdir)/include/
> -I$(top_builddir)/include/
> -AM_CFLAGS = -fno-strict-aliasing
> +# dist files
> +EXTRA_DIST = $(INIT_PY_IN) $(LTTNGUST_PY_PACKAGE_SRC)
>  
> -EXTRA_DIST = lttng_agent.py.in
> +# __init__.py with proper version string
> +all-local: $(INIT_PY)
>  
> -nodist_lttng_agent_PYTHON = lttng_agent.py
> -lttng_agentdir = $(pythondir)
> +$(INIT_PY): $(INIT_PY_IN)
> +	$(SED) "s/@LTTNG_UST_VERSION@/$(PACKAGE_VERSION)/g" < $< > $@
>  
> -lib_LTLIBRARIES = liblttng-ust-python-agent.la
> +# Python package
> +nodist_lttngust_PYTHON = $(LTTNGUST_PY_PACKAGE_SRC) $(INIT_PY)
> +lttngustdir = $(pythondir)/lttngust
>  
> -nodist_liblttng_ust_python_agent_la_SOURCES = lttng_agent.py
> +# tracepoint provider
> +AM_CPPFLAGS = $(PYTHON_INCLUDE) -I$(top_srcdir)/include/ \
> +	-I$(top_builddir)/include/
> +AM_CFLAGS = -fno-strict-aliasing
> +lib_LTLIBRARIES = liblttng-ust-python-agent.la
>  liblttng_ust_python_agent_la_SOURCES = lttng_ust_python.c lttng_ust_python.h
>  liblttng_ust_python_agent_la_LIBADD = -lc -llttng-ust \
> -		-L$(top_builddir)/liblttng-ust/.libs
> -
> -all:
> -	$(SED) 's|LIBDIR_STR|$(libdir)|g' < $(srcdir)/lttng_agent.py.in >
> lttng_agent.py
> +	-L$(top_builddir)/liblttng-ust/.libs
>  
> -CLEANFILES = lttng_agent.py
> +CLEANFILES = $(INIT_PY)
> diff --git a/liblttng-ust-python-agent/__init__.py.in
> b/liblttng-ust-python-agent/__init__.py.in
> new file mode 100644
> index 0000000..0b83d10
> --- /dev/null
> +++ b/liblttng-ust-python-agent/__init__.py.in
> @@ -0,0 +1,24 @@
> +# -*- coding: utf-8 -*-
> +#
> +# Copyright (C) 2015 - Philippe Proulx <pproulx at efficios.com>
> +#
> +# This library is free software; you can redistribute it and/or modify it
> under
> +# the terms of the GNU Lesser General Public License as published by the
> Free
> +# Software Foundation; version 2.1 of the License.
> +#
> +# This library is distributed in the hope that it will be useful, but
> WITHOUT
> +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
> FITNESS
> +# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
> more
> +# details.
> +#
> +# You should have received a copy of the GNU Lesser General Public License
> +# along with this library; if not, write to the Free Software Foundation,
> Inc.,
> +# 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
> +
> +from __future__ import unicode_literals
> +
> +# this creates the daemon threads and registers the application
> +import lttngust.agent
> +
> +
> +__version__ = '@LTTNG_UST_VERSION@'
> diff --git a/liblttng-ust-python-agent/lttng_agent.py.in
> b/liblttng-ust-python-agent/lttng_agent.py.in
> deleted file mode 100644
> index 9e8cf61..0000000
> --- a/liblttng-ust-python-agent/lttng_agent.py.in
> +++ /dev/null
> @@ -1,567 +0,0 @@
> -# -*- coding: utf-8 -*-
> -#
> -# Copyright (C) 2014 - David Goulet <dgoulet at efficios.com>
> -#
> -# This library is free software; you can redistribute it and/or modify it
> under
> -# the terms of the GNU Lesser General Public License as published by the
> Free
> -# Software Foundation; version 2.1 of the License.
> -#
> -# This library is distributed in the hope that it will be useful, but
> WITHOUT
> -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
> FITNESS
> -# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
> more
> -# details.
> -#
> -# You should have received a copy of the GNU Lesser General Public License
> -# along with this library; if not, write to the Free Software Foundation,
> Inc.,
> -# 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
> -
> -from __future__ import unicode_literals
> -
> -import ctypes
> -import errno
> -import logging
> -import os
> -import sys
> -import threading
> -import struct
> -import select
> -
> -from select import epoll, EPOLLIN, EPOLLERR, EPOLLHUP
> -from socket import *
> -from time import sleep
> -
> -__all__ = ["lttng-agent"]
> -__author__ = "David Goulet <dgoulet at efficios.com>"
> -
> -class LTTngAgent():
> -    """
> -    LTTng agent python code. A LTTng Agent is responsible to spawn two
> threads,
> -    the current UID and root session daemon. Those two threads register to
> the
> -    right daemon and handle the tracing.
> -
> -    This class needs to be instantiate once and once the init returns,
> tracing
> -    is ready to happen.
> -    """
> -
> -    SESSIOND_ADDR = "127.0.0.1"
> -    SEM_COUNT = 2
> -    # Timeout for the sempahore in seconds.
> -    SEM_TIMEOUT = 5
> -    SEM_WAIT_PERIOD = 0.2
> -
> -    def __init__(self):
> -        # Session daemon register semaphore.
> -        self.register_sem = threading.Semaphore(LTTngAgent.SEM_COUNT);
> -
> -        self.client_user = LTTngTCPClient(LTTngAgent.SESSIOND_ADDR,
> self.register_sem)
> -        self.client_user.start()
> -
> -        self.client_root = LTTngTCPClient(LTTngAgent.SESSIOND_ADDR,
> self.register_sem)
> -        self.client_root.log_handler.is_root = True
> -        self.client_root.start()
> -
> -        acquire = 0
> -        timeout = LTTngAgent.SEM_TIMEOUT
> -        while True:
> -            # Quit if timeout has reached 0 or below.
> -            if acquire == LTTngAgent.SEM_COUNT or timeout <= 0:
> -                break;
> -
> -            # Acquire semaphore for *user* thread.
> -            if not self.register_sem.acquire(False):
> -                sleep(LTTngAgent.SEM_WAIT_PERIOD)
> -                timeout -= LTTngAgent.SEM_WAIT_PERIOD
> -            else:
> -                acquire += 1
> -
> -    def __del__(self):
> -        self.destroy()
> -
> -    def destroy(self):
> -        self.client_user.destroy()
> -        self.client_user.join()
> -
> -        self.client_root.destroy()
> -        self.client_root.join()
> -
> -class LTTngCmdError(RuntimeError):
> -    """
> -    Command error thrown if an error is encountered in a command from the
> -    session daemon.
> -    """
> -
> -    def __init__(self, code):
> -        super().__init__('LTTng command error: code {}'.format(code))
> -        self._code = code
> -
> -    def get_code(self):
> -        return self._code
> -
> -class LTTngUnknownCmdError(RuntimeError):
> -    pass
> -
> -class LTTngLoggingHandler(logging.Handler):
> -    """
> -    Class handler for the Python logging API.
> -    """
> -
> -    def __init__(self):
> -        logging.Handler.__init__(self, level = logging.NOTSET)
> -
> -        # Refcount tracking how many events have been enabled. This value
> above
> -        # 0 means that the handler is attached to the root logger.
> -        self.refcount = 0
> -
> -        # Dict of enabled event. We track them so we know if it's ok to
> disable
> -        # the received event.
> -        self.enabled_events = {}
> -
> -        # Am I root ?
> -        self.is_root = False
> -
> -        # Using the logging formatter to extract the asctime only.
> -        self.log_fmt = logging.Formatter("%(asctime)s")
> -        self.setFormatter(self.log_fmt)
> -
> -        # ctypes lib for lttng-ust
> -        try:
> -            self.lttng_ust =
> ctypes.cdll.LoadLibrary("LIBDIR_STR/liblttng-ust-python-agent.so")
> -        except OSError as e:
> -            print("Unable to find libust for Python.")
> -
> -    def emit(self, record):
> -        """
> -        Fire LTTng UST tracepoint with the given record.
> -        """
> -        asctime = self.format(record)
> -
> -        self.lttng_ust.py_tracepoint(asctime.encode(),
> -                record.getMessage().encode(), record.name.encode(),
> -                record.funcName.encode(), record.lineno, record.levelno,
> -                record.thread, record.threadName.encode())
> -
> -    def enable_event(self, name):
> -        """
> -        Enable an event name which will ultimately add an handler to the
> root
> -        logger if none is present.
> -        """
> -        # Don't update the refcount if the event has been enabled prior.
> -        if name in self.enabled_events:
> -            return
> -
> -        # Get the root logger and attach our handler.
> -        root_logger = logging.getLogger()
> -        # First thing first, we need to set the root logger to the loglevel
> -        # NOTSET so we can catch everything. The default is 30.
> -        root_logger.setLevel(logging.NOTSET)
> -
> -        self.refcount += 1
> -        if self.refcount == 1:
> -            root_logger.addHandler(self)
> -
> -        self.enabled_events[name] = True
> -
> -    def disable_event(self, name):
> -        """
> -        Disable an event name which will ultimately add an handler to the
> root
> -        logger if none is present.
> -        """
> -
> -        if name not in self.enabled_events:
> -            # Event was not enabled prior, do nothing.
> -            return
> -
> -        # Get the root logger and attach our handler.
> -        root_logger = logging.getLogger()
> -
> -        self.refcount -= 1
> -        if self.refcount == 0:
> -            root_logger.removeHandler(self)
> -        del self.enabled_events[name]
> -
> -    def list_logger(self):
> -        """
> -        Return a list of logger name.
> -        """
> -        return logging.Logger.manager.loggerDict.keys()
> -
> -class LTTngSessiondCmd():
> -    """
> -    Class handling session daemon command.
> -    """
> -
> -    # Command values from the agent protocol
> -    CMD_LIST = 1
> -    CMD_ENABLE = 2
> -    CMD_DISABLE = 3
> -    CMD_REG_DONE = 4
> -
> -    # Return code
> -    CODE_SUCCESS = 1
> -    CODE_INVALID_CMD = 2
> -
> -    # Python Logger LTTng domain value taken from lttng/domain.h
> -    DOMAIN = 5
> -
> -    # Protocol version
> -    MAJOR_VERSION = 1
> -    MINOR_VERSION = 0
> -
> -    def execute(self):
> -        """
> -        This is part of the command interface. Must be implemented.
> -        """
> -        raise NotImplementedError
> -
> -class LTTngCommandReply():
> -    """
> -    Object that contains the information that should be replied to the
> session
> -    daemon after a command execution.
> -    """
> -
> -    def __init__(self, payload = None, reply = True):
> -        self.payload = payload
> -        self.reply = reply
> -
> -class LTTngCommandEnable(LTTngSessiondCmd):
> -    """
> -    Handle the enable event command from the session daemon.
> -    """
> -
> -    def __init__(self, log_handler, data):
> -        self.log_handler = log_handler
> -        # 4 bytes for loglevel and 4 bytes for loglevel_type thus 8.
> -        name_offset = 8;
> -
> -        data_size = len(data)
> -        if data_size == 0:
> -            raise LTTngCmdError(LTTngSessiondCmd.CODE_INVALID_CMD)
> -
> -        try:
> -            self.loglevel, self.loglevel_type, self.name = \
> -                    struct.unpack('>II%us' % (data_size - name_offset),
> data)
> -            # Remove trailing NULL bytes from name.
> -            self.name = self.name.decode().rstrip('\x00')
> -        except struct.error:
> -            raise LTTngCmdError(LTTngSessiondCmd.CODE_INVALID_CMD)
> -
> -    def execute(self):
> -        self.log_handler.enable_event(self.name)
> -        return LTTngCommandReply()
> -
> -class LTTngCommandDisable(LTTngSessiondCmd):
> -    """
> -    Handle the disable event command from the session daemon.
> -    """
> -
> -    def __init__(self, log_handler, data):
> -        self.log_handler = log_handler
> -
> -        data_size = len(data)
> -        if data_size == 0:
> -            raise LTTngCmdError(LTTngSessiondCmd.CODE_INVALID_CMD)
> -
> -        try:
> -            self.name = struct.unpack('>%us' % (data_size), data)[0]
> -            # Remove trailing NULL bytes from name.
> -            self.name = self.name.decode().rstrip('\x00')
> -        except struct.error:
> -            raise LTTngCmdError(LTTngSessiondCmd.CODE_INVALID_CMD)
> -
> -    def execute(self):
> -        self.log_handler.disable_event(self.name)
> -        return LTTngCommandReply()
> -
> -class LTTngCommandRegDone(LTTngSessiondCmd):
> -    """
> -    Handle register done command. This is sent back after a successful
> -    registration from the session daemon. We basically release the given
> -    semaphore so the agent can return to the caller.
> -    """
> -
> -    def __init__(self, sem):
> -        self.sem = sem
> -
> -    def execute(self):
> -        self.sem.release()
> -        return LTTngCommandReply(reply = False)
> -
> -class LTTngCommandList(LTTngSessiondCmd):
> -    """
> -    Handle the list command from the session daemon on the given socket.
> -    """
> -
> -    def __init__(self, log_handler):
> -        self.log_handler = log_handler
> -
> -    def execute(self):
> -        data_size = 0
> -        data = logger_data = bytearray()
> -
> -        loggers = self.log_handler.list_logger()
> -        # First, pack nb_event that must preceed the data.
> -        logger_data += struct.pack('>I', len(loggers))
> -
> -        # Populate payload with logger name.
> -        for logger in loggers:
> -            # Increment data size plus the NULL byte at the end of the name.
> -            data_size += len(logger) + 1
> -            # Pack logger name and NULL byte.
> -            logger_data += struct.pack('>%usB' % (len(logger)), \
> -                    bytes(bytearray(str.encode(logger))), 0)
> -
> -        # Pack uint32_t data_size followed by nb event (number of logger)
> -        data = struct.pack('>I', data_size)
> -        data += logger_data
> -        return LTTngCommandReply(payload = data)
> -
> -class LTTngTCPClient(threading.Thread):
> -    """
> -    TCP client that register and receives command from the session daemon.
> -    """
> -
> -    SYSTEM_PORT_FILE = "/var/run/lttng/agent.port"
> -    USER_PORT_FILE = os.path.join(os.path.expanduser("~"),
> ".lttng/agent.port")
> -
> -    # The time in seconds this client should wait before trying again to
> -    # register back to the session daemon.
> -    WAIT_TIME = 3
> -
> -    def __init__(self, host, sem):
> -        threading.Thread.__init__(self)
> -
> -        # Which host to connect to. The port is fetch dynamically.
> -        self.sessiond_host = host
> -
> -        # The session daemon register done semaphore. Needs to be released
> when
> -        # receiving a CMD_REG_DONE command.
> -        self.register_sem = sem
> -        self.register_sem.acquire()
> -
> -        # Indicate that we have to quit thus stop the main loop.
> -        self.quit_flag = False
> -        # Quit pipe. The thread poll on it to know when to quit.
> -        self.quit_pipe = os.pipe()
> -
> -        # Socket on which we communicate with the session daemon.
> -        self.sessiond_sock = None
> -        # LTTng Logging Handler
> -        self.log_handler = LTTngLoggingHandler()
> -
> -    def cleanup_socket(self, epfd = None):
> -        # Ease our life a bit.
> -        sock = self.sessiond_sock
> -        if not sock:
> -            return
> -
> -        try:
> -            if epfd is not None:
> -                epfd.unregister(sock)
> -            sock.shutdown(SHUT_RDWR)
> -            sock.close()
> -        except select.error:
> -            # Cleanup fail, we can't do anything much...
> -            pass
> -        except IOError:
> -            pass
> -
> -        self.sessiond_sock = None
> -
> -    def destroy(self):
> -        self.quit_flag = True
> -        try:
> -            fp = os.fdopen(self.quit_pipe[1], 'w')
> -            fp.write("42")
> -            fp.close()
> -        except OSError as e:
> -            pass
> -
> -    def register(self):
> -        """
> -        Register to session daemon using the previously connected socket of
> the
> -        class.
> -
> -        Command ABI:
> -            uint32 domain
> -            uint32 pid
> -        """
> -        data = struct.pack('>IIII', LTTngSessiondCmd.DOMAIN, os.getpid(), \
> -                LTTngSessiondCmd.MAJOR_VERSION,
> LTTngSessiondCmd.MINOR_VERSION)
> -        self.sessiond_sock.send(data)
> -
> -    def run(self):
> -        """
> -        Start the TCP client thread by registering to the session daemon and
> polling
> -        on that socket for commands.
> -        """
> -
> -        epfd = epoll()
> -        epfd.register(self.quit_pipe[0], EPOLLIN)
> -
> -        # Main loop to handle session daemon command and disconnection.
> -        while not self.quit_flag:
> -            try:
> -                # First, connect to the session daemon.
> -                self.connect_sessiond()
> -
> -                # Register to session daemon after a successful connection.
> -                self.register()
> -                # Add registered socket to poll set.
> -                epfd.register(self.sessiond_sock, EPOLLIN | EPOLLERR |
> EPOLLHUP)
> -
> -                self.quit_flag = self.wait_cmd(epfd)
> -            except IOError as e:
> -                # Whatever happens here, we have to close down everything
> and
> -                # retry to connect to the session daemon since either the
> -                # socket is closed or invalid data was sent.
> -                self.cleanup_socket(epfd)
> -                self.register_sem.release()
> -                sleep(LTTngTCPClient.WAIT_TIME)
> -                continue
> -
> -        self.cleanup_socket(epfd)
> -        os.close(self.quit_pipe[0])
> -        epfd.close()
> -
> -    def recv_header(self, sock):
> -        """
> -        Receive the command header from the given socket. Set the internal
> -        state of this object with the header data.
> -
> -        Header ABI is defined like this:
> -            uint64 data_size
> -            uint32 cmd
> -            uint32 cmd_version
> -        """
> -        s_pack = struct.Struct('>QII')
> -
> -        pack_data = sock.recv(s_pack.size)
> -        data_received = len(pack_data)
> -        if data_received == 0:
> -            raise IOError(errno.ESHUTDOWN)
> -
> -        try:
> -            return s_pack.unpack(pack_data)
> -        except struct.error:
> -            raise IOError(errno.EINVAL)
> -
> -    def create_command(self, cmd_type, version, data):
> -        """
> -        Return the right command object using the given command type. The
> -        command version is unused since we only have once for now.
> -        """
> -
> -        cmd_dict = {
> -            LTTngSessiondCmd.CMD_LIST: \
> -                    lambda: LTTngCommandList(self.log_handler),
> -            LTTngSessiondCmd.CMD_ENABLE: \
> -                    lambda: LTTngCommandEnable(self.log_handler, data),
> -            LTTngSessiondCmd.CMD_DISABLE: \
> -                    lambda: LTTngCommandDisable(self.log_handler, data),
> -            LTTngSessiondCmd.CMD_REG_DONE: \
> -                    lambda: LTTngCommandRegDone(self.register_sem),
> -        }
> -
> -        if cmd_type in cmd_dict:
> -            return cmd_dict[cmd_type]()
> -        else:
> -            raise LTTngUnknownCmdError()
> -
> -    def pack_code(self, code):
> -        return struct.pack('>I', code)
> -
> -    def handle_command(self, data, cmd_type, cmd_version):
> -        """
> -        Handle the given command type with the received payload. This
> function
> -        sends back data to the session daemon using to the return value of
> the
> -        command.
> -        """
> -        payload = bytearray()
> -
> -        try:
> -            cmd = self.create_command(cmd_type, cmd_version, data)
> -            cmd_reply = cmd.execute()
> -            # Set success code in data
> -            payload += self.pack_code(LTTngSessiondCmd.CODE_SUCCESS)
> -            if cmd_reply.payload is not None:
> -                payload += cmd_reply.payload
> -        except LTTngCmdError as e:
> -            # Set error code in payload
> -            payload += self.pack_code(e.get_code())
> -        except LTTngUnknownCmdError:
> -            # Set error code in payload
> -            payload += self.pack_code(LTTngSessiondCmd.CODE_INVALID_CMD)
> -
> -        # Send response only if asked for.
> -        if cmd_reply.reply:
> -            self.sessiond_sock.send(payload)
> -
> -    def wait_cmd(self, epfd):
> -        """
> -        """
> -
> -        while True:
> -            try:
> -                # Poll on socket for command.
> -                events = epfd.poll()
> -            except select.error as e:
> -                raise IOError(e.errno, e.message)
> -
> -            for fileno, event in events:
> -                if fileno == self.quit_pipe[0]:
> -                    return True
> -                elif event & (EPOLLERR | EPOLLHUP):
> -                    raise IOError(errno.ESHUTDOWN)
> -                elif event & EPOLLIN:
> -                    data = bytearray()
> -
> -                    data_size, cmd, cmd_version =
> self.recv_header(self.sessiond_sock)
> -                    if data_size:
> -                        data += self.sessiond_sock.recv(data_size)
> -
> -                    self.handle_command(data, cmd, cmd_version)
> -                else:
> -                    raise IOError(errno.ESHUTDOWN)
> -
> -    def get_port_from_file(self, path):
> -        """
> -        Open the session daemon agent port file and returns the value. If
> none
> -        found, 0 is returned.
> -        """
> -
> -        # By default, the port is set to 0 so if we can not find the agent
> port
> -        # file simply don't try to connect. A value set to 0 indicates that.
> -        port = 0
> -
> -        try:
> -            f = open(path, "r")
> -            r_port = int(f.readline())
> -            if r_port > 0 or r_port <= 65535:
> -                port = r_port
> -            f.close()
> -        except IOError as e:
> -            pass
> -        except ValueError as e:
> -            pass
> -
> -        return port
> -
> -    def connect_sessiond(self):
> -        """
> -        Connect sessiond_sock to running session daemon using the port file.
> -        """
> -        # Create session daemon TCP socket
> -        if not self.sessiond_sock:
> -            self.sessiond_sock = socket(AF_INET, SOCK_STREAM)
> -
> -        if self.log_handler.is_root:
> -            port = self.get_port_from_file(LTTngTCPClient.SYSTEM_PORT_FILE)
> -        else:
> -            port = self.get_port_from_file(LTTngTCPClient.USER_PORT_FILE)
> -
> -        # No session daemon available
> -        if port == 0:
> -            raise IOError(errno.ECONNREFUSED)
> -
> -        # Can raise an IOError so caller must catch it.
> -        self.sessiond_sock.connect((self.sessiond_host, port))
> diff --git a/liblttng-ust-python-agent/lttngust/__init__.py
> b/liblttng-ust-python-agent/lttngust/__init__.py
> new file mode 100644
> index 0000000..5236bc8
> --- /dev/null
> +++ b/liblttng-ust-python-agent/lttngust/__init__.py
> @@ -0,0 +1,24 @@
> +# -*- coding: utf-8 -*-
> +#
> +# Copyright (C) 2015 - Philippe Proulx <pproulx at efficios.com>
> +#
> +# This library is free software; you can redistribute it and/or modify it
> under
> +# the terms of the GNU Lesser General Public License as published by the
> Free
> +# Software Foundation; version 2.1 of the License.
> +#
> +# This library is distributed in the hope that it will be useful, but
> WITHOUT
> +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
> FITNESS
> +# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
> more
> +# details.
> +#
> +# You should have received a copy of the GNU Lesser General Public License
> +# along with this library; if not, write to the Free Software Foundation,
> Inc.,
> +# 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
> +
> +from __future__ import unicode_literals
> +
> +# this creates the daemon threads and registers the application
> +import lttngust.agent
> +
> +
> +__version__ = '2.6.0-rc1'
> diff --git a/liblttng-ust-python-agent/lttngust/agent.py
> b/liblttng-ust-python-agent/lttngust/agent.py
> new file mode 100644
> index 0000000..8ec26cd
> --- /dev/null
> +++ b/liblttng-ust-python-agent/lttngust/agent.py
> @@ -0,0 +1,389 @@
> +# -*- coding: utf-8 -*-
> +#
> +# Copyright (C) 2015 - Philippe Proulx <pproulx at efficios.com>
> +# Copyright (C) 2014 - David Goulet <dgoulet at efficios.com>
> +#
> +# This library is free software; you can redistribute it and/or modify it
> under
> +# the terms of the GNU Lesser General Public License as published by the
> Free
> +# Software Foundation; version 2.1 of the License.
> +#
> +# This library is distributed in the hope that it will be useful, but
> WITHOUT
> +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
> FITNESS
> +# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
> more
> +# details.
> +#
> +# You should have received a copy of the GNU Lesser General Public License
> +# along with this library; if not, write to the Free Software Foundation,
> Inc.,
> +# 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
> +
> +from __future__ import unicode_literals
> +from __future__ import print_function
> +from __future__ import division
> +import lttngust.debug as dbg
> +import lttngust.loghandler
> +import lttngust.cmd
> +from io import open
> +import threading
> +import logging
> +import socket
> +import time
> +import sys
> +import os
> +
> +
> +try:
> +    # Python 2
> +    import Queue as queue
> +except ImportError:
> +    # Python 3
> +    import queue
> +
> +
> +_PROTO_DOMAIN = 5
> +_PROTO_MAJOR = 1
> +_PROTO_MINOR = 0
> +
> +
> +def _get_env_value_ms(key, default_s):
> +    try:
> +        val = int(os.getenv(key, default_s * 1000)) / 1000
> +    except:
> +        val = -1
> +
> +    if val < 0:
> +        fmt = 'invalid ${} value; {} seconds will be used'
> +        dbg._pwarning(fmt.format(key, default_s))
> +        val = default_s
> +
> +    return val
> +
> +
> +_REG_TIMEOUT = _get_env_value_ms('LTTNG_UST_PYTHON_REGISTER_TIMEOUT', 5)
> +_RETRY_REG_DELAY =
> _get_env_value_ms('LTTNG_UST_PYTHON_REGISTER_RETRY_DELAY', 3)
> +
> +
> +class _TcpClient(object):
> +    def __init__(self, name, host, port, reg_queue):
> +        super(self.__class__, self).__init__()
> +        self._name = name
> +        self._host = host
> +        self._port = port
> +
> +        try:
> +            self._log_handler = lttngust.loghandler._Handler()
> +        except (OSError) as e:
> +            dbg._pwarning('cannot load library: {}'.format(e))
> +            raise e
> +
> +        self._root_logger = logging.getLogger()
> +        self._root_logger.setLevel(logging.NOTSET)
> +        self._ref_count = 0
> +        self._sessiond_sock = None
> +        self._reg_queue = reg_queue
> +        self._server_cmd_handlers = {
> +            lttngust.cmd._ServerCmdRegistrationDone:
> self._handle_server_cmd_reg_done,
> +            lttngust.cmd._ServerCmdEnable: self._handle_server_cmd_enable,
> +            lttngust.cmd._ServerCmdDisable: self._handle_server_cmd_disable,
> +            lttngust.cmd._ServerCmdList: self._handle_server_cmd_list,
> +        }
> +
> +    def _debug(self, msg):
> +        return 'client "{}": {}'.format(self._name, msg)
> +
> +    def run(self):
> +        while True:
> +            try:
> +                # connect to the session daemon
> +                dbg._pdebug(self._debug('connecting to session daemon'))
> +                self._connect_to_sessiond()
> +
> +                # register to the session daemon after a successful
> connection
> +                dbg._pdebug(self._debug('registering to session daemon'))
> +                self._register()
> +
> +                # wait for commands from the session daemon
> +                self._wait_server_cmd()
> +            except (Exception) as e:
> +                # Whatever happens here, we have to close the socket and
> +                # retry to connect to the session daemon since either
> +                # the socket was closed, a network timeout occured, or
> +                # invalid data was received.
> +                dbg._pdebug(self._debug('got exception: {}'.format(e)))
> +                self._cleanup_socket()
> +                dbg._pdebug(self._debug('sleeping for {}
> s'.format(_RETRY_REG_DELAY)))
> +                time.sleep(_RETRY_REG_DELAY)
> +
> +    def _recv_server_cmd_header(self):
> +        data =
> self._sessiond_sock.recv(lttngust.cmd._SERVER_CMD_HEADER_SIZE)
> +
> +        if not data:
> +            dbg._pdebug(self._debug('received empty server command header'))
> +            return None
> +
> +        assert(len(data) == lttngust.cmd._SERVER_CMD_HEADER_SIZE)
> +        dbg._pdebug(self._debug('received server command header ({}
> bytes)'.format(len(data))))
> +
> +        return lttngust.cmd._server_cmd_header_from_data(data)
> +
> +    def _recv_server_cmd(self):
> +        server_cmd_header = self._recv_server_cmd_header()
> +
> +        if server_cmd_header is None:
> +            return None
> +
> +        dbg._pdebug(self._debug('server command header: data size: {}
> bytes'.format(server_cmd_header.data_size)))
> +        dbg._pdebug(self._debug('server command header: command ID:
> {}'.format(server_cmd_header.cmd_id)))
> +        dbg._pdebug(self._debug('server command header: command version:
> {}'.format(server_cmd_header.cmd_version)))
> +        data = bytes()
> +
> +        if server_cmd_header.data_size > 0:
> +            data = self._sessiond_sock.recv(server_cmd_header.data_size)
> +            assert(len(data) == server_cmd_header.data_size)
> +
> +        return lttngust.cmd._server_cmd_from_data(server_cmd_header, data)
> +
> +    def _send_cmd_reply(self, cmd_reply):
> +        data = cmd_reply.get_data()
> +        dbg._pdebug(self._debug('sending command reply ({}
> bytes)'.format(len(data))))
> +        self._sessiond_sock.sendall(data)
> +
> +    def _handle_server_cmd_reg_done(self, server_cmd):
> +        dbg._pdebug(self._debug('got "registration done" server command'))
> +
> +        if self._reg_queue is not None:
> +            dbg._pdebug(self._debug('notifying _init_threads()'))
> +
> +            try:
> +                self._reg_queue.put(True)
> +            except (Exception) as e:
> +                # read side could be closed by now; ignore it
> +                pass
> +
> +            self._reg_queue = None
> +
> +    def _handle_server_cmd_enable(self, server_cmd):
> +        dbg._pdebug(self._debug('got "enable" server command'))
> +        self._ref_count += 1
> +
> +        if self._ref_count == 1:
> +            dbg._pdebug(self._debug('adding our handler to the root
> logger'))
> +            self._root_logger.addHandler(self._log_handler)
> +
> +        dbg._pdebug(self._debug('ref count is {}'.format(self._ref_count)))
> +
> +        return lttngust.cmd._ClientCmdReplyEnable()
> +
> +    def _handle_server_cmd_disable(self, server_cmd):
> +        dbg._pdebug(self._debug('got "disable" server command'))
> +        self._ref_count -= 1
> +
> +        if self._ref_count < 0:
> +            # disable command could be sent again when a session is
> destroyed
> +            self._ref_count = 0
> +
> +        if self._ref_count == 0:
> +            dbg._pdebug(self._debug('removing our handler from the root
> logger'))
> +            self._root_logger.removeHandler(self._log_handler)
> +
> +        dbg._pdebug(self._debug('ref count is {}'.format(self._ref_count)))
> +
> +        return lttngust.cmd._ClientCmdReplyDisable()
> +
> +    def _handle_server_cmd_list(self, server_cmd):
> +        dbg._pdebug(self._debug('got "list" server command'))
> +        names = logging.Logger.manager.loggerDict.keys()
> +        dbg._pdebug(self._debug('found {} loggers'.format(len(names))))
> +        cmd_reply = lttngust.cmd._ClientCmdReplyList(names=names)
> +
> +        return cmd_reply
> +
> +    def _handle_server_cmd(self, server_cmd):
> +        cmd_reply = None
> +
> +        if server_cmd is None:
> +            dbg._pdebug(self._debug('bad server command'))
> +            status = lttngust.cmd._CLIENT_CMD_REPLY_STATUS_INVALID_CMD
> +            cmd_reply = lttngust.cmd._ClientCmdReply(status)
> +        elif type(server_cmd) in self._server_cmd_handlers:
> +            cmd_reply =
> self._server_cmd_handlers[type(server_cmd)](server_cmd)
> +        else:
> +            dbg._pdebug(self._debug('unknown server command'))
> +            status = lttngust.cmd._CLIENT_CMD_REPLY_STATUS_INVALID_CMD
> +            cmd_reply = lttngust.cmd._ClientCmdReply(status)
> +
> +        if cmd_reply is not None:
> +            self._send_cmd_reply(cmd_reply)
> +
> +    def _wait_server_cmd(self):
> +        while True:
> +            try:
> +                server_cmd = self._recv_server_cmd()
> +            except socket.timeout:
> +                # simply retry here; the protocol has no KA and we could
> +                # wait for hours
> +                continue
> +
> +            self._handle_server_cmd(server_cmd)
> +
> +    def _cleanup_socket(self):
> +        try:
> +            self._sessiond_sock.shutdown(socket.SHUT_RDWR)
> +            self._sessiond_sock.close()
> +        except:
> +            pass
> +
> +        self._sessiond_sock = None
> +
> +    def _connect_to_sessiond(self):
> +        # create session daemon TCP socket
> +        if self._sessiond_sock is None:
> +            self._sessiond_sock = socket.socket(socket.AF_INET,
> +                                                socket.SOCK_STREAM)
> +
> +        # Use str(self._host) here. Since this host could be a string
> +        # literal, and since we're importing __future__.unicode_literals,
> +        # we want to make sure the host is a native string in Python 2.
> +        # This avoids an indirect module import (unicode module to
> +        # decode the unicode string, eventually imported by the
> +        # socket module if needed), which is not allowed in a thread
> +        # directly created by a module in Python 2 (our case).
> +        #
> +        # tl;dr: Do NOT remove str() here, or this call in Python 2
> +        # _will_ block on an interpreter's mutex until the waiting
> +        # register queue timeouts.
> +        self._sessiond_sock.connect((str(self._host), self._port))
> +
> +    def _register(self):
> +        cmd = lttngust.cmd._ClientRegisterCmd(_PROTO_DOMAIN, os.getpid(),
> +                                              _PROTO_MAJOR, _PROTO_MINOR)
> +        data = cmd.get_data()
> +        self._sessiond_sock.sendall(data)
> +
> +
> +def _get_port_from_file(path):
> +    port = None
> +    dbg._pdebug('reading port from file "{}"'.format(path))
> +
> +    try:
> +        f = open(path)
> +        r_port = int(f.readline())
> +        f.close()
> +
> +        if r_port > 0 or r_port <= 65535:
> +            port = r_port
> +    except:
> +        pass
> +
> +    return port
> +
> +
> +def _get_user_home_path():
> +    # $LTTNG_HOME overrides $HOME if it exists
> +    return os.getenv('LTTNG_HOME', os.path.expanduser('~'))
> +
> +
> +_initialized = False
> +_SESSIOND_HOST = '127.0.0.1'
> +
> +
> +def _client_thread_target(name, port, reg_queue):
> +    dbg._pdebug('creating client "{}" using TCP port {}'.format(name, port))
> +    client = _TcpClient(name, _SESSIOND_HOST, port, reg_queue)
> +    dbg._pdebug('starting client "{}"'.format(name))
> +    client.run()
> +
> +
> +def _init_threads():
> +    global _initialized
> +
> +    dbg._pdebug('entering')
> +
> +    if _initialized:
> +        dbg._pdebug('agent is already initialized')
> +        return
> +
> +    # This makes sure that the appropriate modules for encoding and
> +    # decoding strings/bytes are imported now, since no import should
> +    # happen within a thread at import time (our case).
> +    'lttng'.encode().decode()
> +
> +    _initialized = True
> +    sys_port = _get_port_from_file('/var/run/lttng/agent.port')
> +    user_port_file = os.path.join(_get_user_home_path(), '.lttng',
> 'agent.port')
> +    user_port = _get_port_from_file(user_port_file)
> +    reg_queue = queue.Queue()
> +    reg_expecting = 0
> +
> +    dbg._pdebug('system session daemon port: {}'.format(sys_port))
> +    dbg._pdebug('user session daemon port: {}'.format(user_port))
> +
> +    try:
> +        if sys_port is not None:
> +            dbg._pdebug('creating system client thread')
> +            t = threading.Thread(target=_client_thread_target,
> +                                 args=('system', sys_port, reg_queue))
> +            t.name = 'system'
> +            t.daemon = True
> +            t.start()
> +            dbg._pdebug('created and started system client thread')
> +            reg_expecting += 1
> +
> +        if user_port is not None:
> +            dbg._pdebug('creating user client thread')
> +            t = threading.Thread(target=_client_thread_target,
> +                                 args=('user', user_port, reg_queue))
> +            t.name = 'user'
> +            t.daemon = True
> +            t.start()
> +            dbg._pdebug('created and started user client thread')
> +            reg_expecting += 1
> +    except:
> +        # cannot create threads for some reason; stop this initialization
> +        dbg._pwarning('cannot create client threads')
> +        return
> +
> +    if reg_expecting == 0:
> +        # early exit: looks like there's not even one valid port
> +        dbg._pwarning('no valid LTTng session daemon port found (is the
> session daemon started?)')
> +        return
> +
> +    cur_timeout = _REG_TIMEOUT
> +
> +    # We block here to make sure the agent is properly registered to
> +    # the session daemon. If we timeout, the client threads will still
> +    # continue to try to connect and register to the session daemon,
> +    # but there is no guarantee that all following logging statements
> +    # will make it to LTTng-UST.
> +    #
> +    # When a client thread receives a "registration done" confirmation
> +    # from the session daemon it's connected to, it puts True in
> +    # reg_queue.
> +    while True:
> +        try:
> +            dbg._pdebug('waiting for registration done (expecting {},
> timeout is {} s)'.format(reg_expecting,
> +
> cur_timeout))
> +            t1 = time.clock()
> +            reg_queue.get(timeout=cur_timeout)
> +            t2 = time.clock()
> +            reg_expecting -= 1
> +            dbg._pdebug('unblocked')
> +
> +            if reg_expecting == 0:
> +                # done!
> +                dbg._pdebug('successfully registered to session daemon(s)')
> +                break
> +
> +            cur_timeout -= (t2 - t1)
> +
> +            if cur_timeout <= 0:
> +                # timeout
> +                dbg._pdebug('ran out of time')
> +                break
> +        except queue.Empty:
> +            dbg._pdebug('ran out of time')
> +            break
> +
> +    dbg._pdebug('leaving')
> +
> +
> +_init_threads()
> diff --git a/liblttng-ust-python-agent/lttngust/cmd.py
> b/liblttng-ust-python-agent/lttngust/cmd.py
> new file mode 100644
> index 0000000..fe180fa
> --- /dev/null
> +++ b/liblttng-ust-python-agent/lttngust/cmd.py
> @@ -0,0 +1,178 @@
> +# -*- coding: utf-8 -*-
> +#
> +# Copyright (C) 2015 - Philippe Proulx <pproulx at efficios.com>
> +# Copyright (C) 2014 - David Goulet <dgoulet at efficios.com>
> +#
> +# This library is free software; you can redistribute it and/or modify it
> under
> +# the terms of the GNU Lesser General Public License as published by the
> Free
> +# Software Foundation; version 2.1 of the License.
> +#
> +# This library is distributed in the hope that it will be useful, but
> WITHOUT
> +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
> FITNESS
> +# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
> more
> +# details.
> +#
> +# You should have received a copy of the GNU Lesser General Public License
> +# along with this library; if not, write to the Free Software Foundation,
> Inc.,
> +# 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
> +
> +from __future__ import unicode_literals
> +import lttngust.debug as dbg
> +import struct
> +
> +
> +# server command header
> +_server_cmd_header_struct = struct.Struct('>QII')
> +
> +
> +# server command header size
> +_SERVER_CMD_HEADER_SIZE = _server_cmd_header_struct.size
> +
> +
> +class _ServerCmdHeader(object):
> +    def __init__(self, data_size, cmd_id, cmd_version):
> +        self.data_size = data_size
> +        self.cmd_id = cmd_id
> +        self.cmd_version = cmd_version
> +
> +
> +def _server_cmd_header_from_data(data):
> +    try:
> +        data_size, cmd_id, cmd_version =
> _server_cmd_header_struct.unpack(data)
> +    except (Exception) as e:
> +        dbg._pdebug('cannot decode command header: {}'.format(e))
> +        return None
> +
> +    return _ServerCmdHeader(data_size, cmd_id, cmd_version)
> +
> +
> +class _ServerCmd(object):
> +    def __init__(self, header):
> +        self.header = header
> +
> +    @classmethod
> +    def from_data(cls, header, data):
> +        raise NotImplementedError()
> +
> +
> +class _ServerCmdList(_ServerCmd):
> +    @classmethod
> +    def from_data(cls, header, data):
> +        return cls(header)
> +
> +
> +class _ServerCmdEnable(_ServerCmd):
> +    _NAME_OFFSET = 8
> +    _loglevel_struct = struct.Struct('>II')
> +
> +    def __init__(self, header, loglevel, loglevel_type, name):
> +        super(self.__class__, self).__init__(header)
> +        self.loglevel = loglevel
> +        self.loglevel_type = loglevel_type
> +        self.name = name
> +
> +    @classmethod
> +    def from_data(cls, header, data):
> +        try:
> +            loglevel, loglevel_type = cls._loglevel_struct.unpack_from(data)
> +            data_name = data[cls._loglevel_struct.size:]
> +            name = data_name.rstrip(b'\0').decode()
> +
> +            return cls(header, loglevel, loglevel_type, name)
> +        except (Exception) as e:
> +            dbg._pdebug('cannot decode enable command: {}'.format(e))
> +            return None
> +
> +
> +class _ServerCmdDisable(_ServerCmd):
> +    def __init__(self, header, name):
> +        super(self.__class__, self).__init__(header)
> +        self.name = name
> +
> +    @classmethod
> +    def from_data(cls, header, data):
> +        try:
> +            name = data.rstrip(b'\0').decode()
> +
> +            return cls(header, name)
> +        except (Exception) as e:
> +            dbg._pdebug('cannot decode disable command: {}'.format(e))
> +            return None
> +
> +
> +class _ServerCmdRegistrationDone(_ServerCmd):
> +    @classmethod
> +    def from_data(cls, header, data):
> +        return cls(header)
> +
> +
> +_SERVER_CMD_ID_TO_SERVER_CMD = {
> +    1: _ServerCmdList,
> +    2: _ServerCmdEnable,
> +    3: _ServerCmdDisable,
> +    4: _ServerCmdRegistrationDone,
> +}
> +
> +
> +def _server_cmd_from_data(header, data):
> +    if header.cmd_id not in _SERVER_CMD_ID_TO_SERVER_CMD:
> +        return None
> +
> +    return _SERVER_CMD_ID_TO_SERVER_CMD[header.cmd_id].from_data(header,
> data)
> +
> +
> +_CLIENT_CMD_REPLY_STATUS_SUCCESS = 1
> +_CLIENT_CMD_REPLY_STATUS_INVALID_CMD = 2
> +
> +
> +class _ClientCmdReplyHeader(object):
> +    _payload_struct = struct.Struct('>I')
> +
> +    def __init__(self, status_code=_CLIENT_CMD_REPLY_STATUS_SUCCESS):
> +        self.status_code = status_code
> +
> +    def get_data(self):
> +        return self._payload_struct.pack(self.status_code)
> +
> +
> +class _ClientCmdReplyEnable(_ClientCmdReplyHeader):
> +    pass
> +
> +
> +class _ClientCmdReplyDisable(_ClientCmdReplyHeader):
> +    pass
> +
> +
> +class _ClientCmdReplyList(_ClientCmdReplyHeader):
> +    _nb_events_struct = struct.Struct('>I')
> +    _data_size_struct = struct.Struct('>I')
> +
> +    def __init__(self, names, status_code=_CLIENT_CMD_REPLY_STATUS_SUCCESS):
> +        super(self.__class__, self).__init__(status_code)
> +        self.names = names
> +
> +    def get_data(self):
> +        upper_data = super(self.__class__, self).get_data()
> +        nb_events_data = self._nb_events_struct.pack(len(self.names))
> +        names_data = bytes()
> +
> +        for name in self.names:
> +            names_data += name.encode() + b'\0'
> +
> +        data_size_data = self._data_size_struct.pack(len(names_data))
> +
> +        return upper_data + data_size_data + nb_events_data + names_data
> +
> +
> +class _ClientRegisterCmd(object):
> +    _payload_struct = struct.Struct('>IIII')
> +
> +    def __init__(self, domain, pid, major, minor):
> +        self.domain = domain
> +        self.pid = pid
> +        self.major = major
> +        self.minor = minor
> +
> +    def get_data(self):
> +        return self._payload_struct.pack(self.domain, self.pid, self.major,
> +                                         self.minor)
> diff --git a/liblttng-ust-python-agent/lttngust/debug.py
> b/liblttng-ust-python-agent/lttngust/debug.py
> new file mode 100644
> index 0000000..6f0e81b
> --- /dev/null
> +++ b/liblttng-ust-python-agent/lttngust/debug.py
> @@ -0,0 +1,46 @@
> +# -*- coding: utf-8 -*-
> +#
> +# Copyright (C) 2015 - Philippe Proulx <pproulx at efficios.com>
> +#
> +# This library is free software; you can redistribute it and/or modify it
> under
> +# the terms of the GNU Lesser General Public License as published by the
> Free
> +# Software Foundation; version 2.1 of the License.
> +#
> +# This library is distributed in the hope that it will be useful, but
> WITHOUT
> +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
> FITNESS
> +# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
> more
> +# details.
> +#
> +# You should have received a copy of the GNU Lesser General Public License
> +# along with this library; if not, write to the Free Software Foundation,
> Inc.,
> +# 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
> +
> +from __future__ import unicode_literals, print_function
> +import time
> +import sys
> +import os
> +
> +
> +_ENABLE_DEBUG = os.getenv('LTTNG_UST_PYTHON_DEBUG', '0') == '1'
> +
> +
> +if _ENABLE_DEBUG:
> +    import inspect
> +
> +    def _pwarning(msg):
> +        fname = inspect.stack()[1][3]
> +        fmt = '[{:.6f}] LTTng-UST warning: {}(): {}'
> +        print(fmt.format(time.clock(), fname, msg), file=sys.stderr)
> +
> +    def _pdebug(msg):
> +        fname = inspect.stack()[1][3]
> +        fmt = '[{:.6f}] LTTng-UST debug: {}(): {}'
> +        print(fmt.format(time.clock(), fname, msg), file=sys.stderr)
> +
> +    _pdebug('debug is enabled')
> +else:
> +    def _pwarning(msg):
> +        pass
> +
> +    def _pdebug(msg):
> +        pass
> diff --git a/liblttng-ust-python-agent/lttngust/loghandler.py
> b/liblttng-ust-python-agent/lttngust/loghandler.py
> new file mode 100644
> index 0000000..e82cf5c
> --- /dev/null
> +++ b/liblttng-ust-python-agent/lttngust/loghandler.py
> @@ -0,0 +1,41 @@
> +# -*- coding: utf-8 -*-
> +#
> +# Copyright (C) 2015 - Philippe Proulx <pproulx at efficios.com>
> +# Copyright (C) 2014 - David Goulet <dgoulet at efficios.com>
> +#
> +# This library is free software; you can redistribute it and/or modify it
> under
> +# the terms of the GNU Lesser General Public License as published by the
> Free
> +# Software Foundation; version 2.1 of the License.
> +#
> +# This library is distributed in the hope that it will be useful, but
> WITHOUT
> +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
> FITNESS
> +# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
> more
> +# details.
> +#
> +# You should have received a copy of the GNU Lesser General Public License
> +# along with this library; if not, write to the Free Software Foundation,
> Inc.,
> +# 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
> +
> +from __future__ import unicode_literals
> +import logging
> +import ctypes
> +
> +
> +class _Handler(logging.Handler):
> +    _LIB_NAME = 'liblttng-ust-python-agent.so'
> +
> +    def __init__(self):
> +        super(self.__class__, self).__init__(level=logging.NOTSET)
> +        self.setFormatter(logging.Formatter('%(asctime)s'))
> +
> +        # will raise if library is not found: caller should catch
> +        self.agent_lib = ctypes.cdll.LoadLibrary(_Handler._LIB_NAME)
> +
> +    def emit(self, record):
> +        self.agent_lib.py_tracepoint(self.format(record).encode(),
> +                                     record.getMessage().encode(),
> +                                     record.name.encode(),
> +                                     record.funcName.encode(),
> +                                     record.lineno, record.levelno,
> +                                     record.thread,
> +                                     record.threadName.encode())
> --
> 2.3.0
> 
> 
> _______________________________________________
> lttng-dev mailing list
> lttng-dev at lists.lttng.org
> http://lists.lttng.org/cgi-bin/mailman/listinfo/lttng-dev
> 

-- 
Mathieu Desnoyers
EfficiOS Inc.
http://www.efficios.com




More information about the lttng-dev mailing list