Refactor Python agent build and install
authorPhilippe Proulx <eeppeliteloop@gmail.com>
Fri, 27 Nov 2015 17:39:51 +0000 (12:39 -0500)
committerMathieu Desnoyers <mathieu.desnoyers@efficios.com>
Fri, 27 Nov 2015 19:25:01 +0000 (14:25 -0500)
Since the Python agent's tracepoint provider,
liblttng-ust-python-agent, does not depend on Python, it can
always be built and installed alongside LTTng-UST.

The Python package of this agent is completely independent
from the rest of the tree, thus it is isolated in its own
directory. This also eases the creation of distribution
packages because the packager can selectively build and
install the Python package without also building/installing the
tracepoint provider.

Signed-off-by: Philippe Proulx <eeppeliteloop@gmail.com>
Signed-off-by: Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
17 files changed:
.gitignore
Makefile.am
configure.ac
liblttng-ust-python-agent/Makefile.am
liblttng-ust-python-agent/lttngust/__init__.py.in [deleted file]
liblttng-ust-python-agent/lttngust/agent.py [deleted file]
liblttng-ust-python-agent/lttngust/cmd.py [deleted file]
liblttng-ust-python-agent/lttngust/debug.py [deleted file]
liblttng-ust-python-agent/lttngust/loghandler.py [deleted file]
liblttng-ust-python-agent/setup.py.in [deleted file]
python-lttngust/Makefile.am [new file with mode: 0644]
python-lttngust/lttngust/__init__.py.in [new file with mode: 0644]
python-lttngust/lttngust/agent.py [new file with mode: 0644]
python-lttngust/lttngust/cmd.py [new file with mode: 0644]
python-lttngust/lttngust/debug.py [new file with mode: 0644]
python-lttngust/lttngust/loghandler.py [new file with mode: 0644]
python-lttngust/setup.py.in [new file with mode: 0644]

index 41966c5d964fdc09d30cbdbf835c2b1091f728f2..287d9e7ac2f4f9bc25e67f9796d962b171f2dba0 100644 (file)
@@ -65,8 +65,7 @@ org_lttng_ust_agent_jul_LttngLogHandler.h
 org_lttng_ust_agent_log4j_LttngLogAppender.h
 
 # Python agent
-liblttng-ust-python-agent/lttngust/__init__.py
-liblttng-ust-python-agent/**/*.pyc
-liblttng-ust-python-agent/build
-liblttng-ust-python-agent/install_files.txt
-liblttng-ust-python-agent/setup.py
+python-lttngust/lttngust/__init__.py
+python-lttngust/**/*.pyc
+python-lttngust/build
+python-lttngust/setup.py
index 16663eef53a4a0a42b09c2ea459a4259b71cb1a5..5bb531128f932d213a199a34bb5be6d49826ce71 100644 (file)
@@ -6,6 +6,7 @@ SUBDIRS = . include snprintf libringbuffer liblttng-ust-comm \
                liblttng-ust-fork \
                liblttng-ust-libc-wrapper \
                liblttng-ust-cyg-profile \
+               liblttng-ust-python-agent \
                tools
 
 if HAVE_DLINFO
@@ -21,7 +22,7 @@ SUBDIRS += liblttng-ust-java-agent
 endif
 
 if BUILD_PYTHON_AGENT
-SUBDIRS += liblttng-ust-python-agent
+SUBDIRS += python-lttngust
 endif
 
 SUBDIRS += tests doc
