From: Philippe Proulx Date: Fri, 27 Nov 2015 17:39:51 +0000 (-0500) Subject: Refactor Python agent build and install X-Git-Tag: v2.8.0-rc1~51 X-Git-Url: http://git.liburcu.org/?p=lttng-ust.git;a=commitdiff_plain;h=5b6ff569c0bf82b72f263a259e7a73a453903648 Refactor Python agent build and install 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 Signed-off-by: Mathieu Desnoyers --- diff --git a/.gitignore b/.gitignore index 41966c5d..287d9e7a 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/Makefile.am b/Makefile.am index 16663eef..5bb53112 100644 --- a/Makefile.am +++ b/Makefile.am @@ -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 diff --git a/configure.ac b/configure.ac index cd92e192..26bf5a59 100644 --- a/configure.ac +++ b/configure.ac @@ -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 diff --git a/liblttng-ust-python-agent/Makefile.am b/liblttng-ust-python-agent/Makefile.am index e2a15f48..726ffe4e 100644 --- a/liblttng-ust-python-agent/Makefile.am +++ b/liblttng-ust-python-agent/Makefile.am @@ -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 index b70c2e83..00000000 --- a/liblttng-ust-python-agent/lttngust/__init__.py.in +++ /dev/null @@ -1,24 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2015 - Philippe Proulx -# -# 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 index ebfa2de1..00000000 --- a/liblttng-ust-python-agent/lttngust/agent.py +++ /dev/null @@ -1,395 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2015 - Philippe Proulx -# Copyright (C) 2014 - David Goulet -# -# 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 index b6d8446b..00000000 --- a/liblttng-ust-python-agent/lttngust/cmd.py +++ /dev/null @@ -1,197 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2015 - Philippe Proulx -# Copyright (C) 2014 - David Goulet -# Copyright (C) 2015 - Jérémie Galarneau -# -# 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 index 6f0e81b1..00000000 --- a/liblttng-ust-python-agent/lttngust/debug.py +++ /dev/null @@ -1,46 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2015 - Philippe Proulx -# -# 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 index e82cf5c5..00000000 --- a/liblttng-ust-python-agent/lttngust/loghandler.py +++ /dev/null @@ -1,41 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2015 - Philippe Proulx -# Copyright (C) 2014 - David Goulet -# -# 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 index 7f21c5cf..00000000 --- a/liblttng-ust-python-agent/setup.py.in +++ /dev/null @@ -1,25 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2015 - Jonathan Rajotte -# -# 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 index 00000000..cc289895 --- /dev/null +++ b/python-lttngust/Makefile.am @@ -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 index 00000000..b70c2e83 --- /dev/null +++ b/python-lttngust/lttngust/__init__.py.in @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2015 - Philippe Proulx +# +# 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 index 00000000..ebfa2de1 --- /dev/null +++ b/python-lttngust/lttngust/agent.py @@ -0,0 +1,395 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2015 - Philippe Proulx +# Copyright (C) 2014 - David Goulet +# +# 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 index 00000000..b6d8446b --- /dev/null +++ b/python-lttngust/lttngust/cmd.py @@ -0,0 +1,197 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2015 - Philippe Proulx +# Copyright (C) 2014 - David Goulet +# Copyright (C) 2015 - Jérémie Galarneau +# +# 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 index 00000000..6f0e81b1 --- /dev/null +++ b/python-lttngust/lttngust/debug.py @@ -0,0 +1,46 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2015 - Philippe Proulx +# +# 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 index 00000000..e82cf5c5 --- /dev/null +++ b/python-lttngust/lttngust/loghandler.py @@ -0,0 +1,41 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2015 - Philippe Proulx +# Copyright (C) 2014 - David Goulet +# +# 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 index 00000000..7f21c5cf --- /dev/null +++ b/python-lttngust/setup.py.in @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2015 - Jonathan Rajotte +# +# 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'}})