index cd92e1928ef9574a08b161cf1c707428b2c265a3..26bf5a59c153d731fc30b7293d39f2d35cf71983 100644 (file)
@@ -394,8 +394,9 @@ AC_CONFIG_FILES([
        liblttng-ust-libc-wrapper/Makefile
        liblttng-ust-cyg-profile/Makefile
        liblttng-ust-python-agent/Makefile
-       liblttng-ust-python-agent/setup.py
-       liblttng-ust-python-agent/lttngust/__init__.py
+       python-lttngust/Makefile
+       python-lttngust/setup.py
+       python-lttngust/lttngust/__init__.py
        tools/Makefile
        tests/Makefile
        tests/hello/Makefile
@@ -410,10 +411,10 @@ AC_CONFIG_FILES([
 
 # Create link for python agent for the VPATH guru.
 AC_CONFIG_LINKS([
-       liblttng-ust-python-agent/lttngust/agent.py:liblttng-ust-python-agent/lttngust/agent.py
-       liblttng-ust-python-agent/lttngust/cmd.py:liblttng-ust-python-agent/lttngust/cmd.py
-       liblttng-ust-python-agent/lttngust/debug.py:liblttng-ust-python-agent/lttngust/debug.py
-       liblttng-ust-python-agent/lttngust/loghandler.py:liblttng-ust-python-agent/lttngust/loghandler.py
+       python-lttngust/lttngust/agent.py:python-lttngust/lttngust/agent.py
+       python-lttngust/lttngust/cmd.py:python-lttngust/lttngust/cmd.py
+       python-lttngust/lttngust/debug.py:python-lttngust/lttngust/debug.py
+       python-lttngust/lttngust/loghandler.py:python-lttngust/lttngust/loghandler.py
 ])
 
 AC_OUTPUT
index e2a15f48da8898a1c5263737ef2b58ef5cf87598..726ffe4e6d88a55cca00ea112bff989a179f9d8d 100644 (file)
@@ -1,34 +1,8 @@
-# tracepoint provider
-AM_CPPFLAGS = $(PYTHON_INCLUDE) -I$(top_srcdir)/include/ \
+# tracepoint provider: always built/installed (does not depend on Python per se)
+AM_CPPFLAGS = -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
-
-# Use setup.py for the installation instead of Autoconf.
-# This ease the installation process and assure a *pythonic*
-# installation.
-agent_path=lttngust
-all-local:
-       $(PYTHON) setup.py build --verbose
-
-install-exec-local:
-       @opts="--prefix=$(prefix) --verbose --no-compile $(DISTSETUPOPTS)"; \
-       if [ "$(DESTDIR)" != "" ]; then \
-               opts="$$opts --root=$(DESTDIR)"; \
-       fi; \
-       $(PYTHON) setup.py install $$opts;
-
-clean-local:
-       rm -rf build
-
-uninstall-local:
-       rm -rf $(DESTDIR)$(pkgpythondir)
-
-EXTRA_DIST=$(agent_path)
-
-# Remove automake generated file before dist
-dist-hook:
-       rm -rf $(distdir)/$(agent_path)/__init__.py
diff --git a/liblttng-ust-python-agent/lttngust/__init__.py.in b/liblttng-ust-python-agent/lttngust/__init__.py.in
deleted file mode 100644 (file)
index b70c2e8..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Copyright (C) 2015 - Philippe Proulx <pproulx@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__ = '@PACKAGE_VERSION@'
diff --git a/liblttng-ust-python-agent/lttngust/agent.py b/liblttng-ust-python-agent/lttngust/agent.py
deleted file mode 100644 (file)
index ebfa2de..0000000
+++ /dev/null
@@ -1,395 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Copyright (C) 2015 - Philippe Proulx <pproulx@efficios.com>
-# Copyright (C) 2014 - David Goulet <dgoulet@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 = 2
-_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))
-
-    if sys_port == user_port and sys_port is not None:
-        # The two session daemon ports are the same. This is not normal.
-        # Connect to only one.
-        dbg._pdebug('both user and system session daemon have the same port')
-        sys_port = None
-
-    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
deleted file mode 100644 (file)
index b6d8446..0000000
+++ /dev/null
@@ -1,197 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Copyright (C) 2015 - Philippe Proulx <pproulx@efficios.com>
-# Copyright (C) 2014 - David Goulet <dgoulet@efficios.com>
-# Copyright (C) 2015 - Jérémie Galarneau <jeremie.galarneau@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
-
-
-# agent protocol symbol size
-_LTTNG_SYMBOL_NAME_LEN = 256
-
-
-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')
-    # filter expression size
-    _filter_exp_len_struct = struct.Struct('>I')
-
-    def __init__(self, header, loglevel, loglevel_type, name, filter_exp):
-        super(self.__class__, self).__init__(header)
-        self.loglevel = loglevel
-        self.loglevel_type = loglevel_type
-        self.name = name
-        self.filter_expression = filter_exp
-        dbg._pdebug('server enable command {}'.format(self.__dict__))
-
-    @classmethod
-    def from_data(cls, header, data):
-        try:
-            loglevel, loglevel_type = cls._loglevel_struct.unpack_from(data)
-            name_start = cls._loglevel_struct.size
-            name_end = name_start + _LTTNG_SYMBOL_NAME_LEN
-            data_name = data[name_start:name_end]
-            name = data_name.rstrip(b'\0').decode()
-
-            filter_exp_start = name_end + cls._filter_exp_len_struct.size
-            filter_exp_len, = cls._filter_exp_len_struct.unpack_from(
-                data[name_end:filter_exp_start])
-            filter_exp_end = filter_exp_start + filter_exp_len
-
-            filter_exp = data[filter_exp_start:filter_exp_end].rstrip(
-                b'\0').decode()
-
-            return cls(header, loglevel, loglevel_type, name, filter_exp)
-        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
deleted file mode 100644 (file)
index 6f0e81b..0000000
+++ /dev/null
@@ -1,46 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Copyright (C) 2015 - Philippe Proulx <pproulx@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
deleted file mode 100644 (file)
index e82cf5c..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Copyright (C) 2015 - Philippe Proulx <pproulx@efficios.com>
-# Copyright (C) 2014 - David Goulet <dgoulet@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())
diff --git a/liblttng-ust-python-agent/setup.py.in b/liblttng-ust-python-agent/setup.py.in
deleted file mode 100644 (file)
index 7f21c5c..0000000
+++ /dev/null
@@ -1,25 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Copyright (C) 2015 - Jonathan Rajotte <jonathan.rajotte-julien@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 distutils.core import setup, Extension
-
-setup(name='lttngust',
-      version='@PACKAGE_VERSION@',
-      description='Lttng ust agent',
-      packages=['lttngust'],
-      package_dir={'lttngust': 'lttngust'},
-      options={'build': {'build_base': 'build'}})
diff --git a/python-lttngust/Makefile.am b/python-lttngust/Makefile.am
new file mode 100644 (file)
index 0000000..cc28989
--- /dev/null
@@ -0,0 +1,25 @@
+# Use setup.py for the installation instead of Autoconf.
+# This ease the installation process and assure a *pythonic*
+# installation.
+agent_path=lttngust
+all-local:
+       $(PYTHON) setup.py build --verbose
+
+install-exec-local:
+       @opts="--prefix=$(prefix) --verbose --no-compile $(DISTSETUPOPTS)"; \
+       if [ "$(DESTDIR)" != "" ]; then \
+               opts="$$opts --root=$(DESTDIR)"; \
+       fi; \
+       $(PYTHON) setup.py install $$opts;
+
+clean-local:
+       rm -rf build
+
+uninstall-local:
+       rm -rf $(DESTDIR)$(pkgpythondir)
+
+EXTRA_DIST=$(agent_path)
+
+# Remove automake generated file before dist
+dist-hook:
+       rm -rf $(distdir)/$(agent_path)/__init__.py
diff --git a/python-lttngust/lttngust/__init__.py.in b/python-lttngust/lttngust/__init__.py.in
new file mode 100644 (file)
index 0000000..b70c2e8
--- /dev/null
@@ -0,0 +1,24 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2015 - Philippe Proulx <pproulx@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__ = '@PACKAGE_VERSION@'
diff --git a/python-lttngust/lttngust/agent.py b/python-lttngust/lttngust/agent.py
new file mode 100644 (file)
index 0000000..ebfa2de
--- /dev/null
@@ -0,0 +1,395 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2015 - Philippe Proulx <pproulx@efficios.com>
+# Copyright (C) 2014 - David Goulet <dgoulet@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 = 2
+_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))
+
+    if sys_port == user_port and sys_port is not None:
+        # The two session daemon ports are the same. This is not normal.
+        # Connect to only one.
+        dbg._pdebug('both user and system session daemon have the same port')
+        sys_port = None
+
+    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/python-lttngust/lttngust/cmd.py b/python-lttngust/lttngust/cmd.py
new file mode 100644 (file)
index 0000000..b6d8446
--- /dev/null
@@ -0,0 +1,197 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2015 - Philippe Proulx <pproulx@efficios.com>
+# Copyright (C) 2014 - David Goulet <dgoulet@efficios.com>
+# Copyright (C) 2015 - Jérémie Galarneau <jeremie.galarneau@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
+
+
+# agent protocol symbol size
+_LTTNG_SYMBOL_NAME_LEN = 256
+
+
+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')
+    # filter expression size
+    _filter_exp_len_struct = struct.Struct('>I')
+
+    def __init__(self, header, loglevel, loglevel_type, name, filter_exp):
+        super(self.__class__, self).__init__(header)
+        self.loglevel = loglevel
+        self.loglevel_type = loglevel_type
+        self.name = name
+        self.filter_expression = filter_exp
+        dbg._pdebug('server enable command {}'.format(self.__dict__))
+
+    @classmethod
+    def from_data(cls, header, data):
+        try:
+            loglevel, loglevel_type = cls._loglevel_struct.unpack_from(data)
+            name_start = cls._loglevel_struct.size
+            name_end = name_start + _LTTNG_SYMBOL_NAME_LEN
+            data_name = data[name_start:name_end]
+            name = data_name.rstrip(b'\0').decode()
+
+            filter_exp_start = name_end + cls._filter_exp_len_struct.size
+            filter_exp_len, = cls._filter_exp_len_struct.unpack_from(
+                data[name_end:filter_exp_start])
+            filter_exp_end = filter_exp_start + filter_exp_len
+
+            filter_exp = data[filter_exp_start:filter_exp_end].rstrip(
+                b'\0').decode()
+
+            return cls(header, loglevel, loglevel_type, name, filter_exp)
+        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/python-lttngust/lttngust/debug.py b/python-lttngust/lttngust/debug.py
new file mode 100644 (file)
index 0000000..6f0e81b
--- /dev/null
@@ -0,0 +1,46 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2015 - Philippe Proulx <pproulx@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/python-lttngust/lttngust/loghandler.py b/python-lttngust/lttngust/loghandler.py
new file mode 100644 (file)
index 0000000..e82cf5c
--- /dev/null
@@ -0,0 +1,41 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2015 - Philippe Proulx <pproulx@efficios.com>
+# Copyright (C) 2014 - David Goulet <dgoulet@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())
diff --git a/python-lttngust/setup.py.in b/python-lttngust/setup.py.in
new file mode 100644 (file)
index 0000000..7f21c5c
--- /dev/null
@@ -0,0 +1,25 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2015 - Jonathan Rajotte <jonathan.rajotte-julien@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 distutils.core import setup, Extension
+
+setup(name='lttngust',
+      version='@PACKAGE_VERSION@',
+      description='Lttng ust agent',
+      packages=['lttngust'],
+      package_dir={'lttngust': 'lttngust'},
+      options={'build': {'build_base': 'build'}})
This page took 0.042819 seconds and 5 git commands to generate.